Мост к DMR-модулю, разбор спектра REFV DualTachyon, карта запчастей прошивки

Баузбенд FM100B:
- найден прозрачный мост ПК<->модуль (включение с зажатой МЕНЮ), проверен живьём
- расшифрована контрольная сумма кадров (one's complement, BE), сверена с ответом рации
- инструмент tools/fm100b.py (ping/send/raw/scan)
- cmd 0x25 = запрос версии, модуль отвечает V1.2.0.32 (совпало с офиц. образом)
- разобран диспетчер входящих кадров: 193 записи, реальный код у 10 команд
- cmd 0x59 = индикация приёма, она же управляет гейтом звука PA14
- исправлено: boot-handshake это cmd 0x84, а не 0x64 (research/re/dmr.md)

Спектроанализатор REFV DualTachyon (docs/refw-spectrum.md):
- вызывается как функция горячей клавиши №22 Analog Spectrum
- вход 0x08009CAA -> обычный 0x080139E4 / по зоне 0x08015318
- спектр это экран №11; тик 0x08013BA4 (автомат на 4 состояния), клавиши 0x08013BFC

Декомпозиция (docs/firmware-parts.md):
- два процессора + внешний SPI = три канала внедрения
- карта ресурсов SPI: шрифты, пиньинь, голос, таблица Unicode
- найден штатный загрузчик ресурсов FontVoicePicture (шрифты/голос/картинки)
- дерево меню целиком: MIC/SPK Gain, RX/TX Limit, SMS Format уже в стоке
- аудио двухступенчатое: PA2 питание УНЧ, PA14 гейт от DMR-модуля

Прочее:
- везде исправлен режим прошивки: тангента PTT вместо клавиши "*"
- устаревший Ru-4D_Flasher.exe удалён из репозитория
- добавлены инструменты реверса: xref, refs, gpiomap, gpioscan, schem
Этот коммит содержится в:
2026-07-22 21:17:20 +09:00
родитель ae36c3b729
Коммит 5f4d207aa5
34 изменённых файлов: 21718 добавлений и 17823 удалений
Двоичные данные
Двоичный файл не отображается.
+2 -2
Просмотреть файл
@@ -28,7 +28,7 @@
<div class="warn" id="nowebserial">Откройте этот файл в <b>Google Chrome</b> или <b>Microsoft Edge</b> (нужен WebSerial).</div>
<ol>
<li><b>Выключите</b> рацию.</li>
<li>Включите её, <b>удерживая клавишу «*»</b> (режим прошивки).</li>
<li>Включите её, <b>удерживая тангенту PTT</b> (режим прошивки).</li>
<li>Подключите рацию к ПК кабелем USB-C.</li>
<li>Нажмите <b>«Прошить»</b> и выберите COM-порт рации.</li>
<li><b>Не отключайте</b> кабель до конца записи.</li>
@@ -69,7 +69,7 @@ async function doFlash(){
log('Порт открыт. Синхронизация с бутлоадером...');
port.flush();let ready=false;const end=Date.now()+7000;
while(Date.now()<end){const b=await port.readByte(150);if(b===null){await port.write([0xFF]);}else if(b===0xFF){ready=true;break;}}
if(!ready)throw new Error('Рация не отвечает. Выключите и включите её с зажатой «*», затем повторите.');
if(!ready)throw new Error('Рация не отвечает. Выключите и включите её с зажатой тангентой PTT, затем повторите.');
log('Бутлоадер найден. Стирание...');
for(const trig of [0x10,0x55]){const p=[0x39,0x33,0x05,trig];p.push(cksum(p));port.flush();await port.write(p);
let ok=false;const e2=Date.now()+4000;while(Date.now()<e2){if(await port.readByte(200)===ACK){ok=true;break;}}if(!ok)throw new Error('Ошибка стирания.');}
+4 -3
Просмотреть файл
@@ -3,7 +3,8 @@
it into a single Windows .exe with PyInstaller."""
import base64, os, subprocess, sys
ROOT = "C:/Users/vikto/Documents/Claude/rt-4d"
# project root = parent of this script's directory (portable)
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FW = f"{ROOT}/stock-fw/rt4d_ru_full.bin"
DIST = f"{ROOT}/dist"
@@ -45,7 +46,7 @@ def do_flash(port_name, log, prog):
if not b: p.write(b"\xff")
elif b == b"\xff": ready = True; break
if not ready:
raise RuntimeError("Рация не отвечает. Выключите и включите её\nс зажатой клавишей * , затем повторите.")
raise RuntimeError("Рация не отвечает. Выключите и включите её\nс зажатой тангентой PTT, затем повторите.")
log("Бутлоадер найден. Стирание...")
for trig in (0x10, 0x55):
pl = bytes([0x39, 0x33, 0x05, trig]); pl += cksum(pl)
@@ -80,7 +81,7 @@ class App:
tk.Label(root, text="Ru-4D - Прошивка рации", font=("Segoe UI", 15, "bold"), **F).pack(pady=(16, 0))
tk.Label(root, text=INFO, font=("Segoe UI", 9), bg="#131a24", fg="#7f8ea3").pack()
steps = ("1. Выключите рацию.\n"
"2. Включите её, удерживая клавишу * (режим прошивки).\n"
"2. Включите её, удерживая тангенту PTT (режим прошивки).\n"
"3. Подключите рацию к ПК кабелем USB-C.\n"
"4. Выберите COM-порт и нажмите Прошить .\n"
"5. Не отключайте кабель до конца записи.")
+4 -3
Просмотреть файл
@@ -3,7 +3,8 @@
embedded. Open in Chrome/Edge, one button flashes the RT-4D."""
import base64, os
ROOT = "C:/Users/vikto/Documents/Claude/rt-4d"
# project root = parent of this script's directory (portable)
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FW = f"{ROOT}/stock-fw/rt4d_ru_full.bin"
OUT = f"{ROOT}/dist/Ru-4D_flasher.html"
@@ -40,7 +41,7 @@ HTML = """<!doctype html><html lang="ru"><head><meta charset="utf-8">
<div class="warn" id="nowebserial">Откройте этот файл в <b>Google Chrome</b> или <b>Microsoft Edge</b> (нужен WebSerial).</div>
<ol>
<li><b>Выключите</b> рацию.</li>
<li>Включите её, <b>удерживая клавишу «*»</b> (режим прошивки).</li>
<li>Включите её, <b>удерживая тангенту PTT</b> (режим прошивки).</li>
<li>Подключите рацию к ПК кабелем USB-C.</li>
<li>Нажмите <b>«Прошить»</b> и выберите COM-порт рации.</li>
<li><b>Не отключайте</b> кабель до конца записи.</li>
@@ -81,7 +82,7 @@ async function doFlash(){
log('Порт открыт. Синхронизация с бутлоадером...');
port.flush();let ready=false;const end=Date.now()+7000;
while(Date.now()<end){const b=await port.readByte(150);if(b===null){await port.write([0xFF]);}else if(b===0xFF){ready=true;break;}}
if(!ready)throw new Error('Рация не отвечает. Выключите и включите её с зажатой «*», затем повторите.');
if(!ready)throw new Error('Рация не отвечает. Выключите и включите её с зажатой тангентой PTT, затем повторите.');
log('Бутлоадер найден. Стирание...');
for(const trig of [0x10,0x55]){const p=[0x39,0x33,0x05,trig];p.push(cksum(p));port.flush();await port.write(p);
let ok=false;const e2=Date.now()+4000;while(Date.now()<e2){if(await port.readByte(200)===ACK){ok=true;break;}}if(!ok)throw new Error('Ошибка стирания.');}