在工业自动化领域,Modbus协议因其简单可靠的特点被广泛应用于设备间通信。但标准Modbus协议仅支持寄存器、线圈等基础数据类型的读写,当我们需要传输完整文件(如固件升级包、配置文件)时,就面临三个关键问题:
针对这些问题,我们采用Write File Record功能码(0x15)实现分块传输,并通过定义特殊的Record0数据包携带文件元信息。这种设计有三大优势:
实际测试表明,这种方案在STM32H5平台可实现稳定的1.2KB/s传输速率,完全满足工业场景下固件更新的需求。
我们设计了一个包含文件核心属性的结构体:
c复制typedef struct FileInfo{
uint32_t version; // 文件版本号
uint32_t file_len; // 文件总长度(字节)
uint32_t load_addr; // 加载地址(用于固件烧录)
uint32_t crc32; // 文件校验值
uint8_t file_name[16]; // 固定长度的文件名
}FileInfo;
这个设计考虑了工业场景的典型需求:
Record0作为元信息载体需要特殊处理:
c复制// 发送Record0的示例代码
FileInfo info = {0};
info.file_len = htonl(file_size); // 转为网络字节序(大端)
strncpy(info.file_name, "firmware.bin", 16);
modbus_write_file_record(ctx, file_no, 0, &info, sizeof(info));
根据Modbus RTU协议限制,我们采用以下分块规则:
分块发送的核心逻辑:
c复制while(pos < total_len){
chunk_size = (total_len - pos) > 240 ? 240 : (total_len - pos);
modbus_write_file_record(ctx, file_no, ++record_no, data + pos, chunk_size);
pos += chunk_size;
}
由于Modbus采用大端序传输,而STM32是小端架构,需要双向转换:
c复制// 小端转大端(发送时使用)
uint32_t LE32toBE32(uint32_t val){
return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) |
((val >> 8) & 0xFF00) | ((val >> 24) & 0xFF);
}
// 大端转小端(接收时使用)
uint32_t BE32toLE32(uint8_t *buf){
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
}
实测发现,未做端序转换会导致接收方解析的文件长度值错误。例如发送1000字节的文件,接收方可能显示为167772160字节。
接收端通过以下特征识别Record0:
解析流程:
c复制if(record_no == 0){
memcpy(&file_info, data, sizeof(FileInfo));
file_info.file_len = BE32toLE32(&file_info.file_len);
total_bytes = file_info.file_len;
received_bytes = 0;
}
我们采用"累计写入+进度跟踪"的方式:
c复制// 数据块处理示例
offset = (record_no - 1) * 240;
memcpy(file_buffer + offset, data, data_len);
received_bytes += data_len;
在FreeRTOS环境下需要特别注意:
c复制void ModbusServerTask(void *pv){
modbus_t *ctx = modbus_new_rtu(...);
while(1){
modbus_receive(ctx, query);
// 处理逻辑
vTaskDelay(1); // 释放CPU
}
}
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 接收方CRC校验失败 | 端序转换未应用于CRC字段 | 在发送/接收时统一转换所有32位字段 |
| 文件尾部数据损坏 | 末块长度计算错误 | 改用total_len - pos计算剩余量 |
| 传输速度慢 | 未启用RTU帧间延时优化 | 设置modbus_set_response_timeout(50ms) |
本方案稍作修改即可支持:
c复制// 固件更新示例
if(strcmp(file_info.file_name, "bootloader.bin") == 0){
ProgramFlash(LOAD_ADDR, file_data, file_info.file_len);
}
经过实际项目验证,这套方案在STM32H5平台可实现:
对于需要更高可靠性的场景,建议在应用层添加ACK确认机制和断点续传功能。