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

接上文:freemodbus源码分析详解(上)

在整个协议栈运行的最初肯定是EV_READY态,然后过了一个3.5T(这个就是modbus的帧头帧尾确认时间啦,不清楚?去翻翻协议吧,我当然不建议你去读国人写的那些“modbus协议整理”之类的葵花宝典,而是建议你去modbus官网下载。找不到下载链接?看这里Modbus Specifications and Implementation Guides,点那个I Accept就可以进去啦。)如果这个时候接收到一个完整的帧那么就会进入EV_FRAME_RECEIVED态,至于是谁负责去接收和检验帧我们后面再去理,你要记住我们还在应用层里打转转。


/* Check if the frame is for us. If not ignore the frame. */ if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ) { ( void )xMBPortEventPost( EV_EXECUTE ); }

在EV_READY态如果检测收到的地址跟从机地址(freemodbus的开源版本只支持从机,如果你想要主机的可以参考一下FreeModbus_Slave-Master-RTT-STM32)匹配,或是广播地址就自己给自己投递一个EV_EXECUTE 事件。


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;

在EV_EXECUTE的第一段就是执行相应的功能码回调,也就是读写寄存器或者是打开线圈什么的,实现上就是执行mbfunctions里面的代码,因为在协议栈初始化的时候这些文件里面的函数都被值给了xFuncHandlers[],去看看xFuncHandlers[]的定义吧。


/* An array of Modbus functions handlers which associates Modbus function * codes with implementing functions. */ static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = { #if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0 {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID}, #endif #if MB_FUNC_READ_INPUT_ENABLED > 0 {MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister}, #endif #if MB_FUNC_READ_HOLDING_ENABLED > 0 {MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister}, #endif #if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0 {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister}, #endif #if MB_FUNC_WRITE_HOLDING_ENABLED > 0 {MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister}, #endif #if MB_FUNC_READWRITE_HOLDING_ENABLED > 0 {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister}, #endif #if MB_FUNC_READ_COILS_ENABLED > 0 {MB_FUNC_READ_COILS, eMBFuncReadCoils}, #endif #if MB_FUNC_WRITE_COIL_ENABLED > 0 {MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil}, #endif #if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0 {MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils}, #endif #if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0 {MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs}, #endif };

看到这里你就明白了xFuncHandlers不过是一个功能码和功能回调函数的对应表,eMBFuncWriteHoldingRegister()就是写保持寄存器回调。我们还是接着看EV_EXECUTE,第一段里面需要注意if( xFuncHandlers[i].ucFunctionCode == 0 )这一句是用来在结束遍历表, freemodbus提供了一个eMBRegisterCB()eMBRegisterCB函数专门用来注册功能码和与之相应的回调,但是对于不响应的功能码freemodbus通过xFuncHandlers[i].ucFunctionCode = 0;将其直接置0。

EV_EXECUTE第二段就是对主机作出回应。讲到这里接收处理就讲完了。在mb.c中我们可以看到这一层并不对EV_FRAME_SENT作处理。

mbrtu.c分析

在mb.c里面我们留了一个疑惑,是谁在投递事件?或者说是谁在改变mb.c里面状态机的状态?

如果是RTU模式,那么就是这mbrtu.c里面的这个函数了


BOOL xMBRTUTimerT35Expired( void ) { BOOL xNeedPoll = FALSE; switch ( eRcvState ) { /* Timer t35 expired. Startup phase is finished. */ case STATE_RX_INIT: xNeedPoll = xMBPortEventPost( EV_READY ); break; /* A frame was received and t35 expired. Notify the listener that * a new frame was received. */ case STATE_RX_RCV: xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED ); break; /* An error occured while receiving the frame. */ case STATE_RX_ERROR: break; /* Function called in an illegal state. */ default: assert( ( eRcvState == STATE_RX_INIT ) || ( eRcvState == STATE_RX_RCV ) || ( eRcvState == STATE_RX_ERROR ) ); } vMBPortTimersDisable( ); eRcvState = STATE_RX_IDLE; return xNeedPoll; }

这个函数是被vMBPortTimerPoll()被调用的,vMBPortTimerPoll()又是被xMBPortEventGet()调用的,这里我们看一下vMBPortTimerPoll()是在什么情况下调用xMBRTUTimerT35Expired:


void vMBPortTimerPoll( ) { /* Timers are called from the serial layer because we have no high * res timer in Win32. */ if( bTimeoutEnable ) { DWORD dwTimeCurrent = GetTickCount( ); if( ( dwTimeCurrent - dwTimeLast ) > dwTimeOut ) { bTimeoutEnable = FALSE; ( void )pxMBPortCBTimerExpired( ); } } }

可以看到就当系统的tickCount间隔达到一定时间时就调用xMBRTUTimerT35Expired()(pxMBPortCBTimerExpired在eMBInit()中被赋值为xMBRTUTimerT35Expired),简单点说吧,就相当于单片机定时器中断函数,定时执行xMBRTUTimerT35Expired()函数。

回到mbrtu.c中来吧,跟踪的第一要点是不能迷路,方向感要好!

xMBRTUTimerT35Expired就是根据eRcvState的不同状态来投递不同的事件给mb.c中的eMBPoll()这个状态机。而eRcvState又是怎么来的呢?在xMBRTUReceiveFSM()中我们看到了它。


BOOL xMBRTUReceiveFSM( void ) { BOOL xTaskNeedSwitch = FALSE; UCHAR ucByte; assert( eSndState == STATE_TX_IDLE ); /* Always read the character. */ ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte ); switch ( eRcvState ) { /* If we have received a character in the init state we have to * wait until the frame is finished. */ case STATE_RX_INIT: vMBPortTimersEnable( ); break; /* In the error state we wait until all characters in the * damaged frame are transmitted. */ case STATE_RX_ERROR: vMBPortTimersEnable( ); break; /* In the idle state we wait for a new character. If a character * is received the t1.5 and t3.5 timers are started and the * receiver is in the state STATE_RX_RECEIVCE. */ case STATE_RX_IDLE: usRcvBufferPos = 0; ucRTUBuf[usRcvBufferPos++] = ucByte; eRcvState = STATE_RX_RCV; /* Enable t3.5 timers. */ vMBPortTimersEnable( ); break; /* We are currently receiving a frame. Reset the timer after * every character received. If more than the maximum possible * number of bytes in a modbus frame is received the frame is * ignored. */ case STATE_RX_RCV: if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX ) { ucRTUBuf[usRcvBufferPos++] = ucByte; } else { eRcvState = STATE_RX_ERROR; } vMBPortTimersEnable( ); break; } return xTaskNeedSwitch; }

这里不兜圈子,直接告诉你xMBRTUReceiveFSM会在串口接收函数中被调用(虽然在这个WIN32例程中并没有中断例程)。我们这里主要分析一下xMBRTUReceiveFSM的流程。(未完待续!)

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

(0)

相关推荐