1. SysTick 是一个向 CPU 提供定时中断信号的计数器,其计数速率是由 Cortex-M 系列处理器的系统时钟频率和 SysTick 计数器的重载值共同决定的。
1.1 Systick 时钟来源之一,Systick 一般设置为1ms 中断一次,为系统任务调度提供服务,24-bit reload寄存器,只能向下计数 Download
2. 查阅Cortex-M3/M4权威指南手册第138 页,表8.9 ,Systick的三个主要寄存器介绍,没列出来的bit位是保留位 reserve
3. Systick的地址、定义
core_cm3.h
/*
* CMSIS_CM3_SysTick
* memory mapped structure for SysTick
*/
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;
#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
#define SysTick_BASE (SCS_BASE + 0x0010) /*!< SysTick Base Address */
#define SysTick ((SysTick_Type *) SysTick_BASE) /*!< SysTick configuration struct */
4. Systick 初始化,1ms一个Systick 中断
static u8 fac_us = 0; // us延时倍乘数, 即1us数了多少个数 (1us对应多少个时钟周期)
static u16 fac_ms = 0; // ms延时倍乘数, 在FreeRTOS下, 代表每个节拍的ms数, 即:1ms数了多少个数 (1ms 对应多少个时钟周期)
/*
* System Clock Source Config
*/
#ifdef SYSCLK_FREQ_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_HSE; // HSE
#elif defined SYSCLK_FREQ_24MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_24MHz; // PLL - 24Mhz
#elif defined SYSCLK_FREQ_36MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_36MHz; // PLL - 36Mhz
#elif defined SYSCLK_FREQ_48MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz; // PLL - 48Mhz
#elif defined SYSCLK_FREQ_56MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_56MHz; // PLL - 56Mhz
#elif defined SYSCLK_FREQ_72MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz; // PLL - 72Mhz
#else
uint32_t SystemCoreClock = HSI_VALUE; // HSI
#endif
/*
#define SysTick_CTRL_TICKINT_Pos 1
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) // Enable Systick interrupt
#define SysTick_CTRL_ENABLE_Pos 0
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) // Enable Systick
#define configTICK_RATE_HZ (1000) // 时钟节拍频率,这里设置为1000,周期就是1ms
* SYSTICK 的时钟配置为 AHB 时钟
* 这里为了兼容 FreeRTOS, 所以将 SYSTICK 的时钟频率改为 AHB 的频率!
* SYSCLK: 系统时钟频率
* reload 为 24-bit 寄存器, 最大值: 16777216 (0xFF FFFF + 1), 在72M下, 约合 0.233s左右 (1 / 72M * 16777216)
* 延时时间 T = reload / systick_clock (s) = 72000 / 72000 000 (s) = 0.001s = 1ms
*/
void delay_init()
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
fac_us = SystemCoreClock / 1000000;
reload = SystemCoreClock / 1000000;
reload *= 1000000 / configTICK_RATE_HZ;
fac_ms = 1000 / configTICK_RATE_HZ;
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
SysTick->LOAD = reload;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
5. 延时 n us
/*
* decription: delay n (us)
* @para nus: delay n us, n[0 ~ 204522252], max: 2^32 / fac_us @fac_us = 168
* return value: none
* Blocking function, occupying CPU
*/
void delay_us(u32 nus)
{
u32 ticks;
u32 told = 0;
u32 tnow = 0;
u32 tcnt = 0; // 统计Systick 数了多少个数
u32 reload = 0;
reload = SysTick->LOAD; // Systick 计数的当前值,数到哪了
ticks = nus * fac_us; // 延时n us需要数 ticks 次
told = SysTick->VAL; // 从哪个数开始数,起始值
while (1)
{
tnow = SysTick->VAL; // 数到哪了,一直算数到了哪了,然后下面再统计数了多少次了
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow; // 统计Systick 数了多个数了, 这里注意一下 SYSTICK 是一个只能递减的计数器就可以了
}
else
{
tcnt += (reload - tnow) + (told - 0); // 统计Systick 数了多个数了
}
told = tnow;
if (tcnt >= ticks) // 当Systick 数了ticks 个数后,延时时间到,表示延时了 n us 时间
{
break;
}
}
}
}
6. 延时n ms
6.1 跑实时操作系统FreeRTOS
/*
* decription: delay n (ms)
* @para nus: delay n ms, n[0 ~ 65535]
* return value: none
* Blocking function, occupying CPU
* 会引起系统任务调度
*/
void delay_ms(u32 nms)
{
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) // 系统已经运行
{
if (nms >= fac_ms) // 延时的时间大于OS的最少时间周期
{
vTaskDelay(nms / fac_ms); // FreeRTOS延时
}
nms %= fac_ms; // OS已经无法提供这么小的延时了, 采用普通方式延时
}
delay_us((u32)(nms * 1000)); // 普通方式延时
}
6.2 没有跑操作系统,nuo奔
/*
* decription: delay n (ms)
* @para nus: delay n ms
* return value: none
* Blocking function, occupying CPU
* 不会引起任务调度
*/
void delay_xms(u32 nms)
{
u32 i = 0;
for (i = 0; i < nms; i++)
{
delay_us(1000);
}
}
7.在 ARM Cortex-M 系列处理器中,SysTick 中断的入口地址是由 NVIC(Nested Vectored Interrupt Controller,嵌套式向量中断控制器)模块自动进行管理和保存的,因此我们无法直接跳转到 SysTick 中断的入口地址。
当 SysTick 计数器减到 0 时,会自动触发 SysTick 中断,并将控制权交给 NVIC 模块,由 NVIC 根据优先级和其他相关配置信息,自动切换到 SysTick 中断服务程序。
在编写代码时,我们只需要提供一个 SysTick 中断服务程序的函数,名称为 SysTick_Handler。当 SysTick 中断被触发时,CPU 会自动跳转到该函数的入口地址,并执行相应的中断处理代码。因此,我们可以在 SysTick_Handler 函数中编写处理中断的代码,例如更新计时器、控制 I/O 口、执行任务等。
需要注意的是,在 Cortex-M 处理器中,中断服务程序的入口地址必须对齐 4 个字节,即地址的最低两位必须为 0。因此,在定义 SysTick_Handler 函数时,需要确保其函数属性或链接器脚本中指定的起始地址符合对齐要求。