
滴答定时器:5 |- K6 q$ x7 v& G% A1 x* |: Z 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。 滴答定时器systick是一个内核外设(即:内核自带的)+ F9 _( L, S: o j, D9 c 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册 《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》 Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。" Y( K+ V6 ~ k9 z Systick的作用 1.产生一个精准的定时, E$ l2 b7 i% d# {+ ?3 m$ j( A, P 2.FreeRTOS时基由Systick提供 B9 B* B$ P* d% j ![]() 配置滴答定时器:: A5 f( M- K8 B, { 4 t; g2 _8 h9 T: A0 X+ R ![]() 红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz2 x' J" U2 ]; _: f7 }9 v 0 j2 v( i" i& n* c 配置滴答定时器我们需要下面几个寄存器: ![]() - X" v; k5 U/ B 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。' D r: H8 j4 Q1 e2 p- X/ l. s) H ![]() 注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF ; d8 m8 G4 b& Q 7 h- C4 f8 W6 g1 |" b0 o ![]() 3 T3 Q/ c5 p2 o* G ![]() 编程; p) J* E* O+ |) W/ R5 Z" Y) I 产生Nms定时编程步骤:+ @0 L4 M; M0 ?* ~7 k6 [3 k1 }8 o& N 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前). H6 B3 X6 y. c t7 \1 c: n5 Z 2》设置计数值---N*9000 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0 4》打开定时器: z& d" Q3 h; L/ A4 H9 S8 _ 5》等待定时器结束 6》关闭定时器 K1 ^$ }, d. W2 _/ t, U; d 9 [2 Z3 a! a0 A/ L* J. l5 h; n2 T 代码:. ~1 F' A+ B4 o# k! G6 x( X uint32_t fu_ms; // 1ms需要计的次数2 x( E+ X$ e+ ] uint32_t fu_us; // 1us需要计的次数 1 g* D( e- x/ S7 v i( B ! {8 W% E: x3 {8 g1 H! J void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz { // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置 ; r+ [7 E' y3 Z# L; C fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms } 4 b7 O( r# E& f1 o( F // 定时Nms程序,ms级延时 void Systick_NmsDelay(uint32_t Nms) {# v% ^6 D* W1 H0 s$ U; w6 N i( o uint32_t temp; // 2》设置计数值---N*9000, R r0 O* l ]" a; g4 l) [ SysTick->LOAD =Nms * fu_ms;8 j0 e/ A; J! K. X b& r ) J1 U3 H" C, s, [8 f/ F* S4 U9 o // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0 SysTick->VAL =0; //清除标志位1 I- X0 T( ?0 H# E2 i# M // 4》打开定时器- `9 t& N: C3 p: n" f SysTick->CTRL |=0x01; 2 u2 { P/ j8 X8 R7 k# ~ // 5》等待定时器结束9 s1 T% [6 z" v do{4 }% M9 ~ l" ` temp=SysTick->CTRL; 2 X# s$ E) b- v1 H* `9 S2 L }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成 // 6》关闭定时器 SysTick->CTRL &=~0x01; }. ~6 D5 M8 q/ v/ Q \& k 6 \1 c& ]4 J# y# D, w 解析: 1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写?$ w/ G% j/ i# ~% g3 h- u0 S' o 8 h _0 k) ~- Q4 s; y 因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。 2.为什么程序中的这部分需要用do.....while而不用while()?# r' r! a0 f5 I ![]() - ~# F% U' v8 [4 Y# D' u. l0 C) Y9 O 这里需要用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循环将不再循环下去,此时计数器计数完成。 * Q& A; A1 I8 k2 _ 3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?( ^, S$ }4 a9 r6 G 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。% ?- a7 z6 y. T5 [7 j 所以,我们改下一下程序: // 定时Nms程序 void Systick_NmsDelay(uint32_t Nms) { uint8_t systick_flag=0; uint32_t temp; 2 y, ]' f& t( d3 [2 m: o + I0 t& A5 ? W% a: i, g/ P7 @4 u while(0==systick_flag){2 f2 I/ e: ~( `8 }3 I' y& a/ I! @ ) [) l$ _& H) t8 d : t" @/ M" M7 \ if(Nms>1864){; u$ C" H9 ?* T% {' n K9 ^ SysTick->LOAD =1864 * fu_ms; Nms =Nms -1864; }else{ // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms; systick_flag=1; }% n8 O) G' r/ i9 \2 } 9 y4 S& ~+ N/ E ) ~9 m3 U1 O X/ F8 U C- U0 a, m6 R // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0# r! l3 z, _( e$ M SysTick->VAL =0;) {; L. o7 @' N$ r5 B3 p6 k // 4》打开定时器 SysTick->CTRL |=0x01; // 5》等待定时器结束 do{ F3 {9 B- H- u temp=SysTick->CTRL; }while(!(temp&(1<<16))); // 6》关闭定时器, L! C' g. Q- V; W SysTick->CTRL &=~0x01;. u% \! W) H6 T 1 N2 j& L9 G& g& g* n f } 7 J) ^# \/ l0 L @$ u, r$ |% @$ q9 H } 这样的话,就解决了1864问题了。 1 i! r2 Z( P# i 4.us级的延时程序是什么? // 定时Nus程序 void Systick_NusDelay(uint32_t Nus)) r O# A! V' ` {% S, ~) q f& t9 x& Q& M uint32_t temp;/ d! }4 F( M/ y$ I // 2》设置计数值---N*9 SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了5 a' X$ @) }3 ~- t // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0 SysTick->VAL =0;6 P! t% x+ |2 S0 }9 X // 4》打开定时器5 d0 f" L ~, y; C6 G SysTick->CTRL |=0x01; 0 |' T) o, A- ^ // 5》等待定时器结束 do{ temp=SysTick->CTRL; }while(!(temp&(1<<16))); // 6》关闭定时器 SysTick->CTRL &=~0x01;2 }- D2 ^/ C5 o% E" i }7 h4 c; L Y$ M$ g% n* R - r% T; B5 X- O2 w" k$ c$ ` 注意:$ B6 O& L6 X* j! ?6 } 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思? FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:& \3 U- U \8 N2 g //初始化延迟函数 //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/86 ?( |9 K: C& ^- x8 z3 T+ i2 i' R //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率! //SYSCLK:系统时钟频率& e7 Q$ P! u% x+ V0 i2 {' u& g void delay_init(u8 SYSCLK)$ R5 l5 T' E; J1 m# B% D9 h {( k. J7 S1 R) g4 x$ A u32 reload;8 i, D4 \+ P1 q q; {3 g0 A SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); / W/ }1 b4 {- ~* Z9 V fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用+ ?4 d( w e5 q$ k1 D! I reload=SYSCLK; //每秒钟的计数次数 单位为M ) B+ h( d# g) W8 n J reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间& x9 J5 {% z. }1 s //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 $ V. a9 @+ _: L& G, T fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 * S+ u' O4 h3 H' M# F9 [ SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断 SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 8 n8 `5 P8 z+ c9 \0 H7 [ SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } 2 C; ?) {! h- z/ b* c2 Q3 e " n5 X$ S. Y# B4 d1 c3 r0 u 5 Q* r9 j# i) G% w! q" e //延时nus //nus:要延时的us数. //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) 7 b! }3 O! q# N- h8 Z8 I void delay_us(u32 nus)& l6 E8 q( N$ ~9 e6 w4 g { . U9 D+ g9 r7 t0 }5 L u32 ticks; u32 told,tnow,tcnt=0; u32 reload=SysTick->LOAD; //LOAD的值 % v7 o/ a: Z% K w ticks=nus*fac_us; //需要的节拍数 2 l' t/ m& |! _ told=SysTick->VAL; //刚进入时的计数器值 while(1) { R( |# H/ J6 y tnow=SysTick->VAL; if(tnow!=told) { if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了. else tcnt+=reload-tnow+told; told=tnow;' b% ^: Y; T- ^/ M) D if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. } }; 1 ~4 `) n$ }, o7 h: v } 4 e7 v6 k3 k! i ^* a3 e3 y //延时nms //nms:要延时的ms数 //nms:0~655352 l, M) R, a7 A& H void delay_ms(u32 nms) { 2 o# j& _) a1 T$ ~" j5 p if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行 { / R7 ] O& T6 t5 m, x9 _0 C if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 { * ^1 h% N( z% I$ p, C vTaskDelay(nms/fac_ms); //FreeRTOS延时* L" e8 {9 D& z } nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 } delay_us((u32)(nms*1000)); //普通方式延时! ~+ `1 P% V/ ]5 y- `. T. @. b } //延时nms,不会引起任务调度. P D; S1 q& n# c3 l% V( _2 h //nms:要延时的ms数 void delay_xms(u32 nms), a! b2 ^ [% Y6 L9 ~! A: `3 l {! a9 j* K5 [6 c- ?' H u32 i;* b+ D2 T4 C! M9 N" A, f5 M7 E for(i=0;i<nms;i++) delay_us(1000); } ———————————————— 版权声明:无敌小小雷 1 T! F0 n9 F1 d. {, b: X |