4 a# G% a r" g4 c2 l9 m# D y
有人使用STM32F4系列开发产品,程序运行过程中需要不时地对外输出一串驱动脉冲,并要求这几串脉冲的频率可变、占空比固定。他想到使用基于STM32定时器的DMA BURST传输。具体点说,他期望不时地通过TIM3的CH1输出一串频率可变、占空比固定的脉冲然后停下来。这个思路在原理上是没问题的,可是他在测试过程中发现怎么也折腾不出预期的效果。
3 N, d5 D4 v' q* o0 v, G8 D
他目前使用的芯片是STM32F401,虽有点老旧,但我查看了手册,确认该芯片的TIM3是支持基于TIMER事件实现TIME寄存器与内存间的DMA BURST传输的。即每个TIMER事件可以申请多个DMA请求从而实现定时器寄存器与内存间的批量数据传输。要知某个STM32 TIMER是否支持上述功能,只需查看STM32参考手册的相关定时器的寄存器中有无TIMx_DCR和TIMx_DMAR寄存器的介绍。 % d" p0 N$ W! ^+ T" a* _+ y" N& Q2 M
/ L) M1 Z: ?) B3 C既然支持,为什么实现不了呢?关于这个功能我们还需要注意几点: 1、所选择的TIMER必须支持基于定时器事件的DMA BURST传输功能。 2、触发事件必须是来自参与DMA传输的定时器事件,不能是别的定时器事件。比方说你想实现TIM1的寄存器与内存间的DMA BURST传输,触发事件不能是来自TIM2、TIM3这些非TIM1的事件。 3、定时器DMA Burst传输时,用来被BURST访问的定时器寄存器应该是同一定时器的而且是地址连续的寄存器,不可跳跃访问。比方说像下面某TIMER的4个比较寄存器物理地址是安排在一起的,而CH2恰好因为某些原因没有被用上。 , ]( ]" d. ]3 {) U6 h# @* d* V
! U7 h3 [4 y1 L- ?
如果你仅对CH1/CH3/CH4三个通道的比较寄存器的值做BURST访问,此时尽管CH2没有被用上,BURST访问的传输个数应该是4而不是3。 4、STM32 HAL库里的例程或函数重点在演示相应的功能或特性,但它不能包罗万象或保证适用于任何场景。有些时候我们可能需要在现有库函数的前提下适当地做些调整来满足需求。
0 U! R6 }5 e/ K- i- V5 Z
结合这几点,我们一起排查下。第1点、第2点已经核对过了,没问题。看看第3点,即设置BURST传输个数。下图是F40x系列TIM2~TIM5的内存地址映射图。 - j9 O+ Z7 H0 G% Q
2 J: r9 ~ B9 `/ d( V3 m! c+ `( r+ [2 `' h+ U7 B
现在使用者真正需要用到的寄存器只有TIM3->ARR和TIM3->CCR1两个寄存器,但二者中间还有个预留空位【其它高级定时器的RCR寄存器的位置】也必须算进来,即这里BURST传输个数应该是3而不是2。 ' u& w; T) y B6 z/ h
再看看上面提到的第4点要注意的地方。这点我就不过多解释了,ST提供的HAL库例程及相关函数只能实现1次BURST传输的功能,如果要实现多次BURST传输就得在其代码基础上做些调整,更多细节可以参考我之前分享的那篇《STM32定时器BURST传输介绍及示例》。不过,在那篇的演示例程里我使用的是DMA Circular模式,现在的用户则要使用DMA Normal模式。采用Normal模式和采用Circular模式基于现有HAL库函数组织代码还略有差异,若没处理好这点小差异,可能让你完全出不来想要的结果。
7 h* z: B- B! |, R4 s
下面我使用STM32G4芯片的TIM3-CH1演示上面用户的功能。每次输出5个脉冲,3次输出为一个循环,同一循环中的3次输出的频率各不相同,占空比一样。【为什么没用STM32F401芯片,是因为此时手上没有带该芯片的开发板。不过演示功能的配置及代码基本一样。】 " f: h7 ^* Q6 [0 `" B" d6 M
我使用CubeMx对TIM3进行配置,参考配置如下:
, @: R. L4 t! e / x- _8 W* K, e& L3 M7 ~2 H& D
开启TIM3基于更新事件的DMA传输功能并做相关配置。
# y$ V! |3 V1 ]& W# d8 {" K% Y
5 t* k( R2 \+ J0 q9 r& P2 T. P' ~$ c5 v( U1 k/ X5 @3 H
配置完成,生成初始化工程,添加用户代码。
$ i U- F6 [) u4 T* _
我准备了3串脉冲的ARR/CCR1的值,分别以数组PulseData1[]、PulseData2[]、PulseData3[]来存放,占空比都设置为50%。显然数组行中间的0值用于前面提到过的预留字,此处无实际作用。另外,每个数组中的第6行数据其实是关闭当前通道PWM输出,具体应用时注意结合所选择的PWM模式及极性选择。 ) C' t* l6 i4 F9 R$ I% z. {" M3 B
下面是参考用户代码。代码在手机模式下可左右滑动。 - /* USER CODE BEGIN PD */ K j4 F! }$ y9 P ^3 C
- #define ARR1 (10000U)) |( d5 r+ S& K5 i4 C F% i. v
- #define ARR2 (20000U), d2 ^) `" s# R5 Z- w8 ]7 A8 R
- #define ARR3 (40000U)4 l* D' o9 P9 I9 u0 e" G2 {1 b
- #define Count (18) //3 * 6
7 F2 l3 P" y; K# Y
) g, l1 O) p+ T6 j/ y/ m4 r- uint16_t PulseData1[]={
: }# r$ ?' v+ H% x; z( T( D6 k9 o - ARR1,0,ARR1*0.5,
5 p6 k @5 a9 u. X2 @ - ARR1,0,ARR1*0.5,( w, _ e* W, |& e% o" u! z
- ARR1,0,ARR1*0.5,
! d" _* `; l% U - ARR1,0,ARR1*0.5,* F/ a, \. a. z; m( c% x
- ARR1,0,ARR1*0.5,//4 f- P5 _9 [8 q" C4 Z
- ARR1,0,0};) C* C8 `0 e, W, x0 u6 ]1 T
6 }* h+ v( z$ O; J5 E- 6 f: F, p7 r5 [( p9 {
- uint16_t PulseData2[]={
$ s3 A/ | N" R% Y" n: z! m& q& A - ARR2,0,ARR2*0.5,4 d3 V; Y' p/ {% {; I4 h( Z8 f2 N
- ARR2,0,ARR2*0.5,
% R; @! ^( X5 X& `7 r3 ^) y; H2 y - ARR2,0,ARR2*0.5,
; O( f# b% Y) F5 G+ h Q - ARR2,0,ARR2*0.5,
- C7 X8 Y0 J2 i6 g: ~# u5 F - ARR2,0,ARR2*0.5,//. `% @9 [* j* i9 u# S
- ARR2,0,0,
* D' y" L) e2 Q r - };
r `3 R4 P" |" W3 e+ [( ~; x
* Q6 m( M0 ^9 f: d- uint16_t PulseData3[]={
2 g. {3 H7 N( ]- p - ARR3,0,ARR3*0.5,5 Z7 l1 y4 |; t7 \2 e. X2 r) A
- ARR3,0,ARR3*0.5,# Y) ?) M5 r% p' A9 R- [: L
- ARR3,0,ARR3*0.5,+ f% |8 [3 \! B
- ARR3,0,ARR3*0.5,
4 e7 w" T# e3 _9 K. T% I7 f7 m - ARR3,0,ARR3*0.5,//5
9 z! ~& q$ L2 m: _( F! J8 z0 N/ ` - ARR3,0,0,( p: }6 n' l$ Y: ^) D9 H
- };/ b- b B7 z: s! S( G. z7 m
- . @! b8 V2 Q! F9 n2 I9 A% g" a& P
- /* USER CODE END PD */
复制代码 X. n' d0 U) g# R
主循环测试代码如下: - int main(void)
/ {1 f1 ~1 u* b - {
5 J: l; w2 R# {! r - /* USER CODE BEGIN 1 */
8 w7 `1 \% K+ L6 C - 6 w& M- T2 V$ ~) d, l$ ~! e
- /* USER CODE END 1 */
% G2 R( q7 R; \# ?
: X, L. s4 e" L& t0 J- /* MCU Configuration--------------------------------------------------------*/
( H7 @2 k5 V2 A4 M' _1 O% W
6 A5 [: T- e1 e1 `( O- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */8 D& x; _: E$ u( a) l
- HAL_Init();. O; B# U: z. c- g7 Y8 T5 t
: m( e. u) S7 O: G! o- /* USER CODE BEGIN Init */
2 Y' L2 d ~- l; X: D
0 Z2 T% e* w6 z6 ~9 t- /* USER CODE END Init */
" N: Q& @4 C% R: q - 5 `; {" S; I; \; b# Q+ Z( I
- /* Configure the system clock */
' T0 k2 K0 m- l2 I% z k: m4 ]+ A - SystemClock_Config();
1 u7 Y; c+ R3 U9 R5 t J4 X( X; B
5 h( O# W5 h [4 |4 k# U+ Y3 ?2 l1 a) Y' Z- /* USER CODE BEGIN SysInit */$ |) N u. X) ]0 q: t# N* W% e
- & X) X1 @' }. {
- /* USER CODE END SysInit */! {1 k4 }7 b. g+ H1 K$ P: i0 C/ q0 l
4 P1 G: ^% z' U* B/ v$ m8 C2 `- /* Initialize all configured peripherals */# a% ~- p5 _ ^ \0 a c
- MX_GPIO_Init();; b( B, l: h$ j* F( B* a/ o# W, Y
- MX_DMA_Init();
. S' x+ k/ e" s. f+ T. w - MX_TIM3_Init();; U0 d. y+ H5 a8 } h6 j! X, t7 t
- /* USER CODE BEGIN 2 */
: k# F) m) c2 P - __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);: _3 Y. N" G2 ]1 G1 g6 V1 y5 o
- TIM_CCxChannelCmd(TIM3,TIM_CHANNEL_1,TIM_CCx_ENABLE);
( L# ]. C8 A0 l) r1 p6 x3 c0 ]: b m - /* USER CODE END 2 */
* v" ^7 b/ V6 |& p2 g; v
) N/ _( k% c2 l' |- /* Infinite loop */0 a8 u9 G1 V$ z3 s
- /* USER CODE BEGIN WHILE */ T5 e/ R# c Z# I6 P
- while (1)
5 ~/ V) p/ }2 Q0 P }- ]% N - {( l5 e! y' ?& s" ?
- /* USER CODE END WHILE */' {! m& F: [* y+ l( C- Z2 F- s5 ^
- . u+ S Q+ X1 E( Y* O9 x- \8 W
- /* USER CODE BEGIN 3 */# z+ {5 P1 y) @
- htim3.DMABurstState = HAL_DMA_BURST_STATE_READY;# K1 x L6 O2 M2 `0 b
- hdma_tim3_up.State = HAL_DMA_STATE_READY ;
/ r: J; `" ?4 J- z' ]8 o! d
+ d4 g8 z% z$ [- __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
4 @4 k% @+ ?% x
5 a: v$ }3 F3 l5 F1 K6 ~- HAL_TIM_DMABurst_MultiWriteStart(&htim3, TIM_DMABASE_ARR,TIM_DMA_UPDATE,(uint32_t *)PulseData1,\
* A2 ^( t6 O! W2 ~* P' P$ { - TIM_DMABURSTLENGTH_3TRANSFERS,Count);! S2 i) f9 S3 U+ |, |$ x
- TIM3->EGR = TIM_EGR_UG;. U8 W& o! X* W7 L
- __HAL_TIM_ENABLE(&htim3);5 F J7 W' [* j& Z/ D1 d, F
0 {% L0 ^- F, X# W* `- HAL_Delay(150); //Prepared for the next 5 Pulses! A- O6 @/ U& f+ E: q
- __HAL_TIM_DISABLE(&htim3);
4 I! z- O+ V/ a% K5 j4 [ - // HAL_DMA_Abort(&hdma_tim3_up);- D+ A- v1 [/ F# I' Q5 y. ?' p
- : w$ N7 S$ x Y4 T* |/ ]! S0 h$ a
- htim3.DMABurstState = HAL_DMA_BURST_STATE_READY;# \0 d" u) ]% A1 o# X5 C- `5 K
- hdma_tim3_up.State = HAL_DMA_STATE_READY ;6 A3 W% G( I0 ^2 y" s
- [2 o* Q* _- P
- __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);1 Y) e* Q, M% \+ P0 B9 w1 o+ ]7 |
- + [5 {+ p/ u+ W( h
- HAL_TIM_DMABurst_MultiWriteStart(&htim3, TIM_DMABASE_ARR,TIM_DMA_UPDATE,(uint32_t *)PulseData2,\
6 Q9 ?9 I4 b( M( `8 V - TIM_DMABURSTLENGTH_3TRANSFERS,Count);
3 e4 U; x. P, j; N$ M7 U - TIM3->EGR = TIM_EGR_UG;
' s5 I* ~7 B0 C" [( T5 V - __HAL_TIM_ENABLE(&htim3);
3 H" n1 [1 f4 l4 l, x& I
" S: e* q& ~# w( d! h3 b& B* L2 y5 Y8 u- HAL_Delay(150);//Prepared for the next 5 Pulses
+ r1 _2 ?) D+ T5 [: E7 P( g - __HAL_TIM_DISABLE(&htim3);4 h. j2 \7 j! S: i, W
- // HAL_DMA_Abort(&hdma_tim3_up);
! I0 W$ V- L: L! e
3 p% R/ q6 g% J K9 X: d1 X; p- htim3.DMABurstState = HAL_DMA_BURST_STATE_READY;' |6 m5 g/ g& O
- hdma_tim3_up.State = HAL_DMA_STATE_READY ;
4 {5 x! G' p+ h
5 ]/ C8 a% w: @4 J- __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
@8 e) g+ E0 b0 ? - ( e) m) c+ v/ Q c' v
- HAL_TIM_DMABurst_MultiWriteStart(&htim3, TIM_DMABASE_ARR,TIM_DMA_UPDATE,(uint32_t *)PulseData3,
+ Y% J! v4 ^# O5 c - TIM_DMABURSTLENGTH_3TRANSFERS,Count);
2 c2 y6 |: ^0 `% c2 P - TIM3->EGR = TIM_EGR_UG;
; I9 ^* L# f8 [% P - __HAL_TIM_ENABLE(&htim3);
4 B4 z% |9 U: ^! i9 r7 @5 Y0 W
1 G/ X* Y2 e9 ?& |* F3 l0 i& C- HAL_Delay(150);//Prepared for the next 5 Pulses
" K$ t, ]2 |8 I) O/ x6 M% t) C3 d - __HAL_TIM_DISABLE(&htim3);
4 T1 a+ o) n+ Z* X% v& y - // HAL_DMA_Abort(&hdma_tim3_up);8 I. P( s7 s: m! |
- & ]9 I# I+ N) T: Q9 ~' @" M
- }: D6 q" |2 a( p! [# @* p- K
- /* USER CODE END 3 */# T: g& B* \. a
- }
复制代码
+ u" Q [. Y4 ?0 q
编译、除错后,运行程序可以看到我所期望的结果。即我每隔一会儿就发出5个脉冲,3次为1个循环。测试代码都放在这里,供参考使用。这里不逐句解释了,具体使用时结合库代码来研究即可。
$ H3 l$ S. {7 m$ T6 m# q
: m: z8 S* h8 x. f. t) }2 u. @* R. X/ e! {1 [
这里的CNT_value连续记录TIM2计数器的值,这里为Analog量。 1 H/ U& D, a! c j
Level_PA8记录GPIOA_PIN8的电平情况,1或0两个值之一,为Bit量。
% a6 I% O! b+ J- B. w
Level_PA9跟Level_PA8是完全相同的数据类型,显示的是GPIOA_PIN9的电平。显然,逻辑分析仪配置里关于Level_PA9的显示算式的屏蔽数应该是0x00000200,右移位为9。我是在SYSTICK的毫秒中断里读取GPIOA->IDR的值即管脚电平到变量Level_PA8和Level_PA9的。【下图中Core Clock填写芯片支持的最高主频】
+ H' \& E: |& v9 d# H* E, O, G& a. [+ v! c$ C
# N$ b- Y' u5 G& X3 _
* t$ w6 F: k1 {! }0 x
|