# FileTransferProtocol **Repository Path**: GKoSon/FileTransferProtocol ## Basic Information - **Project Name**: FileTransferProtocol - **Description**: 本仓库是协议文档 非可执行代码 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-13 - **Last Updated**: 2026-09-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 本文档旨在描述一种灵活高效的文件传输协议 方便上位机下位机交换文件 ## 角色定义 * **文件请求者** * **文件提供者** ![1778637842749](images/readme/1778637842749.png) ## 四种帧:文件信息帧 文件请求帧 文件内容帧 文件结束帧 HEAD 统一定长 **8**字节 (TYPE 4字节 + bodyLEN 4字节) BODY 长度 描述在HEAD的LEN中 TAIL 统一定长 2字节 ![1778652315471](images/readme/1778652315471.png) 注:帧采用string亦即ASCII方便人眼阅读 ## 真实演示 > 准备一个test.bin测试 (编译1.c凭空产生) > 准备一对虚拟串口 com1-com2通讯 > winForm上位机作为文件提供者选择COM1 > XCOM.EXE模拟MCU选择COM2 ### 首先**文件提供者**上位机COM1发起*文件信息帧* ``` 00010078{ "fileSize": 9, "fileName": "test.bin", "md5": "XXX", "ver": 4} ``` ### 然后**文件请求者**COM2发起第一个*文件请求帧* ``` 00020022{ "addr": 0, "len":9} ``` ### 然后**文件提供者**上位机COM1给出*文件内容帧* ``` 00030009123456789XX 二进制hex如下: 30 30 30 33 --HEAD 30 30 30 39 --LEN 31 32 33 34 35 36 37 38 39 --BODY 31 C3 --TAIL ``` ### 最后**文件请求者**COM1发*文件结束帧* ``` 00040022{"fileName":"test.bin","status":0} 00040022{"fileName":"test.bin","status":1} ``` ### 补充:COM2亦可分步骤 去模拟真实MCU行为 ![1778641512843](images/readme/1778641512843.png) 上位机[https://gitee.com/GKoSon/file-transfer-host](https://gitee.com/GKoSon/file-transfer-host) a8353ca MCU [https://gitee.com/GKoSon/hc32f460\_evboard](https://gitee.com/GKoSon/hc32f460_evboard) bd48ef1 ## =======项目实战 新唐RTU项目的蓝牙OTA========== ### 简单介绍 此项目 MCU和BLE 的串口通信协议 基本就是本仓库描述的东西 扩展了MCU参数配置的几个指令 核心就是TLV结构 (弃用TAIL 求简单) ``` #define HEADER_INFO "0001"//-> #define HEADER_REQ "0002"//<- #define HEADER_DATA "0003"//-> #define HEADER_END "0004"//-> #define HEADER_W_CFG "0005"//-> #define HEADER_W_CFG_ACK "0006"//<- #define HEADER_R_CFG "0007"//-> #define HEADER_R_CFG_ACK "0008"//<- ``` 真实报文举例 > QT发 文件信息帧 ``` 00010090{"fileName":"newApp.bin","filesize":1024,"md5":"3cc9975bf197633301b3505f1f15cf07","ver":4} ``` > MCU发 文件请求帧 ``` 00020021{"addr":0,"len":1024} ``` > QT发 文件内容 ``` 00031024123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?亗儎厗噲墛媽崕彁憭摂晼棙櫄洔潪煛ⅲぅΗī氨渤吹斗腹夯冀究懒旅呐魄壬仕掏蜗醒矣哉肿刭谯茌捱?徕沅彐玷殛腱眍镳耱篝貊鼬? ``` ### MCU助手问题 本协议偏向string方便人眼阅读 QT方法高级 不需要助手 MCU需要处理int12和char“0012”的相互转化 这里提供给一组函数 ``` /** * @brief uint16_t(0‑9999) → 4位补零字符串 示例12 -> "0012" */ static void num_to_4str(uint16_t num, char *buf){ buf[3] = (num % 10) + '0'; num /=10; buf[2] = (num % 10) + '0'; num /=10; buf[1] = (num % 10) + '0'; num /=10; buf[0] = (num % 10) + '0'; buf[4] = '\0'; } /** * @brief 4位字符串转为int 示例 "0012" ->12 */ static uint16_t str4_to_num(rt_uint8_t *str){ uint16_t val = 0; uint8_t i; if (str == NULL) return 0; for (i = 0; i < 4; i++) { if ((str[i] < '0') || (str[i] > '9')) return 0; val = val * 10 + (str[i] - '0'); } return val; } ``` ### 上位机BLE发送问题 这是一个真正的挑战 在笔记本串口的世界遇不到 因为串口量大管饱单次发4K个字节没问题 但是CAN口 BLE等都有MTU 每次发送是有上限的! https://mp.weixin.qq.com/s/CPRJ_w4f93ZgW6oE7M8S2w(充电桩公司的设计) 当前我的设计是这样的: 因为BT24S蓝牙模组的MTU是253(QT实测是256但是3个字节是蓝牙协议栈要用 所以就认为MTU是253) 所以MCU直接请求(N个253)个字节 项目是一次性请求16个253 = 4048个字节!对应static rt_uint8_t totalPkg[4048]={0}; 最后这4048个字节 前面8个字节是HEAD 不是有效bin 所以每次MCU请求bin的长度就是4040个 对应#define BLE_OTA_ONE_STEP 4040 当MCU发出这一包 00020021{"addr":0,"len":4048} 挑战就开始了 QT的手机APP不能一口气发4048个字节 MCU串口空闲中断不能一口气收4048个字节 分别要处理 #### QT的处理 > QT收到报文 会准备好4048的数组 放到QByteArray m_resp 这就是工程总量 > QT调用sendNextChunk发出去253个字节 > QT回调走到 onCharacteristicWritten 意味着刚刚一笔TX已经完成 > QT继续调用sendNextChunk发出去253个字节 > 亦即sendNextChunk -- onCharacteristicWritten -- sendNextChunk -- onCharacteristicWritten循环的完成工程总量 串口抓波形如下 ![1788566704487](images/readme/1788566704487.png) #### MCU的处理 > 串口空闲中断的数组维持256即可 因为多了也是浪费吗 每次至多就是收到253个字节 > 串口每次把收到的数据 汇报给顶层 这个拼包的动作是放在顶层的 >> 为什么不能在底层拼包? >> 首先模块有AT交互协议 这不是我约定的协议 不能干扰AT协议 >> 然后充电桩公司当时设计的人造空闲中断 亦即人为放大定时器溢出时间 自动拼包 这个方案不好的! >> 因为BLE控制传输 两包之间的间隔不固定的 最要命的是我核心诉求是快速文件传输 此方案浪费很多时间 集体慢吞吞! >> > MCU拼包代码如下 ``` static rt_uint8_t totalPkg[4048]={0};// 来自QT的全部一包 static int totalPkgIndex=0; static int noheadFlag=0; static int totalbinLen=0; static void data_receive_callback(uint8_t *mtu, size_t mtuLen){//收到一个MTU 进来一次 rt_kprintf("data_receive_callback Received %zu bytes: %.*s\n", mtuLen, (int)mtuLen, mtu); if(rt_memcmp(HEADER_INFO, mtu, 4) == 0) {//文件信息帧 一包搞定 控制253内 if (JSUNPack_info((char *)mtu)) return; JSPack_sendAsk_bin(); return; } if(rt_memcmp(HEADER_DATA, mtu, 4) == 0) {//文件内容帧 第1包 携带HEAD信息 是HEAD+BIN totalPkgIndex = 0; noheadFlag = 1; rt_memset(totalPkg, 0, sizeof(totalPkg)); rt_memcpy(&totalPkg[totalPkgIndex], mtu, mtuLen); totalPkgIndex += mtuLen;//收下第一包 if (totalPkgIndex >= 8) {//防御编程 有可能第一包就把文件发完啦 totalbinLen = str4_to_num(&totalPkg[4]); LOG_E("A totalbinLen %d, current len %d, totalPkgIndex %d\n", totalbinLen, mtuLen, totalPkgIndex); if ((totalbinLen + 8) == totalPkgIndex) { noheadFlag = 0; https_write_newApp(duty.addr, &totalPkg[8], duty.len); duty.addr += duty.len; if (duty.addr == duty.filesize) { JSPack_sendEnd(); } else { JSPack_sendAsk_bin(); } } } return; } if(noheadFlag) {//文件内容帧 第2345包 不携带HEAD信息 是BIN rt_memcpy(&totalPkg[totalPkgIndex], mtu, mtuLen); totalPkgIndex += mtuLen; LOG_E("B totalbinLen %d, current len %d, totalPkgIndex %d\n", totalbinLen, mtuLen, totalPkgIndex); if ((totalbinLen + 8) == totalPkgIndex) { noheadFlag = 0; https_write_newApp(duty.addr, &totalPkg[8], duty.len); duty.addr += duty.len; if (duty.addr == duty.filesize) { JSPack_sendEnd(); } else { JSPack_sendAsk_bin(); } } } } ``` ### 项目小结 QT开源地址: RTU开源地址: 附件是波形文件 9600抓波 和115200抓波 后者文件传输更快