ZigBee CC2530学习(二)认识协议栈

文章目录

  • 1.何为协议栈?
  • 2.使用协议栈
  • 3.协议栈的启动过程

1.何为协议栈?

我的理解是它是协议的实现,类似一种库函数,供上层来调用。ZigBee协议栈将各个层的协议集合在一起,并以函数的形式实现,并且向用户提供接口,用户能够直接调用。

2.使用协议栈

我认为大致分为以下三步:

  1. 开始组网,通过调用协议栈的组网函数等来实现网络的建立以及其他节点的加入网络;
  2. 发送数据,节点通过调用协议栈的消息发送函数来实现数据的无线发送;
  3. 接收数据,节点调用协议栈的消息接收函数来实现无线数据接收功能。

3.协议栈的启动过程

我想从一个工程,从代码层面来论述会好点。
在这里插入图片描述

以下内容出现大量代码,可能会引起不适,无耐心者劝退,感觉我也看不下去了,😂

我们从程序的入口main()函数开始分析;

int main( void )
{// Turn off interruptsosal_int_disable( INTS_ALL );			//关闭中断// Initialization for board related stuff such as LEDsHAL_BOARD_INIT();						//初始化开发板板载设备// Make sure supply voltage is high enough to runzmain_vdd_check();					//检测电压是否正常// Initialize board I/OInitBoard( OB_COLD );					//板载I/O口初始化// Initialze HAL driversHalDriverInit();						//HAL层驱动初始化// Initialize NV Systemosal_nv_init( NULL );					//NV初始化// Initialize the MACZMacInit();							//MAC层初始化// Determine the extended address///检测设备是否有扩展地址,如果没有,就临时分配一个zmain_ext_addr();			// Initialize basic NV itemszgInit();							//初始化基本的NV条目#ifndef NONWK// Since the AF isn't a task, call it's initialization routineafInit();
#endif// Initialize the operating systemosal_init_system();			//初始化OSAL操作系统// Allow interruptsosal_int_enable( INTS_ALL );	//开启中断// Final board initializationInitBoard( OB_READY );		//开发板初始化// Display information about this devicezmain_dev_info();				//显示设备信息/* Display the device info on the LCD */
#ifdef LCD_SUPPORTEDzmain_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 herereturn 0;  // Shouldn't get here.
} // main()
  1. main()函数主要做了以下几件事情:
  • 第一件事,系统的初始化,对HAL,MAC等各层以及操作系统用到的信息如内存等进行了初始化;
  • 第二件事,初始化OSAL操作系统函数osal_init_system(),其中,需要重点关注的是初始化系统任务函数osalInitTask();
void osalInitTasks( void )
{uint8 taskID = 0;//为长度为tasksCnt的数组分配内存空间,taskCnt是任务的个数tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);//将tasksEvents的值初始化为0osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));macTaskInit( taskID++ );nwk_init( taskID++ );Hal_Init( taskID++ );
#if defined( MT_TASK )MT_TaskInit( taskID++ );
#endifAPS_Init( taskID++ );
#if defined ( ZIGBEE_FRAGMENTATION )APSF_Init( taskID++ );
#endifZDApp_Init( taskID++ );
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )ZDNwkMgr_Init( taskID++ );
#endifGenericApp_Init( taskID );
}

此函数为系统各个任务分配了存储空间,并且分配了taskID,ID要与每一层事件处理函数一一对应。taskID初始值为0,每一层初始化后其数值加1,并且在每一层初始化中记录分配给它的taskID值。

我们需要知道其中一些重要的变量:

  • tasksCnt,表示任务总个数;
  • tasksEvent,指向事件表的首地址;
  • tasksArr[],其中每一项都是一个函数指针,指向事件处理函数

事件表和函数表是一一对应的。

可以看一下tasksArr的定义:

const pTaskEventHandlerFn tasksArr[] = {macEventLoop,nwk_event_loop,Hal_ProcessEvent,
#if defined( MT_TASK )MT_ProcessEvent,
#endifAPS_event_loop,
#if defined ( ZIGBEE_FRAGMENTATION )APSF_ProcessEvent,
#endifZDApp_event_loop,
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )ZDNwkMgr_event_loop,
#endifSampleApp_ProcessEvent
};

这个数组存放了所有的任务事件处理函数的地址,如果我们要添加新任务,我们的新任务的初始化函数要放在osalInitTasks中为其分配任务ID,还需要在此数组中添加其任务事件处理函数的地址。

那现在我们来看一下此工程的应用层初始化代码,具体分析我写在了代码注释里。

void SampleApp_Init( uint8 task_id )      //应用层初始化
{SampleApp_TaskID = task_id;             //分发任务IDSampleApp_NwkState = DEV_INIT;          //设定设备网络状态为“初始化”SampleApp_TransID = 0;// Device hardware initialization can be added here or in main() (Zmain.c).// If the hardware is application specific - add it here.// If the hardware is other parts of the device add it in main().#if defined ( BUILD_ALL_DEVICES )// The "Demo" target is setup to have BUILD_ALL_DEVICES and HOLD_AUTO_START// We are looking at a jumper (defined in SampleAppHw.c) to be jumpered// together - if they are - we will start up a coordinator. Otherwise,// the device will start as a router.if ( readCoordinatorJumper() )zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;elsezgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;
#endif // BUILD_ALL_DEVICES#if defined ( HOLD_AUTO_START )// HOLD_AUTO_START is a compile option that will surpress ZDApp//  from starting the device and wait for the application to//  start the device.ZDOInitDevice(0);
#endif//设定周期信息的地址,此地址为广播地址0xFFFF// Setup for the periodic message's destination address// Broadcast to everyoneSampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//设定闪烁信息地址,此地址为组1的地址// Setup for the flash command's destination address - Group 1SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup;     // 1SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;            // 20SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;   // 0x0001//对端点SAMPLEAPP_ENDPOINT进行描述// Fill out the endpoint description.SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;SampleApp_epDesc.task_id = &SampleApp_TaskID;SampleApp_epDesc.simpleDesc= (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;SampleApp_epDesc.latencyReq = noLatencyReqs;//注册端点描述符// Register the endpoint description with the AFafRegister( &SampleApp_epDesc );//注册按键,按键事件由应用层进行处理// Register for all key events - This app will handle all key eventsRegisterForKeys( SampleApp_TaskID );//默认情况,所有设备加入组1// By default, all devices start out in Group 1SampleApp_Group.ID = 0x0001;    //设定组IDosal_memcpy( SampleApp_Group.name, "Group 1", 7  );     //设定组名aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );   //加入组#if defined ( LCD_SUPPORTED )HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 );
#endif
}

在一系列初始化完成后,就会运行到osal_start_system()函数,
OSAL是基于事件驱动的轮询式操作系统,不断查看是否有事件发生,如果有,则调用相应事件处理函数去处理事件。所以最终程序会运行到一个死循环当中去,不断检测是否有事件发生。

以下是此函数代码:

void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )for(;;)  // Forever Loop
#endif{uint8 idx = 0;//用来在事件表中索引osalTimeUpdate();//更新系统时钟,同时查看硬件方面是否有时间发生,比如串口是否收到数据,是否有按键等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);//轮询是否有事件发生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}
}
  • idx 是一个索引,用来在事件表中索引;
  • do-while循环是查看事件表中是否有事件发生;
  • events = tasksEvents[idx],是读取该事件,注意每一个二进制位是表示一个事件的
  • 接着对事件表对应项清理,这是暂时的,后面会恢复,这是因为可能会有几件事情同时发生;
  • events = (tasksArr[idx])( idx, events ),根据taskID调用相应层的事件处理函数,如果处理完,返回0,否则返回未处理的事件;
  • tasksEvents[idx] |= events,或运算将未处理的事件保存到tasksArr[]数组对应项中去,并继续下一次循环。

那事件处理函数又是怎么样运行的呢?
这是一个应用层的事件处理函数:

uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{afIncomingMSGPacket_t *MSGpkt;(void)task_id;  // Intentionally unreferenced parameterif ( events & SYS_EVENT_MSG ){MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );while ( MSGpkt ){switch ( MSGpkt->hdr.event ){// Received when a key is pressedcase KEY_CHANGE:SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );break;// Received when a messages is received (OTA) for this endpointcase AF_INCOMING_MSG_CMD:SampleApp_MessageMSGCB( MSGpkt );break;// Received whenever the device changes state in the networkcase ZDO_STATE_CHANGE:SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);if ( (SampleApp_NwkState == DEV_ZB_COORD)|| (SampleApp_NwkState == DEV_ROUTER)|| (SampleApp_NwkState == DEV_END_DEVICE) ){// Start sending the periodic message in a regular interval.osal_start_timerEx( SampleApp_TaskID,SAMPLEAPP_SEND_PERIODIC_MSG_EVT,SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );}else{// Device is no longer in the network}break;default:break;}// Release the memoryosal_msg_deallocate( (uint8 *)MSGpkt );// Next - if one is availableMSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );}// return unprocessed eventsreturn (events ^ SYS_EVENT_MSG);}// Send a message out - This event is generated by a timer//  (setup in SampleApp_Init()).if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT ){// Send the periodic messageSampleApp_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 eventsreturn (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);}// Discard unknown eventsreturn 0;
}
  • osal_msg_receive函数从消息队列接收一个消息,这个消息包含了事件和接收到的数据;
  • 当消息不为空时,switch-case判断事件类型,有按键事件,OTA消息事件,设备状态改变事件等,并调用相应处理函数;
  • 如果事件全部处理完成,返回0,否则返回为处理完成的事件。

我们可以看一个处理函数的例子:
比如处理 AF_INCOMING_MSG_CMD 事件时,调用SampleApp_MessageMSGCB( )进行处理。

 case AF_INCOMING_MSG_CMD:SampleApp_MessageMSGCB( MSGpkt );break;

SampleApp_MessageMSGCB( )具体实现:

void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{uint16 flashTime;switch ( pkt->clusterId ){case SAMPLEAPP_PERIODIC_CLUSTERID:break;case SAMPLEAPP_FLASH_CLUSTERID:flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );break;}
}

此函数根据簇ID(pkt->clusterId)的不同进行处理,注意,这些处理需要根据自身开发需求来进行编写,内容不固定,但大体的框架格式是近似的。

上面是对接收消息事件进行处理,那么 肯定会有相应的发送消息事件。
我们可以看到,SampleApp_ProcessEvent()函数中出现的SAMPLEAPP_SEND_PERIODIC_MSG_EV,这是一个宏定义,定义发送周期信息事件。

#define SAMPLEAPP_SEND_PERIODIC_MSG_EVT       0x0001

当设备成功启动后,触发 ZDO_STATE_CHANGE 事件,就会调用应用层我们所定义的事件处理函数来进行处理,处理代码如下:
(此工程是SampleApp_ProcessEvent()函数)

 case ZDO_STATE_CHANGE:SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);if ( (SampleApp_NwkState == DEV_ZB_COORD)|| (SampleApp_NwkState == DEV_ROUTER)|| (SampleApp_NwkState == DEV_END_DEVICE) ){// Start sending the periodic message in a regular interval.osal_start_timerEx( SampleApp_TaskID,SAMPLEAPP_SEND_PERIODIC_MSG_EVT,SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );}else{// Device is no longer in the network}break;

若设备网络状态为DEV_ZB_COORD、DEV_ROUTER或DEV_END_DEVICE,表明设备启动成功。若设备启动成功,则调用函数osal_start_timerEx()来定时触发事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT

事件处理代码在SampleApp_ProcessEvent()中,代码如下:

 if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT ){//发送周期信息// Send the periodic messageSampleApp_SendPeriodicMessage();//定时再触发事件  SAMPLEAPP_SEND_PERIODIC_MSG_EVT// 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 eventsreturn (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);}

处理事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT,就会调用
SampleApp_SendPeriodicMessage()来进行信息的发送,并且紧接着再次定时触发SAMPLEAPP_SEND_PERIODIC_MSG_EVT 事件,从而实现周期性信息发送。

SampleApp_SendPeriodicMessage()定义如下:

void SampleApp_SendPeriodicMessage( void )
{if ( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,SAMPLEAPP_PERIODIC_CLUSTERID,1,(uint8*)&SampleAppPeriodicCounter,&SampleApp_TransID,AF_DISCV_ROUTE,AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ){}else{// Error occurred in request to send.}
}

可以看到我们所定义的发送函数其实是调用了AF_DataRequest()函数,我们在使用的时候只需要了解其参数的意义便可以灵活使用,而不必去关心其具体实现。

afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,uint16 cID, uint16 len, uint8 *buf, uint8 *transID,uint8 options, uint8 radius )

依次介绍一下其参数:

  • *dstAddr,发送的目的地址、端点地址以及发送模式(如单播,组播和广播),此工程在应用层初始化中已经定义;
SampleApp_Periodic_DstAddr.addrMode =(afAddrMode_t)AddrBroadcast;SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;
  • *srcEP源端点;
SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;SampleApp_epDesc.task_id = &SampleApp_TaskID;SampleApp_epDesc.simpleDesc= (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;SampleApp_epDesc.latencyReq = noLatencyReqs;//注册端点描述符afRegister( &SampleApp_epDesc );
  • uint16 cID簇ID,接收方可根据其不同从而对消息进行不同的处理;
  • uint16 len数据长度
  • *buf 指向发送数据缓冲区的指针;
  • *transID,发送序列号,可用来计算丢包率;
  • uint8 options,发送选项;
  • uint8 radius,跳数;

注意:可能有人不是很理解源端点的意义,我们虽然可以通过网络地址来唯一确定一个节点,但是一个节点上,还存在着不同的端口,每个节点最多240个端口,端口0是默认ZDO。而不同节点上的端口可以进行通信,比如A节点端口1可以给B节点的端口1发送命令,也可以给B节点的端口2发送命令,此时单单通过网络地址是无法进行区分的,所以还需要指定端口号。

因此,发送数据时我们需要指定网络地址和端口号,前者用来区分不同节点,后者用来区分同一节点不同端口号。

终于写完了,文字的内容不多,代码多,主要是对代码的分析,能看完的人都是勇士呀!希望给大家带来一点帮助。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部