#!/usr/bin/env python3 """ESC 网络侧寄存器诊断(绕过 GD32/HBI,直接从 EtherCAT 线上读 LAN9252)。 背景:GD32 侧 1.5.37 实测 EEPROM 300ms 不加载(DL_STATUS/EEP_CSR 经 PDI 读全 0), 但 PDI 侧读全 0 也可能是"PDI 未使能"的次生现象,不能区分: A) EEPROM boot 加载真的失败(I2C 无应答 / CRC 错 / 寻址错) B) 加载其实成功,PDI 通路另有问题 本工具从网络侧读真实寄存器一锤定音,并可用 --reload 命令 ESC 当场重新加载验证。 步骤: 1) pysoem 读 SII 前 16 字 + 重算 CRC -> EEPROM 内容是否完好(网络侧 I2C 是否可用) 2) Npcap 裸 EtherCAT 帧 APRD 读 0x0000/0x0010/0x0110/0x0130/0x0140/0x0500/0x0502 -> EEP Status(0x0502) 的 loaded/NACK/CRC 错误位 3) --reload:APWR 0x0502=0x0300 触发重新加载,再读一遍状态对比 用法:ecat_esc_regs.py [adapter-substr] [--reload] (默认 I226) """ import ctypes import struct import sys import time import pysoem ECAT_ETHER_TYPE = 0x88A4 CMD_APRD = 0x07 CMD_APWR = 0x08 REGS = [ (0x0000, "Type/Revision"), (0x0010, "Station Address"), (0x0110, "DL Status"), (0x0130, "AL Status"), (0x0140, "ESC Config(PDI type)"), (0x0500, "EEPROM Control"), (0x0502, "EEPROM Status"), ] EEP_FORCE_ECAT = 0x0001 # 0x0500 bit0: ECAT 侧强制取得 EEPROM 接口 EEP_CMD_RELOAD = 0x0300 # 0x0502: reload 命令(同 SOEM EC_ECMD_RELOAD) def crc8_sii(data): """ETG.1000.6 word-7 checksum: CRC-8 poly 0x07, init 0xFF。""" crc = 0xFF for b in data: crc ^= b for _ in range(8): crc = ((crc << 1) ^ 0x07) & 0xFF if (crc & 0x80) else (crc << 1) & 0xFF return crc def find_adapter(substr): for a in pysoem.find_adapters(): desc = a.desc.decode() if isinstance(a.desc, bytes) else a.desc if substr.lower() in desc.lower(): return a raise SystemExit(f"没有找到描述包含 {substr!r} 的适配器") # ---------- 第 1 步:pysoem 读 SII 内容 + CRC 复算 ---------- def sii_check(adapter_name): master = pysoem.Master() master.open(adapter_name) try: if not master.config_init(): print("!! 总线上没有从站应答") return False slave = master.slaves[0] # 与 ecat_flash_eeprom.py 相同的读回偏移探针(写完即恢复) probe = b"\xC3\xA5" slave.eeprom_write(0x0200, probe) rb = bytes(slave.eeprom_read(0x0200)) off = rb.find(probe) slave.eeprom_write(0x0200, b"\xff\xff") if off < 0: print(f"!! 探针字读写失败,读回 {rb.hex().upper()}") print("!! 网络侧 EEPROM 访问已坏 -> I2C 物理层(R12 SDA 上拉/EEPROM 焊接)") return False words = [] for w in range(16): rb = bytes(slave.eeprom_read(w)) words.append(struct.unpack_from(" " f"{'MATCH' if crc_calc == crc_stored else '*** MISMATCH ***'}") vid = words[8] | (words[9] << 16) pc = words[10] | (words[11] << 16) print(f" VendorID=0x{vid:08X} ProductCode=0x{pc:08X}") return crc_calc == crc_stored finally: master.close() # ---------- 第 2 步:Npcap 裸 EtherCAT 帧读 ESC 寄存器 ---------- class PcapPktHdr(ctypes.Structure): _fields_ = [("tv_sec", ctypes.c_long), ("tv_usec", ctypes.c_long), ("caplen", ctypes.c_uint32), ("len", ctypes.c_uint32)] def load_wpcap(): for cand in ("wpcap.dll", r"C:\Windows\System32\Npcap\wpcap.dll"): try: return ctypes.WinDLL(cand) except OSError: continue raise SystemExit("找不到 wpcap.dll(Npcap)") def ecat_frame(cmd, idx, adp, ado, payload): """帧布局按 ecat_sniff.py 对 SOEM 流量的实测: EtherCAT 头 len = datagram 区长度(不含自身 2 字节); datagram 头 = 10 字节(cmd/idx/adp/ado/len/irq=0),后接 data + WKC(2)。""" dst = b"\xff" * 6 src = bytes([0x02, 0x44, 0x43, 0x55, 0x00, 0x01]) # 本地管理 MAC,仅作源标识 eth = dst + src + struct.pack(">H", ECAT_ETHER_TYPE) dlen = 10 + len(payload) + 2 ecat_hdr = struct.pack(" I2C 物理层: R12 SDA 上拉/焊接)") if v & 0x2000: bits.append("ERR:CRC(SII 校验失败 -> 内容或寻址)") if v & 0x4000: bits.append("ERR:CMD") if v & 0x8000: bits.append("BUSY") return "|".join(bits) if bits else "(无错误位置位)" def dump_regs(bus, tag): vals = {} print(f"--- ESC 寄存器({tag})---") for addr, name in REGS: v = bus.read16(addr) vals[addr] = v if v is None: print(f" 0x{addr:04X} {name}: *** 无应答 ***") continue extra = "" if addr == 0x0502: extra = " | " + decode_eep_status(v) elif addr == 0x0140: extra = f" | pdi_type=0x{v & 0xFF:02X}(期望 0x8D)" elif addr == 0x0110: extra = f" | pdi_oper={v & 0x1}" print(f" 0x{addr:04X} {name}: 0x{v:04X}{extra}") return vals def main(): substr = "I226" do_reload = False for arg in sys.argv[1:]: if arg == "--reload": do_reload = True else: substr = arg adapter = find_adapter(substr) print(f"适配器: {adapter.desc.decode() if isinstance(adapter.desc, bytes) else adapter.desc}") crc_ok = sii_check(adapter.name) bus = RawBus(adapter.name) try: vals = dump_regs(bus, "当前") if do_reload: print("\n触发 EEPROM 重新加载(0x0500=0x0001, 0x0502=0x0300)…") bus.transact(CMD_APWR, 0x0500, struct.pack(" GD32 侧读全 0 不是加载失败,而是 HBI 访问核心寄存器的通路问题,转查 PDI。") elif st & 0x1000: print("EEPROM 加载失败:NACK —— LAN9252 在 I2C 上得不到 EEPROM 应答。") print("=> 硬件:量 AT24C32 pin6(SCL)/pin5(SDA) 空闲电平应≈3.3V;") print(" SDA 若≈0V 或悬空 -> R12(10k->D3V3) 没焊(原理图标 DNP 的那颗)。") elif st & 0x2000: print("EEPROM 加载失败:CRC 校验错。") print("=> 内容/CRC/寻址问题:上方 SII CRC 重算" + ("也不符 -> 重跑 ecat_flash_eeprom.py 重刷" if not crc_ok else "相符 -> 内容对但 loader 读错位置,查 E2PSIZE strap 寻址")) elif not (st & 0x0800): print("loaded=0 且无错误位——loader 未跑/未跑完,异常状态,把完整输出发回分析。") else: print("loaded=1 但 pdi_type != 0x8D——EEPROM word0 不对,重刷 SII。") if __name__ == "__main__": main()