前言:一位朋友帮忙写的一个应用笔记本,特意分享给各位。 p* Y0 B6 s& `" N
关于STM32F0和F1外设ADC配置的差异分析
# H3 ]2 T' s# J1 ^3 x
$ I: C2 t. J4 c0 y' R7 L8 @ `0 Q迷惘性思考1." m. U: D$ a$ I0 a; [
" h& s8 \8 `/ q8 fSTM32F0的ADC怎么就无法配置为12位
O. _: x, Y& ^, m5 d2 n. _: O
6 Y6 p7 ?- k( q! Y: `4 Z k6 Q5 y问题提出:! @. L; W t5 {
3 \( {# N5 _0 R% W. u) U在设计一个以STM32F042K6T6为核心的监控板的过程中,用到了常规外设ADC。当然,ADC外设非常常规,使用应该不会有很大问题。于是,迅速码出以下代码。
- y4 K5 h l1 N: `2 P相信绝大部分STM32玩家都是从某某原子开始入门STM32的。以上配置过程也基本与某某原子一致。但是很奇怪,当我在ADC输入端输入1.75V电压时,采集的ADC寄存器应该至少会大于0x7FF。但结果出乎意料,ADC寄存器结果略大于0x210。我尝试把输入电压增加至3.3V,ADC寄存器结果接近0x3FF。到这里就基本证明了ADC采样精度被设置成了10位,并未按代码执行。那么问题出在哪呢?某某原子例程就是这样配置的啊。
1 N' k7 H1 `8 q' q+ X9 n1 w7 K
4 j, Z0 R& Q3 r. K& V8 Q; o& K! f7 k/ U, f6 }5 m& A6 I5 V
那么接着我们就只能去查看寄存器的状态了。
6 |- ~! g& D9 [' p" T可以看到RES[1:0]此2位决定了ADC的采样精度。具体如下图。* {- @1 H$ W: Q: N/ C7 \
也就是说此时寄存器ADC_CFGR1中的RES[1:0]这两位为01。然后,我们需要了解一下ADC是如何给这个寄存器赋值的。查看ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef*ADC_InitStruct)的函数体,如下图。* f- z/ k# P. `( q/ o
其中的tmpreg在与ADC_InitStruct这个结构体中的各个元素进行或运算后,将结果赋值给了CFGR1 寄存器。找到自己写的以上结构体的赋值过程。对比发现ADC_ExternalTrigConvEdge这个元素,我没有进行赋值(因为用不到)。而C语言中没有赋过值的变量,编译器是默认将其赋值为0。如果是0的话,那个或运算就不可能造成RES[1:0]被错误置位的情况。反复确认后,确定了问题就出在这个没有被赋值的变量上。/ r _. h7 j: [7 o$ v" }, p4 E
% }, @# Y7 s }% F6 P" s6 E$ A: g% c9 b% D$ X Y, Y" G# d
这里有一点关于C语言变量初始化问题。外部全局的和静态变量,编译器基本都会(但也有时候不会)把变量初始化为0,其余内部变量都不进行初始化,其值是随机的。因此,保险起见,不论外部全局、静态还是内部的变量都要初始化。但是这里回头想想,某某原子的ADC初始化例程也是这么做的啊,如下图。
3 u0 S5 ~2 ?/ r4 \9 ]* X8 g/ t/ Q最终对比官方的初始化例程,发现缺少ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct)这个函数。函数体如下。
2 o9 X# F/ ~* }, s/ s; i 不出意料,执行完初始化ADC结构体之后,寄存器ADC_CFGR1值全为0。因此,为了防止结构体某些变量未初始化,最好调用结构体初始化函数将结构体提前全部复位后,再进行赋值。最后,正确的配置如下。( {. \# [( [4 d. w2 m6 l% l
迷惘性思考2. p/ a$ @! o7 X1 m, [; i" D
STM32F0的ADC多路单通道采集数据为什么不变
- n& d+ a( `4 `: S3 N7 ]既然完成了ADC配置初始化,下一步就到了ADC的通道数据采集了。先贴出某某原子的例程。再看自己码出的代码。
2 S9 h% E5 P7 Z; }3 U6 x- q8 Y( A3 L9 Z" }9 d3 K9 |( _ F/ }% F
@5 K' x( f" U) ]
$ l8 o' ^# e( O0 _: k1 @% J 因为需要多通道采集,只需要多调用上述函数即可,如下图。
% b+ j; Q7 \0 z 通道1-3输入电压分别为1V,2V,3V。不断调用上述函数后,发现adc_val[0]-adc_val[1]值均为0xE8A。显然与实际的电压不相符。采集到的电压均为通道3的电压。那么,将问题定位到函数ADC_ChannelConfig(ADC1,chn, ADC_SampleTime_239_5Cycles),其函数体如下。
: N3 C( g" u9 H9 } 从函数体中可以看出,问题就出在红色线条处。库函数中对ADC通道选择寄存器的赋值采用了位或运算。第一次调用get_adc_val()的时候ADC_Channel=0x00000001,CHSELR=0x00000001,ADC转换数据对应第一通道;第二次调用get_adc_val()的时候,ADC_Channel=0x00000010,/ e' w7 d: ]: w9 U/ v$ ~- A8 f
2 k. Y6 b' j1 @* m% k1 c0 v3 z( }4 [) H9 q$ a. U
CHSELR=0x00000011;因为是独立采样模式,系统对高位有效通道进行采样,对应结果为第2通道的值。第二次调用get_adc_val()的时候,ADC_Channel=0x0000100,CHSELR=0x00000111,同样,因为是独立采样模式,对应结果为第3通道的值。再次调用Get_adc_val()时,我们预期结果希望是第一通道,但是实际上此时的CHSELR值已经是0x00000111了,再和0x00000001位或,值还是0x00000111,所以实际结果是最高位,即第3通道的值。最终针对此问题,最终采集代码如下。& Q4 }) ?. Z4 n; H
最终,针对STM32F0和STM32F1系列,虽然均为STM32,虽然大多数操作均可借鉴使用,但某些寄存器的操作可能存在细微差别。就如上述所讨论的ADC初始化和ADC通道采集问题。同样的操作在STM32F1上操作无问题。9 z( S7 Q% w; y; L5 d# V
% j: J) U8 H) J# [1 O4 p9 u2 W6 {8 t: ^! n- h+ S
|