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

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

[复制链接]
STMCU小助手 发布时间:2022-4-19 22:00
单片机编程过程中经常用到延时函数,最常用的莫过于微秒级延时delay_us()和毫秒级delay_ms()。本文基于STM32F207介绍4种不同方式实现的延时函数。$ M  f  P  J1 m  n, g
3 ?/ _) n/ G6 _% [- S3 g- R& @
1、普通延时
0 N; ^2 @* F( e( {7 t' C这种延时方式应该是大家在51单片机时候,接触最早的延时函数。这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常用循环来实现,在某些编译器下,代码会被优化,导致精度较低,用于一般的延时,对精度不敏感的应用场景中。! a  _) p; S! q/ z
& y( e  d* ^' ^) }+ v, G3 d
  1. //微秒级的延时
    9 o+ M) \, b  O; }
  2. void delay_us(uint32_t delay_us)
      }- z7 S, b- F: _. o! e1 o
  3. {    , ~7 m: Q% h$ I3 D4 P# q
  4.   volatile unsigned int num;
    8 _* M% @4 U" I8 h
  5.   volatile unsigned int t;
    2 ]) \9 o9 A# m: @$ E
  6.   K- {2 `9 W9 N  x5 Z; t8 F

  7. 1 Q$ @$ `9 m; \7 H2 {) W! Y' e
  8.   for (num = 0; num < delay_us; num++)
    + ?- a5 ?, A; R( b. I% G3 s
  9.   {
    $ S& L8 c  H# r) G/ L4 R, D$ u
  10.     t = 11;
    2 g* Y, z9 j, e" H
  11.     while (t != 0)% K. }( V/ j0 t! Q  e7 e1 _; N
  12.     {
    # g. T9 ]6 X: }- e
  13.       t--;
    4 P) \! c+ X! f$ F9 m  J
  14.     }" o8 l; d1 P# a
  15.   }
    3 B- W% }+ x, |5 z8 t" l* y. B
  16. }
    & S& m  m1 b5 j& S
  17. //毫秒级的延时8 f# N( T4 P* V+ ]: S
  18. void delay_ms(uint16_t delay_ms)
    - `  S9 y3 `8 f5 z% z9 ^
  19. {      i2 ]- W! ~. y( [( X
  20.   volatile unsigned int num;
    3 Z) A% [. q  d5 ?
  21.   for (num = 0; num < delay_ms; num++)
    & Q% Q% e/ i' d+ v
  22.   {- o% e& K7 b4 c& O9 t
  23.     delay_us(1000);+ k* g6 n  h2 V& t8 m
  24.   }. Y2 b3 M( E4 |9 F$ y& S5 ?5 p! |
  25. }
复制代码
; K9 x2 C8 u: a/ K8 z
2、定时器中断  D, D# |- t- \: A
定时器具有很高的精度,我们可以配置定时器中断,比如配置1ms中断一次,然后间接判断进入中断的次数达到精确延时的目的。这种方式精度可以得到保证,但是系统一直在中断,不利于在其他中断中调用此延时函数,有些高精度的应用场景不适合,比如其他外设正在输出,不允许任何中断打断的情况。
# z3 ~) ~# H- {$ d9 l' r+ J) R/ i$ ]3 z
STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍:2 j4 `" Y3 `# i2 B# w
  Q( u% }) i( S, s, F# _$ u* H
初始化SysTick 定时器:
  1. /* 配置SysTick为1ms */* u8 b; A0 [. j1 F$ L. k# W5 O
  2. RCC_GetClocksFreq(&RCC_Clocks);1 `4 i3 R" O" r. R) Z
  3. SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
复制代码
: W7 M7 l+ Y% `0 ^
中断服务函数:% t/ O4 n! s9 d. `( H/ k1 k
& i1 ~5 I, Q% D, s4 O
  1. void SysTick_Handler(void)7 H; J) P" r4 }3 M8 E9 q6 H
  2. {+ W! K7 B0 @8 i4 W. B
  3.   TimingDelay_Decrement();8 ?! y* U' k$ m" r! H/ m
  4. }7 W' r; x+ V- I6 d9 E6 Q
  5. void TimingDelay_Decrement(void)2 A/ l2 l7 r/ N, V
  6. {
    : f9 n/ H  g5 B3 z& U7 Z0 P& W9 E
  7.   if (TimingDelay != 0x00)" p0 l, [7 n7 p: ?- ^  B1 |! L. Z
  8.   {
    6 b2 M6 F- y7 M  a/ v$ \; m
  9.     TimingDelay--;  R! ~; i% t: {1 {* O
  10.   }
    8 o; t& Q& |% G
  11. }
复制代码

6 G5 s& ?* O& [: T! w: n9 |延时函数:) x, u4 O) h) F1 j0 e( c  P

+ d  a9 N- [0 m' `0 [) v. W, N; L( S/ N
  1. void Delay(__IO uint32_t nTime)
    . r3 L! \. `# Q/ _1 [- }
  2. {
    ' w; ]# a; |# E( `
  3.   TimingDelay = nTime;
    " O; e) f; q* B( A  G4 p4 c
  4.   while(TimingDelay != 0);1 R0 u" ?6 B! I# f$ W4 F3 h
  5. }
复制代码
+ Y* k, |! p% N
3、查询定时器; H9 Q9 w' |+ P1 u2 _* w" l
为了解决定时器频繁中断的问题,我们可以使用定时器,但是不使能中断,使用查询的方式去延时,这样既能解决频繁中断问题,又能保证精度。% z& K# q2 I! Y8 |! U8 K  ]+ e* K5 K
  F2 A5 Z" k  _, ~( @
STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍。
0 y3 m, x* p% U3 T1 r3 Y6 J# L$ U: n/ @" t$ G2 y
STM32的CM3内核的处理器,内部包含了一个SysTick定时器,SysTick是一个24位的倒计数定时器,当计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。
/ t$ u  E( t4 m7 o$ s* ^" U: w/ [* l' r+ j
SYSTICK的时钟固定为HCLK时钟的1/8,在这里我们选用内部时钟源120M,所以SYSTICK的时钟为(120/8)M,即SYSTICK定时器以(120/8)M的频率递减。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。8 b8 C* Y# _- Q* E5 _

2 N" z! {. e6 R) G3 y9 h7 I▼CTRL:控制和状态寄存器9 s; C: j% c6 o8 Z& ^! R$ l
2 Q3 G6 U+ c' i$ J3 Z
aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy8wUmc4RGRwQWliZlVaTzVIMFU4dlliOWliMFBp.png # V3 N4 c# ]: t& p# A7 F3 O

' K, y9 G  V2 S  H' h▼LOAD:自动重装载除值寄存器  R/ K" W1 F. u4 V; T2 _

  ^9 n6 {+ c/ [5 {3 a aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy8wUmc4RGRwQWliZlVaTzVIMFU4dlliOWliMFBp.png . E- i  w) @; s8 R

/ G3 W" |) U7 W/ E  p5 l6 \* G ▼VAL:当前值寄存器
! H  Y8 P% Y) E, p2 R" \- M2 Y. U6 f
aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy8wUmc4RGRwQWliZlVaTzVIMFU4dlliOWliMFBp.png + K+ @1 O, |/ D# b! x2 g
8 _5 a7 p( i( F) ^. e% ?
▼CALIB:校准值寄存器
' y* c1 g/ u5 d) c  Z% ?" k* D8 `
5 W0 `4 ?! X9 T4 a8 [! @使用不到,不再介绍- }: I! j8 O6 @( }7 ^- L; Y
& p' l/ A* B2 g. m
示例代码
8 e' p, j! ~' G6 L# x- T6 k) b* [. ^  ~, `  @0 U( O9 ~
  1. void delay_us(uint32_t nus)0 C- ]  J9 p" ^7 l
  2. {) l$ S5 ~* i) A; }7 P+ Y
  3.   uint32_t temp;4 |) D) L: z* O0 K" I0 y& C
  4.   SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;0 V4 L8 a* N4 a: _% n. o
  5.   SysTick->VAL=0X00;//清空计数器0 {9 x/ A5 ?4 x( C% I9 `/ j
  6.   SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
    & x7 l7 L1 v% `, ~
  7.   do
    . p& ^' [) P6 K# f+ [  v7 t
  8.   {
    * d& \+ u+ d- o: e3 B! [! B
  9.     temp=SysTick->CTRL;//读取当前倒计数值5 C! d* O  ]7 B
  10.   }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达6 l3 Y8 u  T. k, N, r0 }6 L
  11.   SysTick->CTRL=0x00; //关闭计数器* U5 E3 R' r4 p7 m; j3 P5 M
  12.   SysTick->VAL =0X00; //清空计数器
    - O) ^, n$ X% i" n
  13. }
    8 |. R* x2 ]6 l3 j
  14. void delay_ms(uint16_t nms)
    7 T- t* p, S0 J
  15. {: a, {" L- q) y# S- P4 r
  16.   uint32_t temp;
    0 d" ^, p! h( g" K9 G3 Y+ _/ {
  17.   SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;1 L. T. n  V9 U. C- y4 t/ L
  18.   SysTick->VAL=0X00;//清空计数器
    ( z) }- f! o2 @: o
  19.   SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
    8 _& p- ~8 T  h* V; ?, y
  20.   do5 f  e/ R0 T3 r% ]6 ~
  21.   {
    $ Z8 _  |2 f* ~$ R
  22.     temp=SysTick->CTRL;//读取当前倒计数值6 D) ]; O/ _4 h* E( e6 e/ Z+ ~
  23.   }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
    . Z4 f0 c. N' }
  24.   SysTick->CTRL=0x00; //关闭计数器
    & m# p, z* ^' |+ }' n( r
  25.   SysTick->VAL =0X00; //清空计数器+ Y. |( L" o# ]: a1 ]
  26. }
复制代码
) s2 b, A3 V. `
4、汇编指令! k& t; ~% a9 `
如果系统硬件资源紧张,或者没有额外的定时器提供,又不想方法1的普通延时,可以使用汇编指令的方式进行延时,不会被编译优化且延时准确。; i, K2 q. F0 u! V& a2 b

2 c2 i$ Q! Z9 J9 ]: h- `& u$ mSTM32F207在IAR环境下
  1. /*!: {" `% W; U; Z
  2. *  @brief         软件延时 4 B( x1 r" o# q- z( m: c, N0 Q! b, s1 y
  3. *  @param        ulCount:延时时钟数1 J2 b! ^) G7 E( ]2 E
  4. *  @return none. u% I( [$ T1 `2 O& S3 J7 q
  5. *        @note         ulCount每增加1,该函数增加3个时钟2 ]2 T" T6 c$ T* t7 q
  6. */  _4 N4 w- Y( D4 z5 o
  7. void SysCtlDelay(unsigned long ulCount)" m6 }3 C; x' c- V1 o
  8. {
    4 z. s+ E2 {1 v" R8 ]
  9.     __asm("    subs    r0, #1\n"9 e8 G  P/ e' J* F
  10.           "    bne.n   SysCtlDelay\n"
    . y: A# X* @: ~& q
  11.           "    bx      lr");
    8 K. E2 |* q! P0 w) ?! \
  12. }
复制代码
3 `( @8 y' N4 P8 o
这3个时钟指的是CPU时钟,也就是系统时钟。120MHZ,也就是说1s有120M的时钟,一个时钟也就是1/120 us,也就是周期是1/120 us。3个时钟,因为执行了3条指令。+ j8 Z# o, R, J7 j' ]9 H7 \
. [7 E& R0 l; _. L' [* t# u
使用这种方式整理ms和us接口,在Keil和IAR环境下都测试通过。
. ?5 U; v5 @3 L& {' Z1 t
4 f; \# T8 e% l6 L1 w9 r0 ~& J; s# z4 \
  1. /*120Mhz时钟时,当ulCount为1时,函数耗时3个时钟,延时=3*1/120us=1/40us*/
    1 V8 f+ o) o# S" g# P( V
  2. /*$ [! s( q+ |- U: m
  3. SystemCoreClock=120000000
    / M. g2 e9 R5 }2 n
  4. us级延时,延时n微秒
    & m8 b, D' Z* k: v  P( B! |7 y& ?) o
  5. SysCtlDelay(n*(SystemCoreClock/3000000));6 ?4 @7 l0 c7 y6 i' G
  6. ms级延时,延时n毫秒' u4 Y$ y9 y8 r  d$ z2 X" h. T9 U
  7. SysCtlDelay(n*(SystemCoreClock/3000));. A2 [: c, @8 t8 @3 ~' F
  8. m级延时,延时n秒
    7 A( i6 G& C2 A( _6 U$ o
  9. SysCtlDelay(n*(SystemCoreClock/3));
    5 c, D1 u4 U% F/ ^8 h& }; j7 I) E+ |
  10. */) m+ G! C( ^+ o% ~
  11. * F0 P* s2 f6 r- {) z) I
  12. #if defined   (__CC_ARM) /*!< ARM Compiler */
    8 C6 w7 T. e" x/ O
  13. __asm void3 c3 I' o3 M8 K
  14. SysCtlDelay(unsigned long ulCount)/ E2 i. Z) W& s
  15. {
    8 ?- q0 O4 V3 o+ w! W1 [
  16.     subs    r0, #1;9 k% x& b, F" P0 h$ U7 s& {$ M
  17.     bne     SysCtlDelay;
    5 e' @1 C4 f9 b+ w. {5 q
  18.     bx      lr;
      b" E  ]: I5 U
  19. }7 ^8 H8 N4 h6 {2 v* h# ^6 Q
  20. #elif defined ( __ICCARM__ ) /*!< IAR Compiler */
    % V. p/ F6 C6 E
  21. void( r7 k! }; M9 O- g( Y, K9 i
  22. SysCtlDelay(unsigned long ulCount)# I7 n# a+ a4 _: v
  23. {1 s- V% P1 {* {( L" K/ X
  24.     __asm("    subs    r0, #1\n"
    2 R- o+ f) g( E
  25.        "    bne.n   SysCtlDelay\n"
    ' E/ x$ j' M: g" t$ }+ t
  26.        "    bx      lr");- d8 K0 A1 o/ ?$ Q0 C7 ~* s- V
  27. }
    ; f& x( y! r6 H+ g1 g2 X. _8 `
  28. 6 h. G# z) [! E
  29. #elif defined (__GNUC__) /*!< GNU Compiler */# w! q9 L' d6 m( S% {
  30. void __attribute__((naked))8 A+ E$ f4 Z+ t( h, u$ q
  31. SysCtlDelay(unsigned long ulCount)* `6 m, v' U+ F! l4 _/ k$ K/ s
  32. {- ]' m. z2 l! V+ v9 V* W
  33.     __asm("    subs    r0, #1\n"
    , s2 t& Z& E# D! [7 L
  34.        "    bne     SysCtlDelay\n"
    ( b( ^) t9 @! S' p& m
  35.        "    bx      lr");
    1 X- X( Y8 z) d9 x9 d: Q( K0 p6 V
  36. }  ^- s* ]' G. @9 s
  37. ! j' H' T. T6 L$ p
  38. #elif defined  (__TASKING__) /*!< TASKING Compiler */                           
    ( u, w8 t5 X5 ]  n. G) f+ B7 Y- X9 Y
  39. /*无*/
    , ]+ N# g& u' m. b' H  W# ~2 r+ ^
  40. #endif /* __CC_ARM */
复制代码

; `5 T' k8 c  e" d2 L备注:1 C6 s4 C: ]5 ^
6 k6 S# @9 Y! x) o+ \6 ^$ M
理论上:汇编方式的延时也是不准确的,有可能被其他中断打断,最好使用us和ms级别的延时,采用for循环延时的函数也是如此。采用定时器延时理论上也可能不准确的,定时器延时是准确的,但是可能在判断语句的时候,比如if语句,判断延时是否到了的时候,就在判断的时候,被中断打断执行其他代码,返回时已经过了一小段时间。不过汇编方式和定时器方式,只是理论上不准确,在实际项目中,这两种方式的精度已经足够高了。6 X+ {, `1 P8 n" a) Q0 E
; J) g8 i  B1 s) a+ b( d6 b$ |0 h' _

% q2 C/ ]1 a/ K: i4 w' o
赞 收藏 1 评论0 发布时间:2022-4-19 22:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版