注:轉載請注明出處 http://www.cnblogs.com/wujing-hubei/p/5935142.html
FreeModbus協議棧作為從機,等待主機傳送的數據,當從機接收到一幀完整的報文后,對報文進行解析,然后響應主機,發送報文給主機,實現主機和從機之間的通信。
1、初始化協議棧---eMBInit函數(mb.c中),以RTU為例
eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
/*函數功能:
*1:實現RTU模式和ASCALL模式的協議棧初始化;
*2:完成協議棧核心函數指針的賦值,包括Modbus協議棧的使能和禁止、報文的接收和響應、3.5T定時器中斷回調函數、串口發送和接收中斷回調函數;
*3:eMBRTUInit完成RTU模式下串口和3.5T定時器的初始化,需用戶自己移植;
*4:設置Modbus協議棧的模式eMBCurrentMode為MB_RTU,設置Modbus協議棧狀態eMBState為STATE_DISABLED;
*/
eMBErrorCode eStatus = MB_ENOERR;
/* check preconditions */ if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) || ( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) ) //驗證從機地址 { eStatus = MB_EINVAL; //地址錯誤 } else { ucMBAddress = ucSlaveAddress; switch ( eMode ) { #if MB_RTU_ENABLED > 0 case MB_RTU: pvMBFrameStartCur = eMBRTUStart; pvMBFrameStopCur = eMBRTUStop; peMBFrameSendCur = eMBRTUSend; peMBFrameReceiveCur = eMBRTUReceive; pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; pxMBFrameCBByteReceived = xMBRTUReceiveFSM; //接收狀態機,串口接受中斷最終調用此函數接收數據 pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM; //發送狀態機,串口發送中斷最終調用此函數發送數據 pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; //報文到達間隔檢查,定時器中斷函數最終調用次函數完成定時器中斷 eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity ); break; #endif #if MB_ASCII_ENABLED > 0 case MB_ASCII: pvMBFrameStartCur = eMBASCIIStart; pvMBFrameStopCur = eMBASCIIStop; peMBFrameSendCur = eMBASCIISend; peMBFrameReceiveCur = eMBASCIIReceive; pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; pxMBFrameCBByteReceived = xMBASCIIReceiveFSM; pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM; pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired; eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity ); break; #endif default: eStatus = MB_EINVAL; } if( eStatus == MB_ENOERR ) { if( !xMBPortEventInit( ) ) { /* port dependent event module initalization failed. */ eStatus = MB_EPORTERR; } else {//設定當前狀態 eMBCurrentMode = eMode; //設定RTU模式 eMBState = STATE_DISABLED; //modbus協議棧初始化狀態,在此初始化為禁止 } } } return eStatus; }
eMBInit函數中pvMBFrameStartCur、pvMBFrameStartCur等為協議棧函數的接口,對於不同的通信模式使用不同的函數進行初始化!!!此編程模式可以借鑒學習!!!
eMBInit函數對底層驅動(串口和定時器)進行初始化。初始化完成並且成功之后對事件也進行了初始化,完成后全局變量eMBState=STATE_DISABLED。
2、啟動協議棧----eMBEnable函數(mb.c函數)
eMBErrorCode eMBEnable( void ) { /*函數功能: *1:實現RTU模式和ASCALL模式的協議棧初始化; *2:完成協議棧核心函數指針的賦值,包括Modbus協議棧的使能和禁止、報文的接收和響應、3.5T定時器中斷回調函數、串口發送和接收中斷回調函數; *3:eMBRTUInit完成RTU模式下串口和3.5T定時器的初始化,需用戶自己移植; *4:設置Modbus協議棧的模式eMBCurrentMode為MB_RTU,設置Modbus協議棧狀態eMBState為STATE_DISABLED; */ eMBErrorCode eStatus = MB_ENOERR; if( eMBState == STATE_DISABLED ) { /* Activate the protocol stack. */ pvMBFrameStartCur( ); //激活協議棧 eMBState = STATE_ENABLED; //設置Modbus協議棧工作狀態eMBState為STATE_ENABLED } else { eStatus = MB_EILLSTATE; } return eStatus; }
---eMBRTUStart函數 (mbrtu.c)
void eMBRTUStart( void ) { /*函數功能 * 1:設置接收狀態機eRcvState為STATE_RX_INIT; * 2:使能串口接收,禁止串口發送,作為從機,等待主機傳送的數據; * 3:開啟定時器,3.5T時間后定時器發生第一次中斷,此時eRcvState為STATE_RX_INIT,上報初始化完成事件,然后設置eRcvState為空閑STATE_RX_IDLE; * 4:每次進入3.5T定時器中斷,定時器被禁止,等待串口有字節接收后,才使能定時器; * */ ENTER_CRITICAL_SECTION( ); /* Initially the receiver is in the state STATE_RX_INIT. we start * the timer and if no character is received within t3.5 we change * to STATE_RX_IDLE. This makes sure that we delay startup of the * modbus protocol stack until the bus is free. */ eRcvState = STATE_RX_INIT; vMBPortSerialEnable( TRUE, FALSE ); //開啟串口接收,發送未開啟 vMBPortTimersEnable( ); //啟動定時器 EXIT_CRITICAL_SECTION( ); }
eMBEnable函數啟動協議棧pvMBFrameStartCur,對於RTU模式,pvMBFrameStartCur函數指針指向 eMBRTUStart,在eMBRTUStart函數中,全局變量eRcvState=STATE_RX_INIT,並使能串口和定時器。注意!!!此時定時器將開始工作!!!。eMBEnable函數中將把全局變量改為 eMBState=STATE_ENABLED。
3、狀態機輪訓---eMBPoll函數(mb.c)
eMBErrorCode eMBPoll( void ) {
/*函數功能:
*1:檢查協議棧狀態是否使能,eMBState初值為STATE_NOT_INITIALIZED,在eMBInit()函數中被賦值為STATE_DISABLED,在eMBEnable函數中被賦值為STATE_ENABLE;
*2:輪詢EV_FRAME_RECEIVED事件發生,若EV_FRAME_RECEIVED事件發生,接收一幀報文數據,上報EV_EXECUTE事件,解析一幀報文,響應(發送)一幀數據給主機;
*/
static UCHAR *ucMBFrame; //接收和發送報文數據緩存區 static UCHAR ucRcvAddress; //modbus從機地址 static UCHAR ucFunctionCode; //功能碼 static USHORT usLength; //報文長度 static eMBException eException; //錯誤碼響應 枚舉 int i; eMBErrorCode eStatus = MB_ENOERR; eMBEventType eEvent; //錯誤碼 /* Check if the protocol stack is ready. */ if( eMBState != STATE_ENABLED ) //檢查協議棧是否使能 { return MB_EILLSTATE; //協議棧未使能,返回協議棧無效錯誤碼 } /* Check if there is a event available. If not return control to caller. * Otherwise we will handle the event. */ if( xMBPortEventGet( &eEvent ) == TRUE ) //判斷事件是否發生 { switch ( eEvent ) //查詢哪個事件發生 { case EV_READY: break; case EV_FRAME_RECEIVED: //接收到一幀數據,此事件發生 eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); //接收數據,並檢驗報文長度和CRC校驗是否正確 /* * ucRcvAddress 主站要讀取的從站的地址 * ucMBFrame 指向PDU的頭部 * usLength PDU的長度 */ if( eStatus == MB_ENOERR ) { /* Check if the frame is for us. If not ignore the frame. */ if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ) { ( void )xMBPortEventPost( EV_EXECUTE ); //修改事件標志為EV_EXECUTE執行事件 } } break; case EV_EXECUTE: //修改事件標志為EV_EXECUTE執行事件 ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; //提取功能碼 eException = MB_EX_ILLEGAL_FUNCTION; //賦錯誤碼初值為無效的功能碼 for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) { /* No more function handlers registered. Abort. */ if( xFuncHandlers[i].ucFunctionCode == 0 ) { break; } else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) //根據報文中的功能碼,處理報文 { eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); //對接收到的報文進行解析 break; } } /* If the request was not sent to the broadcast address we * return a reply. */ if( ucRcvAddress != MB_ADDRESS_BROADCAST ) { if( eException != MB_EX_NONE ) //接收到的報文有錯誤 { /* An exception occured. Build an error frame. */ usLength = 0; //響應發送數據的首字節為從機地址 ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR ); //響應發送數據幀的第二個字節,功能碼最高位置1 ucMBFrame[usLength++] = eException; //響應發送數據幀的第三個字節為錯誤碼標識 } if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ) { vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ); } eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); //modbus從機響應函數,發送響應給主機 } break; case EV_FRAME_SENT: break; } } return MB_ENOERR; }
在eMBPoll函數中,首先由 xMBPortEventGet( &eEvent ) == TRUE 判斷時間是否發生,若無事件發生則不進入狀態機;若有時間發生則進入狀態機開始輪詢。狀態機的時間轉換在定時中斷服務函數中實現。
在eMBEnable函數中啟動定時器后,定時器開始工作(見 eMBRTUStart函數), 在定時器第一次超時之后將會發送xNeedPoll = xMBPortEventPost( EV_READY ) 事件,然后關閉定時器,接收機狀態(全局變量)eRcvState為STATE_RX_IDLE。此時,主循環eMBPoll中將執行一次EV_READY下的操作。至此,完成Modbus協議棧的初始化准備工作,協議棧開始運行,eMBPoll()函數輪詢等待接收完成事件發生。
---定時器中斷服務函數
void BOARD_GPTA_HANDLER() { // BOOL bTaskWoken = FALSE; PRINTF("\r\nTimer Expired:\n\n\r"); vMBPortSetWithinException( TRUE ); GPT_ClearStatusFlag(BOARD_GPTA_BASEADDR, gptStatusFlagOutputCompare1); //關閉定時器
( void )pxMBPortCBTimerExpired( ); //事件轉換 vMBPortSetWithinException( FALSE );
}
---xMBRTUTimerT35Expired T3.5超時函數
BOOL xMBRTUTimerT35Expired( void ) { /* 函數功能 * 1:從機接受完成一幀數據后,接收狀態機eRcvState為STATE_RX_RCV; * 2:上報“接收到報文”事件(EV_FRAME_RECEIVED); * 3:禁止3.5T定時器,設置接收狀態機eRcvState狀態為STATE_RX_IDLE空閑; */ BOOL xNeedPoll = FALSE; switch ( eRcvState ) //上報modbus協議棧的事件狀態給poll函數 { /* Timer t35 expired. Startup phase is finished. */ case STATE_RX_INIT: xNeedPoll = xMBPortEventPost( EV_READY ); //初始化完成事件 break; /* A frame was received and t35 expired. Notify the listener that * a new frame was received. */ case STATE_RX_RCV: //一幀數據接收完成 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); //上報協議棧事件,接收到一幀完整的數據 break; /* An error occured while receiving the frame. */ case STATE_RX_ERROR: break; /* Function called in an illegal state. */ default: assert( ( eRcvState == STATE_RX_INIT ) || ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); } vMBPortTimersDisable( ); //當接收到一幀數據后,禁止3.5T定時器,直到接受下一幀數據開始,開始計時 eRcvState = STATE_RX_IDLE; //處理完一幀數據,接收器狀態為空閑 return xNeedPoll; }
4、報文接收
在定時器第一次中斷之后,狀態機為eRcvState=STATE_RX_IDLE,即讀空閑狀態,eMBPoll也阻塞在等待接收完成事件發生。而在eMBPoll之前的eMBRTUStart函數中已經開啟了串口中斷,因此在接收到數據之后,串口中斷將會響應,在串口中斷服務函數中將調用接收狀態機函數xMBRTUReceiveFSM來接收數據。
---xMBRTUReceiveFSM函數
BOOL xMBRTUReceiveFSM( void ) { /*函數功能 *1:將接收到的數據存入ucRTUBuf[]中; *2:usRcvBufferPos為全局變量,表示接收數據的個數; *3:每接收到一個字節的數據,3.5T定時器清0 */ BOOL xTaskNeedSwitch = FALSE; UCHAR ucByte; assert( eSndState == STATE_TX_IDLE ); //確保沒有數據在發送 /* Always read the character. */ ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); //從串口數據寄存器讀取一個字節數據 switch ( eRcvState ) //根據不同的狀態轉移 { /* If we have received a character in the init state we have to * wait until the frame is finished. */ case STATE_RX_INIT: vMBPortTimersEnable( ); //開啟3.5T定時器 break; /* In the error state we wait until all characters in the * damaged frame are transmitted. */ case STATE_RX_ERROR: //數據幀被損壞,重啟定時器,不保存串口接收的數據 vMBPortTimersEnable( ); break; /* In the idle state we wait for a new character. If a character * is received the t1.5 and t3.5 timers are started and the * receiver is in the state STATE_RX_RECEIVCE. */ case STATE_RX_IDLE: // 接收器空閑,開始接收,進入STATE_RX_RCV狀態 usRcvBufferPos = 0; ucRTUBuf[usRcvBufferPos++] = ucByte; //保存數據 eRcvState = STATE_RX_RCV; /* Enable t3.5 timers. */ vMBPortTimersEnable( ); //每收到一個字節,都重啟3.5T定時器 break; /* We are currently receiving a frame. Reset the timer after * every character received. If more than the maximum possible * number of bytes in a modbus frame is received the frame is * ignored. */ case STATE_RX_RCV: if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) { ucRTUBuf[usRcvBufferPos++] = ucByte; //接收數據 } else { eRcvState = STATE_RX_ERROR; //一幀報文的字節數大於最大PDU長度,忽略超出的數據 } vMBPortTimersEnable( ); //每收到一個字節,都重啟3.5T定時器 break; } return xTaskNeedSwitch; }
在串口中斷前,狀態機為eRcvState=STATE_RX_IDLE,接收狀態機開始后,讀取uart串口緩存中的數據,並進入STATE_RX_IDLE分支中存儲一次數據后開啟定時器,然后進入STATE_RX_RCV分支繼續接收后續的數據,直至定時器超時!如果沒有超時的話,狀態不會轉換,將還可以繼續接收數據。超時之后,在T3.5超時函數xMBRTUTimerT35Expired 中將發送EV_FRAME_RECEIVED事件。然后eMBPoll函數將會調用eMBRTUReceive函數。
/* A frame was received and t35 expired. Notify the listener that * a new frame was received. */ case STATE_RX_RCV: //一幀數據接收完成 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); //上報協議棧事件,接收到一幀完整的數據 break;
---eMBRTUReceive函數
eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) { /*eMBPoll函數輪詢到EV_FRAME_RECEIVED事件時,調用peMBFrameReceiveCur(), * 此函數是用戶為函數指針peMBFrameReceiveCur()的賦值, * 此函數完成的功能: * 從一幀數據報文中,取得modbus從機地址給pucRcvAddress、PDU報文的長度給pusLength, * PDU報文的首地址給pucFrame,函數*形參全部為地址傳遞, */ BOOL xFrameReceived = FALSE; eMBErrorCode eStatus = MB_ENOERR; ENTER_CRITICAL_SECTION( ); assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); //斷言宏,判斷接收到的字節數<256,如果>256,終止程序 /* Length and CRC check */ if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ) { /* Save the address field. All frames are passed to the upper layed * and the decision if a frame is used is done there. */ *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; //保存從站地址 /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus * size of address field and CRC checksum. */ *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); //PDU長度 /* Return the start of the Modbus PDU to the caller. */ *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //pucFrame指向PDU起始位置 xFrameReceived = TRUE; } else { eStatus = MB_EIO; } EXIT_CRITICAL_SECTION( ); return eStatus; }
eMBRTUReceive函數完成了CRC校驗、幀數據地址和長度的賦值,便於給上層進行處理!之后eMBPoll函數發送 ( void )xMBPortEventPost( EV_EXECUTE )事件。在EV_EXECUTE 事件中,從站對接收到的數據進行處理,包括根據功能碼尋找功能函數處理報文和調用eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ) 發送應答報文。
--- eMBRTUSend函數
eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) { /*函數功能 * 1:對響應報文PDU前面加上從機地址; * 2:對響應報文PDU后加上CRC校; * 3:使能發送,啟動傳輸;*/ eMBErrorCode eStatus = MB_ENOERR; USHORT usCRC16; ENTER_CRITICAL_SECTION( ); /* Check if the receiver is still in idle state. If not we where to * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if( eRcvState == STATE_RX_IDLE ) { /* First byte before the Modbus-PDU is the slave address. */ pucSndBufferCur = ( UCHAR * ) pucFrame - 1; //在協議數據單元前加從機地址 usSndBufferCount = 1; /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; usSndBufferCount += usLength; /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */ usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); /* Activate the transmitter. */ eSndState = STATE_TX_XMIT; //發送狀態 //以下為新添加 xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); //發送一個字節的數據,進入發送中斷函數,啟動傳輸 pucSndBufferCur++; /* next byte in sendbuffer. */ usSndBufferCount--; vMBPortSerialEnable( FALSE, TRUE ); //使能發送,禁止接收 } else { eStatus = MB_EIO; } EXIT_CRITICAL_SECTION( ); return eStatus; }
在 eMBRTUSend函數中會調用串口發送數據,在進入串口發送中斷后會調用xMBRTUTransmitFSM發送狀態機函數發送應答報文。
---xMBRTUTransmitFSM函數
BOOL xMBRTUTransmitFSM( void ) { BOOL xNeedPoll = FALSE; assert( eRcvState == STATE_RX_IDLE ); switch ( eSndState ) { /* We should not get a transmitter event if the transmitter is in * idle state. */ case STATE_TX_IDLE: /* enable receiver/disable transmitter. */ vMBPortSerialEnable( TRUE, FALSE ); //發送器處於空閑狀態,使能接收,禁止發送 break; case STATE_TX_XMIT: //發送器處於發送狀態,在從機發送函數eMBRTUSend中賦值STATE_TX_XMIT /* check if we are finished. */ if( usSndBufferCount != 0 ) //發送數據 { xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); pucSndBufferCur++; /* next byte in sendbuffer. */ usSndBufferCount--; } else //傳遞任務,發送完成 { xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); //協議棧事件狀態賦值為EV_FRAME_SENT,發送完成事件,eMBPoll函數會對此事件進行處理 /* Disable transmitter. This prevents another transmit buffer * empty interrupt. */ vMBPortSerialEnable( TRUE, FALSE ); //使能接收,禁止發送 eSndState = STATE_TX_IDLE; //發送器狀態為空閑狀態 } break; } return xNeedPoll; }
至此:協議棧准備工作,從機接受報文,解析報文,從機發送響應報文四部分結束。
參考:
freemodbus庫函數詳解 FreeModbus源碼詳解-程序設計