
滴答定时器:# }( Q1 v2 r% X. H3 n 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。$ B2 t: I/ S4 K. b 4 m. m9 f/ X8 t1 r- d; P 滴答定时器systick是一个内核外设(即:内核自带的) 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册6 e% ]; c! G6 ?% m1 l 《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》) K3 u2 Y4 v l! w% Q7 s! Y Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。: L, i8 e1 G* q! A5 i! p; v Systick的作用) w" o' G7 S; m* B 1.产生一个精准的定时# y2 x2 t) Y. Z 2.FreeRTOS时基由Systick提供 ![]() " n/ R L, L$ R/ f3 e5 d, p% E 配置滴答定时器: * f4 F+ {: |8 p6 Z ![]() 红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz$ [# P) m2 {, v( X4 ~' d$ U4 h 配置滴答定时器我们需要下面几个寄存器:! S7 I& e* G/ U4 {, {% S # q( V4 q6 T% |9 H ![]() 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。 ![]() ' a* ]0 a V* L) e9 t& d* Y# y4 i 注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF ) M+ F- d7 N8 Y% O" K* u / a1 V P, p" h, W& P* Y ![]() ![]() . T7 Y. ?2 G) C C 编程 产生Nms定时编程步骤:. Q. C0 V0 q+ P' U, n- j 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)8 t K, D4 J3 Q. ` b6 y* b+ R 2》设置计数值---N*90001 h: {9 R0 c* [2 A5 C! E 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0: g& M9 b& E; V$ k( p" Q" E8 _ 4》打开定时器 5》等待定时器结束5 d1 V# k( e% [ 6》关闭定时器 ?- b8 r3 F0 z! J# t' H8 |2 }, D 7 l5 W, z9 l A 代码: uint32_t fu_ms; // 1ms需要计的次数1 n$ K3 e- d1 c5 F" S/ f0 s4 X uint32_t fu_us; // 1us需要计的次数8 q9 ^. p" _0 T$ Z7 U7 ^) h6 D : }# z; h* x" L* n6 M6 Q& l5 |3 N void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz {1 W6 _0 \2 j8 y // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)1 G. T/ F3 B+ T, K# \, C/ w5 y( T* n SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置- q$ T& Z( a. S, O2 J) K fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms, a% U5 D5 ~( i; I- d }- m& ~) c* X& K1 z4 [! ]( A 9 S ^: f' N, f' C // 定时Nms程序,ms级延时 void Systick_NmsDelay(uint32_t Nms) {" ?3 u( j- W0 U* l, T uint32_t temp;2 @7 j; {- O! L) K3 Q% p" l // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms; // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0 SysTick->VAL =0; //清除标志位 1 P- {1 w$ O$ G( b- J0 H5 ] // 4》打开定时器 SysTick->CTRL |=0x01;$ j4 O7 i, R+ S' ]/ c2 B - Z& ]( g; @( u/ [ // 5》等待定时器结束- y4 q2 L$ q7 A' F: @) O do{ temp=SysTick->CTRL; }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成: ^3 j7 `2 M9 T- ^. i+ ?: ] // 6》关闭定时器- l9 Z( ?; L9 ~1 n( i4 f! M: ~ SysTick->CTRL &=~0x01; }$ t2 e3 n4 W1 y" D) h 解析: P% Y4 f0 \/ g8 `+ n 1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写? 因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。 ' f) W8 p% @% }1 B8 w, S 2.为什么程序中的这部分需要用do.....while而不用while()?8 X: F7 ]4 ]( K* y9 M! F + E( ~! \9 l6 ~: U. i ![]() 2 P" C! h6 b% \3 N8 x; z 这里需要用do....while而不用while,是因为需要先执行temp = SysTick->CTRL; 将SysTick->CTRL寄存器中的值赋值给变量temp,然后再对temp进行真假判断,即:!(temp&(1<<16))。temp&(1<<16)为读取SysTick->CTRL中的第16位是1还是0,如果第16位是1,则为真,但是由于非 "!" 的存在,所以 ! (temp & (1<<16))就为假,所以此时do.....while循环不再循环下去。如果第16位是0,则为假,但是由于非 "!" 的存在,所以 ! (temp & (1<<16))为真,所以此时do.....while循环将继续循环下去。等到第16位变为1,也就是定时完成标志位置1时,如上所述,while循环将不再循环下去,此时计数器计数完成。 4 D. B. W$ o! c2 Z- I C+ [ 3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办? 8 }) @+ }$ _5 i- N& g; C: E$ k 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。0 J8 J5 n/ ?% C5 z& f 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。; B4 i4 ?3 o$ I8 z 所以,我们改下一下程序: // 定时Nms程序: ]8 d/ f1 u9 w4 I9 D! M void Systick_NmsDelay(uint32_t Nms) { uint8_t systick_flag=0; uint32_t temp;1 }% p1 g; m. O. Q, E / a9 @) g0 H1 Y% V" i: i/ i6 _9 ` n while(0==systick_flag){) k1 }6 Q/ O3 f! W2 q 8 }5 x' I r Q: q) A # Y. E z! G; O1 r5 }& c5 { if(Nms>1864){ SysTick->LOAD =1864 * fu_ms;8 Y) a# v) e, v: b2 v Nms =Nms -1864; }else{ D1 m+ h& d$ e // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms; systick_flag=1; } . {& n5 M/ s) {3 O. X* e 6 l/ j% N7 |7 { // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0) i0 Z7 A* Q# M2 G' l SysTick->VAL =0;+ m; `. Y, P n) }1 ?+ } // 4》打开定时器6 {% D: e/ X b+ ?* d5 U SysTick->CTRL |=0x01; // 5》等待定时器结束, a% Q) M- G# N2 a do{ temp=SysTick->CTRL; }while(!(temp&(1<<16))); // 6》关闭定时器 SysTick->CTRL &=~0x01; " Z; @2 y5 V s9 S1 n6 T } B- h" O" f$ _, I# p4 I ! }& y+ Y7 X' i; q" x } + a: Z: z" D7 O$ z3 v1 m* P 这样的话,就解决了1864问题了。 9 F( d# ]/ E+ }- y9 ~$ K5 | ' i: [, {# L% U 4.us级的延时程序是什么?2 s* }; z9 |8 r& u6 s - d/ C+ y6 C/ M // 定时Nus程序4 `' f5 q6 h; Z' k) Z void Systick_NusDelay(uint32_t Nus); V" f* b& f9 c$ N' F# p* C7 @ { uint32_t temp;, X" X" Y$ B9 V' J' |9 v // 2》设置计数值---N*9 SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了 // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0 SysTick->VAL =0; // 4》打开定时器 SysTick->CTRL |=0x01;2 B; b$ r' A7 C; v4 d // 5》等待定时器结束8 s+ r0 g" P: {' r# H+ S do{) B, h, H3 }$ ^5 o8 P0 E7 `* W# [ temp=SysTick->CTRL; }while(!(temp&(1<<16))); // 6》关闭定时器 SysTick->CTRL &=~0x01;, ]1 M2 G7 t; z# T7 J! f0 e }0 g/ u* R% V' X6 I 注意:; a6 L; ]5 ]$ \1 [( K 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?6 W; d$ ~$ a. i% }* Y/ c FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下: //初始化延迟函数 //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/82 V- x5 a3 ^! D4 z //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率! //SYSCLK:系统时钟频率2 Z7 G) A' N' }, [4 k& C0 W5 k void delay_init(u8 SYSCLK) k% S$ c/ {) V Q1 H { u32 reload;8 C) T, v ]$ @8 `, Z SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); 2 t, p" i0 T# y1 Z! n! Y' u fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用 reload=SYSCLK; //每秒钟的计数次数 单位为M ' ?: Q6 t# K6 m% n reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间8 T; s+ W' x. ]* O, a //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断 SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } 3 ~) x0 N, _% m/ | ' A% k( c9 S- D6 _2 Q5 z $ x) p/ z$ B7 l) W3 e //延时nus: r, W0 D1 M$ L7 g! N) B! P/ M; W //nus:要延时的us数. 0 i4 N8 R- j+ p, ^ ] //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) void delay_us(u32 nus) { % ?. k D, W" i! l8 v6 [ u32 ticks; u32 told,tnow,tcnt=0; u32 reload=SysTick->LOAD; //LOAD的值 ticks=nus*fac_us; //需要的节拍数 7 h; D, H3 k, N; [0 o2 Y told=SysTick->VAL; //刚进入时的计数器值* A: q+ w% k2 { while(1)' I9 E7 p! ?5 b { tnow=SysTick->VAL; ) r8 y% t' p: j5 p7 V if(tnow!=told) { # K6 R0 \" W' S$ C' j if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了. else tcnt+=reload-tnow+told; told=tnow; if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. } + h- F- B# P, Z }; } + L" A- o0 K( ?3 G+ i* h9 I+ x 6 y$ E1 e8 g/ P //延时nms //nms:要延时的ms数3 g5 B( v! u) H //nms:0~65535! P. L; `% j# G1 t$ @8 J) e# A void delay_ms(u32 nms)9 _# g) \7 O. ~4 }, f { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行 { if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 { vTaskDelay(nms/fac_ms); //FreeRTOS延时 }: w: d* f+ v" w* D nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 # z6 f6 k) o) u4 {, R }* r+ {+ G- J+ k delay_us((u32)(nms*1000)); //普通方式延时 } //延时nms,不会引起任务调度 //nms:要延时的ms数 void delay_xms(u32 nms)# ?, K" H' m* x9 n9 B" e' K {% r* u) u5 l! R) b5 U u32 i; J2 e* Q$ s+ P" U7 B) g for(i=0;i<nms;i++) delay_us(1000); } ———————————————— 版权声明:无敌小小雷 : f& f- A7 R& O* h3 H! x, u4 v |