基于Rust no_std ESP32 HAL实现192kHz采样率的ADC DMA非阻塞模式
在Rust no_std环境下基于ESP32 HAL实现192kHz采样率的ADC DMA非阻塞转换
核心实现逻辑
ESP32的ADC要实现高采样率非阻塞转换,必须结合**PDMA(外设DMA)**与ADC连续采样模式,核心流程如下:
- 配置ADC为连续采样模式,匹配192kHz采样率的时钟分频参数
- 初始化PDMA并分配内存缓冲区,绑定ADC采样输出通道与PDMA通道
- 设置PDMA为循环传输模式,启用传输完成中断,用于触发数据处理逻辑
- 启动ADC连续采样与PDMA传输后,CPU可完全释放,专注于以太网传输、DSP运算等任务
完整代码示例
#![no_std] #![no_main] #![allow(non_snake_case)] use esp_backtrace as _; use esp_println::println; use esp32_hal::{ prelude::*, peripherals::Peripherals, clock::{ClockControl, CpuClock}, interrupt, Rtc, timer::TimerGroup, adc::{self, ADC1, AdcConfig, ADC, Attenuation, AdcCalibration, AdcSampling}, IO, pdma::{Dma, DmaChannel, Target, Transfer, TransferMode}, }; // 全局静态变量存储DMA传输实例(供中断处理使用) static mut DMA_TRANSFER: Option<Transfer<DmaChannel, Target::ADC1, &'static mut [u16]>> = None; static mut SAMPLE_BUFFER: [u16; 256] = [0; 256]; #[entry] fn main() -> ! { let peripherals = Peripherals::take(); let mut system = peripherals.DPORT.split(); let clocks = ClockControl::configure(system.clock_control, CpuClock::Clock240MHz).freeze(); let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); // 禁用看门狗 let rtc_cntl = Rtc::new(peripherals.RTC_CNTL); let mut rwdt = rtc_cntl.rwdt; let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); let mut wdt0 = timer_group0.wdt; let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); let mut wdt1 = timer_group1.wdt; rwdt.disable(); wdt0.disable(); wdt1.disable(); // 初始化PDMA let mut dma = Dma::new(peripherals.DMA, &mut system.peripheral_clock_control); let dma_channel = dma.channel0; // 初始化ADC并配置采样参数 let analog = peripherals.SENS.split(); let mut adc_config: AdcConfig<ADC1> = adc::AdcConfig::new(); let mut adc_pin = adc_config.enable_pin( io.pins.gpio34.into_analog(), Attenuation::Attenuation11dB, ); // 计算分频系数:192kHz采样率需要ADC时钟≈2.4MHz,APB时钟80MHz,分频系数=80/2.4 -1≈32 let mut adc1 = ADC::<ADC1>::adc(analog.adc1, adc_config) .unwrap() .set_sampling(AdcSampling::new().set_clk_divider(32)) .calibrate(AdcCalibration::new()); // 配置DMA循环传输:将ADC采样数据持续写入缓冲区 unsafe { let transfer = dma_channel .transfer_to_memory(Target::ADC1, &mut SAMPLE_BUFFER) .with_mode(TransferMode::Circular) .enable_interrupt(); DMA_TRANSFER = Some(transfer); } // 启用DMA通道0中断 interrupt::enable(peripherals::Interrupt::DMA_CH0, interrupt::Priority::Priority1).unwrap(); // 启动ADC连续采样 adc1.start_continuous_sampling(&mut adc_pin).unwrap(); // CPU释放,处理其他任务(示例仅作演示,实际替换为以太网/DSP逻辑) loop { println!("CPU Idle: Processing other tasks..."); cortex_m::asm::wfi(); // 进入低功耗模式,等待中断唤醒 } } // DMA通道0中断处理函数:采样完成后触发数据处理 #[interrupt] fn DMA_CH0() { unsafe { if let Some(transfer) = DMA_TRANSFER.as_mut() { transfer.clear_interrupt(); // 处理采样数据:示例打印前10个采样值,实际替换为以太网传输/DSP运算 let buffer = transfer.buffer(); for i in 0..10 { println!("DMA Sample {}: {}", i, buffer[i]); } } } }
关键注意事项
- 采样率校准:代码中分频系数为理论计算值,实际需根据硬件时钟精度微调,确保采样率准确
- 缓冲区管理:循环模式下DMA会持续覆盖缓冲区,需在中断中及时处理数据,避免丢失
- 中断优先级:DMA中断优先级需高于非实时任务,保证采样数据及时处理
- 硬件限制:ESP32 ADC最高采样率约1MHz,192kHz在硬件支持范围内,连续采样需注意功耗与发热
内容的提问来源于stack exchange,提问作者anton lobanov
相关产品推荐
相关产品推荐

