前言
通过前面6章,我们已经手写完了:
-
Modbus RTU 完整协议帧解析
-
CRC16 标准算法
-
03/06/10 寄存器读写
-
05/0F 线圈开关量读写
-
全套标准异常码反馈
-
真机故障排查、总线干扰处理
-
Modbus TCP 基础原理
但是!之前的代码是教学式分散代码。
真正量产绝对不能堆在一个文件里。
本章我们完成 可用的 Modbus 工程架构重构:
-
彻底模块化分层(配置层、协议层、设备层、业务层)
-
实现一键切换 主机/从机 模式
-
整理完整寄存器、线圈架构
-
裸机高稳定框架 + FreeRTOS 多线程改造方案
-
解决粘包、断连、卡死、总线抢占问题
-
最终产出:可直接开产品的 Modbus 模板工程
读完本章,你以后所有项目的 Modbus 代码,全部可以直接套用这套架构。
1. 为什么你之前的代码不稳定?(架构病根)
新手写的 Modbus 普遍有以下致命问题:
-
所有代码堆在主循环/中断里,耦合严重,无法复用
-
宏定义、参数、业务逻辑混在一起,改一个参数全盘报错
-
主机从机两套代码冲突,不能灵活切换
-
没有分层,底层串口改了,协议层直接崩
-
裸机轮询卡死,没有状态机调度
工业级稳定的核心不是算法,而是架构分层。
2. 最终量产工程标准架构(必记)
我们将工程分为四层,层层隔离、互不干扰:
第一层:配置层(modbus_config.h)
所有可修改参数全部放这里:模式、地址、波特率、超时、寄存器数量。
第二层:协议层(modbus_protocol.c)
纯协议实现:CRC、帧组装、帧解析、异常码、功能码处理。不包含任何硬件、不包含业务。
第三层:设备驱动层(modbus_port.c)
串口收发、485 DE/RE 切换、定时器超时。只做硬件搬运,不做协议解析。
第四层:业务数据层(modbus_data.c)
寄存器、线圈、数据刷新、参数校验、读写拦截。
四层架构最大优势:改硬件不动协议,改业务不动驱动,改参数不动代码。
3. 完整工程文件结构
Modbus/
├─ modbus_config.h // 全局配置(模式、地址、波特率、超时)
├─ modbus_protocol.c // 协议核心、CRC、帧解析、功能码
├─ modbus_port.c // 串口硬件、485控制、收发底层
└─ modbus_data.c // 寄存器、线圈、业务数据管理
4. 第一层:全局配置文件(modbus_config.h)
真正的一键配置,无需改业务代码,即可切换主机/从机。
#ifndef __MODBUS_CONFIG_H
#define __MODBUS_CONFIG_H
// 模式选择:1=主机 0=从机
#define MODBUS_MASTER_MODE 0
// 基础通信配置
#define MODBUS_BAUDRATE 115200
#define MODBUS_TIMEOUT_MS 100
#define MODBUS_POLL_INTERVAL 100
// 设备从机地址
#define MODBUS_SLAVE_ADDR 0x01
// 缓冲区大小
#define MODBUS_TX_BUF_MAX 64
#define MODBUS_RX_BUF_MAX 64
// 寄存器配置
#define REG_HOLD_MAX 30
#define REG_RD_ONLY_END 9
#define REG_WR_START 10
// 线圈配置
#define COIL_MAX_NUM 16
#endif
5. 第二层:协议核心层(纯协议、无硬件)
5.1 CRC16 核心函数(协议层通用)
uint16_t Modbus_CRC16(uint8_t *data, uint16_t len)
{
uint16_t crc = 0xFFFF;
uint16_t i,j;
for(i = 0; i < len; i++)
{
crc ^= data[i];
for(j = 0; j < 8; j++)
{
if(crc & 1)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
void Modbus_Append_CRC(uint8_t *buf, uint8_t *len)
{
uint16_t crc = Modbus_CRC16(buf, *len);
buf[*len] = crc & 0xFF;
buf[*len+1] = (crc >> 8) & 0xFF;
*len += 2;
}
5.2 异常帧生成(协议层统一封装)
void Modbus_Slave_Return_Exception(uint8_t *tx_buf, uint8_t *tx_len, uint8_t func, uint8_t err_code)
{
*tx_len = 0;
tx_buf[(*tx_len)++] = MODBUS_SLAVE_ADDR;
tx_buf[(*tx_len)++] = func | 0x80;
tx_buf[(*tx_len)++] = err_code;
Modbus_Append_CRC(tx_buf, tx_len);
}
6. 第三层:硬件移植层(串口、485驱动)
所有硬件相关全部隔离在此文件,换芯片、换引脚只改这里。
// 485 收发模式切换
void Modbus_TX_EN(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
}
void Modbus_RX_EN(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
}
// 串口发送
void Modbus_Send_Data(uint8_t *buf, uint8_t len)
{
Modbus_TX_EN();
HAL_UART_Transmit(&huart1, buf, len, 50);
Modbus_RX_EN();
}
核心优势:协议层完全不用管485怎么切换,底层硬件变更不影响协议。
7. 第四层:业务数据层(寄存器+线圈管理)
统一管理所有可读写数据,集中刷新、集中拦截。
// 保持寄存器
uint16_t modbus_hold_reg[REG_HOLD_MAX] = {0};
// 线圈缓存
uint8_t modbus_coil_buf[(COIL_MAX_NUM+7)/8] = {0};
// 线圈设置
void Modbus_Coil_Set(uint16_t addr, uint8_t sta)
{
if(addr >= COIL_MAX_NUM) return;
uint16_t byte_id = addr / 8;
uint8_t bit_id = addr % 8;
if(sta)
modbus_coil_buf[byte_id] |= (1<<bit_id);
else
modbus_coil_buf[byte_id] &= ~(1<<bit_id);
}
// 业务数据刷新
void Modbus_Data_Update(void)
{
// 模拟传感器数据刷新
modbus_hold_reg[0] = (uint16_t)(temperature * 10);
modbus_hold_reg[1] = (uint16_t)(voltage * 10);
modbus_hold_reg[2] = motor_speed;
}
8. 状态机调度(彻底解决裸机卡死)
之前的裸机代码是死等应答,极易卡死。
量产必须用 状态机非阻塞调度。
typedef enum
{
MODBUS_IDLE,
MODBUS_SEND_REQ,
MODBUS_WAIT_ACK,
MODBUS_PARSE_ACK,
MODBUS_TIMEOUT_ERR
}Modbus_StateTypeDef;
Modbus_StateTypeDef modbus_state = MODBUS_IDLE;
void Modbus_Master_Schedule(void)
{
switch(modbus_state)
{
case MODBUS_IDLE:
// 定时发起轮询
if(HAL_GetTick() - poll_tick > MODBUS_POLL_INTERVAL)
{
poll_tick = HAL_GetTick();
Modbus_Master_Read_Reg(MODBUS_SLAVE_ADDR, 0, 3);
modbus_state = MODBUS_WAIT_ACK;
}
break;
case MODBUS_WAIT_ACK:
// 超时判断
if(HAL_GetTick() - modbus_tick > MODBUS_TIMEOUT_MS)
{
modbus_state = MODBUS_TIMEOUT_ERR;
}
// 收到应答
else if(rx_len > 0)
{
modbus_state = MODBUS_PARSE_ACK;
}
break;
case MODBUS_PARSE_ACK:
Modbus_Parse_Frame(3);
rx_len = 0;
modbus_state = MODBUS_IDLE;
break;
case MODBUS_TIMEOUT_ERR:
// 超时故障处理
fault_flag = 1;
rx_len = 0;
modbus_state = MODBUS_IDLE;
break;
}
}
核心提升:全程非阻塞,永不卡死!
9. FreeRTOS 多线程终极适配(工业量产标准)
裸机适合简单设备,复杂工控必须 RTOS 分层任务。
9.1 任务拆分原则
-
数据采集任务:10ms 刷新传感器、电压、转速
-
Modbus 通信任务:100ms 轮询、状态机调度
-
故障检测任务:检测掉线、丢包、异常计数
9.2 通信任务伪代码
void Modbus_Task(void *pvParameters)
{
while(1)
{
Modbus_Master_Schedule();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
RTOS 优势:
-
通信卡顿不影响控制逻辑
-
不会因为总线干扰卡死整机
-
可以精准控制轮询节拍
10. 量产终极优化清单(必看)
10.1 软件优化
-
所有读写寄存器增加越界判断、数值范围拦截
-
连续3次超时判定设备掉线,锁定故障状态
-
禁止主机连续发包,严格一问一答状态机
-
接收缓冲区做溢出保护,防止数组崩溃
10.2 硬件优化
-
485 增加 TVS 防静电、防浪涌
-
长距离必须屏蔽双绞线、单端接地
-
多设备组网必须手拉手链式拓扑
-
高频干扰环境使用隔离485、隔离电源
11. 总结
你从 0 基础学到了工业量产全套能力:
-
读懂 Modbus 所有帧结构、协议底层原理
-
手写 CRC、寄存器、线圈、异常处理全套代码
-
掌握主机、从机双模式开发
-
搞定真机99%通信故障排查
-
掌握工业四层架构、状态机调度、RTOS适配
-
拥有一套可以直接商用的 Modbus 工程模板
从此以后,任何传感器、工控板、电机设备、PLC通信,你都可以独立开发、独立调试、独立量产。