Files
mcu-codegen-template/App/Core/ota_ab.c
T

291 lines
9.6 KiB
C
Raw 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.
/******************************************************************************
* @file ota_ab.c
* @brief APP 侧 OTA 下载实现:收固件 → 写入备份区 → CRC 校验 → 通知 Bootloader
* @author cyWu <1917507415@qq.com>
* @date 2026.08.18
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.18, cyWu, 补充注释(逻辑与原先一致)
*
* 本文件只负责「下载阶段」,不负责把新固件切到运行区。
* 切换/拷贝由复位后的 Bootloader 根据状态区 PENDING 完成:
*
* 协议 0x30 ota_start() 擦备份区,准备接收
* 协议 0x32 ota_receive_chunk() 分片写入备份区(页缓冲)
* ota_finish() 长度+CRC 通过后写 PENDING
* 复位
* Bootloader 读到 PENDING 单备份:拷贝到运行区
* 双备份:A/B 交换后写 BOOT_NEW
* 新 APP ota_boot_check_confirm() 双备份首次启动确认(单备份恒 false)
******************************************************************************/
#include "ota_ab.h"
#include "flash.h"
#include "crc32.h"
#include "log.h"
/*============================================================================*/
/* 私有变量 */
/*============================================================================*/
static ota_param_t g_ota; /**< 本次升级参数(长度/CRC/状态),结束时写入状态区 */
static uint32_t g_crc_acc; /**< 流式 CRC32 累加器,边收边算,不必整包再读 Flash */
static uint32_t g_recv_len; /**< 已接收且计入固件的字节数(不含尾包填充) */
static bool g_running; /**< true=正在收包;abort/finish 后清掉 */
/*
* 页缓冲:协议分片约 96B,Flash 页编程必须整页 256B。
* 分片先攒进 s_page_buf,满页或收尾时再 APP_FlashWrite。
*/
static uint8_t s_page_buf[OTA_PAGE_SIZE];
static uint32_t s_page_addr; /**< 当前缓冲对应的 Flash 页起始地址 */
static uint32_t s_page_fill; /**< 当前页已填字节数,0 表示缓冲空 */
/*============================================================================*/
/* 私有函数 */
/*============================================================================*/
/**
* @brief 把页缓冲写入 Flash,然后清空缓冲
* @note 尾页不足 256B 时先补 0xFF(擦除态),满足整页编程要求。
* 补的 0xFF 不参与 CRC(CRC 在收包时已按原始固件字节累计)。
* @return 0 成功;缓冲为空时直接返回 0
*/
static int ota_flush_page(void)
{
if (s_page_fill == 0)
{
return 0;
}
if (s_page_fill < OTA_PAGE_SIZE)
{
memset(&s_page_buf[s_page_fill], 0xFF, OTA_PAGE_SIZE - s_page_fill);
}
APP_FlashWrite(s_page_addr, s_page_buf, OTA_PAGE_SIZE);
s_page_fill = 0;
return 0;
}
/*============================================================================*/
/* 公有函数:下载流程 */
/*============================================================================*/
/**
* @brief 开始 OTA:擦备份区并初始化接收状态(协议 0x30 接受升级后调用)
* @param total_len 固件总长度,来自 0x30 的 fwLength
* @param fw_crc32 固件 CRC32,来自 0x30 的 fwCrc32,收完后与流式 CRC 比对
* @return 0 成功;-1 长度非法
* @note 目标地址固定 OTA_BAK_ADDR_BASE
* 单备份 = BAK 下载缓冲;双备份 = B 区。运行区固件此时不动。
* 下载期间状态保持 IDLE,避免半包掉电被 Bootloader 误当成待升级。
*/
int ota_start(uint32_t total_len, uint32_t fw_crc32)
{
uint32_t base = OTA_BAK_ADDR_BASE;
if ((total_len == 0) || (total_len > OTA_SLOT_MAX_SIZE))
{
Log("[OTA] invalid length %lu\r\n", (unsigned long)total_len);
return -1;
}
Log("[OTA] start len=%lu crc=%08X target=%08X\r\n",
(unsigned long)total_len, (unsigned int)fw_crc32, (unsigned int)base);
memset(&g_ota, 0, sizeof(g_ota));
g_ota.magic = OTA_MAGIC;
g_ota.state = OTA_STATE_IDLE;
g_ota.fw_size = total_len;
g_ota.fw_crc32 = fw_crc32;
g_crc_acc = crc32_init();
g_recv_len = 0;
g_running = true;
s_page_addr = 0;
s_page_fill = 0;
/* 整槽擦除。调用点在 0x30 应答之前,擦完才让 BLE 开始发 0x32 */
APP_FlashEraseWithCheck(base, OTA_SLOT_MAX_SIZE);
return 0;
}
/**
* @brief 写入一个 OTA 数据分片(协议 0x32 每包调用)
* @param data 本包固件数据
* @param len 本包长度;尾包可能按 MaxDataLen 补齐,内部会截到 fw_size
* @return 0 成功(含已收满后的多余包,直接忽略)
* -1 未处于下载中
* -2 参数非法
* -3 超出备份区容量
* @note 边收边做两件事:更新流式 CRC;按 256B 页对齐写入备份区。
*/
int ota_receive_chunk(const uint8_t *data, uint32_t len)
{
uint32_t base;
uint32_t offset;
const uint8_t *p;
uint32_t remain;
if (!g_running)
{
return -1;
}
if ((data == NULL) || (len == 0))
{
return -2;
}
if (g_recv_len >= g_ota.fw_size)
{
return 0; /* 已收满,忽略多余分片 */
}
/* 尾包若带协议填充,只取固件剩余字节,避免把填充算进 CRC / 写入 Flash */
if ((g_recv_len + len) > g_ota.fw_size)
{
len = g_ota.fw_size - g_recv_len;
}
if ((g_recv_len + len) > OTA_SLOT_MAX_SIZE)
{
Log("[OTA] overflow\r\n");
return -3;
}
base = OTA_BAK_ADDR_BASE;
offset = g_recv_len;
p = data;
remain = len;
g_crc_acc = crc32_update(g_crc_acc, data, len);
while (remain > 0)
{
/* 当前字节所属 Flash 页的起始地址(256B 对齐) */
uint32_t page_addr = base + (offset & ~(OTA_PAGE_SIZE - 1));
uint32_t in_page;
uint32_t n;
/* 跨页:先把上一页写进去,再开新页缓冲 */
if (page_addr != s_page_addr)
{
ota_flush_page();
s_page_addr = page_addr;
s_page_fill = 0;
}
/* 本页还能塞多少:页大小 - 页内偏移 */
in_page = OTA_PAGE_SIZE - (offset & (OTA_PAGE_SIZE - 1));
n = (remain < in_page) ? remain : in_page;
memcpy(&s_page_buf[s_page_fill], p, n);
s_page_fill += n;
if (s_page_fill == OTA_PAGE_SIZE)
{
ota_flush_page();
}
p += n;
offset += n;
remain -= n;
}
g_recv_len = offset;
return 0;
}
/**
* @brief 是否已收满 0x30 声明的 fwLength
* @return true 已收满,可调用 ota_finish();不依赖分片序号从 0 还是从 1 起
*/
bool ota_is_download_complete(void)
{
return (g_running && (g_recv_len >= g_ota.fw_size) && (g_ota.fw_size > 0));
}
/**
* @brief 收包结束:刷尾页、核对长度和 CRC,通过则把状态写成 PENDING
* @return 0 校验通过,已写 PENDING,调用方可复位进 Bootloader
* 1 长度或 CRC 失败(备份区数据作废,运行区仍是旧固件)
* -1 当前不在下载中
* @note 只有这里才会把状态区改成 PENDING。半包掉电时仍是 IDLE,下次启动正常跑旧固件。
*/
int ota_finish(void)
{
uint32_t crc;
if (!g_running)
{
return -1;
}
ota_flush_page();
g_running = false;
Log("[OTA] recv %lu bytes, expect %lu\r\n",
(unsigned long)g_recv_len, (unsigned long)g_ota.fw_size);
if (g_recv_len != g_ota.fw_size)
{
Log("[OTA] length mismatch, abort\r\n");
return 1;
}
crc = crc32_final(g_crc_acc);
Log("[OTA] crc=%08X expect=%08X\r\n",
(unsigned int)crc, (unsigned int)g_ota.fw_crc32);
if (crc != g_ota.fw_crc32)
{
Log("[OTA] CRC32 FAILED, abort\r\n");
return 1;
}
/* 告诉 Bootloader:备份区固件有效,复位后请拷贝/交换 */
g_ota.state = OTA_STATE_PENDING;
ota_param_store(&g_ota);
/* 回读确认状态区已落盘,避免写失败后仍去复位 */
ota_param_load(&g_ota);
Log("[OTA] state=%d\r\n", g_ota.state);
Log("[OTA] CRC32 OK, state=PENDING, ready to reboot\r\n");
return 0;
}
/**
* @brief 中止本次 OTA(协议 0x36 / 0x38
* @note 只停接收、丢页缓冲。不改状态区(仍为 IDLE),运行区不受影响。
*/
void ota_abort(void)
{
Log("[OTA] abort\r\n");
g_running = false;
s_page_fill = 0;
}
/*============================================================================*/
/* 公有函数:新固件首次启动确认(仅双备份有意义) */
/*============================================================================*/
/**
* @brief APP 启动时检查是否需要确认「已切到新固件」
* @return true 本次是双备份切换后的首次启动,已把 BOOT_NEW 改成 IDLE
* false 普通启动,或单备份(不会出现 BOOT_NEW)
* @note 应在 APP 自检通过后调用。若新固件崩溃、未跑到这里,
* Bootloader 下次启动仍看到 BOOT_NEW,会回滚旧固件。
*/
bool ota_boot_check_confirm(void)
{
ota_param_t p;
ota_param_load(&p);
if (p.state == OTA_STATE_BOOT_NEW)
{
p.state = OTA_STATE_IDLE;
ota_param_store(&p);
Log("[OTA] new fw confirmed\r\n");
return true;
}
return false;
}