MicroPython rp2.StateMachine 完全指南:掌控 RP2040 可编程 I/O(PIO)状态机
嵌入式语言运行时编程语言解释器编译器物联网系统编程【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址https://gitcode.com/gh_mirrors/mi/micropython点击查看免费下载本指南以官方文档 rp2.StateMachine 为骨架系统讲解 MicroPython 中rp2.StateMachine类的全部 API——从构造函数、init()的十余个配置参数到exec()、FIFO 读写、IRQ 中断与缓冲区协议并结合仓库内 rp2_pio.c 的源码实现与 examples/rp2 中的真实示例帮助你理解状态机在底层如何被配置和驱动并能够独立编写、装载、运行 PIO 程序完成 UART、WS2812 灯带、正交编码器等高速外设时序任务。一、PIO 与 StateMachine 类概览RP2040 芯片内置两个完全相同的 PIOProgrammable I/O可编程 I/O外设每个 PIO 实例拥有 4 个状态机State Machine因此整颗芯片共有8 个状态机编号 07。状态机是 PIO 的核心执行单元它以极低的延迟执行一段专用汇编程序从而在不占用 CPU 的情况下产生或采样精确的时序信号。在 MicroPython 中rp2.StateMachine类就是访问这套可编程 I/O 接口的入口详见 rp2.StateMachine.rst。与之配套的是rp2.asm_pio()装饰器——用 Python 语法编写 PIO 汇编程序再用StateMachine装载运行。汇编相关函数与全部 PIO 指令语法记录在 rp2.rst 的 PIO assembly language instructions 一节。从源码结构看StateMachine的实现在 rp2_pio.c其中rp2_state_machine_obj_t结构体保存了 PIO 指针、IRQ 编号、状态机编号0-3与全局 IDrp2_pio.c L53-L59全局对象数组按pio0的 SM0-3、pio1的 SM0-3 顺序静态注册rp2_pio.c L561-L576这解释了为什么状态机 ID 0-7 会跨两个 PIO 实例分布。当某个状态机被外部资源如 CYW43 WiFi 驱动占用时rp2_state_machine_get_object()会抛出ValueError: StateMachine claimed by external resourcerp2_pio.c L585-L591。二、构造函数获取并初始化状态机StateMachine(id, [program, ...])id为状态机编号取值范围07。构造时可以只传id获取对象稍后通过init()配置也可以直接传入program由rp2.asm_pio()装饰的函数及init()支持的全部参数一次性完成初始化。源码中rp2_state_machine_make_new在检测到额外参数时会直接调用rp2_state_machine_init_helperrp2_pio.c L762-L778因此两种写法等价# 方式一先构造后初始化 sm rp2.StateMachine(0) sm.init(ws2812, freq8_000_000, sideset_basePin(22)) # 方式二构造时直接初始化 sm rp2.StateMachine(0, ws2812, freq8_000_000, sideset_basePin(22))三、init()状态机的核心配置init()方法签名如下是使用 StateMachine 时最重要的 APIStateMachine.init(program, freq-1, *, in_baseNone, out_baseNone, set_baseNone, jmp_pinNone, sideset_baseNone, in_shiftdirNone, out_shiftdirNone, push_threshNone, pull_threshNone)3.1 指令装载与复用programprogram会被加入当前 PIO 实例的指令存储器。RP2040 每个 PIO 只有32 条指令的存储空间MicroPython 对此做了自动去重如果指令存储器中已经存在该程序则复用其偏移量offset以节省指令空间。底层对应rp2_pio_add_managed_program()它调用 SDK 的pio_add_program()并通过rp2_pio_instruction_memory_usage_mask位图跟踪已占用槽位rp2_pio.c L137-L142当程序已在内存中时init()直接读取之前保存的 offsetrp2_pio.c L645-L651。asm_pio()生成的程序对象正是以 program 数据 offset 数组 配置字 的多字段数组形式存在字段布局定义在 rp2.py 的常量。3.2 频率与时钟分频freqfreq为状态机运行频率Hz默认值为系统时钟频率即不进行分频。时钟分频器按system clock frequency / freq计算因此存在舍入误差。最小分频为系统时钟的 1/65536。在默认 125 MHz 系统时钟下freq的最小值为1908Hz若要跑更慢的频率需要先用machine.freq()降低系统时钟。源码中freq的三种取值路径rp2_pio.c L653-L672freq 0默认 -1分频设为 1即全速运行freq 0特殊值将分频寄存器置 0配合手动控制场景正常正值div clk_sys * 256 / freq其中整数部分clkdiv_int与小数部分clkdiv_frac1/256 精度分别写入硬件寄存器越界时抛出ValueError: freq out of range。3.3 引脚基址参数参数作用说明in_basein()指令使用的第一个引脚见下方注意out_baseout()指令使用的第一个引脚与out_init结合初始化set_baseset()指令使用的第一个引脚最多 5 个jmp_pinjmp(pin, ...)指令使用的引脚默认配置为输入sideset_base侧置side-set输出使用的第一个引脚与sideset_init结合重要注意in_base对应的引脚必须手动配置为输入或其他模式PIO 才能读到期望的信号——它们可以是输入引脚、输出引脚或连接到其他外设。jmp_pin同样可以手动配置但默认会被设为输入引脚。源码实现细节rp2_pio.c L687-L756out/set/sideset的引脚会同时检查程序内的*_init配置asm_pio_get_pins解析单引脚或引脚元组方向与初值编码为pindirs/pinvals最终通过asm_pio_init_gpio()一次性设置引脚方向、初值并切换为GPIO_FUNC_PIO0/1复用功能。在 RP2350 上若jmp_pin仍处于隔离isolation状态代码还会自动调用pio_gpio_init()使其可被 PIO 读取rp2_pio.c L742-L753。3.4 移位方向与阈值参数in_shiftdirISR输入移位寄存器的移位方向取PIO.SHIFT_LEFT或PIO.SHIFT_RIGHT。out_shiftdirOSR输出移位寄存器的移位方向取值同上。push_thresh触发自动推送auto-push或条件重推conditional re-push的位数阈值。pull_thresh触发自动拉取auto-pull或条件重拉conditional re-pull的位数阈值。这些参数覆盖rp2.asm_pio()中设置的默认值。底层由asm_pio_override_shiftctrl()直接改写硬件SHIFTCTRL寄存器的对应位段rp2_pio.c L231-L235可见 MicroPython 将这些配置映射到了芯片原生的移位控制字。四、运行控制active() 与 restart()4.1 active([value])获取或设置状态机是否正在运行 sm.active() True sm.active(0) False不带参数时返回布尔值表示当前运行状态传入参数则先设置再返回。源码直接读写 PIO 的CTRL控制寄存器rp2_pio.c L786-L792。在 pio_uart_tx.py 示例中创建 8 个 UART TX 状态机后依次调用sm.active(1)启动pio_uart_tx.py L35-L36。4.2 restart()重启状态机并跳转到程序起点。该方法通过 RP2040 的SM_RESTART寄存器清除状态机的内部状态包括输入/输出移位计数器输入移位寄存器ISR的内容延迟计数器等待 IRQwaiting-on-IRQ状态通过StateMachine.exec()执行的、已停顿的指令。源码中restart()在pio_sm_restart()之外还执行一条pio_encode_jmp(initial_pc)将程序计数器显式跳回该状态机装载程序的起始偏移rp2_pio.c L795-L801。五、单指令注入exec(instr)exec()用于单步执行一条 PIO 指令常用于调试或在不重启整个状态机的情况下注入行为若instr是字符串则通过asm_pio_encode从字符串编码为机器码sm.exec(set(0, 1))若instr是整数则视为已编码的 PIO 机器码指令直接执行sm.exec(rp2.asm_pio_encode(out(y, 8), 0))从源码看字符串形式会动态导入rp2模块并调用asm_pio_encode同时自动读取该状态机当前的 sideset 配置sideset 数量与可选位保证编码与现有程序一致rp2_pio.c L805-L822。asm_pio_encode的 Python 实现在 rp2.py L276-L298注意其jmp不被支持设置为None且要求恰好产生 1 条指令否则抛出PIOASMError。六、FIFO 数据传输put()、get() 与队列查询状态机通过 TX/RX FIFO 与 CPU 交换数据。默认每个方向有4 个 32 位深度的 FIFO 字可通过asm_pio(fifo_join...)合并为单一 8 字方向。6.1 get(bufNone, shift0)从 RX FIFO 拉取一个 32 位字FIFO 为空时阻塞等待直到状态机推入数据返回值先右移shift位返回值为word shift。data sm.get() # 阻塞读取一个字 data sm.get(shift8) # 读取并右移 8 位源码实现rp2_pio.c L826-L874等待期间会调用mp_event_handle_nowait()保持事件响应若传入缓冲区buf参数则可一次批量读取多个字支持bytearray以及b/h/i类型码get会按元素大小反复填充直到缓冲区写满。6.2 put(value, shift0)向 TX FIFO 推入数据value可以是整数、类型为B/H/I的 array或bytearray方法会阻塞直到所有字写入 FIFO若 FIFO 已满或变满会等待状态机拉取足够的字以完成写入每个字先左移shift位状态机实际收到word shift。sm.put(0x12345678) # 推入单个字 sm.put(ar, 8) # 批量推入数组整体左移 8 位在 pio_ws2812.py 中LED 的 24 位 RGB 数据正是通过sm.put(ar, 8)以shift8批量写入配合autopullTrue, pull_thresh24自动补给 OSRpio_ws2812.py L53。UART 示例则用sm.put(ord(c))逐字符推入pio_uart_tx.py L40-L42。6.3 rx_fifo() 与 tx_fifo()返回对应 FIFO 中当前的字数0表示空if sm.rx_fifo(): data sm.get() # 非阻塞读取前先查询 if sm.tx_fifo() 4: sm.put(word) # 确认有空间再写入rx_fifo()适合在调用阻塞的get()前检查是否有数据等待tx_fifo()则用于判断是否还有空间推入新字。两者分别映射到 SDK 的pio_sm_get_rx_fifo_level与pio_sm_get_tx_fifo_levelrp2_pio.c L918-L930。七、中断irq(handlerNone, trigger0|1, hardFalse)irq()返回当前 StateMachine 的 IRQ 对象并可选地完成配置。当状态机程序执行irq()指令例如irq(rel(0))时触发IRQ 0-3 对处理器可见4-7 仅用于状态机间通信详见 rp2.rst 的 irq 指令说明。def on_sm_irq(irq): print(state machine raised IRQ, flags:, irq.flags()) sm.irq(handleron_sm_irq, trigger1, hardFalse)底层实现pio_irq0()中断服务函数在读取INTS0后会依次为 PIO 级 IRQ 与 4 个状态机 IRQ 分发事件rp2_pio.c L77-L98StateMachine.irq()通过写INTE0寄存器的8 sm位使能/禁用对应状态机中断rp2_pio.c L990-L999trigger默认值1表示监听本状态机的 IRQ 标志位。若 PIO 的 IRQ 已被 CYW43 等外部资源独占会抛出ValueError: irq claimed by external resourcerp2_pio.c L125-L134。八、Buffer 协议直连 FIFO 与 DMA 搬运StateMachine类支持buffer protocol缓冲区协议允许直接访问每个状态机的发送与接收 FIFO。其核心用途是将 StateMachine 对象直接作为rp2.DMA()通道的 read/write 参数让 DMA 直接读写 FIFO实现无 CPU 参与的批量数据搬运。rp2_state_machine_get_buffer的实现非常直接rp2_pio.c L932-L947写入方向返回pio-txf[sm]的地址读取方向返回pio-rxf[sm]的地址缓冲区长度固定为 4 字节一个 32 位字类型码为I。这意味着 StateMachine 在 DMA 眼中就是一个固定的 32 位寄存器映射端口DMA 搬运的单位即 FIFO 字。九、实战示例组合以下结合仓库示例展示完整工作流。9.1 WS2812 LED 灯带驱动pio_ws2812.pyimport array, time from machine import Pin import rp2 NUM_LEDS 8 rp2.asm_pio( sideset_initrp2.PIO.OUT_LOW, out_shiftdirrp2.PIO.SHIFT_LEFT, autopullTrue, pull_thresh24, ) def ws2812(): T1 2 T2 5 T3 3 wrap_target() label(bitloop) out(x, 1) .side(0) [T3 - 1] jmp(not_x, do_zero) .side(1) [T1 - 1] jmp(bitloop) .side(1) [T2 - 1] label(do_zero) nop() .side(0) [T2 - 1] wrap() sm rp2.StateMachine(0, ws2812, freq8_000_000, sideset_basePin(22)) sm.active(1) ar array.array(I, [0 for _ in range(NUM_LEDS)]) for i in range(4 * NUM_LEDS): for j in range(NUM_LEDS): r j * 100 // (NUM_LEDS - 1) b 100 - j * 100 // (NUM_LEDS - 1) if j ! i % NUM_LEDS: r 3 b 3 ar[j] r 16 | b sm.put(ar, 8) time.sleep_ms(50)关键点freq8_000_000设定位元速率autopullTrue与pull_thresh24让 OSR 自动从 TX FIFO 补给 24 位颜色数据sm.put(ar, 8)的shift8将数据对齐到 FIFO 字的高 24 位。9.2 多路 UART 发送pio_uart_tx.py该示例在引脚 1017 上同时创建8 个 UART TX展示了多个状态机的并行复用from machine import Pin from rp2 import PIO, StateMachine, asm_pio UART_BAUD 115200 PIN_BASE 10 NUM_UARTS 8 asm_pio(sideset_initPIO.OUT_HIGH, out_initPIO.OUT_HIGH, out_shiftdirPIO.SHIFT_RIGHT) def uart_tx(): pull() set(x, 7) .side(0) [7] label(bitloop) out(pins, 1) [6] jmp(x_dec, bitloop) nop() .side(1) [6] uarts [] for i in range(NUM_UARTS): sm StateMachine( i, uart_tx, freq8 * UART_BAUD, sideset_basePin(PIN_BASE i), out_basePin(PIN_BASE i), ) sm.active(1) uarts.append(sm) def pio_uart_print(sm, s): for c in s: sm.put(ord(c)) for i, u in enumerate(uarts): pio_uart_print(u, Hello from UART {}!\n.format(i))要点freq8 * UART_BAUD因为每个数据位由 8 个时钟周期产生1 起始位 8 数据位 停止位每指令周期耗时对齐out_shiftdirPIO.SHIFT_RIGHT保证 LSB 先发。仓库中 tests/target_wiring/rp2.py 还提供了硬件联动的测试场景examples/rp2目录下另有 pio_uart_rx.py、pio_quadrature_encoder.py、pio_pwm.py、pio_1hz.py 等示例分别覆盖接收、编码器、PWM 与精确计时等典型 PIO 应用可作为进阶参考。十、总结StateMachine 编程要点速查资源有限RP2040 共 8 个状态机、每个 PIO 32 条指令程序相同会自动复用指令内存。频率下限125 MHz 系统时钟下freq最小 1908 Hz更慢需降低系统时钟。引脚归属in_base的引脚需手动配置jmp_pin默认自动配置为输入out/set/sideset引脚由程序内*_init与构造函数基址共同初始化。FIFO 通信put()/get()默认阻塞批量数据可用 array/bytearray读写前用tx_fifo()/rx_fifo()查询水位。调试利器exec()可单步注入指令restart()可干净地重置状态机。高性能搬运利用 buffer protocol 将 StateMachine 直接交给rp2.DMA()可让 FIFO 数据搬运完全脱离 CPU。赞分享嵌入式语言运行时编程语言解释器编译器物联网系统编程【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址https://gitcode.com/gh_mirrors/mi/micropython点击查看免费下载相关推荐MicroPython rp2.PIO 类详解RP2040 可编程 I/OPIO接口的进阶用法MicroPython rp2.PIO 类详解RP2040 可编程 I/OPIO接口的进阶用法 本文基于 MicroPython 仓库中 rp2.PIO.嵌入式语言运行时编程语言解释器编译器物联网系统编程MicroPython rp2 模块完全指南RP2040/RP2350 的 PIO、状态机与 DMA 编程MicroPython rp2 模块完全指南RP2040/RP2350 的 PIO、状态机与 DMA 编程 rp2 模块是 MicroPython 针对树莓派嵌入式语言运行时编程语言解释器编译器物联网系统编程MicroPython RP2 可编程 IOPIO完全指南状态机、PIO 汇编指令与实战MicroPython RP2 可编程 IOPIO完全指南状态机、PIO 汇编指令与实战 本指南基于当前仓库中的 docs/rp2/tutorial/pi嵌入式语言运行时编程语言解释器编译器物联网系统编程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考