it编程 > 编程语言 > rust

Rust嵌入式开发入门教程:从裸机编程到RTOS实战项目详解

92人参与 • 2026-10-02 • rust

一、学习目标与重点

1.1 学习目标

  1. 理解嵌入式开发基础:深入掌握嵌入式系统的定义、特点、架构(arm、risc-v),对比rust与传统嵌入式开发语言(c/c++)的优势
  2. 搭建rust嵌入式开发环境:安装交叉编译工具链(arm-none-eabi、riscv64-unknown-elf)、调试工具(openocd、gdb),配置vs code/clion开发环境
  3. 掌握rust裸机编程:使用cortex-m、cortex-m-rt库进行arm裸机开发,实现gpio操作、串口通信、中断处理
  4. 学习rtos开发:使用rtic(real-time interrupt-driven concurrency)实现多任务编程,理解任务调度、资源共享、中断管理
  5. 实战嵌入式项目:结合stm32f4xx系列开发板、raspberry pi pico,实现led闪烁、温度传感器数据读取、i2c/spi通信、电机控制
  6. 优化与调试:学习rust嵌入式代码的优化方法,使用gdb/openocd进行硬件调试,解决内存泄漏、栈溢出等问题

1.2 学习重点

💡 三大核心难点:

  1. 内存管理:理解嵌入式系统的内存架构(flash、ram、寄存器),使用core、alloc库管理内存,避免内存泄漏
  2. 中断处理:掌握arm cortex-m的中断向量表、中断优先级,使用cortex-m库实现中断函数和临界区保护
  3. rtos调度:深入理解rtic的任务调度机制(优先级调度、时间片轮询),解决资源共享时的互斥问题

⚠️ 三大高频错误点:

  1. 栈溢出:未正确设置栈大小,导致程序崩溃
  2. 寄存器未初始化:未正确初始化硬件寄存器,导致功能无法正常实现
  3. 时序问题:在i2c/spi通信时,未正确处理时序,导致数据传输错误

二、嵌入式开发基础

2.1 嵌入式系统的定义与特点

嵌入式系统是一种以应用为中心、以计算机技术为基础、软件硬件可裁剪、适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。嵌入式系统的核心特点是:

  1. 资源受限:内存(ram)、存储空间(flash)、处理能力(cpu)有限
  2. 实时性要求高:对响应时间有严格要求(硬实时、软实时)
  3. 低功耗:通常使用电池供电,需要优化功耗
  4. 可靠性高:需要长时间稳定运行,无人工干预
  5. 专用性强:针对特定应用场景设计

2.2 嵌入式系统架构

嵌入式系统的架构通常分为以下几种:

  1. 冯·诺依曼架构:程序和数据存放在同一内存空间,如arm cortex-m0
  2. 哈佛架构:程序和数据存放在不同的内存空间,如arm cortex-m4、risc-v

2.3 rust在嵌入式开发中的优势

rust作为一门系统编程语言,非常适合嵌入式开发,其优势包括:

  1. 内存安全:通过所有权、借用、生命周期机制,在编译时检查内存访问错误,避免悬挂指针、内存泄漏
  2. 无垃圾回收:不需要垃圾回收器,减少内存开销和不确定性
  3. 高性能:执行速度接近c/c++,适合实时性要求高的应用
  4. 类型安全:强类型系统,编译时检查类型错误
  5. 现代语言特性:支持泛型、模式匹配、异步编程等,提高开发效率

三、开发环境搭建

3.1 安装rust交叉编译工具链

使用rustup安装arm和risc-v的交叉编译工具链:

# 安装arm cortex-m0/m3/m4的交叉编译工具链
rustup target add thumbv6m-none-eabi
rustup target add thumbv7m-none-eabi
rustup target add thumbv7em-none-eabi
rustup target add thumbv8m.base-none-eabi
rustup target add thumbv8m.main-none-eabi
rustup target add thumbv8m.main-none-eabihf

# 安装risc-v的交叉编译工具链
rustup target add riscv32imc-unknown-none-elf
rustup target add riscv64gc-unknown-none-elf

3.2 安装调试工具

3.2.1 安装openocd

openocd是开源的调试器和编程器,支持多种调试接口(jtag、swd)。

⌨️ ubuntu/debian安装openocd:

sudo apt-get install openocd

⌨️ windows安装openocd:
下载openocd的windows版本,解压后添加到系统path中。

3.2.2 安装gdb

gdb是gnu调试器,支持arm和risc-v架构的调试。

⌨️ ubuntu/debian安装arm gdb:

sudo apt-get install gdb-multiarch

⌨️ windows安装arm gdb:
下载arm gdb的windows版本,解压后添加到系统path中。

3.3 配置vs code开发环境

安装以下vs code扩展:

  1. rust analyzer:rust语言支持
  2. cortex-debug:arm cortex-m调试支持
  3. openocd:openocd调试支持

四、rust裸机编程基础

4.1 创建裸机项目

使用cargo generate创建一个新的rust裸机项目:

# 安装cargo generate
cargo install cargo-generate

# 使用cortex-m-quickstart模板创建项目
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart --name stm32f411re-demo

4.2 项目配置

4.2.1 cargo.toml配置

在cargo.toml中添加依赖:

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"

[dependencies.stm32f4xx-hal]
version = "0.18"
features = ["stm32f411", "rt"]

4.2.2 memory.x配置

在memory.x文件中配置flash和ram的地址和大小:

/* linker script for stm32f411re nucleo */
memory
{
    flash : origin = 0x08000000, length = 512k
    ram : origin = 0x20000000, length = 128k
}

4.3 编写裸机代码

4.3.1 led闪烁

在src/main.rs文件中编写led闪烁的代码:

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::gpio::*;
use stm32f4xx_hal::pac;
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::timer::*;

#[entry]
fn main() -> ! {
    // 获取硬件抽象层(hal)
    let dp = pac::peripherals::take().unwrap();
    let cp = cortex_m::peripherals::take().unwrap();

    // 配置系统时钟
    let rcc = dp.rcc.constrain();
    let clocks = rcc.cfgr.sysclk(84.mhz()).freeze();

    // 配置gpio
    let gpioc = dp.gpioc.split();
    let mut led = gpioc.pc13.into_push_pull_output();

    // 配置定时器
    let mut timer = dp.tim2.timer(&clocks).start_ticker();

    // 配置系统定时器(systick)
    let mut systick = cp.syst.counter_us(&clocks);

    // led闪烁
    loop {
        led.toggle();
        systick.delay(500_000); // 延迟500ms
    }
}

4.3.2 串口通信

在src/main.rs文件中添加串口通信的代码:

use stm32f4xx_hal::serial::*;

#[entry]
fn main() -> ! {
    // 获取硬件抽象层(hal)
    let dp = pac::peripherals::take().unwrap();
    let cp = cortex_m::peripherals::take().unwrap();

    // 配置系统时钟
    let rcc = dp.rcc.constrain();
    let clocks = rcc.cfgr.sysclk(84.mhz()).freeze();

    // 配置gpio
    let gpioa = dp.gpioa.split();
    let tx = gpioa.pa2.into_alternate();
    let rx = gpioa.pa3.into_alternate();

    // 配置串口
    let mut serial = dp.usart2.serial((tx, rx), 9600.bd(), &clocks).unwrap();
    serial.listen(event::rxne);

    // 发送数据
    serial.write_str("hello, rust embedded!\n").unwrap();

    // 接收数据
    loop {
        if let ok(byte) = nb::block!(serial.read()) {
            serial.write(byte).unwrap();
        }
    }
}

4.4 编译与烧录

4.4.1 编译

使用cargo build命令编译项目:

# 编译为arm cortex-m4架构
cargo build --target thumbv7em-none-eabihf --release

4.4.2 烧录

使用openocd烧录程序:

# 连接开发板
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

# 在另一个终端中使用gdb烧录
gdb-multiarch target/thumbv7em-none-eabihf/release/stm32f411re-demo
(gdb) target remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset run

五、rtos开发

5.1 rtic简介

rtic(real-time interrupt-driven concurrency)是rust社区开发的实时操作系统框架,它基于中断驱动的并发模型,支持:

  1. 任务调度:优先级调度、时间片轮询
  2. 资源共享:通过resource宏实现互斥访问
  3. 中断管理:通过interrupt宏实现中断函数
  4. 通信机制:通过channel宏实现任务间通信

5.2 创建rtic项目

使用cargo generate创建一个新的rtic项目:

cargo generate --git https://github.com/rtic-rs/cortex-m-quickstart --name rtic-demo

5.3 编写rtic代码

5.3.1 led闪烁与串口通信

在src/main.rs文件中编写rtic代码:

#![no_std]
#![no_main]

use panic_halt as _;
use rtic::app;
use stm32f4xx_hal::gpio::*;
use stm32f4xx_hal::pac;
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::serial::*;
use stm32f4xx_hal::timer::*;

#[app(device = stm32f4xx_hal::pac, peripherals = true)]
const app: () = {
    struct resources {
        led: gpioc::pc13<output<pushpull>>,
        timer: timer<pac::tim2>,
        serial: serial<pac::usart2, (gpioa::pa2<alternate<af7>>, gpioa::pa3<alternate<af7>>), 9600>,
    }

    #[init]
    fn init(cx: init::context) -> init::lateresources {
        // 获取硬件抽象层(hal)
        let dp = cx.device;
        let cp = cx.core;

        // 配置系统时钟
        let rcc = dp.rcc.constrain();
        let clocks = rcc.cfgr.sysclk(84.mhz()).freeze();

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

        let gpioa = dp.gpioa.split();
        let tx = gpioa.pa2.into_alternate();
        let rx = gpioa.pa3.into_alternate();

        // 配置串口
        let serial = dp.usart2.serial((tx, rx), 9600.bd(), &clocks).unwrap();
        serial.listen(event::rxne);

        // 配置定时器
        let timer = dp.tim2.timer(&clocks).start_ticker();

        // 发送初始化信息
        serial.write_str("rtic demo initialized!\n").unwrap();

        init::lateresources { led, timer, serial }
    }

    #[task(binds = tim2, resources = [led, timer])]
    fn timer_interrupt(cx: timer_interrupt::context) {
        // 清除定时器中断标志
        cx.resources.timer.clear_interrupt(timerinterrupt::update);

        // 切换led状态
        cx.resources.led.toggle();
    }

    #[task(binds = usart2, resources = [serial])]
    fn serial_interrupt(cx: serial_interrupt::context) {
        // 清除串口中断标志
        cx.resources.serial.clear_interrupt(event::rxne);

        // 接收数据
        if let ok(byte) = nb::block!(cx.resources.serial.read()) {
            // 发送数据
            cx.resources.serial.write(byte).unwrap();
        }
    }
};

六、真实案例应用

6.1 案例1:stm32f411re nucleo板——温湿度传感器数据读取

💡 场景分析:需要使用stm32f411re nucleo板连接dht11温湿度传感器,读取温湿度数据,并通过串口发送到电脑。

6.1.1 硬件连接

stm32f411redht11
5vvcc
gndgnd
pb12data

6.1.2 编写代码

在src/main.rs文件中添加温湿度传感器数据读取的代码:

#![no_std]
#![no_main]

use panic_halt as _;
use rtic::app;
use stm32f4xx_hal::gpio::*;
use stm32f4xx_hal::pac;
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::serial::*;
use stm32f4xx_hal::timer::*;

// 定义dht11传感器类型
struct dht11 {
    pin: gpiob::pb12<input<floating>>,
}

impl dht11 {
    // 初始化dht11传感器
    fn new(pin: gpiob::pb12<input<floating>>) -> self {
        dht11 { pin }
    }

    // 读取温湿度数据
    fn read(&mut self) -> option<(u8, u8)> {
        let mut data: [u8; 5] = [0; 5];

        // 发送启动信号
        let mut pin = self.pin.into_push_pull_output();
        pin.set_low();
        cortex_m::asm::delay(18000); // 延迟18ms
        pin.set_high();
        cortex_m::asm::delay(30); // 延迟30us

        // 等待dht11响应
        let pin = pin.into_floating_input();
        while pin.is_high() {}
        while pin.is_low() {}
        while pin.is_high() {}

        // 读取数据
        for i in 0..5 {
            let mut byte = 0;
            for j in 0..8 {
                while pin.is_low() {}
                cortex_m::asm::delay(40); // 延迟40us
                if pin.is_high() {
                    byte |= 1 << (7 - j);
                }
                while pin.is_high() {}
            }
            data[i] = byte;
        }

        // 校验数据
        let checksum = data[0] + data[1] + data[2] + data[3];
        if data[4] == checksum {
            some((data[0], data[2]))
        } else {
            none
        }
    }
}

#[app(device = stm32f4xx_hal::pac, peripherals = true)]
const app: () = {
    struct resources {
        dht11: dht11,
        serial: serial<pac::usart2, (gpioa::pa2<alternate<af7>>, gpioa::pa3<alternate<af7>>), 9600>,
        timer: timer<pac::tim3>,
    }

    #[init]
    fn init(cx: init::context) -> init::lateresources {
        // 获取硬件抽象层(hal)
        let dp = cx.device;
        let cp = cx.core;

        // 配置系统时钟
        let rcc = dp.rcc.constrain();
        let clocks = rcc.cfgr.sysclk(84.mhz()).freeze();

        // 配置gpio
        let gpiob = dp.gpiob.split();
        let dht11 = dht11::new(gpiob.pb12.into_floating_input());

        let gpioa = dp.gpioa.split();
        let tx = gpioa.pa2.into_alternate();
        let rx = gpioa.pa3.into_alternate();

        // 配置串口
        let serial = dp.usart2.serial((tx, rx), 9600.bd(), &clocks).unwrap();
        serial.listen(event::rxne);

        // 配置定时器
        let timer = dp.tim3.timer(&clocks).start_ticker();

        init::lateresources { dht11, serial, timer }
    }

    #[task(binds = tim3, resources = [dht11, serial])]
    fn timer_interrupt(cx: timer_interrupt::context) {
        // 清除定时器中断标志
        cx.resources.timer.clear_interrupt(timerinterrupt::update);

        // 读取温湿度数据
        if let some((temperature, humidity)) = cx.resources.dht11.read() {
            // 发送温湿度数据
            let message = format!("温度: {}°c, 湿度: {}%\n", temperature, humidity);
            cx.resources.serial.write_str(&message).unwrap();
        } else {
            cx.resources.serial.write_str("读取温湿度数据失败\n").unwrap();
        }
    }

    #[task(binds = usart2, resources = [serial])]
    fn serial_interrupt(cx: serial_interrupt::context) {
        // 清除串口中断标志
        cx.resources.serial.clear_interrupt(event::rxne);

        // 接收数据
        if let ok(byte) = nb::block!(cx.resources.serial.read()) {
            // 发送数据
            cx.resources.serial.write(byte).unwrap();
        }
    }
};

七、常见问题与解决方案

7.1 栈溢出

问题现象:程序崩溃,调试器显示“hardfault”。

解决方案:

  1. 增大栈大小:在memory.x文件中修改ram的大小或调整链接器脚本
  2. 减少栈使用:避免在中断函数中使用大量局部变量,避免递归调用
  3. 使用堆分配:对于大的数据结构,使用alloc库进行堆分配

7.2 寄存器未初始化

问题现象:硬件功能无法正常实现。

解决方案:

  1. 确保所有相关的寄存器都已正确初始化
  2. 查看芯片的参考手册,了解寄存器的功能和设置方法
  3. 使用hal库提供的函数,避免直接操作寄存器

7.3 时序问题

问题现象:i2c/spi通信时,数据传输错误。

解决方案:

  1. 调整通信频率:降低i2c/spi的通信频率
  2. 检查时钟配置:确保系统时钟和外设时钟配置正确
  3. 使用硬件中断:使用硬件中断代替软件延时,提高时序精度

八、总结

✅ 理解了嵌入式开发基础:深入掌握了嵌入式系统的定义、特点、架构,对比了rust与传统嵌入式开发语言的优势
✅ 搭建了rust嵌入式开发环境:安装了交叉编译工具链、调试工具,配置了vs code开发环境
✅ 掌握了rust裸机编程:使用cortex-m、cortex-m-rt库进行arm裸机开发,实现了gpio操作、串口通信、中断处理
✅ 学习了rtos开发:使用rtic实现了多任务编程,理解了任务调度、资源共享、中断管理
✅ 实战了嵌入式项目:结合stm32f4xx系列开发板,实现了led闪烁、温湿度传感器数据读取、串口通信
✅ 优化与调试:学习了rust嵌入式代码的优化方法,使用gdb/openocd进行硬件调试

到此这篇关于rust嵌入式开发入门教程:从裸机编程到rtos实战项目详解的文章就介绍到这了,更多相关rust嵌入式开发入门教程内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

赞 (0)

您想发表意见!!点此发布评论

推荐阅读

rust: Proxy Pattern个人经验分享

10-10

C 语言实用调试流程:从发现异常到定位根因

10-10

Rust suppaftp 库详解:基于 FTP 客户端实战指南

09-13

Rust+Slint 实现左右伸缩式图片浏览器源码分享(最新)

09-07

Rust语言的模块源码组织和单元测试详解

08-27

如何用Ansible部署Nginx到多台服务器?Ansible、Playbook从安装到验证实战

08-23

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论