将TIzigbee开源协议栈中的OS操作系统移植出来放在STC12C60S2中使用文档格式.docx
《将TIzigbee开源协议栈中的OS操作系统移植出来放在STC12C60S2中使用文档格式.docx》由会员分享,可在线阅读,更多相关《将TIzigbee开源协议栈中的OS操作系统移植出来放在STC12C60S2中使用文档格式.docx(27页珍藏版)》请在冰豆网上搜索。
说到LTOS操作系统,不得不说说他的任务、事件和消息机制。
据笔者理解,任务就是程序编写员将预实现的功能分成不同的模块,这些模块之间分工明确并且相互合作,共同完成程序员预完成的某个项目的整个功能;
事件是这些任务中要处理的某个小功能的口令,比如老师说张三你站起来或坐下,张三听到站起来就站起来,听到坐下就坐下,同样道理,某个任务得到处理器后,先判断自己的事件是什么,如果是URAT_Writer则任务知道是串口写;
而如果是LED_STOP,则任务知道是小灯停;
消息是任务之间相互通信的方式,任务之间的数据传输一是通过消息来实现,二是通过延时设置任务来完成。
任务内部消息就是一个系统事件。
在进入LTOS系统前,先利用osal_init_system()等初始化程序将操作系统初始化,主要功能就是内存分配函数的初始化、定时器的初始化以及为任务的加入。
任务初始化时将任务按预先设定分配了不同的优先级,LTOS系统按照赋值的优先级顺序从高到底不停的扫描这些任务,查看他们是否被设置了事件,如果该任务被设置了事件,则操作系统将马上进入这个该任务对应的pFnEventProcessor(处理任务函数)中执行该任务中的事件。
初始化和任务加入完成之后就开始进入任务调度函数osalNextActiveTask(void)。
进入任务调度函数首先扫描定时器和串口,查看定时器和串口的变化,然后利用osalNextActiveTask()函数查看任务列表中是否有被设置了事件的任务。
以下是该函数的原型:
osalTaskRec_t*osalNextActiveTask(void)
{
osalTaskRec_t*srchTask;
//Startatthebeginning
srchTask=tasksHead;
//Whenfoundornot
while(srchTask){
if(srchTask-&
gt;
events){
//判断最高优先级中有无事件
returnsrchTask;
}
srchTask=srchTask-&
next;
returnNULL;
进入该函数后,让srchTask指向任务列表的头(tasksHead),然后利用if(srch-&
events)查看改任务中是否有事件,如果没有事件则srchTask指向任务链表的下一个元素,继续以上的工作,一旦查到某个任务有事件就返回任务的任务ID。
然后利用retEvents=(activeTask-&
pfnEventProcessor)(activeTask-&
taskID,events)函数进入改任务中执行事先编写的函数。
值得注意的是任务在执行完成之后一定要记得将任务的事件清空,不然返回的retEvents会跟activeTask-&
events相或(activeTask-&
events|=retEvents),假如该任务的优先级最高,这样每当程序进入下一次的调度时总会进入该任务中(因为该任务的事件不曾清空),这样其余的任务即使有置位的事件也不会被执行。
具体的分析将在下一章中利用实验详细讲解。
1.2加入自己的任务
上一节中讲到了操作系统的基本运行方式,运行中涉及的任务的初始化和运行,下面主要介绍如何加入自己的任务。
在TI-MAC1.2.1中全部的任务加入是利用osalAddTasks(void)在初始化时完成的,该函数属于应用层和OS之间的接口函数,而单个的任务加入就在osalAddTasks(void)函数中利用osalTaskAdd(Task_Init,Task_ProcessEvent,OSAL_TASK_PRIORITY)加入的,其中Task-Init(uint8task_id)是任务的初始化函数,该函数中系统为任务分配特定的唯一的ID号;
Task_ProcessEvent(uint8task_id,uint16events)是任务的执行函数,该函数中程序表达的就是要实现的功能;
最后一个参数OSAL_TASK_PRIORITY是任务的优先级别。
下面以一个小的实验例子来说明自己的任务的加入:
该实验实现一个简单的功能——LED小灯的闪烁,首先是任务的初始化函数
voidLED1Init(uint8taskId)
LED1Id=taskId;
taskId是OS系统为该函数分配的任务ID,利用该初始化函数将taskId赋值给LED1Id。
接下来就是任务的执行函数的编写
uint16LED2_ProcessEvent(uint8taskId,uint16events)
if(events&
amp;
MSA_SEND_EVENT)
HalLedSet(HAL_LED_2,HAL_LED_MODE_ON);
delay(5000);
HalLedSet(HAL_LED_2,HAL_LED_MODE_OFF);
osal_start_timerEx(LED1Id,MSA_SEND_EVENT,100);
return(events^MSA_SEND_EVENT);
return0;
最后就利用osalTaskAdd()函数将该任务加入到操作系统中去
voidosalAddTasks(void)
/*HALDriversTask*/
osalTaskAdd(Hal_Init,Hal_ProcessEvent,OSAL_TASK_PRIORITY_LOW);
osalTaskAdd(LED1Init,LED1_ProcessEvent,OSAL_TASK_PRIORITY_MED);
二:
TI操作系统的运行方式
OS操作系统不是一个完整的操作系统,任务与任务之间也不能抢占,只是简单地利用定时器管理和任务事件设置来周而复始地调用任务,与其他的操作系统一样(ucos,linux)它同样需要定时器来确定一个系统时钟tick,在cc2430中是占用一个硬件定时器来定时。
每次一个任务执行完后系统都会从高优先级到低优先级扫描任务是否被设置了事件,当有任务被设置事件时,就马上进入该任务中。
OS操作系统的思想是:
保证高优先级的任务有事件时最先得到处理器。
在任务的优先级赋值时,MACTask一般赋最高的优先级,这样是为了使得无线电的发射和接受提高到最重要的地位。
2.1一些主要的函数
OS操作系统的运行依靠许多重要的函数,下面介绍一些主要函数以及其中的参数。
1byteosal_set_event(bytetask_id,uint16event_flag)
说明:
该函数与任务的运行至关重要,它是为任务设置事件的函数,该函数被利用为任务设置事件标示符。
参数:
task_id:
欲设置事件的这个任务ID,一旦写入,将为该任务ID的任务设置事件。
event_falg:
事件标示,该事件标示占2个字节,每个位指定一个事件,只能有一个系统事件,其余的事件位在接受任务中自行定义。
2osal_start_timer(uint16event_id,uint16timeout_value)
该函数启动一个计时器,timeout_value单位时间后为这个函数现在所处的任务设置event_id事件标示。
event_id:
同上。
Timeout_value:
设置的时间毫秒数,当时间到是设置事件。
这个函数用的不多,为了精确地给出为哪个任务ID的任务设置事件,这个函数升级为osal_start_timerEx(bytetaskID,uint16event_id,uint16timeout_value)
其中taskID就是所指出的预设置的事件的任务。
也就是说,osal_start_timer()只能为自己所在的任务设置事件,而osal_start_timerEx()不仅可以为自己所在的任务设置事件,也可以为其余的任务设置事件。
3byte*osal_msg_allocate(uint16len)
分配一个消息缓冲器,供任务之间利用osal_msg_send()传送消息。
len:
消息缓冲器的长度。
4byteosal_msg_deallocate(byte*msg_ptr)
当用消息接受完成之后,取消掉分配的消息缓冲器。
*msg_ptr:
指向预取消的消息缓冲器的指针。
5byteosal_msg_send(bytedestination_task,byte*msg_ptr)
该函数用于源任务向目的任务发送命令,数据信息等,目的任务的标示符必须给出一个有效的系统任务ID,当消息发送成功后会给目的任务设置一个事件,该事件为系统事件——SYS_EVENT_MSG.
destination_task:
目的任务的任务标示
*msg_ptr:
指向预发送的消息的指针
6byte*osal_msg_receive(bytetask_id)
该函数用于一个任务去接收消息缓冲器中的消息,接收完成之后最好利用osal_msg_deallocate()取消消息缓冲器。
task_id:
接收者的任务标示号,这里要注意的就是task_id并不是发送消息的任务的任务ID而是接收任务的任务ID,比如说在MSA的任务标示为MSA_TaskId,在该任务中接收其余的任务发给它的消息就应该是osal_msg_receive(MSA_TaskId)。
理解了以上几个函数之后,基本上就可以实现一些小的任务的加入,任务的执行和消息的发送与接收了。
2.2小实验验证系统的运行方式
猜测:
OS系统按照任务的优先级从高到底不停的扫描这些任务,查看他们是否被设置了事件,如果该任务被设置了事件,则操作系统将马上进入这个任务的pFnEventProcessor(处理任务函数)中执行程序员预先编制好的程序。
高优先级的任务处理完成后必须取消该任务的事件,否则处理器一直进入该高优先级的任务中,不能正常执行低优先级的任务。
实验目的:
验证以上猜测是否正确
实验器材:
zigbee实验板一套TI-MAC程序(或者使用移植出来的LTOS以及STC12C60S2实验板)
实验步骤:
1:
设置两个任务,TASK_LED1和TASK_LED2,TASK_LED1的优先级低,TASK_LED2的优先级高。
voidosalAddTasks(void)
{/*HALDriversTask*/
/*MACTask*/
osalTaskAdd(LED1Init,LED1_ProcessEvent,OSAL_TASK_PRIORITY_MED);
/*ApplicationTask*/
osalTaskAdd(LED2Init,LED2_ProcessEvent,OSAL_TASK_PRIORITY_HIGH);
}
2:
在任务TASK_LED1中为TASK_LED2设置开灯关灯事件,并且在TASK_LED2执行完任务后清除事件标志。
在任务TASK_LED2中为TASK_LED1设置开灯关灯事件,并且TASK_LED1执行完成后清除事件标志(注意程序中标I和II的语句)。
运行结果:
两小灯交替闪烁。
uint16LED2_ProcessEvent(uint8taskId,uint16events)
{
LED_START_EVENT)
(I)
uint16LED1_ProcessEvent(uint8taskId,uint16events)
if(events&
LED_START_EVENT)
HalLedSet(HAL_LED_1,HAL_LED_MODE_ON);
HalLedSet(HAL_LED_1,HAL_LED_MODE_OFF);
osal_start_timerEx(LED2Id,MSA_SEND_EVENT,100);
(II)
}
return0;
3:
TASK_LED1,TASK_LED2互相为对方设置开灯关灯事件,并且TASK_LED2执行完成后清除事件标志,而TASK_LED1运行完成后不清除(去掉I的语句)。
4:
TASK_LED1,TASK_LED2互相为对方设置开灯关灯事件,并且TASK_LED1执行完成后清除事件标志,而TASK_LED2运行完成后不清除(去掉II的语句)。
LED1和LED2各闪烁一下,不再闪烁,处理器一直进入TASK_LED2中。
实验分析:
在初始化时,通过osal_start_TimerEx(1,LED_START_EVENT,100)为任务TASK_LED1设置了LED_START_EVENT事件标示,于是程序扫描TASK_LED1时知道该任务中设置了事件,就进入任务TASK_LED1中,TASK_LED1为TASK_LED2设置了事件且运行完成后自己的事件标志清零了,当任务链表从头扫到尾时,扫到TASK_LED1中没事件而TASK_LED2中有事件,则进入TASK_LED2中,而TASK_LED2中为TASK_LED1设置了LED_START_EVENT事件,则TASK_LED2执行完成之后,任务链表从头扫到尾,扫到TASK_LED1中有事件,然后又进入TASK_LED1中,这样一直循环下去。
同过上面的分析不难想到第3步的实验结论是正确的,但对于第4步不好理解。
其实第4步中的TASK_LED2虽然为TASK_LED1设置了事件但是自己的事件号没清除,又因为TASK_LED2的优先级高于TASK_LED1,故先扫描到TASK_LED2,于是进入TASK_LED2中,TASK_LED2执行完成之后任务链表从头扫描,先扫描到TASK_LED2中有事件又进入TASK_LED2中,这样一直在TASK_LED2中。
实验结论:
猜测是正确的。
三:
揭秘TI操作系统:
3.1:
调度,非抢占,不需重入
调度是内核的主要职责之一,就是要决定该轮到哪个任务运行了。
多数实时内核是基于优先级调度法的。
每个任务根据其重要程度的不同被赋予一定的优先级。
基于优先级的调度法指,CPU总是让处在就绪态的优先级最高的任务先运行。
然而,究竟何时让高优先级任务掌握CPU的使用权,有两种不同的情况,这要看用的是什么类型的内核,是非抢占型的还是可抢占型内核。
3.1.1非抢占型内核(Non-PreemptiveKernel)
非抢占型内核也叫做不可剥夺型内核,不可剥夺型内核要求每个任务自我放弃CPU的所有权。
不可剥夺型调度法也称作合作型多任务,各个任务彼此合作共享一个CPU。
异步事件还是由中断服务来处理。
中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。
但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。
不可剥夺型内核的一个优点是响应中断快。
在任务级,不可剥夺型内核允许使用不可重入函数。
每个任务都可以调用不可重入性函数,而不必担心其它任务可能正在使用该函数从而造成数据的破坏。
因为每个任务要运行到完成时才释放CPU的控制权。
使用不可剥夺型内核时,任务级响应时间比前后台系统快得多。
此时的任务级响应时间取决于最长的任务执行时间。
不可剥夺型内核的另一个优点是,几乎不需要使用信号量保护共享数据。
运行着的任务占有CPU,而不必担心被别的任务抢占。
但这也不是绝对的,在某种情况下,信号量还是用得着的。
处理共享I/O设备时仍需要使用互斥型信号量。
例如,在打印机的使用上,仍需要满足互斥条件。
图2示意不可剥夺型内核的运行情况,任务在运行过程之中,[2
(1)]中断来了,如果此时中断是开着的,CPU由中断向量[2
(2)]进入中断服务子程序,中断服务子程序做事件处理[2(3)],使一个有更高级的任务进入就绪态。
中断服务完成以后,中断返回指令[2(4)],使CPU回到原来被中断的任务,接着执行该任务的代码[2(5)]直到该任务完成,调用一个内核服务函数以释放CPU控制权,由内核将控制权交给那个优先级更高的、并已进入就绪态的任务[2(6)],这个优先级更高的任务才开始处理中断服务程序标识的事件[2(7)]。
图片2.jpg)不可剥夺型内核的最大缺陷在于其响应时间。
高优先级的任务已经进入就绪态,但还不能运行,要等,也许要等很长时间,直到当前运行着的任务释放CPU。
3.1.2可剥夺型内核
当系统响应时间很重要时,要使用可剥夺型内核。
最高优先级的任务一旦就绪,总能得到CPU的控制权。
当一个运行着的任务使一个比它优先级高的任务进入了就绪态,当前任务的CPU使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。
如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。
如图3所示。
图片3.jpg)使用可剥夺型内核,最高优先级的任务什么时候可以执行,可以得到CPU的控制权是可知的。
3.1.3LTOS的调度分析
LTOS属于非抢占型操作系统,所以不必担心函数重入的问题,也不必担心临界区的保护问题,对于没有任何操作系统使用经验的人来说,学习并且分析LTOS操作系统,将使你在最短的时间里对操作系统有一个整体的理解。
LTOS中调度函数osal_start_system(void)函数分析
voidosal_start_system(void)
UINT16events;
UINT16retEvents;
halIntState_tintState;
//ForeverLoop
while
(1)
/*ThisreplacesMT_SerialPoll()andosal_check_timer()*/
TaskActive=osalNextActiveTask();
TaskActive是一个全局变量,总是记录着此刻处于就绪态的任务,在LTOS中,所谓就绪态就是有事件等待处理的任务。
osalNextActiveTask()该函数是用来寻找此刻有事件并且处于最高优先级的任务,该函数返回一个指针,指针指向最高优先级有事件任务。
if(TaskActive)
如果TaskActive非空,即某个任务有事件,则进入处理函数中
HAL_ENTER_CRITICAL_SECTION(intState);
宏,保存此时的EA寄存器值,然后关闭中断
events=TaskActive-&
events;
取得TaskActive中的事件。
//CleartheEventsforthistask
TaskActive-&
events=0;
清除TaskActive中的事件,为下一次的调度作准备。
HAL_EXIT_CRITICAL_SECTION(intState);
宏,还原中断值
if(events!
=0)
此处,不少读者可能认为该判断可以不要。
但是TI程序员写程序非常的谨慎,为了避免任何一个不可预知的错误,他们总是很小心。
//Callthetasktoprocesstheevent(s)
if(TaskActive-&
pfnEventProcessor)
如果Tas