Rust for STM32 Developer – 第 0 节 – 配置环境

环境配置

使用 Rust 进行嵌入式开发需要配置相应的开发环境。由于 Windows 系统在嵌入式开发中可能会遇到一些兼容性问题,建议使用 Linux 或 macOS 系统进行开发。如果你使用的是 Windows 系统,可以考虑使用 WSL(Windows Subsystem for Linux)来创建一个 Linux 开发环境。

本文档采取 WSL 作为示例,介绍如何在 Windows 系统上配置 Rust 嵌入式开发环境。

配置 cargo

参照 stm32-template 项目的 README 文档,配置 cargo 的相关设置。

首先,安装 cargo-generate 工具:

cargo install cargo-generate

然后安装烧录工具与解决方案管理器:

cargo install probe-rs-tools --locked

其中 --locked 参数用于确保安装的版本与 probe-rs 的版本一致,避免出现版本不兼容的问题。

最后为 Cortex-M 处理器安装工具链:

rustup target add thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf

部署空白项目

运行如下命令,使用 cargo-generate 创建一个新的项目:

cargo generate --git https://github.com/burrbull/stm32-template/

接下来会问几个问题:

  1. Project Name: 输入你的项目名称,例如 first-stm。
  2. What microcontroller name: 输入你使用的微控制器型号,例如 stm32g431cbu6。
  3. What HAL version to use: 选择 HAL 源,此处直接使用 last-release。
  4. Is it RTIC-based application: 选择 true,表示使用 RTIC 框架。

什么是 RTIC 框架?

RTIC(Real-Time Interrupt-driven Concurrency)是一个专为嵌入式系统设计的、基于 Rust 语言的实时并发框架。它通过硬件加速的方式,将并发任务直接映射到微控制器的中断系统上,从而取代了传统的软件实时操作系统(RTOS)内核。
RTIC 常被视作RTOS 的轻量级替代方案,其定位介于裸机编程和完整 RTOS 之间。

特性 RTIC 传统 RTOS (如 FreeRTOS)
内核 无软件内核,依赖硬件中断控制器 包含软件调度器内核
内存占用 极小,任务共享栈,无动态分配 相对较大,通常需要为每个任务分配独立栈
安全性 编译期保证无竞态、无死锁 通常依赖运行时机制,保证有限
调度开销 极低,硬件完成大部分调度 存在任务切换等软件调度开销
适用场景 安全关键、RAM 受限的系统,追求极致效率和确定性 需要丰富服务(如文件系统、网络协议栈)的复杂系统

此处选择使用 RTIC 框架,主要是因为它在嵌入式开发中提供了更高的安全性和效率,尤其适合资源受限的微控制器环境。

  1. Will this program use defmt logger: 选择 true,表示使用 defmt 日志系统。

什么是 defmt 日志系统?

defmt 是一个专为资源受限设备(如微控制器)设计的高效日志框架。它的名字是“deferred formatting”(延迟格式化)的缩写,这直接点明了其核心设计思想。
极低的运行时开销:由于格式化在主机端完成,设备端的日志调用开销远低于传统方式,非常适合在中断服务程序(ISR)或对时序要求严格的代码中使用。
更小的二进制体积:编译时生成的字符串索引表,避免了将大量格式字符串编译进固件,从而显著减小了二进制文件的大小。
编译期过滤:支持在编译时按模块和日志级别(error, warn, info, debug, trace)过滤日志,未启用的日志代码会被直接移除,不产生任何开销。
清晰的日志级别:提供了与 log crate 类似的 error!、warn!、info!、debug!、trace! 宏。

defmt 常被拿来与 Rust 标准的 log crate 比较,两者定位不同:

特性 defmt log(标准库)
设计目标 资源受限的嵌入式系统 通用 Rust 应用程序(服务器、CLI 等)
格式化位置 主机端(延迟格式化) 设备端(运行时格式化)
运行时开销 极低,仅发送二进制数据和索引 相对较高,需在设备端进行字符串格式化
二进制体积 更小,字符串表在主机端 相对较大,格式字符串嵌入固件
灵活性 较低,对日志格式和类型有约束 高,与 std::fmt 完全兼容
适用平台 主要针对 no_std 嵌入式环境 支持所有 Rust 目标平台
  1. Do you want to load SVD and add it to VSCode task: 选择 true,表示加载 SVD 文件并将其添加到 VSCode 任务中。

什么是 SVD 文件?

SVD(System View Description)是 ARM 定义的一种基于 XML 的标准文件格式,用于描述 Cortex-M 微控制器中外设寄存器的详细信息。一个 SVD 文件通常包含:

外设信息:名称、基地址、中断号等。

寄存器详情:名称、地址偏移、位域定义、访问权限等。

芯片厂商(如 ST、Nordic)会随芯片发布对应的 SVD 文件。

到此,你已经成功创建了一个基于 RTIC 框架的 Rust 嵌入式项目,并配置了相关的开发环境。

创建项目的目录结构

drwxr-xr-x 6 shizuku shizuku  4096 Sep 29 18:19 ./
drwxr-xr-x 3 shizuku shizuku  4096 Sep 29 18:02 ../
drwxr-xr-x 2 shizuku shizuku  4096 Sep 29 18:19 .cargo/
drwxr-xr-x 6 shizuku shizuku  4096 Sep 29 18:20 .git/
-rw-r--r-- 1 shizuku shizuku     8 Sep 29 18:19 .gitignore
drwxr-xr-x 2 shizuku shizuku  4096 Sep 29 18:19 .vscode/
-rw-r--r-- 1 shizuku shizuku   612 Sep 29 18:19 Cargo.toml
-rw-r--r-- 1 shizuku shizuku  3218 Sep 29 18:19 Embed.toml
-rw-r--r-- 1 shizuku shizuku 10846 Sep 29 18:19 LICENSE-APACHE
-rw-r--r-- 1 shizuku shizuku  1022 Sep 29 18:19 LICENSE-MIT
-rw-r--r-- 1 shizuku shizuku  2083 Sep 29 18:19 README.md
-rw-r--r-- 1 shizuku shizuku  1324 Sep 29 18:19 build.rs
-rw-r--r-- 1 shizuku shizuku   367 Sep 29 18:19 memory.x
drwxr-xr-x 2 shizuku shizuku  4096 Sep 29 18:19 src/

上述为项目的目录结构,其中:

Embed.toml 配置

[default.probe] 调试器与 Probe 配置

probe 表示调试器的配置,主要用于指定使用的调试器类型和相关参数。常见的调试器类型包括:

  • jlink:J-Link 调试器,由 Segger 提供,广泛用于 ARM Cortex-M 系> 列微控制器的调试。
  • stlink:ST-Link 调试器,由 STMicroelectronics 提供,专为 > STM32 系列微控制器设计。
  • cmsis-dap:CMSIS-DAP 调试器,符合 ARM 的 CMSIS-DAP 标准,支持> 多种微控制器。
  • daplink:DAPLink 调试器,由 ARM 提供,支持多种微控制器和开发板。
  • usb_vid:指定调试器的 USB 供应商 ID(Vendor ID)。这个值用于识别连接到计算机的 USB 设备。不同的调试器可能有不同的供应商 ID。
    • 0483:STMicroelectronics 的 USB VID。
    • 046d:Logitech 的 USB VID。
    • 05e3:ARM 的 USB VID。
    • 1337:leet speak(通常用于自定义或实验性设备)。

使用 lsusb 命令可以查看连接到计算机的 USB 设备及其 VID 和 PID。例如:

lsusb                                      
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0483:374b STMicroelectronics ST-LINK/V2.1
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

如上,0483:374b 表示 ST-LINK/V2.1 调试器的 VID 为 0483,PID 为 374b。

  • usb_pid:指定调试器的 USB 产品 ID(Product ID)。这个值用于进一步识别连接到计算机的特定设备型号。不同的调试器型号可能有不同的产品 ID。
    • 374b:ST-LINK/V2.1 的 USB PID。
    • 3748:ST-LINK/V2 的 USB PID。
    • 374a:ST-LINK/V2-1 的 USB PID。
    • 374c:ST-LINK/V3 的 USB PID。
  • serial:指定调试器的序列号,用于唯一标识连接的调试器设备。这个参数在同时连接多个相同型号的调试器时非常有用,可以确保选择正确的设备进行调试。

  • protocol:指定调试器使用的通信协议。常见的协议包括:
    • swd:Serial Wire Debug,适用于大多数 ARM Cortex-M 微控制器。
    • jtag:Joint Test Action Group,适用于支持 JTAG 的微控制器。
  • speed:指定调试器与目标设备之间的通信速度,单位为 kHz。常见的速度值包括:
    • 1000:表示 1 MHz 的通信速度。
    • 4000:表示 4 MHz 的通信速度。
    • 8000:表示 8 MHz 的通信速度。

[default.flashing] 烧录配置

  • enable:布尔值,表示是否启用烧录功能。如果设置为 true,则在构建项目后会自动进行烧录操作;如果设置为 false,则不会进行烧录。
  • halt_afterwards:已废弃 布尔值,表示在烧录完成后是否暂停目标设备的执行。如果设置为 true,则烧录完成后目标设备会进入暂停状态,等待调试器的进一步操作;如果设置为 false,则烧录完成后目标设备会继续运行。
  • restore_unwritten_bytes:布尔值,表示在烧录过程中是否恢复未写入的字节。如果设置为 true,则在烧录过程中会尝试恢复那些未被写入的字节,以确保目标设备的内存状态尽可能接近预期;如果设置为 false,则不会进行恢复操作。

对 restore_unwritten_bytes 的解释

假设 Flash 中:

Flash
0x08000000 程序区
0x08010000 数据区
0x08020000 数据区

ELF 只覆盖了 0x08000000 ~ 0x08010000 的程序区,而数据区 0x08010000 ~ 0x08020000 没有被覆盖。如果 restore_unwritten_bytes 设置为 true,则烧录工具会尝试恢复数据区的内容,以确保 Flash 中的数据区保持原有状态;如果设置为 false,则数据区的内容可能会被擦除或变为未定义状态。

这在把特定的 Flash 区域用于存储配置数据或其他重要信息时尤其重要,因为这些区域的数据不应被意外覆盖或丢失。

  • flash_layout_output_path:输出一个 SVG 布局图,参数为 SVG 文件的路径。该图形化布局图显示了 Flash 内存的分区情况,包括程序区、数据区和其他自定义分区。通过查看该图,可以直观地了解 Flash 的使用情况和各个分区的大小及位置。
  • do_chip_erase:布尔值,表示在烧录前是否执行芯片擦除操作。如果设置为 true,则在烧录之前会擦除整个芯片的内容,以确保目标设备的内存处于干净状态;如果设置为 false,则不会进行芯片擦除操作,可能会保留原有数据。

[default.reset] 复位配置

  • enable:布尔值,表示是否启用复位功能。如果设置为 true,则在烧录完成后会自动对目标设备进行复位操作;如果设置为 false,则不会进行复位。
  • halt_afterwards:布尔值,表示在复位完成后是否暂停目标设备的执行。如果设置为 true,则复位完成后目标设备会进入暂停状态,等待调试器的进一步操作;如果设置为 false,则复位完成后目标设备会继续运行。

[default.general] 通用配置

  • chip:指定目标微控制器的型号,例如 STM32G431CBUx。这个参数用于告诉烧录工具和调试器目标设备的具体型号,以便正确地进行烧录和调试操作。
  • chip_description:指定目标微控制器的描述信息,例如 STM32G431CBUx。这个参数用于提供关于目标设备的更多信息,可能包括其特性、功能和其他相关细节。此选项一般留空。
  • log_level:指定日志输出的详细程度。常见的日志级别包括:
    • ERROR:仅输出错误信息。
    • WARN:输出警告和错误信息。
    • INFO:输出一般信息、警告和错误信息。
    • DEBUG:输出调试信息、一般信息、警告和错误信息。
    • TRACE:输出最详细的跟踪信息,包括调试、一般信息、警告和错误信息。
  • connect_under_reset:布尔值,表示是否在复位状态下连接目标设备。如果设置为 true,则调试器会在目标设备复位时尝试建立连接,这对于某些需要在复位期间进行调试的场景非常有用;如果设置为 false,则调试器会在目标设备正常运行时建立连接。

[default.rtt] RTT 配置

  • enable:布尔值,表示是否启用 RTT(Real-Time Transfer)功能。如果设置为 true,则在调试过程中会启用 RTT,以便进行实时数据传输和调试;如果设置为 false,则不会启用 RTT。
  • up_mode:指定 RTT 上行通道的模式。常见的模式包括:
    • BlockIfFull:如果上行缓冲区已满,则阻塞写入操作,直到有空间可用。
    • NoBlockSkip:不阻塞写入操作,如果缓冲区已满,则跳过写入。
    • NoBlockTrim:不阻塞写入操作,如果缓冲区已满,则截断写入数据,只保留部分数据。
  • channels:指定 RTT 通道的配置,包括通道号、名称和缓冲区大小。每个通道可以用于不同类型的数据传输,例如日志输出、调试信息或其他自定义数据。
channels = [
    { up = 0, down = 0, name = "log", format = "Defmt" },
]
  • timeout:毫秒,指定 RTT 操作的超时时间,单位为毫秒。如果在指定时间内未能完成 RTT 操作,则会触发超时处理。
  • show_timestamp:布尔值,表示是否在 RTT 输出中显示时间戳。如果设置为 true,则每条 RTT 输出都会附带时间戳信息,以便进行时间相关的调试和分析;如果设置为 false,则不会显示时间戳。
  • log_enable:布尔值,表示是否启用 RTT 日志功能。如果设置为 true,则会将 RTT 输出作为日志记录;如果设置为 false,则不会记录 RTT 日志。

[default.gdb] GDB 配置

  • enable:布尔值,表示是否启用 GDB(GNU Debugger)功能。如果设置为 true,则在调试过程中会启用 GDB,以便进行源代码级调试;如果设置为 false,则不会启用 GDB。
  • gdb_connect_string:指定 GDB 连接的字符串,用于告诉 GDB 如何连接到目标设备。"127.0.0.1:1337" 表示 GDB 将监听本地 IP 地址的 1337 端口,以便与调试器进行通信。

build.rs 配置

use std::fs;
use std::io::Write;
use std::path::Path;

fn main() {
    // Retrieve the target chip series from the environment variable
    let target = "stm32g431.svd";

    let file_name = format!("{target}.svd");
    let output_path = Path::new(&file_name);
    let url = format!("https://stm32-rs.github.io/stm32-rs/{target}.patched");

    // Check if the file already exists
    if output_path.exists() {
        println!(
            "SVD file already exists at {:?}, skipping download.",
            output_path
        );
    } else {
        // Ensure the output directory exists
        if let Some(parent) = output_path.parent() {
            fs::create_dir_all(parent).expect("Failed to create directory for SVD file");
        }

        // Download the file
        println!("Downloading SVD file from {}...", url);
        let response = reqwest::blocking::get(&url).expect("Failed to fetch SVD file");
        let content = response.text().expect("Failed to read response body");

        // Write the downloaded content to the file
        let mut file = fs::File::create(output_path).expect("Failed to create SVD file");
        file.write_all(content.as_bytes())
            .expect("Failed to write SVD file");

        println!("SVD file saved to {:?}", output_path);
    }
}

这个 build.rs 文件的主要作用是自动下载并保存指定的 SVD 文件,以便在构建项目时使用。它首先检查目标 SVD 文件是否已经存在,如果存在则跳过下载;如果不存在,则从指定的 URL 下载该文件,并将其保存到项目目录中。

flowchart TB A[开始] --> B{检查 SVD 文件是否存在} B -- Yes --> C[跳过下载] B -- No --> D[建立目录(如果不存在)] D --> E[下载 SVD 文件] E --> F[写入 SVD 文件到项目目录] F --> G[结束] C --> G

memory.x 配置

MEMORY
{
  /* NOTE K = KiBi = 1024 bytes */
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
  RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}




/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

初始的 memory.x 文件定义了微控制器的内存布局,包括 Flash 和 RAM 的起始地址和长度。它还定义了 _stack_start,用于指定调用栈的起始位置。

现在使用 STM32G431CBUx 微控制器为例,Flash 的起始地址为 0x08000000,长度为 128K,RAM 的起始地址为 0x20000000,长度为 32K。这些信息对于链接器和运行时环境非常重要,因为它们决定了程序的存储位置和可用内存。

src/main.rs

#![deny(unsafe_code)]
#![no_main]
#![no_std]

// Print panic message to probe console
use panic_probe as _;

#[rtic::app(device = stm32g4xx_hal::pac, peripherals = true)]
mod app {
    use stm32g4xx_hal::{
        prelude::*,
    };

    #[shared]
    struct Shared {}

    // Local resources go here
    #[local]
    struct Local {}

    #[init]
    fn init(mut ctx: init::Context) -> (Shared, Local, init::Monotonics) {
        (
            Shared {
               // Initialization of shared resources go here
            },
            Local {
                // Initialization of local resources go here
            },
            init::Monotonics()
        )
    }

    // Optional idle, can be removed if not needed.
    #[idle]
    fn idle(_: idle::Context) -> ! {
        loop {
            continue;
        }
    }
}

头上:

  • #![deny(unsafe_code)]:禁止使用不安全代码,确保代码的安全性。
  • #![no_main]:表示不使用标准的 main 函数入口点,而是使用 RTIC 框架提供的 #[init] 和 #[idle] 等函数作为入口点。
  • #![no_std]:表示不使用 Rust 标准库,而是使用 core 库,适用于嵌入式系统。

引入了一个未使用的 stm32g4xx_hal 预导入模块 prelude::*,它提供了对 HAL(硬件抽象层)功能的访问。

还有两个结构体:

  • Shared:用于定义在任务之间共享的资源。
  • Local:用于定义仅在特定任务中使用的本地资源。

现在引入 app 模块,它是 RTIC 框架的核心模块,包含了应用程序的主要逻辑。

#[init] 函数

#[init] 函数是 RTIC 框架的初始化函数,用于在系统启动时进行资源的初始化。它接收一个 init::Context 参数,返回一个元组,包含共享资源、局部资源和单调计时器。

GPIO、时钟、UART、SPI、CAN、定时器等初始化通常都会放这里。

mut ctx: init::Context 参数

init::Context 提供了对系统资源的访问权限,包括外设、单调计时器和其他初始化所需的上下文信息。通过 mut ctx,可以在初始化过程中修改这些资源的状态。

#[idle] 函数

#[idle] 函数是 RTIC 框架的空闲任务函数,当系统没有其他任务需要执行时,会进入该函数。它接收一个 idle::Context 参数,并且是一个无限循环,确保系统在空闲时不会退出。

通常把 continue 换做 cortex_m::asm::wfi(),让 CPU 进入低功耗模式,等待中断唤醒。

cortex_m::asm::wfi()

wfi(Wait For Interrupt)是 ARM Cortex-M 处理器中的一条汇编指令,用于让处理器进入低功耗模式,直到下一个中断发生。使用 cortex_m::asm::wfi() 可以有效降低系统的功耗,特别是在嵌入式系统中,当没有任务需要执行时,可以让 CPU 进入休眠状态,等待外部事件或中断唤醒。
警告:wfi() 指令是 ARM Cortex-M 系列处理器的一个指令,用于让处理器进入低功耗模式,等待中断唤醒。但是这样会导致 defmt 调试信息完全被阻断,所以在调试阶段不使用 wfi(),正式编译发布版本时再使用。

其余如 Cargo.toml 等配置与普通 Rust 项目类似,主要区别在于添加了嵌入式相关的依赖和配置。在此不过多赘述。

尝试编译一下

cargo build

编译出现一些警告是正常的,因为现在没有使用任何外设或功能。只要没有错误,说明环境配置成功。

烧录与调试

只烧录:

cargo flash

烧录与运行:

cargo run

多功能烧录、运行、调试:

cargo embed

以上工具都可以 --release 进行 release 模式编译。

尝试烧录,可能看到出现了:

WARN probe_rs::probe::stlink: Target voltage (VAPP) is 0.47 V. Is your target device powered?

这个警告表示目标设备的电压过低,可能是因为目标设备没有正确供电。请确保目标设备已连接到电源,并且电压在正常范围内(通常为 3.3V 或 5V,具体取决于微控制器型号)。

此处发现 G431 无法正常工作,切换 F103C8T6 进行测试。

但是:

     Running `probe-rs run --chip STM32F103C8Tx target/thumbv7m-none-eabi/debug/first-stm-f`
 WARN probe_rs::probe::stlink: Target voltage (VAPP) is 0.47 V. Is your target device powered?
      Erasing ✔ 100% [####################]   2.00 KiB @   3.47 KiB/s (took 1s)                                                                
      Programming ✔ 100% [####################]   2.00 KiB @   3.07 KiB/s (took 1s)                                                                   
      Finished in 1.36s

同样出现了电压过低的警告,但烧录成功了。原因可能在于复刻 STLink 缺少这一功能。

到此,已经成功配置了 Rust 嵌入式开发环境,并创建了一个基于 RTIC 框架的项目。可以继续在此基础上进行开发,添加外设驱动、实现功能逻辑,并使用 cargo flash 或 cargo embed 进行烧录和调试。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇