本文转载于“调度器,极小资源单片机值得一用(转)”及“再出个调度器,极小资源单片机值得一用 ”
自认为有如下特点:
/***********************************************************/
#include
#include
/*****************小小调度器部分开始*************************/
#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; i0) 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();
}
}
作者对上面代码进行了再次优化(2楼)
/****小小调度器开始**********************************************/
#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%了)。应该运行效率会更高。
// 以下为说明
小小调度器任务函数的写法主要注意的,主要有三点:
这三点,并不会明显造成写程序的不方便。
从裸奔到使用OS操作系统或调度系统的代价主要有:
硬件资源代价(对RAM和ROM的消耗),学习代价(学会其原理,并掌握其用法),移植代价(往不同cpu上移植的工作量),效率代价(使用调度系统后带来的额外cpu负担),商业代价(版权费用),稳定性代价(是否引入潜在不稳定因素,或者增大bug跟踪调试工作量)。
从这几方面来讲,应用小小调度器的代价,都是非常小的。
本调度器的宗旨是:以最小的代价,实现基于自然语法的多任务并行处理机制。并具备代码的高度可以移植性。
每个任务占用3个字节RAM。任务数量没有限制。
其工作原理很简单,大家可在此基础上任意DIY自己的调度方法。
102及144楼修改后结果:
在这里插入代码片
#include
/**************小小调度器开始啦 *********************************/
#define MAXTASKS 3 //顶层任务数量,可按需增加,最大为255个。
char timers[MAXTASKS];
char currid; //当前运行的任务号
#define _SS static char lc=0; switch(lc){ case 0: //跳转开始
#define _EE }; lc=0; //跳转结束
#define DelayX(b) settimer(&lc,__LINE__,b); return ; case __LINE__: //任务内延时等待“函数”
#define RunTask(a,b) currid=b; if (timers[b] == 0) {timers[b] = -1; a();} //运行顶层任务
#define CallSub(x) DelayX(0);x(); if (timers[currid]!=-1) return; //调用子任务
void settimer(char *lc, char line,char d) //设置定时器
{
*lc=line; timers[currid]=d;
}
/****************小小调度器结束啦*********************************/
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;
}
//初始化定时器,产生10ms中断
void InitT0()
{
TMOD = 0x21;
IE |= 0x82; // 12t
TL0=0Xff;
TH0=0Xb7;
TR0 = 1;
}
//定时器中断处理函数
void INTT0(void) interrupt 1 using 1
{
unsigned char i;
TL0=0Xff; //10ms 重装
TH0=0Xb7;
//逻辑定时器处理
for (i=0;i0) {
timers[i]--;
}
}
}
sbit LED1= P2^4;
//任务一,状态机写法
void ontimer0()
{
LED1=!LED1; // LED1引脚接在发光管负极,LED1=0 为亮,LED1=1为灭
if (LED1) timers[currid]=45; //450mS 灭
else timers[currid]=5; //50ms 亮
}
//任务二,状态机写法
char keycount=0;
void task1()
{
if(KEY==0) {
keycount++;
if (keycount>20) IAP_CONTR = 0x60;
} else {
keycount=0;
}
timers[currid]=5; //重装定时器
}
//这是一个子任务函数
void subtask()
{
static char i;
_SS
for(i=0;i<=5;i++) {
DelayX(20);
P1=numtab[i];
}
_EE
}
//任务三,“线程”写法,复杂任务请用这种方式来写,比传统状态机写法更简单,更自然,而且更省ROM!
void task2()
{
static char i; //变量请申明为静态的。
_SS
while(1) {
for(i=0;i<=9;i++) {
//先从0--9快速显示,间隔200mS
DelayX(20);
P1=numtab[i];
}
for(i=0;i<=9;i++) {
//然后从0--9慢速显示,间隔500mS
DelayX(50);
P1=numtab[i];
CallSub(subtask); //顶层任务的任何地方都可以调用子任务
}
//CallSub(subtask); //顶层任务的任何地方都可以调用子任务
}
_EE
}
/* task2分解
void task2()
{
static char i;
static char lc=0;
switch(lc){
case 0:
while(1) {
for(i=0;i<=9;i++) {
//先从0--9快速显示,间隔200mS
//DelayX(20);
settimer(&lc,__LINE__,20); // *lc=line; timers[currid]=20;
return ;
case __LINE__:
P1=numtab[i];
}
for(i=0;i<=9;i++) {
//然后从0--9慢速显示,间隔500mS
//DelayX(50);
settimer(&lc,__LINE__,50); // *lc=line; timers[currid]=50;
return ;
case __LINE__:
P1=numtab[i];
//CallSub(subtask); //顶层任务的任何地方都可以调用子任务
DelayX(0);
x();
if (timers[currid]!=-1)
return;
}
//CallSub(subtask); //顶层任务的任何地方都可以调用子任务
}
}; lc=0; //跳转结束
}
*/
void main()
{
P3M0 = 0x00;
P3M1 =0x00;
P1 = 0xff; //关LED数码管显示
InitT0(); //初始化定时器,以便产生10ms中断
KEY =1; //按键IO口
while(1) {
clr_wdt(); //清除看门狗
//这里,只需运行顶层任务即可
RunTask(ontimer0,0); //ontimer0任务放在第0个任务槽
RunTask(task1,1); //task1任务放在第1个任务槽
RunTask(task2,2); //task2任务放在第2个任务槽
//...还可以增加更多任务
}
}
protothread 是经典的状态机,只要看函数开头的switch(pt-﹥lc) 进行散转,就一清二楚了。
因此说protothread是协程,实际很不准确, 准确地说法是: protothread 就是状态机!
protothread使用行号代替人为设置的状态,如果使用1个Byte记忆状态,那么一个任务函数内代码行数不能超过255行,所以用了1个word来记忆,因此占用了两个字节.一个任务函数内代码行数不超过65535行(这完全够了)。
这就是一个protothread任务消耗2个字节的原因:就是状态变量的消耗。只要使用状态机机制,谁能省掉这个呢?
protothread具备状态机的所有特性,只要能使用状态机的地方,就可以使用protothread。即可以单独使用,也可以结合其他多任务系统机制使用,也可以和时间调度触发联合使用。
而protothread主要好处是: protothread任务函数的代码语法,无需人工设计各个状态, 非常类似于多线程环境下的任务代码,大大降低了代码的复杂度.
附带说下:
很多地方有说到protothread的,总提到protothread是协程,好像有很多问题,但protothread根本不是协程,而是典型的状态机!
状态机的实时性本身不是问题,问题在于如何使用状态机这种机,因此,protothread的实时性本身也根本不是问题,问题在于如何使用protothread。
参考“protothread, 就是状态机!”及“Protothread 机制”!