第十九章:嵌入式Rust入门——硬件世界的探索

第十九章:嵌入式Rust入门——硬件世界的探索

本章导读:从服务器到手机,从智能家居到工业设备,软件无处不在。而嵌入式系统是软件与物理世界的桥梁。Rust 凭借零成本抽象、内存安全和强大的类型系统,正逐渐成为嵌入式开发的新选择。本章将带你踏入硬件编程的世界,感受 Rust 在资源受限环境中的魅力。


🔌 19.1 嵌入式系统概述

📊 19.1.1 什么是嵌入式系统

嵌入式系统是专门为特定任务设计的计算机系统,通常具有:

  • 有限的资源(内存、处理能力)
  • 实时性要求
  • 直接与硬件交互
  • 低功耗需求

🎯 19.1.2 Rust 的优势

特性C/C++Rust
内存安全手动管理编译期保证
抽象成本零成本
并发安全易出错编译期检查
工具链分散Cargo 统一

⚙️ 19.2 no_std 环境

🚫 19.2.1 禁用标准库

// 禁用标准库
#![no_std]

// 嵌入式程序没有 main 入口
#![no_main]

use core::panic::PanicInfo;

// panic 处理函数(必须)
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

// 入口点(取决于平台)
#[no_mangle]
pub extern "C" fn _start() -> ! {
    loop {}
}

📦 19.2.2 Cargo 配置

# Cargo.toml
[package]
name = "embedded_hello"
version = "0.1.0"
edition = "2021"

[dependencies]

[profile.dev]
panic = "abort"  # panic 时直接终止

[profile.release]
panic = "abort"  # release 也终止

🔧 19.2.3 编译目标

# 添加 ARM 目标
rustup target add thumbv7m-none-eabi

# 编译
cargo build --target thumbv7m-none-eabi

🎯 19.3 常用嵌入式 Crate

📚 19.3.1 核心生态

Crate用途
cortex-mARM Cortex-M 支持
cortex-m-rt启动代码
embedded-hal硬件抽象层
panic-halt简单 panic 处理
defmt高效日志

📦 19.3.2 依赖配置

# Cargo.toml
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1.0"
panic-halt = "0.2"
defmt = "0.3"
defmt-rtt = "0.4"

[dependencies.stm32f1xx-hal]
version = "0.10"
features = ["stm32f103", "rt"]

💡 19.4 LED 闪烁:Hello World

🔌 19.4.1 完整示例(STM32F103)

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use panic_halt as _;
use stm32f1xx_hal::{
    pac,
    prelude::*,
    gpio::{Output, PushPull},
};

#[entry]
fn main() -> ! {
    // 获取外设
    let dp = pac::Peripherals::take().unwrap();
    let _cp = cortex_m::Peripherals::take().unwrap();

    // 配置时钟
    let mut rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut dp.FLASH.constrain().acr);

    // 配置 GPIO
    let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);

    loop {
        // 点亮 LED
        led.set_high();

        // 延时
        cortex_m::asm::delay(1_000_000);

        // 熄灭 LED
        led.set_low();

        // 延时
        cortex_m::asm::delay(1_000_000);
    }
}

📝 19.4.2 内存布局

/* memory.x */
MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

🌡️ 19.5 硬件抽象层(HAL)

🔌 19.5.1 embedded-hal Trait

// embedded-hal 定义的 trait
use embedded_hal::digital::{InputPin, OutputPin};
use embedded_hal::delay::DelayNs;

struct MyLed<P> {
    pin: P,
}

impl<P: OutputPin> MyLed<P> {
    fn new(pin: P) -> Self {
        Self { pin }
    }

    fn on(&mut self) {
        self.pin.set_low();  // 低电平点亮
    }

    fn off(&mut self) {
        self.pin.set_high();  // 高电平熄灭
    }

    fn toggle(&mut self) {
        // ...
    }
}

// 使用示例
fn blink_led<P: OutputPin, D: DelayNs>(
    led: &mut MyLed<P>,
    delay: &mut D,
) {
    loop {
        led.on();
        delay.delay_ms(500);
        led.off();
        delay.delay_ms(500);
    }
}

📡 19.5.2 SPI 通信

use embedded_hal::spi::{SpiBus, Mode, Phase, Polarity};

fn read_sensor<S: SpiBus>(spi: &mut S) -> Result<u16, S::Error> {
    let mut rx_buf = [0u8; 2];

    // 发送命令并读取响应
    spi.transfer_in_place(&mut rx_buf)?;

    // 组合字节
    let value = ((rx_buf[0] as u16) << 8) | (rx_buf[1] as u16);
    Ok(value)
}

📉 19.5.3 I2C 通信

use embedded_hal::i2c::I2c;

const SENSOR_ADDR: u8 = 0x48;

fn read_temperature<I: I2c>(i2c: &mut I) -> Result<f32, I::Error> {
    let mut buf = [0u8; 2];

    // 读取温度寄存器
    i2c.write_read(SENSOR_ADDR, &[0x00], &mut buf)?;

    // 转换为温度值
    let raw = ((buf[0] as u16) << 4) | ((buf[1] as u16) >> 4);
    let temp = raw as f32 * 0.0625;
    Ok(temp)
}

🖥️ 19.6 模拟器开发

🎮 19.6.1 使用 QEMU

# 安装 QEMU
# Ubuntu: sudo apt install qemu-system-arm

# 运行程序
qemu-system-arm -machine lm3s6965evb -kernel target/thumbv7m-none-eabi/debug/embedded_hello

🎯 19.6.2 测试驱动开发

// 在 host 环境测试
#[cfg(test)]
mod tests {
    use super::*;

    // 模拟 OutputPin
    struct MockPin {
        state: bool,
    }

    impl OutputPin for MockPin {
        fn set_low(&mut self) {
            self.state = true;
        }

        fn set_high(&mut self) {
            self.state = false;
        }
    }

    #[test]
    fn test_led() {
        let mut led = MyLed::new(MockPin { state: false });
        led.on();
        assert!(led.pin.state);
        led.off();
        assert!(!led.pin.state);
    }
}

📦 19.7 实战:温度监测器

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use panic_halt as _;
use stm32f1xx_hal::{
    pac,
    prelude::*,
    adc::Adc,
    delay::Delay,
};

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();

    // 时钟配置
    let mut flash = dp.FLASH.constrain();
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr);

    // 延时器
    let mut delay = Delay::new(cp.SYST, clocks);

    // GPIO 配置
    let mut gpioa = dp.GPIOA.split();
    let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);

    // LED
    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);

    // ADC 配置
    let mut adc = Adc::adc1(dp.ADC1, &mut rcc.apb2, clocks);
    let mut temp_pin = gpioa.pa0.into_analog(&mut gpioa.crl);

    // 阈值温度
    const THRESHOLD: u16 = 2048;  // 约 1.65V (假设 3.3V 参考)

    loop {
        // 读取 ADC
        let value: u16 = adc.read(&mut temp_pin).unwrap();

        // 判断温度是否超过阈值
        if value > THRESHOLD {
            // 高温:快速闪烁
            for _ in 0..3 {
                led.set_low();
                delay.delay_ms(100_u16);
                led.set_high();
                delay.delay_ms(100_u16);
            }
        } else {
            // 正常:慢速闪烁
            led.set_low();
            delay.delay_ms(500_u16);
            led.set_high();
            delay.delay_ms(500_u16);
        }
    }
}

🔧 19.8 调试技巧

📡 19.8.1 使用 defmt 日志

#![no_std]
#![no_main]

use defmt_rtt as _;
use panic_probe as _;

#[entry]
fn main() -> ! {
    defmt::info!("程序启动");

    let value = 42;
    defmt::debug!("变量值: {}", value);

    loop {}
}

🔬 19.8.2 硬件调试

# 使用 OpenOCD 和 GDB
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg

# 另一终端
arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/embedded_hello
(gdb) target remote :3333
(gdb) load
(gdb) break main
(gdb) continue

📝 本章小结

本章我们学习了嵌入式 Rust:

概念说明
no_std禁用标准库
HAL硬件抽象层
PAC外设访问 crate
embedded-hal通用 trait 定义

关键资源:


动手实验

  1. 使用 QEMU 模拟器运行一个简单的程序。
  2. 实现一个基于 HAL 的 LED 驱动,可以在不同硬件上运行。
  3. 使用 defmt 添加日志输出。
  4. 编写可以在 host 和 target 上运行的测试。
← 返回目录