Баузбенд 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
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. Ближайшие возможности
- Загрузочный логотип — регион
0x126000+ штатныйFontVoicePicture. - Диff версий FM100B — 7 образов одинакового размера, изменения видны сразу.
- Выравнивание словаря команд — сопоставить ~180 имён
ATC_*из образа модуля с числовыми кодами (нужна база загрузки образа, указатели смещены). - Своя логика поверх стока — хук
0x080207E6, обязательно вызывая оригинал.