ZigBee协议栈之osal浅析
ZigBee是目前比较流行的一种低功耗无线组网技术,主要用于智能家居控制以及智能工业生产。ZigBee大的特点就是低功耗、自组网。
说到ZigBee就不得不提IEEE802.15和ZigBee联盟,他们公共制定了ZigBee协议栈的标准。组网过程就是基于ZigBee协议栈,协议栈完成了绝大部分的工作,留给没У木褪怯τ贸绦蚪涌凇P檎痪拖褚桓霾僮飨低骋谎没е恍枰ㄖ朴τ贸绦蚓涂梢允褂谩�
先来看一下ZigBee协议栈架构,操作系统下面的可以当做BootLoader,操作系统上面的可以看做应用程序。对于用户来说,只要了解操作系统,会定制task,那么就可以使用协议栈了。
接下来我们以TI公司的ZigBee协议栈为标准,了解一下osal操作系统机制,以方便后续定制task。
Osal源于一种简单的操作系统思想---轮询。在ZigBee协议栈中,OSAL负责调度各个任务的运行,如果有事件发生了,则会调用相应的事件处理函数进行处理。那么,事件和任务的事件处理函数是如何联系起来的呢?
ZigBee中采用的方法是:建立一个事件表,保存各个任务的对应的事件,建立另一个函数表,保存各个任务的事件处理函数的地址,然后将这两张表建立某种对应关系,当某一事件发生时则查找函数表找到对应的事件处理函数即可。
在ZigBee协议栈中,有三个变量至关重要。
● tasksCnt—该变量保存了任务的总个数。
该变量的声明为:uint8 tasksCnt,其中uint8的定义为:typedef unsigned char uint8
● tasksEvents—这是一个指针,指向了事件表的首地址。
该变量的声明为:uint16 *tasksEvents,其中uint16的定义为:typedef unsigned short uint16
● tasksArr—这是一个数组,数组的每一项都是一个函数指针,指向了事件处理函数。
该数组的声明为:const pTaskEventHandlerFn tasksArr[],其中pTaskEventHandlerFn的定义为:typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event ),这是定义了一个函数指针。tasksArr数组的每一项都是一个函数指针,指向了事件处理函数。
事件表和函数表的关系如下图
我们现在来总结下OSAL的工作原理:通过tasksEvents指针访问事件表的每一项,如果有事件发生,则查找函数表找到事件处理函数进行处理,处理完后,继续访问事件表,查看是否有事件发生,无限循环。OSAL就是一种基于事件驱动的轮询式操作系统。事件驱动是指发生事件后采取相应的事件处理方法,轮询指的是不断地查看是否有事件发生。
下面从代码中看一下osal运行机制。在Zmain文件夹下有个Zmain.c文件,打开该文件可以
找到main()函数,这就是整个协议栈的入口点。main()函数原型如下:
int main( void )
{
// Turn off interrupts
osal_int_disable( INTS_ALL );
// Initialization for board related stuff such as LEDs
HAL_BOARD_INIT();
// Make sure supply voltage is high enough to run
zmain_vdd_check();
// Initialize board I/O
InitBoard( OB_COLD );
// Initialze HAL drivers
HalDriverInit();
// Initialize NV System
osal_nv_init( NULL );
// Initialize the MAC
ZMacInit();
// Determine the extended address
zmain_ext_addr();
// Initialize basic NV items
zgInit();
#ifndef NONWK
// Since the AF isn't a task, call it's initialization routine
afInit();
#endif
// Initialize the operating system
osal_init_system();
// Allow interrupts
osal_int_enable( INTS_ALL );
// Final board initialization
InitBoard( OB_READY );
// Display information about this device
zmain_dev_info();
/* Display the device info on the LCD */
#ifdef LCD_SUPPORTED
zmain_lcd_init();
#endif
#ifdef WDT_IN_PM1
/* If WDT is used, this is a good place to enable it. */
WatchDogEnable( WDTIMX );
#endif
osal_start_system(); // No Return from here
return 0; // Shouldn't get here.
} // main()
在osal_start_system()函数之前的函数都是对板载硬件以及协议栈进行的初始化,直到调用osal_start_system()函数,整个ZigBee协议栈才算是真正地运行起来了。硬件驱动不需要多看,我们跳转到osal_start_system(),查看一下它的原型
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
uint8 idx = 0;
osalTimeUpdate();
// This replaces MT_SerialPoll() and osal_check_timer().
Hal_ProcessPoll();
do {
// Task is highest priority that is ready.
if (tasksEvents[idx])
{
break;
}
} while (++idx < tasksCnt);
if (idx < tasksCnt)
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);
events = tasksEvents[idx];
// Clear the Events for this task.
tasksEvents[idx] = 0;
HAL_EXIT_CRITICAL_SECTION(intState);
events = (tasksArr[idx])( idx, events );
HAL_ENTER_CRITICAL_SECTION(intState);
// Add back unprocessed events to the current task.
tasksEvents[idx] |= events;
HAL_EXIT_CRITICAL_SECTION(intState);
}
#if defined( POWER_SAVING )
// Complete pass through all task events with no activity?
else
{
// Put the processor/system into sleep
osal_pwrmgr_powerconserve();
}
#endif
}
}
首先看到,真个osal是在一个for循环中执行的,这就意味正后面的事件一直在不停的重复,也是osal的基础。
第6行,定义了一个变量idx,用来在事件表中索引。
第7-9行,更新系统时钟,同时查看硬件方面是否有事件发生,如串口是否收到数据、是否有按键按下等信息,这部分内容在此可以暂时不予考虑。
第10-16行,使用do-while循环查看事件表是否有事件发生。分析一下这个循环,如果有事件发生,那么就跳出循环,去后面的代码处理事件。如果没有事件,那么久继续扫描表中的下一项。当然真个循环的次数不得多于tasksCnt,因为它记录着事件的总数。
第18~35行是事件的处理过程,23和27行规定了一个临界区。24行取出事件,保存在events变量。26行则将表中的事件清零,因为事件已经被取出将要处理。27行根据id找到对应的函数表,执行处理函数,而且拿到返回值(后面解释返回值)。31和34行又是一个临界区,33行又将事件重新赋值。
上面了解了osal的基本原理,就是两张表的查询和对应。那么其中又有一个events成了重点,为什么处理完事件时候又返回一个events(24行),而且还把events放回到事件表(33行)。
ZigBee协议栈使用一个unsigned short型的变量,因为unsigned short类型占两个字节,即16个二进制位,因此,可以使用每个二进制位表示一个事件,我们来看下协议栈定义的系统事件SYS_EVENT_MSG,十六进制:0x8000,二进制:0b1000000000000000。它用的就是高位来表示该事件:
可以看出在一个任务中多只能有16个事件,因为events是一个16位数据。
在系统初始化时,所有任务的事件初始化为0,因此,第10行通过tasksEvents[idx]是否为0来判断是否有事件发生,如果有事件发生了,则跳出循环。
29行执行完事件处理函数后,需要将未处理的事件返回,也就是说事件处理函数的返回值保存了未处理的事件,将该事件在写入事件表中,以便于下次进行处理。看一下下面的处理函数
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{
// Send the periodic message
SampleApp_SendPeriodicMessage();
// Setup to send message again in normal period (+ a little jitter)
osal_start_timerEx( SampleApp_TaskID, SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
(SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );
// return unprocessed events
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
// Discard unknown events
return 0;
}
前面已经说到,事件用一个二进制位1来表示,那么一个“与”操作就可以判断出来到底有没有事件。后return采用“异或”,这样可以将已经处理的事件清除掉。不清楚的小伙伴可以举一个实际的例子,仔细计算一番之后你会发现的确是这种写法。。。
在ZigBee协议栈中,用户可以定义自己的事件,但是,协议栈同时也给出了几个已经定义好的事件,由协议栈定义的事件成为系统强制事件(Mandatory Events),SYS_EVENT_MSG就是其中的一个事件,SYS_EVENT_MSG的定义如下:
那SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件 是不是就是用户自己定义的事件了?是的!我们来看下它的定义:
提到事件,我们就不得不提到消息。事件是驱动任务去执行某些操作的条件,当系统中产生了一个事件,OSAL将这个事件传递给相应的任务后,任务才能执行一个相应的操作(调用事件处理函数去处理)。
通常某些事件发生时,又伴随着一些附加信息的产生,例如:从天线接收到数据后,会产生AF_INCOMING_MSG_CMD消息,但是任务的事件处理函数在处理这个事件的时候,还需要得到收到的数据。
因此,这就需要将事件和数据封装成一个消息,将消息发送到消息队列,然后在事件处理函数中就可以使用osal_msg_receive,从消息队列中得到该消息。如下代码可以获得指向从消息队列中得到消息的指针。
在使用ZigBee协议栈进行应用程序开发时,如何在应用程序中添加一个新任务呢?
打开OSAL_SampleApp.c文件,可以找到数组tasksArr[]和函数osalInitTasks()。tasksArr[]数组里存放了所有的事件处理函数的地址;osalInitTasks()是OSAL的任务初始化函数,所有任务的初始化工作都在这里边完成,并且自动给每个任务分配一个ID。
因此,要添加新任务,只需要编写两个函数:
● 新任务的初始化函数。
● 新任务的事件处理函数。
将事件处理函数的地址加入tasksArr[]数组,如下代码所示。
将新任务的初始化函数添加在osalInitTasks()函数的后,如下代码所示。
我们需要注意的是:
tasksArr[]数组里各事件处理函数的排列顺序要与osalInitTasks函数中调用各任务初始化函数的顺序保持一致,只有这样才能保证当任务有事件发生时会调用每个任务对应的事件处理函数。为了保存osalInitTasks()函数所分配的任务ID,需要给每一个任务定义一个全局变量。如在SampleApp.c文件中定义了一个全局变量SampleApp_TaskID,并且在osalInitTasks()函数中进行了赋值。
我们现在总结下OSAL的运行机理:
● 通过不断地查询事件表来判断每个任务中是否有事件发生,如果有事件发生,则查找函数表找到对应的事件处理函数对事件进行处理。
● 事件表使用数组来实现,数组的每一项对应一个任务的事件,每一位表示一个事件;函数表使用函数指针数组来实现,数组的每一项是一个函数指针,指向了事件处理函数。
ZigBee协议栈之osal浅析相关推荐
- Zigbee协议栈中OSAL的运行机理
OSAL的运行机理 事件表 函数表 使用查表法来取得事件所对应函数 taskCnt 任务总数 taskEvents 指向事件表首地址的指针 taskArr 事件处理函数数组,每一项都是一个函数指针 ...
- zigbee协议栈OSAL分析
本文从源程序出发,分享本人学习zigbee协议栈的一些理解,介绍zigbee协议栈OSAL任务调度及用户自定义任务的调度处理过程.为了便于抓住本质,理清思路,本文剔除一些无关部分. 程序的入口是ZMa ...
- Zigbee协议栈ZStack构架
协议栈版本信息: ZigBee2006\ZStack-1.4.3-1.2.1 1.ZStack协议栈构架 Zigbee协议栈就是将各个层定义的协议都集合在一起,以函数的形式实现,并给用户提供一些 ...
- ZigBee协议栈Zstack介绍
文中所讲述的协议栈是基于ZigBee2006\Zstack-1.4.3-1.2.1 1.ZSTACK协议栈的架构 ZigBee协议栈就是将各个层定义的协议都集合在一起,以函数的形式实现,并给用户提供一 ...
- ZIGBEE通讯-10.ZigBee协议栈的无线点灯
在ZIGBEE协议栈中已经自带了按键与LED的驱动与使用函数,所以只需要将按键与LED修改为使用的开发板所连接IO就可以使用了.接下来将主要分析在协议栈中按键的初始化.按键的检测以及按键事件的传递与处 ...
- CC2530 ZigBee协议栈 学习心得
最近一直在学习研究cc2530这款单片机,感觉自己的C语言水平还是不够有得提升的空间,但还是有不少收获. CC2530是一款支持ZigBee无线组网协议的低功耗单片机,cc2530主要的应用场景 ...
- zigbee协议栈学习(二)
协议栈规范的 ID号可以通过查询设备发送的 beacon 帧获得.在设备加入网络之前,首先 需要确认协议栈规范的 ID."特定网络"规范 ID号为0: ZigBee协议栈规范的 I ...
- ZigBee协议栈简介和流程
ZigBee协议栈实际上就是ZigBee协议的API接口 一般步骤为: 1.组网:调用协议栈的组网函数.加入网络函数,实现网络的建立与节点的加入 2.发送:发送节点调用协议栈的无线数据发送函数,实现无 ...
- ZIGBEE通讯-7.ZigBee协议栈简介
想要学习协议栈,必须先知道协议是什么.协议定义的是一系列的通信标准,通信双方需要共同按照这一标准进行正常的数据收发,而协议栈是协议的具体实现形式,通俗的理解为用代码实现的函数库,以便于开发人员调用. ...
最新文章
- Windows程序的基本结构(转)
- 同盾科技完成 7280 万美元 C 轮融资
- Java 集合框架综述
- VS2017-VC++校验和计算小工具
- 当查找名字的时候通过外围作用域向外查找(如何理解)
- 如何用Pygame写游戏(六)
- 关于JS闭包,作者不详(转)
- Linux学习之dpkg错误:另外一个进程已为dpkg状态库加锁
- 降低AI开发门槛,飞桨重启“软件定义硬件”浪潮
- python断言语句_Python断言assert的用法代码解析
- 服务器系统详细安装步骤
- gis 六边形网格_ArcGIS中的奇技淫巧(Ⅱ)—蜂巢网格图
- php注入过程详解,PHP注入代码详解
- 时间流逝,岁月里所有的狼狈
- yac163: linux 下的163相册辅助工具
- tf.nn.moments( )函数的使用
- [微积分笔记]第二类曲线/面积分总结
- 聊聊小程序的登录逻辑
- 华为Mate30 4G电路原理图
- 京东热 Key 0.4 发布,单机 QPS 提升至 35 万
热门文章
- 计算机管理无法卸载大容量u盘,u盘的文件删不掉,教您解决删不掉
- 怎样修复计算机系统声音,电脑没声音是怎么回事?小编教你怎么修复
- 生产制造业ERP管理系统
- 西门子编程基础学习分享(4)-位逻辑指令
- 工业革命中的产业规律:原有的产业+蒸汽机=新的产业【所有技术革命的本质规律】
- 03.06 随手记(AMD、CMD、CommonJS、ES6 Module的区别)
- [django框架2]ORM进阶知识
- 美化人像修片—去除皱纹的方法
- 使用单片机非AD方式实现温度测测量
- SQLServer将多行数据合并成一行多列