Мост к 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
Этот коммит содержится в:
@@ -0,0 +1,314 @@
|
||||
# RT-4D: прошивка по запчастям и точки внедрения своего кода
|
||||
|
||||
Цель документа — разложить устройство на независимые «детали», для каждой указать
|
||||
**где лежит, чем читается, чем пишется и как туда внедрить своё**.
|
||||
|
||||
Ключевой факт: рация состоит из **двух процессоров** с раздельными прошивками, плюс
|
||||
внешний SPI-флеш с ресурсами. Это три независимых канала внедрения.
|
||||
|
||||
---
|
||||
|
||||
## 0. Карта на одном экране
|
||||
|
||||
| # | Деталь | Носитель | Размер | Пишется чем | Статус |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Загрузчик MCU | внутр. flash `0x08000000` | 10 КБ | **ничем** (неприкосновенен) | страховка от кирпича |
|
||||
| 2 | Прошивка MCU (UI+логика) | внутр. flash `0x08002800` | ~152 КБ | `flash_rt4d.py` | ✅ наш основной цех |
|
||||
| 3 | Прошивка DMR-модуля FM100B | своя flash в модуле | 1.5 МБ | `DMR Upgrade Tool.exe` / наш мост | ✅ канал открыт |
|
||||
| 4 | Ресурсы: шрифты, пиньинь, юникод | внешний SPI 4 МБ | ~1 МБ | `Unicode Download.exe` | ✅ найден штатный загрузчик |
|
||||
| 5 | Голосовые подсказки | внешний SPI | 569 КБ | тот же загрузчик | найден регион |
|
||||
| 6 | Кодплаг (каналы/контакты/зоны) | внешний SPI | ~100 КБ | штатная CPS | формат менять нельзя |
|
||||
|
||||
---
|
||||
|
||||
## 1. MCU — ARM Cortex-M4F (клон STM32F407, реально **Artery AT32**)
|
||||
|
||||
Опознан по раскладке GPIO: `SCR +0x18` (установить), `CLR +0x28` (сбросить), `TOGR +0x2C` —
|
||||
это Artery, а не ST.
|
||||
|
||||
```
|
||||
0x08000000 ┌────────────────────────┐
|
||||
│ загрузчик (10 КБ) │ НЕ ТРОГАЕМ — он всегда поднимет рацию
|
||||
0x08002800 ├────────────────────────┤
|
||||
│ приложение ~152 КБ │ ← сюда пишем мы
|
||||
│ 0x08008A50 draw_string│
|
||||
│ 0x080207DC ui_tick │
|
||||
│ 0x08028860 наш кейв │
|
||||
│ 0x08029000 наш C-код │
|
||||
0x08028000 └────────────────────────┘
|
||||
SRAM 128 КБ, g_screen @0x200008B3
|
||||
```
|
||||
|
||||
### Как попасть в режим прошивки
|
||||
**Выключить → включить, удерживая тангенту PTT.**
|
||||
|
||||
Протокол загрузчика (реализован в `flash_rt4d.py`):
|
||||
```
|
||||
рукопожатие : слать 0xFF, пока не придёт 0xFF
|
||||
стирание : [39 33 05 10]+CK, затем [39 33 05 55]+CK → ждать ACK 0x06
|
||||
запись : [57 offHi offLo] + 1024 байт + CK → ждать ACK 0x06
|
||||
CK = (0x48 + сумма байтов) & 0xFF
|
||||
```
|
||||
|
||||
### Три проверенных приёма внедрения
|
||||
|
||||
**а) Точечный патч байта** — самый дешёвый.
|
||||
Пример (русификация): в `draw_string` стояло `cmp #0x80` — всё, что ≥0x80, считалось
|
||||
китайским иероглифом шириной 14 px. Меняем на `cmp #0xFF` по адресу `0x08008A6A` — и
|
||||
диапазон 0x80–0xFE уходит в узкую 7-пиксельную ветку. Один байт открыл кириллицу.
|
||||
|
||||
**б) Кодовый кейв** — свободное место после конца кода, куда кладём свою функцию, и
|
||||
перенаправляем на неё вызов. У нас кейв на `0x08028860`, таблица глиф на `0x08028900`.
|
||||
|
||||
**в) Перехват вызова (hook)** — подменяем цель инструкции `bl`.
|
||||
Две главные точки в `ui_tick_normal @0x080207DC`:
|
||||
```
|
||||
0x080207E6 bl → отрисовка экрана (0x0801E1BC)
|
||||
0x08020816 bl → обработка клавиш (0x0801E6EC)
|
||||
```
|
||||
Обе диспетчеризуются по `g_screen @0x200008B3` (значения 0–11) через две tbb-таблицы.
|
||||
|
||||
> **Урок, оплаченный кирпичом:** перехватывать надо **поверх** штатного обработчика, а не
|
||||
> вместо него. Когда мы подменили отрисовку главного экрана целиком, пропала преамбула
|
||||
> `ui_draw_dispatch` — и клавиши умерли, осталась только тангента. Правильно: вызвать
|
||||
> оригинал, потом дорисовать своё.
|
||||
>
|
||||
> И второй урок: `BATT_READ @0x08010960` — **не** безопасный листовой геттер, вызов из
|
||||
> хука вешает рацию намертво (обработчик HardFault — пустой бесконечный цикл).
|
||||
|
||||
### Свой C-код в прошивке
|
||||
```bash
|
||||
clang --target=thumbv7em-none-eabi -mcpu=cortex-m4 -mthumb -Os \
|
||||
-ffreestanding -fno-builtin -nostdlib -fno-pic -c my.c
|
||||
ld.lld -Ttext=0x08029000 -e my_draw_router my.o -o my.elf
|
||||
llvm-objcopy -O binary --only-section=.text my.elf my.bin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. FM100B — DMR-баузбенд, **отдельный процессор**
|
||||
|
||||
Не Cortex-M. Образ `FM100B_V1.2.0.32_20260130.bin` (1 527 808 Б) начинается классической
|
||||
таблицей векторов ARM32 (`B` + семь `LDR PC,[PC,#0x14]`), обработчики по адресам
|
||||
`0x0301xxxx` с взведённым битом 0 → **ARMv5T (ARM926-класс), код в Thumb**.
|
||||
Внутри крутится RTOS: видны имена устройств `rom / flash0 / flash1 / sd / udisk` и
|
||||
константы MD5.
|
||||
|
||||
### Связь MCU ↔ FM100B — USART3 (`0x40004800`)
|
||||
|
||||
```
|
||||
68 | cmd | b | sub | CKhi CKlo | lenHi lenLo | payload… | 10
|
||||
```
|
||||
Контрольная сумма — «интернетная» (one's complement) сумма 16-битных BE-слов по всему
|
||||
кадру, причём **на время расчёта поле CK = 0xFFFF**, результат кладётся BE в `[4..5]`:
|
||||
|
||||
```c
|
||||
u32 s=0; while(len>1){ s += (b[0]<<8)|b[1]; b+=2; len-=2; }
|
||||
if(len) s += b[0]<<8;
|
||||
while(s>>16) s = (s&0xFFFF) + (s>>16);
|
||||
ck = ~s & 0xFFFF;
|
||||
```
|
||||
Проверено на живом ответе рации: кадр `68 84 00 00 87 7b 00 00 10` → расчёт даёт `0x877B`. ✅
|
||||
|
||||
Функции MCU: `fm100b_send1 @0x0801B044`, `fm100b_send @0x0801B0C4`,
|
||||
приём `fm100b_rx_parse @0x08018CB0`, диспетчер `fm100b_on_frame @0x08006348`,
|
||||
насос `poll_serial @0x08003050` (обязателен в любом цикле ожидания).
|
||||
|
||||
### Прозрачный мост ПК ↔ FM100B — **включение с зажатой клавишей МЕНЮ**
|
||||
|
||||
Найдено в `boot_mode_check @0x08003060`:
|
||||
```c
|
||||
if (key == 0x0A) { // МЕНЮ
|
||||
GPIOB->CLR = 2; delay(200); GPIOB->SCR = 2; // PB1 = питание модуля, перезапуск
|
||||
for(;;) { bl 0x08019720; // ПК → FM100B
|
||||
bl 0x08019758; } // FM100B → ПК
|
||||
}
|
||||
if (key == 0x0F) baud = 256000; // клавиша '#'
|
||||
if (key == 0x11) bridge_flag = 1; // SIDE1
|
||||
```
|
||||
На экране при этом «Обновление DMR чипа».
|
||||
Клавиша `#` = 256000 подтверждается официальным changelog CPS V2.01 — независимое совпадение.
|
||||
|
||||
Инструмент: **`tools/fm100b.py`** (`ping` / `send` / `raw` / `scan`).
|
||||
Под WSL порт открывается через `powershell.exe` — WSL2 не пробрасывает COM напрямую.
|
||||
|
||||
### Что модуль отвечает живьём
|
||||
|
||||
| cmd | ответ | смысл |
|
||||
|---|---|---|
|
||||
| `0x25` | `01 02 00 32` | **версия прошивки модуля = V1.2.0.32** |
|
||||
| `0x24` | `01 00 40 01` | идентификатор/возможности |
|
||||
| `0x04` | `03` | параметр |
|
||||
| `0x27`, `0x28` | `00` | статусы |
|
||||
| `0x01`, `0x03`, `0x0C`, `0x1A` | status 0 | приняты |
|
||||
| `0x05` | status `0xFF` | отказ (нужен payload) |
|
||||
| `0x02` | молчит | команды нет |
|
||||
|
||||
Версия `01 02 00 32` совпала с именем официального файла `FM100B_V1.2.0.32` — значит
|
||||
`0x25` это **подтверждённый запрос версии**. REFV ориентируется на более старую V1.2.0.27.
|
||||
|
||||
### Направление «модуль → MCU»
|
||||
Диспетчер `fm100b_on_frame` — таблица 193 смещений с базой `0x0800636C`,
|
||||
handler = база + запись. Обрабатывается 104 кода, но 94 из них — двухбайтовые заглушки.
|
||||
**Реальный код только у десяти:** `05, 06, 07, 09, 0A, 25, 3E, 42, 59, 62`.
|
||||
|
||||
Разобран `cmd 0x59` — индикация активности приёма, и она же рулит звуком:
|
||||
```c
|
||||
payload[0]==1 → GPIOA->SCR = 0x4000; // PA14 включить
|
||||
payload[0]==0 → GPIOA->CLR = 0x4000; // PA14 выключить
|
||||
```
|
||||
|
||||
### Семантика из официального changelog
|
||||
Числа там **шестнадцатеричные** (проверка: `0x33` в таблице отсутствует, `0x51` есть):
|
||||
|
||||
| cmd | назначение |
|
||||
|---|---|
|
||||
| `0x66` | девиация передачи, широкая/узкая полоса (аналог) |
|
||||
| `0x67` | компандер (только аналоговые каналы) |
|
||||
| `0x51` | вокодер, формат 2 — AGC |
|
||||
| `0x3E` | **обновление прошивки модуля — НЕ СЛАТЬ вслепую** |
|
||||
|
||||
Полный словарь имён команд (`ATC_*Req/Cnf/Ind`, ~180 штук) лежит строками прямо в образе
|
||||
модуля начиная с `0x0CDAE7`: `ATC_ChFreqSetReq`, `ATC_CallProcessReq`, `ATC_RadioIDSetReq`,
|
||||
`ATC_SMSProcessReq`, `ATC_MICCodecGainSet`, `ATC_SPKCodecGainSet`, `ATC_RssiReadReq`,
|
||||
`ATC_CalledContactINfoQuery`, `ATC_ChSlotSetReq` и т.д.
|
||||
|
||||
---
|
||||
|
||||
## 3. Аудио — двухступенчатое
|
||||
|
||||
| Линия | Что делает | Как дёрнуть |
|
||||
|---|---|---|
|
||||
| **PA2** | питание УНЧ **LN4898 (U502)**, цепь `AF MUTE4898` = вывод 12 MCU | вкл `*(u32*)0x40020018 = 4` / выкл `*(u32*)0x40020028 = 4` |
|
||||
| **PA14** | гейт звука от DMR-модуля | вкл `*(u32*)0x40020018 = 0x4000` / выкл `0x40020028` |
|
||||
|
||||
PA2 подтверждён трижды: принципиальная схема + сток + REFV.
|
||||
Штатный API со счётчиком ссылок: `audio_request @0x0801B574`, `audio_release @0x0801A348`,
|
||||
`audio_off_if_idle @0x0801A824`, флаги `0x20000C6E` (бит15 = активен).
|
||||
|
||||
**Громкость — аналоговый потенциометр VR500, программно не регулируется.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Внешний SPI-флеш 4 МБ — ресурсы
|
||||
|
||||
Карта занятых регионов (по нашему дампу `radio-spi-dump.bin`):
|
||||
|
||||
| Адрес | Размер | Содержимое |
|
||||
|---|---|---|
|
||||
| `0x000000` | 20 КБ | настройки/кодплаг |
|
||||
| `0x0D0000` | 24 КБ | таблица клавиш (видно `"Key 1"`) |
|
||||
| `0x126000` | 20 КБ | картинки (кандидат на **загрузочный логотип**) |
|
||||
| `0x14C000` | 45 КБ | индекс/сортировка иероглифов GB2312 |
|
||||
| **`0x164000`** | 200 КБ | **таблица пиньиня** (китайский ввод) |
|
||||
| **`0x198000`** | 680 КБ | **шрифты**: ASCII 7×14 @`0x19C000`, CJK 14×14 @`0x19E000` |
|
||||
| **`0x352000`** | 569 КБ | **голосовые подсказки** (8-бит PCM, тишина = `0x80`) |
|
||||
| **`0x3F0000`** | 64 КБ | **таблица Unicode↔GB** (начинается `4E 02 4E 04 4E 05`…) |
|
||||
|
||||
Формат глифа: **колоночный**, 7 колонок × 14 строк, 2 байта на колонку little-endian,
|
||||
бит 0 = верхний пиксель. CJK — 14 колонок, 28 байт.
|
||||
|
||||
### Штатный загрузчик ресурсов — найден
|
||||
`Unicode Download.exe` из пакета RT-4D 20251113 внутри называется **`FontVoicePicture`** —
|
||||
то есть официальный канал заливки **шрифтов, голоса и картинок**. Именно он пишет регионы
|
||||
`0x164000`, `0x198000`, `0x352000`, `0x3F0000`.
|
||||
|
||||
Из официального readme: *«этот инструмент обязателен после обновления прошивки, иначе текст
|
||||
SMS будет в кракозябрах»* — потому что SMS кодируется Unicode, а рации нужна таблица
|
||||
соответствия из `0x3F0000`.
|
||||
|
||||
⇒ Это готовый путь для замены **загрузочного логотипа** и **своих шрифтов** без патча MCU.
|
||||
|
||||
---
|
||||
|
||||
## 5. CPS-протокол — контракт совместимости (USART6 `0x40011400`)
|
||||
|
||||
Менять **нельзя**, иначе отвалится штатная CPS:
|
||||
```
|
||||
0x34/0x10 уведомление · 0x52 чтение SPI · 0x40 и 0x90–0xA5 запись регионов
|
||||
0x34/0xEE закрытие · контрольная сумма = простая сумма, seed 0
|
||||
```
|
||||
Это третий, независимый UART — с кадрами FM100B (`0x68…0x10`) он никак не пересекается.
|
||||
|
||||
---
|
||||
|
||||
## 6. Каталог прошивок — база для сравнения версий
|
||||
|
||||
`.../DMRhub-sale/firmware-catalog/fw/radtel/rt-4d/` — 32 файла, от V3.10 (ноя-2024) до
|
||||
фев-2026, все версии CPS, плюс `rt-4d_data_lost_fixing.rar`.
|
||||
|
||||
Извлечено **7 версий прошивки DMR-модуля**, все ровно по 1 527 808 Б, значит побайтовое
|
||||
сравнение тривиально:
|
||||
```
|
||||
FM100B_V1.2.0.13_20250430 .16_20250620 .18_20250707 .21_20250819
|
||||
.22_20250916 .24_20251010 .32_20260130 ← стоит в рации
|
||||
```
|
||||
|
||||
Полезное из changelog V3.18: *«оптимизирована связь между DMR-баузбендом и MCU,
|
||||
оптимизированы некоторые команды связи»* — то есть протокол между версиями менялся.
|
||||
|
||||
---
|
||||
|
||||
## 7. Что уже сделано и работает
|
||||
|
||||
- ✅ Извлечение стока из официального RAR через 4 слоя вложенности
|
||||
(RAR → ZIP → .NET `Ido_Update` → Intel-HEX в куче управляемых строк `#US`)
|
||||
- ✅ Полный цикл патч → сборка → прошивка → восстановление
|
||||
- ✅ Русификация: 483 записи меню + 335 вхождений в rodata + 11 инлайн-строк, 33 глифа
|
||||
- ✅ Живой двусторонний обмен с DMR-модулем с ПК
|
||||
- ✅ Аудио: обе линии найдены и подтверждены
|
||||
|
||||
## 9. Где какие функции живут (для правки «фишек»)
|
||||
|
||||
Дерево меню целиком извлечено: **181 пункт + 304 набора значений** (`menu_strings.json`,
|
||||
записи по 20 байт: 16 байт метки + позиция). Структура — 7 подменю:
|
||||
|
||||
| Подменю | Что внутри (по позициям) |
|
||||
|---|---|
|
||||
| **Basic Set** | 01 Radio Name … 31 LCD Contrast, 32 Freq Input, 33 Reverse CH Dir, 34 Carrier LED, **35 RSSI Refresh**, 36 APO, 39 Initialization, 41 Version |
|
||||
| **Key Define** | 01 Second PTT, 02–05 боковые клавиши, 06–15 «цифра + долгое нажатие» |
|
||||
| **Analog Set** | 01 SQ Level … **06 MIC Gain, 07 SPK Gain, 08 Glitch TH**, 09 Detect Range, 11–19 DTMF, 21–23 VOX |
|
||||
| **Digital Set** | 01 Personal ID … 06 SQ Level, **07 MIC Gain, 08 SPK Gain, 09 TX Denoise, 10 RX Denoise**, 11 Contacts Set, 17 Call Log |
|
||||
| **Channel Set** | 01 DMR Or Analog, **02 RX/TX Limit**, 03 CH Alias, 04 TX Power, 06 TOT, 09–18 аналоговые `(A)`, 19–28 цифровые `(D)` |
|
||||
| **Message** | 01 New SMS, 02 Inbox, 03 Outbox, 04 Drafts, **07 SMS Format, 08 SMS Font**, 09 SMS Prompt |
|
||||
| **Zone Set** | зоны |
|
||||
|
||||
**Важно:** `MIC Gain` / `SPK Gain` **не спрятаны** — они штатные пункты меню, причём
|
||||
раздельные для аналога и цифры. Открывать ничего не нужно, можно сразу менять диапазоны
|
||||
значений или логику.
|
||||
|
||||
`SMS Format` в стоке принимает значения **`Hytera / Motorola / Unicode / GBK`** —
|
||||
то есть выбор формата уже реализован.
|
||||
|
||||
### Спектроанализатор
|
||||
Пунктом меню **не является** — это функция назначаемой клавиши **`Analog Spectrum`**.
|
||||
Полный список функций клавиш (оттуда же берутся «фишки»):
|
||||
```
|
||||
Monitor(Analog) H/L Power Dual Standby TX Priority Scanning
|
||||
Backlight On-off Roger Beep FM Radio Talkaround Alarm
|
||||
Freq Detect CTC/DCS Scan Send Single Tone Status Query Remote Monitor
|
||||
Remote Stun Remote Kill Remote Wake Up Online Check Called Show
|
||||
RX AM/FM Switch Analog Spectrum SQ Freq Step DA Switch
|
||||
NOAA Mode Save CH New SMS Jump To SMS Menu Brightness
|
||||
Analog CH VOX Zone Select Promiscuous Mode Dual Slot On-off Time Slot Switch
|
||||
Color Code SW DMR Encrypt Off Jump To RX List Jump To Contact Jump To DTMF Sel
|
||||
```
|
||||
Из changelog V3.18: точки скана подняты с 81 до 105, три индикатора слева сверху вниз —
|
||||
**RSSI, Noise, Glitch**. Значит правка спектра = правка этого экрана в MCU
|
||||
(экран выбирается через `g_screen @0x200008B3`).
|
||||
|
||||
### Таблица диапазонов для `RX/TX Limit`
|
||||
```
|
||||
18-64 64-136 136-174 174-240 240-320 320-400
|
||||
400-480 480-560 560-620 840-920 920-1000 МГц
|
||||
```
|
||||
⚠️ Таблицы на `0x0801F4D0` и `0x08011218` — это сегментация ВЧ-тракта (VCO/ФАПЧ),
|
||||
**их патчить нельзя**, это не проверка лимита.
|
||||
|
||||
## 8. Ближайшие возможности
|
||||
|
||||
1. **Загрузочный логотип** — регион `0x126000` + штатный `FontVoicePicture`.
|
||||
2. **Диff версий FM100B** — 7 образов одинакового размера, изменения видны сразу.
|
||||
3. **Выравнивание словаря команд** — сопоставить ~180 имён `ATC_*` из образа модуля
|
||||
с числовыми кодами (нужна база загрузки образа, указатели смещены).
|
||||
4. **Своя логика поверх стока** — хук `0x080207E6`, обязательно вызывая оригинал.
|
||||
Ссылка в новой задаче
Block a user