You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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);
        }
    }
}

关键修改说明

  1. ADC DMA双缓冲配置:

    • 使用into_continuous将ADC转为连续采样模式,绑定DMA并传入双倍大小的静态缓冲区
    • 启用半满(half_transfer)和满(transfer_complete)中断,触发时通知任务处理对应半区数据
    • 调整采样时间为Cycles15,确保采样率满足192kHz要求
  2. 异步任务分离:

    • 新增adc_transmit_task异步任务,专门处理ADC中断响应和UDP数据发送,与主循环、网络任务并行执行
    • 通过interrupt::wait_for等待ADC中断,避免主循环阻塞
  3. 资源生命周期管理:

    • 使用全局静态数组作为DMA缓冲区,确保其生命周期覆盖程序全程
    • 用singleton!宏管理网络栈、UDP socket等静态资源

注意事项

  • 需根据实际硬件调整ADC时钟和采样时间,确保采样率达标
  • 配置中断优先级时,保证ADC中断优先级不低于网络任务,避免数据丢失
  • 可根据UDP传输效率调整缓冲区大小,平衡内存占用和传输延迟

内容的提问来源于stack exchange,提问作者anton lobanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:44:56