游乐游手机版
首页/AI热点日报/热点详情

树莓派Pico PIO程序使用教程

类型:热点整理2026-07-21
树莓派Pico的可编程输入输出(PIO)系统使用独立状态机运行微型程序,实现极低延迟的精确时序控制。通过LED亮度控制实例解释PIO指令与周期计数原理,并演示驱动WS2812B灯条,利用8MHz频率状态机生成精确脉冲宽度实现单线归零码协议。

PIO入门教程:从零开始掌握树莓派Pico的可编程IO

树莓派Pico的一大亮点在于其独特的可编程输入输出(PIO)系统。与传统GPIO控制方式不同,PIO允许你在微控制器内部运行独立且极低延迟的微型程序,从而实现精准的时序控制和信号处理。本教程将从基础概念入手,循序渐进地帮助你理解PIO的工作机制,并通过两个实际案例——LED亮度调节与WS2812B灯条驱动——带你掌握核心应用方法。

1. 什么是PIO?为什么它如此重要?

在日常MicroPython编程中,我们通常无需关注底层细节。例如,执行print("hello")时,底层如何存储字符、如何发送至串行终端、需要多少个时钟周期——这些操作都被自动处理了。然而,当你使用PIO(可编程IO)时,你需要以更底层的视角来思考逻辑:直接操作引脚、精确控制时序、逐位处理数据。

PIO由一组独立的状态机构成(Pico共有8个,编号0–7),每个状态机都能独立运行一段用PIO汇编语言编写的“小程序”。这些程序能够以极快的速度循环执行,非常适合实现WS2812灯条、DHT11温湿度传感器、红外遥控等对时序要求严格的通信协议。

温馨提示:如果你暂时不想深入底层编程,可以直接使用他人封装好的PIO库。不过,理解其原理会让你在遇到复杂问题时更加得心应手。

2. 第一个PIO程序:控制LED亮度

我们先通过一个简单的示例来感受PIO的强大之处——使用三个状态机分别让板载LED以1/4亮度1/2亮度和全亮度进行闪烁。

2.1 代码与运行效果

from rp2 import PIO, StateMachine, asm_pio
from machine import Pin
import time

@asm_pio(set_init=PIO.OUT_LOW)
def led_quarter_brightness():
  set(pins, 0)[2]
  set(pins, 1)

@asm_pio(set_init=PIO.OUT_LOW)
def led_half_brightness():
  set(pins, 0)
  set(pins, 1)

@asm_pio(set_init=PIO.OUT_HIGH)
def led_full_brightness():
  set(pins, 1)

sm1 = StateMachine(1, led_quarter_brightness, freq=10000, set_base=Pin(25))
sm2 = StateMachine(2, led_half_brightness, freq=10000, set_base=Pin(25))
sm3 = StateMachine(3, led_full_brightness, freq=10000, set_base=Pin(25))

while(True):
  sm1.active(1)
  time.sleep(1)
  sm1.active(0)
  sm2.active(1)
  time.sleep(1)
  sm2.active(0)
  sm3.active(1)
  time.sleep(1)
  sm3.active(0)

2.2 原理详解

  • PIO指令:每条指令默认占用1个时钟周期。你可以在指令后面用方括号添加一个1–31的数字,让状态机在跳转到下一条指令前暂停额外周期。例如set(pins, 0)[2]表示:先将引脚设为低电平(占用1周期),再额外等待2周期,总共占用3周期。
  • 亮度控制原理:以led_quarter_brightness为例,程序循环过程为:
    set(pins, 0)[2] :引脚输出低电平,持续3周期
    set(pins, 1) :引脚输出高电平,持续1周期
    总周期数为4,其中低电平占3/4,因此LED亮度仅为全亮的1/4。
  • 状态机初始化StateMachine(id, func, freq, set_base)
    • id:状态机编号(取值范围0–7)
    • func:PIO程序函数
    • freq:运行频率(单位Hz,可设置范围2000–125000000)
    • set_base:指定程序控制的GPIO引脚
  • 启动与停止:通过sm.active(1)启动状态机,使用sm.active(0)停止运行。

常见疑问解答:
Q: 为什么我运行代码后LED完全没有闪烁?
A: 请首先确认set_base=Pin(25)是否正确。Pico板载LED通常连接在GPIO25,但如果使用外接LED,请务必核对引脚编号。此外,确保频率设置在2000–125000000之间,否则系统会报错。

3. 实战演练:驱动WS2812B RGB灯条

WS2812B是一款非常流行的可寻址LED灯条,每个灯珠内部集成了控制芯片,仅需一根数据线即可独立控制所有LED的颜色与亮度。它采用单线归零码协议,每一位数据都需要通过精确的脉冲宽度来表示逻辑0或1。这正是PIO最擅长的应用场景。

3.1 硬件连接方式

  • 将灯条的数据输入引脚(通常标注为DI)连接到Pico的GPIO引脚(本例中选用GPIO0)。
  • 灯条电源正极(VCC)接到Pico的5V引脚(注意:Pico的5V输出电流有限,如需驱动大量LED,建议使用外部电源供电)。
  • 灯条地线(GND)接到Pico的GND引脚。

3.2 PIO程序详细解析

import array, time
from machine import Pin
import rp2
from rp2 import PIO, StateMachine, asm_pio

# 设置LED数量
NUM_LEDS = 10

@asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
  T1 = 2
  T2 = 5
  T3 = 3
  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").side(0)[T2 - 1]
  nop()

# 创建状态机,频率设为8MHz,side引脚指定为GPIO0
sm = StateMachine(0, ws2812, freq=8000000, sideset_base=Pin(0))
sm.active(1)

3.3 理解WS2812的时序要求与指令设计

  • 时序基准:状态机运行频率设置为8MHz,每条指令占1周期即125ns。WS2812要求的0码脉冲宽度约为350ns,1码约为700ns,复位信号需大于50μs。
  • 位循环逻辑:程序每输出一位数据的具体流程如下:
    1. out(x, 1).side(0)[T3-1]:从OSR读取1位数据存入变量x,同时side引脚输出低电平,并等待2周期(总共3周期,产生约375ns的低电平时间)。
    2. 如果x的值为0,则跳转到do_zero分支:.side(0)[T2-1]输出低电平,等待4周期(约625ns),整体构成0码。
    3. 如果x的值为1,则跳转到bitloop分支:.side(1)[T2-1]输出高电平,等待4周期,整体构成1码。
  • 关键组件说明
    • FIFO:主Python程序将待发送的数据写入FIFO(先进先出队列),供PIO状态机依次读取。
    • OSR(输出移位寄存器):每次执行out()指令时,从OSR中取出数据。由于启用了autopull=True且设置pull_thresh=24,每读取完24位后会自动从FIFO拉入下一组数据。
    • side():与主指令同步执行,用于设置侧集引脚的电平状态(本例中为数据输出引脚)。
    • jmp():条件跳转指令,用于判断x变量是否为0。

实用提示:在PIO中,可用的变量只有两个:xy。它们能够存储32位数据,通常用于循环计数或临时数据暂存。

3.4 发送颜色数据(主程序部分)

# 创建数组,存储每个LED的24位颜色值(GRB顺序)
ar = array.array("I", [0 for _ in range(NUM_LEDS)])

print("blue")
for j in range(0, 255):
  for i in range(NUM_LEDS):
    ar[i] = j  # 直接赋值蓝色通道
  sm.put(ar,8)  # 注意第二个参数8表示截掉高8位,只发送低24位
  time.sleep_ms(100)

print("red")
for j in range(0, 255):
  for i in range(NUM_LEDS):
    ar[i] = j<<8  # 左移8位,放入红色通道
  sm.put(ar,8)
  time.sleep_ms(100)

print("green")
for j in range(0, 255):
  for i in range(NUM_LEDS):
    ar[i] = j<<16  # 左移16位,放入绿色通道
  sm.put(ar,8)
  time.sleep_ms(100)

print("white")
for j in range(0, 255):
  for i in range(NUM_LEDS):
    ar[i]=j<<16 + j<<8 + j  # 同时设置G、R、B三通道
  sm.put(ar,8)
  time.sleep_ms(100)

3.5 颜色编码原理详解

  • WS2812的24位数据格式为:G(8位) R(8位) B(8位)
  • 在MicroPython中,整数类型为32位,我们需要将RGB三个8位值合并到一个32位整数中:
    • 蓝色通道 = j(占据最低8位)
    • 红色通道 = j<<8(占据第9–16位)
    • 绿色通道 = j<<16(占据第17–24位)
  • sm.put(ar,8)中的第二个参数8至关重要:它告诉状态机每次从数组中取出一个32位数值,但只截取低24位(丢弃高8位),因为WS2812只需要24位数据。

常见问题排查:
Q: 为什么运行后灯条不亮或者显示的颜色混乱?
A: 请逐一检查以下三点:
1. 数据线是否连接到了正确的GPIO引脚?代码中sideset_base=Pin(0)对应的是GPIO0。
2. 频率设置是否准确?WS2812需要约800kHz的位周期,8MHz是常用值(每个位周期占用10个时钟周期)。
3. 数据写入方式是否正确?注意第二个参数是8,而不是24。如果忘记写,8,状态机会等待完整的32位数据,从而导致时序错乱。

4. PIO指令速查表

除了本教程中使用的指令外,PIO还支持以下常用指令,帮助你实现更丰富的功能:

  • in():从引脚或变量读取1–32位数据到OSR(与out指令功能相反)。
  • push():将OSR中的数据推送至FIFO,供主程序读取使用。
  • pull():从FIFO拉取数据到OSR(如果启用autopull,该操作可自动完成)。
  • mov():在变量x、y、OSR、ISR等寄存器之间移动数据。
  • irq():触发中断,用于向主程序发送通知信号。
  • wait():等待指定事件发生(如引脚电平变化)。

虽然PIO指令集非常精简(仅有9条指令),但通过巧妙的组合,几乎可以实现所有常见的低速通信协议。需要注意的是,复杂的逻辑运算(如颜色计算等)应在主MicroPython程序中完成,PIO只负责底层的时序控制与数据传输任务。

5. 总结与展望

通过本教程的学习,你已经掌握了PIO的基础概念以及两种典型应用:利用PWM原理控制LED亮度,以及驱动WS2812B灯条的位级协议。PIO的核心价值在于能够解放CPU,让微控制器以极低的延迟独立处理高速时序任务。如果你需要实现自定义的通信协议(例如DHT11、红外遥控等),PIO无疑是理想的选择。建议从简单的LED闪烁实验开始,逐步挑战更复杂的协议实现,从而深入掌握这一强大工具。

来源:https://m.elecfans.com/article/2311345.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。