Files

278 lines
8.6 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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~3SINGLE 恒 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);
}