STM32工作笔记0048---Systick滴答定时器---延时函数讲解

技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152

之前咱们用的delay中的这个延时函数,这个函数

就是用的systick来实现的.

这个systick定时器,其实就是一个比如,他会自动的从100数到0,然后再从100数到0这样循环.

当定时器走到0的时候,他会自动去加载初始化值100.再进行倒数.

Systick这个也可以产生中断异常,比如每当定时器到0的时候,就会有中断.

然后中断优先级也可以设置.这个理的NVIC就是中断优先级.

systick有4个寄存器,他的工作是这样的,

比如这里reload寄存器,就是systick自动重装载除值寄存器,R,这里比如记录了100,然后

VAL这个当前值寄存器,这里Val会倒计数,倒计数到0的时候,又会用RELOAD,把100重装载到当前值.

这里是这样的,CTRL寄存器,这里

一个reload寄存器,一个val寄存器,然后val这个寄存器会自动的倒数到0的时候回自动再比如从100倒数,

这里可以定义,比如倒数到0的时候,设置CTRL位1位1的时候,当定时器,倒数到0的时候就会产生systick的异常,

就可以做到中断了.

然后这里的位16,就是当systick定时器,到0以后会自动的把countflag,自动的设置为1,也就是说

如果到0了,你去读这个读出的是1的话,那么说明定时器到0了,并且读完1以后,这个位会自动的

变成0,然后,如果这个时候倒计数没有到0的话,再去读读出的不是1,说明,定时器还没有倒数到0.

主要是用来防止误读的.

这里还要一个位挺重要,这类的位2,CLKSOURCE,对于这个位来说,

可以设置,systick定时器使用的是外部时钟源,还是内核时钟,这里的

外部时钟就是HCLK,AHB总线时钟的8分之一,也就是对于conterx-M3内核来说,这个

频率是72mhz/8=9

然后对于conterx-m4内核来说这个频率是168/8=21mhz

这个寄存器,有24位,因为systick有24位,所以这个重装载寄存器也有24位.这里会存有一个要重装载的值,这个寄存器如果有32位

也是只有24位是有效的.

CURRENT,读这个寄存器的时候,会返回这个寄存器当前倒数的值.

写这个寄存器会清除他为0,并且countflag这个标志也会被清0.

还有一个校准寄存器这个用的少,就没说

然后看一下这个库函数,这里的第一个可以配置时钟源,

第二个可以设置是否开启中断,就是这里可以设置ticks,也就是说比如1秒钟,需要中断多少次这样.

有中断的话,就需要用到中断服务函数.

.接下来看看代码

用跑马灯实验看

走到SysTick这个声明的地方去.

可以看到这里这个Systick定义到了core_cm3.h这个文件中,因为这部分是内核级别的

所以就定义到这里.

然后去看一下对应的结构体,可以看到他定义了要用到的这几个寄存器.

把寄存器和地址映射起来.

注意这里的部分,这里的作用是选择时钟源的,9M

这里可以看到SysTick_CLKSource这个参数可以有两个选择,一个是HCLK_Div8这个是HCLK的8分之一.也就是72/8=9mhz

一个是HCLK也就是72mhz

然后再看这个core_cm3.h这个文件

这类有个SysTick_Config这个方法,这个之所以定义在这个文件中,说明

这个方法是内核级别的.

这个函数的作用就是用来设置中断的,也就是设置的是,两个间隔的时钟周期

也就是设置的两个中断之间的时间间隔.也就是两个中断之间有多少个时钟周期..

来看

/**
 * @brief  Initialize and start the SysTick counter and its interrupt.
 *
 * @param   ticks   number of ticks between two interrupts
 * @return  1 = failed, 0 = successful
 *
 * Initialise the system tick timer and its interrupt and start the
 * system tick timer / counter in free running mode to generate 
 * periodical interrupts.
 */
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  //1.可以看到这里的这句话的作用就是做一个验证
  //因为systick是24位的,这里如果超过24位是要返回失败的.
  //
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
  
//2.然后这里设置这个Load的值,也就是从多少开始计数
//比如这里ticks=1000,那么这里的load寄存器中要设置
//成999,然后再从当前值开始倒计数.
//这里一般1000的话,要减去1,因为装载会消耗一个时钟
//周期                                                             
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

然后:优先级后面再说

/**
 * @brief  Initialize and start the SysTick counter and its interrupt.
 *
 * @param   ticks   number of ticks between two interrupts
 * @return  1 = failed, 0 = successful
 *
 * Initialise the system tick timer and its interrupt and start the
 * system tick timer / counter in free running mode to generate 
 * periodical interrupts.
 */
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
//1.然后这里设置优先级以后,再把VAL初值设置为0
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
//2.然后这里,首先再去设置时钟源
//然后再配置中断
//然后再使能这个定时器
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

这里是设置的时钟源,这里有两个.

这里再说一下.

先设置比如装载值是1000,然后,定时器去倒计数,倒计数到0的时候产生中断,然后

在中断函数中我们就可以写逻辑代码.

这里有一个用SysTick定时器写的delay延时函数

主要看一下这里,比如这里的SystemCoreClock是72mhz,也就是72000000,

然后除以1000,就是72000,

72000/72000000 s

注意这里是由个频率概念的,因为72mhz是1s中震动的频率

也就是咱们设置时钟周期为72000的话,也就是

72000/72000000 s,会震动72000次.

也就是72000,也就是把中断周期设置为1ms了.

也就是把中短间隔设置为1ms.

也就是说每隔1ms就会自动执行上图中的systick_handle这个函数.

仔细看一下这个delay延时函数,使用systick来实现的.

这里可以看到再main函数中,调用了delay(200);也就是延时200毫秒.

这里延时的timingDelay就是200,然后

每当触发一次中断,也就是花费了1ms,就会这个timingdelay就会--减去1,指导

timingdelay为0的时候就退出这个delay函数,从而就实现了

延时的目的.

但是这里通过定时器这样延时是挺耗费系统资源的

所以这里,咱们的系统中是这样做的

比如这里的delay.h和delay.c

.h文件中 

定义了这几个函数.

#ifndef __DELAY_H
#define __DELAY_H 			   
#include "sys.h"  
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2010/1/1
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
// 
	 
void delay_init(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);

#endif

然后去看他的实现.delay.c中

#include "delay.h"
// 	 
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2010/1/1
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
//  

static u8  fac_us=0;							//us延时倍乘数			   
static u16 fac_ms=0;							//ms延时倍乘数,在ucos下,代表每个节拍的ms数
	
//1.---注意这里SYSTEM_SUPPORT_OS定义这个的目的是
//当使用ucos等实时操作系统的时候,这个就有用了,现在用不到.
//
#if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
//    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
//  delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
//    delay_ostimedly:用于OS延时,可以引起任务调度.

//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif

//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif


//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedLock(&err);							//UCOSIII的方式,禁止调度,防止打断us延时
#else											//否则UCOSII
	OSSchedLock();								//UCOSII的方式,禁止调度,防止打断us延时
#endif
}

//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{	
#ifdef CPU_CFG_CRITICAL_METHOD   				//使用UCOSIII
	OS_ERR err; 
	OSSchedUnlock(&err);						//UCOSIII的方式,恢复调度
#else											//否则UCOSII
	OSSchedUnlock();							//UCOSII的方式,恢复调度
#endif
}

//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
	OS_ERR err; 
	OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);	//UCOSIII延时采用周期模式
#else
	OSTimeDly(ticks);							//UCOSII延时
#endif 
}
 
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{	
	if(delay_osrunning==1)						//OS开始跑了,才执行正常的调度处理
	{
		OSIntEnter();							//进入中断
		OSTimeTick();       					//调用ucos的时钟服务程序               
		OSIntExit();       	 					//触发任务切换软中断
	}
}
#endif

			   
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	u32 reload;
#endif
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为K	   
	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   

	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    

#else
	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
#endif
}								    

#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 ticks;
	u32 told,tnow,tcnt=0;
	u32 reload=SysTick->LOAD;					//LOAD的值	    	 
	ticks=nus*fac_us; 							//需要的节拍数	  		 
	tcnt=0;
	delay_osschedlock();						//阻止OS调度,防止打断us延时
	told=SysTick->VAL;        					//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;		//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;				//时间超过/等于要延迟的时间,则退出.
		}  
	};
	delay_osschedunlock();						//恢复OS调度									    
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{	
	if(delay_osrunning&&delay_osintnesting==0)	//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    
	{		 
		if(nms>=fac_ms)							//延时的时间大于OS的最少时间周期 
		{ 
   			delay_ostimedly(nms/fac_ms);		//OS延时
		}
		nms%=fac_ms;							//OS已经无法提供这么小的延时了,采用普通方式延时    
	}
	delay_us((u32)(nms*1000));					//普通方式延时  
}
#else //不用OS时
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; 					//时间加载	  		 
	SysTick->VAL=0x00;        					//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数	  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
	SysTick->VAL =0X00;      					 //清空计数器	 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864 
void delay_ms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;				//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;							//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;	//开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));		//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
	SysTick->VAL =0X00;       					//清空计数器	  	    
} 
#endif 

//1.---注意这里SYSTEM_SUPPORT_OS定义这个的目的是
//当使用ucos等实时操作系统的时候,这个就有用了,现在用不到.
//
#if SYSTEM_SUPPORT_OS                            //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).

这里我们先看这个delay_init()

然后先看上面

上面有两个因子,一个是微妙的因子,一个是毫秒的因子.

毫秒的意思是,比如我们定时1毫秒,需要多少个systick周期.

实际上当systick的时钟确定好了以后,这两个数就是个常数了.

这里首先设置systick定时器的时钟来源,然后

再设置fac_us,也就是一微妙,需要9个时钟周期.

然后

这部分os的,先不看

然后

可以看到这里fac_ms=fac-us *1000 所以这里就可以看出来

毫秒就是把微妙*1000实现的.

也就是初始化的时候,就是把这个延时微妙和毫秒设置好了.

延时微妙需要9次,也就是系统时钟的8分之一

也就是这个微妙因子是9,也就是系统时钟的8分之一

也就是系统时钟的8分之一是1微妙.

这里还是要说明一下,我们知道一秒是72000000次震荡.

然后,1ms的话是,72000/72000000,也就是72000000/1000 次震荡,也就是72000次震荡是1ms

1us的话就是72次震荡.

这里不太明白怎么计算的.

那么前面说了,由于装载也需要1个时钟周期,所以如果指定1000个时钟周期后中断,一般都指定为999

而毫秒因子是9000.

有了这两个因子以后,就可以实现下面两个函数了

delay_us和delay_ms这两个函数了.

这个函数的意思是:

因为1us要震荡,fac_us个时钟周期,然后这里多少us就震荡多少次fac_us,相乘就可以了,这里就是给

Load寄存器,设置,要从多少个时钟周期,开始倒计数

然后,就是清空计数器,

然后使能CTRL寄存器,也就是开始倒计数.,也就是设置的CTRL寄存器的最低位

然后一直去读取CTRL寄存器,

直到,判断这个CTRL寄存器的位16,也就是判断是否倒计数到0了.

到了0以后,然后就把这个计数器关闭,然后再清空计数器.

这个延时毫秒也是一样的.

这里要注意,这个微妙,也好,毫秒也好,这里

都是有限制的,也就是说,因为LOAD寄存器是24位有效,

也就是最大值不能超过2^24-1这个值.

这里注意使用的时候首先引入delay.h然后

要调用delay_init(),这个作用是,先去选择systick定时器的时钟源,然后

再去计算出两个常数.

猜你喜欢

转载自blog.csdn.net/lidew521/article/details/108168448