FreeModbus串口移植因为要考虑到实时性,这部分还是很重要的。为了保证实时,使用中断还是很有必要的。下面以RTU的工作模式为例。如有不对,欢迎指正。
BOOL xMBPortSerialInit( UCHAR ucPort, ULONG ulBaudRate,
UCHAR ucDataBits, eMBParity eParity );//串口初始化
void vMBPortClose( void );//关闭串口
void xMBPortSerialClose( void );//这个一般不需要实现
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable );//是能串口的TX,RX
BOOL xMBPortSerialGetByte( CHAR * pucByte );//从缓冲区获取一个字节
BOOL xMBPortSerialPutByte( CHAR ucByte );//设置一个字节到缓冲区中
串口接收移植是最重要的,如果移植不太好,会大大的影响系统稳定性。为了保证实时性xMBRTUReceiveFSM()最好在串口中断服务程序中处理。同时xMBPortSerialGetByte()直接读取串口的数据寄存器,最好不要干其它事情
,这样的话就连续不断的把串口数据接收到RTU中。
xMBRTUReceiveFSM( void )
{
BOOL xTaskNeedSwitch = FALSE;
UCHAR ucByte;
assert( eSndState == STATE_TX_IDLE );
/* Always read the character. */
//每来一次中断,读取一次数据,这里会从串口接收数据寄存器中取出数据.
( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );
switch ( eRcvState )
{
case STATE_RX_INIT:
vMBPortTimersEnable( );
break;
case STATE_RX_ERROR:
vMBPortTimersEnable( );
break;
case STATE_RX_IDLE://空闲时为这个状态
usRcvBufferPos = 0;
ucRTUBuf[usRcvBufferPos++] = ucByte;
eRcvState = STATE_RX_RCV;
/* Enable t3.5 timers. */
vMBPortTimersEnable( );
break;
case STATE_RX_RCV:
if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
{
ucRTUBuf[usRcvBufferPos++] = ucByte;
}
else
{
eRcvState = STATE_RX_ERROR;
}
vMBPortTimersEnable( ); //重新设置下定时器,即重新计时,准备接收下一个字节。
break;
}
return xTaskNeedSwitch;
}
直接读取串口数据寄存器,最好不要有其它操作。
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = USARTX->DR;
return TRUE;
}
直接在串口中断服务程序中处理,最好不要有其它操作。
void UsartHandleISR()
{
if (is_receive)//判断是哪种中断
pxMBFrameCBByteReceived()
}
由于是发送数据,主动权掌握在发送端。虽然理论上只要把数据发送出去就可以了,但是接收端也同样会检测帧间隔。如果帧间隔控制不好,会导致接收端丢帧。目前有2种方案,一种软件轮询方案,我在本地验证还是很好用的,另外一个中断的,等用stm32验证下,到时在更新下博文。
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: //启动发送之前,发送状态机eSndState = STATE_TX_XMIT
/* check if we are finished. */
if( usSndBufferCount != 0 )
{
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++; /* next byte in sendbuffer. */
usSndBufferCount--;
}
else
{ //到这里数据已经发送完成,设置状态机为EV_FRAME_SENT
xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
/* Disable transmitter. This prevents another transmit buffer
* empty interrupt. */
vMBPortSerialEnable( TRUE, FALSE );
eSndState = STATE_TX_IDLE;
}
break;
}
return xNeedPoll;
}
单字节发送xMBPortSerialPutByte(),一般直接写发送寄存器。
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
USARTX->DR = ucByte;
return TRUE;
}
mb.c中的eMBPoll( void )
一般都是在一个while循环中,而该函数会源源不断的读取消息队列,如果消息队列为空,则会执行发送流程。一般走到send流程,消息队列中是没有消息处理的。如下添加一个xMBPortSerialPoll()
函数,去循环处理发送操作,此接口会暴露给portevent.c。
BOOL
xMBPortSerialPoll( )
{
if( bTxEnabled )
{
while( bTxEnabled )
{
( void )pxMBFrameCBTransmitterEmpty( );
/* Call the modbus stack to let him fill the buffer. */
//这里最好加个超时机制
}
}
}
//mb.c
BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
if( xEventInQueue ) //没
{
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
else
{
( void )xMBPortSerialPoll( ); //这里取执行发送过程
}
return xEventHappened;
}
如下面即为eMBPoll()触发写操作的过程
eMBErrorCode
eMBPoll( void )
{
.....
if( xMBPortEventGet( &eEvent ) == TRUE )//在这里执行发送流程
{
switch ( eEvent )
{
case EV_READY:
break;
case EV_FRAME_RECEIVED:
break;
}
}
}
放到串口中断服务程序中执行,实时性还是很有保证,不过就怕产生中断时,上一次中断服务程序还没有执行完毕,一些中断标记位还没来得及置位。
void UsartHandleISR()
{
if (is_send) {//判断是哪种中断
pxMBFrameCBTransmitterEmpty()
clear_int_bit();//示例,千万不要忘了清除相关中断标志位
}
}
虽然想使用中断服务程序去处理发送流程,但总需要有人去发数据,激活中断服务吧。为此需要在eMBRTUSend()发送第一个字节,修改如下。
eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
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;
vMBPortSerialEnable( FALSE, TRUE );
//前面的数据和串口都已经准备就绪,启动发送第一个字节,触发发送中断,启动发送发动机。
xMBRTUTransmitFSM();
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}