這個週末一直在調試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;
}