如何在 Arduino Uno 上运行 Rust

2025-06-08

如何在 Arduino Uno 上运行 Rust

最初发布于:creativcoder.dev

大约一个月前,在我撰写本文时,rust-avr 分支已合并到上游。这意味着您现在只需运行,并为您的目标cargo +nightly build提供一个( ) ,即可为 avr 微控制器板编译 Rust 程序。这太棒了!.cargo/config.tomlavr-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


Enter fullscreen mode Exit fullscreen mode

我们需要针对 avr 目标(目标三元组avr-unknown-unknown:)对项目进行交叉编译。
为此,我们需要切换到 Nightly 工具链,因为一些依赖的 crate 使用了不稳定的功能来实现这一切。因此,我们将运行:



rustup override set nightly


Enter fullscreen mode Exit fullscreen mode

上述命令将覆盖所选的工具链,以便我们当前的目录仅适合夜间使用。

然后我们将安装所需的软件包:



pacman -S avr-gcc


Enter fullscreen mode Exit fullscreen mode

avr-gcc包是使用链接器所必需的。



pacman -S arduino-avr-core


Enter fullscreen mode Exit fullscreen mode

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"


Enter fullscreen mode Exit fullscreen mode

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
  }



Enter fullscreen mode Exit fullscreen mode

并在以下位置引用它.cargo/config.toml



[build]
target = "avr-atmega328p.json"

[unstable]
build-std = ["core"]



Enter fullscreen mode Exit fullscreen mode

这样,我们的构建配置就完成了。

让我们写一些代码

抛开依赖关系,让我们添加下面的代码main.rs并逐步完成它:

(快速提示:您可以cargo doc --open在目录中运行并获取该项目及其依赖项的文档以供参考)



// main.rs

#![no_std]
#![no_main]


Enter fullscreen mode Exit fullscreen mode

首先,我们需要指定一些全局属性,以便让编译器知道我们处于不同的环境中。
我们处于一个嵌入式环境中,它不具备Rust标准库 crate所依赖的功能,例如堆分配 API、线程、网络 API 等。因此,我们需要#[no_std]在顶部添加该属性。我们还需要fn main()使用 覆盖默认入口点( )#[no_main],因为我们将为程序提供并定义我们自己的入口点。为了定义我们的入口点,我们将使用arduino_unocrate 中的 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;


Enter fullscreen mode Exit fullscreen mode

注意到那个panic_halt箱子了吗?我们需要一个恐慌处理程序,因为:

在标准库中,恐慌具有定义的行为:它会展开恐慌线程的堆栈,除非用户选择在恐慌时中止程序。然而,在没有标准库的程序中,恐慌行为是未定义的。可以通过声明一个 #[panic_handler] 函数来选择行为。来源:[嵌入式 Rust 书籍]

让我们继续:



#[arduino_uno::entry]
fn main() -> ! {

}


Enter fullscreen mode Exit fullscreen mode

然后,我们用 crate中的属性宏main来注释我们的函数。该属性 never 类型,表示该函数永不返回。entryarduino_uno!

要使 LED 闪烁,我们只需要几行代码,并将相关引脚设置为高电平或低电平。让我们看一下 Uno 上 ATmega328P 芯片的引脚图:

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);
    }
}


Enter fullscreen mode Exit fullscreen mode

上面的代码中发生了很多事情!

首先,我们创建一个实例,其中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);
    });
}


Enter fullscreen mode Exit fullscreen mode

在 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);
    }
}



Enter fullscreen mode Exit fullscreen mode

让我们尝试建造这个板条箱:



cargo build


Enter fullscreen mode Exit fullscreen mode

如果一切顺利,您应该会看到目录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"



Enter fullscreen mode Exit fullscreen mode

有了它,我们现在就可以运行(确保您的 Uno 已通过 USB 电缆连接):

./flash.sh target/avr-atmega328p/debug/rust-arduino-blink.elf

就这样:

Arduino 上的 Rust

我们在 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
PREV
10 个最佳管理模板助您入门 Laravel 和 VueJS
NEXT
CSS 网格布局模板区域(轻松布局的秘密)