Modbus RTU/TCP协议在STM32中的完整实现与异常处理
扫描二维码
随时随地手机看文章
在工业现场,Modbus凭借其简单性成为事实标准。在STM32上实现Modbus,核心难点在于RTU帧同步与TCP粘包处理。本文将基于FreeModbus库,详解STM32上Modbus RTU与TCP的完整实现,并提供健壮的异常处理机制。
一、架构选择:RTU vs TCP
特性 Modbus RTU Modbus TCP
物理层 RS485 (UART) Ethernet (LwIP)
帧界定 3.5字符静默时间 MBAP报文头 (Transaction ID)
复杂度 高(需定时器) 低(TCP已处理分包)
实时性 高(无需TCP/IP栈开销) 受网络状况影响
推荐方案:实时性要求高的本地控制选RTU,远程监控或已有以太网设施选TCP。
二、Modbus RTU在STM32上的实现(基于FreeModbus)
RTU的关键挑战是帧边界检测。FreeModbus利用UART的空闲中断(Idle Line Detection)自动处理3.5T静默时间。
1. 硬件初始化(UART + RS485 DE/RE控制)
// 初始化UART为Modbus波特率(如115200)
void Modbus_UART_Init(uint32_t baudrate) {
huart1.Init.BaudRate = baudrate;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart1);
// 使能UART IDLE中断(关键!用于RTU帧结束检测)
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
// RS485发送使能(DE/RE引脚控制)
void RS485_TransmitEnable(void) {
HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);
}
void RS485_ReceiveEnable(void) {
HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);
}
2. FreeModbus移植(mbportserial.c)
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) {
// 调用上面的Modbus_UART_Init
Modbus_UART_Init(ulBaudRate);
return TRUE;
}
// UART接收完成回调(在stm32xx_it.c中调用)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 将接收到的字节送入FreeModbus协议栈
pxMBFrameCBByteReceived();
HAL_UART_Receive_IT(&huart1, &rx_byte, 1); // 重新开启接收
}
}
// IDLE中断处理(帧结束)
void HAL_UART_IdleLineCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 通知协议栈一帧数据接收完毕
pxMBFrameCBReceiveFSMCur();
}
}
三、Modbus TCP在STM32上的实现(LwIP + FreeModbus)
TCP模式下,FreeModbus作为TCP Server,监听502端口。
1. TCP服务器初始化
// 在LwIP初始化完成后调用
void Modbus_TCP_Init(void) {
// FreeModbus TCP端口初始化
eMBTCPInit(502); // 标准Modbus TCP端口
}
2. 处理TCP连接与数据接收
FreeModbus的TCP部分需要你提供一个“接收线程”或回调,将LwIP收到的数据喂给协议栈。
// 在LwIP的tcp_recv回调中
err_t mb_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
if (p != NULL) {
// 将pbuf中的数据复制到Modbus TCP缓冲区
// 注意:TCP是流式协议,可能粘包,但MBAP头包含长度
eMBTCPReceive(p->payload, p->len);
tcp_recved(tpcb, p->len);
pbuf_free(p);
}
return ERR_OK;
}
四、核心:寄存器映射与回调函数
无论RTU还是TCP,应用层逻辑都在回调函数中实现。这是处理“读线圈”、“写保持寄存器”的地方。
// 保持寄存器映射数组
uint16_t usRegHoldingBuf[100];
// 读保持寄存器回调
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress,
USHORT usNRegs, eMBRegisterMode eMode) {
int iRegIndex = usAddress - 1;
if ((iRegIndex + usNRegs) > 100) {
return MB_ENOREG;
}
if (eMode == MB_REG_READ) {
for (int i = 0; i < usNRegs; i++) {
pucRegBuffer[i*2] = (usRegHoldingBuf[iRegIndex+i] >> 8);
pucRegBuffer[i*2+1] = (usRegHoldingBuf[iRegIndex+i] & 0xFF);
}
} else if (eMode == MB_REG_WRITE) {
for (int i = 0; i < usNRegs; i++) {
usRegHoldingBuf[iRegIndex+i] = (pucRegBuffer[i*2] << 8) | pucRegBuffer[i*2+1];
// 关键:写寄存器后的业务逻辑(如更新PWM、IO输出)
Update_Device_State(iRegIndex+i);
}
}
return MB_ENOERR;
}
五、异常处理与健壮性设计
1. RTU常见异常与处理
• CRC错误:FreeModbus自动丢弃,无需处理。
• 帧超时/不完整:IDLE中断配合超时定时器,若超过3.5T仍未收满,调用eMBRTUReceiveFSM()的超时处理,清空接收缓冲区。
• 波特率自适应(高级):检测Break信号后,尝试不同波特率同步。
2. TCP常见异常与处理
• 连接断开:在tcp_err回调中调用eMBTCPDisconnect(),释放资源。
• 非法功能码/地址:在回调函数中返回MB_ILLFUNC或MB_ILLEGAL_ADDRESS,协议栈会自动回复异常报文。
• 看门狗监控:Modbus任务若长时间阻塞,触发IWDG复位。
// 在Modbus Poll任务中
void Modbus_Task(void *arg) {
while(1) {
IWDG_FEED(); // 喂狗
eMBPoll(); // FreeModbus轮询
osDelay(10);
}
}
3. 资源冲突(RTOS下)
• 临界区保护:在eMBRegHoldingCB中,若操作共享数据(如全局变量),需使用互斥锁(osMutexAcquire)。
• 中断优先级:UART中断优先级需高于Modbus任务,但低于系统最高优先级。
六、结语
在STM32上实现Modbus,RTU的难点在于UART IDLE中断与3.5T定时,而TCP的难点在于LwIP回调与线程安全。利用FreeModbus库,将精力集中在寄存器映射回调和异常处理(看门狗、连接管理)上,是构建工业级稳定通信系统的捷径。





