- 嵌入式
- 语言运行时
- 编程语言
- 解释器
- 编译器
- 物联网
- 系统编程
【免费下载链接】micropython
MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
本指南以 ports/qemu/README.md 为骨架,结合 ports/qemu/Makefile、ports/qemu/main.c 等仓库源码,系统讲解 MicroPython 官方 QEMU 移植的用途、工具链依赖、构建方式、REPL 与测试运行方法及全部可调 make 选项。读完你将掌握:无需任何真实 MCU 与 JTAG,即可在 QEMU 上为 ARM Cortex-M、RISC-V RV32/RV64、PowerPC 64 架构构建并运行 MicroPython 固件,跑通官方测试套件与原生模块(natmod)测试,并接入 gdb 进行无硬件调试。
一、这个移植是做什么的
ports/qemu是 MicroPython 仓库中一个实验性、由社区维护的移植,其目标平台不是某块真实开发板,而是 QEMU(Quick Emulator)模拟的虚拟机器。它覆盖三类处理器架构:
- ARM(Cortex-M 系列,如 Cortex-M3)
- RISC-V(RV32IMC / RV64IMC)
- PowerPC 64(little-endian)
README 明确给出了这一移植的三个核心用途,这也是它与其它端口最本质的区别:
- 持续集成(CI)——在虚拟机上直接运行针对代码库中架构相关部分的测试。仓库的 tools/ci.sh 中可以看到完整的 CI 编排:
ci_qemu_build_arm_sabrelite、ci_qemu_build_rv32、ci_qemu_build_rv64等函数分别对SABRELITE、VIRT_RV32、VIRT_RV64板执行test_full、test与test_natmod,覆盖了大小端(MP_ENDIANNESS_BIG)、硬浮点/软浮点(armv6m/armv7m/armv7emsp/armv7emdp)等多种组合。 - 实验与原型验证——任何包含架构相关代码的部分都可以先仿真验证,例如研究某条指令集特性对 MicroPython 或某个模块的优化影响。
- 轻量化调试——不需要 JTAG,不需要真实 MCU 芯片,也不需要 OpenOCD,省去了接线、按键等一切硬件调试的繁琐环节。
二、构建前的工具链依赖
不同架构的板卡需要对应的交叉编译工具链。
ARM(QEMU_ARCH=arm)
需要裸机 ARM 工具链,典型为arm-none-eabi-gcc。对应 Makefile 中CROSS_COMPILE ?= arm-none-eabi-的设置。
RISC-V 32(QEMU_ARCH=riscv32)
- 需要GCC 10 或更新版本的裸机 RISC-V 工具链;
- 要求支持 multilib,或专门的 32 位工具链;
- 必须支持M、C、Zicsr扩展,ABI 为ilp32;
- newlib 与 picolibc 均可,优先使用 picolibc(存在时自动选择)。
一个容易踩坑的细节:GCC 10 及更早版本不识别架构名中的Zicsr扩展,因此 Makefile 中会根据GCC_VERSION自动切换:
RV32_ARCH ?= rv32imac # GCC <= 10 RV32_ARCH ?= rv32imac_zicsr # 新版本 GCC大多数发行版包管理器提供的预编译工具链(或 xPack 等独立分发的riscv-none-elf-gcc)开箱即用。
RISC-V 64(QEMU_ARCH=riscv64)
要求与 RV32 类似,但 ABI 为lp64、扩展为M、C、Zicsr。README 给出了一个自检技巧:对交叉编译器执行gcc -v(仅此一个参数),如果输出中看不到-mcmodel=medany相关说明,则该工具链很可能无法用于本移植。
原因在于链接地址的约束:QEMU 的VIRT_RV64板代码地址空间从0x80000000开始,而一些工具链自带的libc、libm、libgcc无法被放置到超过0x7FFFF7FF的偏移处,一旦构建过程引入这些库代码就会失败。README 直言“市面上很多现成的 RISC-V 工具链在这方面都是坏的”。
针对这一痛点,仓库提供了Docker 容器镜像,内置与 MicroPython RV32/RV64 CI 任务相同、已知可用的编译器(见 Dockerfile.riscv,基于 Ubuntu 24.04,安装gcc-riscv64-unknown-elf、picolibc-riscv64-unknown-elf、qemu-system及 Python 测试依赖)。构建并运行测试:
cd $MICROPYTHON_SOURCE_ROOT/ports/qemu docker build -t micropython/mpy-qemu-riscv -f Dockerfile.riscv .cd $MICROPYTHON_SOURCE_ROOT docker container run -v .:/micropython --rm -it micropython/mpy-qemu-riscv make -C ports/qemu -- BOARD=VIRT_RV64 test使用 Podman 时把docker换成podman即可。将VIRT_RV64换成VIRT_RV32即可运行 32 位测试。
PowerPC 64(QEMU_ARCH=ppc64)
需要面向 Linux PowerPC 64、小端(little-endian)输出的工具链,典型为powerpc64le-linux-gnu-gcc,对应 Makefile 中的CROSS_COMPILE ?= powerpc64le-linux-gnu-。
三、构建步骤
在 ports/qemu 目录下执行。第一步先构建 MicroPython 交叉编译器 mpy-cross(用于冻结模块与.mpy文件生成):
$ make -C ../../mpy-cross然后构建固件:
$ make默认板卡是mps2-an385(Cortex-M3)。选择其它板卡时通过BOARD参数指定:
$ make BOARD=SABRELITE可用板卡一览
README 给出的官方板卡对照表如下(BOARD=值对应 boards 目录下的子目录,每个子目录含一个mpconfigboard.mk定义QEMU_ARCH、QEMU_MACHINE等关键参数):
BOARD=名称 | 架构 | 对应的 QEMU 机器 |
|---|---|---|
MICROBIT | arm | microbit |
MPS2_AN385 | arm | mps2-an385 |
MPS2_AN500 | arm | mps2-an500 |
NETDUINO2 | arm | netduino2 |
POWERNV9 | ppc64 | powernv9 |
SABRELITE | arm | sabrelite |
VIRT_RV32 | riscv32 | virt |
VIRT_RV64 | riscv64 | virt |
从源码结构看,boards 目录下还额外包含MPS3_AN547(arm),README 表格虽未列出,但它同样可作为BOARD=MPS3_AN547使用,且 uart.c 中为其单独实现了 MPS3 外设寄存器映射(UART 基址0x49303000)。
各板卡的关键差异(源码佐证)
以默认板 boards/MPS2_AN385/mpconfigboard.mk 为例,它定义了:
QEMU_ARCH = arm、QEMU_MACHINE = mps2-an385;- 编译参数
-mthumb -mcpu=cortex-m3 -mfloat-abi=soft,并声明QEMU_SOC_MPS2; - ROMFS 分区 0 起始地址
0x21000000、大小0x00400000; - 链接脚本
mcu/arm/mps2.ld; - 原生 GC helper 目标文件
gchelper_native.o gchelper_thumb2.o; - mpy-cross 架构参数
-march=armv7m。
RISC-V 板(如 boards/VIRT_RV64/mpconfigboard.mk)则把 ROMFS 放在0x80620000,并使用mcu/rv64/virt.ld与gchelper_rv64i.o。注意其注释提醒:如果 ROMFS 分区大小不够,需要同步修改 mcu/rv64/virt.ld 中的 ROMFS 段大小。
四、运行与交互:REPL、串口与测试
固件启动后会在模拟硬件的 UART 上提供 MicroPython REPL。
直接进入 REPL(UART 重定向到 stdio)
$ make repl该命令会启动qemu-system-arm(或对应的qemu-system-riscv32等),并把 UART 重定向到终端标准输入输出。退出方式:在 REPL 中执行import machine; machine.reset(),或直接 Ctrl-C 终止命令。Makefile 中repl目标实现为-serial mon:stdio,并提示 “Use machine.reset() to exit”。
后台运行并连接外部串口终端
$ make run该命令启动仿真,并把 UART 重定向到一个pty 伪终端设备,设备名会打印到 stdout。随后可用任何串口终端程序连接,例如 MicroPython 官方工具 mpremote:
$ mpremote connect /dev/pts/1可以反复断开、重连该串口设备。结束时回到启动make run的终端按 Ctrl-C,或在 REPL 中执行import machine; machine.reset()。
运行官方测试套件
一键运行全部测试:
$ make test或手动方式:先make run启动仿真,再在 tests 目录下用run-tests.py针对串口设备执行:
$ cd ../../tests $ ./run-tests.py -t /dev/pts/1make test的内部实现(Makefile)会把run-tests.py与-t execpty:"$(QEMU_SYSTEM) $(QEMU_ARGS) ... -serial pty -kernel ../ports/qemu/$(BUILD)/firmware.elf"组合起来,自动拉起 QEMU 实例,无需人工干预。
测试示例原生模块(natmod)
源码树自带的示例原生模块可用如下命令测试:
$ make test_natmod手动方式的注意事项与上面相同,只是把run-tests.py换成run-natmodtests.py。默认会测试一组模块(Makefile 中TEST_NATMODS ?= btree deflate framebuf heapq random_basic re),可用TEST_NATMODS选项限定子集(见下一节)。
此外 Makefile 还提供了test_full目标,依次执行三次测试:普通运行、--via-mpy(通过.mpy交叉编译字节码运行)、--via-mpy --emit native(原生机器码执行),这正是 CI 中验证 mpy-cross 与各原生后端的关键路径。
五、全部可调 make 选项
以下是 README 列出的全部扩展选项,可在make命令行追加:
| 选项 | 作用 |
|---|---|
CFLAGS_EXTRA | 向编译器传递额外编译标志 |
RUN_TESTS_EXTRA | 在make test/make test_natmod时,向run-tests.py/run-natmodtests.py传递额外参数(如 CI 中使用的--arch armv6m) |
QEMU_BASE | 指定 qemu 可执行文件的部分路径前缀,例如/opt/custom-directory/qemu/bin/qemu-system-,用法与交叉编译器名传给 MicroPython Makefile 的方式类似;默认从系统PATH中查找对应 qemu 二进制 |
QEMU_DEBUG=1 | 运行 qemu(repl/run/test目标)时阻塞等待调试器连接;默认等待 gdb 连接 TCP 1234 端口 |
QEMU_DEBUG_ARGS | 默认-s(即 gdb 于 TCP 1234 端口),可改为其它 qemu gdb 参数 |
QEMU_DEBUG_EXTRA | 当QEMU_DEBUG=1时额外传给 qemu 的选项 |
QEMU_ROMFS_IMG<n> | 传入由 qemu 加载的 romfs 镜像(若板卡启用) |
TEST_NATMODS | 空格分隔的 natmod 名称列表,限定test_natmod只测试指定子集,例如make test_natmod TEST_NATMODS="btree heapq re" |
MICROPY_HEAP_SIZE | 覆盖本移植的 GC 堆大小(字节) |
MICROPY_STACK_SIZE | 覆盖本移植的解释器栈大小(字节) |
选项背后的默认值(源码级)
- 默认板卡:
BOARD ?= MPS2_AN385,构建目录按板卡命名:BUILD ?= build-$(BOARD)(即build-MPS2_AN385等)。 - 堆与栈默认值:arm 与 riscv32 为堆
143360字节、栈10240字节;riscv64 与 ppc64 为堆204800字节,其中 ppc64 栈为20480字节。这些值通过-DMICROPY_HEAP_SIZE=... -DMICROPY_STACK_SIZE=...编译进固件,且 main.c 在编译期强制校验二者必须为正整数。 - QEMU_DEBUG 实现:当
QEMU_DEBUG=1时,Makefile 会向 qemu 追加-S $(QEMU_DEBUG_ARGS) $(QEMU_DEBUG_EXTRA)。-S使 CPU 在启动时暂停,等待调试器通过 gdb 协议接入后再继续执行。 - ROMFS 加载:若配置了
QEMU_ROMFS_IMG<n>,会通过-device loader,file=...,addr=<分区起始地址>,force-raw=on把镜像直接装入模拟内存;测试时还会自动挂载 tests/assets/random_romfs.bin 作为 ROMFS 测试镜像(见 Makefile 中ROMFS_TEST_IMAGE相关逻辑)。 - picolibc 优先:构建时若检测到编译器的
picolibc.specs,会自动追加--specs=...显式选用 picolibc(避免 Ubuntu 22.04 等发行版默认 nosys 导致的链接问题)。
六、源码视角:这个移植的内部结构
主循环与 REPL 分发
main.c 是整个移植的入口,其结构清晰地展示了“软复位”模型:
static uint32_t gc_heap[MICROPY_HEAP_SIZE / sizeof(uint32_t)]; int main(int argc, char **argv) { mp_cstack_init_with_sp_here(MICROPY_STACK_SIZE); gc_init(gc_heap, (char *)gc_heap + MICROPY_HEAP_SIZE); ... for (;;) { mp_init(); for (;;) { if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { if (pyexec_raw_repl() != 0) break; } else { if (pyexec_friendly_repl() != 0) break; } } mp_printf(&mp_plat_print, "MPY: soft reboot\n"); ... gc_sweep_all(); mp_deinit(); } }要点:
- 堆是一个静态数组,
gc_init将其注册为 GC 堆,因此MICROPY_HEAP_SIZE必须在编译期确定; - 外层
for(;;)实现无限软复位循环——这就是import machine; machine.reset()能“重启”而不退出仿真的原因; - 内层循环根据
pyexec_mode_kind分发到 RAW REPL(测试与串口协议用)或友好 REPL(交互用)。
架构相关的配置开关
mpconfigport.h 根据编译时的架构宏自动启用对应的原生代码生成器(native emitter):
- ARMv7(Thumb):
MICROPY_EMIT_THUMB、MICROPY_EMIT_INLINE_THUMB(MPS2_AN385默认;QEMU_SOC_NRF51即MICROBIT板除外); - RISC-V 32:
MICROPY_EMIT_RV32,并启用ZBA、ZCMP扩展及内联汇编MICROPY_EMIT_INLINE_RV32; - RISC-V 64:
MICROPY_PERSISTENT_CODE_LOAD_NATIVE(64 位可加载原生.mpy); - PowerPC 64:通过
MICROPY_MAKE_POINTER_CALLABLE处理函数指针。
同时该文件还启用了MICROPY_VFS_ROM(只读 ROM 文件系统,配合 ROMFS 分区使用)、MICROPY_PY_MACHINE(machine 模块)等特性,并将MICROPY_PY_SYS_PLATFORM设为"qemu"。
UART 驱动:一套接口、多种 SoC
uart.c 是移植中最能体现“架构抽象”的文件:对外只暴露uart_init()、uart_rx_chr()、uart_rx_any()、uart_tx_strn()四个函数,内部则按QEMU_SOC_*宏为不同模拟 SoC 提供不同的寄存器级实现:
QEMU_SOC_STM32(NETDUINO2):STM32 风格 UART,基址0x40011000;QEMU_SOC_NRF51(MICROBIT):nRF51 UART,基址0x40002000,通过事件寄存器(RXDRDY)轮询;QEMU_SOC_MPS2/QEMU_SOC_MPS3(MPS2_AN385 / MPS2_AN500 / MPS3_AN547):Cortex-M 系统设计套件 UART,基址0x40004000/0x49303000,发送时轮询STATE.TXFULL位;QEMU_SOC_IMX6(SABRELITE):i.MX6 UART,基址0x02020000;QEMU_SOC_VIRT(VIRT_RV32 / VIRT_RV64)与QEMU_SOC_POWERNV(POWERNV9):16550 兼容 UART,基址0x10000000/ PowerNV 特有地址。
网络支持(可选)
通过 lan9118.c、network_lan.c 与 mpnetworkport.c 实现 LAN9118 以太网控制器的模拟驱动。当板卡启用MICROPY_HW_ETH_LAN9118时(SABRELITE 等),会自动拉起 lwIP 协议栈(MICROPY_PY_LWIP),提供network.LAN接口与 socket 模块,默认主机名为"mpy-qemu"(见 mpconfigport.h)。
七、冻结模块与原生测试样本
构建时会通过FROZEN_MANIFEST冻结一组测试模块(Makefile 按架构区分):
- ARM(除 MICROBIT):
frozen_asm_thumb.py、frozen_const.py、frozen_viper.py、native_frozen_align.py; - MICROBIT / riscv32 / riscv64 / ppc64:对应的
frozen_asm_rv32.py或仅frozen_const.py。
这些文件位于 test-frzmpy 目录,分别用于验证 Thumb 内联汇编、RV32 内联汇编、viper 模式、原生冻结对齐等架构相关特性在仿真环境中的正确性,是 CI 架构覆盖的重要一环。
八、适用前提与限制
- 实验性质:README 明确标注本移植为 “experimental, community-supported”,适合测试、实验与调试,不代表对目标板卡硬件的完整支持;
- 工具链门槛:RISC-V 64 工具链的
-mcmodel=medany约束是构建失败的最常见原因,建议直接使用仓库提供的 Docker 镜像规避; - 外设覆盖有限:UART、可选以太网与 ROMFS 是主要外设抽象,真实芯片的中断、GPIO 等能力并不在仿真范围内;
- 无 JTAG/OpenOCD 需求:这是本移植的卖点而非缺陷——调试通过
QEMU_DEBUG=1配合 gdb(默认 TCP 1234)即可完成。
对希望验证 MicroPython 架构后端(Thumb/RV32/RV64 原生代码生成)、跑官方测试套件或在无硬件环境下做原型实验的开发者来说,ports/qemu是目前仓库中成本最低、覆盖最广的入口。
- 嵌入式
- 语言运行时
- 编程语言
- 解释器
- 编译器
- 物联网
- 系统编程
【免费下载链接】micropython
MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
相关推荐
跨架构容器终极指南:QEMU仿真与binfmt_misc完美解决方案
跨架构容器终极指南:QEMU仿真与binfmt_misc完美解决方案 在当今多架构计算环境中,容器技术的跨平台运行已成为开发者和运维人员的迫切需求。multia
开发工具虚拟化Zephyr 中的 ESP32-S3-DevKitC:硬件特性、构建烧录、QEMU 仿真与 OpenOCD 调试实战
Zephyr 中的 ESP32 S3 DevKitC:硬件特性、构建烧录、QEMU 仿真与 OpenOCD 调试实战 ESP32 S3 DevKitC 是乐鑫(
操作系统嵌入式RTOS物联网Pwndbg 跨架构测试指南:从 qemu-user 用户态调试到 qemu-system 全系统模拟
Pwndbg 跨架构测试指南:从 qemu user 用户态调试到 qemu system 全系统模拟 Pwndbg 的日常开发与测试大多运行在 x86_64
逆向工程调试器应用安全开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考