Rust嵌入式开发库embassy-stm32的使用,STM32硬件抽象层与异步驱动支持
Embassy STM32 HAL
embassy-stm32 HAL 旨在为所有 STM32 系列提供安全、符合语言习惯的硬件抽象层。该 HAL 为许多外设实现了阻塞式和异步式两种 API。在适当情况下,实现了来自嵌入式-hal v0.2 和 v1.0 的阻塞和异步版本的 trait,以及来自 embedded-io[-async] 的串行 trait。
embassy-stm32 支持所有 STM32 芯片系列
STM32 微控制器有许多系列和种类,支持所有这些是一项艰巨的任务。Embassy 利用了 STM32 外设版本在芯片系列之间共享这一事实。例如,embassy 没有为每个 STM32 芯片系列重新实现 SPI 外设,而是有一个单一的 SPI 实现,依赖于代码生成的寄存器类型,这些类型对于具有相同版本给定外设的 STM32 系列是相同的。
在实践中,这工作如下:
- 您通过功能标志告诉编译器您正在使用哪个芯片
- stm32-metapac 模块在编译时基于来自 stm32-data 模块的数据为该芯片生成寄存器类型
- embassy-stm32 HAL 根据自动生成的功能标志为每个外设选择正确的实现,并应用任何其他使 HAL 在该芯片上工作所需的调整
请注意,虽然 embassy-stm32 努力在所有芯片上一致地支持所有外设,但这种方法可能导致在不同的系列上可用的 API 和功能略有不同。请查阅您使用的特定芯片的文档以确认确切可用的内容。
Embedded-hal
embassy-stm32
HAL 实现了来自嵌入式-hal(v0.2 和 1.0)和嵌入式-hal-async 的 trait,以及嵌入式-io 和嵌入式-io-async。
embassy-time
时间驱动
如果启用了 time-driver-*
功能,embassy-stm32 提供一个时间驱动,用于与 embassy-time 一起使用。您可以通过 time-driver-tim*
功能选择用于此的内部硬件定时器,或让 embassy 通过 time-driver-any
选择。
embassy-time 的默认滴答率为 1MHz,这足够快,会导致目前由 embassy-stm32 时间驱动支持的 16 位定时器出现问题(具体来说,如果关键部分延迟 IRQ 超过 32ms)。为避免这种情况,建议选择较低的滴答率。32.768kHz 对于许多用途来说是一个合理的默认值。
互操作性
此 crate 可以在任何执行器上运行。
可选地,可以通过 time
功能激活一些需要 embassy-time 的功能。如果启用它,您必须在项目中链接一个 embassy-time 驱动。
low-power
功能专门与 embassy-executor
集成,目前不能用于其他执行器。
示例代码
以下是一个使用 embassy-stm32 进行异步 GPIO 控制的完整示例:
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::Peripherals;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner, p: Peripherals) {
let mut led = Output::new(p.PA5, Level::High, Speed::Low);
loop {
led.set_high();
Timer::after_millis(500).await;
led.set_low();
Timer::after_millis(500).await;
}
}
另一个使用异步 UART 通信的示例:
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_stm32::usart::{Config, Uart};
use embassy_stm32::Peripherals;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner, p: Peripherals) {
let config = Config::default();
let mut usart = Uart::new(
p.USART2,
p.PA2,
p.PA3,
p.DMA1_CH4,
p.DMA1_CH5,
config,
);
let mut buf = [0; 8];
loop {
usart.write(b"Hello\r\n").await.unwrap();
usart.read(&mut buf).await.unwrap();
Timer::after_millis(1000).await;
}
}
这些示例展示了如何使用 embassy-stm32 进行基本的嵌入式开发,包括 GPIO 控制和串行通信。注意要选择正确的芯片功能标志来匹配您的硬件。
完整示例代码
1. 异步GPIO控制示例(带详细注释)
// 禁用标准库,使用no_std
#![no_std]
// 禁用main函数的标准入口点
#![no_main]
// 启用类型别名特性
#![feature(type_alias_impl_trait)]
// 引入必要的embassy模块
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::Peripherals;
use embassy_time::Timer;
// 引入调试和panic处理工具
use {defmt_rtt as _, panic_probe as _};
// 定义主函数,使用embassy执行器
#[embassy_executor::main]
async fn main(_spawner: Spawner, p: Peripherals) {
// 初始化PA5引脚为输出模式,初始高电平,低速
let mut led = Output::new(p.PA5, Level::High, Speed::Low);
// 主循环
loop {
// 设置LED高电平
led.set_high();
// 等待500毫秒
Timer::after_millis(500).await;
// 设置LED低电平
led.set_low();
// 等待500毫秒
Timer::after_millis(500).await;
}
}
2. 异步UART通信示例(带详细注释)
// 禁用标准库,使用no_std
#![no_std]
// 禁用main函数的标准入口点
#![no_main]
// 启用类型别名特性
#![feature(type_alias_impl_trait)]
// 引入必要的embassy模块
use embassy_stm32::usart::{Config, Uart};
use embassy_stm32::Peripherals;
use embassy_time::Timer;
// 引入调试和panic处理工具
use {defmt_rtt as _, panic_probe as _};
// 定义主函数,使用embassy执行器
#[embassy_executor::main]
async fn main(_spawner: Spawner, p: Peripherals) {
// 使用默认配置初始化UART
let config = Config::default();
// 初始化USART2外设
// 使用PA2作为TX引脚,PA3作为RX引脚
// 使用DMA1通道4和5进行数据传输
let mut usart = Uart::new(
p.USART2,
p.PA2,
p.PA3,
p.DMA1_CH4,
p.DMA1_CH5,
config,
);
// 定义接收缓冲区
let mut buf = [0; 8];
// 主循环
loop {
// 发送字符串"Hello\r\n"
usart.write(b"Hello\r\n").await.unwrap();
// 读取数据到缓冲区
usart.read(&mut buf).await.unwrap();
// 等待1秒
Timer::after_millis(1000).await;
}
}
3. 异步SPI通信示例(新增完整示例)
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_stm32::spi::{Config, Spi};
use embassy_stm32::Peripherals;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner, p: Peripherals) {
// 配置SPI参数
let mut config = Config::default();
config.frequency = 1_000_000; // 1MHz
// 初始化SPI1外设
// 使用PA5作为SCK,PA6作为MISO,PA7作为MOSI
let mut spi = Spi::new(
p.SPI1,
p.PA5,
p.PA6,
p.PA7,
p.DMA1_CH2,
p.DMA1_CH3,
config,
);
// 定义发送和接收缓冲区
let tx_buf = [0xAA, 0xBB, 0xCC];
let mut rx_buf = [0; 3];
loop {
// 执行SPI传输
spi.transfer(&mut rx_buf, &tx_buf).await.unwrap();
// 等待100毫秒
Timer::after_millis(100).await;
}
}
这些示例展示了如何使用embassy-stm32进行基本的嵌入式开发,包括GPIO控制、串行通信和SPI通信。注意要根据您的具体硬件选择正确的芯片功能标志。
embassy-stm32: Rust嵌入式开发的STM32硬件抽象与异步驱动支持
介绍
embassy-stm32是Rust嵌入式生态系统中的一个重要库,它为STM32微控制器系列提供了硬件抽象层(HAL)和异步驱动支持。作为embassy项目的一部分,它专注于提供高效、现代的嵌入式开发体验,特别强调异步编程模型。
主要特点:
- 完整的STM32系列支持(F1, F2, F3, F4, F7, H7, G0, G4, L0, L1, L4, L5, U5, WB, WL等)
- 基于Rust的async/await异步编程模型
- 零或极低开销的硬件抽象
- 内置RTIC支持
- 丰富的外设驱动支持
使用方法
添加依赖
首先在Cargo.toml中添加依赖:
[dependencies]
embassy-stm32 = { version = "0.1", features = ["stm32f429zi", "unstable-pac", "rt"] }
embassy-executor = { version = "0.1", features = ["arch-cortex-m", "executor-thread", "integrated-timers"] }
embassy-time = { version = "0.1", features = ["tick-hz-32_768"] }
注意:根据你的具体STM32型号选择正确的feature。
基本示例:闪烁LED
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::peripherals::PA5;
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
// 初始化硬件
let p = embassy_stm32::init(Default::default());
// 配置PA5引脚为输出(假设这是开发板上的LED引脚)
let mut led = Output::new(p.PA5, Level::High, Speed::Low);
loop {
// 切换LED状态
led.toggle();
// 等待500ms
Timer::after(Duration from_millis(500)).await;
}
}
异步串口通信示例
use embassy_stm32::usart::{Uart, Config};
use embassy_stm32::peripherals::{USART1, DMA1_CH4, DMA1_CH5};
use embassy_stm32::dma::NoDma;
use defmt::info;
#[embassy_executor::task]
async fn uart_task(
usart: USART1,
tx_dma: DMA1_CH4,
rx_dma: DMA1_CH5,
tx_pin: PA9,
rx_pin: PA10,
) {
// 配置串口
let config = Config::default();
let mut usart = Uart::new(
usart,
tx_pin,
rx_pin,
tx_dma,
rx_dma,
config
);
// 发送欢迎消息
usart.blocking_write(b"Hello from STM32!\r\n").unwrap();
// 接收缓冲区
let mut buf = [0u8; 128];
loop {
// 异步读取数据
let n = usart.read(&mut buf).await.unwrap();
info!("Received: {}", core::str::from_utf8(&buf[..n]).unwrap());
}
}
定时器与PWM示例
use embassy_stm32::timer::simple_pwm::*;
use embassy_stm32::timer::Channel;
use embassy_stm32::peripherals::{TIM3, PC6};
#[embassy_executor::task]
async fn pwm_task(timer: TIM3, pin: PC6) {
// 配置PWM
let mut pwm = SimplePwm::new(
timer,
Some(pin),
None,
None,
None,
Default::default()
);
// 设置PWM频率为1kHz
pwm.set_freq(1_000.Hz());
// 获取通道1
let ch = pwm.channel(Channel::Ch1);
// 设置占空比
ch.set_duty(ch.get_max_duty() / 4); // 25%占空比
// 启用PWM输出
ch.enable();
loop {
// 可以在这里动态调整占空比
Timer::after(Duration::from_millis(100)).await;
}
}
高级功能
中断处理
embassy-stm32提供了简洁的中断处理方式:
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::gpio::{Input, Pull};
#[embassy_executor::task]
async fn button_task(pin: PC13) {
// 配置按钮输入,带中断
let button = Input::new(pin, Pull::Up);
let mut button = ExtiInput::new(button, p.EXTI13);
loop {
// 等待按钮按下
button.wait_for_falling_edge().await;
info!("Button pressed!");
}
}
使用RTIC框架
embassy-stm32可以与RTIC框架无缝集成:
use rtic::app;
use embassy_stm32::gpio::{Output, Level};
#[app(device = embassy_stm32::pac, peripherals = false)]
mod app {
use super::*;
#[shared]
struct Shared {}
#[local]
struct Local {
led: Output<'static, PA5>,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
let p = embassy-stm32::init(Default::default());
let led = Output::new(p.PA5, Level::Low, Speed::Low);
(Shared {}, Local { led })
}
#[task(local = [led])]
fn blink(cx: blink::Context) {
cx.local.led.toggle();
}
}
外设驱动支持
embassy-stm32支持多种常见外设驱动:
- GPIO (输入/输出/中断)
- USART/UART/SPI/I2C
- 定时器/PWM
- ADC/DAC
- USB
- CAN
- RTC
- DMA
- CRC
- 看门狗
开发建议
- 选择正确的芯片型号:确保在Cargo.toml中启用了正确的芯片feature
- 利用异步编程:embassy的设计核心是异步编程,充分利用async/await简化代码
- 查阅芯片参考手册:虽然embassy提供了抽象,但了解底层硬件特性仍然很有帮助
- 使用defmt进行日志记录:embassy生态推荐使用defmt进行高效的日志记录
- 利用社区资源:embassy项目有活跃的Discord社区,遇到问题时可以寻求帮助
完整示例:带LED和UART的多任务系统
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_executor::Spawner;
use embassy_stm32::{
gpio::{Level, Output, Speed},
peripherals::{PA5, USART1, DMA1_CH4, DMA1_CH5, PA9, PA10},
usart::{Uart, Config},
dma::NoDma,
};
use embassy_time::{Duration, Timer};
use defmt::info;
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
// 启动LED闪烁任务
spawner.spawn(led_task(p.PA5)).unwrap();
// 启动UART通信任务
spawner.spawn(uart_task(
p.USART1,
p.DMA1_CH4,
p.DMA1_CH5,
p.PA9,
p.PA10,
)).unwrap();
}
#[embassy_executor::task]
async fn led_task(pin: PA5) {
let mut led = Output::new(pin, Level::High, Speed::Low);
loop {
led.toggle();
Timer::after(Duration::from_millis(500)).await;
}
}
#[embassy_executor::task]
async fn uart_task(
usart: USART1,
tx_dma: DMA1_CH4,
rx_dma: DMA1_CH5,
tx_pin: PA9,
rx_pin: PA10,
) {
let config = Config::default();
let mut usart = Uart::new(
usart,
tx_pin,
rx_pin,
tx_dma,
rx_dma,
config
);
usart.blocking_write(b"System started!\r\n").unwrap();
let mut buf = [0u8; 128];
let mut counter = 0;
loop {
// 发送计数信息
defmt::writeln!(&mut usart, "Counter: {}\r\n", counter).unwrap();
counter += 1;
// 接收数据
if let Ok(n) = usart.read(&mut buf).await {
info!("Received: {}", core::str::from_utf8(&buf[..n]).unwrap());
}
Timer::after(Duration::from_secs(1)).await;
}
}