MicroPython rp2.PIO 类详解:RP2040 可编程 I/O(PIO)接口的进阶用法
2026/9/20 20:47:59 网站建设 项目流程

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 实现源码逐条印证其行为细节。读完本篇后,你将能够理解PIOStateMachineasm_pio三者之间的分工关系,并在多引脚域(GPIO0/GPIO16)、指令内存复用、SM 级中断等进阶场景下正确使用 PIO 接口。

PIO 在 rp2 模块中的定位:为什么需要"进阶类"

RP2040 的 PIO(Programmable I/O)是一套由硬件状态机组成的可编程 I/O 外设:每个 PIO 实例包含一段共享的 32 个 16 位指令存储器,以及 4 个相互独立、可并行运行的状态机(State Machine)。

rp2模块提供了三层接口,职责分工明确:

  1. rp2.asm_pio装饰器:用 Python 语法编写 PIO 汇编程序,在函数定义时完成指令编码;
  2. rp2.StateMachine:日常使用的首选入口,负责把程序加载进指令内存、配置状态机引脚与移位寄存器、管理 TX/RX FIFO;
  3. rp2.PIO:进阶接口,直接操作某个 PIO 实例本身——管理指令内存、切换 GPIO 引脚域、获取裸状态机对象、绑定实例级中断。

官方文档 rp2.PIO.rst 的开篇就明确了这一分层:

The preferred way to interact with PIO is usingrp2.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_pinsValueError("..._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 条指令内存,返回 None

add_programasm_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 中一一对应:

索引名称含义
0PROG_DATAarray("H"),编码好的 16 位指令序列
1PROG_OFFSET_PIO0该程序在 PIO0 指令内存中的偏移,未加载时为 -1
2PROG_OFFSET_PIO1同上,对应 PIO1
3PROG_OFFSET_PIO2同上,对应 PIO2(RP235x 预留)
4PROG_EXECCTRLwrap 位置、sideset 位宽等执行控制字
5PROG_SHIFTCTRL移位方向、阈值、autopush/autopull、FIFO 连接等
6PROG_OUT_PINSout_init初始化的引脚/引脚组
7PROG_SET_PINSset_init初始化的引脚/引脚组
8PROG_SIDESET_PINSsideset_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 中的交互示例);
  • 若继续传入programfreqin_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_initset_initsideset_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_pioin_shiftdir/out_shiftdirStateMachine.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_piofifo_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.irqtrigger参数。这四个位正好对应硬件inte0寄存器中"SMx 事件"的中断使能位,默认trigger=0xF00即全部四个 SM 的事件都参与触发。

综合实战:从程序编写到状态机运行

下面把asm_pioStateMachinePIO进阶接口串起来,全部取自仓库自带示例。

示例一: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相关细节:

  1. in_init=rp2.PIO.IN_LOW——按前文所述,init时 MicroPython 会把in_base起始的引脚方向/电平设好并切到 PIO 功能;
  2. 程序内的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_programStateMachine.init自动加载的区别在于:前者返回 None、把偏移写回程序对象供后续state_machine(id, program)使用,适合一个 PIO 实例上手动编排多个程序的偏移布局;后者对已加载的相同程序会复用偏移(rp2_pio.c)。

资源占用与边界行为

结合 rp2_pio.c 源码,还可以梳理几条文档未展开但实践中会遇到的边界行为:

  1. 状态机占用(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)。
  2. 指令内存上限:每个 PIO 实例 32 条指令,add_program前用pio_can_add_program预检,空间不足抛OSError(ENOMEM);配合 32 位记账掩码,remove_program()无参调用可一次清空本模块占用。
  3. 与板载驱动的共存rp2_pio_deinit的注释明确提到 CYW43 驱动等外部子系统可能使用 PIO 指令内存与状态机,MicroPython 的清理逻辑只回收"自己记账的部分"(rp2_pio.c)。因此在启用了射频功能的板卡上,PIO(0)/PIO(1)的可用指令空间可能与裸 RP2040 不同。
  4. 中断独占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 数据通道initactiveput/getrx_fifo/tx_fifoirqexec
实例rp2.PIO指令内存管理、GPIO 引脚域、裸 SM 获取、实例级 IRQPIO(id)gpio_baseadd_program/remove_programstate_machineirq

日常开发中,"写@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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询