Freemodbus原理分析
2017-10-28 16:38
302 查看
//流程概括
1)初始化串口和定时器,串口先初始化为接收使能,定时器为3.5T时间溢出,启动定时器
2)定时器第一次溢出,更新时间标志
eQueuedEvent = EV_READY;
vMBPortTimersDisable( ); //暂时关掉定时器,为节能,也为后面的接收准备
3) 串口中断接收,说明有来自主机的命令数据
①启动定时器,这有2方面考虑,一是判断接收数据是否延时,二是判断接收数据完成,这样就能有效的实现中断能够接收不等长的数据了
这种方法是不适用DMA中断的另外一种,也更契合Modbus的标准。
② 执行xMBRTUReceiveFSM,就是将串口数据放到接收缓存中,不做任何处理
③ 接收完成后,定时器中断会把 xMBPortEventPost( EV_FRAME_RECEIVED );事件更新。
4)eMBPoll会循环查询 “事件标志”
看到事件标识“EV_FRAME_RECEIVED”执行eMBRTUReceive,这个就是对接收数据进行筛选,处理,
处理完成后,更新事件标志xMBPortEventPost( EV_EXECUTE );
5)根据接收数据筛选的命令,执行对应的功能函数,如果是读命令,则是执行eMBException
eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ),在这个函数里,还要编写一个自己的项目处理函数eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
6)eMBPoll这时候根据事件标志“EV_EXECUTE”,执行eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
这个函数其实就是打包要回复的数据功能,打包完成后,会启动串口发送中断,
eSndState = STATE_TX_XMIT;
vMBPortSerialEnable( FALSE, TRUE );
串口发送中断,进一步执行xMBRTUTransmitFSM( void )
将要回复的数据通过串口逐字节的发送出去,
然后再次将串口设置为 接收中断,以便接收下一次的查询,同时刷新事件标志
xMBPortEventPost( EV_FRAME_SENT );
vMBPortSerialEnable( TRUE, FALSE );
eSndState = STATE_TX_IDLE;
//代码分析-RTU举例
1)初始化串口和定时器,串口先初始化为接收使能,定时器为3.5T时间溢出,启动定时器
2)定时器第一次溢出,更新时间标志
eQueuedEvent = EV_READY;
vMBPortTimersDisable( ); //暂时关掉定时器,为节能,也为后面的接收准备
3) 串口中断接收,说明有来自主机的命令数据
①启动定时器,这有2方面考虑,一是判断接收数据是否延时,二是判断接收数据完成,这样就能有效的实现中断能够接收不等长的数据了
这种方法是不适用DMA中断的另外一种,也更契合Modbus的标准。
② 执行xMBRTUReceiveFSM,就是将串口数据放到接收缓存中,不做任何处理
③ 接收完成后,定时器中断会把 xMBPortEventPost( EV_FRAME_RECEIVED );事件更新。
4)eMBPoll会循环查询 “事件标志”
看到事件标识“EV_FRAME_RECEIVED”执行eMBRTUReceive,这个就是对接收数据进行筛选,处理,
处理完成后,更新事件标志xMBPortEventPost( EV_EXECUTE );
5)根据接收数据筛选的命令,执行对应的功能函数,如果是读命令,则是执行eMBException
eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ),在这个函数里,还要编写一个自己的项目处理函数eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
6)eMBPoll这时候根据事件标志“EV_EXECUTE”,执行eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
这个函数其实就是打包要回复的数据功能,打包完成后,会启动串口发送中断,
eSndState = STATE_TX_XMIT;
vMBPortSerialEnable( FALSE, TRUE );
串口发送中断,进一步执行xMBRTUTransmitFSM( void )
将要回复的数据通过串口逐字节的发送出去,
然后再次将串口设置为 接收中断,以便接收下一次的查询,同时刷新事件标志
xMBPortEventPost( EV_FRAME_SENT );
vMBPortSerialEnable( TRUE, FALSE );
eSndState = STATE_TX_IDLE;
//代码分析-RTU举例
一、eMBInit(MB_RTU, 9, 0, 9600, MB_PAR_NONE); 1、 eMBErrorCode eStatus = MB_ENOERR; ucMBAddress = ucSlaveAddress; //函数指针赋值 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 ); //eStatus = xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) xMBPortEventInit( ) ) eMBCurrentMode = eMode; eMBState = STATE_DISABLED; xEventInQueue = FALSE; 二、eMBErrorCode eMBEnable( void ) pvMBFrameStartCur( ); eRcvState = STATE_RX_INIT; vMBPortSerialEnable( TRUE, FALSE ); //使能接收中断 vMBPortTimersEnable( ); eMBState = STATE_ENABLED; 三、timer第一次中断 xMBPortEventPost( EV_READY ); xEventInQueue = TRUE; eQueuedEvent = EV_READY; vMBPortTimersDisable( ); eRcvState = STATE_RX_IDLE; 四、串口/RS485第一次接收到第一个字节 prvvUARTRxISR(); xMBPortSerialGetByte( ( CHAR * ) & ucByte ); usRcvBufferPos = 0; ucRTUBuf[usRcvBufferPos++] = ucByte; eRcvState = STATE_RX_RCV; /* 启动定时器,因为一个字节传输时间不能超过定时器溢出时间(3.5T)这是modbus要求的 */ vMBPortTimersEnable( ); (定时器启动后,这个溢出时间一般都会在一个字节传输完成,否则就停止接收了, 所以下面的动作应该是在定时器未溢出的时间内进行的) 串口接下来再接收数据: xMBPortSerialGetByte( ( CHAR * ) & ucByte ); ucRTUBuf[usRcvBufferPos++] = ucByte; vMBPortTimersEnable(); //从这里就可以看出,每接收1个字节,重置一下定时器, //所以正常来说,接收的过程中,定时器不会溢出的。 串口接收完成后,定时器会第二次溢出: xMBPortEventPost( EV_FRAME_RECEIVED ); xEventInQueue = TRUE; eQueuedEvent = EV_FRAME_RECEIVED; 五、在这个过程中,eMBPoll会一直查询 事件标志,接收完成后 xMBPortEventGet( &eEvent ) 1. case EV_FRAME_RECEIVED: eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );//��ѯ���ս��������������浽ucMBFrame //其实执行的是eMBRTUReceive BOOL xFrameReceived = FALSE; eMBErrorCode eStatus = MB_ENOERR; usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) //判断CRC校验 *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //指针赋值 xFrameReceived = TRUE; //只需要传输指针即可,不要大堆数据赋值, //这才是高手应该做的工作 ( void )xMBPortEventPost( EV_EXECUTE ); //指针传输完成后,事件标志切换成执行,下面就该执行回复了 break; 2.回复,也是在eMBPoll中执行的 case EV_EXECUTE: ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];//命令码 eException = MB_EX_ILLEGAL_FUNCTION; //异常码初始化 for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )//查找命令码,执行对应的函数,下面是正确命令码 { if( xFuncHandlers[i].ucFunctionCode == 0 ) { break; } else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )//ÏàµÈ£¬Ôò˵Ã÷±¾»úÖ§³Ö´Ë¹¦ÄÜÂë { eException = xFuncHandlers[i].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; } eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); } break; 3.以{0x01,0x03,0x00,0x00,0x00,05,0x85,0xC9}读命令为例, 那么此时: *pucRcvAddress = 0x01; *pusLength = 8-1-2=5; *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //注意,这里的接收指针已经把第一个字节-地址和校验给剔除去了, //所以后面的所有对pucFrame的处理,对应的数组是 //{0x03,0x00,0x00,0x00,0x05} ucFunctionCode = 0x03; //执行处理函数,传送的是 *pucFrame,usLen eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) //这个函数主要功能是打包返回数据 { USHORT usRegAddress; USHORT usRegCount; UCHAR *pucFrameCur; eMBException eStatus = MB_EX_NONE; eMBErrorCode eRegStatus; usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 ); usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] ); //起始地址 usRegAddress++; //这里比较奇葩,非要把命令中的起始地址加1, //这可能与modbus标准定义有关吧, //因为Modbus定义起始地址不为0,最小为1, //所以这里加1了 usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 ); usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] ); //要读的寄存器个数 pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF]; //将pucFrame指针赋值 //给局部指针pucFrameCur *usLen = MB_PDU_FUNC_OFF; //usLen=0 /* First byte contains the function code. */ *pucFrameCur++ = MB_FUNC_READ_HOLDING_REGISTER; //打包指针第一个字节是功能码0x03,虽然别扭 //后面有妙用 下面该pucFrameCur[1]了 *usLen += 1; //usLen = 1; /* Second byte in the response contain the number of bytes. */ *pucFrameCur++ = ( UCHAR ) ( usRegCount * 2 ); //第2个字节为 字节数 5x2,下面该 //pucFramecur[2] *usLen += 1; //usLen = 2 /* Make callback to fill the buffer. */ eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ ); //这个函数功能是赋值,将本地 //的真正寄存器值赋值到pucFrame //中,这里已经开始从pucFrame[2] //开始了,指针的威力真的特别大 //做完上面的打包工作,开始准备发送出去 peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); //这里就把地址0x01,前面的打包数据指针ucMBFrame,以及打包的数据长度传送给该函数 //其实执行的是 eMBRTUSend( UCHAR ucSlaveAddress, const UCH ce87 AR * pucFrame, USHORT usLength ) { OS_CPU_SR cpu_sr=0; eMBErrorCode eStatus = MB_ENOERR; USHORT usCRC16; if( eRcvState == STATE_RX_IDLE ) { pucSndBufferCur = ( UCHAR * ) pucFrame - 1; //此处非常非常巧妙,传送的数据指针中没有设备ID //这里的发送指针执行传送指针的前一位,这个用法不知道危险不, //但是好牛B,效率特别高,不用再copy了,也不用定义一个大数组了 usSndBufferCount = 1; //为了后面的使用 /* 现在copy 数据到缓存中,其实只需要拷贝几个字节而已,很神奇的套路*/ pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; //这样就把地址ID给加上了 usSndBufferCount += usLength; //用于定位crc校验 /* 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( void ) xMBRTUTransmitFSM( void ) { 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 ); //发送完毕后,更新事件标志为 XXX_SEND /* Disable transmitter. This prevents another transmit buffer * empty interrupt. */ vMBPortSerialEnable( TRUE, FALSE ); //再使能串口接收使能,以便下次接收 //从这里也可以看出Freemodbus是专为从机设计 //master主机读写时,才进行回应 eSndState = STATE_TX_IDLE; } break; } return xNeedPoll; }
相关文章推荐
- workqueue原理和分析(转)
- Android LayoutInflater原理分析,带你一步步深入了解View(一)
- JAVA NIO原理图文分析及代码实现
- Unity编译Android的原理解析和apk打包分析
- ConcurrentHashMap原理分析
- RPC原理及RPC实例分析
- docker容器网络通信原理分析
- 深入理解HTTP协议及原理分析
- c函数调用过程原理及函数栈帧分析
- OllyDBG分析报告系列(7)---文件加载调试原理
- web上存漏洞及原理分析、防范方法
- HashMap实现原理分析
- 深入分析java线程池的实现原理
- 揭开智能配置上网(微信Airkiss)的神秘面纱 本文介绍微信利用Airkiss技术对wifi设备进行智能配置上网的场景,并分析其实现的原理。这里再次说明,Airkiss只是用于配置上网,其跟微信
- Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解
- MapReduce原理,实例分析
- 菜鸟学习React Native for Android 之通讯原理分析(JS调用Native)
- AD时间同步原理分析
- XSS的原理分析与解剖
- DFT 频谱分析原理