Files
DCU_APP/GD32E508_DCU_APP/comm/ethercat_slave.cpp
T
cbb 5fd986299c feat(comm): 1.6.51 CAN 空通道自动静默 + PDO 诊断 + OTA 布局共用头
变更点:
1. CAN-FD(device/CANFD.cpp、sys/config.h)
   - 发送统一 FD+BRS(DLC≤8 不再回退经典 CAN);OTA 期仍按通道压制 BRS
   - 空通道自动识别:连续 5 个 1Hz 窗口无有效帧且有错误 → 静默上报;有帧即恢复
   - 三路 CAN 默认全开;RX parse budget 48→64
2. EtherCAT 桥 / PDO
   - 段1 增 txtxid/rx00ff;版本应答 0x00FF 粘滞发布,避免被 CONFIG 冲掉
   - STATUS 禁止写入 xrx 快照;processSeq 新鲜度救援
3. OTA / 配置
   - sys/config.h include 仓库内 shared/ota_layout.h(与 BOOT 共用,不再依赖仓外路径)
   - ota_boot_request 对齐配置页 word 布局
4. docs/audit_ecat_spi_wifi_ota_compatibility.md:ECAT/SPI/WiFi/OTA 兼容性审计
5. .gitignore:忽略 .trae/、docs/traework-import、评审过程稿
2026-09-20 22:58:09 +08:00

165 lines
8.9 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.
/**
* @file ethercat_slave.cpp
* @brief EtherCAT 从站 init 探针实现。
*
* 运行期 PDO 交换由 SSC 协议栈完成(Sync0_Isr → ECAT_Application →
* dc_app.c 的 APPL_InputMapping/APPL_OutputMapping),本文件只负责
* init 阶段的 HBI 探活、芯片 ID 与 AL 状态诊断。
*/
#include "comm/ethercat_slave.h"
#include "comm/esc_lan9252.h"
#include "sys/config.h"
#include <cstdio>
bool EthercatSlave::s_escReady = false;
uint32_t EthercatSlave::s_chipId = 0U;
uint8_t EthercatSlave::s_alState = 0U;
void EthercatSlave::init()
{
ECAT_LOGI("[ECAT][SLAVE] init: begin\r\n");
if (!EscLan9252::init()) {
/* EXMC 总线未就绪:无法继续读芯片 ID 与 AL 状态,统一上报 FAIL。 */
s_chipId = 0U;
s_alState = 0U;
s_escReady = false;
LOG_ERR("[ECAT][SLAVE] init: FAIL, ESC bus not ready\r\n");
return;
}
/* 第 1 层调试可观测性:读取 BYTE_TEST 寄存器(0x064,默认 0x87654321)并打印 selfTest 结果 */
s_chipId = EscLan9252::readChipId();
const bool chipIdOk = (s_chipId == 0x87654321U);
ECAT_LOGI("[ECAT][SLAVE] byte_test=0x%08lX expected=0x%08lX selfTest=%s\r\n",
(unsigned long)s_chipId, (unsigned long)0x87654321UL,
chipIdOk ? "PASS" : "FAIL");
/* AL 状态守卫:仅在芯片 ID 匹配时读取 AL 状态(ID 不匹配时 EXMC 可能读回 0/0xFFFF,无意义)。
* AL ≥ SAFEOP(0x04) 才认为 SYNC0 可触发;完整 ESC 配置(SM/FMMU/AL Control/SYNC0)需 SSC 集成。 */
s_alState = 0U;
uint16_t alStatusReg = 0U; /* 0x0130:AL Status(bit0-3=state,bit8-15=AL Status Code) */
if (chipIdOk) {
alStatusReg = EscLan9252::initOnlyReadReg(0x0130U);
s_alState = (uint8_t)(alStatusReg & 0x000FU);
}
const uint8_t alCode = (uint8_t)((alStatusReg >> 8U) & 0xFFU);
s_escReady = chipIdOk && (s_alState >= AL_STATE_SAFEOP);
/* AL 状态码 → 可读字符串(EtherCAT 状态机定义,见 ETG.1000.6)。
* 0x01=INIT, 0x02=PREOP, 0x03=BOOT, 0x04=SAFEOP, 0x08=OP。
* 本工程 AL_STATE_SAFEOP=0x04 与官方定义一致;守卫 s_alState>=0x04,
* 因此 OP(0x08) 也会通过,SAFEOP(0x04) 刚好通过,BOOT/PREOP/INIT 不通过。 */
const char *alStateName = "UNKNOWN";
switch (s_alState) {
case 0x00U: alStateName = "NONE"; break;
case 0x01U: alStateName = "INIT"; break;
case 0x02U: alStateName = "PREOP"; break;
case 0x03U: alStateName = "BOOT"; break;
case 0x04U: alStateName = "SAFEOP"; break;
case 0x08U: alStateName = "OP"; break;
default: break;
}
/* AL Status Code(寄存器 0x0130 高 8 位):非 0 表示从站进入错误状态
* (如 SM Watchdog=0x1B、FMMU 配置错、邮箱协议错),需主站介入。 */
/* ============================================================
* bring-up 诊断:经 CSR 间接端口读三个关键寄存器
* + 一次写回环,确认 EtherCAT 核心侧状态。
* 注意主站读身份/AL 走 ESC 以太网侧 + EEPROM(SII),完全不经过 GD32 的 PDI,
* 所以"主站能读到身份"不能证明下面任何一项成立。
* 访问必须经 ECAT_CSR_CMD/CSR_DATA(EtherCAT 核心寄存器不在 HBI 直接
* 地址空间,DS 12.11);直读索引恒读全 0。
* 交叉验证用 tools/ecat/ecat_esc_regs.py 从网络侧读同样的寄存器。
* 本段在 EthercatSync::init() 使能 EXTI 之前执行,满足 initOnly* 的 INIT-ONLY 约束。
* ============================================================ */
uint16_t dlStatus = 0U;
if (chipIdOk) {
/* (1) DL Status 0x0110 bit0 = PDI operational。
* ESC 只有认为 PDI 就绪时,才接受 PDI 侧(GD32 经 HBI)写入的 AL Status;
* 该位为 0 时 SSC 的 SetALStatus 被静默丢弃 -> 主站恒读 INIT/code=0。 */
dlStatus = EscLan9252::initOnlyReadReg(0x0110U);
/* (2) ESC Configuration 0x0140:PDI 类型是否真被 EEPROM 加载为 HBI 16-bit(0x8D)。
* 读回 0x00/0xFF 说明 EEPROM 的 PDI Control 未生效、ESC 的 PDI 没启用
* (此时 HBI 读寄存器仍可能照常工作,极易误判为"硬件全通")。 */
const uint16_t escCfg = EscLan9252::initOnlyReadReg(0x0140U);
/* (3) EEPROM 接口状态 0x0502-0x0503(16 位字)。位定义按 ETG.1000.4 表 49
* 与 SSC esc.h:bit11=Checksum Error、bit12=EEPROM loading status(0=OK)、
* bit13=Missing EEPROM Acknowledge、bit14=Write Error、bit15=Busy。
* 0 全部代表正常。
* 真正的"EEPROM 已加载/PDI 可用"判据是 DL_STATUS(0x0110) bit0,见上方 (1)。 */
const uint16_t eepCsr = EscLan9252::initOnlyReadReg(0x0502U);
LOG_ERR("[ECAT][DIAG] DL_STATUS(0x110)=0x%04X pdi_oper=%u | "
"ESC_CFG(0x140)=0x%04X pdi_type=0x%02X(expect 0x8D) | "
"EEP_CSR(0x502)=0x%04X crc_err=%u load_err=%u ack_err=%u wr_err=%u busy=%u"
" (全 0=正常)\r\n",
(unsigned)dlStatus, (unsigned)(dlStatus & 0x0001U),
(unsigned)escCfg, (unsigned)(escCfg & 0x00FFU),
(unsigned)eepCsr,
(unsigned)((eepCsr >> 11U) & 0x1U),
(unsigned)((eepCsr >> 12U) & 0x1U),
(unsigned)((eepCsr >> 13U) & 0x1U),
(unsigned)((eepCsr >> 14U) & 0x1U),
(unsigned)((eepCsr >> 15U) & 0x1U));
/* (4) CSR 端口写回环:用 AL Event Mask(0x0204)做写-读-还原。该寄存器按设计
* 就是 PDI 侧可写(PDI 用它选择哪些 AL Event 产生 IRQ),不像 0x0012 Station
* Alias 在部分 ESC 上对 PDI 只读(会造成假 FAIL);本段在 MainInit/HW_Init
* 之前执行,还原后不影响 SSC 后续建立的 mask 影子。
* 回环必须经 ECAT_CSR_CMD/CSR_DATA 间接端口执行。 */
const uint16_t maskSaved = EscLan9252::initOnlyReadReg(0x0204U);
EscLan9252::initOnlyWriteReg(0x0204U, 0x0A50U);
const uint16_t maskRead = EscLan9252::initOnlyReadReg(0x0204U);
EscLan9252::initOnlyWriteReg(0x0204U, maskSaved); /* 还原,交回 SSC 管理 */
if (maskRead == 0x0A50U) {
LOG_ERR("[ECAT][DIAG] CSR write loopback PASS (0x204 wrote 0x0A50 read back OK)\r\n");
} else {
LOG_ERR("[ECAT][DIAG] CSR write loopback FAIL: wrote 0x0A50 read 0x%04X"
" -> 先查 CSR_CMD(0x304) busy 是否自清/EXMC 时序,再疑硬件\r\n",
(unsigned)maskRead);
}
}
/* 是否有主站在线:DL_STATUS(0x0110) 的 Physical Link 位 bit4/5/6 对应 Port0/1/2
* (DS 12.14.19)。三个端口都无 link = 网线未插/无主站,此时 AL 状态机根本没人驱动
* (AL 迁移由主站写 AL Control 0x0120 触发,从站不自跳),停在 INIT 是唯一正确行为。
* 参考:接主站时 DL_STATUS 通常为 0x5611(Port0 有 link 有 comm);
* 空载时为 0x5503(三口 loop closed、link/comm 全 0)。 */
const bool anyPhyLink = chipIdOk && ((dlStatus & 0x0070U) != 0U);
if (!chipIdOk) {
LOG_ERR("[ECAT][SLAVE] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
} else if (s_alState < AL_STATE_SAFEOP) {
/* 区分"等主站"与"真故障":两者 AL 都 < SAFEOP,但只有后者需要排查。
* 冷启动无主站时必然走到这里,报 WAIT_MASTER 而非 FAIL。 */
if (!anyPhyLink) {
LOG_ERR("[ECAT][SLAVE] WAIT_MASTER: no PHY link (DL_STATUS=0x%04X), "
"AL=0x%02X(%s) 由主站驱动,空载停在 INIT 属预期\r\n",
(unsigned)dlStatus, (unsigned)s_alState, alStateName);
} else {
LOG_ERR("[ECAT][SLAVE] chip id PASS, PHY link up (DL_STATUS=0x%04X) but "
"AL state=0x%02X(%s) < SAFEOP(0x%02X), code=0x%02X\r\n",
(unsigned)dlStatus, (unsigned)s_alState, alStateName,
(unsigned)AL_STATE_SAFEOP, (unsigned)alCode);
LOG_ERR("[ECAT][SLAVE] SYNC0 未触发:检查主站是否逐级 write_state"
" (PREOP->config_map->SAFEOP->OP) 并已配置 DC\r\n");
}
} else {
ECAT_LOGI("[ECAT][SLAVE] ESC ready, chip id=0x%08lX, AL state=0x%02X(%s), code=0x%02X\r\n",
(unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
}
/* 终判措辞:PDI/EEPROM 就绪但只差主站时报 WAIT_MASTER 而非 FAIL——本函数的
* s_escReady 要求 AL>=SAFEOP,无主站永远达不到,而 SSC 放行门控实际用的是
* isBusReady()&&isCoreReady()(见 initial.cpp),两者结论相反不应都叫 FAIL。 */
const char *verdict;
if (s_escReady) {
verdict = "OK";
} else if (chipIdOk && EscLan9252::isCoreReady() && !anyPhyLink) {
verdict = "WAIT_MASTER (PDI 就绪,等主站接入)";
} else {
verdict = "FAIL(partial)";
}
LOG_ERR("[ECAT][SLAVE] init: %s\r\n", verdict);
}