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 系列是相同的。

在实践中,这工作如下:

  1. 您通过功能标志告诉编译器您正在使用哪个芯片
  2. stm32-metapac 模块在编译时基于来自 stm32-data 模块的数据为该芯片生成寄存器类型
  3. 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通信。注意要根据您的具体硬件选择正确的芯片功能标志。


1 回复

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
  • 看门狗

开发建议

  1. 选择正确的芯片型号:确保在Cargo.toml中启用了正确的芯片feature
  2. 利用异步编程:embassy的设计核心是异步编程,充分利用async/await简化代码
  3. 查阅芯片参考手册:虽然embassy提供了抽象,但了解底层硬件特性仍然很有帮助
  4. 使用defmt进行日志记录:embassy生态推荐使用defmt进行高效的日志记录
  5. 利用社区资源: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;
    }
}
回到顶部