文章目錄
- 前情提要
- 問題背景
- CRC校驗失敗
- 問題現象
- 原始問題數據
- 問題分析
- 1. CRC校驗算法驗證
- 2. 手動計算驗證
- 問題解決思路
- 問題解決
- 根本原因
- 解決方式1
- 解決方式2
- 重新編譯測試
前情提要
在自己的開發板上移植了野火的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) //必須設置為串口2return ;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校驗通過
- ? 數據解析正常