Баузбенд 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
229 строки
14 KiB
Markdown
229 строки
14 KiB
Markdown
# 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.
|