单片机/MCU论坛
直播中

HonestQiao

8年用户 545经验值
擅长:嵌入式技术
私信 关注
[文章]

【BPI-Pico-RP2040 开发板】RP2040可编程 IO (PIO) 使用初探

led_breathing

BPI-Pico-RP2040 开发板所搭载的RP2040微处理器,相对于其他微处理,最吸引人的一点就是提供了可编程 IO,简称PIO。

PIO的神奇之处在于,给PIO编程后,它可以自己运行来控制IO口,而不会再占用微处理器的资源,使得同时可以完成其他的逻辑,而相互之间可以完全不干扰。

在官方手册中,PIO单独用了一章,说明其重要性:
image.png

通过官方资料,可以了解到,RP2040提供了两个PIO控制块,每个PIO块中包含4个状态机,都可以通过程序进行控制:
image.png

要使用PIO,可以用官方提供的C-SDK来调用,也可以在MicroPython或者CircuitPython中调用。我在BPI-Pico-RP2040 开发板上使用的是circuitpython,所以下面的实例,都是在CircuitPython进行的。

一、环境配置

开始前,要先设置好CircuitPython开发环境。
我用的开发工具是Thonny,在Thonny中,可以直接给BPI-Pico-RP2040 开发板烧录CircuitPython固件。
按住开发板的BOOT按键,然后重新连接到电脑,就能在Thonny中进行CircuitPython的安装了:
image.png

然后在Thonny的插件管理中,安装adafruit-circuitpython-pioasm和adafruit_blinka:
image.png
image.png

再从Thonny的插件数据目录中,拷贝对应的库到CIRCUITPY U盘中lib目录下即可:
image.png

这样就准备好了PIO运行的环境,可以继续后面的操作了。

二、PIO点灯

因为是初探PIO的使用,而玩板子无不从点灯开始,所以这里也从点灯开始。

为了方便后续的操作,我把BPI-Pico-RP2040 开发板安装到一个扩展板上了:

image.png

从BPI-Pico-RP2040 开发板官方手册得知,BPI-Pico-RP2040 开发板板载了一个可以用户控制的LED,连接到了引脚GPIO-25,那我们就用一段PIO相关的程序,来点亮它。

import board
import rp2pio
import adafruit_pioasm
import time

# 定义PIO控制代码
led_blink = """
.program led_blink
loop:
    pull
    out pins, 1
    jmp loop
"""

# 编译处理
led_blink_assembled = adafruit_pioasm.assemble(led_blink)
# 设置状态机
sm = rp2pio.StateMachine(
    led_blink_assembled,
    frequency=1000_000,
    first_out_pin=board.GP25,
)

while True:
    # 发送数据到状态机
    sm.write(bytes((1,)))
    time.sleep(1)
    
    # 发送数据到状态机
    sm.write(bytes((0,)))
    time.sleep(1)

在BPI-Pico-RP2040 开发板上运行上述代码后,板载的LED就会闪烁起来了:
pio_test1.gif

在上述代码中,led_blink定义的就是PIO控制代码。如果熟悉汇编的话,会发现,这和汇编非常相近。是的,你可以认为他就是RP2040的PIO专属的汇编指令。

设置好了PIO控制代码,再用adafruit_pioasm.assemble()进行编译处理,最后再使用rp2pio.StateMachine()设定好,对哪个GPIO端口启用PIO控制。

而主循环部分,则交替发送1和0到PIO,从而控制LED的亮灭。

三、PIO指令

上述代码中的PIO控制代码部分,我们详细看一下:

.program led_blink
loop:
    pull
    out pins, 1
    jmp loop

控制代码,我们先声明要定义一段程序led_blink,然后设置了一个循环loop,是个死循环,没有任何延时,一直运行。放心,他只是在PIO控制块内运行,对开发板的微处理器没有任何影响。

然后,使用pull指令,把CircuitPython发送给PIO的数据取得,再使用out指令,发送1字节的数据输出给对应的GPIO口,实际上最终就是高低电平信号了。

因为我们在CircuitPython代码中,交替发送了1和0过来,所以,PIO也交替的让GPIO处于高电平和低电平的状态,最终就是LED闪烁了。

PIO的专用指令,一共有9条:
image.png

在这次初探中,使用到了如下的指令:

  • pull:接受32位数据
  • out:输出32位数据到GPIO
  • jmp:跳转
  • set:直接设置GPIO
  • mov:设置数据到目标寄存器
    关于这些指令的详细说明,可以查看官方手册,或者找找网上的资料。

四、PIO控制亮度

说好了PIO可以自己运行来控制IO口,可上面的点灯还是需要Python程序来发送数据点亮,这不科学。

那下面,就用一段程序,来演示PIO自己的控制。

import board
import rp2pio
import adafruit_pioasm
import time

# 定义PIO控制代码
led_brightness = """
.program led_brightness
    set pins, 0
    set pins, 1
"""

# 编译处理
led_brightness_assembled = adafruit_pioasm.assemble(led_brightness)

while True:
    # 设置状态机
    sm = rp2pio.StateMachine(
        led_brightness_assembled,
        frequency=1000_000,
        first_set_pin=board.GP25
    )
    time.sleep(1)
    
    # 释
    sm.deinit()

运行上述程序后,可能会发现,就是LED点亮了,其他没有啥变化。
这个时侯,你可以考虑一下,使用下面的PIO控制代码,看看有什么变化:

常亮:

.program led_brightness
    set pins, 1

长灭:

.program led_brightness
    set pins, 0

亮度:

.program led_brightness
    set pins, 0 [8]
    set pins, 1

最后一段控制代码内,可以尝试修改8为其他值。
你会发现,能控制LED最亮、熄灭,以及不同的亮度。

在上述的PIO控制指令中,set pins, 0表示设置低电平,set pins, 1设置高电平,如果我们用下面的控制指令:

.program led_brightness
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 0
    set pins, 1

就会发现,LED比最亮的时候,按了不少。
因为是汇编指令,所以上面这段控制指令,其结果,就是连续9个执行周期,设置低电平,然后1个执行周期,设置高电平。
如果熟悉PWM的话,你马上知道,这不就是那个味----占空比吗。
是的,使用PIO,可以不用PWM,来控制占空比,从而控制LED的亮度。

上面的代码,相当于是10%的占空比。而一对set pins, 1和一个set pins, 0,则相当于是50%的占空比。可以用多行来设置不同的占空比。
但是写多行,太麻烦了,所以set pins, 0 [8]来帮助了。后面[8]表示执行完set pins, 0后,再经过8个周期,才继续执行后面的指令,也就是相当于9:1,占空比还是10%。

这个部分,最开始的CircuitPython的程序,如果只是控制LED的亮度的话,那么可以不用写入到循环中。
然而,如果我们用两个不同亮度的PIO控制代码,来分别调用的话,就实现了不同亮度之间的变化了。大家可以自己试一试,该怎么实现。

五、呼吸灯

最后,再来一段纯PIO控制实现的呼吸灯,具体代码如下:

import board
import rp2pio
import adafruit_pioasm
import time
import array

# 定义PIO控制代码
led_breathe = """
.program led_breathe
start:
up:
    set x, 31
loop11:
    mov y, x
    set pins, 0 [5]
loop12:
    jmp y-- loop12
    set pins, 1 [5]
    jmp x-- loop11

down:
    set x, 31
loop21:
    mov y, x
    set pins, 1 [5]
loop22:
    jmp y-- loop22
    set pins, 0 [5]
    jmp x-- loop21
end:
    jmp start
"""


# 编译处理
led_breathe_assembled = adafruit_pioasm.assemble(led_breathe)

# 设置状态机
sm = rp2pio.StateMachine(
    led_breathe_assembled,
    frequency=2_000,
    first_set_pin=board.GP25,
    wait_for_txstall=False,
)

while True:
    time.sleep(1)

在上述代码中的PIO控制指令部分,有两个指令说明一下:

  • mov:类似汇编的复制指令
  • jmp:跳转指令,上面的x--y--,表示当其值不为0的时候跳转

PIO控制指令部分,先用y值来控制set pins, 0的执行次数,再用其来控制set pins, 1的执行次数,从而实现了占空比的交替变化,使用x来控制每次变化执行的时长。

执行后,具体效果如下:
led_breathing.gif

六、总结

因为只是初探,所以始于点灯,至于点灯。
RP2040的PIO所能发挥的功能,远远不止上述点灯,如果好好发掘,还会有很多意想不到的用处。

led_blink

更多回帖

发帖
×
20
完善资料,
赚取积分