十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RP2040 PIO驱动WS2812灯带:MicroPython实现完整指南

RP2040 PIO驱动WS2812灯带:MicroPython实现完整指南 1. 项目概述为什么 RP2040 和 WS2812 是天生一对做嵌入式这几年彩灯控制算是绕不开的需求。从最简单的呼吸灯到跑马灯、音乐频谱、氛围灯带WS2812 这个型号的灯珠几乎统治了 DIY 圈和商业项目。它只需要一根数据线就能串联几百个灯珠每个灯珠内部有驱动芯片可以独立控制 RGB 三色亮度这特性让它在设计上省掉了大量的走线和驱动电路。但 WS2812 有一个让很多人头疼的地方它对时序要求极其苛刻。单个 bit 的高低电平宽度都是微秒甚至几百纳秒级别比如写一个 1 码需要 0.8us 左右的高电平写一个 0 码只需要 0.35us 左右。MCU 主频如果不够快或者系统里有中断干扰很容易把时序拉偏导致灯珠颜色乱跳、闪烁甚至是完全不亮。以前我用 STM32 做 WS2812 驱动常用方案是 DMA 定时器 PWM或者直接用 SPI 协议去模拟。说实话这套东西配置起来并不算难但它占用了定时器和 DMA 通道而且对中断比较敏感。后来我拿到树莓派 RP2040 这颗芯片发现它的 PIOProgrammable I/O外设几乎是给 WS2812 这类设备量身定做的。一个独立的状态机就能在后台稳定输出精细时序CPU 完全不用参与甚至在跑 MicroPython 解释器的同时灯带依然能输出流畅的动画效果。这篇文章我就把从 PIO 原理到 MicroPython 实现的完整过程写一遍适合刚接触 RP2040 的开发者也适合那些已经用 MicroPython 做过几个项目、但一直没搞懂 PIO 在干什么的人。需要提前说一下项目使用的是树莓派官方设计的 RP2040 芯片市面上常见的 Pico、Pico W、各种 RP2040 开发板都适用代码逻辑是一样的。2. PIO 是什么为什么 WS2812 需要它2.1 PIO 的定位独立于 CPU 的微型状态机很多人第一次听到 PIO 会觉得它是一个复杂的东西实际上它的核心思想非常简单RP2040 芯片里内置了八个可编程状态机每个 PIO 模块四个共两个模块这些状态机可以独立执行一小段专用程序用来在 GPIO 引脚上产生或读取特定的时序波形。它们和 CPU 共享系统时钟但运行时不需要 CPU 干预。打个形象的比方CPU 是一个什么都管的大老板而 PIO 状态机像是专门派去盯生产线的工人老板交代好流程之后就可以去忙别的事了工人按照既定流程持续干活只要参数不变产线就不会出问题。PIO 的状态机有自己的一套精简指令集只有九条指令分别是 JMP、WAIT、IN、OUT、PUSH、PULL、MOV、IRQ、SET。每条指令执行占一个时钟周期固定 32 位编码。你不需要学一套复杂的汇编语言只用记住几个关键指令基本就能覆盖绝大多数场景。对 WS2812 来说PIO 的核心价值在于它可以在完全不占用 CPU 的情况下按照精确到纳秒的时序输出数据。而且因为状态机是硬件执行的不受中断、调度器或者解释器的影响所以时序的一致性非常有保障。2.2 WS2812 的时序要求为什么普通 GPIO 翻转不行WS2812 使用的是单线归零码协议意思是每个数据 bit 由一个高电平脉冲和一个低电平脉冲组成靠高电平的宽度来区分 0 和 1。具体的时序参数在芯片手册上有明确值但不同批次灯珠会有细微差异常见参考值如下0 码高电平 0.35us低电平 0.80us总周期 1.15us1 码高电平 0.80us低电平 0.45us总周期 1.25us复位码低电平保持 50us 以上表示一帧数据结束普通 GPIO 翻转的方式通常是在循环里用 delay 来控制电平持续时间。听起来简单但实际在 MicroPython 里会出问题。因为 MicroPython 是解释执行每行 Python 代码的耗时不等加上系统定时器中断的影响延迟抖动可以达到几十微秒的级别。哪怕差 0.1us灯珠的接收端也可能误判逻辑电平。我用过几个不同版本的 MicroPython 固件实测直接用 machine.Pin 加 time.sleep_us 控制时序灯带表现极差颜色基本是乱的。所以技术上真正可靠的方案有两种一是使用 ESP32 的 RMT 外设二是使用 RP2040 的 PIO。RMT 是 ESP32 专门为红外遥控设计的后来也被大量用在 LED 控制上但它的精度和灵活性其实不如 RP2040 的 PIO。PIO 的好处是你可以用几条指令就精确生成任意宽度的脉冲而且状态机的时钟频率可以自由配置用起来非常灵活。2.3 选择 PIO 方案时的几个对比项我在决定用 RP2040 PIO 之前也对比了其他几种常见的实现路径。这里把经验列出来方便你做选型方案硬件要求时序精度CPU 占用实现难度适用场景GPIO 软件翻转任意 MCU差易受中断干扰极高低只适合几个灯珠、非实时任务定时器 DMASTM32 等中等偏上低高有一定 PWM/DMA 基础的场景SPI 外设模拟任意 MCU中低中需要额外电路或数据重组ESP32 RMTESP32 系列较高低中ESP32 生态下推荐RP2040 PIORP2040 全系非常高几乎为零中低MicroPython 或 C 开发均推荐3. 硬件连接和开发环境准备3.1 接线和电源注意事项WS2812 灯带的接线只有三根线VCC5V、GND、DIN数据输入。RP2040 的 GPIO 输出是 3.3V 电平WS2812 的逻辑高电平阈值大约在 0.7 倍 VCC 左右如果 VCC 是 5V那高电平阈值大约 3.5V直接用 3.3V 输出理论上会存在电平不匹配的风险。但实际测试告诉我多数 WS2812 灯珠对 3.3V 电平是兼容的我试过 Pico GPIO 直接接灯带 DIN在灯带供电 5V 的情况下能正常工作。不过需要注意不同厂家的灯珠兼容性不一样。如果你手头的灯带在直接连接时出现第一个灯珠正常、后面的灯珠颜色偏色或者完全不亮那大概率是信号电平不够干净这时候需要加一个 74HCT245 电平转换芯片它能把 3.3V 信号转成 5V 信号也能提升信号驱动能力。这一点对超过 50 个灯珠的长灯带尤其重要因为在长距离传输后信号会有衰减。供电上面有个很典型的坑如果灯带电源和开发板电源接在了一起当灯珠亮度拉高时瞬时电流可能达到几十甚至上百毫安每颗。100 个灯珠全亮白光时电流就是 6A 到 10A 的级别。普通 USB 口根本扛不住会造成电压跌落系统复位甚至电脑识别不到开发板。我个人的建议是灯带单独用 5V 电源供电开发板和灯带之间共地GND 相连数据线单接。这样既保证了灯带供电充足也保证了开发板电源稳定。3.2 MicroPython 固件准备RP2040 跑 MicroPython 非常简单去树莓派官方或者 MicroPython 官网下载对应固件.uf2 文件按住开发板上的 BOOTSEL 键再插入 USB电脑上会弹出一个名为 RPI-RP2 的 U 盘把固件文件直接拖进去就完成烧录了。整个过程不需要额外驱动也不需要烧录软件。需要提醒的是MicroPython 固件有不同版本有的带 PIO 支持有的可能精简掉了部分模块。我建议直接下载官方发布的最新稳定版不要用第三方魔改版因为 PIO 相关的 rp2 模块在官方固件里已经内置且经过了充分测试。在 REPL 里可以用以下命令验证import rp2 print(rp2)如果正常打印出模块信息说明 PIO 功能可用。3.3 引脚选择建议RP2040 的 PIO 可以分配状态机到任意 GPIO这让布线非常灵活。但在实际项目中我建议避开那些被板载硬件占用的引脚。以 Raspberry Pi Pico 为例GPIO 0 到 GPIO 22 都可以用但 GPIO 23 到 GPIO 26 分别连接到了板载闪存的 CS、SCK、MOSI、MISOGPIO 24 和 GPIO 25 也有特殊用途其中 GPIO 25 通常连接板载 LED。虽然 PIO 也可以在这些引脚上工作但如果同时操作板载闪存会导致冲突。对于 WS2812 数据线我一般选择 GPIO 0 或 GPIO 16这两个引脚在布局上也比较顺手。4. PIO 汇编状态机详细拆解4.1 核心汇编程序逐行解释MicroPython 里用 PIO 驱动 WS2812是通过rp2.asm_pio修饰器来定义状态机程序的。以下是官方 SDK 和社区常用的经典实现我加了详细注释rp2.asm_pio(sideset_initrp2.PIO.OUT_LOW, out_shiftdirrp2.PIO.SHIFT_LEFT, autopullTrue, pull_thresh24) def ws2812(): wrap_target() label(bitloop) out(x, 1) .side(0) [2] jmp(not_x, do_zero) .side(1) [1] jmp(bitloop) .side(1) [4] label(do_zero) nop() .side(0) [4] wrap()这段汇编看起来很短但信息量很大逐条来拆out(x, 1)从发送 FIFO 中取出一个 bit存到 x 寄存器。因为设置了out_shiftdirSHIFT_LEFT所以每执行一次out(x, 1)状态机就把移位寄存器里的最高位移到 x 上然后数据自动左移。side(0)和side(1)side-set 是 PIO 的特色功能可以在指令执行的同时顺便设置某个指定引脚的电平。这里把sideset_init设为OUT_LOW意思是引脚初始为低电平然后每条指令执行时根据.side(x)的设置来更新引脚状态。由于 side-set 和指令本身是同时发生的所以时序能精确控制在一个时钟周期内。方括号里[2]、[1]、[4]是延时周期数。例如out(x, 1) .side(0) [2]表示这条指令本身占 1 个时钟周期再加上 2 个等待周期总共占 3 个时钟周期。jmp(not_x, do_zero)如果 x 寄存器为 0也就是当前 bit 是 0就跳转到 do_zero 标签否则继续执行下一条也就是输出 1 码。wrap()和wrap_target()这两个标记定义了程序循环的边界。执行到wrap()后状态机自动跳回wrap_target()形成一个无限循环专门用来持续从 FIFO 读取数据。这里最核心的设计思想是通过设置状态机的时钟频率使得上述指令执行周期和 WS2812 的时序要求精确匹配。因为 PIO 的每条指令时间是可以精确计算的所以我们完全可以通过主频和指令周期倒推需要的状态机时钟频率。4.2 时序计算状态机时钟频率怎么确定RP2040 的系统主频在 MicroPython 里默认是 125MHz。PIO 状态机的时钟可以由这个主频分频得到分频系数是一个小数用StateMachine初始化时的freq参数来指定。我们先把上面每一条路径的时钟周期数算出来out(x, 1) .side(0) [2]占 3 个周期jmp(not_x, do_zero) .side(1) [1]占 2 个周期如果 x1继续输出 1 码jmp(bitloop) .side(1) [4]占 5 个周期回到wrap_target后的out(x, 1) .side(0) [2]占 3 个周期总周期数 3 2 5 3 13 个周期如果 x0跳转到do_zeronop() .side(0) [4]占 5 个周期回到wrap_target后的out(x, 1) .side(0) [2]占 3 个周期总周期数 3 2 5 3 13 个周期有趣的地方来了不管当前 bit 是 0 还是 1总周期数都是 13。这意味着状态机输出每一个 bit 的时间完全相同不会因为数据内容不同而产生累计误差。每个 bit 耗时 13 个状态机时钟周期。如果要让一个 bit 的总周期达到 1.25us那么状态机时钟频率就是 13 / 1.25us 10.4MHz考虑到 WS2812 的实际时序容差范围比较宽高电平宽度允许在 0.25us 到 0.6us 之间波动具体看芯片批次10.4MHz 这个值非常合适。在 MicroPython 里创建状态机时直接把freq设为 10_400_000 就行。有的项目会用到更高的状态机频率比如把分频设成 800kHz 再配合额外的延迟周期也能工作。但实测下来 10.4MHz 是最稳的0 码和 1 码的高电平宽度分别是 0.35us 和 0.8us和手册推荐值最接近。4.3 为什么用 24bit 数据和自动填充WS2812 每一颗灯珠需要 24 位数据按 GRB 顺序排列前 8 位是绿色中间 8 位是红色最后 8 位是蓝色。注意不是常见的 RGB 顺序很多新手在这里第一次踩坑。在 PIO 程序里设置了autopullTrue, pull_thresh24意思是状态机从发送 FIFO 中每读取 24 bit 就自动触发一次 PULL 操作从系统内存的 DMA 缓冲区取下一个 32 位数据并截取低 24 位用于移位。这样上层 MicroPython 代码只需要把颜色数据按规则打包成 24 位整数写入状态机PIO 会自动帮你拆成 bit 流发出去你不需要手动一 bit 一 bit 地推数据。MicroPython 写入数据的常用方式是sm.put()它接受整数或列表。写入时需要注意字节序问题下面代码里会有处理。5. MicroPython 完整实现代码与使用说明5.1 基础单灯控制代码先给一个最精简的版本只点亮一颗灯珠import machine import rp2 import ustruct # 定义 PIO 状态机程序 rp2.asm_pio(sideset_initrp2.PIO.OUT_LOW, out_shiftdirrp2.PIO.SHIFT_LEFT, autopullTrue, pull_thresh24) def ws2812(): wrap_target() label(bitloop) out(x, 1) .side(0) [2] jmp(not_x, do_zero) .side(1) [1] jmp(bitloop) .side(1) [4] label(do_zero) nop() .side(0) [4] wrap() # 初始化状态机使用 GPIO 0频率 10.4MHz sm rp2.StateMachine(0, ws2812, freq10_400_000, sideset_basemachine.Pin(0)) sm.active(1) # 点亮一颗灯珠绿色最大亮度 green 0x00FF00 # 按 GRB 顺序绿色在前 sm.put(green)这里sm.put(green)写入的是一个 24 位整数虽然在 Python 内部它会被当成普通 Python int 处理但在 MicroPython 的 PIO 实现里put会自动把整数截断为 32 位并放入 FIFO。因为设置了 24 位自动拉取所以低 24 位有效。如果你看到灯珠颜色不对比如把代码里的绿色值0x00FF00写进去之后灯珠显示红色那基本可以确定是 GRB 顺序问题。交换颜色分量就好。5.2 多灯珠跑马灯和第二颗灯珠的写入时机把多个灯珠串在一起时需要把每一颗灯珠的 24 位数据依次写入状态机。WS2812 的数据协议是控制器发送第一颗灯珠的 24 位数据第一颗灯珠锁存并将后续数据转发给第二颗发送第二颗的 24 位数据第二颗锁存并转发给第三颗依此类推。所以要让 10 颗灯珠显示不同颜色你需要连续发送 10 组 24 位数据每组之间不需要额外延时但全部数据发送完后必须插入一个大于 50us 的低电平复位码否则灯珠不会刷新。在 MicroPython 中可以把所有颜色打包成一个字节数组发送这样效率更高import machine import rp2 import array rp2.asm_pio(sideset_initrp2.PIO.OUT_LOW, out_shiftdirrp2.PIO.SHIFT_LEFT, autopullTrue, pull_thresh24) def ws2812(): wrap_target() label(bitloop) out(x, 1) .side(0) [2] jmp(not_x, do_zero) .side(1) [1] jmp(bitloop) .side(1) [4] label(do_zero) nop() .side(0) [4] wrap() sm rp2.StateMachine(0, ws2812, freq10_400_000, sideset_basemachine.Pin(0)) sm.active(1) # 构造颜色数据10 颗灯珠红色渐变 data array.array(I, [0x0000FF] * 10) # 注意字节序问题 # 在大多数平台上array(I) 是小端存储每个元素占 4 字节 sm.put(data) # 发送完毕后需要保持低电平至少 50us这里直接 sleep 一下 import time time.sleep_us(50)这里有个经典问题array(I)的元素在 RP2040 MicroPython 里实际是 32 位无符号整数占用 4 字节字节序是小端。sm.put(data)把整个数组的字节内容按顺序送入 FIFO但 PIO 读取时是 32 位为单位如果数据源是连续内存效果等同于把每个整数的低 24 位连续送入状态机。所以我一般直接用array(I)存储颜色值然后一次性sm.put()。如果颜色值在内存中被写成小端序而 PIO 设置的是SHIFT_LEFT那么sm.put(0x00FF00)发送的 bit 顺序可能是错误的。首次实验时可以先对单灯测试如果颜色不对请检查字节序方向。一个非常有效的调试方法是发送纯红0x0000FF和纯绿0x00FF00对比实际显示颜色来推断字节序。这个方法屡试不爽。5.3 带亮度渐变和动画的完整示例下面给一个稍微完整一点的示例包含颜色渐变和简单的呼吸灯效果import machine import rp2 import array import time import math rp2.asm_pio(sideset_initrp2.PIO.OUT_LOW, out_shiftdirrp2.PIO.SHIFT_LEFT, autopullTrue, pull_thresh24) def ws2812(): wrap_target() label(bitloop) out(x, 1) .side(0) [2] jmp(not_x, do_zero) .side(1) [1] jmp(bitloop) .side(1) [4] label(do_zero) nop() .side(0) [4] wrap() NUM_LEDS 30 PIN machine.Pin(0) sm rp2.StateMachine(0, ws2812, freq10_400_000, sideset_basePIN) sm.active(1) def set_color(index, r, g, b): # 按 GRB 顺序打包颜色分量 8bit return ((g 0xFF) 16) | ((r 0xFF) 8) | (b 0xFF) def show_breath(cycles10): for _ in range(cycles): for step in range(20): brightness 0.2 0.8 * abs(math.sin(step / 20 * 3.14159)) data array.array(I, [set_color(i, int(255 * brightness), 0, 0) for i in range(NUM_LEDS)]) sm.put(data) time.sleep_us(50) time.sleep_ms(50) show_breath(3)这个呼吸灯实现虽然是基于纯 Python 运算每帧需要循环 20 次每次构造一个 30 元素的数组在 MicroPython 解释器里也不算慢但如果你需要更复杂的动画如彩虹渐变、音乐频谱性能瓶颈很可能是 Python 层的数组构造速度而不是 PIO 输出速度。PIO 数据送入 FIFO 后真正影响实时性的是上层代码生成数据的速度而不是发送速度。所以对大量灯珠或高帧率场景建议尽量在 Python 层减少每帧的计算或者直接用 C 编写扩展模块。不过对大多数常规灯效MicroPython 已经够用。5.4 注意MicroPython 下的 StateMachine 资源限制RP2040 共有 8 个状态机每个 PIO 模块有 4 个。MicroPython 的rp2.StateMachine默认从 PIO0 开始分配如果你需要使用多个 PIO 或多个状态机需要注意编号冲突。PIO 程序还占用指令内存PIO0 和 PIO1 各有 32 条指令上面的 WS2812 程序只占 5 条非常省。但如果你的程序里同时跑了其他 PIO 外设比如 DHT11 读取、红外解码等指令内存和状态机数量都需要提前规划。6. 常见问题与排查技巧实录6.1 灯带完全不亮或者第一个灯微亮后续不亮这种情况十有八九是电平或供电问题。首先用万用表量一下灯带 VCC 和 GND 之间电压是否在 4.5V 到 5.5V 之间。如果电压正常再量 DIN 引脚的电压。没有数据传输时DIN 应该保持低电平。如果 DIN 是悬空状态灯珠内部逻辑可能不稳定表现为随机微弱闪烁。另一个常见原因是接线错误。WS2812 灯带的 DIN 端有方向性输入端通常有标记。有些灯带是 DIN 和 DOUT 都有接反会导致第一个灯珠不亮但后续灯珠可能透过 DOUT 输出延续数据出现颜色错乱。这时候把数据线交换一下方向就好。如果是电平兼容性的问题表现往往会更加隐蔽。我之前用过一款某品牌的 5V WS2812B直接用 Pico GPIO 驱动前 10 个灯珠正常第 11 个开始偶尔闪烁。后来示波器量了 DIN 波形发现高电平幅度只有 2.8V有些灯珠内部逻辑阈值偏高导致部分 bit 误判。加了一颗 74HCT245 电平转换芯片后问题彻底消失。所以如果灯带较长超过 1 米或者灯珠数量较多我强烈建议加上电平转换。6.2 颜色不对比如绿色和红色互换这是最容易解决也最容易踩坑的问题。WS2812 的数据格式是 GRB不是 RGB。很多习惯了普通 RGB 屏幕的人会下意识地按 RGB 顺序写颜色值。只要在打包颜色数据时把顺序调整成 GRB 即可。推荐做法是在代码里封装一个set_color(index, r, g, b)函数统一处理字节序这样后面动画逻辑就不用关心顺序问题。还有一个容易忽略的点如果你从网上找到的例程是 CircuitPython 或其他平台的它们内部可能已经帮你做了颜色顺序转换而你直接在 MicroPython 里套用数据就会出错。务必自己确认灯珠的数据格式。6.3 RP2040 总是进入烧录模式无法正常执行代码很多人在连接 WS2812 灯带以后发现 RP2040 开发板每次上电都会进入 BOOTSEL 烧录模式电脑上识别出 RPI-RP2 磁盘但代码没有运行。这通常不是 PIO 的问题而是电源问题。灯带供电如果和开发板共用同一个 USB 口上电瞬间灯带的浪涌电流会导致 3.3V 电压跌落RP2040 的 boot 逻辑检测到异常复位进而进入烧录模式。这种现象在灯带长度超过 50 颗且全亮时特别容易出现。解决方法很简单灯带使用独立电源并在开发板和灯带之间共地。另外开发板上的 VSYS 引脚如果接了外部 5V建议不要同时插 USB以免两个电源互相冲突。如果是 Pico W 或者第三方 RP2040 开发板部分板子的 BOOTSEL 按键设计比较靠近边缘插 USB 时容易被误触也会造成进入烧录模式的现象。这时候检查按键状态和接线即可。6.4 状态机初始化时报错或者无法写入数据在 MicroPython 里创建 StateMachine 时如果出现ValueError: Invalid pin或者OSError大多数情况下是引脚被其他功能占用或者sideset_base指定错误。PIO 的sideset_base参数需要传一个machine.Pin对象不能传整数。同时要确认 GPIO 编号在 0 到 29 之间且没有和板载 USB、闪存冲突。我在开发中还遇到过一种情况在 REPL 环境里反复运行代码前一次的状态机没有取消激活导致第二次StateMachine()创建失败。解决办法是在程序开头手动清理if machine.Pin(0).is_pio_state_machine(): rp2.PIO(0).remove_program()或者直接在创建前用sm.active(0)关闭旧状态机。6.5 多灯珠刷新率太低动画卡顿这个问题主要是 MicroPython 层的数据构造效率造成的。PIO 本身可以在几个微秒内发送完一个 30 灯珠的帧但 Python 层如果用列表推导式生成 30 个颜色的数组每帧耗时可能在 10ms 以上。如果动画逻辑还包含三角函数、浮点运算帧率就会显著下降。有几个常用的优化手段预先分配数组内存避免每帧重新创建 array 对象。用整数运算替代浮点运算例如用查表法代替sin函数。把动画状态放在一个循环里每次只更新需要变化的那几个灯珠而不是全部重建。如果确实需要非常高的刷新率或者超大灯带比如 512 个灯珠做视频效果建议考虑切换到 C 语言或者用 PIO DMA 配合方式。但在 MicroPython 范围内调整好上述优化点帧率通常能提升 2 到 3 倍。7. 踩坑总结三件我绝不会再忽略的事这个项目做完后有几点经验我觉得比代码本身更有价值在这里直接分享给准备上手的朋友。第一永远不要跳过共地这一步。开发板电源和灯带电源如果不共地DIN 信号的电平参考地不一致轻则颜色偏色重则直接烧毁灯珠数据输入端。我吃过一次亏之后现在画电路或者接线的第一件事就是把所有 GND 连在一起再谈其他。第二调试时先做单灯测试。直接把目标设为点亮第 10 颗之后的某颗灯珠进行验证如果单灯测试通过但多灯出现问题问题基本出在发送顺序或者 FIFO 溢出管理上而不是 PIO 时序。单灯测试能帮你快速隔离问题范围不用一上来就面对几十颗灯珠的复杂现象。第三PIO 程序尽量保持精简。WS2812 的 PIO 程序只有 5 条指令非常容易阅读和维护。有朋友为了炫技把颜色处理也放进 PIO比如在状态机里做 gamma 校正或者亮度缩放最终导致指令内存紧张、调试困难。PIO 适合做边界明确的时序任务颜色计算放在 CPU 层反而更灵活。记住PIO 的目标是稳定可靠地输出时序而不是替你完成业务逻辑。另外我在实际使用中发现把 WS2812 的 PIO 程序和普通 LED 闪烁放在同一个项目里时不要把两者挂在同一个 PIO 模块的不同状态机上否则其中一个状态机频繁触发中断会影响到另一个的启动时序。虽然 PIO 之间是独立执行的但中断共享确实会带来隐蔽的干扰。用两个 PIO 模块完全隔离是最省心的方案。这个项目做完后我还把同一套代码移植到了自己做的 8x8 WS2812 点阵显示模块上配合 MicroPython 的 framebuffer做了一个简易的像素画板。整个过程中 PIO 部分一行没改只改了上层取模和坐标映射的逻辑。这说明 PIO 方案的可复用性非常强值得你在自己的项目里好好储备。
返回列表