Z-Stack操作系统
学习ZigBee有两个月了,学习期间也走了不少弯路,刚开始调试了几个LED小灯的实验,串口通信实验,点对点通信实验,以为这样基本就差不多可以做定位系统了,于是直接跳到定位这一块,实际上远没有那么简单。我想很多初学者都是打开协议栈就被左边那一列给镇住了,看到那么多代码都不知道从何入手,下面我重点介绍一下关于ZigBee协议栈操作系统的原理,让更多初学者能看清Z-Stack操作系统(其实只是一个简单的小系统,看明白了觉得也没有多复杂)的工作过程。
下面的图片是我根据书上的资料自己绘制出来的,从这个图中可以看到Z-Stack的整个系统的工作流程。
[attachimg]2938
Z-Stack操作系统
似乎这个对于初学者来说也看不出什么道道来,我在看代码的时候完全不知道从哪里看起。即使找到了主函数,找到了OSAL_start_system()也不知道他到底是怎么运行的。首先我们先好好看一些这个微操作系统的工作流程:在ZMain主函数当中,基本上都是一些初始化函数,从中断、系统时钟、堆栈等等到最后按键、液晶显示初始化,这个对于我们来说基本上不用看,只需大致了解其功能即可,就像在电脑上编程只需了解软件和底层硬件的接口一样。在初始化函数执行完以后便进入了osal_start_system()函数,开始OSAL操作系统。OSAL操作系统里面有七个任务,该循环轮询查询每个任务是否有需要处理的事件,如果有则处理,没有则跳到下一个任务,这七个任务有不同的优先级,从图中可以看出MAC层拥有最高的优先级,MAC层如果有任务,则下面的任务不会被处理。
好了,基本上对于该操作系统有了一个简单的了解以后,我们再来结合代码看看代码到底是怎么运行的。
先大致浏览一下协议栈的目录,可以看到有ZMain文件夹(如下乳所示),这个应该就是主函数,看了很多资料确定了确实是从这里开始运行的。
打开ZMain.c,找到ZSEG int main( void )函数,函数内容如下:
ZSEG int main( void )
{
// Turn off interrupts
osal_int_disable( INTS_ALL );
// Initialize HAL
HAL_BOARD_INIT();
// Make sure supply voltage is high enough to run
zmain_vdd_check();
// Initialize stack memory
zmain_ram_init();
// Initialize board I/O
InitBoard( OB_COLD );
// Initialze HAL drivers
HalDriverInit();
// Initialize NV System
osal_nv_init( NULL );
// Determine the extended address
zmain_ext_addr();
// Initialize basic NV items
zgInit();
// Initialize the MAC
ZMacInit();
#ifndef NONWK
// Since the AF isn't a task, call it's initialization routine
afInit();
#endif
#ifdef LCD_SUPPORTED
HalLcdInit();
#endif
// Initialize the operating system
osal_init_system();
// Allow interrupts
osal_int_enable( INTS_ALL );
// Final board initialization
InitBoard( OB_READY );
//HalLcdInit();
// Display information about this device
zmain_dev_info();
/* Display the device info on the LCD */
#ifdef LCD_SUPPORTED
zmain_lcd_init();
#endif
osal_start_system(); // No Return from here
} // main()
大致看一下,和上述图中的初始化基本上一样。功能描述都换做了绿色字体以便查看,这些英文不是很难,大都可以看懂什么意思我就不再标注了,实在不懂可以看上边图片,有具体解释。
函数最后是 osal_start_system(); // No Return from here开始OSAL操作系统,并且标注了这里没有返回,也就是说在OSAL操作系统里面会一直永无止境的执行下去知道系统停止工作。那么osal_start_system()这个函数在哪呢?一般习惯好的话看程序就知道什么意思,更何况是那么大的一个公司,在函数前面有一个osal,我们就顺着osal找下去。查看协议栈会发现有一个OSAL文件夹,那么函数很有可能就在这里面。如下图:
在OSAL里面仔细查找可以找到这个函数,具体如下:
void osal_start_system( void )
{
#if !defined ( ZBIT )
for(;;) // Forever Loop
#endif
{
uint8 idx = 0;
Hal_ProcessPoll(); // This replaces MT_SerialPoll() and osal_check_timer().
do {
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break;
}
} while (++idx < tasksCnt);
//得到了待处理的具有最高优先级的任务索引号idx
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);
events = (tasksArr[idx])( idx, events ); //通过指针调用来执行对应的任务处理函数
//进入/退出临界区,保存尚未处理的事件
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
}
}
操作系统专门分配了存放所有任务事件的tasksEvents[]这样一个数组,每一个单元对应存放着每一个任务的所有事件。在这个函数中首先通过do-while来找出这个任务事件数组当中具有优先级最高的事件,得到该事件的idx,通过events = (tasksArr[idx])( idx, events );这一函数来得到事件的指针地址,最后对事件进行处理。那么这样来说我们只需要知道这个tasksEvents里面任务都是什么那么这个操作系统就相当明了了。
一般在协议栈当中都会有例子程序,我们打开其中一个GenericApp,路径如图所示。
接下来打开OSAL_GenericApp.c,该文件是应用层和OSAL操作系统互动的一个文件,这里面没有多少代码,轻易地就可以发现其中的const pTaskEventHandlerFn tasksArr[],具体如下:
const pTaskEventHandlerFn tasksArr[] = {
macEventLoop, //MAC层任务处理函数
nwk_event_loop, //网络层任务处理函数
Hal_ProcessEvent, //板硬件抽象层任务处理函数
#if defined( MT_TASK ) //如果定义了MT_TASK则扫描该处理函数
MT_ProcessEvent, //调试任务处理函数,可选
#endif
APS_event_loop, //应用层任务处理函数,用户不要更改
ZDApp_event_loop, //ZigBee设备应用层任务处理函数,用户可根据需要更改
GenericApp_ProcessEvent //用户应用层任务处理函数,用户自己生成
};
(注:有些初学者不明白#if defined( MT_TASK )--- #endif是什么意思,在此说明一下,这就是一个判断语句,如果定义了MA_TASK则执行该语句,在哪里定义呢,打开Project---Option,如下图所示,如果在Defined symbols这个框中定义了MT_TASK 则执行。其他的同)
结合刚开始的架构图,可以看出操作系统循环扫描tasksArr[]中的七个任务,有任务则处理,而我们需要自己设定的就是GenericApp_ProcessEvent这个函数。
说明一点,Z-Stack操作系统就是这个构架,基本上就是这个模式,代码不会有太大出入,不信可以多找几个例子按照上述顺序看下来,基本都一样。
这下就知道了我们到底要在什么地方写程序了,打开GenericApp.c找到GenericApp_ProcessEvent这个函数,我们来看看里面到底有什么。
其中的代码我先不贴上来,先说一下GenericApp_ProcessEvent这个函数的整体框架,了解了这个以后再看这个函数就非常容易了。
UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events )
{
………………
if ( events & SYS_EVENT_MSG ) //系统消息事件
{
………………
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG: //ZDO的反馈信息
………………
case KEY_CHANGE: //按键事件
………………
case AF_DATA_CONFIRM_CMD: //AF数据确认
………………
case AF_INCOMING_MSG_CMD: //AF信息输入
………………
case ZDO_STATE_CHANGE: //ZDO状态改变
………………
case ZDO_MATCH_DESC_RSP_SENT: //ADO匹配描述符相应
………………
case SAPICB_DATA_CNF: //发送数据确认
………………
default:
………………
}
………………
}
return (events ^ SYS_EVENT_MSG); //返回未处理的事件
}
if ( events & ZB_ALLOW_BIND_TIMER ) //允许绑定时间事件
………………
if ( events & ZB_BIND_TIMER ) //绑定时间事件
………………
if ( events & ZB_ENTRY_EVENT )
………………
if ( events & GENERICAPP_SEND_MSG_EVT ) //用户自定义事件
{
………………
}
………………
return 0;
}
这个函数的意思非常明了,如果有按键,则执行case KEY_CHANGE:下边的代码,如果有消息接收,则执行case AF_INCOMING_MSG_CMD:,其他的都是这样,因为这里是将操作系统构架,所以不再详细讲解过多代码的作用(其实这么多代码我也不是很懂,大致知道常用的就行,需要用到那个在具体查阅),如果用户自定义事件有消息产生,则执行规定的事件。
基本上Z-stack操作系统就是这个构架,看明白了其实觉得这个操作系统其实也挺微小的,和Windows比起来确实没法比,不过这对于初学者足够头痛了。GenericApp_ProcessEvent里面的代码我就不具体分析了,整个操作系统就是这样一个模式,没什么出入,看不明白多看几遍,我当初就看了十几遍才稍微了解了点,对于讲解中有什么错误大家多多指点。
|