一:基础知识
-
SysTick 是什么?
SysTick:系统定时器,是一个 24bit 的向下递减的计数器(计数器每计数一次的时间为 1/SYSCLK,SYSCLK 为系统的是时钟频率(72M、168M等))。属于 CM3 内核中的一个外设,并且内嵌在 NVIC 中。
-
SysTick 的特点
- SysTick 常常用作 延时 或 用于产生时基,维持操作系统的“心跳”。即该硬件定时器用来产生某些使用了时基的系统(FreeRTOS、UCOS 等)需要的“滴答”中断。
- SysTick 是属于 CM3 内核的外设,所有基于 CM3 内核的单片机都具有这个系统定时器。使得操作系统和其它系统软件在 CM3 器件间可以很容易的移植。
- 该定时器的时钟源可以是内部时钟(FCLK, CM3 上的自由运行时钟),或者是外部时钟( CM3 处理器上的 STCLK 信号)。 具体需要检视芯片的器件手册来决定选择什么作为时钟源。
- 节省 MCU 外设资源(不浪费 MCU 内部集成的 TIMER),睡眠模式下也可以工作。
SysTick 定时器可以选择不同的时钟源,可以产生中断,并且 SysTick 定时器是一个倒计数的计数器,每隔一个时钟周期计数值减一,计数至 0 后重新从计数初值处开始倒计数。因此,SysTick 也可用作精准延时。 SysTick 定时器在《STM32F10x- 参考手册》里一个屁都没放,只有在《ARM Cortex-M3 技术参考手册》和《ARM Cortex-M3 权威指南》才找到相关寄存器的介绍。
二:SysTick 的时钟频率
- 在《STM32F10x- 参考手册》中查看有关 SysTick 时钟频率的时钟树。如下图
从图中可以看出 SysTick 有两种模式:一种是 8 分频模式(AHB/8),一种是 FCLK 模式(AHB)。AHB 最大频率可为 SYSCLK (在 f1系列为 72M,f4 系列为 168M )。详细参看《STM32F10x 参考手册》。
三:SysTick 的配置
- 《ARM Cortex-M3 权威指南》中 SysTick 包含四个寄存器,都是 24 位的寄存器,分别是:
- 从上图可以得知,Systick 寄存器 分别为:
- CTRL:控制和状态寄存器。主要用作 配置 SysTick 的时钟源、开启 SysTick 定时器中断以及开启 SysTick 定时器。
- LOAD:重装载数值寄存器。主要作用就是配置 SysTick 的重装载值(一般是配置为 1ms 或者1us 需要递减计数的数值。比如在频率为 72M 下,1us 就需要计数 72 下,1ms 需要递减 72000 下。)并且需要注意的是,SysTick 定时器是一个 24 位递减计数器,所以重装载数值寄存器 LOAD 仅仅用到低 24 位(bit0 ~ bit23),从而使得可以设置的最大值为 16777215。
- VAL:当前数值寄存器。主要作用就是用 延时函数中。(在后面的 us 延时函数中有用到)。
- CALIB:校准数值寄存器。我没怎么用过,就不怎么清楚。
综上所述,SysTick 是一个 24 位的倒计数定时器,当技术到 0 时,会产生 “滴答” 并 将从 RELOAD 寄存器中自动重装载定时初值。只要 SysTick 控制及状态寄存器中的使能位没有清 0,SysTick 定时器就会一直运行。
-
在 core_cm3.h 中声明了可使用的 SysTick 的结构体,声明如下:
typedef struct { __IO uint32_t CTRL; /*控制和状态寄存器 */ __IO uint32_t LOAD; /*重装载数值寄存器*/ __IO uint32_t VAL; /*当前数值寄存器*/ __I uint32_t CALIB; /*校准数值寄存器*/ } SysTick_Type;
-
其次,在 core_cm3.h 中,将 (SysTick_Type *) 进行宏定义为 SysTick。方便后面直接访问结构体内相关成员。
SysTick 初始化编程步骤
-
配置 SysTick 时钟源
固件库函数:SysTick_CLKSourceConfig( );
头文件:misc.h
参数: SysTick_CLKSource_HCLK_Div8 --> AHB 的8分频
SysTick_CLKSource_HCLK --> AHB
-
配置 SysTick 的重装载值
-
开 SysTick 中断 以及 开启 SysTick 定时器
四:SysTick 实现精准延时并兼容 FreeRTOS
SysTick 定时器初始化脑图
SysTick 进行初始化代码
void delay_init(u8 SYSCLK)
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
fac_us=SYSCLK; //不论是否使用 OS,fac_us 都需要使用,即 记录 1us 所需要的节拍数
reload=SYSCLK; //每 1us 的计数次数
reload*=1000000/configTICK_RATE_HZ; //根据 FreeRTOS 中的 configTICK_RATE_HZ设定溢出时间,FreeRTOS 需要每 1ms 的定时器中断,因此设置的重装载数值为 SYSCLK*1000。
fac_ms=1000/configTICK_RATE_HZ; //代表 OS 可以延时的最少单位,在 os 下,代表每个节拍的ms 数
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启 SYSTICK 中断
SysTick->LOAD=reload; //因为加 入FreeRTOS,所以设置为每 1ms 中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启 SYSTICK
}
- 注释:假设系统频率为 72M HZ,那么 SysTick 定时器每 1s 的计数数值为 72000000。同理,SysTick 定时器节拍数为 72000,那么就是 1ms;为 72,就是 1us。
微妙延时函数的实现
void delay_us(u32 nus) // nus:要延时的 us 数
{
u32 ticks; // 记录需要延时微妙时间的 节拍数
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值,重装载的数值
ticks=nus*fac_us; //需要的节拍数 = 需要延时 us 数 * 1us 所需要的节拍数
told=SysTick->VAL; //通过 VAL 寄存器记录刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
- 延时函数的实现过程如下:
毫秒延时函数的实现
// 延时 nms,会引起任务调度
void delay_ms(u32 nms) //nms:要延时的ms数
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期,即大于1ms
{
vTaskDelay(nms/fac_ms); //FreeRTOS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时,毫秒延时 = 微妙延时 * 1000
}
//延时nms,不会引起任务调度
void delay_xms(u32 nms) //nms:要延时的ms数
{
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}
五:参考
【2】Systick定时器
【5】SysTick定时器
【7】《STM32F10x- 参考手册》
【8】《ARM Cortex-M3 技术参考手册》
【9】《ARM Cortex-M3 权威指南》