已阅读5页,还剩14页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
将TIzigbee开源协议栈中的OS操作系统移植出来本人从事zigbee的研发工作已接有多年,在这几年的技术之路上收获了很多,也失去了很多。几年之后,离开了zigbee研发岗位,决定写点什么作为纪念,另外也希望给后来的学习zigbee的同盟们留下一点“砖块”。 不复杂的小系统一般设计成如图1所示的样子,这种系统一般称作为前后台系统或超循环系统。整个应用程序(整个工程)是一个无限循环,对于应用中的具体操作既是在这个无限循环中不停地调用各个相应的函数来实现,这部分可以叫做后台行为。后台也叫做任务级。前台也叫中断级。要求实时响应的操作一般由中断服务来保证。如果前后台程序需要修改,原先整体的循环会被打乱的凌乱不堪,循环的时序也会受到影响,从而也就不能保证修改后的程序继续能够正常无误工作。这样就给中大型程序的升级,应用的增加,工程的管理带来的不可避免的麻烦,最坏的情况就是一个程序的升级相当于一个工程的重新编写。图1 此时就需要引进操作系统了,有了操作系统后,整个工程可以被划分成许多小的模块(任务)互相协调合作共同完成整个项目,程序修改或者升级时只需要修改对应的任务即可完成,不必改动整个工程,这是笔者认为在使用操作系统时最显而易见的好处。另外,使用操作系统使得程序的各个功能模块化,利用各种调度方法(不同操作系统可能不同)实现整个工程,从而也使得大型的程序、杂乱无章的循环变的尽然有序,程序运行更加安全可靠。现在在嵌入式领域被广泛关注和认同的操作系统有uCOS,linux,windowsCE,UCLINUX,等等,据笔者了解(才疏学浅),应用于8位单片机并得到了实践证明的暂时听说了UCOS以及RTX51就是KEIL公司针对51开发的一个小型RTOS。RTOS只提供了库接口函数,对于学者和开发者并不开源,所以使用者和探讨者的人数相对于UCOS并不多,UCOS是一款开源的实时操作系统,UCOS一直受到学者和开发者的青睐,但是由于任务中加入ucos操作系统后编译所占用的code区以及XDATA区增大较多,不适合移植到小容量的单片机上使用。笔者有幸于2008年接触到TI公司应用与zigbee协议栈的一款非抢占操作系统,下面将其叫做LTOS(littleTIOSorLiTieOS),由于它应用简单方便,开发项目稳定可靠,便于理解和学习,使得操作系统初学者可以很容易的对操作系统整体有一个全面的了解,所以笔者决定将该操作系统移植出来,放入STC12C60S2单片机中使用。记录下该文档抛砖引玉,使更多人能更快地理解该操作系统并得到的该小型操作系统更好的发展和应用。 一:对TI操作系统初步分析 11任务、事件、消息 刚拿到TI-MAC1.2.1时被该程序搞蒙了,TI-MAC1.2.1的程序竟然是基于他们自己编写的OS操作系统运行的。说到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-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&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(eventsMSA_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*/ osalTaskAdd(Hal_Init,Hal_ProcessEvent,OSAL_TASK_PRIORITY_LOW); /*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) if(events&LED_START_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(eventsMSA_SEND_EVENT);(I) return0;(I) uint16LED1_ProcessEvent(uint8taskId,uint16events) if(events&LED_START_EVENT) HalLedSet(HAL_LED_1,HAL_LED_MODE_ON); delay(5000); HalLedSet(HAL_LED_1,HAL_LED_MODE_OFF); osal_start_timerEx(LED2Id,MSA_SEND_EVENT,100); return(eventsMSA_SEND_EVENT);(II) return0;(II) 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) 如果TaskActive中的处理函数非空。TaskActive-pfnEventProcessor是一个指向任务的指针,即指向在osal_add_Task()中加入的APPLICATION中设计的FUNCTION()函数,不能理解函数指针的读者可以想阅读一些关于指针的教程。 retEvents=(TaskActive-pfnEventProcessor)(TaskActive-taskID,events); 运行函数(TaskActive-pfnEventProcessor)(TaskActive-taskID,events),即运行函数FUNCTION() /Addbackunprocessedeventstothecurrenttask HAL_ENTER_CRITICAL_SECTION(intState); 宏,保存此时的EA寄存器值,然后关闭中断 TaskActive-events|=retEvents; 将函数的返回值重新赋值给TaskActive-events,这就是为什么任务函数执行完之后,一定要将事件清空的原因。不清空高优先级的任务中事件,高优先级任务将一直运行,低优先级任务得不到运行。 HAL_EXIT_CRITICAL_SECTION(intState); 宏,还原中断值 /Completepassthroughalltaskeventswithnoactivity? 该函数就是整个程序的总调度,他总是在不停地从高到低扫描每一个任务,当任务中有事件时,就进入到该任务中去执行事件。进入该调度函数后,程序就这样无限运行下去,不会退出该调度函数了,除非中断。知道了程序是如何被调度的,读者可能还是一头雾水,不用着急,下一步我们来介绍任务的加载以及任务中的事件是如何被加入进去的。 3.2:任务加载 voidosal_add_Task(pTaskInitFnpfnInit, pTaskEventHandlerFnpfnEventProcessor, UINT8taskPriority) OsalTadkREC_t*TaskNew; OsalTadkREC_t*TaskSech; OsalTadkREC_t*TaskPTR; TaskNew=osal_mem_alloc(sizeof(OsalTadkREC_t); 申请一块空间给新的任务 if(TaskNew) TaskNew-pfnInit=pfnInit; TaskNew-pfnEventProcessor=pfnEventProcessor; TaskNew-taskID=Task_id+; TaskNew-events=0; TaskNew-taskPriority=taskPriority; TaskNew-next=(OsalTadkREC_t*)NULL; 新任务中赋初值 TaskPTR=&TaskHead; TaskSech=TaskHead; 以下为将各个任务一一加入任务链表的操作方法,实现过程中使用了双重指针,掌握起来会有些困难,不过只要在好好揣摩之,也不难理解。 While(TaskSech) if(TaskNew-taskPriorityTaskSech-taskPriority) 如果新任务的优先级高于现在的任务 TaskNew-next=TaskSech; *TaskPTR=TaskNew; 直接让TaskNew放在TaskPTR指向的地址,TaskPTR先前指向的地址可能为前一个优先级高的任务,这里经过比较优先级后直接将TaskNew放入TaskPTR指向的地址,也就是让TaskNew与比他优先级低的任务交换了位置,让比新任务优先级的任务往后靠,而新任务放在了以前这个任务的地方。笔者在此是这样理解,如有错误,请大家指正。 return; TaskPTR=&TaskSech-next; TaskSech=TaskSech-next; 如果优先级不高于以前的任务,则继续往后找 *TaskPTR=TaskNew; 连表头没有任务时,直接让新任务放入连表头。新任务的优先级不高于以往任何一个任务时,则新任务放于链表尾。第一次见识链表还能这样子建立,这种写法相当精妙,让人不得不感叹老外做事之用心。 return; 3.3:事件设置函数 事件被设置只有一个函数,就是osal_set_event(),但是设置的方式有三种: 1直接调用该函数为某个任务设置事件 2使用延时设置事件函数 3使用消息,消息会被LTOS默认为是一个系统事件(system_event) 下面先分析一下osal_set_event函数 byteosal_set_event(bytetask_id,UINT16event_flag) OsalTadkREC_t*srchTask; halIntState_tintState; srchTask=osalFindTask(task_id); 在任务链表中寻找任务号为task_id的节点,并让srchTash指向该地址 task_id是需要增加事件的任务号,event_flag为需要设定的事件号,事件号一般设置成掩码方式,比如0x0001,0x0020,0x0004等等,一个十六位的变量可以同时表示16的事件的置位与否,换句话来说,一个任务中事件数目不要超过15,读者可能会疑惑了,刚刚明明说是16怎么一下又变成了15,其实是这样的,0x8000这个掩码已经被默认为系统事件了,所以供我们使用的还有15个掩码。对于一些小型的工程,每个任务15个事件号足以应对。但是笔者在实际应用中发现有些任务确确实实需要多余15个的事件来完成整个任务,这个时候该怎么办呢?这也是有办法的,将所有要处理的事件分门别类,一类为系统事件,一类为其余事件。系统事件的触发是通过消息来完成的,任务自己给任务自己发送消息同样是可以的,这样系统事件就可以再利用消息中的变量来判断更多的事件标志从而达到更多事件的触发了。但是笔者在移植该操作系统时,考虑到“弱功能”单片机的资源有限,并没有将消息队列移植出来。这就需要读者在做项目规划的时候就将各个任务分配好,考虑项目是划分为3个任务还是4个任务或者更多。每个任务所做的事情不得超过十五个事件就可以了,或者使用全局变量定义标志位,同样可以达到扩展事件的目的。 if(srchTask) 如果找到了合适的节点 /Holdoffinterrupts HAL_ENTER_CRITICAL_SECTION(intState); 宏,保存此时的EA寄存器值,然后关闭中断 /Stufftheeventbit(s) srchTask-events|=event_flag; 将事件掩码赋给节点中的events元素,请注意:上节中所讲解的osalNextActiveTask()函数为什么能搜索到有事件的任务呢,就是因为这里给任务加了事件。 /Releaseinterrupts HAL_EXIT_CRITICAL_SECTION(intState); 宏,还原中断值 else return(INVALID_TASK); return(ZSUCCESS); 3.4:事件设置方式 在前面介绍了事件设置函数,下面介绍设置事件的另外两种方式。 3.4.1延时为某个任务设置事件 byteosal_start_timerEx(bytetaskID,UINT16event_id,UINT16timeout_value) 在该函数中taskID是预设置事件的任务号,evnet_id为预设置的事件,timeout_value为延时多少个时间刻度再设置事件,实际延时的时间为timeout_value*(1/系统频率)。比如系统频率为100,即10ms周期。那么设置timeout_value为100,实际时间也就是1s。 halIntState_tintState; osalTimerRec_t*newTimer; HAL_ENTER_CRITICAL_SECTION(intState);/Holdoffinterrupts. 宏,保存此时的EA寄存器值,然后关闭中断 /Addtimer newTimer=osalAddTimer(taskID,event_id,timeout_value); 在此又要引入一个链表了-时间链表,时间链表的各个节点有三个主要元素:记录时间的变量,记录任务号的变量和记录事件的变量。osalAddTimer函数就是以某个任务的任务号作为主要信息来增加节点,osalAddTimer函数先判断这个任务在之前有无添加过时间节点并还没有被利用,如果有则直接修改该时间节点将最新延时时间放入,如果这个任务之前没有添加时间节点或时间节点已经被利用并删除,则重新申请空间并添加一个节点。添加完后返回该节点的地址。 if(newTimer) 如果添加成功 /Doesthetimerneedtobestarted? if(timerActive=FALSE) timerActive为一个全局变量,用来判断定时器是否开启了,这里如果没有开启,则马上开启定时器,使得定时器开始运作。 osal_timer_activate(TRUE); 定时器运作函数,这个函数最终会被一个跟硬件定时器打交道的函数代替 HAL_EXIT_CRITICAL_SECTION(intState);/Re-enableinterrupts. 宏,还原中断值 return(newTimer!=NULL)?ZSUCCESS:NO_TIMER_AVAIL); 3.4.2消息为某个任务添加事件,添加的事件为系统事件 byteosal_msg_send(bytedestination_task,byte*msg_ptr) destination_task为需要接收消息的任务,msg_prt为指向消息的指针。在使用消息发送函数之前,要先使用osal_msg_allocate(len)来动态申请一个空间存放该消息,用完该消息后同样得调用osal_msg_deallocate(uint8*)pMsg)函数来释放动态申请的空间。 if(msg_ptr=NULL) return(INVALID_MSG_POINTER); 消息为空的话直接返回错误指示信息 if(osalFindTask(destination_task)=NULL) osal_msg_deallocate(msg_ptr); return(INVALID_TASK); 如果找不到需要接收消息的任务,说明该函数绝对被错误调用了,另外直接释放动态申请的空间 /Checkthemessageheader if(OSAL_MSG_NEXT(msg_ptr)!=NULL| OSAL_MSG_ID(msg_ptr)!=TASK_NO_TASK) osal_msg_deallocate(msg_ptr); return(INVALID_MSG_POINTER); 如果msg_ptr的指针指向地址不正确,直接释放动态申请的空间。 OSAL_MSG_ID(msg_ptr)=destination_task; /queuemessage osal_msg_enqueue(&osal_qHead,msg_ptr); 将信号放入消息队列中 /Signalthetaskthatamessageiswaiting osal_set_event(destination_task,SYS_EVENT_MSG); 设置为系统事件 return(ZSUCCESS); 事实上考虑到小型单片机code与xdata,RAM都比较小这个事实,我并没有将消息以及消息队列这个功能移植过来。其实利用延时设置事件、直接设置事件以及15个事件号已经足以完成许多中小心的程序工程。再大点的工程可能小单片机也吃不消了,那个时候需要考虑的不是增加操作系统了,而是要考虑跟换单片机。 3.5:TICK,时间单位,硬件定时器 在介绍完任务和事件的加入之后,我们开始阐述一个隐蔽于LTOS操作系统身后但又十分重要的概念-系统时钟。 LTOS需要用户提供周期性信号源,用于实现事件的定时判断事件延时时间到并将事件置位。节拍率应在每秒10次到1000次之间,或者说10到1000Hz。时钟节拍率越高,系统的额外负荷就越重。时钟节拍的实际频率取决于用户应用程序的精度。TI在他们的zigbee协议栈TI-MAC1.2.1就是使用了1000HZ的时钟频率。在此提出一点异议请读者注意,笔者主要讲解的是将LTOS移植到“弱功能”单片机上使用,所以并不提倡使用太高精度的时钟频率,这样会给小型单片机单来太多的额外负担,建议使用100HZ的系统时钟。时钟节拍源可以是专门的硬件定时器,也可以是一些外部信号源。 对于osalTimerUpdate(UINT8updateTime)函数分析 staticvoidosalTimerUpdate(uint16updateTime) halIntState_tintState; osalTimerRec_t*srchTimer; osalTimerRec_t*prevTimer; osalTimerRec_t*saveTimer; HAL_ENTER_CRITICAL_SECTION(intState);/Holdoffinterrupts. 宏,保存此时的EA寄存器值,然后关闭中断 /Updatethesystemtime osal_systemClock+=updateTime; osal_systemClock是一个全局变量,用于知道系统从启动到现在一直运行了多少个系统时钟,updataTime是时间走动刻度,一般取1。该函数在系统定时器的中断函数中调用。比如,系统的频率设置为100,那么也就是定时器的时间设置成10MS,每10MS进入一次定时器中断,然后执行该osalTimerUpdate()函数。 /Lookforopentimerslot if(timerHead!=NULL) 对时间链表头的分析。头不为空就进入处理函数。那么时间链表头怎么样才不为空呢,也就是osalAddTimer()函数至少被调用了一次,换句话说,也就是只少有一个任务利用osal_start_timerEx()函数为其余任务或自己增加延时任务,时间链表中的节点的结构体中的timeout成员会被加入时间值来代表某个任务多久之后触发特定事件。这里听着可能有点绕口,不过慢慢往后看,就能明白。 /Addittotheendofthetimerlist srchTimer=timerHead; prevTimer=(void*)NULL;
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 教育局信访接待工作制度
- 2026年能源政策与可持续发展考试及答案
- 正畸拔牙矫治后骨开裂与骨开窗的多维度临床剖析与防治策略研究
- 正定古城历史建筑认定:标准、实践与价值探究
- 欧美儿童绘本形式语言对情绪表达的影响与作用探究
- 欧盟新老成员国商品市场一体化进程:基于内部边界效应的深度剖析
- 欧式期权定价的理论、模型与实践应用研究
- 欠驱动系统非线性控制方法:基于倒立摆与水下航行器的对比研究
- 橡胶支座在超限钢框架结构隔震中的效能与应用策略研究
- 横肋竖肋协同作用下加劲钢板剪力墙弹性稳定性解析与优化策略
- 国家安全 青春挺膺-新时代青年的使命与担当
- 西南大学PPT 01 蓝色版通用模板
- 市场监管公务员考核表个人总结5篇
- 辽宁某办公大楼室内装饰装修工程施工组织设计
- 车灯研发设计过程课件
- 部编版语文四年级下册全册教案
- 最新安全生产管理教材电子版
- TPM基础知识培训教材课件
- 石榴花开别样红籽籽同心一家亲民族团结一家亲主题班会课件
- 自考00402《学前教育史》重点归纳
- 通用规范汉字表注音完整版
评论
0/150
提交评论