你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32使用HAL库实现ADC单通道转换

[复制链接]
STMCU小助手 发布时间:2022-2-9 22:16
  STM32的ADC转换还是很强大的,它具有多个通道选择,这里我就不细说,不了解的可以自行百度,这里只是选取单通道,实现ADC转换。在文章开始之前,我说一下数据左对齐跟右对齐的差别,以前一直糊里糊涂的,记录下来以免以后自己忘记。12位二进制最大值为 0x0FFF 左对齐操作后的结果是 0xFFF0,右对齐后还是0x0FFF。反过来看 ,若寄存器里左对齐的数据值X (相当于实际数据*16,所以左对齐转换的值要/16才是实际的值),则X>>4才是实际的数据。而右对齐,则是数据保持不变,采集到多少就多少。至于是按左对齐保存到寄存器还是按照右对齐,就看你的配置里如何选了。
  好了,下面就开始说明怎么用STM32CUBEMX实现ADC单通道转换吧。
利用中断模式
1、配置ADC引脚
  
1433771-20181129153031741-539968200.png

# F/ H% B: i' X3 K, m9 m
2、开定时跟串口,定时器用来定时打开ADC转换,这样可以达到1S内控制ADC转换次数的目的,不过有个限制,这里样子控制ADC转换次数的话,如果采样次数多,配置ADC采样速度时一定要够  快,正常配置ADC的采样频率可以通过改变其采样速度来设置的,这里我是为了方便处理,就直接用定时器去开启了;而串口则是打印转换后的电压用的。
5 U) g/ t6 K+ m+ x- J8 ~( C$ |
1433771-20181129153526549-874517231.png
  
3、配置时钟
  
1433771-20181129153601155-276127748.png

( ?* O& C& f9 `5 K# Y; [) }; |
4、配置ADC设置
  `
1433771-20181129153907972-233216619.png

0 h* D5 {6 @0 \
5、开启中断模式
  
1433771-20181129154113690-349965448.png
9 I4 {. P: |  X. ~
6、串口配置默认即可

4 M( ]+ R1 S" e* w7 u7 o  Y7 o
1433771-20181129154239218-469781284.png
  
7、定时器配置,定时器配置的是进入定时器中断的频率,定时时间可以根据这个频率换算出来,这里定时器的频率 = 72M / 72 /1000 =1000Hz,所以定时时间为 T = 1S/f = 1S/1000 = 1ms,所以我这里配置定时为1ms。
7 @0 ?3 Z; S9 K9 n
1433771-20181129154729430-591115694.png
  
8、基本配置我们完成了,现在我们生成工程用KEIL5打开

' A+ A# p; U) u$ [: t
1433771-20181129154852211-1292340288.png
  
9、打开工程,我们现在进入代码部分
  这里我们只需要重写定时器中断回调函数跟,ADC转换回调中断函数即可。在main文件里添加这下面这两个函数
8 u! P' W# x& E+ s. x
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
    + C2 |) w7 o4 e* ^0 F8 P/ f3 g
  2. {2 P' G* r7 D0 C4 ^9 b  F
  3.     HAL_ADC_Start_IT(&hadc1); //定时器中断里面开启ADC中断转换,1ms开启一次采集    " `4 ~: D0 D+ g
  4. }
    7 C- |2 Q. s0 |. s% F( j( c
  5. 1 r8 W+ o9 ]" W" }4 k6 k: q
  6. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)    //ADC转换完成回调) k1 E0 P: `+ e5 ^+ w" O
  7. {
    - }1 u7 }, [: @1 N
  8.     HAL_ADC_Stop_IT(&hadc1);        //关闭ADC7 t' s- f* {1 w: L* N
  9.     HAL_TIM_Base_Stop_IT(&htim3);    //关闭定时器3 ?# B+ e/ Q3 W$ Q( W1 e3 T. D" [
  10.     AD_Value=HAL_ADC_GetValue(&hadc1);  //获取ADC转换的值: m; r) K" }/ j3 g* p& W
  11.     Value_1=(float)(AD_Value*3.3/4096);     //ADC换算,这里参考电压3.3V,12位的ADC满量程为2^12=4096,转换出来的单位是V
    ! p0 I; u+ z& h7 e4 N
  12.     printf("%.4f\r\n",Value_2[j-10000]);     //串口打印信息& k/ N, r; V# g( y7 I
  13.     HAL_TIM_Base_Start_IT(&htim3);       //开启定时器
    , h, n" j% H) y
  14. }
复制代码
  到这里就完成单通道ADC中断转换的所有步骤啦,通过串口助手实测转换结果误差为0.0008v。
- A3 O7 h( G4 f  g% j4 a0 x- \8 C
不使用中断模式
  不使用中断模式的情况下跟使用中断的类似的,首先配置的过程中不需要开启中断,至于定时器开不开看个人需要,想利用定时器定时采集的可以开,不想的不用开,其他的配置一样。生成代码后,在main文件的main函数中的while循环里添加下面代码:
  1. /* USER CODE BEGIN 3 */
    , u7 M% k9 P) C  H$ K: Q: h8 a
  2.         for(char n=0;n<22;n++) 4 F% N0 n- w) @9 C+ Q- `0 O
  3.         {  //取22个值做滤波用
    ! b# \+ k7 L3 _/ R/ U
  4.             HAL_ADC_Start(&hadc2);8 N" P1 o+ _3 p
  5.             HAL_ADC_PollForConversion(&hadc2, 10);    //等待转换完成,第二个参数表示超时时间,单位ms        
    1 x( `: l/ k* g: R
  6.             if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))! v7 z* _: D. q9 ~6 Y
  7.             {
    % {8 m7 H% M0 M
  8.                 Value[n]=HAL_ADC_GetValue(&hadc2);. X' m2 Q& j- L: ^
  9.                 AD_Value += Value[n];8 p7 L: [( S- s8 Z8 @$ Q6 N
  10.             }               
    ! Y, c( O1 H: j$ Y& n
  11.         }) s7 q, ]( e. a" R4 n) {
  12.         max=Value[0];
    9 v- |4 T6 O7 y" }! ]) |
  13.         min=Value[0];
    ! B9 l2 e8 l/ @
  14.         for(char n=0;n<22;n++)//取最大值、最小值
    9 a' d8 ?; ?& o4 I- i/ Z) }/ A
  15.         {
    2 b- L1 w+ H- s- d
  16.             max=(Value[n]<max)?max:Value[n];    9 G2 ~! O1 p3 L8 `
  17.             min=(min<Value[n])?min:Value[n];2 C6 n; u! z% b7 m2 E
  18.         }   
    * }! @- G- K  z5 R# b; b
  19.         printf("PC0 ADC : %.4f \r\n",(float)((AD_Value -max-min)/20)*(3.1/4096));        
    1 U/ B, k4 U0 V( p5 t
  20.         AD_tr=(float)((AD_Value -max-min)/20)*(3.1/4096);    //这里我做了个去掉最大最小值后,取均值的软件滤波   & E+ t) T' ]0 \" z8 l' ?) }% X
  21.         AD_Value=0;
复制代码
! p+ `( f* ~5 h4 G) @5 n* h4 @% l
  这里面的一些变量就你们自己去定义了,我就不列出来了,实测误差在0.001v以内。

+ I& l1 ]/ x) }* d& v, N
补充注意事项:
  1、ADC初始化后要进行校准,使用下面函数校准,可以放在ADC初始化函数后面校准
  1. HAL_ADCEx_Calibration_Start(&hadc2);    //AD校准
复制代码
/ |8 e: d8 s5 @& L
  2、传入ADC的电压不可以超过3.3V,就是不可以超过你的参考电压,不然结果不准,还有可能烧坏ADC引脚

6 P# O0 y) S! Z7 D: k
使用DMA模式
 再次写写stm32cubemx中AD采集的问题,这次不用while里面的查询,也不用中断采样了,直接用DMA
% ^% F9 G* D, F+ ^( y) l先说下用DMA的好处:无论是中断采样还是查询采样,都需要在主程序中占用好多时间出来,嗯,你可以这样理解$ M# l4 m8 a* S( b8 w
那种采样都需要调用HAL_ADC_GetValue()这个函数,,,就是要取得转换后的值,中断还好点,要是查询的话,有可能会丢失数据啊. 用dma就可以避免了2 M* k8 r7 [2 d0 g: l
DMA用的事总线时间,无线cpu干预,额,这种说法貌似有点问题.管它呢
* G# O* e. C4 Q1 B3 M. i$ K在AD转换结束的时候自动连接你准备存取的变量的地址,数据一步到位.额,省了多少事..
) S) `0 d* v1 O9 ?* c: t3 Y使用stm32cubemx对AD的配置0 w2 M" d0 t+ h6 f' f

* d$ z" @. g! a+ N) W  _2 w$ }: P

9 Y/ Z  D; M7 S# q

& l4 G" [0 J; Y1 q( W然后对她的DMA配置,并开启DMA的中断

9 j; E2 ~1 ^% _

" Y4 \4 q+ I7 N! M8 c/ \然后生成代码吧
" ~/ \9 s7 ^1 |( s0 |打开main.c文件,在这个地方添加代码
  1. /[i] USER CODE BEGIN 0 [/i]/  b* X5 K! v* |' I4 X% ?* g
  2. __IO uint16_t uhADCxConvertedValue = 0;5 Q$ o3 G/ J" G2 y. g
  3. /[i] USER CODE END 0 [/i]/
复制代码

% ?: _0 P7 z$ u6 b) g
在main()函数里添加
  1.   /[i] USER CODE BEGIN 2 [/i]/
      \* k0 |7 X. u
  2. HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1);# w; N" K& u5 Z; W- K5 ]
  3. 4 O( i0 |3 I0 n8 K5 u4 [! k" q
  4.   /[i] USER CODE END 2 [/i]/
复制代码
  F: y3 J. V" t8 p! v- G
意思是开启dma传输,传送一个字的数据到uhADCxConvertedValue这个变量里面
+ v, {- F: x% P6 F/ s5 z然后再文件的末尾处添加
  1. /[i] USER CODE BEGIN 4 [/i]/4 \9 v/ T- s! f: }; c' w
  2. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
    ( Z. y* \: k7 a& X" y. Z, F2 ?+ e
  3. {1 g9 Y6 f; H0 k2 L
  4.   /[i] Turn LED1 on: Transfer process is correct [/i]/
    / m4 {" B2 m  \- z6 ?* Y
  5. // BSP_LED_On(LED1);* F9 ~: W, S$ b0 Y( M
  6.     HAL_GPIO_WritePin (GPIOF,GPIO_PIN_6,GPIO_PIN_SET );8 M+ a: S! g$ k9 U! s, i* C9 j
  7. }, `* G' c/ V% o: Y( v9 e3 `
  8. /[i] USER CODE END 4 [/i]/
复制代码

; x- [4 S4 P$ Q
意思是AD转换完成调用这个函数,函数里使能led8 C+ v/ N0 g( b& _* m3 Y3 T
也许,你会问,为毛是HAL_ADC_ConvCpltCallback()这个函数啊,这个函数不是当开启AD的中断的时候才调用的吗?
. ~  v5 z3 i. Y  D, \嗯,对,这个函数是这样的,但是你仔细去分析下开启AD的DMA中断函数里面,就会发现这个函数也在啊$ h# T% f; _: V
如下图.进入HAL_ADC_Start_DMA函数里面,看到( l; f$ |' D* U% P) w
! G$ i) ]4 w9 D3 I" E+ m

* F0 J) A- L; N) H, G
5 u7 ~3 ?' C# y
在进入到图中的ADC_DMAConvCplt函数里面看到
* c+ c% x/ t3 y; O6 p$ o: ?) |( ]* m$ t: f8 j) i  i  o7 ^

- g% w1 N' F) B' g( o1 B
3 @/ y$ U" f/ e2 C! P% h2 T+ P
OK,疑问解决,
: b5 K( h( k5 F5 P3 t/ r以后用到AD就可以直接调用这个CALL了,不要纠结了.
) P/ R2 K& ^; r! T! ]+ L
赞 收藏 评论0 发布时间:2022-2-9 22:16

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版