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

你可能感兴趣的:(ModBus)