现有工具纳入版本管理: - ecat_tool.py: 一体化命令行主站(status/flash/eeprom/sdo/send) - ecat_server.py + ecat.html/css: 网页/桌面控制台(端口 8090) - ecat_scan.py / ecat_esc_regs.py / ecat_sniff.py: bring-up 扫描、 网络侧 ESC 寄存器诊断、裸帧抓包对照 - ecat_flash_eeprom.py: 经网线烧写 SII 镜像 + 逐字校验 - *.bat / *.vbs 启动入口 新增 .gitignore(排除 venv/ 与 __pycache__)、.gitattributes(不改写换行符)、 requirements.txt(锁定 pysoem 1.1.13 等依赖)、README.md。
126 lines
4.5 KiB
Python
126 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
||
"""Flash the SII EEPROM image (generated by gen_eeprom.py) into the LAN9252
|
||
over the EtherCAT cable — no external programmer, no HBI needed.
|
||
|
||
Usage:
|
||
ecat_flash_eeprom.py [adapter-substr] (default: I226)
|
||
|
||
Steps: load gen_eeprom.py in-process (its asserts validate the image),
|
||
probe the EEPROM byte order with a scratch word, write every non-0xFFFF
|
||
word with immediate read-back verify, then restore the scratch word.
|
||
Afterwards POWER CYCLE the board so the ESC reloads its config.
|
||
"""
|
||
import contextlib
|
||
import importlib.util
|
||
import io
|
||
import struct
|
||
import sys
|
||
|
||
import pysoem
|
||
|
||
GEN = r"G:\Robot_v2\DCU\DCU_APP\GD32E508_DCU_APP\comm\ssc\port\gen_eeprom.py"
|
||
SCRATCH_WORD = 0x0200 # inside the 0xFF filler, far beyond the categories
|
||
|
||
|
||
def load_image():
|
||
spec = importlib.util.spec_from_file_location("gen_eeprom", GEN)
|
||
mod = importlib.util.module_from_spec(spec)
|
||
buf = io.StringIO()
|
||
with contextlib.redirect_stdout(buf):
|
||
spec.loader.exec_module(mod) # runs its own self-check asserts
|
||
img = bytes(mod.eeprom)
|
||
print(f"镜像加载成功: {len(img)} 字节, checksum=0x{img[14]:02X}, "
|
||
f"VendorID=0x{struct.unpack_from('<I', img, 16)[0]:08X}, "
|
||
f"ProductCode=0x{struct.unpack_from('<I', img, 20)[0]:08X}")
|
||
return img
|
||
|
||
|
||
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
|
||
return None
|
||
|
||
|
||
def open_slave(substr):
|
||
adapter = find_adapter(substr)
|
||
if adapter is None:
|
||
raise SystemExit(f"没有找到描述包含 {substr!r} 的适配器")
|
||
master = pysoem.Master()
|
||
master.open(adapter.name)
|
||
try:
|
||
n = master.config_init()
|
||
except Exception as e:
|
||
raise SystemExit(f"总线上没有发现从站({type(e).__name__}: {e})")
|
||
if not n:
|
||
raise SystemExit("总线上没有发现从站:请检查板子供电和网线")
|
||
print(f"从站应答正常({n} 个)")
|
||
return master.slaves[0]
|
||
|
||
|
||
def probe_word_layout(slave):
|
||
"""Write 0x1234 at the scratch word and find where it lands in the
|
||
4-byte read-back, so verification slices the right two bytes."""
|
||
test = b"\x34\x12"
|
||
slave.eeprom_write(SCRATCH_WORD, test)
|
||
rb = bytes(slave.eeprom_read(SCRATCH_WORD))
|
||
off = rb.find(test)
|
||
if off < 0:
|
||
raise SystemExit(f"写入探针失败: 读回 {rb.hex().upper()}")
|
||
slave.eeprom_write(SCRATCH_WORD, b"\xff\xff") # restore erase state
|
||
rb2 = bytes(slave.eeprom_read(SCRATCH_WORD))
|
||
if rb2[off:off + 2] != b"\xff\xff":
|
||
raise SystemExit(f"探针字恢复失败: {rb2.hex().upper()}")
|
||
print(f"字节序/读回偏移探测: 写入 {test.hex()} 落在读回偏移 {off},恢复 OK")
|
||
return off
|
||
|
||
|
||
def main():
|
||
substr = sys.argv[1] if len(sys.argv) > 1 else "I226"
|
||
img = load_image()
|
||
slave = open_slave(substr)
|
||
verify_off = probe_word_layout(slave)
|
||
|
||
total = len(img) // 2
|
||
n_written = n_fail = 0
|
||
fails = []
|
||
for word in range(total):
|
||
val = struct.unpack_from("<H", img, word * 2)[0]
|
||
if val == 0xFFFF: # erase state, nothing to write
|
||
continue
|
||
data = struct.pack("<H", val)
|
||
try:
|
||
slave.eeprom_write(word, data)
|
||
except Exception as e:
|
||
fails.append((word, data.hex(), f"WRITE EXC {type(e).__name__}: {e}"))
|
||
n_fail += 1
|
||
continue
|
||
rb = bytes(slave.eeprom_read(word))[verify_off:verify_off + 2]
|
||
n_written += 1
|
||
if rb != data:
|
||
fails.append((word, data.hex(), rb.hex()))
|
||
n_fail += 1
|
||
if n_written % 32 == 0:
|
||
print(f" ... 已写 {n_written} 字")
|
||
|
||
print(f"\n刷写完成: 成功 {n_written} 字, 失败 {n_fail} 字 "
|
||
f"(跳过 0xFF 填充 {total - n_written - n_fail} 字)")
|
||
if fails:
|
||
print("失败明细 (word, 期望, 实际):")
|
||
for w, exp, act in fails[:20]:
|
||
print(f" word 0x{w:04X}: {exp.upper()} != {act.upper()}")
|
||
sys.exit(3)
|
||
|
||
# summary of key words
|
||
for name, w in (("PDI Control", 0), ("Checksum", 7), ("VendorID lo", 8),
|
||
("ProductCode lo", 0x0A), ("Size", 0x3E), ("Version", 0x3F)):
|
||
val = struct.unpack_from("<H", img, w * 2)[0]
|
||
print(f" word 0x{w:02X} {name} = 0x{val:04X}")
|
||
print("\n>>> 请给板子断电重新上电(ESC 重新加载 EEPROM 配置),")
|
||
print(">>> 然后运行 run_scan.bat I226 验证身份字。")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|