今天我给大家讲一下: 时间触发的嵌入式系统 : 我先给大家了一个程序:这个程序是在PC机上测试过,大家给点意见! #include "stdio.h" #define TASKmax 5 typedef unsigned long u32; typedef unsigned int u16; typedef unsigned char u8; struct Task { void (*Ptask)(void); u8 delay; u8 period; u8 Runme; }; struct Task task[TASKmax]; u8 Task_G; u8 Task1_time= 10;//10ms u8 Task2_time= 20; void task1(void) { int i=0; for(i=0;i<2;i++) { printf("1\r\n"); }} void task2(void ){ char j=0; for(j=0;j<2;j++) { printf("2\r\n"); };} u8 TASK_dele(u8 cn){ if(task[cn].Ptask ==0) return 0; task[cn].Ptask= 0x00; task[cn].delay= 0; task[cn].period= 0; task[cn].Runme= 0; return 1;} void Task_tr(void ) { u8 taskIndex; for(taskIndex=0;taskIndex< TASKmax;taskIndex++) { if(task[taskIndex].Runme>0) { (*task[taskIndex].Ptask)();
task[taskIndex].Runme--; } if(task[taskIndex].period==0) TASK_dele(taskIndex); } } u8 task_add(void (*function)(), u8 delay,u8 period) { u8 dex=0; while((task[dex].Ptask!=0) && (dex< TASKmax)) dex++; if(dex==TASKmax) return 0; task[dex].Ptask= function; task[dex].delay= delay; task[dex].period= period; task[dex].Runme= 1; return 1;} void Task_ref(void )//任务控制 { u8 Task_tran; Task_tran=Task_G; switch(Task_tran) { case task11: //时间判断 // break; case task12: //时间判断 //如果到了就到下一个状态 break; default:break; } } void main(){ //初始化定时器 控制标志位 //初始化须要的变量 //增加任务 //while(1); task_add(task1,10,1); task_add(task2,30,2); //Task_tr();定时器 控制标志位 }//以上是系统和基本思想,可以根据自己的程序更改。 stateMachine + timerTick + queue。
在RTOS环境下的多任务模型: 任务通常阻塞在一个OS调用上(比如从消息队列取数据)。 外部如果想让该任务运转,就要向消息队列发送消息。 任务收到消息时,根据当前状态,决定如何处理消息。这就是状态机。 任务将消息队列中的消息处理完毕后,重新进入阻塞状态。 任务在处理中,有时要延时一段时间,然后才继续工作: 为了充分使用CPU,可以通过OS调用让其它任务去工作。 OS通常会提供一个taskDelay调用。 当任务调用taskDelay时,即进入阻塞状态,直到超时,才重新进入可工作状态(就绪状态)。
下面说说裸奔环境下的多任务模型: 裸奔也可以多任务,但调度是由用户自主控制。 在RTOS环境下,一般提供抢占式调度。在裸奔时,一般是任务在处理告一段落后,主动结束处理。 RTOS环境下的任务,一般处于一个while(1)循环中。 while(1){ 从消息队列接收消息。如果没有,将阻塞。 处理消息。 } 裸奔下的任务,一般采用查询方式: { 查询是否有待处理的事件。 如果没有,返回。 如果有,根据任务的当前状态,进行处理。处理完毕后,可能返回,也可能将待处理事件全部处理完毕后再返回。 } 裸奔任务其实也处于一个while(1)循环中,只不过这个循环在任务外部。 main() { A_taskInit(); //任务的初始化 B_taskInit(); ... while(1){ A_taskProc(); //任务的处理 B_taskProc(); } }
状态机既适用于OS环境,也适用于裸奔环境。 但在裸奔环境下,状态可能被切分得更细。例如后面讲的如何在裸奔环境实现taskDelay()。
消息队列既适用于OS环境,也适用于裸奔环境。 在OS环境下,消息队列机制由OS提供。 在裸奔环境下,消息队列要自己来实现。如果对队列的概念不清楚,可参考《数据结构》教材。 这个队列机制,可做成通用模块,在不同的程序中复用。 消息队列用于缓冲事件。事件不知道什么时候会到来,也不能保证来了就能迅速得到处理。 使用消息队列,可以保证每个事件都被处理到,以及处理顺序。 一般在两种情况下会用到消息队列: 存储外部事件:外部事件由中断收集,然后存储到队列。 串口接收程序中的接收循环缓冲区,可理解为消息队列。 任务间通讯:一个任务给其它任务发送消息。
timerTick,就是系统中的时钟基准。OS中总是有一个这样的基准。 在裸奔时,我们要用一个定时器(或RTC或watchdog)来建立这个时间基准。 一个tick间隔可以设置为10ms(典型RTOS的缺省设置)。让定时器10ms中断一次,中断发生时给tickNum++。 以前,我在定时器中断中设置1S标志、200ms标志等等。时间相关的任务根据这些标志判断是否要执行。 近来,一般让任务直接去察看tickNum。两次相减来判断定时是否到达。 也可以在系统中建立一个通用定时器任务,管理与不同任务相关的多个定时器;在定时到达时,由定时器任务去调用相应的callback。 系统时钟基准是所谓“零耗时裸奔”的基础。 timerTick的分辨率,决定了只适于于较大的时间延时。 在做时序时的小延时,用传统方法好了。
OS中的taskDelay()在裸奔环境下的一种实现: OS环境: void xxxTask(void) {
while(1){ //waitEvent
//do step_1
taskDelay(TIME_OUT_TICK_NUM);
//do step_2 } } 裸奔环境: void xxxTask(void) { static unsigned int taskStat = STAT_GENERAL; //任务状态变量 static timer_t startTick; timer_t currTick; if (taskStat == STAT_GENERAL) { //check event
//if no event return;
//do step_1
startTick = sysGetTick(); //sysGetTick()就是察看系统时间 taskStat = STAT_WAIT; return; } else if (taskStat == STAT_WAIT) { currTick = sysGetTick(); //sysGetTick()就是察看系统时间 if ((currTick - startTick) >= TIME_OUT_TICK_NUM) { //do step_2
taskStat = STAT_GENERAL; return; } else return; } 基于状态机控制的面向对象的前后台协从多任务系统设计
一、任务分析 根据题目要求,划分任务如下: 1、键盘扫描线程 2、灯显示线程 3、LED1-LED4四个独立线程 4、后台监视线程 5、串口收发中断 共计7个线程1个中断。
二、软件整体结构设计
后台 前台 串口中断
---------| --------------- -------------- | V | int10ms中断 | | serial中断 | | ------------- --------------- -------------- | |监视monitor| | | | ------------- ----------------- -------------- | | |键盘扫描keyscan| | 收 RI 检查 | ---------| ----------------- -------------- |
|