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

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

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-код в прошивке

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]:

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:

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 — индикация активности приёма, и она же рулит звуком:

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, обязательно вызывая оригинал.