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

【经验分享】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
0 W* m% ]5 f) O+ B" b$ n- v
2、开定时跟串口,定时器用来定时打开ADC转换,这样可以达到1S内控制ADC转换次数的目的,不过有个限制,这里样子控制ADC转换次数的话,如果采样次数多,配置ADC采样速度时一定要够  快,正常配置ADC的采样频率可以通过改变其采样速度来设置的,这里我是为了方便处理,就直接用定时器去开启了;而串口则是打印转换后的电压用的。

. o* L: U! ]; n$ l0 {( w/ C
1433771-20181129153526549-874517231.png
  
3、配置时钟
  
1433771-20181129153601155-276127748.png
. H1 ]! Q) y1 A
4、配置ADC设置
  `
1433771-20181129153907972-233216619.png

! b+ R5 Y/ Z7 ^4 |) ]* s9 O
5、开启中断模式
  
1433771-20181129154113690-349965448.png
5 j" `# ^) X# o* f; P4 @. M8 c
6、串口配置默认即可
) ]$ E' v" }* y/ W: ^/ N, O
1433771-20181129154239218-469781284.png
  
7、定时器配置,定时器配置的是进入定时器中断的频率,定时时间可以根据这个频率换算出来,这里定时器的频率 = 72M / 72 /1000 =1000Hz,所以定时时间为 T = 1S/f = 1S/1000 = 1ms,所以我这里配置定时为1ms。
3 r" b% f3 o9 l$ H5 r8 y3 f0 c1 ~
1433771-20181129154729430-591115694.png
  
8、基本配置我们完成了,现在我们生成工程用KEIL5打开
1 P1 [3 u! q' L0 ?  E, o1 Y& I
1433771-20181129154852211-1292340288.png
  
9、打开工程,我们现在进入代码部分
  这里我们只需要重写定时器中断回调函数跟,ADC转换回调中断函数即可。在main文件里添加这下面这两个函数
8 E) \5 i9 C! \* w
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
    + R0 R. d6 Q6 A$ t/ Y
  2. {
    ( M. h2 K5 l3 ^
  3.     HAL_ADC_Start_IT(&hadc1); //定时器中断里面开启ADC中断转换,1ms开启一次采集    ( I% v% b+ ^9 ~. W
  4. }  M+ W3 x0 Z- `
  5.   F. j; J' l) P' A( H& {: [
  6. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)    //ADC转换完成回调
    5 Y. Z% Y& h9 H, A
  7. {7 c2 E8 v2 ^3 `4 |; G3 Y, i
  8.     HAL_ADC_Stop_IT(&hadc1);        //关闭ADC0 e! P& B2 I& a9 d7 y  Q( X7 K) [/ ]1 k
  9.     HAL_TIM_Base_Stop_IT(&htim3);    //关闭定时器7 L$ T* n- S8 ]- @: Q
  10.     AD_Value=HAL_ADC_GetValue(&hadc1);  //获取ADC转换的值7 x. N2 a% Q5 I$ f0 V4 m
  11.     Value_1=(float)(AD_Value*3.3/4096);     //ADC换算,这里参考电压3.3V,12位的ADC满量程为2^12=4096,转换出来的单位是V
    $ T$ Q3 K" n  l, }: s8 q
  12.     printf("%.4f\r\n",Value_2[j-10000]);     //串口打印信息% K- q3 c5 j% T
  13.     HAL_TIM_Base_Start_IT(&htim3);       //开启定时器
    / H5 s7 x! u" F2 v
  14. }
复制代码
  到这里就完成单通道ADC中断转换的所有步骤啦,通过串口助手实测转换结果误差为0.0008v。& `( L$ B5 {/ t$ |% e
7 R2 }" m/ O& D& S; y& ]. {
不使用中断模式
  不使用中断模式的情况下跟使用中断的类似的,首先配置的过程中不需要开启中断,至于定时器开不开看个人需要,想利用定时器定时采集的可以开,不想的不用开,其他的配置一样。生成代码后,在main文件的main函数中的while循环里添加下面代码:
  1. /* USER CODE BEGIN 3 */0 i2 v' F# S+ S6 U& \1 Z
  2.         for(char n=0;n<22;n++)
    : Q% A# a8 p  [% y- y% T& r
  3.         {  //取22个值做滤波用2 L$ m2 x% c# z" s5 j% H  Z; S
  4.             HAL_ADC_Start(&hadc2);
    " l) R' u, h1 ]2 n# o
  5.             HAL_ADC_PollForConversion(&hadc2, 10);    //等待转换完成,第二个参数表示超时时间,单位ms        6 U7 d3 Z: K7 N3 p/ l- w/ j
  6.             if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))# R( \* X0 q2 T3 A3 r' C6 f9 p8 y
  7.             {5 r/ \) B0 M# ~2 z
  8.                 Value[n]=HAL_ADC_GetValue(&hadc2);/ Z1 I7 M) q  p1 z2 Y% ^, I; m# h
  9.                 AD_Value += Value[n];
      V, c& L- H- r0 |0 x. {2 v
  10.             }                6 O$ k% r* }$ h! Y5 k3 i
  11.         }' b" q7 `% D7 H
  12.         max=Value[0];
    ( d8 {3 Q5 g, ~6 i3 L0 s
  13.         min=Value[0];
    # k: g9 j0 Z/ \; y! o$ P8 B
  14.         for(char n=0;n<22;n++)//取最大值、最小值  }) J: T0 O+ @, ?
  15.         {5 o: P& ^% k' \0 _( A* E
  16.             max=(Value[n]<max)?max:Value[n];    0 u2 A% f" v# ^8 J  Q
  17.             min=(min<Value[n])?min:Value[n];
    3 E6 j* P% j% Y. |* D! ~
  18.         }    , q; V' \- ^, S( k
  19.         printf("PC0 ADC : %.4f \r\n",(float)((AD_Value -max-min)/20)*(3.1/4096));        
    6 H1 N( {% }; c+ R0 b+ Q
  20.         AD_tr=(float)((AD_Value -max-min)/20)*(3.1/4096);    //这里我做了个去掉最大最小值后,取均值的软件滤波   3 C- }7 G( ^; U3 W/ ]: \: y
  21.         AD_Value=0;
复制代码
4 X7 W% q) @) H/ s8 a
  这里面的一些变量就你们自己去定义了,我就不列出来了,实测误差在0.001v以内。
2 r. f3 j7 X- M0 \/ r' J) w; `1 K4 B
补充注意事项:
  1、ADC初始化后要进行校准,使用下面函数校准,可以放在ADC初始化函数后面校准
  1. HAL_ADCEx_Calibration_Start(&hadc2);    //AD校准
复制代码

& h+ k- P  d4 U) a5 O7 q$ L
  2、传入ADC的电压不可以超过3.3V,就是不可以超过你的参考电压,不然结果不准,还有可能烧坏ADC引脚

. q8 K" v& Q( S8 i0 k( i" Z% P0 T
使用DMA模式
 再次写写stm32cubemx中AD采集的问题,这次不用while里面的查询,也不用中断采样了,直接用DMA
" T; P+ b- L+ @. O先说下用DMA的好处:无论是中断采样还是查询采样,都需要在主程序中占用好多时间出来,嗯,你可以这样理解
( M8 |" ^3 }- e9 m( ~那种采样都需要调用HAL_ADC_GetValue()这个函数,,,就是要取得转换后的值,中断还好点,要是查询的话,有可能会丢失数据啊. 用dma就可以避免了
4 W! |4 W. c7 N4 p- }7 L) e3 w+ ~2 kDMA用的事总线时间,无线cpu干预,额,这种说法貌似有点问题.管它呢
) A8 B5 X5 {. ~在AD转换结束的时候自动连接你准备存取的变量的地址,数据一步到位.额,省了多少事..6 d: `) u$ {! i$ w' }
使用stm32cubemx对AD的配置7 b$ |- u6 e: O
3 D( m' C# c. A6 X
! t  y8 p/ g6 t5 t

* T/ v6 f% K/ E5 m) w/ A然后对她的DMA配置,并开启DMA的中断

  _$ ]+ m. G% z
2 h5 o3 D: C; i
然后生成代码吧) z- t9 G8 v0 v. ~3 S
打开main.c文件,在这个地方添加代码
  1. /[i] USER CODE BEGIN 0 [/i]/
    / w# Y. l! M- M. S7 X
  2. __IO uint16_t uhADCxConvertedValue = 0;
    ! I: I' m5 H2 j& c& h+ |- h/ {
  3. /[i] USER CODE END 0 [/i]/
复制代码
: {0 W% N& C9 \9 W
在main()函数里添加
  1.   /[i] USER CODE BEGIN 2 [/i]/
    7 R( R7 t0 `. [& m7 ]
  2. HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1);
    : T* M* R, y' n1 t! ?; h7 e

  3. 8 z( c/ S. i; E* w0 Y- B
  4.   /[i] USER CODE END 2 [/i]/
复制代码
' Q# r6 r. [. A% [: R
意思是开启dma传输,传送一个字的数据到uhADCxConvertedValue这个变量里面7 n+ @9 o! J' G; D) ^- W' A; B
然后再文件的末尾处添加
  1. /[i] USER CODE BEGIN 4 [/i]/
    0 B* }( t2 G; Y/ |8 D8 Z
  2. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)# u4 i7 f  M+ y7 ~, p" X
  3. {
    7 u: [/ [" M7 j$ D# z
  4.   /[i] Turn LED1 on: Transfer process is correct [/i]/
    # z; D2 B2 V$ a
  5. // BSP_LED_On(LED1);
    ; X, Z: F. _+ e
  6.     HAL_GPIO_WritePin (GPIOF,GPIO_PIN_6,GPIO_PIN_SET );
    & J+ i0 f! ^6 X& j8 _" [# Y
  7. }
    9 Z" N( o& }# O2 C7 _. J  S
  8. /[i] USER CODE END 4 [/i]/
复制代码

& Z% d! A/ K1 R# C' |
意思是AD转换完成调用这个函数,函数里使能led
6 g( i, }: E% T# @也许,你会问,为毛是HAL_ADC_ConvCpltCallback()这个函数啊,这个函数不是当开启AD的中断的时候才调用的吗?
: @6 m( r! ^6 ~. P5 f; b9 j嗯,对,这个函数是这样的,但是你仔细去分析下开启AD的DMA中断函数里面,就会发现这个函数也在啊  r8 W6 \, q- m% ]8 }, w0 G$ {+ H
如下图.进入HAL_ADC_Start_DMA函数里面,看到% W2 P8 M. Z) Y3 B% u
  ^: R: A8 A) Y
. m7 e2 r4 u- e" c/ k
) V9 B& s/ w0 Y! q1 A4 U$ Q# Y% C
在进入到图中的ADC_DMAConvCplt函数里面看到$ U' L2 R# \) s  J# t, Y- j

8 f. F3 @, Z2 I( O  Y' \
0 _" P6 ^8 j" D$ t- e7 `; \1 ?

  U* M% q: z* M9 h( K( Q# wOK,疑问解决,
' H4 ]- J* A) l* p以后用到AD就可以直接调用这个CALL了,不要纠结了.
( F" q6 L8 t" ^# ^" t# ~2 _5 S
赞 收藏 评论0 发布时间:2022-2-9 22:16

举报

0个回答

所属标签

相似技术帖

官网相关资源

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