如何在Raspberry Pi Zero W上进行嵌入式Rust编程

607 阅读2分钟

用Rust进行嵌入式编程需要一个全新的知识库。使用Raspberry Pi Zero W,你可以快速启动并运行嵌入式Rust。

本文将从一个嵌入式的*"Hello World "*开始,然后推进到一个文本到Morse代码的翻译器,指导你完成这一过程:

如何设置Pi

格式化SD卡

使用Raspberry Pi Imager,可以从Raspberry Pi软件网页上下载:

rpi-imager

闪存发行版

我建议使用Raspberry Pi OS Lite。这是一个无头发行版,这意味着它没有图形用户界面:

rpi-imager-os

配置Wifi和SSH

rpi-imager-ssh

一旦完成了这些,你就可以把SD卡插入Raspberry Pi,并给它通电。

完成电路

电路图

rpi-circuit

Pi的引脚

如下图所示,将负极连接到地,正极连接到BCM的第17针:

rpi-pinout

引脚图可以在这里看到:https://pinout.xyz/

IMG_3418-1-

如何设置交叉编译

安装目标

使用rustup ,为你的Raspberry Pi安装必要的目标:

my-pc$ rustup add target arm-unknown-linux-gnueabihf

关于Rust中目标的更多信息,请参见附录

指定链接器

raspberrypi/tools 仓库下载到一个名为rpi_tools 的目录中:

my-pc:~ $ git clone https://github.com/raspberrypi/tools $HOME/rpi_tools

使用你喜欢的文本编辑器编辑~/.cargo/config 文件:

my_pc:~ $ sudo nano ~/.cargo/config

告诉Cargo为你的目标使用一个特定的链接器版本:

[target.arm-unknown-linux-gnueabihf]
linker = "/rpi_tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc"

如何为一个嵌入式的 "Hello World "编程

首先创建一个新的Rust项目,并在你喜欢的文本编辑器中打开main.rs 文件:

my-pc:~ $ cargo new blink
my-pc:~ $ cd blink
my-pc:~/blink $ nano src/main.rs

导入rust_gpiozero crate,并对一个LED进行编程,使其每秒在开和关之间交替进行:

use rust_gpiozero::*;

fn main() {
    // Create a new LED attached to Pin 17
    let mut led = LED::new(17);

    led.blink(1.0, 1.0);

    led.wait();
}

确保将依赖关系添加到Cargo.toml 文件中:

[dependencies]
rust-gpiozero = "0.2.1"

成功地退出程序

rustc 1.61.0 [1]以来,您可以使用std::process::ExitCode 结构来指定返回给进程父代的状态代码:

use std::process::ExitCode;
fn main() -> ExitCode {
    // ...
    if error {
      return ExitCode::from(1);
    }
    ExitCode::SUCCESS
}

否则,你可以简单地返回一个Result :

fn main() -> Result<(), std::io::Error> {
  // ...
  Ok(())
}

如何交叉编译程序

建立一个针对所需架构的程序版本:

my-pc:~/blink $ cargo build --release --target=arm-unknown-linux-gnueabihf

如何将二进制文件传输到Pi上

使用scp ,通过SSH将编译好的二进制文件从你的主机传输到Raspberry Pi:

my-pc:~/blink $ scp target/arm-unknown-linux-gnueabihf/release/blink pi@192.168.1.199:~/blink

注意:你的Pi的本地IP可能会有所不同。

如何用SSH进入Pi

通过Raspberry Pi的本地IP地址进行SSH并登录到它:

my-pc:~ $ ssh pi@192.168.1.199

运行程序

在Raspberry Pi上,运行编译好的二进制文件:

pi:~ $ ./blink

如何编写一个文本到摩尔斯代码的翻译器

下面是一个应用程序的例子,它可以逐行读取stdin,将输入的内容翻译成莫尔斯码,并根据字符的莫尔斯码来切换LED的开关:

use rust_gpiozero::*;
use std::io::{BufRead, self};
use std::collections::HashMap;
use std::thread::sleep;
use std::time::Duration;

fn main() -> Result<(), std::io::Error> {
    println!("Starting...\n- Type in text to turn into Morse Code\n- Type `quit()` to quit\n");
    // Create a new LED attached to Pin 17
    let led = LED::new(17);

    /// Length of a dot in milliseconds
    const DOT_DELAY: u64 = 80;
    /// Length of a dash in milliseconds
    const DASH_DELAY: u64 = DOT_DELAY * 3;
    /// Delay between inputs in milliseconds
    const PUSH_DELAY: u64 = DOT_DELAY;
    /// Delay between letters in milliseconds
    const LETTER_DELAY: u64 = DOT_DELAY * 3;
    /// Delay between words in milliseconds
    const WORD_DELAY: u64 = DOT_DELAY * 7;

    let morse_code_alphabet: HashMap<char, &'static str> =
    [
        ('a', ".-"),
        ('b', "-..."),
        ('c', "-.-."),
        ('d', "-.."),
        ('e', "."),
        ('f', "..-."),
        ('g', "--."),
        ('h', "...."),
        ('i', ".."),
        ('j', ".---"),
        ('k', "-.-"),
        ('l', ".-.."),
        ('m', "--"),
        ('n', "-."),
        ('o', "---"),
        ('p', ".--."),
        ('q', "--.-"),
        ('r', ".-."),
        ('s', "..."),
        ('t', "-"),
        ('u', "..-"),
        ('v', "...-"),
        ('w', ".--"),
        ('x', "-..-"),
        ('y', "-.--"),
        ('z', "--.."),
        ('1', ".----"),
        ('2', "..---"),
        ('3', "...--"),
        ('4', "....-"),
        ('5', "....."),
        ('6', "-...."),
        ('7', "--..."),
        ('8', "---.."),
        ('9', "----."),
        ('0', "-----"),
        ('.', ".-.-.-"),
        (',', "--..--"),
        ('?', "..--.."),
        ('\'', ".----."),
        ('!', "-.-.--"),
        ('/', "-..-."),
        ('(', "-.--."),
        (')', "-.--.-"),
        ('&', ".-..."),
        (':', "---..."),
        (';', "-.-.-."),
        ('=', "-...-"),
        ('+', ".-.-."),
        ('-', "-....-"),
        ('_', "..--.-"),
        ('"', ".-..-."),
        ('$', "...-..-"),
        ('@', ".--.-."),
        (' ', " "),
    ].iter().cloned().collect();

    // Read standard input per line
    for line_res in io::stdin().lock().lines() {
        let line = line_res?;
        if line == "quit()" {
            break;
        }
        // Turn line into morse code
        let mut morse = String::new();
        for c in line.chars() {
            if let Some(morse_code_char) = morse_code_alphabet.get(&c) {
                morse.push_str(morse_code_char);
                // Separate characters with a comma
                morse.push_str(",");
            }
        }
        // Blink LED based on characters
        for c in morse.chars() {
            match c {
                '.' => {
                    led.on();
                    sleep(Duration::from_millis(DOT_DELAY));
                    led.off();
                    sleep(Duration::from_millis(PUSH_DELAY));
                },
                '-' => {
                    led.on();
                    sleep(Duration::from_millis(DASH_DELAY));
                    led.off();
                    sleep(Duration::from_millis(PUSH_DELAY));
                },
                ',' => {
                    sleep(Duration::from_millis(LETTER_DELAY));
                },
                ' ' => {
                    sleep(Duration::from_millis(WORD_DELAY));
                },
                _ => {
                    println!("Unknown character: {}", c);
                    break;
                }
            }
        }
        sleep(Duration::from_millis(WORD_DELAY));
    }

    // Free the variable and associated resources
    led.close();

    Ok(())
}

附录

目标

在Rust中,目标是指程序所编译的平台(架构)。Cargo会根据文件系统的布局自动检测目标[2]。

你可以看到内置目标的列表,通过运行:

rustc --print target-list
# OR
rustup target list

在这里,你可以通过运行以下命令来为你的项目添加一个新的目标:

rustup target add <target>

给定的目标通常是以三重形式出现的[3]:

  • 架构
  • 供应商
  • 操作系统类型
  • 环境类型

这被称为 "目标三要素",因为第四部分是可选的。