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
@@ -1,4 +1,4 @@
#ifndef REGIONS_GD32E508VE_H
#ifndef REGIONS_GD32E508VE_H
#define REGIONS_GD32E508VE_H
@@ -17,7 +17,7 @@
#define __ROM0_BASE 0x08048000
// <o> Region size [bytes] <0x0-0xFFFFFFFF:8>
// <i> Defines size of memory region. Default: 0x00080000
#define __ROM0_SIZE 0x00036000
#define __ROM0_SIZE 0x00035FFC
// </h>
// <h> __ROM1 (unused)
@@ -175,6 +175,48 @@ Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
;=== EARLY DIAG: send 'R' via raw USART0 before any C code ===
; 使 GPIOA + USART0 时钟可用
LDR R1, =0x40021018 ; RCU_APB2EN
LDR R2, [R1]
ORRS R2, R2, #(1 << 2) ; RCU_GPIOA
ORRS R2, R2, #(1 << 14) ; RCU_USART0
STR R2, [R1]
; PA9 设为 AF_PP 50MHz (CTL1 bits[7:4] = 0xB)
LDR R1, =0x40010804 ; GPIOA_CTL1
LDR R2, [R1]
LDR R3, =0xFFFFFF0F ; mask: keep all except bits[7:4]
ANDS R2, R2, R3
ORRS R2, R2, #0xB0 ; bits[7:4] = 0xB (AF_PP 50MHz)
STR R2, [R1]
; 检测当前时钟源,选择正确波特率
LDR R1, =0x40021000 ; RCU_CFG0
LDR R2, [R1]
LSRS R2, R2, #2 ; SCSS bits [3:2]
ANDS R2, R2, #0x3
CMP R2, #0x2 ; SCSS == PLL ?
LDR R3, =1562 ; 180MHz / 115200
LDR R4, =69 ; 8MHz / 115200
ITE EQ
MOVEQ R2, R3 ; PLL -> 180MHz baud divisor
MOVNE R2, R4 ; IRC8M -> 8MHz baud divisor
LDR R1, =0x40013808 ; USART0_BAUD
STR R2, [R1]
LDR R1, =0x40013800 ; USART0_CTL0
MOVS R2, #(1 << 3) ; TE
ORRS R2, R2, #(1 << 13) ; | USARTEN
STR R2, [R1]
LDR R1, =0x40013804 ; USART0_DATA
MOVS R2, #'R'
STR R2, [R1]
LDR R1, =0x40013800 ; USART0_CTL0
_rh_wait_tc
LDR R2, [R1]
LSRS R2, R2, #7 ; TC bit
BCC _rh_wait_tc
;=== END EARLY DIAG ===
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
@@ -162,7 +162,7 @@ static void _soft_delay_(uint32_t time)
*/
void SystemInit (void)
{
/* ===== DIAG: raw USART0 output at IRC8M (8MHz) ===== */
/* ===== DIAG: raw USART0 output — detect current clock for baudrate ===== */
/* Enable AFIO + GPIOA + USART0 clocks via raw register */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
/* PA9 = AF_PP, 50MHz (CTL=0b11, MD=0b11) -> CTL1 bit[4..7]=0b1011 */
@@ -172,8 +172,18 @@ void SystemInit (void)
ctl |= (0xBU << 4); /* AF_PP 50MHz */
*((volatile uint32_t *)0x40010804U) = ctl;
}
/* USART0 baud: BAUD = 8000000/115200 = 69 */
*((volatile uint32_t *)0x40013808U) = 69U;
/* Detect current system clock: if PLL is selected, assume 180MHz; otherwise IRC8M=8MHz.
* This handles both cold boot (IRC8M) and BOOT jump (PLL 180MHz). */
{
uint32_t scss = (*((volatile uint32_t *)0x40021000U) >> 2) & 0x3U; /* RCU_CFG0 SCSS bits */
uint32_t baud_div;
if (scss == 0x2U) { /* PLL selected as system clock -> 180MHz */
baud_div = 180000000U / 115200U; /* = 1562 */
} else { /* IRC8M or HXTAL -> 8MHz */
baud_div = 8000000U / 115200U; /* = 69 */
}
*((volatile uint32_t *)0x40013808U) = baud_div;
}
/* USART0 CTL0: TE + EN = bit3 + bit13 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
/* Send "APP" via USART0 DATA register */
@@ -647,8 +657,18 @@ static void system_clock_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
while((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {} } }
while(1){ }
}
/* AHB = SYSCLK */
@@ -693,8 +713,18 @@ static void system_clock_72m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
while((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {} } }
while(1){ }
}
FMC_WS = (FMC_WS & (~FMC_WS_WSCNT)) | WS_WSCNT(1);
@@ -801,8 +831,18 @@ static void system_clock_120m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
while((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {} } }
while(1){ }
}
FMC_WS = (FMC_WS & (~FMC_WS_WSCNT)) | WS_WSCNT(3);
@@ -910,8 +950,18 @@ static void system_clock_168m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
while((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {} } }
while(1){ }
}
FMC_WS = (FMC_WS & (~FMC_WS_WSCNT)) | WS_WSCNT(4);
@@ -1019,8 +1069,18 @@ static void system_clock_180m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
}
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
while((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {} } }
while(1){ }
}
FMC_WS = (FMC_WS & (~FMC_WS_WSCNT)) | WS_WSCNT(4);
+140 -30
View File
@@ -19,10 +19,12 @@ extern "C" {
#include "gd32e50x_gpio.h"
#include "gd32e50x_misc.h"
#include "gd32e50x_rcu.h"
#include "gd32e50x_dma.h"
#include "gd32e50x_spi.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "sys/systick.h"
#include "main.h"
#include <stdio.h>
#include <string.h>
@@ -44,6 +46,19 @@ static constexpr uint16_t kRxBufSize = 128U;
static constexpr uint16_t kTxBufSize = 64U;
static constexpr uint16_t kLogBufSize = 128U;
/* DMA 配置(与 BOOT SpiOta 一致:DMA0_CH2 用于 SPI0 TX) */
#define SPI_TX_DMA_PERIPH DMA0
#define SPI_TX_DMA_CH DMA_CH2
static void startSpiTxDma(const uint8_t *buf, uint16_t len)
{
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
DMA_INTC(SPI_TX_DMA_PERIPH) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, SPI_TX_DMA_CH);
DMA_CHMADDR(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = (uint32_t)buf;
DMA_CHCNT(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = len;
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) |= DMA_CHXCTL_CHEN;
}
static bool isAllBytesValue(const uint8_t *data, uint8_t len, uint8_t value)
{
if (!data || len == 0U) {
@@ -57,6 +72,24 @@ static bool isAllBytesValue(const uint8_t *data, uint8_t len, uint8_t value)
return true;
}
static bool shouldIgnoreTransaction(const uint8_t *data, uint8_t len)
{
if (!data || len == 0U) {
return true;
}
if (isAllBytesValue(data, len, 0xFFU) || isAllBytesValue(data, len, 0x00U)) {
return true;
}
/* APP 侧只接受两类事务:
* 1) 旧式单字节 0xB0
* 2) 完整协议帧(至少 5 字节)
* 短于 5 字节且不是 0xB0 的事务只能是启动切换期的残包,直接忽略。 */
return (len < kFrameOverhead) &&
!(len == 1U && data[0] == CONFIG_SPI_APP_OTA_BOOT_CMD);
}
/* ============================================================
* 静态成员
* ============================================================ */
@@ -168,13 +201,36 @@ void AppSpiCommand::configureSpi0()
spi_init(SPI0, &spi_init_struct);
spi_nss_internal_low(SPI0);
(void)spi_i2s_data_receive(SPI0);
spi_i2s_data_transmit(SPI0, 0xFFU);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
nvic_irq_enable(SPI0_IRQn, 1U, 0U);
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
spi_enable(SPI0);
}
void AppSpiCommand::configureDma()
{
rcu_periph_clock_enable(RCU_DMA0);
dma_deinit(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
dma_parameter_struct dma_cfg;
dma_struct_para_init(&dma_cfg);
dma_cfg.periph_addr = (uint32_t)(SPI0 + 0x0CU); /* SPI0_DATA 寄存器 */
dma_cfg.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_cfg.memory_addr = (uint32_t)s_txBuf;
dma_cfg.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_cfg.number = 0U;
dma_cfg.priority = DMA_PRIORITY_HIGH;
dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_cfg.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_init(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH, &dma_cfg);
dma_circulation_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
dma_channel_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
}
void AppSpiCommand::configureExti()
{
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_15);
@@ -200,6 +256,7 @@ void AppSpiCommand::init()
memset(s_logBuf, 0, sizeof(s_logBuf));
configureGpio();
configureDma();
configureSpi0();
configureExti();
}
@@ -249,17 +306,47 @@ void AppSpiCommand::processCommand()
break;
case kTypeAppPing:
/* APP 存活探测 — 回复 ACK 携带状态摘要 */
logDeferred("[SPI-APP] PING → APP_ALIVE");
/* APP 存活探测 — 回复 ACK 携带带标签的版本号
*
* 因 SPI 从机首字节延迟导致帧偏移不稳定,
* 采用“标签+值”格式,ESP32 通过扫描标签定位字段。
*
* payload 布局 (19B):
* [0] = 0xA1 魔术字
* [1] = 0x55 APP 版本标签
* [2..5] = APP 版本 (4 x uint8: major, minor, patch, build)
* [6] = 0xAA BOOT 版本标签
* [7..10]= BOOT 版本 (4 x uint8: major, minor, patch, build)
* [11..14]= uptime (uint32_t LE)
* [15..18]= 填充 0 */
{
/* payload: [0]=0xA1(魔术) [1]=运行秒数低8位 [2]=运行秒数高8位 [3]=任务位图低8位 */
extern volatile uint32_t g_app_uptime_sec;
uint8_t alive_payload[4] = {
0xA1U,
(uint8_t)(g_app_uptime_sec & 0xFFU),
(uint8_t)((g_app_uptime_sec >> 8) & 0xFFU),
(uint8_t)(TaskManager::state().task_flags & 0xFFU)
};
uint8_t alive_payload[19];
memset(alive_payload, 0, sizeof(alive_payload));
alive_payload[0] = 0xA1U; /* magic */
alive_payload[1] = 0x55U; /* APP version tag */
alive_payload[2] = (uint8_t)APP_FW_VERSION_MAJOR;
alive_payload[3] = (uint8_t)APP_FW_VERSION_MINOR;
alive_payload[4] = (uint8_t)APP_FW_VERSION_PATCH;
alive_payload[5] = (uint8_t)APP_FW_VERSION_BUILD;
alive_payload[6] = 0xAAU; /* BOOT version tag */
/* BOOT 版本 (从 Flash OTA 配置页 word6/word7 读取) */
const volatile uint32_t *bv0 = (const volatile uint32_t *)CONFIG_BOOT_VERSION_ADDR0;
const volatile uint32_t *bv1 = (const volatile uint32_t *)CONFIG_BOOT_VERSION_ADDR1;
uint32_t bw0 = *bv0;
uint32_t bw1 = *bv1;
alive_payload[7] = (uint8_t)(bw0 & 0xFFU); /* boot major */
alive_payload[8] = (uint8_t)((bw0 >> 16) & 0xFFU); /* boot minor */
alive_payload[9] = (uint8_t)(bw1 & 0xFFU); /* boot patch */
alive_payload[10] = (uint8_t)((bw1 >> 16) & 0xFFU); /* boot build */
/* uptime (uint32_t LE) */
alive_payload[11] = (uint8_t)(g_app_uptime_sec & 0xFFU);
alive_payload[12] = (uint8_t)((g_app_uptime_sec >> 8) & 0xFFU);
alive_payload[13] = (uint8_t)((g_app_uptime_sec >> 16) & 0xFFU);
alive_payload[14] = (uint8_t)((g_app_uptime_sec >> 24) & 0xFFU);
spi_build_frame(kRspAck, alive_payload, sizeof(alive_payload), s_txBuf, &tx_len);
}
break;
@@ -335,11 +422,39 @@ void AppSpiCommand::poll()
void AppSpiCommand::onCsAssert()
{
if (s_csActive) {
return;
}
s_csActive = true;
s_rxLen = 0U;
s_txPos = 0U;
/* 停止 DMA 并清理旧状态 */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
/* 与 BOOT 一致:清除 SPI 双缓冲中的残留数据 */
spi_i2s_data_transmit(SPI0, 0xFFU);
if (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) != RESET) {
spi_i2s_data_transmit(SPI0, 0xFFU);
}
/* 清除错误标志 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
(void)spi_i2s_data_receive(SPI0);
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
}
/* 启动 DMA 发送响应帧(与 BOOT 完全一致) */
if (s_txLen > 0U) {
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE);
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiTxDma(s_txBuf, s_txLen);
} else {
/* 无响应时发送全 0xFF 填充 */
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiTxDma(s_txBuf, kTxBufSize);
}
}
@@ -349,8 +464,19 @@ void AppSpiCommand::onCsRelease()
return;
}
s_csActive = false;
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
if (isAllBytesValue(s_rxBuf, s_rxLen, 0xFFU)) {
/* 停止 DMA */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
uint8_t last_byte = (uint8_t)(spi_i2s_data_receive(SPI0) & 0xFFU);
if (s_rxLen < sizeof(s_rxBuf)) {
s_rxBuf[s_rxLen++] = last_byte;
}
}
if (shouldIgnoreTransaction(s_rxBuf, s_rxLen)) {
s_rxLen = 0U;
return;
}
@@ -373,23 +499,7 @@ void AppSpiCommand::onInterrupt()
}
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_TBE) != RESET) {
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert();
}
uint8_t tx_byte = 0xFFU;
if (s_csActive && s_txPos < s_txLen) {
tx_byte = s_txBuf[s_txPos++];
if (s_txPos >= s_txLen) {
s_txLen = 0U;
s_txPos = 0U;
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
}
} else {
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
}
spi_i2s_data_transmit(SPI0, tx_byte);
}
/* TBE 已由 DMA 替代,不再处理 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
(void)spi_i2s_data_receive(SPI0);
+5 -1
View File
@@ -1,4 +1,4 @@
#ifndef DEVICE_APP_SPI_COMMAND_H
#ifndef DEVICE_APP_SPI_COMMAND_H
#define DEVICE_APP_SPI_COMMAND_H
#include <stdint.h>
@@ -13,6 +13,9 @@
*
* 调试日志采用延迟输出:processCommand() 只写缓冲+置标志,
* poll() 中再 printf,避免在帧处理路径上耗时。
*
* 发送路径使用 DMA0_CH2 推 s_txBuf → SPI0_DATA,
* 与 BOOT 的 SpiOta 实现保持一致;接收仍用 RBNE 字节中断。
*/
class AppSpiCommand {
public:
@@ -44,6 +47,7 @@ private:
static void configureGpio();
static void configureSpi0();
static void configureDma();
static void configureExti();
static void onCsAssert();
static void onCsRelease();
@@ -0,0 +1,55 @@
#ifndef DEVICE_APP_SPI_COMMAND_H
#define DEVICE_APP_SPI_COMMAND_H
#include <stdint.h>
/**
* @brief APP 运行期 SPI 命令从机模块。
*
* 复用 BOOT 的 SpiOta 协议帧格式(SOF+TYPE+LEN+PAYLOAD+CRC):
* - TYPE=0xB0 → 请求进入 Bootloader,回复 ACK 帧
* - TYPE=0x01/0x02/0x03(OTA 命令)→ 回复 WAIT 帧(不在 BOOT 模式)
* - 旧式单字节 0xB0 命令仍兼容
*
* 调试日志采用延迟输出:processCommand() 只写缓冲+置标志,
* poll() 中再 printf,避免在帧处理路径上耗时。
*/
class AppSpiCommand {
public:
/** @brief 初始化 SPI0 从机命令通道。 */
static void init();
/** @brief 主循环轮询:打印延迟日志。 */
static void poll();
/** @brief SPI0 中断处理入口。 */
static void onInterrupt();
/** @brief CS 边沿中断处理入口。 */
static void onCsExtiInterrupt();
private:
static constexpr uint16_t kRxBufSize = 128U;
static constexpr uint16_t kTxBufSize = 64U;
static constexpr uint16_t kLogBufSize = 128U;
static volatile bool s_csActive;
static volatile uint8_t s_rxLen;
static volatile uint8_t s_txLen;
static volatile uint8_t s_txPos;
static volatile bool s_logPending;
static uint8_t s_rxBuf[kRxBufSize];
static uint8_t s_txBuf[kTxBufSize];
static char s_logBuf[kLogBufSize];
static void configureGpio();
static void configureSpi0();
static void configureExti();
static void onCsAssert();
static void onCsRelease();
static void processCommand();
static void logDeferred(const char *msg);
static void logDeferredFmt(const char *fmt, uint8_t val);
};
#endif /* DEVICE_APP_SPI_COMMAND_H */
+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;
}