1. STM32F1 ADC采集基础与DMA应用场景
在嵌入式开发领域,ADC(模数转换器)是连接模拟世界与数字系统的关键桥梁。STM32F1系列芯片内置的12位逐次逼近型ADC,其采样率最高可达1MHz,足以应对大多数工业检测、环境监测等场景的需求。而DMA(直接内存访问)控制器的引入,则彻底解放了CPU在数据传输过程中的负担。
我曾在多个工业传感器采集项目中验证过,当采用传统轮询方式处理ADC数据时,CPU利用率常常高达60%以上。而启用DMA后,同样采样频率下CPU负载可降至5%以下,这种差异在需要实时响应的控制系统中尤为关键。STM32F1的DMA控制器支持从外设到内存、内存到外设以及内存到内存的传输,其双缓冲机制更是为连续采样提供了硬件级的支持。
ADC与DMA的典型应用组合包括:
- 工业现场的多点温度监控(PT100/PT1000)
- 电池管理系统的电压电流采集
- 智能家居的环境参数监测(温湿度、光照等)
- 医疗设备的生理信号采集(心电、血氧等)
2. 硬件设计与外设配置
2.1 ADC通道选择与基准源配置
STM32F103C8T6芯片提供了10个ADC通道,包括8个外部通道和2个内部通道(温度传感器和VREFINT)。在实际项目中,我推荐优先使用ADC1的通道0-7(PA0-PA7),这些引脚布局集中且不易受数字信号干扰。对于基准电压,当系统供电稳定时可采用VDDA作为基准,若需要更高精度则建议外接REF3030等精密基准源。
重要提示:ADC的采样保持时间需根据信号源阻抗调整。对于输出阻抗大于10kΩ的传感器,建议将采样时间设置为239.5个ADC时钟周期(对应寄存器值SAMPLETIME_239CYCLES_5)
2.2 DMA控制器关键参数设置
STM32F1的DMA1控制器有7个通道,其中通道1专用于ADC1数据传输。配置时需特别注意:
c复制DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式实现连续采集
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
3. 两种编程方式实现与对比
3.1 寄存器直接操作方式
寄存器级编程虽然代码量较大,但执行效率最高,适合对时序要求严格的场景。以下是关键步骤:
- 启用时钟和GPIO配置:
c复制RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN;
GPIOA->CRL &= ~(0xF << (4*0)); // PA0模拟输入模式
- ADC校准流程(必须执行):
c复制ADC1->CR2 |= ADC_CR2_ADON;
delay_ms(1);
ADC1->CR2 |= ADC_CR2_RSTCAL;
while(ADC1->CR2 & ADC_CR2_RSTCAL);
ADC1->CR2 |= ADC_CR2_CAL;
while(ADC1->CR2 & ADC_CR2_CAL);
- DMA中断配置示例:
c复制NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
3.2 标准库函数方式
使用STM32标准库可以大幅提升开发效率,特别适合快速原型开发。以下是典型配置流程:
- ADC初始化结构体配置:
c复制ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
- DMA配置的完整示例:
c复制DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adc_value;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
4. 性能优化与误差处理
4.1 采样时序优化技巧
通过实测发现,ADC时钟分频系数对采样精度影响显著。当APB2时钟为72MHz时,推荐配置:
- 6分频(ADC_CLK=12MHz):适合大多数传感器
- 8分频(ADC_CLK=9MHz):用于高阻抗信号源
- 4分频(ADC_CLK=18MHz):仅适用于低精度快速采样
采样时间与精度的实测数据对比:
| 采样周期数 | 转换时间(μs) | 10位有效精度 | 适用场景 |
|---|---|---|---|
| 1.5 | 0.13 | 6-7位 | 高速动态信号 |
| 7.5 | 0.67 | 8位 | 音频信号 |
| 13.5 | 1.20 | 9位 | 普通传感器 |
| 28.5 | 2.53 | 10位 | 高精度测量 |
| 41.5 | 3.69 | 10位 | 高阻抗源 |
| 55.5 | 4.93 | 10位 | 微弱信号 |
| 71.5 | 6.35 | 10位 | 超低噪声 |
| 239.5 | 21.28 | 10位 | 特殊高阻 |
4.2 常见干扰源处理方案
在电机控制项目中遇到的典型问题及解决方案:
- 电源噪声:在VDDA与VSSA间并联10μF钽电容+100nF陶瓷电容
- 数字干扰:ADC输入引脚串联100Ω电阻并添加1nF对地电容
- 通道串扰:未使用的ADC通道应接地或接固定电平
- 采样值跳动:启用硬件平均功能(OVERSAMPLING)或软件滑动滤波
5. 双缓冲技术的进阶应用
对于需要实时处理的连续采样系统,双缓冲DMA是更优的选择。其核心原理是当DMA填满第一个缓冲区时触发中断,同时自动切换到第二个缓冲区继续采集,实现无缝数据流。
实现步骤:
- 定义双缓冲区和状态标志
c复制#define BUF_SIZE 256
uint16_t adc_buf1[BUF_SIZE], adc_buf2[BUF_SIZE];
volatile uint8_t buf_ready = 0;
- DMA中断服务例程
c复制void DMA1_Channel1_IRQHandler(void) {
if(DMA_GetITStatus(DMA1_IT_TC1)) {
if(DMA_GetCurrentMemoryTarget(DMA1_Channel1) == 0) {
// buf1已满,处理buf2
buf_ready = 1;
} else {
// buf2已满,处理buf1
buf_ready = 2;
}
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
}
- 主循环中的处理逻辑
c复制while(1) {
if(buf_ready == 1) {
process_data(adc_buf1, BUF_SIZE);
buf_ready = 0;
} else if(buf_ready == 2) {
process_data(adc_buf2, BUF_SIZE);
buf_ready = 0;
}
__WFI(); // 进入低功耗模式
}
在实际的振动监测系统中,这种技术可以实现50kHz的连续采样率,同时CPU利用率保持在15%以下。关键在于合理设置缓冲区大小 - 通常建议缓冲区能容纳1-2个信号周期的数据量。