【stm32】【DFU】
学习DFU少不了dfu-util工具:dfu-util 是一个开源工具,用于通过 USB 接口对支持 DFU 协议的设备进行固件升级或下载操作。
Ubuntu上可以自己编译获得或者apt-get。window要自己找资源。
DFU实用工具dfu-util-0.9-win64在Windows平台上的应用-CSDN博客
ST官方提供DFU方案集成在内部系统存储器(ISP),唯独F103没有(写bootloader实现占用一部分flash)(IAP),子系列107还是有的。
STM32高级开发(17)-使用DFU方案_stm32 dfu-CSDN博客
自制17键数字机械键盘(3)——STM32F103 DFU功能实现 - 知乎

关键词:
USBd (USB Device)if (interface)DFU(Device firmware update)OB(option bytes)
fops(file operations)MSP(main stack point)
操作集模式
basic parameter(默认即可)
USBD_MAX_NUM_INTERFACES支持的最大接口数量 1
USBD_MAX_NUM_CONFIGURATION支持的最大配置数量 1
USBD_MAX_STR_DESC_SIZ字符串描述符的最大大小 512kb
USBD_SUPPORT_USER_STRING_DESC
usbd_dfu_if.c文件
USBD_DFU_fops_FS 结构体(full speed)
(uint8_t*)FLASH_DESC_STR,
MEM_If_Init_FS,
MEM_If_DeInit_FS,
MEM_If_Erase_FS,
MEM_If_Write_FS,
MEM_If_Read_FS,
MEM_If_GetStatus_FS
#define FLASH_ERASE_TIME (uint16_t)50
#define FLASH_PROGRAM_TIME (uint16_t)50
uint16_t MEM_If_Init_FS(void)
{/* USER CODE BEGIN 0 */HAL_FLASH_Unlock();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 */
}/*** @brief Erase sector.* @param Add: Address of sector to be erased.* @retval 0 if operation is successful, MAL_FAIL else.*/
uint16_t MEM_If_Erase_FS(uint32_t Add)
{/* USER CODE BEGIN 2 */uint32_t PageError;/* Variable contains Flash operation status */HAL_StatusTypeDef status;FLASH_EraseInitTypeDef eraseinitstruct;eraseinitstruct.TypeErase = FLASH_TYPEERASE_PAGES;eraseinitstruct.PageAddress = Add;eraseinitstruct.NbPages = 1U;status = HAL_FLASHEx_Erase(&eraseinitstruct, &PageError);if (status != HAL_OK){return (USBD_FAIL);}return (USBD_OK);/* USER CODE END 2 */
}
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 < Len; i += 4){/* Device voltage range supposed to be [2.7V to 3.6V], the operation will* be done by byte */if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t) (dest + i),*(uint32_t *) (src + i)) == HAL_OK){/* Check the written value */if (*(uint32_t *) (src + i) != *(uint32_t *) (dest + i)){/* Flash content doesn't match SRAM content */return (USBD_FAIL);}}else{/* Error occurred while writing data in Flash memory */return (USBD_FAIL);}}return (USBD_OK);/* USER CODE END 3 */
}
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 < Len; i++){dest[i] = *psrc++;}/* Return a valid address to avoid HardFault */return (uint8_t*)(USBD_OK);/* USER CODE END 4 */
}
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 */
}
语法解析
__ALIGN_BEGIN 和 __ALIGN_END
// 确保结构体在内存中 4 字节对齐
__ALIGN_BEGIN // 开始对齐
USBD_DFU_MediaTypeDef USBD_DFU_fops_FS // 结构体变量
__ALIGN_END // 结束对齐// 等价于:
USBD_DFU_MediaTypeDef USBD_DFU_fops_FS __attribute__((aligned(4)));
这个结构体中是要完善的函数,CubeMX生成的
程序更新失败软件重启
NVIC_SystemReset()函数软件复位失败的解决方法_nvicsystemreset没有复位-CSDN博客
__set_FAULTMASK(1); 和 __disable_irq(); 有什么不同_set faultmask(1)进去硬件错误-CSDN博客
__set_FAULTMASK(1); // 关闭所有中断
NVIC_SystemReset(); // 执行系统复位
main.c文件
思路就是MX_USB_DEVICE_Init()会开启USB功能,其中就有我们的DFU,这时候就会等待接上USB并更新代码,更新代码是程序在处理所有USB功能中断中进行,OTG_FS_IQRHandler中,因此我们在while(1){Hal_Delay(100);};。
但是我们不更新代码就要跳转程序,就写一些判断条件(按键按下),要是没按下就不进入while阻塞反而jumptoapp,跳转失败就打印失败并进入软件重启。
jumpto函数
jump_to_app函数,作用是一个程序跳转到另一个程序(app),更新固件程序跳转到app都是这样写,大差不差。noreturn属性自己去查。原理就是把msp主堆栈指针指向app的FLASH开头,程序的第一步是执行reset_handler的(可以看启动文件得知)。
因此,FLASH里应该分配bootloader(包含DFU)和app程序,首先执行bootloader决定是否执行程序更新和跳转。跳转后ram由app自动清理。跳转前建议关闭中断以免跳转后数据未清除前进入中断,敏感数据可以同时清理一下。
下面是两种跳转,实际功能一样。一个检查了程序首几位地址(stm32程序固定的)。
typedef void (*fctptr_t)(void);static __attribute__((noreturn)) void jumpTo(uint32_t addr)
{__disable_irq();__set_MSP(*(uint32_t*)addr);fctptr_t reset_handler = (fctptr_t)*(uint32_t*)(addr + 4);reset_handler();while(1){}
}typedef void (*pFunction)(void);
void jump_to_application(void)
{__disable_irq();pFunction JumpToApplication;uint32_t JumpAddress;if (((*(__IO uint32_t *) USBD_DFU_APP_DEFAULT_ADD) & 0x2FFFB000) == 0x20000000){/* Jump to user application */JumpAddress = *(__IO uint32_t *) (USBD_DFU_APP_DEFAULT_ADD + 4);JumpToApplication = (pFunction) JumpAddress;/* Initialize user application's Stack Pointer */__set_MSP(*(__IO uint32_t *) USBD_DFU_APP_DEFAULT_ADD);JumpToApplication();}
}

#define APP_START_ADDRESS (uint32_t)(FIRMWARE_ADDRESS + BOOTLOADER_SIZE)
#define BOOTLOADER_SIZE 0x8000//大小自己定,这里32kb = 8*4kb
#define FIRMWARE_ADDRESS 0x08000000
这里noreturn自己查吧。
main函数
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_I2C1_Init();MX_USB_DEVICE_Init(); // DFUif (should_enter_dfu_mode()) {while (1) {printf("Entering DFU Mode - Waiting for USB connection...\n");HAL_Delay(200);}} else {if (!jumptoapp()) {printf("app-start falled..waiting for reboot\n");HAL_Delay(5000);__set_FAULTMASK(1); // 关闭所有中断NVIC_SystemReset(); // 执行系统复位}}
}