Compare commits
3
Commits
6aa21a1f9d
...
3691cf2aba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3691cf2aba | ||
|
|
33037bc5a3 | ||
|
|
b78e36ff37 |
@@ -38,11 +38,19 @@ struct CanChannelConfig {
|
|||||||
|
|
||||||
constexpr CanChannelConfig kChannels[] = {
|
constexpr CanChannelConfig kChannels[] = {
|
||||||
{ CAN0, RCU_CAN0, GPIOA, GPIO_PIN_12, GPIOA, GPIO_PIN_11, GPIOA, GPIO_PIN_8, CAN0_RX0_IRQn, 0U },
|
{ CAN0, RCU_CAN0, GPIOA, GPIO_PIN_12, GPIOA, GPIO_PIN_11, GPIOA, GPIO_PIN_8, CAN0_RX0_IRQn, 0U },
|
||||||
{ CAN1, RCU_CAN1, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_14, CAN1_RX0_IRQn, 14U },
|
{ CAN1, RCU_CAN1, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_14, CAN1_RX0_IRQn, 14U },
|
||||||
{ CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_RX0_IRQn, 2U },
|
{ CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_RX0_IRQn, 2U },
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
|
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
|
||||||
|
|
||||||
|
/* ===== 通道使能开关:在 sys/config.h 中修改 CONFIG_CAN_CHx_ENABLE ===== */
|
||||||
|
constexpr bool kChannelEnabled[kChannelCount] = {
|
||||||
|
CONFIG_CAN_CH0_ENABLE,
|
||||||
|
CONFIG_CAN_CH1_ENABLE,
|
||||||
|
CONFIG_CAN_CH2_ENABLE,
|
||||||
|
};
|
||||||
|
|
||||||
constexpr uint8_t kTxQueueDepth = 4U;
|
constexpr uint8_t kTxQueueDepth = 4U;
|
||||||
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
|
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
|
||||||
|
|
||||||
@@ -98,8 +106,8 @@ static CanSelfTestResult runSelfTestFrame(uint8_t channelIndex, uint32_t extId,
|
|||||||
result.stat = CAN_STAT(canPeriph);
|
result.stat = CAN_STAT(canPeriph);
|
||||||
result.err = CAN_ERR(canPeriph);
|
result.err = CAN_ERR(canPeriph);
|
||||||
result.tec = (result.err >> 16) & 0xFFU;
|
result.tec = (result.err >> 16) & 0xFFU;
|
||||||
result.rec = (result.err >> 8) & 0xFFU;
|
result.rec = (result.err >> 24) & 0xFFU;
|
||||||
result.busOff = (result.stat & (1U << 8)) != 0U;
|
result.busOff = (result.err & CAN_ERR_BOERR) != 0U;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +160,14 @@ uint32_t CanFd::s_feedbackSeq = 0U;
|
|||||||
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
|
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
|
||||||
static TxQueue s_txQueues[3]{};
|
static TxQueue s_txQueues[3]{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询指定通道是否已使能。
|
||||||
|
*/
|
||||||
|
bool CanFd::isChannelEnabled(uint8_t channelIndex)
|
||||||
|
{
|
||||||
|
return (channelIndex < kChannelCount) && kChannelEnabled[channelIndex];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化三路 CANFD 相关 GPIO。
|
* @brief 初始化三路 CANFD 相关 GPIO。
|
||||||
* @param[in] 无。
|
* @param[in] 无。
|
||||||
@@ -166,7 +182,9 @@ void CanFd::initGpio()
|
|||||||
rcu_periph_clock_enable(RCU_GPIOE);
|
rcu_periph_clock_enable(RCU_GPIOE);
|
||||||
rcu_periph_clock_enable(RCU_AF);
|
rcu_periph_clock_enable(RCU_AF);
|
||||||
#ifdef GPIO_CAN1_REMAP
|
#ifdef GPIO_CAN1_REMAP
|
||||||
gpio_pin_remap_config(GPIO_CAN1_REMAP, ENABLE);
|
/* CAN1_REMAP=0: CAN1_RX/PB12, CAN1_TX/PB13
|
||||||
|
* GD32E50x_用户手册_Rev2.2.pdf AFIO_PCF0 bit22 */
|
||||||
|
gpio_pin_remap_config(GPIO_CAN1_REMAP, DISABLE);
|
||||||
#endif
|
#endif
|
||||||
#ifdef AFIO_PE0_CAN2_CFG
|
#ifdef AFIO_PE0_CAN2_CFG
|
||||||
gpio_afio_port_config(AFIO_PE0_CAN2_CFG, ENABLE);
|
gpio_afio_port_config(AFIO_PE0_CAN2_CFG, ENABLE);
|
||||||
@@ -174,6 +192,11 @@ void CanFd::initGpio()
|
|||||||
#ifdef AFIO_PE1_CAN2_CFG
|
#ifdef AFIO_PE1_CAN2_CFG
|
||||||
gpio_afio_port_config(AFIO_PE1_CAN2_CFG, ENABLE);
|
gpio_afio_port_config(AFIO_PE1_CAN2_CFG, ENABLE);
|
||||||
#endif
|
#endif
|
||||||
|
/* CAN0/CAN1 引脚选择由 AFIO_PCF0 的 remap 位控制:
|
||||||
|
* CAN0_REMAP[1:0]=00(复位默认) → CAN0_RX/PA11, CAN0_TX/PA12 (bit14:13)
|
||||||
|
* CAN1_REMAP=0(复位默认) → CAN1_RX/PB12, CAN1_TX/PB13 (bit22)
|
||||||
|
* AFIO_PCFA/PCFB 的 AFCFG 位不含 CAN0/CAN1 选项(GD32E50x_用户手册_Rev2.2.pdf
|
||||||
|
* 第8.5.17/8.5.18节),不可用 AFCFG 配置 CAN0/CAN1。 */
|
||||||
|
|
||||||
for(size_t i = 0; i < kChannelCount; ++i) {
|
for(size_t i = 0; i < kChannelCount; ++i) {
|
||||||
const auto &ch = kChannels[i];
|
const auto &ch = kChannels[i];
|
||||||
@@ -378,6 +401,10 @@ void CanFd::init()
|
|||||||
|
|
||||||
for(size_t i = 0; i < kChannelCount; ++i) {
|
for(size_t i = 0; i < kChannelCount; ++i) {
|
||||||
const auto &ch = kChannels[i];
|
const auto &ch = kChannels[i];
|
||||||
|
if (!kChannelEnabled[i]) {
|
||||||
|
printf("[CAN] CH%u DISABLED, skip init\r\n", static_cast<unsigned>(i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
rcu_periph_clock_enable(ch.canRcu);
|
rcu_periph_clock_enable(ch.canRcu);
|
||||||
gpio_bit_reset(ch.slpPort, ch.slpPin); /* STB=0 -> Normal */
|
gpio_bit_reset(ch.slpPort, ch.slpPin); /* STB=0 -> Normal */
|
||||||
const uint8_t rxLevelBefore = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
|
const uint8_t rxLevelBefore = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
|
||||||
@@ -408,12 +435,9 @@ void CanFd::init()
|
|||||||
static_cast<unsigned>(retry + 1U));
|
static_cast<unsigned>(retry + 1U));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GD32E508: CAN0/CAN1 共享 28 个滤波器 bank,必须显式设置 CAN1 起始 bank */
|
/* 滤波器配置延迟到循环结束后统一执行:
|
||||||
if (ch.canPeriph == CAN1) {
|
* CAN1 的 hardResetCan 含 SWRST,会清零 CAN0/CAN1 共享滤波器寄存器,
|
||||||
can1_filter_start_bank(14U); /* CAN0: bank 0-13, CAN1: bank 14-27 */
|
* 若在循环内配 CAN0 filter 后再做 CAN1 hardReset,CAN0 filter 会被破坏。 */
|
||||||
}
|
|
||||||
|
|
||||||
configureRxFilter(ch.canPeriph, ch.filterNumber, s_mode);
|
|
||||||
|
|
||||||
/* 退出 init mode,确保进入 NORMAL 工作模式 */
|
/* 退出 init mode,确保进入 NORMAL 工作模式 */
|
||||||
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
|
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
|
||||||
@@ -441,7 +465,7 @@ void CanFd::init()
|
|||||||
const uint32_t errAfterExit = CAN_ERR(ch.canPeriph);
|
const uint32_t errAfterExit = CAN_ERR(ch.canPeriph);
|
||||||
const uint8_t rxLevelAfter = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
|
const uint8_t rxLevelAfter = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
|
||||||
const uint32_t tecAfterExit = (errAfterExit >> 16) & 0xFFU;
|
const uint32_t tecAfterExit = (errAfterExit >> 16) & 0xFFU;
|
||||||
const uint32_t recAfterExit = (errAfterExit >> 8) & 0xFFU;
|
const uint32_t recAfterExit = (errAfterExit >> 24) & 0xFFU;
|
||||||
printf("[CAN-DIAG] CH%u init_nom=%u init_fd=%u exit_ok=%u wait=%lu rx_before=%u rx_after=%u stb=%u STAT=0x%08lX ERR=0x%08lX TEC=%lu REC=%lu\r\n",
|
printf("[CAN-DIAG] CH%u init_nom=%u init_fd=%u exit_ok=%u wait=%lu rx_before=%u rx_after=%u stb=%u STAT=0x%08lX ERR=0x%08lX TEC=%lu REC=%lu\r\n",
|
||||||
static_cast<unsigned>(i),
|
static_cast<unsigned>(i),
|
||||||
nominalOk ? 1U : 0U,
|
nominalOk ? 1U : 0U,
|
||||||
@@ -469,6 +493,20 @@ void CanFd::init()
|
|||||||
|
|
||||||
s_nextConsumeChannel = 0U;
|
s_nextConsumeChannel = 0U;
|
||||||
|
|
||||||
|
/* ===== 统一配置滤波器(必须在所有 hardResetCan 完成之后) =====
|
||||||
|
* CAN1 hardResetCan 的 SWRST 会清零 CAN0/CAN1 共享滤波器寄存器域。
|
||||||
|
* 若在循环内先配 CAN0 filter、后做 CAN1 hardReset,CAN0 的 filter 0 会被
|
||||||
|
* 硬件清零(CAN_FW bit0 = 0),导致 CAN0 无法接收任何帧。
|
||||||
|
* 修复方案:所有 hardReset 完成后,再统一配 HBC1F + 三路 filter。 */
|
||||||
|
can1_filter_start_bank(14U); /* CAN0: bank 0-13, CAN1: bank 14-27 */
|
||||||
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
|
if (!kChannelEnabled[i]) continue;
|
||||||
|
configureRxFilter(kChannels[i].canPeriph, kChannels[i].filterNumber, s_mode);
|
||||||
|
}
|
||||||
|
printf("[CAN] filters configured post-init: HBC1F=%lu FW=0x%08lX\r\n",
|
||||||
|
static_cast<unsigned long>((CAN_FCTL(CAN0) >> 8) & 0x3FU),
|
||||||
|
static_cast<unsigned long>(CAN_FW(CAN0)));
|
||||||
|
|
||||||
/* ===== 初始化结果汇总 ===== */
|
/* ===== 初始化结果汇总 ===== */
|
||||||
printf("=== CAN INIT RESULT ===\r\n");
|
printf("=== CAN INIT RESULT ===\r\n");
|
||||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
@@ -498,9 +536,30 @@ void CanFd::init()
|
|||||||
static_cast<unsigned long>((fw2 >> 2) & 1U));
|
static_cast<unsigned long>((fw2 >> 2) & 1U));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 初始化后错误计数器快照(不发帧,避免启动时累积 TEC)===== */
|
||||||
|
printf("=== CAN INIT ERR STATE ===\r\n");
|
||||||
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
|
const auto &ch = kChannels[i];
|
||||||
|
uint32_t err = CAN_ERR(ch.canPeriph);
|
||||||
|
uint32_t tec = (err >> 16) & 0xFFU;
|
||||||
|
uint32_t rec = (err >> 24) & 0xFFU;
|
||||||
|
printf("CH%u: ERR=0x%08lX TEC=%lu REC=%lu boff=%u epv=%u ew=%u\r\n",
|
||||||
|
static_cast<unsigned>(i),
|
||||||
|
static_cast<unsigned long>(err),
|
||||||
|
static_cast<unsigned long>(tec),
|
||||||
|
static_cast<unsigned long>(rec),
|
||||||
|
(err & CAN_ERR_BOERR) ? 1U : 0U, /* BOFF bit2 */
|
||||||
|
(err & CAN_ERR_PERR) ? 1U : 0U, /* EPV bit1 */
|
||||||
|
(err & CAN_ERR_WERR) ? 1U : 0U); /* EWARN bit0 */
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */
|
/* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */
|
||||||
printf("=== CAN SELF-TEST (normal mode) ===\r\n");
|
printf("=== CAN SELF-TEST (normal mode) ===\r\n");
|
||||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
|
if (!kChannelEnabled[i]) {
|
||||||
|
printf("CH%u: SKIP(disabled)\r\n", static_cast<unsigned>(i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const uint8_t ch = static_cast<uint8_t>(i);
|
const uint8_t ch = static_cast<uint8_t>(i);
|
||||||
uint8_t data8[8] = {0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57};
|
uint8_t data8[8] = {0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57};
|
||||||
uint8_t data64[64];
|
uint8_t data64[64];
|
||||||
@@ -525,11 +584,17 @@ void CanFd::init()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */
|
/* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */
|
||||||
printf("=== CAN LOOPBACK (internal) ===\r\n");
|
printf("=== CAN LOOPBACK (internal) ===\r\n");
|
||||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
const auto &ch = kChannels[i];
|
const auto &ch = kChannels[i];
|
||||||
|
|
||||||
|
if (!kChannelEnabled[i]) {
|
||||||
|
printf("CH%u: SKIP(disabled)\r\n", static_cast<unsigned>(i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) {
|
if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) {
|
||||||
printf("CH%u: SKIP(stuck_init)\r\n", static_cast<unsigned>(i));
|
printf("CH%u: SKIP(stuck_init)\r\n", static_cast<unsigned>(i));
|
||||||
continue;
|
continue;
|
||||||
@@ -632,14 +697,74 @@ void CanFd::init()
|
|||||||
brsOk ? "PASS" : "FAIL",
|
brsOk ? "PASS" : "FAIL",
|
||||||
static_cast<unsigned long>(tecLb));
|
static_cast<unsigned long>(tecLb));
|
||||||
|
|
||||||
/* 回到 normal */
|
/* 回环前后寄存器快照,用于诊断 LCMOD 清除是否干净 */
|
||||||
|
uint32_t btBeforeExit = CAN_BT(ch.canPeriph);
|
||||||
|
uint32_t ctlBeforeExit = CAN_CTL(ch.canPeriph);
|
||||||
|
|
||||||
|
/* 回到 NORMAL 模式:进 init → 清 LCMOD+SCMOD → 退出 init */
|
||||||
CAN_CTL(ch.canPeriph) |= CAN_CTL_IWMOD;
|
CAN_CTL(ch.canPeriph) |= CAN_CTL_IWMOD;
|
||||||
for (volatile uint32_t t = 0; t < 100000U; ++t) { if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) break; }
|
for (volatile uint32_t t = 0; t < 100000U; ++t) { if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) break; }
|
||||||
CAN_BT(ch.canPeriph) &= ~(3U << 30); /* 清 LCMOD+SCMOD */
|
CAN_BT(ch.canPeriph) &= ~(3U << 30); /* 清 LCMOD+SCMOD */
|
||||||
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
|
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
|
||||||
for (volatile uint32_t t = 0; t < 100000U; ++t) { if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break; }
|
for (volatile uint32_t t = 0; t < 200000U; ++t) {
|
||||||
|
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t errAfterLb = CAN_ERR(ch.canPeriph);
|
||||||
|
uint32_t tecAfterLb = (errAfterLb >> 16) & 0xFFU;
|
||||||
|
uint32_t recAfterLb = (errAfterLb >> 24) & 0xFFU;
|
||||||
|
uint32_t statAfterLb = CAN_STAT(ch.canPeriph);
|
||||||
|
printf("CH%u: post-lb TEC=%lu REC=%lu STAT=0x%08lX BT=0x%08lX CTL=0x%08lX\r\n",
|
||||||
|
static_cast<unsigned>(i),
|
||||||
|
static_cast<unsigned long>(tecAfterLb),
|
||||||
|
static_cast<unsigned long>(recAfterLb),
|
||||||
|
static_cast<unsigned long>(statAfterLb),
|
||||||
|
static_cast<unsigned long>(CAN_BT(ch.canPeriph)),
|
||||||
|
static_cast<unsigned long>(CAN_CTL(ch.canPeriph)));
|
||||||
}
|
}
|
||||||
printf("========================\r\n");
|
printf("========================\r\n");
|
||||||
|
|
||||||
|
/* ===== 初始化后全寄存器快照:诊断 CAN0/CAN1 不收帧 ===== */
|
||||||
|
printf("=== POST-INIT FULL REG DUMP ===\r\n");
|
||||||
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
|
const auto &ch = kChannels[i];
|
||||||
|
uint32_t ctl = CAN_CTL(ch.canPeriph);
|
||||||
|
uint32_t stat = CAN_STAT(ch.canPeriph);
|
||||||
|
uint32_t bt = CAN_BT(ch.canPeriph);
|
||||||
|
uint32_t dbt = CAN_DBT(ch.canPeriph);
|
||||||
|
uint32_t err = CAN_ERR(ch.canPeriph);
|
||||||
|
uint32_t inten = CAN_INTEN(ch.canPeriph);
|
||||||
|
uint32_t fw = (ch.canPeriph == CAN2) ? CAN_FW(CAN2) : CAN_FW(CAN0);
|
||||||
|
uint32_t fctl = CAN_FCTL(ch.canPeriph);
|
||||||
|
uint32_t rfl0 = CAN_RFIFO0(ch.canPeriph) & 0x3U; /* RFL0 bits 1:0 */
|
||||||
|
|
||||||
|
bool normalMode = ((stat & CAN_STAT_IWS) == 0U) && ((bt & (3U << 30)) == 0U);
|
||||||
|
printf("CH%u: %s CTL=0x%08lX STAT=0x%08lX BT=0x%08lX DBT=0x%08lX\r\n"
|
||||||
|
" ERR=0x%08lX INTEN=0x%08lX FCTL=0x%08lX FW=0x%08lX RFL0=%lu\r\n",
|
||||||
|
static_cast<unsigned>(i),
|
||||||
|
normalMode ? "NORMAL" : "!!!NOT_NORMAL!!!",
|
||||||
|
static_cast<unsigned long>(ctl),
|
||||||
|
static_cast<unsigned long>(stat),
|
||||||
|
static_cast<unsigned long>(bt),
|
||||||
|
static_cast<unsigned long>(dbt),
|
||||||
|
static_cast<unsigned long>(err),
|
||||||
|
static_cast<unsigned long>(inten),
|
||||||
|
static_cast<unsigned long>(fctl),
|
||||||
|
static_cast<unsigned long>(fw),
|
||||||
|
static_cast<unsigned long>(rfl0));
|
||||||
|
|
||||||
|
/* 逐位解析关键标志 */
|
||||||
|
printf(" IWMOD=%u SLPWMOD=%u ARD=%u ABOR=%u | IWS=%u SLPWS=%u | LCMOD=%u SCMOD=%u\r\n",
|
||||||
|
(ctl & CAN_CTL_IWMOD) ? 1U : 0U,
|
||||||
|
(ctl & CAN_CTL_SLPWMOD) ? 1U : 0U, /* bit1: sleep mode request */
|
||||||
|
(ctl & CAN_CTL_ARD) ? 1U : 0U, /* bit4: auto retrans disable */
|
||||||
|
(ctl & CAN_CTL_ABOR) ? 1U : 0U, /* bit6: auto bus-off recovery */
|
||||||
|
(stat & CAN_STAT_IWS) ? 1U : 0U,
|
||||||
|
(stat & CAN_STAT_SLPWS) ? 1U : 0U,
|
||||||
|
(bt & (1U << 30)) ? 1U : 0U, /* LCMOD */
|
||||||
|
(bt & (1U << 31)) ? 1U : 0U); /* SCMOD */
|
||||||
|
}
|
||||||
|
printf("===============================\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -818,10 +943,22 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
|
|||||||
bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||||
const uint8_t *data, uint8_t dlc)
|
const uint8_t *data, uint8_t dlc)
|
||||||
{
|
{
|
||||||
if (channelIndex >= kChannelCount || data == nullptr || dlc == 0U || dlc > 64U) {
|
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]
|
||||||
|
|| data == nullptr || dlc == 0U || dlc > 64U) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 总线健康检查:BUS_OFF 或 TEC 过高时跳过发送,防止测试帧把通道推向 BUS_OFF */
|
||||||
|
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
|
||||||
|
const uint32_t errReg = CAN_ERR(canPeriph);
|
||||||
|
if (errReg & CAN_ERR_BOERR) {
|
||||||
|
return false; /* 通道处于 BUS_OFF,不发送 */
|
||||||
|
}
|
||||||
|
const uint32_t tec = (errReg >> 16) & 0xFFU;
|
||||||
|
if (tec > 96U) {
|
||||||
|
return false; /* TEC 过高(>75% 错误被动阈值),暂停发送保护通道 */
|
||||||
|
}
|
||||||
|
|
||||||
can_trasnmit_message_struct tx_msg{};
|
can_trasnmit_message_struct tx_msg{};
|
||||||
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg);
|
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg);
|
||||||
tx_msg.tx_efid = ext_id;
|
tx_msg.tx_efid = ext_id;
|
||||||
@@ -841,7 +978,6 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
|||||||
tx_msg.tx_data[i] = data[i];
|
tx_msg.tx_data[i] = data[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
|
|
||||||
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
|
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
|
||||||
|
|
||||||
/* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
|
/* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
|
||||||
@@ -969,7 +1105,7 @@ static bool bridgeTransmitRaw(uint32_t canPeriph, const can_receive_message_stru
|
|||||||
*/
|
*/
|
||||||
void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||||
{
|
{
|
||||||
if(channelIndex >= kChannelCount) return;
|
if(channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) return;
|
||||||
const auto &ch = kChannels[channelIndex];
|
const auto &ch = kChannels[channelIndex];
|
||||||
|
|
||||||
const bool fifoFull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFF0) != RESET);
|
const bool fifoFull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFF0) != RESET);
|
||||||
@@ -1148,6 +1284,14 @@ void CanFd::refreshFeedbackSnapshot()
|
|||||||
}
|
}
|
||||||
|
|
||||||
s_feedbackSeq++;
|
s_feedbackSeq++;
|
||||||
|
/* 每 5 秒打印一次 seq 心跳,确认 refreshFeedbackSnapshot 在运行 */
|
||||||
|
if (s_feedbackSeq % 5U == 0U) {
|
||||||
|
printf("[CAN-FB] refresh seq=%lu rx0=%lu rx1=%lu rx2=%lu\r\n",
|
||||||
|
(unsigned long)s_feedbackSeq,
|
||||||
|
(unsigned long)s_channelStats[0].rx_count,
|
||||||
|
(unsigned long)s_channelStats[1].rx_count,
|
||||||
|
(unsigned long)s_channelStats[2].rx_count);
|
||||||
|
}
|
||||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
s_feedback[i].snapshot_seq = s_feedbackSeq;
|
s_feedback[i].snapshot_seq = s_feedbackSeq;
|
||||||
s_feedback[i].stats = s_channelStats[i];
|
s_feedback[i].stats = s_channelStats[i];
|
||||||
@@ -1165,8 +1309,33 @@ void CanFd::refreshFeedbackSnapshot()
|
|||||||
s_feedback[i].stats.bus_state = busState;
|
s_feedback[i].stats.bus_state = busState;
|
||||||
s_idSnapshot[i].seq = s_feedbackSeq;
|
s_idSnapshot[i].seq = s_feedbackSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* BUS_OFF 状态变化检测:仅在状态切换时打印,避免刷屏 */
|
||||||
|
static uint8_t s_prevBusState[3] = {0xFF, 0xFF, 0xFF}; /* 0xFF=首次 */
|
||||||
|
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||||
|
uint8_t cur = s_feedback[i].stats.bus_state;
|
||||||
|
if (cur != s_prevBusState[i]) {
|
||||||
|
const char *stateStr = "NORMAL";
|
||||||
|
if (cur & (1U << 0)) stateStr = "INIT_MODE";
|
||||||
|
else if (cur & (1U << 3)) stateStr = "BUS_OFF";
|
||||||
|
else if (cur & (1U << 2)) stateStr = "ERR_PASSIVE";
|
||||||
|
else if (cur & (1U << 4)) stateStr = "ERR_WARN";
|
||||||
|
printf("[CAN-BUS] CH%u: %s -> %s TEC=%lu REC=%lu seq=%lu\r\n",
|
||||||
|
static_cast<unsigned>(i),
|
||||||
|
(s_prevBusState[i] == 0xFF) ? "?" :
|
||||||
|
(s_prevBusState[i] & (1U << 3)) ? "BUS_OFF" :
|
||||||
|
(s_prevBusState[i] & (1U << 2)) ? "ERR_PASSIVE" :
|
||||||
|
(s_prevBusState[i] & (1U << 4)) ? "ERR_WARN" : "NORMAL",
|
||||||
|
stateStr,
|
||||||
|
static_cast<unsigned long>(hw_tec[i]),
|
||||||
|
static_cast<unsigned long>(hw_rec[i]),
|
||||||
|
static_cast<unsigned long>(s_feedbackSeq));
|
||||||
|
s_prevBusState[i] = cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 拷贝指定通道的最新反馈快照。
|
* @brief 拷贝指定通道的最新反馈快照。
|
||||||
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ public:
|
|||||||
|
|
||||||
static void init();
|
static void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询指定通道是否已使能。
|
||||||
|
* @param[in] channelIndex 通道索引 0/1/2。
|
||||||
|
* @retval bool true=使能,false=禁用。
|
||||||
|
*/
|
||||||
|
static bool isChannelEnabled(uint8_t channelIndex);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 切换 CANFD 接收过滤模式。
|
* @brief 切换 CANFD 接收过滤模式。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,17 +16,28 @@ public:
|
|||||||
|
|
||||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||||
{
|
{
|
||||||
bus.postLog("BOOT_REQ → 进BOOT");
|
bus.postLog("BOOT_REQ → 进BOOT(异步)");
|
||||||
|
|
||||||
const bool ok = OtaBootRequest::requestEnterBoot();
|
/* 立即装 0xB5 ACK,让 ESP32 尽快收到确认。
|
||||||
uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK
|
* Flash 操作延迟到主循环执行,避免阻塞 SPI ISR。
|
||||||
: CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR;
|
*
|
||||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
* ESP32 appRequestBootloader() 发送单字节 0xB0 后,逐字节轮询读回 0xB5。
|
||||||
SpiDefs::kRspAck, &ack, 1U);
|
* 必须返回裸字节 ACK,不能用 buildResponse() 构建帧式 ACK
|
||||||
|
* (帧式 ACK 以 0xA5 开头,ESP32 永远匹配不到 0xB5)。
|
||||||
if (ok) {
|
* tx_buf[0] 必须是 ack 字节本身,因为 ESP32 只读首个 MISO 字节。 */
|
||||||
TaskManager::set(1UL << TASK_HEARTBEAT);
|
if (ctx.tx_buf && ctx.tx_len && ctx.tx_buf_size >= 4U) {
|
||||||
|
ctx.tx_buf[0] = CONFIG_SPI_APP_OTA_BOOT_ACK_OK;
|
||||||
|
ctx.tx_buf[1] = CONFIG_SPI_APP_OTA_BOOT_CMD;
|
||||||
|
ctx.tx_buf[2] = 0x4FU;
|
||||||
|
ctx.tx_buf[3] = 0x54U;
|
||||||
|
*ctx.tx_len = 4U;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 设置 pending 标志,主循环执行 Flash 操作 + 复位。
|
||||||
|
* 原来在 ISR 内直接调 requestEnterBoot() 会执行 Flash 页擦除(20-30ms),
|
||||||
|
* 导致 CPU 总线停顿、SPI RBNE 中断无法响应、ESP32 ACK 轮询丢字节。 */
|
||||||
|
OtaBootRequest::requestEnterBootAsync();
|
||||||
|
TaskManager::set(1UL << TASK_HEARTBEAT);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -38,17 +38,19 @@
|
|||||||
#include "device/spi/app_spi_command_defs.h"
|
#include "device/spi/app_spi_command_defs.h"
|
||||||
#include "device/canfd.h"
|
#include "device/canfd.h"
|
||||||
#include "sys/config.h"
|
#include "sys/config.h"
|
||||||
|
#include "sys/ota_boot_request.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace {
|
/* ---- 文件内共享常量与辅助函数(供匿名命名空间内的 handler 与
|
||||||
|
* 全局作用域的 canFeedbackPushStep() 共同复用) ---- */
|
||||||
|
|
||||||
constexpr uint8_t kFeedbackChannelCount = 3U;
|
static constexpr uint8_t kFeedbackChannelCount = 3U;
|
||||||
constexpr uint16_t kFeedbackPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */
|
static constexpr uint16_t kFeedbackPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */
|
||||||
constexpr uint8_t kPageEntries = 8U;
|
static constexpr uint8_t kPageEntries = 8U;
|
||||||
constexpr uint16_t kFixedHeaderLen = 28U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4)+tec(1)+rec(1)+bus_state(1) */
|
static constexpr uint16_t kFixedHeaderLen = 28U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4)+tec(1)+rec(1)+bus_state(1) */
|
||||||
|
|
||||||
void writeU32Le(uint8_t *dst, uint32_t value)
|
static void writeU32Le(uint8_t *dst, uint32_t value)
|
||||||
{
|
{
|
||||||
dst[0] = static_cast<uint8_t>(value & 0xFFU);
|
dst[0] = static_cast<uint8_t>(value & 0xFFU);
|
||||||
dst[1] = static_cast<uint8_t>((value >> 8) & 0xFFU);
|
dst[1] = static_cast<uint8_t>((value >> 8) & 0xFFU);
|
||||||
@@ -56,6 +58,90 @@ void writeU32Le(uint8_t *dst, uint32_t value)
|
|||||||
dst[3] = static_cast<uint8_t>((value >> 24) & 0xFFU);
|
dst[3] = static_cast<uint8_t>((value >> 24) & 0xFFU);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 构建一页 CAN 反馈 payload(与 ESP32 canFdPollFeedback 解析格式一致)。
|
||||||
|
* @param[in] channel 目标通道 0/1/2。
|
||||||
|
* @param[in] page 页号 0/1。
|
||||||
|
* @param[out] payload 输出缓冲(至少 kFeedbackPayloadLen 字节)。
|
||||||
|
* @param[in] max_len 缓冲最大长度。
|
||||||
|
* @return 构建完成的 payload 长度;0 表示参数错误。
|
||||||
|
*
|
||||||
|
* 提取自 handleFeedback(),供被动响应与主动推送复用。
|
||||||
|
*/
|
||||||
|
static uint16_t buildFeedbackPayload(uint8_t channel, uint8_t page,
|
||||||
|
uint8_t *payload, uint16_t max_len)
|
||||||
|
{
|
||||||
|
if (channel >= kFeedbackChannelCount || page >= 2U) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (max_len < kFeedbackPayloadLen) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拷贝统计快照 */
|
||||||
|
CanFd::ChannelFeedback snap{};
|
||||||
|
CanFd::copyFeedbackSnapshot(channel, &snap);
|
||||||
|
|
||||||
|
/* 拷贝 ID 快照表 */
|
||||||
|
CanFd::IdSnapshotTable table{};
|
||||||
|
CanFd::copyIdSnapshotTable(channel, &table);
|
||||||
|
|
||||||
|
const uint8_t start = static_cast<uint8_t>(page * kPageEntries);
|
||||||
|
if (start >= CANFD_ID_SNAPSHOT_MAX) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 固定头 */
|
||||||
|
payload[0] = channel;
|
||||||
|
payload[1] = page;
|
||||||
|
payload[2] = table.count;
|
||||||
|
/* payload[3] = entry_count 稍后填 */
|
||||||
|
payload[4] = static_cast<uint8_t>(CanFd::mode());
|
||||||
|
writeU32Le(&payload[5], snap.snapshot_seq);
|
||||||
|
writeU32Le(&payload[9], snap.stats.rx_count);
|
||||||
|
writeU32Le(&payload[13], snap.stats.tx_count);
|
||||||
|
writeU32Le(&payload[17], snap.stats.rx_drop_count);
|
||||||
|
writeU32Le(&payload[21], snap.stats.error_count);
|
||||||
|
payload[25] = snap.stats.tec;
|
||||||
|
payload[26] = snap.stats.rec;
|
||||||
|
payload[27] = snap.stats.bus_state;
|
||||||
|
|
||||||
|
/* 打包 ID 条目 */
|
||||||
|
uint16_t offset = kFixedHeaderLen;
|
||||||
|
uint8_t packed = 0U;
|
||||||
|
|
||||||
|
const uint8_t end = static_cast<uint8_t>(
|
||||||
|
(start + kPageEntries < CANFD_ID_SNAPSHOT_MAX)
|
||||||
|
? (start + kPageEntries) : CANFD_ID_SNAPSHOT_MAX);
|
||||||
|
|
||||||
|
for (uint8_t i = start; i < end; ++i) {
|
||||||
|
const auto &e = table.entries[i];
|
||||||
|
if (!e.occupied) continue;
|
||||||
|
|
||||||
|
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
|
||||||
|
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
|
||||||
|
const uint16_t entrySize = static_cast<uint16_t>(8U + dataLen);
|
||||||
|
|
||||||
|
if (offset + entrySize > kFeedbackPayloadLen) break;
|
||||||
|
|
||||||
|
writeU32Le(&payload[offset], e.id);
|
||||||
|
payload[offset + 4U] = e.dlc;
|
||||||
|
payload[offset + 5U] = static_cast<uint8_t>(
|
||||||
|
(e.fd_flag ? 1U : 0U) | (e.brs ? 2U : 0U) | (e.esi ? 4U : 0U));
|
||||||
|
payload[offset + 6U] = 0U; /* reserved */
|
||||||
|
payload[offset + 7U] = 0U; /* reserved */
|
||||||
|
memcpy(&payload[offset + 8U], e.data, dataLen);
|
||||||
|
|
||||||
|
offset += entrySize;
|
||||||
|
packed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[3] = packed;
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
class SpiCanFeedbackHandler final : public ISpiCommandHandler {
|
class SpiCanFeedbackHandler final : public ISpiCommandHandler {
|
||||||
public:
|
public:
|
||||||
bool canHandle(uint8_t frame_type) const override
|
bool canHandle(uint8_t frame_type) const override
|
||||||
@@ -86,80 +172,17 @@ private:
|
|||||||
const uint8_t channel = ctx.payload[0];
|
const uint8_t channel = ctx.payload[0];
|
||||||
const uint8_t page = ctx.payload[1];
|
const uint8_t page = ctx.payload[1];
|
||||||
|
|
||||||
if (channel >= kFeedbackChannelCount) {
|
uint8_t payload[kFeedbackPayloadLen] = {};
|
||||||
uint8_t err = 0xE1U;
|
uint16_t payload_len = buildFeedbackPayload(channel, page, payload, sizeof(payload));
|
||||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
if (payload_len == 0U) {
|
||||||
SpiDefs::kRspError, &err, 1U);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 拷贝统计快照 */
|
|
||||||
CanFd::ChannelFeedback snap{};
|
|
||||||
CanFd::copyFeedbackSnapshot(channel, &snap);
|
|
||||||
|
|
||||||
/* 拷贝 ID 快照表 */
|
|
||||||
CanFd::IdSnapshotTable table{};
|
|
||||||
CanFd::copyIdSnapshotTable(channel, &table);
|
|
||||||
|
|
||||||
const uint8_t start = static_cast<uint8_t>(page * kPageEntries);
|
|
||||||
if (start >= CANFD_ID_SNAPSHOT_MAX) {
|
|
||||||
uint8_t err = 0xE3U;
|
uint8_t err = 0xE3U;
|
||||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||||
SpiDefs::kRspError, &err, 1U);
|
SpiDefs::kRspError, &err, 1U);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t payload[kFeedbackPayloadLen] = {};
|
|
||||||
|
|
||||||
/* 固定头 */
|
|
||||||
payload[0] = channel;
|
|
||||||
payload[1] = page;
|
|
||||||
payload[2] = table.count;
|
|
||||||
/* payload[3] = entry_count 稍后填 */
|
|
||||||
payload[4] = static_cast<uint8_t>(CanFd::mode());
|
|
||||||
writeU32Le(&payload[5], snap.snapshot_seq);
|
|
||||||
writeU32Le(&payload[9], snap.stats.rx_count);
|
|
||||||
writeU32Le(&payload[13], snap.stats.tx_count);
|
|
||||||
writeU32Le(&payload[17], snap.stats.rx_drop_count);
|
|
||||||
writeU32Le(&payload[21], snap.stats.error_count);
|
|
||||||
payload[25] = snap.stats.tec;
|
|
||||||
payload[26] = snap.stats.rec;
|
|
||||||
payload[27] = snap.stats.bus_state;
|
|
||||||
|
|
||||||
/* 打包 ID 条目 */
|
|
||||||
uint16_t offset = kFixedHeaderLen;
|
|
||||||
uint8_t packed = 0U;
|
|
||||||
|
|
||||||
const uint8_t end = static_cast<uint8_t>(
|
|
||||||
(start + kPageEntries < CANFD_ID_SNAPSHOT_MAX)
|
|
||||||
? (start + kPageEntries) : CANFD_ID_SNAPSHOT_MAX);
|
|
||||||
|
|
||||||
for (uint8_t i = start; i < end; ++i) {
|
|
||||||
const auto &e = table.entries[i];
|
|
||||||
if (!e.occupied) continue;
|
|
||||||
|
|
||||||
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
|
|
||||||
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
|
|
||||||
const uint16_t entrySize = static_cast<uint16_t>(8U + dataLen);
|
|
||||||
|
|
||||||
if (offset + entrySize > kFeedbackPayloadLen) break;
|
|
||||||
|
|
||||||
writeU32Le(&payload[offset], e.id);
|
|
||||||
payload[offset + 4U] = e.dlc;
|
|
||||||
payload[offset + 5U] = static_cast<uint8_t>(
|
|
||||||
(e.fd_flag ? 1U : 0U) | (e.brs ? 2U : 0U) | (e.esi ? 4U : 0U));
|
|
||||||
payload[offset + 6U] = 0U; /* reserved */
|
|
||||||
payload[offset + 7U] = 0U; /* reserved */
|
|
||||||
memcpy(&payload[offset + 8U], e.data, dataLen);
|
|
||||||
|
|
||||||
offset += entrySize;
|
|
||||||
packed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload[3] = packed;
|
|
||||||
|
|
||||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||||
SpiDefs::kRspAck, payload, offset);
|
SpiDefs::kRspAck, payload, payload_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSetMode(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
void handleSetMode(AppSpiCommand &bus, const SpiCommandContext &ctx)
|
||||||
@@ -207,6 +230,75 @@ static SpiCanFeedbackHandler s_handler;
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief CAN 反馈主动推送一步(非阻塞)。
|
||||||
|
*
|
||||||
|
* 由主循环每次迭代调用。维护内部 (channel, page) 游标,循环遍历 3 通道 × 2 页。
|
||||||
|
* 若上一帧按铃尚未被 ESP32 读走(m_ringPending)则立即返回等待;
|
||||||
|
* 否则构建当前页反馈 payload 并通过 requestEsp32Read(0xC1) 主动推送到 ESP32,
|
||||||
|
* 然后推进游标。ESP32 检测到 SPIready 下降沿后发起读事务取走该帧,
|
||||||
|
* CS 释放后 GD32 中断唤醒主循环继续推送下一帧。
|
||||||
|
*
|
||||||
|
* 1Hz 节流由任务调度器驱动:taskHeartbeat() 每秒调用 canFeedbackStartRound()
|
||||||
|
* 激活新轮次,canFeedbackPushStep() 推完一轮(回到 ch=0,page=0)后清除标志。
|
||||||
|
* 与 CanFd::refreshFeedbackSnapshot() 的 1Hz 刷新对齐,避免重复推同一个 seq。
|
||||||
|
*/
|
||||||
|
static volatile bool s_roundActive = true; /* 首次允许启动,无需等 1s */
|
||||||
|
|
||||||
|
void canFeedbackStartRound()
|
||||||
|
{
|
||||||
|
s_roundActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void canFeedbackPushStep()
|
||||||
|
{
|
||||||
|
static uint8_t s_channel = 0U;
|
||||||
|
static uint8_t s_page = 0U;
|
||||||
|
static uint32_t s_pushCount = 0U;
|
||||||
|
|
||||||
|
/* 已收到 0xB0 进入 Bootloader 请求,停止主动按铃避免干扰 OTA 流程。
|
||||||
|
* 复位前主循环仍在运行,但不再下拉 SPIready,让 ESP32 的
|
||||||
|
* waitForBootloaderReady 信号检测不受干扰。 */
|
||||||
|
if (OtaBootRequest::isRebootPending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1Hz 节流:当前轮次未激活则等待 taskHeartbeat() 触发 */
|
||||||
|
if (!s_roundActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上一帧尚未被 ESP32 读走,等待(不占用主线带宽) */
|
||||||
|
if (AppSpiCommandBus.isRingPending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ESP32 尚未完成上一帧处理(未释放 SPIready),等待 EXTI6 上升沿。
|
||||||
|
* 防止 GD32 在 ESP32 回到 IDLE 前按铃导致下降沿被 ISR 丢弃。 */
|
||||||
|
if (!AppSpiCommandBus.canRingNow()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t payload[kFeedbackPayloadLen] = {};
|
||||||
|
uint16_t payload_len = buildFeedbackPayload(s_channel, s_page,
|
||||||
|
payload, sizeof(payload));
|
||||||
|
if (payload_len > 0U) {
|
||||||
|
(void)AppSpiCommandBus.requestEsp32Read(SpiDefs::kTypeCanFeedback,
|
||||||
|
payload, payload_len);
|
||||||
|
++s_pushCount; /* 仅保留计数,不打印心跳日志 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推进到下一页/下一通道,循环遍历 3 通道 × 2 页 */
|
||||||
|
if (++s_page >= 2U) {
|
||||||
|
s_page = 0U;
|
||||||
|
if (++s_channel >= kFeedbackChannelCount) {
|
||||||
|
s_channel = 0U;
|
||||||
|
/* 回到 ch=0,page=0:一轮推完,等待下一秒 taskHeartbeat 激活 */
|
||||||
|
s_roundActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void registerSpiCanFeedbackCommand(AppSpiCommand &bus)
|
void registerSpiCanFeedbackCommand(AppSpiCommand &bus)
|
||||||
{
|
{
|
||||||
bus.registerHandler(&s_handler);
|
bus.registerHandler(&s_handler);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* @file app_spi_command.cpp
|
* @file app_spi_command.cpp
|
||||||
* @brief APP 运行期 SPI 链路与命令路由实现。
|
* @brief APP 运行期 SPI 链路与命令路由实现。
|
||||||
*
|
*
|
||||||
@@ -8,9 +8,14 @@
|
|||||||
* - 公共帧格式解析与应答构建
|
* - 公共帧格式解析与应答构建
|
||||||
* - 业务命令的注册与分发
|
* - 业务命令的注册与分发
|
||||||
*
|
*
|
||||||
* 具体命令处理逻辑已经拆到独立的 `app_spi_cmd_*.cpp`,
|
* 具体命令处理逻辑已经拆到独立的 app_spi_cmd_*.cpp,
|
||||||
* 通过 `registerHandler()` 注册进来,后续新增 SPI 业务时
|
* 通过 registerHandler() 注册进来,后续新增 SPI 业务时
|
||||||
* 只需要新增命令处理器,不再继续膨胀本文件。
|
* 只需要新增命令处理器,不再继续膨胀本文件。
|
||||||
|
*
|
||||||
|
* 握手架构(线与 SPIready,PB6<->ESP32 GPIO10 开漏单线):
|
||||||
|
* - 命令处理在 onCsRelease() EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame),
|
||||||
|
* 不再推迟到主循环,消除 PB6 拉高延迟。
|
||||||
|
* - GD32 可通过 requestEsp32Read() 下拉 SPIready 主动按铃请求 ESP32 来读。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "device/spi/app_spi_command.h"
|
#include "device/spi/app_spi_command.h"
|
||||||
@@ -62,12 +67,12 @@ static void startSpiTxDma(const uint8_t *buf, uint16_t len)
|
|||||||
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) |= DMA_CHXCTL_CHEN;
|
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)
|
static bool isAllBytesValue(const uint8_t *data, uint16_t len, uint8_t value)
|
||||||
{
|
{
|
||||||
if (!data || len == 0U) {
|
if (!data || len == 0U) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (uint8_t i = 0U; i < len; ++i) {
|
for (uint16_t i = 0U; i < len; ++i) {
|
||||||
if (data[i] != value) {
|
if (data[i] != value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -75,7 +80,7 @@ static bool isAllBytesValue(const uint8_t *data, uint8_t len, uint8_t value)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool shouldIgnoreTransaction(const uint8_t *data, uint8_t len)
|
static bool shouldIgnoreTransaction(const uint8_t *data, uint16_t len)
|
||||||
{
|
{
|
||||||
if (!data || len == 0U) {
|
if (!data || len == 0U) {
|
||||||
return true;
|
return true;
|
||||||
@@ -110,40 +115,123 @@ static bool isFlashRelatedFrame(uint8_t frame_type)
|
|||||||
* CRC8 / 帧构建 / 帧解析(与 BOOT SpiOta 一致)
|
* CRC8 / 帧构建 / 帧解析(与 BOOT SpiOta 一致)
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
static uint8_t spi_crc8(const uint8_t *data, uint16_t len)
|
static constexpr uint8_t kSyncSof0 = 0xA5U;
|
||||||
|
static constexpr uint8_t kSyncSof1 = 0x5AU;
|
||||||
|
static constexpr uint16_t kSyncHeaderLen = 6U;
|
||||||
|
static constexpr uint16_t kSyncCrcLen = 4U;
|
||||||
|
static constexpr uint16_t kSyncOverhead = (uint16_t)(kSyncHeaderLen + kSyncCrcLen);
|
||||||
|
|
||||||
|
/* 扫描前 16 字节查找同步帧头 SOF0 SOF1,容忍 RXORERR 导致的首字节偏移。
|
||||||
|
* 与 spi_try_parse_frame 的偏移扫描逻辑对齐。 */
|
||||||
|
static bool containsSyncSof(const uint8_t *data, uint16_t len)
|
||||||
{
|
{
|
||||||
uint8_t crc = 0x00U;
|
if (!data || len < 2U) { return false; }
|
||||||
|
uint16_t last = (len < 16U) ? (uint16_t)(len - 1U) : 16U;
|
||||||
|
for (uint16_t i = 0U; i < last; ++i) {
|
||||||
|
if (data[i] == kSyncSof0 && data[i + 1U] == kSyncSof1) { return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GD32 APP 侧同步状态机(ISR 与主循环只读/原子写,避免临界区开销)。 */
|
||||||
|
static volatile uint8_t g_localCredit = 8U;
|
||||||
|
static volatile uint8_t g_remoteCredit = 8U;
|
||||||
|
|
||||||
|
static void putU16Le(uint8_t *pp, uint16_t v)
|
||||||
|
{
|
||||||
|
pp[0] = (uint8_t)(v & 0xFFU);
|
||||||
|
pp[1] = (uint8_t)((v >> 8) & 0xFFU);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t getU16Le(const uint8_t *pp)
|
||||||
|
{
|
||||||
|
return (uint16_t)(pp[0] | ((uint16_t)pp[1] << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void putU32Le(uint8_t *pp, uint32_t v)
|
||||||
|
{
|
||||||
|
pp[0] = (uint8_t)(v & 0xFFU);
|
||||||
|
pp[1] = (uint8_t)((v >> 8) & 0xFFU);
|
||||||
|
pp[2] = (uint8_t)((v >> 16) & 0xFFU);
|
||||||
|
pp[3] = (uint8_t)((v >> 24) & 0xFFU);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t getU32Le(const uint8_t *pp)
|
||||||
|
{
|
||||||
|
return (uint32_t)pp[0] | ((uint32_t)pp[1] << 8) |
|
||||||
|
((uint32_t)pp[2] << 16) | ((uint32_t)pp[3] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t spi_crc32(const uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
uint32_t crc = 0xFFFFFFFFU;
|
||||||
for (uint16_t i = 0U; i < len; ++i) {
|
for (uint16_t i = 0U; i < len; ++i) {
|
||||||
crc ^= data[i];
|
crc ^= (uint32_t)data[i] << 24;
|
||||||
|
for (int b = 0; b < 8; ++b) {
|
||||||
|
if (crc & 0x80000000U) {
|
||||||
|
crc = (crc << 1) ^ 0x04C11DB7U;
|
||||||
|
} else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t spi_crc8(const uint8_t *data, uint16_t len)
|
||||||
|
{
|
||||||
|
uint8_t crc = 0x00U;
|
||||||
|
for (uint16_t i = 0U; i < len; ++i) { crc ^= data[i]; }
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 构建新同步帧;上层接口(type/payload)保持不变,handler 无需改动。
|
||||||
|
* kFrameOverhead 旧常量仍保留供旧路径使用。 */
|
||||||
static bool spi_build_frame(uint8_t type, const uint8_t *payload, uint16_t payload_len,
|
static bool spi_build_frame(uint8_t type, const uint8_t *payload, uint16_t payload_len,
|
||||||
uint8_t *out, uint16_t *out_len)
|
uint8_t *out, uint16_t *out_len)
|
||||||
{
|
{
|
||||||
if (!out || !out_len) { return false; }
|
if (!out || !out_len) { return false; }
|
||||||
uint16_t total = (uint16_t)(kFrameOverhead + payload_len);
|
uint16_t total = (uint16_t)(kSyncOverhead + payload_len);
|
||||||
if (total > AppSpiCommand::kTxBufSize) { return false; }
|
if (total > AppSpiCommand::kTxBufSize) { return false; }
|
||||||
out[0] = kSof;
|
|
||||||
out[1] = type;
|
out[0] = kSyncSof0;
|
||||||
out[2] = (uint8_t)(payload_len & 0xFFU);
|
out[1] = kSyncSof1;
|
||||||
out[3] = (uint8_t)((payload_len >> 8) & 0xFFU);
|
out[2] = g_localCredit;
|
||||||
|
out[3] = type;
|
||||||
|
putU16Le(&out[4], payload_len);
|
||||||
if (payload && payload_len > 0U) {
|
if (payload && payload_len > 0U) {
|
||||||
memcpy(&out[4], payload, payload_len);
|
memcpy(&out[kSyncHeaderLen], payload, payload_len);
|
||||||
}
|
}
|
||||||
out[4 + payload_len] = spi_crc8(out, (uint16_t)(4U + payload_len));
|
uint32_t crc = spi_crc32(out, (uint16_t)(kSyncHeaderLen + payload_len));
|
||||||
|
putU32Le(&out[kSyncHeaderLen + payload_len], crc);
|
||||||
*out_len = total;
|
*out_len = total;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 解析:新同步帧优先;失败回退旧 0xA5 帧(含首字节偏移容错)。 */
|
||||||
static bool spi_try_parse_frame(const uint8_t *frame, uint16_t frame_len,
|
static bool spi_try_parse_frame(const uint8_t *frame, uint16_t frame_len,
|
||||||
uint8_t *type, const uint8_t **payload, uint16_t *payload_len)
|
uint8_t *type, const uint8_t **payload, uint16_t *payload_len)
|
||||||
{
|
{
|
||||||
if (!frame || !type || !payload || !payload_len) { return false; }
|
if (!frame || !type || !payload || !payload_len) { return false; }
|
||||||
|
|
||||||
/* 兼容首字节延迟:在帧头前面可能出现若干 0xFF / 0x00 残留字节,
|
for (uint16_t s = 0U; s + kSyncOverhead <= frame_len; ++s) {
|
||||||
* 搜索到第一个 kSof 后尝试按合法帧长度校验 CRC。 */
|
if (frame[s] != kSyncSof0 || frame[s + 1U] != kSyncSof1) { continue; }
|
||||||
|
uint16_t plen = getU16Le(&frame[s + 4U]);
|
||||||
|
uint16_t total = (uint16_t)(kSyncOverhead + plen);
|
||||||
|
if ((uint16_t)(frame_len - s) < total) { continue; }
|
||||||
|
uint32_t expect = getU32Le(&frame[s + kSyncHeaderLen + plen]);
|
||||||
|
uint32_t calc = spi_crc32(&frame[s], (uint16_t)(kSyncHeaderLen + plen));
|
||||||
|
if (expect != calc) { continue; }
|
||||||
|
|
||||||
|
uint8_t credit = frame[s + 2U];
|
||||||
|
*type = frame[s + 3U];
|
||||||
|
*payload = (plen > 0U) ? &frame[s + kSyncHeaderLen] : nullptr;
|
||||||
|
*payload_len = plen;
|
||||||
|
g_remoteCredit = credit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旧 0xA5 帧兼容:首字节可能偏移,扫描 SOF。 */
|
||||||
uint16_t s = 0U;
|
uint16_t s = 0U;
|
||||||
while (s < frame_len && frame[s] != kSof) { ++s; }
|
while (s < frame_len && frame[s] != kSof) { ++s; }
|
||||||
if (s > 0U && s < frame_len) {
|
if (s > 0U && s < frame_len) {
|
||||||
@@ -214,15 +302,11 @@ void AppSpiCommand::buildResponse(uint8_t *tx_buf, uint16_t tx_buf_size,
|
|||||||
const bool ok = spi_build_frame(rsp_type, payload, payload_len, tx_buf, &built_len);
|
const bool ok = spi_build_frame(rsp_type, payload, payload_len, tx_buf, &built_len);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
*tx_len = built_len;
|
*tx_len = built_len;
|
||||||
} else {
|
|
||||||
uint8_t err = 0xEEU;
|
|
||||||
(void)spi_build_frame(kRspError, &err, 1U, tx_buf, &built_len);
|
|
||||||
*tx_len = built_len;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 硬件配置(与 BOOT 完全一致)
|
* 硬件配置
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
void AppSpiCommand::configureGpio()
|
void AppSpiCommand::configureGpio()
|
||||||
@@ -237,6 +321,23 @@ void AppSpiCommand::configureGpio()
|
|||||||
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
|
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
|
||||||
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
|
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
|
||||||
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
|
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
|
||||||
|
|
||||||
|
/* PB6 复用为 SPI Ready 握手线(开漏输出,线与)。
|
||||||
|
* 开漏模式:写 BRR = 下拉驱动低;写 BSRR = 高阻释放,靠外部/对端上拉拉高。
|
||||||
|
* 默认释放(HIGH),允许主机立刻发起首帧。 */
|
||||||
|
gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN);
|
||||||
|
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppSpiCommand::setSpiReady(bool ready)
|
||||||
|
{
|
||||||
|
/* 开漏线与:ready=true 释放(BSRR,高阻,靠上拉拉高);
|
||||||
|
* ready=false 下拉(BRR,驱动低)。 */
|
||||||
|
if (ready) {
|
||||||
|
gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
||||||
|
} else {
|
||||||
|
gpio_bit_reset(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppSpiCommand::configureSpi0()
|
void AppSpiCommand::configureSpi0()
|
||||||
@@ -295,6 +396,28 @@ void AppSpiCommand::configureExti()
|
|||||||
nvic_irq_enable(EXTI10_15_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 1U);
|
nvic_irq_enable(EXTI10_15_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppSpiCommand::configureSpiReadyExti()
|
||||||
|
{
|
||||||
|
/* PB6 已在 configureGpio() 配置为开漏输出,此处启用 EXTI6 上升沿检测。
|
||||||
|
* EXTI 检测引脚实际电平(IDR),与 GPIO 输出模式兼容。
|
||||||
|
* 用于检测 ESP32 releaseReady 产生的上升沿(线从忙碌→空闲),
|
||||||
|
* 作为 ESP32 完成上一帧处理的可靠信号。 */
|
||||||
|
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_6);
|
||||||
|
exti_init(EXTI_6, EXTI_INTERRUPT, EXTI_TRIG_RISING);
|
||||||
|
exti_interrupt_enable(EXTI_6);
|
||||||
|
exti_interrupt_flag_clear(EXTI_6);
|
||||||
|
nvic_irq_enable(EXTI5_9_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppSpiCommand::onSpiReadyExti()
|
||||||
|
{
|
||||||
|
if (exti_interrupt_flag_get(EXTI_6) != RESET) {
|
||||||
|
exti_interrupt_flag_clear(EXTI_6);
|
||||||
|
/* 线升高(空闲):ESP32 已 releaseReady,上一帧处理完 */
|
||||||
|
m_linkState.onEsp32Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 默认命令注册入口(由各 app_spi_cmd_*.cpp 提供)
|
* 默认命令注册入口(由各 app_spi_cmd_*.cpp 提供)
|
||||||
@@ -315,6 +438,10 @@ void AppSpiCommand::init()
|
|||||||
m_logPending = false;
|
m_logPending = false;
|
||||||
m_txActiveThisCs = false;
|
m_txActiveThisCs = false;
|
||||||
m_commandCount = 0U;
|
m_commandCount = 0U;
|
||||||
|
m_ringPending = false;
|
||||||
|
m_ringDeadline = 0U;
|
||||||
|
m_ringTxLen = 0U;
|
||||||
|
m_linkState.reset();
|
||||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||||
memset(m_logBuf, 0, sizeof(m_logBuf));
|
memset(m_logBuf, 0, sizeof(m_logBuf));
|
||||||
memset(m_commands, 0, sizeof(m_commands));
|
memset(m_commands, 0, sizeof(m_commands));
|
||||||
@@ -330,6 +457,7 @@ void AppSpiCommand::init()
|
|||||||
configureSpi0();
|
configureSpi0();
|
||||||
configureDma();
|
configureDma();
|
||||||
configureExti();
|
configureExti();
|
||||||
|
configureSpiReadyExti();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -382,7 +510,7 @@ void AppSpiCommand::processCommand()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
postLogFmt("[SPI-APP] 未知 type=0x%02X → ERROR", frame_type);
|
postLogFmt("[SPI-APP] 未知 type=0x%02X -> ERROR", frame_type);
|
||||||
uint8_t err = 0xEEU;
|
uint8_t err = 0xEEU;
|
||||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||||
}
|
}
|
||||||
@@ -393,13 +521,13 @@ void AppSpiCommand::processCommand()
|
|||||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_txLen = (uint8_t)tx_len;
|
m_txLen = tx_len;
|
||||||
m_txPos = 0U;
|
m_txPos = 0U;
|
||||||
} else {
|
} else {
|
||||||
/* 非标准帧:保留旧式单字节 0xB0 通道,后续新命令建议走注册式帧协议 */
|
/* 非标准帧:保留旧式单字节 0xB0 通道,后续新命令建议走注册式帧协议 */
|
||||||
const uint8_t first_valid = m_rxBuf[0U];
|
const uint8_t first_valid = m_rxBuf[0U];
|
||||||
if (first_valid == CONFIG_SPI_APP_OTA_BOOT_CMD) {
|
if (first_valid == CONFIG_SPI_APP_OTA_BOOT_CMD) {
|
||||||
postLog("旧式0xB0 → 进BOOT");
|
postLog("旧式0xB0 -> 进BOOT");
|
||||||
|
|
||||||
uint16_t tx_len_legacy = 0U;
|
uint16_t tx_len_legacy = 0U;
|
||||||
SpiCommandContext ctx{};
|
SpiCommandContext ctx{};
|
||||||
@@ -421,28 +549,27 @@ void AppSpiCommand::processCommand()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
const bool ok = OtaBootRequest::requestEnterBoot();
|
/* 兜底路径(handler 未注册时的死代码保护):
|
||||||
uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK
|
* 同样使用异步路径,避免在 ISR 内执行 Flash 操作。
|
||||||
: CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR;
|
* 先装 0xB5 ACK,再设 pending 标志。 */
|
||||||
m_txBuf[0] = CONFIG_SPI_APP_OTA_BOOT_CMD;
|
m_txBuf[0] = CONFIG_SPI_APP_OTA_BOOT_ACK_OK;
|
||||||
m_txBuf[1] = ack;
|
m_txBuf[1] = CONFIG_SPI_APP_OTA_BOOT_CMD;
|
||||||
m_txBuf[2] = 0x4FU;
|
m_txBuf[2] = 0x4FU;
|
||||||
m_txBuf[3] = 0x54U;
|
m_txBuf[3] = 0x54U;
|
||||||
tx_len_legacy = 4U;
|
tx_len_legacy = 4U;
|
||||||
if (ok) {
|
OtaBootRequest::requestEnterBootAsync();
|
||||||
TaskManager::set(1UL << TASK_HEARTBEAT);
|
TaskManager::set(1UL << TASK_HEARTBEAT);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_txLen = (uint8_t)tx_len_legacy;
|
m_txLen = tx_len_legacy;
|
||||||
m_txPos = 0U;
|
m_txPos = 0U;
|
||||||
} else {
|
} else {
|
||||||
/* 无法识别 */
|
/* 无法识别 */
|
||||||
postLogFmt("[SPI-APP] 非协议 0x%02X → ERROR", first_valid);
|
postLogFmt("[SPI-APP] 非协议 0x%02X -> ERROR", first_valid);
|
||||||
uint16_t tx_len = 0U;
|
uint16_t tx_len = 0U;
|
||||||
uint8_t err = 0xEEU;
|
uint8_t err = 0xEEU;
|
||||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||||
m_txLen = (uint8_t)tx_len;
|
m_txLen = tx_len;
|
||||||
m_txPos = 0U;
|
m_txPos = 0U;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,14 +578,70 @@ void AppSpiCommand::processCommand()
|
|||||||
/* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */
|
/* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */
|
||||||
TaskManager::set(1UL << TASK_SPI_LOG);
|
TaskManager::set(1UL << TASK_SPI_LOG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 应答帧已就绪:拉高 PB6(释放 SPIready)通知主机可发起下一笔事务。
|
||||||
|
* 命令处理已在 ISR 内同步完成,无需 m_cmdPending 推迟机制。 */
|
||||||
|
|
||||||
|
/* 有响应待发时,设置按铃标志阻止主循环 canFeedbackPushStep() 覆盖 m_txBuf。
|
||||||
|
* ESP32 下次 CS assert 时 DMA 输出响应,onCsRelease 清除 m_ringPending。
|
||||||
|
* 50ms 超时后 cancelRing() 清除,恢复 CAN 反馈推送。 */
|
||||||
|
if (m_txLen > 0U) {
|
||||||
|
m_ringPending = true;
|
||||||
|
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||||
|
}
|
||||||
|
setSpiReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 主循环轮询:打印延迟日志
|
* GD32 主动按铃:下拉 SPIready 请求 ESP32 来读
|
||||||
|
* ============================================================ */
|
||||||
|
|
||||||
|
void AppSpiCommand::cancelRing()
|
||||||
|
{
|
||||||
|
m_ringPending = false;
|
||||||
|
m_ringTxLen = 0U;
|
||||||
|
/* 清除待发响应,防止超时后残留帧在下一次 CS assert 时被错误输出 */
|
||||||
|
m_txLen = 0U;
|
||||||
|
m_txPos = 0U;
|
||||||
|
/* 超时后允许重试,不依赖 ESP32 releaseReady 上升沿 */
|
||||||
|
m_linkState.onRingTimeout();
|
||||||
|
/* 释放 SPIready(高阻),避免永久占用线 */
|
||||||
|
setSpiReady(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppSpiCommand::requestEsp32Read(uint8_t frame_type, const uint8_t *payload, uint16_t payload_len)
|
||||||
|
{
|
||||||
|
/* 若上一次按铃尚未被 ESP32 读走,拒绝重复按铃 */
|
||||||
|
if (m_ringPending) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 构建请求帧到 m_txBuf(ESP32 下次读事务时通过 DMA 输出到 MISO) */
|
||||||
|
uint16_t built_len = 0U;
|
||||||
|
if (!spi_build_frame(frame_type, payload, payload_len, m_txBuf, &built_len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ringTxLen = built_len;
|
||||||
|
m_txLen = built_len;
|
||||||
|
m_txPos = 0U;
|
||||||
|
m_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||||
|
m_ringPending = true;
|
||||||
|
/* 消费完成 flag,等 ESP32 releaseReady 触发 EXTI6 上升沿 */
|
||||||
|
m_linkState.onRingStart();
|
||||||
|
|
||||||
|
/* 下拉 SPIready,通知 ESP32「我有请求帧要给你读」 */
|
||||||
|
setSpiReady(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* 主循环轮询:打印延迟日志 + 按铃超时看护
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
void AppSpiCommand::poll()
|
void AppSpiCommand::poll()
|
||||||
{
|
{
|
||||||
|
/* 打印 ISR 推迟的日志 */
|
||||||
char log_copy[kLogBufSize] = {};
|
char log_copy[kLogBufSize] = {};
|
||||||
bool has_log = false;
|
bool has_log = false;
|
||||||
|
|
||||||
@@ -473,15 +656,19 @@ void AppSpiCommand::poll()
|
|||||||
if (has_log) {
|
if (has_log) {
|
||||||
printf("%s\r\n", log_copy);
|
printf("%s\r\n", log_copy);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void AppSpiCommand::processPendingCommand()
|
/* 按铃超时看护:ESP32 未在 kRingTimeoutMs 内来读则放弃本次按铃。
|
||||||
{
|
* 防止 ESP32 不响应时永久占用 SPIready 线导致双方死锁。 */
|
||||||
if (!m_cmdPending) {
|
if (m_ringPending) {
|
||||||
return;
|
uint32_t save2 = RT_CRITICAL_SAVE();
|
||||||
|
if (m_ringPending) {
|
||||||
|
if ((int32_t)(SysTickTimer::getMs() - m_ringDeadline) >= 0) {
|
||||||
|
printf("[SPI-APP] 按铃超时取消 (ESP32 未响应, 释放 SPIready 重试)\r\n");
|
||||||
|
cancelRing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RT_CRITICAL_RESTORE(save2);
|
||||||
}
|
}
|
||||||
m_cmdPending = false;
|
|
||||||
processCommand();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -494,6 +681,10 @@ void AppSpiCommand::onCsAssert()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_csActive = true;
|
m_csActive = true;
|
||||||
|
/* CS 下降沿立即拉低 PB6:表示从机即将忙碌,
|
||||||
|
* 防止主机在 onCsRelease 准备好响应前抢跑发起下一笔 CS。
|
||||||
|
* (若此帧被 shouldIgnoreTransaction 忽略,onCsRelease 会恢复 HIGH) */
|
||||||
|
setSpiReady(false);
|
||||||
m_rxLen = 0U;
|
m_rxLen = 0U;
|
||||||
m_txPos = 0U;
|
m_txPos = 0U;
|
||||||
m_txActiveThisCs = false;
|
m_txActiveThisCs = false;
|
||||||
@@ -546,6 +737,48 @@ void AppSpiCommand::onCsRelease()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 若本次 CS 事务是 ESP32 响应 GD32 按铃来读请求帧,
|
||||||
|
* 则 GD32 的请求帧已通过 DMA 输出到 MISO,取消按铃状态。
|
||||||
|
*
|
||||||
|
* 但需区分:ESP32 发的是 dummy 读(按铃响应)还是 0xB0 命令(OTA 请求)。
|
||||||
|
* 若 GD32 正在按铃时 ESP32 发 0xB0,会因 m_ringPending=true 被误吞,
|
||||||
|
* 导致 OTA 无法启动。检测到 0xB0 时取消按铃并走正常命令处理。 */
|
||||||
|
if (m_ringPending) {
|
||||||
|
if (m_rxLen >= 1U && m_rxBuf[0] == CONFIG_SPI_APP_OTA_BOOT_CMD) {
|
||||||
|
/* ESP32 发的是 0xB0 Bootloader 请求,不是按铃响应。
|
||||||
|
* 取消按铃,清空 TX 让 processCommand() 重新装载 0xB5 应答。
|
||||||
|
* 不释放 SPIready,processCommand() 末尾会释放。 */
|
||||||
|
m_ringPending = false;
|
||||||
|
m_ringTxLen = 0U;
|
||||||
|
m_txLen = 0U;
|
||||||
|
m_txPos = 0U;
|
||||||
|
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||||
|
/* 继续走 shouldIgnoreTransaction + processCommand(),不 return */
|
||||||
|
} else if (containsSyncSof(m_rxBuf, m_rxLen)) {
|
||||||
|
/* ESP32 发的是协议帧(如 PING/OTA 等),不是按铃响应。
|
||||||
|
* 取消按铃,清空 TX 让 processCommand() 重新装载对应应答。
|
||||||
|
* 不释放 SPIready,processCommand() 末尾会释放。 */
|
||||||
|
m_ringPending = false;
|
||||||
|
m_ringTxLen = 0U;
|
||||||
|
m_txLen = 0U;
|
||||||
|
m_txPos = 0U;
|
||||||
|
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||||
|
/* 继续走 shouldIgnoreTransaction + processCommand(),不 return */
|
||||||
|
} else {
|
||||||
|
/* 正常按铃响应:请求帧已被读走,取消按铃状态 */
|
||||||
|
m_ringPending = false;
|
||||||
|
m_ringTxLen = 0U;
|
||||||
|
/* 请求帧已被读走,清空 TX 防止下次事务重复输出 */
|
||||||
|
m_txLen = 0U;
|
||||||
|
m_txPos = 0U;
|
||||||
|
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||||
|
/* 释放 SPIready,通知 ESP32 本帧完成 */
|
||||||
|
setSpiReady(true);
|
||||||
|
/* 上层若需要 ESP32 处理该请求帧,由 ESP32 侧读到的帧 TYPE 决定后续动作 */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) {
|
if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) {
|
||||||
m_rxLen = 0U;
|
m_rxLen = 0U;
|
||||||
if (m_txActiveThisCs) {
|
if (m_txActiveThisCs) {
|
||||||
@@ -554,6 +787,9 @@ void AppSpiCommand::onCsRelease()
|
|||||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||||
}
|
}
|
||||||
m_txActiveThisCs = false;
|
m_txActiveThisCs = false;
|
||||||
|
/* 忽略事务(空轮询/残包)后恢复 PB6=HIGH,
|
||||||
|
* 避免 PB6 卡 LOW 导致 ESP32 等待上升沿超时 */
|
||||||
|
setSpiReady(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,8 +797,10 @@ void AppSpiCommand::onCsRelease()
|
|||||||
m_txPos = 0U;
|
m_txPos = 0U;
|
||||||
m_txActiveThisCs = false;
|
m_txActiveThisCs = false;
|
||||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||||
m_cmdPending = true;
|
|
||||||
TaskManager::set(1UL << TASK_SPI_CMD_PROCESS);
|
/* 命令处理在 EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame 模式),
|
||||||
|
* 消除主循环延迟。处理完成后 processCommand() 末尾释放 SPIready。 */
|
||||||
|
processCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -584,8 +822,11 @@ void AppSpiCommand::onInterrupt()
|
|||||||
/* TBE 已由 DMA 替代,不再处理 */
|
/* TBE 已由 DMA 替代,不再处理 */
|
||||||
|
|
||||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||||
|
/* 读取并丢弃 overrun 字节以清除标志,但保留已收到的帧数据。
|
||||||
|
* 丢失的字节会导致 CRC 校验失败,由 spi_try_parse_frame 优雅处理
|
||||||
|
* (返回 false -> processCommand 构建 ERROR 响应),
|
||||||
|
* 而不是重置 m_rxLen 导致整帧丢失。 */
|
||||||
(void)spi_i2s_data_receive(SPI0);
|
(void)spi_i2s_data_receive(SPI0);
|
||||||
m_rxLen = 0U;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||||
@@ -614,3 +855,8 @@ extern "C" void EXTI10_15_IRQHandler(void)
|
|||||||
{
|
{
|
||||||
AppSpiCommandBus.onCsExtiInterrupt();
|
AppSpiCommandBus.onCsExtiInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void EXTI5_9_IRQHandler(void)
|
||||||
|
{
|
||||||
|
AppSpiCommandBus.onSpiReadyExti();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,43 @@
|
|||||||
#ifndef DEVICE_APP_SPI_COMMAND_H
|
#ifndef DEVICE_APP_SPI_COMMAND_H
|
||||||
#define DEVICE_APP_SPI_COMMAND_H
|
#define DEVICE_APP_SPI_COMMAND_H
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
class ISpiCommandHandler;
|
class ISpiCommandHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GD32 侧 SPI 链路状态机:封装按铃状态 + ESP32 完成信号。
|
||||||
|
*
|
||||||
|
* 线状态语义(对齐 OTA):
|
||||||
|
* - 线 HIGH = 空闲(双方释放),GD32 可发起新按铃
|
||||||
|
* - 线 LOW = 忙碌(GD32 按铃下拉 或 ESP32 checkGd32Request 期间下拉)
|
||||||
|
*
|
||||||
|
* 状态流转:
|
||||||
|
* - canRingNow()=true(初始/ESP32 完成后)→ requestEsp32Read() → canRingNow()=false
|
||||||
|
* - ESP32 releaseReady 上升沿 → onEsp32Complete() → canRingNow()=true
|
||||||
|
* - 按铃超时 → onRingTimeout() → canRingNow()=true(允许重试)
|
||||||
|
*/
|
||||||
|
class SpiLinkState {
|
||||||
|
public:
|
||||||
|
/** 是否允许发起新按铃(ESP32 已完成上一帧 或 初始态)。 */
|
||||||
|
bool canRingNow() const { return m_esp32Ready; }
|
||||||
|
|
||||||
|
/** 发起按铃时调用(消费完成 flag)。 */
|
||||||
|
void onRingStart() { m_esp32Ready = false; }
|
||||||
|
|
||||||
|
/** ESP32 releaseReady 上升沿触发(EXTI6 ISR 调用)。 */
|
||||||
|
void onEsp32Complete() { m_esp32Ready = true; }
|
||||||
|
|
||||||
|
/** 按铃超时取消时调用(允许重试,不依赖 EXTI)。 */
|
||||||
|
void onRingTimeout() { m_esp32Ready = true; }
|
||||||
|
|
||||||
|
/** 初始化(首帧允许)。 */
|
||||||
|
void reset() { m_esp32Ready = true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
volatile bool m_esp32Ready = true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief APP 运行期 SPI 链路与命令路由模块。
|
* @brief APP 运行期 SPI 链路与命令路由模块。
|
||||||
*
|
*
|
||||||
@@ -12,26 +45,35 @@ class ISpiCommandHandler;
|
|||||||
* - 维护 SPI0 从机的硬件链路(GPIO/SPI/DMA/EXTI)
|
* - 维护 SPI0 从机的硬件链路(GPIO/SPI/DMA/EXTI)
|
||||||
* - 将接收到的合法帧按 TYPE 注册式分发给 ISpiCommandHandler
|
* - 将接收到的合法帧按 TYPE 注册式分发给 ISpiCommandHandler
|
||||||
*
|
*
|
||||||
* 具体命令处理逻辑不再放在这里,而是拆到 `device/spi/app_spi_cmd_*.cpp`,
|
* 具体命令处理逻辑不再放在这里,而是拆到 device/spi/app_spi_cmd_*.cpp,
|
||||||
* 通过 `registerHandler()` 注册进来,保持本模块可扩展。
|
* 通过 registerHandler() 注册进来,保持本模块可扩展。
|
||||||
|
*
|
||||||
|
* 握手架构(线与 SPIready,PB6<->ESP32 GPIO10 开漏单线):
|
||||||
|
* - 空闲态线 HIGH(双方释放,靠上拉)。
|
||||||
|
* - ESP32 主动发起:下拉 SPIready -> 立刻 CS 发请求 -> CS 释放后保持下拉,
|
||||||
|
* GD32 在 onCsRelease() ISR 内完成帧处理+响应装载后释放 SPIready(高),
|
||||||
|
* ESP32 检测到上升沿后发起 CS#2 读响应。
|
||||||
|
* - GD32 主动按铃:下拉 SPIready -> ESP32 下降沿中断唤醒后发起读事务,
|
||||||
|
* GD32 在 onCsRelease() 中装入请求帧并释放 SPIready。
|
||||||
|
* - PB6 配置为开漏输出,setSpiReady(true)=高阻释放,setSpiReady(false)=下拉。
|
||||||
*/
|
*/
|
||||||
class AppSpiCommand {
|
class AppSpiCommand {
|
||||||
public:
|
public:
|
||||||
/** @brief 初始化 SPI0 从机命令通道并注册默认命令处理器。 */
|
/** @brief 初始化 SPI0 从机命令通道并注册默认命令处理器。 */
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
/** @brief 主循环轮询:打印延迟日志。 */
|
/** @brief 主循环轮询:打印延迟日志 + GD32 主动按铃超时看护。 */
|
||||||
void poll();
|
void poll();
|
||||||
|
|
||||||
/** @brief 主循环调用:执行 ISR 中推迟的 SPI 命令解析(从 onCsRelease 移出)。 */
|
|
||||||
void processPendingCommand();
|
|
||||||
|
|
||||||
/** @brief SPI0 中断处理入口。 */
|
/** @brief SPI0 中断处理入口。 */
|
||||||
void onInterrupt();
|
void onInterrupt();
|
||||||
|
|
||||||
/** @brief CS 边沿中断处理入口。 */
|
/** @brief CS 边沿中断处理入口。 */
|
||||||
void onCsExtiInterrupt();
|
void onCsExtiInterrupt();
|
||||||
|
|
||||||
|
/** @brief SPI Ready(PB6) EXTI 上升沿中断处理入口(ESP32 完成信号)。 */
|
||||||
|
void onSpiReadyExti();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册一个 SPI 命令处理器,用于 TYPE 路由。
|
* @brief 注册一个 SPI 命令处理器,用于 TYPE 路由。
|
||||||
* @param[in] handler 命令处理器指针,调用方需保证其生命周期有效。
|
* @param[in] handler 命令处理器指针,调用方需保证其生命周期有效。
|
||||||
@@ -57,19 +99,46 @@ public:
|
|||||||
uint16_t *tx_len, uint8_t rsp_type,
|
uint16_t *tx_len, uint8_t rsp_type,
|
||||||
const uint8_t *payload, uint16_t payload_len);
|
const uint8_t *payload, uint16_t payload_len);
|
||||||
|
|
||||||
static constexpr uint16_t kRxBufSize = 128U;
|
/**
|
||||||
static constexpr uint16_t kTxBufSize = 128U;
|
* @brief GD32 主动按铃:下拉 SPIready,通知 ESP32「我有请求帧要给你读」。
|
||||||
|
*
|
||||||
|
* 将提供的请求帧装入内部 TX 缓冲区(供下次 ESP32 读事务通过 DMA 输出到 MISO),
|
||||||
|
* 然后下拉 SPIready 线。ESP32 检测到下降沿后会发起一次 SPI 读事务把该帧读走。
|
||||||
|
* 调用方负责确保 frame_type 与 payload 符合协议帧格式。
|
||||||
|
*
|
||||||
|
* @param[in] frame_type 请求帧类型(复用 SPI 协议帧 TYPE)。
|
||||||
|
* @param[in] payload 请求帧载荷,可为 nullptr。
|
||||||
|
* @param[in] payload_len 载荷长度。
|
||||||
|
* @return true 成功下拉并装入 TX;false 当前已有未读按铃或帧构建失败。
|
||||||
|
*
|
||||||
|
* @note 可在主循环或 ISR 中调用。若上一次按铃尚未被 ESP32 读走(m_ringPending),
|
||||||
|
* 则返回 false,调用方应稍后重试。按铃后启动 m_ringDeadline 超时看护,
|
||||||
|
* 超时则放弃本次按铃(防止 ESP32 不响应时永久占用 SPIready 线)。
|
||||||
|
*/
|
||||||
|
bool requestEsp32Read(uint8_t frame_type, const uint8_t *payload, uint16_t payload_len);
|
||||||
|
|
||||||
|
/** @brief 查询当前是否有未读按铃(ESP32 尚未来读)。 */
|
||||||
|
bool isRingPending() const { return m_ringPending; }
|
||||||
|
|
||||||
|
/** @brief 查询 ESP32 是否已完成上一帧处理(SPIready 上升沿触发)。
|
||||||
|
* 初始为 true 允许第一次按铃;每次 requestEsp32Read 后置 false,
|
||||||
|
* ESP32 releaseReady 产生上升沿后由 EXTI 置 true。 */
|
||||||
|
bool canRingNow() const { return m_linkState.canRingNow(); }
|
||||||
|
|
||||||
|
static constexpr uint16_t kRxBufSize = 256U;
|
||||||
|
static constexpr uint16_t kTxBufSize = 256U;
|
||||||
static constexpr uint16_t kLogBufSize = 256U;
|
static constexpr uint16_t kLogBufSize = 256U;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr uint8_t kMaxCommands = 8U;
|
static constexpr uint8_t kMaxCommands = 8U;
|
||||||
|
/** GD32 主动按铃超时(ms):ESP32 未在该时间内来读则放弃。 */
|
||||||
|
static constexpr uint32_t kRingTimeoutMs = 50U;
|
||||||
|
|
||||||
volatile bool m_csActive = false;
|
volatile bool m_csActive = false;
|
||||||
volatile uint8_t m_rxLen = 0U;
|
volatile uint16_t m_rxLen = 0U;
|
||||||
uint8_t m_txLen = 0U;
|
uint16_t m_txLen = 0U;
|
||||||
volatile uint8_t m_txPos = 0U;
|
volatile uint8_t m_txPos = 0U;
|
||||||
volatile bool m_logPending = false;
|
volatile bool m_logPending = false;
|
||||||
volatile bool m_cmdPending = false; /**< ISR 置位,标记有待主循环解析的 SPI 命令。 */
|
|
||||||
bool m_txActiveThisCs = false;
|
bool m_txActiveThisCs = false;
|
||||||
uint8_t m_rxBuf[kRxBufSize] = {};
|
uint8_t m_rxBuf[kRxBufSize] = {};
|
||||||
uint8_t m_txBuf[kTxBufSize] = {};
|
uint8_t m_txBuf[kTxBufSize] = {};
|
||||||
@@ -77,15 +146,52 @@ private:
|
|||||||
uint8_t m_commandCount = 0U;
|
uint8_t m_commandCount = 0U;
|
||||||
ISpiCommandHandler *m_commands[kMaxCommands] = {};
|
ISpiCommandHandler *m_commands[kMaxCommands] = {};
|
||||||
|
|
||||||
|
/** GD32 主动按铃状态:true 表示已下拉 SPIready 等待 ESP32 来读。 */
|
||||||
|
volatile bool m_ringPending = false;
|
||||||
|
/** 按铃超时截止时刻(ms,基于 SysTickTimer::getMs)。 */
|
||||||
|
volatile uint32_t m_ringDeadline = 0U;
|
||||||
|
/** 按铃期间暂存的请求帧长度(供 onCsAssert 装入 DMA)。 */
|
||||||
|
volatile uint16_t m_ringTxLen = 0U;
|
||||||
|
|
||||||
|
/** SPI 链路状态机:封装 ESP32 完成信号。 */
|
||||||
|
SpiLinkState m_linkState;
|
||||||
|
|
||||||
void configureGpio();
|
void configureGpio();
|
||||||
void configureSpi0();
|
void configureSpi0();
|
||||||
void configureDma();
|
void configureDma();
|
||||||
void configureExti();
|
void configureExti();
|
||||||
|
void configureSpiReadyExti();
|
||||||
void onCsAssert();
|
void onCsAssert();
|
||||||
void onCsRelease();
|
void onCsRelease();
|
||||||
void processCommand();
|
void processCommand();
|
||||||
|
|
||||||
|
/** @brief 翻转 SPI Ready 握手引脚(PB6)电平(开漏线与)。
|
||||||
|
* @param[in] ready true=释放(高阻,靠上拉拉高), false=下拉(驱动低)。
|
||||||
|
* @note GPIO BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。
|
||||||
|
* 开漏模式下 setSpiReady(true) 仅写 BRR 不驱动高,由外部上拉拉高。
|
||||||
|
*/
|
||||||
|
void setSpiReady(bool ready);
|
||||||
|
|
||||||
|
/** @brief 取消未完成的按铃(释放 SPIready、清 m_ringPending)。 */
|
||||||
|
void cancelRing();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief CAN 反馈主动推送一步(非阻塞,由主循环每次迭代调用)。
|
||||||
|
*
|
||||||
|
* GD32 APP 主动循环推送 3 通道 × 2 页 CAN 反馈帧到 ESP32,替代 ESP32
|
||||||
|
* 主动轮询。每次调用推进一帧:若上一帧尚未被 ESP32 读走则立即返回,
|
||||||
|
* 否则构建当前 (channel, page) 的反馈 payload 并 requestEsp32Read() 推送,
|
||||||
|
* 然后推进到下一页/下一通道。
|
||||||
|
*
|
||||||
|
* 1Hz 节流由 taskHeartbeat() 调用 canFeedbackStartRound() 激活每秒一轮,
|
||||||
|
* 推完一轮后内部清除激活标志,等待下一次 taskHeartbeat() 触发。
|
||||||
|
*/
|
||||||
|
void canFeedbackPushStep();
|
||||||
|
|
||||||
|
/** @brief 激活新一轮 CAN 反馈推送(由 1Hz taskHeartbeat 调用)。 */
|
||||||
|
void canFeedbackStartRound();
|
||||||
|
|
||||||
/** @brief 全局 SPI 命令总线实例。 */
|
/** @brief 全局 SPI 命令总线实例。 */
|
||||||
extern AppSpiCommand AppSpiCommandBus;
|
extern AppSpiCommand AppSpiCommandBus;
|
||||||
|
|
||||||
|
|||||||
+145
-186
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* @file config.h
|
* @file config.h
|
||||||
* @brief 应用级集中配置项定义。
|
* @brief 应用级集中配置项定义。
|
||||||
* @note 用于统一管理当前项目中可调的外设、时序、通信与算法常量。
|
* @note 用于统一管理当前项目中可调的外设、时序、通信与算法常量。
|
||||||
@@ -6,60 +6,73 @@
|
|||||||
#ifndef SYS_CONFIG_H
|
#ifndef SYS_CONFIG_H
|
||||||
#define SYS_CONFIG_H
|
#define SYS_CONFIG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "gd32e50x.h"
|
||||||
|
|
||||||
/** @brief 应用启动横幅字符串。 */
|
/** @brief 应用启动横幅字符串。 */
|
||||||
#define APP_BANNER_TEXT "GD32E508 DCU task scheduler start"
|
constexpr const char *APP_BANNER_TEXT = "GD32E508 DCU task scheduler start";
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* APP 固件版本号
|
||||||
|
* ============================================================ */
|
||||||
|
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
|
||||||
|
constexpr uint32_t APP_FW_VERSION_MINOR = 2U;
|
||||||
|
constexpr uint32_t APP_FW_VERSION_PATCH = 3U;
|
||||||
|
constexpr uint32_t APP_FW_VERSION_BUILD = 2U;
|
||||||
|
|
||||||
|
|
||||||
/** @brief USART0 默认波特率。 */
|
/** @brief USART0 默认波特率。 */
|
||||||
#define CONFIG_USART0_BAUDRATE 115200U
|
constexpr uint32_t CONFIG_USART0_BAUDRATE = 115200U;
|
||||||
|
|
||||||
/** @brief ADC 外设实例。 */
|
/** @brief ADC 外设实例。 */
|
||||||
#define CONFIG_ADC_INSTANCE ADC0
|
constexpr uint32_t CONFIG_ADC_INSTANCE = ADC0;
|
||||||
/** @brief ADC 外部 VIN 检测通道号(原理图:PA6 -> VIN_ADC)。 */
|
/** @brief ADC 外部 VIN 检测通道号(原理图:PA6 -> VIN_ADC)。 */
|
||||||
#define CONFIG_ADC_VIN_CHANNEL ADC_CHANNEL_6
|
constexpr uint32_t CONFIG_ADC_VIN_CHANNEL = ADC_CHANNEL_6;
|
||||||
/** @brief ADC 内部温度通道号。 */
|
/** @brief ADC 内部温度通道号。 */
|
||||||
#define CONFIG_ADC_TEMP_CHANNEL ADC_CHANNEL_16
|
constexpr uint32_t CONFIG_ADC_TEMP_CHANNEL = ADC_CHANNEL_16;
|
||||||
/** @brief ADC 内部参考电压通道号。 */
|
/** @brief ADC 内部参考电压通道号。 */
|
||||||
#define CONFIG_ADC_VREFINT_CHANNEL ADC_CHANNEL_17
|
constexpr uint32_t CONFIG_ADC_VREFINT_CHANNEL = ADC_CHANNEL_17;
|
||||||
/** @brief ADC 满量程计数值。 */
|
/** @brief ADC 满量程计数值。 */
|
||||||
#define CONFIG_ADC_MAX_VALUE 4095.0f
|
constexpr float CONFIG_ADC_MAX_VALUE = 4095.0f;
|
||||||
/** @brief 内部参考电压典型值,单位 V。 */
|
/** @brief 内部参考电压典型值,单位 V。 */
|
||||||
#define CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE 1.20f
|
constexpr float CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE = 1.20f;
|
||||||
/** @brief 温度传感器 25℃ 对应电压,单位 V。 */
|
/** @brief 温度传感器 25℃ 对应电压,单位 V。 */
|
||||||
#define CONFIG_ADC_TEMP_V25 1.43f
|
constexpr float CONFIG_ADC_TEMP_V25 = 1.43f;
|
||||||
/** @brief 温度传感器平均斜率,单位 V/℃。 */
|
/** @brief 温度传感器平均斜率,单位 V/℃。 */
|
||||||
#define CONFIG_ADC_TEMP_AVG_SLOPE 0.0043f
|
constexpr float CONFIG_ADC_TEMP_AVG_SLOPE = 0.0043f;
|
||||||
/** @brief ADC 上电后稳定等待时间,单位 ms。 */
|
/** @brief ADC 上电后稳定等待时间,单位 ms。 */
|
||||||
#define CONFIG_ADC_STABILIZE_DELAY_MS 2U
|
constexpr uint32_t CONFIG_ADC_STABILIZE_DELAY_MS = 2U;
|
||||||
/** @brief ADC 规则通道采样时间配置。 */
|
/** @brief ADC 规则通道采样时间配置。 */
|
||||||
#define CONFIG_ADC_SAMPLE_TIME ADC_SAMPLETIME_239POINT5
|
constexpr uint32_t CONFIG_ADC_SAMPLE_TIME = ADC_SAMPLETIME_239POINT5;
|
||||||
/** @brief ADC 空输入保护时默认 VDDA,单位 V。 */
|
/** @brief ADC 空输入保护时默认 VDDA,单位 V。 */
|
||||||
#define CONFIG_ADC_DEFAULT_VDDA 3.3f
|
constexpr float CONFIG_ADC_DEFAULT_VDDA = 3.3f;
|
||||||
|
|
||||||
/** @brief ADC 中值滤波窗口长度(最终样本数为该值 + 1)。 */
|
/** @brief ADC 中值滤波窗口长度(最终样本数为该值 + 1)。 */
|
||||||
#define CONFIG_ADC_FILTER_SIZE 6U
|
constexpr uint32_t CONFIG_ADC_FILTER_SIZE = 6U;
|
||||||
|
|
||||||
/** @brief CAN 外设实例。 */
|
/** @brief CAN 外设实例。 */
|
||||||
#define CONFIG_CAN_INSTANCE CAN0
|
constexpr uint32_t CONFIG_CAN_INSTANCE = CAN0;
|
||||||
/** @brief CAN 外设时钟使能枚举。 */
|
/** @brief CAN 外设时钟使能枚举。 */
|
||||||
#define CONFIG_CAN_RCU RCU_CAN0
|
constexpr rcu_periph_enum CONFIG_CAN_RCU = RCU_CAN0;
|
||||||
/** @brief CAN 状态报文标准帧 ID。 */
|
/** @brief CAN 状态报文标准帧 ID。 */
|
||||||
#define CONFIG_CAN_STATUS_ID 0x321U
|
constexpr uint32_t CONFIG_CAN_STATUS_ID = 0x321U;
|
||||||
/** @brief ESP32 请求 DCU_APP 进入 Bootloader OTA 窗口的扩展帧 ID(备用 CAN 入口)。 */
|
/** @brief ESP32 请求 DCU_APP 进入 Bootloader OTA 窗口的扩展帧 ID(备用 CAN 入口)。 */
|
||||||
#define CONFIG_CAN_OTA_BOOT_REQ_ID 0x00000320U
|
constexpr uint32_t CONFIG_CAN_OTA_BOOT_REQ_ID = 0x00000320U;
|
||||||
/** @brief 进入 Bootloader OTA 请求魔术字节 0。 */
|
/** @brief 进入 Bootloader OTA 请求魔术字节 0~3。 */
|
||||||
#define CONFIG_CAN_OTA_BOOT_MAGIC0 0x4FU
|
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC0 = 0x4FU;
|
||||||
/** @brief 进入 Bootloader OTA 请求魔术字节 1。 */
|
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC1 = 0x54U;
|
||||||
#define CONFIG_CAN_OTA_BOOT_MAGIC1 0x54U
|
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC2 = 0x41U;
|
||||||
/** @brief 进入 Bootloader OTA 请求魔术字节 2。 */
|
constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC3 = 0x21U;
|
||||||
#define CONFIG_CAN_OTA_BOOT_MAGIC2 0x41U
|
|
||||||
/** @brief 进入 Bootloader OTA 请求魔术字节 3。 */
|
|
||||||
#define CONFIG_CAN_OTA_BOOT_MAGIC3 0x21U
|
|
||||||
/** @brief ESP32 在 APP 运行期通过 SPI 发送的进入 Bootloader OTA 命令。 */
|
/** @brief ESP32 在 APP 运行期通过 SPI 发送的进入 Bootloader OTA 命令。 */
|
||||||
#define CONFIG_SPI_APP_OTA_BOOT_CMD 0xB0U
|
constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_CMD = 0xB0U;
|
||||||
/** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */
|
/** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */
|
||||||
#define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xB5U
|
constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_OK = 0xB5U;
|
||||||
/** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */
|
/** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */
|
||||||
#define CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR 0xE2U
|
constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR = 0xE2U;
|
||||||
|
/** @brief SPI Ready 握手输出端口(PB6 复用为 SPI Ready)。 */
|
||||||
|
constexpr uint32_t CONFIG_SPI_READY_PORT = GPIOB;
|
||||||
|
/** @brief SPI Ready 握手输出引脚。 */
|
||||||
|
constexpr uint32_t CONFIG_SPI_READY_PIN = GPIO_PIN_6;
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用)
|
* CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用)
|
||||||
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制)
|
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制)
|
||||||
@@ -74,168 +87,134 @@
|
|||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
/** @brief 仲裁段预分频值。 */
|
/** @brief 仲裁段预分频值。 */
|
||||||
#define CONFIG_CAN_NOMINAL_PRESCALER 9U
|
constexpr uint32_t CONFIG_CAN_NOMINAL_PRESCALER = 9U;
|
||||||
/** @brief 仲裁段再同步跳跃宽度。 */
|
/** @brief 仲裁段再同步跳跃宽度。 */
|
||||||
#define CONFIG_CAN_NOMINAL_SJW CAN_BT_SJW_1TQ
|
constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_1TQ;
|
||||||
/** @brief 仲裁段时间段 1(PHSEG1)。 */
|
/** @brief 仲裁段时间段 1(PHSEG1)。 */
|
||||||
#define CONFIG_CAN_NOMINAL_BS1 CAN_BT_BS1_7TQ
|
constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ;
|
||||||
/** @brief 仲裁段时间段 2(PHSEG2)。 */
|
/** @brief 仲裁段时间段 2(PHSEG2)。 */
|
||||||
#define CONFIG_CAN_NOMINAL_BS2 CAN_BT_BS2_2TQ
|
constexpr uint32_t CONFIG_CAN_NOMINAL_BS2 = CAN_BT_BS2_2TQ;
|
||||||
|
|
||||||
/** @brief 数据段预分频值。 */
|
/** @brief 数据段预分频值。 */
|
||||||
#define CONFIG_CAN_DATA_PRESCALER 9U
|
constexpr uint32_t CONFIG_CAN_DATA_PRESCALER = 9U;
|
||||||
/** @brief 数据段再同步跳跃宽度。 */
|
/** @brief 数据段再同步跳跃宽度。 */
|
||||||
#define CONFIG_CAN_DATA_SJW CAN_BT_SJW_1TQ
|
constexpr uint32_t CONFIG_CAN_DATA_SJW = CAN_BT_SJW_1TQ;
|
||||||
/** @brief 数据段时间段 1。 */
|
/** @brief 数据段时间段 1。 */
|
||||||
#define CONFIG_CAN_DATA_BS1 CAN_BT_BS1_3TQ
|
constexpr uint32_t CONFIG_CAN_DATA_BS1 = CAN_BT_BS1_3TQ;
|
||||||
/** @brief 数据段时间段 2。 */
|
/** @brief 数据段时间段 2。 */
|
||||||
#define CONFIG_CAN_DATA_BS2 CAN_BT_BS2_1TQ
|
constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ;
|
||||||
|
|
||||||
/** @brief BRS(比特率切换)使能开关:1=启用数据段加速,0=关闭仅经典 CAN。 */
|
/** @brief BRS(比特率切换)使能开关。
|
||||||
|
* @note 保留 #define:canfd.cpp 中用 #if 做编译期分支。 */
|
||||||
#define CONFIG_CAN_FD_BRS_ENABLE 1U
|
#define CONFIG_CAN_FD_BRS_ENABLE 1U
|
||||||
|
|
||||||
|
/** @brief CAN0 通道使能开关:true=启用初始化/收发/自测,false=完全禁用。 */
|
||||||
|
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
|
||||||
|
/** @brief CAN1 通道使能开关。 */
|
||||||
|
constexpr bool CONFIG_CAN_CH1_ENABLE = true;
|
||||||
|
/** @brief CAN2 通道使能开关。 */
|
||||||
|
constexpr bool CONFIG_CAN_CH2_ENABLE = true;
|
||||||
|
|
||||||
/** @brief CAN 接收过滤器目标标准帧 ID。 */
|
/** @brief CAN 接收过滤器目标标准帧 ID。 */
|
||||||
#define CONFIG_CAN_RX_FILTER_ID CONFIG_CAN_STATUS_ID
|
constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID;
|
||||||
/** @brief CAN 接收过滤器标准帧掩码;`0x7FFU` 表示精确匹配 11-bit ID。 */
|
/** @brief CAN 接收过滤器标准帧掩码;0x7FF 表示精确匹配 11-bit ID。 */
|
||||||
#define CONFIG_CAN_RX_FILTER_MASK 0x7FFU
|
constexpr uint32_t CONFIG_CAN_RX_FILTER_MASK = 0x7FFU;
|
||||||
/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数,避免主循环长期被接收流量占用。 */
|
/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数。 */
|
||||||
#define CONFIG_CAN_RX_PARSE_BUDGET 48U
|
constexpr uint32_t CONFIG_CAN_RX_PARSE_BUDGET = 48U;
|
||||||
/** @brief CAN 状态载荷中调试标识字符 0。 */
|
/** @brief CAN 状态载荷中调试标识字符 0~3。 */
|
||||||
#define CONFIG_CAN_STATUS_TAG0 0x43U
|
constexpr uint8_t CONFIG_CAN_STATUS_TAG0 = 0x43U;
|
||||||
/** @brief CAN 状态载荷中调试标识字符 1。 */
|
constexpr uint8_t CONFIG_CAN_STATUS_TAG1 = 0x41U;
|
||||||
#define CONFIG_CAN_STATUS_TAG1 0x41U
|
constexpr uint8_t CONFIG_CAN_STATUS_TAG2 = 0x4EU;
|
||||||
/** @brief CAN 状态载荷中调试标识字符 2。 */
|
constexpr uint8_t CONFIG_CAN_STATUS_TAG3 = 0x46U;
|
||||||
#define CONFIG_CAN_STATUS_TAG2 0x4EU
|
|
||||||
/** @brief CAN 状态载荷中调试标识字符 3。 */
|
|
||||||
#define CONFIG_CAN_STATUS_TAG3 0x46U
|
|
||||||
|
|
||||||
/** @brief 用户调度定时器实例。 */
|
/** @brief 用户调度定时器实例。 */
|
||||||
#define CONFIG_SCHED_TIMER_INSTANCE TIMER2
|
constexpr uint32_t CONFIG_SCHED_TIMER_INSTANCE = TIMER2;
|
||||||
/** @brief 用户调度定时器中断号。 */
|
/** @brief 用户调度定时器中断号。 */
|
||||||
#define CONFIG_SCHED_TIMER_IRQn TIMER2_IRQn
|
constexpr IRQn_Type CONFIG_SCHED_TIMER_IRQn = TIMER2_IRQn;
|
||||||
/** @brief 用户调度定时器时钟使能宏参数。 */
|
/** @brief 用户调度定时器时钟使能宏参数。 */
|
||||||
#define CONFIG_SCHED_TIMER_RCU RCU_TIMER2
|
constexpr rcu_periph_enum CONFIG_SCHED_TIMER_RCU = RCU_TIMER2;
|
||||||
/** @brief 用户调度定时器预分频值。
|
/** @brief 用户调度定时器预分频值。
|
||||||
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) → APB1=/2=90MHz → TIMER2_CLK=APB1×2=180MHz。
|
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) → APB1=/2=90MHz → TIMER2_CLK=APB1×2=180MHz。
|
||||||
* 保持 100us tick: (PSC+1)×(PER+1)=18000 → 180×100 → PSC=179, PER=99。
|
* 保持 100us tick: (PSC+1)×(PER+1)=18000 → 180×100 → PSC=179, PER=99。
|
||||||
*/
|
*/
|
||||||
#define CONFIG_SCHED_TIMER_PRESCALER 179U
|
constexpr uint32_t CONFIG_SCHED_TIMER_PRESCALER = 179U;
|
||||||
/** @brief 用户调度定时器周期重装值。 */
|
/** @brief 用户调度定时器周期重装值。 */
|
||||||
#define CONFIG_SCHED_TIMER_PERIOD 99U
|
constexpr uint32_t CONFIG_SCHED_TIMER_PERIOD = 99U;
|
||||||
/** @brief 用户调度定时器 NVIC 抢占优先级。 */
|
/** @brief 用户调度定时器 NVIC 抢占优先级。 */
|
||||||
#define CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO 1U
|
constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO = 1U;
|
||||||
/** @brief 用户调度定时器 NVIC 子优先级。 */
|
/** @brief 用户调度定时器 NVIC 子优先级。 */
|
||||||
#define CONFIG_SCHED_TIMER_IRQ_SUB_PRIO 0U
|
constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_SUB_PRIO = 0U;
|
||||||
|
|
||||||
/** @brief 调度基准节拍:ADC VREF 采样周期,单位 100us tick。 */
|
/** @brief 调度基准节拍:ADC VREF 采样周期,单位 100us tick。 */
|
||||||
#define CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS 5U
|
constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U;
|
||||||
/** @brief 调度基准节拍:ADC 温度采样/处理周期,单位 100us tick。 */
|
/** @brief 调度基准节拍:ADC 温度采样/处理周期,单位 100us tick。 */
|
||||||
#define CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS 10U
|
constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U;
|
||||||
/** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */
|
/** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */
|
||||||
#define CONFIG_TASK_PERIOD_CAN_RX_TICKS 100U
|
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_RX_TICKS = 100U;
|
||||||
/** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */
|
/** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */
|
||||||
#define CONFIG_TASK_PERIOD_CAN_TX_TICKS 1000U
|
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 1000U;
|
||||||
/** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */
|
/** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */
|
||||||
#define CONFIG_TASK_PERIOD_HEARTBEAT_TICKS 10000U
|
constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U;
|
||||||
|
|
||||||
/** @brief 周期温度/电压 UART 打印开关:0=关闭,1=开启。 */
|
/** @brief 周期温度/电压 UART 打印开关。@note 保留 #define:task_manager.cpp #if 使用。 */
|
||||||
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
|
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
|
||||||
/** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */
|
/** @brief UART 诊断打印级别。@note 保留 #define:task_runner.cpp #if 使用。 */
|
||||||
#define CONFIG_UART_DIAG_LEVEL 1
|
#define CONFIG_UART_DIAG_LEVEL 1
|
||||||
/** @brief SPI 命令逐帧日志开关:0=关闭,1=开启。高速 CAN 桥接时应关闭。 */
|
/** @brief SPI 命令逐帧日志开关。@note 保留 #define:#if 使用。 */
|
||||||
#define CONFIG_SPI_APP_LOG_ENABLE 0
|
#define CONFIG_SPI_APP_LOG_ENABLE 0
|
||||||
/** @brief CAN_SEND 成功帧详细日志开关:0=关闭,1=开启。开启会显著影响高速传输。 */
|
/** @brief CAN_SEND 成功帧详细日志开关。@note 保留 #define:#if 使用。 */
|
||||||
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
|
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
|
||||||
|
|
||||||
|
|
||||||
/** @brief FWDGT 独立看门狗预分频值。 */
|
/** @brief FWDGT 独立看门狗预分频值。 */
|
||||||
#define CONFIG_FWDGT_PSC FWDGT_PSC_DIV64
|
constexpr uint32_t CONFIG_FWDGT_PSC = FWDGT_PSC_DIV64;
|
||||||
/** @brief FWDGT 独立看门狗重载值。
|
/** @brief FWDGT 独立看门狗重载值。
|
||||||
* 超时 = (64 / 40000) * (RLD + 1) = 1.6ms * (RLD+1)
|
* 超时 = (64 / 40000) * (RLD + 1) = 1.6ms * (RLD+1)
|
||||||
* RLD=1874 → 1.6ms * 1875 ≈ 3.0s
|
* RLD=1874 → 1.6ms * 1875 ≈ 3.0s
|
||||||
*/
|
*/
|
||||||
#define CONFIG_FWDGT_RLD 1874U
|
constexpr uint32_t CONFIG_FWDGT_RLD = 1874U;
|
||||||
/** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */
|
/** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */
|
||||||
#define CONFIG_FWDGT_TIMEOUT_MS 3000U
|
constexpr uint32_t CONFIG_FWDGT_TIMEOUT_MS = 3000U;
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
* APP 固件版本号
|
|
||||||
* ============================================================ */
|
|
||||||
/** @brief APP 主版本号。 */
|
|
||||||
#define APP_FW_VERSION_MAJOR 1U
|
|
||||||
/** @brief APP 次版本号。 */
|
|
||||||
#define APP_FW_VERSION_MINOR 2U
|
|
||||||
/** @brief APP 补丁版本号。 */
|
|
||||||
#define APP_FW_VERSION_PATCH 2U
|
|
||||||
/** @brief APP 构建编号。 */
|
|
||||||
#define APP_FW_VERSION_BUILD 0U
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* BOOT 版本在 OTA 配置页中的 Flash 存储地址
|
* BOOT 版本在 OTA 配置页中的 Flash 存储地址
|
||||||
* 与 BOOT 工程 sys/config.h 保持一致
|
* 与 BOOT 工程 sys/config.h 保持一致
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
/** @brief OTA 配置页基地址。 */
|
constexpr uint32_t CONFIG_OTA_CONFIG_PAGE_ADDR = 0x0807E000U;
|
||||||
#define CONFIG_OTA_CONFIG_PAGE_ADDR 0x0807E000U
|
constexpr uint32_t CONFIG_BOOT_VERSION_ADDR0 = CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U;
|
||||||
/** @brief BOOT 版本存储地址(word 6,低 32 位:major|minor)。 */
|
constexpr uint32_t CONFIG_BOOT_VERSION_ADDR1 = CONFIG_OTA_CONFIG_PAGE_ADDR + 0x1CU;
|
||||||
#define CONFIG_BOOT_VERSION_ADDR0 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U)
|
constexpr uint32_t CONFIG_OTA_CONFIG_WORD_COUNT = 8U;
|
||||||
/** @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
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* NVIC 优先级分组与中断优先级分层
|
* NVIC 优先级分组与中断优先级分层
|
||||||
* (EtherCAT 实时架构改造:PRE4_SUB0,4 位抢占优先级)
|
* (EtherCAT 实时架构改造:PRE4_SUB0,4 位抢占优先级)
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
/** @brief NVIC 优先级分组:4 位抢占 + 0 位子优先级。 */
|
constexpr uint32_t CONFIG_NVIC_PRIORITY_GROUP = NVIC_PRIGROUP_PRE4_SUB0;
|
||||||
#define CONFIG_NVIC_PRIORITY_GROUP NVIC_PRIGROUP_PRE4_SUB0
|
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC = 0U;
|
||||||
|
constexpr uint32_t CONFIG_IRQ_PREEMPT_SCHED = 1U;
|
||||||
/** @brief EtherCAT SYNC0 周期中断抢占优先级(最高,永不被阻塞)。 */
|
constexpr uint32_t CONFIG_IRQ_PREEMPT_CAN = 2U;
|
||||||
#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC 0U
|
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_ESC = 1U;
|
||||||
/** @brief 调度器定时器抢占优先级。 */
|
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 = 1U;
|
||||||
#define CONFIG_IRQ_PREEMPT_SCHED 1U
|
constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U;
|
||||||
/** @brief CAN RX0 抢占优先级。 */
|
|
||||||
#define CONFIG_IRQ_PREEMPT_CAN 2U
|
|
||||||
/** @brief EtherCAT ESC 事件中断抢占优先级(可被 SYNC0 抢占)。 */
|
|
||||||
#define CONFIG_IRQ_PREEMPT_ETHERCAT_ESC 1U
|
|
||||||
/** @brief EtherCAT SYNC1 备用抢占优先级。 */
|
|
||||||
#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 1U
|
|
||||||
/** @brief SPI / EXTI / USART 抢占优先级(最低)。 */
|
|
||||||
#define CONFIG_IRQ_PREEMPT_SPI_USART 3U
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 主循环与实时策略
|
* 主循环与实时策略
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
/** @brief 主循环 WFI 开关:0=关闭(忙等,适配 EtherCAT),1=开启(省电)。 */
|
/** @brief 主循环 WFI 开关。@note 保留 #define:main.cpp #if 使用。 */
|
||||||
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
|
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* EtherCAT LAN9252 使能与配置
|
* EtherCAT LAN9252 使能与配置
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
/** @brief SYNC0 同步模式:0=EXTI 外部触发(ESC 硬件同步,默认),1=TIMER0 降级(DC 未同步时)。 */
|
/** @brief SYNC0 同步模式。@note 保留 #define:#if 使用。 */
|
||||||
#define CONFIG_ETHERCAT_SYNC_MODE 0
|
#define CONFIG_ETHERCAT_SYNC_MODE 0
|
||||||
|
|
||||||
/** @brief periodicIsr 最大执行时间告警阈值(µs)。实测超过此值需评估减小 PDO 大小或优化 EXMC 时序。
|
constexpr uint32_t CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD = 5U;
|
||||||
* 当前阈值 5µs:基于 180MHz + 32 字节 PDO + EXMC 6 HCLK 时序的估算上限。
|
constexpr uint32_t CONFIG_ESC_PDI_INPUT_OFFSET = 0x1000U;
|
||||||
* preempt=0 ISR 阻塞窗口 ≤ 此阈值时,CAN/SPI 中断需容忍。
|
constexpr uint32_t CONFIG_ESC_PDI_OUTPUT_OFFSET = 0x1200U;
|
||||||
* @note 启用 ESC 后需根据实测 HCLK 数重新评估。 */
|
|
||||||
#define CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD 5U
|
|
||||||
|
|
||||||
/** @brief 输入 PDO(TxPDO)在 PDI 内存区的起始偏移。
|
|
||||||
* 对应 LAN9252 SyncManager 内存映射,需与主站 ESI 配置匹配。
|
|
||||||
* @note 占位值,实际数值待主站配置确定后在集成阶段填入。 */
|
|
||||||
#define CONFIG_ESC_PDI_INPUT_OFFSET 0x1000U
|
|
||||||
|
|
||||||
/** @brief 输出 PDO(RxPDO)在 PDI 内存区的起始偏移。
|
|
||||||
* 对应 LAN9252 SyncManager 内存映射,需与主站 ESI 配置匹配。
|
|
||||||
* @note 占位值,实际数值待主站配置确定后在集成阶段填入。 */
|
|
||||||
#define CONFIG_ESC_PDI_OUTPUT_OFFSET 0x1200U
|
|
||||||
|
|
||||||
/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */
|
/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */
|
||||||
/** @brief EXMC NOR/SRAM Region1 基址(NE1 片选)。 */
|
constexpr uint32_t CONFIG_ESC_EXMC_BASE_ADDR = 0x60000000U;
|
||||||
#define CONFIG_ESC_EXMC_BASE_ADDR 0x60000000U
|
/** @note EXMC_BANK0_NORSRAM_REGION1 条件编译,保留 #define。 */
|
||||||
/** @brief EXMC BANK 选择(BANK0 Region1)。
|
|
||||||
* @note EXMC 库未纳入编译时回退为 0U;启用 EtherCAT 时 EXMC 组件将被引入。 */
|
|
||||||
#ifdef EXMC_BANK0_NORSRAM_REGION1
|
#ifdef EXMC_BANK0_NORSRAM_REGION1
|
||||||
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 EXMC_BANK0_NORSRAM_REGION1
|
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 EXMC_BANK0_NORSRAM_REGION1
|
||||||
#else
|
#else
|
||||||
@@ -244,67 +223,47 @@
|
|||||||
|
|
||||||
/* ---------- ESC 中断引脚(PB0/PB1/PB2,三个独立 EXTI 向量) ---------- */
|
/* ---------- ESC 中断引脚(PB0/PB1/PB2,三个独立 EXTI 向量) ---------- */
|
||||||
/** @note 保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)。 */
|
/** @note 保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)。 */
|
||||||
#define CONFIG_ESC_SYNC0_PORT GPIOB
|
constexpr uint32_t CONFIG_ESC_SYNC0_PORT = GPIOB;
|
||||||
#define CONFIG_ESC_SYNC0_PIN GPIO_PIN_0
|
constexpr uint32_t CONFIG_ESC_SYNC0_PIN = GPIO_PIN_0;
|
||||||
#define CONFIG_ESC_SYNC0_EXTI EXTI_0
|
constexpr exti_line_enum CONFIG_ESC_SYNC0_EXTI = EXTI_0;
|
||||||
#define CONFIG_ESC_SYNC0_IRQn EXTI0_IRQn
|
constexpr IRQn_Type CONFIG_ESC_SYNC0_IRQn = EXTI0_IRQn;
|
||||||
#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
|
constexpr uint32_t CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||||
#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_0
|
constexpr uint32_t CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_0;
|
||||||
|
|
||||||
#define CONFIG_ESC_IRQ_PORT GPIOB
|
constexpr uint32_t CONFIG_ESC_IRQ_PORT = GPIOB;
|
||||||
#define CONFIG_ESC_IRQ_PIN GPIO_PIN_1
|
constexpr uint32_t CONFIG_ESC_IRQ_PIN = GPIO_PIN_1;
|
||||||
#define CONFIG_ESC_IRQ_EXTI EXTI_1
|
constexpr exti_line_enum CONFIG_ESC_IRQ_EXTI = EXTI_1;
|
||||||
#define CONFIG_ESC_IRQ_IRQn EXTI1_IRQn
|
constexpr IRQn_Type CONFIG_ESC_IRQ_IRQn = EXTI1_IRQn;
|
||||||
#define CONFIG_ESC_IRQ_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
|
constexpr uint32_t CONFIG_ESC_IRQ_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||||
#define CONFIG_ESC_IRQ_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_1
|
constexpr uint32_t CONFIG_ESC_IRQ_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_1;
|
||||||
|
|
||||||
#define CONFIG_ESC_SYNC1_PORT GPIOB
|
constexpr uint32_t CONFIG_ESC_SYNC1_PORT = GPIOB;
|
||||||
#define CONFIG_ESC_SYNC1_PIN GPIO_PIN_2
|
constexpr uint32_t CONFIG_ESC_SYNC1_PIN = GPIO_PIN_2;
|
||||||
#define CONFIG_ESC_SYNC1_EXTI EXTI_2
|
constexpr exti_line_enum CONFIG_ESC_SYNC1_EXTI = EXTI_2;
|
||||||
#define CONFIG_ESC_SYNC1_IRQn EXTI2_IRQn
|
constexpr IRQn_Type CONFIG_ESC_SYNC1_IRQn = EXTI2_IRQn;
|
||||||
#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
|
constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||||
#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_2
|
constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_2;
|
||||||
|
|
||||||
/* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */
|
/* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */
|
||||||
#define CONFIG_EXMC_D0_PORT GPIOD
|
constexpr uint32_t CONFIG_EXMC_D0_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D0_PIN = GPIO_PIN_14;
|
||||||
#define CONFIG_EXMC_D0_PIN GPIO_PIN_14
|
constexpr uint32_t CONFIG_EXMC_D1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D1_PIN = GPIO_PIN_15;
|
||||||
#define CONFIG_EXMC_D1_PORT GPIOD
|
constexpr uint32_t CONFIG_EXMC_D2_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D2_PIN = GPIO_PIN_0;
|
||||||
#define CONFIG_EXMC_D1_PIN GPIO_PIN_15
|
constexpr uint32_t CONFIG_EXMC_D3_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D3_PIN = GPIO_PIN_1;
|
||||||
#define CONFIG_EXMC_D2_PORT GPIOD
|
constexpr uint32_t CONFIG_EXMC_D4_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D4_PIN = GPIO_PIN_7;
|
||||||
#define CONFIG_EXMC_D2_PIN GPIO_PIN_0
|
constexpr uint32_t CONFIG_EXMC_D5_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D5_PIN = GPIO_PIN_8;
|
||||||
#define CONFIG_EXMC_D3_PORT GPIOD
|
constexpr uint32_t CONFIG_EXMC_D6_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D6_PIN = GPIO_PIN_9;
|
||||||
#define CONFIG_EXMC_D3_PIN GPIO_PIN_1
|
constexpr uint32_t CONFIG_EXMC_D7_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D7_PIN = GPIO_PIN_10;
|
||||||
#define CONFIG_EXMC_D4_PORT GPIOE
|
constexpr uint32_t CONFIG_EXMC_D8_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D8_PIN = GPIO_PIN_11;
|
||||||
#define CONFIG_EXMC_D4_PIN GPIO_PIN_7
|
constexpr uint32_t CONFIG_EXMC_D9_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D9_PIN = GPIO_PIN_12;
|
||||||
#define CONFIG_EXMC_D5_PORT GPIOE
|
constexpr uint32_t CONFIG_EXMC_D10_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D10_PIN = GPIO_PIN_13;
|
||||||
#define CONFIG_EXMC_D5_PIN GPIO_PIN_8
|
constexpr uint32_t CONFIG_EXMC_D11_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D11_PIN = GPIO_PIN_14;
|
||||||
#define CONFIG_EXMC_D6_PORT GPIOE
|
constexpr uint32_t CONFIG_EXMC_D12_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D12_PIN = GPIO_PIN_15;
|
||||||
#define CONFIG_EXMC_D6_PIN GPIO_PIN_9
|
constexpr uint32_t CONFIG_EXMC_D13_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D13_PIN = GPIO_PIN_8;
|
||||||
#define CONFIG_EXMC_D7_PORT GPIOE
|
constexpr uint32_t CONFIG_EXMC_D14_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D14_PIN = GPIO_PIN_9;
|
||||||
#define CONFIG_EXMC_D7_PIN GPIO_PIN_10
|
constexpr uint32_t CONFIG_EXMC_D15_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D15_PIN = GPIO_PIN_10;
|
||||||
#define CONFIG_EXMC_D8_PORT GPIOE
|
constexpr uint32_t CONFIG_EXMC_A16_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A16_PIN = GPIO_PIN_11;
|
||||||
#define CONFIG_EXMC_D8_PIN GPIO_PIN_11
|
constexpr uint32_t CONFIG_EXMC_NOE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NOE_PIN = GPIO_PIN_4;
|
||||||
#define CONFIG_EXMC_D9_PORT GPIOE
|
constexpr uint32_t CONFIG_EXMC_NWE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NWE_PIN = GPIO_PIN_5;
|
||||||
#define CONFIG_EXMC_D9_PIN GPIO_PIN_12
|
constexpr uint32_t CONFIG_EXMC_NE1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE1_PIN = GPIO_PIN_7;
|
||||||
#define CONFIG_EXMC_D10_PORT GPIOE
|
|
||||||
#define CONFIG_EXMC_D10_PIN GPIO_PIN_13
|
|
||||||
#define CONFIG_EXMC_D11_PORT GPIOE
|
|
||||||
#define CONFIG_EXMC_D11_PIN GPIO_PIN_14
|
|
||||||
#define CONFIG_EXMC_D12_PORT GPIOE
|
|
||||||
#define CONFIG_EXMC_D12_PIN GPIO_PIN_15
|
|
||||||
#define CONFIG_EXMC_D13_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_D13_PIN GPIO_PIN_8
|
|
||||||
#define CONFIG_EXMC_D14_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_D14_PIN GPIO_PIN_9
|
|
||||||
#define CONFIG_EXMC_D15_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_D15_PIN GPIO_PIN_10
|
|
||||||
#define CONFIG_EXMC_A16_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_A16_PIN GPIO_PIN_11
|
|
||||||
#define CONFIG_EXMC_NOE_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_NOE_PIN GPIO_PIN_4
|
|
||||||
#define CONFIG_EXMC_NWE_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_NWE_PIN GPIO_PIN_5
|
|
||||||
#define CONFIG_EXMC_NE1_PORT GPIOD
|
|
||||||
#define CONFIG_EXMC_NE1_PIN GPIO_PIN_7
|
|
||||||
|
|
||||||
#endif /* SYS_CONFIG_H */
|
#endif /* SYS_CONFIG_H */
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ static bool requestMagicValid(const uint8_t *data, uint8_t length)
|
|||||||
|
|
||||||
/** @brief 已写入 OTA 标志并等待软复位的挂起标志。 */
|
/** @brief 已写入 OTA 标志并等待软复位的挂起标志。 */
|
||||||
volatile bool OtaBootRequest::s_rebootPending = false;
|
volatile bool OtaBootRequest::s_rebootPending = false;
|
||||||
|
volatile bool OtaBootRequest::s_bootRequestPending = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化进入 Bootloader 请求模块的运行态。
|
* @brief 初始化进入 Bootloader 请求模块的运行态。
|
||||||
@@ -96,6 +97,7 @@ volatile bool OtaBootRequest::s_rebootPending = false;
|
|||||||
void OtaBootRequest::init()
|
void OtaBootRequest::init()
|
||||||
{
|
{
|
||||||
s_rebootPending = false;
|
s_rebootPending = false;
|
||||||
|
s_bootRequestPending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,6 +119,16 @@ bool OtaBootRequest::requestEnterBoot()
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步请求进入 Bootloader(ISR 安全)。
|
||||||
|
* @note 仅设置 pending 标志,Flash 操作和复位由 process() 在主循环执行。
|
||||||
|
* 用于 SPI EXTI ISR 内的 0xB0 命令处理,避免 Flash 擦除阻塞 SPI 中断。
|
||||||
|
*/
|
||||||
|
void OtaBootRequest::requestEnterBootAsync()
|
||||||
|
{
|
||||||
|
s_bootRequestPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 处理备用 CAN 入口的进入 Bootloader 请求。
|
* @brief 处理备用 CAN 入口的进入 Bootloader 请求。
|
||||||
* @note 当前主路径是 SPI 命令;CAN 入口只负责识别魔术字并触发同一套标志写入逻辑。
|
* @note 当前主路径是 SPI 命令;CAN 入口只负责识别魔术字并触发同一套标志写入逻辑。
|
||||||
@@ -138,19 +150,34 @@ bool OtaBootRequest::handleCanFrame(uint8_t channel, uint32_t id, const uint8_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 在主循环任务中执行短延迟软复位。
|
* @brief 在主循环任务中执行异步 Flash 操作和短延迟软复位。
|
||||||
* @note 不在 SPI/CAN 中断里直接复位,避免应答还没发出就重启。
|
* @note 不在 SPI/CAN 中断里直接复位,避免应答还没有发出就重启。
|
||||||
* 这里使用毫秒级短延时,而不是 1s 心跳计数;否则一次 SPI 请求
|
* 异步路径:SPI ISR 收到 0xB0 后只设标志,Flash 操作(可能擦页 20-30ms)
|
||||||
* 实际会拖到 2~3 秒后才复位,ESP32 第二次 OTA 时很容易在 APP 还没
|
* 在此主循环执行,避免阻塞 SPI 中断导致 ESP32 ACK 轮询丢字节。
|
||||||
* 真正重启进入 BOOT 之前就发出 HANDSHAKE_REQ。
|
|
||||||
*/
|
*/
|
||||||
void OtaBootRequest::process()
|
void OtaBootRequest::process()
|
||||||
{
|
{
|
||||||
|
/* 异步处理 0xB0 请求:在主循环执行 Flash 操作(20-30ms),不阻塞 SPI ISR */
|
||||||
|
if(s_bootRequestPending) {
|
||||||
|
s_bootRequestPending = false;
|
||||||
|
const bool ok = requestEnterBoot();
|
||||||
|
if(ok) {
|
||||||
|
s_rebootPending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!s_rebootPending) {
|
if(!s_rebootPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_rebootPending = false;
|
s_rebootPending = false;
|
||||||
|
|
||||||
|
/* 方案A: 拉低 PB6 通知 ESP32 "即将复位"。
|
||||||
|
* ESP32 通过 LOW→HIGH 序列识别 Bootloader 就绪:
|
||||||
|
* APP 拉低 → 复位(μs级HIGH) → Bootloader极早拉低 → init完成释放HIGH
|
||||||
|
* 20ms 延时确保 ESP32 5ms 轮询能捕获到 LOW 信号后再复位。 */
|
||||||
|
gpio_bit_reset(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN);
|
||||||
|
|
||||||
SysTickTimer::delayMs(kRebootDelayMs);
|
SysTickTimer::delayMs(kRebootDelayMs);
|
||||||
__disable_irq();
|
__disable_irq();
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
|
|||||||
@@ -28,14 +28,27 @@ public:
|
|||||||
* @brief 设置 Bootloader OTA 等待标志并安排软复位。
|
* @brief 设置 Bootloader OTA 等待标志并安排软复位。
|
||||||
* @retval true 标志写入成功,稍后会复位进入 Bootloader。
|
* @retval true 标志写入成功,稍后会复位进入 Bootloader。
|
||||||
* @retval false Flash 标志写入失败。
|
* @retval false Flash 标志写入失败。
|
||||||
|
* @note 此函数会执行 Flash 操作(可能擦页 20-30ms),不可在 ISR 内调用。
|
||||||
*/
|
*/
|
||||||
static bool requestEnterBoot();
|
static bool requestEnterBoot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步请求进入 Bootloader(ISR 安全)。
|
||||||
|
* @note 仅设置 pending 标志,Flash 操作和复位由 process() 在主循环执行。
|
||||||
|
* 用于 SPI EXTI ISR 内的 0xB0 命令处理,避免 Flash 擦除阻塞 SPI 中断。
|
||||||
|
*/
|
||||||
|
static void requestEnterBootAsync();
|
||||||
|
|
||||||
/** @brief 若已确认进入 BOOT 请求,则延迟片刻后复位。 */
|
/** @brief 若已确认进入 BOOT 请求,则延迟片刻后复位。 */
|
||||||
static void process();
|
static void process();
|
||||||
|
|
||||||
|
/** @brief 查询是否已收到进入 Bootloader 请求(等待复位中)。
|
||||||
|
* 供主循环在复位前停止主动按铃等可能干扰 OTA 的操作。 */
|
||||||
|
static bool isRebootPending() { return s_rebootPending; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static volatile bool s_rebootPending;
|
static volatile bool s_rebootPending;
|
||||||
|
static volatile bool s_bootRequestPending; ///< 0xB0 异步请求标志,process() 中执行 Flash 操作
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SYS_OTA_BOOT_REQUEST_H */
|
#endif /* SYS_OTA_BOOT_REQUEST_H */
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
static void onInterrupt();
|
static void onInterrupt();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前系统毫秒节拍数。
|
||||||
|
* @return 自启动以来的毫秒数(32 位回绕)。
|
||||||
|
* @note 用于超时判定(如 SPI 按铃看护)。读取 volatile 原子。
|
||||||
|
*/
|
||||||
|
static uint32_t getMs() { return s_ms; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static volatile uint32_t s_ms;
|
static volatile uint32_t s_ms;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -152,37 +152,46 @@ void TaskRunner::taskCanRxPoll() { CanFd::poll(); }
|
|||||||
*/
|
*/
|
||||||
void TaskRunner::taskCanTxSend()
|
void TaskRunner::taskCanTxSend()
|
||||||
{
|
{
|
||||||
/* 启动后每 100ms 向三路 CAN 通道发送 CANFD+BRS 64 字节测试帧。
|
/* 启动后每 100ms 轮流发送一路 CAN 通道的 CANFD+BRS 64 字节测试帧。
|
||||||
|
* 三路通道时分复用:CH0→CH1→CH2→CH0→...,每 100ms 只发一路,
|
||||||
|
* 避免多路同时发送导致总线仲裁冲突(当多路并接到同一总线时)。
|
||||||
* 帧格式:扩展帧 29-bit ID,CANFD+BRS,DLC=64。
|
* 帧格式:扩展帧 29-bit ID,CANFD+BRS,DLC=64。
|
||||||
* 载荷布局:
|
* 载荷布局:
|
||||||
* [0] 通道号 0/1/2
|
* [0] 通道号 0/1/2
|
||||||
* [1] 保留
|
* [1] 保留
|
||||||
* [2..5] 发送序号(小端 uint32,每通道独立递增)
|
* [2..5] 发送序号(小端 uint32,每通道独立递增)
|
||||||
* [6..9] 系统运行秒数 g_app_uptime_sec(小端 uint32)
|
* [6..9] 系统运行秒数 g_app_uptime_sec(小端 uint32)
|
||||||
* [10..63] 递增填充 0x00..0x35(固定模式,便于抓包校验) */
|
* [10..63] 递增填充,按通道偏移便于抓包区分 */
|
||||||
static uint32_t seq[3] = {0U, 0U, 0U};
|
static uint32_t seq[3] = {0U, 0U, 0U};
|
||||||
|
static uint8_t round = 0U; /* 0=CH0, 1=CH1, 2=CH2, 轮流 */
|
||||||
|
|
||||||
for (uint8_t ch = 0U; ch < 3U; ++ch) {
|
const uint8_t ch = round;
|
||||||
uint8_t data[64];
|
round = (round + 1U) % 3U;
|
||||||
data[0] = ch;
|
|
||||||
data[1] = 0U;
|
|
||||||
const uint32_t s = seq[ch];
|
|
||||||
data[2] = static_cast<uint8_t>(s & 0xFFU);
|
|
||||||
data[3] = static_cast<uint8_t>((s >> 8) & 0xFFU);
|
|
||||||
data[4] = static_cast<uint8_t>((s >> 16) & 0xFFU);
|
|
||||||
data[5] = static_cast<uint8_t>((s >> 24) & 0xFFU);
|
|
||||||
data[6] = static_cast<uint8_t>(g_app_uptime_sec & 0xFFU);
|
|
||||||
data[7] = static_cast<uint8_t>((g_app_uptime_sec >> 8) & 0xFFU);
|
|
||||||
data[8] = static_cast<uint8_t>((g_app_uptime_sec >> 16) & 0xFFU);
|
|
||||||
data[9] = static_cast<uint8_t>((g_app_uptime_sec >> 24) & 0xFFU);
|
|
||||||
for (uint8_t i = 10U; i < 64U; ++i) {
|
|
||||||
data[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t extId = 0x1FFFFF00U | static_cast<uint32_t>(ch);
|
/* 跳过已禁用的通道 */
|
||||||
CanFd::transmitFrame(ch, extId, data, 64U);
|
if (!CanFd::isChannelEnabled(ch)) {
|
||||||
seq[ch]++;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t data[64];
|
||||||
|
data[0] = ch;
|
||||||
|
data[1] = 0U;
|
||||||
|
const uint32_t s = seq[ch];
|
||||||
|
data[2] = static_cast<uint8_t>(s & 0xFFU);
|
||||||
|
data[3] = static_cast<uint8_t>((s >> 8) & 0xFFU);
|
||||||
|
data[4] = static_cast<uint8_t>((s >> 16) & 0xFFU);
|
||||||
|
data[5] = static_cast<uint8_t>((s >> 24) & 0xFFU);
|
||||||
|
data[6] = static_cast<uint8_t>(g_app_uptime_sec & 0xFFU);
|
||||||
|
data[7] = static_cast<uint8_t>((g_app_uptime_sec >> 8) & 0xFFU);
|
||||||
|
data[8] = static_cast<uint8_t>((g_app_uptime_sec >> 16) & 0xFFU);
|
||||||
|
data[9] = static_cast<uint8_t>((g_app_uptime_sec >> 24) & 0xFFU);
|
||||||
|
for (uint8_t i = 10U; i < 64U; ++i) {
|
||||||
|
data[i] = static_cast<uint8_t>(i + ch * 0x40U);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t extId = 0x1FFFFF00U | static_cast<uint32_t>(ch);
|
||||||
|
CanFd::transmitFrame(ch, extId, data, 64U);
|
||||||
|
seq[ch]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,7 +251,7 @@ void TaskRunner::taskStatusLed() { /* 预留 */ }
|
|||||||
* @retval 无返回值。
|
* @retval 无返回值。
|
||||||
* @note 当前仅保留任务入口。
|
* @note 当前仅保留任务入口。
|
||||||
*/
|
*/
|
||||||
void TaskRunner::taskHeartbeat() { OtaBootRequest::process(); CanFd::refreshFeedbackSnapshot(); }
|
void TaskRunner::taskHeartbeat() { OtaBootRequest::process(); CanFd::refreshFeedbackSnapshot(); canFeedbackStartRound(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 输出当前温度、电压与 CAN 诊断信息。
|
* @brief 输出当前温度、电压与 CAN 诊断信息。
|
||||||
@@ -322,7 +331,7 @@ void TaskRunner::taskDiagnosticPrint()
|
|||||||
*/
|
*/
|
||||||
void TaskRunner::taskSpiCmdProcess()
|
void TaskRunner::taskSpiCmdProcess()
|
||||||
{
|
{
|
||||||
AppSpiCommandBus.processPendingCommand();
|
AppSpiCommandBus.poll(); // 命令处理已移入 ISR,此处仅日志打印+按铃看护
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -369,6 +378,18 @@ void TaskRunner::runPending()
|
|||||||
{
|
{
|
||||||
Watchdog::feed();
|
Watchdog::feed();
|
||||||
|
|
||||||
|
/* poll() 含按铃超时看门狗(cancelRing),必须每轮无条件执行。
|
||||||
|
* 否则日志关闭时(CONFIG_SPI_APP_LOG_ENABLE=0) m_logPending 永远为 false,
|
||||||
|
* TASK_SPI_LOG 永不置位,poll() 永不执行,m_ringPending 卡死导致
|
||||||
|
* canFeedbackPushStep() 永久 return -> SPI 反馈链路死锁。
|
||||||
|
* 放在 canFeedbackPushStep() 前确保卡死的按铃先被清掉再推送下一帧。 */
|
||||||
|
AppSpiCommandBus.poll();
|
||||||
|
|
||||||
|
/* CAN 反馈主动推送:GD32 APP 循环推送 3 通道 × 2 页反馈帧到 ESP32,
|
||||||
|
* 替代 ESP32 主动轮询。非阻塞:上一帧未读走时立即返回。
|
||||||
|
* 每次主循环迭代调用一次,ESP32 读走一帧后 CS 释放中断唤醒主循环继续推送。 */
|
||||||
|
canFeedbackPushStep();
|
||||||
|
|
||||||
/* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环,
|
/* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环,
|
||||||
* 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。
|
* 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。
|
||||||
* 优先于 takePending() 处理,确保周期数据及时消费。
|
* 优先于 takePending() 处理,确保周期数据及时消费。
|
||||||
@@ -391,7 +412,7 @@ void TaskRunner::runPending()
|
|||||||
if(pending & (1UL << TASK_STATUS_LED)) taskStatusLed();
|
if(pending & (1UL << TASK_STATUS_LED)) taskStatusLed();
|
||||||
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
|
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
|
||||||
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
|
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
|
||||||
if(pending & (1UL << TASK_SPI_LOG)) AppSpiCommandBus.poll();
|
/* TASK_SPI_LOG 的 poll() 已移至 runPending() 开头无条件调用(含按铃看门狗) */
|
||||||
if(pending & (1UL << TASK_SPI_CMD_PROCESS)) taskSpiCmdProcess();
|
if(pending & (1UL << TASK_SPI_CMD_PROCESS)) taskSpiCmdProcess();
|
||||||
if(pending & (1UL << TASK_ETHERCAT_PROCESS)) taskEthercatProcess();
|
if(pending & (1UL << TASK_ETHERCAT_PROCESS)) taskEthercatProcess();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user