# 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.
