Rust嵌入式开发实战——从ARM裸机编程到RTOS应用

Rust嵌入式开发实战——从ARM裸机编程到RTOS应用

Rust嵌入式开发实战——从ARM裸机编程到RTOS应用

在这里插入图片描述

一、学习目标与重点

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-mcortex-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、寄存器),使用corealloc库管理内存,避免内存泄漏
  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

sudoapt-getinstall openocd 

⌨️ Windows安装OpenOCD
下载OpenOCD的Windows版本,解压后添加到系统PATH中。

3.2.2 安装GDB

GDB是GNU调试器,支持ARM和RISC-V架构的调试。

⌨️ Ubuntu/Debian安装ARM GDB

sudoapt-getinstall 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]usecortex_m_rt::entry;use panic_halt as _;usestm32f4xx_hal::gpio::*;usestm32f4xx_hal::pac;usestm32f4xx_hal::prelude::*;usestm32f4xx_hal::timer::*;#[entry]fnmain()->!{// 获取硬件抽象层(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();// 配置GPIOlet gpioc = dp.GPIOC.split();letmut led = gpioc.pc13.into_push_pull_output();// 配置定时器letmut timer = dp.TIM2.timer(&clocks).start_ticker();// 配置系统定时器(SysTick)letmut systick = cp.SYST.counter_us(&clocks);// LED闪烁loop{ led.toggle(); systick.delay(500_000);// 延迟500ms}}
4.3.2 串口通信

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

usestm32f4xx_hal::serial::*;#[entry]fnmain()->!{// 获取硬件抽象层(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();// 配置GPIOlet gpioa = dp.GPIOA.split();let tx = gpioa.pa2.into_alternate();let rx = gpioa.pa3.into_alternate();// 配置串口letmut serial = dp.USART2.serial((tx, rx),9600.Bd(),&clocks).unwrap(); serial.listen(Event::Rxne);// 发送数据 serial.write_str("Hello, Rust Embedded!\n").unwrap();// 接收数据loop{ifletOk(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 _;usertic::app;usestm32f4xx_hal::gpio::*;usestm32f4xx_hal::pac;usestm32f4xx_hal::prelude::*;usestm32f4xx_hal::serial::*;usestm32f4xx_hal::timer::*;#[app(device = stm32f4xx_hal::pac, peripherals = true)]constAPP:()={structResources{ led:gpioc::PC13<Output<PushPull>>, timer:Timer<pac::TIM2>, serial:Serial<pac::USART2,(gpioa::PA2<Alternate<AF7>>,gpioa::PA3<Alternate<AF7>>),9600>,}#[init]fninit(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();// 配置GPIOlet 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])]fntimer_interrupt(cx:timer_interrupt::Context){// 清除定时器中断标志 cx.resources.timer.clear_interrupt(TimerInterrupt::Update);// 切换LED状态 cx.resources.led.toggle();} #[task(binds =USART2, resources =[serial])]fnserial_interrupt(cx:serial_interrupt::Context){// 清除串口中断标志 cx.resources.serial.clear_interrupt(Event::Rxne);// 接收数据ifletOk(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 _;usertic::app;usestm32f4xx_hal::gpio::*;usestm32f4xx_hal::pac;usestm32f4xx_hal::prelude::*;usestm32f4xx_hal::serial::*;usestm32f4xx_hal::timer::*;// 定义DHT11传感器类型structDHT11{ pin:gpiob::PB12<Input<Floating>>,}implDHT11{// 初始化DHT11传感器fnnew(pin:gpiob::PB12<Input<Floating>>)->Self{DHT11{ pin }}// 读取温湿度数据fnread(&mutself)->Option<(u8,u8)>{letmut data:[u8;5]=[0;5];// 发送启动信号letmut 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 in0..5{letmut byte =0;for j in0..8{while pin.is_low(){}cortex_m::asm::delay(40);// 延迟40usif 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)]constAPP:()={structResources{ dht11:DHT11, serial:Serial<pac::USART2,(gpioa::PA2<Alternate<AF7>>,gpioa::PA3<Alternate<AF7>>),9600>, timer:Timer<pac::TIM3>,}#[init]fninit(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();// 配置GPIOlet 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])]fntimer_interrupt(cx:timer_interrupt::Context){// 清除定时器中断标志 cx.resources.timer.clear_interrupt(TimerInterrupt::Update);// 读取温湿度数据ifletSome((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])]fnserial_interrupt(cx:serial_interrupt::Context){// 清除串口中断标志 cx.resources.serial.clear_interrupt(Event::Rxne);// 接收数据ifletOk(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. 使用硬件中断:使用硬件中断代替软件延时,提高时序精度

八、总结与展望

8.1 总结

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

8.2 展望

下一篇文章,我们将深入学习Rust的区块链开发,包括理解区块链的核心概念、Rust在区块链领域的优势、开发智能合约、实现区块链节点、编写DApp应用,通过这些知识我们将能够使用Rust开发高性能、安全的区块链应用。

Read more

【CANN】Pi0机器人大模型 × 昇腾A2 测评

【CANN】Pi0机器人大模型 × 昇腾A2 测评

【CANN】Pi0机器人大模型 × 昇腾A2 测评 * 写在最前面 🌈你好呀!我是 是Yu欸🚀 感谢你的陪伴与支持~ 欢迎添加文末好友🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*) 写在最前面 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。 Pi0机器人VLA大模型测评 哈喽大家好呀!我是 是Yu欸。 最近人形机器人和具身智能真的太火了,大家都在聊 Pi0、聊 VLA 大模型。但是,兄弟们,不管是搞科研还是做落地,咱们始终绕不开一个问题——算力。 今天,我们一起把当下最火的 Pi0 机器人视觉-语言-动作大模型,完完整整地部署在国产算力平台上,也就是华为的昇腾 Atlas 800I A2 服务器上。 在跑通仓库模型的基础上,我们做一次性能测评。 我们要测三个最核心的指标:

By Ne0inhk
【MySQL】视图

【MySQL】视图

目录 一. 视图 1.1 什么是视图 1.2 创建视图  1.3 修改数据  1.4 删除视图 1.5 视图的优点 二. 用户  2.1 查看用户 2.2 创建用户  2.3 修改密码  2.4 删除用户 三. 权限  3.1 查看当前用户权限 3.2 添加权限  3.3 回收权限 一. 视图 上期我们学习了联合查询,但是往往联合查询语句是很复杂的,当我们需要多次使用同一个语句进行查询时,就会显得非常麻烦,所以我们引入的视图来对复杂的SQL语句进行封装,

By Ne0inhk
MCP是什么?让AI每次少写100行爬虫代码

MCP是什么?让AI每次少写100行爬虫代码

MCP是什么?让AI每次少写100行爬虫代码 * 写在最前面 * 方法概述 * 关键观察 * 结语 🌈你好呀!我是 是Yu欸🚀 感谢你的陪伴与支持~ 欢迎添加文末好友🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*) 写在最前面 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。 在数据驱动的产品与分析场景中,如何以最小的维护成本稳定抓取目标站点数据,是常见的技术与采购决策问题。本次测评选择典型的商品详情页作为测试目标,关注点包括抓取成功率、输出结构化程度、以及将抓取结果用于后续清洗和导出的效率。 MCP是什么?让AI每次少写100行爬虫代码 亮数据在以下两个网站上都有官方账号,提供相关技术介绍和代码示例 可供参考及下载。 1. Github中文区:https://github.com/bright-cn 2. Gitee专区:https://gitee.com/bright-data #爬虫API #数据采集 #亮数据

By Ne0inhk
从 Query Mapping 到函数缓存,KingbaseES 高级 SQL 调优手段全揭秘

从 Query Mapping 到函数缓存,KingbaseES 高级 SQL 调优手段全揭秘

前言 在数据库性能优化这块,SQL 调优绝对是提升系统响应速度、降低资源消耗的核心操作!KingbaseES 作为能兼容 Oracle 的企业级数据库,把从 SQL 语句改写、执行计划干预到结果集缓存的优化全给覆盖到了,一套高级调优工具直接拉满。今天就带大家手把手拆解 Query Mapping、物化视图、并行查询、函数结果集缓存这些关键调优手段,再配上实打实的实战代码,不管你是数据库管理员还是开发同学,都能快速上手这些高性能优化技巧! 一、Query Mapping:不用改代码的 SQL“智能替换”神器 Query Mapping 绝对是 KingbaseES 里灵活性拉满的调优工具!简单说就是提前定义好“源 SQL”和“目标 SQL”的对应关系,你输入的 SQL 只要和源 SQL 匹配,就会自动换成目标 SQL 执行,

By Ne0inhk