Files
DCU_APP/GD32E508_DCU_APP/device/spi/app_spi_command.h
T
cbb 76da7a3dd6 Refactor and clean up codebase
- Removed unused constants and type aliases in app_spi_command_defs.h and temp_sensor.h.
- Simplified ISpiCommandHandler destructor to default.
- Eliminated redundant methods and comments in temp_sensor and usart0 files.
- Consolidated writeU32Le function to app_spi_command_defs.h for reuse.
- Updated comments for clarity and accuracy in task_manager.h and task_runner.cpp.
- Removed hardfault.h as it contained no declarations.
- Cleaned up CANFD and watchdog implementations by removing dead code and unnecessary comments.
- Updated version numbers in config.h to reflect recent changes.
- Enhanced error handling and diagnostics in hardfault handling.
2026-07-15 02:49:37 +08:00

169 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef DEVICE_APP_SPI_COMMAND_H
#define DEVICE_APP_SPI_COMMAND_H
#include <cstdint>
class ISpiCommandHandler;
/**
* @brief APP 运行期 SPI 链路与命令路由模块。
*
* 本类现在主要承担两件事:
* - 维护 SPI0 从机的硬件链路(GPIO/SPI/DMA/EXTI)
* - 将接收到的合法帧按 TYPE 注册式分发给 ISpiCommandHandler
*
* 具体命令处理逻辑不再放在这里,而是拆到 device/spi/app_spi_cmd_*.cpp,
* 通过 registerHandler() 注册进来,保持本模块可扩展。
*
* 握手架构(v2 DATA_READY,PB6<->ESP32 GPIO10 开漏单线):
* - PB6 低 = 有待发数据(m_txLen>0 或 m_ringPending),高 = 队列空。
* - ESP32 主动发起:CS 发请求,CS 释放后 GD32 在 onCsRelease() ISR 内
* 完成帧处理+响应装载,由 updateDataReady() 据 m_txLen 驱动 PB6。
* - GD32 主动按铃:requestEsp32Read() 设 m_ringPending=true 且 m_txLen>0,
* updateDataReady() 拉低 PB6,ESP32 检测下降沿后发起读事务读走请求帧。
* - PB6 配置为开漏输出,setSpiReady(true)=高阻释放,setSpiReady(false)=下拉。
* - ESP32 不再操作 PB6,EXTI6 上升沿仅清标志,无业务含义。
*/
class AppSpiCommand {
public:
/** @brief 初始化 SPI0 从机命令通道并注册默认命令处理器。 */
void init();
/** @brief 主循环轮询:打印延迟日志 + GD32 主动按铃超时看护。 */
void poll();
/** @brief SPI0 中断处理入口。 */
void onInterrupt();
/** @brief CS 边沿中断处理入口。 */
void onCsExtiInterrupt();
/**
* @brief 注册一个 SPI 命令处理器,用于 TYPE 路由。
* @param[in] handler 命令处理器指针,调用方需保证其生命周期有效。
*/
void registerHandler(ISpiCommandHandler *handler);
/** @brief 记录一条待输出日志,主循环统一打印。 */
void postLog(const char *msg);
/** @brief 记录一条带 1 字节参数的待输出日志。 */
void postLogFmt(const char *fmt, uint8_t val);
/**
* @brief 将应答帧构建到指定发送缓冲区。
* @param[out] tx_buf 发送缓冲区。
* @param[in] tx_buf_size 发送缓冲区大小。
* @param[out] tx_len 构建完成后的帧长度。
* @param[in] rsp_type 响应类型。
* @param[in] payload 响应载荷。
* @param[in] payload_len 响应载荷长度。
*/
void 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);
/**
* @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 取消未完成的按铃(释放 SPIready、清 m_ringPending)。
* 0xB0 进 BOOT 等路径需公开调用,避免 0xC1 残留盖住 ACK。 */
void cancelRing();
static constexpr uint16_t kRxBufSize = 256U;
static constexpr uint16_t kTxBufSize = 256U;
static constexpr uint16_t kLogBufSize = 256U;
private:
static constexpr uint8_t kMaxCommands = 8U;
/** GD32 主动按铃超时(ms):ESP32 未在该时间内来读则放弃。 */
static constexpr uint32_t kRingTimeoutMs = 50U;
volatile bool m_csActive = false;
/** 本笔 CS 实际收到的字节数(CS 上升沿时由 RX DMA CHCNT 反算得到)。 */
volatile uint16_t m_rxLen = 0U;
volatile uint16_t m_txLen = 0U;
volatile bool m_logPending = false;
bool m_txActiveThisCs = false;
/** RX DMA 接收缓冲(替代旧 RBNE 逐字节累积的 m_rxBuf)。
* CS 上升沿时 m_rxLen 为本笔实际收到的字节数,本缓冲前 m_rxLen 字节有效。 */
uint8_t m_rxBuf[kRxBufSize] = {};
uint8_t m_txBuf[kTxBufSize] = {};
char m_logBuf[kLogBufSize] = {};
uint8_t m_commandCount = 0U;
ISpiCommandHandler *m_commands[kMaxCommands] = {};
/** GD32 主动按铃状态:true 表示已下拉 SPIready 等待 ESP32 来读。 */
volatile bool m_ringPending = false;
/** 按铃超时截止时刻(ms,基于 SysTickTimer::getMs)。 */
volatile uint32_t m_ringDeadline = 0U;
void configureGpio();
void configureSpi0();
void configureDma();
void configureExti();
void onCsAssert();
void onCsRelease();
void processCommand();
/** @brief 设置 m_ringPending=true 并启动超时看护截止时刻。
* 统一 7 处「m_ringPending=true; m_ringDeadline=now+timeout;」赋值模式。 */
void armRingPending();
/** @brief 在 CS 空闲期预装下一笔全双工 DMA(TX + RX)。
* 必须在首个 SCK 之前完成,否则 EXTI CS 下降相对 ESP32 时钟偏晚,
* 整笔事务会收 0 字节。对齐 BOOT SpiOta::armSpiDmasForNextCs。 */
void armSpiDmasForNextCs();
/** @brief 翻转 SPI Ready 握手引脚(PB6)电平(开漏线与)。
* @param[in] ready true=释放(高阻,靠上拉拉高), false=下拉(驱动低)。
* @note GPIO BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。
* 开漏模式下 setSpiReady(true) 仅写 BRR 不驱动高,由外部上拉拉高。
*/
void setSpiReady(bool ready);
/** @brief v2 DATA_READY 语义:依据待发数据驱动 PB6。
* PB6 低 = 有待发数据(m_txLen>0 或 m_ringPending),高 = 队列空。
* 替代旧 BUSY/READY 语义中所有 setSpiReady 调用。 */
void updateDataReady();
};
/**
* @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 命令总线实例。 */
extern AppSpiCommand AppSpiCommandBus;
#endif /* DEVICE_APP_SPI_COMMAND_H */