首页 > 代码库 > STM32之系统滴答定时器

STM32之系统滴答定时器

一、SysTick(系统滴答定时器)概述

  操作系统需要一个滴答定时器周期性产生中断,以产生系统运行的节拍。在中断服务程序里,基于优先级调度的操作系统会根据进程优先级切换任务,基于时间片轮转系统会根据时间片切换任务。总之,滴答定时器是一个操作系统的“心跳”。

  Cortex-M3在内核部分封装了一个滴答定时器--SysTick,在之前的ARM内核通常是不会把定时器做进内核,定时器都是SOC厂商自己制作的外设。显然,Cortex-M3封装了这么一个定时器,对于将操作系统移植到不同SOC厂商生产的Cortex-M3系类MCU上,带来了极大的方便。Cortex-M3内核统一了这样的一个系统滴答定时器,移植操作系统的时候可以使用内核的定时器,而忽略掉不同厂商生产定时器带来的分歧。

二、SysTick control and status register(STK_CTRL)

  SysTick的控制是极其简单的,它的控制和状态都汇聚在同一个寄存器STK_CTRL上。

  STK_CTRL的每一位的含义英文解释都是很清晰的,不必多说。需要额外讨论的是COUNTFLAG标志位,这个标志位代表的含义是:当计数为0时,也即STK_VAL计数至0时,此标志位置1。

  经过笔者一番摸索,对此位有更多的看法。

COUNTFLAG:  

1、此位与SysTick的中断无关,不是中断标志位,可以算作事件标志位(计数至0事件)。

2、此位可以用作软件查询

3、读写此寄存器都会硬件自动更新COUNTFLAG的值,当然此位的值软件也是可以改写的。也就是说,倘若我们轮训查看COUNTFLAG是否置1(也就是计数是否结束)。当SysTick硬件上计数为0,COUNTFLAG因此硬件自动置1。在我们软件读取STK_CTRL的时候,其实SysTick的STK_VAL的值已经不是0(因为自动重装,并且可能计时几个CLK了)。这个时候我们读取到了COUNTFLAG的标志位的1,同时也将COUNTFLAG自动清零。

三、精准延时函数

1、函数实现思路

  函数实现使用“轮询状态位COUNTFLAG”实现精准延时节拍10us。

  在使用的时候,首先调用函数SysTick_Init配置SysTick的定时周期为10us。在延时函数中,当启动定时器后,就调用函数SysTick_GetFlagStatus轮询是否定时10us结束,如果结束就更新一下延时节拍变量nTime。

  由于SysTick定时器自动重装计数器初值,而且SysTick_GetFlagStatus在检测到SET的时候,COUNTFLAG也自动清理。所以软件不必装定时器初值,也不必手动清除标志位COUNTFLAG。

2、函数实现代码

 #include "bsp_sysTick.h"

/**
  * @brief  读取SysTick的状态位COUNTFLAG
  * @param  无
  * @retval The new state of USART_FLAG (SET or RESET).
  */
static FlagStatus SysTick_GetFlagStatus(void) 
{
    if(SysTick->CTRL&SysTick_CTRL_COUNTFLAG_Msk) 
    {
        return SET;
    }
    else
    {
        return RESET;
    }
}

/**
  * @brief  清除SysTick的状态位COUNTFLAG
  * @param  无
  * @retval 无
  */
static void SysTick_ClearFlag(void)
{       
      SysTick->CTRL &= ~ SysTick_CTRL_COUNTFLAG_Msk;
}

/**
  * @brief  配置系统滴答定时器 SysTick
  * @param  无
  * @retval 1 = failed, 0 = successful
  */
uint32_t SysTick_Init(void)
{
    /* SystemFrequency / 1000    1ms中断一次
     * SystemFrequency / 100000     10us中断一次
     * SystemFrequency / 1000000 1us中断一次
     */

       /* 设置定时周期为10us  */
    if (SysTick_Config(SystemCoreClock / 100000)) 
    { 
        /* Capture error */ 
        return (1);
    }

    /* 关闭滴答定时器且禁止中断  */
    SysTick->CTRL &= ~ (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);                                                  
    return (0);
}

/**
  * @brief   us延时程序,10us为一个单位
  * @param  
  *        @arg nTime: Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us
  * @retval  无
  */
void Delay_us(__IO uint32_t nTime)
{     
    /* 清零计数器并使能滴答定时器 */  
    SysTick->VAL   = 0;  
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;     

    for( ; nTime > 0 ; nTime--)
    {
     /* 等待一个延时单位的结束 */
     while(SysTick_GetFlagStatus() != SET);
    }

    /* 关闭滴答定时器 */
    SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}

3、函数的优点和缺陷

优点:

  使用轮询法实现精准延时是可靠的,因为硬件自动重装定时器初值,只要我们在下一次计数结束(为0)之前,将节拍计数变量nTime更新,那么这个延时函数就是可靠的。

  使用轮询法的另一个好处是没有用到全局变量,完全是局部变量搞定了所需功能。倘若使用中断延时,必须利用全局变量给精准延时函数传递参数。

缺陷:

  由于使用的是轮询法,有可能被其他的中断打断,假设其他的中断的服务时间有很长,使得“在下一次计数结束(为0)之前,没有将节拍计数变量nTime更新”,那么延时的时间将要增长。

 

参考资料:《STM32F10xxx Cortex-M3 programming manual.pdf》

     《STM32库开发实战指南》