打印

freemodbusV1.5代码详细解析

[复制链接]
550|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
石头张|  楼主 | 2018-7-11 16:32 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
之前都是其他工程师把通讯部分做好,我只管用,最近花了点时间,详细把freemodbus的代码看了一遍,写个读书笔记。有点乱,以后再整理吧





从机接收01 03 00 00 00 01 84 0A


从机发送 01 03 02 14 7B F7 67

pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;

整个流程:

全局变量
eMBRcvState 接收状态
eMBSndState 发送状态
初始化:

eMBInit(MB_RTU, 0x01, 0x01,115200, MB_PAR_NONE);  
{
初始化3个方面: 1、串口初始化 2、定时器初始化 3、事件队列初始化
}
eMBEnable();            //启动FreeModbus
{
  pvMBFrameStartCur(  );-----> eMBRTUStart( void ) 函数指针调用 {eRcvState = STATE_RX_INIT;允许接收,关闭发送,开启定时器}
}

//大循环里事件查询,处理数据 数据使用中断接收,有接收状态机和发送状态机(xMBRTUReceiveFSM)(xMBRTUTransmitFSM)
eMBPoll( );


////////////////////////////////////////////////
从机中断接收主机发来的数据
注意:eMBEnable(),使能定时器后,会产生一次定时器中断,在定时器中断里执行 xMBRTUTimerT35Expired();
初始化后 eRcvState = STATE_RX_INIT;case STATE_RX_INIT:xNeedPoll = xMBPortEventPost( EV_READY ); 发送接收准备好的事件
                                                       vMBPortTimersDisable(  ); //关闭定时器
                                                       eRcvState = STATE_RX_IDLE;//接收处于空闲状态

********此时,所有准备工作完毕,等待主机发来数据,触发串口接收中断*************
               
               
从机接收数据流程:
中断接收

触发接收中断 prvvUARTRxISR( );  --》 pxMBFrameCBByteReceived(  );---》最终调用xMBRTUReceiveFSM( void )(接收状态机)接收数据

xMBRTUReceiveFSM( void )(接收状态机)  详解
函数中定义:UCHAR  ucByte;(用来临时保存接收到的数据)
1:( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );( *pucByte = USART_ReceiveData(USART2);)//接收到的数据放入  UCHAR  ucByte;

switch (eRcvState):

                                               //进入STATE_RX_IDLE  把接收到的数据放入ucRTUBuf[]数组,切换状态eRcvState = STATE_RX_RCV ,开启定时器
2:接收状态机 :第一次进入中断:(eRcvState = STATE_RX_IDLE ) case STATE_RX_IDLE: usRcvBufferPos = 0; ucRTUBuf[usRcvBufferPos++] = ucByte;eRcvState = STATE_RX_RCV;vMBPortTimersEnable(  ); //开启定时器
                第二次进入中断:(eRcvState = STATE_RX_RCV )  case STATE_RX_RCV:  if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
                                一直进入这一状态机持续接收数据,一直到接受完一帧数据                         {
                                                                                                                                                                          ucRTUBuf[usRcvBufferPos++] = ucByte; //一直接收
                                                                                                                                                                  }
                                                                                                                                                                  else
                                                                                                                                                                  {
                                                                                                                                                                          eRcvState = STATE_RX_ERROR;  //接收完成
                                                                                                                                                                  }
                                                                                                                                                                  vMBPortTimersEnable(  ); //定时器重启
ucRTUBuf[MB_SER_PDU_SIZE_MAX]; 接收的数据存到这个数组中  全局的


接收后的数据存储详细地址
从机接收01 03 00 00 00 01 84 0A
ucRTUBuf[0] = 01  设备地址
ucRTUBuf[1] = 03  功能码
ucRTUBuf[2] = 00  读保持寄存器的起始地址 高8位
ucRTUBuf[3] = 00  读保持寄存器的起始地址 低8位
ucRTUBuf[4] = 00  读保持寄存器的个数 高8位
ucRTUBuf[5] = 01  读保持寄存器的个数 低8位
ucRTUBuf[6] = 84  CRC 高8位
ucRTUBuf[7] = 0A  CRC 低8位



3:没有数据可以接收,不再进入接收状态机,所以定时器不会重启,此时等待产生定时器中断 表明数据接收完成
  prvvTIMERExpiredISR( ); --》 pxMBPortCBTimerExpired---xMBRTUTimerT35Expired
               case STATE_RX_RCV:
                           xMBPortEventPost( EV_FRAME_RECEIVED ); //发送事件,接收完成事件
               vMBPortTimersDisable(  ); //关定时器
               eRcvState = STATE_RX_IDLE;  //接收空闲 可以再次接收            

4:  eMBPoll();  事件查询 大循环中一直在查询事件
    static UCHAR   *ucMBFrame;
    static UCHAR    ucRcvAddress;
    static UCHAR    ucFunctionCode;
    static USHORT   usLength;
    static eMBException eException;
   xMBPortEventGet( &eEvent )  //获取事件

   switch ( eEvent )
   {
   case EV_READY:
          break;
  
  case EV_FRAME_RECEIVED: //接收到一个数据包
      eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
          eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) ---》  进行数据长度和CRC校验(返回pucRcvAddress 设备地址)如果我的数据发送事件( void )xMBPortEventPost( EV_EXECUTE );
                                                 //功能码地址(用到一个C语言技巧)unsigned char *p,a[10]={0},p = a; *(p+i) = p
         
      if( eStatus == MB_ENOERR )
       {   
                /* Check if the frame is for us. If not ignore the frame. */
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )(设备地址)如果我的数据发送事件
                {
                    ( void )xMBPortEventPost( EV_EXECUTE );发送事件
                }
        }
        break;
  
      }

    case EV_EXECUTE://对数据包处理
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];  获取功能码
            eException = MB_EX_ILLEGAL_FUNCTION;  0x01
            for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
            {
                /* No more function handlers registered. Abort. */
                if( xFuncHandlers.ucFunctionCode == 0 )
                {
                    break;
                }
                else if( xFuncHandlers.ucFunctionCode == ucFunctionCode )
                {   //找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
                                        //找到了进行处理 如果是06命令 eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
                    eException = xFuncHandlers.pxHandler( ucMBFrame, &usLength );
                    break;
                }
            }
                        
                        
详细解读
case EV_FRAME_RECEIVED: //接收到一个数据包
{
  1:eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); 函数指针
  最终执行函数 eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
               { 在这个函数中
                             1:对数据长度和数据包CRC校验  if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
                             2:数据填充
                                    *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; *pucRcvAddress = ucRTUBuf[0];   ucRcvAddress 保存了 ucRTUBuf[0] 即设备地址 0x01
                                       
                                *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );//减去CRC两位和头
                                        usLength = 8-1-2 = 5
                                       
                                        *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //从全局数组中获取功能码地址
                                        *pucFrame = ( UCHAR * ) & ucRTUBuf[1];  // ucMBFrame中放的就是功能吗的地址
                           }
  
  
  2:校验地址,看看数据是不是发给本设备的
              if( eStatus == MB_ENOERR )
            {   
                /* Check if the frame is for us. If not ignore the frame. */
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )//校验地址 地址是否是自己的地址或广播地址
                {
                    ( void )xMBPortEventPost( EV_EXECUTE ); //发送事件
                }
            }
}                        
                        
                        
case EV_EXECUTE: //确认接收到的数据包是自己的,进行处理
{

ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];  
(这里使用了一个C语言技巧,)可以这样理解 int * p;int a[10]; p = &a[x] ; *(p+i) = p = a[x+i]
{
  static UCHAR   *ucMBFrame;
  在eMBRTUReceive函数中
  ucMBFrame = ( UCHAR * ) & ucRTUBuf[1]; //从全局数组中获取功能码地址 MB_SER_PDU_PDU_OFF = 1  MB_PDU_FUNC_OFF = 0
  因为此时 ucMBFrame =( UCHAR * &ucRTUBuf[1],所以  ucFunctionCode = ucMBFrame[0] 等价 ucFunctionCode = ucRTUBuf[1];--->  ucFunctionCode = 0x03
}

for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )  //遍历数组
{
    /* No more function handlers registered. Abort. */ //查找是否有对应的功能处理函数
    if( xFuncHandlers.ucFunctionCode == 0 )
    {
        break;
    }   //找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
    else if( xFuncHandlers.ucFunctionCode == ucFunctionCode )
    {   //找到了进行处理 如果是06命令 eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
        eException = xFuncHandlers.pxHandler( ucMBFrame, &usLength );

        break;
    }
        
        if( ucRcvAddress != MB_ADDRESS_BROADCAST )
    {
        if( eException != MB_EX_NONE )
        {
            /* An exception occured. Build an error frame. */
            usLength = 0;
            ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
            ucMBFrame[usLength++] = eException;
        }
        if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
        {
            vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
        }               
        eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); //回调函数处理完后,进入发送,先发送一个字节 ,然后进入发送状态机
    }
}

}                        
详解:
eException = xFuncHandlers.pxHandler( ucMBFrame, &usLength );
根据我的数据,进行0x03命令处理
//找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )

调用这个函数进行处理

eMBException
eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
    USHORT          usRegAddress;
    USHORT          usRegCount;
    UCHAR          *pucFrameCur;

    eMBException    eStatus = MB_EX_NONE;
    eMBErrorCode    eRegStatus;

    if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) ) //4+1 = 5
    {  //因为pucFrame = ( UCHAR * ) & ucRTUBuf[1]  pucFrame[1] = ucRTUBuf[2] //读保持寄存器的起始地址 高8位
        usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 ); //同样使用指针技巧 *(p+i)=p  上面已经说了
        usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] ); //获取一个16位的地址 读保持寄存器的地址
                //usRegAddress 就是接收到数据ucRTUBuf[2]、ucRTUBuf[3] 合成的地址数据
        usRegAddress++; //ws add 不知道为什么++   

        usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 );//获取要读取保持寄存器的个数
        usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] );
        //usRegCount 读取保持寄存器的个数  ucRTUBuf[4] 、ucRTUBuf[5] 合成的地址数据
        /* Check if the number of registers to read is valid. If not
         * return Modbus illegal data value exception.
         */  //读取个数校验
        if( ( usRegCount >= 1 ) && ( usRegCount <= MB_PDU_FUNC_READ_REGCNT_MAX ) )
        {
            /* Set the current PDU data pointer to the beginning. */
            pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF];  //  pucFrameCur = &ucRTUBuf[1]
            *usLen = MB_PDU_FUNC_OFF;  //*usLen = 0;   从这里开始要往 ucRTUBuf[]  缓存区填充数据,发送和接收共用一个缓存区

            /* First byte contains the function code. */
            *pucFrameCur++ = MB_FUNC_READ_HOLDING_REGISTER;  //ucRTUBuf[1] = MB_FUNC_READ_HOLDING_REGISTER = 3   回复的数据时 地址+功能码+发送数据的个数-+数据区----
            *usLen += 1; //数据长度加1

            /* Second byte in the response contain the number of bytes. */
            *pucFrameCur++ = ( UCHAR ) ( usRegCount * 2 ); //ucRTUBuf[2] = 要发送读保持寄存器中数据的个数 保持寄存器是16进制的,usRegCount * 2   转成 UCHAR 发送的个数
            *usLen += 1;//数据长度加1

            /* Make callback to fill the buffer.回调函数进一步填充发送的数据  需要用户自己实现 把要保持寄存器中的数据,复制到发送ucRTUBuf[]内填充 */
            eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ ); //下面详细解释这个函数,需要用户自己写
            /* If an error occured convert it into a Modbus exception. */
            if( eRegStatus != MB_ENOERR )
            {
                eStatus = prveMBError2Exception( eRegStatus );
            }
            else
            {
                *usLen += usRegCount * 2;
            }
        }
        else
        {
            eStatus = MB_EX_ILLEGAL_DATA_VALUE;
        }
    }
    else
    {
        /* Can't be a valid request because the length is incorrect. */
        eStatus = MB_EX_ILLEGAL_DATA_VALUE;
    }
    return eStatus;
}



//发送时回调函数,把保持寄存器的内容填充到ucRTUBuf[]内,发送缓存区
eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ ); //下面详细解释这个函数,需要用户自己写

#define REG_HOLDING_START 0x0000  
//保持寄存器数量  
#define REG_HOLDING_NREGS 8  
//保持寄存器内容  
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]  
= {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};  

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) //参数解释 pucRegBuffer缓存区指针当前是ucRTUBuf[3]
{                                                                                                // usAddress 从接收缓存区提取的读保持寄存的起始地址
    eMBErrorCode    eStatus = MB_ENOERR;                                                         // usRegCount从接收缓存区提取的要读取的保持寄存器的个数
    USHORT          iRegIndex;                                                                   // MB_REG_READ 功能码,读
    USHORT *        pusRegHoldingBuf;
    USHORT          REG_HOLDING_START1;
    USHORT          REG_HOLDING_NREGS1;
    USHORT          usRegHoldStart;

    pusRegHoldingBuf = usRegHoldingBuf; //用户自己定义的
    REG_HOLDING_START1 = REG_HOLDING_START;//用户自己定义的
    REG_HOLDING_NREGS1 = REG_HOLDING_NREGS; //用户自己定义的
    usRegHoldStart = REG_HOLDING_START;//用户自己定义的

    usAddress--;//FreeModbus功能函数中已经加1,为保证与缓冲区首地址一致,故减1  看代码知道
    if( ( usAddress >= REG_HOLDING_START1 ) &&
        ( usAddress + usNRegs <= REG_HOLDING_START1 + REG_HOLDING_NREGS1 ) )  //地址校验
    {
        iRegIndex = usAddress - usRegHoldStart;  //起始偏移量 在该例子中 usRegHoldStart = 0x0000,就是pusRegHoldingBuf[0],假如从0x0003开始读,iRegIndex = 3,pusRegHoldingBuf[3],
        switch ( eMode )                            //用户可以根据自己的需要自由修改
        {
            // Pass current register values to the protocol stack.
        case MB_REG_READ:
            while( usNRegs > 0 )
            {   //填充数据
                *pucRegBuffer++ = ( unsigned char )( pusRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( unsigned char )( pusRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

            // Update current register values with new values from the protocol stack.

        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                pusRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                pusRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}







处理完,数据缓存区后,开始发送数据               

开始发送数据  恢复数据包
peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );---》eMBRTUSend (eSndState = STATE_TX_XMIT;)
装载头地址 和 CRC
         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;
就是把 ucRTUBuf[0] = ucSlaveAddress,数据长度再加1
         usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
        ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
                把CRC填充到ucRTUBuf[]的数据尾部
               
               
eSndState = STATE_TX_XMIT;               

             //插入代码 启动第一次发送,这样才可以进入发送完成中断
            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
            pucSndBufferCur++;
            usSndBufferCount--;
        vMBPortSerialEnable( FALSE, TRUE );//切换到允许发送完成中断,然后等待发送完成中断的到来,开启发送状态机,发送数据

主动发送一次  等待进入发送完成中断(TC)
void USART2_IRQHandler(void)
{
          //发生完成中断
        if(USART_GetITStatus(USART2, USART_IT_TC) == SET)
        {
          prvvUARTTxReadyISR( );
          //清除中断标志
          USART_ClearITPendingBit(USART2, USART_IT_TC);
        }

}

prvvUARTTxReadyISR( ); -- >  pxMBFrameCBTransmitterEmpty(  );(发送状态机)
xMBRTUTransmitFSM();
         case STATE_TX_XMIT:
        /* check if we are finished. */
        if( usSndBufferCount != 0 )  //一直发送数据  知道数据发送完成
        {
            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
            pucSndBufferCur++;  /* next byte in sendbuffer. */
            usSndBufferCount--;
        }
        else
        {
            xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); //发送完成后  发送事件
            /* Disable transmitter. This prevents another transmit buffer
             * empty interrupt. */
            vMBPortSerialEnable( TRUE, FALSE ); //允许接收数据 ,停止发送数据
            eSndState = STATE_TX_IDLE;  //发送空闲
        }
        break;
    }


此时 eMBPoll()   case EV_FRAME_SENT:
                   break;
所以从初始化到第一次接收到数据和第一次回复数据 ,完成一次通信



下次通讯 时,先产生串口接收中断


第一次完成的通讯结束。

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

446

主题

446

帖子

0

粉丝