ZStack中的编程技巧
1. 像函數(shù)一樣使用的宏
//這個宏,用來被其他宏使用,構(gòu)造一個正確有效的表達式。這個適合于一些離散語句的組合,不適合函數(shù)的重新命名
#define st(x)????? do { x } while (__LINE__ == -1)
?
例如:#define aps_GroupsRemaingCapacity() ( APS_MAX_GROUPS - aps_CountAllGroups() )
上述的這個宏,調(diào)用的其他函數(shù)來實現(xiàn)其功能,因此,不適合使用st()宏。
使用場景:? aps_GroupsRemaingCapacity(); 或者?? aps_GroupsRemaingCapacity() 當做函數(shù)的參數(shù)。
?
例如:#define AES_SET_MODE(mode)????? st( ENCCS &= ~0x70; ENCCS |= mode; )
上述這個宏,使用st宏來嵌套,功能為設置AES加密模式。
使用場景: 向個函數(shù)一樣調(diào)用 AES_SET_MODE(AES_MODE_CTR); 即可。
?
2. 消息隊列中的存儲分配機制
在ZStack中,每個任務都在OSAL的大循環(huán)中輪訓處理,任務與任務之間,任務與OSAL之間,是通過消息來傳遞信息的。每一個任務都對于一系列事件,系統(tǒng)發(fā)給每個任務的事件為UINT16 events,它采用獨熱碼的機制,2個字節(jié),一共可以處理16個事件。
#define SYS_EVENT_MSG 0x8000 //系統(tǒng)任務事件#define ZDO_CB_MSG 0xD3 //系統(tǒng)任務事件里面的ZDO消息#define AF_DATA_CONFIRM_CMD 0xFD //系統(tǒng)任務事件里面的接收數(shù)據(jù)指示消息#define AF_INCOMING_MSG_CMD 0x1A //系統(tǒng)任務事件里面的接收數(shù)據(jù)消息#define ZDO_STATE_CHANGE 0xD1 //系統(tǒng)任務事件里面的ZDO狀態(tài)改變消息#define KEY_CHANGE 0xC0 //系統(tǒng)任務事件里面的按鍵消息其他事件為用戶自定義事件,可以使用的值為0x4000,0x2000,0x1000,0x8000...依次類推,供有15個自定義事件,可以通過osal_start_timerEx( ZDAppTaskID, ZDO_NETWORK_INIT, delay ); //延遲發(fā)起事件osal_set_event( ZDAppTaskID, ZDO_NETWORK_INIT ); //立刻發(fā)起事件 來進行通知。2.1 消息分配
函數(shù)原型: uint8 * osal_msg_allocate( uint16 len )
使用場景:任務調(diào)用此函數(shù)用于分配指定長度的消息緩沖。實際上,分配的長度為len加上消息頭的長度
?
消息頭結(jié)構(gòu)體如下:
typedef struct {void *next; //指向下一個消息uint16 len;uint8 dest_id; //指向當前的任務ID,分配消息時,初始化為TASK_NO_TASK } osal_msg_hdr_t; //消息頭結(jié)構(gòu)體 typedef struct {uint8 event;uint8 status; } osal_event_hdr_t; //事件結(jié)構(gòu)體osal_msg_allocate實際返回的指針為指向len長度的地址空間。 實際使用時,返回值經(jīng)常被強制類型轉(zhuǎn)換,以下面的函數(shù)為例子說明:
/********************************************************************** @fn ZDO_SendMsgCBs** @brief This function sends messages to registered tasks.* Local to ZDO and shouldn't be called outside of ZDO.** @param inMsg - incoming message** @return TRUE if sent to at least 1 task, FALSE if not*/ uint8 ZDO_SendMsgCBs( zdoIncomingMsg_t *inMsg ) {uint8 ret = FALSE;ZDO_MsgCB_t *pList = zdoMsgCBs;while ( pList ){if ( pList->clusterID == inMsg->clusterID ) //對比接收到數(shù)據(jù)中的簇ID和ZDO中注冊的簇ID {zdoIncomingMsg_t *msgPtr; // Send the address to the taskmsgPtr = (zdoIncomingMsg_t *)osal_msg_allocate( sizeof( zdoIncomingMsg_t ) + inMsg->asduLen ); //分配消息緩存,包含消息頭和數(shù)據(jù)長度if ( msgPtr ){// copy structosal_memcpy( msgPtr, inMsg, sizeof( zdoIncomingMsg_t )); //分配成功,拷貝相關內(nèi)容到消息中if ( inMsg->asduLen ){msgPtr->asdu = (byte*)(((byte*)msgPtr) + sizeof( zdoIncomingMsg_t ));osal_memcpy( msgPtr->asdu, inMsg->asdu, inMsg->asduLen );}msgPtr->hdr.event = ZDO_CB_MSG; //聲明此消息的事件類型osal_msg_send( pList->taskID, (uint8 *)msgPtr ); //將此消息發(fā)送給指定的任務IDret = TRUE;}}pList = (ZDO_MsgCB_t *)pList->next; // 與當前ZDO中的簇ID不符,指向下一個ZDO的注冊消息 }return ( ret ); }在osal_msg_send中,用到了幾個宏,
//獲得msg_ptr所指向的下一個消息
#define OSAL_MSG_NEXT(msg_ptr)????? ((osal_msg_hdr_t *) (msg_ptr) - 1)->next
?
//獲得msg_ptr所指向的任務(消息分配時,初始化為TASK_NO_TASK)
#define OSAL_MSG_ID(msg_ptr)????? ((osal_msg_hdr_t *) (msg_ptr) - 1)->dest_id
uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr ) {if ( msg_ptr == NULL ) return ( INVALID_MSG_POINTER );if ( destination_task >= tasksCnt ){osal_msg_deallocate( msg_ptr );return ( INVALID_TASK );} //參數(shù)有效性檢查// Check the message headerif ( OSAL_MSG_NEXT( msg_ptr ) != NULL ||OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK ){osal_msg_deallocate( msg_ptr );return ( INVALID_MSG_POINTER );} //確保當前消息為新分配的消息 OSAL_MSG_ID( msg_ptr ) = destination_task; //將當前消息的任務指定為入口參數(shù)// queue messageosal_msg_enqueue( &osal_qHead, msg_ptr ); //將此消息插入到全局消息隊列的尾部// Signal the task that a message is waitingosal_set_event( destination_task, SYS_EVENT_MSG ); //給此任務發(fā)起一個系統(tǒng)消息事件return ( SUCCESS ); } /********************************************************************** @fn osal_set_event** @brief** This function is called to set the event flags for a task. The* event passed in is OR'd into the task's event variable.** @param uint8 task_id - receiving tasks ID* @param uint8 event_flag - what event to set** @return SUCCESS, INVALID_TASK*/ uint8 osal_set_event( uint8 task_id, uint16 event_flag ) {if ( task_id < tasksCnt ){halIntState_t intState;HAL_ENTER_CRITICAL_SECTION(intState); // Hold off interruptstasksEvents[task_id] |= event_flag; // Stuff the event bit(s) 給當前的task賦予事件值,以便于在OSAL大循環(huán)中可以處理HAL_EXIT_CRITICAL_SECTION(intState); // Release interruptsreturn ( SUCCESS );}else{return ( INVALID_TASK );} }在對于任務的事件處理中,需要用到osal_msg_receive(task_id)函數(shù),從指定事件下獲取對于的消息。然后循環(huán)取消息,循環(huán)處理,直到SYS_EVENT_MSG中的消息都處理完了才會退出。
系統(tǒng)事件下的消息類型結(jié)構(gòu)體定義,在開始的部分都采用添加了事件頭,便于后級循環(huán)處理。
OSAL事件頭? 事件內(nèi)部的消息頭結(jié)構(gòu),不同的消息有不同的數(shù)據(jù)內(nèi)容,但有一點是相同的,就是消息頭結(jié)構(gòu)體的第一個元素一定是OSAL事件頭。
以上大概就是OSAL的消息機制。網(wǎng)上有很多其他資料整理的很好,但是,總覺的不是自己的,自己寫出來,才能真實的消化掉。
以按鍵消息為例子:OSAL的消息頭(上)+OSAL的事件頭(中)+事件內(nèi)部的數(shù)據(jù)頭(下)
,結(jié)合上圖和以上的理解,是不是一下子很清楚啦。
消息入隊:
?
?
3.定義變量的技巧
enum
???? {
????? ZDO_SRC_RTG_IND_CBID,???????????????????????? // 0
????? ZDO_CONCENTRATOR_IND_CBID,??????????????? // 1
????? ZDO_NWK_DISCOVERY_CNF_CBID,????????????? // 2
????? ZDO_BEACON_NOTIFY_IND_CBID,?????????????? // 3
????? ZDO_JOIN_CNF_CBID,?????????????????????????????? // 4
????? ZDO_LEAVE_CNF_CBID,???????????????????????????? // 5
????? ZDO_LEAVE_IND_CBID,???????????????????????????? // 6
????? MAX_ZDO_CB_FUNC???????????????????????????????? // 7(枚舉變量個數(shù),數(shù)組索引0~6)
??????????????????????????????????????????????????????????? // Must be at the bottom of the list
??? };
上述枚舉變量,前面都是名稱的定義,取值從0開始,依次遞增,后續(xù),需要增加一個入口的話,直接在倒數(shù)第一個之前添加就可以了,其他的都不用改。
定義通用函數(shù)指針時,入口參數(shù)和出口參數(shù)最好都設置成void*類型,方便后續(xù)類型轉(zhuǎn)換。
函數(shù)指針聲明: /* ZDO Indication Callback Registration */typedef void* (*pfnZdoCb)( void *param );函數(shù)指針數(shù)組定義:pfnZdoCb zdoCBFunc[MAX_ZDO_CB_FUNC];函數(shù)指針數(shù)組初始化void ZDApp_InitZdoCBFunc( void ) {uint8 i;for ( i=0; i< MAX_ZDO_CB_FUNC; i++ ){zdoCBFunc[i] = NULL;} }自定義結(jié)構(gòu)體:
一律采用如下的定義方式:
typedef struct //不需要結(jié)構(gòu)體名 {osal_event_hdr_t hdr;uint8 endpoint;uint8 transID; } afDataConfirm_t; //指定重新定義的結(jié)構(gòu)體名,后面加上_t,表示是一個自定義類型? 在具體使用時, 使用 afDataConfirm_t *afDataConfirm;
?
4.宏定義
對于一些較長的宏定義,建議加上分行符來寫,格式規(guī)范,便于檢查。注意,最后一行,不能有分行符
#define SystemReset()???????????? \
?????? {??????????????????????????????????????? \
?????? HAL_DISABLE_INTERRUPTS();?? \
?????? HAL_SYSTEM_RESET();?????????? \?
?????? }
?
在宏定義中,對于一些預編譯的條件判斷,可以添加#error指令,來確保在編譯過程中,保持條件配置的一致性。
對于一些需要使用默認配置的選項,采用如下的定義方法:
如果不做任何其他的設定,默認是開啟ADC功能的。如果用戶要自定義開啟或者關閉,則定義
#define HAL_ADC TRUE? 或者 #define HAL_ADC FALSE 就可以了。
?
在條件宏定義中,采用適當?shù)目s進比例,便于檢查。
看到左邊的減號了嗎?那是編譯器提供的折疊功能,使用起來也很方便,但是,適當?shù)目s進,還是很有必要的,便于配對檢查,不多不漏。
?
可以在編譯時,添加對過期模塊的編譯檢查,在軟件升級時,可能需要用到。
?
?
5. 在編譯中查看宏值
?? 采用如下的宏,可以在編譯時,打印出想要查看的宏的內(nèi)容。這個技巧,在之前的博文里面提到過,覺的很好用,再次再提一次。
查看已定義宏內(nèi)容:
輸出內(nèi)容:
?
查看未定義宏內(nèi)容:
輸出內(nèi)容:
?
一般編譯器都支持message偽指令,因此,這個小技巧很好用。
如果需要對一些延遲做較為精確的計算或者對時序要求比較嚴格的操作,在函數(shù)上面加上禁止優(yōu)化的偽指令
#pragma optimize=none //指示不對A函數(shù)進行優(yōu)化 void A() { }?
6. 調(diào)試
設計一個程序,重要的是它的設計流程。
所有程序都是調(diào)試出來的,不是寫出來的。如何去調(diào)試,實際上是如何卻解決問題。
把問題拆開來思考,
?
7. 用于設置寄存器的宏
以ZStack中的hal_lcd.c中的寄存器設置為例子。
使用時,直接使用LCD_DO_WRITE就可以了,像個函數(shù)一樣用它。從名字上可以看出,它是往第port的第pin個管腳寫val數(shù)值。LCD_DO_WRITE宏執(zhí)行的功能 就是往P0.0寫1.在這里,P0_0需要有相應的定義才可以正確進行,如果移植到其他處理機,做相應的更改就可以了。
一般來說,IO線都有復用的功能,建議使用不同的宏分開設置。如下圖所示:
前兩個是針對普通GPIO來設置的宏,功能也是類似的,向port的pin管腳輸出val值。
后兩個是針對peripheral function的GPIO來設置的宏。
轉(zhuǎn)載于:https://www.cnblogs.com/cherishui/p/3868214.html
總結(jié)
以上是生活随笔為你收集整理的ZStack中的编程技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python的logging模块
- 下一篇: hive操作DML