单片机裸机代码框架设计思路(四)
前面的文章讲述了裸机代码的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