引言
我们在向一些非专业开发者发放开发板时,有时对方没有调试器,开发者就无法下载体验在后续发布支持新功能的固件。并且,对于非专业开发者的用户来说,仅仅是体验新功能,而不介入开发工作,专门搭建一套开发环境,性价比实在不高。即使有调试器硬件,在不同操作系统平台上,还需要安装专门的工具软件配合工作,才能实现下载固件的功能,操作比较繁琐。为了简化下载固件的操作,本例使用MM32F5270微控制器,基于芯片自带的USB外设,实现了一个基于U盘拖拽更新固件的解决方案。我期望实现的结果是:
将开发板同PC连接后,PC将识别出一个U盘
可将开发板的固件(bin 格式)文件拖拽存放至该U盘中
复位开发板后,开发板能够执行新的固件程序
相对于板载带有U盘拖拽下载功能的daplink方案,本例节约了一个专门运行daplink程序的芯片,利用微控制器自带的USB外设直接建立同PC的连接,更可有机会被实现成ROM,固化在芯片内部。
原理
MM32F5270片内集成了256KB Flash,但可通过QSPI接口外扩spiflash存储设备,并可在外扩spiflash中执行程序(XIP)。本例实际将运行bootloader的片内Flash当做BOOT ROM,而将可执行程序的固件存放在对接的spiflash存储介质中。当芯片上电后,通过一些外部控制手段(例如使用一个按键选择启动模式),先运行带有USB功能的bootloader程序,由bootloader的程序将芯片模拟成U盘(U盘的物理存储空间位于外扩spiflash的后半段)。PC可以看到模拟U盘中的现存文件,也可以向其中拖拽新文件。当拖拽新文件后,bootloader程序会继续将新文件的程序内容复制到程序的执行区域,覆盖掉之前的程序,然后再跳转到程序的执行区域执行程序。
这里专门使用一块物理存储区域(spiflash存储器的后半段)的原因是,USB协议栈模拟U盘时,对文件系统的管理操作全部交由PC完成,这就意味着,PC向模拟U盘发送包含文件内容的数据包时,不一定是按照物理上的先后顺序发送的,微控制器端难以通过数据包本身解析出数据包的先后顺序。但是,bootloader向程序执行区域复制可执行程序的内容必须是顺序且连续的。PC在同U盘的通信过程中,数据包之间的内容可能不连续,但最终发送完成的文件内容一定是完整的。因此,这里使用使用了一个能包含整个固件文件大小的区域作为缓冲区,先缓冲下来整个完整的文件,然后再将完整的文件内容按顺序复制到程序的可执行区域。
理想情况下,如果能用一块RAM作为整个文件的缓冲区,通过U盘拖拽下载过程将会非常快。但本例中使用spiflash作为缓冲区,虽然擦除spiflash并向其写入数据需要花费更多的时间,但能容纳更大的程序文件。
如果芯片内部集成足够大的片内Flash,也可以不依赖外扩spiflash,对内部的Flash做分区,分别作为bootloader、运行程序及文件缓存的存储区。
实现
硬件相关
本例使用BIRD-F5270开发板,开发板基本情况如下:
晶振 12MHz
使用QSPI接口对接外扩spiflash存储芯片FM25Q16(2MB Flash),引脚与MM32F5280片内合封所使用的引脚保持一致:
QSPI_NSS - PF6
QSPI_SCK - PG7
QSPI_D0 - PG6
QSPI_D1 - PF8
QSPI_D2 - PF10
QSPI_D3 - PG8
USB 引脚
USB_DP - PA12
USB_DM - PA11
按键引脚
SW1 - RESET
SW2 - BOOT0
SW3 - PA0
软件相关
启动到2nd bootloader
芯片上电复位后,默认是直接执行到用户程序中,但也可以通过指定的手段改为执行bootloader,执行U盘程序。本例使用一个按键,绑定到一个指定的GPIO输入(BOOT引脚),芯片在启动过程中判定启动模式:默认启动到用户程序,当按键按下时,启动到带有U盘程序的bootloader。
MM32F5芯片自带的BOOT0引脚虽然在微控制器复位后可作为普通GPIO使用,但可能会将芯片引导到芯片内部已经固化到ROM中的bootloader程序中,因此不适合作为2nd bootloader的BOOT引脚。BIRD-F5270开发板上除RESET和BOOT0 之外,还有PA0引脚实现按键(SW3)功能,可作为2nd bootloader中的BOOT引脚。
当 SW3 被按下时,PA0 为低电平,可执行 U 盘任务,当 SW3 松开时,PA0 为高电平,可执行跳转程序的任务,具体实现代码如下:
/*readgpiopinlevel,selectbootmode.*/ if(GPIO_ReadInDataBit(BOARD_BTN_PORT,BOARD_BTN_PIN)) { /*updateapp&run.*/ jump_to_app(); } else { /*runusbmsctask.*/ msc_task(); } /*cannotrunhere.*/ while(1) {}
基于USB外设模拟U盘
MM32F5270微控制器集成了USB外设,配合TinyUSB协议栈,可模拟U盘(MSC设备)。
当运行模拟U盘的程序时,需要指定一块存储区域作为U盘的存储空间,在本例中,将外扩的spiflash存储区域一分为二,前半部分作为执行程序区域,存储需要执行的应用程序,后半部分作为U盘的物理存储空间,将用于缓存用户通过拖拽方式下载的固件。
注意,spiflash作为U盘的存储介质时需要解决一个问题,spiflash的最小擦除块大小是4KB,而U盘设备对存储介质的擦除块大小是512B,擦除块大小不匹配。该问题有两种解法:
每次修改spiflash前,先将包含目标操作空间的4KB大小的数据块读取数据到RAM中缓存,修改指定位置的数据内容后,擦除spiflash中对应的整块,再将修改后的数据块写入到spiflash中。
每次修改spiflash前,先将包含目标操作空间的4KB大小的数据块读取数据到RAM中缓存,修改指定位置的数据内容后,暂不写回到spiflash中。如果接下来要写入的数据仍在该块中,则继续写入RAM缓存。只有当将要新写入的数据不在缓存的数据块中时,才写回最近缓存的数据块,并读出新的数据块到RAM缓存中。另外,每次在写操作之后的读操作,也都会重新清空RAM缓存中的数据。
第一种方法简单易实现,但会频繁擦写spiflash,消耗存储器件的寿命。第二种方法相当于实现了一个cache机制,操作行为较为复杂。当前暂时选择第一种做法,先验证原理,如果后续考虑将这个带U盘功能的bootloader集成到芯片内部,到时也可以专门集成一块以512B作为操作单元的dflash作为固件的缓存存储设备。
运行TinyUSB协议栈的程序中,在执行MSC任务时,会执行对spiflash存储设备进行读写操作的回调函数,如下:
从spiflash读数据:
//CallbackinvokedwhenreceivedREAD10command. //Copydisk'sdatatobuffer(uptobufsize)andreturnnumberofcopiedbytes. int32_ttud_msc_read10_cb(uint8_tlun,uint32_tlba,uint32_toffset,void*buffer,uint32_tbufsize) { (void)lun; uint8_t*addr=(uint8_t*)(BOARD_MEM_BASE+lba*512+offset); memcpy(buffer,addr,bufsize); returnbufsize; }
向spiflash写数据:
staticuint8_tflash_buf[4096]; //CallbackinvokedwhenreceivedWRITE10command. //Processdatainbuffertodisk'sstorageandreturnnumberofwrittenbytes int32_ttud_msc_write10_cb(uint8_tlun,uint32_tlba,uint32_toffset,uint8_t*buffer,uint32_tbufsize) { (void)lun; /*readthe4Ksectorfromspiflashtolocalbuff.*/ uint8_t*flash_addr=(uint8_t*)((BOARD_MEM_BASE+lba*512)&0xFFFFF000); memcpy(flash_buf,flash_addr,4096); /*erasethesectorinspiflash.*/ BOARD_EraseSector4KB((uint32_t)flash_addr); /*writethenewdataintolocalbuff.*/ uint8_t*block=flash_buf+((lba*512)&0xFFF)+offset; memcpy(block,buffer,bufsize); /*writethedatafromlocalbufftospiflash.*/ BOARD_WriteSector4KB((uint32_t)flash_addr,flash_buf); returnbufsize; }
另外,还有一种做法是不需要真实存在的物理存储空间作为U盘的存储介质,而是实时解算PC发过来的要存储在Flash上的数据包,解析其中哪些数据是文件系统相关的信息,哪些数据是文件本身内容。此时,可丢弃文件系统相关的信息,而将文件本身内容按顺序存放在spiflash的指定区域。该方法不需要将spiflash空间一分为二,可提高对spiflash的空间利用率,但该实现该方法需要十分熟悉FAT文件系统格式,且万一电脑未按照指定的序列发数数据时,该方法无法正确解算出文件内容,造成更新固件失败,因此,在本例中未实现该功能。
判断是否存在新固件
前文中实现的U盘功能,将包括文件系统信息的新固件储存在了spiflash的后半段空间中,bootloader程序要读取新固件中的内容也需要能够操作文件系统,本例中为bootloader程序性集成了嵌入式系统上常见的文件系统组件为 FatFs(http://elm-chan.org/fsw/ff/00index_e.html)
本项目中,仅需要 FatFs 实现文件的读功能,不需要对文件进行写操作,因此只需要在适配接口上实现读操作即可, 配置文件ff_conf.h中配置宏选项 FF_FS_READONLY 的值为1。然后在diskio.c文件中适配文件系统对具体物理介质的读操作函数disk_read()如下:
DRESULTdisk_read( BYTEpdrv,/*Physicaldrivenmubertoidentifythedrive*/ BYTE*buff,/*Databuffertostorereaddata*/ LBA_tsector,/*StartsectorinLBA*/ UINTcount/*Numberofsectorstoread*/ ) { (void)pdrv; BYTE*flash_addr=(BYTE*)(BOARD_MEM_BASE+sector*FF_MAX_SS); memcpy(buff,flash_addr,count*FF_MAX_SS); return0; }
如何在文件系统中的众多文件中识别微控制器可执行程序的固件呢?本例采用通过文件名来确定是否为固件。
通过 FatFs 尝试打开一个有宏BOARD_APP_NAME指定名称的文件,若能成功打开该文件,则说明有可用的固件文件存在。
if(FR_OK==f_open(&file,"0:/"BOARD_APP_NAME,FA_READ)) { ... }
为什么不能使用任意bin判断文件作为固件呢?这里考虑到,当文件系统中有多个bin文件时,bootloader 没有依据判断哪一个固件为新固件(根据修改日期判断是否可行,作为可移动存储设备,会插在不同电脑上,如果不同电脑的时间不一致,则会出现判断失误的情况,因此不选择使用修改日期作为新固件的判断依据),指定固定文件名的方式判断固件,最简单有效,问题最少。
验证固件的有效性
不少人都应该有这样的经历,在网上下载大文件如大型游戏时,往往下载页面会提供一串 MD5 码,这段 MD5 码的作用是验证下载的文件是否完整,当用户在网上下载大文件完成后,可在 CMD 中使用 certutil -hashfile
MicrosoftWindows[版本10.0.22621.1848] (c) Microsoft Corporation。保留所有权利。 E:>certutil-hashfileproject.binMD5 MD5的project.bin哈希: 615ca0da12d496b7a8b1bd6c21876a97 CertUtil:-hashfile 命令成功完成。 E:>
在嵌入式领域,Arm的MbedTLS加解密算法库中提供了MD5的计算方法,因此可使用MbedTLS计算MD5,有程序如下:
/*calcmd5.*/ mbedtls_md5_contextmd5_ctx; mbedtls_md5_init(&md5_ctx); mbedtls_md5_starts(&md5_ctx); while(1) { UINTbr=0; f_read(&file,app_buf,sizeof(app_buf),&br); mbedtls_md5_update(&md5_ctx,app_buf,br); if(br< sizeof(app_buf)) { break; } } f_lseek(&file, 0); uint8_t md5_val[16] = {0}; mbedtls_md5_finish(&md5_ctx, md5_val);
除了 MD5 以外,还可以使用如 SHA1,SHA2 或者 SM3 等更高安全等级的散列值算法作为新固件的判断依据。
复制固件到执行区域
复制固件到执行区域的做法较为简单,就是使用 f_read() 函数读取数据,再写入到spiflash的指定区域即可。
但除了复制固件文件内容本身,其文件的MD5值也要写入到Flash中。应在写新固件之前将之前的MD5值擦除,写新固件完成之后,再将新MD5值写入,如此一来,MD5除了作为检查新固件的完整性的方法外,还可作为执行区域固件完整性检查的重要依据。
本例中,将MD5值存放在片内flash的末尾。
检查新固件和复制固件到执行区域的代码如下:
/*checkmd5,themd5lengthis16byte.*/ if(memcmp((uint8_t*)BOARD_MD5_BASE,md5_val,sizeof(md5_val))!=0) { /*newfile,update.*/ BOARD_EraseSector4KB(BOARD_MD5_BASE); uint32_thas_write=0; while(1) { UINTbr=0; memset(app_buf,0xff,sizeof(app_buf)); f_read(&file,app_buf,sizeof(app_buf),&br); BOARD_EraseSector4KB(has_write); BOARD_WriteSector4KB(has_write,app_buf); has_write+=4096; if(br< sizeof(app_buf)) { break; } } /* write new md5. */ memset(app_buf, 0xff, sizeof(app_buf)); memcpy(app_buf, md5_val, sizeof(md5_val)); BOARD_WriteSector4KB(BOARD_MD5_BASE, app_buf); }
需要使用完整的一个块存放MD5值,MD5必须保证单独擦除,单独写入。除了MD5以外,该区域还可以可根据需要存放一些固件相关的信息,比如说修改时间,固件大小等信息,充分利用空间。
跳转执行
跳转程序需要做的事情包括:恢复系统时钟,修改中断向量表位置,复位栈指针,执行应用程序的复位程序等。具体操作如下:
voidjump_to_app(void) { ... /*jumptoapp.*/ CLOCK_ResetToDefault(); vtor=(uint32_t*)BOARD_APP_BASE; __disable_irq(); SCB->VTOR=BOARD_APP_BASE; __ISB(); __DSB(); __set_MSP(vtor[0]); __set_PSP(vtor[0]); __enable_irq(); ((void(*)(void))vtor[1])(); }
用户应用程序
为了配合bootloader正常工作,需要用户在自己的应用程序需调整以下内容:
在Linker File中指定可执行程序在分块后的存储区
避免误配置GPIO和QSPI导致BOOT按键或外扩spiflash失效
本例实现的bootloader,spiflash作为应用程序的执行区域,代码段的起始位置位于spiflash的起始映射地址0x90000000,不是片内Flash的0x08000000,因此,需要调整Linker中对应的配置如下:
/*---------------------FlashConfiguration---------------------------------- ;FlashConfiguration ; *----------------------------------------------------------------------------*/ #define__ROM_BASE0x90000000 #define__ROM_SIZE0x00100000FlashBaseAddress<0x0-0xFFFFFFFF:8> ; FlashSize(inBytes)<0x0-0xFFFFFFFF:8> ;
若使用spiflash存储应用程序,bootloader在执行应用程序前就应该将 QSPI 接口和 GPIO 引脚配置好。在用户应用程序中,如果需要操作QSPI或者bootloader中使用的GPIO,要特别注意避开。
总结
通过USB接口将BIRD-F5270连接电脑,保持SW3按键按下时,再按下并松开RESET按键,稍后会在PC上看到一个大小约为1MB的U盘,将新编译好的名为 “project.bin” 的固件文件拖拽存放至该U盘中,然后保持SW3按键松开的状态下再次按下并松开RESET按键,稍后即可发现微控制器已经在执行新固件中的应用程序了。
作为下载新固件的 U 盘,还可以存放一些别的文件,供应用程序使用,例如一张图片,一首音乐,或一段文字,但需要注意的是,应用程序对该文件系统只有读操作的权限,不能进行写操作(或者在bootloader中实现安全写操作的接口,让应用程序去调用相应的写操作接口)
known issue
开发板通过 USB 连接电脑,保持 SW3 按键按下的状态,多次快速按下复位键,可能会造成文件系统损坏,文件丢失,需重新格式化才能正常使用,其原因是两次按下复位键中间,电脑正在修改 Flash 中的内容,但复位操作打断了写操作,造成文件系统损坏。因此需避免快速多次按下复位键的操作。这里可考虑使用一个可编程的指示灯,指示芯片内部程序的工作状态:当拖拽固件文件到芯片时,指示灯快速闪烁;当复制新固件文件到程序运行区域时,指示灯保持常亮;当复制完毕后,指示灯熄灭。仅当指示灯熄灭后,方可重新复位芯片执行新程序。
审核编辑:刘清
-
微控制器
+关注
关注
48文章
7470浏览量
150954 -
ROM
+关注
关注
4文章
562浏览量
85645 -
调试器
+关注
关注
1文章
300浏览量
23677 -
SPI Flash
+关注
关注
1文章
13浏览量
10328 -
QSPI接口
+关注
关注
0文章
14浏览量
3334
原文标题:定制带U盘功能的bootloader实现拖拽下载固件
文章出处:【微信号:pzh_mcu,微信公众号:痞子衡嵌入式】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论