SysTick_Config()函数的配置
从原理上来说,Systick定时器和开发板上的通用定时器没有区别。从功能上来说,Systick定时器主要是用来用来进行延时的,而通用或者高级定时器往往用来进行PWM输出、输入捕获等功能。至于为什么不用通用定时器或者高级定时器来完成延时功能,则是考虑到节省MCU的资源来做更重要的事。
一、Systick介绍
从原理上来说,Systick定时器和开发板上的通用定时器没有区别。从功能上来说,Systick定时器主要是用来用来进行延时的,而通用或者高级定时器往往用来进行PWM输出、输入捕获等功能。至于为什么不用通用定时器或者高级定时器来完成延时功能,则是考虑到节省MCU的资源来做更重要的事。
SysTick系统定时器是属于内核中的一个外设,内嵌在NVIC中。该定时器是一个24位的向下递减的计数器。在裸机编程中常用做延时函数,而在FreeRTOS中则用来给系统提供时钟的,因此非常重要。
SysTick 是 Cortex-M 系列内核中普遍存在的系统定时器,共 24 位(高 8 位保留),通过不断递减进行计数,到达 0 时将 SYST_CSR 的 COUNTFLAG 标志位置 1 并根据 TICKINT 标志位决定是否产生内核中断。因此,利用 SysTick 实现延时函数就分为中断式和非中断式两种。前者容易产生优先级冲突,也不适合在中断函数中调用;这里选择的是后者,直接对寄存器进行操作和查询。需要注意的是,使用 FreeRTOS 等实时操作系统时,修改 SysTick 会影响系统时钟,建议采用软件延时或使用其他定时器。
SysTick共有4个相关寄存器,通常只用到3个(校准寄存器少用)。
寄存器名称 | 寄存器描述 |
CTRL | SysTick控制及状态寄存器 |
LOAD | SysTick重装载数值寄存器 |
VAL | SysTick当前数值寄存器 |
CALIB | SysTick校准数值寄存器 |
二、systick优先级介绍
(1)裸机中SysTick中断优先级最高
SysTick定时器中断是一个周期性的计时器,在实时操作系统中可以用于提供系统的时钟节拍。SysTick中断具有较高的中断优先级,通常为0(或最低的数值)。由于SysTick定时器的重要性,它通常需要以较高的优先级运行,以确保准确地产生定时中断。
(注意:中断优先级是通过中断优先级寄存器(NVIC_IPR)来配置的。在ARM Cortex-M处理器中,中断优先级的数值越小表示优先级越高,因此PendSV中断的中断优先级数值为最大值,而SysTick中断的中断优先级数值为最小值。)
(2)FreeRTOS中SysTick中断优先级最低
FreeRTOS 在启动任务调度器的函数中设置了 PendSV 和 SysTick 的中断优先级
BaseType_t xPortStartScheduler( void )
{
/* 忽略其他代码 */
/* 设置 PendSV 和 SysTick 的中断优先级为最低中断优先级 */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
/* 忽略其他代码 */
}
三、SysTick_Config(X)的作用
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts */
if (SysTick_Config(SystemCoreClock / 1000U)){
/* capture error */
while (1){
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
SysTick_Config的参数,其实就是一个时钟次数,叫systick重装定时器的值。已知预装载值和时钟频率,那么可以求出进入一次中断的时间
预装载值/时钟频率
(SystemCoreClock / 1000U)/ (SystemCoreClock) S 进入一次中断,也就是1ms进入一次
四、裸机使用systick中断延时1ms
void delay_1ms(uint32_t count)
{
delay = count;
while(0U != delay){
}
}
void delay_decrement(void)
{
if (0U != delay){
delay--;
}
}
void SysTick_Handler(void)
{
delay_decrement();
}
五、裸机使用systick非中断延时1ms
void delay_1ms(uint32_t count)
{
uint32_t ticks;
uint32_t told,tnow,reload,tcnt=0;
reload = SysTick->LOAD; //获取重装载寄存器值
ticks = count * (SystemCoreClock / 1000); //计算定时1ms滴答定时器要计数的时间值
told=SysTick->VAL; //获取当前数值寄存器值
while(1)
{
tnow = SysTick->VAL; //获取当前数值寄存器值
if(tnow!=told) //当前值不等于开始值说明已在计数。切记:滴答定时器是倒计数
{
if(tnow<told) //当前值小于开始数值,说明未计到0
tcnt += told - tnow; //计数值=开始值-当前值
else //当前值大于开始数值,说明已计到0并重新计数
tcnt += reload-( tnow - told ); //told tnow reload (相当于整段事件中只有told到tnow这段时间没有计时
told = tnow; //更新开始值
if(tcnt >= ticks)break; //计数值大于等于要延迟的计数值,则计数完毕
}
}
}
六、nop延时
通过使用__NOP()函数,因为使用了8M晶振9倍频,72MHz,所以一个nop约等于1/72us,所以使用72个nop函数为一个us,然后根据需要的定时时间进行计算。
void delay_us(u32 nTimer)
{
u32 i=0;
for(i=0;i<nTimer;i++){
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
}
}
stm32F407,主频为168Mhz
void delay_u(uint16_t micro)
{
uint32_t Delay = micro * 168/4;
do
{
__NOP();
}
while (Delay --);
}
七、freeRTOS中的systick
(1)在FreeRTOS中SysTick定时器尤为重要,因为它是给FreeRTOS系统提供时钟的。在FreeRTOS中任务的切换即每个任务运行的时间是由SysTick定时器提供的。
(2) 在Cortex-M内核上,FreeRTOS使用Systick定时器作为心跳时钟,一般默认心跳时钟为1ms,进入Systick中断后,内核会进入处理模式进行处理,在Systick中断处理中,系统会在 ReadList 就绪链表从高优先级到低优先找需要执行的任务,进行调度,如果有任务的状态发生了变化,改变了状态链表,就会产生一个pendSV异常,进入pendSV异常,通过改变进程栈指针(PSP)切换到不同的任务。对于相同优先级的任务,每隔一个Systick,运行过的任务被自动排放至该优先级链表的尾部(时间片调度),用户也可以在线程模式下主动触发PendSV,进行任务切换。freeRTOS采取先运行优先级高的任务(优先级优先),然后同等优先级的任务采用时间片轮转
(3)freeRTOS不使用systick进行延时的原因?
delay_ms()这个函数主要是靠不断读取SysTick计数器的值来实现延迟。但是加入了RTOS之后,RTOS强制将systick的中断设置为最低,假设在一个中断优先级比systick高的中断interrupt中调delay_ms()来进行延时,那么由于中断优先级高于systick,从而导致systick无法抢占,也就无法增加计数器的值,就会导致interrupt中断服务函数死等delay_ms()延时,无法退出,从而造成比systick优先级低的中断服务都无法使用,系统也无法调度。
(4)freeRTOS中如何使用systick进行延时
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,reload,tcnt=0;
if((0x0001&(SysTick->CTRL)) ==0) //定时器未工作
vPortSetupTimerInterrupt(); //初始化定时器
reload = SysTick->LOAD; //获取重装载寄存器值
ticks = nus * (SystemCoreClock / 1000000); //计数时间值
vTaskSuspendAll();//阻止OS调度,防止打断us延时
told=SysTick->VAL; //获取当前数值寄存器值(开始时数值)
while(1)
{
tnow=SysTick->VAL; //获取当前数值寄存器值
if(tnow!=told) //当前值不等于开始值说明已在计数
{
if(tnow<told) //当前值小于开始数值,说明未计到0
tcnt+=told-tnow; //计数值=开始值-当前值
else //当前值大于开始数值,说明已计到0并重新计数
tcnt+=reload-tnow+told; //计数值=重装载值-当前值+开始值 (
//已从开始值计到0)
told=tnow; //更新开始值
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
xTaskResumeAll(); //恢复OS调度
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)