STM32 HAL库实战:高效整合DMA与ADC开发指南
一、DMA与ADC基础介绍
1. DMA:解放CPU的“数据搬运工”
DMA(Direct Memory Access) 是STM32中用于在外设与内存之间直接传输数据的硬件模块。其核心优势在于无需CPU干预,可显著提升系统效率。
-
功能特点:
-
支持存储器↔外设、存储器↔存储器的数据传输。
-
多通道管理,每个通道独立控制一个外设的数据传输。
-
传输完成后触发中断通知CPU。
-
-
典型应用场景:
-
ADC多通道连续采样数据的自动存储。
-
串口大数据收发、SPI/I2C通信等。
-
2. ADC:模拟世界的“数字翻译器”
ADC(Analog-to-Digital Converter) 负责将模拟信号(如电压、温度)转换为数字信号。STM32的ADC模块支持多通道、高精度采样。
-
关键参数:
-
分辨率:12位(0~4095)。
-
采样速率:最高1MHz(STM32F1系列)。
-
输入通道:16个外部通道 + 2个内部通道(温度传感器、VREF)。
-
-
工作模式:
- 单次转换、连续转换、扫描模式(多通道轮询)。
二、DMA+ADC整合开发步骤
1. 硬件与工程配置
硬件连接示例
- ADC通道:PA1(通道1)接模拟输入(如MQ2烟雾传感器、电位器等)。
- DMA通道:ADC1使用DMA1通道1(见STM32参考手册)。
硬件代码配置
ADC基础配置
void adc_config(void)
{
adc_handle.Instance = ADC1;
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
adc_handle.Init.ContinuousConvMode = ENABLE;
adc_handle.Init.NbrOfConversion = 1;
adc_handle.Init.DiscontinuousConvMode = DISABLE;
adc_handle.Init.NbrOfDiscConversion = 0;
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&adc_handle);
HAL_ADCEx_Calibration_Start(&adc_handle);
}
ADC硬件配置
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
GPIO_InitTypeDef gpio_init_struct = {0};
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_1;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
ADC通道配置
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime)
{
ADC_ChannelConfTypeDef adc_ch_config = {0};
adc_ch_config.Channel = ch;
adc_ch_config.Rank = rank;
adc_ch_config.SamplingTime = stime;
HAL_ADC_ConfigChannel(hadc, &adc_ch_config);
}
DMA配置:
void dma_config(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel1;
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
dma_handle.Init.MemInc = DMA_MINC_ENABLE;
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
dma_handle.Init.Mode = DMA_CIRCULAR;
HAL_DMA_Init(&dma_handle);
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
}
2. 关键代码实现
初始化并启动DMA
void adc_dma_init(uint32_t *mar)
{
adc_config();
adc_channel_config(&adc_handle, ADC_CHANNEL_1, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
dma_config();
HAL_ADC_Start_DMA(&adc_handle, mar, 1);
}
在主函数中,调用该函数,DMA将会一直将值存放在mar所指的内存上。
3. 优化技巧与常见问题
提升ADC精度
-
硬件优化:
-
添加0.1μF滤波电容到ADC输入引脚。
-
单独为VDDA和VSSA供电。
-
-
软件优化:
- 丢弃前几次采样值(避免电源不稳定)。
常见问题排查
-
数据错位:检查DMA数据宽度(需与ADC对齐方式一致)。
-
采样值跳动:增加采样时间或添加软件滤波(如滑动平均)。
-
DMA不触发:确认DMA通道与ADC的映射关系(参考数据手册)。
三、项目实战:DMA单通道采集(烟雾)
功能需求
- 使用PA1烟雾报警模拟输入。
- 每3秒计算一次并通过串口输出。
核心代码片段
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"
#define CAL_PPM 10 // 校准环境中PPM值
#define RL 10 // RL阻值
#define R0 97 // R0阻值
uint16_t adc_result = 0;
int main(void)
{
float RS ;
float ppm;
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
adc_dma_init((uint32_t *)&adc_result);
printf("hello world!\r\n");
while(1)
{
// printf("adc result: %f\r\n", (float)adc_result / 4096 * 3.3);
RS = (3.3f - (float)adc_result / 4096 * 3.3) / (float)adc_result / 4096 * 3.3 * RL;
ppm = 98.322f * pow(RS/R0, -1.458f);
printf("adc result: %f\r\n", ppm);
delay_ms(3000);
}
}
四、总结
通过DMA+ADC的高效整合,开发者可以实现低CPU占用率的模拟信号采集系统。关键点在于:
- 合理配置ADC的扫描模式与DMA循环传输。
- 利用HAL库的中断回调机制处理数据。
- 通过硬件与软件优化提升信号质量。
掌握这一技术后,可轻松应对传感器数据采集、工业控制等高实时性场景的需求。