前面的文章讲述了裸机代码的4种写法

(1)轮询系统

(2)前后台系统

(3)基于时间片

(4)基于单向链表

这一篇文章我再扩展一点,大家可以看出,第二种写法比较通俗易懂,但是如果添加很多任务,修改的地方较多,第三种和第四种就非常不错,改动的地方比较少。

基于时间片裸机系统和基于链表的裸机系统,虽然一个是用数组来实现的,一个是用链表来实现的,可以看出基于链表的裸机代码更加全面,主要体现在如下几点

1.task_ticks的心跳自加变量,直接放在定时器中断里,作为时基,和判断是否要执行的任务其他代码都有剥离开来,更加容易维护。

2.可以自由的启动任务和停止任务。

3.可以单次触发,也就是只是执行一次。

如果上述三点,您能够总结出来,恭喜您,说明您已经掌握了裸机代码框架的精髓!

虽然单向链表使用起来很灵活,毕竟有用到了数据结构的链表技术点,对于链表,指针,我相信初学者看到这里,大部分都会懵逼了。。。

那再思考下,如果我们在基于链表的裸机代码框架上,用数组来实现,不用链表,并且也保留上述3个功能,能够实现吗?这种写法,我相信初学者也很喜欢,毕竟没有链表和指针

答案是:可以的。我尝试了一下,修改代码如下:

【1】先定义宏定义,方便代码的理解

/*宏定义*/#define ON 1#define OFF 0#define TASK_COUNT 3#define LED1_Task taskArray[0]#define LED2_Task taskArray[1]#define LED3_Task taskArray[2]

【2】定义一个表示的任务的结构体类型

注意:和单链表的裸机代码相比,这里多加了一个enable变量,当为ON,表示执行,当为OFF,表示停止执行,链表是通过插入和删除来实现的,我们这里就用标志位判断就好。

/*定义一个表示的任务的结构体类型*/typedef struct Task_t{ unsigned char enable; // 超时时间(用来与定时器心跳比较), unsigned int timeout; //循环定时触发时间(周期定时设置), 为0时代表单次定时 unsigned int repeat; void (*pTask)(void); //任务的函数指针 }Task_t;

【3】定义心跳全局变量和任务数组,包含3个任务

/*定义变量*/unsigned int g_task_ticks = 0;//心跳计数器Task_t taskArray[TASK_COUNT];

【4】心跳函数将心跳全局变量自加

Task_t tasks[]={ {0,100,100,DoTask1;} {0,200,200,DoTask2;} {0,300,300,DoTask3;}}unsigned int taskCount=sizeof(tasks)/sizeof(Task_t);

【5】任务初始化

void Task_Init(Task_t* htask, void(*pTask)(), unsigned int timeout, unsigned int repeat, unsigned char enable){ htask->timeout = g_task_ticks + timeout;htask->repeat = repeat;htask->pTask = pTask;htask->enable = enable;}

【6】任务启动和停止函数

void Task_Start(Task_t* htask){htask->enable = ON;}void Task_Stop(Task_t* htask){htask->enable = OFF;}

【7】任务处理函数,判断时间是否到了,到了就执行

void Task_Process(){unsigned char i = 0;for (i = 0; i < TASK_COUNT; i++)//遍历任务数组 {if ((&taskArray[i])->enable == ON){if(g_task_ticks >= (&taskArray[i])->timeout){if((&taskArray[i])->repeat == 0){(&taskArray[i])->enable = OFF;}else{(&taskArray[i])->timeout = g_task_ticks + (&taskArray[i])->repeat;}(&taskArray[i])->pTask();}}}}

【8】定义task结构体中指针函数所指向的函数体,每个任务要执行的内容

void DoTask1() {LED1=~LED1;}void DoTask2() {LED2=~LED2;}void DoTask3() {LED3=~LED3;}

通过以上8步,就完成了此框架的所有代码,应用上需要注意的地方

(1)应用方法操作步骤如下,

①Task_Init函数初始化并启动各个任务

③心跳自加函数Task_Ticks()放到定时器中断中,每隔一段时间(本范例5ms)自加一次

④在while(1)死循环中执行Task_Process()

//系统初始化void System_Init(){Timer0_Init();Task_Init(&LED1_Task,DoTask1,100,100,ON);Task_Init(&LED2_Task,DoTask2,200,200,ON);Task_Init(&LED3_Task,DoTask3,300,300,ON);EA = 1;}// 主程序void main(){System_Init();while(1){Task_Process();}}//定时器0初始化void Timer0_Init(){TMOD &= 0xF0;TMOD |= 0x01;//T0工作于模式1,16位定时器TL0 = 0x00;//定时器赋初值TH0 = 0xEE;ET0 = 1;//允许T0中断TR0 = 1;}//定时器中断函数void Timer0_ISR() interrupt 1{TL0 = 0x00;//定时器赋初值TH0 = 0xEE;Task_Ticks();}

仿真结果如下,完全符合预期,并且我写试过,单次触发,停止或者启动,都是OK的,这里就不在演示了,您可以自行调试一下。

打开网易新闻 查看精彩图片

喜欢这篇文章,帮忙点个“关注 + 收藏”哦

附上源码:

taks.h

打开网易新闻 查看精彩图片

task.c

打开网易新闻 查看精彩图片

main.c

打开网易新闻 查看精彩图片