[原创] 【连载】从单片机到操作系统②
982 查看
10 回复
 楼主 | 发布于 2018-05-09 | 只看楼主
分享到:

从前面的一章内容【连载】从单片机到操作系统①   我们没有引入中断系统的概念,都是让单片机一直以要做的事来进行死循环,无法很快速应对突发情况。

下面,我将引入中断系统的概念。


下面是来自百度百科的解释:

中断系统:中断装置和中断处理程序统称为中断系统。

中断系统是计算机的重要组成部分。实时控制、故障自动处理、计算机与外围设备间的数据传送往往采用中断系统。中断系统的应用大大提高了计算机效率。


不同的计算机其硬件结构和软件指令是不完全相同的,因此,中断系统也是不相同的。计算机的中断系统能够加强CPU对多任务事件的处理能力。中断机制是现代计算机系统中的基础设施之一,它在系统中起着通信网络作用,以协调系统对各种外部事件的响应和处理。中断是实现多道程序设计的必要条件。 中断是CPU对系统发生的某个事件作出的一种反应。 引起中断的事件称为中断源。中断源向CPU提出处理的请求称为中断请求。发生中断时被打断程序的暂停点成为断点。CPU暂停现行程序而转为响应中断请求的过程称为中断响应。处理中断源的程序称为中断处理程序。CPU执行有关的中断处理程序称为中断处理。而返回断点的过程称为中断返回。中断的实现实行软件和硬件综合完成,硬件部分叫做硬件装置,软件部分称为软件处理程序。


画重点:要有中断装置及要处理中断程序。这样的完整的系统才是中断系统,那么对于单片机来说,什么是中断装置呢。我们从简单51单片机可以知道,它有5个中断源,那么这5个中断源就是中断装置,因为它能产生中断信号,让CPU知道。对于更强的芯片,会有更多的中断源,先不介绍。顾名思义,我们作为一个程序员,写程序是家常便饭啦,处理中断程序,那么程序里面要干什么就是由我们自己实现的了。


那么,中断系统又跟我们的程序有啥关系呢?如何提高程序的效率?


这才是今天的重点!!!!


有了中断,亦或者说有了定时器的中断,我们想怎么干就怎么干(有点虚吹了)。当然啦,按照常理,我们肯定希望任务以我们的想法去做,就好比说,让第一个LED以100ms的频率闪烁一次,LED2以200ms的频率闪烁一次,还有其他任务要每1ms执行一次,那么我们总不能用延时吧,因为delay是会一直占用CPU的资源,其他任务就得不到运行啦。这样子,跟我们想象的肯定不一样,那么,如何去实现我们想的呢。


 我们可以使用定时器,当时间到了才让CPU干活。使用一个定时器作为“心跳时钟”(不知道解释对不对,如有错误,请指正),以1ms的周期定时。产生定时器中断。你们我们就能知道从时间啦,那么我们就能以时间为轨道,让CPU在什么时间执行什么任务。

  1. int main()  

  2. {  

  3.     /***各种初始化***/  

  4.     while(1)  

  5.     {  

  6.         if(time_100ms >= TIME_100MS)  

  7.         {  

  8.             time_100ms = 0;  

  9.             LED1_Task();  

  10.         }  

  11.         if(time_200ms >= TIME_200MS)  

  12.         {  

  13.             time_200ms = 0;  

  14.             LED2_Task();  

  15.         }  

  16.         if(time_10ms >= TIME_10MS)  

  17.         {  

  18.             time_10ms = 0;  

  19.             XXX_Task();  

  20.         }  

  21.     }  

  22.  return  0;

  23. }  


不知道你们是否看出这份代码是有很多不足的地方!(如果看不出,可以后台私聊我,给你们讲解一下)基于此,假如多人问的话,我下一篇文章会讲解一下下。

 

所以,我会引入新的概念,基于时间的 “时间片轮询法”,我们的很多小型单片机无法使用RTOS的,你们时间片轮询法就很适合啦。


时间片轮询法,在很多书籍中有提到,而且有很多时候都是与操作系统一起出现,也就是说很多时候是操作系统中使用了这一方法。不过我们这里要说的这个时间片轮询法并不是挂在操作系统下,而是在前后台程序中使用此法。也是本文要详细说明和介绍的方法。


时间片轮询法:其实就是模拟系统内核,对 CPU 时间片进行分配,如果有空闲的时间片以及正在等待的作业,就将时间片分配给那个正在等待的作业;这是个帮助实现同步时间调度的程序,需要低层硬件的支持(定时器中断)。它本身利用定时器(TIM),使一个特殊的变量“Time_Num”从0开始随时间增长(1/ms),一旦达到了指定的最大值,又回归到零,如此往复……


任何一段循环的程序可以通过初始化的结构体或者宏来间接检查自己是否在允许的时间片内,如果此时不被允许执行,就跳过这段程序。这段源代码的意义就在于实现简单实用的同步时间调度。异步任务可能引起“竞争条件”等一些复杂的问题,如果只需要一个简单的方案就可以解决问题,那么同步编程仍然是最好的选择,这时如果再需要一个简单算法来调度若干个密集型同步任务,那么这个方法正好可以派上用场!

 

注意事项

    

    1. 任务的划分:任务一定要划分的非常合理,尽量做到任务的相对独立;

    2. 任务的优先:一定要注意任务优先级的设计,把需要及时处理的任务排到任务的最前面;

    3. 任务的执行:任务的执行一定要尽量的快,一定要保证在毫秒级,否则任务还没执行完,其他任务都再等,就到不到实时系统的要求,也谈不上多任务了;

    4. 时间的划分:时间片的划分是整个系统的关键,一定要保证任务在需要执行的时候能够进入该执行的任务中,否则就不能实现真正的时间片轮询了。

1.任务的划分


任务的划分并不难,你需要先全面的了解你的项目是要实现什么功能,把其划分成多个功能模块,每一个模块就是一个任务,每一个任务对应一个函数。


例如一个时钟产品,一般由:按键、显示、时间、闹铃、菜单(设置/查询等)等组成。那么我们可以把其划分成5个任务。

2. 任务的优先


同样通过以上事例来说明任务优先级,可能划分的方法有很多种,而且看不出很大的区别,这里只是随便举个例子:

    A. 时间,这里的时间就是从时钟芯片中获取时间;

    B. 闹铃,获取时间后应该首先判断是否是设置的闹铃时间,如果是就进行闹铃提示,否则,退出执行下一个任务;

    C. 显示,显示时间,如果有闹铃,则显示闹铃标志;

    D. 按键,判断是否有按键,如果有就进入相应的操作;
    E. 菜单,通过按键进入相应的菜单,如果没有按键,就不执行菜单任务直接退出。

 

这就是整个时钟产品需要实现的整个过程,任务之间的通讯已经任务之间的相互制约都是通过全局变量实现的,例如进入时间设置等时,就没有有必要实现时间的读取,闹铃的判断,以及时间的显示。这时只需要执行按键任务以及菜单任务即可,直至退出为止。这里需要说明的是不执行的任务是在判断任务执行情况后不具体执行任务代码,并不是一直在菜单程序中死等等,直至菜单退出。因为那样的话就不是真正的多任务级了,也谈不上时间片了。

3. 任务的执行

 

任务的执行一定要尽量的快,一定不能因为某个任务需要等等特殊的东西,而影响的其他任务,也不能在任务中调用大的延时函数,一定要保证任务的运行速度,要知道每一个任务的具体执行时间。例如上例中,绝对不能因为等等按键的释放而导致其他任务的不运行。那么怎么消抖呢?这个方法有很多,你可要通过利用两次按键任务是时间实现消抖,例如第一按键后,你做个标志,表示有键,但是不执行菜单,可要通过第二次进入按键任务判断,是否是按键的按键,还是误按,这种情况下就必须要保证按键任务的运行时间在消抖也许的时间内容,例如20ms。

 

  4. 时间的划分

 

     时间片的划分尤为重要,需要保证每一任务都能在该执行的时间内运行。就以时钟事例来说,显示和获取时钟一般一秒一次就可以了,如果你有时钟冒号“:”的显示,那么1秒必须执行两次以上才能保证显示的正常。当然在系统允许的情况下可以尽量多允许几次,但一定最低的允许次数。像按键可以使用20ms作为任务的时间片,因为一般按键的消抖时间为20ms,那么时间片划分为20ms完全可以保证即不漏掉按键,也不会误读按键。

 

实现流程:(简单实现3个任务)

使用1个定时器,可以是任意的定时器,这里不做特殊说明,下面假设有3个任务,那么我们应该做如下工作:

1 初始化定时器,这里假设定时器的定时中断为1ms(当然你可以改成10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。

2 设计一个结构体:

  1.  // 任务结构  

  2. typedef struct _TASK_COMPONENTS  

  3. {  

  4.     uint8 Run;                 // 程序运行标记:0-不运行,1运行  

  5.     uint8 Timer;              // 计时器  

  6.     uint8 ItvTime;              // 任务运行间隔时间  

  7.     void (*TaskHook)(void);    // 要运行的任务函数  

  8. } TASK_COMPONENTS;       // 任务定义  


并且把任务结构体进行初始化

  1. static TASK_COMPONENTS TaskComps[] =  

  2. {  

  3.     {0, 600, 600, TaskDisplayClock},     // 显示时钟  

  4.     {0, 200, 200, TaskKeySan},               // 按键扫描  

  5.     {0, 300, 300, TaskDispStatus},       // 显示工作状态  

  6. };  


任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数,这里独立出来,并于移植和理解。

 

在中断中处理这个任务:此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数

  1. void TaskRemarks(void)  

  2. {  

  3.     u8 i;  

  4.     for (i=0; i<TASKS_MAX; i++) // 逐个任务时间处理  

  5.     {  

  6.         if (TaskComps[i].Timer) // 时间不为 0  

  7.         {  

  8.         TaskComps[i].Timer--; // 减去一个节拍  

  9.             if (TaskComps[i].Timer == 0) // 时间减完了  

  10.             {  

  11.                 TaskComps[i].Timer = TaskComps[i].ItvTime; // 恢复  

  12.                 //计时器值,从新下一次  

  13.                 TaskComps[i].Run = 1; // 任务可以运行  

  14.             }  

  15.         }  

  16.     }  

  17. }  

下面的函数就是判断什么时候该执行那一个任务了,实现任务的管理操作,应用者只需要在main()函数中调用此函数就可以了,并不需要去分别调用和处理任务函数。
  1. void TaskProcess(void)  

  2. {  

  3.     u8 i;  

  4.     for (i=0; i<TASKS_MAX; i++) // 逐个任务时间处理  

  5.     {  

  6.         if (TaskComps[i].Run) // 时间不为 0  

  7.         {  

  8.             TaskComps[i].TaskHook(); // 运行任务  

  9.             TaskComps[i].Run = 0; // 标志清 0  

  10.         }  

  11.     }  

  12. }  

  13.   

  14.   

  15. void TaskDisplayClock(void)  


  16. void TaskKeySan(void)  


  17. void TaskDispStatus(void)  





我现在解释一下为什么能做到,定时器是一直在工作的,主程序也是一直在执行,那么当我们的时间到了,我们的代码:


  1. TaskComps[i].Run = 1; // 任务可以运行  


就是表示任务可以执行了,那么在主程序中,我们知道任务可以执行的话,我们就能直接执行了:


  1. TaskComps[i].TaskHook(); // 运行任务  


因为这个结构体定义的


  1. void (*TaskHook)(void);    // 要运行的任务函数  


是一个指向任务的函数,那么执行这句话就是跳到要执行的任务中去了。前提是结构体已经初始化了。

 

到此我们的时间片轮询这个应用程序的架构就完成了,你只需要在我们提示的地方添加你自己的任务函数就可以了。是不是很简单啊,有没有点操作系统的感觉在里面?

 

不防试试把,看看任务之间是不是相互并不干扰?并行运行呢?当然重要的是,还需要,注意任务之间进行数据传递时,需要采用全局变量,除此之外还需要注意划分任务以及任务的执行时间,在编写任务时,尽量让任务尽快执行完成。。。。。。。。。

 

先暂时介绍到这里。网上也很多相关教程,而且作者水平有限,如有错误请指正。

 


未完待续。。。

作者:杰杰

编辑: C K

2018-05-09



本帖有更多资源,需 登录 才可以下载,没有帐号?立即 注册

(0 ) (0 )

嵌入式移植 嵌入式学习

回复 举报

回复于 2018-05-10 沙发

  多谢分享!!!
(0 )
评论 (0) 举报

回复于 2018-05-10 2#

谢谢分享!!!
(0 )
评论 (0) 举报

回复于 2018-05-10 3#

感谢分享;
(0 )
评论 (0) 举报

回复于 2018-05-11 4#

谢谢分享
(0 )
评论 (0) 举报

回复于 2018-05-19 5#

66666666666666
(0 )
评论 (0) 举报

回复于 2018-05-21 6#

谢谢分享
(0 )
评论 (0) 举报

回复于 2018-06-15 7#

谢谢分享
(0 )
评论 (0) 举报

回复于 2018-06-17 8#

谢谢分享
(0 )
评论 (0) 举报

回复于 2018-08-17 9#

看看
(0 )
评论 (0) 举报

回复于 2020-11-12 10#

谢谢分享
(0 )
评论 (0) 举报
  • 发表回复
    0/3000





    举报

    请选择举报类别

    • 广告垃圾
    • 违规内容
    • 恶意灌水
    • 重复发帖

    全部板块

    返回顶部