From f2617a2e796fab625779d8159bab88c51d3fd2f8 Mon Sep 17 00:00:00 2001 From: viktor Date: Fri, 24 Jul 2026 03:52:50 +0900 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D0=B9=20?= =?UTF-8?q?=D1=80=D0=B8=D0=B4-=D0=BE=D0=BD=D0=BB=D0=B8=20=D1=82=D1=8E?= =?UTF-8?q?=D0=BD=D0=B5=D1=80-=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE?= =?UTF-8?q?=D0=BB=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BC=D0=BE=D1=81?= =?UTF-8?q?=D1=82=20+=20baseline=20=D0=BA=D0=B0=D0=BB=D0=B8=D0=B1=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools/tuning_read.py: реплика Read-последовательности тюнера через 0x68-мост (--direct, без 34.. Connect, который в режиме Обновление DMR не отвечает). РАБОТАЕТ: модуль отвечает на CmdOpenCal + Read-команды. Снят baseline: DMR VCCN=0x40 vs аналог 0x80 (аналог рабочий) — вероятный виновник сдвига DMR TX. research/tuning-re/module-cal-baseline.txt. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr --- research/tuning-re/module-cal-baseline.txt | 14 +++ tools/tuning_read.py | 119 +++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 research/tuning-re/module-cal-baseline.txt create mode 100644 tools/tuning_read.py diff --git a/research/tuning-re/module-cal-baseline.txt b/research/tuning-re/module-cal-baseline.txt new file mode 100644 index 0000000..5203f6b --- /dev/null +++ b/research/tuning-re/module-cal-baseline.txt @@ -0,0 +1,14 @@ +RT-4D DMR-модуль (HF6853) — ЖИВАЯ калибровка, снята через мост (tools/tuning_read.py --direct) +Дата: 24.07.2026, режим «Обновление DMR чипа», 0x68 напрямую. Стабильно (3 прогона). + +CmdOpenCal -> st=0x00 OK +ReadDigVCCN -> pl 00 02 40 => DMR VCCN(freq/VCXO trim) = 0x40 (64) +ReadAnaVCCN -> pl 00 02 80 => ANA VCCN = 0x80 (128) <- аналог работает +ReadPWR -> pl 00 01 01 00 05 +ReadSlot -> pl 00 00 03 42 01 08 d9 +ReadMod -> st=0x01 (нужен параметр/иначе) +CmdColseCal -> OK (+ async 68 06 .. ee ee.. = DetectDevice/пусто) + +Гипотеза: VCCN = частотный трим VCXO (btnRead_U_Freq шлёт ReadDigVCCN). DMR(0x40) сильно ниже +аналога(0x80). Проверять сравнением BER на MMDVM при переборе SetDigVCCN. baseline для отката = 0x40. +ВНИМАНИЕ: писать только с возможностью отката; тест требует нормального режима рации + PTT. diff --git a/tools/tuning_read.py b/tools/tuning_read.py new file mode 100644 index 0000000..d48c372 --- /dev/null +++ b/tools/tuning_read.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""RT-4D: РИД-ОНЛИ снятие калибровки DMR-модуля протоколом штатного тюнера. + +Реплика последовательности Radio Tuning Software (реверс, docs/tuning-software-protocol.md): +одна сессия COM @115200, шлём кадры и ловим ответы. ТОЛЬКО ЧТЕНИЕ — ни одной write/set-команды +калибровки (CmdWriteAll/Set* НЕ шлём). Рация в режиме «Обновление DMR чипа». + +Под WSL COM через powershell.exe. Порядок: + CmdConnect(34 52 05 10 9b) -> [опц. CmdOpenCal 68 b0..01] -> Read* (68 b?..) -> CmdColseCal -> CmdEnd +""" +import argparse, subprocess, sys, json + +# --- команды (из реверса, research/tuning-re/command-templates.txt) --- +CMDS = { + "Ping": "68 84 01 01 00 00 00 01 00 10", + "CmdConnect": "34 52 05 10 9b", + "CmdOpenCal": "68 b0 01 01 00 00 00 01 01 10", + "ReadDigVCCN": "68 b4 01 01 00 00 00 02 00 02 10", + "ReadAnaVCCN": "68 b2 01 01 00 00 00 02 00 02 10", + "ReadMod": "68 b6 01 01 00 00 00 03 00 01 00 10", + "ReadPWR": "68 b8 01 01 00 00 00 04 00 01 01 00 10", + "ReadSlot": "68 b7 01 01 00 00 00 01 00 10", + "CmdColseCal": "68 b0 01 01 00 00 00 01 00 10", + "CmdEnd": "34 52 05 ee 79", +} + +def hx(name): return bytes.fromhex(CMDS[name].replace(" ", "")) + +# powershell: open port, run a sequence of (write, wait) steps, capture each response +PS = r''' +$ErrorActionPreference = "Stop" +$p = New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one" +$p.ReadTimeout = 60 +try {{ $p.Open() }} catch {{ Write-Output "ERR:$($_.Exception.Message)"; exit }} +$p.DiscardInBuffer(); $p.DiscardOutBuffer() +$steps = New-Object System.Collections.ArrayList +{steps} +foreach ($s in $steps) {{ + $name = $s[0]; $tx = [byte[]]$s[1] + $p.DiscardInBuffer() + $p.Write($tx,0,$tx.Length) + Start-Sleep -Milliseconds {dwell} + $buf = New-Object System.Collections.Generic.List[byte] + $sw=[Diagnostics.Stopwatch]::StartNew(); $last=0 + while ($sw.ElapsedMilliseconds -lt {tmo}) {{ + try {{ $buf.Add($p.ReadByte()); $last=$sw.ElapsedMilliseconds }} catch {{}} + if ($buf.Count -gt 0 -and ($sw.ElapsedMilliseconds-$last) -gt {idle}) {{ break }} + }} + $hex = ($buf | ForEach-Object {{ "{{0:x2}}" -f $_ }}) -join "" + Write-Output ("RSP:"+$name+":"+$hex) +}} +$p.Close() +Write-Output "DONE" +''' + +def find_port(): + out = subprocess.run(["powershell.exe","-NoProfile","-Command", + "[System.IO.Ports.SerialPort]::GetPortNames() -join ','"], + capture_output=True, text=True).stdout.strip().replace("\r","") + ports=[p for p in out.split(",") if p] + if not ports: sys.exit("COM не найден.") + return ports[0] + +def ps_arr(b): return "[byte[]]("+",".join(str(x) for x in b)+")" + +def run(port, seq, dwell=120, tmo=700, idle=200): + steps = "\n".join(f"$null = $steps.Add(@('{n}',{ps_arr(hx(n))}))" for n in seq) + script = PS.format(port=port, steps=steps, dwell=dwell, tmo=tmo, idle=idle) + r = subprocess.run(["powershell.exe","-NoProfile","-Command",script], + capture_output=True, text=True) + out=r.stdout.replace("\r","") + res=[] + for line in out.split("\n"): + if line.startswith("ERR:"): sys.exit(line) + if line.startswith("RSP:"): + _,name,hexs=line.split(":",2) + res.append((name, bytes.fromhex(hexs) if hexs else b"")) + return res + +def parse68(rx): + if len(rx)<9 or rx[0]!=0x68: return None + cmd=rx[1]; ln=(rx[6]<<8)|rx[7]; pl=rx[8:8+ln] + return dict(cmd=cmd, status=rx[3], length=ln, payload=pl) + +def main(): + ap=argparse.ArgumentParser() + ap.add_argument("--port") + ap.add_argument("--connect-only", action="store_true", help="только CmdConnect (проверка отклика)") + ap.add_argument("--direct", action="store_true", help="без 34.. Connect: 0x68 напрямую в модуль (режим Обновление DMR)") + ap.add_argument("--seq", help="произвольная последовательность имён через запятую") + a=ap.parse_args() + port=a.port or find_port() + print(f"[порт {port} @115200] РИД-ОНЛИ снятие калибровки\n") + + if a.seq: + seq=[s.strip() for s in a.seq.split(",")] + elif a.connect_only: + seq=["CmdConnect"] + elif a.direct: + seq=["Ping","CmdOpenCal","ReadDigVCCN","ReadAnaVCCN","ReadMod","ReadPWR", + "ReadSlot","CmdColseCal"] + else: + seq=["CmdConnect","CmdOpenCal","ReadDigVCCN","ReadAnaVCCN","ReadMod","ReadPWR", + "ReadSlot","CmdColseCal","CmdEnd"] + res=run(port, seq) + if not res: + print("Нет ответов от рации (powershell не вернул RSP). Порт/режим?") + return + for name,rx in res: + tag=f" {name:12s} TX={CMDS[name]}" + if not rx: + print(tag+" RX=(нет ответа)") + else: + p=parse68(rx) + extra=f" [cmd=0x{p['cmd']:02X} st=0x{p['status']:02X} len={p['length']} pl={p['payload'].hex(' ')}]" if p else "" + print(tag+f" RX={rx.hex(' ')}"+extra) + +if __name__=="__main__": + main()