背景
. Z2 G& p/ H; H' G延时函数在STM32单片机开发的项目中有广泛的应用,微秒级延时在一些时间要求严格的场景下(例如软件模拟I2C通讯)是必不可少的。由于FreeRTOS默认采用了system tick作为时间片分配的时基定时器,可能与利用system tick设计的延时函数出现冲突。再加上FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay )最小的延时时间等于FreeRTOS的tick时间(一般设置为1ms),因此需要重新设计一套不基于system tick的微秒级延时函数。利用CM3/4内核中的数据观察点与跟踪(DWT)寄存器,可以在不占用硬件外设定时器的情况下实现微秒级的精准延时。1 Q f3 b) V$ W& D) \1 T. u
5 r) `: _ [" o7 _6 M- P
源码
/ b" r9 K. Y1 t$ i/ J. g/ s通过选择#define USE_SYS_TICK, #define USE_DWT, #define USE_NOP_DELAY其中一个宏定义来决定delay函数的实现方式。需要特别注意的是,由于函数调用是需要时间的,因此使用延时函数时,一般会额外多出一些时间(1微秒左右)。: ?+ p; P2 W# ^1 f9 y" E2 E
& a+ `1 p' S- O
#define USE_SYS_TICK是利用system tick设计的延时函数,在没有使用实时操作系统的时候,它是一个比较适合实现微秒级延时的通用方法。1 R7 v. P1 `, ~5 t) L; i$ x
#define USE_DWT是利用CM3/4内核DWT寄存器设计的延时函数,它具有不占用system tick与外设定时器,延时精度高的特点。% E% \& M( v# I+ u8 E
#define USE_NOP_DELAY是利用了空指令设计的延时函数,它主要实现微秒以下的超短时间延时。6 M( m8 R( k) T. m2 H
头文件( |2 A ~' i( H X
- #ifndef __DELAY_H__% v( Y+ V. t q) l' A9 o. }
- #define __DELAY_H__
# J d! Z6 H: c' H2 k - 8 F- B; p' e1 ~1 m! j2 N: y# [; Y
- #include "stm32f10x_conf.h"8 H- a- A1 O) Y" r( e! T
- #include "stm32f10x.h"
' V% E1 c+ B) n3 p8 K - 7 G" K7 t( ]5 o
- //#define USE_SYS_TICK
+ ?7 n/ R- i4 ] m3 K7 I& `& L - #define USE_DWT
) {' G$ K2 z$ d5 r6 c: ]) i - //#define USE_NOP_DELAY
# Y+ j% N. P: c! Q* |7 ?
* j* c$ c9 L4 D: _# H- void delay_init(void);4 I. t& e& a. D( B9 m
- void delay_ms(uint16_t ms);5 m) F$ j0 z) c8 K- G, O+ |
- void delay_us(u32 us);
$ }. [5 {/ i$ @) G
7 O) Z" d% a0 z$ e" [4 b& K) F- /**
# Q% B: ?4 N: {$ V - * 定义ns级延时,72M主频下,每一条空指令大约14ns- _& J1 J% m( ~% }0 W- Z
- */
- H2 v/ t2 }4 K. C3 q/ p8 G - //#define DELAY_6ns() __NOP()1 y9 j6 k- @& S% b
- //#define DELAY_12ns() DELAY_6ns(); __NOP()% W f1 A1 O* P1 n, w
- //#define DELAY_18ns() DELAY_12ns(); __NOP()
; K$ w9 d7 ~2 d3 T - //#define DELAY_24ns() DELAY_18ns(); __NOP()
/ t8 p1 S) [9 c/ j - //#define DELAY_30ns() DELAY_24ns(); __NOP()( ~- h2 ?# _" q* [7 T
- //#define DELAY_60ns() DELAY_30ns(); DELAY_30ns(), N! Y2 @# m$ F( X; k. J: k
- //#define DELAY_90ns() DELAY_60ns(); DELAY_30ns()9 s1 O$ A2 ^( `' W# Z) o2 V
- , {9 ?& N# U' h) M ^# n" C( t7 B+ s1 s5 y
- - W' k7 B7 U7 t
- #endif: j) p; G7 q# {
- % ^6 B. ~; R |6 K9 a! x
复制代码
, J1 A# |' f0 A源文件& j: Z. t8 B9 ?; {; N# g9 a/ s
- #include "delay.h"! J s, k2 b5 W1 R, V+ t6 i( [
- 9 ?8 Y; ~. i0 A1 p. R2 n
- #ifdef USE_SYS_TICK2 h' `' _1 o1 U s% c- g
- & L6 z' H, @" a' e9 y ]( q- K
- static uint8_t fac_us = 0; //us延时倍乘数
) F8 z1 ]# g; A - static uint16_t fac_ms = 0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
' H( o% m9 Y* ~4 T# _# e( K, C - - b( g; O5 v7 k7 L# o+ O
- //初始化延迟函数
( R ~& |4 s. X/ Z& [ - //当使用OS的时候,此函数会初始化OS的时钟节拍0 R, k$ P0 l# ~( o3 x
- //SYSTICK的时钟固定为HCLK时钟的1/8
1 R) g+ V' z7 D7 a$ P2 U - //SYSCLK:系统时钟7 v0 i* @9 Z7 p" r: v
- void delay_init(); |! e3 y- P/ y
- {8 y7 s3 f" u" `/ P4 w4 K
- SysTick_CLKSourceConfig ( SysTick_CLKSource_HCLK_Div8 ); //选择外部时钟 HCLK/85 l2 J/ Q( U+ |2 w
- fac_us = SystemCoreClock / 8000000; //为系统时钟的1/89 ~" d2 V8 F4 D V& w6 e: ^
- fac_ms = ( uint16_t ) fac_us * 1000; //非OS下,代表每个ms需要的systick时钟数
% l, U. a; k3 r - }+ B0 y* q: h5 w
- + l3 ~7 ]' K. J+ R. I
- //延时nus
. Z2 p! b! N% l- S" @% Q - //nus为要延时的us数.
$ o# k! G2 z, p: h( [* u. O6 } - void delay_us ( u32 us ), F/ P# B6 W; ]0 ]- ?( P F
- {: f' _! r I7 K
- u32 temp;
$ L, r- {7 w) I7 K - SysTick->LOAD = us * fac_us; //时间加载+ j+ B2 |5 T, Q8 n
- SysTick->VAL = 0x00; //清空计数器
+ p1 R+ f: m I) N& W - SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //开始倒数
5 A. ~- H; U6 ~) ]: D, ?/ S- @ - 3 z; I# @/ ]# u! h( B
- do- O! s9 F. n+ u R
- {1 j) l- t) @- j( [% }1 ^' i @" v. W
- temp = SysTick->CTRL;
: F) k: G% |3 \* v7 S - }
i8 H% L% l% X: l* p8 ? - while ( ( temp & 0x01 ) && ! ( temp & ( 1 << 16 ) ) ); //等待时间到达( S4 u- P1 w5 O: `5 l) a
- 6 ]) c! d' k) j( m% j
- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器0 \* U5 N: ?2 }$ K
- SysTick->VAL = 0X00; //清空计数器9 A2 Q% w" {# s0 q" D$ _* F
- }
# V# \* j2 u6 p6 I D3 _ - //延时nms
# c5 X3 n! v) v) @4 d: I+ o - //注意nms的范围; \+ j Y2 V/ @; J9 M: w
- //SysTick->LOAD为24位寄存器,所以,最大延时为:
4 w2 s* _" ]' F" S - //nms<=0xffffff*8*1000/SYSCLK
- C. b5 W: ?& d+ N3 J - //SYSCLK单位为Hz,nms单位为ms+ V: n% l$ r7 k! k9 P7 S
- //对72M条件下,nms<=1864
+ i) S6 I Q2 ]; V% y! f+ ?7 y* D - void delay_ms ( uint16_t ms )& G4 O' m3 A1 z0 g# A5 T
- {
' h3 q. b+ L; ?* l - u32 temp;
5 o4 ~( [$ `, p - SysTick->LOAD = ( u32 ) ms * fac_ms; //时间加载(SysTick->LOAD为24bit)7 L3 x, y4 [& A0 i+ N
- SysTick->VAL = 0x00; //清空计数器. f4 v! b7 j9 W$ n4 B k, b9 v
- SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //开始倒数8 c' T( N9 Y$ M5 v. Z% w3 ?" C
5 x$ e+ h. Y6 P9 X% }- do* f/ E" H, I& F
- {
, {% d6 ^. {3 |9 g6 N' ]* ^" w% x - temp = SysTick->CTRL;
1 p: J3 {, {, Q$ [ - }0 n. b. [0 x) k
- while ( ( temp & 0x01 ) && ! ( temp & ( 1 << 16 ) ) ); //等待时间到达2 b1 p/ e6 T X( f8 R
- % f* r0 e0 U0 h+ p7 s
- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
3 s( I" R% R; b; A1 D( Z - SysTick->VAL = 0X00; //清空计数器4 v; x7 b+ l! v& K- ?0 U
- }! N" m' P/ N- h
0 C3 l7 Q' U9 e0 {8 j2 Z- #elif defined USE_DWT
1 q4 B7 p" _2 Y) d m
8 [$ W8 z7 z4 O" |$ E2 r- // 0xE000EDFC DEMCR RW Debug Exception and Monitor Control Register.
4 p5 d, A* F% H$ S; f8 q - #define DEMCR ( *(__IO uint32_t *)0xE000EDFC )7 s0 h) J3 {2 F) n- L
- #define TRCENA ( 0x01 << 24) // DEMCR的DWT使能位/ I; i' b9 X, b# V: w! K
- $ B5 D9 @$ m, U, A4 P6 X1 j" X, g
- #define DBGMCU_CR *(__IO uint32_t *)0xE0042004 //MCU调试模块控制寄存器,详细内容参考《stm32中文参考手册》调试支持(DBG)章节,747页
5 [0 e) }# a1 D
9 V. E |/ w& |- // 0xE0001000 DWT_CTRL RW The Debug Watchpoint and Trace (DWT) unit
2 ?: `6 a! J, K) L' T1 @ h - #define DWT_CTRL ( *(__IO uint32_t *)0xE0001000 )
4 p# p* {1 q9 h) g P - #define DWT_CTRL_CYCCNTENA ( 0x01 << 0 ) // DWT的SYCCNT使能位' z! G7 }8 H$ i
% T" Z1 B& ?3 V+ [9 @( m8 n' k- // 0xE0001004 DWT_CYCCNT RW Cycle Count register,6 P1 J- @" L' x f" S: ?9 a
- #define DWT_CYCCNT ( *(__IO uint32_t *)0xE0001004) // 显示或设置处理器的周期计数值
9 Z: l3 ]: v5 k3 Z" V0 \% T
. V* s5 m7 I: K9 t" e' i- //#define DWT_DELAY_mS(mSec) DWT_DELAY_uS(mSec*1000)
. H6 a w/ Y# I- v" A, M' I0 F3 _$ A
- P4 v% B; H2 q2 [+ [' {- void delay_init(). [& h1 @$ J5 Q, a
- {9 V/ e' V3 Y. u3 Q
- //使能DWT外设
% W) i: X: r! f2 i% T. M) N - DEMCR |= (uint32_t)TRCENA;$ R' e ]% ~! S' \
; _/ i1 h1 }1 W! Z) f$ F$ U- //DWT CYCCNT寄存器计数清0
2 W. D v! U" ~5 `3 c - DWT_CYCCNT = (uint32_t)0u;, ]1 K$ ~# k: c1 w* F5 u1 b; ]% } N
- - w* I5 F- R6 }, f! w* j
- //使能Cortex-M3 DWT CYCCNT寄存器: l; P# @6 {9 s
- DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA;+ E2 \ Y. ]) a, S% j5 F
- }; q8 [9 X# U* z9 H7 _
- * o: l( l' ]6 @# [
- 8 S$ S" W( X+ ?9 A5 W" r
- // 微秒延时# H' S0 S- q9 Q4 d+ L5 N' n! \
- void delay_us(uint16_t uSec)5 [+ U6 N3 \1 |0 u
- {
; j/ E; i; {8 B) V7 A* j# p8 {4 d - if(uSec > 10000) uSec = 10000;( Z7 @8 M: |+ a1 i" G
- . {" k, g- l6 X ?5 ~% t' O3 H
- uint32_t ticks_start, ticks_end, ticks_delay;
7 m; r$ j: p" _( G. Q - 4 Q! E0 P M5 b$ K8 A. z" y! E
- ticks_start = DWT_CYCCNT;9 O& Y: w: L/ m/ @
- ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数
+ d, t( p0 c: M - ticks_end = ticks_start + ticks_delay;. U6 [3 T/ J& ]' P. x, x6 n
" n. N( n! M. V3 w; M b Z- // ticks_end没有溢出8 J9 R- a: R" h: x4 ]- g7 ]7 c6 `
- if ( ticks_end >= ticks_start )
- `8 _2 C( V, s - {8 _0 E7 n+ ~3 o6 p
- // DWT_CYCCNT在上述计算的这段时间中没有溢出' f1 ^; w% D, N
- if(DWT_CYCCNT > ticks_start)0 n' t6 W- j, T5 |+ R0 z2 D
- {
' b/ ]( K; M) U M- M% I. [; O, { - while( DWT_CYCCNT < ticks_end );
3 \. Z9 }5 P, J' w s A' S - }
b0 r7 D. j1 n" v4 q9 Y3 i9 M7 h - // DWT_CYCCNT溢出
9 q4 c* Q( z( ^+ S4 `+ F) W - else! `% \6 U$ N. x$ P6 [# ~, V
- {
1 y& e& j( V0 F' I - // 已经超时,直接退出
( v, V: b/ b9 e# ^, A) ? - return;, |5 }& G1 q. I4 G. o+ L2 r
- }
. Q8 D4 W9 Q3 }8 w( @ M4 O - }& f+ Q! v" z) ~2 Q
- else // ticks_end溢出
9 }3 C3 b# Z" q R - {, u+ w, g V8 D" M" Q M5 h, k
- // DWT_CYCCNT在上述计算的这段时间中没有溢出
0 u$ J- D6 T' h4 J- J - if(DWT_CYCCNT > ticks_start)
- P' M; K+ t# |! v7 G- L( C - {' w { p/ c' T9 y, {
- // 等待DWT_CYCCNT的值溢出* _. R8 V- y7 M/ ]7 T
- while( DWT_CYCCNT > ticks_end );! \1 S- n$ Y6 S$ {
- }
9 N8 d8 e2 o5 X - // 等待溢出后的DWT_CYCCNT到达ticks_end
# Z6 k0 V1 q6 j( S - while( DWT_CYCCNT < ticks_end );
# V" N% R8 N0 g ?5 b - }
: B9 i/ L) G, U" g2 u3 M - }9 ?' v3 y1 n( p- R7 ?
- - p4 w4 y; k/ t$ T* Y) Y' f- r. d& [
- void delay_ms(uint16_t ms)9 o: {; O* Z+ O' F0 p" i: o. }0 ?
- {! `9 m [1 t% `( L/ {. `' H' G
- for(uint16_t i = 0; i < ms; i++)
3 p- R) d0 O% Z0 ^; C - {2 i A2 K. f" H. _, s+ v9 J, E; X
- // delay 1 ms
/ K2 p( M/ O9 h3 e - delay_us(1000);
# o' x& @5 J, u - }
) o- P: H% l- N2 r# O - }
" N' n0 C* A5 @6 S2 G+ x. _7 \( { - 6 q1 k8 m$ P6 h2 J% h
- #elif defined USE_NOP_DELAY
& Y+ l. c6 |& r3 \- M8 X" I - " Z6 N# A# d5 O
- void delay_init(void)% e0 r* {0 }0 _; J! t
- {
0 j" l2 A- a" R: ]2 R - , A# b* Z Q u, O7 C: I' K
- }. w, j8 D( _& F- y% U1 x1 |
' ^3 O' P$ ?- ~+ ?% {- E+ v6 j% f- void delay_ms(uint16_t ms)
( K0 Z7 k( f3 L& N - {5 ^$ C: _: }/ o4 f0 k' E+ G
6 s; M7 H+ T. C% u$ n+ r: y# c- }
* T- J: P$ I+ L1 y6 r
" W, U3 I4 E0 B$ @: w- void delay_us(u32 us)3 q1 c0 I& U; [
- {+ U4 b# h* I8 t2 T1 V. g
- 2 m) {1 V w" L- z% S( t& v9 M
- }
8 @ C: u2 M" M" C - 7 Z, g( U* _! H2 k8 K- @& O( [- p8 v
- #else
8 ~& G/ S* ~ D9 X' [
Q: J: e5 o q1 n' j a! M: }- #endif
复制代码
( b3 |$ t* A( d2 e2 T特别说明9 ~$ A2 e$ H! ^5 C9 ^( D' \) x
使用DWT内核的延时函数中,涉及了两次数据溢出处理,第一次数据溢出处理是指计算结束时间(ticks_end = ticks_start + ticks_delay)发生溢出,如下图所示:
6 E: l# P% _( A5 u3 n) c/ O1 @( A# E+ v3 B* A
这里有个限制条件就是延时长度(ticks_delay)不能超过uint32_t的最大值,但考虑到这是一个极大的数值,几乎不可能发生,因此可以简化处理限制微秒延时函数的输入参数小于10k,以提高延时精度。对于这个溢出判断就比较明确了:
9 _" @, [* O9 w- F
$ o% P8 b: H2 ]( ^5 S! }- if ( ticks_end >= ticks_start )% y) K/ W: ?. B5 g" y1 W# x$ V" j
- {2 M% `' r+ ~% l- k% L
- // 计算未溢出3 D, X9 o. m7 e: C
- }5 O: S. j" K; L, e. C
- else
2 Z3 x# {3 a" O. j( V4 K - {) p% D5 D1 @9 @* N
- // 计算溢出& \6 r1 F8 l* |2 `# J7 T# N
- }
复制代码
$ q' }% q9 N- c5 O, g5 i" @第二个溢出相对比较复杂,处理不当会导致严重问题,尤其是使用了RTOS的系统中有很可能遇到。其核心原因是从标记初始化时间戳ticks_start = DWT_CYCCNT到准备比较时间戳例如while( DWT_CYCCNT < ticks_end );的这段时间的耗时是不确定的。即使没有使用RTOS,如下代码(使用绿色底色标注)在执行时也可能被硬中断暂停,从而导致DWT_CYCCNT时间戳可能已经越过了ticks_end ,或者也发生了溢出重置。
3 ]; f" r' g! T1 G" W" n! N* i% k# k6 X7 @% k- c3 c: Q
- ticks_start = DWT_CYCCNT;# t$ |3 D3 m ~5 o; W5 q
- ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数" X- i, Q1 S2 c+ [
- ticks_end = ticks_start + ticks_delay;" s- \1 ]1 {' r0 y
- 0 f' T' h! @& P# K
- if ( ticks_end >= ticks_start )4 M. H! y9 p1 E" O% ]
- {
& y: E: _2 ?. C9 r - // DWT_CYCCNT在上述计算的这段时间中没有溢出
+ {+ y. `* y, i. _( E+ D. [+ j - if(DWT_CYCCNT > ticks_start); ?. ^8 j* F0 Q F& i: H! m
- {
. X/ c/ @2 [9 C% _! r
7 k/ ^! Y2 ?) O' p( v2 H5 j+ P- }0 b' |# ]/ h% ?6 y
- else
* z3 y1 e6 V) \ - {
/ m1 J- O) R9 _& m+ b( J* T -
* v+ z- L: I+ k" W - }
* A' ]3 ]. C3 |0 D# L" d - }+ v8 \( B& q+ ^8 }" W \
- else ( T, u. ]/ i7 A4 K( v# H) z/ M
- {! t: k3 d( K6 G% G; s
- // DWT_CYCCNT在上述计算的这段时间中没有溢出
6 Z. }1 G; B" D' w& f+ q4 N - if(DWT_CYCCNT > ticks_start)/ w+ V& |( ^- l
- {
1 L6 `0 F0 D; Z* t5 f# v
: R3 F3 T+ {7 K6 F: z {- }
- \+ ~$ J4 G, k3 ? - else
% ~0 Y4 c# s p% H6 S - {0 r* v; O6 r/ D5 q' N4 D0 u$ x3 n/ g
- ( } e) s0 z, T3 L ^
- }
" A7 }* L% S' b9 P - }
复制代码 $ b0 ^% n" q% O. w0 I9 |
因此需要根据延时等待操作时DWT_CYCCNT所处位置再进行一次判断:
0 X N# E& H; q9 z0 {6 J3 h/ k* m
/ ]" l+ j0 v; ]8 p6 K
! Z; L5 n. G" j! i" p
3 ?) l) A/ Y+ d; o1 f
ticks_end未溢出时(ticks_end1):" w$ {5 s/ w. r+ ~2 ]
DWT_CYCCNT未溢出(DWT_CYCCNT2 & DWT_CYCCNT3)时:while( DWT_CYCCNT < ticks_end );
4 v9 p I- p8 Q/ T, O* F. J1 e2 b' B- kDWT_CYCCNT溢出(DWT_CYCCNT4)时:直接返回。5 r7 H) n) ~: z" n8 F7 P- O' `
ticks_end溢出时(ticks_end2):* Y. e2 W7 ]3 j4 b- m7 h
DWT_CYCCNT未溢出(DWT_CYCCNT2)时:while( DWT_CYCCNT > ticks_end ); while( DWT_CYCCNT < ticks_end );
; z( N0 W0 l2 B6 {DWT_CYCCNT溢出(DWT_CYCCNT4 & DWT_CYCCNT5)时:while( DWT_CYCCNT < ticks_end );( _. U1 U! C0 ~: r' Z
/ T& K4 I8 V" i+ K' j. y
/ n/ }; _% X. H3 p
) L H" X. x/ }( V/ X% p使用指南
' h# g, t* D9 i# ]5 P在程序开始的地方初始化延时函数:delay_init();- o5 r% X# ` d! @! f4 S+ o
在需要延时的地方调用延时函数:delay_us(10);' r/ d' u4 ?0 e3 } f9 [
需要特别说明的是,在FreeRTOS的任务中调用本文介绍的例如void delay_us(uint32_t us)等延时函数,任务会阻塞等待延时结束。如果是调用FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay ),系统会执行低优先级任务直到延时结束。; M: V0 c4 X' e, X; j9 I, ~
1 R5 [! }2 @9 \) j8 w- v v. L1 J- S* j
, q" q3 S' o4 p" V% Q: ?- q |