PIO 控制板载 LED 灯闪烁
以下代码可以在我们提供的资料包中 elegance-devkit v1\Demo\29 PIO_LED 文件夹找到。
我们首先使用 PIO 控制 Pico 的板载 LED 灯闪烁,示例代码如下:
Python
# 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)
这里,我们主要进行以下操作:
- 导入模块:首先导入了必要的模块,包括 machine 用于处理硬件操作,time 用于处理时间相关的任务,以及 rp2 模块,它是 MicroPython 中用于处理 Raspberry Pi Pico RP2040 芯片的特定功能。
- 功能函数: 定义了一个名为
blink的函数,它是一个 PIO 程序,用于控制 LED 灯的闪烁。这个函数使用了@rp2.asm_pio装饰器来定义 PIO 程序,并设置了程序的初始状态、频率和引脚。 - 初始化配置: 创建了一个名为
sm的状态机对象,它加载了之前定义的blink函数,并将其频率设置为 2000Hz,引脚设置为 25 号引脚。 - 主程序: 在主程序部分,首先启动状态机
sm,使其开始执行blink函数,从而控制 LED 灯闪烁。然后等待 5 秒,最后关闭状态机sm,停止 LED 灯的闪烁。
其中,blink 函数是一个 PIO 程序,它被设计成在每次迭代时改变 LED 灯的状态。具体来说,它将输出引脚设置为高电平,保持一段时间(由 nop 指令实现),然后将引脚设置为低电平,再次保持一段时间。这个过程不断重复,形成了 LED 灯的闪烁效果。
状态机 sm 负责执行 blink 函数。当状态机被激活(sm.active(1))时,它会开始执行 blink 函数;当状态机被停用(sm.active(0))时,它会停止执行 blink 函数,频率的设置决定了 LED 灯闪烁的速度。
这里,PIO 程序的头尾为:
Python
wrap_target()
...
wrap() # 程序跳转到wrap_target()处
我们也可以使用 label 标签和 jmp 指令进行替代:
Python
label('loop')
...
jmp('loop') # 程序跳转到loop处
二者区别在于 jmp 指令执行时需要用到 1 个时钟周期的处理时间,而 wrap 不会花费任何时间。
我们通过 set 指令设定引脚电平:
Python
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 个时钟周期。
程序整体流程如下图所示:
烧录程序到 Pico 中,可以看到 Pico 板载的 LED 闪烁 5s 后停止闪烁:

我们将输出引脚修改为 GP28,输入指令如下:
python
sm = rp2.StateMachine(0, blink, freq=2000, set_base=Pin(28))
sm.active(1)
这里,我们使用逻辑分析仪查看输出波形,可以看到波形频率确实为计算的 6.25Hz 左右:
