嵌入式系统裸机的任务调度应用设计*
2018-06-15,,,
,,,
(1.齐鲁工业大学(山东省科学院),济南 250353;2.山东省科学院自动化研究所;3.山东省汽车电子技术重点实验室)
引 言
在嵌入式系统的软件设计中,要根据具体应用要求和系统资源大小,选择合适的嵌入式操作系统,根据应用逻辑划分任务,然后利用操作系统提供的一系列API建立任务队列,以消息或事件的形式进行任务间通信,实现任务调度[1],这种方式可以让开发人员将精力集中在应用逻辑的开发上,不仅可以实现软件的模块化,还可以灵活地进行修改和维护。
在这种方式下,MCU上电初始化时首先建立任务队列,为每个任务设定合适的优先级,并分配一定的堆栈用于存储任务上下文。在系统运行阶段,系统滴答、发送消息或事件、各种系统中断都会触发任务调度。执行任务切换时,首先将当前任务的上下文数据保存到任务堆栈中,然后将新任务堆栈恢复到MCU各类寄存器和系统栈中[2],根据新的PC(程序计数器)指针执行新任务。每个任务堆栈都会消耗宝贵的RAM资源,而且系统运行阶段存在大量频繁的中断时,上下文的存储和恢复会极大消耗MCU的计算资源,因此,只有在RAM资源丰富、主频高的高端MCU中才选择使用操作系统。在那些由于成本限制只能选择RAM资源较小、主频较低的中低端MCU的嵌入式系统的软件设计中,只能采用不加操作系统的裸机方式。
1 方案设计
在不带操作系统的裸机嵌入式系统中,软件系统在一个主循环体中运行。MCU循环调用由各种软件模块组成的主循环体,各个软件模块不存在各自的任务堆栈,共享一个系统栈。执行中断处理程序或调用子函数时,MCU将一些局部变量、中间计算结果和寄存器存入系统栈,执行完中断处理程序或子函数后恢复系统栈。在这种方式下,一般以大量的全局变量和标志位实现各个软件模块的交互,造成各个软件模块之间耦合性强,修改和维护不灵活。
本文借鉴操作系统的任务调度思想,提出一种裸机嵌入式系统的任务调度方法[3]。在裸机开发方式中,设计一种不带任务堆栈的逻辑任务,按照具体应用划分若干逻辑任务,这些逻辑任务共享一个系统栈,每个逻辑任务都有自己的事件队列和任务处理程序,任务之间通过发送事件的形式进行通信。这种方法实现了类似于操作系统的任务调度机制,能够清晰反映应用实现逻辑,同时提高了软件模块的内聚性,降低了软件模块之间的耦合性[4]。
2 软件设计
2.1 逻辑任务设计
为了清晰地反映应用的实现逻辑,以逻辑任务的形式实现各个软件模块,每个逻辑任务都有相应的任务处理函数和事件队列,任务之间通过事件的形式进行通信,发送的事件填充到任务的事件队列中,任务处理函数根据任务事件队列中的事件执行相关操作。
为了便于管理,以结构体的形式描述逻辑任务,结构体成员变量包括任务ID、事件队列、事件产生索引、事件消费索引。逻辑任务结构体如下所示:
typedef struct{
e_Event event[DEFAULT_EVENTQ_SIZE];
e_TaskId task_id;
uint8_ttick_idx;
uint8_ttalk_idx;
}s_Task;
以能够反映任务具体功能的枚举类型定义任务ID,以ID查找对应的逻辑任务结构体。同时,以任务ID作为任务优先级,ID值越大,优先级越高。笔者为某汽车厂开发的BCM的任务包括输入信号检测、RKE、CAN通信、LIN通信、网络管理、门锁控制、车灯控制、雨刮控制、车窗防盗报警、车身防盗报警、故障诊断、发动机防盗、定时器管理[2],定义任务ID枚举类型如下:
typedef enum{
MIN_TASK_PRIO,
INPUT_DETECT_TASK_PRIO = MIN_TASK_PRIO,
RKE_TASK_PRIO,
CAN_TASK_PRIO,
LIN_TASK_PRIO,
NM_TASK_PRIO,
LOCK_CTRL_TASK_PRIO,
LGT_CTRL_TASK_PRIO,
WIPER_CTRL_TASK_PRIO,
WDW_CTRL_TASK_PRIO,
ALARMSTATE_TASK_PRIO,
SYS_MONITOR_TASK_PRIO,
IMMO_TASK_PRIO,
TIMER_TASK_PRIO,
MAX_TASK_PRIO = TIMER_TASK_PRIO,
}e_TaskId;
事件队列从逻辑上来说是一个环形队列,从实现上来说是一个枚举类型的数组,包括两个索引:事件产生索引和事件消费索引,事件产生索引以tick_idx表示,事件消费索引以talk_idx表示。事件队列的大小根据实际应用而定。
2.2 任务调度
MCU上电初始化时,按照任务优先级从低到高的顺序将各个逻辑任务结构体的事件队列数组成员初始化为0,事件产生索引和事件消费索引初始化为0,然后进入主循环体。进入主循环体,在主循环体中执行任务调度程序,按照逻辑任务优先级从高到低的顺序依次调用每个逻辑任务的任务处理程序,直至当前逻辑任务的优先级为最低时,退出任务调度程序,返回进入主循环体。任务调度流程如图1所示。
图1 任务调度流程图
2.3 任务处理程序设计
当中断处理程序或任务处理程序向某个任务发送事件时,向该任务结构体中的事件队列中填充事件,将事件赋值给以该任务结构体的事件产生索引为下标的事件队列数组成员,然后将事件产生索引加1,如果索引值等于事件队列数组的长度,将事件产生索引置0。
执行任务处理程序时,如果事件产生索引和事件消费索引两者相等,说明事件队列中不存在未被处理的事件,如果不相等,说明存在未被处理的事件,读取以该任务结构体的事件消费索引为下标的事件队列数组成员,根据具体事件执行相关操作,然后将事件消费索引加1,如果索引值等于事件队列数组的长度,将事件消费索引置0。再次判断事件产生索引和事件消费索引是否相等,循环处理,直到事件队列中不再存在未被处理的事件。任务处理程序流程如图2所示。
图2 任务处理程序流程图
结 语
[1] 常华利,尹震宇.基于MicroBlaze的μC/OS-II操作系统移植[J].计算机系统应用,2017(5):239-246.
[2] 陈发堂,主父文刚,童庆.Nucleus PLUS操作系统在TMS320C81 68上的移植及TD-LTE中的应用[J].广东通信技术,2016(2):22-25,33.
[3] 山东省科学院自动化研究所.一种嵌入式软件的任务调度方法及装置:中国,201810128162.1[P].2018-2-8.
[4] 张智慧.C语言嵌入式系统编程软件设计架构研究[J].单片机与嵌入式系统应用,2018(1):3-5,10.
[5] 马建辉,王知学,李研强.车身控制系统BCM的设计与实现[J].中国科技成果,2011(13):49-51.