# stm32f4_dfu_iap **Repository Path**: qianqi_gitee/stm32f4_dfu_iap ## Basic Information - **Project Name**: stm32f4_dfu_iap - **Description**: 基于 STM32F4 的 DFU 升级设计与实现 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 1 - **Created**: 2025-06-26 - **Last Updated**: 2025-08-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于 STM32F4 的 DFU 升级设计与实现 ## 引言 随着嵌入式设备的广泛应用,固件升级功能变得尤为重要。本文介绍一种基于 **STM32F4** 系列微控制器的 **DFU(Device Firmware Upgrade)** 升级方案,利用 **Bootloader + 用户App** 双程序架构,结合 **Flash 操作** 和 **USB 通信**,实现安全、高效的固件更新机制。 本系统通过 **按键控制** 来决定是否进入 DFU 升级模式,提供直观的人机交互体验,并确保在意外情况下能够灵活切换运行模式。 --- ## 一、整体架构设计 整个系统采用 **双程序结构**: - **Bootloader**:位于 Flash 起始地址,负责判断是否进入 DFU 升级模式或跳转至用户应用程序。 - **User Application (App)**:主程序,执行正常业务逻辑。 - **DFU Mode**:当检测到特定按键输入时,保持在 Bootloader 阶段,等待 USB 连接并接收新固件进行升级。 ### 主要模块: 1. **GPIO 检测模块**:用于检测按键状态。 2. **Flash 操作模块**:包括擦除和写入 Flash 扇区。 3. **USB DFU 接口模块**:通过 USB 接口接收固件数据。 4. **任务调度机制**:使用协程(Protothread)实现非阻塞倒计时逻辑。 --- ## 二、核心代码分析 ### 2.1 启动选择逻辑(`printf_task` 函数) ```c int printf_task(struct pt *pt) { static uint32_t sec = 0; static uint8_t key_pressed_once = 0; // 是否已按下过按键 PT_BEGIN(pt); if (key_pressed_once) goto wait_key_release; sec = HAL_GetTick(); usart_send_str("倒计时3秒。。。", &cmd); while (HAL_GetTick() < sec + 1000) { if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) == GPIO_PIN_RESET) { usart_send_str("按键按下,取消倒计时。\r\n", &cmd); key_pressed_once = 1; goto wait_key_release; } PT_YIELD(pt); } // 类似地处理倒计时2秒、1秒... jump_to_app(); // 倒计时结束,跳转至用户App wait_key_release: // 按键释放后保持在此模式 while (1) { if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) == GPIO_PIN_RESET) { PT_WAIT_UNTIL(pt, HAL_GetTick() > sec + 1000); sec = HAL_GetTick(); usart_send_str("等待按键释放...\r\n", &cmd); } else { PT_WAIT_UNTIL(pt, HAL_GetTick() > sec + 1000); sec = HAL_GetTick(); usart_send_str("按键已释放,保持在当前模式。\r\n", &cmd); break; } } PT_END(pt); } ``` #### 功能说明: - 使用 **Protothread(协程)** 实现非阻塞倒计时。 - 利用 **PT_YIELD** 让出 CPU 时间,避免阻塞主线程。 - 若检测到 PA9 按键按下,则标记 `key_pressed_once` 并跳过后续倒计时,直接进入 DFU 模式。 - 若未按下按键,则倒计时结束后调用 `jump_to_app()` 跳转至用户 App。 --- ### 2.2 Flash 操作函数 #### 2.2.1 扇区擦除函数 `flash_erase_sector` ```c HAL_StatusTypeDef flash_erase_sector(uint32_t sector) { FLASH_EraseInitTypeDef eraseInitStruct; uint32_t PageError = 0; HAL_FLASH_Unlock(); eraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS; eraseInitStruct.Sector = sector; eraseInitStruct.NbSectors = 1; eraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3; #if defined(FLASH_BANK_1) eraseInitStruct.Banks = FLASH_BANK_1; #else eraseInitStruct.Banks = 0; #endif HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&eraseInitStruct, &PageError); HAL_FLASH_Lock(); return status; } ``` #### 功能说明: - 使用 STM32 HAL 库提供的 API 擦除指定扇区。 - 支持单 Bank 和双 Bank 架构芯片(如 STM32F4/F7/H7)。 - 必须先解锁 Flash 控制器才能操作。 - 擦除前需确认目标扇区不在当前运行代码区域内。 --- #### 2.2.2 Flash 写入函数 `flash_write_only` ```c void flash_write_only(uint8_t *add, const uint8_t *data, uint32_t len) { HAL_FLASH_Unlock(); for (uint32_t i = 0; i < len / 4; i++) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)add + i * 4, *(uint32_t *)(data + i * 4)); } HAL_FLASH_Lock(); } ``` #### 功能说明: - 以 32 位字为单位写入 Flash。 - 目标地址必须是 4 字节对齐。 - 写入前应确保该扇区已被擦除。 - 操作完成后重新锁定 Flash。 --- ### 2.3 USB DFU 接口函数 #### 2.3.1 擦除 Flash 扇区接口 `MEM_If_Erase_FS` ```c uint16_t MEM_If_Erase_FS(uint32_t Add) { UNUSED(Add); if(flash_erase_sector(5) == 0) return USBD_OK; else return USBD_FAIL; } ``` #### 功能说明: - 在 DFU 升级开始前擦除目标 Flash 扇区(此处为扇区 5)。 - 将底层 Flash 操作封装为 USB DFU 标准接口函数。 --- #### 2.3.2 数据写入接口 `MEM_If_Write_FS` ```c uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len) { UNUSED(src); UNUSED(dest); UNUSED(Len); flash_write_only(dest, src, Len); return USBD_OK; } ``` #### 功能说明: - 将 USB 接收的数据写入指定 Flash 地址。 - 调用 `flash_write_only` 完成实际写入操作。 - 符合 USB DFU 接口规范,便于集成进标准协议栈。 --- ## 三、关键技术点总结 | 技术点 | 描述 | |--------|------| | **Bootloader 设计** | 实现启动阶段判断是否进入 DFU 模式,支持按键控制跳转逻辑。 | | **Protothread 协程** | 非阻塞倒计时机制,提升系统响应性,避免阻塞主线程。 | | **Flash 操作** | 包括扇区擦除和数据写入,需注意地址对齐、解锁/上锁流程。 | | **USB DFU 协议** | 通过标准接口函数对接 USB DFU Class,实现固件传输与烧录。 | | **按键检测与状态管理** | 利用 GPIO 输入检测按键状态,配合标志位实现模式切换。 | | **双 Bank 支持** | 兼容不同型号 STM32F4 芯片的 Flash 架构差异(单 Bank / 双 Bank)。 | --- ## 四、注意事项与优化建议 ### 注意事项: 1. **Flash 操作不可逆**:务必在操作前确认目标地址不包含当前运行代码。 2. **电压范围设置正确**:影响最小擦除单位,错误配置可能导致失败。 3. **地址对齐要求**:Flash 写入必须为 4 字节对齐,否则会触发异常。 4. **按键去抖动处理**:建议加入软件延时或硬件滤波,防止误判。 ### 优化建议: 1. **添加 CRC 校验**:在升级过程中增加数据完整性校验,提高可靠性。 2. **支持多扇区擦写**:扩展接口支持多个连续扇区操作,提高效率。 3. **OTA 支持**:可将 DFU 升级机制扩展为无线 OTA 更新。 4. **日志记录**:升级过程输出详细日志,便于调试与问题追踪。 --- ## 五、结语 本文基于 STM32F4 平台,构建了一个完整的 DFU 升级系统。通过合理的 Bootloader 设计、Flash 操作封装和 USB DFU 接口集成,实现了可靠的固件更新机制。同时,结合 Protothread 实现了优雅的倒计时与按键交互逻辑,提升了用户体验。该方案适用于各类需要远程升级的嵌入式产品,具有良好的扩展性和稳定性。 ## 功能验证 #### 正常app跳转: ![正常跳转](image.png) #### 启动时按键维持boot模式: ![输入图片说明](image2.png) #### 设备管理器下的dfu设备: ![输入图片说明](image3.png) #### 选择并连接usb设备: ![输入图片说明](image4.png) #### 链接成功后的输出信息: ![输入图片说明](image5.png) #### 选择bin文件并填写地址: ![输入图片说明](image6.png) #### 成功提示信息: ![输入图片说明](image7.png) #### 下载信息: ![输入图片说明](image8.png) #### 串口打印: ![输入图片说明](image9.png) ## 至此完成了一个app的更新,且耗时非常短1.2秒,比常规串口方式要高效很多。或许可以用一个比较大的固件来尝试说明。 #### 例如下面这个固件,125kb ![输入图片说明](image10.png) #### 125kb的文件更新耗时2秒,stm32一般的flash大小512k比较常见,相当于10秒左右可以完成: ![输入图片说明](image11.png) ## boot程序在优化后只占用约20kbflash,使用usb外设不需要ch340类芯片,简单快速,节约硬件,省时高效,boot阶段的倒计时方式让app即使会崩溃也有升级的机会,拯救变砖的情形。或许这才是iap升级的最优解。 ## 相信一定对你有所帮助。