刚开始学习编写嵌入式的代码,例如在单片机上控制LED灯500ms亮灭,因为属于初学,对于MCU运行效率没有要求,所以大部分的教程都是delay_ms(500)。平时在一些基本调试中,对我们影响很小。但是我们需要了解,当程序使用大量类似delay形式的函数时,会对程序造成严重阻塞。
以delay延时函数为例:一般写程序,都是通过while()或者for()加上条件判断,进行循环累加,如果延时函数执行过程中,有中断响应执行,但是当中断服务函数执行完毕,程序跳回后台主循环执行的时候,由于延时函数里面是条件循环,在条件没有符合要求的时候,程序将一直堵塞到延时函数,无法执行响应中断处理函数的次级函数。
e.g. 曾经在学校项目中,在控制电机的程序中对于匀加速匀减速的部分添加了delay函数,实际设备运行起来后,别说匀加/减速,就是加速减速变化都被阻塞严重,严重影响了操作的体验。所以下面介绍一下我操作的几种解决阻塞的程序实现。
/*while形式*/ void Delay_ms(u16 nms) { while(nms--) { u32 temp; SysTick->LOAD=(u32)1*fac_ms; SysTick->VAL =0x00; SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; do {temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; SysTick->VAL =0X00; } } /*for 形式*/ void delay_ms(void) /{ for(u8 i =0;i<230;i++) for(int j =0;j<320;j++); }
总述
1.通过运用RTOS,线程里面调用系统提供的延时等函数,实现解决阻塞。 2.全局变量方法,定时器计数条件判断替代延时。 3.使用状态机方式,分解动作,替代延时实现。 4.使用链表式,与第三种相似。
一、 RTOS方法
诚然现在RTOS很火,大家可以使用各种实时操作系统,利用优化后的delay函数,这个时候我们可以随便调用delay而不用担心阻塞,因为在RTOS中,在线程使用系统提供的delay函数,线程会被内核挂起,系统会自动切换到其他优先级别更高的线程运行。但是一定记住不是所有程序都适合RTOS。
参照其他人的博文:“如果程序任务可折分性较差,折分后的各个任务之间有 N 多的同步问题和复用资源问题,此时不要用多任务操作系统,我们不能因为任务而任务,任务多并没有想象的好,多任务是用降低实时性来换取软件开发的独立性,不要被实时多任务操作系统的实时两个字骗了,这个实时只是相对于其它非实时性多任务操作系统来讲的,实时性最高的当然是你自己编写的单任务程序。”
RTOS的延时代码做了底层的变化,和我们写的while等循环代码有很大差别。RTOS延时函数进入之后会将需要延时的线程挂起进入阻塞态,底层时钟节拍计数器进行定时,直至预约的延时时间到来,再利用任务调度器唤醒线程,线程按照RTOS的优先级执行。
1.RT_Thread 延时代码部分
2.U/COS III 延时代码部分
3.FreeRTOS 延时代码部分
各大RTOS官网资料
可以按照“RTOS”关键词自行搜索,包括社区资料,源码以及开发手册的提供。下面展示国产操作系统rt_thread官网界面。
正点原子与野火等资料
可以按照”正点原子“关键词自行搜索,包括正点原子的BBS以及各大视频网站都有其资料。
一、 全局变量法
全局变量的方法其实与时间管理的思路很像,将设置变量放置于定时器,然后进行计数器累加,再在主循环或者可以进行反复触发的函数中,通过对此变量数值的判断,从而实现等量delay函数的执行。实现方法也很简单,定义一个变量放置于定时器即可,剩下的通过变量值除以间隔时间来判断时间长短,再进行执行相应的函数。
虽然此种方法比较简单,但是需要大家注意,通过设置全局变量法进行替代delay函数,只能放置于主循环或者多触发的程序执行部分,如果放置在初始化的部分,则没有实际用处。
代码实现部分 ->注释部分需要重点观看
/*定义一个全局变量*/ static u32 G_timer = 0; /*定时器的时间间隔由用户自己把握,此处为了计算方便,定时器间隔设置为 1ms*/ void TIM_IRQ(void) { G_timer++; if(G_timer % 100 == 0)/*计数器每 100ms 进入执行*/ { taskfun();/*此处函数不可以执行复杂运算,只可以实现简单的IO控制, 以及一些基本的变量赋值、加减的。 如果程序过于复杂,会导致此处中断服务函数造成堵塞*/ } } /*主程序*/ int main(int argc,char** argv) { SystemInit(); static u32 lastcnt1 =0 ,lastcnt2 =0 ; while(true) { if(G_timer % 100 == 0)/*此处不能使用如此函数执行,因为主循环中, 一般晶振频率很高,程序在主循环中执行的间隔小于 计数器执行的频率,也就是计数器计一个数,程序 可能执行多次。所以这种方法不能使用,建议使用下面的方法*/ { taskfun1(); } if(G_timer % 1000 == 0)/*计数器每 1000ms或者叫1s 进入执行*/ { taskfun2();/*此处可以执行复杂的运算,不需要担忧阻塞*/ lastcnt1 = G_timer ; } if(G_timer % 10000 == 0)/*计数器每 10000ms或者叫10s 进入执行*/ { taskfun3();/*注释与taskfun2();相同*/ lastcnt2 = G_timer ; } } }
到现在介绍了两种方法,因为篇幅有限,过长的文字不适合大家一次性阅读,我会在下一篇文章继续分享,解决堵塞的第三种及第四种方法,希望可以帮助到大家,谢谢。