如何在 Arduino Uno 上运行 Rust
最初发布于:creativcoder.dev
大约一个月前,在我撰写本文时,rust-avr 分支已合并到上游。这意味着您现在只需运行,并为您的目标cargo +nightly build
提供一个( ) ,即可为 avr 微控制器板编译 Rust 程序。这太棒了!.cargo/config.toml
avr-unknown-unknown
我一直对用代码操纵和影响物理对象的想法很着迷。这一系列博文可能会记录我在 Arduino 上使用 Rust 的冒险经历,将来也可能会记录 ESP8266 和 Discovery F3 的冒险经历。(这些我也有。)以这篇第一篇博文开启这个系列。
目标读者:本文面向初学者到中级水平的读者,他们希望通过 Arduino 轻松掌握 Rust 嵌入式系统。阅读完本文后,您可能想阅读《嵌入式 Rust 基础知识》一书。文中代码在 Linux 机器(Arch Linux)上使用 Rust 编译器版本编译rustc 1.47.0-nightly (22ee68dc5 2020-08-05)
。
我们将快速讲解如何在 Arduino Uno 上运行 Rust,Arduino Uno 可能是嵌入式领域最知名、使用最广泛的开发板。Arduino Uno基于 ATmega328P,这是一款属于 AVR 系列的 8 位微控制器。AVR 是由 Atmel 公司于 1996 年开始开发的微控制器系列,后来被 Microchip 科技公司收购。如果您想了解更多信息,请访问:AVR-Rust 一书
抛开这段简短的历史,让我们开始吧!
我们将演示Arduino的Hello World,它能控制LED闪烁。这是一个非常简单的练习,但对于初学者来说,有很多东西需要学习。
设置我们的项目
首先,我们通过运行以下命令创建一个新的 cargo 项目:
cargo new rust-arduino-blink
我们需要针对 avr 目标(目标三元组avr-unknown-unknown
:)对项目进行交叉编译。
为此,我们需要切换到 Nightly 工具链,因为一些依赖的 crate 使用了不稳定的功能来实现这一切。因此,我们将运行:
rustup override set nightly
上述命令将覆盖所选的工具链,以便我们当前的目录仅适合夜间使用。
然后我们将安装所需的软件包:
pacman -S avr-gcc
该avr-gcc
包是使用链接器所必需的。
pacman -S arduino-avr-core
该arduino-avr-core
软件包包含诸如avrdude之类的实用程序,它是一种使用在系统编程技术上传和操作微控制器的 ROM 和 EEPROM 内容的工具。
我在 arch linux 发行版(endeavour OS)上,pacman
我们的包管理器在哪里。
然后,Cargo.toml
我们将添加以下依赖项:
[dependencies]
# A panic handler is needed. This is a crate with the most basic one.
panic-halt = "0.2.0"
[dependencies.arduino-uno]
git = "https://github.com/Rahix/avr-hal"
rev = "536c5d"
avr-hal
是一个货物工作区,里面有一堆用木板隔开的板条箱,arduino-uno
板条箱就是
其中之一。感谢Rahix整理了这个工作区。
我们还需要为 avr 目标添加 cargo 的构建元数据。我们将avr-atmega328p
在项目根目录创建一个包含以下内容的文件:
{
"llvm-target": "avr-unknown-unknown",
"cpu": "atmega328p",
"target-endian": "little",
"target-pointer-width": "16",
"target-c-int-width": "16",
"os": "unknown",
"target-env": "",
"target-vendor": "unknown",
"arch": "avr",
"data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
"executables": true,
"linker": "avr-gcc",
"linker-flavor": "gcc",
"pre-link-args": {
"gcc": ["-Os", "-mmcu=atmega328p"]
},
"exe-suffix": ".elf",
"post-link-args": {
"gcc": ["-Wl,--gc-sections"]
},
"singlethread": false,
"no-builtins": false,
"no-default-libraries": false,
"eh-frame-header": false
}
并在以下位置引用它.cargo/config.toml
:
[build]
target = "avr-atmega328p.json"
[unstable]
build-std = ["core"]
这样,我们的构建配置就完成了。
让我们写一些代码
抛开依赖关系,让我们添加下面的代码main.rs
并逐步完成它:
(快速提示:您可以cargo doc --open
在目录中运行并获取该项目及其依赖项的文档以供参考)
// main.rs
#![no_std]
#![no_main]
首先,我们需要指定一些全局属性,以便让编译器知道我们处于不同的环境中。
我们处于一个嵌入式环境中,它不具备Rust标准库 crate所依赖的功能,例如堆分配 API、线程、网络 API 等。因此,我们需要#[no_std]
在顶部添加该属性。我们还需要fn main()
使用 覆盖默认入口点( )#[no_main]
,因为我们将为程序提供并定义我们自己的入口点。为了定义我们的入口点,我们将使用arduino_uno
crate 中的 entry 属性宏来定义一个自定义入口点。通常,您使用的 board crate 会为您提供一个入口宏。
然后,我们添加use
语句以从依赖的板条箱中取出范围内所需的项目。
extern crate panic_halt;
use arduino_uno::prelude::*;
use arduino_uno::hal::port::portb::PB5;
use arduino_uno::hal::port::mode::Output;
注意到那个panic_halt
箱子了吗?我们需要一个恐慌处理程序,因为:
在标准库中,恐慌具有定义的行为:它会展开恐慌线程的堆栈,除非用户选择在恐慌时中止程序。然而,在没有标准库的程序中,恐慌行为是未定义的。可以通过声明一个 #[panic_handler] 函数来选择行为。来源:[嵌入式 Rust 书籍]
让我们继续:
#[arduino_uno::entry]
fn main() -> ! {
}
然后,我们用 crate中的属性宏main
来注释我们的函数。该属性是 never 类型,表示该函数永不返回。entry
arduino_uno
!
要使 LED 闪烁,我们只需要几行代码,并将相关引脚设置为高电平或低电平。让我们看一下 Uno 上 ATmega328P 芯片的引脚图:
在上图中,您可以看到微控制器上的各种引脚。大多数(如果不是全部)微控制器都包含允许设备读取和写入外部电路数字值的引脚。其中一些引脚被归类为 I/O 端口。
端口是一组代表标准接口的引脚。这些端口由端口寄存器控制,端口寄存器可以被认为是一个特殊的字节变量,我们可以在代码中更改它。
对于 ATmega328P,我们有三个端口寄存器可供使用:
- C – 模拟引脚 0 至 5
- D – 代表数字引脚 0 至 7
- B – 用于数字引脚 8 至 13
此处有更详细的解释:端口操作
如果你看一下 Uno,你会发现数字引脚 13 连接到内置 LED。
我们需要在代码中访问该引脚才能操作 LED,例如将其设置为高电平或低电平。
让我们添加更多代码:
#[arduino_uno::entry]
fn main() -> ! {
let peripherals = arduino_uno::Peripherals::take().unwrap();
let mut pins = arduino_uno::Pins::new(
peripherals.PORTB,
peripherals.PORTC,
peripherals.PORTD,
);
let mut led = pins.d13.into_output(&mut pins.ddr);
loop {
stutter_blink(&mut led, 25);
}
}
上面的代码中发生了很多事情!
首先,我们创建一个实例,其中Peripheral
包含 Uno 中所有外设的列表。
外设是指连接芯片/CPU 与外部设备、传感器等通信的设备。
外设的例子包括定时器、计数器、串口等。
嵌入式处理器通过一组控制和状态寄存器与外设交互。
然后,我们创建一个新Pin
实例,并将外设实例的端口传入其中peripherals
。
然后,我们定义一个变量led
来保存 LED 所连接的引脚号。创建方法是:使用引脚into_output
的方法将 13 号引脚配置为输出引脚d13
,并传入一个对ddr
寄存器的可变引用。
DDR 寄存器决定特定端口上的引脚是输入还是输出。DDR 寄存器长度为 8 位,每位对应该 I/O 端口的一个引脚。例如,DDRB 的第一位(位 0)决定 PB0 是输入还是输出,而最后一位(位 7)决定 PB7 是输入还是输出。我还需要阅读更多资料才能完全理解 DDR 寄存器。
接下来,我们进入一个loop {}
调用函数,提供对我们的实例stutter_blink
的可变引用和我们想要闪烁的 次数( )。led
25
这是我们的stutter_blink
函数定义:
fn stutter_blink(led: &mut PB5<Output>, times: usize) {
(0..times).map(|i| i * 10).for_each(|i| {
led.toggle().void_unwrap();
arduino_uno::delay_ms(i as u16);
});
}
在 stutter_blink 中,我们所做的就是调用 toggle on led,然后从 arduino_uno 模块调用一个 delay_ms 函数。这一切都在一个迭代器中完成。我们创建一个范围 (0..times),然后调用 map,这样我们就可以逐步延迟 LED 的切换时间,延迟量比较明显。我们当然可以使用 for 循环来实现,而且可读性更高,但我想证明我们可以使用 Rust 的所有高级 API 和抽象。我们正在为一个嵌入式系统编写函数式代码,其中抽象的开销为零。据我所知,这只有在 Rust 中才能实现!
以下是完整代码:
// main.rs
#![no_std]
#![no_main]
extern crate panic_halt;
use arduino_uno::prelude::*;
use arduino_uno::hal::port::portb::PB5;
use arduino_uno::hal::port::mode::Output;
fn stutter_blink(led: &mut PB5<Output>, times: usize) {
(0..times).map(|i| i * 10).for_each(|i| {
led.toggle().void_unwrap();
arduino_uno::delay_ms(i as u16);
});
}
#[arduino_uno::entry]
fn main() -> ! {
let peripherals = arduino_uno::Peripherals::take().unwrap();
let mut pins = arduino_uno::Pins::new(
peripherals.PORTB,
peripherals.PORTC,
peripherals.PORTD,
);
let mut led = pins.d13.into_output(&mut pins.ddr);
loop {
stutter_blink(&mut led, 25);
}
}
让我们尝试建造这个板条箱:
cargo build
如果一切顺利,您应该会看到目录rust-arduino-blink.elf
下生成了一个 ELF 文件target/avr-atmega328p/debug/
。这就是我们需要烧录到 Uno 的二进制文件。要烧录这个 ELF 文件,我们将使用avrdude
实用程序。让我们在根目录下创建一个名为 ELF 的脚本,flash.sh
然后cargo build
将它烧录到 Uno 中:
#! /usr/bin/zsh
set -e
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "usage: $0 <path-to-binary.elf>" >&2
exit 1
fi
if [ "$#" -lt 1 ]; then
echo "$0: Expecting a .elf file" >&2
exit 1
fi
sudo -u creativcoder cargo build
avrdude -q -C/etc/avrdude.conf -patmega328p -carduino -P/dev/ttyACM0 -D "-Uflash:w:$1:e"
有了它,我们现在就可以运行(确保您的 Uno 已通过 USB 电缆连接):
./flash.sh target/avr-atmega328p/debug/rust-arduino-blink.elf
就这样:
我们在 Arduino 上运行 Rust 的第一个闪烁程序!
如果您在访问时收到权限被拒绝的错误/dev/ttyACM0
,您可能需要将您的用户添加
到有权访问串行接口的 Linux 用户组。
首先,我们使用以下命令找到该组:
ls -l /dev/ttyACM0
我的机器上得到以下输出:
crw-rw---- 1 root uucp 166, 0 Aug 19 03:29 /dev/ttyACM0
要将您的用户名添加到uucp
组,我们运行:
sudo usermod -a -G uucp creativcoder
欢迎提出建议/评论/更正。
我计划了几个嵌入式业余项目,以后会根据它们的进展撰写相关文章。以上代码的Github
链接如下。 如果您想了解 Rust 嵌入式领域的最新进展,请关注嵌入式 Rust 工作组。
直到下次!
鏂囩珷鏉ユ簮锛�https://dev.to/creativcoder/how-to-run-rust-on-arduino-uno-40c0