【精品博文】shawge:freemodbus源码分析详解(上)

我这里为了方便代码浏览,用了VS2013,DEMO自然用WIN32的,选用哪个DEMO进行分析也并不影响我们对FREEMODBUS的解剖。

代码组织结构

首先是modbus这个大文件下的文件:

  • ascii目录的文件是用于实现MODBUS ASCII模式的,这个在modbus里是可选实现代码比较简单,看完RTU的分析我相信你对比着自己也就看明白ASCII模式了,这将不是本文的重点。

  • funcions是与RTU的执行功能码相关的代码,主要就是读、写寄存器开关线圈之类的,根据你自己的需要在去实现里面回调,按照相应参数去执行相应功能。

  • include是freemodbus的一些定义,这里先不作分析,在看源代码的时候我们再去看每个数据结构的相关定义。

  • rtu这个文件夹就是RTU模式的实现了,本文分析重点之一。

  • port这个是移植相关,port.h是移植需要的函数声明。portevent.c这个是事件队列的实现,freemodbus只是用了一个消息作为队列简单赋值处理,portother.c是一相从字节里取位等与MODBUS没多大关系的函数,portserial.c是串口移植相关函数,porttimer.c是定时器相关移植(由于RTU方式依赖时间来判断帧头帧尾),移植相关可以参见我的另一篇博文(译自官方文档)freemodbus RTU/ASCII 官方移植文档。

  • mb.c这个就是modbus的应用层实现,本文分析重点之一。

    Source Files目录中的demo.cpp是例程,stdafx.cpp是WIN32的预编译文件与modbusfree无关。

流程分析

win32的main()分析,不感兴趣直接跳到mb.c一节

一切还是从main开始吧。


int _tmain( int argc, _TCHAR * argv[] ) { int iExitCode; TCHAR cCh; BOOL bDoExit; const UCHAR ucSlaveID[] = { 0xAA, 0xBB, 0xCC }; if( eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_EVEN ) != MB_ENOERR ) { _ftprintf( stderr, _T( "%s: can't initialize modbus stack!\r\n" ), PROG ); iExitCode = EXIT_FAILURE; } else if( eMBSetSlaveID( 0x34, TRUE, ucSlaveID, 3 ) != MB_ENOERR ) { _ftprintf( stderr, _T( "%s: can't set slave id!\r\n" ), PROG ); iExitCode = EXIT_FAILURE; } else { /* Create synchronization primitives and set the current state * of the thread to STOPPED. */ InitializeCriticalSection( &hPollLock ); eSetPollingThreadState( STOPPED ); /* CLI interface. */ _tprintf( _T( "Type 'q' for quit or 'h' for help!\r\n" ) ); bDoExit = FALSE; do { _tprintf( _T( "> " ) ); cCh = _gettchar( ); switch ( cCh ) { case _TCHAR( 'q' ): bDoExit = TRUE; break; case _TCHAR( 'd' ): eSetPollingThreadState( SHUTDOWN ); break; case _TCHAR( 'e' ): if( bCreatePollingThread( ) != TRUE ) { _tprintf( _T( "Can't start protocol stack! Already running?\r\n" ) ); } break; case _TCHAR( 's' ): switch ( eGetPollingThreadState( ) ) { case RUNNING: _tprintf( _T( "Protocol stack is running.\r\n" ) ); break; case STOPPED: _tprintf( _T( "Protocol stack is stopped.\r\n" ) ); break; case SHUTDOWN: _tprintf( _T( "Protocol stack is shuting down.\r\n" ) ); break; } break; case _TCHAR( 'h' ): _tprintf( _T( "FreeModbus demo application help:\r\n" ) ); _tprintf( _T( " 'd' ... disable protocol stack.\r\n" ) ); _tprintf( _T( " 'e' ... enabled the protocol stack\r\n" ) ); _tprintf( _T( " 's' ... show current status\r\n" ) ); _tprintf( _T( " 'q' ... quit applicationr\r\n" ) ); _tprintf( _T( " 'h' ... this information\r\n" ) ); _tprintf( _T( "\r\n" ) ); _tprintf( _T( "Copyright 2006 Christian Walter\r\n" ) ); break; default: if( cCh != _TCHAR('\n') ) { _tprintf( _T( "illegal command '%c'!\r\n" ), cCh ); } break; } /* eat up everything untill return character. */ while( cCh != '\n' ) { cCh = _gettchar( ); } } while( !bDoExit ); /* Release hardware resources. */ ( void )eMBClose( ); iExitCode = EXIT_SUCCESS; } return iExitCode; }

eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_EVEN ) != MB_ENOERR 初始化modbus协议栈,如果实始化失败则打印错误信息并退出,否则打印命令提示符,要求输入指令。


do { _tprintf( _T( "> " ) ); cCh = _gettchar( ); switch ( cCh ) { case _TCHAR( 'q' ): bDoExit = TRUE; break; case _TCHAR( 'd' ): eSetPollingThreadState( SHUTDOWN ); break; case _TCHAR( 'e' ): if( bCreatePollingThread( ) != TRUE ) { _tprintf( _T( "Can't start protocol stack! Already running?\r\n" ) ); } break; case _TCHAR( 's' ): switch ( eGetPollingThreadState( ) ) { case RUNNING: _tprintf( _T( "Protocol stack is running.\r\n" ) ); break; case STOPPED: _tprintf( _T( "Protocol stack is stopped.\r\n" ) ); break; case SHUTDOWN: _tprintf( _T( "Protocol stack is shuting down.\r\n" ) ); break; } break; case _TCHAR( 'h' ): _tprintf( _T( "FreeModbus demo application help:\r\n" ) ); _tprintf( _T( " 'd' ... disable protocol stack.\r\n" ) ); _tprintf( _T( " 'e' ... enabled the protocol stack\r\n" ) ); _tprintf( _T( " 's' ... show current status\r\n" ) ); _tprintf( _T( " 'q' ... quit applicationr\r\n" ) ); _tprintf( _T( " 'h' ... this information\r\n" ) ); _tprintf( _T( "\r\n" ) ); _tprintf( _T( "Copyright 2006 Christian Walter\r\n" ) ); break; default: if( cCh != _TCHAR('\n') ) { _tprintf( _T( "illegal command '%c'!\r\n" ), cCh ); } break; } /* eat up everything untill return character. */ while( cCh != '\n' ) { cCh = _gettchar( ); } } while( !bDoExit );

如果用户输入e,则会调用bCreatePollingThread( )启动协议栈线程。那么我们跟进bCreatePollingThread( )去看看。


BOOL bCreatePollingThread( void ) { BOOL bResult; if( eGetPollingThreadState( ) == STOPPED ) { if( ( hPollThread = CreateThread( NULL, 0, dwPollingThread, NULL, 0, NULL ) ) == NULL ) { /* Can't create the polling thread. */ bResult = FALSE; } else { bResult = TRUE; } } else { bResult = FALSE; } return bResult; }

先是确认一下线程状态,然后创建并启动线程函数dwPollingThread(),


DWORD WINAPI dwPollingThread( LPVOID lpParameter ) { eSetPollingThreadState( RUNNING ); if( eMBEnable( ) == MB_ENOERR ) { do { if( eMBPoll( ) != MB_ENOERR ) break; } while( eGetPollingThreadState( ) != SHUTDOWN ); } ( void )eMBDisable( ); eSetPollingThreadState( STOPPED ); return 0; }

从这里就跟MCU\ARM上应用freemodbus一样一样的了,无法是先使能协议栈,然后循环调用eMBPoll( ),同时用eGetPollingThreadState()检测线程状态。eMBPoll( void )就是我们的重点咯,我们现在已经进入mb.c这个文件啦,这个是freemodbus实现的modbus应用层,虽然代码里面对数据链路层以及应用层分的不是很清晰,但这个mb.c是完完全全的应用层了。

mb.c


eMBErrorCode eMBPoll( void ) { static UCHAR *ucMBFrame; static UCHAR ucRcvAddress; static UCHAR ucFunctionCode; static USHORT usLength; static eMBException eException; int i; eMBErrorCode eStatus = MB_ENOERR; eMBEventType eEvent; /* Check if the protocol stack is ready. */ if( eMBState != STATE_ENABLED ) { return MB_EILLSTATE; } /* Check if there is a event available. If not return control to caller. * Otherwise we will handle the event. */ if( xMBPortEventGet( &eEvent ) == TRUE ) { switch ( eEvent ) { case EV_READY: break; case EV_FRAME_RECEIVED: eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); 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; for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) { /* No more function handlers registered. Abort. */ if( xFuncHandlers[i].ucFunctionCode == 0 ) { break; } else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) { eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); break; } } /* If the request was not sent to the broadcast address we * return a reply. */ 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 ); } break; case EV_FRAME_SENT: break; } } return MB_ENOERR; }

eMBPoll()就是一个状态机。它只有下面四种状态:


typedef enum { EV_READY, /*!< Startup finished. */ EV_FRAME_RECEIVED, /*!< Frame received. */ EV_EXECUTE, /*!< Execute function. */ EV_FRAME_SENT /*!< Frame sent. */ } eMBEventType;

从注释中可以看出,分别是启动完成,帧接收完成,执行功能码,执行帧发送。

这个状态机通过xMBPortEventGet( &eEvent ) 获取事件状态,而事件状态的投递方是谁呢?这里我们先不关注(咱们自上向下分析吧)。我们先分析一下这个状态机的流程。

由于我在写这篇文章之前做过功课,所以比较清楚,这里大家过一下就可以了。(未完待续!)

◆ ◆ ◆ ◆ ◆◆ ◆ ◆ ◆ ◆◆ ◆ ◆ ◆ ◆◆ ◆ ◆ ◆ ◆

(0)

相关推荐