zl程序教程

您现在的位置是:首页 >  系统

当前栏目

【GD32】从0开始学GD32单片机(4)—— SysTick系统定时器详解

系统单片机 详解 开始 定时器
2023-09-11 14:21:44 时间

前言

在上一篇文章我们实现了LED的闪烁,但大家应该注意到,我直接很随便地写了一个软件定时,这个软件定时只是为了能用就行,定时并不准确。
在GD32中要实现精确的延时有几种方法,今天介绍一种最常用的方法——SysTick系统定时器。

系统定时器结构

为了理解系统定时器的结构,我们要从GD32的时钟树入手,了解它的时钟信号是怎么来的。
在这里插入图片描述
上图的红线就指示了系统定时器的时钟来源。首先外部晶振的时钟信号进来,外部晶振一般是8MHz,接着会经过一个叫锁相环的电路结构,这个电路结构的作用就是把8MHz的外部时钟信号倍频到我们想要的时钟频率,像GD32F103C8T6的最高时钟频率为108MHz;各位如果想了解更多锁相环电路的原理可以看下面这个视频。
视频链接:让频率提升几十倍的电路!锁相环的工作原理!
通过锁相环出来的时钟频率再通过分频后就是AHB总线的时钟频率,AHB总线频率就是SysTick的时钟频率,如我们使用最高AHB频率——108MHz,那么SysTick频率也是108MHz。

SysTick内部其实就是一个计时器,但它比GD32的其他定时器要更简单、纯粹,它只有24bit,我们可以让SysTick每隔一段时间产生一个中断来达到计时的效果。

SysTick代码移植

为了使用SysTick来实现计时,我们并不需要从0开始写,官方的固件库贴心地提供了代码,我们只需要将其移植到项目中即可。
打开官方固件库文件夹,在“Template”文件夹下将“systick.h”和“systick.c”复制到根目录的“User”文件夹中。

在这里插入图片描述
在Keil中的文件管理器中加入“systick.c”文件。

在这里插入图片描述

接下来在main文件中include头文件,调用里面的函数就能使用了。

在gd32f10x_it.c文件中加入SysTick的头文件,然后在SysTick的中断服务函数中加入delay_decrement函数。

void SysTick_Handler(void)
{
    delay_decrement();
}

systick代码解析

systick.c文件代码:

#include "gd32f10x.h"
#include "systick.h"

volatile static uint32_t delay;

/*!
    \brief      configure systick
    \param[in]  none
    \param[out] none
    \retval     none
*/
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);
}

/*!
    \brief      delay a time in milliseconds
    \param[in]  count: count in milliseconds
    \param[out] none
    \retval     none
*/
void delay_ms(uint32_t count)
{
    delay = count;

    while(0U != delay);
}

/*!
    \brief      delay decrement
    \param[in]  none
    \param[out] none
    \retval     none
*/
void delay_decrement(void)
{
    if (0U != delay){
        delay--;
    }
}

函数较少只有3个,systick_config用于初始化SysTick寄存器和中断;delay_ms函数用于实现毫秒级的延时;delay_decrement函数用在中断服务中,每次SysTick产生中断就要调用一次这个函数,使delay值自减1,直到delay这个值为0。

我们再来详细研究一下,SysTick的初始化代码。

void systick_config(void)
{
    /* 使其每1ms产生一个中断 */
    if (SysTick_Config(SystemCoreClock / 1000U)){
        /* 捕捉错误 */
        while (1){
        }
    }
    /* 设置SysTick中断优先级 */
    NVIC_SetPriority(SysTick_IRQn, 0x00U);
}

代码一开始调用了一个SysTick_Config函数,这是一个位于core_cm3.h文件中的内联函数。在这个函数里面,程序初始化SysTick寄存器的值,并开启SysTick中断,使能SysTick。

解释一下为什么我们往SysTick_Config函数中传入“SystemCoreClock / 1000U”就能实现1ms的延时。
首先SysTick的时钟频率为108MHz;“SystemCoreClock”是一个宏定义,它对应系统的时钟频率,也就是刚从锁相环出来的时钟频率,但因为我们AHB总线是1分频,所以AHB总线频率等于系统频率。
所以计时器只需要递增108000000 / 1000 = 108000次就能产生1ms的延时;但因为计时器是从0开始递增的,所以程序中往重装载寄存器中填入的值是108000 - 1

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

如果理解了以上的原理那么要理解后面要讲到的TIMER计时器就不难了。

例程

LED闪烁

现象:LED以500ms的间隔闪烁。

#include "gd32f10x.h"
#include "main.h"
#include "systick.h"

int main(void)
{	
    systick_config();  // 初始化SysTick
    
    rcu_periph_clock_enable(RCU_GPIOC);  // 初始化GPIO时钟
    // 初始化GPIO,PC13,推挽输出模式,速度50MHz
    gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
        
    while(1)
    {
        gpio_bit_write(GPIOC, GPIO_PIN_13, SET);
        delay_ms(500);
        gpio_bit_write(GPIOC, GPIO_PIN_13, RESET);
        delay_ms(500);
    }
}