前情提要
在自己的开发板上移植了野火的modbus主机程序并尝试使用。
问题背景
我使用STM32显示板作为Modbus主机连接电脑,并在电脑上运行Modbus Slave软件。测试中发现,读取保持寄存器和输入寄存器均失败,但写入操作正常。Modbus Slave可以正确接收到请求帧:
这说明主机发出的命令没有问题。然而,在我的代码中,用于存储保持寄存器的数组 usMRegHoldBuf[][]
始终为0,未能更新。进一步排查发现,程序并未进入回调函数 eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
。
已经解决了接收中断不触发的问题。在使用FreeModbus库进行通信时,发现从机返回的数据CRC校验失败,导致数据解析异常。
CRC校验失败
问题现象
在mb_m.c
文件的eMBErrorCode eMBMasterPoll( void )
中的分支添加解析结果的测试,结果显示parse_success_count
一直为0,parse_fail_count
递增,排查原因是CRC校验失败,导致解析失败。
case EV_MASTER_FRAME_RECEIVED:
eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
/* 测试:记录解析结果 */
extern volatile uint32_t parse_success_count, parse_fail_count;
if(eStatus == MB_ENOERR) {
parse_success_count++;
} else {
parse_fail_count++;
}
/* Check if the frame is for us. If not ,send an error process event. */
if ( ( eStatus == MB_ENOERR ) && ( ucRcvAddress == ucMBMasterGetDestAddress() ) )
{
( void ) xMBMasterPortEventPost( EV_MASTER_EXECUTE );
}
else
{
vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA);
( void ) xMBMasterPortEventPost( EV_MASTER_ERROR_PROCESS );
}
break;
原始问题数据
- 主机发送:
01 03 00 01 00 04 15 C9
- 从机返回:
01 03 08 00 02 00 03 00 04 00 05 73 55
(实际接收) - 期望返回:
01 03 08 00 02 00 03 00 04 00 05 73 D5
问题分析
1. CRC校验算法验证
使用Modbus CRC16算法对接收到的数据进行校验:
// Modbus CRC16计算函数
USHORT usMBCRC16(UCHAR * pucFrame, USHORT usLen)
{
UCHAR ucCRCHi = 0xFF;
UCHAR ucCRCLo = 0xFF;
int iIndex;
while(usLen--)
{
iIndex = ucCRCLo ^ *(pucFrame++);
ucCRCLo = (UCHAR)(ucCRCHi ^ aucCRCHi[iIndex]);
ucCRCHi = aucCRCLo[iIndex];
}
return (USHORT)(ucCRCHi << 8 | ucCRCLo);
}
2. 手动计算验证
对从机返回的数据进行计算:
- 数据部分:
01 03 08 00 02 00 03 00 04 00 05
(11字节) - 接收CRC:
73 55
- 计算CRC:
73 D5
发现差异: 最后一个字节应为 D5
但实际接收为 55
问题解决思路
通过检查代码配置,发现串口校验位不匹配:
- ModbusSlave工具配置:
115200-8-E-1
(偶校验) - 代码中配置:
UART_PARITY_NONE
(无校验)
// 原错误配置
#define MB_MASTER_USART_PARITY UART_PARITY_NONE
// 正确配置应为
#define MB_MASTER_USART_PARITY UART_PARITY_EVEN
问题解决
根本原因
校验位不匹配导致数据接收错误
虽然官方给的文档中说串口配置是无校验位
实际程序中的串口定义也是无校验位
)
但是主函数中,设置的是偶校验
)
而串口的数据位配置的是8位,这就导致了数据接收错误
解决方式1
修改校验位设置为
MB_PAR_NONE, /*!< No parity. */
/* FreeModbus主机初始化 */
eMBMasterInit(MB_RTU, MB_MASTER_USARTx, MB_MASTER_USART_BAUDRATE, MB_PAR_NONE);
解决方式2
修改usart.c
里MX_USART2_UART_Init
的工作模式配置,让它根据选择的校验模式自行判断应该配置成多少位。
/**
* @brief DEBUG_USART GPIO 配置,工作模式配置。115200 8-N-1
* @param 无
* @retval 无
*/
void MX_USART2_UART_Init(uint8_t ucPORT, uint32_t ulBaudRate, uint8_t eParity)
{
if(ucPORT != 2) //必须设置为串口2
return ;
huart2.Instance = DEBUG_USART;
huart2.Init.BaudRate = ulBaudRate;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = eParity;
/* 根据校验位设置数据位长度 */
if(eParity == UART_PARITY_NONE) {
huart2.Init.WordLength = UART_WORDLENGTH_8B;
} else {
huart2.Init.WordLength = UART_WORDLENGTH_9B;
}
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
while(1);
}
}
重新编译测试
修改后重新编译程序,测试结果:
- ✅ 接收数据正确:
01 03 08 00 02 00 03 00 04 00 05 73 D5
- ✅ CRC校验通过
- ✅ 数据解析正常