> 文档中心 > 【GD32F427开发板试用】Systick系统定时器的使用

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

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:HonestQiao

基于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章节,可以了解系统时钟树的具体信息:

其中具体说明如下:

预分频器可以配置 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时,使用逻辑分析仪分析了一下信号:

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

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

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