freemodbus-v1.5.0 源码分析 (转)
FreeModbus协议栈作为从机,等待主机传送的数据,当从机接收到一帧完整的报文后,对报文进行解析,然后响应主机,发送报文给主机,实现主机和从机之间的通信。
1、初始化协议栈---eMBInit函数(mb.c中),以RTU为例
1 eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity ) 2 { 3 4 ?1/*函数功能:*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; 5 /* check preconditions */ 6 if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) || 7 ( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) ) //验证从机地址 8 { 9 eStatus = MB_EINVAL; //地址错误 10 } 11 else 12 { 13 ucMBAddress = ucSlaveAddress; 14 15 switch ( eMode ) 16 { 17 #if MB_RTU_ENABLED > 0 18 case MB_RTU: 19 pvMBFrameStartCur = eMBRTUStart; 20 pvMBFrameStopCur = eMBRTUStop; 21 peMBFrameSendCur = eMBRTUSend; 22 peMBFrameReceiveCur = eMBRTUReceive; 23 pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; 24 pxMBFrameCBByteReceived = xMBRTUReceiveFSM; //接收状态机,串口接受中断最终调用此函数接收数据 25 pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM; //发送状态机,串口发送中断最终调用此函数发送数据 26 pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; //报文到达间隔检查,定时器中断函数最终调用次函数完成定时器中断 27 28 eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity ); 29 break; 30 #endif 31 #if MB_ASCII_ENABLED > 0 32 case MB_ASCII: 33 pvMBFrameStartCur = eMBASCIIStart; 34 pvMBFrameStopCur = eMBASCIIStop; 35 peMBFrameSendCur = eMBASCIISend; 36 peMBFrameReceiveCur = eMBASCIIReceive; 37 pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; 38 pxMBFrameCBByteReceived = xMBASCIIReceiveFSM; 39 pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM; 40 pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired; 41 42 eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity ); 43 break; 44 #endif 45 default: 46 eStatus = MB_EINVAL; 47 } 48 49 if( eStatus == MB_ENOERR ) 50 { 51 if( !xMBPortEventInit( ) ) 52 { 53 /* port dependent event module initalization failed. */ 54 eStatus = MB_EPORTERR; 55 } 56 else 57 {//设定当前状态 58 eMBCurrentMode = eMode; //设定RTU模式 59 eMBState = STATE_DISABLED; //modbus协议栈初始化状态,在此初始化为禁止 60 } 61 } 62 } 63 return eStatus; 64 }
eMBInit函数中pvMBFrameStartCur、pvMBFrameStartCur等为协议栈函数的接口,对于不同的通信模式使用不同的函数进行初始化!!!此编程模式可以借鉴学习!!!
eMBInit函数对底层驱动(串口和定时器)进行初始化。初始化完成并且成功之后对事件也进行了初始化,完成后全局变量eMBState=STATE_DISABLED。
2、启动协议栈----eMBEnable函数(mb.c函数)
1 eMBErrorCode 2 eMBEnable( void ) 3 { 4 /*函数功能: 5 *1:实现RTU模式和ASCALL模式的协议栈初始化; 6 *2:完成协议栈核心函数指针的赋值,包括Modbus协议栈的使能和禁止、报文的接收和响应、3.5T定时器中断回调函数、串口发送和接收中断回调函数; 7 *3:eMBRTUInit完成RTU模式下串口和3.5T定时器的初始化,需用户自己移植; 8 *4:设置Modbus协议栈的模式eMBCurrentMode为MB_RTU,设置Modbus协议栈状态eMBState为STATE_DISABLED; 9 */ 10 eMBErrorCode eStatus = MB_ENOERR; 11 12 if( eMBState == STATE_DISABLED ) 13 { 14 /* Activate the protocol stack. */ 15 pvMBFrameStartCur( ); //激活协议栈 16 eMBState = STATE_ENABLED; //设置Modbus协议栈工作状态eMBState为STATE_ENABLED 17 } 18 else 19 { 20 eStatus = MB_EILLSTATE; 21 } 22 return eStatus; 23 }
---eMBRTUStart函数 (mbrtu.c)
1 void 2 eMBRTUStart( void ) 3 { 4 /*函数功能 5 * 1:设置接收状态机eRcvState为STATE_RX_INIT; 6 * 2:使能串口接收,禁止串口发送,作为从机,等待主机传送的数据; 7 * 3:开启定时器,3.5T时间后定时器发生第一次中断,此时eRcvState为STATE_RX_INIT,上报初始化完成事件,然后设置eRcvState为空闲STATE_RX_IDLE; 8 * 4:每次进入3.5T定时器中断,定时器被禁止,等待串口有字节接收后,才使能定时器; 9 * */ 10 ENTER_CRITICAL_SECTION( ); 11 /* Initially the receiver is in the state STATE_RX_INIT. we start 12 * the timer and if no character is received within t3.5 we change 13 * to STATE_RX_IDLE. This makes sure that we delay startup of the 14 * modbus protocol stack until the bus is free. 15 */ 16 eRcvState = STATE_RX_INIT; 17 vMBPortSerialEnable( TRUE, FALSE ); //开启串口接收,发送未开启 18 vMBPortTimersEnable( ); //启动定时器 19 20 EXIT_CRITICAL_SECTION( ); 21 }
eMBEnable函数启动协议栈pvMBFrameStartCur,对于RTU模式,pvMBFrameStartCur函数指针指向 eMBRTUStart,在eMBRTUStart函数中,全局变量eRcvState=STATE_RX_INIT,并使能串口和定时器。注意!!!此时定时器将开始工作!!!。eMBEnable函数中将把全局变量改为 eMBState=STATE_ENABLED。
3、状态机轮训---eMBPoll函数(mb.c)
1 eMBErrorCode eMBPoll( void ) 2 { 3 /*函数功能: 4 *1:检查协议栈状态是否使能,eMBState初值为STATE_NOT_INITIALIZED,在eMBInit()函数中被赋值为STATE_DISABLED,在eMBEnable函数中被赋值为STATE_ENABLE; 5 *2:轮询EV_FRAME_RECEIVED事件发生,若EV_FRAME_RECEIVED事件发生,接收一帧报文数据,上报EV_EXECUTE事件,解析一帧报文,响应(发送)一帧数据给主机; 6 */ 7 static UCHAR *ucMBFrame; //接收和发送报文数据缓存区 8 static UCHAR ucRcvAddress; //modbus从机地址 9 static UCHAR ucFunctionCode; //功能码 10 static USHORT usLength; //报文长度 11 static eMBException eException; //错误码响应 枚举 12 13 int i; 14 eMBErrorCode eStatus = MB_ENOERR; 15 eMBEventType eEvent; //错误码 16 17 /* Check if the protocol stack is ready. */ 18 if( eMBState != STATE_ENABLED ) //检查协议栈是否使能 19 { 20 return MB_EILLSTATE; //协议栈未使能,返回协议栈无效错误码 21 } 22 23 /* Check if there is a event available. If not return control to caller. 24 * Otherwise we will handle the event. */ 25 26 if( xMBPortEventGet( &eEvent ) == TRUE ) //判断事件是否发生 27 { 28 switch ( eEvent ) //查询哪个事件发生 29 { 30 case EV_READY: 31 break; 32 33 case EV_FRAME_RECEIVED: //接收到一帧数据,此事件发生 34 eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); //接收数据,并检验报文长度和CRC校验是否正确 35 /* 36 * ucRcvAddress 主站要读取的从站的地址 37 * ucMBFrame 指向PDU的头部 38 * usLength PDU的长度 39 */ 40 if( eStatus == MB_ENOERR ) 41 { 42 /* Check if the frame is for us. If not ignore the frame. */ 43 if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ) 44 { 45 ( void )xMBPortEventPost( EV_EXECUTE ); //修改事件标志为EV_EXECUTE执行事件 46 } 47 } 48 break; 49 50 case EV_EXECUTE: //修改事件标志为EV_EXECUTE执行事件 51 ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; //提取功能码 52 eException = MB_EX_ILLEGAL_FUNCTION; //赋错误码初值为无效的功能码 53 for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) 54 { 55 /* No more function handlers registered. Abort. */ 56 if( xFuncHandlers[i].ucFunctionCode == 0 ) 57 { 58 break; 59 } 60 else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) //根据报文中的功能码,处理报文 61 { 62 eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); //对接收到的报文进行解析 63 break; 64 } 65 } 66 67 /* If the request was not sent to the broadcast address we 68 * return a reply. */ 69 if( ucRcvAddress != MB_ADDRESS_BROADCAST ) 70 { 71 if( eException != MB_EX_NONE ) //接收到的报文有错误 72 { 73 /* An exception occured. Build an error frame. */ 74 usLength = 0; //响应发送数据的首字节为从机地址 75 ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR ); //响应发送数据帧的第二个字节,功能码最高位置1 76 ucMBFrame[usLength++] = eException; //响应发送数据帧的第三个字节为错误码标识 77 } 78 if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ) 79 { 80 vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ); 81 } 82 eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); //modbus从机响应函数,发送响应给主机 83 } 84 break; 85 86 case EV_FRAME_SENT: 87 break; 88 } 89 } 90 return MB_ENOERR; 91 }
在eMBPoll函数中,首先由 xMBPortEventGet( &eEvent ) == TRUE 判断时间是否发生,若无事件发生则不进入状态机;若有时间发生则进入状态机开始轮询。状态机的时间转换在定时中断服务函数中实现。
在eMBEnable函数中启动定时器后,定时器开始工作(见 eMBRTUStart函数), 在定时器第一次超时之后将会发送xNeedPoll = xMBPortEventPost( EV_READY ) 事件,然后关闭定时器,接收机状态(全局变量)eRcvState为STATE_RX_IDLE。此时,主循环eMBPoll中将执行一次EV_READY下的操作。至此,完成Modbus协议栈的初始化准备工作,协议栈开始运行,eMBPoll()函数轮询等待接收完成事件发生。
---定时器中断服务函数
1 void BOARD_GPTA_HANDLER() 2 { 3 // BOOL bTaskWoken = FALSE; 4 5 PRINTF("\r\nTimer Expired:\n\n\r"); 6 vMBPortSetWithinException( TRUE ); 7 GPT_ClearStatusFlag(BOARD_GPTA_BASEADDR, gptStatusFlagOutputCompare1); //关闭定时器 8 ( void )pxMBPortCBTimerExpired( ); //事件转换 vMBPortSetWithinException( FALSE ); 9 }
---xMBRTUTimerT35Expired T3.5超时函数
1 BOOL xMBRTUTimerT35Expired( void ) 2 { 3 /* 函数功能 4 * 1:从机接受完成一帧数据后,接收状态机eRcvState为STATE_RX_RCV; 5 * 2:上报“接收到报文”事件(EV_FRAME_RECEIVED); 6 * 3:禁止3.5T定时器,设置接收状态机eRcvState状态为STATE_RX_IDLE空闲; 7 */ 8 BOOL xNeedPoll = FALSE; 9 10 switch ( eRcvState ) //上报modbus协议栈的事件状态给poll函数 11 { 12 /* Timer t35 expired. Startup phase is finished. */ 13 case STATE_RX_INIT: 14 xNeedPoll = xMBPortEventPost( EV_READY ); //初始化完成事件 15 break; 16 17 /* A frame was received and t35 expired. Notify the listener that 18 * a new frame was received. */ 19 case STATE_RX_RCV: //一帧数据接收完成 20 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); //上报协议栈事件,接收到一帧完整的数据 21 break; 22 23 /* An error occured while receiving the frame. */ 24 case STATE_RX_ERROR: 25 break; 26 27 /* Function called in an illegal state. */ 28 default: 29 assert( ( eRcvState == STATE_RX_INIT ) || 30 ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); 31 } 32 33 vMBPortTimersDisable( ); //当接收到一帧数据后,禁止3.5T定时器,直到接受下一帧数据开始,开始计时 34 eRcvState = STATE_RX_IDLE; //处理完一帧数据,接收器状态为空闲 35 36 return xNeedPoll; 37 }
4、报文接收
在定时器第一次中断之后,状态机为eRcvState=STATE_RX_IDLE,即读空闲状态,eMBPoll也阻塞在等待接收完成事件发生。而在eMBPoll之前的eMBRTUStart函数中已经开启了串口中断,因此在接收到数据之后,串口中断将会响应,在串口中断服务函数中将调用接收状态机函数xMBRTUReceiveFSM来接收数据。
---xMBRTUReceiveFSM函数
1 BOOL xMBRTUReceiveFSM( void ) 2 { 3 /*函数功能 4 *1:将接收到的数据存入ucRTUBuf[]中; 5 *2:usRcvBufferPos为全局变量,表示接收数据的个数; 6 *3:每接收到一个字节的数据,3.5T定时器清0 7 */ 8 9 BOOL xTaskNeedSwitch = FALSE; 10 UCHAR ucByte; 11 12 assert( eSndState == STATE_TX_IDLE ); //确保没有数据在发送 13 14 /* Always read the character. */ 15 ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); //从串口数据寄存器读取一个字节数据 16 17 switch ( eRcvState ) //根据不同的状态转移 18 { 19 /* If we have received a character in the init state we have to 20 * wait until the frame is finished. 21 */ 22 case STATE_RX_INIT: 23 vMBPortTimersEnable( ); //开启3.5T定时器 24 break; 25 26 /* In the error state we wait until all characters in the 27 * damaged frame are transmitted. 28 */ 29 case STATE_RX_ERROR: //数据帧被损坏,重启定时器,不保存串口接收的数据 30 vMBPortTimersEnable( ); 31 break; 32 33 /* In the idle state we wait for a new character. If a character 34 * is received the t1.5 and t3.5 timers are started and the 35 * receiver is in the state STATE_RX_RECEIVCE. 36 */ 37 case STATE_RX_IDLE: // 接收器空闲,开始接收,进入STATE_RX_RCV状态 38 usRcvBufferPos = 0; 39 ucRTUBuf[usRcvBufferPos++] = ucByte; //保存数据 40 eRcvState = STATE_RX_RCV; 41 42 /* Enable t3.5 timers. */ 43 vMBPortTimersEnable( ); //每收到一个字节,都重启3.5T定时器 44 break; 45 46 /* We are currently receiving a frame. Reset the timer after 47 * every character received. If more than the maximum possible 48 * number of bytes in a modbus frame is received the frame is 49 * ignored. 50 */ 51 case STATE_RX_RCV: 52 if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) 53 { 54 ucRTUBuf[usRcvBufferPos++] = ucByte; //接收数据 55 } 56 else 57 { 58 eRcvState = STATE_RX_ERROR; //一帧报文的字节数大于最大PDU长度,忽略超出的数据 59 } 60 vMBPortTimersEnable( ); //每收到一个字节,都重启3.5T定时器 61 break; 62 } 63 return xTaskNeedSwitch; 64 }
在串口中断前,状态机为eRcvState=STATE_RX_IDLE,接收状态机开始后,读取uart串口缓存中的数据,并进入STATE_RX_IDLE分支中存储一次数据后开启定时器,然后进入STATE_RX_RCV分支继续接收后续的数据,直至定时器超时!如果没有超时的话,状态不会转换,将还可以继续接收数据。超时之后,在T3.5超时函数xMBRTUTimerT35Expired 中将发送EV_FRAME_RECEIVED事件。然后eMBPoll函数将会调用eMBRTUReceive函数。
1 /* A frame was received and t35 expired. Notify the listener that 2 * a new frame was received. */ 3 case STATE_RX_RCV: //一帧数据接收完成 4 xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); //上报协议栈事件,接收到一帧完整的数据 5 break;
---eMBRTUReceive函数
1 eMBErrorCode 2 eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) 3 { 4 /*eMBPoll函数轮询到EV_FRAME_RECEIVED事件时,调用peMBFrameReceiveCur(), 5 * 此函数是用户为函数指针peMBFrameReceiveCur()的赋值, 6 * 此函数完成的功能: 7 * 从一帧数据报文中,取得modbus从机地址给pucRcvAddress、PDU报文的长度给pusLength, 8 * PDU报文的首地址给pucFrame,函数*形参全部为地址传递, 9 */ 10 BOOL xFrameReceived = FALSE; 11 eMBErrorCode eStatus = MB_ENOERR; 12 13 ENTER_CRITICAL_SECTION( ); 14 assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ); //断言宏,判断接收到的字节数<256,如果>256,终止程序 15 16 /* Length and CRC check */ 17 if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN ) 18 && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) ) 19 { 20 /* Save the address field. All frames are passed to the upper layed 21 * and the decision if a frame is used is done there. 22 */ 23 *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; //保存从站地址 24 25 /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus 26 * size of address field and CRC checksum. 27 */ 28 *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC ); //PDU长度 29 30 /* Return the start of the Modbus PDU to the caller. */ 31 *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //pucFrame指向PDU起始位置 32 xFrameReceived = TRUE; 33 } 34 else 35 { 36 eStatus = MB_EIO; 37 } 38 39 EXIT_CRITICAL_SECTION( ); 40 return eStatus; 41 }
eMBRTUReceive函数完成了CRC校验、帧数据地址和长度的赋值,便于给上层进行处理!之后eMBPoll函数发送 ( void )xMBPortEventPost( EV_EXECUTE )事件。在EV_EXECUTE 事件中,从站对接收到的数据进行处理,包括根据功能码寻找功能函数处理报文和调用eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ) 发送应答报文。
--- eMBRTUSend函数
1 eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) 2 { 3 /*函数功能 4 * 1:对响应报文PDU前面加上从机地址; 5 * 2:对响应报文PDU后加上CRC校; 6 * 3:使能发送,启动传输;*/ 7 eMBErrorCode eStatus = MB_ENOERR; 8 USHORT usCRC16; 9 10 ENTER_CRITICAL_SECTION( ); 11 12 /* Check if the receiver is still in idle state. If not we where to 13 * slow with processing the received frame and the master sent another 14 * frame on the network. We have to abort sending the frame. 15 */ 16 if( eRcvState == STATE_RX_IDLE ) 17 { 18 /* First byte before the Modbus-PDU is the slave address. */ 19 pucSndBufferCur = ( UCHAR * ) pucFrame - 1; //在协议数据单元前加从机地址 20 usSndBufferCount = 1; 21 22 /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ 23 pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; 24 usSndBufferCount += usLength; 25 26 /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */ 27 usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); 28 ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); 29 ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 ); 30 31 /* Activate the transmitter. */ 32 eSndState = STATE_TX_XMIT; //发送状态 33 //以下为新添加 34 xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); //发送一个字节的数据,进入发送中断函数,启动传输 35 pucSndBufferCur++; /* next byte in sendbuffer. */ 36 usSndBufferCount--; 37 38 vMBPortSerialEnable( FALSE, TRUE ); //使能发送,禁止接收 39 } 40 else 41 { 42 eStatus = MB_EIO; 43 } 44 EXIT_CRITICAL_SECTION( ); 45 return eStatus; 46 }
在 eMBRTUSend函数中会调用串口发送数据,在进入串口发送中断后会调用xMBRTUTransmitFSM发送状态机函数发送应答报文。
---xMBRTUTransmitFSM函数
1 BOOL xMBRTUTransmitFSM( void ) 2 { 3 BOOL xNeedPoll = FALSE; 4 5 assert( eRcvState == STATE_RX_IDLE ); 6 7 switch ( eSndState ) 8 { 9 /* We should not get a transmitter event if the transmitter is in 10 * idle state. */ 11 case STATE_TX_IDLE: 12 /* enable receiver/disable transmitter. */ 13 vMBPortSerialEnable( TRUE, FALSE ); //发送器处于空闲状态,使能接收,禁止发送 14 break; 15 16 case STATE_TX_XMIT: //发送器处于发送状态,在从机发送函数eMBRTUSend中赋值STATE_TX_XMIT 17 /* check if we are finished. */ 18 if( usSndBufferCount != 0 ) //发送数据 19 { 20 xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); 21 pucSndBufferCur++; /* next byte in sendbuffer. */ 22 usSndBufferCount--; 23 } 24 else //传递任务,发送完成 25 { 26 xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); //协议栈事件状态赋值为EV_FRAME_SENT,发送完成事件,eMBPoll函数会对此事件进行处理 27 /* Disable transmitter. This prevents another transmit buffer 28 * empty interrupt. */ 29 vMBPortSerialEnable( TRUE, FALSE ); //使能接收,禁止发送 30 eSndState = STATE_TX_IDLE; //发送器状态为空闲状态 31 } 32 break; 33 } 34 35 return xNeedPoll; 36 }
至此:协议栈准备工作,从机接受报文,解析报文,从机发送响应报文四部分结束。
原贴链接: http://www.cnblogs.com/wujing-hubei/p/5935142.html
参考:
freemodbus库函数详解 FreeModbus源码详解-程序设计