前言
在上一篇文章我们实现了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);
}
}