还好没有把版本号提的太高,不然就尴尬了。
因为鱼鹰的需求一直是周期延时,就没往单次延时方向考虑,后来将笔记发布到知乎之后,有网友由此受到启发,想改进他的延时功能(当时他使用的方法类似 V2.1)。一开始鱼鹰很不明白,为什么明知这个版本的两大缺点,还选择这种方式呢? 1、查询频率限制 2、如果延时任务多,中断处理变得复杂后来慢慢讨论,终于知道为什么了,这是当时的讨论:第一,该网友使用的场景是单次延时,而V2.7是周期延时,如果没有好的策略处理是不能实现单次延时的;第二,虽然该网友的中断变量数会增加,但他巧妙的避免了查询频率这一关键缺陷,所以还是很有参考价值的(希望公众号的道友也能如这位网友一般,把自己的见解留言在文章下,这样的交流对技术的提高是很有帮助的)。相信很多道友在读前面几篇笔记时,有看到鱼鹰重点强调了“查询频率”,但又有多少道友理解了这一词呢?查询频率,换句话说就是代码的执行周期,因为是在延时判断环境下,所以鱼鹰称之为“查询频率”。今天这篇笔记测试环境不再是裸机,而是 RT-Thread 操作系统,通过修改线程延时,能让各位道友更深刻理解“查询频率”一词。延时实现V2.8.0现在先来看看这位网友是如何解决查询频率限制的吧:
可以看到,延时时间非常准确,说延时 20 ms,就延时20 ms,绝不含糊。现在将 main 线程执行频率设置为 6 ms,task线程执行频率还是 1 ms。
测试结果如下:
可以看到,鱼鹰只改变了 main 线程的执行频率,就导致延时时间超过了 20 毫秒,达到 25 毫秒,很不准确。这是因为虽然超时时间到了,但是因为代码还没有执行到,所以导致执行到代码时,已经超过延时时间很久了。所以,对于精确延时,必须注意查询频率。这里还有要注意的一点是,因为 delay 变量在多个地方调用,所以注意互斥保护,因为一旦上次延时没有达到,你再次修改延时时间,那么必然影响上次延时效果,这是 V2.8 两个版本都要考虑的问题,切记!
因为鱼鹰的需求一直是周期延时,就没往单次延时方向考虑,后来将笔记发布到知乎之后,有网友由此受到启发,想改进他的延时功能(当时他使用的方法类似 V2.1)。一开始鱼鹰很不明白,为什么明知这个版本的两大缺点,还选择这种方式呢? 1、查询频率限制 2、如果延时任务多,中断处理变得复杂后来慢慢讨论,终于知道为什么了,这是当时的讨论:第一,该网友使用的场景是单次延时,而V2.7是周期延时,如果没有好的策略处理是不能实现单次延时的;第二,虽然该网友的中断变量数会增加,但他巧妙的避免了查询频率这一关键缺陷,所以还是很有参考价值的(希望公众号的道友也能如这位网友一般,把自己的见解留言在文章下,这样的交流对技术的提高是很有帮助的)。相信很多道友在读前面几篇笔记时,有看到鱼鹰重点强调了“查询频率”,但又有多少道友理解了这一词呢?查询频率,换句话说就是代码的执行周期,因为是在延时判断环境下,所以鱼鹰称之为“查询频率”。今天这篇笔记测试环境不再是裸机,而是 RT-Thread 操作系统,通过修改线程延时,能让各位道友更深刻理解“查询频率”一词。延时实现V2.8.0现在先来看看这位网友是如何解决查询频率限制的吧:
uint8_t lcd_show_time;
void SysTick_Handler()
{
if(lcd_show_time > 1) lcd_show_time--; //延时自减
}
int main(void)
{
//.......
while(1)
{
if(lcd_show_time == 1) //停止延时,使用延时,只在==1时进入一次
{
lcd_show_time = 0; //失效延时
TaskRun();
}
//...........
// if<表达式1> //伪代码
// {
// lcd_show_time = <表达式2>; //触发延时
// }
}
}
这是网友在鱼鹰前面的思路下回复的内容,当时鱼鹰那篇文章采用图片形式,根本无法复制粘贴,所以这位网友能够敲出这些代码进行回复也算是有心了。当时初看代码时,还以为和鱼鹰写的版本一样,所以一直在和这位网友(现在晋升为鱼鹰的道友了,后面就以道友相称了)强调查询频率限制,后面经过不断地讨论后,他理解了鱼鹰的查询频率的意思,鱼鹰也理解了这段代码和鱼鹰写的是不一样的。 我们可以看一下中断处理,发现它并不是直接将变量递减至零,而是留下了一个1,这个1就是用来做最后的延时超时时的处理工作,而一旦处理完成,完成清零。 也就是说,一个变量,被这位道友分成两部分用,前部分用来延时,后一个1用来做超时时间达到的标志位,还有一点就是最终延时时间不需要再减1了。 因为时间递减到1之后,中断不再对其递减,所以这个1一直保留,直到判断超时代码执行(查询)完成,才完成最终的清零操作,轻松实现单次延时功能,而且也不存在查询频率的限制。 确实是相当不错的策略,一个变量就解决一个延时问题,没啥副作用。 如果延时不多,这个策略确实很不错,但是一旦延时增多,中断的负担就会增加,远不如V2.7版本的一条代码高效(中断代码要尽可能的少,执行时间尽可能的短)。 由此我们可以思考,是否能对 V2.7 版本进行改进,达到单次延时的效果呢。 一番思考下,鱼鹰终于找到了一个很好的策略去实现它。延时实现V2.8.5 为了实现单次延时,鱼鹰增加了一个延时变量,也就是说,在鱼鹰这种策略下,如果要实现一次单次延时,必须使用两个变量,这是这个方法的一点缺陷,所以对于内存不足的情况,可以使用那位道友的V2.8.0(这个版本号是随便编的,方便鱼鹰说明)。 现在贴上代码(RT-Thread):uint32_t current_time;
uint32_t flag = 0;
int main(void)
{ // 鱼鹰*公***众**号:鱼鹰谈单片机,
rt_kprintf("欢迎关注:鱼鹰谈单片机\n");
uint32_t time = current_time;
uint32_t delay = ~(uint32_t) 0; // 为什么鱼鹰在这里用 0 翻转?
uint32_t temp;
while(1)
{
if(current_time - time > delay)
{
temp = time; // 这个变量用于测试实际延时时间,不关键
time = current_time; // 更新时间
delay = ~(uint32_t) 0; // 设置为最大值
rt_kprintf("time out %u,delay %u\n",current_time, current_time - temp);
}
if(flag) // 满足条件,准备开始延时
{
flag = 0;
// 设置需要的延时时间,注意减 1,注意如果 20 是变量,要判断它是否大于 0。
delay = 20 - 1;
time = current_time; // 更新时间
rt_kprintf("flag is set %u\n",current_time);
}
rt_thread_delay(7);
}
}
// 这个线程优先级比 main 更高,类似于 systick 比所有线程优先级都高
// 鱼鹰太懒,把这个当成中断处理函数更新时间,但道理一样的
void task_thread_entry(void *parameter)
{
while(1)
{
current_time++;
rt_thread_delay(1); // 1 ms 执行一次
}
}
由于鱼鹰懒得找systick处理函数在哪,也不想修改系统的代码,就使用了一个线程增加时间(实际上该系统有一个函数rt_tick_get() 函数可以使用,但怕有些读者不熟悉,所以自己弄了一个变量替代)。关键性的东西已经注释好了,现在分析一下。为了使时间变量准确增加,递增变量的线程task优先级必须比延时线程main更高(这里的main函数也是一个线程)。首先分析一下为什么这个策略可以实现单次延时?关键点就在于延时时间delay被初始化为最大值,而每次延时完毕也会将其设置为最大值,而判断条件是大于号,也就是说只要delay设置为最大值,那么这个条件永远也满足不了,里面的代码也永远不会执行,这样一来就实现了单次延时的效果,而且这个延时时间也可以任意指定(也有限制条件,可回看V2.7)现在进行第一次测试:设置两个线程执行频率1 ms,在这里的超时代码查询频率也可以认为是1 ms,但由于打印函数比较耗时,所以执行时间较长,好在打印函数也是有条件限制的,影响不大。这里用了一个 flag 来表示触发条件,这个 flag 通过一个系统的 shell 命令 led 设置,int led(void)
{
flag = 0x01;
return 0;
}
MSH_CMD_EXPORT(led, RT-Thread first led sample);
现在输入命令,触发标志位:


推荐阅读:
-THE END-
如果觉得文章对你有帮助,欢迎转发、分享给朋友,感谢你的支持!
如果对本文有问题,欢迎留言!即使没有问题也可以留下走心评论。
如需转载请联系我。
微信公众号「鱼鹰谈单片机」
每周一更单片机知识
长按后识别图中二维码关注