refactor(spi): 优化SPI OTA模块代码结构
- 移除未使用的调试相关变量和函数,包括s_isrLogPending、s_isrLogBuf等 - 简化注释内容,保留核心功能说明 - 优化CS信号处理逻辑,提高代码可读性 - 修正帧处理中的竞态条件问题,提前设置响应标志避免重复处理 feat(boot): 添加BOOT固件版本号管理功能 - 在main.cpp中添加版本号写入逻辑,将BOOT版本号存储到OTA配置页 - 更新OTA配置页结构,从6个word扩展到8个word,增加版本号存储空间 - 实现版本变更检测机制,仅在版本更新时重写配置页 - 优化SpiOta模块的日志系统,移除多余的调试日志和缓冲区管理代码
This commit is contained in:
+10
-115
@@ -26,11 +26,10 @@
|
||||
#define SPI_TX_DMA_PERIPH DMA0
|
||||
#define SPI_TX_DMA_CH DMA_CH2
|
||||
|
||||
/* 调试:多缓冲日志(8 条 ISR 日志缓冲区,避免关键事件丢失) */
|
||||
/* 调试日志缓冲 */
|
||||
static constexpr u16 kLogSlotCount = 8U;
|
||||
static constexpr u16 kLogSlotSize = 80U;
|
||||
static u8 s_logSlots[kLogSlotCount][kLogSlotSize] = {};
|
||||
static u16 s_logSlotLens[kLogSlotCount] = {};
|
||||
static volatile u8 s_logWriteIdx = 0U;
|
||||
static volatile u8 s_logReadIdx = 0U;
|
||||
static volatile u8 s_logCount = 0U;
|
||||
@@ -47,10 +46,7 @@ u8 SpiOta::s_txBuf[SpiOta::kTxBufSize] = {};
|
||||
volatile u16 SpiOta::s_txLen = 0U;
|
||||
volatile bool SpiOta::s_txPending = false;
|
||||
volatile bool SpiOta::s_isrResponded = false;
|
||||
volatile bool SpiOta::s_isrLogPending = false;
|
||||
volatile bool SpiOta::s_otaSessionActive = false;
|
||||
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
|
||||
u16 SpiOta::s_isrLogLen = 0U;
|
||||
|
||||
/* ============================================================
|
||||
* CRC8 / 帧构建 / 帧解析
|
||||
@@ -155,12 +151,7 @@ void SpiOta::tryProcessCompleteFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len == 0U) { return; }
|
||||
|
||||
/* Raw 0xB0 BOOT 命令是单字节事务。
|
||||
* 不要在 CS 仍为低时仅凭“首字节 == 0xB0”就立刻处理,
|
||||
* 否则一旦事务错位/丢首字节,普通 OTA 数据流里的某个随机字节也可能
|
||||
* 被误判成 BOOT 命令,直接把会话打回 IDLE。
|
||||
*
|
||||
* 交给 onCsRelease() 在事务结束后再按“完整单字节事务”判定。 */
|
||||
/* 单字节 BOOT 命令留到事务结束后再判定。 */
|
||||
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { return; }
|
||||
|
||||
if (s_rxStream[0] != kSof) { return; }
|
||||
@@ -182,22 +173,11 @@ void SpiOta::isrProcessFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len == 0U) { return; }
|
||||
|
||||
/* 关键修复:一旦开始处理本次 CS 周期中的完整帧,立刻标记“已响应/已处理”。
|
||||
*
|
||||
* 否则会出现这样的竞态:
|
||||
* 1) SPI0 RBNE IRQ 在收到最后一个字节后调用 isrProcessFrame()
|
||||
* 2) 还没执行到函数尾部的 s_isrResponded = true
|
||||
* 3) CS 已经上升,优先级更高的 EXTI IRQ 抢占进来执行 onCsRelease()
|
||||
* 4) onCsRelease() 看到 s_isrResponded 仍为 false,于是再次调用 isrProcessFrame()
|
||||
* 5) 同一帧被重复处理一次,状态机被错误推进(如 challenge 又被当 confirm)
|
||||
*
|
||||
isrLogFrameEx("[RX-RAW]", s_rxStream, (rx_len < 16U) ? rx_len : 16U);
|
||||
* 所以必须在函数入口就先置位,防止 REQ/DATA 被双重分发。 */
|
||||
/* 提前置位,避免同一事务被重复处理。 */
|
||||
s_isrResponded = true;
|
||||
|
||||
if (rx_len == 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) {
|
||||
if (s_otaHandler != nullptr && s_otaHandler->isActive()) {
|
||||
isrLogFrameEx("[BOOT-CMD-IGN]", s_rxStream, rx_len);
|
||||
s_rxStreamLen = 0U;
|
||||
return;
|
||||
}
|
||||
@@ -206,7 +186,6 @@ void SpiOta::isrProcessFrame() {
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
|
||||
s_otaSessionActive = false;
|
||||
s_rxStreamLen = 0U;
|
||||
isrLogFrameEx("[BOOT-CMD]", s_txBuf, s_txLen);
|
||||
s_isrResponded = true;
|
||||
return;
|
||||
}
|
||||
@@ -241,7 +220,6 @@ void SpiOta::isrProcessFrame() {
|
||||
|
||||
if (frame_type == SPI_OTA_CMD_BOOT) {
|
||||
if (s_otaHandler != nullptr && s_otaHandler->isActive()) {
|
||||
isrLogFrameEx("[BOOT-FR-IGN]", s_rxStream, (expected < 24U) ? expected : 24U);
|
||||
s_rxStreamLen = 0U;
|
||||
s_isrResponded = true;
|
||||
return;
|
||||
@@ -292,7 +270,6 @@ static void isrLogFrameEx(const char *tag, const u8 *data, u16 len) {
|
||||
buf[pos++] = (u8)hx[data[i] & 0x0FU];
|
||||
}
|
||||
buf[pos] = 0U;
|
||||
s_logSlotLens[s_logWriteIdx] = pos;
|
||||
s_logWriteIdx = (s_logWriteIdx + 1U) % kLogSlotCount;
|
||||
s_logCount++;
|
||||
}
|
||||
@@ -393,7 +370,6 @@ void SpiOta::init() {
|
||||
s_txStreamPos = 0U;
|
||||
s_txLen = 0U;
|
||||
s_txPending = false;
|
||||
s_isrLogPending = false;
|
||||
s_otaSessionActive = false;
|
||||
memset(s_rxStream, 0, sizeof(s_rxStream));
|
||||
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
|
||||
@@ -414,68 +390,17 @@ void SpiOta::onCsAssert() {
|
||||
s_txStreamPos = 0U;
|
||||
s_isrResponded = false;
|
||||
|
||||
/* 内部 NSS 已在初始化时常置 LOW,这里不再依赖 EXTI 时序切 NSS。
|
||||
* PA15 仅作为“事务开始”的边界信号,用于清接收缓冲和重装 DMA。 */
|
||||
|
||||
/* 关键修复:先彻底停止 DMA,再重新配置并启动。
|
||||
*
|
||||
* 上一版预填 SPI_DATA 方案失败原因:
|
||||
* spi_i2s_data_transmit() 写入的字节进入 TBE 发送缓冲区。
|
||||
* 当 ESP32 第一个 SCK 边沿到来时,TBE 的预填字节被移入移位
|
||||
* 寄存器推到 MISO,同时移位寄存器中的 MOSI 数据被推入 RBNE。
|
||||
* 但 GD32E50x SPI 的 RBNE 标志在 SPI_DATA 写入后可能被误触发,
|
||||
* 导致 isrLogFrame 报出预填值(如 0x80)而非 MOSI 真实数据。
|
||||
*
|
||||
* 正确方案:不预填 SPI_DATA,而是确保 DMA 在 CS↓ 后、ESP32
|
||||
* 第一个 SCK 边沿之前就绪。DMA 第一拍自动把 s_txBuf[0] 写入
|
||||
* SPI_DATA,不会产生 RBNE 误触发。
|
||||
*
|
||||
* 操作顺序:
|
||||
* 1. 禁 DMA 通道 + 禁 SPI DMA 请求(彻底停止旧传输)
|
||||
* 2. 清 RBNE / RXORERR / FERR 等残留标志
|
||||
* 3. 配置并启动 DMA(此时 RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断)
|
||||
* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据
|
||||
*/
|
||||
|
||||
/* 1. 停止旧 DMA */
|
||||
/* PA15 仅作为事务边界信号;这里重装 DMA 并清理旧状态。 */
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
|
||||
/* 1.5 覆盖 SPI 发送缓冲区 (TBE) 中的旧字节
|
||||
*
|
||||
* 关键修复:CS 高期间,queueTxFrame() 会把新 ACK 帧写入 s_txBuf
|
||||
* 并启动 DMA,DMA 把 s_txBuf[0] 写入 SPI_DATA (TBE 缓冲区)。
|
||||
* CS 高时没有外部 SCK,TBE 中的字节停留在发送缓冲区不会被移位输出。
|
||||
*
|
||||
* 当 ESP32 下次 CS↓ 时,onCsAssert() 重启 DMA,但 TBE 中已经有
|
||||
* 旧字节。如果 ESP32 第一个 SCK 边沿在 DMA 写入新数据之前到达,
|
||||
* MISO 上先输出 TBE 中的旧字节,然后 DMA 才开始推 s_txBuf[0]。
|
||||
* 这导致 MISO 上第一个字节是旧 ACK 帧的残留,ESP32 的
|
||||
* spi_ota_find_frame 先匹配到旧帧,拿到比期望少 1 的 seq 号
|
||||
* ("差一拍" 现象)。
|
||||
*
|
||||
* 修复:在重启 DMA 前,写 0xFF 到 SPI_DATA 覆盖 TBE 中的旧字节。
|
||||
* 这样即使 ESP32 第一个 SCK 先到,MISO 输出的是 0xFF 而非旧 ACK 残留。
|
||||
* spi_ota_find_frame 会跳过 0xFF(不是 SOF=0xA5),从后面 DMA 推出的
|
||||
* 正确帧开始匹配。
|
||||
*/
|
||||
/* 覆盖 TBE 里的旧字节,避免下一次事务先吐出残留 ACK。 */
|
||||
spi_i2s_data_transmit(SPI0, 0xFFU);
|
||||
/* 如果 TBE FIFO 有两层,再写一个 0xFF 确保覆盖 */
|
||||
if (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) != RESET) {
|
||||
spi_i2s_data_transmit(SPI0, 0xFFU);
|
||||
}
|
||||
|
||||
/* 2. 清残留错误标志
|
||||
*
|
||||
* 关键:这里绝不能在 CS↓ 时“顺手读掉 RBNE”。
|
||||
*
|
||||
* 因为 internal NSS 现在常 LOW,ESP32 在 CS↓ 后很快就会发出首字节 A5。
|
||||
* 如果 EXTI ISR 略晚于 SPI 接收,A5 可能已经先进了 RX 缓冲并置位 RBNE。
|
||||
* 这时若在 onCsAssert() 里执行 spi_i2s_data_receive(),就会把真实首字节 A5
|
||||
* 当成“残留垃圾”吞掉,导致后续看到的首字节变成 0x01。
|
||||
*
|
||||
* 所以这里仅清真正的错误标志,不主动消费 RBNE。
|
||||
* 已到达的首字节留给 onSpiInterrupt() 正常读走。 */
|
||||
/* 只清错误标志,不主动消费 RBNE。 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
}
|
||||
@@ -483,7 +408,7 @@ void SpiOta::onCsAssert() {
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
|
||||
/* 3. 配置并启动 DMA(RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断) */
|
||||
/* 3. 配置并启动 DMA */
|
||||
if (s_txPending && s_txLen > 0U) {
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
@@ -502,12 +427,7 @@ void SpiOta::onCsRelease() {
|
||||
if (!s_csActive) { return; }
|
||||
s_csActive = false;
|
||||
|
||||
/* 内部 NSS 保持常 LOW,不在 CS↑ 时切回 HIGH。
|
||||
* 这样 SPI 从机始终处于就绪态,下一次 CS↓ 不会因 EXTI 延迟丢首字节。
|
||||
*
|
||||
* 这里只停止 DMA,避免 CS 高期间继续空转消耗 s_txBuf 内容。
|
||||
* s_txPending 保持不变——如果 ISR 已准备好 ACK 帧,
|
||||
* 下次 CS↓ 时 onCsAssert 会重启 DMA 推出。 */
|
||||
/* 内部 NSS 保持常 LOW;这里只停止 DMA。 */
|
||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
|
||||
@@ -530,13 +450,7 @@ void SpiOta::onCsRelease() {
|
||||
|
||||
void SpiOta::onSpiInterrupt() {
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
/* 与 APP 侧稳定实现保持一致:
|
||||
* 如果 EXTI 下降沿因为中断时序略晚到达,但此时 PA15 已经为低,
|
||||
* 则在首个 RBNE 到来时立即补做 onCsAssert()。
|
||||
*
|
||||
* 关键限制:只能在“本事务尚未收过任何字节”时补做。
|
||||
* 否则若 EXTI / SPI 时序偶发抖动,重复 onCsAssert() 会把
|
||||
* s_rxStreamLen 清零,导致已经收到的半帧被冲掉,最终永远拼不成完整帧。 */
|
||||
/* EXTI 略晚时,在首个 RBNE 到来时补做 onCsAssert()。 */
|
||||
if (!s_csActive && s_rxStreamLen == 0U && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
|
||||
onCsAssert();
|
||||
}
|
||||
@@ -545,11 +459,6 @@ void SpiOta::onSpiInterrupt() {
|
||||
if (s_rxStreamLen < kRxBufSize) {
|
||||
s_rxStream[s_rxStreamLen++] = byte;
|
||||
}
|
||||
/* 调试阶段先关闭 RBNE 首字节日志。
|
||||
* 原因:ISR 日志缓冲是单槽的,RBNE 一旦先占位,
|
||||
* onCsRelease() 里的 [CS-RX] 摘要日志就会被丢弃。
|
||||
* 目前排查重点是“本次 CS 周期总共收到了哪些字节”,
|
||||
* 所以优先保留 [CS-RX],暂时关闭 [RBNE]。 */
|
||||
tryProcessCompleteFrame();
|
||||
}
|
||||
/* TBE 已由 DMA 替代,不再处理 */
|
||||
@@ -578,28 +487,14 @@ void SpiOta::poll() {
|
||||
if (s_isrResponded) {
|
||||
s_isrResponded = false;
|
||||
}
|
||||
/* 打印 ISR 日志缓冲区 */
|
||||
while (s_logCount > 0U) {
|
||||
__disable_irq();
|
||||
if (s_logCount == 0U) { __enable_irq(); break; }
|
||||
u8 *buf = s_logSlots[s_logReadIdx];
|
||||
u8 nextRead = (s_logReadIdx + 1U) % kLogSlotCount;
|
||||
s_logReadIdx = nextRead;
|
||||
s_logReadIdx = (s_logReadIdx + 1U) % kLogSlotCount;
|
||||
s_logCount--;
|
||||
__enable_irq();
|
||||
printf("[SPI] %s\r\n", buf);
|
||||
}
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
|
||||
|
||||
/* 调试:定期打印 SPI 从机状态 — 降低频率, 每20000次才打印一次 */
|
||||
static u32 dbg_tick = 0;
|
||||
dbg_tick++;
|
||||
if ((dbg_tick % 20000U) == 0U) {
|
||||
printf("[SPI] st=%u rx=%u tx=%u ota=%u\r\n",
|
||||
(unsigned)s_csActive, (unsigned)s_rxStreamLen,
|
||||
(unsigned)s_txLen,
|
||||
(unsigned)(s_otaHandler ? s_otaHandler->getState() : 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
|
||||
|
||||
@@ -59,10 +59,7 @@ public:
|
||||
static volatile u16 s_txLen;
|
||||
static volatile bool s_txPending;
|
||||
static volatile bool s_isrResponded;
|
||||
static volatile bool s_isrLogPending;
|
||||
static volatile bool s_otaSessionActive;
|
||||
static u8 s_isrLogBuf[kIsrLogBufSize];
|
||||
static u16 s_isrLogLen;
|
||||
|
||||
static void sender(ota_link_tx_type_t type, u8 len, const u8 *data);
|
||||
|
||||
|
||||
@@ -51,6 +51,62 @@ void BootloaderSystem::initHardware() {
|
||||
/** 让 SPI OTA 模块在收到完整帧后,转交给 ota 实例处理。 */
|
||||
SpiOta::setSender(&ota);
|
||||
|
||||
/** 写入 BOOT 版本号到 OTA 配置页(仅当版本变更时重写)。
|
||||
* 采用与 OTA_SET 一致的读-擦-写策略,但将 word6/word7 合并为单次页面擦除,
|
||||
* 避免两次 OTA_SET 调用导致的双重擦除。 */
|
||||
{
|
||||
u32 ver_word0 = (u32)((BOOT_FW_VERSION_MAJOR & 0xFFFFU) | ((BOOT_FW_VERSION_MINOR & 0xFFFFU) << 16));
|
||||
u32 ver_word1 = (u32)((BOOT_FW_VERSION_PATCH & 0xFFFFU) | ((BOOT_FW_VERSION_BUILD & 0xFFFFU) << 16));
|
||||
volatile u32 *cur0 = (volatile u32 *)n_BOOT_VERSION_ADDR0;
|
||||
volatile u32 *cur1 = (volatile u32 *)n_BOOT_VERSION_ADDR1;
|
||||
if (*cur0 != ver_word0 || *cur1 != ver_word1) {
|
||||
/** 版本变更:先读取整页 8 个配置字,再擦除整页后一次性回写。
|
||||
* 读取逻辑与 app_spi_command.cpp 中 BOOT 版本读取方式一致。 */
|
||||
u32 temp_val[OTA_CONFIG_NUM];
|
||||
volatile u32 *flash_ptr;
|
||||
for (int i = 0; i < OTA_CONFIG_NUM; ++i) {
|
||||
flash_ptr = (volatile u32 *)(OTA_CONFIG_PAGE_ADDR + 4U * (u32)i);
|
||||
temp_val[i] = *flash_ptr;
|
||||
}
|
||||
/** 用新版本号替换 word6/word7。 */
|
||||
temp_val[6] = ver_word0;
|
||||
temp_val[7] = ver_word1;
|
||||
|
||||
fmc_unlock();
|
||||
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
|
||||
fmc_state_enum fmc_status = fmc_page_erase(OTA_CONFIG_PAGE_ADDR);
|
||||
#if DEBUG_PRINT
|
||||
printf("[FW] E page=0x%08lX\r\n", (unsigned long)OTA_CONFIG_PAGE_ADDR);
|
||||
#endif
|
||||
if (fmc_status == FMC_READY) {
|
||||
for (int i = 0; i < OTA_CONFIG_NUM; ++i) {
|
||||
if (temp_val[i] != OTA_NOT_AVAL) {
|
||||
u32 current_addr = OTA_CONFIG_PAGE_ADDR + 4U * (u32)i;
|
||||
fmc_status = fmc_word_program(current_addr, temp_val[i]);
|
||||
#if DEBUG_PRINT
|
||||
printf("[FW] W addr=0x%08lX data=0x%08lX\r\n",
|
||||
(unsigned long)current_addr, (unsigned long)temp_val[i]);
|
||||
#endif
|
||||
if (fmc_status != FMC_READY) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] version write FAILED at word %d\r\n", i);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmc_lock();
|
||||
#if DEBUG_PRINT
|
||||
if (fmc_status == FMC_READY) {
|
||||
printf("[BOOT] version written: v%u.%u.%u.%u\r\n",
|
||||
BOOT_FW_VERSION_MAJOR, BOOT_FW_VERSION_MINOR,
|
||||
BOOT_FW_VERSION_PATCH, BOOT_FW_VERSION_BUILD);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查是否需要进入深度睡眠模式。 */
|
||||
{
|
||||
const int sleep_state = BootSleepManager::check();
|
||||
|
||||
@@ -152,7 +152,7 @@ u8 OTA_SET(u32 addr, u32 data) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取当前配置页中的全部状态,供擦除后重写。 */
|
||||
/** 读取当前配置页中的全部状态(含 BOOT 版本 word 6/7),供擦除后重写。 */
|
||||
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
|
||||
temp_val[0] = *flash_ptr;
|
||||
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
|
||||
@@ -165,6 +165,10 @@ u8 OTA_SET(u32 addr, u32 data) {
|
||||
temp_val[4] = *flash_ptr;
|
||||
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
|
||||
temp_val[5] = *flash_ptr;
|
||||
flash_ptr = (volatile u32 *)n_BOOT_VERSION_ADDR0;
|
||||
temp_val[6] = *flash_ptr;
|
||||
flash_ptr = (volatile u32 *)n_BOOT_VERSION_ADDR1;
|
||||
temp_val[7] = *flash_ptr;
|
||||
|
||||
/** 整页擦除后重建配置页。 */
|
||||
fmc_unlock();
|
||||
|
||||
@@ -11,6 +11,12 @@ typedef uint32_t u32;
|
||||
/** 调试打印总开关。置 1 时所有 printf 调试输出编译进去;置 0 时完全剔除,零开销。 */
|
||||
#define DEBUG_PRINT 1
|
||||
|
||||
/* BOOT 固件版本号 */
|
||||
#define BOOT_FW_VERSION_MAJOR 1U
|
||||
#define BOOT_FW_VERSION_MINOR 0U
|
||||
#define BOOT_FW_VERSION_PATCH 0U
|
||||
#define BOOT_FW_VERSION_BUILD 1U
|
||||
|
||||
/**
|
||||
* Flash 布局说明(GD32E508VE,512KB)
|
||||
* -----------------------------------
|
||||
@@ -40,16 +46,19 @@ inline constexpr u32 SLEEP_ENABLE = 0x11111111U;
|
||||
inline constexpr u32 SLEEP_DISABLE = 0x00000000U;
|
||||
|
||||
/**
|
||||
* OTA 配置页一共使用 6 个 word,分别保存:
|
||||
* latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check
|
||||
* OTA 配置页一共使用 8 个 word:
|
||||
* word 0~5: latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check
|
||||
* word 6~7: BOOT 版本号(major|minor / patch|build)
|
||||
*/
|
||||
inline constexpr u32 OTA_CONFIG_NUM = 6U;
|
||||
inline constexpr u32 OTA_CONFIG_NUM = 8U;
|
||||
inline constexpr u32 n_APP_LATEST_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x00U;
|
||||
inline constexpr u32 n_APP_BACKUP_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x04U;
|
||||
inline constexpr u32 n_APP_LATEST_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x08U;
|
||||
inline constexpr u32 n_APP_BACKUP_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x0CU;
|
||||
inline constexpr u32 n_APP_LATEST_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x10U;
|
||||
inline constexpr u32 n_APP_BACKUP_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x14U;
|
||||
inline constexpr u32 n_BOOT_VERSION_ADDR0 = OTA_CONFIG_PAGE_ADDR + 0x18U; /**< BOOT版本 word0: major|minor */
|
||||
inline constexpr u32 n_BOOT_VERSION_ADDR1 = OTA_CONFIG_PAGE_ADDR + 0x1CU; /**< BOOT版本 word1: patch|build */
|
||||
|
||||
/**
|
||||
* 本项目把 0x00000000 视为“有效/可用”,
|
||||
|
||||
Reference in New Issue
Block a user