一.ADC简介
0 t, r v* k) P3 U3 ~" `1 _! |STM32f103系列有3个ADC,精度为12位,每个ADC最多有16个外部通道。其中ADC1和ADC2都有16个外部通道,ADC3般有8个外部通道,各通道的A/D转换可以单次、连续、扫描或间断执行,ADC转换的结果可以左对齐或右对齐储存在16位数据寄存器中。ADC的输入时钟不得超过14MHz,其时钟频率由PCLK2分频产生。9 L* W/ @* D: O7 Z- A* w, [* u
二.ADC功能框图讲解
; L5 s% c% _( D! o学习STM32开发板上的外设时首先要了解其外设的功能框图,如下:
6 B8 M/ r: d* j" V
* Y# a6 I8 `) y7 S7 G3 s
" H1 i3 M/ S, C4 C- p, l) e, ~
6 o3 s& x& }6 T3 V. b, T: M
功能框图可以大体分为7部分,下面一一讲解:
$ L; Q# t7 P0 M2 @5 n
+ }, H$ M4 h& O4 z, f1.电压输入范围
/ n7 {* x6 I2 B6 H/ RADC所能测量的电压范围就是VREF- ≤ VIN ≤ VREF+,把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0~3.3V。
+ X2 F8 b$ f2 l4 p( y8 p5 z* Q
% x( D& U) B$ U* v) N2.输入通道
& V0 N' C/ X' ]% y6 UADC的信号输入就是通过通道来实现的,信号通过通道输入到单片机中,单片机经过转换后,将模拟信号输出为数字信号。STM32中的ADC有着18个通道,其中外部的16个通道已经在框图中标出,如下:& o/ Z- z3 q) M- j. S# C% A, p! M
, `$ a1 u% a# A' u/ O$ l
. O# M: W* O5 X' o. W+ Y/ v
7 v; M# ?4 D3 T1 g
这16个通道对应着不同的IO口,此外ADC1/2/3 还有内部通道: ADC1 的通道 16 连接到了芯片内部的温度传感器, Vrefint 连接到了通道 17。 ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。2 ?! E' Y# E6 G2 j
ADC的全部通道如示:
% h2 q3 X4 C* R; p1 K- j
1 |* q! K" K) d2 h' G
7 D1 ]( M6 c3 e6 v: k
# g' z! P) @, O$ J; A外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路(注入通道貌似使用不多),下面简单介绍一下俩种通道:
& F4 B5 l* v a% P. |7 J( {& ]4 o. K' y+ p1 I
规则通道
& j+ [6 L" \) r) D% M规则通道顾名思义就是,最平常的通道、也是最常用的通道,平时的ADC转换都是用规则通道实现的。0 O$ E' |3 |) X1 Y; _) Q8 T
. M& I! L/ c- P/ [
注入通道
7 c6 O6 \' ], ^; y注入通道是相对于规则通道的,注入通道可以在规则通道转换时,强行插入转换,相当于一个“中断通道”吧。当有注入通道需要转换时,规则通道的转换会停止,优先执行注入通道的转换,当注入通道的转换执行完毕后,再回到之前规则通道进行转换。6 R. q) o* z0 h9 b' h
4 _$ L- [3 E# j, [2 C" v+ ^
3.转换顺序$ L5 g# q- w0 Z! @4 {
知道了ADC的转换通道后,如果ADC只使用一个通道来转换,那就很简单,但如果是使用多个通道进行转换就涉及到一个先后顺序了,毕竟规则转换通道只有一个数据寄存器。多个通道的使用顺序分为俩种情况:规则通道的转换顺序和注入通道的转换顺序。
: e2 n1 H; g& n$ \9 A: T$ Z/ h7 C( r
规则通道转换顺序
( w9 @3 v: g& e7 G- i规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。具体的对应关系如下:
0 o! h% r. i3 o9 `
d8 ~5 N6 ~$ n. U$ [0 S( i7 g) `( k
& N( Q7 H2 D5 e0 n$ Q
- Y9 c2 M3 Y0 b; N通过SQR1寄存器就能了解其转换顺序在寄存器上的实现了:
, s- l) f. L( s5 Z4 _
# Y6 i6 J9 [7 f* B8 L* |' c
" @) z/ i+ \# r# ~: A- G4 o
. S4 K3 D* e/ u+ c' C/ F( K# j) p注入通道转换顺序9 u! v% \! @: f5 Y6 H* ^
和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下:
+ M5 s( X" g& b t7 W* p; ]+ p) m2 O- ~4 B/ H+ h
8 G) f) N% Z; E5 v2 @/ o, M; @- Q% c3 B, o; ?" |, D3 x
需要注意的是,只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。当JL<4时,注入通道的转换顺序恰恰相反,也就是执行顺序为:JSQ4、JSQ3、JSQ2、JSQ1。7 Z% Q- O; c9 v3 W+ ?
配置转换顺序的函数如示:
4 ^" W/ f+ t/ h* j& ?8 c- /**/ y6 T+ {1 X% K. Q
- * @brief Configures for the selected ADC regular channel its corresponding
- }1 l1 Q( J( S) x - * rank in the sequencer and its sample time.
1 r1 ]0 o7 }+ ~$ E: s6 U3 k( \" o - * @param ADCx: where x can be 1, 2 or 3 to select the ADC peripheral.
" C" n) P7 k( i - * @param ADC_Channel: the ADC channel to configure.
# P) E% M8 ]+ L5 N% \* \4 L/ { - * This parameter can be one of the following values:
2 W* a) C. N8 c2 h, x - * @arg ADC_Channel_0: ADC Channel0 selected
$ p0 b1 F7 H; m) @+ j+ @/ W - * @arg ADC_Channel_1: ADC Channel1 selected
* L8 q3 A7 ` g( k - * @arg ADC_Channel_2: ADC Channel2 selected
; p& C7 R3 \) a - * @arg ADC_Channel_3: ADC Channel3 selected& r& V/ D9 D2 [. A
- * @arg ADC_Channel_4: ADC Channel4 selected5 W- V5 m' N; E a+ y. A% v2 D4 J
- * @arg ADC_Channel_5: ADC Channel5 selected8 D" B; r u* Q& S+ A9 _. P v5 H
- * @arg ADC_Channel_6: ADC Channel6 selected" @2 I. l0 G$ q* z; Y4 t
- * @arg ADC_Channel_7: ADC Channel7 selected
: X, C4 q. D$ ^( \) O8 y& E- n - * @arg ADC_Channel_8: ADC Channel8 selected6 E. v" s1 x# }3 n- R% K; w
- * @arg ADC_Channel_9: ADC Channel9 selected" {( G6 u* ^& ^+ p
- * @arg ADC_Channel_10: ADC Channel10 selected
& R- v B B4 r9 f4 ^4 z8 ^( l - * @arg ADC_Channel_11: ADC Channel11 selected
' [/ E) f: o6 q - * @arg ADC_Channel_12: ADC Channel12 selected
0 }0 M( M1 z4 r' |7 |: S' g - * @arg ADC_Channel_13: ADC Channel13 selected
* F. E, U4 u" k$ y. I M0 w2 G( i - * @arg ADC_Channel_14: ADC Channel14 selected
% w8 A/ k9 `0 P3 R2 A - * @arg ADC_Channel_15: ADC Channel15 selected
% j4 W; b% X0 {+ I$ }# ~ - * @arg ADC_Channel_16: ADC Channel16 selected2 f# }3 d- m$ x, S
- * @arg ADC_Channel_17: ADC Channel17 selected' ~& q1 E! i8 N4 _4 p+ \* V
- * @param Rank: The rank in the regular group sequencer. This parameter must be between 1 to 16.
: _, y {" U' P; o - * @param ADC_SampleTime: The sample time value to be set for the selected channel. 7 V! J& E0 A* G9 m
- * This parameter can be one of the following values:
8 o$ O) p. z5 J, {& o - * @arg ADC_SampleTime_1Cycles5: Sample time equal to 1.5 cycles: a7 ^/ m$ ]% D C
- * @arg ADC_SampleTime_7Cycles5: Sample time equal to 7.5 cycles6 t3 ]- v( i5 W
- * @arg ADC_SampleTime_13Cycles5: Sample time equal to 13.5 cycles
9 x, m, D7 H0 h8 D3 T" a5 A - * @arg ADC_SampleTime_28Cycles5: Sample time equal to 28.5 cycles
9 b+ C* b0 u2 d1 v$ c - * @arg ADC_SampleTime_41Cycles5: Sample time equal to 41.5 cycles
$ m, v- W) E$ K - * @arg ADC_SampleTime_55Cycles5: Sample time equal to 55.5 cycles % d( R. A, |3 _" N
- * @arg ADC_SampleTime_71Cycles5: Sample time equal to 71.5 cycles 4 S1 _ p6 b. k- t& ]
- * @arg ADC_SampleTime_239Cycles5: Sample time equal to 239.5 cycles 7 J) f* I/ ]5 I: Q W! c
- * @retval None" {& N/ W% P# p7 X
- */, ~" X7 }* A3 Y' G& D8 ]3 M
- void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)
. s0 F. r- N9 p4 \ - {' p' _& k+ [3 n3 W3 d* U
- 函数内容略;* m7 v% ~# ]4 R# g1 k/ |) F4 a
- }: j# z2 c6 M( v0 z& Y* f7 ], \
- 6 O; d! l( j# c3 Y6 [- @0 {* c2 w
复制代码
# K% v9 |, ^0 W b8 K% O( _. c2 t4.触发源
# y3 G2 M2 G2 d; p" c" r; h; rADC转换的输入、通道、转换顺序都已经说明了,但ADC转换是怎么触发的呢?就像通信协议一样,都要规定一个起始信号才能传输信息,ADC也需要一个触发信号来实行模/数转换。
3 p! N( A( l, ?( b其一就是通过直接配置寄存器触发,通过配置控制寄存器CR2的ADON位,写1时开始转换,写0时停止转换。在程序运行过程中只要调用库函数,将CR2寄存器的ADON位置1就可以进行转换,比较好理解。
- U# |) x5 J" b& \% c0 j; s另外,还可以通过内部定时器或者外部IO触发转换,也就是说可以利用内部时钟让ADC进行周期性的转换,也可以利用外部IO使ADC在需要时转换,具体的触发由控制寄存器CR2决定。8 X4 w L$ p: Z5 X/ I Y
ADC_CR2寄存器的详情如下:5 A4 ~4 Z! ]3 b& t- M
- e) F3 r; H- w
' L" H6 ]* j5 e7 ]) x% ?3 i
5 A1 c% J8 b) i- q- Y) l8 m- I, v' G
2 n2 k2 F0 U8 O9 n
U- h" c# m5 M+ ~$ C2 M
5.转换时间
+ N9 s& z3 Z _8 X还有一点,就是转换时间的问题,ADC的每一次信号转换都要时间,这个时间就是转换时间,转换时间由输入时钟和采样周期来决定。
/ }7 b9 y$ q% ~! X8 W* c1 U9 y/ ?* O+ ~* k& ?: I4 _
输入时钟
2 c3 u! C/ b" h2 [/ Y0 O由于ADC在STM32中是挂载在APB2总线上的,所以ADC得时钟是由PCLK2(72MHz)经过分频得到的,分频因子由 RCC 时钟配置寄存器RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,一般配置分频因子为8,即8分频得到ADC的输入时钟频率为9MHz。0 u9 I3 _8 P" [6 {6 X) K* M2 l: f7 u
' D% k6 t. W6 ?% u/ u1 ]8 f采样周期
0 K: _+ v- Y# L) G采样周期是确立在输入时钟上的,配置采样周期可以确定使用多少个ADC时钟周期来对电压进行采样,采样的周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以配置不同的采样周期,但最小的采样周期是1.5个周期,也就是说如果想最快时间采样就设置采样周期为1.5.
( i/ Y2 x1 I6 ~( Z: k/ K4 q P% R5 r: i' D5 R
转换时间
" a/ ~! f3 O4 Z* p! t+ K4 q转换时间=采样时间+12.5个周期
; [8 y: q: ]+ X5 ]6 b- n! k2 Q5 ]$ M( i12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。
. P. q8 Y6 l8 A8 d; V4 a; J% d* }
( \( u j1 |8 r7 `/ f! ^6.数据寄存器; x. q/ C" d& p1 a5 [ y0 t
转换完成后的数据就存放在数据寄存器中,但数据的存放也分为规则通道转换数据和注入通道转换数据的。
7 n) A# b1 O5 [. @& ~% e! @规则数据寄存器3 B& V' ^& f- v
规则数据寄存器负责存放规则通道转换的数据,通过32位寄存器ADC_DR来存放。; Z, m. k; f$ ?4 ?( _" x
1 G* |! G+ U) F) ?- m! Y" }
% ~% Q; j* k8 ?1 a$ I8 t7 c2 Z( x
) D5 ^& V2 ^7 U+ z当使用ADC独立模式(也就是只使用一个ADC,可以使用多个通道)时,数据存放在低16位中,当使用ADC多模式时高16位存放ADC2的数据。需要注意的是ADC转换的精度是12位,而寄存器中有16个位来存放数据,所以要规定数据存放是左对齐还是右对齐。& K1 j: l6 o, s9 b' L- v5 b
8 r( @) x ~" m q3 j0 {
当使用多个通道转换数据时,会产生多个转换数据,然鹅数据寄存器只有一个,多个数据存放在一个寄存器中会覆盖数据导致ADC转换错误,所以我们经常在一个通道转换完成之后就立刻将数据取出来,方便下一个数据存放。一般开启DMA模式将转换的数据,传输在一个数组中,程序对数组读操作就可以得到转换的结果。
/ ?- ~' N- }* W9 D; e+ S( FDMA的使用之前在一篇博客中介绍过:DMA介绍- p5 n. D& [: ~) H6 ~
4 {8 K! f5 n# V9 C" m, y注入数据寄存器& r1 k( N2 K1 U+ K# {: Y {5 G5 J
注入通道转换的数据寄存器有4个,由于注入通道最多有4个,所以注入通道转换的数据都有固定的存放位置,不会跟规则寄存器那样产生数据覆盖的问题。 ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。( {! e1 o* D. j0 _4 i
3 r4 e* t1 Y# J* H5 v
& k7 _8 i. c" g8 ?
3 b9 k$ Z- |) I
X9 ^5 V+ n5 k e7.中断, Q* P' ~. B5 x7 a- ?' B4 a7 `& n8 o
3 B6 \2 w& b7 e( v; H5 y% K- \! O
7 X' c; U! A+ Y" ^2 e% X: G0 v
8 G; N" U4 D2 E: x$ i6 w. Q% D V从框图中可以知道数据转换完成之后可以产生中断,有三种情况:/ S- k# \& q/ P( s( ?! h: j) F
规则通道转换完成中断5 m$ P* P* n$ y4 f
规则通道数据转换完成之后,可以产生一个中断,可以在中断函数中读取规则数据寄存器的值。这也是单通道时读取数据的一种方法。
, `7 s; Z% I' v/ g9 L# Z. W6 T注入通道转换完成中断
! a+ r( y2 C, K+ ~, x注入通道数据转换完成之后,可以产生一个中断,并且也可以在中断中读取注入数据寄存器的值,达到读取数据的作用。
5 i b' v, p) C: _' A$ }0 ^, e模拟看门狗事件
; r% L8 Y |) q$ v7 g4 D当输入的模拟量(电压)不再阈值范围内就会产生看门狗事件,就是用来监视输入的模拟量是否正常。3 ~9 E+ ?/ L/ e K# ^) B
" |- O8 [. C( B% T
以上中断的配置都由ADC_SR寄存器决定:
/ `# A. m2 o; G! q# q! A% P
% p3 q, D0 J- l
, ?+ }7 ^ @8 |5 C/ Y( A
$ m- S8 N# z: x+ v, A; @ q3 Z当然,在转换完成之后也可以产生DMA请求,从而将转换好的数据从数据寄存器中读取到内存中。 {3 j9 c' L/ v
- W2 D& z0 a1 U& \+ `+ k8.电压转换0 y3 E' O) Y" Q! l5 c+ F* s
要知道,转换后的数据是一个12位的二进制数,我们需要把这个二进制数代表的模拟量(电压)用数字表示出来。比如测量的电压范围是0~3.3V,转换后的二进制数是x,因为12位ADC在转换时将电压的范围大小(也就是3.3)分为4096(2^12)份,所以转换后的二进制数x代表的真实电压的计算方法就是:
- O3 l$ R) v5 \3 K- ~y=3.3* x / 4096: O8 z: }6 `4 c2 ^4 P* d* b
, a% @) y' c' w三.初始化结构体
6 C) O6 B7 K& |( I( [每个外设的核心就是其对应的初始化结构体了,ADC的初始化结构体如下:
( _! K- G9 M/ {7 L8 J* D+ U4 ]- typedef struct4 x; T. d% J s0 M' g6 k j
- {+ T2 S5 h( _3 a" U W B, S
- uint32_t ADC_Mode; // ADC 工作模式选择
+ `( `$ _0 P6 l/ n. K- X - FunctionalState ADC_ScanConvMode; // ADC 扫描(多通道)或者单次(单通道)模式选择 ; i$ L/ z+ A. c8 l; F: E$ Z% N
- FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择
0 O5 p& L4 Y8 w - uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择
. E3 Z7 l6 e$ E7 r0 c4 \4 E7 Y) m- f - uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式
# M. _- H( `% j# p7 W; Q" S - uint8_t ADC_NbrOfChannel; // ADC 采集通道数
7 {( Z" l) R' r: X- a/ S) F - } ADC_InitTypeDef;( \9 n4 U2 ]( E9 k
复制代码
' C. x/ B* T+ t/ ~通过配置初始化结构体来设置ADC的相关信息。
5 @6 E4 E( i8 F
" K( [+ x0 \! w四.单通道电压采集% Z5 E( S x5 g* L
用这个程序来简单熟练一下ADC的单通道电压采集吧,程序使用了ADC1的通道11,对应的IO口是PC^1,因为博主的开发板上PC ^1引脚没有任何复用,使用中断,在中断中读取转换的电压。
v% t# ]1 H- W. c" Q x/ G* u8 q8 Z5 a$ J R8 Z a0 ~6 v
1.头文件5 L7 |5 \% ~( P+ V. N, ^1 J6 E
为了提高文件的可移植性,头文件中定义了一些与ADC和中断相关的量,在移植程序的时候只需要修改头文件中的定义即可。
: h1 c' W1 a, x$ S* }" I# C% a- #ifndef __ADC_H. b/ P6 n3 D4 k/ g0 r# y5 w
- #define __ADC_H0 p( u0 q0 p- C) \
- / X4 \/ w4 H, J! m
- #include "stm32f10x.h"+ ^: ~( ]1 D+ ~ X9 }
- , L# U+ F# s- J" t+ A+ Y
- /* 采用ADC1的通道11 引脚为PC^1 模式必须是模拟输入*/
+ T p s% n6 ~2 Y - #define ADC_GPIO_RCC RCC_APB2Periph_GPIOC+ w! K3 T9 O0 x- M
- #define ADC_GPIO_PORT GPIOC
$ _0 Y0 O7 j0 W" Q- ]! } - #define ADC_GPIO_PIN GPIO_Pin_1- m# i; u! k h; B W- q- W6 I
- #define ADC_GPIO_MODE GPIO_Mode_AIN
5 _% [0 U( `( l! K+ Y - * T& x' Q, |1 a, c! N
- /* 配置与中断有关的信息 */
5 W( v( I7 z, C! ? - #define ADC_IRQn ADC1_2_IRQn3 i& j3 O4 Q+ d9 L1 D9 _ e8 w- Z
- #define ADC_RCC RCC_APB2Periph_ADC1" ]$ E. B. C. K, C- g8 Q2 T- w
5 N* L9 V1 ]1 s& _" I/ D9 q
$ U6 Y7 ^% V; A- /* 配置ADC初始化结构体的宏定义 */* Z2 O; @9 ]3 M% j5 p4 X, F3 _- j) t; ?
- #define ADCx ADC1
, `- k& h4 j) `3 f - #define ADCx_ContinuousConvMode ENABLE //连续转换模式 T, x. g- Q+ b& q
- #define ADCx_DataAlign ADC_DataAlign_Right //转换结果右对齐! ^; c$ b# r4 [2 p4 s' ^- B" H1 y8 k
- #define ADCx_ExternalTrigConv ADC_ExternalTrigConv_None //不使用外部触发转换,采用软件触发- z4 X; \3 j( i% t/ f, f8 N6 d
- #define ADCx_Mode ADC_Mode_Independent //只使用一个ADC,独立模式1 ]9 [2 G+ s/ S# u- u S* ~
- #define ADCx_NbrOfChannel 1 //一个转换通道, ^, R7 L$ t# a( e. f
- #define ADCx_ScanConvMode DISABLE //禁止扫描模式,多通道时使用( e) ]( ?% H$ T. [, T, e d' A; r
- 6 m3 k* w' r w7 `, z. M2 N
- /* 通道信息和采样周期 */9 u+ w/ a. g; U' Y. m
- #define ADC_Channel ADC_Channel_11) }4 K( [. }# ^7 v, K
- #define ADC_SampleTime ADC_SampleTime_55Cycles5
% P% K, n ?% t9 F# G6 z
* ^9 @0 ?- D$ U7 L, z
; K/ f$ W; b; w4 D- /* 函数声明 */) T7 @* Y! D$ y# d
- void ADC_COnfig(void);
( Y+ z7 @: v& G9 K0 z' m# W3 p& p. ] - void ADC_NVIC_Config(void);
# T% e% u" I, d- J" w. ~ - void ADC_GPIO_Config(void);& w: @( q% T. ]3 |5 x6 X" A* X
- void ADCx_Init(void);" b. V7 K3 m7 ~ {2 Y9 I
% A% M& l8 [6 U. Y) y- ?+ X- #endif /* __ADC_H */
. {1 c1 [, [* u; @ - - Y6 ]' _6 K( X' S2 l
+ Y5 o" h& U0 j
复制代码 ! X+ [6 D# o/ d Q H
2.引脚配置函数) c; Y S; n0 G) v/ X
首先配置相应的GPIO引脚,毕竟模拟信号是通过GPIO引脚传输到开发板的,注意的是,引脚的模式一定要是模拟输入!
( @2 c: o) b/ X9 w, F7 C4 w- void ADC_GPIO_Config(void)
) M' K2 y2 q+ s* _ - {
/ x- t c- X5 v5 i, x* @+ ? - GPIO_InitTypeDef GPIO_InitStruct;+ f6 z. B) ?, s) m; s! p9 N6 h+ Q3 {
- RCC_APB2PeriphClockCmd(ADC_GPIO_RCC, ENABLE);
: z0 u% M" C j -
5 `. |( g0 v( k - GPIO_InitStruct.GPIO_Pin = ADC_GPIO_PIN ;) V" I; I; S% S5 |
- GPIO_InitStruct.GPIO_Mode = ADC_GPIO_MODE ;, f7 x" u* ]+ P6 B& ]( z" H* |! j/ G
-
2 U7 ~: n4 d6 S- J8 h, Z3 J! X9 h - GPIO_Init(ADC_GPIO_PORT , &GPIO_InitStruct); C8 {& A* ~) q" G& M. G
- }
) S, f S; q6 E) Q+ M/ y8 W, ` - # K' K& q9 \5 Y- x# O% {$ d9 T3 b
复制代码
9 z m! [6 ^7 n& c G配置引脚就是老套路:声明结构体变量、开启时钟、写入结构体、初始化GPIO
! z+ n- g: N1 H* _
( E1 X7 L8 L# z# {7 ^# E% ~3.NVIC配置函数. L, y& F: [2 V( r
因为我们是在转换完成后利用中断,在中断函数中读取数据,所以要首先配置中断函数的优先级,因为程序中只有这一个中断,所以优先级的配置就比较随意。
7 N4 |# Y( K8 j- g, I- void ADC_NVIC_Config(void)
5 P- O/ _8 ~. i - {3 q. b' U. E) W5 L: H
- 5 Q2 q5 \* i# c* ?
- NVIC_InitTypeDef NVIC_InitStruct ;9 A! @& y% I4 j) I
- ; S, p' b, v; s3 |( U& w) a# a& Y) z+ E
- /* 配置中断优先级分组(设置抢占优先级和子优先级的分配),在函数在misc.c */, b& ^' \, ]# s5 z; T: g) V4 p
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1) ;1 R6 _1 \2 ~! x% F% Z
-
4 }" L7 w, B. }" I& A: C# W" M# e5 K - /* 配置初始化结构体 在misc.h中 */
( O- ]8 [, }$ R( {7 l- f - /* 配置中断源 在stm32f10x.h中 */
: n$ E+ K/ U6 v3 y2 I1 N# O2 G( o - NVIC_InitStruct.NVIC_IRQChannel = ADC_IRQn ;3 n8 m2 M& ^& _1 _4 m) x5 w( a
- /* 配置抢占优先级 */% }8 `+ _& f' y$ J1 n! ~
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1 ;0 W- @! @/ i! }7 p: ~9 p; O- @
- /* 配置子优先级 */
. K% h- v2 k" O+ t) Y - NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1 ;
4 a! R6 }8 G" ~ y - /* 使能中断通道 */, I; P0 X0 g; [+ Z; ^. X2 x9 C" d
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE ;7 u& w' L3 h$ j( k, d9 f. V3 E
- /* 调用初始化函数 */. Q. I* U9 U( G
- NVIC_Init(&NVIC_InitStruct) ;% v9 h5 R0 W+ O3 _2 [
- }1 Z5 }" t F$ |; U+ j2 q
1 q O4 V* e4 r& N. R7 _# C
复制代码
5 v3 Z4 [+ Z5 G8 j C4.ADC配置函数7 G: p+ {8 }2 s+ E* x
ADC的配置函数是ADC的精髓,在这个函数中包含的内容有:ADC的初始化结构体配置、配置了时钟分频、配置了通道转换顺序、打开转换中断、进行校准、软件触发ADC采集等。
; e8 I- R( g7 \" d$ D, |& O" c函数中都有详细的注释:
& Y, k$ a4 r& L" |* [- void ADC_COnfig(void)6 z$ |" A! x" h8 R% o
- {; @$ ~2 F" N5 Z) t5 E+ ]+ {
- ADC_InitTypeDef ADC_InitStruct;
4 y1 R( ~& L% s8 ? - RCC_APB2PeriphClockCmd(ADC_RCC, ENABLE);
/ J7 I! s# p# k. ~% g0 a -
% L. I4 ^0 x4 G4 { - /* 配置初始化结构体,详情见头文件 */3 W0 n" Y7 r( s0 f6 X% O
- ADC_InitStruct.ADC_ContinuousConvMode = ADCx_ContinuousConvMode ;
% O, B9 G, v2 d9 P0 V8 U - ADC_InitStruct.ADC_DataAlign = ADCx_DataAlign ;
+ v% y6 G3 \+ f; L' m - ADC_InitStruct.ADC_ExternalTrigConv = ADCx_ExternalTrigConv ;
0 B: Z8 p% C4 u; n7 w( } ` - ADC_InitStruct.ADC_Mode = ADCx_Mode ;
. A: j% {2 D/ b* p8 z A% l - ADC_InitStruct.ADC_NbrOfChannel = ADCx_NbrOfChannel ;
) }0 s. V( r6 x6 x8 Q - ADC_InitStruct.ADC_ScanConvMode = ADCx_ScanConvMode ;1 L; G3 I3 H5 }6 K+ g" a
- 9 E2 N3 G, g# d' T6 S) c
- ADC_Init(ADCx, &ADC_InitStruct);! X+ r3 \$ C& u8 z+ W! @4 M% n
-
3 w9 c% u7 t1 L/ q {6 p2 h - /* 配置ADC时钟为8分频,即9M */
0 G2 i! R2 \8 l1 q# s' { - RCC_ADCCLKConfig(RCC_PCLK2_Div8);
" P7 ] n+ V2 D4 J% \ - /* 配置ADC通道转换顺序和时间 */
! u7 ?& q; P$ r - ADC_RegularChannelConfig(ADCx, ADC_Channel, 1, ADC_SampleTime );
' Q7 r, s$ k3 P - /* 配置为转换结束后产生中断 在中断中读取信息 */7 Y" v# x1 P6 s( D8 F
- ADC_ITConfig(ADCx, ADC_IT_EOC,ENABLE);# Z* c, j2 w0 V* e1 q
- /* 开启ADC,进行转换 */8 g3 w2 n4 ^/ V0 d5 {/ x
- ADC_Cmd(ADCx, ENABLE );
$ [3 i, H$ J+ G. N; [8 S) I8 R! ^ - /* 重置ADC校准 */2 R6 c5 ?- X. J- C
- ADC_ResetCalibration(ADCx);
6 D& l' T4 p+ B3 { - /* 等待初始化完成 */
4 k% X p7 u# j5 w; F$ O/ N - while(ADC_GetResetCalibrationStatus( ADCx))
' S& E* R4 Z& J$ p+ [ - /* 开始校准 */ k; ]: T% O. ~( B
- ADC_StartCalibration(ADCx);6 e8 r% J g# D0 u4 L4 `
- /* 等待校准完成 */% t% S' l, u5 Z7 N5 O. [3 t u
- while (ADC_GetCalibrationStatus(ADCx));
5 ?# R! t4 K4 _) F; k& e% `) z - /* 软件触发ADC转换 */
! E$ }& r! Y$ ?/ |& o4 b3 R - ADC_SoftwareStartConvCmd(ADCx, ENABLE);
0 ^, V' z& M& A6 R: G8 D; K$ g -
8 y* _) R3 O/ |5 S+ e - }. Q; K7 v% I( P' Y
7 S$ z; h: Q9 a- M1 K
复制代码
! [' ?# v% T( f& q! A u5.中断函数
7 X S9 R& b" h+ z* f" Q在中断函数中进行读取数据,将数据存放在变量result中,此处使用关键字extern声明,代表变量result已经在其他文件中定义
- \7 ~: p9 a5 ?- B7 L- extern uint16_t resurt;5 B; b. E& n; L: t
- ! f9 J) S$ W g0 S
- void ADC1_2_IRQHandler(void), f! {' b& ~6 @, g
- {5 h( E$ r9 Z2 ^6 u1 l
- /* 判断产生中断请求 */# B6 F& g$ G' @9 X
- while(ADC_GetITStatus(ADCx, ADC_IT_EOC) == SET)& T3 V8 x* J4 J- u& l/ k5 Y
- resurt=ADC_GetConversionValue(ADCx);
6 i2 ~/ p0 {& b - /* 清除中断标志 */
2 x* `- R4 Q4 M5 R7 z I - ADC_ClearITPendingBit(ADCx, ADC_IT_EOC);
& `3 o9 {3 S& @$ ~0 u3 H - }$ q# r7 ?/ m1 k9 V, N0 D
- & e. L7 H3 L+ F, _+ Q$ T0 p
复制代码 D @; D4 m) H4 B. a3 z
6.主函数 n) w1 l+ A1 n/ X; Z) w- q
主函数负责接收转换的值,并将其转换为电压值,然后通过串口打印在计算机上,便于调试。
) x$ A: ~: s' C# R& G" ^- j变量result是主函数中的全局变量,注意最后的结果应该转换为浮点型。, |) m: e2 o, K/ v# W1 Q
- #include "stm32f10x.h"
+ P" _7 i, f5 ?% Z* H* ]% B - #include "usart.h") ~/ u7 |) T& g( ]% h# K8 r
- #include "adc.h"# n0 W, P* ?4 R: s7 f9 I, L
8 p/ G3 Q) G, O! ]9 R- uint16_t result;
" L7 H8 L: u, k/ b - 6 U. o* c: x) Z9 [+ o
- void delay(void)
$ I3 S8 U" u$ O1 ?( q - {8 @ M* u" v$ X9 h9 k+ `
- uint16_t k=0xffff;
/ k6 [4 M9 D, h" u! ]$ K7 S - while(k--);
/ l9 Z7 \+ q0 y5 F$ t. G - }0 s' L# w5 L, S3 r# Q. y! ~
/ i, q# ~2 e" K5 Z5 j. O) {- z* B0 x; E5 H8 p3 [5 P
- int main(void)
8 P1 [5 z! ]! R3 G" \# U - {, J% X, g5 G; {% Y' g, ~
- float voltage;
% @, |3 C! S1 ?3 D - /* 串口调试函数 */
" p- i% C7 f! X) X/ _ - DEBUG_USART_Config();
! g/ e; ^& i4 {* z - 8 i, n7 N6 b+ i
- /* 与ADC相关的函数打包在此函数中 */5 {4 x7 @4 w! A6 Y
- ADCx_Init();( _- x- v! e3 E
- while(1): w! b" d2 c, t* E: W& j, z
- {
3 N$ _3 O( N9 A; R1 v - /* 强制转换为浮点型 */
% q; e1 ^/ \8 X7 j) L - voltage = (float) result/4096*3.3;& d& B ]) s) X0 z" z8 c
- printf("\n电压值为:%f\n",voltage);/ E. R, l& ~: T0 {4 @+ ~- d
- delay();7 m0 E* N3 G, M8 t* m: v
- }) W: j/ i4 J5 C; I
- $ u7 c9 o$ _ L7 w, J% {
- }+ V& d1 r& u3 \0 o& N5 ^+ k& h
) ^! I5 e5 }1 m0 k) v" \! ~6 S* W; A" F
1 c" P; M' r |' F
复制代码 % a, \% y% o; x% W
2 e" [( C! j% `————————————————
& ?( Y# e/ w0 R+ X7 h1 G/ r版权声明:Aspirant-GQ
- u1 J; ?- Q0 ` l4 q% a; Y如有侵权请联系删除+ K& ^5 t& K6 X; b0 ]9 i6 D; r
2 l% x0 j, L1 l8 N7 j5 c
: B- P8 k+ U8 D% y; K
0 T7 C# L$ u6 y8 @( h$ [ |