docs: 定名 dcu-ecat-master, 登记 _probe_scan.py

目录名 ecat 过泛(tools/ 下另有 ESP32), 按同级项目 DCU_APP / ESP32_COMM /
spi_link 的风格定名 dcu-ecat-master: dcu 限定归属, ecat-master 表明是
EtherCAT 主站侧工具(区别于从站固件)。远端托管用小写连字符形式。
名字写入 README 标题(对外可见)与 .git/description(本地元数据)。

同时把 _probe_scan.py(扫描响应帧头在 SM1 RAM 的确切偏移)登记进脚本表,
补齐 _probe_wedge2 / _probe_stuck 两行说明。
This commit is contained in:
cbb
2026-09-03 02:41:19 +08:00
parent fc9e8f34e5
commit 8d414ae3fb
2 changed files with 75 additions and 1 deletions
+6 -1
View File
@@ -1,4 +1,6 @@
# ecat — DCU LAN9252 EtherCAT 主站工具集
# dcu-ecat-master
**DCU LAN9252 EtherCAT 主站工具集**(工作目录 `DCU/tools/ecat`)
在 Windows 上用 Python + pysoem(SOEM)直接从网卡驱动 DCU 的 LAN9252 EtherCAT 从站:
扫描总线、读写 SII EEPROM、CoE SDO 读写、PDO 循环收发,以及一个本地网页/桌面控制台。
@@ -26,6 +28,9 @@ venv\Scripts\python.exe -m pip install -r requirements.txt
| `ecat_flash_eeprom.py` | 通过网线烧写 `gen_eeprom.py` 生成的 SII 镜像,逐字回读校验(烧完需断电重启让 ESC 重新加载) |
| `ecat_sniff.py` | 旁路抓包,hexdump SOEM 自己发出的帧,用于对照裸帧格式 |
| `_probe_wedge.py` | 邮箱楔死现场逐环节定位:A 写 SM0 的 WKC → B `0x220` bit0 是否置位 → C SSC 是否清事件并置 SM1 就绪 → D SM1 是否滞留旧响应,用于区分 ESC 硬件楔死与 SSC 软件死循环 |
| `_probe_wedge2.py` | 按真实邮箱协议探测:查写邮箱空 → 写满 256B → 验事件/响应 |
| `_probe_stuck.py` | 验证「SM1 响应滞留 → 邮箱死锁」假说:复现失败 → 手动读走 SM1 清 bit3 → 再读是否恢复 |
| `_probe_scan.py` | 扫描定位响应帧头在 SM1 RAM 中的确切偏移,同步 dump SM0 请求侧 |
## 启动方式
+69
View File
@@ -0,0 +1,69 @@
"""_probe_scan.py — 精确定位响应帧头在 SM1 RAM 的偏移 + SM0 请求侧同步 dump。"""
import struct
import sys
import time
import ecat_server as es
import pysoem
r = es.api_open({"substr": "I226"})
if not r.get("ok"):
sys.exit(1)
m = es.g_master
s = m.slaves[0]
def fprd(addr, size):
try:
return bytes(s._fprd(addr, size, 20000))
except Exception:
return None
def sm_reg(addr):
d = fprd(addr, 8)
if d is None:
return None
start, length = struct.unpack("<HH", d[:4])
return {"start": start, "len": length, "ctl": d[4], "st": d[5], "act": d[6]}
m.state = pysoem.INIT_STATE | pysoem.STATE_ACK
m.write_state()
m.state_check(pysoem.INIT_STATE, 1_000_000)
m.config_init()
m.state = pysoem.PREOP_STATE | pysoem.STATE_ACK
m.write_state()
print("PREOP:", "OK" if m.state_check(pysoem.PREOP_STATE, 1_000_000) == pysoem.PREOP_STATE else "FAIL", flush=True)
sm0, sm1 = sm_reg(0x0800), sm_reg(0x0808)
print(f"SM0: start=0x{sm0['start']:04X} len={sm0['len']} | SM1: start=0x{sm1['start']:04X} len={sm1['len']}", flush=True)
# 注入请求(SOEM 标准格式 SDO 读 0x1018:01)
req = bytes([0x0A, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x10, 0x40, 0x01, 0x18, 0x10, 0x01, 0x00, 0x00, 0x00])
mbx = (req + bytes(sm0["len"]))[:sm0["len"]]
s._fpwr(sm0["start"], mbx)
print("请求已写入 SM0", flush=True)
time.sleep(0.2)
# 扫描 SM1 start-8 .. start+72
base = sm1["start"]
d = fprd(base - 8, 80)
if d:
print(f"\nSM1 RAM 扫描 [0x{base - 8:04X} .. 0x{base + 72:04X}]:", flush=True)
for i in range(0, 80, 8):
chunk = d[i:i + 8]
off = i - 8
mark = " <-- SM1.start" if off == 0 else ""
print(f" {off:+4d} (0x{base + off:04X}): {chunk.hex().upper()}{mark}", flush=True)
st1 = sm_reg(0x0808)
print(f"\nSM1 st=0x{st1['st']:02X}(bit3={st1['st'] >> 3 & 1})", flush=True)
# 同步看 SM0 侧(请求是否被读走)
st0 = sm_reg(0x0800)
print(f"SM0 st=0x{st0['st']:02X}(bit3={st0['st'] >> 3 & 1})", flush=True)
es.api_close({})
print("DONE", flush=True)