说到发送状态机就简单多了,因为不用去判断自己发送的时候是否会超时,基本上就是没需求就在STATE_TX_IDLE状态,要发送就进入STATE_TX_XMIT状态。
看到xMBRTUTimerT35Expired这里想到一个问题,在timer初始化的时候设定的时基是50us的时间就中断一次,那么modbus是如何实现不同波特率下3.5个字符时间判断的呢?我们看到在timer中断中调用了pxMBPortCBTimerExpired这个函数,但是实际上T35超时函数调用的是xMBRTUTimerT35Expired这个函数,它们之间是什么关系呢?发现在eMBRTUInit中对xMBPortTimersInit(
( USHORT )
usTimerT35_50us)进行了重新初始化,如果波特率大于19200,那么就采用固定的1800us时间,否则就用3.5个字符时间间隔。我觉得之所以在波特率大的时候才用固定的时间可能为了防止出现判断时间过小时误判了正常的线路延迟的原因。
(2)运行到eStatus = eMBEnable(
)这里。这个比较简单,就是激活协议栈以及串口和定时器而已。
(3)运行到( void )eMBPoll( ),进去看一下。这里面比较关键的地方就是if(
xMBPortEventGet( &eEvent ) == TRUE
)控制了一个事件处理状态机,这就要问了,这个事件处理是个啥玩意?也许进去看看就可以知道了。原来在这个获取事件的玩意还有个叫事件队列的东西*eEvent
= eQueuedEvent,原来这是上述两个状态机和一个超时函数中会反馈一些事件例如xMBPortEventPost(
EV_FRAME_SENT ),来供调度。具体调度方法不去深究。
六、对modbus
RTU模式的接收发送机制分析
明白了RTU模式的运行流程,下面对RTU模式的接收和发送模式的分析。
(1)我们用于使用的接收buf是哪个呢?什么状态时数据是有效的?
对于这个问题,我们还得从xMBRTUReceiveFSM入手,可以发现当进入STATE_RX_IDLE状态时可能是一帧数据传输完毕,到mbrtu.c
中的eMBInit里看一下,显然STATE_RX_IDLE接收了第一个字节后,STATE_RX_RCV将剩下的字节放到了ucRTUBuf中,usRcvBufferPos为接收的数据长度,那么肯定应该在经过一个T35的时间后结束的对不对,去看看就知道了。在xMBRTUTimerT35Expired函数中发现了以下语句:
case STATE_RX_RCV:
xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED );
break;
不就说明当eMBPoll
收到EV_FRAME_RECEIVED消息的时候,告诉eMBPoll新的一帧数据来了,要去ucRTUBuf中对usRcvBufferPos的数据进行处理吗,这就对了!我们在返回到eMBPoll这个函数中一探究竟。
现在再看事件探测器if( xMBPortEventGet( &eEvent ) == TRUE ) 就明朗多了,当触发case
EV_FRAME_RECEIVED:时,调用peMBFrameReceiveCur去获取消息帧,这个函数的实现是谁呢?就是eMBRTUReceive!打开一看就一目了然。eMBRTUReceive把ucRTUBuf的数据信息取出来通过eStatus
=peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame,
&usLength );
重新赋给了从机地址ucRcvAddress、帧内容开始地址ucMBFrame、数据长度usLength(不包括地址位以及CRC校验位)。
(2)消息取出来了,怎么用?还是回到eMBPoll接着看case
EV_FRAME_RECEIVED:接下来干啥了,检查消息是不是给我们的,要是不是广播的消息并且没有发生异常我们就进行处理,我们的地址在eMBInit中已经设定。于是就到了case
EV_EXECUTE:状态,接下来就要看发来的数据帧是干什么的了,这要通过功能码来判断。
Freemodbus支持以下功能码:
http://img.my.csdn.net/uploads/201303/26/1364282542_4939.jpg
Modbus支持的功能码
如果检测到该帧数据是协议栈支持的功能码,就调用相应的函数进行处理,比如说Read input
register,就会调用在mb.c中定义的static
xMBFunctionHandler
xFuncHandlers[MB_FUNC_HANDLERS_MAX]这个二维数组中注册的eMBFuncReadInputRegister函数进行处理。还记得我们之前实现的eMBRegInputCB函数有什么区别呢,为什么调用的不是这个函数呢,其实仔细看下eMBFuncReadInputRegister这个函数就行了,原来eMBFuncReadInputRegister调用了eMBRegInputCB这个函数!还以为之前搞错了。
看这个函数之前先看主机发送过来的代码格式:
查询输入寄存器命令格式
发送的数据帧里包括从机地址、功能码,寄存器起始地址、以及读寄存器的长度、CRC校验。eMBFuncReadInputRegister要做的事儿就是读出寄存器地址以及寄存器长度后调用eMBRegInputCB,读取成功后返回一个MB_ENOERR状态,表明没有错误发生。
(3)接着看eMBPoll中的case
EV_EXECUTE:执行完
eException=
xFuncHandlers[i].pxHandler( ucMBFrame, &usLength
);这个处理函数,寄存器值读出来了,帧格式也写好了,什么地方调用发送函数了呢?找了大半天,终于在串口中断这个地方看到了,原来当串口发送完成时调用了prvvUARTTxReadyISR,又调用了pxMBFrameCBTransmitterEmpty,也就是xMBRTUTransmitFSM发送状态机,当满足STATE_TX_XMIT状态时,调用xMBPortSerialPutByte将数据发送出去。
那么是什么东西使发送状态机从STATE_TX_IDLE变化到了STATE_TX_XMIT状态呢?我们的eMBRTUSend似乎没有用到?那么到eMBRTUSend里面去看看他做了什么。原来它做的事情就是当状态机处于STATE_RX_IDLE接收空闲的时候把发送缓冲区pucSndBufferCur的数据填写好从机地址、对其中数据进行CRC16校验,把状态机置为STATE_TX_XMIT,使能串口。
到底是谁调用了eMBRTUSend呢?真是一个问题接着一个问题呀。搜一下peMBFrameSendCur这个函数,原来还是在eMBPoll中的case
EV_EXECUTE:中,如果不是广播消息的话,我们就返回一条消息,于是调用了peMBFrameSendCur。
说了大半天可能都给搞糊涂了,整理下发送和接收的整体思路:
协议栈以及定时器初始化T35第一次超时—>eMBPoll
STATE_RX_IDLE—>收到数据中断—>prvvUARTRxISR—>pxMBFrameCBByteReceived—>xMBRTUReceiveFSM接收数据
—> STATE_RX_RCV—>T35超时—>
eMBPollEV_FRAME_RECEIVED(peMBFrameReceiveCur->eMBRTUReceive)提取完整数据帧—>eMBPoll
case EV_EXECUTE:xFuncHandlers[i].pxHandler(eMBRegInputCB)对接收的数据进行处理—>
peMBFrameSendCur—>eMBRTUSend(&STATE_RX_IDLE)—>STATE_TX_XMIT
串口发送完成中断—>
prvvUARTTxReadyISR—>
FSMpxMBFrameCBTransmitterEmpty—>xMBRTUTransmitFSM(&
STATE_TX_XMIT)—>xMBPortSerialPutByte—>发送数据。
这样是不是明白了很多,freemodbus状态机写的还是很巧妙的。
想要深入了解的话,可以读读这篇文章,分析的很好:
FreeModbus移植 经验分享:http://www.amobbs.com/forum.php?mod=viewthread&tid=5491615&highlight=freemodbus