Cortex-M SysTick系统滴答定时器详解

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的初始化是有要求的,正确的初始化步骤如下:

  1. 写入重装载值

  2. 清除当前值

  3. 写入控制、状态寄存器

四、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位,拥有更高的检测上限,我们在下篇文章中介绍。

相关推荐
zlinear数据采集卡2 小时前
USB HS 480M高速传输与SRAM记录仪:DABM-D223数据存储系统全解析
arm开发·嵌入式硬件·fpga开发·开源
wuyk5552 小时前
1.栈:后进先出的线性数据结构
c语言·数据结构·stm32·单片机
Hello_Damon_Nikola3 小时前
AC7911从入门到精通
开发语言·嵌入式硬件·物联网·php
gscsded4 小时前
AUTOSAR CanTp 模块学习笔记
单片机·嵌入式硬件·汽车
Hello_Damon_Nikola4 小时前
扰流筋从原理到应用
人工智能·单片机·嵌入式硬件
某林2124 小时前
从“仿真能跑”到“真机能走”:一套轮腿机械狗强化学习系统的工程化实践
人工智能·stm32·嵌入式硬件·架构·机器人·机械狗
luiyarch5 小时前
IP8到底是什么?
嵌入式硬件·车载系统
hongmai6668885 小时前
ESP32-WROVER-B-N8R8:8MB PSRAM加持的经典双核模组,现在还值得选吗?
单片机·嵌入式硬件·物联网·智能路由器·risc-v
VALENIAN瓦伦尼安教学设备6 小时前
ASHOOTER激光对中仪如何通过颜色确定调整是否合适
数据库·嵌入式硬件·算法