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

【经验分享】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

' V, D+ J# G$ ~, ?( ^
2、开定时跟串口,定时器用来定时打开ADC转换,这样可以达到1S内控制ADC转换次数的目的,不过有个限制,这里样子控制ADC转换次数的话,如果采样次数多,配置ADC采样速度时一定要够  快,正常配置ADC的采样频率可以通过改变其采样速度来设置的,这里我是为了方便处理,就直接用定时器去开启了;而串口则是打印转换后的电压用的。

/ t; \. l* X  h  O3 i3 m7 P4 u4 S
1433771-20181129153526549-874517231.png
  
3、配置时钟
  
1433771-20181129153601155-276127748.png
( m) h% P8 u% F
4、配置ADC设置
  `
1433771-20181129153907972-233216619.png

/ U5 G/ h, v7 O8 E$ t
5、开启中断模式
  
1433771-20181129154113690-349965448.png

" S, _! x8 Q& O. R
6、串口配置默认即可
8 V' b6 I2 W6 Z
1433771-20181129154239218-469781284.png
  
7、定时器配置,定时器配置的是进入定时器中断的频率,定时时间可以根据这个频率换算出来,这里定时器的频率 = 72M / 72 /1000 =1000Hz,所以定时时间为 T = 1S/f = 1S/1000 = 1ms,所以我这里配置定时为1ms。
" d7 [; i* ^; f% a
1433771-20181129154729430-591115694.png
  
8、基本配置我们完成了,现在我们生成工程用KEIL5打开

1 F. L, j- X6 x* c
1433771-20181129154852211-1292340288.png
  
9、打开工程,我们现在进入代码部分
  这里我们只需要重写定时器中断回调函数跟,ADC转换回调中断函数即可。在main文件里添加这下面这两个函数

- n' A( G4 H' O! X7 D7 \1 a
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
    3 P4 c7 h  c( `; C  ~
  2. {' f* n( @3 @" G1 R5 J2 h
  3.     HAL_ADC_Start_IT(&hadc1); //定时器中断里面开启ADC中断转换,1ms开启一次采集   
    , I3 E3 e/ f/ @8 F7 k
  4. }* J& S! E  C9 p- }) x2 O
  5. " r' T0 N6 V. z" R9 q
  6. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)    //ADC转换完成回调
    ) p! P# U( Y3 U4 T
  7. {! a. U& e) Z* n7 L2 G& R/ y3 L
  8.     HAL_ADC_Stop_IT(&hadc1);        //关闭ADC5 A1 b- P) X5 ]0 Z6 @  }/ m5 J
  9.     HAL_TIM_Base_Stop_IT(&htim3);    //关闭定时器
    4 y$ U$ T$ h3 ~9 D& F! H+ O
  10.     AD_Value=HAL_ADC_GetValue(&hadc1);  //获取ADC转换的值
    , Q2 E, y% \2 g. f7 @0 s; ]% i
  11.     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
  12.     printf("%.4f\r\n",Value_2[j-10000]);     //串口打印信息' ~  o+ a1 j" ]3 Y
  13.     HAL_TIM_Base_Start_IT(&htim3);       //开启定时器
    # m$ k* J8 x2 C/ g
  14. }
复制代码
  到这里就完成单通道ADC中断转换的所有步骤啦,通过串口助手实测转换结果误差为0.0008v。
$ g" k0 y% i" n3 L. r- i
1 O2 [' C; U3 B* Z
不使用中断模式
  不使用中断模式的情况下跟使用中断的类似的,首先配置的过程中不需要开启中断,至于定时器开不开看个人需要,想利用定时器定时采集的可以开,不想的不用开,其他的配置一样。生成代码后,在main文件的main函数中的while循环里添加下面代码:
  1. /* USER CODE BEGIN 3 */6 i9 d3 _8 o4 K5 p( o8 w. @& _
  2.         for(char n=0;n<22;n++)
    # I) p) K9 o& e$ X0 S
  3.         {  //取22个值做滤波用0 E* f* v& X+ w: K% I5 m0 h# C' r
  4.             HAL_ADC_Start(&hadc2);' g; o/ E6 d- s" ^9 O
  5.             HAL_ADC_PollForConversion(&hadc2, 10);    //等待转换完成,第二个参数表示超时时间,单位ms        
    * s! a8 Z) q0 f3 T* H2 P6 |! Q
  6.             if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))$ r) \' I# a4 Z# x. i
  7.             {
    / m8 R+ U$ G# P+ ^
  8.                 Value[n]=HAL_ADC_GetValue(&hadc2);; j5 ?3 B5 Z" c+ ], V. n: @
  9.                 AD_Value += Value[n];
    7 R- c' [1 F1 D+ ?2 a9 K1 S' K; f
  10.             }                . n- [. a: g4 G) ?! z
  11.         }
    7 L- Y- V9 M" A3 m# M/ M% X
  12.         max=Value[0];
    2 E; i$ q; ~6 w) }2 T' _6 v
  13.         min=Value[0];- e$ v2 d' i, y% E" }
  14.         for(char n=0;n<22;n++)//取最大值、最小值. ^1 z$ j6 m) W: b4 w( g; z4 M/ @
  15.         {( ]7 Z: {( U: v) [, J
  16.             max=(Value[n]<max)?max:Value[n];   
    8 w4 |0 F; m' R( l3 z! B8 r
  17.             min=(min<Value[n])?min:Value[n];
      z& }" {/ `1 y7 k. W% \" U
  18.         }   
    + i$ L8 Z0 t& L# }+ R% @2 o& _
  19.         printf("PC0 ADC : %.4f \r\n",(float)((AD_Value -max-min)/20)*(3.1/4096));        & A. j# v1 U- T0 k
  20.         AD_tr=(float)((AD_Value -max-min)/20)*(3.1/4096);    //这里我做了个去掉最大最小值后,取均值的软件滤波   
    * N) h# ]) }& t
  21.         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初始化函数后面校准
  1. 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文件,在这个地方添加代码
  1. /[i] USER CODE BEGIN 0 [/i]/
    2 Z3 D( q6 L% X( Z: o
  2. __IO uint16_t uhADCxConvertedValue = 0;
    3 v* T) Q& L5 w9 d) }& z
  3. /[i] USER CODE END 0 [/i]/
复制代码
% S1 p: B0 u8 q
在main()函数里添加
  1.   /[i] USER CODE BEGIN 2 [/i]/
    / D1 E- O' ?1 G* H
  2. HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1);- y0 U; Q6 {+ {0 v

  3. 1 X. l# g7 B8 @' ]
  4.   /[i] USER CODE END 2 [/i]/
复制代码

+ P' n2 z3 U( H
意思是开启dma传输,传送一个字的数据到uhADCxConvertedValue这个变量里面: g: K9 C/ k$ |* v6 p
然后再文件的末尾处添加
  1. /[i] USER CODE BEGIN 4 [/i]/
    . a! z  n) K; z5 ^$ [9 I
  2. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle): b7 F/ i5 b* s% P. u# A
  3. {
    1 }$ P( t1 I  O* F9 X
  4.   /[i] Turn LED1 on: Transfer process is correct [/i]/
    + s  r$ y2 m, g6 K( T  `6 b9 A
  5. // BSP_LED_On(LED1);) X" Q- D1 F. P- m
  6.     HAL_GPIO_WritePin (GPIOF,GPIO_PIN_6,GPIO_PIN_SET );
    : V, r8 n8 p6 o0 Q3 s
  7. }9 b0 }6 l7 L7 E" D% B; m
  8. /[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
收藏 评论0 发布时间:2022-2-9 22:16

举报

0个回答

所属标签

相似技术帖

官网相关资源

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