第一篇中写了一个程序在延时的时候最好把CPU让出, 但是如果只是让出, 什么时候回来不知道, 万一回来的时候还没延时够, 那也不合理. 如下图所示
首先程序A yield(), 让出了CPU, 希望等待10ms后再执行下一个步骤, 结果程序B的那一段程序较短, 很快也 yield()回到程序A, 这种情况就没有达到程序A的预期.
那么如何实现非阻塞的延时呢, 既能让出CPU, 还要到时候回来.
我们需要一个定时器, 每一段时间触发一次中断, 中断中对一个计数值自增, 例如每5ms产生一次中断, 计数值tick++, 通过tick的数值, 就可以知道时间过去了多久. 在cortex-M中常常使用systick作为这个定时器.
程序A执行完第一段, 需要延时10ms, 让出CPU(此时tick为2), 轮到程序B执行一段程序, B执行完后也让出CPU了, 但此时tick为3, A还没延时够, 又没别的程序执行, 那么CPU就空等, 当tick值增加到4的时候, 说明A肯定延时够了, 再次返回A, 同理B也一样.
这种方式可以实现非阻塞的延时, 但是它的缺点也很明显, 精度不高(图中返回A的时候, 延时其实已经超过10ms了), 跟systick的中断周期有关, 如果需要更高精度, 例如1ms中断一次, 则可减小误差, 但是也会带来更多的中断开销.
###protothread中的定时器
struct timer {
int start; //记录启动时的tick值
int interval; //要定时的tick数
};
// 定时器超时判断
static int timer_expired(struct timer *t)
{
// clock_time()会获取当前的tick值, 如果当前值-开始值超过了延时的tick数, 那么就是超时了
return (int)(clock_time() - t->start) >= (int)t->interval; // 思考:溢出会如何呢?
}
clock_time函数需要返回当前tick值, 那么需要在相应的CPU中启用一个定时器, 并对一个全局的tick变量进行自增操作, 例如
volatile int global_tick;
void systick_handler(void)
{
global_tick++;
}
int clock_time(void)
{
return (int)global_tick;
}
void timer_set(struct timer *t, int interval)
{
t->interval = interval; // 存储需要延时的tick数
t->start = clock_time(); //获取当前的tick值
}
将timer_set和PT_WAIT_UNTIL配合使用即可完成延时的功能
timer_set将当前的tick值记录起来, PT_WAIT_UNTIL会检测timer_expired是否超时, 超时即可继续执行下面的程序, 这样就可以实现非阻塞延时.
struct timer input_timer;
PT_THREAD(input_thread(struct pt *pt))
{
PT_BEGIN(pt);
timer_set(&input_timer, 1000); // 延时1000个tick
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
timer_set(&input_timer, 100); // 延时100个tick
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
timer_set(&input_timer, 300);
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
timer_set(&input_timer, 2000);
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
PT_END(pt);
}
当然这种方式又要定义struct timer input_timer; 还要timer_set还要PT_WAIT_UNTIL什么的一大串, 没有delay_ms那么简洁, 后面再想办法让它优雅起来, 这里只为说明原理.
实例工程代码
https://gitee.com/kalimdorsummer/c_language_program_template.git