MicroPython rp2.PIO 类详解:RP2040 可编程 I/O(PIO)接口的进阶用法
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
本文基于 MicroPython 仓库中 rp2.PIO.rst 文档展开,系统讲解rp2.PIO类的构造、GPIO 基址配置、程序内存管理、状态机获取与中断绑定等进阶 API,并结合 rp2_pio.c 的 C 实现源码逐条印证其行为细节。读完本篇后,你将能够理解PIO与StateMachine、asm_pio三者之间的分工关系,并在多引脚域(GPIO0/GPIO16)、指令内存复用、SM 级中断等进阶场景下正确使用 PIO 接口。
PIO 在 rp2 模块中的定位:为什么需要"进阶类"
RP2040 的 PIO(Programmable I/O)是一套由硬件状态机组成的可编程 I/O 外设:每个 PIO 实例包含一段共享的 32 个 16 位指令存储器,以及 4 个相互独立、可并行运行的状态机(State Machine)。
rp2模块提供了三层接口,职责分工明确:
rp2.asm_pio装饰器:用 Python 语法编写 PIO 汇编程序,在函数定义时完成指令编码;rp2.StateMachine:日常使用的首选入口,负责把程序加载进指令内存、配置状态机引脚与移位寄存器、管理 TX/RX FIFO;rp2.PIO:进阶接口,直接操作某个 PIO 实例本身——管理指令内存、切换 GPIO 引脚域、获取裸状态机对象、绑定实例级中断。
官方文档 rp2.PIO.rst 的开篇就明确了这一分层:
The preferred way to interact with PIO is using
rp2.StateMachine, the PIO class is for advanced use.
PIO对象是单例式的:每次rp2.PIO(id)构造返回的都是同一个 C 侧对象。这一点可以从源码 rp2_pio.c 得到确认——模块内维护了一个固定的对象数组:
static rp2_pio_obj_t rp2_pio_obj[] = { { { &rp2_pio_type }, pio0, PIO0_IRQ_0 }, { { &rp2_pio_type }, pio1, PIO1_IRQ_0 }, #if NUM_PIOS >= 3 { { &rp2_pio_type }, pio2, PIO2_IRQ_0 }, #endif }; // constructor(id) static mp_obj_t rp2_pio_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 1, false); int pio_id = mp_obj_get_int(args[0]); if (!(0 <= pio_id && pio_id < MP_ARRAY_SIZE(rp2_pio_obj))) { mp_raise_ValueError(MP_ERROR_TEXT("invalid PIO")); } return MP_OBJ_FROM_PTR(&rp2_pio_obj[pio_id]); }构造函数签名严格限定为一个且仅一个位置参数(mp_arg_check_num(n_args, n_kw, 1, 1, false)),因此传入多余参数会抛出ValueError,这与文档中 "Raises aValueErrorif any other argument is provided" 的描述一致。值得注意的是,源码中通过NUM_PIOS >= 3条件编译支持了第三个 PIO 实例——从源码结构看,该数组是为 RP235x 系列(3 个 PIO、共 12 个状态机)预留的,RP2040 上则只有 2 个实例。
构造函数 PIO(id)
import rp2 from rp2 import PIO pio0 = PIO(0) # 第一个 PIO 实例 pio1 = PIO(1) # 第二个 PIO 实例 print(pio0) # PIO(0) print(pio1) # PIO(1)RP2040 上实例编号为 0 和 1;越界的id会抛出ValueError(源码中错误消息为 "invalid PIO")。对象的打印格式由 rp2_pio.c 的rp2_pio_print实现,输出PIO(<索引>)。
两个实例在硬件上完全对称:各自拥有 32 条指令的共享指令内存和 4 个状态机,因此系统共有 8 个全局状态机,编号 0–7(PIO0 的 SM0–SM3 对应全局 0–3,PIO1 的 SM0–SM3 对应全局 4–7)。
PIO.gpio_base([base]):查询与设置 GPIO 引脚域
base = PIO(0).gpio_base() # 查询当前 GPIO 基址,返回 Pin 对象 PIO(0).gpio_base(machine.Pin(16)) # 切换到 GPIO16 基址(32~48 号引脚域) print(PIO(0).gpio_base()) # Pin(16)RP2040 的 32 个 GPIO 被划分为两个可寻址域:GPIO0 域(引脚 0–31)与 GPIO16 域(引脚 16–48,其中 32–48 号引脚只能通过该域访问)。每个 PIO 实例可以独立选择一个基址,从而让该实例的in()/out()等指令去驱动不同引脚域上的引脚。
文档中强调了两个关键约束:
- 参数必须是引脚对象(或对应的引脚号整数),且只能是 GPIO0 或 GPIO16;
- 设置 GPIO 基址必须在添加任何程序、创建任何状态机之前完成。
源码 rp2_pio.c 实现了这两条约束:
#if PICO_PIO_USE_GPIO_BASE // PIO.gpio_base([base]) static mp_obj_t rp2_pio_gpio_base(size_t n_args, const mp_obj_t *args) { rp2_pio_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args > 1) { uint gpio_base = mp_hal_get_pin_obj(args[1]); // Must be 0 for GPIOs 0 to 31 inclusive, or 16 for GPIOs 16 to 48 inclusive. if (!(gpio_base == 0 || gpio_base == 16)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid GPIO base")); } if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { mp_raise_OSError(MP_EINVAL); } } // Return current gpio_base value. return pio_get_gpio_base(self->pio) == 0 ? (mp_obj_t)pin_GPIO0 : (mp_obj_t)pin_GPIO16; } #endif返回值为Pin(0)或Pin(16)对象,分别表示当前基址是 GPIO0 还是 GPIO16。当基址为 GPIO16 时,后续通过StateMachine.init()传入的out_base等引脚会做范围校验——见 rp2_pio.c 中asm_pio_get_pins的ValueError("..._base not within gpio_base range")检查,即引脚号必须落在当前基址覆盖的区间内。
典型应用场景:RP2040 的 GPIO32–39 默认接内部闪存,通常不可用;而 GPIO40–47(以及 16–31 中的部分引脚)可以通过 GPIO16 基址被 PIO 访问。需要同时使用两组物理引脚做不同协议时,可以把两个 PIO 实例分别绑定到不同基址上。
PIO.add_program(program):向指令内存加载程序
@rp2.asm_pio() def blink(): set(pins, 1) set(pins, 0) prog = blink # asm_pio 装饰器返回的是"程序对象",不是普通函数 pio = PIO(0) pio.add_program(prog) # 占用 2 条指令内存,返回 Noneadd_program把asm_pio生成的程序写入该 PIO 实例的共享指令内存。这里有一个容易误解的点:每个 PIO 实例只有 32 条 16 位指令的存储空间,且是实例内所有状态机共享的。多个状态机若要运行同一程序,StateMachine.init会复用已加载的偏移(rp2_pio.c 中先读取程序对象里保存的PROG_OFFSET_PIOx字段,offset < 0才真正加载);而加载两个不同程序则分别占用内存。空间不足时抛出OSError(ENOMEM),源码实现见 rp2_pio.c:
// PIO.add_program(prog) static mp_obj_t rp2_pio_add_program(mp_obj_t self_in, mp_obj_t prog_in) { rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_obj_t *prog; mp_obj_get_array_fixed_n(prog_in, PROG_MAX_FIELDS, &prog); mp_buffer_info_t bufinfo; mp_get_buffer_raise(prog[PROG_DATA], &bufinfo, MP_BUFFER_READ); struct pio_program pio_program = { bufinfo.buf, bufinfo.len / 2, -1 }; if (!pio_can_add_program(self->pio, &pio_program)) { mp_raise_OSError(MP_ENOMEM); } uint offset = rp2_pio_add_managed_program(self->pio, &pio_program); // Store the program offset in the program object. prog[PROG_OFFSET_PIO0 + pio_get_index(self->pio)] = MP_OBJ_NEW_SMALL_INT(offset); return mp_const_none; }程序对象的内部结构
asm_pio装饰器返回的"程序"实际上是一个 9 元数组,其字段定义在 Python 侧 rp2.py 与 C 侧 rp2_pio.c 中一一对应:
| 索引 | 名称 | 含义 |
|---|---|---|
| 0 | PROG_DATA | array("H"),编码好的 16 位指令序列 |
| 1 | PROG_OFFSET_PIO0 | 该程序在 PIO0 指令内存中的偏移,未加载时为 -1 |
| 2 | PROG_OFFSET_PIO1 | 同上,对应 PIO1 |
| 3 | PROG_OFFSET_PIO2 | 同上,对应 PIO2(RP235x 预留) |
| 4 | PROG_EXECCTRL | wrap 位置、sideset 位宽等执行控制字 |
| 5 | PROG_SHIFTCTRL | 移位方向、阈值、autopush/autopull、FIFO 连接等 |
| 6 | PROG_OUT_PINS | out_init初始化的引脚/引脚组 |
| 7 | PROG_SET_PINS | set_init初始化的引脚/引脚组 |
| 8 | PROG_SIDESET_PINS | sideset_init初始化的引脚/引脚组 |
这个结构解释了为什么同一程序可以"跨实例复用记录":每个实例的加载偏移独立存放在各自字段中。asm_pio的编码实现(双 pass 汇编、shiftctrl/execctrl寄存器的位域拼装)可以在 rp2.py 的PIOASMEmit类中完整阅读。
指令内存的记账机制
除了 SDK 提供的pio_can_add_program空间检查,MicroPython 还用一张位掩码跟踪"由本模块管理"的指令内存占用,见 rp2_pio.c:
// These masks keep track of PIO instruction memory used by this module. static uint32_t rp2_pio_instruction_memory_usage_mask[NUM_PIOS];rp2_pio_add_managed_program/rp2_pio_remove_managed_program在调用 SDK 的pio_add_program/pio_remove_program的同时置位/清除对应区间的掩码位;位宽 32 正好与 32 条指令一一对应。这套记账还有一个重要用途:系统重置资源时(如进入 USB 复合模式、端口 deinit),rp2_pio_deinit 会调用rp2_pio_remove_all_managed_programs只清除本模块管理的程序,源码注释明确说明"其他子系统(如 CYW43 无线驱动)可能也在用 PIO,它们的程序必须保持完好"。也就是说 PIO 指令内存是与系统内其他 C 驱动共享的资源,PIO对象层面通过记账避免误删他人程序。
PIO.remove_program([program]):释放指令内存
pio.remove_program(prog) # 仅移除指定程序 pio.remove_program() # 移除本模块在 PIO0 上加载的所有程序行为要点(与 rp2.PIO.rst 一致):
- 无参调用时移除全部程序:实现上按"offset=0、length=32"构造一个覆盖整个指令内存的程序来删除(见 rp2_pio.c);
- 重复移除一个已删除的程序不是错误;
- 移除时会把程序对象中对应的偏移字段重置为 -1,因此之后再次
add_program会重新分配位置; - 若传入的程序从未加载(偏移 < 0),则抛出
ValueError("prog not in instruction memory")。
// PIO.remove_program([prog]) static mp_obj_t rp2_pio_remove_program(size_t n_args, const mp_obj_t *args) { rp2_pio_obj_t *self = MP_OBJ_TO_PTR(args[0]); uint8_t length = 32; // 默认:整段 32 条指令 uint offset = 0; if (n_args > 1) { // ... 取程序数据与偏移 ... if (offset < 0) { mp_raise_ValueError(MP_ERROR_TEXT("prog not in instruction memory")); } prog[PROG_OFFSET_PIO0 + pio_get_index(self->pio)] = MP_OBJ_NEW_SMALL_INT(-1); } struct pio_program pio_program = { NULL, length, -1 }; rp2_pio_remove_managed_program(self->pio, &pio_program, offset); return mp_const_none; }PIO.state_machine(id, [program, ...]):获取并可选初始化状态机
sm = PIO(1).state_machine(3) # 返回全局编号 7 的状态机 print(sm) # StateMachine(7) sm2 = PIO(0).state_machine(1, prog) # 获取并顺带 init(等价于 StateMachine.init)- 每个 PIO 实例有 4 个状态机,
id取 0–3,越界抛ValueError; - 返回的是与
rp2.StateMachine(全局id)完全相同的对象,rp2.PIO(1).state_machine(3)即rp2.StateMachine(7),打印为StateMachine(7)(示例见 rp2.PIO.rst 中的交互示例); - 若继续传入
program及freq、in_base等参数,则等价于调用 rp2.StateMachine.rst 中定义的StateMachine.init(...)完成初始化。
源码 rp2_pio.c 展示了获取逻辑:先根据pio_id * 4 + sm_id定位全局状态机对象(获取过程中会做 claim 检查,防止与其他使用者冲突,见下文"资源占用"一节),再在有额外参数时转调rp2_state_machine_init_helper:
// PIO.state_machine(id, prog, freq=-1, *, set=None) static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { rp2_pio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); mp_int_t sm_id = mp_obj_get_int(pos_args[1]); if (!(0 <= sm_id && sm_id < 4)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid StateMachine")); } // Return the correct StateMachine object. const rp2_state_machine_obj_t *sm = rp2_state_machine_get_object(pio_get_index(self->pio) * 4 + sm_id); if (n_args > 2 || kw_args->used > 0) { rp2_state_machine_init_helper(sm, n_args - 2, pos_args + 2, kw_args); } return MP_OBJ_FROM_PTR(sm); }通过PIO.state_machine()还是直接StateMachine(id)获取,最终落到同一个静态对象数组 rp2_pio.c,因此状态(FIFO 内容、配置)是共享的,与"PIO 单例"同理。
PIO.irq(handler, trigger, hard):PIO 实例级中断
def on_sm_event(pio): print("SM event!") pio = PIO(0) irq_obj = pio.irq(on_sm_event, trigger=rp2.PIO.IRQ_SM0 | rp2.PIO.IRQ_SM1, hard=False)要点(对照 rp2.PIO.rst 与 rp2_pio.c):
- MicroPython 只使用每个 PIO 实例的 IRQ 0,IRQ 1 不可用(RP2040 每个 PIO 有两条硬件 IRQ 线,
inte0/ints0供 PIO0/PIO1 各自使用); trigger默认值为 0xf00,即四个IRQ_SMx常量的按位或(对应inte0寄存器中 SM0–SM3 的位),可以只订阅其中一部分;- 返回的 IRQ 对象是 MicroPython 统一中断框架(
mp_irq_obj_t)的成员,支持.handler、.flags、.triggers属性; hard=True注册硬中断(在调度器层面同步执行),hard=False为软中断(通过事件循环派发);- 该中断同时服务于 SM 级事件:源码中 pio_irq0 处理函数会先按
irq->trigger匹配 PIO 级 IRQ 对象,再逐一遍历 4 个状态机各自的 IRQ 对象,分别触发回调。
static void pio_irq0(PIO pio) { uint32_t ints = pio->ints0; // Acknowledge SM0-3 IRQs if they are enabled on this IRQ0. pio->irq = ints >> 8; rp2_pio_irq_obj_t *irq = MP_STATE_PORT(rp2_pio_irq_obj[pio_get_index(pio)]); if (irq != NULL && (ints & irq->trigger)) { irq->flags = ints & irq->trigger; mp_irq_handler(&irq->base); } // Call handler if it is registered, for StateMachine irqs. for (size_t i = 0; i < 4; ++i) { rp2_state_machine_irq_obj_t *irq = MP_STATE_PORT(rp2_state_machine_irq_obj[pio_get_index(pio) * 4 + i]); if (irq != NULL && ((ints >> (8 + i)) & irq->trigger)) { irq->flags = 1; mp_irq_handler(&irq->base); } } }中断资源被占用时的保护
绑定 PIO IRQ 时,实现会检查该硬件中断是否已被其他资源独占(rp2_pio.c):
void rp2_pio_irq_set_exclusive_handler(PIO pio, uint irq) { irq_handler_t current = irq_get_exclusive_handler(irq); if ((current && current != rp2_pio_get_irq_handler(pio)) || irq_has_shared_handler(irq)) { mp_raise_ValueError(MP_ERROR_TEXT("irq claimed by external resource")); } else if (!current) { irq_set_exclusive_handler(irq, rp2_pio_get_irq_handler(pio)); } }若某个 C 驱动(或另一个线程/核上的使用者)已经接管了 PIO0_IRQ_0,则抛出ValueError: irq claimed by external resource,避免了静默覆盖别人的中断处理器。
常量:PIN 初始化、移位方向、FIFO 连接与 IRQ 触发位
PIO类上挂载的常量全部定义在 rp2_pio.c 的类字典中,取值如下:
IN_LOW / IN_HIGH / OUT_LOW / OUT_HIGH
PIO.IN_LOW # 0:引脚配置为输入,初值低 PIO.IN_HIGH # 1:引脚配置为输入,初值高 PIO.OUT_LOW # 2:引脚配置为输出,初值低 PIO.OUT_HIGH # 3:引脚配置为输出,初值高这组常量用于asm_pio装饰器的out_init、set_init、sideset_init参数,在init时由 MicroPython 一次性完成目标引脚的方向与初始电平设置。从源码看(rp2_pio.c 的asm_pio_get_pins/asm_pio_init_gpio),每个 pin 值的最高位是方向(1=输出)、次低位是电平;初始化时会用位掩码批量写引脚值与方向,并把每个受管引脚的gpio_set_function设为GPIO_FUNC_PIOx。若某类引脚用了多个,则传元组,例如out_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW)。
SHIFT_LEFT / SHIFT_RIGHT
PIO.SHIFT_LEFT # 0 PIO.SHIFT_RIGHT # 1用于asm_pio的in_shiftdir/out_shiftdir或StateMachine.init的同名参数,分别控制 ISR(输入移位寄存器)与 OSR(输出移位寄存器)的移位方向。默认均为左移;若希望字节低位先出/先入,常改为SHIFT_RIGHT。
JOIN_NONE / JOIN_TX / JOIN_RX
PIO.JOIN_NONE # 0:TX、RX 各 4 字 PIO.JOIN_TX # 1:合并为单方向 8 字 TX FIFO PIO.JOIN_RX # 2:合并为单方向 8 字 RX FIFO用于asm_pio的fifo_join参数。默认每个状态机各有一个 4 字的 TX 和一个 4 字的 RX FIFO;JOIN_TX/JOIN_RX会把两个 4 字 FIFO 合并成一个 8 字单向 FIFO,用于吞吐更高或需要更深缓冲的单向数据流。该值最终写入shiftctrl寄存器的高 2 位——在 Python 侧 rp2.py 中可以看到拼装过程:
shiftctrl = ( fifo_join << 30 | (pull_thresh & 0x1F) << 25 | (push_thresh & 0x1F) << 20 | out_shiftdir << 19 | in_shiftdir << 18 | autopull << 17 | autopush << 16 )IRQ_SM0 ~ IRQ_SM3
PIO.IRQ_SM0 # 0x100 PIO.IRQ_SM1 # 0x200 PIO.IRQ_SM2 # 0x400 PIO.IRQ_SM3 # 0x800用于PIO.irq的trigger参数。这四个位正好对应硬件inte0寄存器中"SMx 事件"的中断使能位,默认trigger=0xF00即全部四个 SM 的事件都参与触发。
综合实战:从程序编写到状态机运行
下面把asm_pio、StateMachine与PIO进阶接口串起来,全部取自仓库自带示例。
示例一:WS2812 灯带驱动(pio_ws2812.py)
import array, time from machine import Pin import rp2 NUM_LEDS = 8 @rp2.asm_pio( sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24, ) 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, freq=8_000_000, sideset_base=Pin(22)) sm.active(1)这里用到的都是PIO常量:OUT_LOW决定侧置引脚(数据脚)初始为低;SHIFT_LEFT配合 24 位阈值pull_thresh=24+autopull=True实现"OSR 攒够 3 字节(24 bit)自动从 TX FIFO 取下一个像素",从而主循环只需不断sm.put(ar, 8)推送 RGB 字。
示例二:PIO 进阶接口 + 状态机中断(pio_quadrature_encoder.py)
该示例演示了"程序检测状态变化 → push 到 RX FIFO → 触发 IRQ"的完整数据通路,并使用了sm.irq(最终挂到与PIO.irq同一条pio_irq0硬件中断上):
@rp2.asm_pio() def pio_quadrature(in_init=rp2.PIO.IN_LOW): wrap_target() label("again") in_(pins, 2) mov(x, isr) jmp(x_not_y, "push_data") mov(isr, null) jmp("again") label("push_data") push() irq(block, rel(0)) mov(y, x) wrap() sm = StateMachine(0, pio_quadrature, freq=160000, in_base=Pin(2)) sm.irq(encoder_state_changed_irq_handler) sm.exec("set(y, 99)") # 保证首次比较一定"不同" sm.active(1)注意其中两个PIO相关细节:
in_init=rp2.PIO.IN_LOW——按前文所述,init时 MicroPython 会把in_base起始的引脚方向/电平设好并切到 PIO 功能;- 程序内的
irq(block, rel(0))会置位"相对 IRQ"(状态机内部 IRQ),进而拉高ints0中该 SM 的位,最终经由 pio_irq0 派发到sm.irq(...)注册的 Python 回调,回调中用sm.rx_fifo()轮询 FIFO 深度再sm.get()取数。
示例三:手动管理指令内存与 GPIO 基址
PIO进阶接口的典型用法是把"资源生命周期"完全掌握在自己手里:
import rp2 from machine import Pin from rp2 import PIO pio = PIO(1) # 1) 若程序需要驱动 32 号及以上引脚,先切换引脚域(必须在加载程序前完成) pio.gpio_base(Pin(16)) # 2) 手动加载程序(同一程序重复加载会复用记录、不重复占内存的是 StateMachine.init 路径; # 直接 add_program 相同程序对象则会再次占用空间,注意 32 条上限) @rp2.asm_pio() def prog_a(): set(pins, 1) set(pins, 0) pio.add_program(prog_a) # 占用 2 条 # ... 创建状态机、运行 ... # 3) 用完释放;重复 remove 不是错误 pio.remove_program(prog_a) pio.remove_program() # 兜底:清空本模块加载的全部程序 pio.gpio_base(Pin(0)) # 恢复默认引脚域使用PIO.add_program与StateMachine.init自动加载的区别在于:前者返回 None、把偏移写回程序对象供后续state_machine(id, program)使用,适合一个 PIO 实例上手动编排多个程序的偏移布局;后者对已加载的相同程序会复用偏移(rp2_pio.c)。
资源占用与边界行为
结合 rp2_pio.c 源码,还可以梳理几条文档未展开但实践中会遇到的边界行为:
- 状态机占用(claim):
StateMachine(0)或PIO(0).state_machine(0)首次获取某状态机时会调用pio_sm_claim;若该状态机已被外部 C 驱动 claim(例如板载外设驱动),则抛出ValueError("StateMachine claimed by external resource")(rp2_pio.c)。系统资源重置时rp2_state_machine_reset_all会解除本模块 claim 并关闭全部 SM(rp2_pio.c)。 - 指令内存上限:每个 PIO 实例 32 条指令,
add_program前用pio_can_add_program预检,空间不足抛OSError(ENOMEM);配合 32 位记账掩码,remove_program()无参调用可一次清空本模块占用。 - 与板载驱动的共存:
rp2_pio_deinit的注释明确提到 CYW43 驱动等外部子系统可能使用 PIO 指令内存与状态机,MicroPython 的清理逻辑只回收"自己记账的部分"(rp2_pio.c)。因此在启用了射频功能的板卡上,PIO(0)/PIO(1)的可用指令空间可能与裸 RP2040 不同。 - 中断独占:
PIO.irq(handler=...)绑定回调时走独占中断检查(见前文 "irq claimed by external resource"),handler=None或不再传 handler 的再次调用则只取回 IRQ 对象、不改变硬件使能。
小结:三层接口的协作关系
| 层级 | 对象 | 职责 | 关键 API |
|---|---|---|---|
| 程序 | rp2.asm_pio返回的程序对象 | 16 位指令序列 + 引脚初值 + 移位/FIFO 默认配置 | @rp2.asm_pio(...)、rp2.asm_pio_encode |
| 状态机 | rp2.StateMachine | 运行程序、引脚/移位配置、FIFO 数据通道 | init、active、put/get、rx_fifo/tx_fifo、irq、exec |
| 实例 | rp2.PIO | 指令内存管理、GPIO 引脚域、裸 SM 获取、实例级 IRQ | PIO(id)、gpio_base、add_program/remove_program、state_machine、irq |
日常开发中,"写@rp2.asm_pio程序 +StateMachine运行"已经能覆盖绝大多数协议类外设(WS2812、软 SPI/I2C、编码器、按键扫描等,可参考 examples/rp2 目录下的示例);当需要多个实例分工、跨越 GPIO16 引脚域、精细控制指令内存布局或绑定 SM 级中断时,再下探到rp2.PIO这一层。更多参数细节可继续查阅 rp2 模块总览文档、rp2.StateMachine,以及 C 侧实现 rp2_pio.c 与 Python 侧汇编器 rp2.py。
【免费下载链接】micropythonMicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems项目地址: https://gitcode.com/gh_mirrors/mi/micropython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考