一、什么是ADC( u% o2 k9 J3 d5 ^$ N2 _/ R
ADC(Analogto-Digital Converter)模拟数字转换器,是将模拟信号转换成数字信号的一种外设。比如某一个电阻两端的是一个模拟信号,单片机无法直接采集,此时需要ADC先将短租两端的电压这个模拟信号转化成数字信号,单片机才能够进行处理。+ G: y" `7 |( U, G, X
9 k. @, Z7 i$ r" D1 {* p3 p, R二、ADC的用途; [# T' E- [& C
ADC具有将模拟信号转换成数字信号的能力,比如将模拟的电压转换成数字信号,单片机进行处理。可以用作温度监测或者电流监测等方面,用途极广。6 s" B4 H) i' R' x3 X% i
5 t; S. P( j$ N1 H
三、STM32F103ZET6的ADC& c8 R1 @& L6 F4 N, s
根据中文参考手册介绍,STM32F103ZET6单片机有3个12位ADC,共有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
$ f- t& U. A, g3 ?2 \7 E" i8 K/ m# e! n( q; `: E# f3 b
. [4 S, c* B0 w3 o' w! l3.1 ADC通道对应引脚+ N r" l4 C: D
STM32F103ZET6的ADC各通道对应IO如下
% M9 h8 }! m5 d) ], x/ R0 C- h3 Y
5 Z4 I/ d+ a7 G& L6 T
; A) ^* ] y. P2 }- ]7 H " d- V6 _& t+ }* X& r
3.2ADC时钟
2 q# M: Q/ l( m* L9 g$ i# d) UADC输入时钟ADC_CLK由APB2分频产生,最大值是14MHz。库函数提供了设置分频因子的函数
2 U" W# X1 \/ |0 G: W- void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
复制代码
5 b$ ~' {5 y6 P9 C2 \% |可选的分频因子有% f1 v, X; j/ |* Q4 D
- #define RCC_PCLK2_Div2 ((uint32_t)0x00000000) u; e1 h0 D% `0 J3 F
- #define RCC_PCLK2_Div4 ((uint32_t)0x00004000)
4 y. B- d* e6 e2 d5 a) Q - #define RCC_PCLK2_Div6 ((uint32_t)0x00008000)6 Y9 c U) ?9 }! f1 K' u" z* @
- #define RCC_PCLK2_Div8 ((uint32_t)0x0000C000)
复制代码
- i8 b4 Y/ N, w3 V+ Y( b E# bAPB2总线时钟为72MHz,而ADC的最大工作频率为14MHz,所以,分频因子一般设置为6,这样ADC的输入时钟频率为12MHz。7 X8 ?2 w- Q5 C3 x- S
+ S% l) ^$ U, G2 L( K) f3.3 ADC工作模式# G% R# V* Q' o. }" h" n* L
根据中文参考手册介绍,STM32F1的ADC有三种工作模式+ H3 l- ?. B* S* X
• 单次转换模式 单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0。) r6 t' K" p( Z( R
% U0 \0 C2 L# ?5 T% c& p• 连续转换模式 在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。& t3 C+ U0 l) ]. {* D
. \0 ]7 G+ w% I% R9 B. g• 扫描模式
" a, b9 X, }2 G' N; i( K. T. A( i6 ]6 I1 i( Y! Y( d
+ c3 i3 v5 u {33.4 ADC转换时间8 t+ o+ s: [: d
ADC的总转换时间与时钟频率有关,总转换时间 = 采样时间 + 12.5个周期。其中,采样时间最短为1.5个周期,也就是最短转换时间为14个时钟周期。使用软件触发时,可选择的采样时间如下: A7 k4 |( l6 [- l- r% ^" H
- #define ADC_SampleTime_1Cycles5 ((uint8_t)0x00)! w' R& H9 |+ [- S
- #define ADC_SampleTime_7Cycles5 ((uint8_t)0x01)8 `2 g' O8 D# t
- #define ADC_SampleTime_13Cycles5 ((uint8_t)0x02)5 _! O. b2 q+ h2 k, B [! P; a
- #define ADC_SampleTime_28Cycles5 ((uint8_t)0x03)( H0 l' G, j% r$ c7 n
- #define ADC_SampleTime_41Cycles5 ((uint8_t)0x04)
2 E: S) M' L3 {2 W - #define ADC_SampleTime_55Cycles5 ((uint8_t)0x05)
- l* R. R; ^! g' J% x: G; R9 B - #define ADC_SampleTime_71Cycles5 ((uint8_t)0x06)
& _) [, H0 q8 l9 l; z6 h - #define ADC_SampleTime_239Cycles5 ((uint8_t)0x07)
复制代码
0 A% H' n! {" E$ j3.5 ADC校准6 x _7 }7 H8 @
使能ADC后,需要对ADC进行校准。使用库函数开发时,提供了ADC校准的函数; Q0 \ L, d9 K9 a6 d# e
- ADC_ResetCalibration(ADC1);//重置指定的ADC的校准寄存器
2 z! q. ]1 A4 Z: [ - while(ADC_GetResetCalibrationStatus(ADC1));//获取ADC重置校准寄存器的状态
% X. c/ W+ `/ m" Y8 ?+ e -
3 _6 [/ a* l1 |1 A7 N - ADC_StartCalibration(ADC1);//开始指定ADC的校准状态
7 ~! w% N' b% {) {8 E# l - while(ADC_GetCalibrationStatus(ADC1));//获取指定ADC的校准程序
复制代码
' q, I% }9 h3 O/ ]$ d3.6 ADC转换结果与实际电压的换算: L/ [1 X. Z4 q4 G" X
获取到的AD转换结果并不是实际电压,如果想要得到实际电压,需要经过换算。上面介绍了,STM32的ADC为12位,也就是AD值取值范围为0~4095。采集电压范围为0到3.3V。AD值与实际电压之间存在比例关系。
0 J) ?+ u: M2 x# i z- ]& n
7 }* L* ]* z$ q3 r& k6 q) y实际电压 = (AD值 / 4095) * 3.3。单位为伏特(V)7 _1 t8 B5 |& ~; V7 j; W+ D
) p; k* d! H/ Q# ^/ H" w
8 e. L0 H, H; I四、ADC配置步骤; d& x/ K' Z* l5 x6 Q+ q/ E/ y* o
• 使能GPIO时钟和ADC时钟,设置引脚为模拟输入
/ O- A8 e |0 T, E. l% N• 设置ADC的分频因子; M( s* G0 M1 N! `" @ U3 k
• 初始化ADC参数,包括ADC工作模式,规则序列等1 e5 a; y! K2 l: h+ R1 i; K
• 使能ADC并校准
8 N( Z+ K, J' |7 y* A' L6 b2 e9 _• 触发AD转换,读取AD转换值
) @4 v! {2 H% m
% _4 | D7 Y. I. a$ ~
) _9 K; Q4 @: }% z& N/ C0 i: E% o五、ADC配置程序
9 k' a- M! k3 Q3 n9 r6 l8 J55.1 ADC初始化程序
9 N0 f3 I3 O' E8 S' E" u0 E这里以配置ADC1的通道1为例,给出ADC的配置例程,分频因子设置为6,单次转换模式,软件触发。
2 W3 ?! h, i) e' n- /*
6 {" g& _' ]; X/ U - *==============================================================================" l+ g; y) \/ h8 I% y; [
- *函数名称:ADC1_Init! @. m( Q7 X' V3 b5 b& J8 O
- *函数功能:初始化ADCx
1 w1 z7 C9 [9 J( j& } - *输入参数:无' b( f$ m, B: T9 E" z
- *返回值:无
4 ?: U5 q7 o) U* b+ `- h0 X( y5 A - *备 注:无+ L8 O0 U0 g- w, F$ g2 P
- *==============================================================================
' {, Z( e. f# S4 b6 ]) P - */' B! x, h( ^. O% u- Q
- void ADC1_Init(void)) `) [: I$ ]0 a k9 {" D
- {
! X! `9 G/ z1 h% \ - // 结构体定义
$ I: p( _2 v( y - GPIO_InitTypeDef GPIO_InitStructure;
( F2 J: b4 V/ q9 w2 L - ADC_InitTypeDef ADC_InitStructure;
# M2 \' G9 D3 i) F8 k -
: h9 K5 v7 m4 E, R& f& G0 z - // 开启时钟
9 L# Q7 L/ K( Z- w$ ?9 f" z) n/ ^ - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);
+ ~% j/ X$ s8 e, r4 F - ) @5 ]+ ^2 Y# t& M' f
- // 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
/ ^/ e* k5 d/ _/ B - RCC_ADCCLKConfig(RCC_PCLK2_Div6);1 l- s: S$ S6 b6 u
- % e; E! l* i0 T. D6 q
- // GPIO配置* q1 _8 F! R, g& C5 M
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1
0 P: X$ G- Z6 N8 ?( j+ E4 j - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模拟输入
+ O4 k7 Z% i( u9 k) ]( g - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;& u1 t7 H2 b" i1 s" e2 d
- GPIO_Init(GPIOA,&GPIO_InitStructure);7 }4 {! e5 Y( A/ d% ?3 v3 Z
- 2 X; o1 X$ W% Z, d; M) `
- // ADC参数配置
/ J* s+ P1 ]: M3 E5 T5 n4 s0 F - ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
$ {9 ~3 C" L- K9 a7 { - ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
5 g2 j% b! d# Z0 a: ^ - ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 关闭连续转换
0 z6 M- A6 l; t" u- d( Y$ W4 U - ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 禁止触发检测,使用软件触发
7 q1 c4 J* H* m' [; D - ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
3 S3 }2 r5 }$ J" A3 j - ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个转换在规则序列中 也就是只转换规则序列1 / p2 ]# _0 @4 _4 e* p
- ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化; u' ~9 c: L5 G, Y+ l4 s+ ^' x
- $ A0 ?/ A/ x2 `
- ADC_Cmd(ADC1, ENABLE); // 开启AD转换器 C, h0 w+ q4 M( [* h$ g7 I( t
- 9 Y; l' f/ o* c' T# m
- // ADC校准
3 _( A1 s& @. r6 d9 H' Y - ADC_ResetCalibration(ADC1); // 重置指定的ADC的校准寄存器3 \- C& s: I- f& i2 y( t
- while(ADC_GetResetCalibrationStatus(ADC1)); // 获取ADC重置校准寄存器的状态
+ J+ E9 U. S9 t - & @) G' K! D! ~
- ADC_StartCalibration(ADC1); // 开始指定ADC的校准状态8 H) s2 B7 J) v' s- [3 p5 k- b
- while(ADC_GetCalibrationStatus(ADC1)); // 获取指定ADC的校准程序
* h' p* b! f; J3 u! u/ V6 \; H
7 c0 `, _3 T% ?- ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的软件转换启动功能! E, m- u1 ?8 {
- }
复制代码 # e& K/ N/ V2 }, P1 H* S( J: l4 P3 _; g
5.2 软件触发AD转换
0 x0 R. A7 \1 {2 D' x8 W$ A7 Q, `5 u库函数开发,配置为软件触发时,可以通过下面的函数触发AD转换
( u: w5 n5 W- ~- void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)
复制代码 4 V% u" T# z8 { w3 u0 T8 a, \7 L2 d2 V
5.3 读取AD转换结果; ?4 t3 K" n% F+ y
库函数提供了用于读取AD转换结果的函数9 K# r- H$ a% ^* Y6 l/ d9 ]
- uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx)
复制代码 ) `; b5 _& y& y: R0 `; m) ~
这里给出另一个函数,用于软件触发AD转换并读取转换结果0 s4 l$ a0 X. r0 {/ E. k6 t
- /*0 D8 Q8 K+ P6 a
- *==============================================================================; o6 x+ b7 ]1 z5 W1 F3 `8 p
- *函数名称:Get_ADC_Value) v; L6 W/ _& L/ T
- *函数功能:读取某一规则通道AD值* N- E* ?: Z7 z
- *输入参数:ch:规则通道ADC_Channel_x;times:读取次数; z& v& E. y+ q
- *返回值:无
% k) H) C! @. A! O; w P - *备 注:该函数配置好后,返回的结果是N次后的平均值: I" ]9 M' [! c4 I& d+ y
- *==============================================================================& `4 U$ {* W* }2 \; i
- */- W8 m7 V B1 W0 N9 _* Z- z& B
- u16 Get_ADC_Value(u8 ch,u8 times)
: {) D/ v* K$ u" R0 h - {: U' \% O. E& J
- u32 temp_val = 0;
6 B! A9 E8 U7 c - u8 t;
U7 m) j; n/ R% X- [' _; \3 ~ - // 设置指定ADC的规则组通道,一个序列,采样时间
$ n1 E0 q/ _ A9 b3 E6 Z6 L - // ADC1,ADC通道,239.5个周期,提高采样时间可以提高精确度
0 v1 Q. j& h. J - ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
8 q% C L8 o' r7 c5 M! { -
8 _* v! \3 `" I: ~ - for(t=0;t<times;t++)
! U$ }" Y# [# K& S - {
7 @9 Y4 W- _( I - ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能指定的ADC1的软件转换启动功能
2 i0 X8 a# I2 z3 _; ] - while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC )); // 等待转换结束- G+ x/ l5 e9 D0 V7 H" j! L* w. {
- temp_val+=ADC_GetConversionValue(ADC1);
( ], R3 }( v+ W7 ^' \ - delay_ms(5);7 ` m' M4 C" w/ A
- }
. e- E* J; k# ]( ] - return temp_val/times;! _3 U ~7 I3 n3 r' u+ x
- }
复制代码 1 t3 @4 Z O3 s' K# |# X' j
六、实战项目' o( t$ k+ C% {: G! _" G0 J
用ADC1的通道1采集某电阻两端电压(由于普中核心板没有可供采集的电阻,可以直接将采集引脚接到3.3V查看一下结果),将结果通过串口打印到电脑。其中关于串口的配置就不再做介绍,给出ADC的配置和main函数。
+ @4 g& n$ L, d5 ^' S6 ^1 |7 n) F
6.1 ADC初始化
/ U/ \$ L% x9 b4 s. J4 P( [- /*
: J2 k. k# S# l6 k7 a) p - *==============================================================================( [& H' q0 K5 C& @ r
- *函数名称:ADC1_Init9 Z9 k. Q6 Y; g- E! P- h
- *函数功能:初始化ADCx; S+ R0 d5 R" }. n) z9 a: g* d
- *输入参数:无
0 T, L: n% z% k - *返回值:无
# Q5 c# {7 L! Q. Y - *备 注:无2 F5 F2 l, J( `) T! ~
- *==============================================================================% d. V( g$ b( d6 ]! Q* E& ?
- */
4 C+ s/ v9 j- `# v2 Q - void ADC1_Init(void): s7 Y# l0 S1 {5 p
- {! q3 o8 P& e7 k8 d4 i, Z& _
- // 结构体定义. O1 ~ [( }. Y5 f# h# }! d
- GPIO_InitTypeDef GPIO_InitStructure;$ R5 d- n' _/ l& N8 U: j
- ADC_InitTypeDef ADC_InitStructure;
; P; A% z3 w* a* f& N. } -
' m7 Z1 {- B& V& s - // 开启时钟
/ ]- Q) i: b( `3 ~ - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);) G/ k4 o R$ o
-
3 F- z! f5 M" D2 p( @+ r" O - // 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M9 _2 g/ y, c L/ Y) X* c( y
- RCC_ADCCLKConfig(RCC_PCLK2_Div6); X( k- E. a; l
- - n1 M T$ {, {5 h
- // GPIO配置6 f! g6 j/ |) \5 L W1 b0 c# T
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1
, J" \. o0 J, s3 f8 _% W4 o4 a - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模拟输入- u9 y o5 i f& g* v0 |
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
+ i+ f. }7 t9 Z" J. j1 f+ t - GPIO_Init(GPIOA,&GPIO_InitStructure);( H$ j8 t! \* c" a3 x
-
, j' R( H$ W' \& y - // ADC参数配置/ g7 |" g( ] @% w: q
- ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式( u [# _7 g' d5 X7 g
- ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
4 r$ G0 Z( ~# y4 i - ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 关闭连续转换
! x$ X4 P, h) g2 u - ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 禁止触发检测,使用软件触发4 j& C& J* X; b8 J
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
' [5 f, l. X; n - ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个转换在规则序列中 也就是只转换规则序列1 2 N; U4 y8 ?3 R# I7 L( I0 Q- [
- ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化1 ~! k+ p5 S2 T5 [0 p
-
" ]2 \5 u9 V" Z1 {% a - ADC_Cmd(ADC1, ENABLE); // 开启AD转换器
9 j( v! A8 F4 p: Q( j -
) W& g) f* O$ U% d& c$ H - // ADC校准0 v7 }# e/ t5 C
- ADC_ResetCalibration(ADC1); // 重置指定的ADC的校准寄存器# q b) @9 O0 q
- while(ADC_GetResetCalibrationStatus(ADC1)); // 获取ADC重置校准寄存器的状态
2 T, N/ u1 f0 C* [$ A0 q2 W - ) c! x) i6 a* Q4 a. ?. _
- ADC_StartCalibration(ADC1); // 开始指定ADC的校准状态: [) K+ }+ x# _4 q& T9 {3 g r
- while(ADC_GetCalibrationStatus(ADC1)); // 获取指定ADC的校准程序
+ z& j/ B* I( E L& t - ) l) j3 b9 a8 M1 T8 D8 J, E
- ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的软件转换启动功能
% D4 r+ c4 Q+ e3 f$ N: L9 L - }
复制代码 4 s. P2 Q) [7 v9 Y+ ]9 V$ C- s( ~
, a/ h; D9 ~6 r1 K
6.2 main函数
9 j. O3 h! s* B& M+ A- u16 gAdcAdValue = 0; // 存储AD值
% O6 F5 e# B$ P0 v& _( ^. t7 N4 x - float gAdcVol = 0; // 实际电压值1 B* [# U! q6 y
- Q& _4 e9 x; \ C4 O
- int main(void)" [) r! X, R4 i7 l# E
- {
2 o9 `0 X. M3 ]+ g' O) o9 c - Med_Mcu_Iint(); // 系统初始化) j: ?8 \8 D; N
- 0 y7 H; v ~& ]
- while(1)" L7 m" F5 E' u- P, f1 |- x
- {
: {3 E' `* O7 z8 U - gAdcAdValue = Get_ADC_Value (ADC_Channel_1,10); // 获取转换结果
0 L2 D" t1 ~! |2 T - gAdcVol = (gAdcAdValue / 0xFFF) * 3.3; // 计算实际电压* A( i( m5 X" p/ g6 t) ?3 _
- printf ("Vol=%.1f V\r\n",gAdcVol); // 串口打印结果
/ @1 |: N- A5 @" n7 b' ^& @ -
. o# j+ M$ t& W% b/ r" g - delay_ms (500); // 防止打印过快
9 S# \/ e% k g. P - }
: N* O- j" _4 [' }4 x; @" I0 w8 g: t - }
复制代码 7 l8 ]: h8 F* p/ G0 I; ?" K
3 r6 w* S5 ^! B4 t七、拓展) [& F( \ f7 q* d6 T2 s" N* f/ ?
7.1 定时器触发ADC采集
9 Z. I$ W) c; t# M根据中文参考手册介绍,ADC可以通过定时器触发AD转换(只有PWM的上升沿可以触发AD转换)。触发方式有以下几种% D' J1 G# o9 u/ \' e
• TIM1_CH1 :定时器 1 的通道 1 的 PWM 触发! ?' u" b3 Y+ a3 \% n* \8 r
• TIM1_CH2 : 定时器 2 的通道 2 的 PWM 触发( a$ J! W$ ]/ s9 a& P
• TIM1_CH3: 定时器 1 的通道 3 的 PWM 触发
) L& [/ B4 ^/ S; {" B• TIM2_CH2 : 定时器 2 的通道 2 的 PWM 触发
) ^" C/ K/ ], E• TIM3_TRGO: 定时器 3 触发* w+ Y% U! G' v* M$ W( `& O& H
• TIM4_CH4 : 定时器 4 的通道 4 的 PWM 触发
& o" v0 O( \9 S8 ~9 N6 h3 h9 s2 t- y8 `" V, W
, V; i' m8 a4 W
- h6 Q! t; S5 ?; f) g* ` A; RADC外部触发方式 * G) Q) l4 e$ v/ I E
* N; y" ]* D% ?( I3 N7 z9 K
7 ~! H8 m j: B& d这里以TIM4的通道4触发ADC采集为例,给出程序配置。3 g! u/ ^! g: I6 Z+ i1 A
' U8 H3 V3 [. R8 P8 K/ s首先是定时器PWM的配置,不对引脚进行重映射。
: x8 n8 v. c* i0 @3 h- /*
' J) s% z' M6 U$ i) U- w - *==============================================================================
) t+ |& G7 u. b: [7 m" _ - *函数名称:TIM4_CH4_PWM_Init
3 A; z" ^- m- y$ T& a. O - *函数功能:初始化定时器4的PWM通道47 m& [* l$ \3 |: G4 x' n8 D
- *输入参数:per:自动重装载值;psc:预分频系数
( H. j$ H* ^0 P5 S - *返回值:无
. c: o" s' @/ W, q" l! d - *备 注:无 k c, p. q1 U" [) Q8 g
- *==============================================================================6 C; ^. K6 A ]$ u4 C0 z1 @
- */. ^; | m7 }1 P" \' V
- void TIM4_CH4_PWM_Init (u16 per,u16 psc)
/ R t0 [5 Q2 G4 F0 x - {
2 b# o h" p7 t - // 结构体定义
* J- M3 B9 q9 E/ \$ X# ]) V - TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
$ a* Y/ R8 n- x( [; X - TIM_OCInitTypeDef TIM_OCInitStructure;
1 g Z& }4 k$ W3 ` _( P* D5 a0 D - GPIO_InitTypeDef GPIO_InitStructure;$ i6 H, K0 C) O' @6 K: v
-
" w$ R# h; H- k7 H, _. V - // 开启时钟
; {6 Q/ S; a0 k! A" D& {7 N - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);" ?( f2 k4 I) T. b; U
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
5 q5 `2 X+ S8 C/ l! J% K - 7 u. v; X1 j) z; U1 o( b* A
- // 初始化GPIO
% ` D0 `5 c5 w6 M3 i - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;: v4 r4 e" h" m* F6 M/ L
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;3 q3 |7 g6 p: V- r0 a2 H
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出5 V* _$ }: D W6 j; ]5 z
- GPIO_Init(GPIOB,&GPIO_InitStructure);
6 L' N* R4 H" z7 g% G8 j - ' `1 {7 W/ l% G' a7 F- q+ S( y
- // 初始化定时器参数
) F+ j4 u# E: z# c q8 _ - TIM_TimeBaseInitStructure.TIM_Period = per; // 自动装载值
: W( w. U- l. f* r% p" g6 w - TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 分频系数! w9 x* G8 D' j
- TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;3 U* D) n% E1 _( E( {
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 设置向上计数模式
) v, _2 t2 h8 k; `1 v) k - TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure); 2 a1 M T* c8 z4 }% P
- 3 `3 {, s* [' S$ P) }" _
- // 初始化PWM参数
9 ]/ d6 X. {$ [6 \) B& N- w9 { - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 比较输出模式
8 P4 R. ^6 D: k3 F - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 输出极性
@0 H) e# i0 x; f& T$ j - TIM_OCInitStructure.TIM_Pulse = 500; // 脉冲宽度
8 q4 u W/ ~9 ]2 Z# O - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能) F" N5 M$ _7 {& W9 m
- TIM_OC4Init(TIM4,&TIM_OCInitStructure); // 输出比较通道2初始化
; q- v# n8 w( m0 @! z - W. p( [$ v6 F0 d8 p. I
- TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable); // 使能TIMx在 CCR2 上的预装载寄存器
! S. q, R& f- A8 D* U' A - TIM_ARRPreloadConfig(TIM4,ENABLE); // 使能预装载寄存器
1 ?2 ] q% E0 |& Z7 F* e( R -
- w3 ^9 [7 e' L" F6 |6 T - TIM_Cmd(TIM4,ENABLE); // 使能定时器8 ^4 q& X& c2 H; h. u, t
- }
复制代码
# M7 D6 d9 F0 a# X+ z. kADC配置程序如下,触发源选择TIM4的CH4,使能外部触发。
* L# q$ C3 X. v% @- /*; e" F- I' b" ?
- *==============================================================================
% K/ y6 p2 }+ I6 V- O" T - *函数名称:ADC1_Init
; Q- e- `# H7 ^* ?" b/ I - *函数功能:初始化ADCx0 M' ^5 s3 B7 a5 x
- *输入参数:无
{+ B6 F8 C L - *返回值:无3 ^+ H% c5 q# x" J8 Z3 c7 d
- *备 注:无
5 e- t6 n* \' ?, e# ]9 q - *==============================================================================% n* e$ U9 {7 R: c0 J3 g$ g
- */
) B, a1 K# P+ { a - void ADC1_Init(void) `' U3 A$ i W( k
- {
! L |1 n( c" v' L- j, S* N - // 结构体定义, W) x6 v! a. J+ o! S
- GPIO_InitTypeDef GPIO_InitStructure;
2 M: R9 q9 A5 j9 W( \; D - ADC_InitTypeDef ADC_InitStructure;
+ e0 p: a# u) ]9 ]! S - - R! `% e* X6 w3 q! Q2 t; w
- // 开启时钟
6 B4 d$ I. e0 {2 R# Y$ b0 b - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);4 C9 U/ [3 F' n1 @7 m* O& Z
- , Y: Y5 c n+ @. C1 t8 f# }: w0 V
- // 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M, c& H" M, e' u1 g* y
- RCC_ADCCLKConfig(RCC_PCLK2_Div6);+ N. `9 l2 W& l/ ^6 Q* s+ G, ^
- // 规则通道配置
, O/ |7 @3 J5 L: u% }4 p! y9 G2 z - ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);0 @- X5 T5 Q- D9 ~/ I: i
- 7 E4 x( X; z% c Z' [$ {
- // GPIO配置3 u5 p! g6 ]& S! k' m" l* q
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1/ @5 o6 Z4 I: ]* x0 H7 d
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模拟输入* F& n3 T0 |# q
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
# K' z3 ~( W; j. z/ X - GPIO_Init(GPIOA,&GPIO_InitStructure); z& m, ~. C5 @8 x1 J
- # t. u, }6 L9 ]) L% T
- // ADC参数配置( g6 y8 y ] v) i) t t
- ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式: I/ G+ B7 ]/ Q! O
- ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
( q1 P! c9 K, ~; r# L4 j - ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 关闭连续转换 F) H; u1 t% e5 B
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4; // TIM2通道2触发' `* z) H7 g3 T; T
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
9 F4 i* z/ ~, n9 M# o* C - ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个转换在规则序列中 也就是只转换规则序列1 $ Z; Z5 w; ]! @' K6 {2 @* I
- ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化+ j) e, j0 \: g4 X5 Z* F8 B A) ?
-
& g* R' R& }, p( G5 o) h - // 使能外部触发5 d* I0 o2 |3 K$ c1 T3 I
- ADC_ExternalTrigConvCmd(ADC1, ENABLE);
) i3 B: u( a4 z8 ~) V/ a& m - . L V7 B* G! Z1 _4 c- c
- ADC_Cmd(ADC1, ENABLE); // 开启AD转换器
) `! `' s) _) l* b5 S, [( o -
) g5 @2 V7 y8 N) O - // ADC校准
7 _, E# C3 \* z. Z - ADC_ResetCalibration(ADC1); // 重置指定的ADC的校准寄存器
; ^7 f0 w1 @! y - while(ADC_GetResetCalibrationStatus(ADC1)); // 获取ADC重置校准寄存器的状态 Z4 M4 r+ E( J R6 ?) B
-
0 @3 x; ^' Q- ]+ W& {4 K# U - ADC_StartCalibration(ADC1); // 开始指定ADC的校准状态8 c4 ?: `+ o1 F! p5 c( U
- while(ADC_GetCalibrationStatus(ADC1)); // 获取指定ADC的校准程序- Z3 M% s' |' f* S
- 8 G/ E Z5 S9 k, B/ z4 o0 C6 g
- ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的软件转换启动功能
/ \6 N5 u! Q I7 A6 J - }
复制代码
j7 H4 D9 n) y1 |0 {main函数如下9 s1 Z* v+ m; Y( @* N! r
- u16 gAdcAdValue = 0; // 存储AD值8 A( w a( E' b" E! J- U, L
- float gAdcVol = 0; // 实际电压值0 X+ m/ g- q3 X2 A
- }: `/ S4 N! B$ f' |- int main(void)
% n7 C. m! R* y$ L - {
# a3 y! ^% D5 d% n - Med_Mcu_Iint(); // 系统初始化
8 w2 P: b6 [" r8 R9 [1 R - 8 h; T+ c3 i6 O* Z' ^, [% _
- while(1)/ A* \, ~* ]2 g9 z
- {
7 v4 }# Y0 I2 U4 x( ]( B8 [ - gAdcAdValue = ADC_GetConversionValue (ADC1); // 获取转换结果2 N7 D! I! b* J h# R# T# |1 p
- gAdcVol = (gAdcAdValue / 0xFFF) * 3.3; // 计算实际电压
" {/ \' _+ U& d6 V) ^+ { - printf ("Vol=%.1f V\r\n",gAdcVol); // 串口打印结果: E" h" K' r1 p& M m
-
% t- P( M3 m2 t6 l" M2 E n& m E - delay_ms (500); // 防止打印过快5 o: Z# w# l) ~" O2 Y% p
- }
" y8 h+ O6 `3 q' v7 X" u& w - }
复制代码
4 g2 M! l. _7 s# y- [1 N: R% @6 L初始化定PWM时,程序为
; r% f( a6 h$ B! V- TIM4_CH4_PWM_Init(1000,71); // 初始化TIM4的通道4
复制代码
- P, v/ {+ u8 n& r# f# F分频系数为71 + 1,自动重装载值为1000,也就是1KHz的方波,也就是触发AD转换的频率为1KHz,与占空比无关。
) N# y |* f% x. `) h' N6 \8 V# E- L2 ?$ i1 ?! V
, I# v( P& l' T7.2 ADC采集交流信号0 E+ ]; V5 {$ {. g0 Q
ADC能够采集的电压范围是0~3.3V,也就是说负电压无法采集。比如,需要采集下图中的一个交流信号& T' U- h6 J3 H( o' ^
, f J& w. V- i" {/ N
0 { g4 z& i) b7 w0 R6 ^交流信号图 9 v2 U0 ^3 V- T) g0 Z
5 K' m! J' x9 n) O
( k- p( `. n6 m其位于0以下的部分是无法采集的。因此,在利用STM32采集交流信号时,在交流信号输入ADC引脚前,给交流信号增加一个直流偏置,将交流信号的最低点抬升到0以上,之后再输入ADC引脚。: h2 r4 i4 o9 n
i! B8 v+ e- y/ |3 W( v( K6 O. z0 q9 s1 n: v; B, K* ^
7.3 计算交流信号有效值$ D J+ r* V; Y, j& C* {
ADC可以用于电流监测,实时监测主线路中的电流。当然,硬件方面需要搭配电流互感线圈,通过采集互感线圈两端的电压,来监测主线路电流。由于一般都是交流信号,所以需要计算有效值。# I' v2 }! @$ y8 q4 P
6 F0 e3 a6 z, t# [0 C根据我们所学的知识,计算交流信号有效值常用两种方法。一种是峰峰值除以根号2,另一种是计算均方根得到有效值。通常我们采用计算均方根的方法来计算有效值。因为如果用峰峰值除以根号2去计算有效值,峰峰值很容易不准确。如果在某一个时刻,由于环境干扰或者硬件问题,导致突然出现了一个很大的值,会导致计算结果与实际偏差较大。关于为什么计算均方根可以得到交流信号的有效值,这里就不做介绍了,只给出部分程序设计。由于博主目前身边没有合适的设备验证,因此仅供参考。
* k7 A: S6 f' J. q b3 y1 _1 u
. e# u0 ]+ F) ]假设需要计算一个50Hz交流信号的的有效值,在其输入到ADC采集引脚之前,增加一个稳定的1.65V的偏置。ADC的采样频率为1KHz,也就是一个正弦波的周期可以采集20个点。假设采集到的AD值存储到一个数组中,计算有效值的程序设计如下1 p- L1 b8 G0 r+ K
- int gAdcAdValue[20]; // 存储采样结果AD值的数组
% k7 V' C# F& s' i( u - int gAdcValidValue = 0; // 有效值2 ~- k7 ]8 ~; S2 D) ?! {; E4 w8 O
- , d5 O4 R# j1 f; q* H/ a
- void Med_Adc_ValidValueCal (void)& J0 u8 r) T0 P( K) u
- {4 E* U- B, o4 p, {" S. q
- int tempVar = 0; // 循环变量( T+ V" x& _ C n N
- int squarSum = 0; // 平方和
3 s0 l, l4 k5 {- C0 K# H G# o$ k2 @ -
- y2 ?7 _; D; ] - // 求平方和
9 X% _5 _: k: m$ r - for (tempVar = 0;tempVar < 20;tempVar ++)
% ^! ~$ h/ k) J; ^* ~9 a E - {& ?* m3 \; e* N
- // 减去直流偏置4 ?8 o2 d; t/ q' ]3 S
- gAdcAdValue[tempVar] = gAdcAdValue[tempVar] - 2048;) k9 l1 o! S9 J1 ~# a
- // 计算平方和
1 g1 h% K. F3 x& T! n - squarSum = squarSum + gAdcAdValue[tempVar] * gAdcAdValue[tempVar];
- [4 v7 p5 V4 L: R$ d" C - }" v2 Y6 p" G( K, U0 Q
- 5 |+ u6 {# T/ W' y" r7 }0 Q& ~
- // 求平均* s% \6 E6 n2 y
- squarSum = squarSum / 20;" e7 \% G/ ?. l! s2 ^1 Z* r
-
) l4 y! N; X ?6 j* T - // 开根号得到均方根(有效值)0 }# x) g4 o( t
- gAdcValidValue = sqrt (squarSum);
( `+ ^7 b" l c8 c# a0 |3 Y3 C, D - }
复制代码
, G2 i: X; x) X. w2 z在进行程序设计时需要注意不要超出数据类型范围。在实际应用时肯定会存在误差,这里也简单介绍一下误差消除方法。目前用到的有两种方法,第一种是分段矫正,在不同的区间内,误差满足线性关系时可以使用。另一种是按比例矫正,这种方法常用于误差随着测量值的增大而增大的情况。在计算出有效值后,减去或者加上一定比例的计算值来做矫正。
6 w, x1 k! [* O9 r1 z% ?. }
" y' g% w% r: y; g, a4 f' p+ x
/ b6 L$ U2 S+ Y8 G) I7 C0 E; l
7 Y" R3 g5 c6 h5 O转载自: 二土电子
# p+ _2 o( i* \- P" r" f如有侵权请联系删除0 U3 d4 g0 I$ m; `: L8 i' G) i
5 G. z+ y3 ]0 ~, ~$ |# ^" G6 D |