FreeModBus源码解析(1)---开篇
一、设计思想
任何通信协议的实现都是基于状态机的设计思想,就是来了一串数据判断是是干啥的在调用相应的处理函数只不过高手一般采用回调处理。
- 如果你熟悉了回调、源码里的状态机的实现又可以理解,那么恭喜你已经掌握了通信协议的实现方法。
- 如果你可以从源妈里体会到分层的设计思想,那么恭喜你已经触碰到了架构师的门槛。
本系列文章就是通过对FreeModeBus源码进行解析来掌握以上技能。
二、ModBus协议简介以及状态机的实现
为啥把ModBus协议简介与状态机的实现放在一起呢???
状态机编写当然是基于通信协议标准。
如果通信数据格式、功能码、处理流程都搞不清楚,你编出来的状态机也是你“个人版的通信协议标准”,与符合ModBus通信协议标准的设备也是通信不上的。
1、 ModBus 设备间通信处理流程如下:
- 主设备向从设备发送请求
- 从设备分析并处理主设备的请求,然后向主设备发送结果
- 如果出现任何差错,从设备将返回一个异常功能码
这里不对ModBus协议进行过多介绍,网上有很多资料。但有个疑点还是有必要搞清楚。在ModbusTCP里什么是主站(主设备)?什么是从站(从设备)??
我相信会有人不假思索地说主站不就是服务器,从站不就是客户端码???
哈哈,如果你仔细读了 ModBus 设备间通信处理流程的话,你可以搞懂的。俺老李就不老生常谈了。
2、状态机的实现解析
首先从网上下载modbus协议源码文件,打开文件夹如下:
首先使用Source Insight编辑器打开mb.c文件(一般重要的文件放在工程的根目录下相相当于main函数,这个和人类的编程习惯有关)。
打开之后。嗯分析文件之前先说下状态机,状态机的运行是要轮询poll是否建立modbus通信、判断通信数据是接收请求还是请求响应。
注意关键字poll在编辑器的左下角找到带有poll的函数。
就是eMBPoll函数,这里面就有实现设备间Modbus通信状态机源代码以及相应的简单注释如下所示:
1 /* Check if the protocol stack is ready. */
//判断协议栈硬件环境有没有准备。好比如ModBusTCP协议里基于W5500以太网芯片的初始化,sockt通信建立提供TCP服务。 2 if( eMBState != STATE_ENABLED ) 3 { 4 return MB_EILLSTATE; 5 } 6 7 /* Check if there is a event available. If not return control to caller. 8 * Otherwise we will handle the event. */
//通过xMBPortEventGet函数处理如果有事件发生得到状态机的状态值并赋值给eEvent,
//然后通过switch语句运行状态机调用相应函数进行处理,当然这里用了函数指针、回调。 9 if( xMBPortEventGet( &eEvent ) == TRUE ) 10 {
// 11 switch ( eEvent ) 12 { 13 case EV_READY: 14 break; 15 16 case EV_FRAME_RECEIVED: 17 eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); 18 if( eStatus == MB_ENOERR ) 19 { 20 /* Check if the frame is for us. If not ignore the frame. */ 21 if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ) 22 { 23 ( void )xMBPortEventPost( EV_EXECUTE ); 24 } 25 } 26 break; 27 28 case EV_EXECUTE: 29 ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; 30 eException = MB_EX_ILLEGAL_FUNCTION; 31 for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) 32 { 33 /* No more function handlers registered. Abort. */ 34 if( xFuncHandlers[i].ucFunctionCode == 0 ) 35 { 36 break; 37 } 38 else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) 39 { 40 eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); 41 break; 42 } 43 } 44 45 /* If the request was not sent to the broadcast address we 46 * return a reply. */ 47 if( ucRcvAddress != MB_ADDRESS_BROADCAST ) 48 { 49 if( eException != MB_EX_NONE ) 50 { 51 /* An exception occured. Build an error frame. */ 52 usLength = 0; 53 ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR ); 54 ucMBFrame[usLength++] = eException; 55 } 56 if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ) 57 { 58 vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ); 59 } 60 eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); 61 } 62 break; 63 64 case EV_FRAME_SENT: 65 break; 66 } 67 }
从程序源码里面解析框架图如下:
大家可以根据上图理解状态机的结构框架,本文解析到此结束。关于相关在状态机里面怎么回调相关处理函数的下章解析。