HonestQiao · 2022年11月24日 · 天津市

【GD32F427开发板试用】Systick系统定时器的使用

基于Arm Cortex-M系列内核的MCU,都包含了SysTick定时器。

所谓SysTick即为系统定时器,又称嘀嗒定时器,是Cortex-M内核的一个外设,集成在NVIC中。SysTick是一个24bit的向下递减的计数器,每计数一次的时间为1/SYSCLK。它的节拍,就相当于是MCU的心跳,让系统用整齐的步伐,来运行具体的系统和程序。操作系统需要执行多任务管理,用SysTick产生中断,确保单个任务不会锁定整个系统,同时SysTick还可用于闹钟定时、时间测量等。

GD32F4xx系列是基于Arm® Cortex®-M4处理器的32位通用微控制器,自然也包含了都包含了SysTick。

通过用户手册4.2.1章节,可以了解系统时钟树的具体信息:
image.png

其中具体说明如下:

预分频器可以配置 AHB、APB2 和 APB1 域的时钟频率。AHB 和 APB2/APB1 域的最高时率 分别为 240 MHz/120 MHz/60 MHz。RCU 通过 AHB 时钟(HCLK)8 分频后作为 Cortex 系 统定时器(SysTick)的外部时钟。通过对 SysTick 控制和状态寄存器的设置,可选择上述时 钟或 AHB(HCLK)时钟作为 SysTick 时钟。

通过硬件开发手册,可以了解到GD32F4xx系列的运行频率:

注意:GD32F405xx/ GD32F407xx系列MCU最高主频为168M;GD32F425xx/ GD32F427xx/ GD32F450xx系列MCU最高主频为200M;GD32F470xx系列MCU最高主频为240M。

从上面的信息可以得知,GD32F427开发板最高运行主频为200MHz。
在开发板系统定义文件system_gd32f4xx.c中,也有如下的定义:

/* select a system clock by uncommenting the following line */
//#define __SYSTEM_CLOCK_IRC16M                   (uint32_t)(__IRC16M)
//#define __SYSTEM_CLOCK_HXTAL                    (uint32_t)(__HXTAL)
//#define __SYSTEM_CLOCK_120M_PLL_IRC16M          (uint32_t)(120000000)
//#define __SYSTEM_CLOCK_120M_PLL_8M_HXTAL        (uint32_t)(120000000)
//#define __SYSTEM_CLOCK_120M_PLL_25M_HXTAL       (uint32_t)(120000000)
//#define __SYSTEM_CLOCK_168M_PLL_IRC16M          (uint32_t)(168000000)
//#define __SYSTEM_CLOCK_168M_PLL_8M_HXTAL        (uint32_t)(168000000)
//#define __SYSTEM_CLOCK_168M_PLL_25M_HXTAL       (uint32_t)(168000000)
//#define __SYSTEM_CLOCK_200M_PLL_IRC16M          (uint32_t)(200000000)
//#define __SYSTEM_CLOCK_200M_PLL_8M_HXTAL        (uint32_t)(200000000)
#define __SYSTEM_CLOCK_200M_PLL_25M_HXTAL       (uint32_t)(200000000)
//#define __SYSTEM_CLOCK_240M_PLL_IRC16M          (uint32_t)(240000000)
//#define __SYSTEM_CLOCK_240M_PLL_8M_HXTAL        (uint32_t)(240000000)
//#define __SYSTEM_CLOCK_240M_PLL_25M_HXTAL       (uint32_t)(240000000)

其中,运行频率定义为200MHz。

通常说的,12MHZ=12×10的6次方,即每秒发出12000000个脉冲信号,那么发出一个脉冲的时间就是时钟周期,也就是1/12微秒。

那200MHz,每秒就会发出2000000000个脉冲信号,那一个时钟周期,将达到1/200微秒。

那么在GD32F427开发板上,基于SysTick,就能实现us级的精确计时。

在系统内核定义文件core_m4.h中,有关于SySTick的具体定义:

typedef struct
{
  __IO uint32_t CTRL;                    /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
  __IO uint32_t LOAD;                    /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register       */
  __IO uint32_t VAL;                     /*!< Offset: 0x008 (R/W)  SysTick Current Value Register      */
  __I  uint32_t CALIB;                   /*!< Offset: 0x00C (R/ )  SysTick Calibration Register        */
} SysTick_Type;

其具体含义如下:

  • CTRL:控制和状态寄存器,用于使能SysTick计数
  • LOAD:重装载寄存器,倒计时计数初值
  • VAL:当前值寄存器,当前计数值
  • CALIB:校准值寄存器,系统自动配置的

那要使用SysTick,一个基础的用法就是用来做高精度延时:

  1. 初始化SysTick,并设置重置初值,也就是SysTick->LOAD
  2. 设置用户计数变量和初值
  3. 使能SysTick
  4. SysTick计数到零,中断触发,用户计数变量递减
  5. 判断用户计数变量是否归零

在core_m4.h中,提供了SysTick出初始化的调用:

__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 */
}

从上述代码中可以看到,其将传入的充值初值设置到了SysTick->LOAD,当前计数值SysTick->VAL归零,设置SysTick->CTRL使能中断和定时。

在系统中断服务程序gd32f4xx_it.c中,有关于SysTick中断的调用:

void SysTick_Handler(void)
{
    delay_decrement();
}

我们要使用SysTick,就需要具体定义其具体的处理逻辑,用于用户计数变量的
处理。

在演示代码的systick.c中,有如下的定义:

volatile static uint32_t delay;

void delay_decrement(void)
{
    if(0U != delay) {
        delay--;
    }
}

那么,一旦重新使能SysTick定时器,给delay赋一个初值,那么每经过一次SysTick中断触发,就会递减,直到归零为止。

而我们的代码中,就可以通过判断delay的值,来检查是否经过了所需次数的Tick。因为系统时钟周期为1/200微秒,那么只要设定好合理的Tick重置初值,通过合适的Tick数量,就能实现精确的us计时。

在systick.c中,有演示的SysTick设置:

void systick_config(void)
{
    /* setup systick timer for 1000Hz interrupts */
    if(SysTick_Config(SystemCoreClock / 1000U)) {
        /* capture error */
        while(1) {
        }
    }
    /* configure the systick handler priority */
    NVIC_SetPriority(SysTick_IRQn, 0x00U);
}

上述代码中,取系统时钟频率的千分之一进行设置,就使得每计数一千次,刚好经过1秒,那么每一次就是1毫秒。

参考上述代码,编写可以实现us级别的初始化设置:

void systick_config_us(void)
{
    /* SystemCoreClock / 1000    1ms中断一次
     * SystemCoreClock / 100000  10us中断一次
     * SystemCoreClock / 1000000 1us中断一次
     */
    /* setup systick timer for 1000Hz interrupts */
    if(SysTick_Config(SystemCoreClock / 1000000U)){
        /* capture error */
        while(1){
        }
    }

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

    /* configure the systick handler priority */
    NVIC_SetPriority(SysTick_IRQn, 0x00U);
}

void delay_us(uint32_t count)
{
    delay = count;

    // 使能滴答定时器
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;

    while(0U != delay){
    }
}

在上述代码中,定义了1us中断一次,然后定义了delay_us()函数:

  • 定义用户计数变量初值
  • 使能SysTick定时器
  • 然后检测delay是否归零,归零说明1us时间到达

然后,在程序中,就可以调用上述的初始化函数,以及延时us的操作了。
main.c具体如下:

#include "gd32f4xx.h"
#include "gd32f427v_start.h"
#include "systick.h"

int main(void)
{
    systick_config_us();    //配置系统时钟
    
    rcu_periph_clock_enable(RCU_GPIOC);                                                //使能外部时钟

    gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_6);                //配置端口模式
    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);    //输出选项配置
    gpio_bit_reset(GPIOC, GPIO_PIN_6);                                                //PC6复位

    while(1) 
    {
        gpio_bit_toggle(GPIOC, GPIO_PIN_6);    //反转PC6
        delay_us(1000*1000);
    }

在上述代码中,延时时间为1000*1000us,也就是1000ms,即1秒。

编译烧录代码后,就可以看到板载的LED2按秒亮灭;如果修改延时数值为100* 1000,那么就会大幅提高闪烁频率了,即1/10秒。

当设置为100* 1000时,使用逻辑分析仪分析了一下信号:
iShot_2022-11-24_14.31.18.png
iShot_2022-11-24_14.33.40.png

通过上图可以看到,这个延时,快准狠!!!

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