远小刘 · 2022年12月03日 · 广东

【GD32F427开发板试用】SysTick定时器的ms中断,us计时

1、系统滴答定时器

参考资料《Cortex M3与M4权威指南.pdf》中的9.5 The SysTick timer小节,里面详细讲解了
1、为什么要用SysTick计时器;
2、SysTick定时器的操作情况;
3、如何使用SysTick定时器;
4、使用SysTick定时器。

2、简介

系统定时器【SysTick】它挂载在AHB时钟线上,是一个24bit的向下递减的计数器,计数器每计数一次的时间为1/SystemCoreClock,当前我们的开发板选用的是25M的外部晶振,倍频至200M,且当前提供的标准库设置为不分频,则系统时钟SystemCoreClock等于200M。当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。

3、SysTick寄存器

SysTick寄存器:
CTRL:SysTick控制及状态寄存器
LOAD:SysTick重装载数值寄存器
VAL:SysTick当前数值寄存器
CALIB:SysTick校准数值寄存器

 title=

SysTick控制及状态寄存器:

COUNTFLAG:如果在上次读取本寄存器后 SysTick已经计到了0,则该位为 1;
CLKSOURCE:时钟源选择位 ,0=AHB/8,1=处理器时钟AHB
TICKINT:1=SysTick倒数计数到 0时产生 SysTick异常请求,0=数到 0 时无动作。也可以通过读取COUNTFLAG标志位来确定计数器是否递减到0
ENABLE:SysTick 定时器的使能位

 title=

思路

SysTick->VAL寄存器的值每一个时钟周期就会递减1,当它递减到0时候,SysTick->LOAD的值将会进入SysTick->VAL中,并且SysTick->CTRL的COUNTFLAG位将会置1,如果还使能了中断,将还会进入中断。

延时原理就是通过使能中断,并设定SysTick->LOAD的值以及时钟周期的数值(通常设定为1kHz = 1ms)来实现的。

4、SysTick代码如下:

/** \brief  System Tick Configuration The function initializes the System Timer and its interrupt, and starts the System Tick Timer.
    Counter is in free running mode to generate periodic interrupts.
    
    \param [in]  ticks  Number of ticks between two interrupts.
    
    \return          0  Function succeeded.
    \return          1  Function failed.
    
    \note     When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then the
    function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
    must contain a vendor-specific implementation of this function.
    */
    __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
    {
      if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */
    
      SysTick->LOAD  = ticks - 1;                                  /* set reload register */
      NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
      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 */
    }

M4手册解说

如果你只想产生一个周期性的SysTick中断,最简单的方法是使用CMSIS-Core函数“SysTick_Config”:

   SysTick_Config(uint32_t ticks);

该函数将SysTick中断间隔设置为“ticks”,启用使用处理器时钟的计数器,并启用异常优先级最低的SysTick异常。

例如,如果你有一个200MHz的时钟频率,你想要触发一个1KHz的SysTick异常,你可以使用:

   (SystemCoreClock / 1000);
   变量“SystemCoreClock”应该保持正确的时钟频率值200M。   “SysTick_Handler(void)”将以1khz的速率被触发。

如果SysTick_Config函数的输入参数不能装进24位重新加载值寄存器(大于OxFFFFFF), SysTick_Config函数返回1;否则,它返回0。

个人深入理解

或者可以这么理解AHB为200M,则计数周期为0.02us(1/200M)1ms/0.02us=10^3/0.02=200000,也就是计数1ms要递减200000次,也就是SysTick->VAL递减200000次,所以SysTick->LODA是200000,也可以说ticks参数必须是200000

5、SysTick库函数添加延时函数

先初始化SysTick配置函数:将SysTick函数配置成ms级中断,如果配置成us级中断,中断就会频繁的进入,将会占用cpu的使用资源,

 \brief    configure systick
    \param[in]  none
    \param[out] none
    \retval     none
    */
    #define sys_1ms  1000U
    #define sys_1us  1000000U
    void systick_config(void)
    {
        /* setup systick timer for 1000Hz interrupts */
        if(SysTick_Config(SystemCoreClock / sys_1ms)) 
        {
            /* capture error */
            while(1) 
            {
    
            }
        }
        /* configure the systick handler priority */
    NVIC_SetPriority(SysTick_IRQn,0x00U);
    usTicks=SystemCoreClock/sys_1us;
    }

定义一个全局变量,在每次触发SysTick定时器中断让它加1;

 volatile uint32_t sysTickUptime;
    /*!
    \brief    this function handles SysTick exception
    \param[in]  none
    \param[out] none
    \retval     none
    */
    void SysTick_Handler(void)
    {
        sysTickUptime++;
    }
    
    /*!
    \brief      获取运行时间
    \param[in]  none
    \param[out] none
    \retval     none
    */
    
    uint32_t get_system_run_time(void)
    {
        return sysTickUptime;                 //ms
    }
    
    
    /*
    \brief    delay a time in milliseconds
    \param[in]  count: count in milliseconds
    \param[out] none
    \retval     none
    */
    void delay_ms(uint32_t millis_)
    {
        uint32_t target;
    
        target = sysTickUptime + millis_;
        while (sysTickUptime < target);
    }

us计数器原理:由于SysTick->VAL 的计数范围20 0000~0就是1ms,递减计数器,因此可以根据它的递减计数器,推出us延时。

 //微妙计数器
    uint32_t systick_get_micros(void)
    {
    //register请求编译器尽可能的将变量存在CPU内部寄存器,而不是通过内存寻址访问。
        register uint32_t ms, cycle_cnt;
        do
        {
            ms = sysTickUptime;
            cycle_cnt = SysTick->VAL;//SysTick->VAL 的计数范围20 0000~0就是1ms,递减计数器
        }while (ms != sysTickUptime);
    return (ms*1000)+ (usTicks * 1000 - cycle_cnt)/usTicks; 
    
    }
    
    /********************************************************************************************************
    **函数信息 :void delay_us(u16 nus)
    **功能描述 :延时微秒
    **输入参数 :nms微秒数
    **输出参数 :无
    ********************************************************************************************************/
    void delay_us(uint32_t micros_)
    {
        uint32_t tcnt=systick_get_micros();
        while(systick_get_micros() - tcnt < micros_);
    }

6、函数测试

《灯语测试》
默认离线模式:led1 100ms亮,100ms灭 共两次,周期为1s;
KEY1按下:在线模式,led1 200ms亮,200ms灭 共两次,周期为2s;

7、代码下载

https://gitee.com/ShinesY/gd32-f427.git

推荐阅读
关注数
10708
内容数
187
中国高性能通用微控制器领域的领跑者兆易创新GD系列芯片技术专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息