|
STM32的ADC转换还是很强大的,它具有多个通道选择,这里我就不细说,不了解的可以自行百度,这里只是选取单通道,实现ADC转换。在文章开始之前,我说一下数据左对齐跟右对齐的差别,以前一直糊里糊涂的,记录下来以免以后自己忘记。12位二进制最大值为 0x0FFF 左对齐操作后的结果是 0xFFF0,右对齐后还是0x0FFF。反过来看 ,若寄存器里左对齐的数据值X (相当于实际数据*16,所以左对齐转换的值要/16才是实际的值),则X>>4才是实际的数据。而右对齐,则是数据保持不变,采集到多少就多少。至于是按左对齐保存到寄存器还是按照右对齐,就看你的配置里如何选了。 好了,下面就开始说明怎么用STM32CUBEMX实现ADC单通道转换吧。 利用中断模式 1、配置ADC引脚
' V, D+ J# G$ ~, ?( ^ 2、开定时跟串口,定时器用来定时打开ADC转换,这样可以达到1S内控制ADC转换次数的目的,不过有个限制,这里样子控制ADC转换次数的话,如果采样次数多,配置ADC采样速度时一定要够 快,正常配置ADC的采样频率可以通过改变其采样速度来设置的,这里我是为了方便处理,就直接用定时器去开启了;而串口则是打印转换后的电压用的。
/ t; \. l* X h O3 i3 m7 P4 u4 S3、配置时钟 ( m) h% P8 u% F
4、配置ADC设置 ` 
/ U5 G/ h, v7 O8 E$ t5、开启中断模式
" S, _! x8 Q& O. R6、串口配置默认即可 8 V' b6 I2 W6 Z
7、定时器配置,定时器配置的是进入定时器中断的频率,定时时间可以根据这个频率换算出来,这里定时器的频率 = 72M / 72 /1000 =1000Hz,所以定时时间为 T = 1S/f = 1S/1000 = 1ms,所以我这里配置定时为1ms。 " d7 [; i* ^; f% a
8、基本配置我们完成了,现在我们生成工程用KEIL5打开
1 F. L, j- X6 x* c9、打开工程,我们现在进入代码部分 这里我们只需要重写定时器中断回调函数跟,ADC转换回调中断函数即可。在main文件里添加这下面这两个函数
- n' A( G4 H' O! X7 D7 \1 a- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时器中断回调
3 P4 c7 h c( `; C ~ - {' f* n( @3 @" G1 R5 J2 h
- HAL_ADC_Start_IT(&hadc1); //定时器中断里面开启ADC中断转换,1ms开启一次采集
, I3 E3 e/ f/ @8 F7 k - }* J& S! E C9 p- }) x2 O
- " r' T0 N6 V. z" R9 q
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) //ADC转换完成回调
) p! P# U( Y3 U4 T - {! a. U& e) Z* n7 L2 G& R/ y3 L
- HAL_ADC_Stop_IT(&hadc1); //关闭ADC5 A1 b- P) X5 ]0 Z6 @ }/ m5 J
- HAL_TIM_Base_Stop_IT(&htim3); //关闭定时器
4 y$ U$ T$ h3 ~9 D& F! H+ O - AD_Value=HAL_ADC_GetValue(&hadc1); //获取ADC转换的值
, Q2 E, y% \2 g. f7 @0 s; ]% i - Value_1=(float)(AD_Value*3.3/4096); //ADC换算,这里参考电压3.3V,12位的ADC满量程为2^12=4096,转换出来的单位是V
9 \' E5 k# w+ b" ^4 V" p: B" k - printf("%.4f\r\n",Value_2[j-10000]); //串口打印信息' ~ o+ a1 j" ]3 Y
- HAL_TIM_Base_Start_IT(&htim3); //开启定时器
# m$ k* J8 x2 C/ g - }
复制代码 到这里就完成单通道ADC中断转换的所有步骤啦,通过串口助手实测转换结果误差为0.0008v。
$ g" k0 y% i" n3 L. r- i
1 O2 [' C; U3 B* Z不使用中断模式 不使用中断模式的情况下跟使用中断的类似的,首先配置的过程中不需要开启中断,至于定时器开不开看个人需要,想利用定时器定时采集的可以开,不想的不用开,其他的配置一样。生成代码后,在main文件的main函数中的while循环里添加下面代码: - /* USER CODE BEGIN 3 */6 i9 d3 _8 o4 K5 p( o8 w. @& _
- for(char n=0;n<22;n++)
# I) p) K9 o& e$ X0 S - { //取22个值做滤波用0 E* f* v& X+ w: K% I5 m0 h# C' r
- HAL_ADC_Start(&hadc2);' g; o/ E6 d- s" ^9 O
- HAL_ADC_PollForConversion(&hadc2, 10); //等待转换完成,第二个参数表示超时时间,单位ms
* s! a8 Z) q0 f3 T* H2 P6 |! Q - if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))$ r) \' I# a4 Z# x. i
- {
/ m8 R+ U$ G# P+ ^ - Value[n]=HAL_ADC_GetValue(&hadc2);; j5 ?3 B5 Z" c+ ], V. n: @
- AD_Value += Value[n];
7 R- c' [1 F1 D+ ?2 a9 K1 S' K; f - } . n- [. a: g4 G) ?! z
- }
7 L- Y- V9 M" A3 m# M/ M% X - max=Value[0];
2 E; i$ q; ~6 w) }2 T' _6 v - min=Value[0];- e$ v2 d' i, y% E" }
- for(char n=0;n<22;n++)//取最大值、最小值. ^1 z$ j6 m) W: b4 w( g; z4 M/ @
- {( ]7 Z: {( U: v) [, J
- max=(Value[n]<max)?max:Value[n];
8 w4 |0 F; m' R( l3 z! B8 r - min=(min<Value[n])?min:Value[n];
z& }" {/ `1 y7 k. W% \" U - }
+ i$ L8 Z0 t& L# }+ R% @2 o& _ - printf("PC0 ADC : %.4f \r\n",(float)((AD_Value -max-min)/20)*(3.1/4096)); & A. j# v1 U- T0 k
- AD_tr=(float)((AD_Value -max-min)/20)*(3.1/4096); //这里我做了个去掉最大最小值后,取均值的软件滤波
* N) h# ]) }& t - AD_Value=0;
复制代码
3 A3 p! @* ?0 p' d5 Q8 v. y3 I 这里面的一些变量就你们自己去定义了,我就不列出来了,实测误差在0.001v以内。
% h2 b/ `: S7 c% U& k4 ^8 \补充注意事项: 1、ADC初始化后要进行校准,使用下面函数校准,可以放在ADC初始化函数后面校准 - HAL_ADCEx_Calibration_Start(&hadc2); //AD校准
复制代码
; i8 E# p/ m) e6 P0 v3 d$ b 2、传入ADC的电压不可以超过3.3V,就是不可以超过你的参考电压,不然结果不准,还有可能烧坏ADC引脚
7 c& c/ Q3 D* c+ Y" ` 使用DMA模式 再次写写stm32cubemx中AD采集的问题,这次不用while里面的查询,也不用中断采样了,直接用DMA; C8 z0 P: T* e! R# o
先说下用DMA的好处:无论是中断采样还是查询采样,都需要在主程序中占用好多时间出来,嗯,你可以这样理解$ Y1 f* A8 N* \9 Y& a
那种采样都需要调用HAL_ADC_GetValue()这个函数,,,就是要取得转换后的值,中断还好点,要是查询的话,有可能会丢失数据啊. 用dma就可以避免了
3 ]3 e+ H3 X$ t& n6 ]DMA用的事总线时间,无线cpu干预,额,这种说法貌似有点问题.管它呢+ A. b5 f4 h2 `/ v
在AD转换结束的时候自动连接你准备存取的变量的地址,数据一步到位.额,省了多少事.. f+ V9 ]3 k! b& G* P
使用stm32cubemx对AD的配置
$ h( }( B, `9 @$ H" s8 m. x+ z( F% Z( \2 s. c
1 c9 `& z- Q0 H" O0 s4 Z3 K
3 P1 u# g7 H& p: l; a8 {然后对她的DMA配置,并开启DMA的中断
7 ~8 Q3 ]8 ~, [1 X0 s! B3 `
L1 H5 f4 k/ y. ]然后生成代码吧9 J! J+ W! N/ [ c$ ]8 z) v5 z
打开main.c文件,在这个地方添加代码
- /[i] USER CODE BEGIN 0 [/i]/
2 Z3 D( q6 L% X( Z: o - __IO uint16_t uhADCxConvertedValue = 0;
3 v* T) Q& L5 w9 d) }& z - /[i] USER CODE END 0 [/i]/
复制代码 % S1 p: B0 u8 q
在main()函数里添加 - /[i] USER CODE BEGIN 2 [/i]/
/ D1 E- O' ?1 G* H - HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1);- y0 U; Q6 {+ {0 v
1 X. l# g7 B8 @' ]- /[i] USER CODE END 2 [/i]/
复制代码
+ P' n2 z3 U( H意思是开启dma传输,传送一个字的数据到uhADCxConvertedValue这个变量里面: g: K9 C/ k$ |* v6 p
然后再文件的末尾处添加 - /[i] USER CODE BEGIN 4 [/i]/
. a! z n) K; z5 ^$ [9 I - void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle): b7 F/ i5 b* s% P. u# A
- {
1 }$ P( t1 I O* F9 X - /[i] Turn LED1 on: Transfer process is correct [/i]/
+ s r$ y2 m, g6 K( T `6 b9 A - // BSP_LED_On(LED1);) X" Q- D1 F. P- m
- HAL_GPIO_WritePin (GPIOF,GPIO_PIN_6,GPIO_PIN_SET );
: V, r8 n8 p6 o0 Q3 s - }9 b0 }6 l7 L7 E" D% B; m
- /[i] USER CODE END 4 [/i]/
复制代码 ) c; [7 L* }" u1 ?" N' B
意思是AD转换完成调用这个函数,函数里使能led
. G3 U0 Q3 c+ }" e6 ^: c/ u9 U也许,你会问,为毛是HAL_ADC_ConvCpltCallback()这个函数啊,这个函数不是当开启AD的中断的时候才调用的吗?% U# d: I6 T, G2 l8 n
嗯,对,这个函数是这样的,但是你仔细去分析下开启AD的DMA中断函数里面,就会发现这个函数也在啊2 J# I; ]- i( _" v m+ P7 E
如下图.进入HAL_ADC_Start_DMA函数里面,看到
) K0 i. `0 S& i) J3 E! t
# A8 Z% X% _5 ?) Z& K/ V! n8 u2 {2 k% ? ; G& J! U4 \3 t* n
# T/ T1 V; r& |
在进入到图中的ADC_DMAConvCplt函数里面看到- E1 d4 b. Q( e3 G) k" ^* n
: ]1 K, _7 l; H, ]) V9 B7 Q 
a1 }$ B3 I; s! W" X4 |. }$ J2 s8 |9 N
OK,疑问解决,
6 }* ~- j2 F+ }& D( @' R" u" S2 Y) b以后用到AD就可以直接调用这个CALL了,不要纠结了.
C8 Z( V- R! |( T |