modbus協議詳解二
阿新 • • 發佈:2020-12-30
modbus的程式碼實現有很多開源的版本,比如libmodbus、freemodbus等。下面講解一下freemodbus的原始碼,因為freemodbus更適合微控制器的應用場景。
freemodbus原始碼下載,原始碼的講解從串列埠接收開始,怎麼接收到一幀資料,進而怎麼解析,怎麼執行動作。
一、串列埠接收部分和定時器部分
串列埠接收狀態機:
//這個函式在串列埠接收中斷中被呼叫
BOOL xMBRTUReceiveFSM( void )
{
BOOL xTaskNeedSwitch = FALSE;
UCHAR ucByte;
assert( eSndState == STATE_TX_IDLE );
/* Always read the character. */
// 讀串列埠資料的函式,位元組資料存放在ucByte中。
( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );
// eRcvState初始值是STATE_RX_INIT,但是如果總線上出現空閒時間超過t3.5,
// 定時器中斷函式會將eRcvState設定為STATE_RX_IDLE,開始正常存串列埠資料到ucRTUBuf[]
// 這樣做的目的就是為了將 啟動modbus接收資料 延遲到匯流排空閒,也就是說程式上電後要等待匯流排空閒才可接收資料。
// 這種延遲只是在上電後發生一次。
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( );
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:
usRcvBufferPos = 0;
ucRTUBuf[usRcvBufferPos++] = ucByte;
eRcvState = STATE_RX_RCV;
/* Enable t3.5 timers. */
vMBPortTimersEnable( );
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;
}
vMBPortTimersEnable( );
break;
}
return xTaskNeedSwitch;
}
定時器中斷函式:
BOOL xMBRTUTimerT35Expired( void )
{
BOOL xNeedPoll = FALSE;
switch ( eRcvState )
{
/* 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. */
// 在接收過程中出現定時時間到,說明一幀資料接收完畢,傳送一個幀資料接收完畢的事件,待處理
// 到這裡就有了一幀完整的資料,已經存放在ucRTUBuf[]陣列中了,接著開始解析。
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( );
// 只要t3.5時間到,串列埠的接收狀態就設定為空閒STATE_RX_IDLE
eRcvState = STATE_RX_IDLE;
return xNeedPoll;
}
二、解析資料部分
eMBPoll這個函式一直輪詢定時器中斷函式中傳送過來的事件
傳送事件函式:xMBPortEventPost()
接收事件函式:xMBPortEventGet()
eMBErrorCode eMBPoll( void )
{
static UCHAR *ucMBFrame;
static UCHAR ucRcvAddress;
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;
// 接收完一幀資料,peMBFrameReceiveCur這個函式將PDU部分提取到ucMBFrame中,
// 並且將從裝置地址提取到ucRcvAddress中,進行比對,一致則傳送執行命令的事件,進入到EV_EXECUTE
case EV_FRAME_RECEIVED:
eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
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 );
}
}
break;
// 開始執行功能碼對應的函式,功能碼和對應函式的對映在初始化時分配好了。
case 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 );
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 );
}
break;
case EV_FRAME_SENT:
break;
}
}
return MB_ENOERR;
}
三、執行功能碼對應的函式
功能碼對應的函式分別在下列檔案中:
到此 協議解析完成並執行了相應的讀寫動作。