你好!我们继续学习"综合实现"课程中的产品框架 部分。之前我们了解了硬件、软件和界面,现在要深入一个具体的技术点:Modbus 协议中的 Write File Record 功能。为什么需要它?因为我们的上位机需要把"映射关系表"(即哪些传感器对应哪些寄存器)下发给中控,还需要给中控升级固件(传输整个文件)。这些数据往往比较大,不能用一个简单的Modbus读写命令一次性完成,所以需要分块传输。Write File Record 正是Modbus协议里专门用来传输文件的功能。
下面我将用最通俗的语言,一步一步拆解这个功能。请对照我提到的图片进行理解。
一、为什么要用 Write File Record?
想象你要给朋友寄一本很厚的书(比如几百页)。你不能把整本书塞进一个信封里,因为信封太小。所以你只能把书拆成一叠一叠的页,每叠用一个小信封寄出。每个信封上要注明:
-
这是哪本书(文件编号)
-
这是第几叠(记录编号)
-
这一叠有多少页(记录长度)
-
这一叠的内容是什么(记录数据)
Write File Record 就是Modbus协议里的"寄书方法"。它允许我们把大文件分成多个"记录"(Record),每个记录最大可以放约250字节的数据,然后通过多个Modbus报文依次发送。接收方(中控)收到后,再按照信封上的信息把数据重新拼成完整的文件。
在我们的产品中,这个功能用来:
-
下发映射表:用户在上位机配置好所有"点"后,生成一个配置文件(比如JSON格式),通过Write File Record发送给中控,中控解析后保存在Flash里。
-
升级固件:中控的应用程序(APP)需要升级时,上位机把新的固件文件分块发送,中控的Bootloader负责接收并写入Flash,完成升级。
二、Write File Record 报文格式详解

这张图展示了Write File Record请求报文的完整结构。我们从左到右,从上到下逐一解释。图中标注了一些数字,我们结合这些数字来理解。
1. 报文整体结构
一个Modbus报文通常包含:地址码 + 功能码 + 数据 + CRC校验 。但Write File Record有点特殊,它属于Modbus的扩展功能码,数据部分比较复杂。
-
功能码(Function code):图中第一行写着"1 Byte 0x15"。0x15(十进制21)就是Write File Record的功能码。Modbus规定,功能码0x15表示"写文件记录"。
-
请求数据长度(Request data length):功能码后面紧接着一个字节,表示后面所有数据的字节数(不包括这个长度字节本身)。图中标注"1 Byte 0x09 to 0xEB",意思是这个长度可以从0x09(9)到0xEB(235)。为什么有这个范围?因为后面要携带多个子请求,每个子请求有固定长度,所以总数据长度必须合理。
2. 子请求结构(Sub-Request)
Write File Record允许在一个报文中包含多个子请求(即同时操作多个文件记录)。每个子请求的格式相同。图中用"Sub-Req. x, ..."表示第一个子请求,然后"Sub-Req. x+1, ..."表示第二个,以此类推。每个子请求包含以下字段:
-
Reference Type(引用类型):2字节,图中标注"2 Bytes / 0x0001 to 0xFFFF"。这个字段通常固定为0x0006,表示"文件记录"。具体值由Modbus规范定义,我们不必深究,记住它是6即可。
-
File Number(文件编号):2字节,范围0x0001~0xFFFF。这就是我们之前说的"哪本书"。上位机在发送文件时,会给每个文件分配一个唯一的编号。比如映射表文件用1,固件文件用2。这样中控就能区分不同的文件。
-
Record Number(记录编号):2字节,范围0x0000~0x270F(0~9999)。这是当前数据块在整个文件中的序号。比如第一个数据块的Record Number是0,第二个是1,以此类推。中控根据这个编号将数据按顺序组装。
-
Record Length(记录长度):2字节,表示这个子请求中携带的数据有多少个字节。注意:图中标注"N x 2 Bytes"?实际上Record Length是指后面Record data的字节数。但图中可能画得不准确。标准定义中,Record Length后面紧跟着的就是Record data,数据长度就是Record Length的值。
-
Record Data(记录数据):长度由Record Length决定。这里就是实际的文件内容片段。
3. 报文总长度限制
图中右下角标注了长度计算:
-
"255 - 1 - 2 = 253" 等。这涉及到Modbus RTU模式下报文最大长度为256字节(包括地址和CRC)。我们来推导一下:
-
标准Modbus RTU报文最大长度为256字节。
-
减去1字节地址码,减去2字节CRC校验,剩下253字节可用于功能码+数据。
-
功能码占1字节,所以数据部分最多252字节(253-1=252)。但图中又有"1 Byte 0x15"和"1 Byte 长度",所以数据部分还要减去长度字节本身?实际上我们需要仔细看。
-
图中第一行有"1 Byte 0x15"和"1 Byte 0x09 to 0xEB",这表示:功能码1字节,后面跟着1字节的"请求数据长度"(即后面所有子请求的总字节数)。然后才是子请求。所以数据部分的总长度(不包括功能码和长度字节)最大是253-1=252?但长度字节本身是1,所以子请求总长度最大是251?图中计算"255 - 1 - 1 = 251 = 0xFB",这个255可能是指整个报文最大255?实际上常见Modbus RTU最大256,地址1+功能码1+数据+CRC2=256,所以数据最大252。但这里他们可能用了另一种习惯。无论如何,我们知道有限制即可。
关键限制:每个子请求中,Record Data的最大长度受到限制。图中底部标注:"最大Record 长度: 24byte"和"最大 Record 长度: 2byte"?这有点矛盾。实际上,Modbus协议规定,在一个报文中,所有子请求的数据总和不能超过某个值,且每个子请求的数据长度也有上限。通常,为了简单,我们往往每个报文只发送一个子请求,这样Record Data最大可以接近250字节。图中可能是在推导具体数值,比如总数据长度为251,减去每个子请求固定开销(Reference Type 2 + File Number 2 + Record Number 2 + Record Length 2 = 8字节),那么数据最多243字节。但不同资料可能有不同说法。我们只需知道:每个报文能携带的数据量有限,所以大文件必须分块。
三、如何用Write File Record发送文件
现在我们来模拟一下上位机发送一个固件文件的过程。假设固件文件大小为10KB,我们需要把它分成多个记录(Record)发送。
-
初始化:上位机决定文件编号为2(因为1可能已经用于映射表)。
-
分块:将文件按每块240字节(留点余量)分割,得到多个数据块。每个块对应一个记录。
-
发送第一个记录:构造报文,功能码0x15,请求数据长度 = 1个子请求的固定8字节 + 数据块长度240 = 248字节。子请求中:Reference Type=6,File Number=2,Record Number=0,Record Length=240,Record Data=第一块数据。
-
发送第二个记录:Record Number=1,以此类推,直到最后一个记录(可能不足240字节)。
-
结束:所有记录发送完成后,中控根据收到的Record Number顺序组合成完整文件。
注意:中控需要知道文件传输何时结束。通常有两种方式:
-
上位机先发送一个文件头,包含文件总大小、块数等信息。
-
或者在最后一个记录中,Record Length小于块大小,表示结束。
在我们的产品中,可能通过约定好的协议(比如JSON封装)来管理文件传输过程,但底层都依赖Write File Record来实际传输数据。
4、在我们的产品框架中如何应用
回顾之前的内容:
-
硬件框架:上位机通过串口连接中控,中控通过通道连接传感器。
-
设计思路:上位机把映射关系(5个参数)通过Write File Record发送给中控,中控保存后建立映射表。
-
软件框架:中控的Bootloader和APP都支持接收Write File Record,APP收到映射表后存入Flash,Bootloader收到固件后升级。
所以,Write File Record是连接上位机和下位机(中控)的"数据管道",让大文件传输成为可能。
总结
通过今天的讲解,你应该明白了:
-
为什么需要Write File Record(传输大文件,如映射表和固件)。
-
它的报文结构:功能码0x15,包含一个或多个子请求,每个子请求有文件号、记录号、记录长度和记录数据。
-
如何分块发送文件。
-
图片中的标注和要点对应哪些字段。