一个抢先式“裸奔“系统的设计
首先,main()在完成硬件初始化Sys_init()后,调用I2c_svr()总线通信服务程序。
I2c_svr()服务程序里,首先完成类似通用RTOS的任务现场保护的过程。再通过切换堆栈指针,完成了新任务堆栈的初始化过程。然后进入I2C总线通信模块主循环(类似创建任务的操作),再通过调用idelay(),将CPU的控制权交还给main()。奥妙就在于idelay()首先保存通信程序的寄存器现场(ACC和PSW),然后转换到main()的堆栈空间,并恢复刚才被I2c_svr()保存的寄存器现场(ACC和PSW)。所以;i2c_svr()里的idelay()函数返回后将不执行其下面的i2write(),而是执行main()里的while(1)。
i2write()又如何能得到执行呢?它是通过定时中断服务程序timer1()再次获得CPU控制权的。如果在main()的执行中发生timer1()中断,因为timer1()里也进行与idelay()类似的任务切换操作,这时候将切换到I2c_svr()的堆栈和寄存器(现场)。此时timer1()中断返回时,不会返回到main()里,而是执行i2write()。
另外,函数i2write()内部执行中也会调用idelay(),在I2c_svr()中的每次调用idelay()都会将CPU控制权交给main()的切换。main()和I2c_svr()的切换关系如图2所示。
当然,timer1()并不总是引起任务的切换,通过判断bi2csvr标志可以避免(在不需要数据传输时)不必要的任务切换。另外,timer1()也可能进行从I2c_svr()到main()的切换。所以即使I2c_svr()里很长时间没有调用idelay(),也不会阻塞main()的执行。
切换现场一般基于80C51的RTOS,通常要保存所有的CPU寄存器(包括8个工作寄存器、ACC、PSW、B、DPTR等),而这里与它们不同,因为在笔者的通信服务模块I2c_svr()中使用了另外的寄存器组,且未使用B和DPTR,因此不需要保存8个工作寄存器及B和DPTR,仅保存和恢复PSW和ACC这两个寄存器就可以了,大大提高了切换效率。
本系统里仅有两个“任务”,即main()和I2c_svr(),也没有固定优先级,处于“等待”状态任务的优先级总比当前运行中的任务高,所以相当于同优先级时间片轮转调度方式。但相对于RTOS,这里还缺少操作系统必须管理的与任务相关的状态和数据结构,所以笔者还将其称做“裸奔”系统。
6 现场保护的补充说明
任务切换中的寄存器现场保护代码如下:
上面是Keil C51对定时中断服务函数timer1()编译生成的LST文件。编译器在中断服务里自动生成压栈和出栈寄存器的指令,所以在写idelay()函数的寄存器现场切换的时候,必须完全遵守这个寄存器压栈和出栈顺序规则才能正常工作。
结语
通过学习和借鉴RTOS的CPU时间抢先调度和分配方法,可以将本系统中总线时序里许多很短的延时都交给主程序使用,最大程度利用CPU时间,实现主程序和通信服务程序的并行执行,从而让主程序和通信服务程序均达到系统要求的实时性能。
本文为时间紧张的系统设计提供了一个新的解决思路。应该有助于初学操作系统的读者理解操作系统任务切换的工作机理。
嵌入式系统 抢先式调度 实时操作系统 STC12C5410 相关文章:
- Linux嵌入式系统开发平台选型探讨(11-09)
- 嵌入式系统中文输入法的设计(03-02)
- 基于MPC755的嵌入式计算机系统设计(05-10)
- WinCE下光电编码器的驱动程序设计(04-12)
- 为什么嵌入式开发人员要使用FPGA(05-13)
- VxWorks几种常用的延时方法介绍(05-16)