前言:一位朋友帮忙写的一个应用笔记本,特意分享给各位。
关于STM32F0和F1外设ADC配置的差异分析
迷惘性思考1.
STM32F0的ADC怎么就无法配置为12位
问题提出:
在设计一个以STM32F042K6T6为核心的监控板的过程中,用到了常规外设ADC。当然,ADC外设非常常规,使用应该不会有很大问题。于是,迅速码出以下代码。
相信绝大部分STM32玩家都是从某某原子开始入门STM32的。以上配置过程也基本与某某原子一致。但是很奇怪,当我在ADC输入端输入1.75V电压时,采集的ADC寄存器应该至少会大于0x7FF。但结果出乎意料,ADC寄存器结果略大于0x210。我尝试把输入电压增加至3.3V,ADC寄存器结果接近0x3FF。到这里就基本证明了ADC采样精度被设置成了10位,并未按代码执行。那么问题出在哪呢?某某原子例程就是这样配置的啊。
那么接着我们就只能去查看寄存器的状态了。
可以看到RES[1:0]此2位决定了ADC的采样精度。具体如下图。
也就是说此时寄存器ADC_CFGR1中的RES[1:0]这两位为01。然后,我们需要了解一下ADC是如何给这个寄存器赋值的。查看ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef*ADC_InitStruct)的函数体,如下图。
其中的tmpreg在与ADC_InitStruct这个结构体中的各个元素进行或运算后,将结果赋值给了CFGR1 寄存器。找到自己写的以上结构体的赋值过程。对比发现ADC_ExternalTrigConvEdge这个元素,我没有进行赋值(因为用不到)。而C语言中没有赋过值的变量,编译器是默认将其赋值为0。如果是0的话,那个或运算就不可能造成RES[1:0]被错误置位的情况。反复确认后,确定了问题就出在这个没有被赋值的变量上。
这里有一点关于C语言变量初始化问题。外部全局的和静态变量,编译器基本都会(但也有时候不会)把变量初始化为0,其余内部变量都不进行初始化,其值是随机的。因此,保险起见,不论外部全局、静态还是内部的变量都要初始化。但是这里回头想想,某某原子的ADC初始化例程也是这么做的啊,如下图。
最终对比官方的初始化例程,发现缺少ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct)这个函数。函数体如下。
不出意料,执行完初始化ADC结构体之后,寄存器ADC_CFGR1值全为0。因此,为了防止结构体某些变量未初始化,最好调用结构体初始化函数将结构体提前全部复位后,再进行赋值。最后,正确的配置如下。
迷惘性思考2.
STM32F0的ADC多路单通道采集数据为什么不变
既然完成了ADC配置初始化,下一步就到了ADC的通道数据采集了。先贴出某某原子的例程。再看自己码出的代码。
因为需要多通道采集,只需要多调用上述函数即可,如下图。
通道1-3输入电压分别为1V,2V,3V。不断调用上述函数后,发现adc_val[0]-adc_val[1]值均为0xE8A。显然与实际的电压不相符。采集到的电压均为通道3的电压。那么,将问题定位到函数ADC_ChannelConfig(ADC1,chn, ADC_SampleTime_239_5Cycles),其函数体如下。
从函数体中可以看出,问题就出在红色线条处。库函数中对ADC通道选择寄存器的赋值采用了位或运算。第一次调用get_adc_val()的时候ADC_Channel=0x00000001,CHSELR=0x00000001,ADC转换数据对应第一通道;第二次调用get_adc_val()的时候,ADC_Channel=0x00000010,
CHSELR=0x00000011;因为是独立采样模式,系统对高位有效通道进行采样,对应结果为第2通道的值。第二次调用get_adc_val()的时候,ADC_Channel=0x0000100,CHSELR=0x00000111,同样,因为是独立采样模式,对应结果为第3通道的值。再次调用Get_adc_val()时,我们预期结果希望是第一通道,但是实际上此时的CHSELR值已经是0x00000111了,再和0x00000001位或,值还是0x00000111,所以实际结果是最高位,即第3通道的值。最终针对此问题,最终采集代码如下。
最终,针对STM32F0和STM32F1系列,虽然均为STM32,虽然大多数操作均可借鉴使用,但某些寄存器的操作可能存在细微差别。就如上述所讨论的ADC初始化和ADC通道采集问题。同样的操作在STM32F1上操作无问题。