Вариант A: загрузчик HKLFBOOT write-only (read флеша нет) — дамп через него недостижим
Загрузчик ловится/держится на Linux (catch link-check + SetBaudRate смена бода). Но read-команды флеша нет (0x03/08/09/0a/0b не дают флеш; 0x08 = битовая карта). Сходится с DMR Upgrade Tool (только erase/write). Дамп только физически (SPI-клипса). Но BK4819 найден -> TX-фикс без дампа. tools/bk_module_dump.py, bk_bootloader_catch.py, bk_read_probe.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr
Этот коммит содержится в:
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Beken-загрузчик модуля RT-4D: catch -> SetBaudRate(hold) -> read (Linux, /dev/ttyACM0).
|
||||
Фрейминг из DMR Upgrade Tool: preamble 01 E0 FC; short: <len> <cmd> <payload>;
|
||||
long: FF F4 <len16 LE> <cmd> <payload>. SetBaudRate=short CMD 0x0F <baud:4 LE> <delay>.
|
||||
Erase=long CMD 0x0F. Read пробуем long CMD 0x09/0x03/0x0a. ТОЛЬКО чтение (никаких erase/write).
|
||||
"""
|
||||
import serial, time, sys, argparse
|
||||
|
||||
def long_frame(cmd, payload=b""):
|
||||
b=bytes([cmd])+payload; ln=len(b)
|
||||
return bytes([1,0xE0,0xFC,0xFF,0xF4, ln&0xFF,(ln>>8)&0xFF])+b
|
||||
def short_frame(cmd, payload=b""):
|
||||
b=bytes([cmd])+payload; ln=len(b)
|
||||
return bytes([1,0xE0,0xFC, ln&0xFF])+b
|
||||
def le32(v): return bytes([v&0xFF,(v>>8)&0xFF,(v>>16)&0xFF,(v>>24)&0xFF])
|
||||
|
||||
def catch(sp, secs):
|
||||
lc=bytes([1,0xE0,0xFC,1,0]); roll=b""; t0=time.time(); banner=False
|
||||
while time.time()-t0<secs:
|
||||
sp.write(lc)
|
||||
n=sp.in_waiting
|
||||
if n:
|
||||
roll=(roll+sp.read(n))[-400:]
|
||||
if b"HKLF" in roll: banner=True
|
||||
if b"\x04\x0e\x05" in roll: return True, banner
|
||||
return False, banner
|
||||
|
||||
def drain(sp, ms):
|
||||
q=time.time()
|
||||
while (time.time()-q)*1000<ms:
|
||||
n=sp.in_waiting
|
||||
if n: sp.read(n); q=time.time()
|
||||
sp.reset_input_buffer()
|
||||
|
||||
def readone(sp, cmd, addr, tries=3):
|
||||
for _ in range(tries):
|
||||
sp.reset_input_buffer()
|
||||
sp.write(long_frame(cmd, le32(addr)))
|
||||
time.sleep(0.04)
|
||||
buf=b""; q=time.time()
|
||||
while (time.time()-q)<0.25:
|
||||
n=sp.in_waiting
|
||||
if n: buf+=sp.read(n); q=time.time()
|
||||
else: time.sleep(0.003)
|
||||
if buf: return buf
|
||||
return b""
|
||||
|
||||
def main():
|
||||
ap=argparse.ArgumentParser()
|
||||
ap.add_argument("--port",default="/dev/ttyACM0"); ap.add_argument("--secs",type=int,default=45)
|
||||
ap.add_argument("--baud",type=int,default=921600,help="baud для SetBaudRate (0=не слать)")
|
||||
ap.add_argument("--rcmds",default="9,3,10")
|
||||
a=ap.parse_args()
|
||||
sp=serial.Serial(a.port,115200,timeout=0)
|
||||
print(f"[{a.port}] MENU+включение рации СЕЙЧАС (окно {a.secs}с)",flush=True)
|
||||
ok,banner=catch(sp,a.secs)
|
||||
if not ok:
|
||||
print(f" не поймал (баннер={banner}) — повтори",flush=True); return
|
||||
print(" ✅ пойман link-check",flush=True)
|
||||
if a.baud:
|
||||
# SetBaudRate — фиксирует download-режим
|
||||
sb=short_frame(0x0F, le32(a.baud)+bytes([0x0F]))
|
||||
sp.write(sb); time.sleep(0.08)
|
||||
if a.baud!=115200:
|
||||
sp.baudrate=a.baud; time.sleep(0.05)
|
||||
drain(sp,150)
|
||||
print(f" SetBaudRate({a.baud}) + порт на {sp.baudrate}",flush=True)
|
||||
# comprehensive: опкоды на 0x1000 + диапазон для 0x08
|
||||
for c in (0x03,0x08,0x09,0x0a,0x0b):
|
||||
r=readone(sp,c,0x1000,tries=2)
|
||||
nz=sum(1 for x in r if x!=0)
|
||||
print(f" op0x{c:02x}@0x1000: len={len(r)} nz={nz} f64={r[:64].hex(" ")}",flush=True)
|
||||
print(" -- 0x08 диапазон --",flush=True)
|
||||
for addr in (0x1000,0x1400,0x1800,0x1c00,0x0400,0x0000):
|
||||
r=readone(sp,0x08,addr,tries=2)
|
||||
nz=sum(1 for x in r if x!=0)
|
||||
print(f" 0x08@0x{addr:04x}: len={len(r)} nz={nz} f32={r[:32].hex(" ")}",flush=True)
|
||||
sp.close()
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
Ссылка в новой задаче
Block a user