持续关注阿杰在线更新保姆式笔记~~坚持日更

一、 什么是SysTick?

这是一个24位的系统节拍定时器system tick timer,SysTick,具有自动重载和溢出中断功能,所有基于Cortex_M3处理器的微控制器都可以由这个定时器获得一定的时间间隔。(Systick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有Systick定时器。)

二、SysTick作用

在单任务引用程序中,因为其架构就决定了它执行任务的串行性,这就引出一个问题:当某个任务出现问题时,就会牵连到后续的任务,进而导致整个系统崩溃。

要解决这个问题,可以使用实时操作系统(RTOS).因为RTOS以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。这样用户出于可靠性的考虑可能就会基于RTOS来设计自己的应用程序。SYSTICK存在的意义就是提供必要的时钟节拍,为RTOS的任务调度提供一个有节奏的“心跳”。

Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。 

Systick定时器是什么?

Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

微控制器的定时器资源一般比较丰富,比如STM32存在8个定时器,为啥还要再提供一个SYSTICK?
原因就是所有基于ARM Cortex_M3内核的控制器都带有SysTick定时器,这样就方便了程序在不同的器件之间的移植。而使用RTOS的第一项工作往往就是将其移植到开发人员的硬件平台上,由于SYSTICK的存在无疑降低了移植的难度。

SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。

要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。

三、SysTick相关的寄存器

/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick
  memory mapped structure for SysTick
  @{
*/
typedef struct
{
  __IO uint32_t CTRL; /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD; /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL; /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB; /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

四、systick相关函数说明

/**
*@brief初始化并启动SysTick计数器及其中断。
*@param ticks两次中断之间的滴答数
*@return 1=失败,0=成功
*初始化系统计时及其中断,然后启动
*自由运行模式下的系统计时/计数器生成
*定期中断。
*/
static __INLINE uint32_t SysTick_Config(uint32_t ticks)

  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);  // Reload value impossible 
     //ticks有效性判断
                                                          
  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 */
}

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;//72MHZ
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;//72MHZ/8
  }
}

五、Systick使用实践

 Systick定时时间的设定

重装载值=systick 时钟频率(Hz)X想要的定时时间(S)

如果时钟频率为:AHB的8分频;AHB=72MHz那么systick的时钟频率为72/8MHz=9MHz

若要定时1秒,则重装载值=9000000X1=9000000,调用函数:SysTick_Config(9000000X1);

若要定时1毫秒,重状态值=9000000X0.001=90000, 调用函数:SysTick_Config( 9000000/1000 );

SysTick_Config的参数,其实就是一个时钟次数,叫systick重装定时器的值。意思就是我要多少个1/fosc 时间后中断一下。

根据学过的物理中的时间与频率的公式:fosc=1/T   T=1/fosc ,fosc为系统的频率。
如果STM32时钟频率为:72MHz,每次的时间为:T=1/72MHz。1秒钟为:1/(每次的时间)=1/(1/72MHz)=72 000 000次。1MHz是:1000 000。

反过来讲。SysTick_Config(72000)代表:72000*(1/72MHz)=1/1000=1(ms)。即定时为1ms。
如果需要1S则,可以通一设置一个全局变量,然后定初值得为1000,这样,每个systick中断一次,这个全局变量减1,减到0,即systick中断1000次,时间
为:1ms*1000=1S。从而实现1S的定时。

因为SysTick定时器是:24位的,最大定时时间为:2的24次方*(1/72MHz)的时间,这里系统频率为:72MHz的情况下。

Systick的中断处理函数

在startup_stm32f10x_hd.s启动文件中有定义。

DCD     SysTick_Handler            ; SysTick Handler

根据需要直接编写中断处理函数即可:

Void SysTick_Handler (void)

{

}

注意:

如果在工程中,加入了stm32f10x_it.c,而又在主函数中编写中断函数,则会报错。

因为在stm32f10x_it.c文件中,也有这个中断函数的声明,只是内容是空的。

/**

  * @brief  This function handles SysTick Handler.

  * @param  None

  * @retval None

  */

void SysTick_Handler(void)

{

}

中断优先级的修改

在调用SysTick_Config(uint32_t ticks)之后,调用 void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)。这个函数在core_cm3.h头文件中。

具体内容如下:

/**
* @brief  Set the priority for an interrupt
*
* @param  IRQn      The number of the interrupt for set priority
* @param  priority  The priority to set
*
* Set the priority for the specified interrupt. The interrupt
* number can be positive to specify an external (device specific)
* interrupt, or negative to specify an internal (core) interrupt.
*
* Note: The priority cannot be set for every core interrupt.
*/
 
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if(IRQn < 0) {
    SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */
  else {
    NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);    }        /* set Priority for device specific Interrupts  */
}

下面以一个实例来说明:

利用systick来实现以1秒的时间间隔,闪亮一个LED指示灯,指示灯接在GPIOA.8,低电平点亮。

#include "stm32f10x.h"
//函数声明
void GPIO_Configuration(void);//设置GPIOA.8端口
u32 t;//定义一个全局变量
int main(void)
{
// SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
       SysTick_Config(9000000);
       SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
       GPIO_Configuration();
       while(1);      
}
 
//GPIOA.8设置函数
void GPIO_Configuration(void)
{
GPIO_InitTypeDef  GPIO_InitStruct;//定义一个端口初始化结构体
       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//打开GPIOA口时钟
       GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//设置为推挽输出
       GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//设置输出频率50M
       GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;//指定第8脚
       GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA.8      
       GPIO_SetBits( GPIOA,  GPIO_Pin_8);//置高GPIOA.8,关闭LED
}
//systick中断函数
void SysTick_Handler(void)
{
t++;
       if(t>=1)
       {
              if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==1)
              {GPIO_ResetBits( GPIOA, GPIO_Pin_8);}      
       }
       if(t>=2)
       {
              if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)==0)
                     {GPIO_SetBits( GPIOA, GPIO_Pin_8);}
                     t=0;
       }
}

利用systick来实现毫秒延迟和微妙延迟

#include "delay.h"
//      
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"                    //ucos 使用      
#endif
 
static u8  fac_us=0;//us延时倍乘数               
static u16 fac_ms=0;//ms延时倍乘数,在ucos下,代表每个节拍的ms数
 
#if SYSTEM_SUPPORT_OS//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
 
(关于OS的代码就省略掉了,以后用到再来补充)
 
//初始化延迟函数
//当使用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
}    
    
#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;                           //清空计数器              

六、总结

1、要使用systick定时器,只需调用SysTick_Config(uint32_t ticks)函数即可,

     函数自动完成:重装载值的装载,时钟源选择,计数寄存器复位,中断优先级的设置(最低),开中断,开始计数的工作。

2、要修改时钟源调用SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource),也可按照 SysTick_Config()中默认设置FCLK不变。

3、要修改中断优先级调用

     void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)

应用说明:

1、因systick是一个24位的定时器,故重装值最大值为2的24次方=16 777 215,

   要注意不要超出这个值。

2、systick是cortex_m3的标配,不是外设。故不需要在RCC寄存器组打开他的时钟。

3、每次systick溢出后会置位计数标志位和中断标志位,计数标志位在计数器重装载后被清除,而中断标志位也会随着中断服务程序的响应被清除,所以这两个标志位都不需要手动清除。

4、采用使用库函数的方法,只能采用中断的方法响应定时器计时时间到,如要采用查询的方法,那只能采用设置systick的寄存器的方法,具体操作以后再做分析。