278 lines
8.6 KiB
C
278 lines
8.6 KiB
C
#include "flash.h"
|
||
#include <stddef.h> /* 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);
|
||
}
|