1. 项目概述
在嵌入式系统开发中,ADC(模数转换器)是最常用的外设之一。本次项目基于STM32微控制器,实现独立模式下ADC单通道电压采集的完整解决方案。通过配置PC0引脚作为模拟输入,采集可变电阻器的电压值,并将转换结果通过串口发送至PC端显示。
这个案例非常适合刚接触STM32 ADC功能的开发者学习,涵盖了从硬件连接到软件实现的完整流程。我们将采用两种实现方式:寄存器直接操作和HAL库开发,帮助开发者理解不同抽象层次的开发方法。
2. 硬件设计解析
2.1 电路原理分析
硬件电路设计非常简单但非常实用:
- 使用一个10KΩ的可变电阻(电位器)
- 电阻一端接3.3V电源(VCC)
- 另一端接地(GND)
- 中间抽头连接至STM32的PC0引脚
这种设计形成了一个简单的分压电路,当旋转电位器时,PC0引脚上的电压会在0-3.3V之间线性变化。PC0对应STM32内部ADC1的通道10(ADC1_IN10),这也是我们选择PC0的原因。
2.2 关键硬件参数
-
ADC分辨率:STM32F10x系列内置12位ADC,理论分辨率为:
code复制分辨率 = VREF / (2^12 - 1) = 3.3V / 4095 ≈ 0.0008V (0.8mV) -
输入阻抗:ADC输入引脚在模拟输入模式下具有较高的输入阻抗(典型值>1MΩ),确保不会对被测电路造成明显负载效应。
-
参考电压:使用芯片内部VREF+(连接至VDDA),典型值为3.3V。对于更高精度的应用,建议使用外部精密参考电压源。
3. 软件实现详解
3.1 寄存器方式实现
3.1.1 时钟配置
ADC时钟需要特别关注,STM32F10x的ADC最大时钟频率为14MHz。系统时钟通常为72MHz,因此需要进行分频:
c复制// 开启ADC1时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// 6分频配置:72MHz/6=12MHz (<14MHz)
RCC->CFGR |= RCC_CFGR_ADCPRE_1; // 设置分频位1
RCC->CFGR &= ~RCC_CFGR_ADCPRE_0; // 清除分频位0
注意:不同STM32系列的分频系数可能不同,务必查阅对应芯片的参考手册。
3.1.2 GPIO配置
PC0需要配置为模拟输入模式,这是ADC采集的标准配置:
c复制// 配置PC0为模拟输入模式
// CNF[1:0]=00(模拟输入), MODE[1:0]=00(输入模式)
GPIOC->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);
有趣的是,模拟输入模式下实际上不需要开启GPIOC的时钟,因为信号不经过输入数据寄存器,直接进入ADC模块。这是STM32的一个特殊设计。
3.1.3 ADC基础参数配置
关键配置包括工作模式、采样时间和数据对齐:
c复制// 禁止扫描模式(单通道)
ADC1->CR1 &= ~ADC_CR1_SCAN;
// 启用连续转换模式
ADC1->CR2 |= ADC_CR2_CONT;
// 右对齐数据
ADC1->CR2 &= ~ADC_CR2_ALIGN;
// 设置通道10采样时间为7.5周期
ADC1->SMPR1 |= ADC_SMPR1_SMP10_0;
ADC1->SMPR1 &= ~(ADC_SMPR1_SMP10_2 | ADC_SMPR1_SMP10_1);
采样时间选择7.5周期是平衡速度和精度的折中方案。更长的采样时间可以提高精度但降低转换速率。
3.1.4 规则通道配置
配置规则通道组的转换序列:
c复制// 规则通道组序列长度为1(只转换1个通道)
ADC1->SQR1 &= ~ADC_SQR1_L;
// 序列1选择通道10
ADC1->SQR3 &= ~ADC_SQR3_SQ1; // 先清除
ADC1->SQR3 |= (10 << 0); // 再设置
3.1.5 触发方式配置
使用软件触发方式启动转换:
c复制// 启用规则通道外部触发
ADC1->CR2 |= ADC_CR2_EXTTRIG;
// 选择软件触发(SWSTART)
ADC1->CR2 |= ADC_CR2_EXTSEL;
3.2 转换流程实现
完整的ADC转换流程包括上电、校准、启动转换和数据读取:
c复制void ADC1_StartConvert(void)
{
// 1. 上电唤醒ADC
ADC1->CR2 |= ADC_CR2_ADON;
// 2. 执行校准
ADC1->CR2 |= ADC_CR2_CAL;
while(ADC1->CR2 & ADC_CR2_CAL) {} // 等待校准完成
// 3. 启动转换
ADC1->CR2 |= ADC_CR2_SWSTART;
// 4. 等待转换完成
while(!(ADC1->SR & ADC_SR_EOC)) {}
}
double ADC1_GetVol(void)
{
// 读取数据寄存器并转换为电压值
return ADC1->DR * 3.3 / 4095;
}
重要提示:每次系统复位后都需要重新校准ADC,这是保证测量精度的关键步骤。
4. HAL库实现方案
4.1 CubeMX配置要点
-
时钟配置:
- 在Clock Configuration中设置ADC预分频器为PCLK2的6分频
- 确保最终ADC时钟不超过14MHz
-
ADC参数配置:
- 选择ADC1
- 启用IN10通道
- 配置参数:
- Resolution: 12位
- Scan Conversion Mode: Disabled
- Continuous Conversion Mode: Enabled
- Data Alignment: Right
- Sampling Time: 7.5 Cycles
-
GPIO配置:
- PC0自动配置为Analog模式
- 无需额外设置
4.2 关键代码实现
HAL库简化了底层操作,但校准步骤需要特别注意:
c复制// 初始化后执行校准
HAL_ADCEx_Calibration_Start(&hadc1);
// 启动连续转换
HAL_ADC_Start(&hadc1);
// 获取转换值
uint32_t rawValue = HAL_ADC_GetValue(&hadc1);
float voltage = rawValue * 3.3f / 4095.0f;
5. 调试与优化技巧
5.1 常见问题排查
-
ADC读数不稳定:
- 检查电源稳定性,VDDA和VSSA最好接去耦电容(0.1μF+1μF)
- 适当增加采样时间
- 确保模拟地(VSSA)和数字地良好连接
-
读数始终为0或4095:
- 检查GPIO模式是否正确配置为Analog
- 确认参考电压连接正常
- 检查电位器连接是否正确
-
转换速度慢:
- 减少采样时间(但可能影响精度)
- 提高ADC时钟频率(不超过14MHz)
- 考虑使用DMA传输数据
5.2 精度优化建议
-
参考电压处理:
- 使用外部精密参考电压源
- 在VDDA和VSSA之间添加滤波电容
-
软件滤波:
- 实现滑动平均滤波:
c复制#define SAMPLE_SIZE 8 float filteredVoltage = 0; for(int i=0; i<SAMPLE_SIZE; i++){ filteredVoltage += ADC1_GetVol(); HAL_Delay(1); } filteredVoltage /= SAMPLE_SIZE; -
校准技巧:
- 在系统温度稳定后进行校准
- 定期重新校准(特别是温度变化大的环境)
6. 项目扩展思路
这个基础案例可以扩展为更多实用应用:
-
多通道采集:
- 配置扫描模式
- 设置多个通道的转换序列
- 使用DMA传输数据
-
低功耗应用:
- 使用间断模式
- 配合定时器触发采样
- 采样后进入低功耗模式
-
高精度测量:
- 使用过采样技术提高分辨率
- 添加硬件滤波电路
- 实现软件校准算法(两点校准)
-
实时监控系统:
- 结合FreeRTOS创建独立采集任务
- 实现阈值报警功能
- 添加数据记录功能
在实际项目中,我经常发现ADC性能受PCB布局影响很大。模拟信号走线应尽量短,远离数字信号线,必要时使用屏蔽措施。另外,对于精密测量,建议在软件中实现自动校准功能,定期测量内部参考电压并修正读数。
