概述
Z-Stack协议栈包含了ZigBee协议所规定的基本功能,这些功能是以函数的形式实现的,为了便于管理这些函数集,从ZigBee 2006协议栈开始,ZigBee协议栈内加入了实时操作系统,称为OSAL。要学习协议栈的运行方式,其实就是在学习OSAL操作系统。
1、OSAL常用术语
在讲解之前,先介绍操作系统有关的部分基础知识。操作系统(OS)基本术语如下。
①资源(Resource) :任何任务所占用的实体都可以称为资源,如一个变量、数组、结构体等。
②共享资源(Shared Resource):至少可以被两个任务使用的资源称为共享资源,为了防止共享资源被破坏,每个任务在操作共享资源时,必须保证是独占该资源。
③任务(Task): 一个任务,又称作一个线程,是一个简单的程序的执行过程,在任务执行过程中,可以认为CPU完全属于该任务。在任务设计时,需要将问题尽可能地分为多个任务,每个任务独立完成某种功能,同时被赋予一定的优先级,拥有自己的CPU寄存器和堆栈空间。一般将任务设计为一个无限循环。
④多任务运行(Muti-task Running) :实际上只有一个任务在运行,但是CPU可以使用任务调度策略将多个任务进行调度,每个任务执行特定的时间,时间片到了以后,就进行任务切换,由于每个任务执行时间很短,例如:10ms,因此,任务切换很频繁,这就造成了多任务同时运行的“假象”。
⑤内核(Kernel) :在多任务系统中,内核负责管理各个任务,主要包括:为每个任务分配CPU时间;任务调度;负责任务间的通信。内核提供的基本的内核服务是任务切换。使用内核可以大大简化应用系统的程序设计方法,借助内核提供的任务切换功能,可以将应用程序分为不同的任务来实现。
⑥互斥(Mutual Exclusion) :多任务间通信最简单,常用的方法是使用共享数据结构。对于单片机系统,所有任务都在单一的地址空间下,使用共享的数据结构包括全局变量、指针、缓冲区等。虽然共享数据结构的方法简单,但是必须保证对共享数据结构的写操作具有唯一性,以避免晶振和数据不同步。
保护共享资源最常用的方法是:
- 关中断;
- 使用测试并叉位指令(T&S指令);
- 禁止任务切换;
- 使用信号量。
其中,在ZigBee 协议栈内嵌操作系统中,经常使用的方法是关中断。
⑦消息队列(Message Queue) 消息队列用于任务间传递消息,通常包含任务间同步的信息。通过内核提供的服务、任务或者中断服务程序将一条消息放入消息队列,然后,其他任务可以使用内核提供的服务从消息队列中获取属于自己的消息。为了降低传递消息的开支,通常传递指向消息的指针。
在ZigBee协议栈中,OSAL主要提供如下功能:
- 任务注册、初始化和启动;
- 任务间的同步、互斥;
- 中断处理;
- 存储器分配和管理。
2、OSAL运行机理
ZigBee协议栈与ZigBee协议之间并不能完全画等号。ZigBee协议栈仅仅是ZigBee协议的具体实现,因此,存在于ZigBee协议栈中使用的OSAL并没有出现在ZigBee协议中。在ZigBee协议中可以找到使用OSAL的某些“根源”。在基于ZigBee协议栈的应用程序开发过程中,用户只需要实现应用层的程序开发即可。
应用程序框架中包含了最多240个应用程序对象,每个应用程序对象运行在不同的端口上,因此,端口的作用是区分不同的应用程序对象。可以把一个应用程序对象看成一个任务,因此,需要一个机制来实现任务的切换、同步和互斥,这就是OSAL产生的根源。
因此,从上面的分析可以得出下面的结论:OSAL就是一种支持多任务运行的系统资源分配机制。
下面我们从协议栈工程开始学习OSAL的运行机理,协议栈中的SampleApp.c文件有两个非常重要的函数SampleApp_Init和SampleApp_ProcessEvent,SampleApp_Init是APP层任务初始化函数,SampleApp_ProcessEvent函数则负责判断由参数传递的事件类型,然后执行相应的事件函数。
因此,在协议找中,OSAL负责调度各个任务的运行,如果有事件发生了,则会调用相应的事件处理函数进行处理。OSAL的工作原理示意图如图所示:
下面我们来回到程序中,找到系统运行函数,进行分析。在main函数中,有一个叫做osal_start_system()的函数,这个函数就是系统运行函数,右键跳进去后,发现函数里是一个死循环,循环中调用了一个函数:
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
osal_run_system();
}
}
被调用的函数osal_run_system()就是协议栈的核心,系统运行就是在这个函数中实现:
/*********************************************************************
* @fn osal_run_system
*
* @brief
*
* This function will make one pass through the OSAL taskEvents table
* and call the task_event_processor() function for the first task that
* is found with at least one event pending. If there are no pending
* events (all tasks), this function puts the processor into Sleep.
*
* @param void
*
* @return none
*/
void osal_run_system( void )
{
uint8 idx = 0;
osalTimeUpdate();
Hal_ProcessPoll();
do {
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break; // 得到待处理的最高优先级任务索引号 idx
}
} while (++idx < tasksCnt);
if (idx < tasksCnt)
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);
events = tasksEvents[idx]; // 提取需要处理的任务事件
tasksEvents[idx] = 0; // Clear the Events for this task.
HAL_EXIT_CRITICAL_SECTION(intState);
activeTaskID = idx;
events = (tasksArr[idx])( idx, events ); // 通过索引号调用相应的事件处理函数
activeTaskID = TASK_NO_TASK;
HAL_ENTER_CRITICAL_SECTION(intState);
tasksEvents[idx] |= events; // Add back unprocessed events to the current task.
HAL_EXIT_CRITICAL_SECTION(intState);
}
#if defined( POWER_SAVING )
else // Complete pass through all task events with no activity?
{
osal_pwrmgr_powerconserve(); // Put the processor/system into sleep
}
#endif
/* Yield in case cooperative scheduling is being used. */
#if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0)
{
osal_task_yield();
}
#endif
}
变量idx用来在事件表中索引,函数osalTimeUpdate()和Hal_ProcessPoll()则用来更新时钟,并查看硬件方面是否有时间发生,如串口接收、按键闭合等。Do-while循环则一直查看是否有事件,若有则break跳出,进行处理。tasksEvents是指向事件表首地址的指针,而tasksArr是存放事件处理函数指针的数组。有事件发生时,变量events从tasksEvents中获取需要处理的事件,编号为idx,然后通过tasksArr调用相应的事件处理函数,编号也为idx,不难看出,事件表与函数表存在着对应的关系,如图:
在事件处理完成后,事件处理函数又会返回一个值给events,然后这个events又放进了事件表tasksEvents中,这是因为事件表中的每一项都是一个16位的数,它的每一位代表一个事件,若有几个事件同时发生,而处理函数每次只能处理一个事件,处理函数会将代表当前处理事件的那个位用异或操作清零,其他位值不变,然后将这个值再返回给events,其他事件就会在之后的轮询中被依次处理。
根据上面所讲,应该可以将OSAL的运行机理初步的总结为:通过不断地查询事件表来判断是否有事件发生,如果有事件发生,则查找函数表找到对应的事件处理函数对事件进行处理。事件表使用数组来实现,数组的每一项对应一个任务的事件,每一位表示一个事件:函数表使用函数指针数组来实现,数组的每一项是一个函数指针,指向了事件处理函数。
3、OSAL消息队列
讲解消息队列之前需要讲解一下消息与事件的区别。
事件是驱动任务去执行某些操作的条件,当系统中产生了一个事件,OSAL将这个事件传递给相应的任务后,任务才能执行一个相应的操作(调用事件处理函数去处理)。
通常某些事件发生时,又伴随着一些附加信息的产生,例如:从天线接收到数据后,会产生AF_INCOMING MSG_CMD消息,但是任务的事件处理函数在处理这个事件的时候,还需要得到所收到的数据。
因此,这就需要将事件和数据封装成一个消息,将消息发送到消息队列,然后在事件处理函数中就可以使用osal_msg_receive,从消息队列中得到该消息。如下代码可以从消息队列中得到一个消息。
MSGpkt=(afIncomingMSGPacket_t *)osal msg_ receive(GenericApp_TaskID);
OSAL维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后调用消息处理函数进行相应的处理即可。
每个消息都包含一个消息头osal_msg_hdr_t和用户自定义的消息,osal_msg_hdr_t结构体的定义为:
typedef struct
{
void *next;
uintl6 len;
uint8 dest_ id;
}osal_msg_hdr_t;
4、OSAL应用编程接口
既然ZigBee协议栈内嵌了操作系统来支持多任务运行,那么任务间同步、互斥等都需要相应的API来支持,下面讲解一下OSAL提供的API。
总体而言,OSAL提供了以下8个方面的API:
- 消息管理
- 任务同步
- 时间管理
- 中断管理
- 任务管理
- 内存管理
- 电源管理
- 非易失性闪存管理
下面,选取部分典型的API进行讲解。
①消息管理API主要用于处理任务间消息的交换,主要包括为任务分配消息缓存、释放消息缓存、接收消息和发送消息等API函数。
osal_msg_allocate()
函数原型:uint8 *osal_msg_allocate( uint16 len)
功能描述:为消息分配缓存空间。
osal_ msg_ deallocate()
函数原型:uint8 osal_msg_deallocate( uint8 *msg_ptr )
功能描述:释放消息的缓存空间。
osal_mag_send( )
函数原型:uint8 osal_msg_send(uint8 destination_task, uint8 *msg_ptr)
功能描述:一个任务发送消息到消息队列。
osal_msg_ receive()
函数原型:uint8 *osal_msg_receive(uint8 task_id)
功能描述:一个任务从消息队列接收属于自己的消息。
②任务同步API 主要用于任务间的同步,允许一个任务等待某个事件的发生。
osal_set_event()
函数原型:uint8 osal_set_event(uint8 task_id, uint16 event_flag)
功能描述:运行一个任务设置某一事件。
③时间管理API 用于开启和关闭定时器,定时时间一般为毫秒级定时,使用该API,用户不必关心底层定时器是如何初始化的,只需要调用即可,在ZigBee协议栈物理层已经将定时器初始化了。
osal_start_timerEx ()
函数原型:uint8 osal_start_timerEx( uint8 task_id, uintl6 eventee id, uintl6 timeout_value)
功能描述:设置一个定时时间,定时时间到后,相应的事件被设置。
osal_ stop_timerEx()
函数原型:uint8 osal_stop_timerEx(uint8 task_id, uintl6 event_id)
功能描述:停止已经启动的定时器。
④中断管理API 主要用于控制中断的开启与关闭。一般很少使用,所以在此不赞述,如果需要相关的函数,请参考《OS Abstraction Layer Application Programming Interface》。
⑤任务管理API 主要功能是对OSAL进行初始化和启动,主要包括osal_init_system()和osal_start_system()。
osal_init_system()
函数原型:uint8 osal_init_system( void)
功能描述:初始化OSAL,该函数是第一个被调用的OSAL 函数。
osal_start_system()
函数原型:uint8 osal_start_system( void)
功能描述:该两数包含一个无限循环,它将查询所有的任务事件,如果有事件发生,则调用相应的事件处理函数,处理完该事件后,返回主循环继续检测是否有事件发生,如果开启了节能模式,则没有事件发生时,该函数将使处理器进入休眠模式,以降低系统功耗。
⑥内存管理API 用于在堆上分配缓冲区,这里需要注意:以下两个API函数一定要成对使用,防止产生内存泄漏。
osal_mem_alloc()
函数原型:void *osal_mem_alloc( uint 16 size )
功能描述:在堆上分配指定大小的缓冲区。
osal_mem_free()
函数原型:void osal_mem_free( void *ptr)
功能描述:释放使用osal_mem_alloc()分配的缓冲区。
⑦电源管理API主要是用于电池供电的ZigBee网络节点,在此不做讨论。
⑧非易失性闪存管理API主要是添加了对非易失性闪存的管理函数,一般这里的非易失性闪存指的是系统的Flash存储器〔也可以是E2PROM),每个NV条目分配唯一的ID号。
osal_nv_item_init()
函数原型:byte osal_nv_item_init( uintl6 id, uintl6 len, void *buf );
功能描述:初始化NV条目,该函数检查是否存在NV条目,如果不存在,它将创建并初始化该条目。如果该条目存在,每次调用osal_nv_read()或osal_nv_write()函数对该条目进行读、写之前,都要调用该函数。
osal_nv_read()
函数原型:byte osal_nv_read( uintl6 id, uintl6 offset, uintl6 len, void *buf)
功能描述:从NV条目中读取数据,可以读取整个条目的数据,也可以读取部分数据。
osal_nv_write()
函数原型:byte osal_nv_write( uintl6 id, uintl6 offset, uintl6 len, void *buf );
功能描述:写数据到NV条目。
最后
以上就是酷酷芝麻为你收集整理的ZIGBEE通讯-9.ZigBee协议栈的运行方式的全部内容,希望文章能够帮你解决ZIGBEE通讯-9.ZigBee协议栈的运行方式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复