日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

zstack流程梳理与串口事件详解及zigbee调试助手实现细节

發布時間:2023/12/20 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 zstack流程梳理与串口事件详解及zigbee调试助手实现细节 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

聲明:本文撰寫時間為2019年6月,能力有限,有錯誤歡迎批評指正

首先先梳理一遍zstack的流程

協議棧的文件包層次結構:

  • App:應用層目錄,這是用戶創建各種不同工程的區域,在這個目錄中包含了應用層的內容和這個項目的主要內容,在協議中一般是以操作系統的任務實現的。

  • HAL:硬件層目錄,包含有與硬件相關的配置和驅動及操作函數

  • MAC:MAC層目錄,包含了MAC層的參數配置文件及其MAC的LIB庫的函數接口文件

  • MT:實現通過串口可控制各層,并與各層進行直接交互

  • NWK:網絡層目錄,包含網絡層配置參數文件網絡層庫的函數接口文件及APS層庫的函數接口

  • OSAL:協議棧的操作系統

  • Profile:AF(Applicationframework應用框架)層目錄

  • Security:安全層目錄,包含安全層處理函數,比如加密函數等

  • Services:地址處理函數目錄,包括地址模式的定義及地址處理函數

  • Tools: 工程配置目錄,包括空間劃分及Z-Stack相關配置信息

  • ZDO:ZDO目錄

  • ZMac:MAC層目錄,包括MAC層參數配置及MAC層LIB庫函數回調處理函數

  • ZMain:主函數目錄,包括入口函數及硬件配置文件

  • Output:輸出文件目錄,由IAR IDE自動生成

一些名詞

英文中文含義備注
EndPoint端點是協議棧應用層的入口,即入口地址,也可以理解應用對象(Application Object)存在的地方,它是為實現一個設備描述而定義的一組群集端點0 :用于整個ZigBee設備的配置和管理,附屬在端點0的對象被稱為ZigBee設備對象(ZD0)
端點255:用于向所有的端點進行廣播
端點241~254:保留端點
其他端點:映射應用對象,并使得應用程序可以跟ZigBee堆棧其他層進行通信。
Cluster一個具體的應用(例如智能家居系統)有大量細節上的小規范例如電燈的控制:開燈、關燈等)這個規范即成為簇
COORDINATOR協調器協調器是整個網絡的核心,它最主要的作用是啟動網絡,其方法是其方法是選擇一個相對空閑的信道,形成一個PANID
Router路由器路由器的主要功能是提供接力作用,能擴展信號的傳輸范圍,因此一般情況下應該一直處于活動狀態,不應休眠。終端設備可以睡眠也可以喚醒,因此可以用電池來供電。
Channel信道2.4GHz的射頻頻段被分為16個獨立的信道。每一個設備都有一個默認的信道集(DEFAULT_CHANLIST)。協調器掃描自己的默認信道并選擇噪聲最小的信道作為自己所建的網絡信道。設備節點和路由器也要掃描默認信道集并選擇信道上已經存在的網絡加入。
PANID網絡編號PANID指網絡編號,用于區分不同的網絡設備,PANID值與ZDAPP_CONFIG_PAN_ID的值設定有關。如果協調器的ZDAPP_CONFIG_PAN_ID設置為0xFFFF,則協調器將產生一個隨機的PANID,如果路由器和終端節點的ZDAPP_CONFIG_PAN_ID設置為0xFFFF,路由器和終端節點將會在自己默認信道上隨機的選擇一個網絡加入,網絡協調器的PANID即為自己的PANID。如果協調器的ZDAPP_CONFIG_PAN_ID設置為非0xFFFF值,則協調器根據自己的網絡長地址(IEEE地址)或ZDAPP_CONFIG_PAN_ID隨機產生PANID的值。不同的是如果路由器和終端節點的ZDAPP_CONFIG_PAN_ID 的值設置為非0xFFFF,則會以ZDAPP_CONFIG_PAN_ID值作為PANID。如果協調器的值設為小于等于0x3FFF的有效值,協調器就會以這個特定的PANID值建立網絡,但是如果在默認信道上已經有了該PANID值的網絡存在,則協調器會繼續搜尋其它的PANID,直到找到不沖突的網絡為止,這樣就可能產生一個問題如果協調器在默認信道上發生PANID沖突而更換PANID,終端節點并不知道協調器已經更換了PANID,還會繼續加入到PANID為ZDAPP_CONFIG_PAN_ID值的網絡中

在main函數中:

int main( void ) {// Turn off interrupts//關閉中斷osal_int_disable( INTS_ALL );//初始化硬件// Initialization for board related stuff such as LEDsHAL_BOARD_INIT();// Make sure supply voltage ishigh enough to run//電壓檢測,確保芯片能正常工作的電壓zmain_vdd_check();// Initialize board I/O//初始化板載I/OInitBoard( OB_COLD );// Initialze HAL drivers//初始化硬件驅動HalDriverInit();// Initialize NV System//初始化NV系統osal_nv_init( NULL );// Initialize the MAC//初始化MACZMacInit();// Determine the extended address//確定擴展地址(64位IEEE/物理地址)zmain_ext_addr(); #if defined ZCL_KEY_ESTABLISH// Initialize the Certicom certificate information.// 初始化CERT認證系統zmain_cert_init(); #endif// Initialize basic NVitems//初始化基本NV條目zgInit(); #ifndef NONWK//Since the AF isn't a task, call it's initialization routine//如果task里沒有AF任務,需要在此調用他的初始化函數afInit(); #endif// Initialize the operating system//初始化操作系統osal_init_system();...}

其中的osal_init_system便是操作系統的初始化,里面包含了os中各個app以及os各組成部分的初始化方法的調用,比如:初始化內存,堆棧等,其中對app的初始化函數為osalInitTasks();

uint8 osal_init_system( void ) {// Initialize the Memory Allocation System//初始化內存分配系統osal_mem_init();// Initialize the message queue//初始化消息隊列 任務之間的通信靠的就是消息隊列osal_qHead = NULL;// Initialize the timers//初始化定時器osalTimerInit();// Initialize the Power Management System//初始化電源管理系統osal_pwrmgr_init();//osal_mem_alloc()該函數是OSAL中的內存管理函數,是一個存儲分配函數,返回指向一個緩存的指針,參數是被分配緩存的大小,其tasksCnt的定義如下const uint8tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );tasksEvents指向被分配的內存空間,這里注意tasksArr[]函數指針數組的聯系是一一對應的。tasksEvents就是指向第一個被分配的任務的內存空間tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);//把申請的內存空間全部設置為0,tasksCnt任務數 * 單個任務占的內存空間(4byte)osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));// Initialize the system tasks.//初始化系統任務,重點關注osalInitTasks();// Setup efficient search for the first free block of heap.//設置有效的查找堆上的第一個空閑塊osal_mem_kick();return ( SUCCESS ); }

任務初始化函數-------osalInitTasks();

void osalInitTasks( void ) {uint8 taskID = 0;//下面就是Z-Stack協議棧中,從MAC層到ZDO層的初始化函數,其中的參數都是任務的ID,不過ID號是依次遞增的macTaskInit(taskID++ ); //mac_ID = 0nwk_init( taskID++ ); //nwk_ID = 1Hal_Init( taskID++ ); //Hal_ID = 2 #if defined( MT_TASK )MT_TaskInit( taskID++ );//mt_ID = 3 #endifAPS_Init( taskID++ ); //APS_ID =4 #if defined ( ZIGBEE_FRAGMENTATION )APSF_Init(taskID++ ); //ZDO_ID =5 #endifZDApp_Init( taskID++ ); ;//ZDO_ID =6 #if defined ( ZIGBEE_FREQ_AGILITY ) ||defined ( ZIGBEE_PANID_CONFLICT )ZDNwkMgr_Init( taskID++ ); //ZDO_ID =7 #endif //協議棧工程下如果選擇Coordinator或EndDevice或Router工程則只會進入這個 #if defined(SAPP_ZSTACK)sapp_taskInitProcess();//ZDO_ID =8 #endif //協議棧工程下如果選擇Deemo工程則只會進入這個 #if defined(SAPP_ZSTACK_DEMO)// 任務建立實驗范例代碼// 啟動定時器osal_start_timerEx(taskID, 0x0001, 1000);//ZDO_ID =8 #endif }

追蹤到sapp_taskInitProcess,這里便是app的初始化函數了,在這個協議棧中,app只有一個,但這個app是一個管理很多小功能的APP,所有的小功能被放在functionlist里統一管理

void sapp_taskInitProcess(void) {#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// 構造功能列表funcTableBuffer = createFuncTable(funcCount);funcTableBuffer->ft_type = 0x01;funcTableBuffer->ft_count = funcCount;int i;for(i = 0; i < funcCount; i++){funcTableBuffer->ft_list[i].type = funcList[i].function.type;funcTableBuffer->ft_list[i].id = funcList[i].function.id;funcTableBuffer->ft_list[i].cycle = funcList[i].function.cycle;}controlTaskId = tasksCnt - 2;functionTaskId = tasksCnt - 1;HalIOInit(functionTaskId);createEndPoint(&controlEndPointInfo, &controlTaskId, CONTROL_ENDPOINT);for(i = 0; i < funcCount; i++){struct ep_info_t *ep = &funcList[i];createEndPoint(ep, &functionTaskId, i + 1);if(ep->res_available)(*ep->res_available)(ep, ResInit, NULL);} //這里選擇是否注冊串口事件 #if defined(ZDO_COORDINATOR)// || defined(RTR_NWK) // RegisterForKeys( SampleApp_TaskID );MT_UartRegisterTaskID(controlTaskId); #endif }

好了所有的初始化任務完成了,現在回到main中,再經過幾個初始化后就進入osal_start_system開始正式啟動OS了

...// Initialize the operating system//初始化操作系統osal_init_system();// Allow interrupts//使能中斷osal_int_enable( INTS_ALL );// Final board initialization//最終板載初始化InitBoard( OB_READY );// Display informationabout this device//顯示設備信息zmain_dev_info();/*Display the device info on the LCD *///添加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 ); #endifosal_start_system(); // No Return from here沒有返回,即進入操作系統return 0; // Shouldn't get here.//不會運行到這

進入osal_start_system發現這里有個死循環,也就是為什么main函數不return的原因

void osal_start_system( void ) { #if !defined ( ZBIT ) && !defined (UBIT )for(;;) // 一直循環,是“輪詢”中的輪,即不斷循環執行 #endif{osal_run_system();} }

進入osal_run_system中,osal_start_system函數是ZigBee協議棧的靈魂,實現的方法是不斷查詢事件表,如果有事情發生就調用相應的事件處理函數。

void osal_run_system( void ) {uint8 idx = 0;osalTimeUpdate();Hal_ProcessPoll();do {if (tasksEvents[idx]) // 這里就是“輪詢”中的詢,即不斷查詢,而且這個查詢是有優先級順序的{break;//如果有事件發生則跳出循環}} while (++idx < tasksCnt);//判斷是主動跳出循環還是全部判斷完跳出的循環if (idx < tasksCnt){uint16 events;halIntState_t intState;HAL_ENTER_CRITICAL_SECTION(intState);events = tasksEvents[idx];tasksEvents[idx] = 0; // 清除事件標志HAL_EXIT_CRITICAL_SECTION(intState);activeTaskID = idx;events = (tasksArr[idx])( idx, events );//調用相應的事件處理函數,tasksArr[]是一個函數指針數組,每一個元素都是函數指針activeTaskID = TASK_NO_TASK;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/* Yield in case cooperative scheduling is being used. */ #if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0){osal_task_yield();} #endif }

這里可以去看我們的taskArr里有什么了

// 任務列表 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, #endif #if defined(SAPP_ZSTACK)//重點,這兩個便是我們自己定義的事件處理函數sapp_controlEpProcess,sapp_functionEpProcess, #endif #if defined(SAPP_ZSTACK_DEMO)// Deemo工程對應的事件處理函數Hello_ProcessEvent, #endif }; //學習一下這里sizeof的用法,之后會經常用到 const uint8 tasksCnt = sizeof(tasksArr)/sizeof(tasksArr[0]);

這里的taskArr(包含系統級任務如macEventLoop等和用戶app如sapp_functionEpProcess)非常像我們平常使用的操作系統里的任務管理器里的任務(圖中包含了系統自帶應用如windowServer何kernel_task等,也包括了用戶app如QQ),對于我們的OS來說,多個網絡應用是通過端口號(圖中如果有一份數據包發給QQ則會發送給0.0.0.0:1194)來區分的,而對于OSAL來說,接收到一份數據包決定將數據包發送給哪個app則是通過數據包中指定的EndPoint決定的。

比如這一份數據包,就會被發送到EndPoint為0x01的task里。


進入第一個app的處理函數sapp_controlEpProcess,這里主要是為了配合zigbee調試助手使用,原來是讓協調器接收PC中的ZigBee調試助手里發送的命令幀,以及解析命令幀里命令,01對應發送functionlist,02對應發送拓撲信息,03對應發送function的數據?關于zigbee調試助手的原理在最后面會講一下。

uint16 sapp_controlEpProcess(uint8 task_id, uint16 events) {afIncomingMSGPacket_t *MSGpkt;//如果是系統事件if ( events & SYS_EVENT_MSG ){//message要通過osal_msg_send發送也要對應通過osal_msg_receive接收MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive(task_id);while ( MSGpkt ){//HalUARTWrite(0, &MSGpkt->hdr.event,1);switch ( MSGpkt->hdr.event ){ #if defined(ZDO_COORDINATOR)//如果是串口事件case CMD_SERIAL_MSG:uartMsgProcesser((uint8 *)MSGpkt);HalLedBlink( HAL_LED_1, 2, 50, 90 );break; #endif#if ! defined(ZDO_COORDINATOR) && defined(RTR_NWK)//如果是串口事件case CMD_SERIAL_MSG:uartMsgProcesser1((uint8 *)MSGpkt);HalLedBlink( HAL_LED_1, 2, 50, 90 );break; #endif// Received when a messages is received (OTA) for this endpoint//如果收到了無線數據包case AF_INCOMING_MSG_CMD:{// TODO: QueryProfile or QueryTopo//先判斷是不是跟自己一個clusterswitch(MSGpkt->clusterId){case SAPP_PERIODIC_CLUSTERID:switch(MSGpkt->cmd.Data[0]){case 0x01:// CtrlQueryProfile//這個也是配合zigbee調試助手使用的,目的是顯示functionlist里有幾個functionSendData(CONTROL_ENDPOINT, funcTableBuffer->ft_data, MSGpkt->srcAddr.addr.shortAddr, MSGpkt->srcAddr.endPoint, sizeof(FUNCTABLE) + funcCount * sizeof(FUNCINFO));break;case 0x02:// CtrlQueryTopo//如果是查詢拓撲信息的命令幀則把自己的拓撲數據發送給源地址(也就是協調器)這個是配合zigbee調試助手顯示拓撲結構功能使用的,原理在之后會講SendData(CONTROL_ENDPOINT, (unsigned char *)&topoBuffer, MSGpkt->srcAddr.addr.shortAddr, MSGpkt->srcAddr.endPoint, sizeof(TOPOINFO));break;case 0x03:// CtrlQuerySpecialFunction//也是配合zigbee調試助手使用的,應該是發送function里的數據{uint8 i;for(i = 0; i < funcTableBuffer->ft_count; i++){if((funcTableBuffer->ft_list[i].type == MSGpkt->cmd.Data[1])&& (funcTableBuffer->ft_list[i].id == MSGpkt->cmd.Data[2])){// 0x03, EndPoint, rCycleuint8 specialFunc[3] = { 0x03, i + 1, funcTableBuffer->ft_list[i].cycle };SendData(CONTROL_ENDPOINT, specialFunc, MSGpkt->srcAddr.addr.shortAddr, MSGpkt->srcAddr.endPoint, sizeof(specialFunc));break;}}}break;default:{int i;for(i = 0; i < funcCount; i++){struct ep_info_t *ep = &funcList[i];if(ep->res_available) (*ep->res_available)(ep, ResControlPkg, MSGpkt);//執行對應的資源可用函數}}break;}HalLedBlink( HAL_LED_2, 1, 50, 250 );break;}break;}// Received whenever the device changes state in the network//如果是網絡狀態改變信息case ZDO_STATE_CHANGE:{devStates_t st = (devStates_t)(MSGpkt->hdr.status);if ( (st == DEV_ZB_COORD)|| (st == DEV_ROUTER)|| (st == DEV_END_DEVICE) ){ // topoBuffer->type = 0x02;//改變自己存儲的拓撲結構數據memcpy(topoBuffer.IEEE, NLME_GetExtAddr(), 8); #if !defined(ZDO_COORDINATOR)topoBuffer.PAddr = NLME_GetCoordShortAddr(); #elsetopoBuffer.PAddr = 0xFFFF; #endifosal_memcpy(&topoBuffer.panid, &_NIB.nwkPanId, sizeof(uint16));osal_memcpy(&topoBuffer.channel, &_NIB.nwkLogicalChannel, sizeof(uint8));//向協調器發送拓撲信息SendData(CONTROL_ENDPOINT, (unsigned char *)&topoBuffer, 0x0000, TRANSFER_ENDPOINT, sizeof(TOPOINFO));HalLedBlink( HAL_LED_2, 4, 50, 250 );}}break;default:break;}// Release the memoryosal_msg_deallocate( (uint8 *)MSGpkt );// Next - if one is availableMSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( task_id );}// return unprocessed eventsreturn (events ^ SYS_EVENT_MSG);}// 定時器時間到, 遍歷所有端點看是否有userTimerif(events & SAPP_SEND_PERIODIC_MSG_EVT){int i;uint8 hasUserTimer = 0;for(i = 0; i < funcCount; i++){struct ep_info_t *ep = &funcList[i];if(ep->userTimer && ep->res_available){hasUserTimer = 1;ep->userTimer = ep->userTimer - 1;if(ep->userTimer <= 1){ep->userTimer = 0;(*ep->res_available)(ep, ResUserTimer, NULL);//執行對應的超時函數}}}if(hasUserTimer){// 重新啟動定時器osal_start_timerEx(task_id, SAPP_SEND_PERIODIC_MSG_EVT, 1000);}else{isUserTimerRunning = 0;osal_stop_timerEx(task_id, SAPP_SEND_PERIODIC_MSG_EVT);}// return unprocessed eventsreturn (events ^ SAPP_SEND_PERIODIC_MSG_EVT);}// Discard unknown eventsreturn 0; }

最后進入sapp_functionEpProcess看看,這里主要是根據事件執行的functionlist里對應的定義的那些函數

uint16 sapp_functionEpProcess(uint8 task_id, uint16 events) {afIncomingMSGPacket_t *MSGpkt;if(events & SYS_EVENT_MSG){MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( task_id );while ( MSGpkt ){switch ( MSGpkt->hdr.event ){// 接收到數據包case AF_INCOMING_MSG_CMD:{switch ( MSGpkt->clusterId ){case SAPP_PERIODIC_CLUSTERID:if(MSGpkt->endPoint <= funcCount){struct ep_info_t *ep = &funcList[MSGpkt->endPoint - 1];if(ep->incoming_data)(*ep->incoming_data)(ep, MSGpkt->srcAddr.addr.shortAddr, MSGpkt->srcAddr.endPoint, &MSGpkt->cmd);//執行對應的incoming處理函數HalLedBlink( HAL_LED_2, 1, 50, 250 );}break;}}break;case ZDO_STATE_CHANGE:{curNwkState = (devStates_t)(MSGpkt->hdr.status);if ( (curNwkState == DEV_ZB_COORD)|| (curNwkState == DEV_ROUTER)|| (curNwkState == DEV_END_DEVICE) ){int i;int hasTimeOut = 0;for(i = 0; i < funcCount; i++){struct ep_info_t *ep = &funcList[i];if(ep->nwk_stat_change)(*ep->nwk_stat_change)(ep);// 重置端點計數器if(ep->time_out && ep->function.cycle){ep->timerTick = ep->function.cycle;hasTimeOut = 1;}}if(hasTimeOut){// 加入網絡成功,啟動定時器,為各個端點提供定時osal_start_timerEx(task_id,SAPP_SEND_PERIODIC_MSG_EVT,1000);}}elseosal_stop_timerEx(task_id, SAPP_SEND_PERIODIC_MSG_EVT);}break;//IO事件case IOPORT_INT_EVENT:{OSALIOIntData_t* IOIntData;IOIntData =(OSALIOIntData_t*)MSGpkt;if(IOIntData->endPoint <= funcCount){struct ep_info_t *ep = &funcList[IOIntData->endPoint - 1];if(ep->res_available)(*ep->res_available)(ep, ResIOInt, IOIntData->arg);//執行對應的資源可用處理函數}}break; #if defined(HAL_IRDEC) && (HAL_IRDEC == TRUE)case IRDEC_INT_EVENT: //{OSALIRDecIntData_t* TimerIntData = (OSALIRDecIntData_t*)MSGpkt;if(TimerIntData->endPoint <= funcCount){struct ep_info_t *ep = &funcList[TimerIntData->endPoint - 1];if(ep->res_available)(*ep->res_available)(ep, ResTimerInt, TimerIntData->data);}}break; #endifdefault:break;}// Release the memoryosal_msg_deallocate( (uint8 *)MSGpkt );// Next - if one is availableMSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( task_id );}// return unprocessed eventsreturn (events ^ SYS_EVENT_MSG);}// 定時器時間到, 遍歷所有端點看是否有需要調用time_outif(events & SAPP_SEND_PERIODIC_MSG_EVT){int i;for(i = 0; i < funcCount; i++){struct ep_info_t *ep = &funcList[i];if(ep->time_out && ep->function.cycle){// 端點需要周期執行ep->timerTick = ep->timerTick - 1;if(ep->timerTick == 0){// 定時時間到,執行time_out函數(*ep->time_out)(ep);ep->timerTick = ep->function.cycle;}} #if 0if(ep->userTimer && ep->res_available){ep->userTimer = ep->userTimer - 1;if(ep->userTimer <= 1){(*ep->res_available)(ep, ResUserTimer, NULL);ep->userTimer = 0;}} #endif}// 重新啟動定時器osal_start_timerEx(task_id, SAPP_SEND_PERIODIC_MSG_EVT, 1000);// return unprocessed eventsreturn (events ^ SAPP_SEND_PERIODIC_MSG_EVT);}// Discard unknown eventsreturn 0; }

Zigbee 串口事件

UART(通用串口)是由zigbee協議棧中的MT層主管的,因此要在預編譯選項中添加上ZTOOL_P1或ZAPP_P1和MT_TASK

相關的文件有:

  • MT_UART.c
  • MT_UART.h

略過硬件層的一些關于串口的初始化后,我們又回到了OSAL的初始化函數,在這里面有個非常重要的函數MT_TaskInit,這里面是對MT層的一系列設置。

void osalInitTasks( void ) {uint8 taskID = 0;//下面就是Z-Stack協議棧中,從MAC層到ZDO層的初始化函數,其中的參數都是任務的ID,不過ID號是依次遞增的macTaskInit(taskID++ ); //mac_ID = 0nwk_init( taskID++ ); //nwk_ID = 1Hal_Init( taskID++ ); //Hal_ID = 2 #if defined( MT_TASK )MT_TaskInit( taskID++ );//mt_ID = 3...

追蹤進去后:

void MT_TaskInit(uint8 task_id) {MT_TaskID = task_id;/* Initialize the Serial port *///初始化UartMT_UartInit();/* Register taskID - Do this after UartInit() because it will reset the taskID *///注冊事件MT_UartRegisterTaskID(task_id);osal_set_event(task_id, MT_SECONDARY_INIT_EVENT); }

其中的UartInit中

void MT_UartInit () {halUARTCfg_t uartConfig;/* Initialize APP ID */App_TaskID = 0;/* UART Configuration *///設置為已配置uartConfig.configured = TRUE;//設置為默認波特率115200uartConfig.baudRate = MT_UART_DEFAULT_BAUDRATE;//設置是否采用流控及流控設置uartConfig.flowControl = MT_UART_DEFAULT_OVERFLOW;uartConfig.flowControlThreshold = MT_UART_DEFAULT_THRESHOLD;//設置最大發送緩沖區長度128uartConfig.rx.maxBufSize = MT_UART_DEFAULT_MAX_RX_BUFF;//設置最大接收緩沖區長度128uartConfig.tx.maxBufSize = MT_UART_DEFAULT_MAX_TX_BUFF;//設置超時時間uartConfig.idleTimeout = MT_UART_DEFAULT_IDLE_TIMEOUT;uartConfig.intEnable = TRUE; #if defined (ZTOOL_P1) || defined (ZTOOL_P2)//重要:設置串口回調函數uartConfig.callBackFunc = MT_UartProcessZToolData1; #elif defined (ZAPP_P1) || defined (ZAPP_P2)uartConfig.callBackFunc = MT_UartProcessZAppData; #elseuartConfig.callBackFunc = NULL; #endif/* Start UART */ #if defined (MT_UART_DEFAULT_PORT)//開啟UARTHalUARTOpen (MT_UART_DEFAULT_PORT, &uartConfig); #else/* Silence IAR compiler warning */(void)uartConfig; #endif/* Initialize for ZApp */ #if defined (ZAPP_P1) || defined (ZAPP_P2)/* Default max bytes that ZAPP can take */MT_UartMaxZAppBufLen = 1;MT_UartZAppRxStatus = MT_UART_ZAPP_RX_READY; #endif}

追蹤這個回調函數發現這個是我自己寫的回調函數,自己添加新的回調函數時要在MT_UART.h里面聲明

//自定義的串口接收回調函數 void MT_UartProcessZToolData1 ( uint8 port, uint8 event ) {uint8 flag = 0,i,j = 0; //flag判斷有沒有數據,j記錄數據長度uint8 buf[128]; //緩沖128(void)event;while(Hal_UART_RxBufLen(port)) //檢測串口數據是否完成{HalUARTRead(port,&buf[j],1);//數據接收到bufj++;flag = 1;}if(flag == 1) //有數據時{//分配內存,結構體+內容+長度pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof( mtOSALSerialData_t )+j+1);pMsg->hdr.event = CMD_SERIAL_MSG;//事件類型選擇為CMD_SERIAL_MSGpMsg->msg = (uint8*)(pMsg+1); //把數據定位到結構體pMsg->msg [0]= j; //記錄數據長度for(i=0;i<j;i++)pMsg->msg [i+1]= buf[i];osal_msg_send( App_TaskID, (byte *)pMsg ); // 登記任務并發往上層osal_msg_deallocate ( (uint8 *)pMsg ); // 釋放內存} }//原本的回調函數,在后面講zigbee調試助手原理的時候會講到,暫時不用管 void MT_UartProcessZToolData ( uint8 port, uint8 event ) {uint8 ch;uint8 bytesInRxBuffer;(void)event; // Intentionally unreferenced parameterwhile (Hal_UART_RxBufLen(port)){HalUARTRead (port, &ch, 1);switch(state){...}

注意串口事件也要在這里注冊才行

void sapp_taskInitProcess(void) {... //這里選擇是否注冊串口事件 #if defined(ZDO_COORDINATOR)// || defined(RTR_NWK) // RegisterForKeys( SampleApp_TaskID );MT_UartRegisterTaskID(controlTaskId); #endif }

這樣我們回到sapp_controlEpProcess函數中,就可以對串口事件進行處理了,在這里路由器和協調器的處理函數是不同的

uint16 sapp_controlEpProcess(uint8 task_id, uint16 events) {afIncomingMSGPacket_t *MSGpkt;if ( events & SYS_EVENT_MSG ){//HalLedBlink( HAL_LED_1, 2, 50, 90 );MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive(task_id);while ( MSGpkt ){//HalUARTWrite(0, &MSGpkt->hdr.event,1);switch ( MSGpkt->hdr.event ){ #if defined(ZDO_COORDINATOR)case CMD_SERIAL_MSG:uartMsgProcesser((uint8 *)MSGpkt);HalLedBlink( HAL_LED_1, 2, 50, 90 );break; #endif#if ! defined(ZDO_COORDINATOR) && defined(RTR_NWK)case CMD_SERIAL_MSG:uartMsgProcesser1((uint8 *)MSGpkt);HalLedBlink( HAL_LED_1, 2, 50, 90 );break; #endif

Zigbee調試助手原理

  • 解析的思路
  • 注意到在CMD_SERIAL_MSG事件中有讓LED1閃兩下的代碼,實際現象中是當點擊ZigBee調試助手的打開端口按鈕時,LED1和LED2都閃,而且經過一段固定的時間后也會都閃,而控制LED1閃是在發送無線數據包出現的。
  • case CMD_SERIAL_MSG:uartMsgProcesser((uint8 *)MSGpkt);HalLedBlink( HAL_LED_1, 2, 50, 90 );break;
  • 注意到在這個case里還有一句uartMsgProcesser,追蹤進去發現這個函數的功能是將受到的數據包進行解析,然后把數據包里的數據按照數據包里的地址發送出去,并且這個數據包里還有一個cmd和一個cmdEndPoint的幀控制域,只有當這兩個控制域分別為0x0018和0xF1時才會觸發發送數據包的函數,由此我們可以推測,PC通過串口發來了一串具有特定格式的數據包,協調器對數據包進行解析,然后把數據包里的數據發送到數據包里的目的地址。
  • static uint8 uartMsgProcesser(uint8 *msg) {mtOSALSerialData_t *pMsg = (mtOSALSerialData_t *)msg;mtUserSerialMsg_t *pMsgBody = (mtUserSerialMsg_t *)pMsg->msg;if ( (curNwkState != DEV_ZB_COORD)&& (curNwkState != DEV_ROUTER)&& (curNwkState != DEV_END_DEVICE) )return 1;switch(pMsgBody->cmd){case 0x0018:{switch(pMsgBody->cmdEndPoint){case 0xF1:{// 轉發數據SendData(TRANSFER_ENDPOINT, pMsgBody->data,pMsgBody->addr, pMsgBody->endPoint,pMsgBody->len - 6);}break;}}break;}return 1;
  • 那么這個數據包長什么樣子呢,我們追蹤到MT層中的串口回調函數,終于發現了官方對于這個數據包的格式定義,SOP(Start Of Packet我猜的)根據抓包的結果,發現一直是0x02,至于是什么含義在后面會講到;我們繼續看這個函數,發現他完成了對一個數據包的解析和FSC校驗。
  • #if defined (ZTOOL_P1) || defined (ZTOOL_P2) /**************************************************************************************************** @fn MT_UartProcessZToolData** @brief | SOP | CMD |Data Length| cmdEP | Address | EndPoint | Data | FSC |* | 1 | 2 | 1 | 2 | 2 | 1 | 1 ~119 | 1 |** Parses the data and determine either is SPI or just simply serial data* then send the data to correct place (MT or APP)** @param port - UART port* event - Event that causes the callback*** @return None***************************************************************************************************/ void MT_UartProcessZToolData ( uint8 port, uint8 event ) {uint8 ch;uint8 bytesInRxBuffer;(void)event; // Intentionally unreferenced parameterwhile (Hal_UART_RxBufLen(port)){//讀數據緩沖區HalUARTRead (port, &ch, 1);//判斷幀域類型switch(state){//如果是SOP控制域case SOP_STATE://如果是MT層的UART的SOPif(ch == MT_UART_SOF)state = LEN_STATE;break;//如果是長度控制域case LEN_STATE:if(ch < 7){// 不合法的長度state = SOP_STATE;// 直接丟棄了break;}//合法后開始構建一個數據包pMsg = (mtOSALSerialData_t *)osal_msg_allocate(sizeof(mtOSALSerialData_t) +ch + 3);//SOP+LEN+FSCtempDataLen = 0;/* Allocate memory for the data */ // pMsg = (mtOSALSerialData_t *)osal_msg_allocate(sizeof(mtOSALSerialData_t) + sizeof(mtUserSerialMsg_t) + // ch - 5);if (pMsg){/* Fill up what we can *///把能填的都填上,這里的事件類型CMD_SERIAL_MSG就是我們在處理函數里判斷的事件類型pMsg->hdr.event = CMD_SERIAL_MSG;pMsg->msg = (uint8*)(pMsg + 1);pMsgContent = (mtUserSerialMsg_t *)pMsg->msg;pMsgContent->sop = MT_UART_SOF;pMsgContent->len = ch;state = DATA_STATE;}else{pMsgContent = NULL;state = SOP_STATE;return;}break;//如果是數據域case DATA_STATE:pMsgContent->dataBody[tempDataLen++] = ch;/* Check number of bytes left in the Rx buffer */bytesInRxBuffer = Hal_UART_RxBufLen(port);/* If the remain of the data is there, read them all, otherwise, just read enough *///讀到數據域結束if (bytesInRxBuffer <= pMsgContent->len - tempDataLen){HalUARTRead (port, &pMsgContent->dataBody[tempDataLen], bytesInRxBuffer);tempDataLen += bytesInRxBuffer;}else{HalUARTRead (port, &pMsgContent->dataBody[tempDataLen], pMsgContent->len - tempDataLen);tempDataLen += (pMsgContent->len - tempDataLen);}/* If number of bytes read is equal to data length, time to move on to FCS */if ( tempDataLen == pMsgContent->len )state = FCS_STATE;break;//進行幀校驗 case FCS_STATE:/* Make sure it's correct */{pMsgContent->fsc = ch;uint8 fcs = MT_UartCalcFCS(0, &pMsgContent->len, 1);fcs = MT_UartCalcFCS(fcs, pMsgContent->dataBody, pMsgContent->len);if(fcs == ch)//如果對了那就把數據包發送給應用層osal_msg_send(App_TaskID, (byte *)pMsg);elseosal_msg_deallocate((uint8 *)pMsg);}/* Reset the state, send or discard the buffers at this point */state = SOP_STATE;break;default:break;}} }
  • 這里也就解釋里為什么我們要重寫他的串口回調函數,因為我們在串口調試助手里發送的數據包在這個回調函數看來是不合法的都被丟棄了,而且也是因為我們不想做這么麻煩的數據包,不過壞處也是有的,就是由于沒有幀校驗位,導致很多數據包的內容都出錯了,因此最好保留一位校驗位,如果可以的話可以自己實現一個漢明碼校驗,這樣就不用重發數據了。

  • 接下來繼續觀察實驗現象,發現zigbee調試助手打開后只有一個協調器節點,等待一會(大概十秒)后才突然出現子節點,同時接收LED燈閃爍,子節點的發送LED燈閃爍,這時整個流程就很清晰了:

  • zigbee調試助手周期性地發送一個特定格式的串口數據包給協調器

  • 協調器接收到后對數據包進行解析發送給應用層并觸發CMD事件

  • 在CMD的事件處理函數中向廣播地址(包括協調器)發送命令幀

  • 所有設備接收到命令幀后在處理函數里立即向協調器發送拓撲信息包

  • 協調器接收到拓撲信息包后將報文通過串口寫給電腦

  • 電腦中的zigbee調試助手分析拓撲信息包中的節點父子關系繪制圖像

  • 下面我們驗證一下整個流程。步驟123在上面已經介紹了。

  • 步驟5的實現源碼在functionlist中協調器的接收到數據包的處理函數里:

  • void CoordinatorIncomingRoutine(struct ep_info_t *ep, uint16 addr, uint8 endPoint, afMSGCommandFormat_t *msg) {if(msg->DataLength > 0){mtUserSerialMsg_t *pMsg = osal_mem_alloc(sizeof(mtUserSerialMsg_t) + msg->DataLength - 1);//在這里可以看到,為什么SOP一直是0x02,因為這里的MT_UART_SOP就是0x02,也就是串口事件規定的SOPpMsg->sop = MT_UART_SOF;//長度pMsg->len = msg->DataLength + 6;//命令位pMsg->cmd = 0x0018;//處理命令的endpointpMsg->cmdEndPoint = 0xF1;//網絡地址pMsg->addr = addr;//endpointpMsg->endPoint = endPoint;//數據區memcpy(pMsg->data, msg->Data, msg->DataLength);//幀校驗pMsg->fsc = MT_UartCalcFCS(0, &pMsg->len, 1);pMsg->fsc = MT_UartCalcFCS(pMsg->fsc, pMsg->dataBody, pMsg->len);//將整個數據包通過串口寫給電腦HalUARTWrite(HAL_UART_PORT_0, &pMsg->sop, sizeof(mtUserSerialMsg_t) - 2 + msg->DataLength);HalUARTWrite(HAL_UART_PORT_0, &pMsg->fsc, 1);//不釋放會死機osal_mem_free(pMsg);} }
  • 通過并聯一個USB轉TTL的小工具,連接傳感器安裝孔中的P0.2(TXD),和P0.3(RXD)截獲了協調器向PC發送的數據包:
  • 可以看到最后一行是一個終端節點發送的一個數據,數據的內容是字符串"Z-Stack for SAPP",他在zigbee調試助手中如下圖所示(這個圖是后來補的并不完全一致,當時的實驗情況與此圖類似)

    注意打開某個節點時可能會出現多個傳感器標簽0,1,2等,對應的就是ENDPORT。

  • 這樣Ubiqua之類的軟件實現原理也明白了,通過抓取所有的數據包,利用數據包中目的地址、源地址、PANID等信息分析出節點的父子層級,最終把拓撲圖繪制出來。
  • 最后總結的zigbee調試助手繪制拓撲圖及遠程控制實現內部原理

  • zigbee調試助手周期性地發送一個特定格式的串口數據包給協調器

  • 協調器接收到后對數據包進行解析發送給應用層并觸發CMD事件

  • 在CMD的事件處理函數中向廣播地址(包括協調器)發送命令幀

  • 所有設備接收到命令幀后在處理函數里立即向協調器發送拓撲信息包

  • 協調器接收到拓撲信息包后將報文通過串口寫給電腦

  • 電腦中的zigbee調試助手分析拓撲信息包中的節點父子關系繪制圖像

  • 總結

    以上是生活随笔為你收集整理的zstack流程梳理与串口事件详解及zigbee调试助手实现细节的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。