本文主要解决的问题是实现IAP功能,包括升级应用程序(APP)和升级数据包到外部Flash。方法是利用USB DFU Class以及ST提供的DfuSe demo软件工具实现,至于DFU class移植直接利用cube生成。
IAP(In application program)主要为使用者提供了一种更加灵活的固件升级方式,可以根据应用需要定义何时、何种情况发生时进行固件升级。在介绍步骤前,需要对IAP原理有一定认识。结合下图(仅供参考,IAP实际应用方式更加灵活),IAP的应用中,用户程序与IAP驱动程序位于不同的存储区域。在应用过程中,利用IAP驱动程序将用户固件加载到固定位置,完成升级。更多关于IAP应用介绍请参考AN3965。
其中,IAP驱动程序首先烧录固化,并不会随着用户程序的升级而改变。用户固件升级完毕后,在IAP驱动程序引导下,跳转到用户固件对应Flash位置,执行用户程序。
设计一个用户程序,程序正常运行;当用户按键被按下,产生EXTI 中断,在中断中选择后备数据寄存器RTC_BKP0R,写入值0x32F2,然后产生软件复位;软件复位后,进入Bootloader(IAP代码),在主函数对RTC_BKP0R 进行判断,如果其值不是0x32F2 则直接去运行用户代码,如果其值为0x32F2 则是需要跳转初始化DFU类,然后一直循环闪烁LED,并在这之前将RTC_BKP0R 清零。
安装DFU demo工具DfuSe v3.0.5,主要提供了驱动、Dfu file manager 和 DfuSeDemo。关于DfuSe的详细内容(安装步骤、使用介绍)请参考UM0412。
在工程里面选择USB设备,然后选择DFU类。
参数设置如下图:
简单介绍一下上面两个主要参数:
USBD_DFU_APP_DEFAULT_ADD表示APP程序的起始地址为0x08008000,本工程(也称IAP代码或者Bootloader,下面用Bootloader表示本工程)的起始地址为0x0800 0000,长度为0x8000,要在该界面进行设置。Bootloader一般用来实现固件的升级,不用来实现其他功能,所以大小0x8000(32KB)已经足够。
第二个参数对应于于usbd_dfu_flash.c 里边定义的描述符FLASH_DESC_STR,如下:
#define FLASH_DESC_STR "@Internal Flas /0x08008000/02*016Ka,02*016Kg,01*064Kg,07*128Kg"
0x08000000 为起始地址。“a”代表的是Read-only,“g”代表Read/Write/Erase。也就是说,“a”所指明的区域为
Bootloader 的空间,“g”所指明的区别为用户代码空间。大小由前面的数字决定,乘号“*”前面的为Sector 的个数,后面
的为Sector 的大小,这里的意思就是从0x08000000 开始,前面2个Sector(每个Sector 为16字节)为Read-only,后
面剩下的Sector为Read/Write/Erase。下图为F40系列的FLASH模块构成:
生成代码之后工程之后,在MDK设置ROM如下:
在主程序添加如下代码,用于检测是否要升级应用程序(APP)。
/*通过读取后备寄存器的值检测是否要升级应用程序,该值是通过应用程序写入*/
if((BSP_RTCEx_BKUPRead(&RtcHandle,RTC_BKP_DR0)==0x32F2))
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSE_DIV2);
__HAL_RCC_RTC_ENABLE();
BSP_RTCEx_BKUPWrite(&RtcHandle, RTC_BKP_DR0, 0x0);
__HAL_RCC_RTC_DISABLE();
HAL_PWR_DisableBkUpAccess();
__HAL_RCC_PWR_CLK_DISABLE();
/* 初始化USB的DFU模式 */
MX_USB_DEVICE_Init();
/*这里通过闪烁LED在告知用户进入了Bootloader*/
while(1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500);
}
}
/*直接进入APP*/
else
{
/*APP第二个字为程序开始地址(复位地址)*/
JumpAddress = *(__IO uint32_t*) (USBD_DFU_APP_DEFAULT_ADD+ 4);
/* 加载系统APP地址 */
Jump_To_Application = (pFunction) JumpAddress;
/* 初始化MSP,MSP为用户主栈指针 */
__set_MSP(*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD);
/* 跳转到用户代码*/
Jump_To_Application();
}
用户固件对应的首地址被定义在0x08008000。用户也可以自定义地址,需要注意如下几点:
1. 禁止定义在0x08008000地址前(STM32F7系列,0x0~0x0800FFFF属于Sector0,已经开辟为IAP驱动程序区域)
2. 自定义地址所属Sector在升级时会全部擦除,即使定义地址并不位于对应Sector的首地址
3. 自定义地址需要保持512-byte对齐
4. 自定义地址需要与用户固件对应的装载地址以及用户中断向量表地址保持一致
在实现跳转到用户程序的代码中,将用户中断向量表中第一个4字节指向的栈首地址分配给MSP。第二个4字节为复位中断向量,指向执行首地址。
在开发IAP驱动程序时,需要避免PC指针跳到用户程序区域。同时,充分考虑Stack & Heap大小,避免出现USB DFU正常识别,但不能正常工作情况。
配置GPIO中断比较简单,这里就省略。直接打开cube生成的APP程序。利用MDK设置APP的ROM分配如下图:
这里的0x0800 0000要和Bootloader设置的USBD_DFU_APP_DEFAULT_ADD一致,否则无法跳转到APP。0xF8000就是F4系列剩下的ROM。
如下startup_stm32f407xx.s中的代码,可以看出在进入main函数之前,程序先进入SystemInit函数。
SystemInit函数在system_stm32f4xx.c文件中,SystemInit 函数主要执行初始化FPU、复位RCC 时钟寄存器、配置向量表等功能,所以可以再这里修改VECT_TAB_OFFSET为0x8000来设置中断向量表。但是为了避免每次用cube工具生成之后VECT_TAB_OFFSET会重新恢复默认值。我们可以在主函数里面设置。
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset CFGR register */
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
在主函数前设置中断向量表如下:
int main(void)
{
/* USER CODE BEGIN 1 */
/*设置向量表偏移量FLASH_BASE = 0x0800 8000*/
SCB->VTOR = (FLASH_BASE|0X8000);
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
中断的程序流程图如下:
在main.c添加如下按键中断代码:
RTC_HandleTypeDef RtcHandle;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY_Pin)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSE_DIV2);
__HAL_RCC_RTC_ENABLE();
RtcHandle.Instance = RTC;
HAL_RTCEx_BKUPWrite(&RtcHandle, RTC_BKP_DR0, 0x32F2);
if(HAL_RTCEx_BKUPRead(&RtcHandle, RTC_BKP_DR0) != 0x32F2)
{
/*Write backup data memory failed*/
}
else
{
/*Write backup data memory succeeded.*/
HAL_NVIC_SystemReset(); // Software reset for going into bootloader
while (1) ;
}
}
}
当判断到用户按键按下,需要进行用户代码升级时,先启动备份域的访问时序,将RTC_BKP_DR0 的值写为0x32F2。再读
回来判断是否写入成功,以方便调试。如果写入成功后,则就调用HAL_NVIC_SystemReset()进行软件复位。重新复位后,
就可以进入Bootloader 了。
APP编译完成之后会有.hex文件生成,但是升级程序用到的dfu文件,需要利用DfuFileMgr.exe把hex转为dfu文件。打开
DfuFileMgr.exe工具,选择把hex转为dfu,点击OK
按照下图步骤加载应用程序的hex文件,点击Generate完成HEX>DFU文件转换。其它选项并不影响DFU文件生成的内容,可以选择默认。
接下来就是利用USB线把板子和电脑连接起来。
打开DfuSeDemo,结合下图步骤,点击’Choose’加载之前转换的.dfu文件;选择配置;点击’Update’完成擦除与下载;另外,可以通过点击’Verify’验证是否下载成功
点击上面红色矩形框可以看到Internal Flash 的详细信息,这里对应Boodloadr中usbd_dfu_flash.c 里边定义的描述符FLASH_DESC_STR中。
点击Upgrade之后如下:
升级成功之后如下图:
大工完成,断电重新开机即可。
因本人能力有限,本文章有不对的地方还望指出。