你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32延时函数的四种方法

[复制链接]
STMCU小助手 发布时间:2022-4-19 22:00
单片机编程过程中经常用到延时函数,最常用的莫过于微秒级延时delay_us()和毫秒级delay_ms()。本文基于STM32F207介绍4种不同方式实现的延时函数。* k/ C5 j4 R9 H3 q3 I$ j% g

% i' w1 p, ?" B1 E7 n& T- t1、普通延时
+ Z6 e1 y& u" N9 k: v1 e这种延时方式应该是大家在51单片机时候,接触最早的延时函数。这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常用循环来实现,在某些编译器下,代码会被优化,导致精度较低,用于一般的延时,对精度不敏感的应用场景中。
" g; `- S6 n& F( g
6 d0 U  o0 a4 T' ]! J
  1. //微秒级的延时2 @5 y2 w, k. e/ @& h3 U+ W
  2. void delay_us(uint32_t delay_us)% @0 C2 Z% @, {, H  Y5 Z( m/ z
  3. {   
    % e& I3 e8 Z; S, o" L3 |
  4.   volatile unsigned int num;
    9 |8 w% l  @: e7 H8 U5 A
  5.   volatile unsigned int t;
    & z. V9 E' i# g- ]+ n$ H( D# }

  6. ) {& B1 K' L! \& F+ M

  7. 9 T' m- m' _  v/ f4 c
  8.   for (num = 0; num < delay_us; num++)
    $ K4 e# X- ]. v+ U
  9.   {
    4 M! ^9 ]! h/ s; [5 z; b4 n
  10.     t = 11;6 t. D" e0 F3 H6 n
  11.     while (t != 0)3 M4 p. M1 k+ l7 W* N
  12.     {! V- h1 m8 k  {8 M+ ~
  13.       t--;
    ) X6 H$ V& ~  u5 t
  14.     }' I. U& K( W' H- ~* d$ B
  15.   }
    8 `! F9 `. o" D; n$ y$ }3 t
  16. }$ J- W; m/ G$ ~0 V- [% V6 ]
  17. //毫秒级的延时
    " r# M3 B# y/ Y) q/ l6 w
  18. void delay_ms(uint16_t delay_ms)
    % M0 Q* u+ s" l1 h4 N
  19. {    - B+ a( z( T8 s& u4 A
  20.   volatile unsigned int num;
    ) T2 z8 }+ ^. f. l' i
  21.   for (num = 0; num < delay_ms; num++)+ U$ \0 c( L+ S( |
  22.   {4 j  C4 M/ t% a) B3 U0 k
  23.     delay_us(1000);
    0 e4 ?$ v4 ?8 J. b+ {
  24.   }
    ' S7 e; @8 j5 ~# K3 Q
  25. }
复制代码

0 `* O! `4 w& l: {/ ^- l7 O  P+ _2、定时器中断
% \7 j" [: A; w5 N7 V定时器具有很高的精度,我们可以配置定时器中断,比如配置1ms中断一次,然后间接判断进入中断的次数达到精确延时的目的。这种方式精度可以得到保证,但是系统一直在中断,不利于在其他中断中调用此延时函数,有些高精度的应用场景不适合,比如其他外设正在输出,不允许任何中断打断的情况。% p7 r  ?5 h) _+ M1 _) b

! j% e. I+ G3 A. h+ T1 ]5 aSTM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍:4 c* K9 \9 P" T& P% F
: E. ~4 |. Z9 A
初始化SysTick 定时器:
  1. /* 配置SysTick为1ms */
    ( V' ^+ ^8 R9 V
  2. RCC_GetClocksFreq(&RCC_Clocks);1 `6 B. G  l% _$ o+ d
  3. SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
复制代码
! o/ B! h  a7 C) s( q
中断服务函数:
/ @5 ]. r" P; B* N2 @$ Z8 v3 _- |; f
  1. void SysTick_Handler(void)  |9 X9 k3 l" Q/ ~* |/ n  i" g  z& B
  2. {  V9 f6 j4 b" \
  3.   TimingDelay_Decrement();
    ' P% F- g6 Z# |: m, L
  4. }
    ! `8 g0 b5 v- {" k7 r
  5. void TimingDelay_Decrement(void)7 H' z9 A4 T) h! V
  6. {
    ' K5 l8 e! A$ \6 v9 T  Z
  7.   if (TimingDelay != 0x00)0 \, S; [: {0 s$ X* {; J  F  i
  8.   { ( N- n2 N  x. \! X9 `1 T
  9.     TimingDelay--;
    # L; u9 R* P, z) _& m4 t
  10.   }0 {/ W! S. S7 `* V' B/ e2 e+ k$ U, |
  11. }
复制代码
& m  x4 P0 O% \/ E  s
延时函数:0 A  ]$ ?" V5 H: W  F3 f

5 B( ?. Y3 S2 _/ V% t% e& A
  1. void Delay(__IO uint32_t nTime)$ `; o. x8 n. z! H, L* e
  2. {2 o8 X5 j6 ]" o' y$ \3 }, y/ |
  3.   TimingDelay = nTime;
    * m* V3 q( p% f3 Z7 W
  4.   while(TimingDelay != 0);9 U" _; w( H- l6 }8 j
  5. }
复制代码

9 P3 l) R+ }3 ^; `, {  S8 n: b3、查询定时器; F6 ?, }) ~% |4 u, E5 L2 o# o
为了解决定时器频繁中断的问题,我们可以使用定时器,但是不使能中断,使用查询的方式去延时,这样既能解决频繁中断问题,又能保证精度。- K$ M# \( \- D5 t, p( u
5 z# N- l+ X" Y' M+ ?! ~
STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍。
( Z8 \" V* N% a2 q8 s8 v1 S# ^  C* f. d) q
STM32的CM3内核的处理器,内部包含了一个SysTick定时器,SysTick是一个24位的倒计数定时器,当计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。1 z1 h. Q+ v9 O% \: v3 c/ u9 ]) q
0 \/ p1 e" r) V* {& G" f9 x/ s4 }  K
SYSTICK的时钟固定为HCLK时钟的1/8,在这里我们选用内部时钟源120M,所以SYSTICK的时钟为(120/8)M,即SYSTICK定时器以(120/8)M的频率递减。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。
1 f0 J* h* L$ o
: J5 T+ ]) R" M6 C) p* {2 @6 }▼CTRL:控制和状态寄存器
5 u5 a5 }; @4 D- u5 L
3 ^, _; {  m: z aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy8wUmc4RGRwQWliZlVaTzVIMFU4dlliOWliMFBp.png 8 q! J& p7 m; y  m+ e& d
4 M4 M$ Z' Z. J4 j1 d- k
▼LOAD:自动重装载除值寄存器
( {0 m8 b0 S$ K- I; Q' v
1 i7 @1 O& |3 S) E aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy8wUmc4RGRwQWliZlVaTzVIMFU4dlliOWliMFBp.png
1 L6 \: Z( q* v# b, ^( F2 _( n* [+ B8 i$ k3 f9 x+ L  _
▼VAL:当前值寄存器' ~: Y' x& {+ C/ N- C1 @3 ], S

/ k$ U2 ?# o! x2 ]. v aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy8wUmc4RGRwQWliZlVaTzVIMFU4dlliOWliMFBp.png 0 z& n+ v' R" K3 {3 \
; h& v& c( |) n. X$ ]$ `3 t
▼CALIB:校准值寄存器
* o% u( |' |% u2 C( s" l
8 O# J& X5 t0 ^使用不到,不再介绍7 t8 @$ c, _4 s5 u  @: K) h
4 ]1 N9 l1 k- b/ A# d! q
示例代码
0 o0 v8 F3 l& `; V1 i. h
" h6 K; s7 J1 i. ?
  1. void delay_us(uint32_t nus): e- t- ?1 @. }4 }* N1 a: m3 F
  2. {
    5 h2 B, a* w+ o' k
  3.   uint32_t temp;5 F1 Y0 R8 x% c( K* c( q
  4.   SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;
    2 V7 g+ s# t' A+ O
  5.   SysTick->VAL=0X00;//清空计数器' E. v, r& B; O7 W& G7 @1 X
  6.   SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
    0 c( a9 j5 u( a, o+ `: N
  7.   do0 P; k* K$ V/ @6 J8 l8 f
  8.   {, |5 ?% V& a1 ^6 t2 ^
  9.     temp=SysTick->CTRL;//读取当前倒计数值
    ! b* F+ _$ }8 m: r5 a
  10.   }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达4 z! Z2 O/ a, C& E; _
  11.   SysTick->CTRL=0x00; //关闭计数器
    : z. ~( n8 |* Z' D
  12.   SysTick->VAL =0X00; //清空计数器% B  N: m5 a* V" o, R! o6 ]
  13. }! ]: o2 f! H$ }& ?
  14. void delay_ms(uint16_t nms)
    6 _/ Q1 f9 S# V. k5 y
  15. {
    ' t& e# q3 t" |# O2 H
  16.   uint32_t temp;0 n7 y& l0 t- A$ S2 Q. ~: R
  17.   SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;- n& b9 D. i$ H; a% r
  18.   SysTick->VAL=0X00;//清空计数器
    * \1 G  h: O* l# y! }# W3 R" O
  19.   SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源( J9 s: O; e; {7 F/ u
  20.   do
    9 `+ Y* p, W  X' k- h* }
  21.   {
    ' u$ E# i: F; T3 i9 s$ c+ q" D
  22.     temp=SysTick->CTRL;//读取当前倒计数值
    " H, M4 [% r  H) |" O) T
  23.   }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达8 @, R6 J( X5 S
  24.   SysTick->CTRL=0x00; //关闭计数器
    9 Q* @3 W+ I: {) Z- f# u
  25.   SysTick->VAL =0X00; //清空计数器  p2 x; b1 [# L9 }+ T
  26. }
复制代码

0 D" o1 {4 n: E) l4、汇编指令
, b5 a  H- g1 v  v如果系统硬件资源紧张,或者没有额外的定时器提供,又不想方法1的普通延时,可以使用汇编指令的方式进行延时,不会被编译优化且延时准确。
, a- k3 c) |( ]
+ N5 t0 Y3 y0 _% f6 LSTM32F207在IAR环境下
  1. /*!
    : L$ p3 D+ R; ]4 e
  2. *  @brief         软件延时
    2 U3 ~+ E% S) q1 y. t
  3. *  @param        ulCount:延时时钟数
    ; M1 n9 e$ M4 l
  4. *  @return none
    5 A, x; J0 K) {( }# j7 V! @& u
  5. *        @note         ulCount每增加1,该函数增加3个时钟
    ) H5 V8 Z7 e/ \0 c8 f; N7 \8 O3 u
  6. */2 o6 x6 U6 ]$ I6 e; d" T- P
  7. void SysCtlDelay(unsigned long ulCount)
    : b8 F0 ^8 T  P9 h
  8. {
    - B  g4 b! G/ H- V: n
  9.     __asm("    subs    r0, #1\n", a/ x8 p5 T( ]
  10.           "    bne.n   SysCtlDelay\n"
    8 q+ F& h2 G0 u4 F7 J0 M5 c5 Q
  11.           "    bx      lr");8 w! a7 ~$ {3 T4 u
  12. }
复制代码
! n2 @' y' |* Y
这3个时钟指的是CPU时钟,也就是系统时钟。120MHZ,也就是说1s有120M的时钟,一个时钟也就是1/120 us,也就是周期是1/120 us。3个时钟,因为执行了3条指令。
) X! P. X0 @, o1 I3 I
  ^, ~9 r" I0 v3 I# o4 t使用这种方式整理ms和us接口,在Keil和IAR环境下都测试通过。9 {) ?9 J, H2 F
3 M4 y% x) I0 m! Z! E6 V' h- u
  1. /*120Mhz时钟时,当ulCount为1时,函数耗时3个时钟,延时=3*1/120us=1/40us*/. M3 A! ~! [5 ^& W  A) }3 o2 @
  2. /*' Q& b$ @7 H) b
  3. SystemCoreClock=120000000: C# z5 F7 `: ], p
  4. us级延时,延时n微秒4 P* ~! g6 Y& j7 @$ X
  5. SysCtlDelay(n*(SystemCoreClock/3000000));
    7 \5 ?# @. s' u9 @0 q+ n+ R
  6. ms级延时,延时n毫秒- {3 h' ~3 X. a* M  I
  7. SysCtlDelay(n*(SystemCoreClock/3000));
    9 g4 W; a# H7 u
  8. m级延时,延时n秒
    9 @# U6 G/ G) C
  9. SysCtlDelay(n*(SystemCoreClock/3));
    : o7 k- T  k+ ?- B6 Y
  10. */5 W* w! `) c' R& ]/ X  m5 a# G9 _3 w1 b

  11. " Q6 M- Q! Y5 l. c, F; `
  12. #if defined   (__CC_ARM) /*!< ARM Compiler */" S9 R; V3 }# x! M9 Z' L
  13. __asm void
    : m, o4 o: B" Q: C4 i1 m( S$ T1 J
  14. SysCtlDelay(unsigned long ulCount)' G' v1 r5 A1 Q, U; o! Z
  15. {
    7 P2 H6 a% u; e( Q7 y
  16.     subs    r0, #1;
    / _, Y0 x7 l$ I/ n3 ]2 V
  17.     bne     SysCtlDelay;. K" l2 O& X# O' Y& I% k! L3 M
  18.     bx      lr;; `0 @. h# |% c& j; n5 L+ g0 g! H1 [
  19. }$ \1 @6 z0 f. U8 A) Y- M" K9 V- s
  20. #elif defined ( __ICCARM__ ) /*!< IAR Compiler */
    1 Y4 W# y" N0 o( i3 o  c1 R
  21. void
    8 ~  d/ L9 h. u5 Q. i7 v
  22. SysCtlDelay(unsigned long ulCount)) l1 R6 z$ p/ R4 ^( K. e
  23. {( m3 ?" R7 k6 t" g4 C2 P1 ~, l3 C
  24.     __asm("    subs    r0, #1\n"
    8 b: S# H1 ]! {+ |7 {# \
  25.        "    bne.n   SysCtlDelay\n"
    : L/ p" _! p1 Z% U9 ^
  26.        "    bx      lr");1 M) u  p0 x: t) `, Q) I; `
  27. }6 k* u. {6 p$ t8 Y  [# e" R

  28. ( h1 j7 m3 l" |' N) ^
  29. #elif defined (__GNUC__) /*!< GNU Compiler */) ?  B: O" l& v. W& K
  30. void __attribute__((naked))0 }; E2 H  ?1 W2 f
  31. SysCtlDelay(unsigned long ulCount)
    8 K8 ~5 l. r2 C! V% P, p6 h5 B
  32. {
    1 C4 R: l+ w# ]2 G- P( N$ f( P' C  K
  33.     __asm("    subs    r0, #1\n"
    ; ~; B& T1 _$ d8 o/ G2 i/ f8 z
  34.        "    bne     SysCtlDelay\n"
    * d' u7 x$ H: J4 w( T
  35.        "    bx      lr");# t' }1 c5 [! p/ H8 X- }
  36. }1 ]1 X6 ?- Z: O4 H; J7 P
  37. ! ?1 J& |: q5 w+ G( ~7 v
  38. #elif defined  (__TASKING__) /*!< TASKING Compiler */                           1 p' S! r! _: E+ E" u
  39. /*无*/+ S, q- C2 |2 i
  40. #endif /* __CC_ARM */
复制代码

& v$ k, n1 y3 `- |7 N2 P备注:
& c% L/ Q& K- o# ~) J5 N8 G0 Z
3 c' K2 j9 x, ^8 g& q理论上:汇编方式的延时也是不准确的,有可能被其他中断打断,最好使用us和ms级别的延时,采用for循环延时的函数也是如此。采用定时器延时理论上也可能不准确的,定时器延时是准确的,但是可能在判断语句的时候,比如if语句,判断延时是否到了的时候,就在判断的时候,被中断打断执行其他代码,返回时已经过了一小段时间。不过汇编方式和定时器方式,只是理论上不准确,在实际项目中,这两种方式的精度已经足够高了。. O. S) s0 c" D% }( e' [

. \, l9 {& \. E, L% e
+ o" n  j% ]  q. @! a& b% y
收藏 1 评论0 发布时间:2022-4-19 22:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版