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