1. 文件传输协议设计与实现
在嵌入式系统和工业控制领域,Modbus协议因其简单可靠而广泛应用。但标准Modbus协议本身并不直接支持文件传输功能,这就需要我们在应用层自行设计实现方案。本文将详细介绍如何基于Modbus的Write File Record功能构建完整的文件传输机制。
文件传输的核心挑战在于:标准Modbus RTU帧最大仅256字节,而实际文件往往远大于此。因此必须设计合理的分块传输机制和文件信息结构。
1.1 为什么需要自定义文件传输协议
标准Modbus的Write File Record功能(功能码0x15)虽然可以写入文件记录,但存在以下局限性:
- 单次传输数据量有限(最大约244字节)
- 缺乏文件元信息(文件名、总大小等)
- 没有校验机制确保数据完整性
为解决这些问题,我们需要在应用层设计协议扩展:
- 使用记录号0专门传输文件元信息
- 后续记录号传输实际文件数据
- 增加CRC校验字段验证数据完整性
这种设计类似于快递系统:先发送"快递单"告知文件信息,再分批次发送"包裹"传输实际内容。
2. 文件信息结构设计
2.1 FileInfo结构体详解
c复制typedef struct FileInfo {
uint32_t version; // 协议版本号
uint32_t file_len; // 文件总长度(字节)
uint32_t load_addr; // 文件加载地址
uint32_t crc32; // 文件CRC32校验值
uint8_t file_name[16];// 文件名(最大15字符+'\0')
} FileInfo, *PFileInfo;
各字段作用说明:
version:协议版本控制,便于后续升级file_len:文件总大小,接收方据此计算需要接收的数据量load_addr:文件在设备中的加载地址(如固件烧录位置)crc32:整个文件的校验值,用于验证传输完整性file_name:便于用户识别的文件名
2.2 字节序处理要点
Modbus协议规定多字节数据必须采用大端(Big-Endian)传输,而多数现代CPU使用小端存储,因此需要转换:
c复制// 小端转大端
static uint32_t LE32toBE32(uint8_t *buf) {
return ((uint32_t)buf[0]<<24) | ((uint32_t)buf[1]<<16) |
((uint32_t)buf[2]<<8) | ((uint32_t)buf[3]<<0);
}
转换原理:
- 小端存储:低位在前,如0x12345678存储为0x78 0x56 0x34 0x12
- 大端传输:高位在前,需要转换为0x12 0x34 0x56 0x78
3. 客户端发送实现
3.1 文件发送函数设计
c复制int modbus_write_file(modbus_t *ctx, uint16_t file_no,
uint8_t *file_name, uint8_t *buffer, uint16_t len) {
FileInfo tFileInfo;
int rc;
uint16_t record_no = 0; // 记录号从0开始
uint16_t pos = 0; // 已发送位置
uint16_t send_len = 0; // 本次发送长度
// 初始化文件信息
memset(&tFileInfo, 0, sizeof(tFileInfo));
tFileInfo.file_len = len;
tFileInfo.file_len = LE32toBE32(&tFileInfo.file_len); // 转大端
if(file_name) {
strncpy(tFileInfo.file_name, file_name, sizeof(tFileInfo.file_name));
}
// 发送文件头(记录0)
rc = modbus_write_file_record(ctx, file_no, record_no,
&tFileInfo, sizeof(tFileInfo));
if(rc < 0) return rc;
record_no++; // 下一个记录号
// 分块发送文件数据
while(pos < len) {
send_len = len - pos;
if(send_len > 240) send_len = 240; // 单块不超过240字节
rc = modbus_write_file_record(ctx, file_no, record_no,
buffer + pos, send_len);
if(rc < 0) return -5;
record_no++;
pos += send_len;
}
return 1; // 成功
}
3.2 分块策略解析
选择240字节作为单块大小的原因:
- Modbus RTU帧限制:最大256字节
- 地址:1字节
- 功能码:1字节
- 数据长度:1字节
- 其他头部:约7字节
- CRC:2字节
- 剩余约244字节用于数据
- 对齐考虑:240是16的倍数,便于后续Flash写入操作
- 性能平衡:太小增加传输次数,太大容易因单帧错误导致重传量大
4. 服务器端解析实现
4.1 报文结构解析
Modbus Write File Record请求报文格式:
| 字节偏移 | 字段说明 | 长度 |
|---|---|---|
| 0 | 从机地址 | 1 |
| 1 | 功能码(0x15) | 1 |
| 2 | 请求数据长度 | 1 |
| 3 | 引用类型(0x06) | 1 |
| 4-5 | 文件编号 | 2 |
| 6-7 | 记录编号 | 2 |
| 8-9 | 记录长度 | 2 |
| 10... | 记录数据 | N |
数据长度计算:
- 总数据长度 = msg[2]
- 固定头部 = 7字节(偏移3-9)
- 实际数据长度 = msg[2] - 7
4.2 解析函数实现
c复制static void modbus_parse_file_record(uint8_t *msg, uint16_t msg_len) {
uint16_t record_no;
FileInfo tFileInfo;
static int recv_len = 0; // 累计接收长度
if(msg[1] == MODBUS_FC_WRITE_FILE_RECORD) {
// 提取记录号
record_no = ((uint16_t)msg[6] << 8) | msg[7];
if(record_no == 0) { // 文件头记录
memcpy(&tFileInfo, &msg[10], sizeof(FileInfo));
tFileInfo.file_len = BE32toLE32(&tFileInfo.file_len); // 转回小端
printf("FileInfo: name=%s, len=%d\n",
tFileInfo.file_name, tFileInfo.file_len);
recv_len = 0; // 重置接收计数器
}
else { // 数据记录
int chunk_len = msg[2] - 7; // 计算数据长度
recv_len += chunk_len;
printf("Received chunk %d, len=%d, total=%d\n",
record_no, chunk_len, recv_len);
}
}
}
5. 完整任务实现
5.1 客户端任务
c复制void ClientTask(void *arg) {
modbus_t *ctx = modbus_new_rtu("/dev/ttyS1", 115200, 'N', 8, 1);
modbus_set_slave(ctx, 1);
uint8_t *file_data = malloc(500);
memset(file_data, 0x5A, 500); // 填充测试数据
while(1) {
int ret = modbus_write_file(ctx, 1, "firmware.bin", file_data, 500);
if(ret < 0) {
printf("Send failed: %d\n", ret);
} else {
printf("Send success\n");
}
sleep(2);
}
}
5.2 服务器任务
c复制void ServerTask(void *arg) {
modbus_t *ctx = modbus_new_rtu("/dev/ttyS2", 115200, 'N', 8, 1);
modbus_set_slave(ctx, 1);
uint8_t query[MODBUS_RTU_MAX_ADU_LENGTH];
while(1) {
int rc = modbus_receive(ctx, query);
if(rc > 0) {
modbus_reply(ctx, query, rc, NULL); // 标准回复
modbus_parse_file_record(query, rc); // 自定义解析
}
}
}
6. 传输优化与错误处理
6.1 常见问题解决方案
-
数据校验不通过
- 增加CRC32校验整个文件
- 每块数据可增加校验和
- 实现重传机制
-
传输中断恢复
- 记录已传输的块号
- 支持从断点续传
- 超时重传机制
-
大文件支持
- 分文件传输
- 压缩传输
- 差分升级
6.2 性能优化建议
-
动态块大小调整
- 根据信道质量动态调整
- 错误率低时增大块大小
- 错误率高时减小块大小
-
并行传输
- 多文件并行传输
- 管道化请求提高吞吐
-
数据压缩
- 传输前压缩
- 接收后解压
- 节省传输时间
7. 实际应用扩展
7.1 固件升级场景
本协议特别适合固件升级:
- 文件头指定加载地址
- 接收端可直接编程Flash
- 校验确保烧录正确性
7.2 数据采集场景
用于设备数据导出:
- 将采集数据打包为文件
- 分块传输到上位机
- 上位机重组为完整文件
7.3 协议扩展方向
-
安全增强
- 增加加密传输
- 数字签名验证
- 身份认证
-
功能扩展
- 目录列表
- 文件删除
- 空间查询
-
性能优化
- 滑动窗口协议
- 选择性重传
- 数据压缩
通过这种设计,我们成功在Modbus协议上实现了可靠的文件传输功能。在实际项目中,可根据具体需求进一步优化和扩展协议功能。
