文章目录
写在前面
该系列文章旨在记录树莓派pico与RP2040的学习记录,将包括编程环境的搭建,API的说明解释,更多应用等。希望能同大家多交流交流。
月底我要考研,可能下一篇文章得等到一月了,很抱歉😢在职考研压力挺大的,希望多多理解,但应该没什么人看我的文章吧(笑)。
如果您觉得文章对您有帮助就点个赞吧😀
前言
本文主要是对官方文档的翻译,以及自己的一些理解。
第一章 关于树莓派pico的SDK
1.1 简介
树莓派SDK(Software Development Kit,即软件开发工具包)提供了头文件、库、以及编程控制以RP2040为核心的设备(例如树莓派pico)所需的构建系统。
该SDK旨在提供API(Application Programming Interface,即封装好的函数),以及适用于爱好者与专业开发人员的编程环境。程序通过传统的main()函数在设备上运行。标准C/C++库支持与API一同访问RP2040的硬件,包括DMA(Direct Memory Access,即直接存储器访问),IRQs(Interupt ReQuest,即中断请求),以及各类固定功能接口与PIO(Programmable IO,即可编程IO)。
另外,该SDK还提供了更高级的库用以处理定时器,USB,同步与多核编程,以及用PIO构建的其他高级功能,例如音频。这些库应该足够全面使得用户代码不太需要直接访问硬件寄存器。但是,如果用户确实需要访问寄存器,用户亦可在该SDK中找到全面且注释完善的寄存器定义头文件。用户不必在芯片手册查找寄存器地址。
总结:树莓派SDK有丰富的库,必要时可以直接访问寄存器。
1.2 简析SDK应用
1.2.1 C程序简析
在全面阅读该SDK前,可以先简单看看该SDK提供的一个示例。
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/stdlib.h"
int main() {
#ifndef PICO_DEFAULT_LED_PIN
#warning blink example requires a board with a regular LED
#else
const uint LED_PIN = PICO_DEFAULT_LED_PIN;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
while (true) {
gpio_put(LED_PIN, 1);
sleep_ms(250);
gpio_put(LED_PIN, 0);
sleep_ms(250);
}
#endif
}
该程序仅有一个C文件,仅有一个函数。在几乎所有C语言编程环境中,main()是特殊的,在初始化变量后该函数是用户程序的入口。如上面的代码所示,在该SDK中main()函数不包含参数,也没有返回(return)。
在C文件的顶部,有一个叫pico/stdlib.h的头文件,这个头文件包含了其他常用的头文件。在该示例中,这个头文件包含了hardware/gpio.h,这个头文件用于访问RP2040的通用IO(即GPIO),上面程序中的gpio_put()等以gpio开头的函数就包含于hardware/gpio.h;还包含了pico/time.h,上面程序中的sleep_ms()就包含于pico/time.h。一般来说,以pico开头的库(例如pico/time.h)提供了高级API,或者集合了更小的接口;以hardware开头的库(例如hardware/gpio.h)表明用户代码与RP2040的片上硬件更加接近。
上面最后一句话我是这么理解的:
- 以
pico开头的库,例如pico/time.h,提供了sleep_ms(),这个库显然是用于控制定时器的,但官方已经对定时器做好了更好的封装以降低编程难度。学过51单片机的同学应该还记得,51单片机的定时器有多种工作方式,例如自动重装载等,使用起来还是有一些难度,所以官方提前根据定时器的常见用途写好了相关的函数,例如sleep_ms(),也就是所谓的高级API;- 以
hardware开头的库,例如hardware/gpio.h,这个库显然是用于控制GPIO的。学过51、STM32或其他单片机的同学应该都知道,类似与控制GPIO的操作其实是比较简单的,没有必要像定时器那样进行更多的封装,也就是我们可以更直接地进行控制GPIO等硬件,所以说是用户代码与RP2040的片上硬件更加接近。
所以,主要使用hardware/gpio.h库与pico/time.h库,该程序即可实现一个LED的反复亮灭。
该程序使用到的LED连接在树莓派pico上的GPIO25端口,在购买的RP2040开发板的正面或背面一般都有写,一般为GPIOxx,或GPxx,或IOxx,xx表示端口号,例如在该程序中端口号为25。如下图所示,上面的程序中,当运行语句
gpio_put(LED_PIN, 1)GPIO25输出高电平,LED点亮;当运行语句gpio_put(LED_PIN, 0)GPIO25输出高电平,LED熄灭。事实上,您可以根据手中的板子,更改LED_PIN为其他端口,例如更改为gpio_put(24, 0),注意:其他语句中的LED_PIN也需要更改。
1.2.2 CMake简析
在项目文件夹中,您或许还发现了一个名为CMakeLists.txt的文件,这个文件将告知系统要如何将C文件转换为可以烧录到RP2040芯片的二进制文件。稍后我们将详细说明什么时CMake,以及为什么要用CMake。现在我们可以简单浏览这个CMakeLists.txt而无需过度关注其中的细节。
add_executable(blink
blink.c
)
# pull in common dependencies
target_link_libraries(blink pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(blink)
# add url via pico_set_program_url
example_auto_set_url(blink)
在上面的程序中,add_executable函数声明了一个名为blink的程序,该程序将从更前面介绍的C程序构建。这也是用于构建程序的目标名称:在pico-examples仓库中,您可以在build文件夹中使用make blink命令来构建。您可以在一个文件夹中存放多个可执行文件,pico-examples仓库便是这样一个文件。
target_link_libraries将从SDK中拉去所需的程序。如果您不需要任何一个库,它将不会出现在您的程序二进制文件中。程序中,pico_stdlib包含了pico/time.h与hardware/gpio.h。如果您需要其他库,例如DMA,您可以在pico_stdlib的前面或后面加上hardware_dma。
到此,CMake文件的说明本可以结束,以上这些已经足以构建blink程序。默认情况下,该构建会产生一个ELF文件,它将包含您的代码以及SDK中所需的库。您可以将这个ELF文件通过串行调试端口与类似与gdb或openocd的调试器加载到RP2040的RAM或外置的flash。但我们一般是使用另一种更简单的方法,即使用USB接口,而这需要另一种名为UF2的文件,其作用与ELF一样,但前者更方便传输。pico_add_extra_outputs函数将告知系统需要生成UF2文件,还会产生一些额外的输出。
example_auto_set_url函数是关于如何阅读源文件,点击链接可以跳转到GitHub。
实际上该函数作用并不是很大。
总结:个人认为,在刚开始学习与使用SDK时不需要过多了解
CMakeLists.txt的内容,知道该文件是用于构建程序的即可。构建项目文件可以通过pico-project-generator构建,有图形化界面;构建用于下载的二进制文件可以通过make进行构建,也可以使用vscode或CLion。
第二章 SDK整体结构
RP2040是一款性能强大的芯片,其RAM容量高达264KB。但由于它是微控制器,所以仍需要权衡性能与其他因素。
SDK旨在使开发变得更为简单,同时给予开发者尽可能多的控制权以更好地调整自己的程序。
接下来的部分将说明SDK设计背后的一些决策:如何(HOW)、为什么(WHY)以及什么(WHAT)。
该部分可能有些难度,您可以先简单浏览本部分,在编写一些程序以及熟悉SDK后再彻底阅读。
2.1 构建系统
SDK使用CMake进行构建。CMake广泛支持各类IDE(Integrated Development Environments,即vscode,CLion等),它使用CMakeLists.txt来寻找源文件并生成代码自动补全建议。CMakeLists.txt文件提供项目如何构建的简洁说明,CMake使用make,ninja或其他构建工具来生成一个可靠的构建系统。生成的构建系统是为Windows或Linux平台以及开发人员选择的任何配置而量身打造的。
2.2 每一个库都是一个接口( INTERFACE)
每个接口库源自依赖关系树,这包括每个源文件、包含路径、编译器设置以及编译构建选项。SDK内的所有库都是接口库,我们简单地引用它们。

总结:第二章主要是讲SDK的架构,个人认为对SDK使用的帮助不是很大,所以很多都一笔带过了。
第三章 可编程IO(PIO,Programmable I/O)的使用
3.1 PIO简介
PIO是一种为RP2040设计的新型硬件,能使你在RP2040上制作新型硬件接口。如果您认为微控制器上的固定外围设备不足以满足您的需求,例如:“我想添加四个串口”、或“我想输出DPI视频”、甚至”我需要与串行设备通信,但我没有硬件支持“,那您应该看看这一章。

本章聚焦如何使用PIO、何时使用PIO、为何使用PIO。在开始之前,我们应当先了解为什么IO接口如此困难,现有处理方法是什么,以及PIO有何不同之处。
与其他数字硬件组件通信是比较困难的,主要是由于有大量数据需要传输,以及对时钟同步的高要求。一般地,这需要您的电脑有高速USB接口,HDMI输出口,PCle插槽,SATA驱动控制器等能够处理复杂且对时钟敏感的数据收发任务的硬件设备,以及能够以最小延迟进行响应。一般情况下,这些接口都是定制的,FPGA可以自行定制接口,但学习难度较大。
| 接口速度 | 接口 |
|---|---|
| 1-10Hz | 按钮,LED |
| 300Hz | HDMI |
| 10-100kHz | DHT11,单线通信 |
| <100kHz | I2C |
| 22-100+kHz | PCM audio |
| 300+kHz | PWM audio |
| 400-1200kHz | WS2812 LED |
| 10-3000kHz | 串口 |
| 12MHz | 全速USB |
| 1-100MHz | SPI |
| 20-300MHz | DPI/VGA 视频 |
| 480MHz | 高速USB |
| 10-4000MHz | 以太网 |
| 12-4000MHz | SD卡 |
| 250-20000MHz | HDMI/DVI视频 |
RP2040上的PIO子系统允许您为PIO状态机编写小而简单的程序。一个状态机负责设置与读取一个或多个GPIO,缓存来自或送往处理器(或者是RO2040的超快DMA系统)的数据,并在必要时通过中断或轮询告知处理器。这些程序可以高速运行(高达系统时钟速度),也可以较慢速度运行。
PIO状态机比RP2040上的通用Cortex-M0+处理器小得多。事实上,它们跟一个标准的SPI外围设备差不多大。一共有八个状态机,其指令集是小型而有规律的。尽管它们比较小,但是在一个周期内一个状态机能做的事比Cortex-M0+处理器多得多。有得必有失,PIO状态机无法运行通用软件。对PIO状态机编程,这对有编写汇编代码经验的人来说会很熟悉,而小型的指令集也同样能使没有相关经验的人快速上手。
对于简单的硬件协议,例如PWM或SPI,一个PIO状态机即可完成处理实现硬件接口的任务;对于SDIO或DPI等协议,则需要两到三个。PIO适合用于需要GPIO反复读取或写入的任务。PIO可以用C++ SDK写,也可以用MicroPython。
3.2 第一个PIO应用
在深入了解PIO汇编语言之前,我们先来看看一个小而完备的示例吧!示例地址
该示例将完成:
-
将一个程序装载到PIO指令内存中;
-
启动PIO状态机以运行程序;
-
在程序运行后与之交互。
该示例包括:
- 一个PIO程序
- 一些由C语言编写的程序
- 一个
CMake文件,它将说明以上两者是如何烧录到RP2040芯片的
3.2.1 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缓冲区提取一个数据,并将其放到输出移位寄存器output shift register(OSR)。数据每次从FIFO移动一个字(32bits)到OSR。OSR通过指令out一次能将多个数据移位输出到更远的目的地。这里,out每次移动1比特(1 bit)。
指令jmp跳转回loop,使得程序可以无限循环。
总的来说,这个程序反复从FIFO提取一个数据,并提取这个数据中的一个比特,然后将其写入一个引脚。
在文件中,还包括了一个帮助函数,以设置一个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`
// 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);
}
除了以上程序,我们还需要写一个C程序来启动。
3.2.2 C程序
pio_sm_put_blocking()的作用是向FIFO放数据。
/**
* 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
}
3.2.3 CMake程序
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()声明我们有一个名为hello.pio的PIO程序,我们希望它内置于C头文件中,以便它能够与我们的程序一起使用。
target_sources()列出程序的源文件,在该示例中仅有一个C程序文件
target_link_libraries()列出所用的库,确保我们的程序与PIO的API一起构建(因为使用了PIO)
pico_add_extra_outputs()构建烧录到芯片的UF2文件
最后使用以下命令来构建:
mkdir build
cd build
cmake ..
make hello_pio
参考文献
[1]RP2040说明书1
[2]RP2040说明书2
[3]开始使用树莓派pico
[4]RP2040 SDK

&spm=1001.2101.3001.5002&articleId=128222110&d=1&t=3&u=5819e0dd3f9f4f5dbb6d0be2c561195a)
1749

被折叠的 条评论
为什么被折叠?



