1. STM32 ADC+DMA 单通道采集方案解析
在嵌入式开发中,ADC(模数转换器)是连接模拟世界与数字世界的重要桥梁。STM32F1系列微控制器内置12位精度的ADC模块,配合DMA(直接内存访问)控制器可以实现高效的数据采集。本文将深入讲解两种不同的编程实现方式,并分析其适用场景。
使用DMA进行ADC采集的核心优势在于解放CPU资源,转换完成后数据自动传输到指定内存区域,无需CPU频繁介入。
1.1 硬件架构与工作原理
STM32F1的ADC模块采用逐次逼近型(SAR)架构,具有以下关键特性:
- 12位分辨率(0~4095)
- 18个复用通道(16个外部+2个内部)
- 单次或连续转换模式
- 可编程采样时间(1.5~239.5个时钟周期)
- 模拟看门狗功能
当与DMA配合使用时,ADC转换完成的数据会通过DMA控制器自动传输到预设的内存缓冲区。整个过程完全由硬件完成,CPU仅在DMA传输完成时通过中断得到通知。
2. 方案设计与配置流程
2.1 硬件连接与初始化顺序
典型的单通道ADC采集硬件连接如下:
- 模拟信号输入 → PA1(ADC1通道1)
- 参考电压:VDDA接3.3V,VSSA接地
- 在VDDA和VSSA之间放置0.1μF和1μF去耦电容
配置流程应遵循以下顺序:
- 初始化DMA控制器
- 配置ADC基本参数
- 关联DMA与ADC
- 配置ADC通道参数
- 启用DMA中断
- 启动DMA传输
- 触发ADC转换
2.2 关键参数计算
-
ADC时钟配置:
- STM32F1 ADC最大时钟14MHz
- 系统时钟72MHz,6分频后得到12MHz(符合要求)
-
转换时间计算:
- 总转换时间 = 采样时间 + 12.5个周期
- 采样时间239.5周期 + 12.5周期 = 252个周期
- 12MHz时钟下,转换时间 = 252/12MHz ≈ 21μs
-
电压换算公式:
c复制voltage = (adc_value * 3.3f) / 4096.0f;
3. 混合编程实现方案(HAL+寄存器)
3.1 工程文件结构
code复制├── Core
│ ├── Src
│ │ ├── main.c
│ │ ├── adc.c
│ │ └── ...
│ ├── Inc
│ │ ├── adc.h
│ │ └── ...
└── ...
3.2 关键代码实现
adc.c 核心函数解析:
c复制void adc_dma_init(uint32_t mar)
{
// DMA配置
dma_adc_handle.Instance = DMA1_Channel1;
dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
HAL_DMA_Init(&dma_adc_handle);
// ADC基本配置
adc_handle.Instance = ADC1;
adc_handle.Init.ContinuousConvMode = ENABLE;
adc_handle.Init.NbrOfConversion = 1;
HAL_ADC_Init(&adc_handle);
// 通道配置
adc_ch_conf.Channel = ADC_CHANNEL_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&adc_handle, &adc_ch_conf);
}
DMA中断处理技巧:
c复制void DMA1_Channel1_IRQHandler(void)
{
if (__HAL_DMA_GET_FLAG(&dma_adc_handle,DMA_FLAG_TC1))
{
sta = 1; // 设置完成标志
__HAL_DMA_CLEAR_FLAG(&dma_adc_handle, DMA_FLAG_TC1);
}
}
3.3 动态调整CNDTR的实现
CNDTR(DMA通道数据传输数量寄存器)的动态修改需要特殊处理:
c复制void DMA_cndtr_change_enable(uint16_t cndtr)
{
__HAL_ADC_DISABLE(&adc_handle);
__HAL_DMA_DISABLE(&dma_adc_handle);
while(__HAL_DMA_GET_FLAG(&dma_adc_handle,
__HAL_DMA_GET_TC_FLAG_INDEX(&dma_adc_handle)));
dma_adc_handle.Instance->CNDTR = cndtr;
__HAL_DMA_ENABLE(&dma_adc_handle);
__HAL_ADC_ENABLE(&adc_handle);
HAL_ADC_Start(&adc_handle);
}
修改CNDTR前必须确保DMA传输完全停止,否则可能导致数据一致性问题。
4. 纯HAL库实现方案
4.1 工程结构优化
code复制├── Core
│ ├── Src
│ │ ├── main.c
│ │ ├── adc.c
│ │ ├── dma.c
│ │ └── ...
│ ├── Inc
│ │ ├── adc.h
│ │ ├── dma.h
│ │ └── ...
└── ...
4.2 HAL库完整实现
ADC初始化:
c复制void MX_ADC1_Init(void)
{
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Mode = DMA_NORMAL;
HAL_DMA_Init(&hdma_adc1);
hadc1.Instance = ADC1;
hadc1.Init.ContinuousConvMode = ENABLE;
HAL_ADC_Init(&hadc1);
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}
转换完成回调:
c复制void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1){
adc_dma_done = 1;
}
}
4.3 主循环处理
c复制while(1){
if(adc_dma_done){
for(int i=0; i<ADC_DMA_BUF_SIZE; i++){
float v = adc_dma_buf[i] * 3.3f / 4096.0f;
printf("%.3fV ", v);
}
ADC_DMA_Start(); // 重启采集
}
HAL_Delay(100);
}
5. 两种方案的对比与选择
5.1 性能对比
| 特性 | 混合方案 | 纯HAL方案 |
|---|---|---|
| 代码效率 | 高(直接寄存器操作) | 中(经过库封装) |
| 开发效率 | 低 | 高 |
| 可移植性 | 差 | 好 |
| CNDTR动态调整 | 支持 | 不支持 |
| 中断响应时间 | 约1.2μs | 约2.5μs |
5.2 选择建议
- 需要动态调整传输长度:选择混合方案
- 快速原型开发:选择纯HAL方案
- 对性能要求苛刻:选择混合方案
- 多平台移植需求:选择纯HAL方案
5.3 常见问题排查
问题1:ADC读数不稳定
- 检查参考电压是否稳定
- 增加采样时间(如调整为239.5周期)
- 在输入引脚添加0.1μF滤波电容
问题2:DMA传输不触发
- 确认
__HAL_LINKDMA调用正确 - 检查DMA通道是否匹配(ADC1使用DMA1通道1)
- 验证NVIC中断是否使能
问题3:数据对齐错误
- 确保
PeriphDataAlignment和MemDataAlignment一致 - 检查目标缓冲区地址是否对齐
6. 进阶优化技巧
6.1 软件滤波算法
在ADC采集后添加滑动平均滤波:
c复制#define FILTER_SIZE 5
float sliding_filter(float new_val){
static float buffer[FILTER_SIZE] = {0};
static uint8_t index = 0;
static float sum = 0;
sum -= buffer[index];
buffer[index] = new_val;
sum += new_val;
index = (index + 1) % FILTER_SIZE;
return sum / FILTER_SIZE;
}
6.2 低功耗优化
- 仅在需要采集时使能ADC
- 使用定时器触发代替连续转换
- 降低ADC时钟频率(不低于1MHz)
6.3 多通道扩展
虽然本文以单通道为例,但扩展到多通道时需注意:
- 修改
NbrOfConversion参数 - 配置多个通道的
Rank和SamplingTime - 增加DMA缓冲区大小
- 扫描模式需设置为
ENABLE
在实际项目中,我通常会根据具体需求选择实现方案。对于需要频繁修改传输参数的场景,混合方案提供了更大的灵活性;而对于需要快速开发和维护的项目,纯HAL方案则更具优势。无论哪种方案,理解底层硬件工作原理都是写出高质量代码的基础。