FM100B = Sicomm SCT3258TD (документирован); Talker Alias, агент, SPI-инструменты, восстановление TX
Главное: - Опознан DMR-чип: FM100B = Sicomm SCT3258TD. Датащит Packet Interface (126 стр) в docs/. Открывает калибровку/AMBE/вокодер по документации. Родня: UV-4R, китайские клоны. Реализовано: - Приём Talker Alias (cmd 0x60) — позывной крупно на экране (build_ta.py) - Интерактивный агент — управление FM100B по COM без перешивки (build_agent.py + agent.py) - Полная карта команд FM100B с живой рации (fm100b-commands.md) - Работа с SPI: чтение/запись/восстановление codeplug и калибровки (spi_read/spi_wr/spi_restore) - Конструктор дисплея + редактор прошивки (tools/display-editor) Документация: - docs/SCT3258_packet_interface.txt/.pdf — спека DMR-чипа - docs/fm100b-commands.md — карта команд - docs/dmr-tx-recovery.md — диагноз/восстановление DMR-передачи Инструменты: flash_ps.py (прошивка через WSL/powershell), sniff.py (снифер кадров), hotspot_rxoffset_sweep.py, apply_patch.py, build_* сборщики. Важно: DMR TX-калибровка сбита в экспериментах со SET-командами модулю; восстановление через offset-компенсацию / .updat Radtel / калибровку по прибору (docs/dmr-tx-recovery.md).
Этот коммит содержится в:
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Восстановление калибровки рации (SPI region 0x40, 4 КБ) из заводского эталона.
|
||||
|
||||
Пишет 4 КБ калибровки CPS-протоколом (USART6) из WSL через powershell.exe.
|
||||
Источник — radio-spi-dump.bin[0:0x1000] (снят ДО экспериментов).
|
||||
|
||||
Протокол записи (CPS):
|
||||
notify : 34 00 00 10 44 -> ACK 06
|
||||
write : <region> <blkHi> <blkLo> <1024 Б> <cksum> -> ACK 06 (region=0x40 калибровка)
|
||||
cksum : sum & 0xFF, seed 0
|
||||
|
||||
python3 tools/spi_write_cal.py # запись region 0x40 из эталона (с подтверждением)
|
||||
python3 tools/spi_write_cal.py --yes # без подтверждения
|
||||
"""
|
||||
import argparse, subprocess, sys, os, shutil
|
||||
|
||||
WIN_TMP_WSL = "/mnt/c/Users/vikto/AppData/Local/Temp"
|
||||
WIN_TMP_WIN = r"C:\Users\vikto\AppData\Local\Temp"
|
||||
REF = "radio-spi-dump.bin"
|
||||
REGION = 0x40 # calibration
|
||||
SIZE = 0x1000 # 4 КБ
|
||||
|
||||
PS = r'''
|
||||
$ErrorActionPreference='Stop'
|
||||
$data=[System.IO.File]::ReadAllBytes("{datawin}")
|
||||
$p=New-Object System.IO.Ports.SerialPort "{port}",115200,"None",8,"one"
|
||||
$p.ReadTimeout=1500; $p.WriteTimeout=3000
|
||||
try {{ $p.Open() }} catch {{ Write-Output "ERR:openfail"; exit 1 }}
|
||||
function ck([byte[]]$a,$n){{ $s=0; for($i=0;$i -lt $n;$i++){{ $s=($s+$a[$i]) -band 0xFF }}; return [byte]$s }}
|
||||
$p.DiscardInBuffer()
|
||||
$nf=[byte[]](0x34,0x00,0x00,0x10,0x44); $p.Write($nf,0,5)
|
||||
$ok=$false; $sw=[Diagnostics.Stopwatch]::StartNew()
|
||||
while($sw.ElapsedMilliseconds -lt 3000){{ try {{ if($p.ReadByte() -eq 6){{ $ok=$true; break }} }} catch {{}} }}
|
||||
if(-not $ok){{ Write-Output "ERR:notify"; $p.Close(); exit 1 }}
|
||||
Write-Output "INFO:notify ok"
|
||||
$region={region}; $size={size}
|
||||
for($i=0; $i -lt $size; $i+=1024){{
|
||||
$cmd=New-Object byte[] 1028
|
||||
$cmd[0]=$region
|
||||
$blk=[int]($i/1024)
|
||||
$cmd[1]=(($blk -shr 8) -band 0xFF); $cmd[2]=($blk -band 0xFF)
|
||||
[Array]::Copy($data,$i,$cmd,3,1024)
|
||||
$cmd[1027]=ck $cmd 1027
|
||||
$p.DiscardInBuffer(); $p.Write($cmd,0,1028)
|
||||
$ok=$false; $sw2=[Diagnostics.Stopwatch]::StartNew()
|
||||
while($sw2.ElapsedMilliseconds -lt 3000){{ try {{ if($p.ReadByte() -eq 6){{ $ok=$true; break }} }} catch {{}} }}
|
||||
if(-not $ok){{ Write-Output ("ERR:write@"+$blk); $p.Close(); exit 1 }}
|
||||
Write-Output ("PROG:"+$blk)
|
||||
}}
|
||||
$cl=[byte[]](0x34,0x52,0x05,0xEE,0x79); $p.Write($cl,0,5)
|
||||
$p.Close()
|
||||
Write-Output "OK"
|
||||
'''
|
||||
|
||||
|
||||
def find_port():
|
||||
out = subprocess.run(["powershell.exe","-NoProfile","-Command",
|
||||
"[System.IO.Ports.SerialPort]::GetPortNames() -join ','"],
|
||||
capture_output=True, text=True).stdout
|
||||
ports=[p for p in out.strip().replace("\r","").split(",") if p]
|
||||
if not ports: sys.exit("нет COM-порта")
|
||||
return ports[0]
|
||||
|
||||
|
||||
def main():
|
||||
ap=argparse.ArgumentParser()
|
||||
ap.add_argument("--yes", action="store_true")
|
||||
ap.add_argument("--port")
|
||||
a=ap.parse_args()
|
||||
|
||||
ref=open(REF,"rb").read()
|
||||
cal=ref[:SIZE]
|
||||
print(f"калибровка из эталона: {SIZE} Б, region 0x{REGION:02X}")
|
||||
print(f" начало: {cal[:16].hex(' ')}")
|
||||
if not a.yes:
|
||||
print("\nЗАПИСЬ в SPI-калибровку рации. Рация должна быть в ОБЫЧНОМ режиме.")
|
||||
if input("продолжить? [y/N] ").strip().lower() != "y":
|
||||
print("отменено"); return
|
||||
|
||||
name="rt4d_cal.bin"
|
||||
open(os.path.join(WIN_TMP_WSL,name),"wb").write(cal)
|
||||
datawin=os.path.join(WIN_TMP_WIN,name)
|
||||
port=a.port or find_port()
|
||||
script=PS.format(datawin=datawin.replace("\\","\\\\"),port=port,region=REGION,size=SIZE)
|
||||
proc=subprocess.Popen(["powershell.exe","-NoProfile","-Command",script],
|
||||
stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,encoding="utf-8",errors="replace")
|
||||
rc=0
|
||||
for line in proc.stdout:
|
||||
line=line.strip()
|
||||
if line.startswith("PROG:"): print(f" записан блок {line[5:]}")
|
||||
elif line.startswith("INFO:"): print(f" {line[5:]}")
|
||||
elif line.startswith("ERR:"): print(f" ОШИБКА: {line[4:]}"); rc=1
|
||||
elif line=="OK": print(" ✅ калибровка восстановлена")
|
||||
proc.wait()
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
Ссылка в новой задаче
Block a user