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:
cbb
2026-09-01 03:07:48 +08:00
parent a2d0b28b3d
commit cfe7725b26
32 changed files with 22382 additions and 1349 deletions
+115 -3
View File
@@ -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);
}
/**