feat(canfd): 添加收发器静默模式支持和SPI命令接口
添加了CAN FD收发器静默模式功能,允许通过硬件引脚控制发送使能: - 新增setTxSilent/isTxSilent接口控制TCAN1057收发器Silent模式 - 静默时驱动器关断但接收机继续工作,支持只听不发场景 - 添加SPI命令0xC8用于ESP32远程控制静默模式切换 - 在总线状态位图中增加bit5表示静默状态 - 新增canBusyWaitUs函数用于ISR安全的微秒延时 fix(esc_lan9252): 改进warm jump后LAN9252探测逻辑 修复warm jump导致的LAN9252初始化问题: - 识别warm jump后芯片非POR状态(idx0!=0x1234)的情况 - 自动补发硬件复位对齐到POR状态后再进行正常探测 - 改进调试信息输出,区分POR状态和warm jump残值情况 - 修正失败路径的复位标志初始化逻辑 refactor(spi): 整理SPI命令文件结构 调整SPI命令相关文件的组织结构: - 移动诊断相关文件到更合适的位置 - 重新整理SPI命令处理文件的分组
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "device/spi/app_spi_diag.h"
|
||||
#include "spi_link_v2.h"
|
||||
#include "sys/ota_boot_request.h"
|
||||
|
||||
@@ -168,7 +169,113 @@ static uint16_t s_lastRespLen = 0;
|
||||
static bool s_hasCachedResp = false;
|
||||
|
||||
/* ============================================================
|
||||
* v2 帧构建 / 帧解析(委托 spi_link_v2.h,三工程共用)
|
||||
* SPI 诊断统计与现场快照
|
||||
* ============================================================ */
|
||||
#if (CONFIG_SPI_DIAG_ENABLE != 0)
|
||||
#define SPI_DIAG_RECORD(...) do { __VA_ARGS__; } while (0)
|
||||
|
||||
static SpiDiagStats s_diag = {};
|
||||
|
||||
const SpiDiagStats& getSpiDiagStats()
|
||||
{
|
||||
s_diag.current_local_credit = (uint8_t)g_localCredit;
|
||||
s_diag.expected_rx_seq = g_expectedRxSeq;
|
||||
return s_diag;
|
||||
}
|
||||
|
||||
void resetSpiDiagStats()
|
||||
{
|
||||
memset(&s_diag, 0, sizeof(s_diag));
|
||||
}
|
||||
|
||||
const char* spiParseResultToString(SpiParseResult res)
|
||||
{
|
||||
switch (res) {
|
||||
case SPI_PARSE_OK: return "OK";
|
||||
case SPI_PARSE_ERR_TOO_SHORT: return "TOO_SHORT";
|
||||
case SPI_PARSE_ERR_ALL_FF: return "ALL_FF";
|
||||
case SPI_PARSE_ERR_ALL_00: return "ALL_00";
|
||||
case SPI_PARSE_ERR_NO_SOF: return "NO_SOF";
|
||||
case SPI_PARSE_ERR_BAD_VERSION: return "BAD_VERSION";
|
||||
case SPI_PARSE_ERR_BAD_LEN: return "BAD_LEN";
|
||||
case SPI_PARSE_ERR_CRC_MISMATCH: return "CRC_MISMATCH";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define SPI_DIAG_RECORD(...) ((void)0)
|
||||
#endif
|
||||
|
||||
bool AppSpiCommand::isV2Active() const { return g_v2Active; }
|
||||
uint32_t AppSpiCommand::getSessionId() const { return g_sessionId; }
|
||||
uint8_t AppSpiCommand::getLocalCredit() const { return (uint8_t)g_localCredit; }
|
||||
void AppSpiCommand::dumpDiag() const { dumpSpiDiag(); }
|
||||
|
||||
#if (CONFIG_SPI_DIAG_ENABLE != 0)
|
||||
void dumpSpiDiag()
|
||||
{
|
||||
const SpiDiagStats &st = getSpiDiagStats();
|
||||
const uint32_t now = SysTickTimer::getMs();
|
||||
const uint8_t cs_pin = (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == SET) ? 1U : 0U;
|
||||
const uint8_t rdy_pin = (gpio_input_bit_get(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN) == SET) ? 1U : 0U;
|
||||
const uint16_t stat = (uint16_t)SPI_STAT(SPI0);
|
||||
|
||||
printf("\r\n==================== [GD32 SPI0 LINK DIAGNOSTICS] ====================\r\n");
|
||||
printf("Status : v2_active=%d session_id=0x%08lX local_credit=%u uptime=%lums\r\n",
|
||||
AppSpiCommandBus.isV2Active() ? 1 : 0, (unsigned long)AppSpiCommandBus.getSessionId(),
|
||||
(unsigned)AppSpiCommandBus.getLocalCredit(), (unsigned long)now);
|
||||
printf("Pins & HW: CS(PA15)=%u DATA_READY(PB6)=%u ring_pending=%d cs_active=%d\r\n",
|
||||
cs_pin, rdy_pin, AppSpiCommandBus.isRingPending() ? 1 : 0, AppSpiCommandBus.isCsActive() ? 1 : 0);
|
||||
printf("SPI_STAT : 0x%04X (TRANS:%d TBE:%d RBNE:%d OVR:%d FERR:%d)\r\n",
|
||||
stat, (stat & SPI_FLAG_TRANS) ? 1 : 0, (stat & SPI_FLAG_TBE) ? 1 : 0,
|
||||
(stat & SPI_FLAG_RBNE) ? 1 : 0, (stat & SPI_FLAG_RXORERR) ? 1 : 0,
|
||||
(stat & SPI_FLAG_FERR) ? 1 : 0);
|
||||
printf("CS Stats : assert=%lu release=%lu missed=%lu short(<20B)=%lu zero_len=%lu\r\n",
|
||||
(unsigned long)st.total_cs_assert, (unsigned long)st.total_cs_release,
|
||||
(unsigned long)st.missed_cs_assert, (unsigned long)st.short_rx_len_count,
|
||||
(unsigned long)st.zero_rx_len_count);
|
||||
printf("Frames : valid_v2=%lu parse_fail=%lu all_FF=%lu all_00=%lu\r\n",
|
||||
(unsigned long)st.valid_v2_frames, (unsigned long)st.parse_fail_count,
|
||||
(unsigned long)st.all_ff_count, (unsigned long)st.all_00_count);
|
||||
printf("ParseErr : CRC_err=%lu no_SOF=%lu SOF_drift=%lu bad_ver=%lu bad_len=%lu\r\n",
|
||||
(unsigned long)st.err_crc_mismatch, (unsigned long)st.err_no_sof,
|
||||
(unsigned long)st.err_sof_drift, (unsigned long)st.err_bad_version,
|
||||
(unsigned long)st.err_bad_len);
|
||||
printf("Hw Errors: RXORERR(ovr)=%lu FERR(fmt)=%lu\r\n",
|
||||
(unsigned long)st.rx_overrun_count, (unsigned long)st.format_error_count);
|
||||
printf("Session : HELLO_cnt=%lu mismatch=%lu seq_ok=%lu seq_jump=%lu seq_dup=%lu\r\n",
|
||||
(unsigned long)st.hello_count, (unsigned long)st.session_mismatch,
|
||||
(unsigned long)st.seq_ok_count, (unsigned long)st.seq_jump_count,
|
||||
(unsigned long)st.seq_dup_count);
|
||||
printf("Sequences: last_rx_seq=0x%02X exp_rx_seq=0x%02X last_tx_seq=0x%02X\r\n",
|
||||
st.last_rx_seq, st.expected_rx_seq, st.last_tx_seq);
|
||||
printf("Commands : last_rx_type=0x%02X last_tx_type=0x%02X error_rsp(0x82)=%lu (last_code=0x%02X)\r\n",
|
||||
st.last_rx_type, st.last_tx_type, (unsigned long)st.error_rsp_count, st.last_error_code);
|
||||
printf("Routing : unknown_type=%lu (last=0x%02X) handler_empty=%lu (last=0x%02X)\r\n",
|
||||
(unsigned long)st.unknown_type_count, st.last_unknown_type,
|
||||
(unsigned long)st.handler_empty_count, st.last_empty_type);
|
||||
printf("Ring Push: req=%lu timeout=%lu collision=%lu\r\n",
|
||||
(unsigned long)st.ring_request_count, (unsigned long)st.ring_timeout_count,
|
||||
(unsigned long)st.ring_collision_count);
|
||||
if (st.last_error_uptime_ms > 0U) {
|
||||
printf("LastErr : at %lums, rxLen=%u, reason=%s(0x%02X), SOF_off=%u\r\n",
|
||||
(unsigned long)st.last_error_uptime_ms, (unsigned)st.last_error_rx_len,
|
||||
spiParseResultToString((SpiParseResult)st.last_error_reason),
|
||||
st.last_error_code, (unsigned)st.last_error_sof_offset);
|
||||
printf("LastBytes: [%02X %02X %02X %02X %02X %02X %02X %02X]\r\n",
|
||||
st.last_error_bytes[0], st.last_error_bytes[1], st.last_error_bytes[2], st.last_error_bytes[3],
|
||||
st.last_error_bytes[4], st.last_error_bytes[5], st.last_error_bytes[6], st.last_error_bytes[7]);
|
||||
if (st.last_error_reason == SPI_PARSE_ERR_CRC_MISMATCH) {
|
||||
printf("LastCRC : calc=0x%08lX exp=0x%08lX\r\n",
|
||||
(unsigned long)st.last_error_calc_crc, (unsigned long)st.last_error_expected_crc);
|
||||
}
|
||||
}
|
||||
printf("======================================================================\r\n\r\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ============================================================
|
||||
* v2 帧构建 / 诊断帧解析(委托 spi_link_v2.h,三工程共用)
|
||||
* ============================================================ */
|
||||
|
||||
/* 构建响应帧;上层接口(type/payload)保持不变,handler 无需改动。
|
||||
@@ -192,36 +299,120 @@ static bool spi_build_frame(uint8_t type, const uint8_t *payload, uint16_t paylo
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 解析 v2 帧;委托 v2_parse_frame,扫描 SOF 并校验 VERSION/CRC32。
|
||||
* 仅 v2:v2_parse_frame 扫 SOF0/SOF1 + CRC32;禁止再加 v1 CRC8 分支。 */
|
||||
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,
|
||||
v2_header_t *out_hdr = nullptr)
|
||||
/* 细粒度诊断帧解析:逐项分析失败原因(无SOF/相位漂移/版本不符/长度截断/CRC位错)。 */
|
||||
static bool spi_diagnose_and_parse_frame(
|
||||
const uint8_t *buf, size_t buf_len,
|
||||
uint8_t *out_type, const uint8_t **out_payload, uint16_t *out_payload_len,
|
||||
v2_header_t *out_hdr,
|
||||
SpiParseResult *out_res,
|
||||
uint32_t *out_calc_crc, uint32_t *out_exp_crc,
|
||||
uint8_t *out_sof_offset)
|
||||
{
|
||||
if (!frame || !type || !payload || !payload_len) { return false; }
|
||||
if (!buf || buf_len == 0U) {
|
||||
if (out_res) *out_res = SPI_PARSE_ERR_TOO_SHORT;
|
||||
if (out_sof_offset) *out_sof_offset = 0xFFU;
|
||||
return false;
|
||||
}
|
||||
|
||||
v2_header_t hdr;
|
||||
const uint8_t *pload = nullptr;
|
||||
const bool ok = v2_parse_frame(frame, frame_len, &hdr, &pload);
|
||||
if (!ok) { return false; }
|
||||
if (isAllBytesValue(buf, (uint16_t)buf_len, 0xFFU)) {
|
||||
if (out_res) *out_res = SPI_PARSE_ERR_ALL_FF;
|
||||
if (out_sof_offset) *out_sof_offset = 0xFFU;
|
||||
return false;
|
||||
}
|
||||
|
||||
*type = hdr.type;
|
||||
*payload = pload;
|
||||
*payload_len = hdr.payload_len;
|
||||
if (out_hdr) { *out_hdr = hdr; }
|
||||
return true;
|
||||
if (isAllBytesValue(buf, (uint16_t)buf_len, 0x00U)) {
|
||||
if (out_res) *out_res = SPI_PARSE_ERR_ALL_00;
|
||||
if (out_sof_offset) *out_sof_offset = 0xFFU;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buf_len < V2_OVERHEAD) {
|
||||
if (out_res) *out_res = SPI_PARSE_ERR_TOO_SHORT;
|
||||
if (out_sof_offset) *out_sof_offset = 0xFFU;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool found_sof = false;
|
||||
size_t first_sof_offset = 0xFFU;
|
||||
SpiParseResult best_err = SPI_PARSE_ERR_NO_SOF;
|
||||
|
||||
for (size_t i = 0; i + V2_OVERHEAD <= buf_len; ++i) {
|
||||
if (buf[i + V2_OFF_SOF0] != V2_SOF0 || buf[i + V2_OFF_SOF1] != V2_SOF1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!found_sof) {
|
||||
found_sof = true;
|
||||
first_sof_offset = i;
|
||||
}
|
||||
|
||||
const uint8_t version = buf[i + V2_OFF_VERSION];
|
||||
if (version != V2_VERSION) {
|
||||
best_err = SPI_PARSE_ERR_BAD_VERSION;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint16_t plen = v2_get_u16le(&buf[i + V2_OFF_LEN]);
|
||||
if (plen > V2_MAX_PAYLOAD) {
|
||||
best_err = SPI_PARSE_ERR_BAD_LEN;
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t total = V2_OVERHEAD + plen;
|
||||
if (i + total > buf_len) {
|
||||
best_err = SPI_PARSE_ERR_BAD_LEN;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t expect = v2_get_u32le(&buf[i + V2_HEADER_LEN + plen]);
|
||||
const uint32_t calc = v2_crc32(&buf[i], V2_HEADER_LEN + plen);
|
||||
if (expect != calc) {
|
||||
best_err = SPI_PARSE_ERR_CRC_MISMATCH;
|
||||
if (out_calc_crc) *out_calc_crc = calc;
|
||||
if (out_exp_crc) *out_exp_crc = expect;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (out_hdr) {
|
||||
out_hdr->version = version;
|
||||
out_hdr->flags = buf[i + V2_OFF_FLAGS];
|
||||
out_hdr->session_id = v2_get_u32le(&buf[i + V2_OFF_SESSION]);
|
||||
out_hdr->tx_seq = buf[i + V2_OFF_TX_SEQ];
|
||||
out_hdr->ack_seq = buf[i + V2_OFF_ACK_SEQ];
|
||||
out_hdr->credit = buf[i + V2_OFF_CREDIT];
|
||||
out_hdr->type = buf[i + V2_OFF_TYPE];
|
||||
out_hdr->payload_len = plen;
|
||||
out_hdr->frame_offset = i;
|
||||
}
|
||||
if (out_type) *out_type = buf[i + V2_OFF_TYPE];
|
||||
if (out_payload) *out_payload = (plen > 0U) ? &buf[i + V2_HEADER_LEN] : nullptr;
|
||||
if (out_payload_len) *out_payload_len = plen;
|
||||
if (out_res) *out_res = SPI_PARSE_OK;
|
||||
if (out_sof_offset) *out_sof_offset = (uint8_t)i;
|
||||
if (out_calc_crc) *out_calc_crc = calc;
|
||||
if (out_exp_crc) *out_exp_crc = expect;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (out_res) *out_res = best_err;
|
||||
if (out_sof_offset) *out_sof_offset = (uint8_t)first_sof_offset;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 调试日志辅助
|
||||
* 调试日志辅助(环形队列,避免事件并发时丢日志)
|
||||
* ============================================================ */
|
||||
|
||||
void AppSpiCommand::postLog(const char *msg)
|
||||
{
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
if (!msg) return;
|
||||
snprintf(m_logBuf, kLogBufSize, "[SPI-APP] %s", msg);
|
||||
m_logPending = true;
|
||||
uint8_t next = (uint8_t)((m_logHead + 1U) % kLogSlotCount);
|
||||
if (next != m_logTail) {
|
||||
strncpy(m_logQueue[m_logHead], msg, kLogSlotLen - 1U);
|
||||
m_logQueue[m_logHead][kLogSlotLen - 1U] = '\0';
|
||||
m_logHead = next;
|
||||
}
|
||||
#else
|
||||
(void)msg;
|
||||
#endif
|
||||
@@ -231,14 +422,29 @@ void AppSpiCommand::postLogFmt(const char *fmt, uint8_t val)
|
||||
{
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
if (!fmt) return;
|
||||
snprintf(m_logBuf, kLogBufSize, fmt, val);
|
||||
m_logPending = true;
|
||||
char tmp[kLogSlotLen];
|
||||
snprintf(tmp, sizeof(tmp), fmt, val);
|
||||
postLog(tmp);
|
||||
#else
|
||||
(void)fmt;
|
||||
(void)val;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppSpiCommand::postLogFmt32(const char *fmt, uint32_t v1, uint32_t v2)
|
||||
{
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
if (!fmt) return;
|
||||
char tmp[kLogSlotLen];
|
||||
snprintf(tmp, sizeof(tmp), fmt, (unsigned long)v1, (unsigned long)v2);
|
||||
postLog(tmp);
|
||||
#else
|
||||
(void)fmt;
|
||||
(void)v1;
|
||||
(void)v2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppSpiCommand::buildResponse(uint8_t *tx_buf, uint16_t tx_buf_size,
|
||||
uint16_t *tx_len, uint8_t rsp_type,
|
||||
const uint8_t *payload, uint16_t payload_len)
|
||||
@@ -402,18 +608,20 @@ extern void registerSpiAdcFeedbackCommand(AppSpiCommand &bus);
|
||||
extern void registerSpiMotorStateCommand(AppSpiCommand &bus);
|
||||
extern void registerSpiMotorOtaCommand(AppSpiCommand &bus);
|
||||
extern void registerSpiExmcDiagCommand(AppSpiCommand &bus);
|
||||
extern void registerSpiDiagCommand(AppSpiCommand &bus);
|
||||
AppSpiCommand AppSpiCommandBus;
|
||||
void AppSpiCommand::init()
|
||||
{
|
||||
m_csActive = false;
|
||||
m_rxLen = 0U;
|
||||
m_txLen = 0U;
|
||||
m_logPending = false;
|
||||
m_logHead = 0U;
|
||||
m_logTail = 0U;
|
||||
m_commandCount = 0U;
|
||||
m_ringPending = false;
|
||||
m_ringDeadline = 0U;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
memset(m_logBuf, 0, sizeof(m_logBuf));
|
||||
memset(m_logQueue, 0, sizeof(m_logQueue));
|
||||
memset(m_commands, 0, sizeof(m_commands));
|
||||
|
||||
registerSpiBootRequestCommand(AppSpiCommandBus);
|
||||
@@ -428,6 +636,7 @@ void AppSpiCommand::init()
|
||||
registerSpiMotorStateCommand(AppSpiCommandBus); /* CONFIG 关时为空注册 */
|
||||
registerSpiMotorOtaCommand(AppSpiCommandBus);
|
||||
registerSpiExmcDiagCommand(AppSpiCommandBus);
|
||||
registerSpiDiagCommand(AppSpiCommandBus);
|
||||
|
||||
configureGpio();
|
||||
configureDma();
|
||||
@@ -530,47 +739,122 @@ void AppSpiCommand::processCommand()
|
||||
const uint8_t *payload = nullptr;
|
||||
uint16_t payload_len = 0U;
|
||||
v2_header_t hdr;
|
||||
SpiParseResult parse_res = SPI_PARSE_OK;
|
||||
uint32_t calc_crc = 0U;
|
||||
uint32_t exp_crc = 0U;
|
||||
uint8_t sof_offset = 0xFFU;
|
||||
|
||||
if (!spi_try_parse_frame(m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len, &hdr)) {
|
||||
postLogFmt("[SPI-APP] 非v2帧 0x%02X -> ERROR", m_rxBuf[0]);
|
||||
/* 0xE8 payload 携带接收诊断——[0]=0xE8, [1]=rxLen, [2..5]=rxBuf 前 4 字节。
|
||||
* ESP32 /system/status rsp_err_last_p0..p3 直读,无需串口即可判定损坏形态
|
||||
* (全FF=DMA 未装/CS 丢失;SOF 错位=相位漂移;SOF 对但 CRC 败=帧内位错)。 */
|
||||
const bool parse_ok = spi_diagnose_and_parse_frame(
|
||||
m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len, &hdr,
|
||||
&parse_res, &calc_crc, &exp_crc, &sof_offset);
|
||||
|
||||
if (!parse_ok) {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.parse_fail_count++;
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
s_diag.last_error_rx_len = m_rxLen;
|
||||
s_diag.last_error_calc_crc = calc_crc;
|
||||
s_diag.last_error_expected_crc = exp_crc;
|
||||
s_diag.last_error_sof_offset = sof_offset;
|
||||
s_diag.last_error_reason = (uint8_t)parse_res;
|
||||
s_diag.last_error_code = 0xE8U;
|
||||
memcpy(s_diag.last_error_bytes, m_rxBuf, (m_rxLen < 8U) ? m_rxLen : 8U);
|
||||
);
|
||||
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0 && CONFIG_SPI_DIAG_ENABLE != 0)
|
||||
switch (parse_res) {
|
||||
case SPI_PARSE_ERR_CRC_MISMATCH: {
|
||||
SPI_DIAG_RECORD(s_diag.err_crc_mismatch++);
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] CRC mismatch: calc=0x%08lX exp=0x%08lX rxLen=%u",
|
||||
(unsigned long)calc_crc, (unsigned long)exp_crc, (unsigned)m_rxLen);
|
||||
postLog(log_msg);
|
||||
break;
|
||||
}
|
||||
case SPI_PARSE_ERR_NO_SOF: {
|
||||
SPI_DIAG_RECORD(s_diag.err_no_sof++);
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] No SOF: rxLen=%u raw=[%02X %02X %02X %02X]",
|
||||
(unsigned)m_rxLen, m_rxBuf[0], m_rxBuf[1], m_rxBuf[2], m_rxBuf[3]);
|
||||
postLog(log_msg);
|
||||
break;
|
||||
}
|
||||
case SPI_PARSE_ERR_BAD_VERSION: {
|
||||
SPI_DIAG_RECORD(s_diag.err_bad_version++);
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] Bad ver=0x%02X (exp 0x02) SOF_off=%u rxLen=%u",
|
||||
(sof_offset < m_rxLen - 2) ? m_rxBuf[sof_offset + 2] : 0xFFU,
|
||||
(unsigned)sof_offset, (unsigned)m_rxLen);
|
||||
postLog(log_msg);
|
||||
break;
|
||||
}
|
||||
case SPI_PARSE_ERR_BAD_LEN: {
|
||||
SPI_DIAG_RECORD(s_diag.err_bad_len++);
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] Bad len at SOF_off=%u rxLen=%u",
|
||||
(unsigned)sof_offset, (unsigned)m_rxLen);
|
||||
postLog(log_msg);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] Parse fail res=%d rxLen=%u",
|
||||
(int)parse_res, (unsigned)m_rxLen);
|
||||
postLog(log_msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sof_offset > 0U && sof_offset != 0xFFU) {
|
||||
SPI_DIAG_RECORD(s_diag.err_sof_drift++);
|
||||
}
|
||||
|
||||
/* 0xE8 payload 携带接收诊断——[0]=0xE8, [1]=rxLen, [2..3]=rxBuf 前 2 字节, [4]=parse_res, [5]=sof_offset */
|
||||
uint16_t tx_len = 0U;
|
||||
uint8_t diag[8];
|
||||
diag[0] = 0xE8U;
|
||||
diag[1] = (m_rxLen > 255U) ? 255U : (uint8_t)m_rxLen;
|
||||
diag[2] = (m_rxLen > 0U) ? m_rxBuf[0] : 0xFFU;
|
||||
diag[3] = (m_rxLen > 1U) ? m_rxBuf[1] : 0xFFU;
|
||||
diag[4] = (m_rxLen > 2U) ? m_rxBuf[2] : 0xFFU;
|
||||
diag[5] = (m_rxLen > 3U) ? m_rxBuf[3] : 0xFFU;
|
||||
diag[4] = (uint8_t)parse_res;
|
||||
diag[5] = sof_offset;
|
||||
diag[6] = 0x5AU;
|
||||
diag[7] = 0xA5U;
|
||||
/* 非v2帧时 hdr 未初始化:禁止缓存/推进序号(垃圾缓存会被 dup 重放) */
|
||||
SPI_DIAG_RECORD(s_diag.error_rsp_count++);
|
||||
spi_build_frame(kRspError, diag, sizeof(diag), m_txBuf, &tx_len);
|
||||
m_txLen = tx_len;
|
||||
finishResponseAndReady();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 正常帧不打日志(避免 UART 刷屏);CONFIG_SPI_APP_LOG_ENABLE
|
||||
* 只覆盖异常路径(非v2帧/未知type/SEQ跳变/session不匹配),正常态静默。 */
|
||||
// 解析合法 v2 帧成功
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.valid_v2_frames++;
|
||||
s_diag.last_rx_type = frame_type;
|
||||
s_diag.last_rx_seq = hdr.tx_seq;
|
||||
);
|
||||
if (sof_offset > 0U && sof_offset != 0xFFU) {
|
||||
SPI_DIAG_RECORD(s_diag.err_sof_drift++);
|
||||
}
|
||||
|
||||
g_pendingAckSeq = hdr.tx_seq;
|
||||
|
||||
if (frame_type == V2_TYPE_LINK_HELLO) {
|
||||
/* 合法 HELLO 已到:停掉起机补清,避免 reset 打断建连 */
|
||||
SPI_DIAG_RECORD(s_diag.hello_count++);
|
||||
Esp32En::cancelRelinkFromSpi();
|
||||
g_sessionId = hdr.session_id;
|
||||
g_txSeq = 0;
|
||||
g_expectedRxSeq = 0;
|
||||
g_v2Active = true;
|
||||
s_hasCachedResp = false;
|
||||
|
||||
/* 与 requestEsp32Read() 一致(L5 8bc01dd):建帧前 credit=1,
|
||||
* 避免 TX 占用时 g_localCredit=0 写入 HELLO_ACK,导致主机 remoteCredit=0 拒发 PING。 */
|
||||
g_localCredit = 1U;
|
||||
/* 建连 + 开机身份一次完成:HELLO_ACK = 6B 链路 + 16B 身份(版本/开关) */
|
||||
|
||||
uint8_t identity[V2_APP_IDENTITY_LEN];
|
||||
fillGd32AppIdentityPayload(identity);
|
||||
size_t built_len = 0U;
|
||||
@@ -583,6 +867,10 @@ void AppSpiCommand::processCommand()
|
||||
memcpy(s_lastRespBuf, m_txBuf, m_txLen);
|
||||
s_lastRespLen = m_txLen;
|
||||
s_hasCachedResp = true;
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.last_tx_type = V2_TYPE_LINK_HELLO;
|
||||
s_diag.last_tx_seq = 0;
|
||||
);
|
||||
|
||||
finishResponseAndReady();
|
||||
return;
|
||||
@@ -590,42 +878,77 @@ void AppSpiCommand::processCommand()
|
||||
|
||||
if (g_v2Active) {
|
||||
if (hdr.session_id != g_sessionId) {
|
||||
postLogFmt("[SPI-APP] session 不匹配 0x%02X -> LINK_ERROR",
|
||||
(uint8_t)(hdr.session_id & 0xFFU));
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.session_mismatch++;
|
||||
s_diag.last_error_code = V2_ERR_SESSION;
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
s_diag.error_rsp_count++;
|
||||
);
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] Session mismatch: rx=0x%08lX != cur=0x%08lX",
|
||||
(unsigned long)hdr.session_id, (unsigned long)g_sessionId);
|
||||
postLog(log_msg);
|
||||
#endif
|
||||
|
||||
size_t built_len = 0U;
|
||||
v2_build_error_frame(g_sessionId, g_txSeq, hdr.tx_seq,
|
||||
(uint8_t)g_localCredit, V2_ERR_SESSION,
|
||||
(uint8_t)g_expectedRxSeq, m_txBuf, &built_len);
|
||||
m_txLen = (uint16_t)built_len;
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.last_tx_type = V2_TYPE_LINK_ERROR;
|
||||
s_diag.last_tx_seq = g_txSeq;
|
||||
);
|
||||
finishResponseAndReady();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t dist = v2_seq_distance(hdr.tx_seq, g_expectedRxSeq);
|
||||
if (dist == 0U) {
|
||||
SPI_DIAG_RECORD(s_diag.seq_ok_count++);
|
||||
g_expectedRxSeq = (uint8_t)(g_expectedRxSeq + 1U);
|
||||
} else if (dist >= 128U) {
|
||||
SPI_DIAG_RECORD(s_diag.seq_dup_count++);
|
||||
if (s_hasCachedResp && hdr.tx_seq == s_lastReqSeq) {
|
||||
postLogFmt("[SPI-APP] 重复请求 seq=0x%02X -> 重发缓存", hdr.tx_seq);
|
||||
postLogFmt("[SPI-DIAG][INFO] 重复请求 seq=0x%02X -> 重发缓存", hdr.tx_seq);
|
||||
memcpy(m_txBuf, s_lastRespBuf, s_lastRespLen);
|
||||
m_txLen = s_lastRespLen;
|
||||
finishResponseAndReady();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
postLogFmt("[SPI-APP] SEQ jump rx=0x%02X", hdr.tx_seq);
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.seq_jump_count++;
|
||||
s_diag.last_error_code = V2_ERR_SEQ_JUMP;
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
);
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][WARN] SEQ jump: rx=0x%02X exp=0x%02X (gap=%u)",
|
||||
hdr.tx_seq, g_expectedRxSeq, (unsigned)dist);
|
||||
postLog(log_msg);
|
||||
#endif
|
||||
|
||||
g_expectedRxSeq = (uint8_t)(hdr.tx_seq + 1U);
|
||||
|
||||
if (frame_type != kTypeBootReq) {
|
||||
SPI_DIAG_RECORD(s_diag.error_rsp_count++);
|
||||
size_t built_len = 0U;
|
||||
v2_build_error_frame(g_sessionId, g_txSeq, hdr.tx_seq,
|
||||
(uint8_t)g_localCredit, V2_ERR_SEQ_JUMP,
|
||||
(uint8_t)g_expectedRxSeq, m_txBuf, &built_len);
|
||||
m_txLen = (uint16_t)built_len;
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.last_tx_type = V2_TYPE_LINK_ERROR;
|
||||
s_diag.last_tx_seq = g_txSeq;
|
||||
);
|
||||
finishResponseAndReady();
|
||||
return;
|
||||
}
|
||||
postLog("SEQ jump but BOOT_REQ still handled");
|
||||
postLog("[SPI-DIAG] SEQ jump but BOOT_REQ still handled");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,19 +973,46 @@ void AppSpiCommand::processCommand()
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
postLogFmt("[SPI-APP] 未知 type=0x%02X -> ERROR", frame_type);
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.unknown_type_count++;
|
||||
s_diag.last_unknown_type = frame_type;
|
||||
s_diag.last_error_code = 0xE9U;
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
s_diag.error_rsp_count++;
|
||||
);
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] 未知 type=0x%02X -> ERROR 0xE9", frame_type);
|
||||
postLog(log_msg);
|
||||
#endif
|
||||
uint8_t err = 0xE9U; /* E9=无 handler 认领 */
|
||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||
}
|
||||
|
||||
if (tx_len == 0U) {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.handler_empty_count++;
|
||||
s_diag.last_empty_type = frame_type;
|
||||
s_diag.last_error_code = 0xEAU;
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
s_diag.error_rsp_count++;
|
||||
);
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"[SPI-DIAG][ERR] handler t=0x%02X tx_len=0 -> ERROR 0xEA", frame_type);
|
||||
postLog(log_msg);
|
||||
#endif
|
||||
uint8_t err = 0xEAU; /* EA=handler 命中但未产出响应 */
|
||||
spi_build_frame(kRspError, &err, 1U, m_txBuf, &tx_len);
|
||||
/* handler 命中但没产出响应(典型=buildResponse 构帧失败/业务拒绝)走此兜底错误路径。 */
|
||||
postLogFmt("[SPI-APP] handler t=0x%02X tx_len=0 -> ERROR", frame_type);
|
||||
}
|
||||
|
||||
m_txLen = tx_len;
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.last_tx_type = m_txBuf[V2_OFF_TYPE];
|
||||
s_diag.last_tx_seq = g_txSeq;
|
||||
);
|
||||
|
||||
if (g_v2Active) {
|
||||
s_lastReqSeq = hdr.tx_seq;
|
||||
@@ -728,11 +1078,15 @@ void AppSpiCommand::resetEsp32Link()
|
||||
armSpiDmasForNextCs();
|
||||
updateDataReady();
|
||||
configureExti();
|
||||
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
|
||||
printf("[SPI-APP] resetEsp32Link: SPI0/DMA/EXTI re-init done\r\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AppSpiCommand::requestEsp32Read(uint8_t frame_type, const uint8_t *payload, uint16_t payload_len)
|
||||
{
|
||||
SPI_DIAG_RECORD(s_diag.ring_request_count++);
|
||||
|
||||
/* 若上一次按铃尚未被 ESP32 读走,拒绝重复按铃 */
|
||||
if (m_ringPending) {
|
||||
return false;
|
||||
@@ -765,39 +1119,61 @@ bool AppSpiCommand::requestEsp32Read(uint8_t frame_type, const uint8_t *payload,
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主循环轮询:打印延迟日志 + 按铃超时看护
|
||||
* 主循环轮询:打印延迟日志 + 按铃超时看护 + 周期/异常诊断报告
|
||||
* ============================================================ */
|
||||
|
||||
void AppSpiCommand::poll()
|
||||
{
|
||||
/* 打印 ISR 推迟的日志 */
|
||||
char log_copy[kLogBufSize] = {};
|
||||
bool has_log = false;
|
||||
|
||||
uint32_t save = RT_CRITICAL_SAVE();
|
||||
if (m_logPending) {
|
||||
memcpy(log_copy, m_logBuf, sizeof(log_copy));
|
||||
m_logPending = false;
|
||||
has_log = true;
|
||||
}
|
||||
RT_CRITICAL_RESTORE(save);
|
||||
|
||||
if (has_log) {
|
||||
printf("%s\r\n", log_copy);
|
||||
/* 1. 打印 ISR 推迟的日志队列(环形缓冲,避免丢弃) */
|
||||
while (m_logTail != m_logHead) {
|
||||
printf("%s\r\n", m_logQueue[m_logTail]);
|
||||
m_logTail = (uint8_t)((m_logTail + 1U) % kLogSlotCount);
|
||||
}
|
||||
|
||||
/* 按铃超时看护:ESP32 未在 kRingTimeoutMs 内来读则放弃本次按铃。
|
||||
/* 2. 按铃超时看护:ESP32 未在 kRingTimeoutMs 内来读则放弃本次按铃。
|
||||
* 防止 ESP32 不响应时永久占用 SPIready 线导致双方死锁。 */
|
||||
if (m_ringPending) {
|
||||
uint32_t save2 = RT_CRITICAL_SAVE();
|
||||
if (m_ringPending) {
|
||||
if ((int32_t)(SysTickTimer::getMs() - m_ringDeadline) >= 0) {
|
||||
printf("[SPI-APP] 按铃超时取消 (ESP32 未响应, 释放 SPIready 重试)\r\n");
|
||||
SPI_DIAG_RECORD(s_diag.ring_timeout_count++);
|
||||
#if (CONFIG_SPI_DIAG_PERIODIC_PRINT != 0)
|
||||
printf("[SPI-DIAG][WARN] 按铃超时取消 (ESP32 未在 %lums 内响应, 释放 SPIready 重试)\r\n",
|
||||
(unsigned long)kRingTimeoutMs);
|
||||
#endif
|
||||
cancelRing();
|
||||
}
|
||||
}
|
||||
RT_CRITICAL_RESTORE(save2);
|
||||
}
|
||||
|
||||
#if (CONFIG_SPI_DIAG_ENABLE != 0 && CONFIG_SPI_DIAG_PERIODIC_PRINT != 0)
|
||||
/* 3. 定期 / 增量打印 SPI 诊断摘要 */
|
||||
static uint32_t s_lastDiagPrintMs = 0;
|
||||
static uint32_t s_lastDiagErrCount = 0;
|
||||
const uint32_t now = SysTickTimer::getMs();
|
||||
const uint32_t totalErrs = s_diag.parse_fail_count + s_diag.rx_overrun_count +
|
||||
s_diag.short_rx_len_count + s_diag.session_mismatch +
|
||||
s_diag.seq_jump_count + s_diag.ring_timeout_count +
|
||||
s_diag.unknown_type_count + s_diag.handler_empty_count;
|
||||
|
||||
const bool hasNewError = (totalErrs > s_lastDiagErrCount && (now - s_lastDiagPrintMs >= 2000U));
|
||||
const bool periodicCheck = (s_diag.total_cs_assert > 0U && (now - s_lastDiagPrintMs >= 5000U));
|
||||
|
||||
if (hasNewError || periodicCheck) {
|
||||
s_lastDiagPrintMs = now;
|
||||
s_lastDiagErrCount = totalErrs;
|
||||
const uint8_t cs_pin = (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == SET) ? 1U : 0U;
|
||||
const uint8_t rdy_pin = (gpio_input_bit_get(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN) == SET) ? 1U : 0U;
|
||||
printf("[SPI-DIAG] CS:assert=%lu rls=%lu ok_v2=%lu | err[CRC:%lu noSOF:%lu short:%lu ovr:%lu sessMis:%lu jmp:%lu tmo:%lu] pins[CS:%d RDY:%d STAT:0x%02X]\r\n",
|
||||
(unsigned long)s_diag.total_cs_assert, (unsigned long)s_diag.total_cs_release,
|
||||
(unsigned long)s_diag.valid_v2_frames,
|
||||
(unsigned long)s_diag.err_crc_mismatch, (unsigned long)s_diag.err_no_sof,
|
||||
(unsigned long)s_diag.short_rx_len_count, (unsigned long)s_diag.rx_overrun_count,
|
||||
(unsigned long)s_diag.session_mismatch, (unsigned long)s_diag.seq_jump_count,
|
||||
(unsigned long)s_diag.ring_timeout_count, cs_pin, rdy_pin, (unsigned)(s_diag.last_spi_stat & 0xFFU));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -806,6 +1182,7 @@ void AppSpiCommand::poll()
|
||||
|
||||
void AppSpiCommand::onCsAssert()
|
||||
{
|
||||
SPI_DIAG_RECORD(s_diag.total_cs_assert++);
|
||||
if (m_csActive) {
|
||||
return;
|
||||
}
|
||||
@@ -817,9 +1194,16 @@ void AppSpiCommand::onCsAssert()
|
||||
|
||||
void AppSpiCommand::onCsRelease()
|
||||
{
|
||||
SPI_DIAG_RECORD(s_diag.total_cs_release++);
|
||||
if (!m_csActive) {
|
||||
/* EXTI 丢失下降沿时:仍尝试收尾并 re-arm,避免永久失步(对齐 BOOT) */
|
||||
m_rxLen = stopSpiDmas(kRxBufSize);
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.missed_cs_assert++;
|
||||
s_diag.last_rx_len = m_rxLen;
|
||||
s_diag.last_spi_stat = (uint16_t)SPI_STAT(SPI0);
|
||||
);
|
||||
postLogFmt("[SPI-DIAG][WARN] CS release without assert (got %u B)", (uint8_t)m_rxLen);
|
||||
drainSpiRxHw();
|
||||
if (m_txLen > 0U) {
|
||||
m_txLen = 0U;
|
||||
@@ -839,18 +1223,15 @@ void AppSpiCommand::onCsRelease()
|
||||
|
||||
/* 停 DMA 并取本笔 RX 长度(先读 CHCNT 再 disable,否则计数器被清) */
|
||||
m_rxLen = stopSpiDmas(kRxBufSize);
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.last_rx_len = m_rxLen;
|
||||
s_diag.last_spi_stat = (uint16_t)SPI_STAT(SPI0);
|
||||
);
|
||||
drainSpiRxHw();
|
||||
|
||||
/* 若本次 CS 事务是 ESP32 响应 GD32 按铃来读请求帧,
|
||||
* 则 GD32 的请求帧已通过 DMA 输出到 MISO,取消按铃状态。
|
||||
*
|
||||
* 但需区分:ESP32 发的是 dummy 读(按铃响应)还是 v2 协议帧(如 PING/OTA/0xB0)。
|
||||
* 若 GD32 正在按铃时 ESP32 发协议帧,会因 m_ringPending=true 被误吞,
|
||||
* 导致命令无法处理。检测到 v2 SOF 时取消按铃并走正常命令处理。 */
|
||||
* 则 GD32 的请求帧已通过 DMA 输出到 MISO,取消按铃状态。 */
|
||||
if (m_ringPending) {
|
||||
/* 判断本次收到的数据是否为 v2 协议帧(扫描 SOF0 SOF1)。
|
||||
* 取代旧 containsSyncSof,直接用 V2_SOF0/V2_SOF1。
|
||||
* 0xB0 进 BOOT 现已走标准 v2 帧 handler,不再单独检测裸字节。 */
|
||||
bool is_v2_frame = false;
|
||||
if (m_rxLen >= 2U) {
|
||||
const uint16_t last = (m_rxLen < 16U) ? (uint16_t)(m_rxLen - 1U) : 16U;
|
||||
@@ -862,9 +1243,8 @@ void AppSpiCommand::onCsRelease()
|
||||
}
|
||||
}
|
||||
if (is_v2_frame) {
|
||||
/* ESP32 发的是 v2 协议帧(如 PING/OTA/0xB0 等),不是按铃响应。
|
||||
* 取消按铃,清空 TX 让 processCommand() 重新装载对应应答。
|
||||
* processCommand() 末尾由 updateDataReady() 依据新 m_txLen 驱动 PB6。 */
|
||||
SPI_DIAG_RECORD(s_diag.ring_collision_count++);
|
||||
postLog("[SPI-DIAG][INFO] Ring collision: host sent v2 frame while ring pending");
|
||||
m_ringPending = false;
|
||||
m_txLen = 0U;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
@@ -883,6 +1263,42 @@ void AppSpiCommand::onCsRelease()
|
||||
}
|
||||
|
||||
if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) {
|
||||
if (m_rxLen == 0U) {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.zero_rx_len_count++;
|
||||
s_diag.last_error_reason = (uint8_t)SPI_PARSE_ERR_TOO_SHORT;
|
||||
s_diag.last_error_rx_len = 0U;
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
);
|
||||
postLog("[SPI-DIAG][WARN] Ignored 0-byte transaction (SCK did not toggle)");
|
||||
} else if (isAllBytesValue(m_rxBuf, m_rxLen, 0xFFU)) {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.all_ff_count++;
|
||||
s_diag.last_error_reason = (uint8_t)SPI_PARSE_ERR_ALL_FF;
|
||||
s_diag.last_error_rx_len = m_rxLen;
|
||||
memcpy(s_diag.last_error_bytes, m_rxBuf, (m_rxLen < 8U) ? m_rxLen : 8U);
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
);
|
||||
postLogFmt("[SPI-DIAG][WARN] Ignored all-0xFF transaction (%u bytes)", (uint8_t)m_rxLen);
|
||||
} else if (isAllBytesValue(m_rxBuf, m_rxLen, 0x00U)) {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.all_00_count++;
|
||||
s_diag.last_error_reason = (uint8_t)SPI_PARSE_ERR_ALL_00;
|
||||
s_diag.last_error_rx_len = m_rxLen;
|
||||
memcpy(s_diag.last_error_bytes, m_rxBuf, (m_rxLen < 8U) ? m_rxLen : 8U);
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
);
|
||||
postLogFmt("[SPI-DIAG][WARN] Ignored all-0x00 transaction (%u bytes)", (uint8_t)m_rxLen);
|
||||
} else {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.short_rx_len_count++;
|
||||
s_diag.last_error_reason = (uint8_t)SPI_PARSE_ERR_TOO_SHORT;
|
||||
s_diag.last_error_rx_len = m_rxLen;
|
||||
memcpy(s_diag.last_error_bytes, m_rxBuf, (m_rxLen < 8U) ? m_rxLen : 8U);
|
||||
s_diag.last_error_uptime_ms = SysTickTimer::getMs();
|
||||
);
|
||||
postLogFmt("[SPI-DIAG][WARN] Ignored short transaction (%u bytes < 20B)", (uint8_t)m_rxLen);
|
||||
}
|
||||
m_rxLen = 0U;
|
||||
/* 起机毛刺/相位漂移后仅 re-arm DMA 不够:SPI0 可能已 OVR 卡死。
|
||||
* 顺手重配 SPI0+DMA(不动 EXTI/session),下一笔 HELLO 才能收进来。 */
|
||||
@@ -894,12 +1310,6 @@ void AppSpiCommand::onCsRelease()
|
||||
|
||||
m_txLen = 0U;
|
||||
memset(m_txBuf, 0xFF, sizeof(m_txBuf));
|
||||
|
||||
/* ★ 关键:在 processCommand 之前同步 g_localCredit。
|
||||
* 此时 m_txLen=0, m_ringPending=false → g_localCredit=1。
|
||||
* 若不做,buildResponse 会读到上一次 pushStatus/requestEsp32Read 留下的
|
||||
* g_localCredit=0 → 响应帧携带 credit=0 → ESP32 m_remoteCredit=0 → 后续发送死锁。
|
||||
* 这是电机 OTA 吐量只有 1 chunk/2s 的根因。 */
|
||||
updateDataReady();
|
||||
|
||||
/* 命令处理在 EXTI ISR 内同步完成(对齐 BOOT 的 isrProcessFrame 模式),
|
||||
@@ -920,12 +1330,20 @@ void AppSpiCommand::onInterrupt()
|
||||
/* RX 已改走 DMA0_CH1:RBNE 逐字节中断已禁用,本 ISR 只保留错误标志清理。
|
||||
* CS 边沿处理全部在 onCsExtiInterrupt 的 EXTI15 ISR 里完成。 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
/* RXORERR 理论上不应再出现(RX 走 DMA),但兜底清理避免标志卡死。
|
||||
* 读 SPI_DATA + SPI_STAT 清除 OVR 标志。 */
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.rx_overrun_count++;
|
||||
s_diag.last_spi_stat = (uint16_t)SPI_STAT(SPI0);
|
||||
);
|
||||
postLog("[SPI-DIAG][ERR] SPI0 RXORERR (RX Overrun Error)!");
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
(void)SPI_STAT(SPI0);
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
|
||||
SPI_DIAG_RECORD(
|
||||
s_diag.format_error_count++;
|
||||
s_diag.last_spi_stat = (uint16_t)SPI_STAT(SPI0);
|
||||
);
|
||||
postLog("[SPI-DIAG][ERR] SPI0 FERR (Frame Format Error)!");
|
||||
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user