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

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

+ o- S0 h* N! S3 h# V
2、开定时跟串口,定时器用来定时打开ADC转换,这样可以达到1S内控制ADC转换次数的目的,不过有个限制,这里样子控制ADC转换次数的话,如果采样次数多,配置ADC采样速度时一定要够  快,正常配置ADC的采样频率可以通过改变其采样速度来设置的,这里我是为了方便处理,就直接用定时器去开启了;而串口则是打印转换后的电压用的。
5 l8 s, K9 `7 G
1433771-20181129153526549-874517231.png
  
3、配置时钟
  
1433771-20181129153601155-276127748.png
9 |" V5 W+ x( M. z: P6 ?6 l9 {+ E" p
4、配置ADC设置
  `
1433771-20181129153907972-233216619.png
' j. P; N3 @( x) c& u) K
5、开启中断模式
  
1433771-20181129154113690-349965448.png
0 {1 Z7 A! W: ~! J
6、串口配置默认即可

: Y; n) ]5 a% A9 E7 A7 |
1433771-20181129154239218-469781284.png
  
7、定时器配置,定时器配置的是进入定时器中断的频率,定时时间可以根据这个频率换算出来,这里定时器的频率 = 72M / 72 /1000 =1000Hz,所以定时时间为 T = 1S/f = 1S/1000 = 1ms,所以我这里配置定时为1ms。
9 F( X) E/ q6 X) H5 T
1433771-20181129154729430-591115694.png
  
8、基本配置我们完成了,现在我们生成工程用KEIL5打开
& o) Q+ F: [. N! J) l# e; p
1433771-20181129154852211-1292340288.png
  
9、打开工程,我们现在进入代码部分
  这里我们只需要重写定时器中断回调函数跟,ADC转换回调中断函数即可。在main文件里添加这下面这两个函数

( W5 \. i: C- q0 w; E2 W
  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
    ( h& s2 W& I* \5 y% K7 H5 f
  2. {
    + p" \) t: J3 r; m- G$ r+ S- W
  3.     HAL_ADC_Start_IT(&hadc1); //定时器中断里面开启ADC中断转换,1ms开启一次采集   
    + e' T- H. m( o! f9 `( v
  4. }8 A! }. }. R9 W1 w  v# X6 z

  5. 7 T# D0 W6 {# Z1 b, S/ y# U" z
  6. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)    //ADC转换完成回调6 J* w# K8 M4 Q$ Q4 Q
  7. {2 n0 ^' Z1 S& ]" {
  8.     HAL_ADC_Stop_IT(&hadc1);        //关闭ADC( O& C+ e8 h' w7 i4 m
  9.     HAL_TIM_Base_Stop_IT(&htim3);    //关闭定时器
    $ H" d% j/ r8 d+ ]# J8 i5 B
  10.     AD_Value=HAL_ADC_GetValue(&hadc1);  //获取ADC转换的值
    * ~8 }' j7 X, Q% G- j$ g: {
  11.     Value_1=(float)(AD_Value*3.3/4096);     //ADC换算,这里参考电压3.3V,12位的ADC满量程为2^12=4096,转换出来的单位是V6 F$ \. l+ |) {  z; @. m  p  q
  12.     printf("%.4f\r\n",Value_2[j-10000]);     //串口打印信息
    1 ^1 ?. m6 e, E! e
  13.     HAL_TIM_Base_Start_IT(&htim3);       //开启定时器: V, w" \7 [1 s9 ^8 \
  14. }
复制代码
  到这里就完成单通道ADC中断转换的所有步骤啦,通过串口助手实测转换结果误差为0.0008v。6 N, ^9 r9 h) f
% X( l$ `. ?9 s( i4 y3 D" }/ W
不使用中断模式
  不使用中断模式的情况下跟使用中断的类似的,首先配置的过程中不需要开启中断,至于定时器开不开看个人需要,想利用定时器定时采集的可以开,不想的不用开,其他的配置一样。生成代码后,在main文件的main函数中的while循环里添加下面代码:
  1. /* USER CODE BEGIN 3 */9 _' g2 B. e2 f+ J9 _* Q
  2.         for(char n=0;n<22;n++) ! J$ |  Y) M# c
  3.         {  //取22个值做滤波用9 m; V2 W/ ~. E
  4.             HAL_ADC_Start(&hadc2);4 C- R7 k* w4 V) }& O  K
  5.             HAL_ADC_PollForConversion(&hadc2, 10);    //等待转换完成,第二个参数表示超时时间,单位ms        
    & ?% ], d" U; |8 H8 i+ [- A+ v
  6.             if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))) f% {: L$ @8 t4 H$ G' }
  7.             {
    1 h. o8 k9 c! b: t
  8.                 Value[n]=HAL_ADC_GetValue(&hadc2);
    # h3 M4 d* ]1 H/ r, C5 d" C' M
  9.                 AD_Value += Value[n];
    ' Y7 S+ j0 _$ c* C' I# Z& V
  10.             }                & a6 J# I' P- M3 Q4 J
  11.         }6 Y% |: b% z4 P, [. ], L
  12.         max=Value[0];7 B) e) d  ]1 \# W  \3 E2 g
  13.         min=Value[0];% E2 G6 H& w0 \2 K% n$ p
  14.         for(char n=0;n<22;n++)//取最大值、最小值- m% }4 S8 Y9 i) H9 C
  15.         {
    8 l6 e% z& c' p$ a2 ]3 X7 y5 E
  16.             max=(Value[n]<max)?max:Value[n];    0 [# x4 ~0 L! n# [
  17.             min=(min<Value[n])?min:Value[n];
    / X6 Q$ U' z; }1 a+ X) q; e
  18.         }    9 ^4 X9 i( W" M3 I1 h
  19.         printf("PC0 ADC : %.4f \r\n",(float)((AD_Value -max-min)/20)*(3.1/4096));        
    , g5 D1 w2 T: b& W
  20.         AD_tr=(float)((AD_Value -max-min)/20)*(3.1/4096);    //这里我做了个去掉最大最小值后,取均值的软件滤波   , v6 w' E! y) h# R( @8 h- m
  21.         AD_Value=0;
复制代码
/ b" s$ H/ P' q1 i0 [
  这里面的一些变量就你们自己去定义了,我就不列出来了,实测误差在0.001v以内。

- D4 Q, I+ [; |0 x: o1 k
补充注意事项:
  1、ADC初始化后要进行校准,使用下面函数校准,可以放在ADC初始化函数后面校准
  1. HAL_ADCEx_Calibration_Start(&hadc2);    //AD校准
复制代码
- ^; {  o7 H0 u2 a$ O1 @& f
  2、传入ADC的电压不可以超过3.3V,就是不可以超过你的参考电压,不然结果不准,还有可能烧坏ADC引脚
" T  R4 Q3 u: j7 ]- {! k
使用DMA模式
 再次写写stm32cubemx中AD采集的问题,这次不用while里面的查询,也不用中断采样了,直接用DMA, x/ a8 U! G+ k
先说下用DMA的好处:无论是中断采样还是查询采样,都需要在主程序中占用好多时间出来,嗯,你可以这样理解$ w) V- G5 T3 d4 C  @
那种采样都需要调用HAL_ADC_GetValue()这个函数,,,就是要取得转换后的值,中断还好点,要是查询的话,有可能会丢失数据啊. 用dma就可以避免了
0 h: b( L0 p% k/ U( `DMA用的事总线时间,无线cpu干预,额,这种说法貌似有点问题.管它呢
, }# N# A* @, J! \在AD转换结束的时候自动连接你准备存取的变量的地址,数据一步到位.额,省了多少事../ j+ H5 O1 P) J/ i
使用stm32cubemx对AD的配置
# n- g# W: d: ]
7 }, ^* m: O( Y( W' t7 I! Z* t
) v) w0 }# M, k) E5 [+ U& f

( }9 Z* U2 g& t4 c, A然后对她的DMA配置,并开启DMA的中断
3 G! d4 ]& B6 N8 \: x& R
9 s$ j$ Y! f" @8 g2 }% ]+ B
然后生成代码吧
4 g0 F, h1 B; o7 T* h, m打开main.c文件,在这个地方添加代码
  1. /[i] USER CODE BEGIN 0 [/i]/
      u' i6 r: T, X! ~1 B" W
  2. __IO uint16_t uhADCxConvertedValue = 0;
    % E+ |, v% d1 j
  3. /[i] USER CODE END 0 [/i]/
复制代码

, ~* M  R& f3 Q
在main()函数里添加
  1.   /[i] USER CODE BEGIN 2 [/i]/& B; }8 k/ d/ o
  2. HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1);
    ) J2 B2 G: b9 j
  3. ' }+ Y: z, s! {! B: k) a; U& i+ e% U
  4.   /[i] USER CODE END 2 [/i]/
复制代码
6 ?% s4 S. T: J9 `6 `
意思是开启dma传输,传送一个字的数据到uhADCxConvertedValue这个变量里面
8 }) P: L5 Q! G; L然后再文件的末尾处添加
  1. /[i] USER CODE BEGIN 4 [/i]/
      P6 n. A8 Z/ J
  2. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
    ) W5 T) J" y7 Q2 t* S
  3. {3 R: [* U+ D! B; b. F7 d
  4.   /[i] Turn LED1 on: Transfer process is correct [/i]/0 ]  m1 J( p- c& J- u7 q" c
  5. // BSP_LED_On(LED1);6 \1 S( ?; K) `; t7 \' d
  6.     HAL_GPIO_WritePin (GPIOF,GPIO_PIN_6,GPIO_PIN_SET );
    * _. J; q4 V! _7 V- x# ]! M( x2 ]! h
  7. }
    5 T3 M% c; `5 [- C. }! s( \
  8. /[i] USER CODE END 4 [/i]/
复制代码

5 M4 }3 Q% S  p1 a. W( v+ Y
意思是AD转换完成调用这个函数,函数里使能led+ x9 b5 C* S$ E
也许,你会问,为毛是HAL_ADC_ConvCpltCallback()这个函数啊,这个函数不是当开启AD的中断的时候才调用的吗?7 H! M3 |; F' F- f
嗯,对,这个函数是这样的,但是你仔细去分析下开启AD的DMA中断函数里面,就会发现这个函数也在啊
" K( V; s! N: ]8 c' @' F8 M, j如下图.进入HAL_ADC_Start_DMA函数里面,看到
4 q- x: M9 X6 O% A& g
, c! ~" y, c! l) [- D9 y

6 ]: d9 M6 ~: S4 j4 G) ^
/ Z. |6 H  C. b! h
在进入到图中的ADC_DMAConvCplt函数里面看到
! v# }& f" o% L
& ]6 M9 e4 s' ~& }  @8 B) O

/ f: y* F  R3 u& S6 s

4 E. v% [$ x- H; C% S0 X: w& _OK,疑问解决,# t: i: n( B/ k- V0 p
以后用到AD就可以直接调用这个CALL了,不要纠结了.

5 ]9 U: h! Q9 Z7 @
收藏 评论0 发布时间:2022-2-9 22:16

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版