DFU(Device Firmware Upgrade),即固件升级,STM32提供有专门的USB通信协议实现DFU升级BootLoader
我用的开发板是STM32F070,支持DFU升级,故用来做做实验,下面对整个过程做个记录。
首先是使用STM32Cube MX使能USB外设,并在Class for FS IP中选择Download Firmware Class(DFU),我这里通过判断PA2引脚电平决定进入DFU模式还是APP模式,因此PA2引脚配置成输入模式。
之后配置DFU设备的相关参数
红色方框中的两个参数
第一个USBD_DFU_APP_DEFAULT_ADD即是APP程序的开始地址,而从0x08000000开始到0x08007000的空间则是留给BootLoader 。
第二个USBD_DFU_MEDIA Interface是向DFUse软件描述单片机内部Flash分配的情况,@Internal Flash是Flash的名称,0x08000000是Flash的起始地址,09*2Ka表示前面9个页的空间,a表示只读,因此用来存放BootLoader,2k表示每页大小为2k(这个具体要查看芯片手册来设定);g则表示可读可写,故用来存放APP程序。
点击生成代码,在keil中开始编辑BootLoader代码,用户要编辑的主要是usbd_dfu.c文件,在这个文件中提供了Flash的初始化、反初始化、擦除、编程、读取以及获取状态几个函数接口,我们要做的就是实现这些函数的具体操作。
static uint16_t MEM_If_Init_FS(void);
static uint16_t MEM_If_Erase_FS(uint32_t Add);
static uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len);
static uint8_t *MEM_If_Read_FS(uint8_t *src, uint8_t *dest, uint32_t Len);
static uint16_t MEM_If_DeInit_FS(void);
static uint16_t MEM_If_GetStatus_FS(uint32_t Add, uint8_t Cmd, uint8_t *buffer);
1、Init和Deinit里面主要是解锁Flash和上锁Flash
uint16_t MEM_If_Init_FS(void)
{
/* USER CODE BEGIN 0 */
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
return (USBD_OK);
/* USER CODE END 0 */
}
/**
* @brief De-Initializes Memory
* @retval USBD_OK if operation is successful, MAL_FAIL else
*/
uint16_t MEM_If_DeInit_FS(void)
{
/* USER CODE BEGIN 1 */
HAL_FLASH_Lock();
return (USBD_OK);
/* USER CODE END 1 */
}
2、Flash的擦除操作,这里擦除的是存放APP程序的整个空间
#define USBD_DFU_APP_END_ADD 0x0801F800
#define USBD_DFU_APP_DEFAULT_ADD 0x08007000
#define FLASH_PAGE_SIZE 0x800U //2k
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
/* USER CODE BEGIN 2 */
uint32_t NbOfPages = 0;
uint32_t PageError = 0;
FLASH_EraseInitTypeDef pEraseInit;
NbOfPages = (USBD_DFU_APP_END_ADD - USBD_DFU_APP_DEFAULT_ADD)/FLASH_PAGE_SIZE;
pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
pEraseInit.PageAddress = USBD_DFU_APP_DEFAULT_ADD;
pEraseInit.NbPages = NbOfPages; //erase all pages of APP
if(HAL_FLASHEx_Erase(&pEraseInit,&PageError)!= HAL_OK)
return 1;
return (USBD_OK);
/* USER CODE END 2 */
}
3、写Flash操作
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
/* USER CODE BEGIN 3 */
uint32_t i =0;
for(i=0;i
4、读Flash操作
uint8_t *MEM_If_Read_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
/* Return a valid address to avoid HardFault */
/* USER CODE BEGIN 4 */
uint32_t i = 0;
uint8_t *psrc = src;
for(i=0;i
5、获取Flash状态
//这个只是一个擦除或编程时间的设定
#define FLASH_ERASE_TIME (uint16_t)50
#define FLASH_PROGRAM_TIME (uint16_t)50
uint16_t MEM_If_GetStatus_FS(uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
/* USER CODE BEGIN 5 */
switch (Cmd)
{
case DFU_MEDIA_PROGRAM:
buffer[1] = (uint8_t)FLASH_PROGRAM_TIME;
buffer[2] = (uint8_t)(FLASH_PROGRAM_TIME << 8);
buffer[3] = 0;
break;
case DFU_MEDIA_ERASE:
default:
buffer[1] = (uint8_t)FLASH_ERASE_TIME;
buffer[2] = (uint8_t)(FLASH_ERASE_TIME << 8);
buffer[3] = 0;
break;
}
return (USBD_OK);
/* USER CODE END 5 */
}
usbd_dfu_if.c文件编写完成,之后就是main函数中的操作
首先定义APP程序入口指针和存放入口地址的变量
typedef void (*pFunction)(void);
pFunction JumpToApplication;
uint32_t JumpAddress;
读取PA2引脚电平决定是否进入APP以及判断APP程序入口地址是否存在或正确
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2) != GPIO_PIN_SET)
{
if(((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000) == 0x20000000)
{
JumpAddress = *(__IO uint32_t*)(USBD_DFU_APP_DEFAULT_ADD +4);
JumpToApplication = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD);
JumpToApplication();
}
}
MX_USB_DEVICE_Init();
至此,BootLoader程序就编写好了,接下来是制作APP程序
用keil编写一个闪灯的APP程序,点击魔术棒设置程序起始地址
另外,在APP程序的main函数中还要进行中断向量表的重映射
#define APPLICATION_ADDRESS (uint32_t)0x08007000
#if (defined ( __CC_ARM ))
__IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
#elif (defined (__ICCARM__))
#pragma location = 0x20000000
__no_init __IO uint32_t VectorTable[48];
#elif defined ( __GNUC__ )
__IO uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));
#endif
int main(void)
{
/* USER CODE BEGIN Init */
for(i=0;i<48;i++)
{
VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
}
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_SYSCFG_REMAPMEMORY_SRAM();
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
}
编译生成app.hex文件,接下来需要将hex文件转换为DFU文件,去ST官网上搜索DFuse,下载stsw-stm32080这个代号的压缩包,里面是DFuse工具。需要注意,安装完这个DFuse工具后打开软件并不能检测到DFU设备,因为还要安装一个驱动。这个驱动在DFUse安装路径下,例如我的是:C:\Program Files (x86)\STMicroelectronics\Software\DfuSe v3.0.6\Bin\Driver\Win7\x64,找到对应的路径根据自己电脑的版本安装dpinst_amd64.exe。
DFUse软件自带有将hex文件转换成DFU文件的工具 DFU File Manager,在电脑开始菜单中搜索dfu即可找到这个软件,运行选择“I Want to GENERATE a DFU file from s19,HEX or BIN files ”,进入以下界面,这里的Target ID有不同的值,其中0代表片内Flash;1代表外部Flash;2代表外部Nor Flash。因此我们这里选择0,点击S19 or Hex选择hex文件即可生成DFU文件。
之后打开DFuse软件,可以看到软件检测到了我们的USB DFU设备,Choose刚刚生成的DFU文件,点击Upgrade进行升级。
将PA2接低电平,reset单片机,闪灯程序正常执行,证明跳转APP成功,DFU升级完成。