该代码是基于RT-thread操作系统的主机通信程序。 现将代码分析写下来,以便自己的移植操作以后后期的维护。 话还得从这里说起。。。 1,在mb.c文件中,eMBInit()函数,该函数首先进行回调函数指针的初始化。然后执行了很重要 的操作eMBRTUInit() -> xMBPortSerialInit()进行了串口的初始化操作,同时进行T3.5的初始化 回到函数eMBRTUInit()函数中,继续进行串口事件模型的初始化,初始化eMBState(协议栈工作状态) 为STATE_DISABLED。 2, 然后执行eMBEnable()函数,使能协议栈。调用函数指针pvMBFrameStartCur = eMBRTUStart 而函数eMBRTUStart()中,设置mbrtu.c文件中的静态变量接收状态机(eRcvState)为 STATE_RX_INIT,然后开启串口的接收状态,然后打开超时定时器。返回eMBEnable()后,设置 eMBState(协议栈工作状态)为使能状态STATE_ENABLED。 3, 截止目前为止初始化工作完成。接下来进入协议栈Poll函数eMBPoll()。 4, 如果初始化正确,则直接进入eMBPoll()函数的大循环。{ 获取事件=>查询事件 }一直执行。 5, 当前是硬件串口工作在接收状态,接收状态机STATE_RX_INIT, 当串口收到数据时候,进入串口中断、 调用prvvUARTRxISR()函数,ISR又调用pxMBFrameCBByteReceived(),该回调函数又在eMBRTUInit() 函数中被初始化为:mbrtu.c文件中的函数xMBRTUReceiveFSM(); 注意:当前的接收状态机STATE_RX_INIT,这个是为了保证接收是从一个完整的数据帧开始。具体是 怎么实现的呢?如下:当串口接收到一个字符时候,就根据当前的接收状态机,更新T3.5定时器。直到 当前接收的数据帧结束,这时候T3.5定时器中断服务函数执行xMBRTUTimerT35Expired()。 这样就可以将刚初始化状态即收到的数据帧舍去掉。 定时器中断是怎么执行到上面的函数的呢:TIMx_IRQHandler() -> prvvTIMERExpiredISR() -> pxMBPortCBTimerExpired() ,而回调函数pxMBPortCBTimerExpired被初始化为xMBRTUTimerT35Expired 6, 在mbrtu.c文件xMBRTUTimerT35Expired(),函数中:根据接收状态机的STATE_RX_INIT,更新事件模型 为EV_READY。然后关闭T3.5定时器,设置接收状态机状态为:STATE_RX_IDLE; 这个事件模型EV_READY在eMBPoll()函数中,最终是被剔除,不执行任何操作。接下来的一次接收 将会是一个完整的数据帧。 7 , 在下一帧接收的数据中,在函数xMBRTUReceiveFSM()中, 根据当前的接收状态机的状态:STATE_RX_IDLE。设置接收缓冲区指针为0, 根据当前的接收缓冲区指针usRcvBufferPos在缓冲区ucRTUBuf[]放置接收数据。同时设置接受状态机 状态为:STATE_RX_RCV;打开T3.5定时器。 8 , 在下一次进入xMBRTUReceiveFSM()函数中时候,当前的接收状态机的状态:STATE_RX_RCV 根据当前的接收缓冲区指针usRcvBufferPos,继续存放接收的数据,直到接收完成。每次要记得更新 超时定时器。如果指针大于一帧数据的最大值(MB_SER_PDU_SIZE_MAX),则设置接收状态机的 状态为接收错误STATE_RX_ERROR。 9 , 数据帧接受完成后,进入T3.5的中断服务函数xMBRTUTimerT35Expired()。根据接收状态机 STATE_RX_RCV,则抛出事件EV_FRAME_RECEIVED。同时关闭定时器。设置接受状态机为空闲 STATE_RX_IDLE。 10 ,现在切换到eMBPoll()函数,根据事件模型EV_FRAME_RECEIVED,调用peMBFrameReceiveCur() 函数。该回调函数被初始化为函数eMbRTUReceive()函数。在该函数eMbRTUReceive(),实际执行的 就是数据帧长度检查和CRC校验检查。同时提取数据帧中的从机地址,还提取出数据帧有效地址返回、 应用层。检查地址完毕,如果接收的主机地址是匹配从机地址或者收到的主机地址是广播地址。那么、 现在eMBPoll() 函数抛出事件:EV_EXECUTE。 11 , 现在继续在eMBPoll()函数中,根据事件模型EV_EXECUTE,执行以下功能:根据数据帧中提取出的 功能码,执行对应功能码的函数。该功能码函数就是进行即将发送出去的数据帧的提取,拷贝到发送 数据缓冲区ucRTUBuf[]。 如果接收到的不是广播地址,那么我们要进行回复。回复时候调用函数 peMBFrameSendCur()函数,该函数同样也是回调函数,他被初始化为:eMBRTUSend()函数。在函数 eMBRTUSend()函数中:首先判断接收状态机为接收空闲:根据上层传入的数据帧地址,提取从机地址 同时拷贝有用数据到ucRTUBuf[]。最后设置发送状态机STATE_TX_XMIT;关闭串口接收,打开串口发送 12 ,串口发送数据移位寄存器为空中断,在打开串口之后立马到来。则程序立即推进到串口发送中断服务 函数进行执行。prvvUARTTxReadyISR()->pxMBFrameCBTransmitterEmpty = xMBMasterRTUTransmitFSM()。 在函数xMBRTUTransmitFSM()中,根据发送状态机状态:STATE_TX_XMIT,同时根据发送缓冲区指针 进行数据的发送。直到数据发送完成。抛出世家模型EV_FRAME_SENT,设置发送状态机为空闲状态 STATE_TX_IDLE。 关闭串口的发送,开启串口接收。 13 ,根据事件EV_FRAME_SENT,在eMBPoll()中,退出大循环。 到此为止,modbus slave从机通信流程梳理完毕。 这里有三个状态的循环: 发送状态机(STATE_TX), 接收状态机(STATE_RX) 事件状态机(EV):上文中有颜色区分。 这是这两天重新分析了modbus主机从机协议,一点个人见解。其中也是经过了不少实际项目验证的。 但是所发文中不免有疏漏之处,欢迎各位朋友一起交流学习提高。
|