我们知道,在RTOS中会提供若干延时函数的API。对于没有仔细了解过RTOS的同学而言,可能会产生这样疑惑:这些API与普通自写的延时函数相比究竟有何优势呢?为什么要额外提供这些API?
在这篇文章中,我们来分析下这些API接口的工作原理、特点以及其相比自写的延时函数,究竟有什么优缺点,我们该如何选择。
普通延时方式
在没有使用RTOS之前,我们可能通过以下方式实现延时或定时。
指令软延时
在比较简单、各条指令执行时间可预测的MCU上,也许你写过如下类似的代码。
void delay () {
int i, j;
for (i = 0; i < 100; i++) {
for (j = 0; j < 1000; j++) {
}
}
}
对于上面这种方式,要实现特定时长的延时,有两种办法:一、根据实际测量的时间,调整比较数值,使得整体时长接近期望值;二、用汇编指令实现,根据每条指令运行时长及运行的次数总和,统计出整个函数的运行时长,然后再同样调整计数值,使得时长接近期望值。由此可见,这种方式用起来也很麻烦。
这种方式有着明显的缺点:受CPU主频的强烈影响,一旦主频发生变化,上面的延时函数必须调整。 所以这种方式只适合不精确的延时,以及主频确定不会发生变化的场合。
定时器软延时
有的同学可能会借助于定时器实现软延时,代码大致如下:
void delay () {
initTimer();
startTimer();
while (!timerIsOverFlow()); // 等待定时器溢出
}
这种方式的工作原理是给定时器设置定时值,然后程序不断检查定时器是否溢出或计数到达。相对前一种方式,显然我们不再需要进行麻烦的计算和统计就能更加容易的设定好延时时间。虽然定时器本身的工作频率是由硬件决定(可能也由主频决定),但是我们可以通过程序根据不同的主频自动计算出相应的配置值,所以其不易受主频影响。
但这种方式也有着明显的缺点:为了延时而占用一个硬件定时器资源。
定时中断处理
上述两种方式还存在另一个问题;在延时期间,程序什么也干不了,只能原地等待。 也许有的人会在等待期间插入一些代码来减少一些无谓的箸待开销;但这种做法并不多见。
所以,有时我们会考虑设定定时器,配置定时器中断,在定时器中断中执行相应的功能;或者在中断中设置时间到达标记,然后在主程序中处理。例如:
void timerISR () {
timeoutFlag = 1;
}
int main () {
setTimer();
for (;;) {
if (timeoutFlag) {
// do something
timeoutFlag = 0;
setTimer();
}
}
return 0;
}
这种方式存在着同样的缺点:为了延时而占用一个硬件定时器资源。相比之下,可以减少很多CPU空转的消耗。
上面几种方式,同时还存在几个严重的问题,很难做到同时给多个任务使用。例如
- 指令/定时器软延时,延时过程中会死等,空耗CPU。这在RTOS环境上不能容忍的;
- 不同任务延时时长可能差异很大,很难做到写一个通用的指令软延时函数;
- 定时器延时/中断方式,一次只能够一个任务使用;如果多个任务使用还得考虑定时器资源竞争。
所以, RTOS还有必要提供另一种模式的延时API。
系统延时方式
RTOS提供的延时API,相比于软延时,其最大的特点是:在任务延时过程中,能够切换到其它任务去运行。这样就避免了死等,可用于执行一些其它功能的代码。显然CPU的利用率就大大提高。 实种接口的实现原理大体类似:
通过一个硬件定时器定期产生中断,称之为时间节拍。在每个时钟节拍发生时,递减任务内部的一个延时计数器,当延时计数器减至0时,将任务插入到就绪列表等待获取CPU运行。而在延时期间,任务一直在延时队列中,此时CPU执行其它非延时任务。
可以看到,只需要一个定时器资源,就要以给系统中所有任务提供延时功能。每次延时的基本单位是一个时钟节拍周期。也就是说每次任务延时,必须至少延时1个周期长。这正是使用这种方式存在的问题,即不能实现更小的时间延时。
此外,在实际使用过程 ,我们会发现在某些情况下会出现延时存在较大误差的情况,下图中说明了这种情况。
因此,这种方式是通过软件资源+牺牲了一定的功能条件下,换取了CPU利用率的提升以及可被多任务通用。
方式比较
前面介绍的几种方式,在实际使用时,并不一定说某种延时方式是最好的。
各种延时方式各有优缺点,根据实际需要选择。
- 如果提升CPU利用率,且延时时间>=1个时钟节拍周期,可考虑延时API,但要注意可能存在最大1个时钟节拍周期的偏差;
- 如果需要延时的精度小于1个周期节拍周期,就只能考虑软延时/定时器延时
- 如果定时器资源足够,且想提升CPU利用率,在定时中断中处理或者中断中向任务发送信号量处理事件
- 也可考虑使用指令软延时/定时器软延时。如果使用定时器软延时,由于要执行配置、启动、检查等操作,代码执行时间较长。如果延时小于执行时长,无法采用此种方式,应考虑用指令延时。
当然,以上只是建议,并不是绝对的。在实际使用过程,还要考虑个人习惯、精度要求等。在某些要求并不苛刻的应用中,有少量的延时误差或者CPU利用率不高,其实也没有多大关系。没有最好的方式,只有最适合的方式!