添加普冉 PY32F040 OTA 双工程代码生成模板
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
#include "crc32.h"
|
||||
|
||||
/* CRC-32/IEEE 802.3 查表实现(表在首次使用时生成,避免占用 Flash) */
|
||||
static uint32_t crc32_table[256];
|
||||
static uint8_t crc32_table_ready = 0;
|
||||
|
||||
static void crc32_build_table(void)
|
||||
{
|
||||
for (uint32_t i = 0; i < 256; i++)
|
||||
{
|
||||
uint32_t c = i;
|
||||
for (uint32_t k = 0; k < 8; k++)
|
||||
{
|
||||
c = (c & 1U) ? (0xEDB88320UL ^ (c >> 1)) : (c >> 1);
|
||||
}
|
||||
crc32_table[i] = c;
|
||||
}
|
||||
crc32_table_ready = 1;
|
||||
}
|
||||
|
||||
uint32_t crc32_init(void)
|
||||
{
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
uint32_t crc32_update(uint32_t crc, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
if (!crc32_table_ready)
|
||||
{
|
||||
crc32_build_table();
|
||||
}
|
||||
while (len--)
|
||||
{
|
||||
crc = crc32_table[(crc ^ *data++) & 0xFFU] ^ (crc >> 8);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint32_t crc32_final(uint32_t crc)
|
||||
{
|
||||
return crc ^ 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
uint32_t crc32_compute(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
return crc32_final(crc32_update(crc32_init(), data, len));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef __CRC32_H
|
||||
#define __CRC32_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* CRC-32/IEEE 802.3: poly 0x04C11DB7, init 0xFFFFFFFF, final XOR 0xFFFFFFFF
|
||||
* APP 与 Bootloader 共用同一实现,保证固件校验一致 */
|
||||
|
||||
uint32_t crc32_init(void);
|
||||
uint32_t crc32_update(uint32_t crc, const uint8_t *data, uint32_t len);
|
||||
uint32_t crc32_final(uint32_t crc);
|
||||
uint32_t crc32_compute(const uint8_t *data, uint32_t len);
|
||||
|
||||
#endif /* __CRC32_H */
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef __IWDG_CONFIG_H
|
||||
#define __IWDG_CONFIG_H
|
||||
|
||||
/**
|
||||
* @file iwdg_config.h
|
||||
* @brief IWDG 超时参数(APP / Bootloader 共用,换芯片时与 HAL 预分频一并调整)
|
||||
*
|
||||
* 当前:LSI ≈ 40 kHz,预分频 /16 → 计数 2.5 kHz,Reload = 2500 → 超时约 1 s。
|
||||
*
|
||||
* APP 侧使用 HAL:Prescaler = IWDG_PRESCALER_16,Reload = IWDG_RELOAD_VALUE。
|
||||
* Bootloader 跳转前使能 IWDG;APP 启动后须尽早 HAL_IWDG_Init / Refresh。
|
||||
*
|
||||
* 约束:IWDG 一旦启动,LSI 被硬件锁定,APP 时钟配置中不可写 LSI OFF。
|
||||
*/
|
||||
|
||||
#define IWDG_RELOAD_VALUE 2500U
|
||||
|
||||
/* Bootloader 裸寄存器写 PR 时的值(= IWDG_PRESCALER_16 对应位域) */
|
||||
#define IWDG_PR_REG 0x02U
|
||||
|
||||
#endif /* __IWDG_CONFIG_H */
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
#ifndef LOG_H_
|
||||
#define LOG_H_
|
||||
|
||||
#define LOG_CRITICAL 0X00
|
||||
#define LOG_ERROR 0x01
|
||||
#define LOG_WARNING 0X02
|
||||
#define LOG_NOTIC 0X03
|
||||
#define LOG_DEBUG 0x04
|
||||
#define LOG_DUMP 0x05
|
||||
|
||||
/* GAgent 日志等级 通过该宏设置*/
|
||||
#define LOG_LOGLEVEL LOG_NOTIC//LOG_NOTIC
|
||||
#define LOG_TIMERLEVEL LOG_NOTIC//LOG_NOTIC
|
||||
#define LOG_MEMLEVEL LOG_DEBUG//linux版本不打印剩余内存
|
||||
#define LOG_TRACELEVEL LOG_NOTIC
|
||||
|
||||
#ifdef ELK_ENABLE
|
||||
void rlog(uint8 level, const uint8 *message,...);
|
||||
#endif
|
||||
|
||||
/*这个函数是纯粹的打印,不受等级影响*/
|
||||
#define Log(format, args...) printf( format, ##args ) /*这个地方替换成平台相关的打印函数*/
|
||||
|
||||
#define logAssert() do{Log("[func:%s][line:%d]param is illegal.\r\n", __FUNCTION__, __LINE__);}while(0)
|
||||
|
||||
/*key 日志打印接口*/
|
||||
#define keyPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[key] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*LED 日志打印接口*/
|
||||
#define ledPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[led] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*adc 日志打印接口*/
|
||||
#define adcPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[adc] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*bat 日志打印接口*/
|
||||
#define batPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[bat] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*pressure 日志打印接口*/
|
||||
#define pressurePrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[pressure] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*motor 日志打印接口*/
|
||||
#define motorPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[pump] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*app 日志打印接口*/
|
||||
#define appPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "[app] "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
/*ngf 日志打印接口*/
|
||||
#define ngfPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "NGF "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
//IWDG 日志打印接口
|
||||
#define iwdgPrintf(level,format, args...)\
|
||||
{\
|
||||
if(LOG_LOGLEVEL>=level) \
|
||||
{\
|
||||
Log( "IWDG "format, ##args);\
|
||||
}\
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,130 @@
|
||||
#ifndef __OTA_CONFIG_H
|
||||
#define __OTA_CONFIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define OTA_PAGE_SIZE 256UL /* Flash 页大小(硬件固定 256B,任何模式通用)*/
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* OTA 模式配置 — 客户唯一配置入口
|
||||
*
|
||||
* 本文件被 APP(Core/ota_ab.c)与 Bootloader(boot_main.c)共用,
|
||||
* 修改后需重新编译两个工程。
|
||||
*
|
||||
* 只需改 ① OTA_BACKUP_MODE(备份模式)。其余配置均由 #if 条件
|
||||
* 编译保护,自动只编译生效模式需要的部分:
|
||||
* ① OTA_BACKUP_MODE = SINGLE / AB 备份模式(唯一必选)
|
||||
* ② OTA_SWAP_STRATEGY = SCRATCH / RAM 仅 AB 模式生效
|
||||
* ③ OTA_SCRATCH_* 仅 AB+SCRATCH 生效
|
||||
* 结构体 ota_param_t 的 seq/progress/phase 仅 AB 模式存在,
|
||||
* OTA_STATE_SWAPPING 仅 AB 模式存在(单备份下不编译)。
|
||||
*
|
||||
* ┌─ 三种模式优缺点速查 ─────────────────────────────────────────┐
|
||||
* │ SINGLE(单备份) │
|
||||
* │ 优:最简单;最省 Flash(主区+BAK 各 58K,无需额外区) │
|
||||
* │ 缺:升级失败/变砖只能整片重烧,无自动回滚 │
|
||||
* │ AB + RAM(双备份 + RAM 缓冲交换) │
|
||||
* │ 优:新固件崩溃可自动回滚旧固件;不预留 scratch; │
|
||||
* │ 无 scratch 擦写磨损;占 Flash = A/B 各 58K │
|
||||
* │ 缺:升级耗时较长(~7~12s,逐页 RAM 缓冲交换); │
|
||||
* │ 固件需 2× Flash(A、B 两份) │
|
||||
* │ AB + SCRATCH(双备份 + Flash 暂存区交换) │
|
||||
* │ 优:升级最快(~1~3s);掉电恢复最稳 │
|
||||
* │ 缺:需预留 scratch 区(暂存区,仅 1 页); │
|
||||
* │ scratch 固定页反复擦写有磨损(固件越大页数越多、 │
|
||||
* │ 磨损越快);固件需 2× Flash(A、B 两份) │
|
||||
* │ 注:双备份两策略都需 2× 固件 Flash,scratch 仅多 1 页缓冲; │
|
||||
* │ 128K 芯片上固件 > ~57K 时 2× 放不下,需外部 SPI Flash。 │
|
||||
* └────────────────────────────────────────────────────────────┘
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ① 备份模式 ----------------------------------------------------- */
|
||||
#define OTA_MODE_SINGLE 0 /* 单备份:BAK 下载→校验→拷贝覆盖主区(同 AppVersion)*/
|
||||
#define OTA_MODE_AB 1 /* 双备份:A/B 区交换(新固件崩溃可回滚旧固件)*/
|
||||
#define OTA_BACKUP_MODE OTA_MODE_SINGLE /* 默认单备份:最省 Flash、最简单、无磨损 */
|
||||
|
||||
|
||||
/* ═══════ 以下 ②③ 配置仅 OTA_BACKUP_MODE == OTA_MODE_AB 时生效 ═══════ */
|
||||
#if (OTA_BACKUP_MODE == OTA_MODE_AB)
|
||||
|
||||
/* ② 双备份交换策略 ----------------------------------------------- */
|
||||
#define OTA_SWAP_SCRATCH 0 /* Flash 暂存区交换:升级快、掉电恢复最稳(需预留暂存区)*/
|
||||
#define OTA_SWAP_RAM 1 /* RAM 缓冲交换:不预留暂存区,A/B 保持 55K */
|
||||
#define OTA_SWAP_STRATEGY OTA_SWAP_SCRATCH
|
||||
|
||||
/* ③ 暂存区参数(仅 OTA_SWAP_STRATEGY == OTA_SWAP_SCRATCH 生效) --- */
|
||||
#if (OTA_SWAP_STRATEGY == OTA_SWAP_SCRATCH)
|
||||
#define OTA_SCRATCH_ADDR 0x0801F800UL /* 暂存区起始地址:放在 Flash 最底部(必须页对齐)*/
|
||||
#define OTA_SCRATCH_SIZE OTA_PAGE_SIZE /* 暂存区只需 1 页:交换时每次仅暂存 1 页(256B),
|
||||
故大小=1 页即可;Bootloader 每次也只擦这 1 页,
|
||||
避免原 1KB 整块擦除的 4 倍磨损浪费 */
|
||||
#endif /* OTA_SWAP_STRATEGY == SCRATCH */
|
||||
|
||||
#endif /* OTA_BACKUP_MODE == AB */
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 布局宏自动联动(以下由上面配置推导,勿手工修改)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define OTA_PARAM_ADDR_BASE 0x08002C00UL /* OTA 状态区,1KB(固定):Bootloader 11K 后 */
|
||||
#define OTA_PARAM_MAX_SIZE (1UL * 1024)
|
||||
|
||||
#if (OTA_BACKUP_MODE == OTA_MODE_AB) && (OTA_SWAP_STRATEGY == OTA_SWAP_SCRATCH)
|
||||
/* 双备份 + 暂存区:A/B 各 57KB(128K-12K固定-256B暂存区 ≈ 57K/份),
|
||||
B 区收尾于 0x0801F800,scratch(1页)紧接其后放在 Flash 最底部 0x0801F800 */
|
||||
#define OTA_RUN_ADDR_BASE 0x08003000UL /* A 区 = 运行区(固件链接地址)*/
|
||||
#define OTA_BAK_ADDR_BASE 0x08011400UL /* B 区 = 备份区 */
|
||||
#define OTA_SLOT_MAX_SIZE (57UL * 1024)
|
||||
#else
|
||||
/* 单备份(主区+BAK) 或 双备份+RAM(A/B 各 58K):布局相同,A+B 正好填满 128K */
|
||||
#define OTA_RUN_ADDR_BASE 0x08003000UL /* 主区/A 区 = 运行区 */
|
||||
#define OTA_BAK_ADDR_BASE 0x08011800UL /* BAK/B 区 */
|
||||
#define OTA_SLOT_MAX_SIZE (58UL * 1024)
|
||||
#endif
|
||||
|
||||
#define OTA_SLOT_PAGE_NB (OTA_SLOT_MAX_SIZE / OTA_PAGE_SIZE)
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* OTA 状态机(APP 与 Bootloader 共用)
|
||||
*
|
||||
* 单备份:IDLE → PENDING →(Bootloader 拷贝主区)→ 运行,无回滚
|
||||
* 双备份:IDLE → PENDING →(Bootloader 交换)→ BOOT_NEW →(APP 确认)→ IDLE
|
||||
* SWAPPING = 交换掉电中断,下次启动续做
|
||||
* 新固件崩溃(IWDG复位):BOOT_NEW 未确认 → 反向交换回滚 → IDLE
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define OTA_MAGIC 0xA55A
|
||||
#define OTA_STATE_IDLE 0x00 /* 正常运行 */
|
||||
#define OTA_STATE_PENDING 0x01 /* 新固件已下载,待 Bootloader 校验/拷贝/交换 */
|
||||
#if (OTA_BACKUP_MODE == OTA_MODE_AB)
|
||||
#define OTA_STATE_SWAPPING 0x02 /* 双备份:交换进行中(掉电恢复入口)*/
|
||||
#endif
|
||||
#define OTA_STATE_BOOT_NEW 0x03 /* 已切到新固件,等待 APP 确认(未确认=回滚)*/
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* OTA 状态结构(APP 与 Bootloader 共用,flash.c 负责读写)
|
||||
*
|
||||
* seq / progress / phase 仅双备份需要(4 页轮换判新、交换掉电恢复),
|
||||
* 由 #if 保护 —— 单备份模式下字段不存在,结构体更小(20B vs 28B)。
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
typedef struct
|
||||
{
|
||||
#if (OTA_BACKUP_MODE == OTA_MODE_AB)
|
||||
uint32_t seq; /* 写入序号:4 页轮换判新(参与 CRC16 保护)*/
|
||||
#endif
|
||||
uint16_t magic; /* OTA_MAGIC,有效标志 */
|
||||
uint8_t state; /* IDLE / PENDING / [SWAPPING] / BOOT_NEW */
|
||||
uint8_t rsv;
|
||||
uint32_t fw_size; /* 新固件长度(字节) */
|
||||
uint32_t fw_crc32; /* 新固件 CRC32 */
|
||||
uint32_t fw_version; /* 新固件版本(透传,可留 0) */
|
||||
#if (OTA_BACKUP_MODE == OTA_MODE_AB)
|
||||
uint16_t progress; /* 双备份:交换已完成页数(掉电恢复) */
|
||||
uint8_t phase; /* 双备份:交换阶段标志(掉电恢复) */
|
||||
#endif
|
||||
uint8_t rsv2;
|
||||
uint16_t crc16; /* 结构体自身 CRC16(CRC-16/CCITT)保护 */
|
||||
} ota_param_t;
|
||||
|
||||
#define OTA_PARAM_SIZE ((uint16_t)sizeof(ota_param_t))
|
||||
|
||||
#endif /* __OTA_CONFIG_H */
|
||||
Reference in New Issue
Block a user