×

中断驱动多任务--- 单片机(MCU) 下的一种软件设计结构资料下载

消耗积分:0 | 格式:pdf | 大小:139.12KB | 2021-04-22

旧念

分享资料个

MCU由于内部资源的限制,软件设计有其特殊性,程序一般没有复杂的算法以及数据结构,代码量也不大, 通常不会使用 OS (Operating System), 因为对于一个只有若干K ROM、一百多byte RAM 的MCU来说,一个简单OS 也会吃掉大部分的资源。 对于无OS的系统,流行的设计是主程序(主循环 )+(定时)中断,这种结构虽然符合自然想法,不过却有很多不利之处,首先是中断可以在主程序的任何地方发生,随意打断主程序。其次主程序与中断之间的耦合性(关联度)较大,这种做法 使得主程序与中断缠绕在一起,必须仔细处理以防不测。 那么换一种思路,如果把主程序全部放入(定时)中断中会怎么样?这么做至少可以立即看到几个好处:系统可以处于低功耗的休眠状态,将由中断唤醒进入主程序;如果程序跑飞,则中断可以拉回;没有了主从之分(其他中断另计),程序易于模块化。 为了把主程序全部放入(定时)中断中,必须把程序化分成一个个的模块,即任务,每个任务完成一个特定的功能,例如扫描键盘并检测按键。 设定一个合理的时基 (tick),例如 5、10 或 20 ms,每次定时中断,把所有任务执行一遍,为减少复杂性,一般不做动态调度(最多使用固定数组以简化设计,做动态调度就接近 OS了),这实际上是一种无优先级时间片轮循的变种。来看看主程序的构成: void main() { …. // Initialize while (true) { IDLE; //sleep } } 这里的 IDLE 是一条sleep 指令,让MCU进入低功耗模式。中断程序的构成: void Timer_Interrupt() { SetTimer(); ResetStack(); Enable_Timer_Interrupt; …. 进入中断后,首先重置Timer,这主要针对8051、8051 自动重装分频器只有 8-bit,难以做到长时间定时;复位 stack ,即把stack指针赋值为栈顶或栈底,用以表示与过去决裂,而且不准备返回到中断点,保证不会保留程序在跑飞时stack 中的遗体。Enable_Timer_Interrupt 也主要是针对8051。8051 由于中断控制较弱,只有两级中断优先级,而且使用了如果中断程序不用 reti 返回,则不能响应同级中断这种偷懒方法,所以对于 8051,必须调用一次 reti 来开放中断: _Enable_Timer_Interrupt: acall _reti _reti: reti 下面就是任务的执行了,这里有几种方法。第一种是采用固定顺序,由于MCU程序复杂度不高,多数情况下可以采用这种方法: … Enable_Timer_Interrupt; ProcessKey(); RunTask2(); … RunTaskN(); while (1) IDLE; 可以看到中断把所有任务调用一遍,至于任务是否需要运行,由程序员自己控制。另一种做法是通过函数指针数组: #define CountOfArray(x) (sizeof(x)/sizeof(x[0])) typedef void (*FUNCTIONPTR)(); const FUNCTIONPTR[] tasks = { ProcessKey, RunTask2, … RunTaskN }; void Timer_Interrupt() { SetTimer(); ResetStack(); Enable_Timer_Interrupt; for (i=0; i (*tasks[i])(); while (1) IDLE; } 使用const 是让数组内容位于 code segment (ROM) 而非 data segment (RAM) 中,8051 中使用 code 作为 const 的替代品。 (题外话:关于函数指针赋值时是否需要取地址操作符 & 的问题,与数组名一样,取决于 compiler. 对于熟悉汇编的人来说,函数名和数组名都是常数地址,无需也不能取地址。对于不熟悉汇编的人来说,用 & 取地址是理所当然的事情。Visual C++ 2005对此两者都支持) 这种方法在汇编下表现为散转,一个小技巧是利用 stack 获取跳转表入口:

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !