|
滴答定时器:' ]! q; J1 X+ V3 q 定时器的本质就是计数器。我们设置一个定值,然后计数器开始计数,从我们给的定值开始往下一直数,当数到0时,就做相应的动作(也可以不做什么,当把它用作延时计时的时候)。 滴答定时器systick是一个内核外设(即:内核自带的) 所以在《STM32F10xxx参考手册中文版.pdf》手册中没有相关描述,我们需要参考内核手册+ d0 @# \7 a) t4 f1 I) z 《STM32F10xxx20xxx21xxxL1xxxx Cortex-M3 programming manual.pdf》 t7 W, f/ o/ N' J" C 2 [- `# w4 D" _1 U. p Systick是一个24bit的系统定时器(stm32F407的寄存器名字与位数都与f103一样,但是有些芯片定时器位数不同),向下计数(从定值开始数到0),当计数到0时,在下一个时钟边沿,会重复计数。所以如果我们用完定时器后,不再继续使用它时,记得要将定时器关掉,否则它就一直在重复计数,增加了功耗。( s# [( e/ H" ]+ _ 0 C1 S! V. d$ n; l# R8 k Systick的作用 1.产生一个精准的定时 2.FreeRTOS时基由Systick提供1 s6 ^% w4 g$ _: p
配置滴答定时器: + t2 `: ]; T( r/ Z4 y! B* z. K/ c
3 F1 ]5 p" O6 h7 e( | 红框中就是滴答定时器的时钟,我们可以看到分频系数为8,所以滴答定时器的频率为:72M/8 = 9Mhz+ x R B; O2 g / p% K$ M, `7 _' `9 J3 p 配置滴答定时器我们需要下面几个寄存器:
. D* i2 |# v& A I 注意:第16位,是定时完成标志位,每当计数到0时,该标志位就会置1。# t* q0 [. [% T ) N* S, j) b+ I/ s3 n+ s2 P1 M
注意:由于定时器是24位的,所以给定时器装载的定值是有一个范围的,红框中就是范围,范围为0x000001 ~ 0xFFFFFF
9 W6 i J; E) ~. ~7 \ 编程 产生Nms定时编程步骤: 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) 2》设置计数值---N*9000 3》清除当前值寄存器----注意:清除当前值寄存器,STK_CTRL寄存器的第16位会自动清04 A" F. M" O C- E5 V- Z 4》打开定时器0 n" d9 r/ P& Q) d 5》等待定时器结束. O- x" @; }* }3 @$ |9 k 6》关闭定时器7 J" I/ m$ a0 o4 I' l* ?! F& Z7 q+ Z6 c 代码: uint32_t fu_ms; // 1ms需要计的次数 uint32_t fu_us; // 1us需要计的次数* Z: q9 `# E/ u0 I % V3 E4 G+ o/ w) B$ p) W8 D void Systick_Config(uint32_t Sysclk) //Sysclk就是系统时钟频率72Mhz,但是这里用72表示72Mhz { // 1》配置时钟源---AHB8分频,并且关闭定时器-----初始化(while(1)之前) SysTick->CTRL &=~0x05; //这里关闭定时器的原因是,有可能之前没有关闭,所以一开始先将其关闭,再进行配置, ?) g( G5 Q+ N1 c2 p 3 ~2 l3 J4 j4 N fu_us =Sysclk/8; // 72/8 = 9,即:表示9Mhz5 ^: d& e, _5 z- {& G fu_ms =fu_us*1000; //9*1000 = 9000,由前面知道,在9Mhz下,计数9000次为1ms5 A0 X2 B- Y% x; ^: ? } // 定时Nms程序,ms级延时 void Systick_NmsDelay(uint32_t Nms) { uint32_t temp; // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms;( B* b7 H! @9 Y6 ]: n7 T3 S // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位(定时完成标志位)就会自动清0 SysTick->VAL =0; //清除标志位- G2 L6 s3 f8 j$ D' f7 A2 |: B // 4》打开定时器* s: s: r5 ?) g( H; p0 O) s SysTick->CTRL |=0x01;! C0 [$ \( w# _3 E# A z9 u . y* H5 a: D" }3 ^' \- a& l, Q7 o // 5》等待定时器结束0 W5 N$ A& _6 W1 p( u1 I do{ temp=SysTick->CTRL; 5 q; u4 V- }% h. I; [* Z }while(!(temp&(1<<16))); //判断CTRL寄存器第16位是否为1。如果为1,则说明计数数到0了,此时计数完成 0 D, D2 @7 b, r. @: U5 W. { // 6》关闭定时器 SysTick->CTRL &=~0x01;5 {2 R* g. h, u, g/ @& w1 @) s } 解析:& P9 e1 M3 ]2 A/ Q( z" b- v % b; I! @* U C2 j) w 1.我们先来看程序,定时器程序为什么被分成 Systick_Config(uint32_t Sysclk) 、Systick_NmsDelay(uint32_t Nms)两部分来写?$ M9 t! d1 y& |1 R: w2 I , D1 L- D& [8 a1 Q 因为,Systick_Config(uint32_t Sysclk) 中的配置,在整个程序中只需要被初始化一次就行了,而Systick_NmsDelay(uint32_t Nms)是延时函数,它会被多次使用。如果将Systick_Config(uint32_t Sysclk)与Systick_NmsDelay(uint32_t Nms)写在一起的话,那么多次调用该函数时,原本只需要被初始化一次的语句,就会被多次执行,浪费CPU资源。 - Z* i! e3 Y% i, e1 K 2.为什么程序中的这部分需要用do.....while而不用while()?. G9 \; _8 x9 U- s
这里需要用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循环将不再循环下去,此时计数器计数完成。 3. 由上面知滴答定时器的计数器是24位的,其计数范围为 0x000001 ~ 0xFFFFFF ,即:1 ~ 16,777,215,那么如果我需要计的数超过了这个范围呢?我该怎么办?% ?- V d: }) g; H& z/ {& F% C& ^ 由前面知,计数9000下,延时1ms。所以,16,777,215可以延时1864ms,即: = 1864 。也就是说,在9Mhz频率下,将计数器计满,可以实现延时1864ms。 ( h8 ?+ Y' E7 }. P h+ h 的确,如果我想要延时2000ms,那么由于超过计数器的计数范围,那么滴答定时器就不会正常工作了。5 V6 b+ L& b8 r) a 6 |7 b; ^0 t, E: ~7 M 所以,我们改下一下程序: // 定时Nms程序 void Systick_NmsDelay(uint32_t Nms)' _6 V# t+ `$ x {& b! i1 m1 F3 c( V9 ? uint8_t systick_flag=0; uint32_t temp; 8 j8 I4 M" Z+ u5 a% U while(0==systick_flag){! W9 h# \: k d4 e$ V; ? # K8 d; N K! }" x5 ^ if(Nms>1864){ SysTick->LOAD =1864 * fu_ms; Nms =Nms -1864; }else{3 |7 O6 i$ y% c# X/ ~' ~ // 2》设置计数值---N*9000 SysTick->LOAD =Nms * fu_ms; systick_flag=1;" D2 D: ?, I4 e) s# l# x r } 4 @% }1 s# G2 B3 J' n // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清0" ?. q! T- d0 @4 s* s: X SysTick->VAL =0;) @% o7 Y4 n3 K' [8 g( Z 0 U$ D9 f( a% ]. m+ H+ j // 4》打开定时器 SysTick->CTRL |=0x01; // 5》等待定时器结束 do{ temp=SysTick->CTRL; }while(!(temp&(1<<16))); 9 b9 P* ~0 d/ X) y5 X, s // 6》关闭定时器 SysTick->CTRL &=~0x01; }4 l% L4 F9 m& |* {, h8 V } : m) H# Q# @+ F. @" C& |# l' ` 这样的话,就解决了1864问题了。, a4 H* ~' P& Z" u0 l2 J 5 i) G& ^$ m$ p+ R! X$ b. C 4.us级的延时程序是什么? // 定时Nus程序 void Systick_NusDelay(uint32_t Nus) y% q- \$ R( k, @+ u { uint32_t temp;5 u! e% J5 O5 Y/ L // 2》设置计数值---N*9 SysTick->LOAD =Nus * fu_us; //us与ms的程序就只有这里改变了 // 3》清除当前值寄存器----因为清除当前值寄存器,STK_CTRL的第16位会清07 X7 T" ^# A/ D Z SysTick->VAL =0;4 V# z: Q& X) H+ }% m: W ; x( A) y( B2 M: \ S0 K // 4》打开定时器9 U3 Z( r6 O% U- R SysTick->CTRL |=0x01; 1 N0 e$ w3 f- l+ e0 }; M5 w // 5》等待定时器结束 do{ temp=SysTick->CTRL;+ x* u+ @7 [) y- P- d5 q }while(!(temp&(1<<16))); // 6》关闭定时器 SysTick->CTRL &=~0x01; } 0 C0 G- b1 X' E2 M! B" { 注意:! ~2 [- D, u) n 在FreeRTOS操作系统中,不能使用我们自己设置的Systick来进行延时,因为FreeRTOS时基是由Systick提供(裸机的时基是由晶振提供的)。这是什么意思?8 ` z5 @! {8 B4 K % E) L1 [, }* x. [. h" x FreeRTOS时基是由Systick提供,时基是需要保持不变的,所以Systick寄存器中的值必须是固定的,是不允许被修改的。但是,如果我们在FreeRTOS中用我们手动设置的Systick来进行延时,我们必定会去修改Systick寄存器中的值来得到我们需要的延时。所以,此时如果在FreeRTOS中使用我们手动设置的Systick去进行延时,那么就会卡死。解决办法:使用软件延时,delay()函数来代替Systick延时。代码如下:! S1 r8 q- p0 v8 F 9 @' `0 h q) W1 K# @ //初始化延迟函数 //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8 //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率! //SYSCLK:系统时钟频率 void delay_init(u8 SYSCLK)0 t" \. P! y$ q6 W/ a {1 U( d% ]# t: p& d( W u32 reload;- G5 ^/ K* l8 G$ N7 ]2 | SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用 reload=SYSCLK; //每秒钟的计数次数 单位为M ; T( u# k( v* K5 ^% k reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间 //reload为24位寄存器,最大值:16777216,在168M下,约合0.0998s左右 fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 5 v4 t7 B" l) N8 `& s SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断1 v" s& @( H- K- X( W SysTick->LOAD=reload; //每1/configTICK_RATE_HZ断一次 \, M. X: V; r3 L5 l& b0 d SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } 4 l$ o# w% K: e' D; k //延时nus$ x4 @( `2 d' ^3 @) f6 R //nus:要延时的us数. M* {7 \7 M% G9 ^+ C8 E //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) void delay_us(u32 nus); T, h; L9 s# D" f6 D/ s( } { & Q4 s2 B2 n5 U& `3 N u32 ticks;4 \% _) t& E; r) `' e* S u32 told,tnow,tcnt=0;% j5 P6 b9 M3 j+ V, W7 K u32 reload=SysTick->LOAD; //LOAD的值 ticks=nus*fac_us; //需要的节拍数 told=SysTick->VAL; //刚进入时的计数器值* r9 g" G" Y9 w( n while(1)7 ` e T: p* G( [5 }* d! n { tnow=SysTick->VAL; ' k7 l7 V: q$ |8 w) c if(tnow!=told); }% l3 n- F( V& ~* c( k% J( X { 2 L3 V- W% w8 l4 B if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.' y1 B$ B b4 B# X else tcnt+=reload-tnow+told; : K) y- b3 ]9 h8 P told=tnow;) i' N9 J+ j' t5 T+ g if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.7 v- E- q6 y Y; b7 B7 G } 9 s! y7 w b l' A7 V7 z. E }; 3 N" G$ k+ f* h0 A } ) q" f: b5 C) \8 T: D1 N+ u; w+ Q ( l6 \" \# Q0 T! F, |& g + X& l9 C: q1 n/ G, l2 J //延时nms //nms:要延时的ms数 //nms:0~655353 Q8 E$ W) P: m+ }( Y- k2 `0 s void delay_ms(u32 nms) { 2 i4 V2 o8 A3 _* d if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行% p5 D; `2 b# C' X8 k1 j4 L { if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 - {: B1 P3 v% M { vTaskDelay(nms/fac_ms); //FreeRTOS延时+ h, f8 M+ N1 e# b }1 s5 z5 p. |, q" P" P {- {5 @" R; X" y nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 ( d6 y* E# A. T5 X8 S/ n }5 Y' K: y) w" d5 O delay_us((u32)(nms*1000)); //普通方式延时 }6 h9 o% f4 q/ u/ U2 G" c //延时nms,不会引起任务调度' `" S2 U. C: k //nms:要延时的ms数# L* R- p: F" _- l void delay_xms(u32 nms)8 I" ^* z% _0 k+ Q, f7 ~ {6 C/ [0 m2 [+ [* P. v u32 i; for(i=0;i<nms;i++) delay_us(1000); } ————————————————! V( F Q( i# S$ \7 ^& }8 [ 版权声明:无敌小小雷9 d4 L0 X4 G% R4 ]4 M& g ( E4 w; A! z5 g! [: A |
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 不起振排查指南:五大核心原因与快速解决方案
微信公众号
手机版