FreeModbus版本:1.6
eMBPoll在mb.c文件中
eMBPoll 函数是一个核心的 Modbus 协议栈事件处理函数,负责接收和发送帧,处理不同的事件,并根据需要返回错误码。
eMBErrorCode
eMBPoll( void )
{
static UCHAR *ucMBFrame; //接收到的帧数据
static UCHAR ucRcvAddress; //接收到的帧的地址
static UCHAR ucFunctionCode; //接收到的帧的功能码
static USHORT usLength; //接收到的帧的长度
static eMBException eException; //接收到的帧的异常码,用于发回去的错误帧
int i; //for循环变量
eMBErrorCode eStatus = MB_ENOERR;//函数返回值 错误码
eMBEventType eEvent; //事件状态类型
/* Check if the protocol stack is ready. */
if( eMBState != STATE_ENABLED ) //协议栈使能后是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: /*协议栈使能后,会将接收状态机赋值为初始化态,且定时器也使能,
* 定时器进入溢出中断,会将接收状态机由初始化态变为 空闲态
* 并且发布一个EV_READY事件*/
break;
case EV_FRAME_RECEIVED:/*接收状态机为空闲态时,接收到一个字符,会进入串口接收中断,
* 串口接收中断中会将接收到的字符保存作为帧首,并且把接收状态机变为STATE_RX_RCV。
* 也就是接收状态机为接收态,每接收到一个字符,会进入中断,然后把定时器清0。
* 直到距离下个字符超过定时器溢出时间,进入定时器中断,此时接受状态机为STATE_RX_RCV,这就意味着接受完成了一帧。
* 然后发布事件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 ) )
{
/*检查地址是否匹配,如果是则发布事件EV_EXECUTE*/
( 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;
}
eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );//发送响应帧
}
break;
case EV_FRAME_SENT:
break;
}
}
return MB_ENOERR;
}
注释已经写详细
在具体来看一下吧
首先定义变量,帧相关的变量都是静态的,循环进入的时候并不会被清0。
在调用协议栈使能函数之后,协议栈状态为使能态
获取事件,如果有事件发生xMBPortEventGet会返回TRUE,反之返回FALSE
然后根据获取到的时间进行分别处理
就是一个switch语句。
也就是说在while(1)大循环里调用eMBPoll ,就是在不停地读事件然后处理事件,每次只处理一个。
EV_READY
当就绪事件时啥也不做,就绪事件怎么来的呢。协议栈使能后,会将接收状态机赋值为初始化态,且定时器也使能,定时器进入溢出中断,会将接收状态机由初始化态变为空闲态,并且发布一个EV_READY事件。
也就是说,协议栈使能后,并不是立马开始工作的,万一这个时候串口接收寄存器已将有数据呢?并不能判断它是不是帧首,所以要等一个定时器溢出中断,发布一个就绪事件EV_READY,然后如果这时候在接收到数据才可以作为帧首。
妙啊,刚开始看别人讲一直没想明白,直到读源码才看明白。
EV_FRAME_RECEIVED
当一帧接收完成时,这个事件怎么来的呢。
接收状态机为空闲态时(每次定时器溢出中断会将接收机状态置为空闲态),接收到一个字符,会进入串口接收中断,串口接收中断中会将接收到的字符保存作为帧首,并且把接收状态机变为STATE_RX_RCV。也就是接收状态机为接收态,每接收到一个字符,会进入串口接收中断,然后把定时器计数清0。直到一帧结束,也就是距离下个字符超过定时器溢出时间,进入定时器中断,此时接收状态机还为STATE_RX_RCV,这就意味着接受完成了一帧。然后发布事件EV_FRAME_RECEIVED
通过函数eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );接收这一帧数据
检查帧地址是否匹配,发布事件EV_EXECUTE,这样下一次大循环进入eMBPoll就会处理EV_EXECUTE处理事件。
EV_EXECUTE
处理帧数据。
从一帧数据中,获取功能码。
异常码默认为非法功能码,如果处理成功则改为正常功能码。
遍历数组xFuncHandlers,这里面存放的是功能码以及对应的处理函数。
找到数组中的功能码,然后调用对应的处理函数
蓝色框框出来的就是对应的处理函数。
继续往下
如果地址不是广播地址,则要进行响应。(可以修改一下,就算是广播地址也响应,把外面的if注释掉就行)
如果处理过程中出现异常,则返回异常响应帧
取值 : 功能码 | 0x80 + 异常码
调用peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );发送响应帧
peMBFrameSendCur 会调用 eMBRTUSend,此函数就是组合好响应帧,真发还是串口中断发。但是这个函数里好像没有调用发送函数?
eMBRTUSend会将发送状态机设为发送态STATE_TX_XMIT。
在发送完成中断里会调用这个函数xMBRTUTransmitFSM
如果发送状态机为发送态,并且把该发送的数量都发完了,会发布事件EV_FRAME_SENT
发送完成事件不做处理
至此eMBPoll轮询完成。