跳转至

Rust高级嵌入式开发实战

项目概述

项目简介

本项目将构建一个**多传感器数据采集与处理系统**,综合运用Rust嵌入式开发的高级技术。系统将实现:

  • 多个传感器的并发数据采集(温湿度、光照、加速度)
  • 异步I2C/SPI通信
  • 实时数据处理和滤波
  • UART数据上报
  • 低功耗管理
  • 错误处理和恢复机制

项目特点

技术亮点: - 使用Embassy异步框架实现高效并发 - 自定义HAL抽象层设计 - 实现可复用的传感器驱动 - 集成RTIC实时中断驱动框架 - 零成本抽象和类型安全保证

学习价值: - 掌握Rust异步编程在嵌入式中的应用 - 理解HAL抽象层的设计原则 - 学会编写可移植的驱动程序 - 实践RTOS集成和任务调度 - 构建完整的嵌入式系统

学习目标

完成本项目后,你将能够:

  • 使用Embassy框架进行异步嵌入式开发
  • 设计和实现HAL抽象层
  • 编写符合embedded-hal标准的驱动程序
  • 集成和使用RTIC框架
  • 实现多任务并发和资源共享
  • 处理复杂的错误场景
  • 优化系统性能和功耗
  • 构建可维护的大型嵌入式项目

适用人群

本项目适合: - 已完成Rust嵌入式入门教程的开发者 - 有C/C++嵌入式开发经验,想转向Rust的工程师 - 希望深入学习Rust高级特性的开发者 - 需要构建复杂嵌入式系统的工程师

技术栈

硬件清单

名称 数量 规格 用途 参考价格
STM32F411CEU6开发板 1 黑色药丸板,100MHz 主控MCU ¥25
DHT22温湿度传感器 1 数字输出 环境监测 ¥15
BH1750光照传感器 1 I2C接口 光照检测 ¥5
MPU6050加速度计 1 I2C接口,6轴 运动检测 ¥8
OLED显示屏 1 0.96寸,I2C 数据显示 ¥12
ST-Link V2调试器 1 - 程序下载调试 ¥15
面包板 1 标准尺寸 电路搭建 ¥5
杜邦线 若干 公对公、公对母 连接 ¥5

总预算: 约¥90

软件要求

开发环境: - Rust 1.70+ (stable) - cargo 1.70+ - rustup

目标平台: - thumbv7em-none-eabihf (Cortex-M4F with FPU)

核心依赖

[dependencies]
# 异步运行时
embassy-executor = { version = "0.5", features = ["arch-cortex-m", "executor-thread"] }
embassy-time = { version = "0.3", features = ["tick-hz-32_768"] }
embassy-stm32 = { version = "0.1", features = ["stm32f411ce", "time-driver-any", "exti"] }

# HAL和外设
embedded-hal = "1.0"
embedded-hal-async = "1.0"

# 传感器驱动
dht-sensor = "0.2"
bh1750 = "0.1"
mpu6050 = "0.1"
ssd1306 = "0.8"

# 实用工具
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }
heapless = "0.8"

# RTIC (可选)
cortex-m-rtic = "1.1"

开发工具: - probe-rs: 烧录和调试 - defmt: 高效日志 - cargo-embed: 集成开发环境 - VS Code + rust-analyzer

系统架构

整体架构设计

graph TB
    subgraph "应用层"
        A1[数据采集任务]
        A2[数据处理任务]
        A3[显示任务]
        A4[通信任务]
    end

    subgraph "驱动层"
        D1[DHT22驱动]
        D2[BH1750驱动]
        D3[MPU6050驱动]
        D4[OLED驱动]
    end

    subgraph "HAL抽象层"
        H1[I2C抽象]
        H2[GPIO抽象]
        H3[UART抽象]
        H4[Timer抽象]
    end

    subgraph "Embassy运行时"
        E1[异步执行器]
        E2[时间驱动]
        E3[中断处理]
    end

    subgraph "硬件层"
        HW1[STM32F411]
        HW2[传感器]
        HW3[显示屏]
    end

    A1 --> D1
    A1 --> D2
    A1 --> D3
    A2 --> A3
    A3 --> D4
    A4 --> H3

    D1 --> H2
    D2 --> H1
    D3 --> H1
    D4 --> H1

    H1 --> E1
    H2 --> E1
    H3 --> E1
    H4 --> E2

    E1 --> HW1
    E2 --> HW1
    E3 --> HW1

    HW1 --> HW2
    HW1 --> HW3

模块划分

1. 应用层模块: - app::sensor: 传感器数据采集 - app::processor: 数据处理和滤波 - app::display: 显示控制 - app::comm: 通信管理

2. 驱动层模块: - drivers::dht22: DHT22温湿度驱动 - drivers::bh1750: BH1750光照驱动 - drivers::mpu6050: MPU6050加速度驱动 - drivers::ssd1306: OLED显示驱动

3. HAL抽象层: - hal::i2c: I2C总线抽象 - hal::gpio: GPIO抽象 - hal::uart: UART抽象 - hal::timer: 定时器抽象

4. 核心模块: - core::types: 公共类型定义 - core::error: 错误处理 - core::config: 配置管理

数据流设计

sequenceDiagram
    participant S as 传感器
    participant D as 驱动层
    participant P as 处理器
    participant M as 显示/通信

    loop 每1秒
        S->>D: 读取原始数据
        D->>D: 数据解析
        D->>P: 发送传感器数据
        P->>P: 数据滤波
        P->>P: 异常检测
        P->>M: 发送处理后数据
        M->>M: 更新显示
        M->>M: UART上报
    end

实现步骤

阶段1:项目搭建和基础框架 (30分钟)

步骤1.1:创建项目

# 使用cargo-generate创建Embassy项目
cargo install cargo-generate
cargo generate --git https://github.com/embassy-rs/embassy \
    --name sensor-system

cd sensor-system

步骤1.2:配置Cargo.toml

[package]
name = "sensor-system"
version = "0.1.0"
edition = "2021"

[dependencies]
# Embassy异步运行时
embassy-executor = { version = "0.5", features = ["arch-cortex-m", "executor-thread", "integrated-timers"] }
embassy-time = { version = "0.3", features = ["tick-hz-32_768"] }
embassy-stm32 = { version = "0.1", features = [
    "stm32f411ce",
    "time-driver-any",
    "exti",
    "memory-x",
] }
embassy-sync = "0.5"

# HAL traits
embedded-hal = "1.0"
embedded-hal-async = "1.0"
embedded-io-async = "0.6"

# 数据结构
heapless = "0.8"

# 日志和调试
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-defmt"] }

# Cortex-M支持
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7"

[profile.release]
opt-level = "z"          # 优化代码大小
lto = true               # 链接时优化
codegen-units = 1        # 更好的优化
debug = 2                # 保留调试信息
overflow-checks = false  # 禁用溢出检查以减小代码

步骤1.3:配置.cargo/config.toml

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs run --chip STM32F411CEUx"

[build]
target = "thumbv7em-none-eabihf"

[env]
DEFMT_LOG = "info"

步骤1.4:配置Embed.toml

[default.general]
chip = "STM32F411CEUx"

[default.rtt]
enabled = true

[default.gdb]
enabled = false

步骤1.5:创建项目结构

# 创建模块目录
mkdir -p src/{app,drivers,hal,core}

# 创建模块文件
touch src/app/{sensor,processor,display,comm}.rs
touch src/drivers/{dht22,bh1750,mpu6050,ssd1306}.rs
touch src/hal/{i2c,gpio,uart,timer}.rs
touch src/core/{types,error,config}.rs

# 创建模块声明文件
touch src/{app,drivers,hal,core}.rs

步骤1.6:定义核心类型

创建 src/core/types.rs:

use heapless::Vec;

/// 传感器数据类型
#[derive(Debug, Clone, Copy, defmt::Format)]
pub struct SensorData {
    pub timestamp: u64,
    pub temperature: f32,
    pub humidity: f32,
    pub light: u16,
    pub accel_x: i16,
    pub accel_y: i16,
    pub accel_z: i16,
}

impl SensorData {
    pub fn new() -> Self {
        Self {
            timestamp: 0,
            temperature: 0.0,
            humidity: 0.0,
            light: 0,
            accel_x: 0,
            accel_y: 0,
            accel_z: 0,
        }
    }
}

/// 数据缓冲区
pub type DataBuffer = Vec<SensorData, 10>;

/// 系统配置
#[derive(Debug, Clone, Copy)]
pub struct SystemConfig {
    pub sample_interval_ms: u32,
    pub display_update_ms: u32,
    pub uart_baud_rate: u32,
}

impl Default for SystemConfig {
    fn default() -> Self {
        Self {
            sample_interval_ms: 1000,
            display_update_ms: 500,
            uart_baud_rate: 115200,
        }
    }
}

步骤1.7:定义错误类型

创建 src/core/error.rs:

use defmt::Format;

/// 系统错误类型
#[derive(Debug, Clone, Copy, Format)]
pub enum SystemError {
    /// I2C通信错误
    I2cError,
    /// 传感器读取超时
    SensorTimeout,
    /// 数据无效
    InvalidData,
    /// 缓冲区满
    BufferFull,
    /// 配置错误
    ConfigError,
}

pub type Result<T> = core::result::Result<T, SystemError>;

阶段2:HAL抽象层实现 (40分钟)

步骤2.1:I2C抽象层设计

创建 src/hal/i2c.rs:

use embassy_stm32::i2c::{I2c, Error as I2cError};
use embassy_stm32::mode::Async;
use embassy_time::{Duration, Timer};
use embedded_hal_async::i2c::I2c as I2cTrait;

/// I2C总线抽象
pub struct I2cBus<'d, T: embassy_stm32::i2c::Instance> {
    i2c: I2c<'d, T, Async>,
}

impl<'d, T: embassy_stm32::i2c::Instance> I2cBus<'d, T> {
    pub fn new(i2c: I2c<'d, T, Async>) -> Self {
        Self { i2c }
    }

    /// 异步写入数据
    pub async fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), I2cError> {
        self.i2c.write(addr, data).await
    }

    /// 异步读取数据
    pub async fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> {
        self.i2c.read(addr, buffer).await
    }

    /// 异步写入后读取
    pub async fn write_read(
        &mut self,
        addr: u8,
        write_data: &[u8],
        read_buffer: &mut [u8],
    ) -> Result<(), I2cError> {
        self.i2c.write_read(addr, write_data, read_buffer).await
    }

    /// 带超时的读取
    pub async fn read_with_timeout(
        &mut self,
        addr: u8,
        buffer: &mut [u8],
        timeout_ms: u64,
    ) -> Result<(), I2cError> {
        embassy_time::with_timeout(
            Duration::from_millis(timeout_ms),
            self.read(addr, buffer)
        )
        .await
        .map_err(|_| I2cError::Timeout)?
    }
}

// 实现embedded-hal-async的I2c trait
impl<'d, T: embassy_stm32::i2c::Instance> I2cTrait for I2cBus<'d, T> {
    async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
        self.i2c.read(address, read).await
    }

    async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
        self.i2c.write(address, write).await
    }

    async fn write_read(
        &mut self,
        address: u8,
        write: &[u8],
        read: &mut [u8],
    ) -> Result<(), Self::Error> {
        self.i2c.write_read(address, write, read).await
    }

    async fn transaction(
        &mut self,
        address: u8,
        operations: &mut [embedded_hal_async::i2c::Operation<'_>],
    ) -> Result<(), Self::Error> {
        self.i2c.transaction(address, operations).await
    }
}

步骤2.2:GPIO抽象层

创建 src/hal/gpio.rs:

use embassy_stm32::gpio::{Output, Level, Speed};

/// GPIO输出抽象
pub struct GpioOutput<'d> {
    pin: Output<'d>,
}

impl<'d> GpioOutput<'d> {
    pub fn new(pin: Output<'d>) -> Self {
        Self { pin }
    }

    pub fn set_high(&mut self) {
        self.pin.set_high();
    }

    pub fn set_low(&mut self) {
        self.pin.set_low();
    }

    pub fn toggle(&mut self) {
        self.pin.toggle();
    }

    pub fn is_set_high(&self) -> bool {
        self.pin.is_set_high()
    }
}

阶段3:传感器驱动开发 (50分钟)

步骤3.1:BH1750光照传感器驱动

创建 src/drivers/bh1750.rs:

use crate::core::error::{Result, SystemError};
use crate::hal::i2c::I2cBus;
use embassy_time::{Duration, Timer};
use defmt::info;

const BH1750_ADDR: u8 = 0x23;
const BH1750_POWER_ON: u8 = 0x01;
const BH1750_CONTINUOUS_HIGH_RES: u8 = 0x10;

/// BH1750光照传感器驱动
pub struct Bh1750<'d, T: embassy_stm32::i2c::Instance> {
    i2c: &'d mut I2cBus<'d, T>,
}

impl<'d, T: embassy_stm32::i2c::Instance> Bh1750<'d, T> {
    /// 创建新的BH1750实例
    pub fn new(i2c: &'d mut I2cBus<'d, T>) -> Self {
        Self { i2c }
    }

    /// 初始化传感器
    pub async fn init(&mut self) -> Result<()> {
        info!("Initializing BH1750...");

        // 上电
        self.i2c
            .write(BH1750_ADDR, &[BH1750_POWER_ON])
            .await
            .map_err(|_| SystemError::I2cError)?;

        Timer::after(Duration::from_millis(10)).await;

        // 设置为连续高分辨率模式
        self.i2c
            .write(BH1750_ADDR, &[BH1750_CONTINUOUS_HIGH_RES])
            .await
            .map_err(|_| SystemError::I2cError)?;

        Timer::after(Duration::from_millis(180)).await;

        info!("BH1750 initialized");
        Ok(())
    }

    /// 读取光照强度 (单位: lux)
    pub async fn read_light(&mut self) -> Result<u16> {
        let mut buffer = [0u8; 2];

        self.i2c
            .read_with_timeout(BH1750_ADDR, &mut buffer, 200)
            .await
            .map_err(|_| SystemError::SensorTimeout)?;

        let raw = u16::from_be_bytes(buffer);
        let lux = (raw as f32 / 1.2) as u16;

        Ok(lux)
    }
}

步骤3.2:MPU6050加速度计驱动

创建 src/drivers/mpu6050.rs:

use crate::core::error::{Result, SystemError};
use crate::hal::i2c::I2cBus;
use embassy_time::{Duration, Timer};
use defmt::info;

const MPU6050_ADDR: u8 = 0x68;
const PWR_MGMT_1: u8 = 0x6B;
const ACCEL_XOUT_H: u8 = 0x3B;

/// MPU6050加速度计数据
#[derive(Debug, Clone, Copy, defmt::Format)]
pub struct AccelData {
    pub x: i16,
    pub y: i16,
    pub z: i16,
}

/// MPU6050驱动
pub struct Mpu6050<'d, T: embassy_stm32::i2c::Instance> {
    i2c: &'d mut I2cBus<'d, T>,
}

impl<'d, T: embassy_stm32::i2c::Instance> Mpu6050<'d, T> {
    pub fn new(i2c: &'d mut I2cBus<'d, T>) -> Self {
        Self { i2c }
    }

    /// 初始化传感器
    pub async fn init(&mut self) -> Result<()> {
        info!("Initializing MPU6050...");

        // 唤醒传感器
        self.write_register(PWR_MGMT_1, 0x00).await?;
        Timer::after(Duration::from_millis(100)).await;

        info!("MPU6050 initialized");
        Ok(())
    }

    /// 读取加速度数据
    pub async fn read_accel(&mut self) -> Result<AccelData> {
        let mut buffer = [0u8; 6];

        self.i2c
            .write_read(MPU6050_ADDR, &[ACCEL_XOUT_H], &mut buffer)
            .await
            .map_err(|_| SystemError::I2cError)?;

        Ok(AccelData {
            x: i16::from_be_bytes([buffer[0], buffer[1]]),
            y: i16::from_be_bytes([buffer[2], buffer[3]]),
            z: i16::from_be_bytes([buffer[4], buffer[5]]),
        })
    }

    /// 写入寄存器
    async fn write_register(&mut self, reg: u8, value: u8) -> Result<()> {
        self.i2c
            .write(MPU6050_ADDR, &[reg, value])
            .await
            .map_err(|_| SystemError::I2cError)
    }
}

阶段4:异步任务实现 (40分钟)

步骤4.1:数据采集任务

创建 src/app/sensor.rs:

use crate::core::types::SensorData;
use crate::core::error::Result;
use crate::drivers::{bh1750::Bh1750, mpu6050::Mpu6050};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::{Channel, Sender};
use embassy_time::{Duration, Timer};
use defmt::{info, warn};

/// 传感器数据通道
pub type SensorChannel = Channel<CriticalSectionRawMutex, SensorData, 5>;

/// 数据采集任务
#[embassy_executor::task]
pub async fn sensor_task(
    mut bh1750: Bh1750<'static, embassy_stm32::peripherals::I2C1>,
    mut mpu6050: Mpu6050<'static, embassy_stm32::peripherals::I2C1>,
    sender: Sender<'static, CriticalSectionRawMutex, SensorData, 5>,
) {
    info!("Sensor task started");

    // 初始化传感器
    if let Err(e) = bh1750.init().await {
        warn!("BH1750 init failed: {:?}", e);
    }

    if let Err(e) = mpu6050.init().await {
        warn!("MPU6050 init failed: {:?}", e);
    }

    let mut timestamp = 0u64;

    loop {
        let mut data = SensorData::new();
        data.timestamp = timestamp;

        // 读取光照数据
        match bh1750.read_light().await {
            Ok(light) => {
                data.light = light;
                info!("Light: {} lux", light);
            }
            Err(e) => warn!("Failed to read light: {:?}", e),
        }

        // 读取加速度数据
        match mpu6050.read_accel().await {
            Ok(accel) => {
                data.accel_x = accel.x;
                data.accel_y = accel.y;
                data.accel_z = accel.z;
                info!("Accel: x={}, y={}, z={}", accel.x, accel.y, accel.z);
            }
            Err(e) => warn!("Failed to read accel: {:?}", e),
        }

        // 发送数据到处理任务
        sender.send(data).await;

        timestamp += 1;
        Timer::after(Duration::from_millis(1000)).await;
    }
}

步骤4.2:数据处理任务

创建 src/app/processor.rs:

use crate::core::types::SensorData;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::{Channel, Receiver, Sender};
use heapless::Vec;
use defmt::info;

/// 滑动平均滤波器
struct MovingAverageFilter<const N: usize> {
    buffer: Vec<i16, N>,
    index: usize,
}

impl<const N: usize> MovingAverageFilter<N> {
    fn new() -> Self {
        Self {
            buffer: Vec::new(),
            index: 0,
        }
    }

    fn update(&mut self, value: i16) -> i16 {
        if self.buffer.len() < N {
            self.buffer.push(value).ok();
        } else {
            self.buffer[self.index] = value;
            self.index = (self.index + 1) % N;
        }

        let sum: i32 = self.buffer.iter().map(|&x| x as i32).sum();
        (sum / self.buffer.len() as i32) as i16
    }
}

/// 数据处理任务
#[embassy_executor::task]
pub async fn processor_task(
    receiver: Receiver<'static, CriticalSectionRawMutex, SensorData, 5>,
    sender: Sender<'static, CriticalSectionRawMutex, SensorData, 5>,
) {
    info!("Processor task started");

    let mut filter_x = MovingAverageFilter::<5>::new();
    let mut filter_y = MovingAverageFilter::<5>::new();
    let mut filter_z = MovingAverageFilter::<5>::new();

    loop {
        let mut data = receiver.receive().await;

        // 应用滤波
        data.accel_x = filter_x.update(data.accel_x);
        data.accel_y = filter_y.update(data.accel_y);
        data.accel_z = filter_z.update(data.accel_z);

        // 异常检测
        if data.light > 10000 {
            info!("Warning: Light level too high!");
        }

        // 发送处理后的数据
        sender.send(data).await;
    }
}

步骤4.3:主程序集成

创建 src/main.rs:

#![no_std]
#![no_main]

use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::i2c::{I2c, Config as I2cConfig};
use embassy_stm32::time::Hertz;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::Channel;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};

mod app;
mod core;
mod drivers;
mod hal;

use app::sensor::sensor_task;
use app::processor::processor_task;
use core::types::SensorData;
use drivers::bh1750::Bh1750;
use drivers::mpu6050::Mpu6050;
use hal::i2c::I2cBus;

// 定义全局通道
static SENSOR_CHANNEL: Channel<CriticalSectionRawMutex, SensorData, 5> = Channel::new();
static PROCESSED_CHANNEL: Channel<CriticalSectionRawMutex, SensorData, 5> = Channel::new();

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    info!("System starting...");

    // 初始化外设
    let p = embassy_stm32::init(Default::default());

    // 配置LED (PC13)
    let mut led = Output::new(p.PC13, Level::High, Speed::Low);

    // 配置I2C1 (PB6: SCL, PB7: SDA)
    let i2c = I2c::new(
        p.I2C1,
        p.PB6,
        p.PB7,
        embassy_stm32::interrupt::take!(I2C1_EV),
        embassy_stm32::interrupt::take!(I2C1_ER),
        p.DMA1_CH0,
        p.DMA1_CH1,
        Hertz(100_000),
        I2cConfig::default(),
    );

    let mut i2c_bus = I2cBus::new(i2c);

    // 创建传感器驱动
    let bh1750 = Bh1750::new(&mut i2c_bus);
    let mpu6050 = Mpu6050::new(&mut i2c_bus);

    // 启动任务
    spawner.spawn(sensor_task(
        bh1750,
        mpu6050,
        SENSOR_CHANNEL.sender(),
    )).unwrap();

    spawner.spawn(processor_task(
        SENSOR_CHANNEL.receiver(),
        PROCESSED_CHANNEL.receiver(),
    )).unwrap();

    info!("All tasks spawned");

    // 主循环 - LED闪烁表示系统运行
    loop {
        led.toggle();
        Timer::after_millis(500).await;
    }
}

阶段5:RTIC集成 (可选,20分钟)

步骤5.1:RTIC版本实现

创建 src/main_rtic.rs:

#![no_std]
#![no_main]

use panic_halt as _;
use rtic::app;
use stm32f4xx_hal::{
    gpio::{Output, PushPull, gpioc::PC13},
    i2c::I2c,
    pac,
    prelude::*,
};

#[app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [EXTI0])]
mod app {
    use super::*;
    use systick_monotonic::*;

    #[shared]
    struct Shared {
        sensor_data: SensorData,
    }

    #[local]
    struct Local {
        led: PC13<Output<PushPull>>,
        i2c: I2c<pac::I2C1>,
    }

    #[monotonic(binds = SysTick, default = true)]
    type MonoTimer = Systick<1000>;

    #[init]
    fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
        let dp = cx.device;

        // 配置时钟
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(100.MHz()).freeze();

        // 配置LED
        let gpioc = dp.GPIOC.split();
        let led = gpioc.pc13.into_push_pull_output();

        // 配置I2C
        let gpiob = dp.GPIOB.split();
        let scl = gpiob.pb6.into_alternate_open_drain();
        let sda = gpiob.pb7.into_alternate_open_drain();
        let i2c = I2c::new(dp.I2C1, (scl, sda), 100.kHz(), &clocks);

        // 启动定时任务
        read_sensors::spawn_after(1.secs()).ok();
        blink_led::spawn_after(500.millis()).ok();

        let mono = Systick::new(cx.core.SYST, clocks.sysclk().to_Hz());

        (
            Shared {
                sensor_data: SensorData::new(),
            },
            Local { led, i2c },
            init::Monotonics(mono),
        )
    }

    #[task(local = [i2c], shared = [sensor_data])]
    fn read_sensors(mut cx: read_sensors::Context) {
        // 读取传感器数据
        // ...

        // 重新调度
        read_sensors::spawn_after(1.secs()).ok();
    }

    #[task(local = [led])]
    fn blink_led(cx: blink_led::Context) {
        cx.local.led.toggle();
        blink_led::spawn_after(500.millis()).ok();
    }
}

完整代码

项目仓库结构

sensor-system/
├── Cargo.toml
├── Embed.toml
├── .cargo/
│   └── config.toml
├── src/
│   ├── main.rs
│   ├── app/
│   │   ├── mod.rs
│   │   ├── sensor.rs
│   │   ├── processor.rs
│   │   ├── display.rs
│   │   └── comm.rs
│   ├── drivers/
│   │   ├── mod.rs
│   │   ├── bh1750.rs
│   │   ├── mpu6050.rs
│   │   └── ssd1306.rs
│   ├── hal/
│   │   ├── mod.rs
│   │   ├── i2c.rs
│   │   ├── gpio.rs
│   │   └── uart.rs
│   └── core/
│       ├── mod.rs
│       ├── types.rs
│       ├── error.rs
│       └── config.rs
└── README.md

模块声明文件

src/app/mod.rs:

pub mod sensor;
pub mod processor;
pub mod display;
pub mod comm;

src/drivers/mod.rs:

pub mod bh1750;
pub mod mpu6050;
pub mod ssd1306;

src/hal/mod.rs:

pub mod i2c;
pub mod gpio;
pub mod uart;

src/core/mod.rs:

pub mod types;
pub mod error;
pub mod config;

代码仓库

完整代码已上传至GitHub:

https://github.com/embedded-rust-examples/sensor-system

克隆并运行:

git clone https://github.com/embedded-rust-examples/sensor-system
cd sensor-system
cargo build --release
cargo embed

测试验证

测试步骤1:硬件连接

I2C设备连接

STM32F411    BH1750    MPU6050    OLED
-----------------------------------------
PB6 (SCL) -> SCL   -> SCL     -> SCL
PB7 (SDA) -> SDA   -> SDA     -> SDA
3.3V      -> VCC   -> VCC     -> VCC
GND       -> GND   -> GND     -> GND

LED连接: - PC13: 板载LED(无需外接)

测试步骤2:编译和烧录

# 编译项目
cargo build --release

# 查看程序大小
cargo size --release

# 烧录到开发板
cargo embed --release

预期输出

Finished release [optimized] target(s) in 12.34s
   text    data     bss     dec     hex filename
  15234     128    2048   17410    4402 sensor-system

Flashing...
████████████████████ 100% Done
Finished in 2.1s

测试步骤3:查看日志输出

使用defmt-rtt查看日志:

# 在另一个终端运行
probe-rs attach --chip STM32F411CEUx

预期日志

INFO  System starting...
INFO  Initializing BH1750...
INFO  BH1750 initialized
INFO  Initializing MPU6050...
INFO  MPU6050 initialized
INFO  Sensor task started
INFO  Processor task started
INFO  All tasks spawned
INFO  Light: 245 lux
INFO  Accel: x=128, y=-64, z=16384
INFO  Light: 248 lux
INFO  Accel: x=130, y=-62, z=16380

测试步骤4:功能验证

验证清单: - [ ] LED以0.5秒间隔闪烁 - [ ] 光照传感器数据正常(0-65535 lux) - [ ] 加速度计数据正常(静止时Z轴约16384) - [ ] 数据采集间隔为1秒 - [ ] 滤波器正常工作(数据平滑) - [ ] 无I2C通信错误 - [ ] 系统稳定运行

测试步骤5:性能测试

测量指标

# 测量CPU使用率
# 在main.rs中添加:
use cortex_m::peripheral::DWT;

// 在init中启用DWT
let mut dwt = cx.core.DWT;
dwt.enable_cycle_counter();

// 在任务中测量
let start = DWT::cycle_count();
// ... 执行任务 ...
let end = DWT::cycle_count();
let cycles = end - start;
info!("Task took {} cycles", cycles);

预期性能: - I2C读取延迟: < 5ms - 任务切换开销: < 100 cycles - 内存使用: < 10KB RAM - CPU空闲率: > 90%

扩展思路

扩展1:添加OLED显示

// 在src/drivers/ssd1306.rs中实现
use embedded_graphics::{
    mono_font::{ascii::FONT_6X10, MonoTextStyle},
    pixelcolor::BinaryColor,
    prelude::*,
    text::Text,
};
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};

pub struct Display<'d, T: embassy_stm32::i2c::Instance> {
    display: Ssd1306<
        I2CInterface<I2cBus<'d, T>>,
        DisplaySize128x64,
        BufferedGraphicsMode<DisplaySize128x64>,
    >,
}

impl<'d, T: embassy_stm32::i2c::Instance> Display<'d, T> {
    pub async fn show_data(&mut self, data: &SensorData) -> Result<()> {
        self.display.clear();

        let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);

        Text::new(
            &format!("Temp: {:.1}°C", data.temperature),
            Point::new(0, 10),
            style,
        )
        .draw(&mut self.display)
        .ok();

        Text::new(
            &format!("Light: {} lux", data.light),
            Point::new(0, 25),
            style,
        )
        .draw(&mut self.display)
        .ok();

        self.display.flush().await.map_err(|_| SystemError::I2cError)?;
        Ok(())
    }
}

扩展2:添加UART数据上报

// 在src/app/comm.rs中实现
use embassy_stm32::usart::{Uart, Config};
use core::fmt::Write;

#[embassy_executor::task]
pub async fn uart_task(
    mut uart: Uart<'static, embassy_stm32::peripherals::USART1, Async>,
    receiver: Receiver<'static, CriticalSectionRawMutex, SensorData, 5>,
) {
    info!("UART task started");

    loop {
        let data = receiver.receive().await;

        // 格式化JSON数据
        let json = format!(
            "{{\"ts\":{},\"temp\":{:.1},\"hum\":{:.1},\"light\":{}}}\r\n",
            data.timestamp,
            data.temperature,
            data.humidity,
            data.light
        );

        uart.write(json.as_bytes()).await.ok();
    }
}

扩展3:添加低功耗模式

use embassy_stm32::low_power::{Executor, stop_with_rtc};

#[embassy_executor::task]
pub async fn power_management_task() {
    loop {
        // 检查是否可以进入低功耗模式
        if can_enter_low_power() {
            info!("Entering low power mode");

            // 进入STOP模式
            stop_with_rtc(|rtc| {
                rtc.set_wakeup_timer(Duration::from_secs(10));
            });

            info!("Woke up from low power mode");
        }

        Timer::after(Duration::from_secs(1)).await;
    }
}

扩展4:添加数据存储

use embassy_stm32::flash::Flash;

pub struct DataLogger<'d> {
    flash: Flash<'d>,
    write_addr: u32,
}

impl<'d> DataLogger<'d> {
    const FLASH_START: u32 = 0x0801_0000;
    const FLASH_SIZE: u32 = 64 * 1024;

    pub async fn log_data(&mut self, data: &SensorData) -> Result<()> {
        let bytes = unsafe {
            core::slice::from_raw_parts(
                data as *const _ as *const u8,
                core::mem::size_of::<SensorData>(),
            )
        };

        self.flash.write(self.write_addr, bytes).await
            .map_err(|_| SystemError::ConfigError)?;

        self.write_addr += bytes.len() as u32;

        if self.write_addr >= Self::FLASH_START + Self::FLASH_SIZE {
            self.write_addr = Self::FLASH_START;
        }

        Ok(())
    }
}

扩展5:添加无线通信

// 使用ESP8266或nRF24L01模块
use embassy_stm32::spi::{Spi, Config as SpiConfig};

pub struct WirelessModule<'d> {
    spi: Spi<'d, embassy_stm32::peripherals::SPI1, Async>,
    cs: Output<'d>,
}

impl<'d> WirelessModule<'d> {
    pub async fn send_data(&mut self, data: &SensorData) -> Result<()> {
        self.cs.set_low();

        // 发送数据
        let bytes = unsafe {
            core::slice::from_raw_parts(
                data as *const _ as *const u8,
                core::mem::size_of::<SensorData>(),
            )
        };

        self.spi.write(bytes).await
            .map_err(|_| SystemError::I2cError)?;

        self.cs.set_high();
        Ok(())
    }
}

总结

项目总结

通过本项目,我们构建了一个完整的多传感器数据采集系统,展示了Rust在嵌入式开发中的强大能力:

技术成果: - ✅ 实现了基于Embassy的异步并发系统 - ✅ 设计了可复用的HAL抽象层 - ✅ 开发了符合embedded-hal标准的驱动 - ✅ 实现了多任务协作和数据流处理 - ✅ 应用了滤波算法和异常检测 - ✅ 构建了可维护的模块化架构

关键技术点: 1. 异步编程: 使用Embassy实现高效的并发任务 2. HAL抽象: 设计可移植的硬件抽象层 3. 驱动开发: 编写符合标准的传感器驱动 4. 错误处理: 实现完善的错误处理机制 5. 类型安全: 利用Rust类型系统保证安全性 6. 零成本抽象: 高级特性无运行时开销

学到的技能

完成本项目后,你已经掌握:

Rust高级特性: - ✅ 异步/等待语法和Future - ✅ 生命周期和借用检查 - ✅ trait和泛型编程 - ✅ 宏和元编程 - ✅ 错误处理最佳实践

嵌入式开发: - ✅ Embassy异步框架 - ✅ HAL抽象层设计 - ✅ I2C/SPI通信协议 - ✅ 中断和DMA - ✅ 低功耗管理

软件工程: - ✅ 模块化架构设计 - ✅ 接口抽象和解耦 - ✅ 代码复用和可维护性 - ✅ 测试和调试方法 - ✅ 文档和注释规范

技术难点

难点1:异步编程理解 - Future和Poll机制 - 异步运行时原理 - 任务调度和优先级

难点2:生命周期管理 - 静态生命周期的使用 - 引用和所有权转移 - 跨任务的数据共享

难点3:HAL抽象设计 - trait设计原则 - 泛型约束 - 零成本抽象实现

难点4:错误处理 - Result类型的传播 - 错误恢复策略 - 异步错误处理

性能优化建议

1. 编译优化

[profile.release]
opt-level = "z"          # 优化代码大小
lto = "fat"              # 完整LTO
codegen-units = 1        # 单个代码生成单元
strip = true             # 移除符号

2. 内存优化: - 使用heapless避免堆分配 - 合理设置缓冲区大小 - 使用#[inline]减少函数调用开销

3. 功耗优化: - 使用WFI指令等待中断 - 降低时钟频率 - 关闭未使用的外设

4. 通信优化: - 使用DMA减少CPU占用 - 批量传输数据 - 优化I2C时钟频率

下一步学习建议

深入Rust: 1. 学习高级trait和关联类型 2. 掌握宏编程和过程宏 3. 理解unsafe Rust和FFI 4. 学习并发原语和同步机制

扩展嵌入式知识: 1. 学习更多通信协议(CAN、USB、Ethernet) 2. 实现复杂的控制算法 3. 集成实时操作系统 4. 学习功耗管理和优化

实践项目: 1. 智能家居控制系统 2. 无人机飞控系统 3. 工业数据采集网关 4. 可穿戴健康监测设备

参与社区: 1. 为embedded-hal贡献代码 2. 开发开源驱动库 3. 分享项目经验 4. 参与Rust嵌入式工作组

延伸阅读

官方文档

进阶主题

相关教程

建议继续学习: - Rust嵌入式开发入门 - 基础教程 - 多语言混合编程实践 - Rust与C互操作 - 嵌入式系统架构设计 - 架构设计

开源项目

学习资源: - awesome-embedded-rust - 资源列表 - embassy-examples - Embassy示例 - stm32-rs - STM32 Rust生态

参考项目: - drone-core - 嵌入式操作系统 - embedded-graphics - 图形库 - smoltcp - TCP/IP协议栈

社区资源

常见问题

Q1: Embassy和RTIC应该选择哪个?

A: 两者各有优势:

Embassy: - 优点:真正的异步/等待,代码更直观,生态更现代 - 缺点:相对较新,文档较少 - 适用:新项目,需要复杂异步逻辑

RTIC: - 优点:成熟稳定,零开销,硬实时保证 - 缺点:学习曲线陡峭,代码结构固定 - 适用:实时性要求高,资源受限

Q2: 如何调试异步代码?

A: 调试技巧: 1. 使用defmt日志输出 2. 使用probe-rs的RTT功能 3. 添加任务状态监控 4. 使用GDB断点调试 5. 分析任务调度时序

Q3: 如何处理I2C总线冲突?

A: 解决方案: 1. 使用Mutex保护I2C总线 2. 使用embassy-sync的Mutex 3. 设计总线仲裁机制 4. 使用I2C多路复用器

Q4: 如何优化程序大小?

A: 优化方法: 1. 启用LTO和优化选项 2. 移除未使用的代码 3. 使用panic-halt而非panic-probe 4. 减少泛型实例化 5. 使用#[inline(never)]减少代码膨胀

Q5: 如何实现固件升级?

A: 实现方案: 1. 使用Bootloader 2. 实现双区固件 3. 添加固件校验 4. 支持回滚机制 5. 使用OTA更新


版权声明: 本项目由嵌入式知识平台创作,采用MIT许可协议。

反馈与改进: 如发现错误或有改进建议,请通过GitHub Issues提交。

最后更新: 2026-03-10

项目难度: ⭐⭐⭐⭐⭐ (高级)

预计完成时间: 3-4小时

推荐学习路径: Rust基础 → Rust嵌入式入门 → 本项目 → 实际应用开发