由于RP2040微处理器驱动树莓派Pico开发板的普及,使得很多人都想了解更多关于该板和芯片的信息。因此,今天我们将讨论RP2040的可编程IO,这一特性使其与其他大多数微控制器板不同。
RP2040中的两个PIO(PIO模式是一种通过CPU执行I/O端口指令来进行数据的读写的数据交换模式)块或我们称之为硬件接口都有四个状态机(由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心)。这两个PIO块可以同时执行程序来操纵GPIO并传输原始数据。
现在,这些状态机做什么?
PIO状态机执行从各种来源获取的程序。有时程序取自PIO库(UART,SPI或I2C)或用户软件。

为什么要使用可编程I / O?
通常,所有开发板都带有对 I2C、SPI 和 UART 等数字通信协议的硬件支持。但是,如果你计划使用的这些接口多于开发板可用的接口,你就可以使用RP2040微处理器中提供的可编程IO。
它的功能超乎人们的想象。假设你想输出一个DPI视频或“与全球速卖通上找到的串行设备通信”现在可以通过可编程I/O实现。顾名思义,“可编程”IO表明它可以直接编程支持SD卡接口、VGA输出和更高的速度数据传输在内的接口。
接下来是文章中最激动人心的部分——“如何对这些可编程I/O进行编程以简化你的工作”。
如何开始RP2040 PIO编程?
Pico SDK(软件开发工具包)提供了在C、C ++或Arm汇编语言中为基于RP2040的设备(如树莓派 Pico)编写程序所必需的头文件、库和构建系统。
如果你打算使用Python进行编码,则只需要在开发板上安装合适的编辑器(如Thonny)和MicroPython。但是对于C / C ++,您则需要CMake文件来告诉Pico SDK如何将C文件转换为基于RP2040的微处理器开发板的二进制应用程序,正如我们最近在树莓派 Pico的MicroPython和C教程中解释的那样。
PIO汇编器解析PIO源文件并输出组装后的包含在RP2040应用程序中的汇编版本。这包括基于Pico SDK构建的C和C ++应用程序以及在RP2040 MicroPython端口上运行的Python程序。
为开始给你的PIO应用程序编写状态机,有三个基于C / C ++程序的组件。
- 一个PIO程序。
- 基于C语言的软件运行显示。
- 一个CMake文件,描述了如何将二者组合成程序映像以加载到基于RP2040的开发板上。
PIO汇编指令
当对这些IO接口进行编程时,有9条汇编指令“ JMP、WAIT、IN、OUT、PUSH、PULL、MOV、IRQ和SET”。虽然大多数人可能对使用C / C ++或Python语言对PIO接口进行编程感兴趣,但让我们看看用于IO接口的一些汇编语言指令。
- JMP:此“跳转”指令可以是条件语句或非条件语句。在此,它通过更改指令指针寄存器来传输执行流程。简而言之,使用“ jmp”语句,执行流程进入代码的另一部分。
- WAIT:该指令会暂停代码的执行。每条指令需要一个周期,除非被停止(使用WAIT指令)。
- OUT:该指令将数据从输出移位寄存器移至其他目标,每次1…32位。
- PULL:该指令将32位字从TX FIFO传输到输出移位寄存器中。
- IN:该指令一次将1…32位移入寄存器。
- PUSH:该指令用于将ISR内容写入RX FIFO。

有关汇编语言说明的更多信息,请参见RP2040数据表。
C / C ++和MicroPython中的RP2040 PIO编程示例
为了简化操作,我们将研究hello_world程序,该程序使用可编程IO和TX FIFO的32位数据(PULL指令)使板载LED闪烁。
C / C ++中的程序如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include "pico/stdlib.h" #include "hardware/pio.h" // Our assembled program: #include "hello.pio.h" int main() { // Choose which PIO instance to use (there are two instances) PIO pio = pio0; // Our assembled program needs to be loaded into this PIO's instruction // memory. This SDK function will find a location (offset) in the // instruction memory where there is enough space for our program. We need // to remember this location! uint offset = pio_add_program(pio, &hello_program); // Find a free state machine on our chosen PIO (erroring if there are // none). Configure it to run our program, and start it, using the // helper function we included in our .pio file. uint sm = pio_claim_unused_sm(pio, true); hello_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN); // The state machine is now running. Any value we push to its TX FIFO will // appear on the LED pin. while (true) { // Blink pio_sm_put_blocking(pio, sm, 1); sleep_ms(500); // Blonk pio_sm_put_blocking(pio, sm, 0); sleep_ms(500); } } |
上述的C / C ++代码以1秒的完整周期闪烁LED。对LED进行编程的方式是:先点亮500毫秒,然后熄灭500毫秒。但是,在状态机可以运行程序之前,我们需要将程序加载到该指令存储器中。“函数pio_add_program()在给定的PIO的指令存储器中查找程序的可用空间,并将其加载。” 这样,我们将状态机配置为将其数据输出到板载LED。
如下所示的.pio文件的汇编代码包含了设置C / C ++代码的所有C助手函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
.program hello loop: pull out pins, 1 jmp loop % c-sdk { static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) { pio_sm_config c = hello_program_get_default_config(offset); // Map the state machine's OUT pin group to one pin, namely the `pin` // parameter to this function. sm_config_set_out_pins(&c, pin, 1); // Set this pin's GPIO function (connect PIO to the pad) pio_gpio_init(pio, pin); // Set the pin direction to output at the PIO pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); // Load our configuration, and jump to the start of the program pio_sm_init(pio, sm, offset, &c); // Set the state machine running pio_sm_set_enabled(pio, sm, true); } %} |
除此以外,你还需要一个CMake文件来描述如何将.pio和.c文件构建成二进制文件,以便加载到你的树莓派Pico开发板上。
在没有使用MicroPython编写相同示例的基础上,我们可以看到一个更简单的、用于板载LED闪烁的PIO MicroPython代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import time from rp2 import PIO, asm_pio from machine import Pin # Define the blink program. It has one GPIO to bind to on the set instruction, which is an output pin. # Use lots of delays to make the blinking visible by eye. @asm_pio(set_init=rp2.PIO.OUT_LOW) def blink(): wrap_target() set(pins, 1) [31] nop() [31] nop() [31] nop() [31] nop() [31] set(pins, 0) [31] nop() [31] nop() [31] nop() [31] nop() [31] wrap() # Instantiate a state machine with the blink program, at 1000Hz, with set bound to Pin(25) (LED on the rp2 board) sm = rp2.StateMachine(0, blink, freq=1000, set_base=Pin(25)) # Run the state machine for 3 seconds. The LED should blink. sm.active(1) time.sleep(3) sm.active(0) |
在这个示例中没有单独的.pio文件,且MicroPython和汇编代码都放置在.py文件中。
值得注意的是,虽然可以使用MicroPython对PIO进行编程,但Python SDK文档也指出它目前不稳定/正在开发中,因此建议使用C / C ++。
通过在RGB十六进制格式的帮助下添加要显示的颜色,可以对代码进行许多修改。然而,有很多现实生活中的示例,如PWM、UART甚至与NeoPixels的接口。对感兴趣的人而言,便可以在GitHub的C和MicroPython示例库中找到许多PIO编程示例。
结论
RP2040可编程IO能够同时执行程序以支持VGA输出和高速数据传输等接口。你可以查看C / C ++和Python的SDK文档中的第3章,以了解有关RP2040可编程IO的更多信息。

文章翻译者:Nicholas,技术支持工程师、瑞科慧联(RAK)高级工程师,深耕嵌入式开发技术、物联网行业多年,拥有丰富的行业经验和新颖独到的眼光!