FreeModbus--完全分析
说明:freemodbus-v1.5.0
主流程
[objc] view plaincopy
1. /* ----------------------- Start implementation ----------------------------
-*/
2. int
3. main( void )
4. {
5. eMBErrorCode eStatus;
6.
7. eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );
8.
9. /* Enable the Modbus Protocol Stack. */
10. eStatus = eMBEnable( );
11.
12. for( ;; )
13. {
14. ( void )eMBPoll( );
15.
16. /* Here we simply count the number of poll cycles. */
17. usRegInputBuf[0]++;
18. }
19. }
由上述主函数可知协议栈经 eMBInit 和 eMBEnable 初始化、使能后进入
协议栈的循环 eMBPoll 中。
eMBInit 分析
首先,使用 eMBInit 初始化协议栈,根据你使用的参数 eMBMode eMode
初始化相应的函数入口!
[objc] view plaincopy
1. #if MB_RTU_ENABLED > 0
2. case MB_RTU:
3. pvMBFrameStartCur = eMBRTUStart;
4. pvMBFrameStopCur = eMBRTUStop;
5. peMBFrameSendCur = eMBRTUSend;
6. peMBFrameReceiveCur = eMBRTUReceive;
7. pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
8. pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
9. pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
10. pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
11.
12. eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity )
;
13. break;
14. #endif
15. #if MB_ASCII_ENABLED > 0
16. case MB_ASCII:
17. pvMBFrameStartCur = eMBASCIIStart;
18. pvMBFrameStopCur = eMBASCIIStop;
19. peMBFrameSendCur = eMBASCIISend;
20. peMBFrameReceiveCur = eMBASCIIReceive;
21. pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
22. pxMBFrameCBByteReceived = xMBASCIIReceiveFSM;
23. pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM;
24. pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired;
25.
26. eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity
);
27. break;
28. #endif
以上代码中 pvMBFrameStartCur、pvMBFrameStopCur 等即协议栈函
数的接口,对于不同模式使用不同的函数进行赋值初始化!!此编写模
式可以借鉴学习!!
其中 eMBRTUInit 函数对底层驱动(串口和定时器)进行了初始化。
在上述初始化完成并且成功后对事件功能也进了初始化,最后全局变量
eMBState = STATE_DISABLED。
eMBEnable 的分析
[objc] view plaincopy
1. eMBErrorCode
2. eMBEnable( void )
3. {
4. eMBErrorCode eStatus = MB_ENOERR;
5.
6. if( eMBState == STATE_DISABLED )
7. {
8. /* Activate the protocol stack. */
9. pvMBFrameStartCur( );
10. eMBState = STATE_ENABLED;
11. }
12. else
13. {
14. eStatus = MB_EILLSTATE;
15. }
16. return eStatus;
17. }
由第一节的分析,此时将启动协议栈 pvMBFrameStartCur,查看程序
该函数指针被分配到为 eMBRTUStart。
该函数中将全局变量 eRcvState = STATE_RX_INIT,并使能串口和定
时器,注意此时的定时开始工作!!!
全局变量 eMBState =STATE_ENABLED。
eMBPoll 的分析
在此循环函数中 xMBPortEventGet(&eEvent ) == TRUE 先判断是否有事件,无事件发生则
不进入状态机!
还记得第二节定时器开始工作了吗?我们先看看该定时器如果超时了会发生什么事件!
在超时中断中我们将会调用 pxMBPortCBTimerExpired 函数,其中有以下代码:
[objc] view plaincopy
1. BOOL
2. xMBRTUTimerT35Expired( void )
3. {
4. BOOL xNeedPoll = FALSE;
5.
6. switch ( eRcvState )
7. {
8. /* Timer t35 expired. Startup phase is finished. */
9. case STATE_RX_INIT:
10. xNeedPoll = xMBPortEventPost( EV_READY );
11. break;
12.
13. /* A frame was received and t35 expired. Notify the listener that
14. * a new frame was received. */
15. case STATE_RX_RCV:
16. xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
17. break;
18.
19. /* An error occured while receiving the frame. */
20. case STATE_RX_ERROR:
21. break;
22.
23. /* Function called in an illegal state. */
24. default:
25. assert( ( eRcvState == STATE_RX_INIT ) ||
26. ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERR
OR ) );
27. }
28.
29. vMBPortTimersDisable( );
30. eRcvState = STATE_RX_IDLE;
31.
32. return xNeedPoll;
33. }
上一节分析中全局变量 eRcvState =STATE_RX_INIT,因此第二节所说的定时器第一次超
时将会发送 xNeedPoll =xMBPortEventPost( EV_READY )事件,
然后关闭定时器,全局变量 eRcvState =STATE_RX_IDLE。此时,在主循环 eMBPoll 中将
会执行一次 EV_READY 下的操作,
之后会一直执行 eMBPoll,整个协议栈开始运行!
接收数据分析
由于 FreeModbus 只支持从机模式,因此我们分析一下其在接收到数据后的操作!!!
接收数据
在上三节的操作中,我们可以知道进入 eMBPoll 循环后,串口中断是开启的。因此在接收
到数据的时候,首先响应的应该是串口中断程序。
接收中断中将会调用接收状态机:
[objc] view plaincopy
1. BOOL
2. xMBRTUReceiveFSM( void )
3. {
4. BOOL xTaskNeedSwitch = FALSE;
5. UCHAR ucByte;
6.
7. assert( eSndState == STATE_TX_IDLE );
8.
9. /* Always read the character. */
10. ( void )xMBPortSerialGetByte( ( CHARCHAR * ) & ucByte );
11.
12. switch ( eRcvState )
13. {
14. /* If we have received a character in the init state we have to
15. * wait until the frame is finished.
16. */
17. case STATE_RX_INIT:
18. vMBPortTimersEnable( );
19. break;
20.
21. /* In the error state we wait until all characters in the
22. * damaged frame are transmitted.
23. */
24. case STATE_RX_ERROR:
25. vMBPortTimersEnable( );
26. break;
27.
28. /* In the idle state we wait for a new character. If a character
29. * is received the t1.5 and t3.5 timers are started and the
30. * receiver is in the state STATE_RX_RECEIVCE.
31. */
32. case STATE_RX_IDLE:
33. usRcvBufferPos = 0;
34. ucRTUBuf[usRcvBufferPos++] = ucByte;
35. eRcvState = STATE_RX_RCV;
36.
37. /* Enable t3.5 timers. */
38. vMBPortTimersEnable( );
39. break;
40.
41. /* We are currently receiving a frame. Reset the timer after
42. * every character received. If more than the maximum possible
43. * number of bytes in a modbus frame is received the frame is
44. * ignored.
45. */
46. case STATE_RX_RCV:
47. if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
48. {
49. ucRTUBuf[usRcvBufferPos++] = ucByte;
50. }
51. else
52. {
53. eRcvState = STATE_RX_ERROR;
54. }
55. vMBPortTimersEnable( );
56. break;
57. }
58. return xTaskNeedSwitch;
59. }
经过第 3 节的分析,此时全局变量 eRcvState =STATE_RX_IDLE。接收状态机开始后,读
取 UART 串口缓存中的数据,
并进入 STATE_RX_IDLE 分支中存储一次数据后开启超时定时器,进入 STATE_RX_RCV
分支继续接收后续的数据,
直至定时器超时!为什么要等待超时才能停止接收呢!
[objc] view plaincopy
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;
可以发现接收数据时发生超时后,协议栈会发送 EV_FRAME_RECEIVED 接收完成这个信
号。此时 eMBPoll 接收到此信号后会调用 eMBRTUReceive 函数。
[objc] view plaincopy
1. eMBErrorCode
2. eMBRTUReceive( UCHARUCHAR * pucRcvAddress, UCHARUCHAR ** pucFrame, USHORTUSH
ORT * pusLength )
3. {
4. BOOL xFrameReceived = FALSE;
5. eMBErrorCode eStatus = MB_ENOERR;
6.
7. ENTER_CRITICAL_SECTION( );
8. assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
9.
10. /* Length and CRC check */
11. if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
12. && ( usMBCRC16( ( UCHARUCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
13. {
14. /* Save the address field. All frames are passed to the upper layed
15. * and the decision if a frame is used is done there.
16. */
17. *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];
18.
19. /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
20. * size of address field and CRC checksum.
21. */
22. *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SE
R_PDU_SIZE_CRC );
23.
24. /* Return the start of the Modbus PDU to the caller. */
25. *pucFrame = ( UCHARUCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];
26. xFrameReceived = TRUE;
27. }
28. else
29. {
30. eStatus = MB_EIO;
31. }
32.
33. EXIT_CRITICAL_SECTION( );
34. return eStatus;
35. }
eMBRTUReceive 函数完成了 CRC 校验、帧数据地址、长度的赋值,便于给上层进行处理!
之后发送( void)xMBPortEventPost( EV_EXECUTE )事件。
处理数据时根据功能码调用相应的函数,这些函数存储在 xFuncHandlers 数组中!之后发
送响应!完成一次操作!
功能码 0x04 读输入寄存器
在一个远程设备中,使用该功能码读取 1 至大约 125 的连续输入寄存
器。请求 PDU 说明了起始地址和寄存器数量。
将响应报文中的寄存器数据分成每个寄存器为两字节,在每个字节中直
接地调整二进制内容。
对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位
比特。
实例:
以下是 FreeModbus 的代码:
[objc] view plaincopy
1. eMBException
2. eMBFuncReadInputRegister( UCHARUCHAR * pucFrame, USHORTUSHORT * usLen )
3. {
4. USHORT usRegAddress;
5. USHORT usRegCount;
6. UCHAR *pucFrameCur;
7.
8. eMBException eStatus = MB_EX_NONE;
9. eMBErrorCode eRegStatus;
10.
11. if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) )
12. {
13. usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8
);
14. usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1]
);
15. usRegAddress++;
16.
17. usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8
);
18. usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1]
);
19.
20. /* Check if the number of registers to read is valid. If not
21. * return Modbus illegal data value exception.
22. */
23. if( ( usRegCount >= 1 )