和大多数编程语言一样(如 Node.js、Java、Python 等),Rust 的实用价值很大一部分来自于其庞大的外部库生态系统。在开始深入 Rust 的世界时,我很快意识到,理解各种依赖关系并将其添加到 Cargo.toml
文件中是一个巨大的学习曲线。
好消息是,随着 embedded-hal
crate 在经历了四年的开发后于 2024 年 1 月 9 日发布了 1.0.0 版本,我们正处于一个激动人心的时刻!紧随其后,Embassy 项目于 1 月 22 日发布了针对主要微控制器系列(Nordic nRF、STM32 和我将在本系列文章中重点介绍的 RP2040)的 crate。
这标志着一个期待已久的时刻到来:标准已经成熟,硬件开发者可以为各种微控制器板和外设创建驱动程序,而不再像以前那样各自为政。
让我举一个例子。我尝试使用的第一个外设是气压传感器 BMP180。我四处寻找任何现有的 Rust crate,发现了一些库,其中一个是 7 年前编写的,没有任何文档或 GitHub 链接;另一个是 6 年前编写的,依赖于 i2cdev 库,而该库是为 Linux 操作系统设计的 i2c 驱动程序。(我假设这是针对功能更全面的 Raspberry Pi 开发板,因为你需要类似的东西才能运行完整的 Linux 操作系统。)
有趣的是,就在几周前我还在琢磨这些东西的时候,我曾想过将编写 BMP180 驱动程序作为我接下来的个人项目之一。在撰写本文时,我发现已经有两个人捷足先登了,一个是在 11 天前发布的,另一个是在 3 天前发布的。所以你看,embedded-hal
1.0.0 版本的最终发布意义重大。
理解 Embedded-HAL 抽象层
接下来,让我们回顾一下什么是 HAL,以及整个抽象结构生态系统。
我发现“嵌入式”微控制器世界真的很有趣,它让人想起早期的 PC 时代,当时所有的计算机都基于相同的 8088、80286、80386 等微处理器,但你可以在其中插入各种图形卡、网卡或声卡,或者在串口上添加打印机或扫描仪。只要你有合适的驱动程序,并且这些驱动程序能够按照一些内部标准工作,一切都能正常运行。
所有现代微控制器都基于 ARM Cortex 家族的某种谱系。RP2040(我们 Raspberry Pi Pico 的核心)是一款双核 32 位 ARM Cortex-M0+ 微控制器,它经过精简,能够以非常低的价格安装在一个微型芯片上。相比之下,Nordic nRF 芯片基于 ARM Cortex-M4 或 M33 架构,STM-M33 则基于 ARM Cortex-M33。它们的 HAL(硬件抽象层)crate 在内部都依赖于 cortex-m
和 cortex-m-rt
crate。它们还提供了 PAC(外设访问 crate)标准的实现,以便设备驱动程序(例如我之前提到的 BMP180 气压传感器)的作者可以编写仅依赖于 embedded-hal
家族部分内容的驱动程序,并简单地假设一个通用的 HAL crate(例如 rp2040-hal
)或一个更具体的开发板 crate(例如 rp-pico
)将传递一个可用的 i2c 总线引用。
事实上,你并不需要真正理解这一切是如何运作的。如果你的开发板有一个 crate,例如 Raspberry Pi Pico 或 Pico W 的 rp-pico
,你应该直接使用该 crate。它将自动包含 rp2040-hal
来处理 RP2040 微控制器内置的组件(i2c、uart、spi,当然还有多用途 GPIO)。除此之外,它还将包含 Raspberry Pi Pico 从 RP2040 构建的特定引脚定义,例如:
另一方面,如果我有一个 SparkFun Pro Micro RP2040(我确实有),我会使用 sparkfun-pro-micro-rp2040
crate,它支持相同 RP2040 的核心功能,但引脚将暴露更紧凑的功能集:
关于 embedded-hal
项目的最后一点说明。五年来,人们一直在使用相当稳定的 0.2.x 系列。四年前,我们终于在今天推出了 1.0.0 系列。有趣的是,在 0.2 系列中,开发者们意识到,他们试图创建一个单一的统一 HAL 层,这过度抽象了问题。反复试验让他们找到了合适的平衡点,使他们能够“开箱即用地在整个 Rust 嵌入式生态系统中将任何驱动程序与任何 HAL crate 一起使用”。
使用 Embassy 进行异步开发
当我开始着手我的第一个项目示例时,我实际上不会直接使用 rp-pico
,因为我是在针对 Project Embassy 进行开发。具体来说,我们将使用 rp-pico
演示最简单的“hello world”应用程序——闪烁的灯光——然后我们将使用 Embassy 重写它。
让我解释一下为什么 Embassy 如此出色。这一切都与异步有关。
当人们第一次编写程序来学习电子学的原理时,他们会做一些简单的任务——闪烁灯光、读取引脚上的电压、从传感器上获取读数。这些都是我使用 Arduino 和简单的 C 程序所做的事情,编写一个基本的同步/阻塞程序似乎完全没问题。但当你想要做任何_有意义_的事情时,你会发现自己在主执行循环中加入了大量的复杂逻辑。
令人惊叹的 RP2040 芯片塞满了大量的东西,这些东西的设计就像一个现代操作系统,可以同时做很多事情。
- 你有一组 GPIO 引脚,可以配置为在电压变化时做出反应。(例如,按下按钮)
- 你有一组通信总线(i2c、spi、uart)与传感器进行通信。
- 你有一组时钟,可以设置在特定时间触发各种活动。
- 有一组非常棒的 PIO 状态机,可以模拟复杂的通信协议。
- 你可能有一个网络设备(比如 Pico W 内置的 CYW43),它正在发送和/或接收指令和遥测数据。
所有这些事情都可能同时发生。其中很多操作都可能是阻塞操作。如果你考虑一个中等规模的物联网项目,你的微控制器——以及你为它编写的应用程序——将同时协调许多任务。这几乎就像编写一个完整的操作系统来管理各种进程和资源。
以下是采用 Embassy 等异步执行器的另一个动机:你的 RP2040 微控制器有两个核心!年轻的读者可能会对此嗤之以鼻,但我仍然记得在 90 年代,当 Digital 的 Alpha 处理器(当时是英特尔奔腾处理器的一个略显冷门但仍然非常酷的 PC 替代品)首次涉足 64 位计算和双处理器并行计算时的情景!
我的观点是:如果你有多个核心,为什么要编写只能使用一半计算能力的单线程程序?你可能会认为编写一个程序,在其中生成第二个线程来执行一些特定任务并在结果上进行同步并不难,但即使是这样也需要大量的计划和协调。
更好的做法是采用一种完全不同的范式。拥抱异步,让它改变你的大脑对问题的建模方式。毕竟,我们踏上 Rust 学习之旅是为了拓展思维,成为更好的开发者,对吧?
准备你的购物清单
是时候换挡了。如果你想跟随我的例子,现在就去购物吧!我将把硬件保持在最低限度。我将提供一些我最喜欢的在线数字商店的链接:Adafruit。它的创始人,麻省理工学院的工程师 Limor “Ladyada” Fried,激励了许多想进入电子工程领域的年轻人。她的商店是一个可以一次性订购大量微型电子元件的好地方,而且他们在易于使用的传感器板方面做了很多很酷的工程设计。另一个值得探索(并购买一些产品)的选择是 SparkFun。还有其他的。我想我要强调的是,电子设备和小工具的销售商和生产商组成了一个很棒的生态系统,值得探索和光顾,而不仅仅是从亚马逊上购买普通的元件。
好了!话虽如此,让我们开始吧!
Raspberry Pi Pico W 和 Raspberry Pi Pico 或 Raspberry Pi Debug Probe
这听起来可能很奇怪,但你需要_两块_基于 RP2040 的 Raspberry Pi 开发板。相信我,这听起来我也觉得很奇怪,但只需要额外花费 5 美元或 12 美元,你就应该这么做,事实上,如果没有这两块开发板,你将无法完成工作。
RP2040 的有趣之处在于,芯片中有三根特殊的线,用于对其进行编程和调试。是的,你获得了一种硬件支持的机制,可以停止芯片运行,逐步执行指令,读取变量的值,所有这些都可以在 Visual Studio Code IDE 中轻松完成!哇!
直到大约一年前(2023 年 2 月),搭建开发环境的方法是使用一块专用的 Raspberry Pi Pico 作为“调试控制器”。
为了使工作正常进行,你需要在调试控制器上烧录一个名为 picoprobe.uf2
的特殊应用程序(固件),通过 USB 将其插入笔记本电脑,并将其连接到你正在开发的开发板中间的一些特殊接头。
从 2023 年 2 月开始,Raspberry Pi Ltd 提供了一种更小、更紧凑的“调试探针”,专门用于此目的,从而简化了操作。你无需购买售价 5 美元的 Pico 并将其投入使用,而是可以购买售价 12 美元的调试探针(它配有一些精美的包装和实用的电缆!),让你的生活更轻松。
所以需要明确的是,你至少需要一块 Raspberry Pi Pico W,以及一块普通的 Raspberry Pi Pico(如果它只是作为调试控制器,则不需要 WiFi)或 Raspberry Pi Debug Probe。就我个人而言,我认为调试探针值得购买,因为如果你像我在上图中那样将两块开发板都放在面包板上,那么会浪费很多空间。
排针
在订购电子元件时,你需要注意它是预先焊接了排针,还是需要你自己焊接(通常还需要自己提供)排针。
如果你购买的 Pico 预先焊接了排针(Pico H 和 Pico W H)(正如我建议的那样),那么你的调试/引导加载连接将通过 JST 插座连接,如上图左侧箭头所示。如果是这种情况,你将需要购买 这款 JST SH 兼容 3 针转优质公头电缆 来插入其中。
相反,如果你购买的 Pico_没有_预先焊接排针(分别是 Pico 和 Pico W,名称中没有“H”),那么你需要将排针焊接在侧面(朝下指向面包板),并在顶部焊接一个小的 3 针排针,用于调试连接。如果是这种情况,你就不需要 JST 插座 3 针连接器。你只需使用自己的跳线连接即可。我的设置如下图所示。
不要太担心接线:它并不像看起来那么复杂。
面包板和跳线
如上图所示,你将使用面包板将你的项目连接在一起。面包板是一个非常酷的发明,它通过将跳线(只有两端裸露的绝缘线)插入特定的孔中,使连接元件变得快速而简单。如上图所示,我的两块 Pico 占用了大约 2/3 的全尺寸面包板,所以购买我之前提到的调试探针可能是个好主意。(它不会占用任何行。)
你还需要一些跳线。你可以考虑购买 这款半尺寸面包板 + 78 件跳线 套装,或者只购买几套便宜的跳线,比如 这个页面 上的跳线。(我有一些公对公和公对母跳线。)
可选:Raspberry Pi Zero 2 W
正如我在本系列的第一篇文章中提到的,我喜欢在家中运行一台完整的 Raspberry Pi 作为临时服务器。在本系列文章中,我将使用 Raspberry Pi Zero 2 W(仅售 15 美元,不包括外壳和 miniSD 卡)作为服务器,我的嵌入式项目将通过 Pico W 内置的 WiFi 向其发送数据。
我将来可能会写一篇关于传统 Raspberry Pi 的文章,但使用这些设备更多的是安装 Linux 操作系统,而不是与 Rust 或嵌入式微控制器相关的任何事情,所以我要说这超出了本博客系列的范围。
首先,我将展示一些简单的(不到 20 行)Python 脚本,它们可以充当临时服务器,我们的项目将通过 Pico W 内置的 WiFi 向其发送遥测数据。关键是,为了学习,你应该能够像在 Linux 服务器上一样轻松地在笔记本电脑上运行它。Python 绝不是我最喜欢的编程语言,但它的简单性和普遍性使其非常适合这个目的。(我将来可能会尝试写一篇教程,用 Rust 编写服务器应用程序。)
LED 和电阻
正如我之前提到的,嵌入式版本的“hello world”涉及到闪烁 LED 灯。为此,你需要一个 LED 和一个电阻。现在,这些元件非常便宜和基本,你不会找到任何人单独出售它们。我的建议是,要么购买 Adafruit 的 这款 基本电子入门套件,其中包含一些 LED、电阻、二极管和晶体管等,要么购买 混合包装的 LED 和一套电阻。对于电阻,你可以购买 25 个 220 欧姆电阻的简单包装,这对 LED 来说是一个很好的通用范围,价格仅为 75 美分,或者,如果你想满足你对电阻的可预见需求,SparkFun 提供了 这款很酷的套件,其中包含各种规格的电阻。
传感器
可以说,微控制器的意义在于通过电子设备与世界互动。我们以传感器的形式获取输入,我们的输出可以是通过伺服电机发出光、声音或移动物体的形式。在我们完成简单的 LED 示例之后,我将转到两个传感器示例,一个示例通过 I2C 总线获取数字传感器信息,另一个示例使用模拟引脚输入以电压的形式读取值。
我的第一个 Rust I2C 项目使用的是一个旧的 BMP180 气压传感器,它已经停产了,但我发现了一个升级版的 BMP390,它有一个很好的特性,即它有一个与 SparkFun Qwiic I2C 连接器兼容的插头。这意味着,如果你还没有准备好自己焊接排针,你可以(也应该)购买 这款 Qwiic 电缆 将传感器插入面包板。
另一个流行的传感器是 MCP9808 温度传感器,所以在我的传感器博客中,我将展示使用这两种传感器的代码。如果你愿意,你可以选择其中一个或两个都选择。如果你购买的两个传感器都带有 Qwiic 连接器,你应该购买 这款电缆 将它们方便地菊花链连接在一起!
对于模拟项目,我们将使用一个简单的 TMP36 传感器,它以简单电压输出的形式输出温度读数。
就是这样!你唯一需要考虑购买的其他购物项目是一根 microUSB 电缆,用于插入你的 Pico,如果你手边还没有的话。(如果你购买 Pico Debug Control 套件,它将附带一根电缆。)