1.SysTick定时器初始化流程
) } }2 f: c: T2 K) B( B<1>调用SysTick_Config()函数完成SysTick定时器初始化。5 O f) j& n7 R2 H: y, }# ]/ Q
<2>完成中断服务函数,在中断服务函数中递减定时变量,当定时变量递减到0时即到达定时时间。
7 b6 J# ]5 g+ ~# U<3>封装延时函数以供调用。' c8 v9 i* j4 h. I
/ X) L8 S0 D0 Z2.SysTick定时器中断延时例程2 z3 x) h) X8 H$ \" L# G
- <font face="微软雅黑" size="3">#include "systick.h"! O, ` F' S6 A& Y! R% x& N
- #include "misc.h"
5 k. R& K3 k1 v - #include "core_cm3.h"
1 X4 |: `3 W5 b% R. b* P1 ]
5 N& y# a8 j# E- static __IO uint32_t delay_ticks; //__IO == volatile & e, J, l f7 y' N
- + U! C# J2 V, K
- /*freqency为systick计数周期的分频系数*/
* P+ d, z! n4 Q# G" E- q- P1 T/ U - /*freqency一般取值100000,10us中断一次*/
) i* U9 w' ?0 z% O+ x0 F - void SysTick_Init(freqency)8 ^, A6 s1 Y! s7 ?* T, U, `
- {% ^0 f9 l" z( ^) Y$ ^0 I- `4 i5 c# f
- /* SystemFrequency / 1000 1ms中断一次
0 g8 i% @- \4 j( ?- X# Z - * SystemFrequency / 100000 10us中断一次
; k, @7 ^9 o8 s9 n, b. s - * SystemFrequency / 1000000 1us中断一次
, E/ i& l9 i* R' x - */- J5 d3 `$ [# t5 T2 ~ \; v( w
-
: w: u ^% r c0 Z( {$ | - //初始化并启动SysTick计数器及其中断。
0 ^% z* [. U1 W- k - //1 = failed, 0 = successful6 {. G: W! \' f+ y9 H ]: l
- if (SysTick_Config(SystemCoreClock / freqency))! P3 ~* ~4 B+ O
- {
( f6 P$ k7 y$ M - /* 捕获到错误,SysTick启动失败 */
. Z$ g: J# A9 C - while (1);+ s6 U! P6 i9 M$ W7 ]
- }
. c( f. a! \! T+ ^& H' q - }9 S9 t- W% i) f$ L
7 `, B: P: b1 Q y) d) ]2 R- /*该函数延时times个1us*/
# ?, _0 ~5 ]' ~% I# i. O5 b9 I - void delay_us(uint32_t times)
$ N- m% K9 ]. g# @ - {
. f) s7 J! }4 t- l: y0 u' e2 ] - delay_ticks = times;# D) c+ [. Z! H$ z/ s
- SysTick_Init(1000000);
0 b+ U A _' p6 \8 C - while(delay_ticks != 0);3 V. H' E* S* F D
- & n# O {! v' ?! C. \ |$ T
- SysTick->CTRL = 0x00; //失能SysTick+ k9 L$ I. e& Z( K1 K4 q) O: M
- SysTick->VAL = 0x00; //当前值清零
6 g+ Y) |, z, d- Q* j* _1 ?5 q5 P - }
$ `6 o5 f/ G: c: l, o. Q - 2 s5 U5 Z; g/ R% y L) W% W1 q
' g1 x M5 n5 d$ a0 {5 ~- /*该函数延时times个1ms*/
/ A2 H7 L1 h. W% N3 w3 J - void delay_ms(uint32_t times)
$ I% ~7 R# U* Y. ?& Z4 Y" V - {, ~1 @' z. ?! X4 L9 W
- delay_ticks = times;
. e3 H7 w% |- P$ o - SysTick_Init(1000);
( v7 F4 k8 W, [- M - while(delay_ticks != 0);# v6 H& H i& e/ a' m3 @
-
! t6 ^! o! B6 l5 T' |; j - SysTick->CTRL = 0x00; //失能SysTick
: D/ Q" u8 i5 O. b5 q* Y7 L - SysTick->VAL = 0x00; //当前值清零
+ o, ?9 e1 S! H6 A% B - }
9 x, Y! J" ~0 [$ b5 k6 z
8 K3 H6 p) B( B/ d7 F7 G- /*在中断函数中判断是否到达定时时间*/7 D) W/ e" Q4 z3 g) T# f
- void SysTick_Handler(void), m$ k5 Y7 C7 T8 t; j8 h
- {0 f m4 Z' @. K& k
- if(0 != delay_ticks)
; w$ P! H8 d5 ^5 k3 {& J, o% u& z - delay_ticks--;
& I4 f2 @ I6 P8 p4 Z W - }
% k! Z& a9 E4 N. o' ?, E+ x - </font>
复制代码 这种延时函数只有在调用延时函数后SysTick才会开始计数,相较于野火那种一开始就完成SysTick初始化,然后一直SysTick一直计数,永不停歇,调用延时函数才阻塞的方式我觉得有以下几个方面的优势。
# ?$ s7 p2 m9 h, s
. L4 w3 {0 X) N/ [* b3 }
. [( M) r. K y, n2 ^3 E1:降低MCU功耗,定时器工作毕竟是耗电的。只有在调用中断延时程序时才打开SysTick定时器,函数调用完成后就关闭SysTick定时器- b, o9 v! v# F, \! |- ^
2:提高程序稳定性,野火的方式下SysTick定时器中断一直在发生,只不过没有调用延时函数时,中断里什么也不做。在多任务多中断程序中,这样的延时方式可能会有潜在的风险。
# Q; M* C! N2 y6 ~* ~3:降低MCU负担,如果在野火的方式SysTick初始化为1us的中断,那么,每1usMCU就要响应一次SysTick无意义的中断,浪费了大量系统资源。并且频繁的进出中断会让主程序无法进行。
( [7 R2 T* g7 ?# s# v ?注意:使用这种延时方式在普通情况下是可以的,但是一旦在其他中断中调用此延时函数,便会使程序卡死,比如在按键外部中断中进行按键消抖延时。
4 K B) O4 F9 }3 e! P0 N- <font face="微软雅黑" size="3">void EXTI1_IRQHandler(void)6 P* R3 x# N1 w Y( S! g) \4 x
- {* F$ s5 n# h; W$ k
- delay_ms(10);/*此时采用SysTick定时器中断延时就不妙*/
5 J/ L6 b. ?) i. O8 ? - if(key == 0)% ]0 |8 ]' R' o# P# p
- {
* i, W5 U% M+ V - LED = ~LED;6 f% q% t$ x5 J- K8 v
- }: T( f0 r$ u1 ?7 ^) p" {
- EXTI_ClearITPendingBit(EXTI_Line1);* M+ v, a+ t, v+ Z
- }
# ^- [. w3 X% f$ I' O - </font>
复制代码 原因 :触发中断进入延时函数后会死在 while(delay_ticks != 0)这个循环中; 这是因为SysTick_Handler(void) 函数是内核中断,内核将其中断优先级设置为最低,导致在外部中断函数里无法抢占执行,此时延时参数delay_ticks也就无法减小,程序在此处卡死。# f/ a) n, c+ s0 B8 t
* p4 g$ c! d" O; H
8 {) l& A% e% t解决:此内核中断优先级的设置在core_cm3.h中 ,比如将优先级设置为0后可以抢占使用,但是需注意调用延时函数的中断的优先级要小于systick所改的优先级,经实验是可行的。! [2 G; F5 }! n3 v
- <font face="微软雅黑" size="3">static __INLINE uint32_t SysTick_Config(uint32_t ticks)9 {/ C q% m: e; Q% M
- { 2 e9 Y- ?- }4 {3 {
- if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ j" C: D- r$ @2 G& ~$ ]
-
2 Y9 h/ S! _1 J( l& | - SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */9 ]3 D& z# W1 ]% }8 r4 e* B
- NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /*中断优先级设置为-1,这也是系统默认的设置 */ ! v$ D& e3 x X, u
- //NVIC_SetPriority (SysTick_IRQn, 0); //将中断优先级改为09 V" m7 i: p* x9 X
- SysTick->VAL = 0; /* Load the SysTick Counter Value */
; N) j1 A: ?" G7 r - SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
# e) \% w2 K! W1 B$ r1 { - SysTick_CTRL_TICKINT_Msk | 6 P$ K6 M$ q/ H' S
- SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
1 K; W6 h: u- s+ l8 I - return (0); /* Function successful */
}) K' @3 o) `8 @* a. ]6 j - }
" { X/ _* O/ G4 M1 s4 }+ H* G1 w - </font>
复制代码 3.SysTick定时器查询延时例程0 W3 ~2 A/ Q g6 I7 j4 }
这种方式利用设置Systick时钟的重装载值,然后重装载值倒数完毕后会将SysTick->CTRL的位15置1,对该位进行判断便可知道延时完毕否。这种延时方式无需进入 void SysTick_Handler(void) 中断,在中断中使用也不怕优先级问题了,就不需要改内核文件了。这种方式虽然看起来有点繁琐,但是也不难理解。\
7 Y1 [! j$ D; K* U- <font face="微软雅黑" size="3">#include "delay.h"
! z' U1 z/ z* Z9 N# i -
9 I6 _/ G# K5 v# H* G0 r - static uint8_t fac_us=0; //us延时倍乘数
! Q# {# x. M$ b! Q' [8 M0 b - static uint16_t fac_ms=0; //ms延时倍乘数
& \/ ?' p( v9 J7 C8 y -
5 P: J$ \+ T! K: W - //SYSTICK的时钟固定为AHB时钟的1/8
0 o1 F' J; t& ]1 H$ ]7 i - //SYSCLK:系统时钟频率/ O7 z) D* W9 `* j0 c
- void SysTick_Init(u8 SYSCLK)
' F4 ~6 O5 C& k8 n - {$ |! c6 \$ R4 D( w4 f
- /*此函数来源于misc.c*/
; I; |* I: f/ o - SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 4 Q& \. D! J0 Q n" L
- fac_us=SYSCLK/8; 9 b3 Z; }' U4 u1 t9 k& V
- fac_ms=(u16)fac_us*1000; % r I; b! P u% w! G9 J
- } 9 K9 A. M: Y7 N8 P+ u2 ?- [. `
- / J2 S; m5 t# G" r- P6 F% V
- ! d$ w7 z: |- k* S1 u7 H* D3 {3 }
- /*延时nus*/ 9 y+ M7 N8 \; f7 q8 I4 `0 \
- void delay_us(u32 nus)
1 C5 f5 R3 \+ ~ {+ o - { i9 w! `, C, B( b1 p2 U- \
- u32 temp; : b) R! c9 p r, f/ m E6 l" P
- SysTick->LOAD=nus*fac_us; //时间加载 ' o3 d2 V/ m' c4 a i
- SysTick->VAL=0x00; //清空计数器
5 T$ x* n$ |7 J0 i \$ @3 o8 q - SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
7 T; _/ \* |9 p" m. O - do+ g; l8 ]4 s) d$ f* S' A
- {8 C, S5 u& L- r [% U- F8 q
- temp=SysTick->CTRL;$ W5 y. J' K6 x8 D8 s8 `& L) e
- }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
2 x0 X( Z" t8 @- T - SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
# F1 D4 K) Y8 N* j( b6 C1 l - SysTick->VAL =0X00; //清空计数器 / h, o" h7 W) e/ K' s3 j
- }
; t8 f2 ^% I( D, d2 V' b1 d- V5 N" F -
0 w% p4 D( W* h+ I/ d3 j( p8 H - /*延时nms*/2 K8 J! m/ P5 Z. C
- /*注意nms的范围2 A( X8 @$ |9 `- U/ f/ f
- SysTick->LOAD为24位寄存器,所以,最大延为:nms<=0xffffff*8*1000/SYSCLK' P, O0 R& W$ B
- SYSCLK单位为Hz,nms单位为ms,在72M条件下,nms<=1864 */# w8 l7 o- D7 k+ c
- void delay_ms(u16 nms)
) r, z# ?4 m4 }5 ^% J$ k - { ( z% n- y" b7 z2 I% j
- u32 temp; 3 W$ E" P4 K8 s% B
- SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)+ o5 P( h/ d& I, Y/ O) [- [
- SysTick->VAL =0x00; //清空计数器
+ r$ N$ a4 P: D! W9 E - SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 " R- W P; ^4 t s4 n4 P p* ^7 [
- do
# O( ]" x# y, Y9 F - {) b$ @4 W, R3 L7 g' u
- temp=SysTick->CTRL;
R0 v' h4 D! J9 f ?$ n: v3 w. R. W - }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
/ ^% L! Y& @6 q4 p& G - SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
2 y: d3 W* X, ^ - SysTick->VAL =0X00; //清空计数器 ) R0 R, \1 r! e$ _% }
- }
6 L C' c! f7 X/ O - </font>
复制代码 4.SysTick相关函数- \" [' g) g' E) \0 Q" ]# Q
- <font face="微软雅黑" size="3">/*Systick时钟源选择 misc.c文件中*/
) [; K D, F8 A( o+ P5 G - @arg ticks
& ~1 ]3 z s/ l6 P+ [" ]1 e - /*也就是两次中断间的计数次数,即两次中断之间有多少个sysTick时钟周期,归根结底也就是规定两次中断的时间间隔。*/, r2 ?4 m" H5 q, E, {
- static __INLINE uint32_t SysTick_Config(uint32_t ticks)
?( L( P: T2 E - 5 H4 G8 f& z- ~/ ?. T# b# U
- /*初始化systick,时钟为HCLK,并开启中断*/; |# R' O' Z1 j& t& w
- @arg SysTick_CLKSource_HCLK8 ~2 N( a: w1 p7 Q) `& r
- @arg SysTick_CLKSource_HCLK_Div8& {$ @+ T0 d1 Z/ c5 q2 O" G8 i
- void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource) //core_cm3.h/core_cm4.h文件中- a6 G9 F0 S3 w) M& o* p$ S" l
- % I" ?( m1 F: S* `+ ^
- </font>
复制代码 举个例子:假设SysTick定时器的时钟为HCLK即72M,tick为1000,即两次中断之间时间间隔为1ms(此处并非外设定时器的计1000个数,而是定1ms时)。
. X$ ^" _' }$ F! T
+ v/ g0 G9 {9 N- R$ U9 a
) o. C1 r, _) I; `* u9 g2 X1 m5.SysTick相关补充
$ e: u% y9 V# s0 Z3 w1 \# g
& Y0 r& e2 J2 v* B9 g* Q
+ I. w2 |' i& T9 F% I; h9 B6 y& ~1.Systick 是一个24位数据宽度的倒计数定时器,其计数范围只能到 1677215(2^24),当计数到0时会从RELOAD寄存器 中自动重装定时初值。只要不把SysTick的控制及状态寄存器中的使能位清除,计数器就不会停止。
4 j/ _* B- P: V$ N2.SysTick 可以产生中断、设置中断优先级,有专门的中断处理函数SysTick_Handler()。SysTick定时器被捆绑在NVIC中(这也就是为什么SysTick的设置在misc.c文件中),用于产生SYSTICK异常(异常号:15)。即它从计数初值记到0时可以产生中断。Systick中断的优先级也可以设置。
& g7 t1 [( N1 `( |3 b3.Systick相关的寄存器有4个。: `+ G8 R& M. Q; l
- <font face="微软雅黑" size="3">1.CTRL SysTick 控制和状态寄存器
& x c1 @7 N- i7 k - 2.LOAD 自动重装载初值寄存器
' ]2 E$ C) w4 I$ s" | - 3.VAL 当前值寄存器
' G% ]8 }6 T/ ~! v S - 4.CALIB 标准值寄存器(用于校准,不常用)</font>
复制代码
' C5 h; y! P' ? E" ~/ B
l+ V. A/ i, P4 h6 K4 c9 q$ n b |