初始化

This commit is contained in:
lhx
2026-06-09 10:19:38 +08:00
commit d03691451f
6 changed files with 1589 additions and 0 deletions

316
ir_diag.py Normal file
View 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()