Files
DCU_APP/GD32E508_DCU_APP/comm/ethercat_slave.cpp
T
cbb 44d407390b fix(esc_lan9252): 修复LAN9252 HBI访问模型和IRQ输出使能问题
- 修正HBI访问模型:将ESC核心CSR(0x0000-0x0FFF)改为通过ECAT_CSR_CMD/DATA
  间接端口访问,解决此前直接寻址导致核心寄存器恒读0/写丢弃的问题

- 新增csrRead/csrWrite/pramRead/pramWrite接口,正确实现LAN9252访问端口操作

- 添加PDI就绪检测:等待DL_STATUS bit0置位确认EEPROM加载完成和EtherCAT核心使能

- 实现IRQ引脚输出使能:配置IRQ_CFG和INT_EN寄存器,使EtherCAT事件能到达PB1,
  解决PDI_Isr未触发导致SAFEOP->OP无限挂起的问题

- 为ESC_EXMC硬件诊断添加CONFIG_ESC_EXMC_DIAG门控,默认关闭避免影响正常运行

- 修正system_gd32e50x.c中HXTAL故障诊断的寄存器偏移和位设置错
2026-09-02 02:38:35 +08:00

172 lines
9.8 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 诊断(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);
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,但只有后者需要排查。
* 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");
}
} 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);
}