|
滴答定时器:- p) d; o6 `5 w0 B4 I* o6 F2 ^ 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。 滴答定时器systick是一个内核外设(即:内核自带的) . |8 P* _+ Z6 k/ j 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册/ B( d" L% _+ ? m* D 《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》0 `* J( p5 i0 p ( s' O) f9 H5 z3 O5 N Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。: g4 F2 f$ T3 B2 y' } Systick的作用+ r8 p. v- ?, U7 ^ 1.产生一个精准的定时 2.FreeRTOS时基由Systick提供
) {; s& \7 ]0 ^8 q. R& E) O 配置滴答定时器:' u! N+ F/ X3 \( k6 N& } `; o $ K2 ?' M( p! l2 N
# i1 W& U/ M: i. W* T R 红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz' N3 l4 z R `$ w! c( ~" w 配置滴答定时器我们需要下面几个寄存器:2 P9 ^ W' Z7 O7 u$ ^" n3 y 9 o9 } H, e+ A9 p0 V, S5 r
) C; {# l( g/ E E+ A5 H" V 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。! O6 l; U0 E. p: H; U; W
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF
编程# _, t5 {( u1 W& L ] 产生Nms定时编程步骤: 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前): m* S$ T5 h7 T% v7 U. D5 y5 l 2》设置计数值---N*9000 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清0 4》打开定时器 5》等待定时器结束 6》关闭定时器2 m. z7 g7 _( @& S $ `! n/ `7 K6 b; T. T# s/ I. ~ 代码: uint32_t fu_ms; // 1ms需要计的次数# c& v. `) S. k* j0 W6 b8 J uint32_t fu_us; // 1us需要计的次数 " L# i! c1 L. u$ L% k7 F void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz- Y/ M% N8 p) E( W8 N/ M3 X { // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前)9 t0 I) Y8 P5 v. K SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置5 n, }3 N# `7 l& {; i$ L' q 5 v. t+ j8 `. r3 T' u! _* d* ~ fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms } $ P! E+ E# O) F- T* O // 定时Nms程序,ms级延时 void Systick_NmsDelay(uint32_t Nms)" K( D5 D2 \. l" |' M6 x { uint32_t temp; // 2》设置计数值---N*90000 a% l* D5 \* `" K$ v$ ^* L5 c SysTick->LOAD =Nms * fu_ms;3 V6 V* F$ T5 z& Z7 O( Y 9 L5 s1 n5 F+ R2 y# e6 D4 [ // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0 SysTick->VAL =0; //清除标志位9 Y# t9 W0 H7 ]3 M4 i , }6 `( Q Z, I z // 4》打开定时器 SysTick->CTRL |=0x01;8 i: `9 V( [: G$ ~3 F* N & ]0 Q. F. ]. p) q K# J // 5》等待定时器结束; j/ C$ P8 _, ^6 C0 a! y: ] do{3 Z4 ]7 j: A) I% u temp=SysTick->CTRL; , ~0 s c3 o* {" V* Y+ `. H }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成 // 6》关闭定时器9 b7 ~& ]0 N7 l SysTick->CTRL &=~0x01; }" i+ c2 B% i8 Q( J6 o7 b 解析:$ f5 n: R; V0 C: @0 y @' `% m$ D 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资源。) |, E/ U! ~, }5 Z) @- L( Q 5 X( V$ o* i6 D$ t: u, g+ F 2.为什么程序中的这部分需要用do.....while而不用while()?' k% p1 j& n+ P# n; B & e* D) Q3 O% t- G1 X2 U
) f. l" Q ~! h: K Q, f 这里需要用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循环将不再循环下去,此时计数器计数完成。& t; @2 g9 I# W& _/ I, { u- c / L( D6 O$ ~; V2 A$ I 3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办? # v5 o y& P- ~3 _6 E( y4 z 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。# v+ W8 I; _: o / `6 C _" M% _! f" Z8 } 所以,我们改下一下程序: // 定时Nms程序 void Systick_NmsDelay(uint32_t Nms) { uint8_t systick_flag=0; uint32_t temp; 5 e3 m* v: m, v) e" ]0 R 3 o) B9 S/ |+ u: q6 N2 { while(0==systick_flag){3 ]; R; v- G8 n( z 9 p* j* S% g5 g+ N ( J/ @' G/ B1 D7 |4 v; y if(Nms>1864){ SysTick->LOAD =1864 * fu_ms;& N X7 `3 H5 w8 I# @6 E0 O Nms =Nms -1864;6 _# t' V' F' @0 @: z }else{ // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms; systick_flag=1; } 8 q: w! v# d; Y ` // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清08 @3 n4 j' D2 V. D8 y" P SysTick->VAL =0;! n$ X3 C9 a+ \. H- ?; l // 4》打开定时器 {8 _ _5 B/ O& t0 O SysTick->CTRL |=0x01; // 5》等待定时器结束 do{2 u7 o" [8 m% k* v h; d0 g& k! ` temp=SysTick->CTRL; }while(!(temp&(1<<16))); // 6》关闭定时器 SysTick->CTRL &=~0x01; O! }& c, I$ u# _4 a- ]+ s$ a } 7 v0 O2 q* ]. R; u ( p9 K; ]; r5 E8 H5 B } 这样的话,就解决了1864问题了。. A9 @( G) @3 F8 V7 O9 w1 a $ r) M b+ _& R- w0 p0 @ 9 B& o# ~ c3 ~' n$ r @+ p 4.us级的延时程序是什么? // 定时Nus程序 void Systick_NusDelay(uint32_t Nus)% [ m5 T+ R( M { uint32_t temp; // 2》设置计数值---N*95 R' _/ h8 ?: H% n: ] SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了 // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清02 n# }# ~* e: G: l# T SysTick->VAL =0;: S) I' U- ^2 g- X+ s6 y4 b) v o ' U% O# I$ Z1 Z6 P/ v- Y // 4》打开定时器6 p! p5 S" N' I! t SysTick->CTRL |=0x01;+ ~2 n7 ?/ ]5 A* S( f 9 C9 d0 b8 b, d0 R# } // 5》等待定时器结束& g$ a" e1 a2 b- o _ do{ temp=SysTick->CTRL; }while(!(temp&(1<<16))); // 6》关闭定时器5 s1 n! v; C) G# T& V; y SysTick->CTRL &=~0x01;9 ]7 g2 R7 h3 q1 {( u) [ } 注意:' k: E$ [3 r9 D: c$ v 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?/ o: U" w* _; E3 p$ ] FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:# @6 \3 L- o9 J$ D6 }% q" Z1 d 2 {+ Z+ W9 H$ M, e- G //初始化延迟函数 //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8 //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率! //SYSCLK:系统时钟频率! K |' W0 v! F n% y* w void delay_init(u8 SYSCLK)8 e8 Q( T' u; R; y; h! j {8 l4 j* B2 x" U# L# V5 R1 j u32 reload; SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); 9 C8 S& M) R/ |) W, `6 r- f* P0 Y fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用 I+ n! \/ X9 Z9 Y9 ^- { reload=SYSCLK; //每秒钟的计数次数 单位为M reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间 //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 + p7 O4 Y9 K* a7 W3 V' \ fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 1 e9 l' o) J5 u' J SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断8 t$ Z; _1 j. M- O. F SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } @+ Z. k$ z5 C! k/ G . |; T) Q3 y$ w/ @$ e //延时nus6 t1 w3 @ V5 S9 B* k2 L+ a //nus:要延时的us数. //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) void delay_us(u32 nus) { + }% O: {9 Q/ O0 E u32 ticks;9 q) i/ T2 R0 f3 v* U N% t; H) B u32 told,tnow,tcnt=0; u32 reload=SysTick->LOAD; //LOAD的值 & F: P" Z/ W. q) r5 \+ H- c6 s ticks=nus*fac_us; //需要的节拍数 $ X9 \) M! S( h' n& W# g told=SysTick->VAL; //刚进入时的计数器值' V h& E; ^9 { while(1) |* j; f! v( u! o' ? { tnow=SysTick->VAL; 1 E1 W. i0 `- e$ Q5 U if(tnow!=told) { if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.) j3 |6 b1 m$ u+ v3 ^5 p else tcnt+=reload-tnow+told; told=tnow;% }& }% D' o; ~6 ?2 { if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. } }; } 8 R. P2 {/ ~4 D! r //延时nms$ ^3 b" E; P$ p7 h, K- c4 o5 m //nms:要延时的ms数 //nms:0~65535 void delay_ms(u32 nms)1 Y" \6 o6 B# y! S$ R { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行 { if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 ( _% ~6 K3 u% G% ` { vTaskDelay(nms/fac_ms); //FreeRTOS延时 } nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 ) L, }& {; U6 z( P# B4 J } delay_us((u32)(nms*1000)); //普通方式延时: P- f p" ?3 W% e" f } - R7 G$ l2 X% l4 { //延时nms,不会引起任务调度, t6 v4 f( a% D f //nms:要延时的ms数 void delay_xms(u32 nms)' k4 \1 _+ r+ W% w/ j) j7 N$ h% @ {' O- b% j8 F+ t. w5 I u32 i; for(i=0;i<nms;i++) delay_us(1000); W3 F: R# x! R- I9 V( o; O } ————————————————7 @; j y: R) m5 u4 J& X+ c* c. W 版权声明:无敌小小雷 $ C7 V% H& a- Y+ X5 y/ H) H! g 5 l* F, S, F8 D4 o: h5 R/ ?! `- H + { R. P( d, U- p, x |
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 不起振排查指南:五大核心原因与快速解决方案
微信公众号
手机版