FreeModBus主從機聯調

這個週末一直在調試FreeModBus,事先已經對ModBus協議有了初步認識,並且也閱讀過FreeModBus源代碼。看着代碼很簡單,本以爲半天功夫就可以移植後,可確花了2天時間。現在整理下調試筆記。

  • 主機復位後發送請求數據,然後進入無休止的發送狀態
  • 在定時器時間調試不完全的情況下,容易出現斷言錯誤
  • 接收模式時有時會接收到無效幀
  • T35_50US時序調整

1.主機復位後發送請求數據,然後進入無休止的發送狀態

此中情況發生在Master設備上,主要是由於發送數據打包後,我們需要發送一個(void)xMBPortEventPost(EV_FRAME_SENT);的消息給eMBPoll狀態機,然後對數據添加目的地址和CRC校驗碼。緊接着調用xMBRTUTransmitFSM將數據發送出去。但是這將會導致會重新發送MBPortEventPost( EV_FRAME_SENT );消息,導致在下一個eMBPoll被調用時,又發送數據。如此循環發送數據。所以需要把EV_FRAME_SENT消息屏蔽掉。

@@ -293,7 +317,7 @@ xMBRTUTransmitFSM( void )
     BOOL            xNeedPoll = FALSE;

     assert( eRcvState == STATE_RX_IDLE );
-
+    //LOGD("sndstate:%d.sndCnt:%d",eSndState,usSndBufferCount);
     switch ( eSndState )
     {
         /* We should not get a transmitter event if the transmitter is in
@@ -305,6 +329,7 @@ xMBRTUTransmitFSM( void )

     case STATE_TX_XMIT:
         /* check if we are finished. */
+        //LOGD("SndBufferCur:0x%x,sndBuffCnt:%d",pucSndBufferCur,usSndBufferCount);
         if( usSndBufferCount != 0 )
         {
             xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
@@ -313,11 +338,17 @@ xMBRTUTransmitFSM( void )
         }
         else
         {
+            //如果是主設備,這裏需要屏蔽掉。要不然會導致主循環poll中一直髮送數據
+            //當時調試時,一直在發送數據,停不下來了。我在POLL中實現了EV_FRAME_SENT case
+#ifndef MASTER_DEVICE
             xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
+#else
+            xNeedPoll = TRUE;
+#endif
             /* Disable transmitter. This prevents another transmit buffer
              * empty interrupt. */
             //這裏將eSndState 提前置爲STATE_TX_IDLE,主要是由於在T35_50US沒有調試好的情況下,在下次
             //使能接收中斷後,如果此時從設備發送了數據,主設備就進入了錯誤的狀態,一去不復返。
+            eSndState = STATE_TX_IDLE; //move this code front
             vMBPortSerialEnable( TRUE, FALSE );
-            eSndState = STATE_TX_IDLE;
         }
         break;
     }

3.接收模式時有時會接收到無效幀

接收到無效幀,不處理就是了。這裏在定時器超時函數中,會檢測接收到的數據長度。最短的幀就是異常幀了。設備地址+功能碼(異常功能碼)+異常數據+CRC16校驗數據,總共5個字節。所以下面如果檢測到接收的數據少於5個字節,就不要發送EV_FRAME_RECEIVED事件。

@@ -330,6 +361,7 @@ xMBRTUTimerT35Expired( void )
 {
     BOOL            xNeedPoll = FALSE;

+    LOGD("recvState:%d", eRcvState);
     switch ( eRcvState )
     {
         /* Timer t35 expired. Startup phase is finished. */
@@ -342,7 +374,12 @@ xMBRTUTimerT35Expired( void )
     case STATE_RX_RCV:
         //這裏添加接收數據清零操作-爲下次接收數據做準備
         xMBPortSerialClearRecvCount();
-        xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
+        if (usRcvBufferPos >= 5) {
+            DEBUG(("usRcvBufferPos:%d",usRcvBufferPos));
+            xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
+        } else {
+            xNeedPoll = TRUE;
+        }
         break;

3.T35_50US時間調整

這個時序調整上花了不少時間。首先FreeModBus作者當時想的是如果幀間超過3.5個字符沒有發送數據就以爲後面沒有數據了。但是在調試時3.5個字符時間,真的是太短了,沒等你發過來就超時了。所以這個要根據具體硬件的性能來做響應調整。有價值的都在下面代碼註釋中。

/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    ULONG           usTimerT35_50us;

    ( void )ucSlaveAddress;
    ENTER_CRITICAL_SECTION(  );

    /* Modbus RTU uses 8 Databits. */
    if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
    {
        eStatus = MB_EPORTERR;
    }
    else
    {
        /* If baudrate > 19200 then we should use the fixed timer values
         * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
         */
        //t35:即爲在制定波特率下,傳輸3.5個字符需要的時間
        //從上面的說明,如果波特率超過19200,t35超時要設置爲一個固定值1750us,
        //如果低於19200,就可以使用動態的t35.
        if( ulBaudRate > 19200 )
        {
            //usTimerT35_50us = 35 + 165;       /* 1800us. */
            //usTimerT35_50us = 210;       /* 1800us. */
            usTimerT35_50us = 20;       /* 1800us. */
        }
        else
        {
            /* The timer reload value for a character is given by:
             *
             * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
             *             = 11 * Ticks_per_1s / Baudrate
             *             = 220000 / Baudrate
             * The reload for t3.5 is 1.5 times this value and similary
             * for t3.5.
             */
            //其實下面的公式是這麼來的,
            /* 1.傳輸一個bit的時間爲1/baudRate,
             * 2.一個字符的傳輸一般包括1個起始,8位數據,1個停止位,有時候還有1個校驗位
             * 3.則傳入一個字符時間爲11/baudRate那麼傳輸3.5個字符需要的時間爲3.5 * 11/baudRate
             * 4.轉換成us,分子分母同時乘2,然後分子乘1000000,3.5 * 11* 2* 1000000) / (2 * baudRate)
             * 5.繼而 7 * 11 * 20000 * 50 / (2 * baudRate)
             * 6 然後((7*220000) / (2 * baudRate))* 50us,可以看到usTimerT35_50us是
             * 以50us爲基準的。那麼我們就要設置定時器爲50us基準了。
             * */
            usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
        }
        if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
        {
            LOGE("timer init failed");
            eStatus = MB_EPORTERR;
        }
    }
    EXIT_CRITICAL_SECTION(  );

    return eStatus;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章