【modbus学习】野火Modbus主机接收到数据后的完整函数调用流程
文章目录
- Modbus主机接收数据流程:
- 1. **串口接收中断触发**
- 2. **接收中断处理** (`prvvUARTRxISR`)
- 3. **接收状态机** (`xMBMasterRTUReceiveFSM`)
- 4. **定时器启动** (`vMBMasterPortTimersT35Enable`)
- 5. **T3.5超时触发**
- 6. **定时器中断处理** (`prvvTIMERExpiredISR`)
- 7. **协议栈定时器处理** (`xMBMasterRTUTimerExpired`)
- 8. **事件处理** (`xMBMasterPortEventPost`)
- 9. **主程序轮询** (`eMBMasterPoll`)
- 9.1 `eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );`
- 9.2 后续判断代码:
- 10. **帧处理** (`eMBMasterRTUReceive`)
- 11. **回调函数调用**
- 完整调用链:
Modbus主机接收数据流程:
1. 串口接收中断触发
硬件中断 → USART2_IRQHandler → HAL_UART_IRQHandler → prvvUARTRxISR()
2. 接收中断处理 (prvvUARTRxISR
)
void prvvUARTRxISR(void)
{// 读取接收到的字节(void)xMBMasterPortSerialGetByte((CHAR*)&ucByte);// 调用接收状态机(void)pxMBMasterFrameCBByteReceived();// 计数器增加rx_int_count++;
}
3. 接收状态机 (xMBMasterRTUReceiveFSM
)
BOOL xMBMasterRTUReceiveFSM(void)
{// 读取字节(void)xMBMasterPortSerialGetByte((CHAR*)&ucByte);switch(eRcvState) {case STATE_M_RX_IDLE:// 收到第一个字节usMasterRcvBufferPos = 0;ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;eRcvState = STATE_M_RX_RCV; // 切换到接收状态// 启动T3.5定时器vMBMasterPortTimersT35Enable();break;case STATE_M_RX_RCV:// 继续接收数据if(usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX) {ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;} else {eRcvState = STATE_M_RX_ERROR; // 缓冲区溢出}// 重启T3.5定时器vMBMasterPortTimersT35Enable();break;}
}
4. 定时器启动 (vMBMasterPortTimersT35Enable
)
void vMBMasterPortTimersT35Enable()
{vMBMasterSetCurTimerMode(MB_TMODE_T35); // 设置T3.5模式HAL_TIM_Base_Start_IT(&htim4); // 启动定时器
}
5. T3.5超时触发
定时器中断 → TIM4_IRQHandler → HAL_TIM_IRQHandler → HAL_TIM_PeriodElapsedCallback → prvvTIMERExpiredISR()
6. 定时器中断处理 (prvvTIMERExpiredISR
)
void prvvTIMERExpiredISR(void)
{// 调用协议栈定时器处理(void)pxMBMasterPortCBTimerExpired();// 计数器增加timer_int_count++;
}
7. 协议栈定时器处理 (xMBMasterRTUTimerExpired
)
BOOL xMBMasterRTUTimerExpired(void)
{switch(eRcvState) {case STATE_M_RX_RCV:// 帧接收完成,发送事件xNeedPoll = xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED);break;}// 重置状态eRcvState = STATE_M_RX_IDLE;eSndState = STATE_M_TX_IDLE;// 停止定时器vMBMasterPortTimersDisable();return xNeedPoll;
}
8. 事件处理 (xMBMasterPortEventPost
)
BOOL xMBMasterPortEventPost(eMBMasterEventType eEvent)
{xMasterEventInQueue = eEvent; // 保存事件return TRUE;
}
9. 主程序轮询 (eMBMasterPoll
)
eMBErrorCode eMBMasterPoll(void)
{eMBMasterEventType eEvent;// 获取事件if(xMBMasterPortEventGet(&eEvent) == TRUE) {switch(eEvent) {case EV_MASTER_FRAME_RECEIVED:// 处理接收到的帧eStatus = peMBMasterFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength);break;}}
}
9.1 eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
这个函数的作用是解析接收到的Modbus帧:
参数说明:
&ucRcvAddress
:从机地址指针,函数会提取帧中的从机地址
&ucMBFrame
:数据帧指针,函数会提取帧中的数据部分
&usLength
:数据长度指针,函数会返回数据部分的长度
返回值:
eStatus
:解析结果 MB_ENOERR
:解析成功 MB_EIO
:解析失败(CRC错误、长度错误等)
9.2 后续判断代码:
if ( ( eStatus == MB_ENOERR ) && ( ucRcvAddress == ucMBMasterGetDestAddress() ) )
{( void ) xMBMasterPortEventPost( EV_MASTER_EXECUTE );
}
else
{vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA);( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS );
}
判断逻辑:
-
eStatus == MB_ENOERR
:
检查帧解析是否成功
如果CRC校验失败或长度错误,eStatus
就不是MB_ENOERR
-
ucRcvAddress == ucMBMasterGetDestAddress()
:
检查接收到的从机地址是否匹配
ucRcvAddress
:接收帧中的从机地址
ucMBMasterGetDestAddress()
:当前主机请求的目标从机地址
处理结果:
-
如果两个条件都满足:
发送EV_MASTER_EXECUTE
事件
表示"帧解析成功且地址匹配,可以执行功能处理" -
如果任一条件不满足:
设置错误类型为EV_ERROR_RECEIVE_DATA
发送EV_MASTER_ERROR_PROCESS
事件
表示"接收数据错误,需要错误处理"
简单说:
这段代码就是在验证接收到的Modbus帧是否有效:
- 帧格式是否正确(CRC、长度等)
- 从机地址是否匹配
- 根据验证结果决定下一步处理
10. 帧处理 (eMBMasterRTUReceive
)
eMBErrorCode eMBMasterRTUReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame, USHORT *pusLength)
{// CRC校验if(usMBCRC16(ucMasterRTURcvBuf, usMasterRcvBufferPos) == 0) {// 提取地址和数据*pucRcvAddress = ucMasterRTURcvBuf[MB_SER_PDU_ADDR_OFF];*pusLength = usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC;*pucFrame = &ucMasterRTURcvBuf[MB_SER_PDU_PDU_OFF];}
}
这个eMBMasterRTUReceive
函数是Modbus RTU帧解析函数:
函数功能:
解析接收到的Modbus RTU帧,提取地址、数据和长度信息。
参数说明:
pucRcvAddress
:从机地址指针,函数会将解析出的从机地址存储到这里
pucFrame
:数据帧指针,函数会将解析出的数据部分存储到这里
pusLength
:数据长度指针,函数会将数据部分的长度存储到这里
解析过程:
- 长度检查:
if( ( usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN )&& ( usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 ) )
检查接收缓冲区长度是否大于等于最小长度
检查CRC16校验是否正确(CRC校验结果为0表示正确)
- 提取从机地址:
*pucRcvAddress = ucMasterRTURcvBuf[MB_SER_PDU_ADDR_OFF];
从接收缓冲区中提取从机地址(第0个字节)
- 计算数据长度:
*pusLength = ( USHORT )( usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
总长度 = 接收长度 - 地址长度(1) - CRC长度(2)
得到实际的数据部分长度
- 设置数据指针:
*pucFrame = ( UCHAR * ) & ucMasterRTURcvBuf[MB_SER_PDU_PDU_OFF];
指向数据部分的起始位置(第1个字节开始)
返回值:
MB_ENOERR
:解析成功
MB_EIO
:解析失败(长度不够或CRC错误)
11. 回调函数调用
// 根据功能码调用相应的回调函数
switch(ucFunctionCode) {case MB_FUNC_READ_HOLDING_REGISTERS:eMBMasterRegHoldingCB(&ucMBFrame, &usLength);callback_count++; // 回调计数器增加break;
}
完整调用链:
串口中断 → 接收状态机 → 启动定时器 → T3.5超时 → 定时器中断 → 协议栈处理 → 事件通知 → 主程序轮询 → 帧处理 → 回调函数