#include "flash.h" #include /* offsetof */ /** * @brief Erase Flash * @param None * @retval None */ static void APP_FlashErase(uint32_t PageAddress, uint32_t DataSize) { uint32_t PAGEError = 0; FLASH_EraseInitTypeDef EraseInitStruct = {0}; EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGEERASE; /* Erase type: FLASH_TYPEERASE_PAGEERASE = Page erase, FLASH_TYPEERASE_SECTORERASE = Sector erase */ EraseInitStruct.PageAddress = PageAddress; /* Starting address for erase */ EraseInitStruct.NbPages = (DataSize + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE; /* Number of pages to be erased */ if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) /* Perform page erase, PAGEError returns the page with erase error, returns 0xFFFFFFFF for successful erase */ { Error_Handler(__FILE__, __LINE__); } } /** * @brief Check if Flash is blank * @param None * @retval None */ static void APP_FlashBlank(uint32_t PageAddress, uint32_t DataSize) { uint32_t addr = 0; while (addr < DataSize) { if (*(uint32_t *)(PageAddress + addr) != 0xFFFFFFFF) // 确保 4 字节对齐访问 { Error_Handler(__FILE__, __LINE__); } addr += 4; // 每次检查 4 字节 } } /** * @brief Program Flash * @param None * @retval None */ static void APP_FlashProgram(uint32_t PageAddress, uint8_t *Data, uint32_t DataSize) { uint32_t flash_program_start = PageAddress; /* Start address for flash program */ uint32_t flash_program_end = (PageAddress + DataSize); /* End address for flash program */ uint32_t *src = (uint32_t *)Data; /* Pointer to the array */ while (flash_program_start < flash_program_end) { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_PAGE, flash_program_start, src) == HAL_OK) // 按 4 字节写入 { flash_program_start += FLASH_PAGE_SIZE; /* Set flash start pointer to the first page */ src += FLASH_PAGE_SIZE / 4; /* Update the data pointer */ } } } /** * @brief Verify Flash * @param None * @retval None */ static void APP_FlashVerify(uint32_t PageAddress, uint8_t *Data, uint32_t DataSize) { uint32_t addr = 0; while (addr < DataSize) { if (Data[addr] != HW8_REG(PageAddress + addr)) { Error_Handler(__FILE__, __LINE__); } addr += 1; } } /** * @brief 往 FLASH 写入数据 * @param PageAddress 目标 Flash 地址 * @param Data 待写入的数据指针 * @param DataSize 数据大小(单位: 字节) */ void APP_FlashWrite(uint32_t PageAddress, uint8_t *Data, uint32_t DataSize) { /* 解锁 FLASH */ HAL_FLASH_Unlock(); /* 擦除 FLASH */ APP_FlashErase(PageAddress, DataSize); /* 检查 FLASH 是否已擦除 */ APP_FlashBlank(PageAddress, DataSize); /* 写入 FLASH */ APP_FlashProgram(PageAddress, Data, DataSize); /* 重新上锁 FLASH */ HAL_FLASH_Lock(); /* 校验 FLASH 写入是否正确 */ APP_FlashVerify(PageAddress, Data, DataSize); } /** * @brief 从 FLASH 读取数据 * @param PageAddress 源 Flash 地址 * @param Data 目标缓冲区 * @param DataSize 读取大小(单位: 字节) */ void APP_FlashRead(uint32_t PageAddress, uint8_t *Data, uint32_t DataSize) { uint32_t addr = 0; uint8_t *dst = Data; while (addr < DataSize) { /* 逐字节读取 FLASH 数据 */ *dst = *(uint8_t *)(PageAddress + addr); addr++; dst++; } } /** * @brief 擦除 FLASH 并检查是否成功 * @param PageAddress 目标 Flash 地址 * @param DataSize 数据大小(单位: 字节) */ void APP_FlashEraseWithCheck(uint32_t PageAddress, uint32_t DataSize) { /* 解锁 FLASH */ HAL_FLASH_Unlock(); /* 擦除 FLASH */ APP_FlashErase(PageAddress, DataSize); /* 检查 FLASH 是否已擦除 */ APP_FlashBlank(PageAddress, DataSize); /* 重新上锁 FLASH */ HAL_FLASH_Lock(); } /* ════════════════════════════════════════════════════════════════ * OTA 状态区 — 写入策略由备份模式决定(与 ota_config.h 的 #if 对应) * * · 双备份(AB):swap 交换每页要写 2 次状态区,一次升级约 440 次 * 写入。若固定单页整页擦写,10K 次寿命只能撑约 23 次升级 → 用 * 4 页轮换磨损均衡:每页存一个完整状态副本,seq 单调递增(在 * 结构体内、参与 CRC16 保护),读时取"seq 最大且校验通过"副本。 * · 单备份(SINGLE):一次升级只写 2~3 次状态区(无逐页交换过程), * 10K 寿命可撑数千次升级,无需磨损均衡 → 固定页 0 直写(结构体 * 无 seq 字段)。 * ════════════════════════════════════════════════════════════════ */ #define OTA_PARAM_PAGE_NB (OTA_PARAM_MAX_SIZE / OTA_PAGE_SIZE) /* 4 页 */ static uint32_t s_param_page_idx; /* 下一个要写的页(0~3,SINGLE 恒 0) */ #if (OTA_BACKUP_MODE == OTA_MODE_AB) static uint32_t s_param_seq; /* 写入序号(仅 AB 轮换使用) */ #endif /* CRC-16/CCITT,保护状态副本完整性(半写/掉电检测)。 * 覆盖范围 = crc16 字段之前的全部字节(由 offsetof 决定,与结构体尾部对齐 * 填充无关)。注意:绝不能把 crc16 字段本身算进去——否则写入时(字段=0)算出的 * 值与读出时(字段=已存值)算出的不同,导致每页自校验失败、load 永远返回 IDLE。 * (AB 模式 sizeof=28 含 2 字节对齐填充,OTP_PARAM_SIZE-2=26 会把 crc16 字段 * 卷进去,正是此前的 Bug;单备份 sizeof=20 无填充才没暴露。) */ static uint16_t ota_param_crc16(const ota_param_t *p) { const uint8_t *b = (const uint8_t *)p; uint16_t crc = 0xFFFF; uint16_t i; for (i = 0; i < (uint16_t)offsetof(ota_param_t, crc16); i++) { crc ^= ((uint16_t)b[i] << 8); for (int k = 0; k < 8; k++) { crc = (crc & 0x8000U) ? (uint16_t)((crc << 1) ^ 0x1021U) : (uint16_t)(crc << 1); } } return crc; } /* 读某一页副本:校验通过返回 1(seq 直接从结构体读出) */ static int ota_param_page_read(uint32_t page_idx, ota_param_t *p) { uint8_t buf[OTA_PAGE_SIZE]; uint32_t addr = OTA_PARAM_ADDR_BASE + page_idx * OTA_PAGE_SIZE; APP_FlashRead(addr, buf, OTA_PAGE_SIZE); memcpy(p, buf, OTA_PARAM_SIZE); if ((p->magic != OTA_MAGIC) || (ota_param_crc16(p) != p->crc16) || (p->state > OTA_STATE_BOOT_NEW)) { return 0; } return 1; } void ota_param_load(ota_param_t *p) { #if (OTA_BACKUP_MODE == OTA_MODE_AB) uint32_t best_seq = 0, best_idx = 0; int found = 0; uint32_t i; for (i = 0; i < OTA_PARAM_PAGE_NB; i++) { ota_param_t tmp; if (ota_param_page_read(i, &tmp)) { if (!found || (tmp.seq > best_seq)) { found = 1; best_seq = tmp.seq; best_idx = i; *p = tmp; } } } if (found) { s_param_page_idx = (best_idx + 1) % OTA_PARAM_PAGE_NB; s_param_seq = best_seq; return; } #else /* 单备份:固定页 0 */ if (ota_param_page_read(0, p)) { return; } #endif /* 无有效状态(首次上电/整区被擦)→ 默认 IDLE */ memset(p, 0, sizeof(*p)); p->magic = OTA_MAGIC; p->state = OTA_STATE_IDLE; s_param_page_idx = 0; #if (OTA_BACKUP_MODE == OTA_MODE_AB) s_param_seq = 0; #endif } void ota_param_store(const ota_param_t *p) { uint8_t buf[OTA_PAGE_SIZE]; ota_param_t tmp; uint32_t addr; #if (OTA_BACKUP_MODE == OTA_MODE_AB) s_param_seq++; s_param_page_idx = (s_param_page_idx + 1) % OTA_PARAM_PAGE_NB; #else s_param_page_idx = 0; /* 单备份:固定页 0,无轮换 */ #endif addr = OTA_PARAM_ADDR_BASE + s_param_page_idx * OTA_PAGE_SIZE; /* 整页缓冲:状态副本 + 0xFF 填充(HAL 页编程要求整页 256B) */ memset(buf, 0xFF, sizeof(buf)); tmp = *p; #if (OTA_BACKUP_MODE == OTA_MODE_AB) tmp.seq = s_param_seq; /* 序号由存储层维护,调用方无需关心 */ #endif tmp.crc16 = ota_param_crc16(&tmp); memcpy(buf, &tmp, OTA_PARAM_SIZE); APP_FlashWrite(addr, buf, OTA_PAGE_SIZE); }