基于STM32F7+Embedded Rust&Embassy:ADC DMA异步采样配置咨询
问题描述
硬件配置
- 信号接入STM32F767的模拟输入引脚
- 输入信号频率范围1Hz~40kHz,要求ADC采样率至少192kHz
- STM32通过ETH端口以UDP协议连接客户端
需求
实现ADC的DMA异步读取,要求缓冲区半满时触发中断,在将就绪采样数据通过以太网传输至客户端的同时,DMA继续填充缓冲区另一半。
当前实现问题
目前在主循环中同步完成采样读取与数据传输,存在速度不足、采样周期不恒定的问题,现有代码如下:
#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![allow(non_snake_case)] use defmt::*; use heapless::Vec; use embassy_executor::Spawner; use embassy_net::udp::UdpSocket; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, PacketMetadata}; use embassy_time::{Duration, Timer, Delay}; use embassy_stm32::adc::{Adc, SampleTime}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; use embassy_stm32::{interrupt, Config}; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; const udpPort: u16 = 15180; const SYN: u8 = 22; const EOT: u8 = 4; const ADC_READ_DELAY: Duration = Duration::from_micros(61); const QSIZE: usize = 512; const QSIZE_DOUBLE: usize = QSIZE * 2; macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell<T> = StaticCell::new(); let (x,) = STATIC_CELL.init(($val,)); x }}; } type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack<Device>) -> ! { stack.run().await } #[embassy_executor::main] async fn main(spawner: Spawner) -> ! { info!("[main] enter"); let mut config = Config::default(); config.rcc.sys_ck = Some(mhz(200)); let dp = embassy_stm32::init(config); let mut adcPin = dp.PA3; let mut adc = Adc::new(dp.ADC1, &mut Delay); adc.set_sample_time(SampleTime::Cycles480); let mut rng = Rng::new(dp.RNG); let mut seed = [0; 8]; rng.fill_bytes(&mut seed); let seed = u64::from_le_bytes(seed); let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; let device = Ethernet::new( singleton!(PacketQueue::<16, 16>::new()), dp.ETH, eth_int, dp.PA1, dp.PA2, dp.PC1, dp.PA7, dp.PC4, dp.PC5, dp.PG13, dp.PB13, dp.PG11, GenericSMI, mac_addr, 0, ); let localIp = Ipv4Address::new(192, 168, 120, 173); let config = embassy_net::Config::Static(embassy_net::StaticConfig { address: Ipv4Cidr::new(localIp, 24), dns_servers: Vec::new(), gateway: Some(Ipv4Address::new(192, 168, 120, 1)), }); let stack = &*singleton!( Stack::new(device, config, singleton!(StackResources::<2>::new()), seed) ); unwrap!(spawner.spawn(net_task(&stack))); info!("Network task initialized"); let mut rx_meta = [PacketMetadata::EMPTY; 16]; let mut rx_buffer = [0; QSIZE_DOUBLE]; let mut tx_meta = [PacketMetadata::EMPTY; 16]; let mut tx_buffer = [0; QSIZE_DOUBLE]; let mut bufDouble = [0; QSIZE_DOUBLE]; loop { let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); info!("UDP bind on {}:{}...", localIp, udpPort); let r = socket.bind(udpPort); info!("UDP bind result: {:?}", r); if let Err(e) = r { info!("UDP bind error: {:?}", e); continue; } info!("UDP server ready!"); loop { info!("waiting handshake message..."); let (_n, remoteAddr) = socket.recv_from(&mut bufDouble).await.unwrap(); info!("received message from {:?}: {:?}", remoteAddr, bufDouble); if bufDouble[0] == SYN && bufDouble[1] == EOT { info!("received handshake from {:?}", remoteAddr); loop { for i in (0..QSIZE_DOUBLE).step_by(2) { let measured = adc.read(&mut adcPin); let bytes = measured.to_be_bytes(); bufDouble[i] = bytes[0]; bufDouble[i + 1] = bytes[1]; } if socket.is_open() { let r = socket.send_to(&bufDouble, remoteAddr).await; if let Err(e) = r { info!("write error: {:?}", e); break; } } else { info!("socket is not open"); break; } } } else { info!("received wrong handshake from({:?}): {:?}", remoteAddr, bufDouble); } } } }
解决方案
基于embassy-stm32框架,采用ADC DMA循环双缓冲模式实现异步采样,配合异步任务处理UDP传输,实现采样与传输并行。
修改后代码实现
#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![allow(non_snake_case)] use defmt::*; use heapless::Vec; use embassy_executor::{Spawner, task}; use embassy_net::udp::UdpSocket; use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources, PacketMetadata}; use embassy_time::{Duration, Delay}; use embassy_stm32::adc::{Adc, SampleTime, Continuous, Dma, Interrupts}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::{ADC1, DMA2_STREAM0, ETH, PA3}; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; use embassy_stm32::{interrupt, Config}; use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; const udpPort: u16 = 15180; const SYN: u8 = 22; const EOT: u8 = 4; const QSIZE: usize = 512; const QSIZE_DOUBLE: usize = QSIZE * 2; // DMA双缓冲静态数组,确保生命周期与程序一致 static mut ADC_BUFFER: [u16; QSIZE_DOUBLE] = [0; QSIZE_DOUBLE]; macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; static STATIC_CELL: StaticCell<T> = StaticCell::new(); let (x,) = STATIC_CELL.init(($val,)); x }}; } type Device = Ethernet<'static, ETH, GenericSMI>; #[task] async fn net_task(stack: &'static Stack<Device>) -> ! { stack.run().await } // ADC数据处理异步任务:响应半满/满中断,传输对应缓冲区数据 #[task] async fn adc_transmit_task(socket: &'static mut UdpSocket<'static>, remote_addr: embassy_net::IpAddrPort) { let mut tx_buf = [0u8; QSIZE * 2]; loop { // 等待ADC半满中断,处理前半区数据 interrupt::wait_for(interrupt::ADC); for (i, &sample) in unsafe { ADC_BUFFER[0..QSIZE] }.iter().enumerate() { let bytes = sample.to_be_bytes(); tx_buf[i*2] = bytes[0]; tx_buf[i*2+1] = bytes[1]; } if let Err(e) = socket.send_to(&tx_buf, remote_addr).await { info!("UDP send error (half buffer): {:?}", e); break; } // 等待ADC满中断,处理后半区数据 interrupt::wait_for(interrupt::ADC); for (i, &sample) in unsafe { ADC_BUFFER[QSIZE..QSIZE_DOUBLE] }.iter().enumerate() { let bytes = sample.to_be_bytes(); tx_buf[i*2] = bytes[0]; tx_buf[i*2+1] = bytes[1]; } if let Err(e) = socket.send_to(&tx_buf, remote_addr).await { info!("UDP send error (full buffer): {:?}", e); break; } } } #[embassy_executor::main] async fn main(spawner: Spawner) -> ! { info!("[main] enter"); let mut config = Config::default(); config.rcc.sys_ck = Some(mhz(200)); let dp = embassy_stm32::init(config); // 配置ADC+DMA双缓冲采样 let mut adc_pin = dp.PA3; let mut adc = Adc::new(dp.ADC1, &mut Delay); // 调整采样时间以满足192kHz采样率:36MHz/(15+12)≈1.33MHz,远高于需求 adc.set_sample_time(SampleTime::Cycles15); let dma = Dma::new(dp.DMA2_STREAM0); let mut adc_continuous = adc.into_continuous::<PA3, _>(adc_pin, dma, unsafe { &mut ADC_BUFFER }); // 启用半满、满中断 adc_continuous.enable_interrupts(Interrupts { half_transfer: true, transfer_complete: true, ..Default::default() }); adc_continuous.start(); info!("ADC DMA continuous sampling started"); // 网络初始化 let mut rng = Rng::new(dp.RNG); let mut seed = [0; 8]; rng.fill_bytes(&mut seed); let seed = u64::from_le_bytes(seed); let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; let device = Ethernet::new( singleton!(PacketQueue::<16, 16>::new()), dp.ETH, eth_int, dp.PA1, dp.PA2, dp.PC1, dp.PA7, dp.PC4, dp.PC5, dp.PG13, dp.PB13, dp.PG11, GenericSMI, mac_addr, 0, ); let localIp = Ipv4Address::new(192, 168, 120, 173); let config = embassy_net::Config::Static(embassy_net::StaticConfig { address: Ipv4Cidr::new(localIp, 24), dns_servers: Vec::new(), gateway: Some(Ipv4Address::new(192, 168, 120, 1)), }); let stack = &*singleton!( Stack::new(device, config, singleton!(StackResources::<2>::new()), seed) ); unwrap!(spawner.spawn(net_task(&stack))); info!("Network task initialized"); let mut rx_meta = [PacketMetadata::EMPTY; 16]; let mut rx_buffer = [0; QSIZE_DOUBLE]; let mut tx_meta = [PacketMetadata::EMPTY; 16]; let mut tx_buffer = [0; QSIZE_DOUBLE]; let socket = singleton!(UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer)); loop { info!("UDP bind on {}:{}...", localIp, udpPort); if let Err(e) = socket.bind(udpPort) { info!("UDP bind error: {:?}", e); continue; } info!("UDP server ready!"); let mut buf = [0; QSIZE_DOUBLE]; let (_n, remoteAddr) = socket.recv_from(&mut buf).await.unwrap(); info!("received message from {:?}", remoteAddr); if buf[0] == SYN && buf[1] == EOT { info!("received handshake from {:?}", remoteAddr); // 启动数据传输任务 unwrap!(spawner.spawn(adc_transmit_task(socket, remoteAddr))); // 主循环保持存活,可添加监控逻辑 loop { embassy_time::Timer::after(Duration::from_secs(10)).await; info!("Sampling and transmitting..."); } } else { info!("received wrong handshake from {:?}", remoteAddr); } } }
关键修改说明
ADC DMA双缓冲配置:
- 使用
into_continuous将ADC转为连续采样模式,绑定DMA并传入双倍大小的静态缓冲区 - 启用半满(
half_transfer)和满(transfer_complete)中断,触发时通知任务处理对应半区数据 - 调整采样时间为
Cycles15,确保采样率满足192kHz要求
- 使用
异步任务分离:
- 新增
adc_transmit_task异步任务,专门处理ADC中断响应和UDP数据发送,与主循环、网络任务并行执行 - 通过
interrupt::wait_for等待ADC中断,避免主循环阻塞
- 新增
资源生命周期管理:
- 使用全局静态数组作为DMA缓冲区,确保其生命周期覆盖程序全程
- 用
singleton!宏管理网络栈、UDP socket等静态资源
注意事项
- 需根据实际硬件调整ADC时钟和采样时间,确保采样率达标
- 配置中断优先级时,保证ADC中断优先级不低于网络任务,避免数据丢失
- 可根据UDP传输效率调整缓冲区大小,平衡内存占用和传输延迟
内容的提问来源于stack exchange,提问作者anton lobanov
相关产品推荐
相关产品推荐

