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