这个周末一直在调试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;
}