freemodbus-v1.5.0 源碼分析


 注:轉載請注明出處   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源碼詳解-程序設計  

Freemodbus完全分析_百度文庫  

FreeModbus學習筆記_百度文庫 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM