STM32-SysTick定时器

1 SysTick定时器简介

        什么是SysTick?
                SysTick定时器也叫SysTick滴答定时器,它是Cortex-M3内核的一个外设,被嵌入在NVIC 中,用于产生SYSTICK异常(异常号:15)。SysTick是一个24位的倒计数系统节拍计时器System Tick timer,每计数一次所需时间为1/SYSTICK,SYSTICK是系统定时器时钟,它可以直接取系统时钟,还可以通过系统时钟8分频后获取。当计数到0时,它就会从Load寄存器中自动重装定时初值,重新向下递减计数,如此循环往复。如果开启SysTick中断的话,当定时器计数到0,将产生一个中断信号。因此只要知道计数的次数就可以准确得到它的延时时间。只要不把CTRL寄存器中的ENABLE清0,它就永不停止。

        SysTick作用?
               
 在单任务引用程序中,因为其架构就决定了它执行任务的串行性,这就引出一个问题:当某个任务出现问题时,就会牵连到后续的任务,进而导致整个系统崩溃。要解决这个问题,可以使用实时操作系统(RTOS)。因为RTOS以并行的架构处理任务,单一任务的崩溃并不会牵连到整个系统。这样用户出于可靠性的考虑可能就会基于RTOS来设计自己的应用程序。SYSTICK存在的意义就是提供必要的时钟节拍,为RTOS的任务调度提供一个有节奏的“心跳”。微控制器的定时器资源一般比较丰富,比如STM32存在8个定时器,为啥还要再提供一个SYSTICK?原因就是所有基于ARM Cortex_M3内核的控制器都带有SysTick定时器,这样就方便了程序在不同的器件之间的移植。而使用RTOS的第一项工作往往就是将其移植到开发人员的硬件平台上,由于SYSTICK的存在无疑降低了移植的难度。SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。

2 SysTick时钟的选

        SysTick寄存器说明在《Cortex-M3权威指南》(SysTick定时器章节)有说明:

        SysTick->CTRL:控制和状态寄存器

        SysTick->LOAD:重装载寄存器

        SysTick->VAL:当前值寄存器

        SysTick->CALIB:校准值寄存器

        用户可以在位于Cortex_M3处理器系统控制单元中的系统节拍定时器控制和状态寄存器(SysTick control and status register ,SCSR)选择SysTick 时钟源。如将SCSR中的CLKSOURCE位置位,SysTick会在CPU频率下运行;而将CLKSOUCE位清除则SysTick会以CPU主频的1/8频率运行。
        在3.5版本的库函数中与SysTick相关的函数有两个:第一个,SysTick_Config(uint32_t ticks),在core_cm3.h头文件中进行定义的。第二个,void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource),在misc.c文件中定义的。
        在core_cm3.h头文件中进行定义的SysTick_Config(uint32_t ticks)函数主要的作用有一下方面:
                1、初始化systick
                2、打开systick
                3、打开systick的中断并设置优先级
                4、返回一个0代表成功或1代表失败

        Uint32_t ticks  即为重装值,这个函数默认使用的时钟源是AHB,即不分频。要想分频,调用void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource),但是要注意函数调用的次序,先调用SysTick_Config(uint32_t ticks),后调用SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
        在misc.c头文件中进行定义的void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)函数主要的作用有一下方面:
                1、选择systick的时钟源,AHB时钟或AHB的8分频
                2、库函数中默认使用的是AHB时钟(在SysTick_Config()函数中设置),即72MHz

        1)core_cm3.h头文件中uint32_t SysTick_Config(uint32_t ticks)函数说明:

/**
 * @摘要    初始化并启动SysTick计数器及其中断。
 *
 * @参数    ticks   两个中断之间的滴答数
 * @返回值  1 = failed, 0 = successful
 *
 * 初始化系统滴答计时器及其中断,并在自由运行模式下启动系统滴答计时器/计数器,以产生定时中断。
 */
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* 不可能的重新加载值,重装载值必须小于0XFF FFFF,因为这是一个24位的递减计数器 */
                                                               
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* 设置重装载值寄存器,SysTick_LOAD_RELOAD_Msk定义见后面*/
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* 设置Cortex-M3系统中断优先级 */
  SysTick->VAL   = 0;                                          /* 加载SysTick计数器值 */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |      //配置CTRL寄存器,选择内核时钟FCLK为时钟源(STM32 的FCLK为72M)
                   SysTick_CTRL_TICKINT_Msk   |      //开启SysTick中断
                   SysTick_CTRL_ENABLE_Msk;          //SysTick使能      
  return (0);                                                  /* 初始化成功 */
}

        在core_cm3.h头文件SysTick相关的寄存器定义:

typedef struct
{
  __IO uint32_t CTRL;                         /*  Offset: 0x00  SysTick控制和状态寄存器 */
  __IO uint32_t LOAD;                         /*  Offset: 0x04  SysTick重新加载值寄存器 */
  __IO uint32_t VAL;                          /*  Offset: 0x08  SysTick当前值寄存器 */
  __I  uint32_t CALIB;                        /*  Offset: 0x0C  SysTick校准寄存器 */
} SysTick_Type;

        在core_cm3.h头文件SysTick寄存器相关的位的宏定义:

/* SysTick控制/状态寄存器定义 */
#define SysTick_CTRL_COUNTFLAG_Pos         16                                             /*!< SysTick CTRL: COUNTFLAG Position */
#define SysTick_CTRL_COUNTFLAG_Msk         (1ul << SysTick_CTRL_COUNTFLAG_Pos)            /*!< SysTick CTRL: COUNTFLAG Mask */

#define SysTick_CTRL_CLKSOURCE_Pos          2                                             /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)            /*!< SysTick CTRL: CLKSOURCE Mask */

#define SysTick_CTRL_TICKINT_Pos            1                                             /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)              /*!< SysTick CTRL: TICKINT Mask */

#define SysTick_CTRL_ENABLE_Pos             0                                             /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos)               /*!< SysTick CTRL: ENABLE Mask */

/* SysTick重新加载寄存器定义 */
#define SysTick_LOAD_RELOAD_Pos             0                                             /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)        /*!< SysTick LOAD: RELOAD Mask */

/* SysTick当前寄存器定义 */
#define SysTick_VAL_CURRENT_Pos             0                                             /*!< SysTick VAL: CURRENT Position */
#define SysTick_VAL_CURRENT_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick VAL: CURRENT Mask */

/* SysTick校准寄存器定义 */
#define SysTick_CALIB_NOREF_Pos            31                                             /*!< SysTick CALIB: NOREF Position */
#define SysTick_CALIB_NOREF_Msk            (1ul << SysTick_CALIB_NOREF_Pos)               /*!< SysTick CALIB: NOREF Mask */

#define SysTick_CALIB_SKEW_Pos             30                                             /*!< SysTick CALIB: SKEW Position */
#define SysTick_CALIB_SKEW_Msk             (1ul << SysTick_CALIB_SKEW_Pos)                /*!< SysTick CALIB: SKEW Mask */

#define SysTick_CALIB_TENMS_Pos             0                                             /*!< SysTick CALIB: TENMS Position */
#define SysTick_CALIB_TENMS_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick CALIB: TENMS Mask */

        2)core_cm3.h头文件中void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)函数说明:

/**
  * @摘要   配置SysTick时钟源
  * @参数   SysTick_CLKSource: SysTick时钟源。
  * 该参数可以是以下值之一:
  *     @arg SysTick_CLKSource_HCLK_Div8: SysTick时钟源为AHB时钟除以8。
  *     @arg SysTick_CLKSource_HCLK: 选择AHB时钟作为SysTick时钟源。
  * @返回值 无
  */
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* 检查参数 */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}

        SysTick时钟源的定义:

#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)   //将控制状态寄存器的第二位置0,即用外部时钟源
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)   //将控制状态寄存器的第二位置1,即用内核时钟
#define IS_SYSTICK_CLK_SOURCE(SOURCE)  (((SOURCE) == SysTick_CLKSource_HCLK) || \
                                       ((SOURCE) == SysTick_CLKSource_HCLK_Div8))

3 Systick使用实践

        Systick定时时间的设定:
                
重装载值=systick 时钟频率(Hz) X 想要的定时时间(S)
                如果时钟频率为:AHB的8分频;AHB=72MHz那么systick的时钟频率为72/8MHz=9MHz。若要定时1秒,则重装载值=9000000X1=9000000,调用函数:SysTick_Config(9000000X1)。若要定时1毫秒,重状态值=9000000X0.001=9000,调用函数:SysTick_Config(9000000/1000);

        Systick的中断处理函数:
                在startup_stm32f10x_hd.s启动文件中有定义:DCD     SysTick_Handler         ; SysTick Handler
                根据需要直接编写中断处理函数即可:Void SysTick_Handler (void)
                注意:如果在工程中,加入了stm32f10x_it.c,而又在主函数中编写中断函数,则会报错。因为在stm32f10x_it.c文件中,也有这个中断函数的声明,只是内容是空的。

/**
  * @摘要   这个函数处理SysTick Handler。
  * @参数   无
  * @返回值 无
  */
void SysTick_Handler(void)
{
}

        中断优先级的修改:
                在调用SysTick_Config(uint32_t ticks)之后,调用 void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)。这个函数在core_cm3.h头文件中。具体内容如下:

/**
* @摘要   设置中断的优先级
* @参数   IRQn      用于设置优先级的中断数
* @参数   priority  设定的优先级
*为指定的中断设置优先级。中断号可以是正数来指定外部(设备特定的)中断,也可以是负数来指定内部(核心)中断。
* 注释:   不能为每个核心中断设置优先级。
*/ 
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); } /* 设置Cortex-M3系统中断优先级 */
  else {
    NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);    }        /* 设置Cortex-M3系统中断优先级  */
}

        下面以一个实例来说明:利用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;
       }
}

        以上基本讲完了,下面是我在网上看到的一些其他理解。
        基于STM32F10x V3.5.0库如何操作Systick定时器呢?首先:STM32 的内核库已经提供了这个功能。只要配置SysTick_Config()即可实现。看下面的程序段:

/*
* 函数名:SysTick_Init
* 描述         :启动系统滴答定时器 SysTick
* 输入  : 无
* 输出  :无
* 调用  : 外部调用
*/
void SysTick_Init(void)
{
        if(SysTick_Config(SystemCoreClock/1000))         //1ms定时器
        {
                while(1);
        }
        //SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;            //若无法启动则关闭
}

        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用于程序设计上的延时或是定时作用呢?如下:__IO uint32_t TimingDelay;  定义一个全局变量,注意类型为 volatile的。volatile的作用: 作为指令关键字,确保本条指令不会因为编译器的优化而省略,且要求每次直接读值,然后定义一个延时或是定时函数:

/*
* 函数名:Delay_ms
* 描述         :ms延时程序,1ms为一个单位
* 输入  : - nTime
* 输出  :无
* 示例  : Delay_ms(1) 实现的延时为:1*ms=1ms
* 调用  :外部调用
*/
void Delay_ms(uint16_t nTime)
{
        TimingDelay = nTime;
        //使能系统滴答定时器
        while(TimingDelay !=0);
}

        还要在系统的中断函数文件:stm32f10x_it.c/h  里面,修改系统自带的 SysTick 函数。这个函数要么没有声明或是为空操作。这里加入定时延时里的处理。即中断后,全局变量做个延时处理即可。在stm32f10x_it.c里修改如下:添加外部的声明:extern __IO uint32_t TimingDelay;  修改这个函数:SysTick_Handler  ,这是系统的关于SysTick_Handler的中断服务程序名,在启动文件里如:startup_stm32f10x_hd.s   有它的定义的名字。不要弄错了。否则无法中断处理。

/**
  * @摘要   这个函数处理SysTick Handler。
  * @参数   无
  * @返回值 无
  */
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
        {   
                TimingDelay--; 
        }
}

        以上,即定义配置好了Systick定时器。如何使用呢?很简单。Delay_ms(500);  即为延时500ms。当然,使用前,请先初始化:SysTick_Init();否则无法使用并影响后续的程序运行,这个很重要,就像打开了串口中断,你不清标志位,也同样在接收字符后,CPU中断在那里,而不能继续执行!。使用外设功能,需要初始化!

4 配置延时函数(systick定时器)

4.1 delay_init函数

        该函数初始化了两个重要参数:fac_usfac_ms,同时将SysTick是时钟源配置为外部时钟,具体代码如下:

//初始化延迟函数
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
    //选择外部时钟  HCLK/8
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);

    fac_us = SystemCoreClock/8000000;  //为系统时钟的1/8
    fac_ms = fac_us * 1000;            //1ms = 1us * 1000
}

        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 这里把SysTick的时钟配置为外部时钟,要注意的是SysTick 的时钟源自与HCLK的8分频,如果外部晶振为8MHz,然后倍频到72MHz,那么SysTick的时钟就是72/8 = 9MHz,即是SysTick的计数器每减1,时间就过去了1/9us。所以fac_us = SystemCoreClock/8000000; 表示过了1us,可能有部分人对这句话不太理解,下面做个大概解释,上面说到systick为系统时钟的8分频,假设系统时钟倍频到72MHz,8分频即为9MHz,所以每一个systick时钟周期的时间为1/9us,SystemCoreClock/8000000 的意思是算出 1us 需要几个 systick 周期,即72000000/8000000 = 9个systick时钟周期,fac_us = 9 * 1/9us = 1us 没毛病,还没看懂的多看几遍就理解了,接下来的fac_ms = fac_us * 1000,1ms等于1000 us 这个不用怎么解释了。
        该注意的是delay_init函数需要在main函数的开头调用作为对systick定时器的配置,fac_us和fac_ms变量需要在定义为全局变量,因为接下来的两个函数还将用到。

static uint8_t   fac_us;   //us延时倍乘数
static uint16_t  fac_ms;   //ms延时倍乘数,在ucos下,代表每个节拍的ms数

4.2 delay_us函数

        该函数用来延时指定的us,其参数nus为要延时的毫秒数,代码如下:

//功能:     delay_us
//参数:     nus
void delay_us(uint32_t nus)
{
    uint32_t 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;                        //清空计数器
}

        以上是配置SysTick的寄存器实现的延时,有兴趣的可以参阅core_cm3.h头文件CTRL、LOAD、VAL、CALIB 这4个寄存器。

4.3 delay_ms函数

        该函数用来延时指定的ms,其参数nms为要延时的毫秒数,代码如下:

//功能:     delay_ms
//参数:     nms  注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms,对72M条件下,nms<=1864 
void delay_ms(uint16_t nms)
{
    uint32_t temp;

    SysTick->LOAD = 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;                        //清空计数器	
}

        创建 delay.h 加入上面3个函数的声明,然后在main函数或者其他函数中直接调用 delay_us() 函数和 delay_ms() 函数就可以了。

5 总结

        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的寄存器的方法,具体操作以后再做分析。

  • 9
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值