1. ADC简介 stm32f103最少有2个AD模数转换器,每个ADC都有18个通道,可以测量16个外部和2个内部模拟量。最大转换频率为1Mhz,也就是转换时间为1us(在 ADCCLK = 14Mhz,采样周期为1.5个时钟周期时)。最大时钟超过14Mhz,将导致ADC转换准确度降低。stm32的ADC是12位精度的。 stm32的ADC转换有两种通道,规则通道和注入通道,注入通道可以抢占式地打断规则通道的采样,执行注入通道采样后,再执行之前的规则通道采样,和中断类似。本例只使用规则通道实现独立模式的中断采样,这里不再赘述两种通道区别。 stm32的ADC可以由外部事件触发(例如定时器捕获,EXTI线)和软件触发(即在配置相关寄存器时,直接开启采样)。 STM32的ADC在单次转换模式下,只执行一次转换,该模式可以通过ADC_CR2 寄存器的ADON 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这是CONT 位为0 。 以规则通道为例,一旦所选择的通道转换完成,转换结果将被存在ADC_DR 寄存器,EOC (转换结束)标志将被置位,如果设置了EOCIE ,则会产生中断。然后ADC将停止,直到下次启动。
! w, L/ D( R& `3 a3 R) q 2. 寄存器简介2.1. ADC控制寄存器(ADC_CR1和ADC_CR2)
ADC_CR1的SCAN 位,该位用于设置扫描模式,由软件设置和清除,如果设置为1 ,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx或ADC_JSQRx寄存器选中的通道被转换。如果设置了 EOCIE 或JEOCIE,只在最后一个通道转换完毕后才会产生EOC 或JEOC 中断。 ' Y1 p+ Y4 C' `, V2 I

ADCON 位用于开关AD转换器。而CONT 位用于设置是否进行连续转换,我们使用单次转换,所以CONT 位必须为0。CAL 和RSTCAL 用于AD校准。ALIGN用于设置数据对齐,我们使用右对齐,该位设置为0 。 EXTSEL[2:0]用于选择启动规则转换组转换的外部事件,详细的设置关系如下: 
这里使用的是软件触发(SWSTART ),所以设置这3 个位为111 。ADC_CR2 的SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1 。AWDEN 为用于使能温度传感器和Vrefint 。
/ q' x Q: V! [3 E6 }% @ 2.2. ADC采样事件寄存器(ADC_SMPR1 和ADC_SMPR2 )这两个寄存器用于设置通道0~17的采样时间,每个通道占用 3 个位。 
ADC_SMPR2 的各位描述如下 
对于每个要转换的通道,采样时间建议尽量长一点,以获得较高的准确度,但是这样会降低ADC的转换速率。ADC的转换时间可以由下式计算: Tcovn= 采样时间+12.5 个周期 其中:Tcovn 为总转换时间,采样时间是根据每个通道的SMP位的设置来决定的。例如,当ADCCLK=14Mhz 的时候,并设置 1.5个周期的采样时间,则得到:Tcovn=1.5+12.5=14 个周期=1us 。 ( {" ~; p* N; K, |4 O
2.3. ADC规则序列寄存器(ADC_SQR1~3)
L[3:0] 用于存储规则序列的长度,我们这里只用了 1 个,所以设置这几个位的值为 0 。其他的SQ13~16 则存储了规则序列中第13~16 个通道的编号(0~17)。另外两个规则序列寄存器同ADC_SQR1大同小异,我们这里就不再介绍了,要说明一点的是:我们选择的是单次转换,所以只有一个通道在规则序列里面,这个序列就是SQ0 ,通过ADC_SQR3的最低5 位设置。 # @# P) i- W% v& p
2.4. ADC规则数据寄存器(ADC_DR)
这里要提醒一点的是,该寄存器的数据可以通过ADC_CR2 的ALIGN位设置左对齐还是右对齐。在读取数据的时候要注意。 8 S7 Q" \& {! m/ u2 p1 M
2.5. ADC状态寄存器(ADC_SR )
这里我们要用到的是EOC 位,我们通过判断该位来决定是否此次规则通道的AD转换已经完成,如果完成我们就从ADC_DR 中读取转换结果,否则等待转换完成。
: D0 u3 F+ Z4 V A5 Z8 Y 3. 寄存器操作步骤1 、开启PA口时钟,设置PA0 为模拟输入。 STM32F103RBT6的ADC通道0 在PA 0 上,所以,我们先要使能 PORTA的时钟,然后设置PA 0 为模拟输入。 2 、使能ADC1 时钟,并设置分频因子。 要使用ADC1,第一步就是要使能 ADC1 的时钟,在使能完时钟之后,进行一次 ADC1 的复位。接着我们就可以通过RCC_CFGR设置ADC1 的分频因子。分频因子要确保 ADC1 的时钟(ADCCLK)不要超过14Mhz 。 3 、设置ADC1 的工作模式。 在设置完分频因子之后,我们就可以开始 ADC1 的模式配置了,设置单次转换模式、触发方式选择、数据对齐方式等都在这一步实现。 4 、设置ADC1 规则序列的相关信息。 接下来我们要设置规则序列的相关信息,我们这里只有一个通道,并且是单次转换的,所以设置规则序列中通道数为1 ,然后设置通道 0 的采样周期。 5 、开启AD转换器,并校准。 在设置完了以上信息后,我们就开启AD转换器,执行复位校准和AD校准,注意这两步是必须的!不校准将导致结果很不准确。 6 )读取ADC值。 在上面的校准完成之后,ADC就算准备好了。接下来我们要做的就是设置规则序列 0 里面的通道,然后启动ADC转换。在转换结束后,读取ADC1_DR 里面的值就是了。 硬件设置:我们通过ADC1 的通道0 (PA 0 )来读取外部电压值。 注意:这里不能接到板上5V电源上去测试,这可能会烧坏 ADC! - #include <stm32f10x_lib.h>
, X/ s- J' N, ]$ e! Z - #include "adc.h"
' z+ c3 r- b3 T - //ADC 驱动代码 2 C {2 @1 E5 [) A% I ^
- //初始化ADC
! S3 b- f1 i; j5 [$ u1 ] - //这里我们仅以规则通道为例
* J0 v h% v" S% @( D3 t - //我们默认将开启通道0~3
5 A/ T- s3 l0 { - void Adc_Init(void)
5 r k: k. t( t+ u k5 i7 F) J% h - { : w3 m4 N5 X8 t- N/ w; j2 h( G) u
- //先初始化IO口! z; i! E f8 @$ q0 J
- RCC->APB2ENR|=1<<2; //使能PORTA口时钟
$ x, M) L# j1 Z5 i2 O. W+ s - GPIOA->CRL&=0XFFFF0000;//PA0 1 2 3 anolog输入" {" A1 H- | F+ F5 U+ P
- //通道10/11设置
2 c* S6 P; t+ h1 g - RCC->APB2ENR|=1<<9; //ADC1时钟使能
9 z" L2 H5 H: Y0 e3 m6 ? - RCC->APB2RSTR|=1<<9; //ADC1复位
" i D- ^/ m2 M - RCC->APB2RSTR&=~(1<<9);//复位结束
( i, f- d( K: n - RCC->CFGR&=~(3<<14); //分频因子清零
3 b/ y7 g! m0 w - //SYSCLK/DIV2=12M ADC时钟设置为12M,ADC最大时钟不能超过14M!9 k8 ?0 x$ Y/ I' A2 N
- //否则将导致ADC准确度下降!
3 p. \9 W0 ]2 ` - RCC->CFGR|=2<<14; 1 ^# ]# ^6 u" D2 Z6 ?* S
-
- z1 Q# X6 a/ H6 @ - ADC1->CR1&=0XF0FFFF; //工作模式清零
4 M/ [. K* ]2 O2 d: F! w - ADC1->CR1|=0<<16; //独立工作模式
: w# e9 h" P! V! z9 f, C - ADC1->CR1&=~(1<<8); //非扫描模式 $ ~& x6 A d# r* V% N
- ADC1->CR2&=~(1<<1); //单次转换模式" N% w# D' c, M9 D: S% E6 j
- ADC1->CR2&=~(7<<17);
# S% y3 ?3 s7 w - ADC1->CR2|=7<<17; //软件控制转换
, B) V8 D1 ?/ f7 i$ q; `! n O - ADC1->CR2|=1<<20; //使用用外部触发(SWSTART)!!! 必须使用一个事件来触发
& ?3 y! m" `: e$ h5 j# N6 W; A5 I - ADC1->CR2&=~(1<<11); //右对齐 ' ~5 T6 K" }" f5 e% M
- ADC1->SQR1&=~(0XF<<20);
2 }9 D2 B( z. D. d$ e - ADC1->SQR1&=0<<20; //1个转换在规则序列中 也就是只转换规则序列1
: V* _0 ~, i v. D; h - //设置通道0~3的采样时间
# J: D1 n, G1 O- E/ ^ - ADC1->SMPR2&=0XFFFFF000;//通道0,1,2,3采样时间清空
3 a/ ?) O! L. M* ~- v: K; U - ADC1->SMPR2|=7<<9; //通道3 239.5周期,提高采样时间可以提高精确度
4 O2 ?1 m' L9 x W! }# s - ADC1->SMPR2|=7<<6; //通道2 239.5周期,提高采样时间可以提高精确度 " A1 i1 }' ~: N4 A& g8 M: y
- ADC1->SMPR2|=7<<3; //通道1 239.5周期,提高采样时间可以提高精确度
0 M3 Z6 j% ~1 \$ b - ADC1->SMPR2|=7<<0; //通道0 239.5周期,提高采样时间可以提高精确度 - N/ N, E: K% E% F, h& v& F
- # ?$ Q# u6 D! w, M2 g
- ADC1->CR2|=1<<0; //开启AD转换器 2 `- E6 V* ?. U. ~1 f( i
- ADC1->CR2|=1<<3; //使能复位校准
9 y& d0 a% ?$ i' x - while(ADC1->CR2&1<<3); //等待校准结束 # i( p1 [) _( T# k m3 X
- //该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。 4 r: C7 d' ]6 `
- ADC1->CR2|=1<<2; //开启AD校准
9 L( B' n6 L f4 w0 S! r: q - while(ADC1->CR2&1<<2); //等待校准结束' r" }9 L- F5 d# s0 M1 N) L( K* ]" U
- //该位由软件设置以开始校准,并在校准结束时由硬件清除
/ Q. o6 E I# ^4 ?& i4 r/ G - }
# N3 j/ k! ~4 f6 Z/ b, u* z" a - //获得ADC值 ?* [% w" [# w0 v+ ~. ~
- //ch:通道值 0~3
# Y9 F, t( ~4 C - u16 Get_Adc(u8 ch)
" S: A6 r$ _$ @ - {, p5 ~1 |: c( [# a: f2 V' L, G2 R
- //设置转换序列 3 }4 r( w; {& ^: l% m
- ADC1->SQR3&=0XFFFFFFE0;//规则序列1 通道ch$ Z0 g) M9 X' ^8 J$ \# b4 w2 X
- ADC1->SQR3|=ch; 4 a; e9 x0 u' f1 g$ ]1 ]
- ADC1->CR2|=1<<22; //启动规则转换通道 * Z, \ A6 b, T2 k1 V4 V7 }
- while(!(ADC1->SR&1<<1));//等待转换结束 # J$ u* Z+ k0 U0 G* Q5 v4 h4 n
- return ADC1->DR; //返回adc值
3 c6 E5 |+ Q/ Z4 y9 z4 O - }
复制代码
% i% W' E0 ?7 }1 I- #include <stm32f10x_lib.h>4 D2 ]0 V) p T% S" u
- #include "adc.h"4 |4 G" W+ u( Z% c7 [0 H0 C \ e
- //ADC 驱动代码
( _" W7 s( s2 n! Q/ G/ Z - //初始化ADC
; L/ [. g$ O; o) m$ H5 o - //这里我们仅以规则通道为例
; o8 ]% S: n$ S - //我们默认将开启通道0~3
: k' L) ] s" |* n8 x - void Adc_Init(void)+ i0 f9 Y! `2 L
- { & O* s9 s" ~$ G% j& n
- //先初始化IO口$ r. q3 q* _" a6 \* ?3 I
- RCC->APB2ENR|=1<<2; //使能PORTA口时钟 - D" W: z6 c! C4 s6 m! _& E
- GPIOA->CRL&=0XFFFF0000;//PA0 1 2 3 anolog输入$ g1 z6 E X- Q, w- `; K$ {3 E
- //通道10/11设置 ; F; s$ h' P, g$ I3 t0 F
- RCC->APB2ENR|=1<<9; //ADC1时钟使能 $ F& |, J2 }! F/ r
- RCC->APB2RSTR|=1<<9; //ADC1复位
2 g6 Z S* `- y; E- J8 x- \ - RCC->APB2RSTR&=~(1<<9);//复位结束
: e; {/ F4 ~5 i+ R E* h) A/ ] - RCC->CFGR&=~(3<<14); //分频因子清零
; `* r: T9 z4 s" @: L, B - //SYSCLK/DIV2=12M ADC时钟设置为12M,ADC最大时钟不能超过14M!
+ s" _4 M M% v2 h7 _ - //否则将导致ADC准确度下降!
! p* |8 F$ A) E# l& g" p5 u3 L - RCC->CFGR|=2<<14; ( A; }) l2 i5 X( _' Z
- ! D( ?3 d; O) R5 j3 Q
- ADC1->CR1&=0XF0FFFF; //工作模式清零/ B2 T# C7 b. H
- ADC1->CR1|=0<<16; //独立工作模式 * V# J( {! O6 h0 |: E
- ADC1->CR1&=~(1<<8); //非扫描模式 ( v: y8 y* w3 M! _
- ADC1->CR2&=~(1<<1); //单次转换模式
# M( k# Z! O% m8 ^ - ADC1->CR2&=~(7<<17);
) N& Z6 s8 L% `& n& Y# B- v - ADC1->CR2|=7<<17; //软件控制转换 + D: y$ \$ X3 T, j' e, f4 [! P
- ADC1->CR2|=1<<20; //使用用外部触发(SWSTART)!!! 必须使用一个事件来触发
9 N6 h/ C1 C' i' e7 g - ADC1->CR2&=~(1<<11); //右对齐
6 W1 k9 ?5 O/ I' q2 q - ADC1->SQR1&=~(0XF<<20);: D- ^% o W( l) w: v
- ADC1->SQR1&=0<<20; //1个转换在规则序列中 也就是只转换规则序列1 2 C$ J3 c4 o* ^' d4 {5 Z4 y$ X
- //设置通道0~3的采样时间
# C+ w3 R) U6 Z3 X# q - ADC1->SMPR2&=0XFFFFF000;//通道0,1,2,3采样时间清空
% }3 c( [: j0 E9 ^8 Y# i - ADC1->SMPR2|=7<<9; //通道3 239.5周期,提高采样时间可以提高精确度 6 ^1 U8 q& |0 c
- ADC1->SMPR2|=7<<6; //通道2 239.5周期,提高采样时间可以提高精确度
6 F8 g* d7 @3 w* M$ U6 F1 c - ADC1->SMPR2|=7<<3; //通道1 239.5周期,提高采样时间可以提高精确度
/ ~4 q$ u2 Q) N - ADC1->SMPR2|=7<<0; //通道0 239.5周期,提高采样时间可以提高精确度 8 x- R/ ~) h% Z/ g/ a# x
-
3 ~ I6 x8 G5 O* I6 ^ - ADC1->CR2|=1<<0; //开启AD转换器 / Y0 R- O6 w) F4 {+ [ M' u
- ADC1->CR2|=1<<3; //使能复位校准
) |! n( v6 E1 R5 d ]' Z1 i3 `6 x - while(ADC1->CR2&1<<3); //等待校准结束 : n/ v* b9 J7 T0 S: x
- //该位由软件设置并由硬件清除。在校准寄存器被初始化后该位将被清除。
+ D6 G* Z/ J) G; f3 b - ADC1->CR2|=1<<2; //开启AD校准
3 Z2 n: N" C, [" D" v - while(ADC1->CR2&1<<2); //等待校准结束
/ ~# r% I# q( @' F - //该位由软件设置以开始校准,并在校准结束时由硬件清除
" _2 j6 u% u9 m" i - } $ Q$ C, @* H9 i' ? ]! P
- //获得ADC值& r5 k( a% Z. i
- //ch:通道值 0~3$ j5 ?* e: K& R4 |3 J& z1 y
- u16 Get_Adc(u8 ch)
3 ]" I2 K* _ ]+ ~9 y9 M0 ?5 H7 z - {
1 u. J) }; f0 g' V8 H9 J V - //设置转换序列
J$ p$ _1 @5 C - ADC1->SQR3&=0XFFFFFFE0;//规则序列1 通道ch2 z: {$ D0 o3 M6 z1 B/ v* W3 c1 t- e
- ADC1->SQR3|=ch;
& K# D1 F6 T! g5 W$ x/ S5 @ - ADC1->CR2|=1<<22; //启动规则转换通道
, _+ {, q3 \0 J" j+ h, | - while(!(ADC1->SR&1<<1));//等待转换结束 * Z* K$ G2 Y, P' j
- return ADC1->DR; //返回adc值 ' w3 Y" w: w2 Q; i* s( G& {
- }
复制代码
& I2 D7 o. i3 E$ c6 `/ o9 V9 @) K) A- #ifndef __ADC_H
- #define __ADC_H
-
- #define ADC_CH0 0 //通道0
- #define ADC_CH1 1 //通道1
- #define ADC_CH2 2 //通道2
- #define ADC_CH3 3 //通道3
- void Adc_Init(void);
- u16 Get_Adc(u8 ch);
- #endif
7 Z6 S) v' }* O! Q+ G! s- ^2 \! e+ s; a8 E0 i& e. @
% y2 H6 K h: K1 j. {. ~
5 g2 s- g" I" p' L- w( ]
|