初始化
This commit is contained in:
316
ir_diag.py
Normal file
316
ir_diag.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
红外板 — 收发联合诊断
|
||||
====================
|
||||
一边发送候选协议帧,一边同时读取串口返回,
|
||||
看板子收到数据后有没有任何回应(ACK/NAK/回显等)。
|
||||
|
||||
同时抓取原始遥控信号的完整原始字节(不合并,带时间戳),
|
||||
检查是否有被 IDLE_TIMEOUT 截断的帧头/帧尾。
|
||||
|
||||
用法:
|
||||
python ir_diag.py -p COM8 # 诊断模式1: 重新抓遥控信号
|
||||
python ir_diag.py -p COM8 --echo # 诊断模式2: 发送+监听回应
|
||||
python ir_diag.py -p COM8 --boot # 诊断模式3: 捕获上电启动信息
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
|
||||
BAUDRATE = 9600
|
||||
|
||||
# 已知数据
|
||||
CUSTOMER_HI = 0x3B
|
||||
CUSTOMER_LO = 0xC4
|
||||
KEY_CODES = {0x0D: "上", 0x15: "下", 0x10: "左", 0x12: "右"}
|
||||
|
||||
|
||||
def find_port():
|
||||
"""自动找 USB-to-TTL"""
|
||||
ports = serial.tools.list_ports.comports()
|
||||
keywords = ["ch343", "ch340", "ch341", "usb-enhanced-serial",
|
||||
"usb serial", "ft232", "cp210", "pl2303", "wch"]
|
||||
for p in ports:
|
||||
combined = f"{p.description or ''} {p.hwid or ''}".lower()
|
||||
if any(kw in combined for kw in keywords):
|
||||
return p.device
|
||||
all_p = list(ports)
|
||||
return all_p[0].device if all_p else None
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 模式1: 详细捕获原始遥控信号 (逐字节, 不合并)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def capture_remote_detail(port: str):
|
||||
"""
|
||||
逐字节捕获,每个字节都带时间戳。
|
||||
可能发现: 帧头(如 AA 55)、长度字节、校验和等之前被忽略的字节。
|
||||
"""
|
||||
print(f"打开 {port} @ {BAUDRATE} baud")
|
||||
print("请按遥控器按键... (Ctrl+C 退出)\n")
|
||||
|
||||
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.1)
|
||||
print(f"{'时间':<12} {'字节':<6} {'HEX':<8} {'BIN':<12} {'ASCII'}")
|
||||
print("-" * 65)
|
||||
|
||||
last_time = time.time()
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
n = ser.in_waiting
|
||||
except serial.SerialException:
|
||||
break
|
||||
|
||||
if n > 0:
|
||||
now = time.time()
|
||||
gap = now - last_time
|
||||
data = ser.read(n)
|
||||
|
||||
for i, b in enumerate(data):
|
||||
ascii_ch = chr(b) if 32 <= b < 127 else "."
|
||||
ts = time.strftime("%H:%M:%S") + f".{int((now % 1) * 1000):03d}"
|
||||
|
||||
if i == 0 and gap > 0.3:
|
||||
# 新的红外信号 — 画分隔线
|
||||
print(f"\n{'─' * 65}")
|
||||
print(f" 新信号 ↑ 距上次 {gap:.1f}s")
|
||||
print(f"{ts:<12} [{i+1}/{n}] {b:02X} {b:08b} {ascii_ch}")
|
||||
|
||||
last_time = now
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
ser.close()
|
||||
|
||||
print("\n✓ 诊断完成。检查上面的字节序列:")
|
||||
print(" 1. 每次按键是否只有 3 个字节?")
|
||||
print(" 2. 前后有没有额外的帧头/帧尾字节?")
|
||||
print(" 3. 字节间有没有明显的停顿(说明是分帧发送)?")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 模式2: 发送并监听回应
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
# 候选发送协议 — 带"编号"以便快速定位有效协议
|
||||
CANDIDATES = [
|
||||
# (名称, 帧bytes)
|
||||
("raw_3bytes", bytes([0x3B, 0xC4, 0x0D])),
|
||||
("raw_3bytes_rev", bytes([0x0D, 0xC4, 0x3B])),
|
||||
("raw_plus_CR", bytes([0x3B, 0xC4, 0x0D, 0x0D])),
|
||||
("raw_plus_CRLF", bytes([0x3B, 0xC4, 0x0D, 0x0D, 0x0A])),
|
||||
("NEC_std", bytes([0x3B, 0xC4, 0x0D, 0xF2])),
|
||||
("NEC_addr_inv", bytes([0x3B, 0xC4 ^ 0xFF, 0x0D, 0x0D ^ 0xFF])),
|
||||
("A5_frame", bytes([0xA5, 0x03, 0x3B, 0xC4, 0x0D])),
|
||||
("A5_frame_sum", bytes([0xA5, 0x03, 0x3B, 0xC4, 0x0D, 0xE9])),
|
||||
("AA55_frame", bytes([0xAA, 0x55, 0x3B, 0xC4, 0x0D])),
|
||||
("AA55_frame_sum", bytes([0xAA, 0x55, 0x3B, 0xC4, 0x0D, 0xA2])),
|
||||
("FF_prefix", bytes([0xFF, 0xFF, 0x3B, 0xC4, 0x0D])),
|
||||
("FE_FD_frame", bytes([0xFE, 0x3B, 0xC4, 0x0D, 0xFD])),
|
||||
("A0_prefix", bytes([0xA0, 0x3B, 0xC4, 0x0D, 0x0A])),
|
||||
("01_prefix", bytes([0x01, 0x3B, 0xC4, 0x0D])),
|
||||
("AT_send_crlf", b"AT+SEND=3BC40D\r\n"),
|
||||
("text_hex_crlf", b"3BC40D\r\n"),
|
||||
("text_spaced_crlf", b"3B C4 0D\r\n"),
|
||||
("text_send_crlf", b"SEND 3BC40D\r\n"),
|
||||
("text_at_crlf", b"AT+IR=3BC40D\r\n"),
|
||||
("IR_SEND_crlf", b"IR_SEND=3BC40D\r\n"),
|
||||
]
|
||||
|
||||
|
||||
def send_and_listen(port: str):
|
||||
"""
|
||||
发送每个候选帧,然后立即监听串口回应。
|
||||
使用非阻塞读取: 发完等100ms,把期间收到的所有字节打印出来。
|
||||
"""
|
||||
print(f"打开 {port} @ {BAUDRATE} baud")
|
||||
print("收发联合诊断 — 每个协议发送后监听板子是否有回应\n")
|
||||
|
||||
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.1)
|
||||
|
||||
for i, (name, frame) in enumerate(CANDIDATES):
|
||||
# 清空接收缓冲
|
||||
ser.reset_input_buffer()
|
||||
ser.reset_output_buffer()
|
||||
|
||||
# 发送
|
||||
try:
|
||||
ser.write(frame)
|
||||
ser.flush()
|
||||
except serial.SerialException as e:
|
||||
print(f" [{i+1:>2}] {name}: ✗ 发送失败 — {e}")
|
||||
continue
|
||||
|
||||
# 等待回应 (给板子足够的处理时间)
|
||||
time.sleep(0.15)
|
||||
|
||||
# 读取所有可用数据
|
||||
response = bytearray()
|
||||
while True:
|
||||
n = ser.in_waiting
|
||||
if n == 0:
|
||||
break
|
||||
response.extend(ser.read(n))
|
||||
time.sleep(0.02)
|
||||
|
||||
# 打印结果
|
||||
resp_str = ""
|
||||
if response:
|
||||
resp_str = f"← 板子回应 {len(response)}B: {response.hex(' ').upper()}"
|
||||
try:
|
||||
ascii_str = response.decode("ascii", errors="replace").strip()
|
||||
if ascii_str:
|
||||
resp_str += f" [{ascii_str}]"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f" [{i+1:>2}] {name}")
|
||||
print(f" 发送: {frame.hex(' ').upper()}")
|
||||
if resp_str:
|
||||
print(f" {resp_str}")
|
||||
else:
|
||||
print(f" (板子无回应)")
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
ser.close()
|
||||
|
||||
print("\n" + "=" * 65)
|
||||
print("分析要点:")
|
||||
print(" 1. 有回应的 → 可能是板子返回了 ACK/NAK/回显,说明协议接近")
|
||||
print(" 2. 回应 = 发送内容 → 回显模式,说明板子期望特定格式")
|
||||
print(" 3. 回应包含 'OK'/'ERROR' 等 → 文本协议")
|
||||
print(" 4. 全无回应 → 要么协议全错,要么板子是单向的(不回显)")
|
||||
print("\n提示: 同时也用手机摄像头看红外LED是否发光")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 模式3: 上电启动捕获
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def capture_boot(port: str):
|
||||
"""
|
||||
打开串口后立即开始接收,看板子有没有上电启动信息。
|
||||
也尝试切换 DTR (部分板子用 DTR 供电/复位)。
|
||||
"""
|
||||
print("上电诊断 — 捕获板子启动信息\n")
|
||||
print("提示: 拔掉 USB-to-TTL 再插入,然后按 Enter 继续...")
|
||||
input()
|
||||
|
||||
# 先用 DTR 低电平"复位"
|
||||
try:
|
||||
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.1)
|
||||
ser.dtr = False
|
||||
time.sleep(0.5)
|
||||
ser.dtr = True
|
||||
print(f"已打开 {port} (DTR 复位完成)")
|
||||
except serial.SerialException as e:
|
||||
print(f"✗ 无法打开 {port}: {e}")
|
||||
return
|
||||
|
||||
print("等待板子启动输出... (10s)\n")
|
||||
|
||||
buffer = bytearray()
|
||||
start = time.time()
|
||||
|
||||
try:
|
||||
while time.time() - start < 10:
|
||||
n = ser.in_waiting
|
||||
if n > 0:
|
||||
data = ser.read(n)
|
||||
buffer.extend(data)
|
||||
# 有数据就打印
|
||||
ts = time.strftime("%H:%M:%S")
|
||||
print(f" [{ts}] {data.hex(' ').upper()}", end="")
|
||||
try:
|
||||
ascii_str = data.decode("ascii", errors="replace").strip()
|
||||
if ascii_str:
|
||||
print(f" [{ascii_str}]")
|
||||
else:
|
||||
print()
|
||||
except Exception:
|
||||
print()
|
||||
start = time.time() # 有新数据就重置计时
|
||||
else:
|
||||
time.sleep(0.05)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
ser.close()
|
||||
|
||||
if buffer:
|
||||
print(f"\n启动阶段共收到 {len(buffer)} 字节:")
|
||||
print(f" HEX: {buffer.hex(' ').upper()}")
|
||||
try:
|
||||
text = buffer.decode("ascii", errors="replace")
|
||||
if text.strip():
|
||||
print(f" ASCII: {text.strip()}")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
print("\n无启动输出 — 板子不发送启动信息")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 综合诊断: 以上全部
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def run_all(port: str):
|
||||
"""运行全部诊断"""
|
||||
print("=" * 65)
|
||||
print(" 阶段1: 上电启动捕获")
|
||||
print("=" * 65)
|
||||
capture_boot(port)
|
||||
|
||||
print("\n" + "=" * 65)
|
||||
print(" 阶段2: 详细遥控信号捕获")
|
||||
print("=" * 65)
|
||||
capture_remote_detail(port)
|
||||
|
||||
print("\n" + "=" * 65)
|
||||
print(" 阶段3: 发送+监听回应")
|
||||
print("=" * 65)
|
||||
send_and_listen(port)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="红外板 — 收发联合诊断",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument("-p", "--port", default=None, help="串口")
|
||||
parser.add_argument("--boot", action="store_true", help="捕获上电启动信息")
|
||||
parser.add_argument("--echo", action="store_true", help="发送候选协议+监听回应")
|
||||
parser.add_argument("--capture", action="store_true", help="详细捕获遥控信号")
|
||||
parser.add_argument("--all", action="store_true", help="运行全部诊断")
|
||||
args = parser.parse_args()
|
||||
|
||||
port = args.port or find_port()
|
||||
if not port:
|
||||
print("✗ 未找到串口。请用 -p 指定。")
|
||||
return
|
||||
print(f"串口: {port}")
|
||||
|
||||
if args.all:
|
||||
run_all(port)
|
||||
elif args.boot:
|
||||
capture_boot(port)
|
||||
elif args.echo:
|
||||
send_and_listen(port)
|
||||
elif args.capture:
|
||||
capture_remote_detail(port)
|
||||
else:
|
||||
# 默认: 全部运行
|
||||
run_all(port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user