返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

[单片机芯片] CH32V307 支持手机的虚拟U盘实现拖拽固件升级

[复制链接]
 楼主| kissdb 发表于 2025-7-3 11:45 | 显示全部楼层 |阅读模式
<
本帖最后由 kissdb 于 2025-7-3 11:52 编辑

#每日话题# #申请原创#  @21小跑堂  上次写完使用串口工具进行IAP升级后,总觉得还是太依赖电脑了,还得使用串口工具,就想有没有什么方法用手机也能升级。
看到虚拟优盘进行拖拽升级后,我也进行了几天测试尝试,终于还是找到了方法,能够在手机也能升级。

模拟优盘主要是向电脑发送DBR表,DBR(DOS Boot Record):磁盘操作系统引导记录,DBR是每个逻辑分区的一个引导记录,里面记录了该分区的众多重要信息以及引导代码,所以十分重要。在U盘系统中,必须有DBR,DBR占据逻辑分区的0扇区,大小通常为512字节,DBR各个部分的含义见《圈圈教你玩USB》,
搜到了好多篇文章,很顺利,电脑也显示了优盘图标和容量。
  1. uint8_t DBR[512] = // DOS 引导记录
  2.     {        
  3.         0xEB, 0x3C, 0x90,                                                 // 跳转指令
  4.         0x4d, 0x53, 0x44, 0x4f, 0x53, 0x35, 0x2e, 0x30,                   // 文件系统版本信息
  5.         0x00, 0x02,                                                       // 扇区字节数
  6.         0x08,                                                             // 每簇扇区数
  7.         0x01, 0x00,                                                       // 保留扇区数
  8.         0x02,                                                             // 该分区的 FAT 副本数
  9.         0xe0, 0x00,                                                       // 根目录项数
  10.         0x00, 0x50,                                                       // 小扇区数
  11.         0xf0,                                                             // 媒体描述符
  12.         0x08, 0x00,                                                       // 每 FAT 扇区数
  13.         0x20, 0x00,                                                       // 每道扇区数
  14.         0x40, 0x00,                                                       // 磁头数
  15.         0x00, 0x00, 0x00, 0x00,                                           // 隐藏扇区数
  16.         0x00, 0x50, 0x00, 0x00,                                           // 大扇区数
  17.         0x80,                                                             // 磁盘驱动器参数,80 表示硬盘
  18.         0x00,                                                             // 保留
  19.         0x29,                                                             // 扩展引导标记,0x29 表示后三个区可用
  20.         0x88, 0x09, 0x71, 0x20,                                           // 标卷序列号
  21.         0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 磁盘标卷:
  22.         0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20,                   // 文件系统类型信息
  23.        // 更多引导代码填充0
  24.        [62 ... 509] = 0x00,
  25.        0x55, 0xaa,
  26. };

每扇区字节数是512,可以计算每次要读取的扇区号,然后根据DBR表来上传内容。电脑可以识别可是手机总是提示需要格式化。
mmexport1751510213731.jpg
测试了很多方法,无论怎么修改上传的DBR表FAT表手机都无法识别。
在测试官方的例程里面使用内部FLASH空间模拟的U盘,可以识别也可以存入数据,就想是不是把两种结合一下呢。

首先更改磁盘的大小,把读取的空间改大与写入的空间,写入空间为开始地址0x08030000,结束地址为0x08037800,总空间大小为30K,读取的空间地址为200K,经测试手机和电脑都可以读取和写入。
首次启动还是需要用电脑格式化,有点麻烦,直接做成固件写入也不方便,启动时直接把DBR等数组写入指定位置吧
  1. // 初始化根目录  
  2.         IFlash_Prog_512(IFLASH_UDISK_START_ADDR,(uint32_t *)DBR);
  3.         IFlash_Prog_512(IFLASH_UDISK_START_ADDR+0x800,(uint32_t *)FAT);
  4.         IFlash_Prog_512(IFLASH_UDISK_START_ADDR+0xC00,(uint32_t *)FAT);        
  5.         EEprom_Write_u8Array(IFLASH_UDISK_START_ADDR+0x1000, BL_FAT_root_dir, sizeof(BL_FAT_root_dir) / 4);
烧写好程序后,不用格式化也有U盘出来了
QQ_1751513807088.png
如何判断是否是写入的bin文件呢,方法一般是采用后缀名的方法来判断,确定是bin文件后再写入指定的地址,如何判断结束有点麻烦,主要是根据写入的地址是否小于数据区的地址来判断,不是太好,最终还是采用文件头的方法来判断是否是需要的固件写入了。
文件使用的是RTT OTA的固件打包程序,支持AES256的加密,文件头格式如下
  1. char type[4];              /* RBL 字符头 */
  2.         uint16_t fota_algo;        /* 算法配置: 表示是否加密或者使用了压缩算法 */
  3.         uint8_t fm_time[6];        /* 原始 bin 文件的时间戳, 6 位时间戳, 使用了 4 字节, 包含年月日信息 */
  4.         char app_part_name[16];    /* app 执行分区名 */
  5.         char download_version[24]; /* 固件代码版本号 */
  6.         char current_version[24];  /* 这个域在 rbl 文件生成时都是一样的,我用于表示 app
  7.                                       分区当前运行固件的版本号,判断是否固件需要升级 */
  8.         uint32_t code_crc;         /* 代码的 CRC32 校验值, 它是的打包后的校验值, 即 rbl 文件 96 字节后的数据 */
  9.         uint32_t hash_val;         /* hash校验值 */
  10.         uint32_t raw_size;         /* 原始代码的大小 */
  11.         uint32_t com_size;         /* 打包代码的大小 */
  12.         uint32_t head_crc;         /* rbl 文件头的 CRC32 校验值,即 rbl 文件的前 96 字节 */


UDISK_Down_OnePack写入函数中读取前96字节,先判断是否为RBL头,计算crc32,如果正确则开始写入固件备份区,并开始记录长度,长度大于打包代码的大小则认为是写入完成了,开始校验文件的CRC,如果正确则使用AES256解密再写入程序区。
  1. void UDISK_Down_OnePack(uint8_t *pbuf, uint16_t packlen)
  2. {
  3.     uint32_t address;
  4.     uint32_t sec_start_addr;
  5.     static uint32_t BIN_LENG = 0; //BIN文件长度
  6.     if (UDISK_Sec_Pack_Count == 0x00)
  7.     {
  8.         address = (uint32_t)UDISK_Cur_Sec_Lba * DEF_UDISK_SECTOR_SIZE;
  9.         sec_start_addr = (address / DEF_FLASH_SECTOR_SIZE) * DEF_FLASH_SECTOR_SIZE;
  10.     }
  11.     memcpy(UDisk_Down_Buffer + (uint32_t)UDISK_Sec_Pack_Count * UDISK_Pack_Size, pbuf, UDISK_Pack_Size);
  12.     UDISK_Sec_Pack_Count++;
  13.     UDISK_Transfer_DataLen -= UDISK_Pack_Size;

  14.     if (UDISK_Sec_Pack_Count == (DEF_UDISK_SECTOR_SIZE / UDISK_Pack_Size))
  15.     {
  16.         address = (uint32_t)UDISK_Cur_Sec_Lba * DEF_UDISK_SECTOR_SIZE;
  17.         sec_start_addr = (address / DEF_FLASH_SECTOR_SIZE) * DEF_FLASH_SECTOR_SIZE;


  18.         if (Flag_bin==0)//判断是否是文件头
  19.         {
  20.             IFlash_Prog_512(IFLASH_UDISK_START_ADDR + sec_start_addr, (uint32_t *)UDisk_Down_Buffer);
  21.             memcpy(&OTA_part.data[0], UDisk_Down_Buffer, 96);            
  22.             //先判断文件头RBL;
  23.             // 检查文件标志是否正确
  24.             if (memcmp(OTA_part.data, FILE_FLAG, strlen(FILE_FLAG)))
  25.             {
  26.                 // printf("文件标志错误!\r\n");                    
  27.             }
  28.             else
  29.             {
  30.                 // printf("文件标志正确!计算CRC\r\n");
  31.                 rt_fota_crc_init();
  32.                 uint32_t CRCValue = rt_fota_crc((uint8_t *)OTA_part.data, 92);
  33.                 if (CRCValue != OTA_part.fota_part_head.head_crc)
  34.                 {
  35.                     // printf("文件头CRC校验失败\r\n");                    
  36.                 }
  37.                 else
  38.                 {
  39.                     printf("文件头 = %s\r\n", OTA_part.data);
  40.                     printf("版本号 = %s\r\n", OTA_part.fota_part_head.download_version);
  41.                     printf("名称 = %s\r\n", OTA_part.fota_part_head.app_part_name);
  42.                     printf("代码大小 = %d\r\n", OTA_part.fota_part_head.raw_size);
  43.                     printf("打包大小 = %d\r\n", OTA_part.fota_part_head.com_size);
  44.                     printf("hash_val = %x\r\n", OTA_part.fota_part_head.hash_val);
  45.                     printf("CRC32_code = %x\r\n", OTA_part.fota_part_head.code_crc);
  46.                     printf("CRC32_head = %x\r\n", OTA_part.fota_part_head.head_crc);
  47.                     IAP_EEPROM_ERASE_108k(BACKUP_IMAGE_START_ADD);
  48.                     printf("接收文件头正确,开始写入文件\r\n");                    
  49.                     Flag_bin = 1;                    
  50.                     BIN_start_addr=sec_start_addr; // 初始化BIN文件起始地址
  51.                     BIN_LENG=512; // 初始化BIN文件长度
  52.                 }
  53.             }
  54.         }
  55.         if (Flag_bin == 1) // 开始写入文件
  56.         {
  57.             uint32_t  start_addr=sec_start_addr- BIN_start_addr;

  58.             if (sec_start_addr >= BIN_start_addr )
  59.             {   
  60.                 if (start_addr < BACKUP_IMAGE_MAX_SIZE)
  61.                 {
  62.                     IAP_EEPROM_WRITE_512(BACKUP_IMAGE_START_ADD + start_addr, (uint32_t *)UDisk_Down_Buffer);
  63.                 }
  64.                 else
  65.                 {   
  66.                     printf("文件过大!\r\n");
  67.                     Flag_bin = 0; // 重置Flag_bin
  68.                 }
  69.             }            
  70.             BIN_LENG += 512;
  71.             if (BIN_LENG >= OTA_part.fota_part_head.com_size+BIN_INF_LEN+512)//BIN文件写入完成
  72.             {
  73.                 Flag_bin_count = 0;
  74.                 Flag_bin = 0;
  75.                 printf("文件写入完成,开始校验\r\n");
  76.             }            
  77.         }

  78.         if (UDISK_Transfer_DataLen == 0x00)
  79.         {
  80.             Udisk_Transfer_Status &= ~DEF_UDISK_BLUCK_DOWN_FLAG;
  81.             UDISK_Up_CSW();
  82.         }
  83.         UDISK_Sec_Pack_Count = 0x00;
  84.         UDISK_Cur_Sec_Lba++;
  85.     }
  86. }
开始校验文件的CRC
  1. rt_fota_crc_init();    // 初始化CRC计算器
  2.                 body_crc = 0xffffffff; // 初始化CRC值
  3.                 // 总大小
  4.                 uint32_t total_size = OTA_part.fota_part_head.com_size;
  5.                 // 起始地址
  6.                 uint32_t start_addr = BACKUP_IMAGE_START_ADD + 96;
  7.                 // 已处理字节数
  8.                 uint32_t offset = 0;
  9.                 // 每次读取的数据长度
  10.                 uint32_t chunk_size;
  11.                 while (offset < total_size) {
  12.                     chunk_size = (total_size - offset) > FLASH_PAGE_SIZE ? FLASH_PAGE_SIZE : (total_size - offset);
  13.                     IAP_EEPROM_READ(start_addr + offset, (u8 *)pBuf, chunk_size);
  14.                     body_crc = rt_fota_step_crc(body_crc, pBuf, chunk_size);
  15.                     offset += chunk_size;
  16.                 }
  17.                 body_crc = body_crc ^ 0xffffffff;
  18.                 printf("body_crc:%08x\r\n", body_crc);
  19.                 printf("code_crc:%08x\r\n", OTA_part.fota_part_head.code_crc);
  20.                 // 将文件头的CRC值与计算得到的CRC值进行比较
  21.                 if (body_crc == OTA_part.fota_part_head.code_crc)
  22.                 {
  23.                     // 打印校验和正确的信息
  24.                     printf("CRC32 is right!\r\n");
  25. }
解密写入程序区
  1. // 定义并初始化局部变量
  2.     // 密钥
  3.     uint8_t KEY[32];
  4.     // 偏移量
  5.     uint8_t IV[16];
  6.     struct AES_ctx ctx;
  7.     AES_init_ctx_iv(&ctx, KEY, IV);
  8.     u16 i, j;
  9.     u32 *pBuf;
  10.     u32 flashAddr;
  11.     u32 updataBuff[READ_DATA_LEN / 4];
  12.     // 设置缓冲区指针
  13.     pBuf = updataBuff;
  14. // 循环复制数据,这样可以确保即使 com_size 不能被 READ_DATA_LEN 整除,循环次数也会多一次,从而覆盖所有数据。
  15.     for (i = 0; i < ((OTA_part.fota_part_head.com_size + READ_DATA_LEN - 1) / READ_DATA_LEN);
  16.          i++) // i < (BACKUP_IMAGE_MAX_SIZE / READ_DATA_LEN)
  17.     {
  18.         // 计算当前循环对应的闪存地址
  19.         flashAddr =  i * READ_DATA_LEN;
  20.         // 从用户闪存区读取数据到缓冲区
  21.         IAP_EEPROM_READ((flashAddr + BACKUP_IMAGE_START_ADD+BIN_INF_LEN), (u8 *)pBuf, READ_DATA_LEN);

数据写入完后就可以跳转了。

由于只有小米的手机,经测试可以升级固件.
Screenshot_2025-07-03-11-40-49-295_com.android.fileexplorer.jpg QQ_1751514708650.png


打赏榜单

21小跑堂 打赏了 50.00 元 2025-07-09
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论

便捷性再度升级,在PC连接单片机串口进行IAP升级的基础上。使用手机的虚拟U盘实现拖拽固件升级。  发表于 2025-7-9 14:04
破晓战神 发表于 2025-7-7 19:25 | 显示全部楼层
这个方法太酷了!用手机升级固件确实方便很多,不需要每次都依赖电脑。你测试的时候遇到哪些问题?
暖茶轻语 发表于 2025-7-9 13:06 | 显示全部楼层
非常实用的分享!通过手机实现固件升级确实方便很多,免去了每次都要连接电脑的麻烦。你提到的DBR表和FAT表的修改是关键步骤,能否详细解释一下如何根据手机的反馈调整这些参数?
 楼主| kissdb 发表于 2025-7-9 16:44 | 显示全部楼层
暖茶轻语 发表于 2025-7-9 13:06
非常实用的分享!通过手机实现固件升级确实方便很多,免去了每次都要连接电脑的麻烦。你提到的DBR表和FAT表 ...

我只测试了小米的手机,手机不识别的主要原因是他会写分区内容,和DBR表FAT表无关。我这属于投机取巧,我就直接给了他几十K的分区让他去写,没想到竟然可以。有精力的可以根据他写的内容存在一个数组里,等他读的时候再给他返回去,这样就不用浪费几十K的空间了。
 楼主| kissdb 发表于 2025-7-9 16:46 | 显示全部楼层
破晓战神 发表于 2025-7-7 19:25
这个方法太酷了!用手机升级固件确实方便很多,不需要每次都依赖电脑。你测试的时候遇到哪些问题?
...

使用虚拟出来的U盘,电脑可以识别,手机识别不了,测试了很多DBR表,都不行
您需要登录后才可以回帖 登录 | 注册

本版积分规则

13

主题

290

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部

13

主题

290

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部