Files
rt-4d/docs/firmware-parts.md
T
viktor 5f4d207aa5 Мост к 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

315 строки
19 KiB
Markdown
Исходник Ответственный История

Этот файл содержит неоднозначные символы Юникода
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`, обязательно вызывая оригинал.