feat(device): 添加早期诊断功能并优化SPI通信实现

- 在startup_gd32e50x_cl.s中添加USART0早期诊断代码,在Reset_Handler中
  发送'R'字符以确认启动过程

- 修改system_gd32e50x.c中的时钟初始化逻辑,支持动态检测当前时钟源
  (PLL或IRC8M)并相应调整串口波特率设置

- 在系统时钟配置函数中添加HXTAL故障诊断输出,当外部晶振启动失败时
  通过USART0输出"HXTAL FAIL"错误信息

- 在AppSpiCommand类中实现DMA传输机制,使用DMA0_CH2进行SPI0数据发送,
  提升传输效率并与BOOT端保持一致

- 重构SPI命令处理逻辑,优化PING命令响应格式,包含APP和BOOT版本信息
  以及运行时间统计

- 添加事务过滤机制,忽略全0xFF或全0x00的数据包以及短帧

- 更新配置文件定义APP固件版本号和OTA配置页面地址

- 修复region配置文件中的ROM大小数值精度问题

- 调整NVIC中断优先级设置,优化SPI中断处理
This commit is contained in:
cbb
2026-06-03 03:13:49 +08:00
parent 771d34398f
commit d0b6d9537e
10 changed files with 502 additions and 49 deletions
+26
View File
@@ -116,4 +116,30 @@
#define CONFIG_FWDGT_RLD 1874U
/** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */
#define CONFIG_FWDGT_TIMEOUT_MS 3000U
/* ============================================================
* APP 固件版本号
* ============================================================ */
/** @brief APP 主版本号。 */
#define APP_FW_VERSION_MAJOR 1U
/** @brief APP 次版本号。 */
#define APP_FW_VERSION_MINOR 0U
/** @brief APP 补丁版本号。 */
#define APP_FW_VERSION_PATCH 0U
/** @brief APP 构建编号。 */
#define APP_FW_VERSION_BUILD 1U
/* ============================================================
* BOOT 版本在 OTA 配置页中的 Flash 存储地址
* 与 BOOT 工程 sys/config.h 保持一致
* ============================================================ */
/** @brief OTA 配置页基地址。 */
#define CONFIG_OTA_CONFIG_PAGE_ADDR 0x0807E000U
/** @brief BOOT 版本存储地址(word 6,低 32 位:major|minor)。 */
#define CONFIG_BOOT_VERSION_ADDR0 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U)
/** @brief BOOT 版本存储地址(word 7,低 32 位:patch|build)。 */
#define CONFIG_BOOT_VERSION_ADDR1 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x1CU)
/** @brief OTA 配置页总 word 数(6 个 OTA 标志 + 2 个 BOOT 版本)。 */
#define CONFIG_OTA_CONFIG_WORD_COUNT 8U
#endif /* SYS_CONFIG_H */
+150
View File
@@ -0,0 +1,150 @@
/**
* @file hardfault.cpp
* @brief HardFault 诊断处理——通过 USART0 输出故障寄存器信息。
*
* 当 APP 在 BOOT 跳转后立即死机时,标准 printf 不可用,
* 所以使用直接寄存器操作输出。
*/
#include "gd32e50x.h"
#include <stdint.h>
/**
* @brief 通过 USART0 直接发送一个字节(纯寄存器操作,不依赖库函数/宏)。
*/
static void rawPutc(char ch) {
volatile uint32_t *stat0 = (volatile uint32_t *)0x40013800U;
volatile uint32_t *data = (volatile uint32_t *)0x40013804U;
while ((*stat0 & (1U << 7)) == 0U) {} /* 等待 TBE */
*data = (uint32_t)ch;
}
/**
* @brief 通过 USART0 直接发送字符串。
*/
static void rawPuts(const char *s) {
while (*s) { rawPutc(*s++); }
}
/**
* @brief 输出 32 位十六进制值。
*/
static void rawHex32(uint32_t val) {
const char hex[] = "0123456789ABCDEF";
rawPuts("0x");
for (int i = 28; i >= 0; i -= 4) {
rawPutc(hex[(val >> i) & 0xF]);
}
}
/**
* @brief 检测当前实际 APB2 时钟频率(Hz)。
*
* 通过读取 RCU_CFG0 的 SCSS / AHBPSC / APB2PSC 字段,
* 实时计算 USART0 所在的 APB2 总线时钟频率,
* 而不依赖可能已过时的 SystemCoreClock 全局变量。
*
* @param stack_frame 指向异常栈帧的指针(由汇编桩压入)。
* 包含: R0, R1, R2, R3, R12, LR, PC, xPSR
* @param lr_value EXC_RETURN 值,用于判断使用 MSP 还是 PSP。
*/
static uint32_t detectApb2Clock(void) {
volatile uint32_t *rcu_cfg0 = (volatile uint32_t *)0x40021000U;
uint32_t cfg0 = *rcu_cfg0;
/* 1. 确定 SYSCLK 频率 */
uint32_t scss = (cfg0 >> 2) & 0x3U;
uint32_t sysclk;
if (scss == 0x2U) {
sysclk = 180000000U; /* PLL = 180MHz(唯一配置的 PLL 目标频率) */
} else {
sysclk = 8000000U; /* IRC8M = 8MHz 或 HXTAL(未倍频时视为 8MHz) */
}
/* 2. AHB 预分频 */
uint32_t ahbpsc = (cfg0 >> 4) & 0xFU;
uint32_t ahb_div;
switch (ahbpsc) {
case 0x8: ahb_div = 2; break;
case 0x9: ahb_div = 4; break;
case 0xA: ahb_div = 8; break;
case 0xB: ahb_div = 16; break;
case 0xC: ahb_div = 64; break;
case 0xD: ahb_div = 128; break;
case 0xE: ahb_div = 256; break;
case 0xF: ahb_div = 512; break;
default: ahb_div = 1; break;
}
uint32_t ahb_clk = sysclk / ahb_div;
/* 3. APB2 预分频 */
uint32_t apb2psc = (cfg0 >> 11) & 0x7U;
uint32_t apb2_div;
switch (apb2psc) {
case 0x4: apb2_div = 2; break;
case 0x5: apb2_div = 4; break;
case 0x6: apb2_div = 8; break;
case 0x7: apb2_div = 16; break;
default: apb2_div = 1; break;
}
return ahb_clk / apb2_div;
}
extern "C" void HardFault_Handler_C(uint32_t *stack_frame, uint32_t lr_value) {
(void)lr_value;
/* 确保 USART0 时钟可用 */
volatile uint32_t *rcu_apb2en = (volatile uint32_t *)0x40021018U;
*rcu_apb2en |= (1U << 2) | (1U << 14); /* RCU_GPIOA | RCU_USART0 */
/* 确保 PA9 处于 AF_PP 50MHz 模式(可能被其他代码覆盖) */
volatile uint32_t *gpioa_ctl1 = (volatile uint32_t *)0x40010804U;
uint32_t ctl = *gpioa_ctl1;
ctl &= ~(0xFU << 4);
ctl |= (0xBU << 4); /* PA9: AF_PP 50MHz */
*gpioa_ctl1 = ctl;
/* 根据实际 APB2 时钟计算波特率分频值 */
uint32_t apb2_clk = detectApb2Clock();
uint32_t baud_div = apb2_clk / 115200U;
if (baud_div == 0U) baud_div = 1U;
volatile uint32_t *usart_baud = (volatile uint32_t *)0x40013808U;
volatile uint32_t *usart_ctl0 = (volatile uint32_t *)0x40013800U;
*usart_baud = baud_div;
*usart_ctl0 = (1U << 3) | (1U << 13); /* TE | USARTEN */
rawPuts("\r\n!!! HardFault !!!\r\n");
if (stack_frame) {
uint32_t pc = stack_frame[6];
uint32_t lr = stack_frame[5];
uint32_t psr = stack_frame[7];
rawPuts("PC="); rawHex32(pc); rawPuts("\r\n");
rawPuts("LR="); rawHex32(lr); rawPuts("\r\n");
rawPuts("PSR="); rawHex32(psr); rawPuts("\r\n");
}
rawPuts("CFSR="); rawHex32(SCB->CFSR); rawPuts("\r\n");
rawPuts("HFSR="); rawHex32(SCB->HFSR); rawPuts("\r\n");
rawPuts("MMFAR="); rawHex32(SCB->MMFAR); rawPuts("\r\n");
rawPuts("BFAR="); rawHex32(SCB->BFAR); rawPuts("\r\n");
/* 死循环 */
while (1) {}
}
/**
* @brief HardFault_Handler 汇编桩——保存栈帧后调用 C 诊断函数。
*/
__attribute__((naked)) void HardFault_Handler(void) {
__asm volatile(
"tst lr, #4 \n" /* 检查 EXC_RETURN 位 2 */
"ite eq \n"
"mrseq r0, msp \n" /* 位 2 = 0: 使用 MSP */
"mrsne r0, psp \n" /* 位 2 = 1: 使用 PSP */
"mov r1, lr \n" /* 保存 EXC_RETURN */
"b HardFault_Handler_C \n"
);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef HARDFAULT_H
#define HARDFAULT_H
void HardFault_Diagnostic(void);
#endif
+3 -3
View File
@@ -48,8 +48,8 @@ static bool otaConfigSet(uint32_t targetAddr, uint32_t value)
return writeWord(targetAddr, value);
}
uint32_t words[6];
for(uint32_t i = 0U; i < 6U; ++i) {
uint32_t words[CONFIG_OTA_CONFIG_WORD_COUNT];
for(uint32_t i = 0U; i < CONFIG_OTA_CONFIG_WORD_COUNT; ++i) {
words[i] = *((volatile uint32_t *)(kOtaConfigPageAddr + i * 4U));
}
words[(targetAddr - kOtaConfigPageAddr) / 4U] = value;
@@ -63,7 +63,7 @@ static bool otaConfigSet(uint32_t targetAddr, uint32_t value)
}
bool ok = true;
for(uint32_t i = 0U; i < 6U; ++i) {
for(uint32_t i = 0U; i < CONFIG_OTA_CONFIG_WORD_COUNT; ++i) {
if(words[i] == kOtaNotAval) {
continue;
}