STM32延时函数详解及操作系统对比(基于HAL库)
我们从接触的第一个跑马灯项目开始,就用到了delay函数,初学者只知道调用一下delay_ms,delay_us就可以进行系统延时。那么对于这个函数,你了解多少呢?下面让我们根据代码分析一下这个函数实现的过程。
首先,delay函数分为裸机版本和操作系统版本,如果你在操作系统的项目下面运行裸机版本的delay函数,那么程序不会按你所想的方式运行,所以在操作系统下面我们应该修改delay函数为兼容系统的,下面我们以ucos系统为例,说说这两个版本的区别。
1.delay_init函数
第一个我们来分析初始化函数delay_init,宏定义SYSTEM_SUPPORT_OS为1的时候是支持操作系统,为0的时候是裸机程序,SysTick 是 MDK 定义了的一个结构体(在 core_m4.h 里面),里面包含 CTRL、LOAD、VAL、CALIB 4 个寄存器。
SysTick->CTRL 的各位定义:
SysTick-> LOAD 的定义:
SysTick-> VAL 的定义:
SysTick-> CALIB的话我们目前不用到,不做描述。
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);这行代码的意思就是SysTick的时钟选择为系统内核时钟,SysTick的时钟来源于HCLK,和系统时钟同样为180Mhz,SysTick-> VAL每减小1就过去了1/180us,fac_us=SYSCLK,这里我们传入的SYSCLK为180,意思就是延时1us需要180个 SysTick 时钟周期。
在不使用操作系统的时候,只要给fac_us写入周期数就可以了,但是在使用操作系统的时候,fac_us不被写入SysTick->LOAD 实现延时,fac_ms=1000/delay_ostickspersec; 代表操作系统可以延时的最少毫秒(当delay_ostickspersec=1000的时候,表示操作系统最少可以延迟1毫秒)
2.delay_us 函数
2.1带操作系统的delay_us函数
//延时nus
//nus:要延时的us数.
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
delay_osschedlock(); //阻止OS调度,防止打断us延时
told=SysTick->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; //时间超过/等于要延迟的时间,则退出.
}
};
delay_osschedunlock(); //恢复OS调度
此函数想对于不带操作系统的delay_us函数,多一个delay_osschedlock(); 来阻止OS调度,防止打断us延时,然后在延时函数结束之前,调用delay_osschedunlock();恢复OS调度。
2.2不带操作系统的delay_us函数
这里利用了时钟摘取法,ticks 是延时 nus 需要等待的 SysTick 计数次数,told 用于记录最近一次的 SysTick->VAL 值,然后 tnow 则是当前的SysTick->VAL 值,通过他们的对比累加,实现 SysTick 计数次数的统计,统计值存放在 tcnt 里面,通过对比 tcnt 和 ticks,来判断延时是否完成,实现 nus 的延时。
3. delay_ms
3.1带操作系统的delay_ms函数
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u16 nms)
{
if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
delay_ostimedly(nms/fac_ms); //OS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
带操作系统的delay_ms函数,会判断需要延时的时间是否大于之前在init里面设置的最小时间周期fac_ms,如果大于fac_ms,就会进行nms/fac_ms次时间延时,并且这段时间操作系统进行正常的任务调度,最后计算nms%=fac_ms,如果存在余数表示最后差nms没有延时完成,所以又用delay_us补上剩余没有延时的时间,此时不进行任务调度。
3.2不带操作系统的delay_ms函数
这个没什么好说的,就是调用了n次delay_us实现。
4.总结
在裸机程序中,delay_ms函数与delay_us函数正常使用即可,在操作系统中,使用delay_ms函数的时候,如果设置的延时时间大于操作系统设置的最小的调度时间,就会进行正常的任务调度拉起当前任务,执行下一个已就绪的任务,直到剩余延时时间小于操作系统设置的最小的调度时间,就进行delay_us函数,注意的是,只要是delay_us函数被调用的时候,系统不会进行任务调度。
作者:千千道