自认为有如下特点:
1) 超级可以移植性,与CPU无关,几乎任何支持C语言编程的CPU都可以用!(本文仅仅以51单片机为例而已,但实际上可以任意移植)
2) 小之又小, 原理很简单,一看就懂。
3) 省之又省, 可以说对RAM和ROM省到极致。
4) 取protothread之精华,将定时器与状态机和伪线程语法融合到一个框架,任务函数可以有两种写法。
5) 基于定时器触发,调度效率高,最大化减少无效的代码运行时间。
***********************************************************/ #include <stc89c51.h> #include <stdio.h> /*****************小小调度器部分开始********************************************/ #define _SS static char lc=0; switch(lc){ case 0: lc=0; #define _EE }; lc=0; #define WaitX(a,b) settimer(&lc,__LINE__,a,b); return ; case __LINE__: struct TASK { char td; void (*fp)(); }; #define MAXTASKS 5 struct TASK tasks[MAXTASKS]; //设置定时器 void settimer(char *lc,char line,char tmrid,int d){ *lc=line; tasks[tmrid].td=d; } //逻辑定时器处理,在定时器中断里调用 void dectimers() { unsigned char i; for (i=0;i<MAXTASKS;i++){ if (tasks[i].td>0) tasks[i].td--; } } //任务调度函数,在main里面运行 void runtasks() { unsigned char i; for(i=0;i<MAXTASKS;i++) { if (tasks[i].fp!=0){ if (tasks[i].td==0){ tasks[i].td=-1; tasks[i].fp(); } } } } /****************小小调度器部分结束*******************************************************/ sbit KEY = P3^2; unsigned char code numtab[16]={0x24,0x6F,0xE0,0x62,0x2B,0x32,0x30,0x67,0x20,0x22,0x21,0x38,0xB4,0x68,0xB0,0xB1}; sfr IAP_CONTR = 0xC7; sfr WDT_CONTR = 0xC1; //清除看门狗 void clr_wdt() { WDT_CONTR =0x3C; } //初始化定时器 void InitT0() { TMOD = 0x21; IE |= 0x82; // 12t TL0=0Xff; TH0=0Xb7; TR0 = 1; } //定时器中断 void INTT0(void) interrupt 1 using 1 { TL0=0Xff; //10ms 重装 TH0=0Xb7; dectimers(); } sbit LED1= P2^4; //任务一,状态机写法 void ontimer0(){ LED1=!LED1; // LED1引脚接在发光管负极,LED1=0 为亮,LED1=1为灭。 //重装定时器 if (LED1) tasks[0].td=45; //450mS 灭 else tasks[0].td=5; //50ms 亮 } //任务二,状态机写法 char keycount=0; void task1(){ if(KEY==0) { keycount++; if (keycount>20) IAP_CONTR = 0x60; //持续按下键1秒,将重启并进入固件升级 } else{ keycount=0; } //重装定时器 tasks[1].td=5; } //任务三,伪线程写法 void task2() { static char i; _SS while(1){ for(i=0;i<=9;i++){ //从0--9快速显示,间隔200mS WaitX(2,20); // 等待200mS,实际是设置定时器2为200mS P1=numtab[i]; } for(i=0;i<=9;i++){ //从0--9慢速显示,间隔500mS WaitX(2,50); // 等待500mS,实际是设置定时器2为500mS P1=numtab[i]; } } _EE } void main() { unsigned char KeyNum; P3M0 = 0x00; P3M1 =0x00; //WDT_CONTR= 0x00; //关闭看门狗 P1 = 0xff; //关显示 clr_wdt(); InitT0(); KEY =1; //按键IO口 KeyNum=0; //按下次数 //装载任务: tasks[0].fp=ontimer0; tasks[1].fp=task1; tasks[2].fp=task2; //循环调度 while(1){ runtasks(); clr_wdt(); } }
优化无止境!呵呵,330楼看似不能再优化了,但我再尝试做一次优化:
敬请评测该版本,看是否还能优化:
/****小小调度器开始**********************************************/ #define MAXTASKS 2 static unsigned char timers[MAXTASKS]; unsigned char currdt; #define _SS static unsigned char _lc; switch(_lc){default: #define _EE ;}; _lc=0; return 255; #define WaitX(tickets) do {_lc=__LINE__+((__LINE__%256)==0); return tickets ;} while(0); case __LINE__+((__LINE__%256)==0): #define RunTask(TaskName,TaskID) do { if (timers[TaskID]==0) timers[TaskID]=TaskName(); } while(0); #define CallSub(SubTaskName) do { _lc=__LINE__+((__LINE__%256)==0); return 0; case __LINE__+((__LINE__%256)==0): currdt=SubTaskName(); if(currdt!=255) return currdt;} while(0); #define UpdateTimers() unsigned char i; for(i=MAXTASKS;i>0 ;i--){if((timers[i-1]!=0)&&(timers[i-1]!=255)) timers[i-1]--;} #define SEM unsigned int //初始化信号量 #define InitSem(sem) sem=0; //等待信号量 #define WaitSem(sem) do{ sem=1; WaitX(0); if (sem>0) return 1;} while(0); //等待信号量或定时器溢出, 定时器tickets 最大为0xFFFE #define WaitSemX(sem,tickets) do { sem=tickets+1; WaitX(0); if(sem>1){ sem--; return 1;} } while(0); //发送信号量 #define SendSem(sem) do {sem=0;} while(0); /*****小小调度器结束*******************************************************/ sbit LED1 = P2^1; sbit LED2 = P2^2; void InitT0() { TMOD = 0x21; IE |= 0x82; // 12t TL0=0Xff; TH0=0XDB;//22M---b7; TR0 = 1; } void INTT0(void) interrupt 1 using 1 { UpdateTimers(); TL0=0Xff; //10ms 重装 TH0=0XDB;//b7; } void task1(){ _SS while(1){ WaitX(50); LED1=!LED1; } _EE } void task2(){ _SS while(1){ WaitX(100); LED2=!LED2; } _EE } void main() { InitT0(); while(1){ RunTask(task1,0); RunTask(task2,1); } }
在keil下编译,又减少了18字节的ROM(超过10%了)。应该运行效率会更高。
------------------以下为说明-----------------------------------
小小调度器任务函数的写法主要注意的,主要有三点:
1) 任务函数内部变量,建议都用静态局部变量来定义。
2) 任务函数内不能用switch语句。
3) 任务函数内,不能用return语句。 因为return已经被赋予任务延时的特定意义。(这是返回型任务函数版本的一个强制要求)
这三点,并不会明显造成写程序的不方便。
---------------------------
从裸奔到使用OS操作系统或调度系统的代价主要有:
硬件资源代价(对RAM和ROM的消耗),学习代价(学会其原理,并掌握其用法),移植代价(往不同cpu上移植的工作量),效率代价(使用调度系统后带来的额外cpu负担),商业代价(版权费用),稳定性代价(是否引入潜在不稳定因素,或者增大bug跟踪调试工作量)。
从这几方面来讲,应用小小调度器的代价,都是非常小的。
1) 硬件资源代价: 前面的优化版本已经说明问题。keil下,本例程ram消耗 : 22字节,rom消耗126字节.
2) 学习代价: 小小调度器总共只有十多行代码,如果我们做一个简单的解释说明,理解起来其实是很快的。我相信学习时间比其他调度系统要短。
3) 移植代价: 几乎没有什么移植工作量,对于各种cpu,几乎是通吃。
4) 效率代价: 我们一直在努力优化,相信调度效率已经不低了。比如任务切换时间,应该是可以做到uS级别,甚至亚uS级别。
5) 商业代价: 小小本调度器为免费使用,无需支付任何费用。
6) 稳定性代价:小小调度器本质上仅仅是几个宏而已,未涉及任何对内部寄存器或堆栈的操作,避免了引入不稳定风险因素,所有操作都在可预见,可把控的前提下进行。
--------------------------------------------------------------------------
本调度器的宗旨是:以最小的代价,实现基于自然语法的多任务并行处理机制。并具备代码的高度可以移植性。
每个任务占用3个字节RAM。任务数量没有限制。
其工作原理很简单,大家可在此基础上任意DIY自己的调度方法。