添加普冉 PY32F040 OTA 双工程代码生成模板
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/******************************************************************************
|
||||
* @file app_boot.c
|
||||
* @brief APP 启动流程(Bootloader 交接、时钟、IWDG、OTA 确认)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.18
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.18, cyWu, 从 main.c 拆出启动逻辑
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_boot.h"
|
||||
#include "application.h"
|
||||
#include "iwdg.h"
|
||||
#include "ota_ab.h"
|
||||
#include "main.h"
|
||||
|
||||
/**
|
||||
* @brief 从 Bootloader 跳转过来后,重新打开全局中断
|
||||
*
|
||||
* Bootloader 跳转 APP 前会 __disable_irq(),且函数跳转不会复位芯片,
|
||||
* 因此 PRIMASK 仍为 1(中断关)。APP 第一件事须 __enable_irq(),
|
||||
* 否则 SysTick / TIM6 / UART 等中断进不来,HAL_Delay、协议超时都会失效。
|
||||
*/
|
||||
static void app_boot_early_handover(void)
|
||||
{
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置 APP 系统时钟(HSI → PLL → SYSCLK)
|
||||
*
|
||||
* 与官网例程的差异:OscillatorType 故意不含 LSI。
|
||||
* Bootloader 跳转前已启动 IWDG,IWDG 运行期间 LSI 被硬件锁定关不掉;
|
||||
* 若在此配置 LSI OFF,HAL_RCC_OscConfig 会卡死等待 LSIRDY 变 0。
|
||||
* LSI 由 IWDG 维持即可,APP 只配置 HSI/PLL 主时钟树。
|
||||
*/
|
||||
static void app_system_clock_config(void)
|
||||
{
|
||||
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
|
||||
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
|
||||
|
||||
/* 主时钟:HSI 24MHz → PLL,不含 LSI(见上方说明) */
|
||||
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSE;
|
||||
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
|
||||
RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
|
||||
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_24MHz;
|
||||
RCC_OscInitStruct.HSEState = RCC_HSE_OFF;
|
||||
RCC_OscInitStruct.LSEState = RCC_LSE_OFF;
|
||||
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
|
||||
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
|
||||
|
||||
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
|
||||
{
|
||||
Error_Handler(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
|
||||
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
|
||||
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
|
||||
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
|
||||
|
||||
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
|
||||
{
|
||||
Error_Handler(__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 双备份 OTA:新固件启动确认后,上报一次升级完成(0x34)
|
||||
*
|
||||
* 须在 app_Init() 之后调用(依赖 uartInit / jbInit)。
|
||||
* ota_boot_check_confirm() 已将 Flash 置为 IDLE,0x34 仅为尽力通知云端;
|
||||
* 发一次即可,丢帧由 jbSendOtaResult 内 SEND_MAX_NUM 重传兜底。
|
||||
*/
|
||||
static void app_boot_ota_report(void)
|
||||
{
|
||||
uint8_t ver[3];
|
||||
|
||||
if (!ota_boot_check_confirm())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ver[0] = JB_MCU_SW_VERSION_MAJOR;
|
||||
ver[1] = JB_MCU_SW_VERSION_MINOR;
|
||||
ver[2] = JB_MCU_SW_VERSION_PATCH;
|
||||
(void)jbSendOtaResult(JB_OK, ver);
|
||||
appPrintf(LOG_NOTIC, "OTA upgrade complete reported\r\n");
|
||||
}
|
||||
|
||||
void app_startup(void)
|
||||
{
|
||||
app_boot_early_handover();
|
||||
HAL_Init();
|
||||
iwdg_init();
|
||||
app_system_clock_config();
|
||||
app_Init();
|
||||
app_boot_ota_report();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef __APP_BOOT_H
|
||||
#define __APP_BOOT_H
|
||||
|
||||
/**
|
||||
* @brief APP 启动入口:Bootloader 交接 → HAL/时钟/IWDG → 应用初始化 → OTA 确认
|
||||
*/
|
||||
void app_startup(void);
|
||||
|
||||
#endif /* __APP_BOOT_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef __FLASH_H
|
||||
#define __FLASH_H
|
||||
|
||||
#include "main.h"
|
||||
#include "ota_config.h"
|
||||
|
||||
void APP_FlashWrite(uint32_t PageAddress, uint8_t *Data, uint32_t DataSize);
|
||||
void APP_FlashRead(uint32_t PageAddress, uint8_t *Data, uint32_t DataSize);
|
||||
void APP_FlashEraseWithCheck(uint32_t PageAddress, uint32_t DataSize);
|
||||
|
||||
/* OTA 状态区读写(AB:4 页轮换磨损均衡;SINGLE:固定页 0 直写) */
|
||||
void ota_param_load(ota_param_t *p);
|
||||
void ota_param_store(const ota_param_t *p);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
/******************************************************************************
|
||||
* @file iwdg.c
|
||||
* @brief 独立看门狗(HAL 封装)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.18
|
||||
* @version V1.1.0
|
||||
* @history
|
||||
* - V1.1.0, 2026.08.18, cyWu, 改用 HAL_IWDG,参数见 Shared/iwdg_config.h
|
||||
* - V1.0.0, 2026.08.18, cyWu, 首次发布(寄存器版)
|
||||
******************************************************************************/
|
||||
|
||||
#include "iwdg.h"
|
||||
#include "main.h"
|
||||
#include "iwdg_config.h"
|
||||
|
||||
static IWDG_HandleTypeDef s_hiwdg;
|
||||
|
||||
/**
|
||||
* @brief 初始化/接管 IWDG(Bootloader 可能已启动,HAL 调用幂等)
|
||||
*/
|
||||
void iwdg_init(void)
|
||||
{
|
||||
s_hiwdg.Instance = IWDG;
|
||||
s_hiwdg.Init.Prescaler = IWDG_PRESCALER_16;
|
||||
s_hiwdg.Init.Reload = IWDG_RELOAD_VALUE;
|
||||
|
||||
if (HAL_IWDG_Init(&s_hiwdg) != HAL_OK)
|
||||
{
|
||||
Error_Handler(__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 喂狗,主循环周期调用(间隔须小于超时时间)
|
||||
*/
|
||||
void iwdg_feed(void)
|
||||
{
|
||||
(void)HAL_IWDG_Refresh(&s_hiwdg);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef __IWDG_H
|
||||
#define __IWDG_H
|
||||
|
||||
/**
|
||||
* @brief 独立看门狗 HAL 封装(参数见 Shared/iwdg_config.h)
|
||||
*
|
||||
* Bootloader 跳转前可能已启动 IWDG;APP 须在 HAL_Init 后尽早 iwdg_init()。
|
||||
* 时钟配置勿关闭 LSI,详见 app_boot.c 中 app_system_clock_config()。
|
||||
*/
|
||||
|
||||
void iwdg_init(void);
|
||||
void iwdg_feed(void);
|
||||
|
||||
#endif /* __IWDG_H */
|
||||
@@ -0,0 +1,66 @@
|
||||
/******************************************************************************
|
||||
* @file jb_port.c
|
||||
* @brief 协议硬件对接层:串口/定时器 ↔ 协议 API
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.10
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.10, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#include "jb_port.h"
|
||||
#include "jb_uart.h"
|
||||
#include "jb_timer.h"
|
||||
#include "jb_protocol.h"
|
||||
#include "main.h"
|
||||
|
||||
/**
|
||||
* @brief UART RX 单字节 → 协议环形缓冲(不放在 jb_product 中)
|
||||
* @param byte 收到的字节
|
||||
*/
|
||||
static void jb_port_on_uart_rx(uint8_t byte)
|
||||
{
|
||||
jbPutData(&byte, 1U);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化协议串口
|
||||
*/
|
||||
void jb_port_uart_init(void)
|
||||
{
|
||||
jb_uart_set_rx_callback(jb_port_on_uart_rx);
|
||||
if (jb_uart_init() != JB_UART_OK)
|
||||
{
|
||||
Error_Handler((uint8_t *)__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 协议串口发送
|
||||
* @param buf 数据
|
||||
* @param len 长度
|
||||
* @return 成功返回 len,失败 -1
|
||||
*/
|
||||
int32_t jb_port_uart_write(uint8_t *buf, uint32_t len)
|
||||
{
|
||||
return jb_uart_write(buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主循环串口处理
|
||||
*/
|
||||
void jb_port_uart_run(void)
|
||||
{
|
||||
jb_uart_run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 1ms 定时器硬件
|
||||
*/
|
||||
void jb_port_timer_init(void)
|
||||
{
|
||||
if (jb_timer_init() != JB_TIMER_OK)
|
||||
{
|
||||
Error_Handler((uint8_t *)__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
* @file jb_port.h
|
||||
* @brief 协议硬件对接层(与自动生成的 jb_product 解耦)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.10
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.10, cyWu, 首次发布
|
||||
*
|
||||
* @note
|
||||
* 移植新协议生成代码时:
|
||||
* 1. 覆盖 Protocol/jb_*.c/h(保留本文件不改)
|
||||
* 2. 在 jb_product.c 的 uartInit/timerInit/uartWrite 中各填一行 jb_port_xxx
|
||||
* 3. jbTimerIrq / jbGetTimerCount / timerMs 保持生成模板原样
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __JB_PORT_H__
|
||||
#define __JB_PORT_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief 初始化协议串口(含 RX→jbPutData 回调注册)
|
||||
*/
|
||||
void jb_port_uart_init(void);
|
||||
|
||||
/**
|
||||
* @brief 协议串口发送
|
||||
* @param buf 数据
|
||||
* @param len 长度
|
||||
* @return 成功返回 len,失败返回 -1
|
||||
*/
|
||||
int32_t jb_port_uart_write(uint8_t *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief 主循环调用:串口调试日志等
|
||||
*/
|
||||
void jb_port_uart_run(void);
|
||||
|
||||
/**
|
||||
* @brief 初始化协议用 1ms 定时器硬件(TIM6)
|
||||
*/
|
||||
void jb_port_timer_init(void);
|
||||
|
||||
#endif /* __JB_PORT_H__ */
|
||||
@@ -0,0 +1,86 @@
|
||||
/******************************************************************************
|
||||
* @file jb_timer.c
|
||||
* @brief TIM6 1ms 基础定时器硬件驱动实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.10
|
||||
* @version V1.0.1
|
||||
* @history
|
||||
* - V1.0.1, 2026.08.10, cyWu, 软件 ms 计数交还协议层,本模块仅负责 TIM6 硬件
|
||||
* - V1.0.0, 2026.08.10, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#include "jb_timer.h"
|
||||
#include "log.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有变量 */
|
||||
/*============================================================================*/
|
||||
|
||||
TIM_HandleTypeDef g_jb_tim_handle;
|
||||
static uint8_t s_initialized;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 公有接口 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 初始化 TIM6 为 1ms 周期中断
|
||||
* @return JbTimer_Ret_t
|
||||
*/
|
||||
JbTimer_Ret_t jb_timer_init(void)
|
||||
{
|
||||
uint32_t pclk1;
|
||||
uint32_t tim_clk;
|
||||
|
||||
if (s_initialized)
|
||||
{
|
||||
return JB_TIMER_OK;
|
||||
}
|
||||
|
||||
/* APB 不分频时 TIMCLK=PCLK1;分频时 TIMCLK=2*PCLK1 */
|
||||
pclk1 = HAL_RCC_GetPCLK1Freq();
|
||||
if ((RCC->CFGR & RCC_CFGR_PPRE) == RCC_HCLK_DIV1)
|
||||
{
|
||||
tim_clk = pclk1;
|
||||
}
|
||||
else
|
||||
{
|
||||
tim_clk = pclk1 * 2U;
|
||||
}
|
||||
|
||||
g_jb_tim_handle.Instance = JB_TIMER;
|
||||
g_jb_tim_handle.Init.Prescaler = (tim_clk / 1000000U) - 1U;
|
||||
g_jb_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
|
||||
g_jb_tim_handle.Init.Period = (1000U * JB_TIMER_PERIOD_MS) - 1U;
|
||||
g_jb_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
||||
g_jb_tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
|
||||
g_jb_tim_handle.Init.RepetitionCounter = 0;
|
||||
|
||||
if (HAL_TIM_Base_Init(&g_jb_tim_handle) != HAL_OK)
|
||||
{
|
||||
return JB_TIMER_ERR_HAL;
|
||||
}
|
||||
|
||||
if (HAL_TIM_Base_Start_IT(&g_jb_tim_handle) != HAL_OK)
|
||||
{
|
||||
return JB_TIMER_ERR_HAL;
|
||||
}
|
||||
|
||||
s_initialized = 1U;
|
||||
appPrintf(LOG_NOTIC, "TIM6 1ms timer started, pclk=%lu tim_clk=%lu\r\n",
|
||||
(unsigned long)pclk1, (unsigned long)tim_clk);
|
||||
return JB_TIMER_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取初始化状态
|
||||
* @return JbTimer_Ret_t
|
||||
*/
|
||||
JbTimer_Ret_t jb_timer_get_status(void)
|
||||
{
|
||||
if (!s_initialized)
|
||||
{
|
||||
return JB_TIMER_ERR_NOT_INIT;
|
||||
}
|
||||
return JB_TIMER_OK;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* @file jb_timer.h
|
||||
* @brief TIM6 1ms 基础定时器硬件驱动(ms 计数由协议层 jbTimerIrq 维护)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.10
|
||||
* @version V1.0.1
|
||||
* @history
|
||||
* - V1.0.1, 2026.08.10, cyWu, 软件 ms 计数交还协议层,本模块仅负责 TIM6 硬件
|
||||
* - V1.0.0, 2026.08.10, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __JB_TIMER_H__
|
||||
#define __JB_TIMER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "py32f0xx_hal.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 宏定义 */
|
||||
/*============================================================================*/
|
||||
|
||||
#ifndef JB_TIMER_PERIOD_MS
|
||||
#define JB_TIMER_PERIOD_MS (1U) /**< 定时周期 ms */
|
||||
#endif
|
||||
|
||||
#define JB_TIMER TIM6
|
||||
#define JB_TIMER_IRQn TIM6_LPTIM1_IRQn
|
||||
#define JB_TIMER_CLK_ENABLE() __HAL_RCC_TIM6_CLK_ENABLE()
|
||||
|
||||
/*============================================================================*/
|
||||
/* 错误码 */
|
||||
/*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
JB_TIMER_OK = 0,
|
||||
JB_TIMER_ERR_PARAM,
|
||||
JB_TIMER_ERR_BUSY,
|
||||
JB_TIMER_ERR_TIMEOUT,
|
||||
JB_TIMER_ERR_NOT_INIT,
|
||||
JB_TIMER_ERR_HAL,
|
||||
JB_TIMER_ERR_UNKNOWN
|
||||
} JbTimer_Ret_t;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 句柄(供 MSP / IRQ) */
|
||||
/*============================================================================*/
|
||||
|
||||
extern TIM_HandleTypeDef g_jb_tim_handle;
|
||||
#define TimHandle g_jb_tim_handle
|
||||
|
||||
/*============================================================================*/
|
||||
/* 外部接口 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 初始化 TIM6 为 1ms 周期中断
|
||||
* @note 更新回调中应调用协议层 jbTimerIrq(),由协议维护 timerMs
|
||||
* @return JbTimer_Ret_t
|
||||
*/
|
||||
JbTimer_Ret_t jb_timer_init(void);
|
||||
|
||||
/**
|
||||
* @brief 获取初始化状态
|
||||
* @return JbTimer_Ret_t
|
||||
*/
|
||||
JbTimer_Ret_t jb_timer_get_status(void);
|
||||
|
||||
#endif /* __JB_TIMER_H__ */
|
||||
@@ -0,0 +1,280 @@
|
||||
/******************************************************************************
|
||||
* @file jb_uart.c
|
||||
* @brief USART3 协议串口驱动实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.10
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.10, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#include "jb_uart.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有宏 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define UART_DBG_BUF_SIZE (256U)
|
||||
#define UART_DBG_IDLE_MS (30U)
|
||||
/* 协议 MAX_PACKAGE_LEN(128) + 前置 0x00 */
|
||||
#define JB_UART_TX_MAX_LEN (129U)
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有变量 */
|
||||
/*============================================================================*/
|
||||
|
||||
UART_HandleTypeDef g_jb_uart_handle;
|
||||
|
||||
static uint8_t s_initialized;
|
||||
static uint8_t s_uart_rx_byte;
|
||||
static JbUartRxCallback_t s_rx_cb;
|
||||
|
||||
static uint8_t s_dbg_buf[UART_DBG_BUF_SIZE];
|
||||
static volatile uint16_t s_dbg_head;
|
||||
static volatile uint16_t s_dbg_tail;
|
||||
static volatile uint32_t s_dbg_last_rx_ms;
|
||||
static volatile uint32_t s_rx_total;
|
||||
static volatile uint32_t s_err_code;
|
||||
static volatile uint8_t s_err_pending;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有函数 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief ISR 安全:调试缓冲写入 1 字节
|
||||
* @param byte 数据
|
||||
*/
|
||||
static void uart_dbg_push(uint8_t byte)
|
||||
{
|
||||
uint16_t next = (uint16_t)((s_dbg_head + 1U) % UART_DBG_BUF_SIZE);
|
||||
|
||||
if (next == s_dbg_tail)
|
||||
{
|
||||
s_dbg_tail = (uint16_t)((s_dbg_tail + 1U) % UART_DBG_BUF_SIZE);
|
||||
}
|
||||
s_dbg_buf[s_dbg_head] = byte;
|
||||
s_dbg_head = next;
|
||||
s_dbg_last_rx_ms = HAL_GetTick();
|
||||
s_rx_total++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打印十六进制
|
||||
* @param tag 标签
|
||||
* @param buf 数据
|
||||
* @param len 长度
|
||||
*/
|
||||
static void uart_dbg_print_hex(const char *tag, const uint8_t *buf, uint32_t len)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
printf("UART %s %lu bytes:", tag, (unsigned long)len);
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
printf(" %02X", buf[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 刷出 RX 调试与错误日志
|
||||
*/
|
||||
static void uart_dbg_flush(void)
|
||||
{
|
||||
uint8_t dump[UART_DBG_BUF_SIZE];
|
||||
uint16_t dump_len = 0;
|
||||
uint16_t tail;
|
||||
uint16_t head;
|
||||
|
||||
if (s_err_pending)
|
||||
{
|
||||
s_err_pending = 0;
|
||||
printf("UART RX error: 0x%08lX, total_rx=%lu\r\n",
|
||||
(unsigned long)s_err_code,
|
||||
(unsigned long)s_rx_total);
|
||||
}
|
||||
|
||||
head = s_dbg_head;
|
||||
tail = s_dbg_tail;
|
||||
if (head == tail)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((HAL_GetTick() - s_dbg_last_rx_ms) < UART_DBG_IDLE_MS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while ((tail != head) && (dump_len < UART_DBG_BUF_SIZE))
|
||||
{
|
||||
dump[dump_len++] = s_dbg_buf[tail];
|
||||
tail = (uint16_t)((tail + 1U) % UART_DBG_BUF_SIZE);
|
||||
}
|
||||
s_dbg_tail = tail;
|
||||
|
||||
uart_dbg_print_hex("RX", dump, dump_len);
|
||||
printf("UART RX total=%lu\r\n", (unsigned long)s_rx_total);
|
||||
}
|
||||
|
||||
/*============================================================================*/
|
||||
/* 公有接口 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 注册 RX 回调
|
||||
* @param cb 回调
|
||||
*/
|
||||
void jb_uart_set_rx_callback(JbUartRxCallback_t cb)
|
||||
{
|
||||
s_rx_cb = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 USART3
|
||||
* @return JbUart_Ret_t
|
||||
*/
|
||||
JbUart_Ret_t jb_uart_init(void)
|
||||
{
|
||||
if (s_initialized)
|
||||
{
|
||||
return JB_UART_OK;
|
||||
}
|
||||
|
||||
g_jb_uart_handle.Instance = JB_USART;
|
||||
g_jb_uart_handle.Init.BaudRate = JB_UART_BAUDRATE;
|
||||
g_jb_uart_handle.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
g_jb_uart_handle.Init.StopBits = UART_STOPBITS_1;
|
||||
g_jb_uart_handle.Init.Parity = UART_PARITY_NONE;
|
||||
g_jb_uart_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
||||
g_jb_uart_handle.Init.Mode = UART_MODE_TX_RX;
|
||||
g_jb_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;
|
||||
g_jb_uart_handle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
|
||||
|
||||
if (HAL_UART_Init(&g_jb_uart_handle) != HAL_OK)
|
||||
{
|
||||
return JB_UART_ERR_HAL;
|
||||
}
|
||||
|
||||
if (HAL_UART_Receive_IT(&g_jb_uart_handle, &s_uart_rx_byte, 1U) != HAL_OK)
|
||||
{
|
||||
return JB_UART_ERR_HAL;
|
||||
}
|
||||
|
||||
s_initialized = 1U;
|
||||
printf("USART3 ready: PB10=TX PB11=RX baud=%lu 8N1, waiting RX...\r\n",
|
||||
(unsigned long)JB_UART_BAUDRATE);
|
||||
return JB_UART_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主循环周期处理
|
||||
*/
|
||||
void jb_uart_run(void)
|
||||
{
|
||||
if (!s_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uart_dbg_flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 阻塞发送
|
||||
* @param buf 数据
|
||||
* @param len 长度
|
||||
* @return 成功返回 len,失败 -1
|
||||
*/
|
||||
int32_t jb_uart_write(uint8_t *buf, uint32_t len)
|
||||
{
|
||||
static uint8_t s_tx_buf[JB_UART_TX_MAX_LEN];
|
||||
uint32_t tx_len;
|
||||
|
||||
if (!buf)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (len == 0U)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!s_initialized)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if ((len + 1U) > JB_UART_TX_MAX_LEN)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 协议帧前加 0x00 后连续发送,避免越界读与帧间空隙 */
|
||||
s_tx_buf[0] = 0x00U;
|
||||
s_tx_buf[1] = 0x00U;
|
||||
memcpy(&s_tx_buf[2], buf, len);
|
||||
tx_len = len + 2U;
|
||||
|
||||
uart_dbg_print_hex("TX", s_tx_buf, tx_len);
|
||||
|
||||
if (HAL_UART_Transmit(&g_jb_uart_handle, s_tx_buf, (uint16_t)tx_len, 1000U) != HAL_OK)
|
||||
{
|
||||
printf("UART TX failed, len=%lu\r\n", (unsigned long)tx_len);
|
||||
return -1;
|
||||
}
|
||||
return (int32_t)len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取初始化状态
|
||||
* @return JbUart_Ret_t
|
||||
*/
|
||||
JbUart_Ret_t jb_uart_get_status(void)
|
||||
{
|
||||
if (!s_initialized)
|
||||
{
|
||||
return JB_UART_ERR_NOT_INIT;
|
||||
}
|
||||
return JB_UART_OK;
|
||||
}
|
||||
|
||||
/*============================================================================*/
|
||||
/* HAL 回调 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief UART RX 完成回调
|
||||
* @param huart 句柄
|
||||
*/
|
||||
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
|
||||
{
|
||||
if (huart->Instance == JB_USART)
|
||||
{
|
||||
uart_dbg_push(s_uart_rx_byte);
|
||||
if (s_rx_cb != NULL)
|
||||
{
|
||||
s_rx_cb(s_uart_rx_byte);
|
||||
}
|
||||
(void)HAL_UART_Receive_IT(&g_jb_uart_handle, &s_uart_rx_byte, 1U);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART 错误回调
|
||||
* @param huart 句柄
|
||||
*/
|
||||
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
|
||||
{
|
||||
if (huart->Instance == JB_USART)
|
||||
{
|
||||
s_err_code = HAL_UART_GetError(huart);
|
||||
s_err_pending = 1U;
|
||||
__HAL_UART_CLEAR_PEFLAG(huart);
|
||||
__HAL_UART_CLEAR_FEFLAG(huart);
|
||||
__HAL_UART_CLEAR_NEFLAG(huart);
|
||||
__HAL_UART_CLEAR_OREFLAG(huart);
|
||||
(void)HAL_UART_Receive_IT(&g_jb_uart_handle, &s_uart_rx_byte, 1U);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/******************************************************************************
|
||||
* @file jb_uart.h
|
||||
* @brief USART3 协议串口驱动(PB10=TX / PB11=RX)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.10
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.10, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __JB_UART_H__
|
||||
#define __JB_UART_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "py32f0xx_hal.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 宏定义(PY32F040 手册 PortB AF:PB10/PB11 = USART3 @ AF4) */
|
||||
/*============================================================================*/
|
||||
|
||||
#ifndef JB_UART_BAUDRATE
|
||||
#define JB_UART_BAUDRATE (9600U)
|
||||
#endif
|
||||
|
||||
#define JB_USART USART3
|
||||
#define JB_USART_IRQn USART3_4_IRQn
|
||||
#define JB_USART_CLK_ENABLE() __HAL_RCC_USART3_CLK_ENABLE()
|
||||
|
||||
#define JB_USART_TX_PIN GPIO_PIN_10
|
||||
#define JB_USART_TX_GPIO_PORT GPIOB
|
||||
#define JB_USART_TX_AF GPIO_AF4_USART3
|
||||
#define JB_USART_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
|
||||
|
||||
#define JB_USART_RX_PIN GPIO_PIN_11
|
||||
#define JB_USART_RX_GPIO_PORT GPIOB
|
||||
#define JB_USART_RX_AF GPIO_AF4_USART3
|
||||
#define JB_USART_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
|
||||
|
||||
/*============================================================================*/
|
||||
/* 错误码 */
|
||||
/*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
JB_UART_OK = 0,
|
||||
JB_UART_ERR_PARAM,
|
||||
JB_UART_ERR_BUSY,
|
||||
JB_UART_ERR_TIMEOUT,
|
||||
JB_UART_ERR_NOT_INIT,
|
||||
JB_UART_ERR_HAL,
|
||||
JB_UART_ERR_UNKNOWN
|
||||
} JbUart_Ret_t;
|
||||
|
||||
/**
|
||||
* @brief 单字节 RX 回调(上层注册,驱动不依赖协议)
|
||||
* @param byte 收到的字节
|
||||
*/
|
||||
typedef void (*JbUartRxCallback_t)(uint8_t byte);
|
||||
|
||||
/*============================================================================*/
|
||||
/* 句柄(供 MSP / IRQ) */
|
||||
/*============================================================================*/
|
||||
|
||||
extern UART_HandleTypeDef g_jb_uart_handle;
|
||||
#define UartHandle g_jb_uart_handle
|
||||
|
||||
/*============================================================================*/
|
||||
/* 外部接口 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 注册 RX 回调(建议在 jb_uart_init 前调用)
|
||||
* @param cb 回调,NULL 表示不向上层投递
|
||||
*/
|
||||
void jb_uart_set_rx_callback(JbUartRxCallback_t cb);
|
||||
|
||||
/**
|
||||
* @brief 初始化 USART3:9600 8N1 + RX 中断
|
||||
* @return JbUart_Ret_t
|
||||
*/
|
||||
JbUart_Ret_t jb_uart_init(void);
|
||||
|
||||
/**
|
||||
* @brief 主循环调用:刷出 RX 调试日志
|
||||
*/
|
||||
void jb_uart_run(void);
|
||||
|
||||
/**
|
||||
* @brief 阻塞发送
|
||||
* @param buf 数据
|
||||
* @param len 长度
|
||||
* @return 成功返回 len,失败返回 -1
|
||||
*/
|
||||
int32_t jb_uart_write(uint8_t *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief 获取初始化状态
|
||||
* @return JbUart_Ret_t
|
||||
*/
|
||||
JbUart_Ret_t jb_uart_get_status(void);
|
||||
|
||||
#endif /* __JB_UART_H__ */
|
||||
@@ -0,0 +1,290 @@
|
||||
/******************************************************************************
|
||||
* @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;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/******************************************************************************
|
||||
* @file ota_ab.h
|
||||
* @brief APP 侧 OTA 下载接口(写入备份区;切换由 Bootloader 完成)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.18
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.18, cyWu, 补充接口注释
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __OTA_AB_H
|
||||
#define __OTA_AB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "ota_config.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 说明 */
|
||||
/*============================================================================*/
|
||||
/*
|
||||
* 布局与状态机由 Shared/ota_config.h 唯一配置:
|
||||
* OTA_BAK_ADDR_BASE / OTA_SLOT_MAX_SIZE / ota_param_t / OTA_STATE_*
|
||||
*
|
||||
* 目标区固定 = OTA_BAK_ADDR_BASE:
|
||||
* 单备份 = BAK 下载缓冲;双备份 = B 备份区。APP 不必判断「当前跑的是 A 还是 B」。
|
||||
*
|
||||
* 状态区读写在 flash.h:ota_param_load / ota_param_store。
|
||||
*/
|
||||
|
||||
/*============================================================================*/
|
||||
/* 下载流程(协议 0x30 / 0x32 / 0x36) */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 0x30 接受升级后调用:擦备份区,准备收包
|
||||
* @param total_len 固件总字节数(fwLength)
|
||||
* @param fw_crc32 固件 CRC32(fwCrc32)
|
||||
* @return 0 成功,-1 长度非法
|
||||
*/
|
||||
int ota_start(uint32_t total_len, uint32_t fw_crc32);
|
||||
|
||||
/**
|
||||
* @brief 0x32 每包调用:写入备份区(内部 256B 页缓冲)
|
||||
* @param data 本包数据
|
||||
* @param len 本包长度
|
||||
* @return 0 成功,负值失败
|
||||
*/
|
||||
int ota_receive_chunk(const uint8_t *data, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief 是否已收满 fwLength(不依赖分片序号从 0 还是 1 起)
|
||||
*/
|
||||
bool ota_is_download_complete(void);
|
||||
|
||||
/**
|
||||
* @brief 收完后调用:核对长度+CRC,通过则写 PENDING
|
||||
* @return 0 成功可复位,1 校验失败,-1 未在下载中
|
||||
*/
|
||||
int ota_finish(void);
|
||||
|
||||
/**
|
||||
* @brief 0x36 / 0x38 停止升级:丢弃本次接收,不改运行区
|
||||
*/
|
||||
void ota_abort(void);
|
||||
|
||||
/*============================================================================*/
|
||||
/* 新固件确认(仅双备份) */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief APP 启动自检通过后调用
|
||||
* @return true 双备份下刚切到新固件,已确认(主循环可据此回复 OTA 完成)
|
||||
* false 普通启动;单备份恒为 false
|
||||
*/
|
||||
bool ota_boot_check_confirm(void);
|
||||
|
||||
/**
|
||||
* @brief 软件复位(实现在 Protocol/jb_product.c)
|
||||
*/
|
||||
void mcuRestart(void);
|
||||
|
||||
#endif /* __OTA_AB_H */
|
||||
Reference in New Issue
Block a user