4、单通道ADC采集实战:用STM32CubeMX生成代码,读取氧气浓度传感器的模拟电压
好,咱们终于到了动手环节。
前面讲了那么多理论,什么采样定理、量化误差、滤波算法……说实话,光听不练容易犯困。我自己带新人时有个习惯:先让他们把ADC调通,看到电压值在屏幕上跳,兴趣就来了。
这一章,我们就用STM32CubeMX生成一个完整的单通道ADC工程。目标很明确——读取氧气浓度传感器的模拟电压,把原始值换算成实际电压,再映射成氧浓度百分比。
4.1 硬件准备:你需要什么?
先列个清单,免得你焊了半天发现少个电阻。
- 主控板:STM32F103C8T6(蓝色小板子,我项目里用得最多)
- 氧气浓度传感器:比如ME2-O2,输出0~2V对应0~25%氧浓度
- 分压电阻:如果传感器输出超过3.3V,需要分压。ME2-O2最高2V,可以直接接
- 100nF电容:在ADC引脚对地并联,滤高频噪声。我习惯在传感器输出端也加一个
- 杜邦线、面包板:调试用
4.2 CubeMX配置步骤
打开STM32CubeMX,新建工程,选好你的芯片型号。我以F103C8为例。
4.2.1 配置时钟
先把时钟树搞定。我个人习惯用外部8MHz晶振,通过PLL倍频到72MHz。
- RCC → HSE → Crystal/Ceramic Resonator
- Clock Configuration → HCLK设为72MHz
ADC的时钟频率不能太高。F103的ADC最大时钟是14MHz,我一般分频到12MHz,采样时间设长一点,精度更好。
4.2.2 配置ADC
在Pinout & Configuration里找到ADC1,开始配置:
- Mode:IN0(对应PA0引脚)
- ADC_Settings:
- Data Alignment:Right alignment(右对齐,默认)
- Scan Conversion Mode:Disabled(单通道不需要扫描)
- Continuous Conversion Mode:Enabled(连续转换,省得每次手动触发)
- Number of Conversion:1
- ADC_Regular_Conversion:
- Sampling Time:55.5 Cycles(我习惯选长一点,信号更稳)
4.2.3 配置DMA(可选但推荐)
如果你不想让CPU一直轮询ADC,可以用DMA自动搬运数据。
- 添加DMA:ADC1 → DMA1 Channel1
- Mode:Circular(循环模式,一直转)
- Data Width:Half Word(ADC是12位,半字刚好)
用DMA的好处是:CPU可以干别的事,ADC数据自动存到数组里。我早期做项目时傻傻地用轮询,结果CPU占用率飙到30%,后来改用DMA,降到2%不到。
4.3 生成代码并编写逻辑
点击GENERATE CODE,Keil或STM32CubeIDE工程就出来了。打开main.c,找到main()函数。
4.3.1 添加全局变量
/* 在main.c顶部添加 */
uint16_t adc_raw_value = 0; // ADC原始值(0~4095)
float voltage = 0.0f; // 实际电压值(单位:V)
float oxygen_concentration = 0.0f; // 氧浓度百分比(%)
4.3.2 启动ADC转换
在main()的while(1)之前,加上启动代码:
/* 启动ADC校准(F103需要这一步) */
HAL_ADCEx_Calibration_Start(&hadc1);
/* 启动连续转换 */
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adc_raw_value, 1);
如果你没用DMA,就用:
HAL_ADC_Start(&hadc1);
4.3.3 主循环里做计算
while (1)
{
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
{
adc_raw_value = HAL_ADC_GetValue(&hadc1);
/* 换算成电压:3.3V / 4096 * 原始值 */
voltage = (3.3f / 4096.0f) * (float)adc_raw_value;
/* 映射成氧浓度:传感器0~2V对应0~25% */
oxygen_concentration = (voltage / 2.0f) * 25.0f;
/* 限幅保护 */
if (oxygen_concentration < 0.0f) oxygen_concentration = 0.0f;
if (oxygen_concentration > 25.0f) oxygen_concentration = 25.0f;
}
HAL_Delay(100); // 100ms采集一次,够用了
}
关键点:
- ADC是12位,所以最大值是4095(2^12 - 1),不是4096。但用4096算电压更准,因为0V对应0,3.3V对应4095,中间是线性关系。
- 传感器映射公式一定要看数据手册。ME2-O2是0~2V对应0~25%,但不同型号可能不一样。
4.4 避坑指南:我踩过的三个坑
做ADC采集,看着简单,实际坑不少。我一个个说:
坑一:参考电压不准
STM32的内部参考电压(VREFINT)其实挺稳的,但如果你用VDDA作为参考,而VDDA有纹波,那采集值就会跳。我曾经在一个项目里,电源纹波200mV,ADC值跳了30多个LSB。
解决办法:在VDDA引脚对地加一个10μF+100nF的滤波电容,或者用外部基准电压芯片(比如TL431)。
坑二:引脚悬空
ADC引脚如果不接传感器,悬空状态下会采集到随机值。我见过有人调试时没接传感器,看到电压在1.2V~2.8V之间乱跳,以为是代码写错了,查了两天。
解决办法:不用的ADC引脚接地,或者配置成GPIO输出低电平。
坑三:采样时间太短
传感器输出有内阻,如果采样时间太短,采样电容充不满电,结果偏小。F103的ADC采样电容约4pF,如果传感器内阻10kΩ,采样时间至少需要1.5μs。
经验值:我一般选55.5 Cycles(约4.6μs@12MHz),对于大多数传感器都够用。
4.5 验证结果
程序烧进去后,用串口打印出来看看:
printf("ADC Raw: %d, Voltage: %.3f V, O2: %.1f%%\r\n",
adc_raw_value, voltage, oxygen_concentration);
正常情况,在空气中(21%氧浓度),你应该读到:
- ADC原始值:约2600~2700(取决于参考电压精度)
- 电压值:约1.68V(21% / 25% * 2V)
- 氧浓度:约21%
如果数值偏差大,先检查分压电阻和参考电压。我习惯用万用表量一下传感器输出脚,确认实际电压对不对。
4.6 小结
这一章我们走通了完整的ADC采集流程:从CubeMX配置,到代码生成,再到实际换算。说白了,ADC就是个电压-数字转换器,你给它电压,它给你数字,剩下的就是小学数学。
下一章我们会讲怎么对采集到的原始数据进行滤波处理。你想想看,如果ADC值直接拿去用,稍微有点噪声,氧浓度就上下乱跳,病人看着都慌。所以,滤波是必须的。
嗯,今天就到这里。去把你的传感器接上,看看串口打印出来的数据稳不稳。