3.2. 开始使用PIO
3.2.1. 第一个PIO程序
在了解pio汇编语言的所有细节之前,我们需要花点时间看一下这件简短但是完整的应用过程:
1、加载程序到pio的指令内存
2、设置pio状态机运行程序
3、当状态机运行起来之后,与他进行互动
这些操作主要需要已下几个内容:
- 一个 PIO 程序
- 一些 c 程序
- cmake file描述文件,描述这两个部分如何组成一个程序镜像然后加载到RP2040开发板
3.2.1.1 PIO 编程
第一个pio程序,使用pio 汇编语言
.program hello
; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is
; empty. Write the least significant bit to the OUT pin group.
loop:
pull
out pins, 1
jmp loop
pull
指令从传输fifo中获取一个数据,将数据放入到输出移位寄存器中(OSR),每次会移动32位数据从缓冲到输出移位寄存器。输出移位寄存器可以一次移出多个数据,使用out
指令可以实现这个功能
FIFOs?
FIFO 是一个数据缓冲区,每个状态机有2个FIFO,在状态机和系统总线之间,用于数据输入输出到芯片
out
指令从我们刚刚从fifo获取到输出移位寄存器的数据中取出来一个bit,把这个bit的数据写入到某个pin,控制该pin的电平。后面会接收如何配置对应的pin
jmp
指令返回到loop:
标记处,程序马上从新开始执行。故这个程序的功能就是循环从FIFO中获取数据,然后取一个bit输出到引脚
.pio文件包含辅助函数用于设置状态机正确执行一个程序
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`
// 配置使用那个pin作为输出功能
sm_config_set_out_pins(&c, pin, 1);
// 设置这个gpio作pio功能
pio_gpio_init(pio, pin);
// 设置pin的方向时输出方向
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 加载配置,然后跳转到程序开始的地方
pio_sm_init(pio, sm, offset, &c);
// 开始运行状态机
pio_sm_set_enabled(pio, sm, true);
}
这里的主要工作就是设置我们要输出数据的引脚。需要完成以下三件事情:
1、状态机需要指导使用那个gpio作输出功能。这里有四种不同的引脚组,对应不同的指令和不同的情况使用。 现在我们使用输出引脚组,因为我我们只使用了out
指令。
2、GPIO 需要被告知 PIO 正在控制它(选择复用功能)。
3、如果我们只使用一个pin作输出功能,我们需要确保 PIO 配置了引脚的输出方向使能。pio可以控制引脚输入或者输出方向。例如使用 out pindirs
指令,这里我们在启动程序之前进行设置
3.2.1.2. c语言程序
PIO 不会做任何事情直到被配置完成,所以我们需要一些软件去做这个事情。上面编辑的PIO文件,会自动转换为一个头文件,头文件中会包含二进制格式的汇编程序,以及一些辅助函数。c程序中需要包含这个文件 hello.pio.h
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/stdlib.h"
#include "hardware/pio.h"
// Our assembled program:
#include "hello.pio.h"
int main() {
#ifndef PICO_DEFAULT_LED_PIN
#warning pio/hello_pio example requires a board with a regular LED
#else
// 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);
}
#endif
}
可以你已经了解到RP2040包含2个PIO 单元,每个pio单元有四个状态机。每个PIO单元有32条指令空间可以被四个状态机共享。我们需要在状态机运行起来之前把指令加载到指令存储空间。 pio_add_programe()
这个程序寻找空闲的指令内存空间,然后加载过去。
32 条指令
这个可能听上去有点少,但是pio指令集可以非常高效一旦你了解了他的全部特性。一个串口程序可以使用四条指令完成,如
pio/uart_tx
例子。这里同样有一系列方法让状态机从其他来源执行指令,比如可以直接从FIFO中获取,可以查询RP2040 Datasheet。
一旦程序被加载,我们会寻找一个空闲的状态机并且告诉他运行我们的程序。我们还可以控制多个状态机运行同一个程序,也可以不同状态机运行不同的程序。
在这个程序中状态机会立即停下来,因为他会等待TX_FIFO中的数据,但是我们没有提供。处理器可以使用pio_sm_put_blocking()
函数推送数据到TX_FIFO(blocking 是因为如果TX FIFO满了,该指令会阻塞等待),写1会让等亮,写0会让灯灭掉。
3.2.1.3 CMake 文件
我们有2种喜爱的文件在我们的电脑上,分别是.c
.pio
文件,一个cmake file文件描述如何构建一个二进制镜像。
add_executable(hello_pio)
pico_generate_pio_header(hello_pio ${CMAKE_CURRENT_LIST_DIR}/hello.pio)
target_sources(hello_pio PRIVATE hello.c)
target_link_libraries(hello_pio PRIVATE
pico_stdlib
hardware_pio
)
pico_add_extra_outputs(hello_pio)
# add url via pico_set_program_url
example_auto_set_url(hello_pio)
add_executable():
声明我们构建一个叫做 hello_pio 的程序pico_generate_pio_header():
声明我们有一个pio程序,hello.pio我们会构建一个c头文件给我们程序target_sources():
列出hello_pio程序需要的文件,只有c文件target_link_libraries():
确保我们程序可以使用pio hardware api构建,例如调用 pio_add_programe()`程序pico_add_extra_outputs()
默认我们会获取一个elf文件作为输出的app,这里我们声明一个额外的编译格式,比如uf2可以通过usb拖拽文件实现下载文件
确保程序的SDK安装完成之后,运行下面的命令可以编译程序
mkdir build
cd build
cmake ..
make hello_pio
文章截取自树莓派 RP2040 C/C++ 软件编程手册