|
滴答定时器:5 X/ t3 I0 A' }- Y+ j 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。 * { _6 ^; d4 N( u; `1 |' \ 滴答定时器systick是一个内核外设(即:内核自带的) 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册 《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》 Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。' Y3 Z# z1 s& J+ o8 t # U/ i2 ]& e4 Z+ b Systick的作用 1.产生一个精准的定时. n; L; t2 o) V( z 2.FreeRTOS时基由Systick提供9 r- H. e$ d0 e 8 ?6 E# t( w4 x& Y. I* N5 q
配置滴答定时器:2 |* }9 x/ q" ]* ?6 a: i g; T # ~6 G4 Z& O9 V3 ~# H
红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz 配置滴答定时器我们需要下面几个寄存器: 0 v- u) J6 h; `- E ?+ w5 L- V5 i5 x; C
4 r% g4 o8 Q- [# o; ?5 L 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。: S: T2 l& J4 B, z( ?( S7 ~& e
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF * f) e/ }( H2 F w; c, ~( f6 M
7 X8 r$ x: k2 j
6 `. V+ r- c( \. I, e# R1 j p* o 编程) L3 F `. s! p S% ] 产生Nms定时编程步骤:5 Z: ]2 a1 X5 [+ H 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) 2》设置计数值---N*90001 G3 x0 {; R( \, _7 T' x2 s 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0% v* x( r' `$ o6 ]0 |2 e 4》打开定时器) b$ v7 q6 V) R5 N0 W: z* p# q7 E 5》等待定时器结束+ f6 @; z9 n6 o' g& { 6》关闭定时器0 k7 m, l: w8 ]0 f2 L : E, a9 ^0 S# c8 O 代码:! _7 A! \( ?) d- k' Z4 R8 G& r uint32_t fu_ms; // 1ms需要计的次数 uint32_t fu_us; // 1us需要计的次数 3 _. @& ~" P! {) V& Z4 H 2 |6 `* Q" l. z9 r( O void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz/ I" t' S) ]6 J6 ^; g! r { // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置+ r: O/ Q2 g3 l) { T4 @* l fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz' u! S l: m. y& C fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms. d6 G% J8 g" y% X) u- `4 b3 o }' k! d9 O2 @' R& @! B( g4 @4 K+ H ) e: Y$ g/ G3 P) r, z! h // 定时Nms程序,ms级延时) _2 s. S9 Z$ r# l1 R5 ^% [ void Systick_NmsDelay(uint32_t Nms) { uint32_t temp;" W0 Z, ]: W$ p2 {# D, r // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms;. {6 J0 K" L$ A* W // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清03 ] B& h: k7 _, B; S* g/ p; [ SysTick->VAL =0; //清除标志位' S8 J7 V& T% q% M& M0 C {9 S+ K7 `1 S2 A# T // 4》打开定时器 SysTick->CTRL |=0x01;; Z( M- s9 O5 o8 Y % ]) P: z) U) L. J$ }0 n // 5》等待定时器结束 do{7 k1 s% }! K6 E2 v temp=SysTick->CTRL; & |/ L; y9 d6 r; n* `7 V9 ` }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成 // 6》关闭定时器; a: y0 j, w$ k3 e7 H5 h SysTick->CTRL &=~0x01; } 解析: 8 Q; b0 b7 \/ x0 N l 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资源。 2.为什么程序中的这部分需要用do.....while而不用while()?0 W' ?- z: \: |# x* z ) e! `1 v# v. w! Y$ a
这里需要用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循环将不再循环下去,此时计数器计数完成。 ) |0 w+ _ D1 c& H. m 3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办? * F7 z! \8 J! @1 O7 }) z! B 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。% `% h( E: P: y! h 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。 所以,我们改下一下程序: Y6 [. i7 k: P1 I: N4 o // 定时Nms程序" {1 h) D3 \" e& j( q0 l void Systick_NmsDelay(uint32_t Nms) { uint8_t systick_flag=0; uint32_t temp; while(0==systick_flag){7 i$ y% V4 b1 [ v- g% H3 I2 e 7 v+ m. I& S. O if(Nms>1864){( ~# o: x6 B- Q SysTick->LOAD =1864 * fu_ms; Nms =Nms -1864; }else{1 F1 j% ^- ]* H; U; }! K // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms; systick_flag=1;2 z4 g" A3 D& r6 i* I9 ^# }) N } # c, Z) @4 m& Q2 } 8 K$ [* i& `: f // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清04 o0 Q, W- e, i+ L2 r% R# ^/ A- d SysTick->VAL =0;- [# N5 X0 M" P- w4 L // 4》打开定时器4 P6 A& H# W1 I3 t SysTick->CTRL |=0x01; % z2 S6 q" u ^ T( l // 5》等待定时器结束1 o' ?2 o w6 D2 g1 q7 p( |# [/ r/ ~ do{ temp=SysTick->CTRL;* P3 e! ?/ E' }- | }while(!(temp&(1<<16))); * h1 u: B/ M* j: J2 j7 }6 ~$ s // 6》关闭定时器 SysTick->CTRL &=~0x01; } : W! u+ |! E: {8 i% a/ U/ T b( r) z% I* F$ R9 ? } 6 ^' k2 ]5 T! G5 B! F& i6 n: |: ^ 这样的话,就解决了1864问题了。 2 b$ D+ t6 r4 ?! W 4.us级的延时程序是什么?- m+ X1 D/ L) D, }, |$ J, O- s // 定时Nus程序) w2 \/ N5 \4 f5 I void Systick_NusDelay(uint32_t Nus), k* ?& T" K i3 r {" r; N; Q1 B) n0 a7 U7 S0 i7 c" U uint32_t temp;& k" p: n/ C# X0 l, P3 I // 2》设置计数值---N*9 SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了" V% K( F. [! G9 v2 \* h A ] // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0 SysTick->VAL =0; // 4》打开定时器 SysTick->CTRL |=0x01;- l9 g; _: R2 A @: o5 z: l5 [& L4 B6 J // 5》等待定时器结束 do{ temp=SysTick->CTRL; }while(!(temp&(1<<16)));' F. G4 b! S7 E, Q2 O % a. [( W" q D+ o0 ?- [. m // 6》关闭定时器 SysTick->CTRL &=~0x01; } 注意:+ g$ _, M6 m! U9 E$ l) V7 ? 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?. I# k3 k1 z* G, J! b# n4 i9 |. | FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下: ( e Q( O; N& b5 P- M @ //初始化延迟函数 //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8 //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!* Z4 O- {9 Q7 H5 n+ |5 D //SYSCLK:系统时钟频率 void delay_init(u8 SYSCLK) l) w. V" S. k( d+ b { u32 reload;7 c! n( {% L3 \4 d SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); , Y7 n$ t) L& c7 I1 h fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用 reload=SYSCLK; //每秒钟的计数次数 单位为M & X- g& c3 U: I! U reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间 //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 ( ^0 j2 Z {" i" X! Y4 Q( r( ] fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 - p) S I. _/ R5 ?/ I SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断 SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } ' r: r, I3 a9 M7 Z! O# i ! k# ?+ s% o6 k# x, @4 s 4 F6 f! v# l, q9 b' Y V0 w5 \! n //延时nus //nus:要延时的us数. # g" [$ M+ @0 a! Z6 g' C //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) 7 D2 L$ ]* m8 W6 N void delay_us(u32 nus)+ R# l+ S m4 c0 _' M# w { ) G% Z5 Q8 p+ E" Q u32 ticks; u32 told,tnow,tcnt=0; u32 reload=SysTick->LOAD; //LOAD的值 ticks=nus*fac_us; //需要的节拍数 told=SysTick->VAL; //刚进入时的计数器值5 R4 a7 g: k! c, r: A2 E" V while(1) { tnow=SysTick->VAL; 6 m& M% _$ M: [3 U; I' l if(tnow!=told)4 i9 t% n2 w( o& C { if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了./ Z1 H) h4 G2 [) E( K6 W) c else tcnt+=reload-tnow+told; : u6 p, P. Y: G' Q5 y told=tnow;# h5 B. v; T: h, H( p/ o# F7 b5 V if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出./ h: u- ?4 x6 n' v9 Z3 `6 I } ( O2 r$ L- Q, }/ B1 M }; ! u# C/ s. T( P, w" H } //延时nms //nms:要延时的ms数 //nms:0~65535 void delay_ms(u32 nms)% m+ n; Y1 T; h { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行) T; @5 Q: ]( E: `; C { if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 { vTaskDelay(nms/fac_ms); //FreeRTOS延时4 r/ W E( Z% Q. j# i5 c } nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 / h% W# p4 m# t1 ^ }8 v m% M; f' T5 }8 q: E) [" C delay_us((u32)(nms*1000)); //普通方式延时 }2 J, C4 p6 j9 r- V* [1 W) l+ p //延时nms,不会引起任务调度 m4 E* M5 r" L" T! [: x //nms:要延时的ms数3 B4 J( u+ U! E. e( n K3 ?# P void delay_xms(u32 nms)- d$ W5 w0 B, I: O8 @/ _7 i {6 Y+ @5 { `, K7 P! R; j7 t' w: C. U4 H u32 i;) I* i0 h9 z1 {) M+ W for(i=0;i<nms;i++) delay_us(1000); } g, h9 ~8 A, z: U/ n a ————————————————3 M- v+ I. k6 E+ Y( d% l 版权声明:无敌小小雷/ V; t- C4 x0 h0 V- j! X 0 n! a2 O! ^4 Y% Z 6 Z2 B: q5 L& V0 c" _3 K |
DMA 原理从 0 到 1:为什么它能解放 CPU?一文讲透 DMA 的工作流程
【福利三:雨露均沾·逢7狂欢】之五:STM32系统时钟PLL故障排除1例
【福利三:雨露均沾·逢7狂欢】之三:AI帮我查BUG
福利三:雨露均沾·逢7狂欢】之二:STM32F407VG串口通信乱码故障的排除
基于STM32F103的I2C主从机通信
OpenBLT移植到STM32F103战舰开发板上适用于所有STM32F103系列的Bootloader
TFT LCD 与 FSMC 的硬件连接大容量 STM32F10xxx FSMC 接口
经验分享 | 利用TIMER定时和捕获功能实现编解码演示
单片机:初学者该了解的STM32F103基础知识
STM32 LSE 不起振排查指南:五大核心原因与快速解决方案
微信公众号
手机版