FreeModbus串口移植因为要考虑到实时性,这部分还是很重要的。为了保证实时,使用中断还是很有必要的。下面以RTU的工作模式为例。如有不对,欢迎指正。


文章目录

  • 1. 串口接口描述
  • 2. 串口接收方法xMBRTUReceiveFSM()
  • 2.1 串口xMBPortSerialGetByte()移植示例
  • 2.2 xMBRTUReceiveFSM移植示例
  • 3. 串口发送方法xMBRTUTransmitFSM()
  • 3.1 软件循环发送xMBRTUTransmitFSM()
  • 3.2 发送函数添加到串口中断服务中


1. 串口接口描述

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 );//设置一个字节到缓冲区中

2. 串口接收方法xMBRTUReceiveFSM()

串口接收移植是最重要的,如果移植不太好,会大大的影响系统稳定性。为了保证实时性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;
}

2.1 串口xMBPortSerialGetByte()移植示例

直接读取串口数据寄存器,最好不要有其它操作。

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = USARTX->DR;
    return TRUE;
}

2.2 xMBRTUReceiveFSM移植示例

直接在串口中断服务程序中处理,最好不要有其它操作。

void UsartHandleISR()
{
	if (is_receive)//判断是哪种中断
		pxMBFrameCBByteReceived()
}

3. 串口发送方法xMBRTUTransmitFSM()

由于是发送数据,主动权掌握在发送端。虽然理论上只要把数据发送出去就可以了,但是接收端也同样会检测帧间隔。如果帧间隔控制不好,会导致接收端丢帧。目前有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;
}

3.1 软件循环发送xMBRTUTransmitFSM()

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

3.2 发送函数添加到串口中断服务中

放到串口中断服务程序中执行,实时性还是很有保证,不过就怕产生中断时,上一次中断服务程序还没有执行完毕,一些中断标记位还没来得及置位。

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