添加普冉 PY32F040 OTA 双工程代码生成模板

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 18:59:56 +08:00
co-authored by Cursor
commit 331864dc64
496 changed files with 329040 additions and 0 deletions
+99
View File
@@ -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 跳转前已启动 IWDGIWDG 运行期间 LSI 被硬件锁定关不掉;
* 若在此配置 LSI OFFHAL_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 置为 IDLE0x34 仅为尽力通知云端;
* 发一次即可,丢帧由 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();
}
+9
View File
@@ -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 */
+15
View File
@@ -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
+39
View File
@@ -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 初始化/接管 IWDGBootloader 可能已启动,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);
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef __IWDG_H
#define __IWDG_H
/**
* @brief 独立看门狗 HAL 封装(参数见 Shared/iwdg_config.h
*
* Bootloader 跳转前可能已启动 IWDGAPP 须在 HAL_Init 后尽早 iwdg_init()。
* 时钟配置勿关闭 LSI,详见 app_boot.c 中 app_system_clock_config()。
*/
void iwdg_init(void);
void iwdg_feed(void);
#endif /* __IWDG_H */
+66
View File
@@ -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__);
}
}
+45
View File
@@ -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__ */
+86
View File
@@ -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;
}
+68
View File
@@ -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__ */
+280
View File
@@ -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);
}
}
+101
View File
@@ -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 AFPB10/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 初始化 USART39600 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__ */
+290
View File
@@ -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;
}
+83
View File
@@ -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.hota_param_load / ota_param_store。
*/
/*============================================================================*/
/* 下载流程(协议 0x30 / 0x32 / 0x36 */
/*============================================================================*/
/**
* @brief 0x30 接受升级后调用:擦备份区,准备收包
* @param total_len 固件总字节数(fwLength
* @param fw_crc32 固件 CRC32fwCrc32
* @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 */