FreeModbus协议栈作为从机,等待主机传送的数据,当从机接收到一帧完整的报文后,对报文进行解析,然后响应主机,发送报文给主机,实现主机和从机之间的通信。

1、初始化协议栈---eMBInit函数(mb.c中),以RTU为例

eMBErrorCode  eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
/*函数功能:
*1:实现RTU模式和ASCALL模式的协议栈初始化;
*2:完成协议栈核心函数指针的赋值,包括Modbus协议栈的使能和禁止、报文的接收和响应、3.5T定时器中断回调函数、串口发送和接收中断回调函数;
*3:eMBRTUInit完成RTU模式下串口和3.5T定时器的初始化,需用户自己移植;
*4:设置Modbus协议栈的模式eMBCurrentMode为MB_RTU,设置Modbus协议栈状态eMBState为STATE_DISABLED;
*/
eMBErrorCode    eStatus MB_ENOERR;
 
*/
    
        ( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) )   验证从机地址
    {
        eStatus = MB_EINVAL;                                  地址错误
    }
    
    {
        ucMBAddress = ucSlaveAddress;

         ( eMode )
        {
#if MB_RTU_ENABLED > 0
         MB_RTU:
            pvMBFrameStartCur = eMBRTUStart;
            pvMBFrameStopCur = eMBRTUStop;
            peMBFrameSendCur = eMBRTUSend;
            peMBFrameReceiveCur = eMBRTUReceive;
            pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
            pxMBFrameCBByteReceived = xMBRTUReceiveFSM;         接收状态机,串口接受中断最终调用此函数接收数据
            pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;    发送状态机,串口发送中断最终调用此函数发送数据
            pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;     报文到达间隔检查,定时器中断函数最终调用次函数完成定时器中断

            eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );
            ;
#endif
#if MB_ASCII_ENABLED > 0
         MB_ASCII:
            pvMBFrameStartCur = eMBASCIIStart;
            pvMBFrameStopCur = eMBASCIIStop;
            peMBFrameSendCur = eMBASCIISend;
            peMBFrameReceiveCur = eMBASCIIReceive;
            pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
            pxMBFrameCBByteReceived = xMBASCIIReceiveFSM;
            pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM;
            pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired;

            eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity );
            ;
#endif
        :
            eStatus = MB_EINVAL;
        }

         MB_ENOERR )
        {
            xMBPortEventInit(  ) )
            {
                
                eStatus = MB_EPORTERR;
            }
            
            {设定当前状态
                eMBCurrentMode = eMode;           设定RTU模式
                eMBState = STATE_DISABLED;        modbus协议栈初始化状态,在此初始化为禁止
            }
        }
    }
     eStatus;
}
此编程模式可以借鉴学习!!!
  eMBInit函数对底层驱动(串口和定时器)进行初始化。初始化完成并且成功之后对事件也进行了初始化,完成后全局变量eMBState=STATE_DISABLED。

 2、启动协议栈----eMBEnable函数(mb.c函数)

eMBErrorCode
eMBEnable(  )
{
    函数功能:
     *1:实现RTU模式和ASCALL模式的协议栈初始化;
     *2:完成协议栈核心函数指针的赋值,包括Modbus协议栈的使能和禁止、报文的接收和响应、3.5T定时器中断回调函数、串口发送和接收中断回调函数;
     *3:eMBRTUInit完成RTU模式下串口和3.5T定时器的初始化,需用户自己移植;
     *4:设置Modbus协议栈的模式eMBCurrentMode为MB_RTU,设置Modbus协议栈状态eMBState为STATE_DISABLED;
     
    eMBErrorCode    eStatus = MB_ENOERR;

     STATE_DISABLED )
    {
        
        pvMBFrameStartCur(  );       激活协议栈
        eMBState = STATE_ENABLED;    设置Modbus协议栈工作状态eMBState为STATE_ENABLED
    }
    
    {
        eStatus = MB_EILLSTATE;
    }
     eStatus;
}

 

---eMBRTUStart函数 (mbrtu.c)

eMBRTUStart(  )
{
    函数功能
     * 1:设置接收状态机eRcvState为STATE_RX_INIT;
     * 2:使能串口接收,禁止串口发送,作为从机,等待主机传送的数据;
     * 3:开启定时器,3.5T时间后定时器发生第一次中断,此时eRcvState为STATE_RX_INIT,上报初始化完成事件,然后设置eRcvState为空闲STATE_RX_IDLE;
     * 4:每次进入3.5T定时器中断,定时器被禁止,等待串口有字节接收后,才使能定时器;
     * 
    ENTER_CRITICAL_SECTION(  );
     Initially the receiver is in the state STATE_RX_INIT. we start
     * the timer and if no character is received within t3.5 we change
     * to STATE_RX_IDLE. This makes sure that we delay startup of the
     * modbus protocol stack until the bus is free.
     
    eRcvState = STATE_RX_INIT;
    vMBPortSerialEnable( TRUE, FALSE );   开启串口接收,发送未开启
    vMBPortTimersEnable(  );              启动定时器

    EXIT_CRITICAL_SECTION(  );
}

此时定时器将开始工作!!!。eMBEnable函数中将把全局变量改为 eMBState=STATE_ENABLED。

 

3、状态机轮训---eMBPoll函数(mb.c)

 )
{
  */
接收和发送报文数据缓存区
    modbus从机地址
    功能码
    报文长度
    错误码响应  枚举

                 i;
    eMBErrorCode    eStatus = MB_ENOERR;
    eMBEventType    eEvent;                 错误码

    */
    检查协议栈是否使能
    {
        协议栈未使能,返回协议栈无效错误码
    }

     Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */

    判断事件是否发生
    {
        查询哪个事件发生
        {
         EV_READY:
            ;

        接收到一帧数据,此事件发生
            eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );  接收数据,并检验报文长度和CRC校验是否正确
            
             * ucRcvAddress 主站要读取的从站的地址
             * ucMBFrame  指向PDU的头部
             * usLength  PDU的长度
             */
             MB_ENOERR )
            {
                */
                 MB_ADDRESS_BROADCAST ) )
                {
                    ( 修改事件标志为EV_EXECUTE执行事件
                }
            }
            ;

        修改事件标志为EV_EXECUTE执行事件
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];  提取功能码
            eException = MB_EX_ILLEGAL_FUNCTION;       赋错误码初值为无效的功能码
             )
            {
                */
                 )
                {
                    ;
                }
                根据报文中的功能码,处理报文
                {
                    eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); 对接收到的报文进行解析
                    ;
                }
            }

             If the request was not sent to the broadcast address we
             * return a reply. */
             MB_ADDRESS_BROADCAST )
            {
                接收到的报文有错误
                {
                    
                    usLength = 响应发送数据的首字节为从机地址
                    ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );  响应发送数据帧的第二个字节,功能码最高位置1
                    ucMBFrame[usLength++] = eException;          响应发送数据帧的第三个字节为错误码标识
                }
                 MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
                {
                    vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
                }                
                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );  modbus从机响应函数,发送响应给主机
            }
            ;

         EV_FRAME_SENT:
            ;
        }
    }
     MB_ENOERR;
}

   

  在eMBPoll函数中,首先由 xMBPortEventGet( &eEvent ) == TRUE 判断时间是否发生,若无事件发生则不进入状态机;若有时间发生则进入状态机开始轮询。状态机的时间转换在定时中断服务函数中实现。

接收完成事件发生。

---定时器中断服务函数

 BOARD_GPTA_HANDLER()
{
    BOOL            bTaskWoken = FALSE; 

    PRINTF();
    vMBPortSetWithinException( TRUE );
    GPT_ClearStatusFlag(BOARD_GPTA_BASEADDR, gptStatusFlagOutputCompare1);  //关闭定时器 
}

 

---xMBRTUTimerT35Expired  T3.5超时函数

 )
{
     函数功能
     * 1:从机接受完成一帧数据后,接收状态机eRcvState为STATE_RX_RCV;
     * 2:上报“接收到报文”事件(EV_FRAME_RECEIVED);
     * 3:禁止3.5T定时器,设置接收状态机eRcvState状态为STATE_RX_IDLE空闲;
     
    BOOL            xNeedPoll = FALSE;

    上报modbus协议栈的事件状态给poll函数
    {
        */
     STATE_RX_INIT:
        xNeedPoll = xMBPortEventPost( EV_READY );   初始化完成事件
        ;

         A frame was received and t35 expired. Notify the listener that
         * a new frame was received. */
    一帧数据接收完成
        xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );    上报协议栈事件,接收到一帧完整的数据
        ;

        */
     STATE_RX_ERROR:
        ;

        */
    :
        assert( ( eRcvState == STATE_RX_INIT ) ||
                ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) );
    }

    vMBPortTimersDisable(  );  当接收到一帧数据后,禁止3.5T定时器,直到接受下一帧数据开始,开始计时
    eRcvState = STATE_RX_IDLE;  处理完一帧数据,接收器状态为空闲

     xNeedPoll;
}

 

4、报文接收

  在定时器第一次中断之后,状态机为eRcvState=STATE_RX_IDLE,即读空闲状态,eMBPoll也阻塞在等待接收完成事件发生。而在eMBPoll之前的eMBRTUStart函数中已经开启了串口中断,因此在接收到数据之后,串口中断将会响应,在串口中断服务函数中将调用接收状态机函数xMBRTUReceiveFSM来接收数据。

---xMBRTUReceiveFSM函数

 )
{
    函数功能
     *1:将接收到的数据存入ucRTUBuf[]中;
     *2:usRcvBufferPos为全局变量,表示接收数据的个数;
     *3:每接收到一个字节的数据,3.5T定时器清0
     

    BOOL            xTaskNeedSwitch = FALSE;
    UCHAR           ucByte;

    assert( eSndState == STATE_TX_IDLE );   确保没有数据在发送

    
    ( 从串口数据寄存器读取一个字节数据

    根据不同的状态转移
    {
         If we have received a character in the init state we have to
         * wait until the frame is finished.
         */
     STATE_RX_INIT:
        vMBPortTimersEnable(  );       开启3.5T定时器
        ;

         In the error state we wait until all characters in the
         * damaged frame are transmitted.
         */
    数据帧被损坏,重启定时器,不保存串口接收的数据
        vMBPortTimersEnable(  );
        ;

         In the idle state we wait for a new character. If a character
         * is received the t1.5 and t3.5 timers are started and the
         * receiver is in the state STATE_RX_RECEIVCE.
         */
     接收器空闲,开始接收,进入STATE_RX_RCV状态
        usRcvBufferPos = ;
        ucRTUBuf[usRcvBufferPos++] = ucByte;    保存数据
        eRcvState = STATE_RX_RCV;

        
        vMBPortTimersEnable(  );          每收到一个字节,都重启3.5T定时器
        ;

         We are currently receiving a frame. Reset the timer after
         * every character received. If more than the maximum possible
         * number of bytes in a modbus frame is received the frame is
         * ignored.
         */
     STATE_RX_RCV:
         MB_SER_PDU_SIZE_MAX )
        {
            ucRTUBuf[usRcvBufferPos++] = ucByte;   接收数据
        }
        
        {
            eRcvState = STATE_RX_ERROR;     一帧报文的字节数大于最大PDU长度,忽略超出的数据
        }
        vMBPortTimersEnable(  );            每收到一个字节,都重启3.5T定时器
        ;
    }
     xTaskNeedSwitch;
}

直至定时器超时!如果没有超时的话,状态不会转换,将还可以继续接收数据。超时之后,在T3.5超时函数xMBRTUTimerT35Expired 中将发送EV_FRAME_RECEIVED事件。然后eMBPoll函数将会调用eMBRTUReceive函数。

 A frame was received and t35 expired. Notify the listener that
         * a new frame was received. */
    一帧数据接收完成
        xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );    上报协议栈事件,接收到一帧完整的数据
        break;

---eMBRTUReceive函数

eMBErrorCode
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{
    eMBPoll函数轮询到EV_FRAME_RECEIVED事件时,调用peMBFrameReceiveCur(),
     * 此函数是用户为函数指针peMBFrameReceiveCur()的赋值,
     * 此函数完成的功能:
     * 从一帧数据报文中,取得modbus从机地址给pucRcvAddress、PDU报文的长度给pusLength,
     * PDU报文的首地址给pucFrame,函数*形参全部为地址传递,
     
    BOOL            xFrameReceived = FALSE;
    eMBErrorCode    eStatus = MB_ENOERR;

    ENTER_CRITICAL_SECTION(  );
    assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );  断言宏,判断接收到的字节数<256,如果>256,终止程序

    */
     MB_SER_PDU_SIZE_MIN )
        && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) ==  ) )
    {
         Save the address field. All frames are passed to the upper layed
         * and the decision if a frame is used is done there.
         */
        *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; 保存从站地址

         Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         * size of address field and CRC checksum.
         */
        *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); PDU长度

        */
        *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];  pucFrame指向PDU起始位置
        xFrameReceived = TRUE;
    }
    
    {
        eStatus = MB_EIO;
    }

    EXIT_CRITICAL_SECTION(  );
     eStatus;
}

  eMBRTUReceive函数完成了CRC校验、帧数据地址和长度的赋值,便于给上层进行处理!之后eMBPoll函数发送 ( void )xMBPortEventPost( EV_EXECUTE )事件。在EV_EXECUTE 事件中,从站对接收到的数据进行处理,包括根据功能码寻找功能函数处理报文和调用eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ) 发送应答报文。

--- eMBRTUSend函数

 pucFrame, USHORT usLength )
{
    函数功能
     * 1:对响应报文PDU前面加上从机地址;
     * 2:对响应报文PDU后加上CRC校;
     * 3:使能发送,启动传输;
    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.
     */
     STATE_RX_IDLE )
    {
        
        pucSndBufferCur = ( UCHAR * ) pucFrame - 在协议数据单元前加从机地址
        usSndBufferCount = ;

        
        pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
        usSndBufferCount += usLength;

        
        usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 &  );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >>  );

        
        eSndState = STATE_TX_XMIT;   发送状态
                                                          以下为新添加
        xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );   发送一个字节的数据,进入发送中断函数,启动传输
        pucSndBufferCur++;    
        usSndBufferCount--;

        vMBPortSerialEnable( FALSE, TRUE );   使能发送,禁止接收
    }
    
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
     eStatus;
}

在 eMBRTUSend函数中会调用串口发送数据,在进入串口发送中断后会调用xMBRTUTransmitFSM发送状态机函数发送应答报文。

---xMBRTUTransmitFSM函数

 )
{
    BOOL            xNeedPoll = FALSE;

    assert( eRcvState == STATE_RX_IDLE );

     ( eSndState )
    {
         We should not get a transmitter event if the transmitter is in
         * idle state.  */
     STATE_TX_IDLE:
        
        vMBPortSerialEnable( TRUE, FALSE );        发送器处于空闲状态,使能接收,禁止发送
        ;

    发送器处于发送状态,在从机发送函数eMBRTUSend中赋值STATE_TX_XMIT
        */
        发送数据
        {
            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
            pucSndBufferCur++;  
            usSndBufferCount--;
        }
        传递任务,发送完成
        {
            xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );   协议栈事件状态赋值为EV_FRAME_SENT,发送完成事件,eMBPoll函数会对此事件进行处理
             Disable transmitter. This prevents another transmit buffer
             * empty interrupt. 
            vMBPortSerialEnable( TRUE, FALSE );   使能接收,禁止发送
            eSndState = STATE_TX_IDLE;    发送器状态为空闲状态
        }
        ;
    }

     xNeedPoll;
}

至此:协议栈准备工作,从机接受报文,解析报文,从机发送响应报文四部分结束。

 

参考:

freemodbus库函数详解 FreeModbus源码详解-程序设计  

Freemodbus完全分析_百度文库  

FreeModbus学习笔记_百度文库

相关文章:

  • 2021-05-20
  • 2021-09-10
  • 2021-10-27
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-12-31
  • 2021-05-17
猜你喜欢
  • 2022-02-15
  • 2021-07-23
  • 2021-06-09
  • 2021-04-12
相关资源
相似解决方案