单片机产品开发时可以使用JLink、DAPLink等烧录器进行固件下载和调试,当产品开发完成量产后如果需要升级固件一般都会通过串口、CAN等配合Bootloader进行升级,除了在单片机端实现一个Bootloader外还需要实现一个按照既定通讯协议发送固件的程序,如果使用的单片机支持USB则有更方便的升级方式,接下来实现一个虚拟成U盘拖拽固件升级的Bootloader
这里用到的是一个将USB引出的STM32F103C8T6的核心板
打开STM32CubeMX新建一个STM32F103的工程,配置USB
定义Bootloder大小,实现APP跳转方法,如果不勾选USB MicroLIB需要再定义大一点
复制
#define BOOT_SIZE 0x4000#define APP_ADDR (FLASH_BASE + BOOT_SIZE)typedef void (*pFunction)(void);void BL_StartAPP(void){pFunction start_application;uint32_t app_address;__disable_irq();app_address = *(__IO uint32_t*) (APP_ADDR + 4);start_application = (pFunction) app_address;__set_MSP(*(__IO uint32_t*) app_address);start_application();}
Bootloder大小可以完成Bootloader后再调整,可以通过编译出的Hex文件计算,例如下面这个
关于Hex文件的格式可以在网上搜索,最后的地址是0x3FE0,最后一条的数据长度是0C,所以Bootloder大小为0x3FEC,定义为0x4000就够了
使用PB9来控制是否进入Bootloader,当PB9为高电平时跳转到APP,在GPIO初始化后添加
复制
if(LL_GPIO_IsInputPinSet(KEY_GPIO_Port,KEY_Pin)){LL_mDelay(100);if(LL_GPIO_IsInputPinSet(KEY_GPIO_Port,KEY_Pin)){LL_GPIO_SetOutputPin(LED_GPIO_Port,LED_Pin);BL_StartAPP();}}LL_GPIO_ResetOutputPin(LED_GPIO_Port,LED_Pin);if(LL_GPIO_IsInputPinSet(KEY_GPIO_Port,KEY_Pin)){LL_mDelay(100);if(LL_GPIO_IsInputPinSet(KEY_GPIO_Port,KEY_Pin)){LL_GPIO_SetOutputPin(LED_GPIO_Port,LED_Pin);BL_StartAPP();}}
接下来需要虚拟一个文件系统,这里如果对文件系统不熟悉也没关系,可以使用分区工具DiskGenius分出一个最小容量的分区,没有空闲分区的可以在虚拟机里操作,格式化后顺便放进一个提示文件,选中这个分区,记录下这两个数值
打开扇区编辑,全选然后另存为一个文件
用winhex打开刚才保存的文件,找到这几个不为0的数据段
使用winhex的复制为C源码的选项将数据粘贴到代码中,其中DBR中有一大段是启动代码可以忽略,FAT1和FAT2的数据一样
复制
#define BL_FAT_CLUSTER_SIZE 0x200 //簇大小#define BL_FAT_INDEX_START_ADDR 0x11000 //目录起始地址#define BL_FAT_INDEX_BIN_START_ADDR 0x11080 //写入的BIN文件起始地址#define BL_FAT_DATA_START_ADDR 0x15000 //数据起始地址//以下是一个8M的FAT16分区的数据结构//DBR 0x00-0x1FF 以55 AA结尾,引导程序代码部分省略const uint8_t BL_FAT_dbr[70] = {0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, 0x02, 0x01, 0x08, 0x00,0x02, 0x00, 0x02, 0x00, 0x40, 0xF8, 0x40, 0x00, 0x3F, 0x00, 0xFF, 0x00, 0x00, 0x08, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x29, 0x23, 0x48, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20,0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x41, 0x54, 0x31, 0x36, 0x20, 0x20, 0x20, 0x59, 0x55,0x59, 0x59, 0x31, 0x39, 0x38, 0x39};//FAT1 0x1000 FAT2 0x9000const uint8_t BL_FAT_fat1_fat2[4]={0xF8,0xFF,0xFF,0xFF};//根目录 0x11000const uint8_t BL_FAT_root_dir[128] = {0x53, 0x54, 0x4D, 0x33, 0x32, 0x42, 0x4F, 0x4F, 0x54, 0x20, 0x20, 0x08, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x95, 0xCC, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x42, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x64, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,0x01, 0x70, 0x00, 0x75, 0x00, 0x74, 0x00, 0x20, 0x00, 0x62, 0x00, 0x0F, 0x00, 0x64, 0x69, 0x00,0x6E, 0x00, 0x20, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x65, 0x00, 0x20, 0x00,0x50, 0x55, 0x54, 0x42, 0x49, 0x4E, 0x7E, 0x31, 0x20, 0x20, 0x20, 0x20, 0x00, 0xB3, 0x93, 0x95,0xCC, 0x5A, 0xCC, 0x5A, 0x00, 0x00, 0x94, 0x95, 0xCC, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
接下来实现USB读取方法,将这些数据发送给电脑
复制
void BL_FAT_ReadBlocks512(uint32_t block_addr,uint8_t *data,uint16_t block_len){uint32_t data_index = block_addr * 512;uint32_t data_len = block_len * 512;memset(data,0,data_len);if(data_index == 0)//DBR{memcpy(data,BL_FAT_dbr,70);data[510] = 0x55;data[511] = 0xAA;}else if(data_index == 0x1000 || data_index == 0x9000) //FAT1 FAT2{memcpy(data,BL_FAT_fat1_fat2,4);}else if(data_index == 0x11000)//根目录{memcpy(data,BL_FAT_root_dir,128);}}void BL_FAT_ReadBlocks512(uint32_t block_addr,uint8_t *data,uint16_t block_len){uint32_t data_index = block_addr * 512;uint32_t data_len = block_len * 512;memset(data,0,data_len);if(data_index == 0)//DBR{memcpy(data,BL_FAT_dbr,70);data[510] = 0x55;data[511] = 0xAA;}else if(data_index == 0x1000 || data_index == 0x9000) //FAT1 FAT2{memcpy(data,BL_FAT_fat1_fat2,4);}else if(data_index == 0x11000)//根目录{memcpy(data,BL_FAT_root_dir,128);}}
在usbd_storage_if.c中的STORAGE_Read_FS调用这个方法,编译烧录后拉低PB9通过USB连接电脑和核心板,电脑上会出现这个盘符
打开也能看到提示文件
接下来实现通过U盘接收固件数据,原本想用固定地址接收的,但是有些系统接入U盘后会写入一些系统文件,这样就占用了原本的地址,虽然并没有成功写入但是可能是系统缓存的原因继续添加文件的话文件数据的起始地址就变了,在0x11000这个地址存储着U盘中文件的索引,数据格式如图
向U盘添加文件后会在后面顺序添加这个文件的索引,对于长文件名的文件最后也会追加一个短文件名的索引,因此只需要判断最后一个文件索引的扩展名是不是BIN就行了。
虚拟数据已经占用了前4个索引,因此从第5个索引开始查找,找到最后一个索引,判断文件是否为BIN格式,如果是BIN格式,获取到数据的簇偏移号和大小,成功获取到簇偏移号后就能计算出数据的写入地址,然后等待USB写入数据并将固件的数据写入内部FLASH
复制
void BL_FAT_WriteBlocks512(uint32_t block_addr,uint8_t *data,uint16_t block_len){uint32_t data_index = block_addr * 512;uint32_t data_len = block_len * 512;uint16_t sum = 0;if(data_index < BL_FAT_INDEX_START_ADDR)return;if(find_bin_data_addr == 0) //还未找到固件起始地址,寻找最后一个不为0的文件索引{if(data_index < BL_FAT_DATA_START_ADDR){if(data_index < BL_FAT_INDEX_BIN_START_ADDR){data_index += 0x80;data += 0x80;data_len -= 0x80;find_bin_data_addr = 0;current_fat_file_addr = BL_FAT_INDEX_BIN_START_ADDR;current_bin_data_addr = 0;current_bin_data_size = 0;current_flash_addr = APP_ADDR;}if(data_index > current_fat_file_addr + 31)return;while(data_len > 0){sum = 0;for(uint8_t i = 0;i<32;i++){sum += data[i];}if(sum == 0){if(current_bin_data_addr != 0 && current_bin_data_size != 0){find_bin_data_addr = 1;}break;}else{current_fat_file_addr = data_index;if(data[11] == 0x20 && data[8] == 'B' && data[9] == 'I' && data[10] == 'N' && (data[26] > 1 || data[27] > 0)){current_bin_data_addr = BL_FAT_DATA_START_ADDR + (data[26]+(data[27]<<8)-2)*BL_FAT_CLUSTER_SIZE; //减2才是真正的簇号current_bin_data_size = data[28] + (data[29]<<8) + (data[30]<<16) + (data[31]<<24);if(current_bin_data_size + BOOT_SIZE> (*(uint16_t*)(FLASHSIZE_BASE))*1024) //固件过大{current_bin_data_size = 0;led_delay = 200;}}}data_index += 32;data += 32;data_len -= 32;}}}else{if(data_index < current_bin_data_addr)return;if(data_len > current_bin_data_size){BL_FlashApp(data,current_bin_data_size);//写入FLASH的方法省略,网上例程很多current_bin_data_size = 0;}else{BL_FlashApp(data,data_len);current_bin_data_size -= data_len;}if(current_bin_data_size == 0){find_bin_data_addr = 0;led_delay = 1000;}}}
至此Bootloader部分就完成了,接下来实现APP部分,新建一个工程,在工程设置中修改起始地址和长度
在C/C++标签添加USER_VECT_TAB_ADDRESS到末尾
升级使用的是BIN文件,在User选项卡中添加fromelf --bin -o ".\@L\@L.bin" "#L"就能在编译后生成bin文件了
打开system_stm32f1xx.c修改偏移量
之后就能像平常一样进行编程了,编译出的bin文件就可以拖动到Bootloader的U盘中实现升级了,效果如下
在此基础上还可以增加文件校验加密传输防止降级等功能,有兴趣的可以自行尝试
---------------------
作者:yuyy1989
链接:https://bbs.21ic.com/icview-3461600-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。