目录
概述
一、功能描述
1、BootLoader部分:
2、APP部分:
二、BootLoader程序制作
1、分区定义
2、 主函数
3、配置USB
4、配置fatfs文件系统
5、程序跳转
三、APP程序制作
四、工程配置(默认KEIL5)
五、运行测试
六、 结束语
概述
IAP(In Application Programming)即在应用中编程,允许在应用程序运行时更新或切换固件。STM32通过修改MSP(主堆栈指针)和PC(程序计数器)实现从不同地址启动,包括Flash或RAM地址。默认情况下,嵌入式程序以连续二进制形式烧录到STM32的可寻址Flash区域。若Flash容量足够存储多个完整程序,每个程序独立且完整,上电后可通过修改MSP值选择不同程序入口,从而实现多固件切换或升级。
BootLoader(引导加载程序)是嵌入式系统或计算机启动时运行的一段小型程序,负责初始化硬件、加载操作系统内核并将其控制权移交。它是系统从关机状态到操作系统完全运行之间的桥梁。
所以,固件升级的基本思路是将stm32的flash划分为若干个区域,其中包括BootLoader区域和APP区等,将各自的程序写到对应的flash区域里。
一、功能描述
使用STM32的USB总线及外置W25Q64实现USB模拟U盘升级程序功能。
分区介绍:
本文使用stm32f103vet6,flash是512k,sector是1k,BootLoader整个代码编译下来有23K左右,所以使用0x08000000~0x00007FFF(如果想更大化地利用flash,可以不要setting区域,具体看自己如何写了),APP整个代码使用剩下的0x08009000~0x0807FFFF 区域。
区域 | 起始地址 | 区域大小 | 功能 |
---|---|---|---|
BOOT | 0x08000000 | 0x00008000(32k) | 存放BootLoader程序 |
SETTING | 0x08008000 | 0x00001000(4k) | 存放升级标志位/其它掉电不丢失标志位 |
APP | 0x08009000 | 0x00077000(476k) | 存放产品主程序 |
1、BootLoader部分:
运行程序时首先从SETTING区域读取升级标志位,如果需要升级就进入识别U盘程序,否则就直接跳转到APP。上电长按KEY1并复位,电脑上即可模拟出U盘,识别到U盘后拖拽固件bin文件到U盘,然后将W25Q64里的fatfs文件系统的升级文件拷贝到APP起始地址,即可实现升级程序,具体请查看本文源码。
2、APP部分:
该部分只需要设置中断向量跳转指针就行,如果通过串口等下发升级标志位也可以设置SETTING区域后复位进入BootLoader升级。
二、BootLoader程序制作
需要包含USB Device中的Mass_Strorage和w25q64以及fatfs文件系统的驱动代码。(这部分是需要仔细研究做好的,我是根据正点原子和野火的教程移植的,具体流程不做了)
1、分区定义
#define FLASH_SECTOR_SIZE 1024 //MCU sector size
#define FLASH_SECTOR_NUM 512 // 512K
#define FLASH_START_ADDR ((uint32_t)0x8000000)
#define FLASH_END_ADDR ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))#define BOOT_SECTOR_ADDR 0x08000000 // BOOT sector start address
#define BOOT_SECTOR_SIZE 0x8000
#define SETTING_SECTOR_ADDR 0x08008000 //APP设置的boot升级标志位
#define SETTING_SECTOR_SIZE 0x1000
#define APP_SECTOR_ADDR 0x08009000 // APP sector start address
#define APP_SECTOR_SIZE 0x77000#define APP_ERASE_SECTORS (APP_SECTOR_SIZE / FLASH_SECTOR_SIZE) typedef enum {NONE = 0,START_PROGRAM, //进入APP主程序或者有更新就执行更新UPDATE_PROGRAM, //进入更新UPDATE_SUCCESS //更新成功写标志位
}Update_Process; //更新状态
2、 主函数
这部分包含了升级的所有状态,具体看代码。
Update_Process bootupdate_process;static void iap_process(void)
{uint8_t offline_cnt=0;uint8_t tct=0;uint8_t USB_STA;uint8_t Device_STA; // 定义最大重试次数和每次等待间隔const uint8_t max_retries = 5;const uint8_t wait_interval_ms = 100;uint8_t retry_count = 0;switch (bootupdate_process) {case NONE:break;case START_PROGRAM:spiflash_update();printf("start app...\r\n");delay_ms(50);if ((((*(vu32*)(APP_SECTOR_ADDR+4))&0xFF000000)==0x08000000)&&(!iap_load_app(APP_SECTOR_ADDR))) {printf("no program\r\n");delay_ms(1000);}printf("start app failed\r\n");break;case UPDATE_PROGRAM:MAL_Init(0);Max_Lun=0; USB_Interrupts_Config();/*设置USB时钟为48M*/Set_USBClock();USB_Init();
// while (bDeviceState != CONFIGURED); //等待配置完成// 等待配置完成,最多重试 5 次while (bDeviceState != CONFIGURED && retry_count < max_retries) {delay_ms(wait_interval_ms);retry_count++;}// 若 5 次后仍未配置完成,进入 START_PROGRAM 状态if (bDeviceState != CONFIGURED) {printf("USB 配置失败,尝试 5 次后退出,进入 START_PROGRAM 状态。\r\n");bootupdate_process = START_PROGRAM;return;}while(1){delay_ms(1);if(USB_STA!=USB_STATUS_REG)//状态改变了 {if(USB_STATUS_REG&0x01)//正在写{printf("USB Writing...\r\n");//提示USB正在写入数据 }if(USB_STATUS_REG&0x02)//正在读{printf("USB Reading...\r\n");//提示USB正在读出数据 } if(USBD_User_App() == FR_OK) //检测到有对应的bin文件{bootupdate_process=UPDATE_SUCCESS;delay_ms(500);break;}if(USB_STATUS_REG&0x04)printf("USB Write Err\r\n");//提示写入错误 if(USB_STATUS_REG&0x08)printf("USB Read Err\r\n");//提示读出错误 USB_STA=USB_STATUS_REG;//记录最后的状态}if(Device_STA!=bDeviceState) {if(bDeviceState==CONFIGURED){LED1_ON;//提示USB连接已经建立}else {LED1_OFF;//提示USB被拔出了}Device_STA=bDeviceState;}tct++;if(tct==200){tct=0;LED1_TOGGLE;//提示系统在运行if(USB_STATUS_REG&0x10){LED1_ON;offline_cnt=0;//USB连接了,则清除offline计数器bDeviceState=CONFIGURED;}else//没有得到轮询 {LED1_OFF;offline_cnt++; if(offline_cnt>10)bDeviceState=UNCONNECTED;//2s内没收到在线标记,代表USB被拔出了}USB_STATUS_REG=0;}}break;case UPDATE_SUCCESS:bootupdate_process=START_PROGRAM;write_setting_boot_state(bootupdate_process);NVIC_SystemReset();break;default:break;}
}int main(void)
{USART_Config();LED_GPIO_Config();printf("\r\n 使用指南者底板时 左上角排针位置 不要将PC0盖有跳帽 防止影响PC0做SPIFLASH片选脚 \r\n");bootupdate_process=(Update_Process)read_setting_boot_state();if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == 1){bootupdate_process=UPDATE_PROGRAM;}while (1){iap_process();}
}
3、配置USB
这部分根据野火代码移植而来,usb硬件配置自己看代码理解就好,一般没什么问题,w25q64最高有8Mbyte,前面2M用作其它用途,后面6M用做U盘,所以需要偏移2M,w25q64的底层读写函数需要特别注意,如果U盘出bug,大概率是以下几个函数有误。
uint16_t MAL_Init(uint8_t lun)
{ u16 Status=MAL_OK; switch (lun){case 0: W25QXX_Init(); //初始化W25Q64break; case 1: break; default:return MAL_FAIL;}return Status;
}uint16_t MAL_Write(uint8_t lun, uint64_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{switch (lun) //这里,根据lun的值确定所要操作的磁盘{case 0: //磁盘0为 SPI FLASH盘 Memory_Offset+=(512*4096);//扇区偏移,外部Flash文件系统空间放在外部Flash后面6M空间W25QXX_Write((u8*)Writebuff, Memory_Offset, Transfer_Length); printf("Memory_Offset=%llu\r\n",Memory_Offset);break; case 1: //磁盘1为SD卡 break; default:return MAL_FAIL;}return MAL_OK;
}uint16_t MAL_Read(uint8_t lun, uint64_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{switch (lun) //这里,根据lun的值确定所要操作的磁盘{case 0: //磁盘0为 SPI FLASH盘 Memory_Offset+=(512*4096);//扇区偏移,外部Flash文件系统空间放在外部Flash后面6M空间W25QXX_Read((u8*)Readbuff, Memory_Offset, Transfer_Length); break; case 1: //磁盘1为SD卡 break; default:return MAL_FAIL;}return MAL_OK;
}uint16_t MAL_GetStatus (uint8_t lun)
{switch(lun){case 0:if(W25QXX_TYPE!=W25Q64) //最高只能到8Mbyteprintf("W25Q64 Error!\r\n");//检测spiflash错误else //SPI FLASH 正常{ Mass_Block_Size[0] =4096; //设置SPI FLASH的操作扇区大小为4096Mass_Block_Count[0]=1536;Mass_Memory_Size[0]=Mass_Block_Size[0]*Mass_Block_Count[0]; //总字节
// printf("SPI FLASH Size:%dMB\r\n",(uint8_t)(Mass_Memory_Size[0]/1024/1024));}return MAL_OK;case 1:return MAL_OK; default:return MAL_FAIL;}
}
4、配置fatfs文件系统
这部分也是根据野火代码移植而来,需要特别注意扇区大小和数量需要和USB配置的一样。
//初始化磁盘
DSTATUS disk_initialize (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{DSTATUS status = STA_NOINIT; uint16_t i;switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flash/* 初始化SPI Flash */W25QXX_Init();/* 延时一小段时间 */i=500;while(--i); /* 唤醒SPI Flash */W25QXX_WAKEUP();/* 获取SPI Flash芯片状态 */status=disk_status(EX_FLASH);break;default:status=STA_NOINIT; } return status;
}
//读扇区
DRESULT disk_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to read */
)
{DRESULT status = RES_PARERR; switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flash/* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间 */sector+=512; W25QXX_Read(buff, sector <<12, count<<12);status = RES_OK;break;default:status = RES_PARERR; }return status;
}
//写扇区
DRESULT disk_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to write */
)
{uint32_t write_addr; DRESULT status = RES_PARERR;if (!count) {return RES_PARERR; /* Check parameter */}switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flashsector+=512;write_addr = sector<<12; W25QXX_Write((u8 *)buff,write_addr,count<<12);status = RES_OK;break;default:status = RES_PARERR; }return status;
}
//其他表参数的获得
DRESULT disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT status = RES_PARERR;switch (pdrv) {case SD_CARD: /* SD CARD */status = RES_OK;break;case EX_FLASH:switch (cmd){/* 扇区数量:1536*4096/1024/1024=6(MB) */case GET_SECTOR_COUNT:*(DWORD * )buff = 1536; break;/* 扇区大小 */case GET_SECTOR_SIZE :*(WORD * )buff = 4096;break;/* 同时擦除扇区个数 */case GET_BLOCK_SIZE :*(DWORD * )buff = 1;break; }status = RES_OK;break;default:status = RES_PARERR;} return status;
}
5、程序跳转
跳转这部分网上也很多,基本没什么区别,关于中断可能要注意一下,可能跳转之前,某些外设中断是开启的,跳转之后,中断产生了,但是APP代码中没有处理对应该中断的中断处理函数,所以就可能会直接死机。
uint8_t iap_load_app(u32 appxaddr)
{uint8_t i;uint32_t jump_addr;if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000) { jump_addr = *(__IO uint32_t*) (appxaddr + 4); jump2app = (iapfun)jump_addr; /* 关闭所有中断,清除所有中断挂起标志 */ for (i = 0; i < 8; i++){NVIC->ICER[i]=0xFFFFFFFF;NVIC->ICPR[i]=0xFFFFFFFF;} __set_MSP(*(__IO uint32_t*)appxaddr); jump2app();return 1;}return 0;
}
三、APP程序制作
这部分设置一下flash的偏移量就行。
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x9000);
四、工程配置(默认KEIL5)
BootLoader部分:0x08000000~0x00007FFF
APP部分:0x08009000~0x800FFFFF
五、运行测试
长按KEY1点击复位(断电重启)可以看到识别到U盘(H:),大小也正常,然后往里面复制一个固件firmware.bin(注意这里的固件名一定要是唯一的,不然程序识别不到),复制进去就会执行升级程序,可以看到打印信息显示固件更新成功。
结束语
以上SPIFLASH模拟U盘升级固件功能已实现,这只是其中的一种升级方式,后面大家看到的也希望可以得到大家的指点。主要的USB库和fatfs库移植教程就不给出来了,网上很多,基本都能实现。
完整代码下载地址:SPIFLASH模拟U盘升级固件资源-CSDN下载