ARM官方Cortex-M4数据手册链接分享:Cortex -M4 Devices Generic User Guide
一、什么是系统滴答定时器systick
这是一个24位的向下计数器,不可设置分频,每一个时钟周期都会递减1,当计数值递减到0时,会从SYST_RVR寄存器中重新获取装载值,开启新一轮的递减,如此往复循环。当递减到0时,可触发systick_handler中断。该计数器在debug模式下暂停时,会停止向下递减。
时钟周期:该时钟周期可以是MCU主频,也可以是外部时钟,取决于寄存器的配置,一般接入MCU的主频时钟,如72Mhz。
二、systick寄存器介绍
|------------|------|----------|------------------------------------|
| 名称 | 读写类型 | 描述 | 详细描述 |
| SYST_CSR | RW | 控制、状态寄存器 | 可获取systick的溢出状态,控制是否产生中断,设置时钟源,使能等 |
| SYST_RVR | RW | 存放重装载值 | |
| SYST_CVR | RW | 存放当前的计数值 | |
| SYST_CALIB | RO | 校准寄存器 | |
- SYST_CSR

|--------|-----------|--------------------------------------------------------|
| Bits | Name | Function |
| 16 | COUNTFLAG | 当systick溢出时,改为被置1,软件读取可清零 |
| 2 | CLKSOURCE | 设置时钟源: 0 = 外部时钟 1 = 内部MCU HCLK时钟 |
| 1 | TICKINT | 控制systick的中断请求: 0 = 下溢时不触发systick中断 1 = 下溢时触发systick中断 |
| 0 | ENABLE | 控制systick是否使能 0 = 失能 1 = 使能 |
- SYST_RVR

该寄存器存储的是溢出后的重装载值,只用到了24bit,与systick计时器一致。
需要注意的是,只有计数值SYST_CVR从1递减到0的边沿,才会触发COUNTFLAG,所以SYS_RVR的值可以设置为0,只不过是无效值。
如果期望定时器在100个时钟周期后溢出,设置SYST_RVR为99即可,即N-1
- SYST_CVR

存储当前的计数值。
如果尝试写入改值,会将整个字段清零,并且设置COUNTFLAG也为0
- SYST_CALIB

该寄存器为systick的校准属性,可以在MCU厂商的文档里找到更多关于位段的定义。
|----------|-------|-------------------------------------------|
| Bits | Name | Function |
| 31 | NOREF | 表明设备是否有参考时钟 0 = 存在参考时钟 1 = 无参考时钟 |
| 30 | SKEW | 表明TENMS字段是否有效 0 = TENMS字段有效 1 = TENMS字段无效 |
| 23:0 | TENMS | 中断频率100Hz的重装载值 |
三、使用注意事项

在深度睡眠模式下,处理器的时钟信号会停止,这也会停止systick时钟。
确保软件使用对其的字来访问systick寄存器。
systick的重装载值和当前值并不是由硬件初始化的,这意味着对于systick的初始化是有要求的,正确的初始化步骤如下:
-
写入重装载值
-
清除当前值
-
写入控制、状态寄存器
四、systick的两个示例
4.1 systick触发1kHz中断
一般用作RT-Thread等实时操作系统的基准时钟,与系统调度的频率一致
关键在于SysTick_Config函数的参数,工程一般会提供该函数。
该函数的作用是:初始化Systick使用MCU内部时钟,开启中断并使能
入口参数只有一个,就是设置的重装载值。
cpp
/**
\brief System Tick Configuration
\details Initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
\param [in] ticks Number of ticks between two interrupts.
\return 0 Function succeeded.
\return 1 Function failed.
\note When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then the
function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
must contain a vendor-specific implementation of this function.
*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* 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 (0UL); /* Function successful */
}
这里将重装载值设置为MCU主频的千分之一,这样systick每次从MCU主频的千分之一递减到0时,都会触发一次systick中断,1s中会触发1000次中断,也就实现了1KHz systick中断。
cpp
#include <main.h>
#include <drv_usart.h>
int main(void)
{
SystemClockConfig();
NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_2);
SysTick_Config(SystemCoreClock / 1000);
/* 初始化必要的外设 */
usart1_init();
/* 固件信息打印*/
printf("SystemCoreClk: %d \r\n", SystemCoreClock);
printf("FirmWare: APM32F402 FOC Driver \r\n");
printf("Version: V1.00.00 \r\n");
printf("Author: crkylin \r\n");
for (;;)
{
}
}
void SysTick_Handler(void)
{
static uint32_t tick = 0;
tick++;
if (tick % 1000 == 0)
{
printf("%d \r\n", tick);
}
}
查看带时间戳的日志,验证中断频率基本一致。
cpp
[11-16-32:50:915]SystemCoreClk: 120000000
[11-16-32:50:953]FirmWare: APM32F402 FOC Driver
[11-16-32:50:953]Version: V1.00.00
[11-16-32:50:953]Author: crkylin
[11-16-32:51:941]1000
[11-16-32:52:944]2000
[11-16-32:53:943]3000
[11-16-32:54:943]4000
[11-16-32:55:941]5000
[11-16-32:56:940]6000
[11-16-32:57:944]7000
[11-16-32:58:943]8000
[11-16-32:59:941]9000
[11-16-33:00:941]10000
[11-16-33:01:944]11000
[11-16-33:02:943]12000
[11-16-33:03:942]13000
[11-16-33:04:941]14000
[11-16-33:05:945]15000
[11-16-33:06:944]16000
[11-16-33:07:943]17000
[11-16-33:08:941]18000
[11-16-33:09:940]19000
[11-16-33:10:939]20000
4.2 systick用作延时
drv_delay.c
cpp
#include <main.h>
void delay_init(void)
{
/* 清空重装载值和当前计数值 */
SysTick->LOAD = 0U;
SysTick->VAL = 0U;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk; /* 使用内部mcu作为时钟源 */
}
void delay_us(uint32_t nus)
{
/* 一定要使用64bit,否则计算溢出 */
SysTick->LOAD = (uint64_t)nus * SystemCoreClock / 1000000 - 1;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0U);
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; /* 停止计时 */
}
void delay_ms(uint32_t nms)
{
uint32_t i = 0;
for (i = 0; i < nms; i++)
{
delay_us(1000U);
}
}
drv_delay.h
cpp
#ifndef __DRV_DELAY_H
#define __DRV_DELAY_H
void delay_init(void);
void delay_us(uint32_t nus);
void delay_ms(uint32_t nms);
#endif
main.c
cpp
#include <main.h>
#include <drv_usart.h>
#include <drv_delay.h>
int main(void)
{
SystemClockConfig();
NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_2);
/* 初始化PID参数 */
delay_init();
/* 初始化必要的外设 */
usart1_init();
/* 固件信息打印*/
delay_ms(1000);
printf("SystemCoreClk: %d \r\n", SystemCoreClock);
printf("FirmWare: APM32F402 FOC Driver \r\n");
delay_ms(1000);
printf("Version: V1.00.00 \r\n");
printf("Author: crkylin \r\n");
for (;;)
{
}
}
验证两次延迟时间与预期一致
cpp
[11-16-42:00:443]SystemCoreClk: 120000000
[11-16-42:00:443]FirmWare: APM32F402 FOC Driver
[11-16-42:01:466]Version: V1.00.00
[11-16-42:01:466]Author: crkylin
五、总结
以上接上了systick的功能、寄存器,给出了两个基础用法以及注意事项,希望能帮助到大家。
题外话:除了这两个示例外,systick还可以实现代码、函数运行时间检测的功能,在foc等对性能要求高的场景下,可以帮助判断函数的运行时间。思路是在进入函数前,设置装载值为24位最大值,然后启动systick计时,函数执行完毕后,关闭systick的计时。需要注意的是,该用法会存在溢出的可能性,24位最大值为2^24-1 = 16777215,示例中主频为120Mhz,计算得到最大溢出时间为139.8ms,执行时间超过该阈值的函数运行时间将不能被正常统计。
针对代码运行时间统计,更加专业的工具是DWT工具,可以实现CPU周期级别的精度,并且是32位,拥有更高的检测上限,我们在下篇文章中介绍。