2026-06-26 00:57:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @file ethercat_slave.cpp
|
2026-09-01 03:07:48 +08:00
|
|
|
|
* @brief EtherCAT 从站 init 探针实现。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 运行期 PDO 交换由 SSC 协议栈完成(Sync0_Isr → ECAT_Application →
|
|
|
|
|
|
* dc_app.c 的 APPL_InputMapping/APPL_OutputMapping),本文件只负责
|
|
|
|
|
|
* init 阶段的 HBI 探活、芯片 ID 与 AL 状态诊断。
|
2026-06-26 00:57:59 +08:00
|
|
|
|
*/
|
|
|
|
|
|
#include "comm/ethercat_slave.h"
|
|
|
|
|
|
#include "comm/esc_lan9252.h"
|
|
|
|
|
|
#include "sys/config.h"
|
2026-09-01 03:07:48 +08:00
|
|
|
|
#include <cstdio>
|
2026-06-26 00:57:59 +08:00
|
|
|
|
|
2026-06-29 01:51:02 +08:00
|
|
|
|
bool EthercatSlave::s_escReady = false;
|
|
|
|
|
|
uint32_t EthercatSlave::s_chipId = 0U;
|
|
|
|
|
|
uint8_t EthercatSlave::s_alState = 0U;
|
2026-06-26 00:57:59 +08:00
|
|
|
|
|
|
|
|
|
|
void EthercatSlave::init()
|
|
|
|
|
|
{
|
2026-07-27 01:49:18 +08:00
|
|
|
|
ECAT_LOGI("[ECAT][SLAVE] init: begin\r\n");
|
2026-07-20 02:36:56 +08:00
|
|
|
|
|
|
|
|
|
|
if (!EscLan9252::init()) {
|
|
|
|
|
|
/* EXMC 总线未就绪:无法继续读芯片 ID 与 AL 状态,统一上报 FAIL。 */
|
|
|
|
|
|
s_chipId = 0U;
|
|
|
|
|
|
s_alState = 0U;
|
|
|
|
|
|
s_escReady = false;
|
2026-07-27 01:49:18 +08:00
|
|
|
|
LOG_ERR("[ECAT][SLAVE] init: FAIL, ESC bus not ready\r\n");
|
2026-07-20 02:36:56 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-29 01:51:02 +08:00
|
|
|
|
|
2026-08-09 03:27:21 +08:00
|
|
|
|
/* 第 1 层调试可观测性:读取 BYTE_TEST 寄存器(0x064,默认 0x87654321)并打印 selfTest 结果 */
|
2026-06-29 01:51:02 +08:00
|
|
|
|
s_chipId = EscLan9252::readChipId();
|
|
|
|
|
|
const bool chipIdOk = (s_chipId == 0x87654321U);
|
2026-08-09 03:27:21 +08:00
|
|
|
|
ECAT_LOGI("[ECAT][SLAVE] byte_test=0x%08lX expected=0x%08lX selfTest=%s\r\n",
|
2026-07-20 02:36:56 +08:00
|
|
|
|
(unsigned long)s_chipId, (unsigned long)0x87654321UL,
|
|
|
|
|
|
chipIdOk ? "PASS" : "FAIL");
|
2026-06-29 01:51:02 +08:00
|
|
|
|
|
|
|
|
|
|
/* AL 状态守卫:仅在芯片 ID 匹配时读取 AL 状态(ID 不匹配时 EXMC 可能读回 0/0xFFFF,无意义)。
|
|
|
|
|
|
* AL ≥ SAFEOP(0x04) 才认为 SYNC0 可触发;完整 ESC 配置(SM/FMMU/AL Control/SYNC0)需 SSC 集成。 */
|
|
|
|
|
|
s_alState = 0U;
|
2026-07-20 02:36:56 +08:00
|
|
|
|
uint16_t alStatusReg = 0U; /* 0x0130:AL Status(bit0-3=state,bit8-15=AL Status Code) */
|
2026-06-29 01:51:02 +08:00
|
|
|
|
if (chipIdOk) {
|
2026-07-20 02:36:56 +08:00
|
|
|
|
alStatusReg = EscLan9252::initOnlyReadReg(0x0130U);
|
|
|
|
|
|
s_alState = (uint8_t)(alStatusReg & 0x000FU);
|
2026-06-26 00:57:59 +08:00
|
|
|
|
}
|
2026-07-20 02:36:56 +08:00
|
|
|
|
const uint8_t alCode = (uint8_t)((alStatusReg >> 8U) & 0xFFU);
|
2026-06-29 01:51:02 +08:00
|
|
|
|
s_escReady = chipIdOk && (s_alState >= AL_STATE_SAFEOP);
|
|
|
|
|
|
|
2026-07-20 02:36:56 +08:00
|
|
|
|
/* 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 配置错、邮箱协议错),需主站介入。 */
|
|
|
|
|
|
|
2026-09-02 02:38:35 +08:00
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* bring-up 诊断(1.5.35 引入,1.5.39 重新定性):经 CSR 间接端口读三个关键寄存器
|
|
|
|
|
|
* + 一次写回环,确认 EtherCAT 核心侧状态。
|
|
|
|
|
|
* 注意主站读身份/AL 走 ESC 以太网侧 + EEPROM(SII),完全不经过 GD32 的 PDI,
|
|
|
|
|
|
* 所以"主站能读到身份"不能证明下面任何一项成立。
|
|
|
|
|
|
* 1.5.39 起访问已改经 ECAT_CSR_CMD/CSR_DATA(EtherCAT 核心寄存器不在 HBI 直接
|
|
|
|
|
|
* 地址空间,DS 12.11);此前直读索引使本段恒读全 0,曾被误判为"EEPROM 加载竞态"
|
|
|
|
|
|
* 与"HBI 写路径损坏"两个错误结论。现在读到全 0 才真正指向 EEPROM/PDI 故障,
|
|
|
|
|
|
* 交叉验证用 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。
|
|
|
|
|
|
* 注意 1.5.40 及之前本行把 bit11 打成 "loaded",语义还正好反了
|
|
|
|
|
|
* (bit11=1 才是校验错),于是正常板子恒显示 "loaded=0",字面像"EEPROM 未加载",
|
|
|
|
|
|
* 正是历史上 1.5.37 那个已撤回的错误结论的诱因;现按位分别命名,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 影子。
|
|
|
|
|
|
* 1.5.39 起本回环经 ECAT_CSR_CMD/CSR_DATA 间接端口执行。历史注意:1.5.35
|
|
|
|
|
|
* 该回环报 FAIL 曾被误判为"HBI 写路径损坏",真因是当时直读直写 HBI 索引——
|
|
|
|
|
|
* EtherCAT 核心寄存器不在 HBI 直接地址空间(DS 12.11),读恒 0、写被丢弃。
|
|
|
|
|
|
* 网络侧诊断(tools/ecat/ecat_esc_regs.py)已证实 ESC 本体与 EEPROM 一直正常。 */
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2026-06-29 01:51:02 +08:00
|
|
|
|
if (!chipIdOk) {
|
2026-07-27 01:49:18 +08:00
|
|
|
|
LOG_ERR("[ECAT][SLAVE] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
|
2026-06-29 01:51:02 +08:00
|
|
|
|
} else if (s_alState < AL_STATE_SAFEOP) {
|
2026-09-02 02:38:35 +08:00
|
|
|
|
/* 区分"等主站"与"真故障":两者 AL 都 < SAFEOP,但只有后者需要排查。
|
|
|
|
|
|
* 1.5.40 及之前一律打 FAIL(partial),而冷启动无主站时必然走到这里,
|
|
|
|
|
|
* 与紧随其后的 "SSC MainInit OK" 自相矛盾,极易被误读为初始化失败。 */
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
2026-06-29 01:51:02 +08:00
|
|
|
|
} else {
|
2026-07-27 01:49:18 +08:00
|
|
|
|
ECAT_LOGI("[ECAT][SLAVE] ESC ready, chip id=0x%08lX, AL state=0x%02X(%s), code=0x%02X\r\n",
|
2026-07-20 02:36:56 +08:00
|
|
|
|
(unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
|
2026-06-29 01:51:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 02:38:35 +08:00
|
|
|
|
/* 终判措辞: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);
|
2026-06-26 00:57:59 +08:00
|
|
|
|
}
|