|
滴答定时器:' g" g, F0 \3 b3 Q& Y 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。6 o2 G7 e" Z9 a 滴答定时器systick是一个内核外设(即:内核自带的) 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册( z4 s, b: U5 h% ? 《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》5 E: y( \9 J) @) v * S! ?# ?3 C% T. c t Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。 Systick的作用, { D* v2 ~7 D l& o% J 1.产生一个精准的定时: a+ K* J. @+ F 2.FreeRTOS时基由Systick提供$ w1 `5 w* W, i1 ]5 ^/ K: t) u
0 a Z$ X H4 ~* ?3 v 配置滴答定时器:6 L+ c! G% O# M! k/ G- ? R1 i
红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz - c& W2 e/ i0 D7 J8 Z 配置滴答定时器我们需要下面几个寄存器: 7 y1 U( W6 U4 ~
注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。1 C; |5 D% H5 W# k( N9 y6 f 1 N& D" @- H& o: ^
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF 1 t+ ~+ s) Q7 Q1 [; n% w% G# a
( I* w4 S6 b) y$ H% q" n) G: l6 z
@* a* m9 X! l5 b1 m: J1 g, [0 n) B1 _ 编程, B5 p2 p9 ~/ s 产生Nms定时编程步骤: 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) 2》设置计数值---N*9000 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0 4》打开定时器) G0 ]% N4 J: ~& c' q 5》等待定时器结束% A, Y- I3 g% P2 \/ o+ v$ a, E" t 6》关闭定时器* S. R& S) p* M% ?. E 0 u" v" J$ d w1 J* T) s+ q 代码: uint32_t fu_ms; // 1ms需要计的次数/ v q6 ]4 j9 L8 I" W uint32_t fu_us; // 1us需要计的次数) m( `: z4 J* T& R9 h & j" s6 O! z. k2 o8 v- b 8 S$ J1 Q' a# i6 x( w- p! D void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz { // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置 ( t' F/ S8 i1 ~1 t fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz/ ^8 s$ c$ O- r( x fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms' H) J+ B1 G5 `3 h3 o/ Y8 P } ) W% w! j' n# N( I: A- Z+ t1 U ( Y; C. B6 x: @9 ?3 l4 U // 定时Nms程序,ms级延时 void Systick_NmsDelay(uint32_t Nms)' j2 L$ H" m. A0 g% o$ ^! Z { uint32_t temp; // 2》设置计数值---N*9000: x2 J y/ r3 N$ o' K; t- m SysTick->LOAD =Nms * fu_ms;. o5 p, U3 r! y$ Y4 m, ~6 B // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0 SysTick->VAL =0; //清除标志位) O9 Y) ~. E" J // 4》打开定时器 SysTick->CTRL |=0x01; // 5》等待定时器结束 do{6 u8 F0 ?/ V8 `. i: S( U temp=SysTick->CTRL; }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成" g6 Q6 O( r- i" X 7 v5 g: g, F# w8 l6 E* R. A- i // 6》关闭定时器, \, d" Y& M+ ` SysTick->CTRL &=~0x01; }! b7 A5 ?. \9 S: G4 s0 R6 _* w% w5 F 解析:% x- k% W) X' F4 p% p 1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写?+ F- b0 O: B5 G; N# u7 ?3 f( P 因为,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()?
这里需要用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循环将不再循环下去,此时计数器计数完成。: X5 Y/ k- O7 e W- l7 n $ K: W: Q/ r( N" M3 {2 c0 q9 T 3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办? 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。, W9 Q; D5 @0 a' f7 z. N 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。5 f. }! x5 ^9 X' X3 T# d # [. L2 ~4 w1 F I 所以,我们改下一下程序: // 定时Nms程序' i3 O) d. [* h4 i# J. g3 h& n* U void Systick_NmsDelay(uint32_t Nms) { uint8_t systick_flag=0; uint32_t temp; : q% Z" s- z0 l while(0==systick_flag){ if(Nms>1864){ SysTick->LOAD =1864 * fu_ms; S' m) X: }& H Nms =Nms -1864;2 v2 ~/ m- y6 u }else{4 g/ b4 a% P1 j2 ~2 } // 2》设置计数值---N*90008 V: ]# A, W- w6 k% S+ ~) x* i) ? SysTick->LOAD =Nms * fu_ms; systick_flag=1;3 [7 `3 ?/ B2 V, B# p } : [) |1 v: N2 W4 T! e: R // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0 SysTick->VAL =0; // 4》打开定时器5 z7 d! _! ]' k0 X SysTick->CTRL |=0x01; 4 |: q% P9 L& T; i // 5》等待定时器结束 do{$ K& e2 U$ ^ W9 ^/ }+ y! q- H temp=SysTick->CTRL; }while(!(temp&(1<<16))); 5 |- ^$ T0 M4 i4 u7 z% ^9 w // 6》关闭定时器5 |5 x( Y' c* J; q, g SysTick->CTRL &=~0x01;' Y, [6 k; U% R$ W2 \) N' Q7 D . l" u5 @. A2 K, A! c. B% @ } }' O Z6 q9 R7 w* m+ e' A- r# z . D0 o0 K; X2 q5 u. r; v. _ 这样的话,就解决了1864问题了。) \. B Q1 y2 C* H 3 G$ ~6 `5 H: q. t ^- S 4.us级的延时程序是什么?3 I& s4 h* O' B/ N7 O3 j9 r ( @* P: o* [0 y' S0 {1 Z" L7 G // 定时Nus程序 void Systick_NusDelay(uint32_t Nus)/ t( Q8 q- C. w0 o( | {: z' H4 z# j" D uint32_t temp;8 ?/ E Q- k% x! v // 2》设置计数值---N*9 SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了 3 P. J. ^% u! F7 T* C4 u& u' @ // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0 SysTick->VAL =0; 6 y$ f9 N! M- y: G // 4》打开定时器# @, ?& y; q1 e" N SysTick->CTRL |=0x01;' S6 L. ~; H" H% R0 B: p 7 N9 g% H p# ~0 n" } // 5》等待定时器结束0 G1 W' O3 c( t5 `: g do{ temp=SysTick->CTRL;! W8 k, Y$ g' Q7 O* m" }) O }while(!(temp&(1<<16))); // 6》关闭定时器9 L" h0 H2 `; K, z1 Z: U SysTick->CTRL &=~0x01;; f& O% f2 y8 {% Z3 M! G7 c1 K0 U } / H7 b; O8 e, E* [- o 注意: 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思? FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下: //初始化延迟函数& r, G8 R' q( ~& i" i //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8 //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!0 \" C7 t5 n/ ]7 [$ W) _ //SYSCLK:系统时钟频率 void delay_init(u8 SYSCLK) {+ S! U9 j: W$ s$ h. y; S u32 reload; SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用 reload=SYSCLK; //每秒钟的计数次数 单位为M ) `' k/ A2 R# N reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间# S8 L9 l' p" O) T //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 8 o7 k, u! J' w; x5 G) c2 N SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断 SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } 8 Y# J7 A: ?* W 1 ~. D. a6 k% \9 A- H6 `4 D //延时nus //nus:要延时的us数. * G/ W1 ]% w' C, g //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) void delay_us(u32 nus) { u32 ticks; u32 told,tnow,tcnt=0; u32 reload=SysTick->LOAD; //LOAD的值 ticks=nus*fac_us; //需要的节拍数 9 X/ y( T! |3 D; n4 y2 A/ { told=SysTick->VAL; //刚进入时的计数器值" d" L# V# m" f8 u: V' Y while(1) {2 U9 t$ \3 @; S( O& F# k- { tnow=SysTick->VAL; ! ^: [5 K p* d' p1 j7 o+ ?% O if(tnow!=told) { if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了. else tcnt+=reload-tnow+told; told=tnow; if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.+ @2 A8 V) `& } } }; } $ `8 S! C0 e, b1 K4 D( I $ H. l( K& n' x6 K3 @2 i //延时nms //nms:要延时的ms数( {, @0 y f; q; ~$ p2 S //nms:0~65535 void delay_ms(u32 nms) { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行6 K. P- U+ p/ A { if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 5 G1 o$ J: j+ C! G4 U. G: m( p { 0 z8 D! ^) P# y" v3 { vTaskDelay(nms/fac_ms); //FreeRTOS延时 }' {+ {: V; R) c3 j" L7 { nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 }& r; T9 `6 i# p' t: S delay_us((u32)(nms*1000)); //普通方式延时$ ^5 g0 ~+ a) D5 k4 o% o' h }3 B% n- c/ T9 j$ x5 t& ] //延时nms,不会引起任务调度 //nms:要延时的ms数 void delay_xms(u32 nms) {$ q c" W! Q' ]9 ]0 e; ]* w u32 i; for(i=0;i<nms;i++) delay_us(1000);. ^5 u* P& D0 e& T; x2 c- N2 Y5 h }9 }: N$ k; ^( Y! [6 q ———————————————— 版权声明:无敌小小雷 # ?" l5 U( A* s5 v$ P# M 6 b0 C5 @3 S! N/ T$ L3 i- i |
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 不起振排查指南:五大核心原因与快速解决方案
微信公众号
手机版