告别轮询!用STM32CubeMX+DMA搞定ADC多通道数据采集(附F103工程)

张开发
2026/6/17 16:06:24 15 分钟阅读
告别轮询!用STM32CubeMX+DMA搞定ADC多通道数据采集(附F103工程)
STM32CubeMXDMA实现ADC多通道采集的工程实践第一次用STM32做多通道ADC采集时你是不是也遇到过这样的困扰明明只是采集几路简单的电压信号CPU使用率却居高不下系统响应变得迟钝。更头疼的是当需要同时处理其他任务时采集数据还会出现丢失。这种低效的采集方式正是很多嵌入式开发者面临的共同痛点。1. 为什么需要DMA进行ADC采集传统ADC采集方式主要分为轮询和中断两种。轮询模式下CPU需要不断检查ADC转换完成标志这种死等的方式会完全占用CPU资源。中断模式虽然有所改善但每次转换完成都会触发中断当采样频率较高时频繁的中断响应仍然会消耗大量CPU时间。DMA直接内存访问技术的引入彻底改变了这一局面。它允许外设如ADC直接与内存交换数据完全不需要CPU参与。想象一下你只需要配置好DMA控制器告诉它把ADC转换结果存放到这个内存地址然后就可以去做其他事情了DMA会自动完成所有的数据传输工作。三种采集方式对比采集方式CPU占用率实现复杂度适用场景轮询100%简单单次触发、低频率中断中-高中等中等频率、需要实时性DMA接近0%较复杂高频率、多通道、低功耗在实际项目中我遇到过这样一个案例需要同时采集4路传感器信号采样率要求1kHz。最初使用中断方式实现发现系统响应明显变慢偶尔还会丢失数据包。改用DMA后CPU占用率从70%降到了不足5%系统运行变得非常流畅。2. CubeMX配置关键步骤2.1 基础环境搭建打开CubeMX新建工程时首先需要选择正确的芯片型号。以常见的STM32F103C8T6为例它的ADC时钟最高不能超过14MHz。在Clock Configuration标签页中需要确保ADC时钟分频设置合理。提示ADC时钟频率会影响转换时间频率越高转换越快但超过芯片规格会导致采样精度下降。RCC配置启用HSE外部高速时钟根据硬件实际情况选择晶振频率SYS配置Debug选项选择Serial Wire这是为了保留SWD调试接口2.2 ADC参数设置在Analog标签页下找到ADC1进行配置启用Scan Conversion Mode扫描模式设置Data Alignment为Right数据右对齐Regular Conversion Mode选择Discontinuous非连续转换模式Number Of Conversion设置为实际使用的通道数通道配置示例Channel 8对应某个GPIO引脚Sampling Time设为15 cyclesChannel 9另一个GPIO引脚相同采样时间Channel 16VREFINT内部基准电压通道// CubeMX生成的ADC初始化代码片段 hadc1.Instance ADC1; hadc1.Init.ScanConvMode ENABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode ENABLE; hadc1.Init.NbrOfDiscConversion 3; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT;2.3 定时器触发配置为了实现定期自动采样我们需要配置一个定时器作为ADC的触发源选择TIM3或其他可用定时器配置预分频器(Prescaler)和计数器周期(Counter Period)在Trigger Output选项中选择Trigger Event Selection计算采样频率的公式为采样频率 定时器时钟频率 / (Prescaler 1) / (Counter Period 1)2.4 DMA配置要点DMA配置是整个工程的核心也是最容易出错的部分添加DMA通道用于ADC1Mode选择Circular循环模式Data Width选择Half Word16位Memory Increment设置为Enable根据需要配置中断优先级// DMA配置示例 hdma_adc1.Instance DMA1_Channel1; hdma_adc1.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc DMA_PINC_DISABLE; hdma_adc1.Init.MemInc DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode DMA_CIRCULAR; hdma_adc1.Init.Priority DMA_PRIORITY_HIGH;3. 代码实现与数据处理3.1 初始化与启动生成代码后需要在main函数中添加必要的初始化和启动代码#define ADC_BUF_LEN 6 // 每个通道采样2次 uint16_t adcBuf[ADC_BUF_LEN]; // 在main函数中初始化后添加 HAL_ADCEx_Calibration_Start(hadc1); HAL_ADC_Start_DMA(hadc1, (uint32_t*)adcBuf, ADC_BUF_LEN); HAL_TIM_Base_Start(htim3);这里我特意将缓冲区长度设置为通道数的两倍这样可以避免DMA传输过程中数据被覆盖的风险。在实际项目中缓冲区大小需要根据具体应用场景进行调整。3.2 电压值计算与校准使用内部基准电压VREFINT进行校准是提高测量精度的关键。STM32的内部基准电压典型值为1.2V但实际值会有微小偏差。// 获取校准后的电压值 float vrefint_voltage 1.2f; // 典型值实际应参考芯片手册 float ch8_voltage adcBuf[0] * vrefint_voltage / adcBuf[2]; float ch9_voltage adcBuf[1] * vrefint_voltage / adcBuf[2];注意VREFINT值会随温度变化对精度要求高的应用需要进行温度补偿。3.3 数据滤波处理直接从ADC读取的数据往往会有噪声简单的软件滤波可以显著提高数据稳定性#define FILTER_SIZE 5 float filterBuffer[FILTER_SIZE]; uint8_t filterIndex 0; float movingAverageFilter(float newValue) { filterBuffer[filterIndex] newValue; filterIndex (filterIndex 1) % FILTER_SIZE; float sum 0; for(int i0; iFILTER_SIZE; i) { sum filterBuffer[i]; } return sum / FILTER_SIZE; }4. 调试技巧与性能优化4.1 使用ST-Link Utility进行调试ST-Link Utility是一个强大的调试工具可以实时查看内存数据连接开发板并启动调试会话在Memory Browser中输入ADC缓冲区的地址设置自动刷新频率观察数据变化通过这种方式可以直观地验证DMA是否正常工作数据是否正确写入内存。4.2 常见问题排查DMA传输不启动检查DMA和ADC时钟是否使能验证DMA通道是否配置正确确保缓冲区地址有效数据不更新检查定时器是否正常启动验证ADC触发配置确认DMA模式设置为Circular测量值不准确检查电源稳定性验证参考电压适当增加采样时间4.3 性能优化建议合理设置采样时间采样时间过短会导致精度下降过长会限制最大采样率。需要根据信号源阻抗找到平衡点。内存布局优化将DMA缓冲区放在特定的内存区域如CCM RAM可以减少总线冲突提高性能。双缓冲技术使用两个缓冲区交替工作可以避免处理数据时被新数据覆盖。// 双缓冲实现示例 #define BUF_SIZE 3 uint16_t adcBuf1[BUF_SIZE], adcBuf2[BUF_SIZE]; volatile uint8_t currentBuf 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { currentBuf !currentBuf; if(currentBuf) { HAL_ADC_Start_DMA(hadc1, (uint32_t*)adcBuf1, BUF_SIZE); } else { HAL_ADC_Start_DMA(hadc1, (uint32_t*)adcBuf2, BUF_SIZE); } // 处理非当前缓冲区数据 }在完成所有配置和调试后不妨实际测量一下系统的性能表现。在我的测试中使用DMA方式的ADC采集CPU占用率几乎可以忽略不计系统能够轻松实现10kHz的多通道采样同时还有充足资源处理其他任务。

更多文章