#ifndef DEVICE_APP_SPI_DIAG_H #define DEVICE_APP_SPI_DIAG_H #include #include #include "sys/config.h" /** * @file app_spi_diag.h * @brief SPI 链路全量硬件与协议诊断模块声明。 * * 用于快速量化与定位 SPI 通信故障根因: * - 物理/时序层:CS 边沿跳变、丢失下降沿、0 字节事务(SCK未走/DMA落后)、短包(<20B)、全FF/全00 * - 硬件状态:SPI0 RXORERR (接收FIFO溢出)、FERR (格式错误)、SPI_STAT 寄存器值 * - 协议/帧层:SOF丢失、相位偏移(SOF drift)、版本错误、超长/截断、CRC32位跳变(附带期望值与计算值) * - 状态机/会话:Session ID脱节、序号跳变(丢包)、序号重复(重发)、未处理命令TYPE、空响应 * - 握手/流控:PB6 DATA_READY 引脚电平、PA15 CS 引脚电平、主动按铃超时、按铃竞争碰撞 * - 现场快照:记录最近一次异常发生时刻、实际接收长度、前导原始字节及 CRC 对比 */ /** * @brief SPI 帧解析失败原因细分 */ enum SpiParseResult : uint8_t { SPI_PARSE_OK = 0U, ///< 成功解析合法 v2 帧 SPI_PARSE_ERR_TOO_SHORT = 1U, ///< 事务长度小于 v2 最小帧头 (rxLen < 20) SPI_PARSE_ERR_ALL_FF = 2U, ///< 接收数据全为 0xFF (从机浮空/DMA未就绪/MISO未连接) SPI_PARSE_ERR_ALL_00 = 3U, ///< 接收数据全为 0x00 (地电平短路或总线死锁) SPI_PARSE_ERR_NO_SOF = 4U, ///< 未扫描到 SOF 同步字 (0xA5 0x5A) SPI_PARSE_ERR_BAD_VERSION = 5U, ///< 找到 SOF,但 VERSION != 0x02 SPI_PARSE_ERR_BAD_LEN = 6U, ///< 长度异常 (payload_len > 236 或帧总长超出实际收到的 rxLen) SPI_PARSE_ERR_CRC_MISMATCH = 7U, ///< CRC32 校验失败 (物理信号干扰/位跳变) }; /** * @brief SPI 链路全量诊断计数器与状态快照 */ struct SpiDiagStats { /* 1. CS 边沿与事务统计 */ uint32_t total_cs_assert; ///< CS 下降沿次数 uint32_t total_cs_release; ///< CS 上升沿次数 uint32_t missed_cs_assert; ///< 丢失 CS 下降沿异常次数 uint32_t zero_rx_len_count; ///< RX 收到 0 字节 (SCK 未时钟化或 DMA 启动严重落后) uint32_t short_rx_len_count; ///< RX 收到 1~19 字节 (<20B 残包) uint32_t all_ff_count; ///< 全 0xFF 事务数 uint32_t all_00_count; ///< 全 0x00 事务数 /* 2. 硬件外设与状态 */ uint32_t rx_overrun_count; ///< SPI0 RXORERR (接收 FIFO 溢出) uint32_t format_error_count; ///< SPI0 FERR (帧格式错误) uint16_t last_spi_stat; ///< 最近一笔事务结束时的 SPI_STAT 寄存器值 /* 3. 帧解析统计 */ uint32_t valid_v2_frames; ///< 成功解析的合法 v2 帧总数 uint32_t parse_fail_count; ///< 帧解析失败总数 uint32_t err_no_sof; ///< 未找到 SOF0(0xA5)+SOF1(0x5A) uint32_t err_sof_drift; ///< SOF 偏移不在索引 0 (前导毛刺或字节时序偏移) uint32_t err_bad_version; ///< 版本不匹配 uint32_t err_bad_len; ///< 长度字段错误或事务截断 uint32_t err_crc_mismatch; ///< CRC32 校验失败 /* 4. 会话与序号流控 */ uint32_t hello_count; ///< 收到 LINK_HELLO 次数 uint32_t session_mismatch; ///< Session ID 不匹配 (对端重置或会话脱节) uint32_t seq_ok_count; ///< 序号严格递增正常次数 uint32_t seq_jump_count; ///< 序号跳变 (丢帧) uint32_t seq_dup_count; ///< 序号重复 (重发包) uint16_t last_rx_len; ///< 最近收到的 rx_len (字节数) uint8_t last_rx_seq; ///< 最近收到的 rx_seq uint8_t expected_rx_seq; ///< 当前期望的 rx_seq uint8_t last_tx_seq; ///< 最近发送的 tx_seq /* 5. 命令路由与处理 */ uint8_t last_rx_type; ///< 最近收到的命令 TYPE uint8_t last_tx_type; ///< 最近构建的响应 TYPE uint32_t unknown_type_count; ///< 未知命令 TYPE (无 handler 注册) uint8_t last_unknown_type; ///< 最近未知的 TYPE 值 uint32_t handler_empty_count; ///< Handler 执行但返回 tx_len == 0 uint8_t last_empty_type; ///< 最近产出 0 字节的 TYPE 值 uint32_t error_rsp_count; ///< GD32 主动返回 0x82 ERROR 响应次数 uint8_t last_error_code; ///< 最近一次返回 0x82 的错误码 (0xE8/0xE9/0xEA/V2_ERR_*) /* 6. 握手引脚与流控 */ uint32_t ring_request_count; ///< 主动按铃请求次数 uint32_t ring_timeout_count; ///< 按铃超时 (ESP32 未在期限内读走) uint32_t ring_collision_count; ///< 按铃与 ESP32 主动发帧竞争碰撞次数 uint8_t current_local_credit; ///< 当前 local credit (0 或 1) /* 7. 最近异常快照 (用于定位具体错误现场) */ uint32_t last_error_uptime_ms; ///< 异常发生时刻 SysTick 毫秒数 uint16_t last_error_rx_len; ///< 异常事务收到的字节数 uint8_t last_error_bytes[8]; ///< 异常事务前 8 字节原始数据 uint32_t last_error_calc_crc; ///< CRC 错误时: 计算出的 CRC uint32_t last_error_expected_crc;///< CRC 错误时: 帧内携带的 CRC uint8_t last_error_sof_offset; ///< SOF 所在的偏移位置 (255 表示未找到) uint8_t last_error_reason; ///< SpiParseResult 错误原因枚举 }; #if (CONFIG_SPI_DIAG_ENABLE != 0) /** @brief 获取当前全局 SPI 诊断统计快照引用 */ const SpiDiagStats& getSpiDiagStats(); /** @brief 复位清零所有 SPI 诊断统计计数器 */ void resetSpiDiagStats(); /** @brief 将解析结果枚举转换为可读字符串 */ const char* spiParseResultToString(SpiParseResult res); /** @brief 打印完整 SPI 链路诊断报告到串口 (ASCII 表格) */ void dumpSpiDiag(); #else inline const SpiDiagStats& getSpiDiagStats() { static const SpiDiagStats s_empty{}; return s_empty; } inline void resetSpiDiagStats() {} inline const char* spiParseResultToString(SpiParseResult /*res*/) { return "DISABLED"; } inline void dumpSpiDiag() {} #endif #endif /* DEVICE_APP_SPI_DIAG_H */