Полный реверс протокола калибровки тюнера (IL-декомпиляция)
- research/tuning-re/cil_disasm.py — минимальный CIL-дизассемблер (dnfile). - research/tuning-re/command-templates.txt — извлечённые шаблоны всех команд. - docs/tuning-software-protocol.md: точная карта команд-полей + раскладка bufDigTune + ВЫВОД: частотная калибровка = ЗАМКНУТЫЙ ЦИКЛ (Set-команды несут band/опорные частоты, не абсолютное смещение; WriteAll данных не несёт). Слепой скрипт записи freq-трима может снова сбить калибровку -> безопаснее гнать штатным тулом с MMDVM BER вместо частотомера, либо сперва рид-онли снять текущую калибровку. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr
Этот коммит содержится в:
@@ -52,10 +52,56 @@ flags 00 ff 00
|
|||||||
12 e2 34 40 2a 4f b3 (×2)
|
12 e2 34 40 2a 4f b3 (×2)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что осталось (для скриптования)
|
## Точная карта команд-полей (из .ctor через InitializeArray) — ПОДТВЕРЖДЕНО
|
||||||
Точное соответствие «num_U_DigFreq → cmd+байт» — в IL методов `btnRead_U_Freq_Click` /
|
|
||||||
`ReadDigDataToUI` / `SaveDigTuneData`. Кандидат — `0xB7` (частотные точки/оффсет) или отдельный
|
Транспорт: **115200 8N1**, ответы 9 или 12 байт (`ReceivedBytesThreshold`). CK-поле `00 00`
|
||||||
freq-cmd. Для ручной калибровки штатным тулом это не нужно: `num_U_DigFreq` крутится в UI.
|
в шаблонах и НЕ пересчитывается → **модуль не проверяет CRC для cal-команд**.
|
||||||
|
|
||||||
|
Управление мостом (кадр `34 52 05 <cmd> <ck>` — к AT32, не к модулю):
|
||||||
|
```
|
||||||
|
CmdConnect 34 52 05 10 9b # войти в cal-мост
|
||||||
|
CmdEnd 34 52 05 ee 79 # выход
|
||||||
|
CmdTCOn 34 52 05 54 df #透传 (прозрачный TX) UHF
|
||||||
|
CmdTCOn_V 34 52 05 58 e3 # прозрачный TX VHF
|
||||||
|
```
|
||||||
|
Калибровка (0x68 → модуль):
|
||||||
|
```
|
||||||
|
CmdOpenCal 68 b0 01 01 00 00 00 01 01 10 # 0xB0 payload 01 — открыть cal
|
||||||
|
CmdColseCal 68 b0 01 01 00 00 00 01 00 10 # 0xB0 payload 00 — закрыть
|
||||||
|
CmdWriteAll 68 bd 01 01 00 00 00 01 01 10 # 0xBD — сохранить cal в NV (БЕЗ данных!)
|
||||||
|
SetCalFreq_U 68 b1 ...01 01 03 <400.125M×2><435.125M×2><469.975M×2> 10 # опорные UHF (BE Гц)
|
||||||
|
SetCalFreq_V 68 b1 ...01 02 03 <136.125M×2><150.125M×2><174.125M×2> 10 # опорные VHF
|
||||||
|
SetAnaTX 68 b3 ...01 01 02 02 10 # рантайм патч [8..11]=01 01 02 02
|
||||||
|
SetDigTX 68 b5 ...01 01 00 02 10 # рантайм: [8]=1 [9]=1 [10]=DetectDevice [11]=2
|
||||||
|
SetAnaVCCN 68 b2 ...01 01 80 10
|
||||||
|
SetDigVCCN 68 b4 ...01 01 80 10
|
||||||
|
SetCalPWR 68 b8 ...01 01 01 02 78 10
|
||||||
|
SetCalMod 68 b6 ...01 01 01 96 10 # девиация, дефолт 0x96=150
|
||||||
|
SetCalSlot 68 b7 ...01 00 05 9a 01 08 75 10
|
||||||
|
Read*: ReadAnaVCCN 68 b2..00 02 | ReadDigVCCN 68 b4..00 02 | ReadMod 68 b6..00 01 00
|
||||||
|
ReadPWR 68 b8..00 01 01 00 | ReadSlot 68 b7..00
|
||||||
|
```
|
||||||
|
|
||||||
|
## Раскладка bufDigTune (частотные трим-значения, из Save/ReadDigDataToUI)
|
||||||
|
```
|
||||||
|
[0]=устройство(cboxDevice) [1]=V_DigFreq [2]=U_AnaFreq [3]=V_AnaFreq [4]=U_DigFreq
|
||||||
|
дефолты bufDigTuneInit = 01 00 00 00 00 b9×6 32×6 9b×6 → все трим-смещения по умолчанию 0
|
||||||
|
```
|
||||||
|
Трим — **однобайтовый знаковый** (0 = центр). `num_U_DigFreq ↔ track_U_DigFreq`.
|
||||||
|
|
||||||
|
## ⚠️ ВАЖНО: частотная калибровка — ЗАМКНУТЫЙ ЦИКЛ (нужен частотомер)
|
||||||
|
Реверс `RecvDataOperation`: Set-команды несут band/DetectDevice и опорные частоты, но **НЕ
|
||||||
|
абсолютное смещение**; `CmdWriteAll` payload не несёт. Т.е. штатная калибровка: `CmdOpenCal` →
|
||||||
|
модуль ПЕРЕДАЁТ несущую → оператор смотрит частотомер/SDR и крутит `num_U_DigFreq` → модуль
|
||||||
|
подстраивается → `CmdWriteAll` фиксирует. Без прибора абсолютное значение не выставить «в одну
|
||||||
|
команду», а слепой скрипт записи МОЖЕТ СНОВА СБИТЬ калибровку.
|
||||||
|
|
||||||
|
**Вывод по путям:**
|
||||||
|
- Безопасно: гонять калибровку ШТАТНЫМ тулом (правильные кадры), MMDVM BER — вместо частотомера
|
||||||
|
(перебор `num_U_DigFreq` → WriteAll → PTT → BER). Уточнить, пишет ли ручной num+WriteAll трим
|
||||||
|
в NV модуля (WriteAll данных не несёт — возможно, только auto-cal реально пишет частоту).
|
||||||
|
- Скрипт: сначала РИД-онли реплицировать Connect+Read и снять текущую калибровку, потом решать.
|
||||||
|
Cлепую запись freq-трима НЕ слать.
|
||||||
|
|
||||||
## План применения (см. dmr-tx-recovery.md, путь через 0x68)
|
## План применения (см. dmr-tx-recovery.md, путь через 0x68)
|
||||||
Вариант A (штатный тул): пользователь открывает Tuning SW → Read → крутит `num_U_DigFreq` →
|
Вариант A (штатный тул): пользователь открывает Tuning SW → Read → крутит `num_U_DigFreq` →
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import dnfile, struct
|
||||||
|
pe = dnfile.dnPE("tuning.exe")
|
||||||
|
net = pe.net
|
||||||
|
data = bytes(pe.__data__)
|
||||||
|
def rva2off(rva):
|
||||||
|
for s in pe.sections:
|
||||||
|
vs=max(s.Misc_VirtualSize,s.SizeOfRawData)
|
||||||
|
if s.VirtualAddress<=rva<s.VirtualAddress+vs:
|
||||||
|
return s.PointerToRawData+(rva-s.VirtualAddress)
|
||||||
|
def sstr(x):
|
||||||
|
try: return str(x)
|
||||||
|
except: return "?"
|
||||||
|
# token resolvers
|
||||||
|
mdef=net.mdtables.MethodDef.rows
|
||||||
|
mref=net.mdtables.MemberRef.rows if net.mdtables.MemberRef else []
|
||||||
|
fld=net.mdtables.Field.rows if net.mdtables.Field else []
|
||||||
|
tref=net.mdtables.TypeRef.rows if net.mdtables.TypeRef else []
|
||||||
|
def us(tok):
|
||||||
|
# #US token 0x70xxxxxx
|
||||||
|
idx=tok&0xffffff
|
||||||
|
try:
|
||||||
|
return '"'+sstr(net.user_strings.get(idx).value)+'"'
|
||||||
|
except Exception as e:
|
||||||
|
return f"US#{idx:x}"
|
||||||
|
def resolve(tok):
|
||||||
|
t=tok>>24; i=(tok&0xffffff)
|
||||||
|
if t==0x0A and i-1<len(mref): return "ref:"+sstr(mref[i-1].Name)
|
||||||
|
if t==0x06 and i-1<len(mdef): return "mdef:"+sstr(mdef[i-1].Name)
|
||||||
|
if t==0x04 and i-1<len(fld): return "fld:"+sstr(fld[i-1].Name)
|
||||||
|
if t==0x0A: return f"memberref#{i}"
|
||||||
|
return f"tok0x{tok:08x}"
|
||||||
|
|
||||||
|
# opcode table (subset)
|
||||||
|
OPS={0x00:"nop",0x01:"break",0x02:"ldarg.0",0x03:"ldarg.1",0x04:"ldarg.2",0x05:"ldarg.3",
|
||||||
|
0x06:"ldloc.0",0x07:"ldloc.1",0x08:"ldloc.2",0x09:"ldloc.3",0x0a:"stloc.0",0x0b:"stloc.1",
|
||||||
|
0x0c:"stloc.2",0x0d:"stloc.3",0x0e:("ldarg.s",1),0x0f:("ldarga.s",1),0x10:("starg.s",1),
|
||||||
|
0x11:("ldloc.s",1),0x12:("ldloca.s",1),0x13:("stloc.s",1),0x14:"ldnull",0x15:"ldc.i4.m1",
|
||||||
|
0x16:"ldc.i4.0",0x17:"ldc.i4.1",0x18:"ldc.i4.2",0x19:"ldc.i4.3",0x1a:"ldc.i4.4",0x1b:"ldc.i4.5",
|
||||||
|
0x1c:"ldc.i4.6",0x1d:"ldc.i4.7",0x1e:"ldc.i4.8",0x1f:("ldc.i4.s",1),0x20:("ldc.i4",4),
|
||||||
|
0x21:("ldc.i8",8),0x22:("ldc.r4",4),0x23:("ldc.r8",8),0x25:"dup",0x26:"pop",
|
||||||
|
0x28:("call",4,"tok"),0x29:("calli",4),0x2a:"ret",
|
||||||
|
0x2b:("br.s",1,"br"),0x2c:("brfalse.s",1,"br"),0x2d:("brtrue.s",1,"br"),0x2e:("beq.s",1,"br"),
|
||||||
|
0x2f:("bge.s",1,"br"),0x30:("bgt.s",1,"br"),0x31:("ble.s",1,"br"),0x32:("blt.s",1,"br"),
|
||||||
|
0x33:("bne.un.s",1,"br"),0x38:("br",4,"br"),0x39:("brfalse",4,"br"),0x3a:("brtrue",4,"br"),
|
||||||
|
0x3b:("beq",4,"br"),0x58:"add",0x59:"sub",0x5a:"mul",0x5b:"div",0x5f:"and",0x60:"or",
|
||||||
|
0x61:"xor",0x62:"shl",0x63:"shr",0x65:"neg",0x66:"not",
|
||||||
|
0x6f:("callvirt",4,"tok"),0x72:("ldstr",4,"us"),0x73:("newobj",4,"tok"),
|
||||||
|
0x74:("castclass",4,"tok"),0x75:("isinst",4,"tok"),
|
||||||
|
0x7b:("ldfld",4,"tok"),0x7d:("stfld",4,"tok"),0x7e:("ldsfld",4,"tok"),0x80:("stsfld",4,"tok"),
|
||||||
|
0x8c:("box",4,"tok"),0x8d:("newarr",4,"tok"),0x8e:"ldlen",
|
||||||
|
0x91:"ldelem.i1",0x92:"ldelem.u1",0x93:"ldelem.i2",0x94:"ldelem.u2",0x95:"ldelem.i4",
|
||||||
|
0x9c:"stelem.i1",0x9d:"stelem.i2",0x9e:"stelem.i4",0xa2:"stelem.ref",0x8f:("ldelema",4,"tok"),
|
||||||
|
0xa3:("ldelem",4,"tok"),0xa4:("stelem",4,"tok"),
|
||||||
|
0x6a:"conv.i8",0x69:"conv.i4",0x67:"conv.i1",0x68:"conv.i2",0xd1:"conv.u2",0xd2:"conv.u1",
|
||||||
|
0x20:("ldc.i4",4)}
|
||||||
|
|
||||||
|
def disasm(rva, name):
|
||||||
|
off=rva2off(rva)
|
||||||
|
b0=data[off]
|
||||||
|
if (b0&3)==2:
|
||||||
|
codesize=b0>>2; ilstart=off+1
|
||||||
|
else:
|
||||||
|
# fat header 12 bytes
|
||||||
|
codesize=struct.unpack_from("<I",data,off+4)[0]; ilstart=off+12
|
||||||
|
il=data[ilstart:ilstart+codesize]
|
||||||
|
print(f"\n===== {name} (RVA 0x{rva:x}, {codesize} bytes IL) =====")
|
||||||
|
p=0
|
||||||
|
while p<len(il):
|
||||||
|
op=il[p]; p0=p; p+=1
|
||||||
|
if op==0xfe:
|
||||||
|
op2=il[p]; p+=1
|
||||||
|
nm={0x01:"ceq",0x02:"cgt",0x04:"clt",0x09:("ldarg",2),0x0b:("ldloc",2),0x0c:("ldloca",2),0x0d:("stloc",2),0x16:"constrained",0x1e:"readonly"}.get(op2,f"fe{op2:02x}")
|
||||||
|
if isinstance(nm,tuple):
|
||||||
|
sz=nm[1]; val=int.from_bytes(il[p:p+sz],'little'); p+=sz
|
||||||
|
print(f" {p0:04x}: {nm[0]} {val}")
|
||||||
|
else:
|
||||||
|
print(f" {p0:04x}: {nm}")
|
||||||
|
continue
|
||||||
|
info=OPS.get(op)
|
||||||
|
if info is None:
|
||||||
|
print(f" {p0:04x}: .byte 0x{op:02x}"); continue
|
||||||
|
if isinstance(info,str):
|
||||||
|
print(f" {p0:04x}: {info}"); continue
|
||||||
|
nm=info[0]; sz=info[1]; kind=info[2] if len(info)>2 else None
|
||||||
|
raw=il[p:p+sz]; p+=sz
|
||||||
|
if kind=="tok":
|
||||||
|
tok=int.from_bytes(raw,'little'); print(f" {p0:04x}: {nm} {resolve(tok)}")
|
||||||
|
elif kind=="us":
|
||||||
|
tok=int.from_bytes(raw,'little'); print(f" {p0:04x}: {nm} {us(tok)}")
|
||||||
|
elif kind=="br":
|
||||||
|
disp=int.from_bytes(raw,'little',signed=True); print(f" {p0:04x}: {nm} -> {p+disp:04x}")
|
||||||
|
else:
|
||||||
|
val=int.from_bytes(raw,'little',signed=(nm in('ldc.i4','ldc.i4.s'))); print(f" {p0:04x}: {nm} {val} (0x{val&0xffffffff:x})")
|
||||||
|
|
||||||
|
import sys
|
||||||
|
for nm,rva in [("btnRead_U_Freq_Click",0x5f330),("btnCal_U_Freq_Click",0x5f53c),
|
||||||
|
("num_U_DigFreq_ValueChanged",0x5f160)]:
|
||||||
|
disasm(rva,nm)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Radio Tuning Software 241219 (UV5118_4819_Tune) — извлечённые шаблоны команд
|
||||||
|
(из .ctor через RuntimeHelpers.InitializeArray; baud 115200 8N1; CK не проверяется)
|
||||||
|
|
||||||
|
# bridge (34 52 05 <cmd> <ck>) -> AT32
|
||||||
|
CmdConnect 34 52 05 10 9b
|
||||||
|
CmdEnd 34 52 05 ee 79
|
||||||
|
CmdTCOn 34 52 05 54 df
|
||||||
|
CmdTCOn_V 34 52 05 58 e3
|
||||||
|
# calibration (0x68) -> module
|
||||||
|
CmdWriteAll 68 bd 01 01 00 00 00 01 01 10
|
||||||
|
CmdOpenCal 68 b0 01 01 00 00 00 01 01 10
|
||||||
|
CmdColseCal 68 b0 01 01 00 00 00 01 00 10
|
||||||
|
SetCalFreq_U 68 b1 01 01 00 00 00 1b 01 01 03 17 d9 6c 48 17 d9 6c 48 19 ef 7b 08 19 ef 7b 08 1c 03 3f d8 1c 03 3f d8 10
|
||||||
|
SetCalFreq_V 68 b1 01 01 00 00 00 1b 01 02 03 08 1d 1a 48 08 1d 1a 48 08 f2 b9 c8 08 f2 b9 c8 0a 5e a5 d8 0a 5e a5 d8 10
|
||||||
|
SetAnaTX 68 b3 01 01 00 00 00 04 01 01 02 02 10
|
||||||
|
SetDigTX 68 b5 01 01 00 00 00 04 01 01 00 02 10
|
||||||
|
SetAnaVCCN 68 b2 01 01 00 00 00 03 01 01 80 10
|
||||||
|
SetDigVCCN 68 b4 01 01 00 00 00 03 01 01 80 10
|
||||||
|
SetCalPWR 68 b8 01 01 00 00 00 05 01 01 01 02 78 10
|
||||||
|
SetCalMod 68 b6 01 01 00 00 00 04 01 01 01 96 10
|
||||||
|
SetCalSlot 68 b7 01 01 00 00 00 07 01 00 05 9a 01 08 75 10
|
||||||
|
ReadAnaVCCN 68 b2 01 01 00 00 00 02 00 02 10
|
||||||
|
ReadDigVCCN 68 b4 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
|
||||||
|
bufDigTuneInit 01 00 00 00 00 b9 b9 b9 b9 b9 b9 32 32 32 32 32 32 9b 9b 9b 9b 9b 9b
|
||||||
|
# bufDigTune layout: [0]device [1]V_DigFreq [2]U_AnaFreq [3]V_AnaFreq [4]U_DigFreq (signed byte, 0=center)
|
||||||
Ссылка в новой задаче
Block a user