环境搭建:现代工具链的优势

嵌入式中C工具链根据不同目标平台需要不同的工具链,这是单片机开发者最常遇到的第一个挑战。ARM平台需要安装ARM-GCC或ARMCC,8051需要Keil C51或者SDCC,ESP32使用xtensa-esp32-gcc/riscv-32... 还有很多Risc-V架构的单片机。不同平台工具链之间互不兼容,每换一个芯片平台,开发者往往需要重新配置整个环境。即使是同一CPU架构,不同厂商和不同系列的单片机,也可能需要不同的SDK和工具链配置。

C语言开发方式

从原理上来讲我们可以直接通过gcc配合烧录调试工具直接进行开发,但是C语言需要编写makefile或者cmake且调试上也会很麻烦

在C语言ARM单片机开发中,很多开发者使用的是像Keil5或者IAR这样的IDE: 比如Keil需要先在官网下载并激活后配置对应soc的工具包

很多厂商也给出了自己的集成开发环境比如TI的CCS,能用于编译调试TI的单片机,DSP,蓝牙SOC等处理器芯片

ST公司为自家SOC提供的CubeIDE NXP公司提供的NXP MCUXpresso

也有很多厂商通过eclipse或者VScode自定义自家SOC的开发环境。

每个芯片厂商往往有自己的IDE和工具链,例如STM32CubeIDE、NXP MCUXpresso、ESP-IDF等。这导致开发者在切换平台时需要重新学习一套工具,项目移植困难,学习成本高。

相比之下,Rust嵌入式开发生态提供了一套更加现代化、统一的工具链体验

Rust环境配置

首先我们来安装Rust工具链

对于windows用户只需一行

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Rust 生态系统由许多工具组成,主要包括:

  • rustc:Rust 编译器,可将 .rs 文件转换为二进制文件和其他中间格式。

  • cargo:Rust 依赖项管理器和构建工具。Cargo 知道如何下载托管在 https://crates.io 上的依赖项,并在构建项目时将它们传递给 rustc。Cargo 还附带一个内置的测试运行程序,用于执行单元测试。

  • rustup:Rust 工具链安装和更新工具。当 Rust 发布新版本时,此工具用于安装并更新 rustc 和 cargo。 此外,rustup 还可以下载标准库的文档。可以同时安装多个版本的 Rust,rustup 会根据需要让你在这些版本之间切换。

利用rustup工具可以快速配置不同嵌入式平台支持:

rustup target add thumbv6m-none-eabi  # 添加ARM Cortex-M0支持
rustup target add thumbv7m-none-eabi  # 添加ARM Cortex-M3支持
rustup target add riscv32imac-unknown-none-elf  # 添加RISC-V32imac支持

在这之后就完成编译rust的基础配置环境了。然后就是针对嵌入式环境的烧录和调试。

烧录和调试

除了通过命令行或IDE使用传统的openocd ,调试器等。也可以用Rust生态中的强力工具probe-rs probe-rs实现了针对不同SOC厂商和不同调试协议的适配 利用probe-rs可以快速实现编译后代码的烧录和调试。 在.cargo/config.toml中添加对应的编译目标平台例如stm32g031

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace STM32G031G8Ux with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32G031G8Ux"

即可通过cargo run命令将代码烧录并运行在目标平台

 cargo run --release --target thumbv6m-none-eabi

probe-rs还支持与VSCode/CLion等IDE的集成,可以实现代码的断点调试。具体可以参考probe-rs-vscode 在单片机编程中也经常有"printf调试",probe-rs中利用调试器的RTTdefmt 实现很小的延迟和无阻塞的方式将日志记录打印到电脑,代码中利用defmt中的debug! error! info!等宏直接实现不同日志的打印

#![allow(unused)]
fn main() {
defmt::info!("This is an info message: {}", 42);
defmt::warn!("This is a warning message: {}", true);
defmt::debug!("This is a debug message: {}", "hello");
defmt::error!("This is an error message: {}", 3.14);
defmt::println!("This is a println message: {}", 42);
defmt::assert!(1 + 1 == 2, "Assertion failed: 1 + 1 != 2");
}

整体代码示例:

#![no_main]
#![no_std]

use defmt::info;
use defmt_rtt as _; // 使用 RTT 作为传输层
use panic_probe as _; // 使用探针作为 panic handler

#[cortex_m_rt::entry]
fn main() -> ! {
    info!("This is an info message: {}", 42);
    defmt::debug!("This is a debug message: {}", "hello");
    defmt::warn!("This is a warning message: {}", true);
    defmt::error!("This is an error message: {}", 3.14);
    defmt::trace!("This is a trace message: {}", [1, 2, 3]);

    defmt::assert!(1 + 1 == 2, "Assertion failed: 1 + 1 != 2");
    defmt::assert_eq!(1 + 1, 2, "Assertion failed: 1 + 1 != 2");
    defmt::assert_ne!(1 + 1, 3, "Assertion failed: 1 + 1 == 3");

    let mut buffer = [0u8; 32];
    defmt::write!(&mut buffer, "Hello, {}!", "world");

    let args = defmt::format_args!("Hello, {}!", "world");
    defmt::println!("This is a println message: {}", 42);

    defmt::log!(defmt::Level::Info, "This is a log message: {}", 42);

    loop {}
}

具体实现和原理可以参考defmt-book

这里使用的时候是无感的,单片机工程执行cargo run 之后调用probe-rs。 probe-rs内置了SEGGER 的 RTT,通过RTT将日志发送给电脑。

至此通过Rust工具的生态能非常简单的完成基础配置,且无需特定的IDE便能轻松实现编译和调试