feat(project): 更新项目配置并增加ESC EEPROM功能

- 添加新的SPI命令文件:app_spi_cmd_can_burst.cpp 和 app_spi_cmd_exmc_diag.cpp
- 移除旧的SPI命令文件:app_spi_cmd_ota_wait.cpp, app_spi_cmd_ping.cpp,
  app_spi_cmd_can_bridge.cpp, app_spi_cmd_can_feedback.cpp,
  app_spi_cmd_id_snapshot.cpp, app_spi_cmd_adc_feedback.cpp,
  app_spi_cmd_motor_state.cpp, app_spi_cmd_motor_version.cpp,
  app_spi_cmd_motor_ota.cpp
- 新增SSC EtherCAT从站协议栈相关文件组,包含头文件和源文件
- 添加ESC EEPROM操作相关的头文件和源文件
- 修正HBI寄存器EXMC偏移地址映射错误
- 实现8位、16位、32位ESC寄存器读写函数
- 改进EEPROM初始化逻辑,添加重试机制和诊断功能
This commit is contained in:
cbb
2026-08-09 03:27:21 +08:00
parent 8e5da18100
commit a2d0b28b3d
49 changed files with 13258 additions and 554 deletions
@@ -1,15 +1,15 @@
/**
* @file app_spi_cmd_adc_feedback.cpp
* @brief GD32 ADC 调试反馈(温度 / VDDA / VIN)SPI 命令与 1Hz 主动推送。
* @brief GD32 ADC 调试反馈(温度 / VDDA / VIN)SPI 被动查询处理器。
*
* TYPE = SpiDefs::kTypeAdcFeedback (0xC4)
*
* 总开关:sys/config.h 中 CONFIG_SPI_ADC_FEEDBACK_ENABLE
* - 0:不注册 handler、不推送(本文件仍参与链接,函数空实现)
* - 1:1Hz 主动 requestEsp32Read(0xC4) + 支持 ESP32 被动查询
* - 0:不注册 handler(本文件仍参与链接,函数空实现)
* - 1:ESP32 周期查询(1s),本 handler 被动回当前 ADC 快照
*
* 与 0xC1 CAN 反馈同款模型:主循环 pushStep 非阻塞;ring 未读走则跳过。
* 优先级低于 CAN 反馈(runPending 里先 canFeedbackPushStep 再本函数)。
* 2026-08 请求驱动改造:0xC4 不再主动按铃推送(原 1Hz pushStep 移除),
* 查询响应 seq 每次递增,供前端判断有新快照。
*
* payload v1 固定 20 字节(小端):
* [0] ver=1
@@ -19,7 +19,7 @@
* [6..7] vin_adc_mV uint16 引脚分压后电压 mV
* [8..9] temp_raw uint16
* [10..11] vref_raw uint16
* [12..15] seq uint32 推送序号
* [12..15] seq uint32 快照序号(每次查询 +1)
* [16..19] uptime_sec uint32
*/
@@ -42,7 +42,6 @@ static constexpr uint8_t kPayloadVer = 1U;
static constexpr uint16_t kPayloadLen = 20U;
static uint32_t s_pushSeq = 0U;
static volatile bool s_needPush = false;
/**
* @brief 构建 0xC4 ADC 反馈 payload(20B)。
@@ -100,6 +99,7 @@ public:
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
uint8_t payload[kPayloadLen] = {};
++s_pushSeq; /* 每次查询回一帧新序号,前端可据此判断有新快照 */
const uint16_t n = buildAdcFeedbackPayload(payload, sizeof(payload));
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kTypeAdcFeedback, payload, n);
@@ -110,51 +110,8 @@ static SpiAdcFeedbackHandler s_handler;
} // namespace
/**
* @brief 标记需要推送一帧 0xC4(由 1Hz taskHeartbeat 调用)。
*/
void adcFeedbackStartRound()
{
s_needPush = true;
}
/**
* @brief 非阻塞推送一步:ring 空闲则 requestEsp32Read(0xC4)。
* @note 主循环调用;与 CAN 反馈共用 ring,忙则跳过。
*/
void adcFeedbackPushStep()
{
if (!s_needPush) {
return;
}
if (OtaBootRequest::isRebootPending()) {
s_needPush = false;
return;
}
/* 电机 OTA relay 活跃期间停止 ADC 反馈推送,避免按铃干扰 0xD1 数据流
* (详见 canFeedbackPushStep 同款注释)。 */
if (MotorOtaRelay::isActive()) {
return;
}
/* 与 CAN 反馈共用 ring:未读走则等下一主循环,不抢占 */
if (AppSpiCommandBus.isRingPending()) {
return;
}
uint8_t payload[kPayloadLen] = {};
const uint16_t n = buildAdcFeedbackPayload(payload, sizeof(payload));
if (n == 0U) {
s_needPush = false;
return;
}
++s_pushSeq;
writeU32Le(&payload[12], s_pushSeq);
if (AppSpiCommandBus.requestEsp32Read(SpiDefs::kTypeAdcFeedback, payload, n)) {
s_needPush = false;
}
}
/* 2026-08 请求驱动改造:0xC4 已改 ESP32 周期查询(1s,经 handler 被动响应),
* 主动按铃推送状态机整体移除(s_needPush/s_pushSeq 一并删除)。 */
/** @brief 注册 0xC4 处理器。 */
void registerSpiAdcFeedbackCommand(AppSpiCommand &bus)
@@ -164,10 +121,6 @@ void registerSpiAdcFeedbackCommand(AppSpiCommand &bus)
#else /* CONFIG_SPI_ADC_FEEDBACK_ENABLE == 0 */
/** @brief 开关关闭时的空实现。 */
void adcFeedbackStartRound() {}
/** @brief 开关关闭时的空实现。 */
void adcFeedbackPushStep() {}
/** @brief 开关关闭时不注册 handler。 */
void registerSpiAdcFeedbackCommand(AppSpiCommand & /*bus*/) {}
@@ -0,0 +1,99 @@
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "device/canfd.h"
#include "sys/config.h"
namespace {
/* 单次突发最多帧数。SPI payload 上限 236B,DLC=32 时实际 ~7 帧;这里放宽到 16 作硬上限,
* 约束 EXTI ISR 内背靠背 transmitFrame 的总耗时。 */
constexpr uint8_t kBurstMaxFrames = 16U;
/**
* @brief CAN 跨通道突发发送处理器(TYPE 0xC7)。
*
* 一次 SPI 事务携带多帧,GD32 在 CS 释放中断内对每一帧调用非阻塞的
* CanFd::transmitFrame()(仅装载对应通道 CAN 邮箱、立即返回),各通道邮箱
* 背靠背装载完成后几乎同时上线,实现跨通道「近同时」发送(组内偏差 <1ms)。
*
* payload:
* [0] count 条目数(1..kBurstMaxFrames)
* [1] dlc 所有条目统一的数据长度(CAN FD 0..64,CMD 为 32)
* [2..3] suffix CAN ID 低 16 位,大端(CMD=0x0200)
* [4..] count 个条目,每条目 = chnode(1) + data[dlc]
* chnode = (ch<<4) | node,id = (node<<16) | suffix
*
* 响应 ACK:count(1) + 每条目 1 字节状态(CanTxResult 码,0x00=成功)。
*/
class SpiCanBurstHandler final : public ISpiCommandHandler {
public:
bool canHandle(uint8_t frame_type) const override
{
return frame_type == SpiDefs::kTypeCanBurstSend;
}
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
/* 头部至少 4 字节:count + dlc + suffix(2) */
if (ctx.payload_len < 4U) {
uint8_t err = 0xE0U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
const uint8_t count = ctx.payload[0];
const uint8_t dlc = ctx.payload[1];
const uint16_t suffix = static_cast<uint16_t>(
(static_cast<uint16_t>(ctx.payload[2]) << 8) | ctx.payload[3]);
if (count == 0U || count > kBurstMaxFrames || dlc > 64U) {
uint8_t err = 0xE5U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
/* payload 必须与 count*(1+dlc) 精确匹配,防截断/越界 */
const uint16_t entry_len = static_cast<uint16_t>(1U + dlc);
const uint16_t need = static_cast<uint16_t>(4U + count * entry_len);
if (ctx.payload_len != need) {
uint8_t err = 0xE0U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
uint8_t resp[1U + kBurstMaxFrames];
resp[0] = count;
const uint8_t *p = &ctx.payload[4];
for (uint8_t i = 0U; i < count; ++i, p += entry_len) {
const uint8_t chnode = p[0];
const uint8_t ch = static_cast<uint8_t>((chnode >> 4) & 0x0FU);
const uint8_t node = static_cast<uint8_t>(chnode & 0x0FU);
const uint32_t id = (static_cast<uint32_t>(node) << 16) | suffix;
if (ch >= 3U) {
resp[1U + i] = 0xE1U; /* 通道非法:跳过该帧,其余照常发送 */
continue;
}
const CanFd::CanTxResult r =
CanFd::transmitFrame(ch, id, (dlc > 0U) ? &p[1] : nullptr, dlc);
resp[1U + i] = static_cast<uint8_t>(r);
}
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, resp, static_cast<uint16_t>(1U + count));
}
};
static SpiCanBurstHandler s_handler;
} // namespace
void registerSpiCanBurstCommand(AppSpiCommand &bus)
{
bus.registerHandler(&s_handler);
}
@@ -44,8 +44,8 @@
#include <cstdio>
#include <cstring>
/* ---- 文件内共享常量与辅助函数(供匿名命名空间内的 handler 与
* 全局作用域的 canFeedbackPushStep() 共同复用) ---- */
/* ---- 文件内共享常量与辅助函数(仅供本文件 handler 使用;原全局推送函数已随
* 2026-08 请求驱动改造移除) ---- */
static constexpr uint8_t kFeedbackChannelCount = 3U;
/* 业务 payload 上限:APP TX 缓冲 256 - v2 帧开销 20 = 236(与 ESP32 kFeedbackPayloadLen 一致) */
@@ -243,104 +243,8 @@ static SpiCanFeedbackHandler s_handler;
} // 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 */
/**
* @brief 激活新一轮 1Hz CAN 反馈推送(taskHeartbeat 调用)。
*/
void canFeedbackStartRound()
{
s_roundActive = true;
}
/**
* @brief 非阻塞推送一步 0xC1(单页);跳过未使能通道。
* @note 主循环调用;ring 忙或本轮未激活则立即返回。
*/
void canFeedbackPushStep()
{
static uint8_t s_channel = 0U;
static uint8_t s_page = 0U;
/* 已收到 0xB0 进入 Bootloader 请求,停止主动按铃避免干扰 OTA 流程。
* 复位前主循环仍在运行,但不再下拉 SPIready,让 ESP32 的
* waitForBootloaderReady 信号检测不受干扰。 */
if (OtaBootRequest::isRebootPending()) {
return;
}
/* 电机 OTA relay 活跃期间停止 CAN 反馈主动推送:
* OTA 数据流(0xD1)在 SPI 上是紧循环 stop-and-wait,本推送会拉低 PB6 按
* 铃、占用 m_txBuf,与 0xD1 的 ACK 响应争用单缓冲;ESP32 的 send/poll
* 两步法会把 0xC1 EVENT 帧当作 0xD1 响应误读,导致 feedTask 误判失败。
* OTA 结束后 s_roundActive 由 taskHeartbeat 重新激活。 */
if (MotorOtaRelay::isActive()) {
return;
}
/* 1Hz 节流:当前轮次未激活则等待 taskHeartbeat() 触发 */
if (!s_roundActive) {
return;
}
/* 上一帧尚未被 ESP32 读走,等待(不占用主线带宽) */
if (AppSpiCommandBus.isRingPending()) {
return;
}
/* 跳过编译期关闭的通道(与 APP PING V2_FEAT_CAN_CHx / CONFIG_CAN_CHx_ENABLE 一致) */
if (!CanFd::isChannelEnabled(s_channel)) {
s_page = 0U;
for (;;) {
++s_channel;
if (s_channel >= kFeedbackChannelCount) {
s_channel = 0U;
s_roundActive = false;
return;
}
if (CanFd::isChannelEnabled(s_channel)) {
break;
}
}
}
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);
}
/* 推进:每通道 2 页,仅遍历使能通道;到末通道后结束本秒轮次 */
if (++s_page >= 2U) {
s_page = 0U;
for (;;) {
++s_channel;
if (s_channel >= kFeedbackChannelCount) {
s_channel = 0U;
s_roundActive = false;
break;
}
if (CanFd::isChannelEnabled(s_channel)) {
break;
}
}
}
}
/* 2026-08 请求驱动改造:0xC1 CAN 反馈已改 ESP32 请求驱动(handleFeedback 被动
* 响应),主动推送早已禁用;本次随 0xC5/0xC4 一并移除全部推送残留。 */
/**
* @brief 注册 0xC1/0xC2 CAN 反馈与模式切换处理器。
@@ -0,0 +1,42 @@
/**
* @file app_spi_cmd_exmc_diag.cpp
* @brief SPI 0xA2 EXMC/HBI 诊断命令处理器:驱动 LAN9252 通断测试 pattern。
*
* 网页按钮 → ESP32 /spi/send → SPI 0xA2 → 本 handler → EscExmcBus::continuityDrive()
* 立即返回 ACK,不阻塞 SPI 总线。电平保持到下次调用或 action=0(stop)。
*/
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "comm/esc_exmc_bus.h"
namespace {
/** @brief SPI 0xA2 EXMC 诊断处理器。 */
class SpiExmcDiagHandler final : public ISpiCommandHandler {
public:
bool canHandle(uint8_t frame_type) const override
{
return frame_type == SpiDefs::kTypeExmcDiag;
}
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
uint8_t status = 0xEE;
if (ctx.payload_len >= 1) {
status = EscExmcBus::continuityDrive(ctx.payload[0]);
}
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, &status, 1);
}
};
static SpiExmcDiagHandler s_handler;
} // namespace
/** @brief 注册 0xA2 EXMC 诊断命令处理器。 */
void registerSpiExmcDiagCommand(AppSpiCommand &bus)
{
bus.registerHandler(&s_handler);
}
@@ -1,11 +1,9 @@
/**
* @file app_spi_cmd_motor_state.cpp
* @brief 多电机状态 250ms 主动推送 (TYPE 0xC5) 与被动查询处理器。
* @brief 多电机状态查询处理器 (TYPE 0xC5)。
*
* 与 0xC1 CAN 反馈 / 0xC4 ADC 反馈同款模型:
* - 主循环 pushStep 非阻塞;ring 未读走则跳过。
* - 优先级:CAN feedback > Motor state > ADC(runPending 调用顺序决定)。
* - 启动节拍:taskMotorStatePush()(250ms 独立任务位,与 1Hz heartbeat 解耦)。
* 2026-08 请求驱动改造:0xC5 由 ESP32 周期查询(250ms,ESP32 can_fb 任务),
* 本 handler 被动响应(请求 payload[0]=page,回该页快照);不再主动按铃推送。
* - 总开关:sys/config.h 中 CONFIG_MOTOR_STATE_ENABLE
*
* payload 格式 (每页):
@@ -81,61 +79,8 @@ static SpiMotorStateHandler s_handler;
} // namespace
/* ---- 主动推送状态机 ---- */
static volatile bool s_roundActive = true; /* 首次允许启动,无需等 1s */
static uint8_t s_page = 0U;
/**
* @brief 激活新一轮 0xC5 推送。
* @note 若上一轮仍在进行中(s_roundActive=true),不重置 s_page,
* 避免在 250ms 节拍 < 单轮 P0+P1 排空时间时反复打断、
* 导致 P1 永远没机会推(前端只看到 P0 的 5 个节点)。
* 新一轮请求会被合并到当前轮次,推完 P1 后自然进入下一轮。
* 这与 0xC1 canFeedbackStartRound() 的语义不同:CAN 反馈 1Hz
* 远大于排空时间,不会发生自打断;0xC5 在 250ms 节拍下需要这层保护。
*/
void motorStateStartRound()
{
s_roundActive = true;
/* 仅在轮次已结束时重置页号;进行中保持 s_page 不动 */
if (s_page >= kMotorTotalPages) {
s_page = 0U;
}
}
void motorStatePushStep()
{
if (!s_roundActive) {
return;
}
if (OtaBootRequest::isRebootPending()) {
s_roundActive = false;
return;
}
/* 电机 OTA relay 活跃期间停止 motor state 推送,避免按铃干扰 0xD1 数据流
* (详见 canFeedbackPushStep 同款注释)。 */
if (MotorOtaRelay::isActive()) {
return;
}
if (AppSpiCommandBus.isRingPending()) {
return;
}
uint8_t payload[kMotorPayloadMax] = {};
const uint16_t n = MotorState::buildPayload(s_page, payload, sizeof(payload));
if (n > 0U) {
(void)AppSpiCommandBus.requestEsp32Read(SpiDefs::kTypeMotorState, payload, n);
}
/* 推进页号;到末页后结束本轮 */
s_page++;
if (s_page >= kMotorTotalPages) {
s_page = 0U;
s_roundActive = false;
}
}
/* 2026-08 请求驱动改造:0xC5 已改 ESP32 周期查询(经 handler 被动响应),
* 主动按铃推送状态机整体移除。 */
void registerSpiMotorStateCommand(AppSpiCommand &bus)
{
@@ -144,8 +89,6 @@ void registerSpiMotorStateCommand(AppSpiCommand &bus)
#else /* CONFIG_MOTOR_STATE_ENABLE == 0 */
void motorStateStartRound() {}
void motorStatePushStep() {}
void registerSpiMotorStateCommand(AppSpiCommand & /*bus*/) {}
#endif
@@ -44,7 +44,7 @@ void motorVersionPushStep()
return;
}
/* 电机 OTA relay 活跃期间停止版本推送,避免按铃干扰 0xD1 数据流
* (详见 canFeedbackPushStep 同款注释)。dirty 保留不丢,OTA 结束后继续推。 */
* (0xC5/0xC4 已改请求驱动,0xC6 保留本门控)。dirty 保留不丢,OTA 结束后继续推。 */
if (MotorOtaRelay::isActive()) {
return;
}
@@ -383,11 +383,13 @@ extern void registerSpiBootRequestCommand(AppSpiCommand &bus);
extern void registerSpiOtaWaitCommand(AppSpiCommand &bus);
extern void registerSpiAppPingCommand(AppSpiCommand &bus);
extern void registerSpiCanBridgeCommand(AppSpiCommand &bus);
extern void registerSpiCanBurstCommand(AppSpiCommand &bus);
extern void registerSpiCanFeedbackCommand(AppSpiCommand &bus);
extern void registerSpiIdSnapshotCommand(AppSpiCommand &bus);
extern void registerSpiAdcFeedbackCommand(AppSpiCommand &bus);
extern void registerSpiMotorStateCommand(AppSpiCommand &bus);
extern void registerSpiMotorOtaCommand(AppSpiCommand &bus);
extern void registerSpiExmcDiagCommand(AppSpiCommand &bus);
AppSpiCommand AppSpiCommandBus;
void AppSpiCommand::init()
{
@@ -406,11 +408,13 @@ void AppSpiCommand::init()
registerSpiOtaWaitCommand(AppSpiCommandBus);
registerSpiAppPingCommand(AppSpiCommandBus);
registerSpiCanBridgeCommand(AppSpiCommandBus);
registerSpiCanBurstCommand(AppSpiCommandBus);
registerSpiCanFeedbackCommand(AppSpiCommandBus);
registerSpiIdSnapshotCommand(AppSpiCommandBus);
registerSpiAdcFeedbackCommand(AppSpiCommandBus); /* CONFIG 关时为空注册 */
registerSpiMotorStateCommand(AppSpiCommandBus); /* CONFIG 关时为空注册 */
registerSpiMotorOtaCommand(AppSpiCommandBus);
registerSpiExmcDiagCommand(AppSpiCommandBus);
configureGpio();
configureDma();
@@ -486,7 +490,8 @@ void AppSpiCommand::registerHandler(ISpiCommandHandler *handler)
void AppSpiCommand::finishResponseAndReady()
{
/* 有响应待发时 arm 按铃,阻止 canFeedbackPushStep 覆盖 m_txBuf;
/* 有响应待发时 arm 按铃(ESP32 主动请求的响应即靠此通知;
* 推送源已随 2026-08 请求驱动改造移除,无其他写者争用 m_txBuf)。
* updateDataReady 按 m_txLen/m_ringPending 驱动 PB6。
* 延迟日志由 poll() 无条件消费 m_logPending,无需再置任务位。 */
if (m_txLen > 0U) {
+5 -34
View File
@@ -148,43 +148,14 @@ private:
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 ADC 调试反馈(0xC4)主动推送一步。
* @note CONFIG_SPI_ADC_FEEDBACK_ENABLE=0 时为空操作。
*/
void adcFeedbackPushStep();
/** @brief 激活一次 ADC 调试反馈推送(1Hz taskHeartbeat)。 */
void adcFeedbackStartRound();
/**
* @brief 多电机状态(0xC5)主动推送一步。
* @note CONFIG_MOTOR_STATE_ENABLE=0 时为空操作。
*/
void motorStatePushStep();
/** @brief 激活新一轮多电机状态推送(1Hz taskHeartbeat)。 */
void motorStateStartRound();
/* 2026-08 请求驱动改造:0xC1 CAN 反馈 / 0xC5 电机状态 / 0xC4 ADC 已改
* ESP32 周期查询(本端仅被动响应 handler),主动按铃推送函数整体移除
* (canFeedbackPushStep/canFeedbackStartRound/adcFeedbackPushStep/
* adcFeedbackStartRound/motorStatePushStep/motorStateStartRound)。 */
/**
* @brief 多电机版本(0xC6)事件驱动推送步进:有 dirty 版本且 ring 空闲时按铃推一帧。
* @note CONFIG_MOTOR_VERSION_ENABLE=0 时为空操作。优先级低于 0xC1/0xC5/0xC4。
* @note CONFIG_MOTOR_VERSION_ENABLE=0 时为空操作。低频、事件驱动,保留推送。
*/
void motorVersionPushStep();
@@ -19,6 +19,10 @@ static constexpr uint8_t kTypeOtaReq = 0x01U;
static constexpr uint8_t kTypeOtaData = 0x02U;
static constexpr uint8_t kTypeOtaCancel = 0x03U;
static constexpr uint8_t kTypeAppPing = 0xA1U;
/** @brief EXMC/HBI 诊断命令(ESP32→GD32):payload[0]=action。
* 0=stop(恢复AF_PP), 1-4=数据pattern(0xFFFF/0x0000/0xAAAA/0x5555),
* 5=CS低, 6=RD低, 7=WR低, 8=A1高, 9=A2高。用于 LAN9252 通断测试。 */
static constexpr uint8_t kTypeExmcDiag = 0xA2U;
static constexpr uint8_t kTypeBootReq = 0xB0U;
static constexpr uint8_t kTypeCanSend = 0xC0U;
static constexpr uint8_t kTypeCanFeedback = 0xC1U; /**< ESP32 -> GD32 轮询 CAN 反馈快照 */
@@ -35,6 +39,13 @@ static constexpr uint8_t kTypeMotorState = 0xC5U;
* 由 CONFIG_MOTOR_VERSION_ENABLE 门控。 */
static constexpr uint8_t kTypeMotorVersion = 0xC6U;
/** @brief CAN 跨通道突发发送(ESP32→GD32):一次 SPI 事务背靠背入多个 CAN 邮箱,
* 实现跨通道近同时发送(组内 <1ms)。
* payload: count(1) dlc(1) suffix_hi(1) suffix_lo(1) 然后 count 个条目,
* 每条目 = chnode(1) + data[dlc];chnode=(ch<<4)|node,id=(node<<16)|suffix。
* 响应 ACK: count(1) + 每条目 1 字节状态(CanTxResult 码)。 */
static constexpr uint8_t kTypeCanBurstSend = 0xC7U;
/** @brief 电机 OTA relay 命令(ESP32→GD32):start / cancel / query。
* payload[0]=cmd(0=start,1=cancel,2=query), start 时 payload[1]=ch, payload[2..5]=fw_size LE */
static constexpr uint8_t kTypeMotorOtaCmd = 0xD0U;