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

【经验分享】STM32F0使用LL库实现DMA方式AD采集

[复制链接]
STMCU小助手 发布时间:2021-9-17 17:29
! _4 g3 U2 M) A; c

2 |+ q# j7 P# G7 S4 Q在本次项目中,限于空间要求我们选用了STM32F030F4作为控制芯片。这款MCU不但封装紧凑,而且自带的Flash空间也非常有限,所以我们选择了LL库实现。在本文中我们将介绍基于LL库的ADC的DMA采集方式。
* a4 n# |* z& c9 e5 _+ \8 ?8 s/ r2 A0 v8 G) B) M, v
5 m1 L5 w' {4 Y6 C# J& I) e3 O( G6 t
1、概述0 M, j( O# M- Y0 q( C" s
这次我们使用DMA方式实现对AD的采集,在遗忘我们使用HAL库和标准库都做过,这次我们使用LL库来实现。接下来我们简单了解一下STM32F030F4中的ADC和DMA。6 a2 b2 o' x- d8 M# y7 v9 m
6 }0 b. S" U9 E5 y% A
/ y$ ~' \: Q, o
首先看一看ADC,STM32F030F4是12位的ADC。它有多达19个多路复用通道,允许它测量来自16个外部和2个内部源的信号。各种通道的A/D转换可采用单通道、连续通道、扫描通道或不连续通道进行。ADC的结果存储在左对齐或右对齐的16位数据寄存器中。ADC结构图如下:
# Z1 M+ C& Z$ ?- Z- V7 ^8 S
14.png
这次我们只使用第1路外部输入。接下来说一说DMA,直接内存访问(DMA)用于在外设和内存以及内存到内存之间提供高速数据传输。DMA可以在没有任何CPU操作的情况下快速移动数据。这使CPU资源可以用于其他操作。STM32F030F4中的DMA控制器有5个通道,每个通道用于管理来自一个或多个外围设备的内存访问请求。它有一个仲裁器来处理DMA请求之间的优先级。DMA结构图如下:
( \- i6 M: V. n7 l' d! o; y
15.png
这次我们也使用DMA的第1通道。# p0 s. m/ `: ?7 n

! i4 R1 O2 T9 x! K6 p/ ^9 |% r8 O$ d

$ Y/ p2 o* w+ }2 b. h, I; C2、ADC配置
3 p' T- I' Z9 c) Y在使用之前我们需要对ADC和DMA的相关寄存器惊醒必要的配置,才能实现我们想要的功能。我们来看看ADC需要配置的寄存器。ADC需要注意的寄存器主要有两个:ADC控制寄存器(ADC_CR)和ADC配置寄存器1(ADC_CFGR1)。首先我们来说说ADC控制寄存器(ADC_CR),器结构如下:
* T0 s6 q5 I/ o6 @8 o1 y5 o
16.png
关于ADC控制寄存器(ADC_CR),有几个设置需要说明一下。! W) L/ `/ _. Z6 y# N/ S+ I

: {: N) ?- g* a9 _# v
5 ]& _" H4 O7 f$ S( ]! f
ADCAL:ADC校准,设置该位可以软件启动校准,校准完成硬件会复位掉这一位。需要注意的是只有ADC处于失能状态,软件对ADCAL的操作才是有效的。也就是说软件对ADCAL操作时,ADC控制寄存器(ADC_CR)必须是全复位状态,即ADCAL=0,ADSTART=0,ADSTP=0, ADDIS=0和 ADEN=0。
* {( c7 @% K( `' m. i% N* V% G
$ e7 F5 c0 X5 m5 q4 U( N
, c* w2 L( U+ N) b9 E0 a/ \
ADSTART: ADC启动转换命令。需要注意只有在ADC已启用,并且没有禁用ADC的挂起请求。也就是说ADEN=1和ADDIS=0时,软件对ADSTART的操作才有效。" J. ]" X' c9 w3 P! W

% R0 J) E; d. F& o) A+ Z
$ C* e2 |6 @+ x+ s' G( N  Q; t& N; F% V
ADEN: ADC使能命令。只有在ADC控制寄存器(ADC_CR)处于全复位状态,即ADCAL=0,ADSTART=0,ADSTP=0,ADDIS=0 和 ADEN=0下,软件对ADEN的操作才有效。这就有一个问题,如果你使用了ADCAL必须等校准完成,才能使能,否则无效。$ ]$ e9 s* {  |5 a5 v- j: M

; w- f; D! `! |' L/ g
$ o- s$ w; i( H3 r6 X/ g' ~
接下来我们看一看ADC配置寄存器1(ADC_CFGR1),其结构如下:
) M. t! \' c1 S# F
17.png
关于ADC配置寄存器1(ADC_CFGR1),我们需要关注:CONT(转换模式)、EXTEN[1:0](外部触发使能)、DMACFG(DMA访问配置)、DMAEN(DMA访问使能)。需要说明的是,这几个配置都必须在启动转换前完成配置,即配置时ADSTART=0。: R: g* w  i6 x- d0 K

1 u- P' C; Q( Q

$ J/ W0 R0 Q6 k8 i2 u3、DMA配置
' e* L4 g. B& {/ d2 t2 G, Z1 s配置了ADC还需要配置DMA才能实现我们的想法。关于DMA的配置我们主要说一下4个寄存器:DMA通道配置寄存器(DMA_CCRx)、DMA通道数据数量寄存器(DMA_CNDTRx)、DMA通道外设地址寄存器(DMA_CPARx)、DMA通道内存地址寄存器(DMA_CMARx)。, Z+ R4 m. N5 O" x5 q7 A

9 m: d5 s+ @( c! R: N
* b& M4 B4 [) ]0 u
首先,我们来看看DMA通道配置寄存器(DMA_CCRx),其结构如下:
0 h. A' `$ U' W7 v- n
18.png
对于DMA通道配置寄存器(DMA_CCRx),我们需要关注如下位:MSIZE[1:0](内存大小)、PSIZE[1:0] (外设大小)、MINC(内存的增加模式)、PINC(外设增加模式)、CIRC(循环模式)、DIR(数据传输方向)、EN(通道使能)。除通道使能外,其它均可通过初始化函数进行配置。: C( R2 J3 ?  |0 g* b9 o/ [+ K; D4 J( P
0 h: }" t: t  k9 E: ]9 X9 U
: v& F+ P& D" q7 _7 G  p9 m
接下来,我们来看看DMA通道数据数量寄存器(DMA_CNDTRx),其结构如下:0 O8 c+ Y% \4 K- A# w
19.png
其实DMA通道数据数量寄存器(DMA_CNDTRx)用于配置传送数据的个数,如果是往内存中写,就是内存缓冲区的大小,单位与配置寄存器中MSIZE和PSIZE有关。接下来,我们来看一看DMA通道外设地址寄存器(DMA_CPARx),其结构如下:
7 t$ T' ^! c. X
20.png
       对于DMA通道外设地址寄存器(DMA_CPARx),就是存储外设的地址,如果我们的外设是ADC,那就是ADC的地址。最后,我们来看一看DMA通道内存地址寄存器(DMA_CMARx),其结构如下:
5 ]  }+ i1 K5 y* C4 C+ s: D6 g
21.png
       对于DMA通道内存地址寄存器(DMA_CMARx),其存储的就是对应的变量在内存中的地址,就是我们开辟的数据缓存区的首地址。: B5 x' S8 f& R4 C
. L& x0 J7 E" B' {2 y4 C1 g6 d. C

% y, g4 v5 Z5 u. f4 W5 j4、软件实现; P1 g2 `  y9 K5 ^/ ]$ e8 l. c5 d  r* j5 F
我们已经说明了ADC和DMA的配置,在这一小节,我们将根据我们前面的分析实现代码。首先来实现ADC的配置代码。
3 e' a7 ~: t) D9 }$ S+ G
  1. /* ADC 初始化配置 */; b3 m" O8 H1 _- l7 L

  2. % J& [' i6 W$ k! c* M5 M  ~
  3. static void ADC_Init_Configuration(void)
    : @* R* i; i2 G3 g" ?; g% C
  4. , p8 A+ h7 w# V3 l) o) E% f
  5. {
    6 E/ v) w8 A  O$ \# ~- P6 N

  6. 4 I+ u0 q: I' _
  7. LL_ADC_InitTypeDef ADC_InitStruct = {0};
    9 ?0 J; c+ m8 Q$ U+ ?

  8. * x. [5 P/ f* P9 I, Z' M3 L" g9 U/ S6 _
  9. LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
    $ ~4 v) e) Z' o" H" M8 V6 l( W
  10. " m+ x" e# [/ r  I

  11. + ^5 S+ ]) N- m
  12. ' K0 A% ?+ n7 P% f! N8 L
  13. LL_GPIO_InitTypeDef GPIO_InitStruct = {0};, `3 ]3 e( S0 w; ?+ V2 \+ D& X
  14. " B5 x3 E  L/ J

  15. ; H' h* V7 H, r7 D" o/ ^, G

  16.   O$ y1 C( C$ M9 _$ n1 V# i
  17.   /* ADC相关外设时钟使能 */
    5 a& r& m9 o: G4 ?+ A! G% L

  18. : c1 x" Z9 u4 n! ~6 A
  19. LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_ADC1);4 @* i, E9 o% W: O. s+ g8 J
  20. / t* N- B" o% H; ~6 l
  21. LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
    . d* w, y* z4 u* S1 r

  22. * Y+ z/ x6 u( X6 x& A% X# s8 S0 Z) |
  23. % u+ ~. J% I9 U

  24. 6 I, E8 J# m4 k9 i# L$ E' x* k
  25.   /**ADCGPIO 配置:PA0   ------> ADC_IN0  */
    ( {# G% p. P* L, `" G

  26. 1 T/ K# H; v) ?) o+ ^! U
  27. GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
    " ^# w7 L  w& k. }: q1 a2 G
  28. ( y, C/ n9 {' j  @2 `) w( b
  29. GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;+ f4 |) r! f/ X; M2 O  o' {! p
  30.   y  d: ~1 d0 V- ?
  31. GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;* A/ A! p2 v8 ?  L6 B, l

  32. ; m) K, d  q0 J# C4 i) y# F
  33. LL_GPIO_Init(GPIOA, &GPIO_InitStruct);+ M5 Z- i( |# {: A5 g" u" Y

  34. % f2 o0 ~( t/ h' q; L5 Q
  35.   /* ADCDMA初始化 */' E0 H5 s, F' J

  36. ' o+ ]" a( a: ^9 ~; O; r& ?0 V
  37. LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1,LL_DMA_DIRECTION_PERIPH_TO_MEMORY);- W- n6 i- N2 `9 O. M# a$ d

  38. * q1 ^% |2 z; ~5 ]
  39.   LL_DMA_SetChannelPriorityLevel(DMA1,LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
    - j$ H9 t# z, }( W5 h, U# t
  40. 8 U, ~+ r8 r# n" D9 x" X

  41. 7 S, z4 H6 }% ^# G& K8 L. t
  42. ; Y7 M' w( x$ A2 L
  43. LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
    ( A$ g7 w9 [5 z2 }. `8 f

  44. ; A; a1 Z* o, P7 I( V6 `& l4 h
  45. LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1,LL_DMA_PERIPH_NOINCREMENT);6 r7 n9 ~2 u" c9 q
  46. ) D; i" z0 H* `! z' ]
  47. LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1,LL_DMA_MEMORY_INCREMENT);
    ! u; f( D! ?7 A* T( s4 q
  48. % _0 z' d/ U: x/ m/ U* q
  49. LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_WORD);
    ) W  e" K- J: s3 U# T. u- |
  50. / @# Q+ ?5 U2 j% L4 O. t
  51. LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_WORD);
      ^! n4 ~0 R# M' Y

  52. . h& {( i* ^' D+ t
  53. LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_1,ADBufferSize);
    " J  T3 z0 k0 t6 D

  54. ( @- o. f; B; X; e
  55. LL_DMA_SetPeriphAddress(DMA1,LL_DMA_CHANNEL_1,LL_ADC_DMA_GetRegAddr(ADC1,LL_ADC_DMA_REG_REGULAR_DATA));
    ' H; {! _8 [$ {  ~* y# ~
  56. " ]7 B" M) r# ]) q: u) i$ ~
  57. LL_DMA_SetMemoryAddress(DMA1,LL_DMA_CHANNEL_1,(uint32_t)ADC_ConvertedValue);
    $ }5 b/ w6 I  ~: h
  58. 1 R+ H% u0 }: W8 t

  59. 1 x/ c0 s2 _( n
  60. - e# T. X+ S" v, N  Z
  61.   LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);4 U" m6 [% @; Y/ x  H2 [$ {
  62. . H. ?' E9 ~1 Q6 D8 D5 d7 d

  63. 3 x$ G7 q% z1 g0 z: \. b
  64. : k; Q$ |+ O8 q5 _& e# w
  65.   /* 配置ADC通道 */
    , B4 x8 C1 K# ]7 ]

  66. - p% Y. `' x$ {4 [" Z* p
  67. LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_0);: r  _& x% d" {( M( n
  68. : r. A# n5 u( U" o8 X- |! C
  69. ) v- I  b7 O( g1 i) j- k2 V

  70. 1 ]/ l& o  V/ T/ D" ~
  71.   /* 配置ADC的全局特性:时钟、分辨率、数据对齐和转换次数 */( q: C6 @' w) ^2 y) R9 @2 o

  72. ; O3 m8 b! b2 }( T* Y7 M6 r/ ^& ]
  73. ADC_InitStruct.Clock = LL_ADC_CLOCK_ASYNC;
    8 m2 Q9 h0 d5 D3 u) |0 U$ q
  74. 5 N8 N; H" C$ t7 Z
  75. ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;# ~" h5 _4 c" ]. i: s. G
  76. ( D; v$ f4 B) o7 U) Y5 O
  77.   ADC_InitStruct.DataAlignment= LL_ADC_DATA_ALIGN_RIGHT;
    3 P; L# ^# e4 D
  78. 9 S- C8 f7 k7 x' g& f
  79. ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
    3 w! s& [! ?: Z9 @; ^! D
  80. & V8 y! |1 ~9 l
  81. LL_ADC_Init(ADC1, &ADC_InitStruct);
    ' o$ m, d, K' i. d

  82. . J! J+ Q. N7 h
  83. ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
    3 Y5 b- Y0 G0 R& R
  84. , I1 |/ ~; w. c- m" J& ?$ ^
  85. ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;8 U' D4 ^/ H8 r
  86. . G/ e3 f2 y% B( f8 Z' Q
  87. ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
    0 o6 H4 N( y' v( n* M$ C

  88. ! B5 l7 ?$ F* I8 y! Z/ {
  89. ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;( l( I9 o# I" H# q9 f4 z! v$ \
  90. 7 E: D  O$ m: s
  91. ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;, t( {) R: W9 I: J
  92. . c( Q/ v0 O+ [+ Z( o- t
  93. LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);8 ]" M8 m+ N4 ~& z7 W

  94. & J- B% {3 o2 d6 Z8 Q( Q
  95. LL_ADC_REG_SetSequencerScanDirection(ADC1,LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);
    ( {8 v& N$ ]3 c( _- `6 f* J

  96. ) k. t- J9 ?4 j- z9 {
  97. LL_ADC_SetSamplingTimeCommonChannels(ADC1,LL_ADC_SAMPLINGTIME_239CYCLES_5);, n; H, O8 F8 ~7 f0 |

  98. & C' p) T# c1 T& S, G" m) {3 Q
  99. LL_ADC_DisableIT_EOC(ADC1);# n! ~0 l4 W9 i6 I$ E) i

  100. 2 X4 D& T" E0 `" W+ }" Y7 Q
  101. LL_ADC_DisableIT_EOS(ADC1);
    ( C% Z; H. n# H/ D; s- x& p, T( D- J5 Q

  102. 6 H' q; o6 Q2 f
  103. LL_ADC_StartCalibration(ADC1);! @4 k) E; }; J- V
  104. : P& R! _2 T# Z. W
  105.   while(LL_ADC_IsCalibrationOnGoing(ADC1));! s1 u! F7 v% l8 d
  106. % x) S" Q# T4 Y
  107. LL_ADC_Enable(ADC1);; Z0 Q) N. X5 ^+ J8 Q
  108. ) i0 M6 D  A' ]! O2 T  j$ M5 y
  109. LL_ADC_REG_SetDMATransfer(ADC1,LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
    $ O) y  {8 t2 R- V  m; p, ^
  110. # ~# j0 t& x# @* ~4 ]4 k
  111. LL_ADC_REG_StartConversion(ADC1);
      m! R6 Y8 T6 Y9 Q* V
  112. + A- p( z4 z3 K! a
  113. }
    & [: Y. b. R% ?7 W  x

  114. - `6 D% o! n# X/ o
  115. 其实在ADC的初始化配置中也对DMA作了配置,但DMA还需要对始终和中断进行配置。6 J" Z3 v1 C; p4 }

  116. ' j" `4 U  r( }# V0 R( g! O
  117. /**  DMA 控制器初始化配置  */! d( l/ G. a; V' g
  118. ( S; T" b- h- ~; i+ V$ k% n
  119. static void DMA_Init_Configuration(void)
    ! t( z- b4 {1 g* L* {0 V$ E

  120. ; g5 V& g7 F: m* g! N( |
  121. {
    / B+ G- i3 j' P  y

  122. ! [. t9 W% j; H) x2 {- Q: z* M; c
  123.   /* DMA 控制器时钟使能 */
    % X4 F! d, R7 u. ]
  124. 9 A4 F3 A% H& y* [4 j
  125. LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);( X0 [3 b+ d7 I$ `
  126. : M" b# j1 s/ v, o+ p& T
  127.   /*DMA1_Channel1_IRQn中断配置 */
    ! @7 A- E. e6 w/ d: v% p

  128. # y5 X# C# W" E5 E
  129. NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
    ) X7 ~: w# K. Z. Y
  130. 0 Q9 T+ S7 u3 ]# U  h
  131. NVIC_EnableIRQ(DMA1_Channel1_IRQn);0 {# h( m  B4 q; x
  132. }
复制代码
配置后,ADC的寄存器如下:
" i% E+ s' k% {+ M# R9 X, n
22.png
配置后,DMA的寄存器如下:' K& q* Z! h* i! S
23.png
       其实,到这里ADC采集世纪上已经实现了,DMA已经将数据从ADC读出来存到了指定的内存区域,后续的处理就很简单了。. s# ~/ B' V5 Z3 I! F  ^9 ^

4 S. U8 b$ K% l( B& Q9 J

2 x( i/ D6 i1 f1 S" O( j5 K5、总结
( k0 b; |4 M4 a0 ^- I8 l我们已经实现了基于LL库使用DMA方式获取ADC的数据。下面我们就下载到目标设备并检测一下结果。测试结果如下:
6 V1 T" J6 e6 X
24.png
4 }- z, A6 v! G' ]! U! W! D' c
上图中,上部是计算完成的物理量值,下部则是DMA写到内存缓存区的ADC的原始码值。
# l- ?( u; L* c& r/ z
& p8 r) q$ j, _' D

5 L" I; T6 \8 ^* i
$ X! f# `" }7 z+ b

9 {) j5 }  ]/ ?
9 ^; J7 |; U+ ?0 I/ Q

$ h% \8 ]  n, d4 U. p) I
* X6 E/ A3 b1 @3 |$ f

# u) f, E# y) Y/ ^- @& j/ {$ ]* a& _9 B* y1 a- L
赞 收藏 评论0 发布时间:2021-9-17 17:29

举报

0个回答

所属标签

相似技术帖

官网相关资源

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