一、設計思想
任何通信協議的實現都是基於狀態機的設計思想,就是來了一串數據判斷是是干啥的在調用相應的處理函數只不過高手一般采用回調處理。
- 如果你熟悉了回調、源碼里的狀態機的實現又可以理解,那么恭喜你已經掌握了通信協議的實現方法。
- 如果你可以從源媽里體會到分層的設計思想,那么恭喜你已經觸碰到了架構師的門檻。
本系列文章就是通過對FreeModeBus源碼進行解析來掌握以上技能。
二、ModBus協議簡介以及狀態機的實現
為啥把ModBus協議簡介與狀態機的實現放在一起呢???
狀態機編寫當然是基於通信協議標准。
如果通信數據格式、功能碼、處理流程都搞不清楚,你編出來的狀態機也是你“個人版的通信協議標准”,與符合ModBus通信協議標准的設備也是通信不上的。
1、 ModBus 設備間通信處理流程如下:
- 主設備向從設備發送請求
- 從設備分析並處理主設備的請求,然后向主設備發送結果
- 如果出現任何差錯,從設備將返回一個異常功能碼
這里不對ModBus協議進行過多介紹,網上有很多資料。但有個疑點還是有必要搞清楚。在ModbusTCP里什么是主站(主設備)?什么是從站(從設備)??
我相信會有人不假思索地說主站不就是服務器,從站不就是客戶端碼???
哈哈,如果你仔細讀了 ModBus 設備間通信處理流程的話,你可以搞懂的。俺老李就不老生常談了。
2、狀態機的實現解析
首先從網上下載modbus協議源碼文件,打開文件夾如下:
首先使用Source Insight編輯器打開mb.c文件(一般重要的文件放在工程的根目錄下相相當於main函數,這個和人類的編程習慣有關)。
打開之后。嗯分析文件之前先說下狀態機,狀態機的運行是要輪詢poll是否建立modbus通信、判斷通信數據是接收請求還是請求響應。
注意關鍵字poll在編輯器的左下角找到帶有poll的函數。
就是eMBPoll函數,這里面就有實現設備間Modbus通信狀態機源代碼以及相應的簡單注釋如下所示:
1 /* Check if the protocol stack is ready. */
//判斷協議棧硬件環境有沒有准備。好比如ModBusTCP協議里基於W5500以太網芯片的初始化,sockt通信建立提供TCP服務。 2 if( eMBState != STATE_ENABLED ) 3 { 4 return MB_EILLSTATE; 5 } 6 7 /* Check if there is a event available. If not return control to caller. 8 * Otherwise we will handle the event. */
//通過xMBPortEventGet函數處理如果有事件發生得到狀態機的狀態值並賦值給eEvent,
//然后通過switch語句運行狀態機調用相應函數進行處理,當然這里用了函數指針、回調。 9 if( xMBPortEventGet( &eEvent ) == TRUE ) 10 {
// 11 switch ( eEvent ) 12 { 13 case EV_READY: 14 break; 15 16 case EV_FRAME_RECEIVED: 17 eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); 18 if( eStatus == MB_ENOERR ) 19 { 20 /* Check if the frame is for us. If not ignore the frame. */ 21 if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ) 22 { 23 ( void )xMBPortEventPost( EV_EXECUTE ); 24 } 25 } 26 break; 27 28 case EV_EXECUTE: 29 ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; 30 eException = MB_EX_ILLEGAL_FUNCTION; 31 for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) 32 { 33 /* No more function handlers registered. Abort. */ 34 if( xFuncHandlers[i].ucFunctionCode == 0 ) 35 { 36 break; 37 } 38 else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) 39 { 40 eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); 41 break; 42 } 43 } 44 45 /* If the request was not sent to the broadcast address we 46 * return a reply. */ 47 if( ucRcvAddress != MB_ADDRESS_BROADCAST ) 48 { 49 if( eException != MB_EX_NONE ) 50 { 51 /* An exception occured. Build an error frame. */ 52 usLength = 0; 53 ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR ); 54 ucMBFrame[usLength++] = eException; 55 } 56 if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ) 57 { 58 vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ); 59 } 60 eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); 61 } 62 break; 63 64 case EV_FRAME_SENT: 65 break; 66 } 67 }
從程序源碼里面解析框架圖如下:
大家可以根據上圖理解狀態機的結構框架,本文解析到此結束。關於相關在狀態機里面怎么回調相關處理函數的下章解析。