Marlin 二进制文件传输协议(BFT)实战指南:从 M28 B1 到 SD 卡高速写入
2026/9/13 13:02:58 网站建设 项目流程

Marlin 二进制文件传输协议(BFT)实战指南:从 M28 B1 到 SD 卡高速写入

【免费下载链接】MarlinMarlin is a firmware for RepRap 3D printers optimized for both 8 and 32 bit microcontrollers. Marlin supports all common platforms. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.项目地址: https://gitcode.com/GitHub_Trending/ma/Marlin

导读

本文基于 Marlin 固件仓库中的协议规范文档与源码,完整讲解通过串口向 SD 卡进行二进制文件传输的 BFT(Binary File Transfer)协议。文中将带你掌握协议的数据包格式、Fletchers 校验和算法、连接控制与传输控制两类报文,以及从M28 B1进入二进制模式到最终关闭连接的标准交互流程,并深入binary_stream.cppM28_M29.cpp等源码印证每个协议细节的实现方式。

协议概览与启用前提

Marlin 通过串口将二进制数据直接写入内部存储(SD 卡)的能力,由编译期选项BINARY_FILE_TRANSFER控制。该选项默认关闭,需要在 Marlin/Configuration_adv.h 中取消注释:

// Add an optimized binary file transfer mode, initiated with 'M28 B1' //#define BINARY_FILE_TRANSFER #if ENABLED(BINARY_FILE_TRANSFER) // Include extra facilities (e.g., 'M20 F') supporting firmware upload via BINARY_FILE_TRANSFER //#define CUSTOM_FIRMWARE_UPLOAD #endif

启用后,配合CUSTOM_FIRMWARE_UPLOAD还可支持固件上传等扩展用途。在 ini/features.ini 中可以确认该功能关联的编译单元:

BINARY_FILE_TRANSFER = build_src_filter=+<src/feature/binary_stream.cpp> +<src/libs/heatshrink>

即启用该选项后,固件会额外编译 Marlin/src/feature/binary_stream.cpp(BFT 协议状态机与文件传输实现)以及Marlin/src/libs/heatshrink(heatshrink 解压器,用于可选的压缩传输)。在真实构建环境中,buildroot/tests/mks_robin_nano_v1v2_maple/config-04.ini即为一个同时启用 BFT 的测试配置示例。

一旦固件以该选项编译,主机端(PC 软件等)向打印机串口发送 ASCII 命令M28 B1即可进入二进制传输模式。这一入口定义在 Marlin/src/gcode/sd/M28_M29.cpp 中:M28解析到以B开头且后跟数字的参数时,若数字大于 0 则置位card.flag.binary_mode,并回显Switching to Binary Protocol;同时记录当前命令所在串口作为transfer_port_index,后续二进制数据将从该端口接收。

进入二进制模式后,Marlin 的串口命令循环不再按 ASCII 行解析,而是直接把串口数据喂给BinaryStream::receive()状态机,这一点可以从 Marlin/src/gcode/queue.cpp 的实现得到印证:当card.flag.binary_mode为真时,直接调用binaryStream[card.transfer_port_index.index].receive(...)并以serial_line_buffer作为接收缓冲。

字节序(Endianness)

协议中所有多字节数据结构均采用little-endian(小端序)。构造数据包时,低位字节必须先行发送。每个数据包以 16 位起始标记(Start Token)0xB5AD开头,因此线上实际先发送0xAD,再发送0xB5

一个只有头部、没有载荷的 Connection SYNC 数据包在字节流中的排列如下:

S S P P P H t y r a a e a n o c y a r c t k l d t o e o e c t a r o d l t C y l S p e e n ---- -- - - ---- ---- ADB5 00 0 1 0000 0103

即:AD B5(Start Token)→00(Sync Number)→0(协议 ID 高位 4bit)与1(包类型低位 4bit)合成meta字节 →00 00(Payload Length)→01 03(Header Checksum)。在源码中,头部的解析对应 Marlin/src/feature/binary_stream.h 中定义的Header联合体,其中meta字节的高 4 位是protocol()、低 4 位是type()

数据包头部(Packet Header)

头部固定为 8 字节,布局如下:

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-------------------------------+---------------+-------+-------+ | Start Token (0xB5AD) | Sync Number | Prot- | Pack- | | | | ocol | et | | | | ID | Type | +-------------------------------+---------------+-------+-------+ | Payload Length | Header Checksum | +-------------------------------+-------------------------------+

各字段说明:

字段宽度说明
Start Token16 bits每个数据包必须以 16 位值0xB5AD开始。
Sync Number8 bits同步值。从同步(SYNC)之后的每个数据包都应将其递增 1。
Protocol ID4 bits协议 ID。0为 Connection Control(连接控制),1为 Transfer(文件传输)。详见下文。
Packet Type4 bits数据包类型 ID。取值取决于 Protocol ID,见下文各节。
Payload Length16 bits载荷数据长度。若该值大于 0,则头部之后紧跟数据包载荷。
Header Checksum16 bits头部数据(不含 Start Token)的 16 位 Fletchers 校验和。

数据包载荷(Packet Payload)

当头部中的 Payload Length 字段非零时,头部之后应跟随后续载荷:

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-------------------------------+-------------------------------+ | Payload Data ... | +-------------------------------+-------------------------------+ | ... | Packet Checksum | +-------------------------------+-------------------------------+
字段宽度说明
Payload DataPayload Length 字节载荷数据。长度不应超过 Connection SYNC 操作报告的缓冲长度。
Packet Checksum16 bits头部与载荷的 16 位 Fletchers 校验和,包含 Header Checksum,但不含 Start Token。

从源码看,接收端的状态机PACKET_HEADER → PACKET_DATA → PACKET_FOOTER → PACKET_PROCESS会严格校验头部校验和与整包校验和;任何一步失败都会进入PACKET_RESEND状态,向主机发送rs重传请求(Marlin/src/feature/binary_stream.h)。

Fletchers 校验和

无论头部校验和还是整包校验和,都使用 16 位 Fletchers 校验和算法,且两种情况都不包含数据包前两个字节(即 Start Token)。一个简单实现示例:

uint16_t cs = 0; for (size_t i = 2; i<packet.size(); i++) { uint8_t cslow = (((cs & 0xFF) + packet[i]) % 255); cs = ((((cs >> 8) + cslow) % 255) << 8) | cslow; }

在固件侧,BinaryStream::checksum()实现了完全相同的迭代公式:

// fletchers 16 checksum uint32_t checksum(uint32_t cs, uint8_t value) { uint16_t cs_low = (((cs & 0xFF) + value) % 255); return ((((cs >> 8) + cs_low) % 255) << 8) | cs_low; }

(Marlin/src/feature/binary_stream.h)。接收时头部校验和会在读到第 6 字节时被暂存(因为头部校验和字段本身不能参与自己的计算),随后与头部中的校验和字段比对(Marlin/src/feature/binary_stream.h)。

通用响应(General Responses)

ok

除 SYNC 数据包外,所有数据包都会收到一条ok<SYNC>消息作为确认。该确认仅表示客户端已收到数据包且头部格式良好;并不表示操作成功——在客户端还会发送详细响应消息的情况下,ok不代表业务成功。当前实现中最值得注意的是:当主机发送多个相同 Sync Number 的数据包时,客户端仍会回复ok,但不会发送正确的业务响应或任何错误信息。

注意ok确认会在任何数据包类型专属输出之前发送。SYNC值应与最后发送数据包的 Sync Number 一致,下一个数据包应使用该值 + 1 的 Sync Number。

示例:

ok1

从源码看,ok是在PACKET_PROCESS状态处理业务前先输出的(Marlin/src/feature/binary_stream.h):

case StreamState::PACKET_PROCESS: sync++; packet_retries = 0; bytes_received += packet.header.size; SERIAL_ECHOLNPGM("ok", packet.header.sync); // transmit valid packet received dispatch();

rs

当数据包乱序到达(Sync Number 不是上一个 Sync Number + 1)时,客户端会发送一条rs<SYNC>消息,其中携带最后接收到的 Sync Number,提示主机据此重传。

示例:

rs1

源码中的触发场景包括:头部校验和损坏、载荷校验和损坏、数据流超时等,此时状态机进入PACKET_RESEND并输出rs(Marlin/src/feature/binary_stream.h)。另外,若主机的 Sync Number 恰好是上一个已确认包的 Sync(即sync - 1),客户端会判定此前的ok应答可能丢失,于是补发ok并丢弃重复载荷(Marlin/src/feature/binary_stream.h)。

连接控制(Connection Control,Protocol ID 0)

Protocol ID 0 的数据包负责控制二进制连接本身,只有 2 种类型:

Packet Type名称说明
1SYNC同步主机与客户端并获取连接信息。
2CLOSE关闭二进制连接并切回 ASCII 模式。

SYNC 数据包

SYNC 数据包应为主机在启用二进制模式后发送的第一个数据包。成功后,客户端会发送 sync 响应。

注意:这是唯一一个不会被ok响应的数据包。

返回的 sync 响应格式:

ss<SYNC>,<BUFFER_SIZE>,<VERSION_MAJOR>.<VERSION_MINOR>.<VERSION_PATCH>
说明
SYNC当前 Sync Number,应作为下一个数据包的 Sync Number,并在其后每个数据包上递增 1。
BUFFER_SIZE客户端缓冲区大小。数据包 Payload Length 不得超过该值。
VERSION_MAJOR客户端 Marlin BFT 协议主版本号,例如0
VERSION_MINOR客户端 Marlin BFT 协议次版本号,例如1
VERSION_PATCH客户端 Marlin BFT 协议补丁版本号,例如0

示例响应:

ss0,96,0.1.0

源码中,SYNC 数据包被特殊处理:它在头部校验通过后立即响应,且不经过 Sync Number 校验(Marlin/src/feature/binary_stream.h):

// The SYNC control packet is a special case in that it doesn't require the stream sync to be correct if (static_cast<Protocol>(packet.header.protocol()) == Protocol::CONTROL && static_cast<ProtocolControl>(packet.header.type()) == ProtocolControl::SYNC) { SERIAL_ECHOLN(F("ss"), sync, C(','), buffer_size, C(','), version_major, C('.'), version_minor, C('.'), version_patch); stream_state = StreamState::PACKET_RESET; break; }

示例响应中的96即为buffer_size(对应 Marlin/src/feature/binary_stream.h 附近的buffer_size常量),实际可用的接收缓冲为serial_line_buffer,其大小由MAX_CMD_SIZE决定(见 Marlin/src/gcode/queue.cpp 注释)。

CLOSE 数据包

CLOSE 数据包应为主机发送的最后一个数据包。成功后,客户端切回 ASCII 模式。源码中的实现非常直接——将card.flag.binary_mode置为 false(Marlin/src/feature/binary_stream.h):

case ProtocolControl::CLOSE: // revert back to ASCII mode card.flag.binary_mode = false; break;

传输控制(Transfer Control,Protocol ID 1)

Protocol ID 1 的数据包控制连接上的文件传输:

Packet Type名称说明
0QUERY查询客户端协议细节与压缩参数。
1OPEN打开文件用于写入并开始接收待传输数据。
2CLOSE结束写入并关闭当前文件。
3WRITE向已打开文件写入数据。
4ABORT中止文件传输。

这些类型的解析与分发由SDFileTransferProtocol::process()完成(Marlin/src/feature/binary_stream.h),各分支与上表一一对应。

QUERY 数据包

QUERY 数据包应为主机在 SYNC 数据包之后发送的第二个数据包。成功后,除ok<sync>确认外,还会返回 query 响应。

返回的 query 响应格式:

PFT:version:<VERSION_MAJOR>.<VERSION_MINOR>.<VERSION_PATCH>:compression:<COMPRESSION_ALGO>(,<COMPRESSION_PARAMS>)
说明
VERSION_MAJOR客户端 Marlin BFT 协议主版本号,例如0
VERSION_MINOR客户端 Marlin BFT 协议次版本号,例如1
VERSION_PATCH客户端 Marlin BFT 协议补丁版本号,例如0
COMPRESSION_ALGO压缩算法。当前为heatshrinknone
COMPRESSION_PARAMS压缩参数,以逗号分隔。当前若算法为 heatshrink,则为窗口大小(window size)与前瞻大小(lookahead size)。

示例响应:

PFT:version:0.1.0:compression:heatshrink,8,4

其中heatshrink,8,484对应 Marlin/src/libs/heatshrink/heatshrink_config.h 中的编译期常量:

#define HEATSHRINK_STATIC_WINDOW_BITS 8 #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4

客户端实现(Marlin/src/feature/binary_stream.h)会按是否启用BINARY_STREAM_COMPRESSION分别输出 heatshrink 参数或none

case FileTransfer::QUERY: SERIAL_ECHO(F("PFT:version:"), version_major, C('.'), version_minor, C('.'), version_patch); #if ENABLED(BINARY_STREAM_COMPRESSION) SERIAL_ECHOLN(F(":compression:heatshrink,"), HEATSHRINK_STATIC_WINDOW_BITS, C(','), HEATSHRINK_STATIC_LOOKAHEAD_BITS); #else SERIAL_ECHOLNPGM(":compression:none"); #endif break;

BINARY_STREAM_COMPRESSION在 Marlin/src/feature/binary_stream.h 中定义,并随BINARY_FILE_TRANSFER一同启用 heatshrink 解码器,同时为 DMA 传输准备了 512 字节、按sizeof(size_t)对齐的解码缓冲区。

OPEN 数据包

打开一个文件用于写入。文件名与其他选项在数据包载荷中指定。若固件编译时支持长文件名,则文件名可以是长文件名;但整个数据包载荷长度不得超过 SYNC 数据包返回的缓冲长度。文件名值必须包含空终止符(NULL 字节)。

载荷布局:

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +---------------+---------------+-------------------------------+ | Dummy | Compression | Filename | +---------------------------------------------------------------| | ... | +-------------------------------+-------------------------------+ | ... | NULL (0x0) | Packet Checksum | +-------------------------------+-------------------------------+
字段宽度说明
Dummy8 bits布尔值,指示本次文件传输是否真正执行。若为1,客户端将假装文件已打开并接受数据传输,但实际上不写入任何数据。
Compression8 bits布尔值,指示待传输数据是否使用 QUERY 数据包返回的算法与参数进行压缩。
Filename...包含空终止符字节的文件名。
Packet Checksum16 bits头部与载荷的 16 位 Fletchers 校验和,包含 Header Checksum,但不含 Start Token。

响应:

响应说明
PFT:success文件已打开,可开始写入。
PFT:fail客户端无法打开文件。
PFT:busy文件已经处于打开状态。

源码中 OPEN 的解析对应Packet::Open结构(Marlin/src/feature/binary_stream.h):DummyCompression各占 1 字节(各取低 1 位判断布尔值),其后为以\0结尾的可变长文件名;validate()校验载荷长度并确保末尾是\0。业务逻辑上,若已有传输在进行则回PFT:busy;校验或打开失败则回PFT:fail(Marlin/src/feature/binary_stream.h)。真正打开文件时调用card.openFileWrite(filename)(Marlin/src/feature/binary_stream.h)。

CLOSE 数据包

关闭当前打开的文件。

响应:

响应说明
PFT:success缓冲已刷新且文件已关闭。
PFT:ioerror客户端存储设备故障。
PFT:invalid没有已打开的文件。

实现上,file_close()会先刷新压缩解码缓冲中残留的数据(若启用压缩),再调用card.closefile()card.release()(Marlin/src/feature/binary_stream.h)。

WRITE 数据包

向当前打开的文件写入载荷数据。若文件以 Compression 为 1 打开,则数据会先解压再写入。载荷长度不得超过 SYNC 数据包返回的缓冲大小。

响应:成功后返回ok<SYNC>响应。出错时,ok<SYNC>响应后还会跟一条错误响应:

响应说明
PFT:ioerror客户端存储设备故障。
PFT:invalid没有已打开的文件。

从源码看,WRITE 分支在没有打开文件时立即回PFT:invalid;写入失败(如card.write()返回负值)时回PFT:ioerror(Marlin/src/feature/binary_stream.h)。启用压缩时,file_write()将数据送入 heatshrink 解码器,并在解码缓冲攒满 512 字节后一次性写入 SD 卡,兼顾吞吐与 DMA 对齐要求(Marlin/src/feature/binary_stream.h)。

ABORT 数据包

关闭当前打开的文件并将其删除。

响应:

响应说明
PFT:success传输已中止,文件已删除。

实现上,transfer_abort()依次调用card.closefile()card.removeFile(card.filename)card.release(),并重置传输状态(Marlin/src/feature/binary_stream.h)。此外,固件还实现了传输看门狗:若传输过程中途中断且超过超时时间(默认 10 秒,见timeout = 10000),SDFileTransferProtocol::idle()会自动执行中止逻辑,避免 SD 卡上残留处于打开状态的文件(Marlin/src/feature/binary_stream.h)。

典型使用流程

完整的二进制文件传输流程共 8 步:

  1. 发送 ASCII 命令M28 B1进入 Binary Transfer 模式。
  2. 发送 Connection SYNC 数据包,记录 Sync Number 与 Buffer Size。
  3. 发送 Transfer QUERY 数据包,使用上一步得到的 Sync Number;记录压缩算法与参数。
  4. 发送 Transfer OPEN 数据包,使用「最后 Sync Number + 1」,携带文件名与压缩选项。若出错,发送 Connection CLOSE 数据包并中止。
  5. 发送 Transfer WRITE 数据包,使用「最后 Sync Number + 1」携带文件数据。载荷长度不得超过第 2 步报告的 Buffer Size。出错时,先发送 Transfer ABORT 数据包,再发送 Connection CLOSE 数据包,然后中止传输。
  6. 发送 Transfer CLOSE 数据包,使用「最后 Sync Number + 1」。
  7. 发送 Connection CLOSE 数据包,使用「最后 Sync Number + 1」。
  8. 客户端此时已回到 ASCII 模式,传输完成。

一个基于示例响应的简化报文序列示意(SYNC=0 起步,每包递增):

步骤数据包协议/类型Sync载荷要点期望响应
1无(ASCII)M28 B1Switching to Binary Protocol
2SYNC0/10ss0,96,0.1.0
3QUERY1/01ok1+PFT:version:0.1.0:compression:heatshrink,8,4
4OPEN1/12Dummy=0, Compression=1, 文件名+\0ok2+PFT:success
5WRITE × N1/33,4,5,…压缩后文件数据(≤ Buffer Size)ok3ok4、…
6CLOSE1/26ok6+PFT:success
7CLOSE0/27ok7,随后切回 ASCII

与主机工具对接的注意事项

  • 缓冲上限:每个 WRITE 包的载荷长度上限由 SYNC 响应中的BUFFER_SIZE决定(示例中为 96 字节,实际以固件serial_line_buffer/MAX_CMD_SIZE为准)。发送端应按此值切分文件数据,超出会导致接收端报Datastream packet data buffer overrun(Marlin/src/feature/binary_stream.h)。
  • 乱序与重传:任何rs<SYNC>响应都意味着主机需要从SYNC指示的位置重新发送;头部/载荷校验失败、数据流超时(packet_max_wait = 500ms,见 Marlin/src/feature/binary_stream.h)都会触发重传。
  • 压缩可选:QUERY 响应的heatshrink,8,4表明压缩窗口为 8 位、前瞻为 4 位。若主机端实现压缩,须使用与固件一致的参数,且在 OPEN 时置 Compression=1;否则以未压缩明文传输,置 Compression=0。
  • Dummy 传输:OPEN 中 Dummy=1 可用于传输演练/压力测试——客户端照常应答并接收数据,但不会写 SD 卡(Marlin/src/feature/binary_stream.h)。
  • 能力查询:主机可通过M115响应中的BINARY_FILE_TRANSFER能力标识(Marlin/src/gcode/host/M115.cpp)判断固件是否启用本协议,再决定是否发起M28 B1

【免费下载链接】MarlinMarlin is a firmware for RepRap 3D printers optimized for both 8 and 32 bit microcontrollers. Marlin supports all common platforms. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.项目地址: https://gitcode.com/GitHub_Trending/ma/Marlin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询