feat(ethercat): LAN9252 bring-up完成,EEPROM烧录改主站侧并增强DC/AL可观测性
- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪, 满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放 - 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000; ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0) - 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output, 否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF; gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐 - 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环 烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过); HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址 - DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器, task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示 - AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码, task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag - 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B); EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控 - CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径, 邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1) - motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3 - SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印 - 版本号更新至1.5.32
This commit is contained in:
@@ -157,6 +157,7 @@ static void hardResetCan(uint32_t canPeriph, rcu_periph_enum canRcu)
|
||||
|
||||
CanFd::Diag CanFd::s_diag{};
|
||||
CanFd::RxQueue CanFd::s_rxQueues[3]{};
|
||||
CanFd::TxQueue CanFd::s_txQueues[3]{};
|
||||
uint8_t CanFd::s_nextConsumeChannel = 0U;
|
||||
volatile CanFd::Mode CanFd::s_mode = CanFd::Mode::kModeAllPass;
|
||||
CanFd::ChannelStats CanFd::s_channelStats[3]{};
|
||||
@@ -420,6 +421,9 @@ void CanFd::initChannelHardware(uint8_t i)
|
||||
s_rxQueues[i].head = 0U;
|
||||
s_rxQueues[i].tail = 0U;
|
||||
s_rxQueues[i].count = 0U;
|
||||
s_txQueues[i].head = 0U;
|
||||
s_txQueues[i].tail = 0U;
|
||||
s_txQueues[i].count = 0U;
|
||||
|
||||
const bool exitOk = ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U);
|
||||
s_channelHwOk[i] = (nominalOk && fdOk && exitOk);
|
||||
@@ -930,7 +934,8 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
|
||||
* 网页发送数仅在 TX 中断确认 MTFNERR 后增加,不能用本函数返回值判断总线 ACK。
|
||||
*/
|
||||
CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc, bool periodic)
|
||||
const uint8_t *data, uint8_t dlc, bool periodic,
|
||||
bool allow_abort)
|
||||
{
|
||||
/* DLC=0 合法(仅 ID);DLC>0 时 data 必填 */
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) {
|
||||
@@ -990,8 +995,12 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
|
||||
/* 非周期业务发送在邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
|
||||
* SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort
|
||||
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */
|
||||
if (mailbox == CAN_NOMAILBOX) {
|
||||
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。
|
||||
*
|
||||
* ★ allow_abort=false(突发/软件队列路径)时不做 abort:邮箱全满直接返回
|
||||
* kErrMailbox,由 enqueueTx 把帧送入软件队列、TX 完成中断回灌。
|
||||
* 否则 abort+重投会把同通道已装载的旧帧顶掉(7 帧同通道突发只发 3 帧)。 */
|
||||
if (mailbox == CAN_NOMAILBOX && allow_abort) {
|
||||
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
|
||||
if (can_transmit_states(canPeriph, mb) != CAN_TRANSMIT_PENDING) {
|
||||
continue;
|
||||
@@ -1028,6 +1037,105 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
return CanTxResult::kErrMailbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 突发排入软件 TX 队列(详见头声明)。
|
||||
*
|
||||
* 场景:电机控制台 0xC7 突发一次发 4 帧同通道命令,
|
||||
* GD32 只有 3 个硬件邮箱,第 4 帧必丢。此队列把超载帧留下来,
|
||||
* TX 完成中断里持续回灌,前端 /can/send_burst 不再因 >3 帧掉帧。
|
||||
*/
|
||||
CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) {
|
||||
return CanTxResult::kErrInvalid;
|
||||
}
|
||||
if (dlc > 0U && data == nullptr) {
|
||||
return CanTxResult::kErrInvalid;
|
||||
}
|
||||
|
||||
TxQueue &q = s_txQueues[channelIndex];
|
||||
|
||||
/* 快速路径:队列空 → 直通硬件邮箱。装得下立即返回,不用等待 TX 完成中断。
|
||||
* ★ allow_abort=false:邮箱满时禁止 abort 旧帧顶替(那会把同通道已装载的
|
||||
* 旧帧静默丢弃,>3 帧同通道突发只剩最后 3 帧上线),一律落入软件队列,
|
||||
* 由 TX 完成中断 drainTxQueueIsr 回灌。 */
|
||||
if (q.count == 0U) {
|
||||
const CanTxResult r = transmitFrame(channelIndex, ext_id, data, dlc, false, false);
|
||||
if (r != CanTxResult::kErrMailbox) {
|
||||
return r; /* 装上了(kOk)或失败原因非邮箱满(kErrInvalid/kErrBusOff) */
|
||||
}
|
||||
/* 邮箱满:落入下面的入队路径 */
|
||||
}
|
||||
|
||||
/* 关中段:EXTI15(SPI burst) 与 CANx TX ISR 并发,保护 head/tail/count。
|
||||
* 用 CMSIS NVIC 接口(SPL 的 nvic_irq_enable 是 3 参版)。
|
||||
* 关的是该通道自身的 txIrq,避免关全局影响其他通道的实时性。 */
|
||||
const IRQn_Type irqn = kChannels[channelIndex].txIrq;
|
||||
NVIC_DisableIRQ(irqn);
|
||||
|
||||
if (q.count >= kTxQueueDepth) {
|
||||
/* 队列也满了:彻底装不下。此时不能再返回 kOk,
|
||||
* 否则前端会误判成功;只能丢弃并返回 kErrMailbox。 */
|
||||
NVIC_EnableIRQ(irqn);
|
||||
s_diag.error_count++;
|
||||
s_channelStats[channelIndex].error_count++;
|
||||
return CanTxResult::kErrMailbox;
|
||||
}
|
||||
|
||||
TxQueuedFrame &f = q.frames[q.tail];
|
||||
f.id = ext_id;
|
||||
f.dlc = dlc;
|
||||
if (dlc > 0U) {
|
||||
memcpy(f.data, data, dlc);
|
||||
}
|
||||
q.tail = static_cast<uint8_t>((q.tail + 1U) % kTxQueueDepth);
|
||||
q.count++;
|
||||
|
||||
NVIC_EnableIRQ(irqn);
|
||||
return CanTxResult::kOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CAN TX 完成中断末尾调用:用硬件邮箱空闲出的位置,把队列里的帧装回去。
|
||||
* @note 只在 ISR 内调用;关中断保护已在 transmitFrame 返回值判定外层完成,
|
||||
* 本函数本身不关中(邮箱空位是"单调减少再单调增加",多读无害)。
|
||||
*/
|
||||
void CanFd::drainTxQueueIsr(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount) {
|
||||
return;
|
||||
}
|
||||
TxQueue &q = s_txQueues[channelIndex];
|
||||
while (q.count > 0U) {
|
||||
TxQueuedFrame &f = q.frames[q.head];
|
||||
/* 复用 transmitFrame 的装载逻辑;allow_abort=false:这里邮箱刚空出来,
|
||||
* 必能装上,禁止 abort 其它 PENDING 邮箱(会静默丢弃同通道排队帧)。 */
|
||||
const CanTxResult r = transmitFrame(channelIndex, f.id, f.data, f.dlc, false, false);
|
||||
if (r == CanTxResult::kOk) {
|
||||
q.head = static_cast<uint8_t>((q.head + 1U) % kTxQueueDepth);
|
||||
q.count--;
|
||||
} else {
|
||||
/* kErrMailbox:理论上不该发生(刚刚空出一个邮箱),保守 break;
|
||||
* 其它错误(kErrInvalid)已经入队又因参数错必败,直接丢弃防死循环。 */
|
||||
if (r != CanTxResult::kErrMailbox) {
|
||||
q.head = static_cast<uint8_t>((q.head + 1U) % kTxQueueDepth);
|
||||
q.count--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CanFd::txQueueBacklog(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount) {
|
||||
return 0U;
|
||||
}
|
||||
return s_txQueues[channelIndex].count;
|
||||
}
|
||||
|
||||
void CanFd::abortPendingTx(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
|
||||
@@ -1334,6 +1442,10 @@ void CanFd::handleTxInterrupt(uint8_t channelIndex)
|
||||
}
|
||||
|
||||
CAN_TSTAT(canPeriph) = tstat & (CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2);
|
||||
|
||||
/* 队列里有积压帧时,趁刚空出的邮箱位置回灌。必须在 TSTAT 清标志之后调,
|
||||
* 否则 transmitFrame 看到的还是旧 PENDING 状态。 */
|
||||
drainTxQueueIsr(channelIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -142,8 +142,17 @@ public:
|
||||
|
||||
static bool hasRxFrame();
|
||||
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
|
||||
/**
|
||||
* @brief 直接向指定 CAN 通道发送一帧(绕过软件队列)。
|
||||
* @param allow_abort 邮箱全占时是否允许 abort 一个 PENDING 邮箱并重投一次。
|
||||
* - true(0xC0 单帧路径默认):非阻塞顶替,适合"最新命令优先"的单帧发送;
|
||||
* - false(突发/队列路径):邮箱满直接返回 kErrMailbox,交由 enqueueTx
|
||||
* 的软件队列缓存,由 TX 完成中断回灌——避免 abort 把同通道已装载的
|
||||
* 旧帧静默丢弃(>3 帧同通道突发只剩最后 3 帧上线的根因)。
|
||||
*/
|
||||
static CanTxResult transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc, bool periodic = false);
|
||||
const uint8_t *data, uint8_t dlc, bool periodic = false,
|
||||
bool allow_abort = true);
|
||||
|
||||
/** @brief 立即停掉指定通道所有 PENDING 发送邮箱(阻止 auto_retrans 残留重发,供停等式协议用)。 */
|
||||
static void abortPendingTx(uint8_t channelIndex);
|
||||
@@ -185,8 +194,36 @@ public:
|
||||
static void handleRxInterrupt(uint8_t channelIndex);
|
||||
static void handleTxInterrupt(uint8_t channelIndex);
|
||||
|
||||
/**
|
||||
* @brief 突发排入软件 TX 队列(供 SPI 0xC7 burst handler / 突发场景使用)。
|
||||
*
|
||||
* 解决「3 个发送邮箱是物理上限、>3 帧突发必丢」的问题:
|
||||
* - 队列空时尝试直通 transmitFrame(不排队、零延迟)。
|
||||
* - 邮箱满时把本帧追加到软件队列,TX 完成中断里持续出队回灌,
|
||||
* 保证任意大小突发(同通道 ≤ 队列深度)都不丢帧。
|
||||
*
|
||||
* 同步:本函数可在 EXTI15 ISR 内运行(SPI burst handler),与 CAN TX ISR
|
||||
* 并发;队列 Push/Pop 通过关中段互斥,无需 LDREX/STREX。
|
||||
*
|
||||
* @param[in] channelIndex 目标通道(0/1/2)。
|
||||
* @param[in] ext_id 29 位扩展 ID。
|
||||
* @param[in] data 数据负载;dlc>0 时必填。
|
||||
* @param[in] dlc 数据长度 0..64。
|
||||
* @retval CanTxResult::kOk 已进入硬件邮箱 OR 已入软件队列(统一视为已受理)。
|
||||
* @retval CanTxResult::kErrMailbox 软件队列也满。
|
||||
* @retval CanTxResult::kErrInvalid 参数非法。
|
||||
*/
|
||||
static CanTxResult enqueueTx(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
|
||||
/** @brief 软件 TX 队列当前积压帧数(统计/诊断用)。 */
|
||||
static uint32_t txQueueBacklog(uint8_t channelIndex);
|
||||
|
||||
private:
|
||||
static constexpr uint8_t kRxQueueDepth = 16U;
|
||||
/** 每通道软件突发队列深度:覆盖整块突发(CAN_BURST_HARD_MAX=16 帧)中同一通道的
|
||||
* 全部超载帧(3 邮箱之外最多 13 帧 = 16-3),保证任意 DLC 的整块突发不因队列满丢帧。 */
|
||||
static constexpr uint8_t kTxQueueDepth = 13U;
|
||||
|
||||
struct RxFrame {
|
||||
uint8_t channel;
|
||||
@@ -202,6 +239,22 @@ private:
|
||||
volatile uint8_t count;
|
||||
};
|
||||
|
||||
/** 排队等待硬件邮箱空位的 CAN 帧(仅 SDO 突发用,payload ≤ 64B)。 */
|
||||
struct TxQueuedFrame {
|
||||
uint32_t id;
|
||||
uint8_t data[64];
|
||||
uint8_t dlc;
|
||||
};
|
||||
struct TxQueue {
|
||||
TxQueuedFrame frames[kTxQueueDepth];
|
||||
volatile uint8_t head;
|
||||
volatile uint8_t tail;
|
||||
volatile uint8_t count;
|
||||
};
|
||||
|
||||
/** @brief CAN TX ISR 内驱动:把队列里能装的全部装进硬件邮箱。 */
|
||||
static void drainTxQueueIsr(uint8_t channelIndex);
|
||||
|
||||
static void initGpio();
|
||||
/**
|
||||
* @brief 根据当前模式为指定控制器配置接收过滤器。
|
||||
@@ -224,6 +277,7 @@ private:
|
||||
|
||||
static Diag s_diag;
|
||||
static RxQueue s_rxQueues[3];
|
||||
static TxQueue s_txQueues[3]; /**< CAN 突发软件队列(邮箱装不下时缓存) */
|
||||
static uint8_t s_nextConsumeChannel;
|
||||
static volatile Mode s_mode;
|
||||
static ChannelStats s_channelStats[3];
|
||||
|
||||
@@ -34,32 +34,54 @@ void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len)
|
||||
return;
|
||||
}
|
||||
|
||||
/* 掩码匹配 STATUS ID: 0x000N0100 */
|
||||
if ((id & kMotorStatusIdMask) != kMotorStatusIdMatch) {
|
||||
const uint32_t masked = id & kMotorStatusIdMask;
|
||||
|
||||
/* STATUS 帧 0x000N0100 (DLC 32, 实时值) */
|
||||
if (masked == kMotorStatusIdMatch) {
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
slot.node_id = node;
|
||||
slot.channel = channel;
|
||||
slot.online = 1U;
|
||||
slot.dlc = (len <= kMotorStatusDlc) ? len : kMotorStatusDlc;
|
||||
slot.rx_count++;
|
||||
slot.last_rx_sec = g_app_uptime_sec;
|
||||
slot.seq = ++s_motorTable.global_seq;
|
||||
|
||||
std::memcpy(slot.data, data, slot.dlc);
|
||||
if (slot.dlc < kMotorStatusDlc) {
|
||||
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
/* CONFIG 帧 0x000N0101 (DLC 24, 目标值回报) */
|
||||
if (masked == kMotorConfigIdMatch) {
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
/* CONFIG 帧不更新 online 状态(仅 STATUS 帧维护在线检测) */
|
||||
slot.node_id = node; /* 确保槽位不空 */
|
||||
slot.channel = channel;
|
||||
slot.config_rx_count++;
|
||||
slot.config_seq = ++s_motorTable.global_seq;
|
||||
|
||||
const uint8_t copyLen = (len <= kMotorConfigDlc) ? len : kMotorConfigDlc;
|
||||
std::memcpy(slot.config_data, data, copyLen);
|
||||
if (copyLen < kMotorConfigDlc) {
|
||||
std::memset(&slot.config_data[copyLen], 0, kMotorConfigDlc - copyLen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* node×channel 双键槽位:同节点号在不同通道各自独立缓存,互不覆盖 */
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
slot.node_id = node;
|
||||
slot.channel = channel;
|
||||
slot.online = 1U;
|
||||
slot.dlc = (len <= kMotorStatusDlc) ? len : kMotorStatusDlc;
|
||||
slot.rx_count++;
|
||||
slot.last_rx_sec = g_app_uptime_sec;
|
||||
slot.seq = ++s_motorTable.global_seq;
|
||||
|
||||
/* 拷贝原始 payload */
|
||||
std::memcpy(slot.data, data, slot.dlc);
|
||||
if (slot.dlc < kMotorStatusDlc) {
|
||||
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
|
||||
}
|
||||
}
|
||||
|
||||
void checkOffline()
|
||||
@@ -137,6 +159,9 @@ uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
|
||||
writeU32Le(&buf[offset + 4U], slot.rx_count);
|
||||
writeU32Le(&buf[offset + 8U], slot.seq);
|
||||
std::memcpy(&buf[offset + 12U], slot.data, kMotorStatusDlc);
|
||||
writeU32Le(&buf[offset + 44U], slot.config_rx_count);
|
||||
writeU32Le(&buf[offset + 48U], slot.config_seq);
|
||||
std::memcpy(&buf[offset + 52U], slot.config_data, kMotorConfigDlc);
|
||||
|
||||
offset += kMotorEntrySize;
|
||||
count++;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
static constexpr uint32_t kMotorStatusIdMask = 0xFFF0FFFFU; /* 屏蔽 node nibble [19:16] */
|
||||
static constexpr uint32_t kMotorStatusIdMatch = 0x00000100U; /* STATUS 类型码 */
|
||||
static constexpr uint32_t kMotorConfigIdMatch = 0x00000101U; /* CONFIG 类型码 (Motor→Host, DLC 24) */
|
||||
|
||||
static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES;
|
||||
/* 同一节点号可能同时挂在多个 CAN 通道上(两台电机同号异线),
|
||||
@@ -32,6 +33,7 @@ static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES;
|
||||
static constexpr uint8_t kMotorChannels = 3U;
|
||||
static constexpr uint8_t kMotorSlotCount = kMotorMaxNodes * kMotorChannels; /* 9×3 = 27 */
|
||||
static constexpr uint8_t kMotorStatusDlc = 32U;
|
||||
static constexpr uint8_t kMotorConfigDlc = 24U; /* CONFIG 帧 DLC */
|
||||
static constexpr uint16_t kMotorOfflineSec = CONFIG_MOTOR_OFFLINE_MS / 1000U;
|
||||
|
||||
/* ── 0xC5 推送周期运行时可调(ESP32 经 0xC5 子命令 0xFE 下发)── */
|
||||
@@ -48,11 +50,11 @@ static constexpr uint32_t kBootHeartbeatTimeoutSec = 3U; /* >3s 无 0x103 →
|
||||
static constexpr uint8_t kMotorActiveBootBit = 0x80U; /* 0xC5 page0 header active 字节 bit7 = boot_active */
|
||||
|
||||
/* ── 序列化常量 (0xC5 payload) ── */
|
||||
static constexpr uint8_t kMotorEntrySize = 44U; /* 1+1+1+1+4+4+32 */
|
||||
static constexpr uint8_t kMotorEntrySize = 76U; /* 1+1+1+1+4+4+32+4+4+24 (STATUS+CONFIG) */
|
||||
static constexpr uint8_t kMotorPageHeaderLen = 4U; /* page+total+count+active */
|
||||
static constexpr uint8_t kMotorEntriesPerPage = 5U; /* (236-4)/44 = 5.27 → 5 */
|
||||
static constexpr uint8_t kMotorEntriesPerPage = 3U; /* (236-4)/76 = 3.05 → 3 */
|
||||
static constexpr uint8_t kMotorTotalPages =
|
||||
(kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/5) = 6 */
|
||||
(kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/3) = 9 */
|
||||
|
||||
/// 单电机状态槽
|
||||
struct MotorSlot {
|
||||
@@ -64,6 +66,9 @@ struct MotorSlot {
|
||||
uint32_t last_rx_sec; ///< 最后收到帧时的 g_app_uptime_sec
|
||||
uint32_t seq; ///< 更新序号 (每次刷新 +1)
|
||||
uint8_t data[kMotorStatusDlc]; ///< 原始 STATUS payload
|
||||
uint32_t config_rx_count; ///< 累计收到 CONFIG 帧数
|
||||
uint32_t config_seq; ///< CONFIG 更新序号
|
||||
uint8_t config_data[kMotorConfigDlc]; ///< 原始 CONFIG payload (目标值回报)
|
||||
};
|
||||
|
||||
/// 全局电机状态表
|
||||
|
||||
@@ -13,9 +13,11 @@ constexpr uint8_t kBurstMaxFrames = 16U;
|
||||
/**
|
||||
* @brief CAN 跨通道突发发送处理器(TYPE 0xC7)。
|
||||
*
|
||||
* 一次 SPI 事务携带多帧,GD32 在 CS 释放中断内对每一帧调用非阻塞的
|
||||
* CanFd::transmitFrame()(仅装载对应通道 CAN 邮箱、立即返回),各通道邮箱
|
||||
* 背靠背装载完成后几乎同时上线,实现跨通道「近同时」发送(组内偏差 <1ms)。
|
||||
* 一次 SPI 事务携带多帧,GD32 在 CS 释放中断内对每一帧调用 CanFd::enqueueTx():
|
||||
* - 邮箱有空位:立即装入硬件邮箱。
|
||||
* - 邮箱满(>3 帧突发同通道时必然发生):进入每通道 13 帧深的软件队列
|
||||
* (禁用 transmitFrame abort+重投,否则旧帧会被静默顶替),
|
||||
* CAN TX 完成中断里自动回灌,保证整块突发(<=16 帧同通道)不丢帧。
|
||||
*
|
||||
* payload:
|
||||
* [0] count 条目数(1..kBurstMaxFrames)
|
||||
@@ -24,7 +26,7 @@ constexpr uint8_t kBurstMaxFrames = 16U;
|
||||
* [4..] count 个条目,每条目 = chnode(1) + data[dlc]
|
||||
* chnode = (ch<<4) | node,id = (node<<16) | suffix
|
||||
*
|
||||
* 响应 ACK:count(1) + 每条目 1 字节状态(CanTxResult 码,0x00=成功)。
|
||||
* 响应 ACK:count(1) + 每条目 1 字节状态(CanTxResult 码,0x00=已受理;邮箱满+队列满才回 0xE4)。
|
||||
*/
|
||||
class SpiCanBurstHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
@@ -80,7 +82,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
const CanFd::CanTxResult r =
|
||||
CanFd::transmitFrame(ch, id, (dlc > 0U) ? &p[1] : nullptr, dlc);
|
||||
CanFd::enqueueTx(ch, id, (dlc > 0U) ? &p[1] : nullptr, dlc);
|
||||
resp[1U + i] = static_cast<uint8_t>(r);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
/**
|
||||
* @file app_spi_cmd_exmc_diag.cpp
|
||||
* @brief SPI 0xA2 EXMC/HBI 诊断命令处理器:驱动 LAN9252 通断测试 pattern。
|
||||
* @brief SPI 0xA2 EXMC/HBI 诊断命令处理器:驱动 LAN9252 通断测试 pattern / NRST 手动诊断。
|
||||
*
|
||||
* 网页按钮 → ESP32 /spi/send → SPI 0xA2 → 本 handler → EscExmcBus::continuityDrive()
|
||||
* 网页按钮 → ESP32 /spi/send → SPI 0xA2 → 本 handler → 分流:
|
||||
* action 0~10 → EscExmcBus::continuityDrive() (EXMC 数据/控制线 pattern)
|
||||
* action 20~24 → EscLan9252::nrstDrive() (ECAT_NRST/PB10 拉低/拉高/浮空/回读)
|
||||
* 立即返回 ACK,不阻塞 SPI 总线。电平保持到下次调用或 action=0(stop)。
|
||||
* NRST action 返回的 status 字节 = 实际引脚电平(0/1),非 OK 码。
|
||||
*/
|
||||
#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"
|
||||
#include "comm/esc_lan9252.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -24,7 +28,12 @@ public:
|
||||
{
|
||||
uint8_t status = 0xEE;
|
||||
if (ctx.payload_len >= 1) {
|
||||
status = EscExmcBus::continuityDrive(ctx.payload[0]);
|
||||
const uint8_t action = ctx.payload[0];
|
||||
if (action >= 20U) {
|
||||
status = EscLan9252::nrstDrive(action - 20U);
|
||||
} else {
|
||||
status = EscExmcBus::continuityDrive(action);
|
||||
}
|
||||
}
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &status, 1);
|
||||
|
||||
Reference in New Issue
Block a user