Zstack OSAL资料汇编

Zstack OSAL详解
2010-08-16 13:33

Zstack OSAL详解

 1. void osal_start_system( void )

所有应用程序,无论是自己写的最简单的测试程序还是复杂的OSAL操作系统,都必须从main( )来入口。所谓的OS操作系统,我们不妨这样想像:自己写一个最简单的main( ),里面就一句打印“Hello, World”.如果需要加入Key, LED这样的输入输出功能,那么就需要扩充main( ),加入Key, LED的驱动,如果要实现多线程调度,就要加入Timer驱动等等。其实操作系统就是这么来的,简单吧:)。
一般在OS中都会有个死循环,在这个循环中去处理各种各样的事件。在Zstack OSAL中,这个死循环就存在于osal_start_system()这个函数中。下面来详细分析在这个死循环中到底在做哪些事情。

 2. OSAL的“心跳”

在OSAL的死循环中,各个事件只是在某些特定的情况下发生,如果OSAL一刻不停去轮询去处理这些应用程序,迟早会累死(热量,功耗,寿命…),这样做是完全没有必要的。所以这里就引入了心跳的概念,也就是OS的时钟节奏。在Zstack OSAL中这个节奏定义为1ms, 由8 bits HW_TIMER4来控制,当然这些都可以由程序员来修改,后面就以系统的默认值来讲述。在void InitBoard( byte level )这个函数中有下面这段代码就是在定义系统的心跳Timer。
HalTimerConfig (OSAL_TIMER,
HAL_TIMER_MODE_CTC,                  HAL_TIMER_CHANNEL_SINGLE,
HAL_TIMER_CH_MODE_OUTPUT_COMPARE,
                  OnboardTimerIntEnable,
                  Onboard_TimerCallBack);

在OSAL的Timer定义好了以后,就要启动Timer, 至于如何启动Timer, 请自行查阅2430 Spec, 我这里想说的是,在一步步跟踪源码到死循环开始,都没有发现启动OSAL Timer的代码,最后通过观察Timer相关的控制寄存器,发现,在网络层初始化函数nwk_init( taskID++ )执行完毕后Timer启动了,也就是说在网络层初始化函数中有启动Timer的语句,因为网络层初始化是不开源的,无从去看源代码验证,总之,Timer启动了就好。

每当1ms心跳来临时,Timer4的中断标志置位,这样在OSAL的死循环中检测到这个标志置位后,就去轮询处理各事件。没有检测到这个标志位则继续死循环。在死循环的开始有调用Hal_ProcessPoll()这条语句,实际上就是在查询中断标志并作相应的处理。

 3. OSAL的“心跳”来临后的处理

上节提到Hal_ProcessPoll()这条语句,实际上就是在查询中断标志并作相应的处理。那么当1ms心跳来临时,我们跟踪进这个函数看看它到底干了些什么。
当判断到中断标志,表明1ms心跳来临了,就去调用Timer4相应的回调函数。这个回调函数由HalTimerConfig()的最后一个参数来定义,请回看上节,OSAL Timer的回调函数就是Onboard_TimerCallBack(),一步步跟进,最终调用osalTimerUpdate()这个函数。在这个函数中会去轮询Timer事件链表。
Timer事件链表是下面这样一个结构,next指向下一个Timer事件,timeout值表明本Timer事件还需要timeout个心跳才需要被处理,因为此处心跳是1ms,所以也就是说还需要timeout个ms才处理。所谓的处理也就是检测timeout是否小于1ms,如果小于1ms, 则发出event_flag这个消息到消息队列,这个消息隶属于task_id这个任务。如果大于1ms,说明该Timer事件还不到处理的时候,则Timeout = Timeout-1,然后继续耐心等待下一次心跳。注:Timer事件链表的维护是通过osal_start_timerEx()这个函数来实现的。

typedef struct
{
void *next;
UINT16 timeout;
UINT16 event_flag;
byte task_id;
} osalTimerRec_t;


 4. 消息发出后的处理

上节讲到在心跳中发出任务的事件消息到消息队列。那么这个消息由谁来处理?回头再看osal_start_system( )中的死循环,有检测消息队列的语句,当发现有消息时,判断该消息隶属于哪个任务就去调用对应于该任务的消息处理函数。各任务的消息处理函数是在tasksArr[]这个常量数组中定义。这个数组中定义的消息处理函数和任务初始化函数中的任务必须一一对应。

 5. 节电模式

细心的同学会发现在死循环体的后面有调用osal_pwrmgr_powerconserve()这样一条语句。从名字及注释来看,属于节电模式的调用。此处不详细列举代码,只讲其工作原理。
上面章节讲到1ms心跳来临时去轮询各事件Timer是否需要处理。这里心跳很快(1ms),各事件的Timeout很慢(往往成百上千)。譬如Key检测的Timer事件的Timeout是100,意思是说100ms才去检测一次是否有Key按下。假如说Key检测的Timout在各Timer事件中Timeout最小,那么也就是说有99次心跳都不会有事件需要处理,但是死循环依然在跑,在做无用功,为了解决这个问题,就加入了节电模式。
在osal_pwrmgr_powerconserve()这个函数中会检测Timer时间链表中Timeout最小的值,假设为next, 然后设定CPU进入休眠模式next个毫秒。休眠时间到了苏醒过来立即就会有Timer事件需要处理,这样就可以达到省电的目的。

 6. 小结

到此为止,OSAL神秘面纱已完全揭开,为了巩固知识,下面以Key为例讲述从Key按下到Key消息被处理的整个过程。
首先在Key的初始化过程中会调用下面这条语句:osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE),这条语句的功能就是将检测Key的这个事件放入Timer事件链表。这个事件隶属于Hal_Task, Timeout是HAL_KEY_POLLING_VALUE。
当1ms心跳来临时,判断timeout是否小于1,如果不小于,则timeout=timeout-1并等待下一次心跳。如果小于1,则发出HAL_KEY_EVENT这个消息到消息队列,然后调用Hal_Task的事件处理函数Hal_ProcessEvent()处理HAL_KEY_EVENT消息,在处理这个消息的过程中调用函数HalKeyPoll()。这个函数检测当前有无按键,如果有按键并且和上次按键值不同则认为有新的按键按下并发出相应的按键消息。

在上面这个过程完成后,必须通过osal_start_timerEx()这个函数将Key检测事件继续放入Timer事件链表,以便后面心跳时能检测到该事件,也就是说每100ms都会扫描看有无按键按下。



OSAL调度机制

摘要:

1、任务调度:osal采用一个链表结构来管理协议栈各层相应任务。相关操作函数有,添加任务到链表中;获取下一个活动任务;根据taskID查找下一个任务。osal采用轮询任务调度队列(任务链表),通过两个函数:调度程序主循环函数和设置事件发生标志函数。

2、时间管理:通过为事件设置超时等待时间,一旦等待时间结束,便为对应任务设置事件发生标志,从而达到对事件进行延时处理目的。

3、原语通信:请求响应原语操作:一旦调用了下层相关函数后,就立即返回。下层处理函数在操作结束后,将结果以消息的形式发送到上层并产生一个系统事件,调度程序发现这个事件后就会调用相应的事件处理函数对它进行处理。两个相关函数:向目标任务发送消息的函数;消息提取函数。

一、操作系统介绍
现有的嵌入式操作系统可以分为两类,即通用的多任务操作系统(General—purpose Multi-tasking OS)和事件驱动的操作系统(Event-driven OS)。前者能够很好地支持多任务或者多线程,但是会随着内部任务切换频率的增加而产生很大的开销,这类操作系统有:uC/OS-II、嵌入式Linux、WinCE等。后者支持数据流的高效并发,并且考虑了系统的低功耗要求,在功耗、运行开销等方面具有优势。典型的代表如TinyOSl291。

目前TinyOS操作系统支持的平台有ATMEL公司的AVR系列、TI公司的MSP430系列。由于TinyOS操作系统还没有对Chipcon公司(才知道TI把它收购了OSAL调度机制 - 小峰 - happy~)提供CC2430开发平台提供支持,因此,要在CC2430开发平台上使用TinyOS系统来开发Zigbee协议栈软件,就必须首先对TinyOS进行移植。灰常麻烦OSAL调度机制 - 小峰 - happy~……

因此Chipcon公司为自己设计的ZStack协议栈中提供了一个名为操作系统抽象层OSAL的协议栈调度程序

//-------------------------------------------------------------------------------------

二、下面分析下这个协议栈调度程序(OSAL)的调度机制。

三部分:1、任务调度

2、时间管理

3、原语通信

(一)任务调度

//每层任务=对应事件处理函数

//任务链表,任务按优先级插入
ZigBee协议栈中的每一层都有很多原语操作要执行,因此对于整个协议栈来说,就会有很多并发操作要执行。协议栈的每一层都设计了一个事件处理函数,用来处理与这一层操作相关的各种事件。这些事件处理函数可以看成是与协议栈每一层相对应的任务,由ZigBee协议栈中调度程序OSAL来进行管理。这样,对于协议栈来说,无论何时发生了何种事件,我们都可以通过调度协议栈相应层的任务即事件处理函数来进行处理。这样,整个协议栈便会按照时间顺序有条不紊的运行。
ZigBee协议栈的实时性要求并不高,因此在设计任务调度程序时,OSAL只采用了轮询任务调度队列的方法来进行任务调度管理。

OSAL采用一个链表结构来管理协议栈各层相应的任务。链表中的每一项是一个结构体,用来记录链表中相关任务的基本信息链表的建立是按照任务优先级从高到低的顺序进行插入的。优先级高的任务将被插入到优先级低的任务前面。如果俩任务优先级相同,则按照时间顺序加入到链表中。那么这个任务链表在系统启动的时候建立,一旦建立后便一直存在于事个系统运行的过程中,直到系统关闭或硬件复位才被销毁。

链表中的每一项数据结构声明:

typedef void (*pTaskInitFn)(unsigned char task_id) ; //指向任务初始化函数
typedef void (*pTaskEventHandlerFn)(usigned char task_id unsigned short event_flag);
 //指向事件处理函数

typedef struct osalTaskRec
{
struct osalTaskRec *next; //指向链表中下一个结构体
pTaskInitFn pfnInit; //指向相关层任务初始化函数
pTaskEventHandlerFn pfnEventProcessor; //指向相关层事件处理函数
byte taskID; //对应当前任务ID
byte taskPriority; //当前任务优先级
uint16 events; //需要被处理的事件,0表示没有要被处理事件
} osalTaskRec_t; 
//链表中的每一项数据结构

//-------------------------------------------------------------------------------------

对于pfnInit,是指向相关层任务初始化函数的指针,比如:

网络层中,任务初始化函数为nwkInit(),用来对网络层相关数据进行初始化操作。函数声明为

extern void nwkInit(byte task_id);

//task_id表示为网络层任务分配的唯一任务号

//-------------------------------------------------------------------------------------
对于 pfnEventProcessor是指向协议栈相关层的事件处理函数指针。比如:

网络层中,事件处理函数nwk_event_loop(),用来处理与网络层相关的各种事件。函数声明为

extern void nwk_event_loop(byte task_id, uint16 event_flag);

//event_flag标志需要在网络层处理的事件

//-------------------------------------------------------------------------------------

上面记录的是链表中的每一项数据结构,与任务链表有关的主要操作有:添加任务到列表中;获取下一个活动任务;根据taskID值查找相应的任务。

(1)在任务管理列表中添加任务

这个函数遍历整个任务队列链表,并按照优先级的高低将优先级高的任务插入到优先级低的任务前面;否则,就将任务插入到链表的尾部。在这个过程中,将为每个任务分配一个唯一的任务号。函数声明为:

Extern osalTaskAdd(pTaskInitFn pfnInit,pTaskEventHandleFn pfnEventProcessor,byte taskPriorty);

(2)获取下一个活动任务

这个函数将根据osalTaskRec_t结构中的events标记来获取任务队列中下一个要执行的任务。函数声明:

Extern osalTaskRec_t *osalNextActiveTask(void) ;

(3)根据taskID查找任务

这个函数将根据任务列表在建立过程中为协议栈中每个任务分配的任务号,来查找对应任务。函数声明:

Extern osalTaskRec_t *osalFindTask(byte taskID);

当任务链表建立成功后,系统便开始运行。如果在系统运行的过程中有事件发生,系统就会通过调用相应的任务,即事件处理函数,对所发生的事件进行相应处理。在整个运行过程中,调度程序(OSAL)始终不停地轮询任务队列链表,以发现需要处理的事件。这个过程涉及两个函数操作:

1、调度程序主循环函数

2、设置事件发生标志函数

(1)系统主循环

这个函数始终不停地轮询队列链表,来处理系统发生的各种事件。函数声明和部分实现如下:

extern void osal_start_system(void);

//无限循环

for(; ; )

{

activeTask=osalNextActiveTask();

if(activeTask)

{

StoreDisableInts;

events=activeTask—>events;

activeTask—>events=0;

if(events!=0)

{

(activeTask—>pfnEventProcessor)(activeTask—>taskID,events);

RestoreInts;

}

}

}

(2)设置事件发生标志

当协议栈中有任何事件发生时,我们可以通过设置osalTaskRec_t结构中的events来标记有事件发生,以便主循环函数能够及时加以处理。函数声明如下:

extern byte osal_set_event(byte task_id,uint16 event_flag);

(二)时间管理

协议栈中的每层都会有很多不同的事件发生,这些事件发生的时间顺序各不相同。很多时候,事件并不要求立即得到处理,而是经过一定的时间后再进行处理。OSAL调度程序设计了与时间管理相关的函数,用来各种不同的要被处理的事件。

对事件进行时间管理,OSAL也采用了链表的方式进行,有时发生一个要被处理的事件,就启动一个逻辑上的定时器,并将此定时器添加到链表当中。利用硬件定时器作为时间操作的基本单元。设置时间操作的最小精度为1ms,每1ms硬件定时器便产生一个时间中断,在时间中断处理程序中去更新定时器链表。每次更新,就将链表中的每一项时间计数减1,如果发现定时器链表中有某一表项时间计数已减到0,则将这个定时器从链表中删除,并设置相应的事件标志。这样任务调度程序便可以根据事件标志进行相应的事件处理。具体参见关于“系统时钟”的记录。

时间管理函数:

extern byte osal_start_timer(byte task_id, uint16 event_id, uint16 timeout_value);

这个函数为事件event_id设置超时等待时间timeout_value。一旦等待结束,便为task_id所对应的任务设置相应的事件发生标记,再对事件进行相应处理。

(三)原语通信

原语只是一个理论层面上的术语,描述了服务层次的关系,以及两个通信的N用户和它们相连的N层(子层)对待协议实体之间的关系。初学时总是想不通原语跟协议栈的代码有什么关系,后来才了解了原语只是规范里面的一个术语,反映到协议栈代码里就是一个个具体的函数了!例如我们可以看到很多原语是以request,confirm等为后缀的,到了程序里面就是相应的request请求函数,confirm确认函数了。)

对请求(request)、响应(response)原语可以直接使用函数调用来实现

对确认(confirm)、指示(indication)原语则需采用间接处理机制来完成

一个原语的操作往往需要逐层调用下层函数并根据下层返回的结果来进行进一步的操作。在这种情况下,一个原主的操作从发起到完成需要很长时间。因此,如果让程序一直等待下层返回的结果再进一步处理,会使微处理器大部分时间处于循环等待之中,无法及时处理其它请求。

因此,与请求、响应原语操作相对应的函数,一旦调用了下层相关函数后,就立即返回。下层处理函数在操作结束后,将结果以消息的形式发送到上层并产生一个系统事件,调度程序发现这个事件后就会调用相应的事件处理函数对它进行处理。(调用就返回,而不管函数有没有处理完成。当函数处理完成后将结果以消息的形式发送到上层产生一个系统事件)。

OSAL调度程序用两个相关的函数来完成这个过程:

1、向目标任务发送消息的函数

这个函数主要用来将原语操作结果以消息的形式往上层任务发送,并产生一个系统事件来通知调度程序。函数声明如下:

extern byte osal_msg_send(byte destination_task,byte *msg_ptr,byte len);

参数destination_task是目标任务的任务号,参数指针msg_ptr指向要被发送的消息,参数len为消息长度

2、消息提取函数

这个消息用来从内存空间中提取相应的消息。其中消息结构和函数声明如下:

typedef struct

{

byte task_id;

byte dst_task_id;

byte send_len;

}osal_msg_rec_header_t;

typedef struct

{

osal_msg_rec_header_t hdr;

byte *msg_ptr;

}osal_msg_received_t; //消息结构(上面结构体包含在里面)

extern osal_msg_received_t osal_rcvd; //全局变量

extern osal_msg_received_t *osal_msg_receive(byte task_id); //接收任务

这个函数返回一个指向osal_msg_received结构的指针,通过msg_ptr这个指针就可以提取出所需要的信息。(有一个例子,是以MAC层返回原语MCPS_DATA.confirm所对应的函数为例,下次再说明)

说明:本文作者所记录,错误处还请高手指点,本人随时更新,转载请注明出处。

本文《OSAL调度机制》主要内容参考厦门大学闫沫的《zigbee协议栈分析与设计》

参考资料:《zigbee技术基础及案例分析》;《zigbee协议栈分析与设计》;




TI Z-stack协议栈开发环境和工作流程


       系统软件设计是在硬件设计的基础上进行的,良好的软件设计是实现系统功能的重要环节,也是提高系统性能的关键所在。节点设计基于通用性及便于开发的考虑,移植了TI公司的Z-Stack协议栈,其主要特点就是其兼容性,完全支持IEEE 802. 15. 4/ZigBee的CC2430片上系统解决方案。Z-Stack还支持丰富的新特性,如无线下载,可通过ZigBee网状网络(Mesh Network)下载节点更新。

Zstack OSAL资料汇编_第1张图片

图 ZigBee节点开发环境

     TI的Z-Stack装载在一个基于IAR开发环境的工程里。强大的IAR Embedded Workbench除了提供编译下载功能外,还可以结合编程器进行单步跟踪调试和监测片上寄存器、Flash数据等。Z-Stack根据IEEE 802. 15.4和ZigBee标准分为以下几层:API(Application Programming Interface),HAL (Hardware Abstract Layer),MAC(Media Access Control), NWK(Zigbee Network Layer),OSAL(Operating System Abstract System),Security,Service,ZDO(Zigbee Device Objects)。使用IAR打开工程文件SampleApp.eww后,即可查看到整个协议栈从HAL层到APP层的文件夹分布。该协议栈可以实现复杂的网络链接,在协调器节点中实现对路由表和绑定表的非易失性存储,因此网络具有一定的记忆功能。

      Z-Stack采用操作系统的思想来构建,采用事件轮循机制,当各层初始化之后,系统进入低功耗模式,当事件发生时,唤醒系统,开始进入中断处理事件,结束后继续进入低功耗模式。如果同时有几个事件发生,判断优先级,逐次处理事件。这种软件构架可以极大地降级系统的功耗。

    整个Z-stack的主要工作流程,大致分为系统启动,驱动初始化,OSAL初始化和启动,进入任务轮循几个阶段,下面将逐一详细分析。

Zstack OSAL资料汇编_第2张图片

图 Z-Stack系统运行流程图

Figure . The Flow Chart of Z-Stack


        系统初始化

系统上电后,通过执行ZMain文件夹中ZMain.c的ZSEG int main( )函数实现硬件的初始化,其中包括关总中断osal_int_disable( INTS_ALL )、初始化板上硬件设置HAL_BOARD_INIT( )、初始化I/O口InitBoard( OB_COLD )、初始化HAL层驱动HalDriverInit( )、初始化非易失性存储器sal_nv_init( NULL )、初始化MAC层ZMacInit( )、分配64位地址zmain_ext_addr( )、初始化操作系统osal_init_system( )等。

硬件初始化需要根据HAL文件夹中的hal_board_cfg.h文件配置寄存器8051的寄存器。TI官方发布Z-stack的配置针对的是TI官方的开发板CC2430DB、CC2430EMK等,如采用其他开发板,则需根据原理图设计改变hal_board_cfg.h文件配置,例如本方案制作的实验板与TI官方的I/O口配置略有不同,其中状态指示LED2的需要重新设置LED2控制引脚口、通用I/O口方向和控制函数定义等。

当顺利完成上述初始化时,执行osal_start_system( )函数开始运行OSAL系统。该任务调度函数按照优先级检测各个任务是否就绪。如果存在就绪的任务则调用tasksArr[ ]中相对应的任务处理函数去处理该事件,直到执行完所有就绪的任务。如果任务列表中没有就绪的任务,则可以使处理器进入睡眠状态实现低功耗。程序流程如图3-13所示。osal_start_system( )一旦执行,则不再返回Main( )函数。

          

  Zstack OSAL资料汇编_第3张图片
                           

OSAL任务调度流程图

Figure . The Flow Chart of OSAL Scheduler


        OSAL任务

OSAL是协议栈的核心,Z-stack的任何一个子系统都作为OSAL的一个任务,因此在开发应用层的时候,必须通过创建OSAL任务来运行应用程序。通过osalInitTasks( )函数创建OSAL任务,其中TaskID为每个任务的唯一标识号。任何OSAL任务必须分为两步:一是进行任务初始化;二是处理任务事件。任务初始化主要步骤如下:

(1)    初始化应用服务变量。

const pTaskEventHandlerFn tasksArr[ ]数组定义系统提供的应用服务和用户服务变量,如MAC层服务macEventLoop、用户服务SampleApp_ProcessEvent等

(2)    分配任务ID和分配堆栈内存

void osalInitTasks( void )主要功能是通过调用osal_mem_alloc( )函数给各个任务分配内存空间,和给各个已定义任务指定唯一的标识号。

(3)    在AF层注册应用对象

    通过填入endPointDesc_t数据格式的EndPoint变量,调用 afRegister( )在AF层注册EndPoint应用对象。

通过在AF层注册应用对象的信息,告知系统afAddrType_t地址类型数据包的路由端点,例如用于发送周期信息的SampleApp_Periodic_DstAddr和发送LED闪烁指令的SampleApp_Flash_DstAddr。

(4)注册相应的OSAL或则HAL系统服务

    在协议栈中,Z-stack提供键盘响应和串口活动响应两种系统服务,但是任何Z-Stask任务均不自行注册系统服务,两者均需要由用户应用程序注册。值得注意的是,有且仅有一个OSAL Task可以注册服务。例如注册键盘活动响应可调用RegisterForKeys( )函数。

(5)处理任务事件

处理任务事件通过创建“ApplicationName”_ProcessEvent( )函数处理。一个OSAL任务除了强制事件(Mandatory Events)之外还可以定义15个事件。

SYS_EVENT_MSG(0x8000)是强制事件。该事件主要用来发送全局的系统信息,包括以下信息:

AF_DATA_CONFIRM_CMD:该信息用来指示通过唤醒AF DataRequest( )函数发送的数据请求信息的情况。ZSuccess确认数据请求成功的发送。如果数据请求是通过AF_ACK_REQUEST置位实现的,那么ZSussess可以确认数据正确的到达目的地。否则,ZSucess仅仅能确认数据成功的传输到了下一个路由。

AF_INCOMING_MSG_CMD:用来指示接收到的AF信息。

KEY_ CHANGE:用来确认按键动作。

ZDO_ NEW_ DSTADDR:用来指示自动匹配请求。

ZDO_STATE_CHANGE:用来指示网络状态的变化。

   网络层信息

Zigbee设备有两种网络地址:1个是64位的IEEE地址,通常也叫作MAC地址或者扩展地址(Extended address),另一个是16位的网络地址,也叫做逻辑地址(Logical address)或者短地址。64位长地址是全球唯一的地址,并且终身分配给设备。这个地址可由制造商设定或者在安装的时候设置,是由IEEE来提供。当设备加入Zigbee网络被分配一个短地址,在其所在的网络中是唯一的。这个地址主要用来在网络中辨识设备,传递信息等。

协调器(Coordinator)首先在某个频段发起一个网络,网络频段的定义放在DEFAULT_ CHANLIST配置文件里。如果ZDAPP_ CONFIG_ PANID定义的PAN ID是0xFFFF(代表所有的PAN ID),则协调器根据它的IEEE地址随机确定一个PAN ID。否则,根据ZDAPP_ CONFIG_ PANID的定义建立PAN ID。当节点为Router或者End Device时,设备将会试图加入DEFAULT_ CHANLIST所指定的工作频段。如果ZDAPP_ CONFIG_ PANID没有设为0xFFFF,则Router或者End Device会加入ZDAPP_ CONFIG_ PANID所定义的PAN ID。

设备上电之后会自动的形成或加入网络,如果想设备上电之后不马上加入网络或者在加入网络之前先处理其他事件,可以通过定义HOLD_AUTO_START来实现。通过调用ZDApp_StartUpFromApp( )来手动定义多久时间之后开始加入网络。

设备如果成功的加入网络,会将网络信息存储在非易失性存储器(NV Flash)里,掉电后仍然保存,这样当再次上电后,设备会自动读取网络信息,这样设备对网络就有一定的记忆功能。对NV Flash的动作,通过NV_RESTORE( )和NV_ITNT( )函数来执行。

有关网络参数的设置大多保存在协议栈Tools文件夹的f8wConfig.cfg里。

        路由

Z-Stack采用无线自组网按需平面距离矢量路由协议AODV,建立一个Hoc网络,支持移动节点,链接失败和数据丢失,能够自组织和自修复。当一个Router接受到一个信息包之后,NMK层将会进行以下的工作:首先确认目的地,如果目的地就是这个Router的邻居,信息包将会直接传输给目的设备;否则,Router将会确认和目的地址相应的路由表条目,如果对于目的地址能找到有效的路由表条目,信息包将会被传递到该条目中所存储的下一个hop地址;如果找不到有效的路由表条目,路由探测功能将会被启动,信息包将会被缓存直到发现一个新的路由信息。

ZigBee End Device不会执行任何路由函数,它只是简单的将信息传送给前面的可以执行路由功能的父设备。因此,如果End Device想发送信息给另外一个End Device,在发送信息之间将会启动路由探测功能,找到相应的父路由节点。

转自:http://blog.ednchina.com/Jam/247711/message.aspx

PS:百度博客发文章有字数限制,太烦人了,一篇文章分成好几段,看起来也很不爽。终于找到解决方法了:在记事本里把文字写好再复制过来,图表单独插入,即可解决问题。

Z-stack1.4.3及以后的版本里包含了一个操作系统,名义上为实时操作系统,可是在应用上,它的实时性能并不理想。至少不少人这样认为,我也是,呵。。。先说下几个概念。

任务:协议栈是多任务,但它们不是同时执行的,是准并行的,轮流使用CPU,就像很多人一起读一本书,每个人只能拿到书时才可以读。同进,在轮流过程中处理器要为每个任务维护其状态信息,也就是任务的场景,就跟书签一样,每个读者都有必要有一根自己的书签(taskID),以便知道自己读到上回读到哪了,也就是读者上下文环境信息。

void osalInitTasks( void )

{

  uint8 taskID = 0;

 

  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);

  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));

 

  macTaskInit( taskID++ );

  nwk_init( taskID++ );

  Hal_Init( taskID++ );

#if defined( MT_TASK )

  MT_TaskInit( taskID++ );

#endif

  APS_Init( taskID++ );

  ZDApp_Init( taskID++ );

  OuhsApp_Init( taskID );

}

任务调度机制osal_start_system()将查询所有的任务事件,并使每个任务事件调用对应的事件处理函数。如果某个特定的任务有事件发生,那么该函数就将调用该任务事件的处理函数,当做完相应的处理后,再次返回到主循环函数,继续查找其它任务事件。如果所有的任务都没有事件发生,那么该函数将使处理器进入睡眠模式。该函数定义在osal.c

void osal_start_system( void )

{

#if !defined ( ZBIT )

  for(;;)     //主大循环

#endif

  {

    uint8 idx = 0;

 

    Hal_ProcessPoll();  // uarttimer

 

    do {

      if (tasksEvents[idx])  // 当有任务事件发生时,跳出任务查询

      {

        break;

      }

    } while (++idx < tasksCnt);

 

    if (idx < tasksCnt)

    {

      uint16 events;

      halIntState_t intState;

 

      HAL_ENTER_CRITICAL_SECTION(intState); //临界区,先保存EA然后禁止所有中断

      events = tasksEvents[idx];         

      tasksEvents[idx] = 0;                 // 清除该任务的事件

      HAL_EXIT_CRITICAL_SECTION(intState);  //临界区,恢复前面保存EA状态

 

      events = (tasksArr[idx])( idx, events ); //调用处理函数

 

          HAL_ENTER_CRITICAL_SECTION(intState);

      tasksEvents[idx] |= events;  // 返回没有处理的事件到当前任务中

          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

    }

}

这里的任务事件处理的函数的顺序必须要和osalInitTasks里的顺序一致。

const pTaskEventHandlerFn tasksArr[] = {

  macEventLoop,

  nwk_event_loop,

  Hal_ProcessEvent,

#if defined( MT_TASK )

  MT_ProcessEvent,

#endif

  APS_event_loop,

  ZDApp_event_loop,

  OuhsApp_ProcessEvent

};

 

调度程序:调度程序是操作系统的核心和灵魂,它决定在给定的时间内哪一个任务有权使用处理器。普通的调度算法:先进先出、短任务优先、循环法。这些是不太适合嵌入式的简单的调度算法。

先进先出(FIFO):每一个任务一直运行到它结束,至结束时下一个任务才被启动。非多任务操作系统。

短任务优先:每一次运行的任务在完成或挂起的时候,下一个被选择的任务是需要最小处理器完成时间的任务。

循环法:运行中的任务可以被占先,也就是在运行过程中可以被中断。在这种情况下,每个任务都运行一个预先决定的时间,那个时间间隔过后,运行的程度被操作系统占先,然后下一个任务有机会运行,被占先的任务直到其它所有的任务都有机会运行一轮之后再次开始运行。

 

但这些简单的调度算法不太适合嵌入式系统,大部分嵌入式操作系统使用一个支持占先的基于优先级的的调度算法,也就是在任何一个给定的时刻,正在使用处理器的任务保证就是就绪任务中优先级最高的,低优先级的任务必须等待高优先级的任务运行完成才能使用处理器。“占先”就是如果一个高优先级的任务就绪之后,任何任务都能被操作系统中断。

 

z-stack1.4.3以前不含操作系统,z-stack1.4.3的操作系统,有定义了任务优先级(共三个),可是1.4.3-1.2.1里没有再定义优先级了,它使用基于数组索引的序号顺序调度,所以并不是真正意义上的实时系统,它使用了一个定时器来完成调度。它的调度方式决定了用户的应用并不实时。

 

任务同步:把一个任务当做完全独立的实体来讨论是不完全准确的,必须偶尔和其它的任务通信使得它们的活动同步。实现任务同步的机制有:互斥体、信号灯、消息队列以及监视器。互斥体是为了保护共享资源(全局变量、存储缓存、被多任务存取的设备寄存器)。Z-stack的消息队列遵循FIFO原则。

 

参考书目:C/C++嵌入式系统编程


你可能感兴趣的:(timer,网络,任务调度,byte,任务,events)