01、前言
$ B% n/ E6 T/ G. I" \2 M在之前的文章在《STM32延时函数的四种方法》使用定时器延时,在《如何测量代码运行时间》中提到使用定时器外设计算代码运行时间。文中提到这种方法的明显缺点就是需要占用一个定时器,一些MCU在特定应用场景下定时器外设资源是十分稀缺的。在留言区有位大佬提到可以使用DWT,我就研究了一番。
3 E9 w$ N' _; S% N! O' r6 x- m S; l5 i$ J3 q' q
- h% o( _3 M1 c( D1 w" b& r7 B
4 D- j* O# k' \& p6 J02、DWT
; L. `( K/ {% @4 g. i0 r在Cortex-M里面有一个外设叫DWT(DataWatchpoint andTrace),是用于系统调试及跟踪,DWT的中文名字应该是:数据观察点触发。在STM32用户手册的第32章节Debugsupport (DBG)有如下框图。$ M! }& B" `7 T0 b3 L
( G" N3 V% d7 m
+ j$ g8 Z, @8 t" m+ `6 j( s
; \0 u, P$ O& K& _明显DWT属于DBG部分的功能,从上图的标题可以看出DWT属于CortexM3内核的,理论上M3内核的MCU都支持的,这个下文会说明。在这里我将其称之为“隐藏的定时器”,因为他可以代替定时器外设实现上文提到延时功能和测量代码运行时间的功能,DWT不能代替定时器的其他功能。, m5 { X& ^( b+ o$ Z/ D; Q+ D7 C
' ]! v! G& z) u( f5 e2 Z' y之所以DWT可以实现延时功能,因为它有一个32的计数器CYCCNT,这是一个向上计数的计数器,当它溢出时会自动清零并重新开始向上计数,它的频率就是内核的主频。简单点说,就是内核时钟跳动一下,CYCCNT计数器就加1。
% M" f0 O% U9 y7 @5 t5 A
9 d2 B, [8 q2 I, K( m7 J. m5 y很明显DWT计数器的精度和系统主频有关,我们常用的STM32F103主频一般为72Mhz,STM32F207一般为120Mhz,STM32H7主频一般为400Mhz。以为主频最低为72Mhz的STM32F103为例,精度是1/72M= 14ns,这个精度足以满足大部分延时函数的需求,同样程序的运行时间都是微秒级别的,远远满足测量代码运行时间的要求。
# ?5 w3 g# }! Y' ]. r$ X% N7 C/ `- O3 }1 b$ \5 y$ }
03、DWT的配置, W D; x2 g$ m8 F0 j! {) c! E( j
首选使用DWT前必须使能DBG的系统跟踪,控制使能位在DEMCR寄存器的bit24。注意该寄存器详细说明在STM32的用户手册上查不到,需要在CortexM3内核手册查到,在《Cortex-M3权威指南》书中也可以查到。
6 k2 B8 ]" u" c2 m+ d
8 E8 u4 O H* @+ w% [0 L
8 |$ h% F7 a" k! y2 H5 L# H* a: x8 ~; O. E
在使能CYCCNT计数器前,必须先将其清零。下图是从ARM的官方手册《Cortex-M3Technical Reference Manual》中查到的。/ v, Y9 G) J+ L
j: [) B h) ]9 F- G+ n
: y' i. |0 U$ l) s
) f! t( Z* N+ J* \1 Z4 j, l8 F使能CYCCNT计数器,其控制位是DWT控制寄存器的第一位,写1使能,则启用CYCCNT计数器,否则CYCCNT计数器将不会工作。
1 J4 U8 z+ v$ E) m) S, ~% {; T7 ?9 F
0 R2 X: d+ l/ K S/ _1 b/ ` [5 T0 x; _+ D) J/ t
总结一下:& P8 E) \- {1 ^& ^% p: L
3 r) H( B8 d I$ K( s0 p, a }
a.先使能DWT外设,由内核调试寄存器DEM_CR的位24控制,写1使能。( Z) |# O/ N5 p9 y( X/ H4 ^! ~
. H8 m- I" L3 x: E. z0 Zb.使能CYCCNT寄存器之前,先清0。
! Z7 `3 Q2 O! Y# ?" j) r3 Z/ }# m7 C! R, V* `( R6 n
c.使能CYCCNT寄存器,由DWT_CTRL的位0控制,写1使能。
! m t: `. F" G' S T; v7 g
5 A% s6 ?+ g: Y% K3 a代码如下
( b" {5 B ~/ b) y& l. f3 {
3 v: D3 D% V: b4 H" k8 ]- //寄存器基地址
: V. Y/ y: E% r3 A9 X2 g( |( U - #define DWT_CR *(uint32_t*)0xE0001000# I/ C+ y( s* Q4 R& C% ]) o
- #define DWT_CYCCNT *(uint32_t*)0xE00010048 e0 u. M8 t) C7 \
- #define DEM_CR *(uint32_t*)0xE000EDFC
( t$ c; v* L) q J - 4 a8 `6 }% i# [4 h- r
- //定义需使能位
8 x+ s% h& k5 S, ?8 J! y8 n - #define DEM_CR_TRCENA (1<<24)& s" ?1 k/ I; h
- #define DWT_CR_CYCCNTENA (1<<0)3 L; B" v1 n2 v6 h
- 5 d* j5 W! s9 O/ u# S" o
- //DWT init
6 F/ u: V" G' s+ V( b; r - void DWT_init(void)
+ x" ^% g# C' {, P7 F- B - {
1 g* B) B' p$ |: ^6 T - DEM_CR |= (uint32_t)DEM_CR_TRCENA;& ^: S7 }0 s% H# }
- DWT_CYCCNT = (uint32_t)0u;
! |% s) A( K& f+ X( D( ?& q - DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA;( J6 h- v9 p; H
- }
# U) N7 C( E2 t8 ^ - //get DWT count
9 l. W* D( R9 V4 ]# [# W( [& Z0 [ - uint32_t DWT_TS_GET(void)
- X: X4 |& k# A2 P! K* k% H - {; _5 @/ F+ T' |) j# @ ~3 a
- return((uint32_t)DWT_CYCCNT);
4 l9 |5 i4 \: J9 z' X. p" x$ e - }- M2 K. H" p8 Z/ ]/ w
复制代码
6 B# C. U& g$ d4 L, S* Y04、代码, t8 q% r: n7 h
从上文我们得知,我们已经获得了一个32位向上累加的计数器,溢出会自动清零并累加,频率是系统主频。那么我们简单封装下,就可以实现延时函数。以下代码在120Mhz的STM32F207测试。
, N% C. ` U" q! A3 h2 k6 X* g' d Y) m: j
}& q4 u* H5 v- //使用DWT延时time_ms毫秒
1 p8 y" g4 l+ Y+ w0 t5 h$ m - void DWT_Delay_Ms(uint32_t time_ms)
6 d$ L2 P/ N9 {+ ~( k9 u6 P8 z - {# ?3 n& p# s! \( t
- uint32_t old_counter,current_counter;- _( N1 z9 Q+ i: D: F% t4 K5 v
- uint32_t delay_ms;
2 n3 _3 W+ ^ b
/ G1 Z- m1 a$ W k9 |- I9 K" n- old_counter = DWT_TS_GET();
% z0 g, D+ _: ^: X0 ] - current_counter = DWT_TS_GET();
9 v2 T. r- B, e# T - delay_ms = 0;7 W# m$ R9 w! [4 x/ S0 ?' R
- while(delay_ms<time_ms)$ G+ k; O' R+ {- Z) A
- {
5 [6 n+ z, \" h& t+ v - current_counter = DWT_TS_GET();
1 S" I5 h5 `9 o5 [' l2 R$ B7 A( e - if(current_counter > old_counter)
5 W' l/ b7 L; Z# q1 |$ k! h - delay_ms = (current_counter - old_counter)/(SystemCoreClock/1000);& }$ l, P# t3 R
- else+ M1 K0 e5 o. Y1 M% q
- delay_ms = (current_counter + 0XFFFFFFFF - old_counter)/(SystemCoreClock/1000);
2 i+ k8 r: i) n - }
* \' [! a) H, z- y - }
复制代码
5 p1 C9 S! j& [; w, E6 Z) n2 j( u: I- m9 J
使用之前的文章《如何测量代码运行时间》测量延时函数是否准确。
( J& J! x! k" q
8 |5 W5 q/ q& i7 b- M7 Z7 @- 7 |8 L4 x' J9 E1 J8 K* k2 j; y
复制代码 ; t; o) G* ? L8 ?# X% |
如下图,延时函数精确延时,没有问题
8 G1 {$ A+ q0 Q* C
( a8 q* j7 T- u( u3 m
; H! n3 c F/ v" a/ Q4 V; X& h1 ]- e" s2 p+ ^! k
实现测量代码运行时长的函数接口
. x1 G9 Z; c! w. U0 ^ D7 |# H7 a9 W/ Z1 d& A) @6 L9 C
# S- v: W+ {- v z( t( x$ w( g% c- //使用DWT测量函数运行时间& F7 y+ C% @. G/ I9 U; P, M
- float DTW_Time_Difference_ms(void)
" I/ y" Z& K4 v4 k1 D - {5 d4 L4 f! I0 l. K3 h7 d
- static uint32_t old_counter;3 _5 T/ D' |7 n0 n8 F( b" @( b. B' n
- uint32_t counter,couter_current;7 V2 V# S$ P- R# D
- couter_current = DWT_TS_GET();; D) I& F$ @! l. \" e# Y
- if(couter_current > old_counter)& U: k! g: E1 K' _
- counter = couter_current - old_counter;
; q7 P g' L/ p3 ~ - else
+ q/ t* D# @* L9 ^ - counter = couter_current + 0XFFFFFFFF - old_counter;0 a; B( V- Y8 U* ^# v
- old_counter = couter_current;/ K& @" l- O8 b8 Q& i2 }
- return (counter / (SystemCoreClock/1000));
( w. b) y/ X+ \2 p* I0 [( e - }
复制代码
6 h+ N7 g. C- _# ^* P+ S7 m使用之前的文章《STM32延时函数的四种方法》精确延时,然后使用DWT测量延时时间。9 k3 K& w. p# \/ F0 E* x
' W* D5 S% p4 k( p" j
- # b& S U) o! F" r
- delay_ms(300);//延时300ms2 L, q3 t% n1 Y3 |5 X
- time_ms=DTW_Time_Difference_ms();
复制代码
3 D e8 @ G j0 O; D+ v5 k如下图,可以精确测量代码运行时间,没有问题。, t7 s' o8 F! m% h
* f" K& r( _ z( ?$ t) \% i; t
7 l V0 c6 }; j6 y
$ N( [: B5 A; H9 K5 k% d% I$ U4 ~05、后记
; v9 e# s, @% J" n I% J本文使用DWT代替了定时器部分功能,它的优缺点如下:# _4 D6 @' c6 ^
* I" u3 B& u8 M M+ k* T2 u! o, t1、优点是:方便移植,经过测试在M3、M4、M7内核的MCU上都可以使用。
3 S( H" b" C1 e- S/ `- H: }+ v1 h/ i3 R7 P/ t3 O
2、缺点是:和定时器一样,都有一个延时的最大时间,测量代码运行时间的最大值。
- O8 |! z t+ V0 y# x. V
: K7 d' x; g$ w* _) i如果项目使用MCU有空闲的定时器,且不考虑换MCU的话,我个人建议还是使用通用的定时器外设,不要使用DWT,虽然DWT方便移植,但通用定时器外设简单易懂,对于没有了解过这部分知识的小白,看到DWT的延时函数,还需要学**。) U n3 }7 A. F) w( d+ M/ S/ P
4 n6 Q# ]. t. D0 G
+ ^' @+ s" h' A" U$ e4 ~" E |