1. 程式人生 > 其它 >modbus協議詳解二

modbus協議詳解二

技術標籤:匯流排協議微控制器串列埠通訊嵌入式

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;
}

三、執行功能碼對應的函式

功能碼對應的函式分別在下列檔案中:
在這裡插入圖片描述
到此 協議解析完成並執行了相應的讀寫動作。