Files
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

229 строки
14 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 — управление DMR-баузбендом (чтение/запись) и аудио (вкл/выкл)
Источники: реверс стоковой прошивки V3.25 (`rt4d_stock_v3.25_abs_0x08002800.bin`, база `0x08002800`,
ARM Thumb) + **принципиальная схема `RT4DDLT01 v2.1`** (Xiamen Radtel, 2024-06-04).
---
## 0. Что даёт схема (ключ к обеим задачам)
MCU — **U500, 48 pin** (распиновка LQFP48, F1-совместимая ⇒ AT32F403A/407-класс; подтверждается раскладкой
регистров GPIO, см. §2).
| Пин | Цепь на схеме | Назначение |
|---:|---|---|
| **12** | **AF MUTE4898** | **вкл/выкл УНЧ** → через `R522 (0R)` на **пин 1 (EN)** усилителя `U502 = LN4898` |
| **16 / 17** | **DIG-RXD / DIG-TXD** | UART к DMR-баузбенду FM100B (= **USART3** `0x40004800`) |
| **19** | **DIG POWER-EN** | питание/включение баузбенда |
| **23** | DIG/ANA AF-SW | выбор источника звука: цифра (FM100B) ↔ аналог |
| 21 | DIG/ANA-SW | переключение тракта модуляции |
| 2 / 3 / 4 | SCK1080 / SDA1080 / FM POWER-EN | I²C и питание FM-вещательного приёмника (BK1080) |
| 25–28 | W25Q CS/SCK/DO/DI | SPI-флеш (codeplug/шрифты/калибровка) |
| 32 / 33 | TXD, RXD/PTT | CPS-UART (USART6) и PTT |
Аудио-тракт: `DIG-AFOUT`(баузбенд) / аналог → ключи **BL1555 (U504/U505)** → потенциометр громкости
**VR500 10K** (громкость **аналоговая**, MCU ею не управляет) → `U502 LN4898` → динамик **SPK 16R/1W**
и гнездо гарнитуры `J502/PJ-208B`. Часть цепей (U701, Q700, Q502/Q503) помечены **NC** — не установлены.
---
## 1. Аудио: включение/выключение
### 1.1 Железо
`AF MUTE4898` = **GPIOA, бит 2 (PA2)**`EN` усилителя LN4898. Активный уровень — **высокий = звук есть**.
### 1.2 Регистры (AT32F43x-раскладка GPIO, подтверждена по коду)
`GPIOA = 0x40020000`; `SCR = +0x18` (установить биты), `CLR = +0x28` (сбросить), `TOGR = +0x2C`.
```c
#define GPIOA_SCR (*(volatile unsigned*)0x40020018)
#define GPIOA_CLR (*(volatile unsigned*)0x40020028)
GPIOA_SCR = 0x4; // усилитель ВКЛ (звук есть)
GPIOA_CLR = 0x4; // усилитель ВЫКЛ (тишина)
```
### 1.3 Штатный API прошивки (ref-counted — лучше использовать его)
Счётчик запросов звука — 16-битное слово флагов **`0x20000C6E`**; бит `0x8000` = «звук активен»,
младшие биты — отдельные источники (бип, тон, приём…).
| vaddr | функция | смысл |
|---|---|---|
| `0x0801B574` | `audio_request(u16 mask)` | `flags \|= mask \| 0x8000` — заявка на звук |
| `0x0801A348` | `audio_release(u16 mask)` | `flags &= ~mask`, затем вызывает ↓ |
| `0x0801A824` | `audio_off_if_idle()` | если `flags == 0x8000``flags = 0; GPIOA_CLR = 4` (**выкл**) |
| — | включение | там, где заявка первая: `if (flags == 0) GPIOA_SCR = 4;` (напр. `0x080100DE`, `0x08014D42`) |
Проверенные места: `0x08014D44` (SCR, тракт тона/бипа), `0x0801A836` (CLR, простой),
`0x080100E0` (SCR), `0x08010840` (CLR, последовательность выключения).
> **Как рулить:** для разовых экспериментов достаточно писать в `SCR/CLR`. Для «правильного» поведения
> (чтобы прошивка сама не выключила звук) — использовать `audio_request()/audio_release()`.
### 1.4 Смежное
- **Громкость** — аналоговый потенциометр `VR500`, программно не регулируется. Громкость DMR-голоса
задаётся в самом FM100B: `atc_set_call_spk_vol` (msg `0x02`) @`0x0800760A`.
- **Тон/бип** генерирует **TIM1** (`0x40010000`, CR1.CEN через `set_bit0()` @`0x08021C4C`);
функция бипа — `0x08014CB8` (частота `0x1B8` ok / `0x65C` ошибка).
- **Выбор источника** (цифра/аналог) — `DIG/ANA AF-SW`, пин 23.
---
## 2. Баузбенд FM100B: запись (команды)
Связь — **USART3 `0x40004800`** (пины 16/17), собственный бинарный протокол, полностью независимый
от CPS-протокола (USART6) и от формата codeplug.
### 2.1 Формат кадра
```
68 | cmd | b | sub | CKh CKl | lenH lenL | <payload …len> | 10
```
Полный размер = `len + 9`.
### 2.2 Примитивы отправки (блокирующие, сами качают приём)
| vaddr | функция |
|---|---|
| `0x0801B044` | `fm100b_send1(u8 cmd,u8 b,u8 sub,u8 data,void*resp,u16 timeout)` — 1 байт данных |
| `0x0801B0C4` | `fm100b_send(u8 cmd,u8 b,u8 sub,u16 len,void*resp,const u8*payload,u16 timeout)` |
| `0x08006C9C` | `usart3_tx_buf(const u8*,u16)` — сырая отправка |
| `0x08006CB8` | `usart3_tx_byte(u8)` |
| `0x08003050` | `poll_serial()`**насос**: разбор FM100B + CPS + слив кольца (звать в любом ожидании) |
Таймауты из стока: `0x3E8` (1000) для установки вызова, `0x64` (100) для конфигурации.
### 2.3 Примеры (из стока)
```c
atc_set_radio_id(id) : fm100b_send(0x49,1,1,4,resp,be32(id),0x64); // 0x080074FA (msg 0x2A вариант)
call setup : fm100b_send(0x06,1,type,5,resp,[type,be32(id)],0x3E8);
radio_apply_channel : 0x0801AE9C композит: частота/режим/полоса/мощность/CC/шумодав/ID/CTCSS
```
Полная таблица команд — в [`subsystems/dmr.md`](subsystems/dmr.md) §2 и [`subsystems/radio.md`](subsystems/radio.md) §9.
---
## 3. Баузбенд FM100B: чтение (ответы)
### 3.1 Путь приёма
```
USART3 ISR @0x080205B0 → кольцо 4 КБ
fm100b_rx_parse @0x08018CB0 — ищет кадр 0x68…0x10, проверяет CRC
fm100b_on_frame @0x08006348 — разбор:
status = f[3]; cmd = f[1];
*(u8*)(0x20007476 + cmd) = status; // ← ТАБЛИЦА СТАТУСОВ (193 записи)
if (cmd < 0xC1) jump_table[cmd](); // таблица @0x0800636C
```
### 3.2 Что читаемо
- **Статус любой команды**: `status = *(u8*)(0x20007476 + cmd)` сразу после возврата из `fm100b_send*`.
- **Данные ответа**: раскладываются персональными обработчиками `*Cnf`/`*Ind` из jump-таблицы
`@0x0800636C` (адрес обработчика = `0x0800636C + word[0x0800636C + cmd*4]`).
**104 из 193 команд имеют свой обработчик** (остальные 89 — заглушка `0x08006C26`).
Наиболее «содержательные» (с реальным телом): `cmd 05 @0x0800668C`, `06 @0x080066AA`,
`07 @0x0800671E`, `09 @0x08006816`, `0A @0x08006870`; далее идут короткие переходники к общим обработчикам.
### 3.3 Как получить «сырое» чтение
Штатный парсер сразу диспатчит кадр. Чтобы видеть **любой** ответ целиком, нужно перехватить
`fm100b_on_frame @0x08006348` (или `fm100b_rx_parse @0x08018CB0`) и скопировать кадр в свой буфер —
это открывает полный read-доступ к баузбенду.
---
## 4. Рекомендуемый способ «рулить» баузбендом
Самый практичный вариант для экспериментов — **мост FM100B ↔ ПК**: инъекция в свободный флеш
(`0x08029000+`, см. `UI-Architecture.md`), которая
1. перехватывает `fm100b_on_frame` и отправляет сырые кадры в CPS-UART (USART6),
2. принимает из USART6 команды и вызывает `fm100b_send*`.
Тогда весь протокол баузбенда доступен из скрипта на ПК — можно перебирать команды, читать ответы
и документировать неизвестные (89 «незанятых» cmd — потенциально интересны).
> ⚠️ Безопасность: не трогать калибровочные NV-элементы FM100B (`SPCali_*`) — это заводская трим-настройка
> VCO/PLL/мощности. И не писать «сырую» мощность в обход `radio_apply_channel` — риск для PA.
---
## 5. Перепроверка
Для независимой сверки доступна **REFW beta42** (`rt-4d-refw-beta42.bin`, 251 904 Б = 0x3D800 — полный
образ приложения) — другая, с нуля написанная реализация того же железа: удобно сверять GPIO и команды.
Открытый CPS: <https://github.com/jcalado/rt4d-cps/releases/tag/v0.5.4>.
---
## 6. 🔑 Мост ПК ↔ FM100B — уже реализован в прошивке
Самое важное открытие (найдено через разбор REFW и подтверждено в стоке): рация умеет работать
**прозрачным serial-мостом между ПК и DMR-баузбендом**. Именно так заводская утилита
«DMR Upgrade Tool» шьёт FM100B — через USB рации.
### 6.1 Как это сделано в РЕФВ (REFW beta42) — эталонный поток
Меню «Update DMR Chip» (`0x08008EAC` → UI `0x08005070`), затем:
```c
GPIOB->CLR = 0x2; // PB1 (DIG POWER-EN, пин 19) = LOW → выключить баузбенд
delay_ms(200);
GPIOB->SCR = 0x2; // PB1 = HIGH → включить (холодный ребут FM100B)
for (;;) { // бесконечный мост
pc_to_dmr(); // 0x0801A0EC
dmr_to_pc(); // 0x0801A11C
}
```
| Функция REFW | Роль | Кольцо |
|---|---|---|
| `0x0801A0EC` | ПК → FM100B | 4 КБ @`0x200078E4` (head `0x200078E0`, tail `0x20004F78`) |
| `0x0801A11C` | FM100B → ПК | 1 КБ @`0x200054B4` (head `0x200054AC`, tail `0x200054B0`) |
| `0x0801A094` | байт → USART3 (ждёт TC) | |
| `0x0801ADA8` | байт → USART6 (ПК) | |
| `0x0801A0D4` | `fm100b_tx_buf(buf,len)` | побайтно |
### 6.2 То же самое в стоке — **мост уже встроен**
**Направление ПК → FM100B реализовано в двух местах, оба под флагами:**
```c
// USART6 RX ISR @0x0802061C (байт с ПК)
b = USART6->DR;
ring[0x200082EF + (head & 0xFFF)] = b; // 4 КБ кольцо, head @0x20000C60
if (*(u8*)0x20000B67) // ← ФЛАГ МОСТА
usart3_tx_byte(b); // сразу в баузбенд (0x0802064A)
head++;
// главный цикл @0x080213E8
if (*(u8*)0x20000B66) { // ← ФЛАГ РЕЖИМА (CPS/мост)
cps_framer(); ...
if (tail < head) usart3_tx_byte(ring[...]); // (0x08021406)
}
```
| Адрес | Смысл |
|---|---|
| **`0x20000B67`** | флаг: пересылать байты ПК→FM100B прямо в ISR (он же loopback-флаг в `usart3_tx_byte`) |
| **`0x20000B66`** | флаг режима сессии (ветка CPS/мост в главном цикле) |
| `0x200082EF` | 4 КБ кольцо приёма с ПК, head `0x20000C60` |
| `0x08006CB8` | `usart3_tx_byte()` |
**Практический вывод:** чтобы «рулить бекеном» с компьютера, не нужно писать свой мост —
достаточно перевести рацию в этот режим (пункт меню «Update DMR Chip» / установка флагов),
после чего с ПК можно слать произвольные кадры `68 …` и читать ответы обычным скриптом.
### 6.3 Новые команды FM100B, найденные в REFW
REFW хранит **готовые шаблоны кадров** в флеше и шлёт их `fm100b_tx_buf(buf,len)`:
| Адрес шаблона | Кадр | Назначение |
|---|---|---|
| `0x08020072` | `68 3E 01 01 [CK CK] 00 0F ×15 0F 10` (24 Б) | **cmd `0x3E`** — «Update DMR Chip» (перевод FM100B в режим обновления) |
| `0x0802008A` | `68 42 01 01 [CK CK] 00 01 FF 10` (10 Б) | **cmd `0x42`** |
Команд `0x3E` и `0x42` **не было** в нашем разборе стока — это новые, полученные из REFW.
Отправщик cmd `0x3E``0x0800509C`, вызывается из 4 мест (`0x080064DA`, `0x08008018`,
`0x08011186`, `0x0801129C`).
### 6.4 Проверка версии прошивки DMR (REFW)
`0x08008DE0`+ : REFW читает версию FM100B в `0x20004F74` и сравнивает с `0x01020027`;
если ниже — показывает «Upgrade DMR FW / to v1.2.0.28 / or higher». То есть версия баузбенда
**читается программно** — это готовый пример «чтения» из FM100B.