背景; h$ o% k" \( j8 f D" K
延时函数在STM32单片机开发的项目中有广泛的应用,微秒级延时在一些时间要求严格的场景下(例如软件模拟I2C通讯)是必不可少的。由于FreeRTOS默认采用了system tick作为时间片分配的时基定时器,可能与利用system tick设计的延时函数出现冲突。再加上FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay )最小的延时时间等于FreeRTOS的tick时间(一般设置为1ms),因此需要重新设计一套不基于system tick的微秒级延时函数。利用CM3/4内核中的数据观察点与跟踪(DWT)寄存器,可以在不占用硬件外设定时器的情况下实现微秒级的精准延时。
3 S1 {5 a; r, J" H) y' B5 ]9 y3 u+ e' T$ O3 k. N: V
源码# |8 s4 Y& c- A6 ]- }) d2 f
通过选择#define USE_SYS_TICK, #define USE_DWT, #define USE_NOP_DELAY其中一个宏定义来决定delay函数的实现方式。需要特别注意的是,由于函数调用是需要时间的,因此使用延时函数时,一般会额外多出一些时间(1微秒左右)。9 {% y3 I1 m4 I9 ~, O
* c% q/ q, e8 P
#define USE_SYS_TICK是利用system tick设计的延时函数,在没有使用实时操作系统的时候,它是一个比较适合实现微秒级延时的通用方法。0 g! A* J' z8 X& k
#define USE_DWT是利用CM3/4内核DWT寄存器设计的延时函数,它具有不占用system tick与外设定时器,延时精度高的特点。
" h3 X* |' E b9 Y% ^9 ]#define USE_NOP_DELAY是利用了空指令设计的延时函数,它主要实现微秒以下的超短时间延时。8 j/ q e! w4 a( E0 {% A% r% v2 ?5 h% @
头文件
/ r) U8 z/ m( H! ]8 \8 q6 \- #ifndef __DELAY_H__
, a) l8 D- [# R3 C - #define __DELAY_H__
- r' ~$ a- K5 R3 } - . m; k; W% u' G9 z6 i
- #include "stm32f10x_conf.h"
% Y, H' N+ C( Z. h6 o - #include "stm32f10x.h"9 K8 u+ p- o4 I! W' W
9 U' r+ R( }. v9 I8 B; Y3 `9 O- //#define USE_SYS_TICK
: c' ?4 x7 K- e0 G/ m# `1 }7 j - #define USE_DWT: r, }% P, D q$ q. c' N- S6 O
- //#define USE_NOP_DELAY
* M! c/ O' H. F- w7 R - 0 Z* M' @. l0 r. W# I, }3 I+ i: H
- void delay_init(void);9 j5 w- a: ~4 s4 H2 \4 g
- void delay_ms(uint16_t ms);5 e1 j6 s8 V0 ~
- void delay_us(u32 us);# j$ U& Z' J5 G! j# H
- ' N7 m* Q7 W. q
- /**
8 R6 }- k+ P# j1 z, T4 M2 t1 { - * 定义ns级延时,72M主频下,每一条空指令大约14ns
$ C, }! I- h- `; D" J+ Q" A6 a - */' N3 {% g/ H: z# y' |; H# R
- //#define DELAY_6ns() __NOP()5 V! n5 N3 X [% L) w6 f6 y
- //#define DELAY_12ns() DELAY_6ns(); __NOP()
( H# A( T( P b# y# j0 l/ | - //#define DELAY_18ns() DELAY_12ns(); __NOP()
& ~5 I2 `: H) B& ^6 |$ k - //#define DELAY_24ns() DELAY_18ns(); __NOP()1 u$ W' J' o5 J
- //#define DELAY_30ns() DELAY_24ns(); __NOP()
: B% u0 A' I& _! C" b - //#define DELAY_60ns() DELAY_30ns(); DELAY_30ns()0 \. |( R& ^/ C" f9 K* n3 H# K t9 O2 o
- //#define DELAY_90ns() DELAY_60ns(); DELAY_30ns()
% K+ R( C: n$ h
9 \% d+ L' `- N: |* j% X1 g7 y- : K4 w5 j7 K& U. a, O; P
- #endif( a' ?' D3 ~6 y. A$ w4 Z1 j E
! s2 s0 o& k& n% h
复制代码
/ Z3 @, e1 [ y9 _7 _源文件
4 K0 F+ d$ S, y/ e* C$ m- #include "delay.h"
1 B. B4 {. z- q& ?' Q, c5 t
5 y7 ?8 y' L4 Q; g- #ifdef USE_SYS_TICK8 @# w" j1 \/ R3 R1 s/ ^+ Q# Z! G
- 9 M) @& ]. M0 m% v" F
- static uint8_t fac_us = 0; //us延时倍乘数
5 ^9 X @4 k% M4 u i/ F+ U - static uint16_t fac_ms = 0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
$ n* v% }; v( g+ c. A! A - : e8 [% L/ F7 W5 _
- //初始化延迟函数/ ~/ w9 @4 b" S( j
- //当使用OS的时候,此函数会初始化OS的时钟节拍4 B# L" j+ n3 i
- //SYSTICK的时钟固定为HCLK时钟的1/8
* \3 S5 z0 ^3 B* ]8 ]+ l - //SYSCLK:系统时钟* j) ^: m0 d% P) b% n1 h1 b
- void delay_init()8 h7 r6 P2 r0 @' j7 ], @
- {
1 E* G0 N% C' N- C - SysTick_CLKSourceConfig ( SysTick_CLKSource_HCLK_Div8 ); //选择外部时钟 HCLK/8
# d- V" }3 _8 O3 j d1 h# M - fac_us = SystemCoreClock / 8000000; //为系统时钟的1/8
6 m, Q9 U& q, I" `6 ^9 K - fac_ms = ( uint16_t ) fac_us * 1000; //非OS下,代表每个ms需要的systick时钟数
/ S L. C# Y! Z! ?5 j - }- N' D& I( b) H; f$ G5 I7 _
/ D# Z, C! I+ A S& S$ e& l) D- //延时nus
7 q7 F3 }7 `: C" F" a, o+ S0 p - //nus为要延时的us数.5 a$ t4 M2 @9 b8 f6 P* n3 |" l7 P
- void delay_us ( u32 us )1 w$ i; p& Z) `2 f1 H
- {
, j9 B1 l! z, y0 M* H4 `( v - u32 temp;
4 ^5 u; J9 D" r; n* H* D- f% g- ^ - SysTick->LOAD = us * fac_us; //时间加载
4 r$ h5 m, o1 ]7 o9 Y( C1 h - SysTick->VAL = 0x00; //清空计数器/ t3 h8 Q& h- X, u7 y1 b
- SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //开始倒数
+ H0 O( |8 x: K5 L! d - $ A: J8 V! b1 I" K
- do+ h9 z V. r. ^/ M5 F
- {4 o, M. P. b4 q4 D
- temp = SysTick->CTRL;5 k6 O* O& n4 `3 @ I, h
- }
7 B4 z3 x' h' Q. W - while ( ( temp & 0x01 ) && ! ( temp & ( 1 << 16 ) ) ); //等待时间到达; g$ V1 b2 T6 A7 ?
8 O+ T& G; l0 }8 [- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器 K1 o C ~1 W
- SysTick->VAL = 0X00; //清空计数器5 a2 T# O- Z6 Y7 ]: d
- }
' }* ^0 a7 v! ^8 D1 g( V9 u - //延时nms0 h& B- V, c0 R
- //注意nms的范围
" g$ `; u6 @: B s, @9 Q - //SysTick->LOAD为24位寄存器,所以,最大延时为:
& y' }+ a* M, K$ B: ? - //nms<=0xffffff*8*1000/SYSCLK
7 T" t( @; L& l! `& y4 Y - //SYSCLK单位为Hz,nms单位为ms
- `, ^/ B/ g" K2 C$ ] - //对72M条件下,nms<=1864( K6 r3 b9 Y# u3 m( w( _8 b, G
- void delay_ms ( uint16_t ms ), U3 A+ ~$ j+ [8 n
- {' {# [+ s! R% n5 F' x
- u32 temp;
; }: w$ w1 W( W; g - SysTick->LOAD = ( u32 ) ms * fac_ms; //时间加载(SysTick->LOAD为24bit)& E/ ]/ v9 e5 k0 _. b2 @: J1 o
- SysTick->VAL = 0x00; //清空计数器
7 O5 c: m/ x. z7 G - SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //开始倒数
, r. h' ]+ B i/ B - $ i0 R& r5 e9 \0 E# U
- do
2 S6 Q+ p# R. }+ d( Z- z p - {
8 [2 u b: E# F; N. _% N - temp = SysTick->CTRL;
& E6 w' L8 h' u' b' A - }
/ h7 @, Z( }+ G# f( h# ^+ m - while ( ( temp & 0x01 ) && ! ( temp & ( 1 << 16 ) ) ); //等待时间到达
- V1 f1 O, Z; ?! D+ W6 C& C* I$ q
5 l C5 x1 D6 @- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器6 o: O* f) T* P' T/ D/ }( m
- SysTick->VAL = 0X00; //清空计数器
* l' _3 B5 A! {& d - }4 L, _2 |+ O, o: s1 T* g
9 u" {9 l# ]6 A; a5 N- #elif defined USE_DWT5 Y, M# `7 p" R8 g+ I4 }
9 L" V. S. \3 }& r7 x- // 0xE000EDFC DEMCR RW Debug Exception and Monitor Control Register.
: h, Y0 Q( r% [+ m2 M - #define DEMCR ( *(__IO uint32_t *)0xE000EDFC )
; o Z# D& R1 ] - #define TRCENA ( 0x01 << 24) // DEMCR的DWT使能位
/ g+ l! u5 X1 @! x5 N# I6 m ^
7 v J' H6 ]! z) c- #define DBGMCU_CR *(__IO uint32_t *)0xE0042004 //MCU调试模块控制寄存器,详细内容参考《stm32中文参考手册》调试支持(DBG)章节,747页
5 j& y7 M' H, x - ! f) i3 d- m( Y! ~9 b
- // 0xE0001000 DWT_CTRL RW The Debug Watchpoint and Trace (DWT) unit
0 T3 Q( U5 \/ v. a$ W4 ` - #define DWT_CTRL ( *(__IO uint32_t *)0xE0001000 )
; p$ |7 c `) l' C! X - #define DWT_CTRL_CYCCNTENA ( 0x01 << 0 ) // DWT的SYCCNT使能位
0 o' k& N. J& d# B& ^3 X) F v
6 R- N8 {9 @ c3 @2 h: A- A- // 0xE0001004 DWT_CYCCNT RW Cycle Count register,
; [# t4 [. I& Y9 z$ w! C I - #define DWT_CYCCNT ( *(__IO uint32_t *)0xE0001004) // 显示或设置处理器的周期计数值
! w7 S1 N5 |" R2 ^% J3 d
2 b7 S, l, v' J5 q( n- //#define DWT_DELAY_mS(mSec) DWT_DELAY_uS(mSec*1000)4 U4 G2 r1 o, _4 v- m) V
- ) }: O( T% \/ S! s# @% A( j/ R
- void delay_init()
5 W5 U' j. @8 i. w& ~* K; \ - {) f) ?$ n, s: D! o) H/ m* c1 H% S- [% k
- //使能DWT外设: k3 d* }' I9 C% n1 C/ f; Z
- DEMCR |= (uint32_t)TRCENA;
# h5 k0 V3 m1 Q - / T! u6 u9 q- V# k0 S# ]$ _8 M
- //DWT CYCCNT寄存器计数清02 m3 ~$ ~3 [- T7 p! k3 f- ~
- DWT_CYCCNT = (uint32_t)0u;; n: F+ g# b6 d4 p; e
- $ ], E, [4 a8 k5 }* a
- //使能Cortex-M3 DWT CYCCNT寄存器
2 T2 V+ p4 s# ?, Z - DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA;
1 D) T1 n t1 K% M - }& m$ {" Y" q0 E Y* b- U
s* X/ s2 W2 S e- 1 U6 W v2 ~5 q
- // 微秒延时3 s3 n8 v! ?/ }" `
- void delay_us(uint16_t uSec)
9 m# e: {" C6 A - {! J ^1 _, a8 }9 B( E N
- if(uSec > 10000) uSec = 10000;
; y6 P9 k' V6 ?9 f9 b5 G' A. K
5 V1 {1 `$ J- [0 T+ I; D- uint32_t ticks_start, ticks_end, ticks_delay;
0 d. m: c, ~9 C( P( _3 y, ~: Z+ ]2 R - 9 o% x" y# X7 `% A
- ticks_start = DWT_CYCCNT;
/ n5 a, j- S* D. q. b0 y/ ~/ ^ - ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数
7 ~, j: H7 Z( l4 A9 h - ticks_end = ticks_start + ticks_delay;
8 V1 J f6 G0 h' n% ?/ Y - * B: g: x# W5 g8 G$ W* m
- // ticks_end没有溢出9 W; ?, b# O, z0 }
- if ( ticks_end >= ticks_start )9 }" C, s# c( [" Y, ~+ L
- {/ B" B9 u P9 F) {4 F
- // DWT_CYCCNT在上述计算的这段时间中没有溢出
5 h! o' H+ C& P$ h) B - if(DWT_CYCCNT > ticks_start)
, c' o* u* H8 o/ P0 D' ^: L - {
! q. {& `$ f0 Y+ E4 ~ - while( DWT_CYCCNT < ticks_end );
$ }% j$ k# H. h. L& } - }# H9 j' _2 m. e* c1 V i8 P7 {
- // DWT_CYCCNT溢出
) I1 n9 I- N$ T1 J) R5 o; } - else0 ?- H* ]$ R- P2 c/ r) w0 _
- {+ Z" S9 u1 u+ w: P3 o
- // 已经超时,直接退出' r L& \0 k6 J8 \9 p+ @$ b
- return;
& l' r. Y$ A( Z. z - }3 t: K/ M8 g$ b' P0 @
- }+ c' O+ e! }& X' z4 H
- else // ticks_end溢出
( K, _& P8 u9 R2 g0 D" _ - {
2 k/ j' z$ Q' a" U% C& _' } - // DWT_CYCCNT在上述计算的这段时间中没有溢出, ?( n1 h- g: ]9 f* ], x+ P" M
- if(DWT_CYCCNT > ticks_start)& p7 z8 ` q1 H2 G$ }8 l
- {$ J# ~! ~# {$ ?
- // 等待DWT_CYCCNT的值溢出 X i2 I# L$ d3 L8 G$ I2 `
- while( DWT_CYCCNT > ticks_end );5 a% {6 a4 D& L
- }8 U2 E" @. A) X( C( b. ?
- // 等待溢出后的DWT_CYCCNT到达ticks_end4 X% n, N/ T4 C. z f, l
- while( DWT_CYCCNT < ticks_end );
6 F ^# b& g/ x9 ~8 ]4 P. F; i - }
' _7 O9 h+ k, f5 f# Z - }+ `# }! q4 M- K9 D$ k
- - L* d) k7 J/ N2 n5 T
- void delay_ms(uint16_t ms)4 I/ s% T/ ?5 }3 X) ^
- {) @7 W% A ^: x) J8 v6 m5 p$ B
- for(uint16_t i = 0; i < ms; i++)
( p: ~6 I+ h6 \2 I" r2 Z - {
; G. M- f4 n' k' O - // delay 1 ms
% Y9 }/ |6 d2 w/ q6 B- \ - delay_us(1000);
( |% a% h7 N+ z5 L* x4 U4 I- y& h - }. V% V q6 h# M9 d5 A. \& T
- }0 a# B1 T) b2 n8 }
- , g, I) A# v4 b$ ] r& z# T& z
- #elif defined USE_NOP_DELAY1 u' i! `2 j7 a+ H
5 C3 I" Z" a& y) b# p- void delay_init(void)) |$ [7 [0 P5 J( F9 R3 c7 G
- {
j$ q& f* n. D
- s2 L7 f( M/ _. q5 p0 y- }
& h9 c* L/ G6 _: G% O R
7 s2 V6 p' d% \: O( }" d- void delay_ms(uint16_t ms)( Z5 R8 C, L. s. N5 E. p% Y
- {
4 Z% W" _7 O- l( B- r- w# {8 t - 1 T1 v& W) s# k/ P# V9 ~) P) s/ }
- }$ u! `, d. F, z- q; T
- ~" Y2 ^2 h/ O- void delay_us(u32 us)) k8 O7 c" c0 h$ D2 H
- {
! k# e9 w1 N4 H$ L1 a# m5 p - ! L2 T+ J& N+ l5 }6 W- l0 F
- }, x+ H) a' Y, \
7 V0 w( B. x: n$ E% W( C- #else9 ^" ?* C% Z$ F5 U3 | ~! T
- 8 m% ]' n4 P P1 C
- #endif
复制代码
2 w9 c5 l9 f: |6 V/ t2 p) x特别说明
' m. U# Y) Z/ I; l4 W, m3 ]使用DWT内核的延时函数中,涉及了两次数据溢出处理,第一次数据溢出处理是指计算结束时间(ticks_end = ticks_start + ticks_delay)发生溢出,如下图所示:7 v2 H/ y0 J/ H3 o9 ~
4 x7 d1 r R% z$ A& @+ `6 t这里有个限制条件就是延时长度(ticks_delay)不能超过uint32_t的最大值,但考虑到这是一个极大的数值,几乎不可能发生,因此可以简化处理限制微秒延时函数的输入参数小于10k,以提高延时精度。对于这个溢出判断就比较明确了:' J. f$ p' ~, M- E: f
7 J6 U. I2 k& {6 ^3 o- if ( ticks_end >= ticks_start )
4 k* `* Z% ^9 J) W# O - {
; w3 s( M! ^0 V B - // 计算未溢出
) S: { j8 H# k - }
) |1 F! h |' X4 p, g - else 7 _0 D4 s5 ^- w8 ]
- {+ Q9 ^% m9 P6 ~ a5 P2 H
- // 计算溢出& T9 F% {% I# _+ U1 h& Y P( t
- }
复制代码
# y# C4 E0 ^/ B; s- ^" b. ~% M2 I第二个溢出相对比较复杂,处理不当会导致严重问题,尤其是使用了RTOS的系统中有很可能遇到。其核心原因是从标记初始化时间戳ticks_start = DWT_CYCCNT到准备比较时间戳例如while( DWT_CYCCNT < ticks_end );的这段时间的耗时是不确定的。即使没有使用RTOS,如下代码(使用绿色底色标注)在执行时也可能被硬中断暂停,从而导致DWT_CYCCNT时间戳可能已经越过了ticks_end ,或者也发生了溢出重置。$ @( d4 D) {/ m% Z$ \) w
. H! \8 _ Z' m
- ticks_start = DWT_CYCCNT;1 H) C) r, Z% J+ Q \, n
- ticks_delay = ( uSec * ( SystemCoreClock / (1000000) ) ); // 将微秒数换算成滴答数
7 U3 {9 x S5 g/ O& k1 a - ticks_end = ticks_start + ticks_delay;' k) h* z* d, e7 q9 e
! _3 G W" [& J5 F6 ], ^' @- if ( ticks_end >= ticks_start )3 I) }& S: s' `( A Z- ?! o
- {9 Z- O ?8 r5 u4 D
- // DWT_CYCCNT在上述计算的这段时间中没有溢出) G8 F- b$ U C$ r( E1 G
- if(DWT_CYCCNT > ticks_start)
' U6 ]6 j& R0 h2 x5 N6 E# f3 u8 Q - { b' @# [3 Z3 \3 P6 Y9 @* V( ]8 l
- 6 m. u# J9 R: { |, ~$ d
- }
% {3 M, `! u3 Z7 c% O+ x# f V% _ - else6 T0 g: B8 V# S. s
- {
6 H9 N. _: D0 X7 j - * R% M0 s, m; ?
- }
# R0 ~( l2 h2 [+ Y - }& u- m/ _2 ^2 y
- else # U( w! ?; ?9 H3 x" B
- {
$ m+ N) ]0 b) t6 A# y - // DWT_CYCCNT在上述计算的这段时间中没有溢出
+ C5 v" K6 f0 n; I8 ]: V$ u - if(DWT_CYCCNT > ticks_start)
* V, X& ^! c( w9 S - {
: v3 v" s3 g: N/ W
3 X" d' |2 @+ s$ @- }3 y% Y/ S0 Z7 }7 z
- else
/ }: |9 j0 D% \5 b. B - {
- _' m' k: f$ ^$ ]" ` - 2 I" Z2 I2 E6 F" v' H& D2 D$ K
- }
" j; V* ], R, U' t; A8 @: j! U2 J7 a - }
复制代码
* k0 I" _; b/ H9 f2 H$ I7 Y因此需要根据延时等待操作时DWT_CYCCNT所处位置再进行一次判断:: ~/ T) e0 N B- m. @) o
4 l1 v, L1 B% m/ c/ @
& p7 ?/ a* A0 X; G7 }: Q2 i4 U. g8 E8 j6 ~ ]& e/ X2 Y0 I9 A% U) @
ticks_end未溢出时(ticks_end1):8 i. v" h9 u( f+ o9 p+ n1 t
DWT_CYCCNT未溢出(DWT_CYCCNT2 & DWT_CYCCNT3)时:while( DWT_CYCCNT < ticks_end );) K( q! r5 V1 U0 I" r7 |* i
DWT_CYCCNT溢出(DWT_CYCCNT4)时:直接返回。
8 x9 L; l: h0 I$ p+ Rticks_end溢出时(ticks_end2):* G; L$ \# J; |2 n
DWT_CYCCNT未溢出(DWT_CYCCNT2)时:while( DWT_CYCCNT > ticks_end ); while( DWT_CYCCNT < ticks_end );
- _' I9 E4 }. Y" d& V( W6 g( F6 e( n# eDWT_CYCCNT溢出(DWT_CYCCNT4 & DWT_CYCCNT5)时:while( DWT_CYCCNT < ticks_end );
8 O; s$ s5 E, ?3 ?
& u5 a" l8 y+ u1 T, L7 w5 ^
! q9 ]! K, p6 c! k4 v; S p' I; t7 O) o' I5 D% b; V* ~
使用指南$ S0 M9 [% e% g* p4 h' h e
在程序开始的地方初始化延时函数:delay_init();" Y0 I& M9 V# J
在需要延时的地方调用延时函数:delay_us(10);9 D$ h% {4 B8 d
需要特别说明的是,在FreeRTOS的任务中调用本文介绍的例如void delay_us(uint32_t us)等延时函数,任务会阻塞等待延时结束。如果是调用FreeRTOS提供的延时函数void vTaskDelay( const TickType_t xTicksToDelay ),系统会执行低优先级任务直到延时结束。
3 _- P3 x8 s4 ?1 w/ l8 }1 X' |9 u& [# b" W
8 i _% @4 |5 g$ H
' q% p* y; ~! q. q |