基于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,一个基础的用法就是用来做高精度延时:
- 初始化SysTick,并设置重置初值,也就是SysTick->LOAD
- 设置用户计数变量和初值
- 使能SysTick
- SysTick计数到零,中断触发,用户计数变量递减
- 判断用户计数变量是否归零
在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时,使用逻辑分析仪分析了一下信号:
通过上图可以看到,这个延时,快准狠!!!