1 SysTick介绍
官方详细介绍在 STM32F10xxx Cortex-M3编程手册 - 英文版 4.5 SysTick timer(STK)。
1.1 SysTick是什么
SysTick(System Timer)是一种系统计时器,是一个24位只能向下递减的计数器(计数器每计数一次的时间为1/SYSCLK),通常用于嵌入式系统中,用于提供基本的系统定时和时钟服务。SysTick 定时器是 ARM Cortex-M 处理器架构中的一个标准组件(所有的ARM Cortex-M 内核的单片机都具有这个定时器),存在于内核。
1.2 SysTick特点
-
1.硬件内置:SysTick 定时器是 ARM Cortex-M 处理器内部集成的计时器,因此几乎所有 Cortex-M 系列处理器都支持它。这意味着它在这些处理器中是标准的、内置的组件,无需额外的硬件组件。
-
2.简单性:SysTick 定时器的设计相对简单,使得它易于配置和使用。通常,你只需编写少量的寄存器配置代码,就可以启用和控制定时器的基本功能。
-
3.低功耗:SysTick 定时器通常设计成具有低功耗特性,这使得它在嵌入式系统中非常节能。它可以在不需要时进入低功耗模式,以降低功耗。
-
4.通用性:虽然 SysTick 定时器的具体实现可能因处理器型号而异,但它遵循 ARM Cortex-M 处理器的通用规范,因此可以在不同的 Cortex-M 处理器上使用相似的代码。这增加了代码的可移植性。
-
5.计数精度:SysTick 定时器的计时精度通常与处理器的时钟频率相关。因此,你可以根据需要选择合适的时钟源,以实现所需的计时精度。
-
6.多用途性:除了用作简单的延时定时器和任务调度器之外,SysTick 定时器还可以用于各种用途,如性能分析、时间戳生成、实时性能监测等。
1.3 SysTick功能
-
1.提供基本定时功能:SysTick 定时器允许你生成定期的定时中断,以执行特定的任务或操作。你可以设置定时器的计数器初始值和中断间隔,从而实现不同的定时需求。
-
2.任务调度:SysTick 定时器在实时操作系统(RTOS)中广泛用于任务调度。通过定期生成中断,RTOS可以切换不同的任务,从而实现多任务处理。
-
3.延时功能:你可以使用 SysTick 定时器来创建延时函数,以等待特定的时间间隔。这对于需要进行精确时间控制的应用程序非常有用。
-
4.低功耗模式:SysTick 定时器通常设计成具有低功耗特性,可以在不需要时进入低功耗模式,从而减少能量消耗。
-
5.性能监测:SysTick 定时器可以用于测量代码执行时间,帮助进行性能分析和优化。通过记录定时器的值,你可以计算代码段的执行时间。
-
6.系统时钟的一部分:SysTick 定时器通常与系统时钟相关,它可以作为系统的一个部分来提供定时服务,帮助控制和同步不同部分的操作。
-
7.时间戳生成:SysTick 定时器可以用于生成时间戳,以记录事件的发生时间。这对于数据记录和调试非常有用。
-
8.周期性操作:你可以配置 SysTick 定时器以生成周期性的定时中断,用于执行重复的操作,如数据采集或状态检查。
2 SysTick定时时间计算
t = reload * (1/clk)
- t :一个计数循环的时间,跟reload与clk有关。
- CLK:72M或者9M,由CTRL寄存器配置。
- reload:24位,由用户自己配置。
当 CLK = 72M 时, t = (72) * (1/72M) = 1us
当 CLK = 72M 时, t = (72000) * (1/72M) = 1ms
时间单位换算:1s = 1000ms = 1000 000us = 1000 000 000ns
3 功能框图讲解
- 在counter时钟的驱动下,从reload初值开始向下递减计数,当reload值减到0是,产生中断和置位COUNTERFLAG标志。
- 然后reload又从初值开始向下递减,重复循环。
4 SysTick寄存器
SysTick 是属于 CM3 内核的外设,有关寄存器的定义和部分库函数都在 core_cm3.h 这个头文件中实现。
SysTick系统定时器有4个寄存器:CTRL(SysTick控制及状态寄存器)、LOAD(SysTick重装载数值寄存器)、VAL(SysTick当前数值寄存器)、CALIB(SysTick校准数值寄存器)。在使用SysTick系统定时器进行计数时,只需要配置前三个寄存器。
4.1 CTRL
CLKSOURCE位:为0的话是9M,为1的话是72M。
TICKINT:使能中断,将TICKINT置1会产生中断,置0不会产生中断。
4.2 LOAD
24位有效。
4.3 VAL
4.4 CALIB
4.5 SysTick寄存器结构体
// core.cm3.h 文件
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */
__IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */
__IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */
__I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */
} SysTick_Type;
4.6 SysTick配置库函数
4.6.1 SysTick初始化函数
// core.cm3.h 文件
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* reload 寄存器为24bit,最大值为2^24 */
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
/* 配置 reload 寄存器初始值 */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
/* 配置中断优先级为 1<<4 -1 = 15,优先级为最低 */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
/* 配置 counter 计数器的值 */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
/* 配置SysTick 的时钟为 72M */
/* 使能中断 */
/* 使能SysTick */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
- __NVIC_PRIO_BITS:
#define __NVIC_PRIO_BITS 4 /*!< STM32 uses 4 Bits for the Priority Levels */
如果想要更改SysTick中断的优先级,可以更改 1<<__NVIC_PRIO_BITS) - 1 处的值。
改为 0000 的话优先级最高。
4.6.2 SysTick中断优先级设置函数
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
/* 设置优先级 :Cortex-M3 系统中断 */
if(IRQn < 0) {
SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */
/* 设置优先级 : 外设中断 */
else {
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */
}
5 优先级比较
从4.6.2 SysTick中断优先级设置函数 章节里的 NVIC_SetPriority() 函数中我们可以知道:
- SysTick中断优先级配置的是SCB->SHPRX寄存器;
- 而外设的中断优先级配置的是NVIC->IPRX寄存器,有优先级分组,有抢占优先级和子优先级的说法(由SCB->AIRCR寄存器的[8:10]位决定)。
== 那么SysTick的优先级跟片上外设的优先级相比,哪个高?==
-
STM32里面无论是内核还是外设都是使用4个二迚制位来表示中断优先级。
-
中断优先级的分组对内核和外设同样适用。当比较的时候,只需要把内核外设的中断优先级的四个位按照外设的中断优先级来分组来解析即可,即人为的分出抢占优先级和子优先级。
以 4.6.1 SysTick初始化函数 章节 SysTick_Config()中配置中断优先级的代码为例进行分析:
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); // (1<<__NVIC_PRIO_BITS) - 1) = 15
- 假设代码中设置的普通外设中断的分组为 组2:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- 组2的前2位表示抢占优先级,后2位表示子优先级,那么 1111(SysTick_Config中设置的数) 的前2位就表示抢占优先级为3,后2位就表示子优先级为3。
6 微妙与毫秒代码讲解
为了使工程更加有条理,我们把SysTick相关的代码独立分开存储,方便以后移植。新建“bsp_systick.c”文件与"bsp_systick.h"文件。
// bsp_systick.h 文件
#ifndef __BSP_SYSTICK_H
#define __BSP_SYSTICK_H
#include "stm32f10x.h" // GPIO所有外设的寄存器定义都在这个头文件中
#include "core_cm3.h" // 内核里所有外设的寄存器定义都在这个头文件中
void SysTick_Delay_us(uint32_t us);
void SysTick_Delay_ms(uint32_t ms);
#endif /* __BSP_SYSTICK_H */
// bsp_systick.c 文件
#include "bsp_systick.h"
void SysTick_Delay_us(uint32_t us)
{
uint32_t i;
SysTick_Config(72);
for(i = 0; i < us; i++)
{
while( 0 == (SysTick->CTRL & (1<<16)));
}
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; // 失能,停止计数
}
void SysTick_Delay_ms(uint32_t ms)
{
uint32_t i,j;
SysTick_Config(72000);
for(i = 0; i < ms; i++)
{
j = i;
while( 0 == (SysTick->CTRL & (1<<16)));
}
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
// main.c 文件
#include "stm32f10x.h"
#include "bsp_systick.h"
#include "bsp_led.h"
int main(void)
{
LED_B_GPIO_Config();
GPIO_SetBits(LED_PROT, GPIO_Pin_All);
while(1)
{
LED_B(ON);
SysTick_Delay_ms(500);
LED_B(OFF);
SysTick_Delay_ms(500);
}
}
-
在SysTick_Delay_ms()函数中将 while( 0 == (SysTick->CTRL & (1<<16))); 改为 while( 1 != (SysTick->CTRL & (1<<16))); 的话灯就会一直亮着。
- 原因:修改为 while( 1 != (SysTick->CTRL & (1<<16))) 之后,SysTick_Delay_ms()函数的频率被改变了,我们将main函数中 SysTick_Delay_ms(500); 改为 SysTick_Delay_us(500000); 的话依旧能观察到灯在闪烁。