! _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这次我们只使用第1路外部输入。接下来说一说DMA,直接内存访问(DMA)用于在外设和内存以及内存到内存之间提供高速数据传输。DMA可以在没有任何CPU操作的情况下快速移动数据。这使CPU资源可以用于其他操作。STM32F030F4中的DMA控制器有5个通道,每个通道用于管理来自一个或多个外围设备的内存访问请求。它有一个仲裁器来处理DMA请求之间的优先级。DMA结构图如下:
( \- i6 M: V. n7 l' d! o; y这次我们也使用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关于ADC控制寄存器(ADC_CR),有几个设置需要说明一下。! W) L/ `/ _. Z6 y# N/ S+ I
: {: N) ?- g* a9 _# v5 ]& _" 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关于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对于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
其实DMA通道数据数量寄存器(DMA_CNDTRx)用于配置传送数据的个数,如果是往内存中写,就是内存缓冲区的大小,单位与配置寄存器中MSIZE和PSIZE有关。接下来,我们来看一看DMA通道外设地址寄存器(DMA_CPARx),其结构如下:
7 t$ T' ^! c. X 对于DMA通道外设地址寄存器(DMA_CPARx),就是存储外设的地址,如果我们的外设是ADC,那就是ADC的地址。最后,我们来看一看DMA通道内存地址寄存器(DMA_CMARx),其结构如下:
5 ] }+ i1 K5 y* C4 C+ s: D6 g 对于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- /* ADC 初始化配置 */; b3 m" O8 H1 _- l7 L
% J& [' i6 W$ k! c* M5 M ~- static void ADC_Init_Configuration(void)
: @* R* i; i2 G3 g" ?; g% C - , p8 A+ h7 w# V3 l) o) E% f
- {
6 E/ v) w8 A O$ \# ~- P6 N
4 I+ u0 q: I' _- LL_ADC_InitTypeDef ADC_InitStruct = {0};
9 ?0 J; c+ m8 Q$ U+ ?
* x. [5 P/ f* P9 I, Z' M3 L" g9 U/ S6 _- LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
$ ~4 v) e) Z' o" H" M8 V6 l( W - " m+ x" e# [/ r I
-
+ ^5 S+ ]) N- m - ' K0 A% ?+ n7 P% f! N8 L
- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};, `3 ]3 e( S0 w; ?+ V2 \+ D& X
- " B5 x3 E L/ J
-
; H' h* V7 H, r7 D" o/ ^, G
O$ y1 C( C$ M9 _$ n1 V# i- /* ADC相关外设时钟使能 */
5 a& r& m9 o: G4 ?+ A! G% L
: c1 x" Z9 u4 n! ~6 A- LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_ADC1);4 @* i, E9 o% W: O. s+ g8 J
- / t* N- B" o% H; ~6 l
- LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
. d* w, y* z4 u* S1 r
* Y+ z/ x6 u( X6 x& A% X# s8 S0 Z) |- % u+ ~. J% I9 U
6 I, E8 J# m4 k9 i# L$ E' x* k- /**ADCGPIO 配置:PA0 ------> ADC_IN0 */
( {# G% p. P* L, `" G
1 T/ K# H; v) ?) o+ ^! U- GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
" ^# w7 L w& k. }: q1 a2 G - ( y, C/ n9 {' j @2 `) w( b
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;+ f4 |) r! f/ X; M2 O o' {! p
- y d: ~1 d0 V- ?
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;* A/ A! p2 v8 ? L6 B, l
; m) K, d q0 J# C4 i) y# F- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);+ M5 Z- i( |# {: A5 g" u" Y
% f2 o0 ~( t/ h' q; L5 Q- /* ADCDMA初始化 */' E0 H5 s, F' J
' o+ ]" a( a: ^9 ~; O; r& ?0 V- LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1,LL_DMA_DIRECTION_PERIPH_TO_MEMORY);- W- n6 i- N2 `9 O. M# a$ d
* q1 ^% |2 z; ~5 ]- LL_DMA_SetChannelPriorityLevel(DMA1,LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
- j$ H9 t# z, }( W5 h, U# t - 8 U, ~+ r8 r# n" D9 x" X
-
7 S, z4 H6 }% ^# G& K8 L. t - ; Y7 M' w( x$ A2 L
- LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
( A$ g7 w9 [5 z2 }. `8 f
; A; a1 Z* o, P7 I( V6 `& l4 h- LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1,LL_DMA_PERIPH_NOINCREMENT);6 r7 n9 ~2 u" c9 q
- ) D; i" z0 H* `! z' ]
- LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1,LL_DMA_MEMORY_INCREMENT);
! u; f( D! ?7 A* T( s4 q - % _0 z' d/ U: x/ m/ U* q
- LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_WORD);
) W e" K- J: s3 U# T. u- | - / @# Q+ ?5 U2 j% L4 O. t
- LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_WORD);
^! n4 ~0 R# M' Y
. h& {( i* ^' D+ t- LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_1,ADBufferSize);
" J T3 z0 k0 t6 D
( @- o. f; B; X; e- LL_DMA_SetPeriphAddress(DMA1,LL_DMA_CHANNEL_1,LL_ADC_DMA_GetRegAddr(ADC1,LL_ADC_DMA_REG_REGULAR_DATA));
' H; {! _8 [$ { ~* y# ~ - " ]7 B" M) r# ]) q: u) i$ ~
- LL_DMA_SetMemoryAddress(DMA1,LL_DMA_CHANNEL_1,(uint32_t)ADC_ConvertedValue);
$ }5 b/ w6 I ~: h - 1 R+ H% u0 }: W8 t
-
1 x/ c0 s2 _( n - - e# T. X+ S" v, N Z
- LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);4 U" m6 [% @; Y/ x H2 [$ {
- . H. ?' E9 ~1 Q6 D8 D5 d7 d
-
3 x$ G7 q% z1 g0 z: \. b - : k; Q$ |+ O8 q5 _& e# w
- /* 配置ADC通道 */
, B4 x8 C1 K# ]7 ]
- p% Y. `' x$ {4 [" Z* p- LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_0);: r _& x% d" {( M( n
- : r. A# n5 u( U" o8 X- |! C
- ) v- I b7 O( g1 i) j- k2 V
1 ]/ l& o V/ T/ D" ~- /* 配置ADC的全局特性:时钟、分辨率、数据对齐和转换次数 */( q: C6 @' w) ^2 y) R9 @2 o
; O3 m8 b! b2 }( T* Y7 M6 r/ ^& ]- ADC_InitStruct.Clock = LL_ADC_CLOCK_ASYNC;
8 m2 Q9 h0 d5 D3 u) |0 U$ q - 5 N8 N; H" C$ t7 Z
- ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;# ~" h5 _4 c" ]. i: s. G
- ( D; v$ f4 B) o7 U) Y5 O
- ADC_InitStruct.DataAlignment= LL_ADC_DATA_ALIGN_RIGHT;
3 P; L# ^# e4 D - 9 S- C8 f7 k7 x' g& f
- ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
3 w! s& [! ?: Z9 @; ^! D - & V8 y! |1 ~9 l
- LL_ADC_Init(ADC1, &ADC_InitStruct);
' o$ m, d, K' i. d
. J! J+ Q. N7 h- ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
3 Y5 b- Y0 G0 R& R - , I1 |/ ~; w. c- m" J& ?$ ^
- ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;8 U' D4 ^/ H8 r
- . G/ e3 f2 y% B( f8 Z' Q
- ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
0 o6 H4 N( y' v( n* M$ C
! B5 l7 ?$ F* I8 y! Z/ {- ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;( l( I9 o# I" H# q9 f4 z! v$ \
- 7 E: D O$ m: s
- ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;, t( {) R: W9 I: J
- . c( Q/ v0 O+ [+ Z( o- t
- LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);8 ]" M8 m+ N4 ~& z7 W
& J- B% {3 o2 d6 Z8 Q( Q- LL_ADC_REG_SetSequencerScanDirection(ADC1,LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);
( {8 v& N$ ]3 c( _- `6 f* J
) k. t- J9 ?4 j- z9 {- LL_ADC_SetSamplingTimeCommonChannels(ADC1,LL_ADC_SAMPLINGTIME_239CYCLES_5);, n; H, O8 F8 ~7 f0 |
& C' p) T# c1 T& S, G" m) {3 Q- LL_ADC_DisableIT_EOC(ADC1);# n! ~0 l4 W9 i6 I$ E) i
2 X4 D& T" E0 `" W+ }" Y7 Q- LL_ADC_DisableIT_EOS(ADC1);
( C% Z; H. n# H/ D; s- x& p, T( D- J5 Q
6 H' q; o6 Q2 f- LL_ADC_StartCalibration(ADC1);! @4 k) E; }; J- V
- : P& R! _2 T# Z. W
- while(LL_ADC_IsCalibrationOnGoing(ADC1));! s1 u! F7 v% l8 d
- % x) S" Q# T4 Y
- LL_ADC_Enable(ADC1);; Z0 Q) N. X5 ^+ J8 Q
- ) i0 M6 D A' ]! O2 T j$ M5 y
- LL_ADC_REG_SetDMATransfer(ADC1,LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
$ O) y {8 t2 R- V m; p, ^ - # ~# j0 t& x# @* ~4 ]4 k
- LL_ADC_REG_StartConversion(ADC1);
m! R6 Y8 T6 Y9 Q* V - + A- p( z4 z3 K! a
- }
& [: Y. b. R% ?7 W x
- `6 D% o! n# X/ o- 其实在ADC的初始化配置中也对DMA作了配置,但DMA还需要对始终和中断进行配置。6 J" Z3 v1 C; p4 }
' j" `4 U r( }# V0 R( g! O- /** DMA 控制器初始化配置 */! d( l/ G. a; V' g
- ( S; T" b- h- ~; i+ V$ k% n
- static void DMA_Init_Configuration(void)
! t( z- b4 {1 g* L* {0 V$ E
; g5 V& g7 F: m* g! N( |- {
/ B+ G- i3 j' P y
! [. t9 W% j; H) x2 {- Q: z* M; c- /* DMA 控制器时钟使能 */
% X4 F! d, R7 u. ] - 9 A4 F3 A% H& y* [4 j
- LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);( X0 [3 b+ d7 I$ `
- : M" b# j1 s/ v, o+ p& T
- /*DMA1_Channel1_IRQn中断配置 */
! @7 A- E. e6 w/ d: v% p
# y5 X# C# W" E5 E- NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
) X7 ~: w# K. Z. Y - 0 Q9 T+ S7 u3 ]# U h
- NVIC_EnableIRQ(DMA1_Channel1_IRQn);0 {# h( m B4 q; x
- }
复制代码 配置后,ADC的寄存器如下:
" i% E+ s' k% {+ M# R9 X, n配置后,DMA的寄存器如下:' K& q* Z! h* i! S
其实,到这里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 X4 }- 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
|