PIO 入门:手把手实现板载 LED 闪烁,原理与代码全解析

PIO 控制板载 LED 灯闪烁

以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\29 PIO_LED 文件夹找到。

我们首先使用 PIO 控制 Pico 的板载 LED 灯闪烁,示例代码如下:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/7/27 下午11:48   
# @Author  : 李清水            
# @File    : main.py       
# @Description : 通过PIO控制板载LED灯闪烁

# ======================================== 导入相关模块 ========================================

# 导入硬件相关的模块
from machine import Pin
# 导入时间相关的模块
import time
# 导入RP2040相关的模块
import rp2

# ======================================== 全局变量 ============================================

# ======================================== 功能函数 ============================================

# 定义一个 PIO 程序,将输出引脚初始化为低电平
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink() -> None:
    """
​    定义一个闪烁LED灯的PIO程序。

​    Args:
​        None

​    Returns:
​        None

​    Description:
​        该PIO程序通过设置引脚的高低电平来控制LED灯的闪烁。
​        高电平和低电平各持续160个时钟周期,总周期为320个时钟周期。
​    """
​    ​# 标记程序的起始位置
    wrap_target()
    # 将输出引脚设置为高电平,并延迟 31 个时钟周期
    set(pins, 1)   [31]
    # 以下nop指令无操作,并延迟 31 个时钟周期
    nop()          [31]
    nop()          [31]
    nop()          [31]
    nop()          [31]
    # 将输出引脚设置为低电平,并延迟 31 个时钟周期
    set(pins, 0)   [31]
    # 以下nop指令无操作,并延迟 31 个时钟周期
    nop()          [31]
    nop()          [31]
    nop()          [31]
    nop()          [31]
    # 程序跳转到wrap_target()处
    wrap()

# ======================================== 自定义类 ============================================

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试信息
print("FreakStudio : Use PIO to control onboard LED")
# 创建状态机0,加载blink()程序,设置PIO执行频率为 系统时钟/2000 Hz,设置引脚为25号引脚
sm = rp2.StateMachine(0, blink, freq=2000, set_base=Pin(25))

# ========================================  主程序  ===========================================

# 启动状态机0
sm.active(1)
# 延时5s
time.sleep(5)
# 关闭状态机0
sm.active(0)

这里,我们主要进行以下操作:

  1. ​导入模块:​首先导入了必要的模块,包括 machine 用于处理硬件操作,time 用于处理时间相关的任务,以及 rp2 模块,它是 MicroPython 中用于处理 Raspberry Pi Pico RP2040 芯片的特定功能。
  2. ​功能函数:​定义了一个名为 blink 的函数,它是一个 PIO 程序,用于控制 LED 灯的闪烁。这个函数使用了 @rp2.asm_pio 装饰器来定义 PIO 程序,并设置了程序的初始状态、频率和引脚。
  3. ​初始化配置:​创建了一个名为 sm 的状态机对象,它加载了之前定义的 blink 函数,并将其频率设置为 2000Hz,引脚设置为 25 号引脚。
  4. ​主程序:​在主程序部分,首先启动状态机 sm,使其开始执行 blink 函数,从而控制 LED 灯闪烁。然后等待 5 秒,最后关闭状态机 sm,停止 LED 灯的闪烁。

其中,blink 函数是一个 PIO 程序,它被设计成在每次迭代时改变 LED 灯的状态。具体来说,它将输出引脚设置为高电平,保持一段时间(由 nop 指令实现),然后将引脚设置为低电平,再次保持一段时间。这个过程不断重复,形成了 LED 灯的闪烁效果。

状态机 sm 负责执行 blink 函数。当状态机被激活(sm.active(1))时,它会开始执行 blink 函数;当状态机被停用(sm.active(0))时,它会停止执行 blink 函数,频率的设置决定了 LED 灯闪烁的速度。

这里,PIO 程序的头尾为:

wrap_target()
...
wrap()  # 程序跳转到wrap_target()处

我们也可以使用 label 标签和 jmp 指令进行替代:

label('loop')
...
jmp('loop') # 程序跳转到loop处

二者区别在于 jmp 指令执行时需要用到 1 个时钟周期的处理时间,而 wrap 不会花费任何时间。

我们通过 set 指令设定引脚电平:

set(pins, 1)  # 高电平
set(pins, 0)  # 低电平

这里,pins 是 pioasm 会自动解读的一个名称,代表 set 指令要写入的引脚,这我们通过 rp2.StateMachine() 的 set_base 参数来指定 pins 的基引脚(pins 为多个引脚的元组,set_base 为 pins 首个元素​),也就是 GPIO25。

其中每个完整的闪烁周期由 320 个时钟周期组成(高电平和低电平各占 160 个时钟周期),而状态机的频率为 2000 Hz,因此每个周期耗时 0.16 秒,对应 LED 闪烁频率为 6.25 Hz:

  • set(pins, 1)​/​set(pins, 0)​指令:​指令本身消耗 1 个时钟周期,延迟 31 个时钟周期,总共消耗 32 个时钟周期。
  • nop()​指令:​每个 nop() 消耗 1 个时钟周期,加上延迟 31 个时钟周期,每个 nop() 总共消耗 32 个时钟周期,4 个 nop()总共消耗 128 个时钟周期。
  • ​高电平部分:​set(pins, 1) 4 个 nop() = 32 + 128 = 160 个时钟周期。
  • ​低电平部分:​set(pins, 0) 4 个 nop() = 32 + 128 = 160 个时钟周期。

程序整体流程如下图所示:

8ef9500b-24c3-4e9e-b1cc-c8c7fd530426.png

烧录程序到 Pico 中,可以看到 Pico 板载的 LED 闪烁 5s 后停止闪烁:

mmexport1780913672531.gif

我们将输出引脚修改为 GP28,输入指令如下:

sm = rp2.StateMachine(0, blink, freq=2000, set_base=Pin(28))
sm.active(1)

这里,我们使用逻辑分析仪查看输出波形,可以看到波形频率确实为计算的 6.25Hz 左右:

819e5d1c-0a3a-4042-a214-72300008089d.png

posted @ 2026-09-01 15:45  FreakStudio  阅读(3)  评论(0)    收藏  举报