3. STM32 ADC外设配置:时钟配置、采样时间、扫描模式,一步步教你初始化
好,咱们接着往下走。上一章我们把ADC的硬件原理和引脚搞清楚了,这一章就动真格的了——配置STM32的ADC外设。
说实话,ADC初始化这块,我当年刚入行时也踩过不少坑。尤其是时钟配置,稍不注意,采样出来的数据就是一堆乱码。你想想看,一个制氧机要是氧浓度算错了,那可是要出大事的。
所以这一章,我会手把手带你走一遍初始化流程。咱们以STM32F103系列为例,其他型号大同小异。
3.1 时钟配置——ADC的“心跳”
ADC要工作,首先得有时钟。就像人要有心跳一样。STM32的ADC时钟来自APB2总线,但有个关键点:ADC最大时钟不能超过14MHz。
为什么?因为ADC内部是逐次逼近型结构,时钟太快,比较器来不及稳定。我在项目中遇到过,有人直接把APB2的72MHz分给ADC,结果采样值跳得像心电图——嗯,那肯定是不行的。
正确的做法是:先配置APB2时钟(通常是72MHz),然后通过ADC预分频器分频。分频系数可选2、4、6、8。72MHz分频后,只有选8才能得到9MHz,安全落在14MHz以内。
我个人习惯用6分频,得到12MHz。这样既满足最大频率限制,又能保证采样速度。代码里这样写:
// 使能ADC1时钟,挂在APB2上
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// ADC预分频:6分频 -> 72MHz / 6 = 12MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
你看,就两行。但这两行决定了后面所有数据的质量。
3.2 采样时间——别让信号“饿着”
时钟配好了,接下来是采样时间。这个参数很多人忽略,但它直接影响采样值的准确性。
采样时间,说白了就是ADC内部采样电容充电的时间。如果时间太短,电容没充满,采到的电压就偏低。你想想看,制氧机里氧浓度传感器输出的是微弱信号,本来就小,再被“饿着”采样,那误差能不大吗?
STM32的采样时间可以配置为1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个ADC时钟周期。我一般给传感器信号配55.5或71.5个周期,给电源电压这种稳定信号配13.5就够了。
配置代码示例:
// 配置ADC1的采样时间
// 通道0(PA0)接氧浓度传感器,用55.5个周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 通道1(PA1)接温度补偿,用13.5个周期就够了
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_13Cycles5);
这里有个细节:第二个参数是通道号,第三个参数是规则组里的序列位置。我习惯把最重要的传感器放在序列1,这样心里踏实。
3.3 扫描模式——一次搞定多通道
制氧机里,我们通常要采集多个信号:氧浓度、温度、压力、流量……一个个手动触发太傻了。这时候就要用扫描模式。
扫描模式的意思是:ADC自动把规则组里配置的所有通道依次转换一遍。你只需要启动一次,它自己跑完所有通道,然后产生一个DMA请求或中断。
配置扫描模式很简单:
// 开启扫描模式
ADC_ScanModeCmd(ADC1, ENABLE);
// 开启连续转换模式(可选,看需求)
ADC_ContinuousModeCmd(ADC1, ENABLE);
注意:扫描模式必须配合DMA使用,否则你来不及读数据,下一个转换结果就把上一个覆盖了。我刚开始做的时候忘了开DMA,结果读到的全是最后一个通道的值——排查了半天才发现。
- 扫描模式 + 连续转换 + DMA = 自动采集多通道
- 扫描模式 + 单次转换 + 查询 = 手动触发多通道
- 不开启扫描模式 = 只转换一个通道
3.4 完整初始化代码——一步到位
好了,把上面所有东西串起来,就是一套完整的ADC初始化。我直接贴代码,注释写得很清楚:
void ADC_Init_For_Oxygen(void)
{
// 1. 时钟配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 12MHz
// 2. GPIO配置:PA0、PA1为模拟输入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. ADC基本配置
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 2; // 2个通道
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 配置采样时间
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_13Cycles5);
// 5. 使能ADC并校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 6. 启动转换(配合DMA使用)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
这段代码我用了很多项目,基本没出过问题。唯一要注意的是校准那一步——必须等校准完成再启动转换,否则第一次采到的数据是错的。
3.5 避坑指南——我踩过的雷
最后,分享几个我实际项目中遇到的坑,你遇到了可以直接绕过去:
- 时钟分频选错: 我曾经用2分频,ADC跑到36MHz,结果数据全是0xFFFF。查了半天才发现是时钟超了。记住:不超过14MHz。
- 采样时间太短: 有个项目用7.5个周期采温度传感器,结果温度每跳1度,ADC值跳20个LSB。后来改成55.5,稳如老狗。
- 忘记开DMA: 扫描模式下不开DMA,你只能读到最后一个通道的值。这个坑我至少见过3个新手踩过。
- 校准没等完成: 校准标志位没检查就直接启动转换,第一次数据偏差很大。加个while循环等一等就好。
嗯,这一章的内容就这些。配置ADC其实不难,关键是每个参数都要理解它为什么这么设。下一章我们会讲DMA传输,把数据从ADC搬到内存里,那才是真正的高效玩法。
你先把初始化代码跑通,有问题随时翻回来看看。