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