Мост к 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
родитель ae36c3b729
Коммит 5f4d207aa5
34 изменённых файлов: 21718 добавлений и 17823 удалений
+2 -2
Просмотреть файл
@@ -479,7 +479,7 @@ Write mechanics (`0x8018b76`): for a normal region, before programming it **eras
The **`0x39`-based firmware-flash protocol** (handshake `[0x39,0x33,0x05,0x10,00]`, erase-trigger `[0x39,0x33,0x05,0x55,00]`, and `0x57 <offHi><offLo> + 1024B` write) with checksum **seed `0x48`** is **not present anywhere in the application binary** — a scan finds no `cmp #0x39` command comparison in the dispatcher (the only `#0x39` compares are the ASCII hex-digit parser at `0x8002e78`). This confirms the `0x39` flasher lives in the **bootloader at `0x08000000..0x08002800`**, which is a separate image not contained in `rt4d_stock_v3.25_abs_0x08002800.bin`. The bootloader is what the CLI's `probe_bootloader` (spamming `0xFF` until it echoes `0xFF`) and `command_handshake` talk to.
**Entering the bootloader from the app:** the app itself never writes internal MCU flash — it only ever reboots via the `NVIC_SystemReset` in `0x801a38c` (the `0x34..0xEE` Close). On reset, execution returns to the bootloader at `0x08000000`, which decides whether to stay in flash mode (based on a key/GPIO check at power-on — the documented "hold `*` at power-on" path — or a magic word left in RAM/backup register) or to jump to the app at `0x08002800` (`SP=0x2000AE48`, `reset=0x08002AC1`). The two firmware-flash speed modes (115200 default vs 256000 requiring `#` held at boot) are also bootloader behavior. Because the app's Close reboots into that bootloader, the practical "enter flash mode" sequence is: open a normal session (`0x34..0x10`), then reboot with hold-key to land in the `0x39` flasher — or power-cycle holding `*`/`#`.
**Entering the bootloader from the app:** the app itself never writes internal MCU flash — it only ever reboots via the `NVIC_SystemReset` in `0x801a38c` (the `0x34..0xEE` Close). On reset, execution returns to the bootloader at `0x08000000`, which decides whether to stay in flash mode (based on a key/GPIO check at power-on — the flash-mode entry (empirically: **power on while holding PTT**) — or a magic word left in RAM/backup register) or to jump to the app at `0x08002800` (`SP=0x2000AE48`, `reset=0x08002AC1`). The two firmware-flash speed modes (115200 default vs 256000 requiring `#` held at boot) are also bootloader behavior. Because the app's Close reboots into that bootloader, the practical "enter flash mode" sequence is: open a normal session (`0x34..0x10`), then reboot with hold-key to land in the `0x39` flasher — or power-cycle holding **PTT**.
### 4.5 MCU ↔ FM100B (DMR baseband) internal UART
@@ -804,4 +804,4 @@ The **4 KB calibration block at SPI offset `0x000000`** is **per-unit factory RF
1. **Read and archive the full 4 MB SPI dump** (`radio-spi-dump.bin` is one such capture) and separately verify the first `0x1000` bytes are non-`0xFF` (a valid calibration block is 100% populated per §6.3).
2. **Never issue a full-chip erase** or a bulk write that spans `0x000000`. The `0x40` region write erases a 4 KB sector at offset 0 — treat it as off-limits unless you are deliberately restoring a verified backup.
3. When modding the **codeplug only**, restrict writes to `0x002000–0x0D6FFF` (settings/channels/zones/contacts/keys). Everything at `0x100000+` is stock, regenerable firmware assets.
4. For **MCU or FM100B firmware** flashing, keep the stock vendor images (`rt4d_stock_v3.25*`, `FM100B_V1.2.0.32_20260130.bin`) on hand for rollback, and confirm you can reach the bootloader (hold `*` at power-on, `0xFF` probe echoes `0xFF`) *before* erasing, so a failed flash is recoverable.
4. For **MCU or FM100B firmware** flashing, keep the stock vendor images (`rt4d_stock_v3.25*`, `FM100B_V1.2.0.32_20260130.bin`) on hand for rollback, and confirm you can reach the bootloader (**power on holding PTT**, `0xFF` probe echoes `0xFF`) *before* erasing, so a failed flash is recoverable.
+150 -150
Просмотреть файл
@@ -1,151 +1,151 @@
## RT-4D Menu Russification — Feasibility & Plan
**Verdict: YES (in-place) — every Russian letter already has a shipped glyph and the stock GBK double-byte render path draws it with zero firmware modification; the only real work is rewriting labels in place and abbreviating any that exceed 7 Cyrillic chars.**
## Why (the three gating facts)
1. **Cyrillic glyphs present? YES.** All 66 modern-Russian letters (33+33, incl. Ё/ё) exist in the shipped SPI font ROM, addressed as GB2312 row A7 (`0xA7A1..0xA7F1`). Confirmed two ways: the Unicode codepoint index at SPI `0x3F0000` lists `U+0410..U+044F` + `U+0401/U+0451` contiguously (slots 9825–9905), and feeding the raw GBK bytes through the firmware's own glyph-address math lands on populated bitmaps (`А`=`A7A7``0x1D00D8`, 20 nonzero bytes).
2. **Render supports double-byte? YES.** `draw_string @0x08008A50` classifies any byte `≥0x80` as a GBK lead byte, consumes the trail byte, forms `(lead<<8)|trail`, and blits a 14-px cell — this is the same classic GBK path the stock Chinese mode uses. No modification needed.
3. **Fields fit? PARTIALLY — but workably.** Records are a hard 16 bytes with the last 2 reserved for the item id, leaving **14 usable bytes = 7 Cyrillic chars** (2 bytes each). Short labels (`Зоны`, `Меню`, `Каналы`, `Имя`) fit natively; labels needing ≥8 Cyrillic chars must be abbreviated (`Настр.`, `Основ.`) — standard practice for Russian ham-radio UIs and not a blocker.
The single most important constraint: records are reached by `base + 16·index` and via 80 hard literal-pool pointers, so **every patch must stay exactly 16 bytes — no resize, no reorder.**
---
## A. Menu / UI string tables & the language system
**The `0x080253ED` table is the entire on-screen UI text pool**, a contiguous array of fixed 16-byte records spanning `0x080253ED → 0x0802723D` (7,760 bytes, 480 records). It starts cleanly after code/literal-pool at `0x080252CD..0x080253EC` and ends where the country/MCC table begins (`0x0802724A`). Verified head:
```
0x80253ed 42 61 73 69 63 20 53 65 74 20 20 20 20 20 30 31 |Basic Set 01|
0x80253fd 4b 65 79 20 44 65 66 69 6e 65 20 20 20 20 30 32 |Key Define 02|
0x802544d 4d 65 73 73 61 67 65 20 20 20 20 20 20 20 30 37 |Message 07|
0x802545d 52 61 64 69 6f 20 4e 61 6d 65 20 20 20 20 30 31 |Radio Name 01| <- submenu restarts at 01
```
It interleaves two 16-byte record kinds, both translatable:
| Kind | Count | Unique | Examples | Suffix |
|---|---|---|---|---|
| Menu items (label + `NN` ordinal) | 181 | 157 | `Basic Set␠␠␠␠␠01`, `Scan Direction15`, `Personal ID␠␠␠01` | 2 ASCII digits |
| Option / enum-value / field labels | 299 | 282 | `Off`, `High`, `Slot 1`, `FM`/`AM`/`SSB`, `Show Caller Info`, `Callsign :`, `Unicode`/`GBK`/`None` | none |
**String model — pointer-indexed, not a packed blob.** A menu-descriptor table at `0x08015214`+ holds 80 literal-pool pointers, each targeting a 16-byte record at `base + 16·index` (all 16-aligned): `lit@0x0801521c → 0x080253ED` (rec #0), `lit@0x08015238 → 0x0802545D` (rec #7), etc. Each descriptor is `[RAM state word 0x2000xxxx][pointer to fixed-16 title record][NUL-terminated inline ASCII copy of the parent label]`. Consequences:
- A record's text = its 16 bytes; its identity = its index `(vaddr 0x080253ED)/16`. **Records must stay exactly 16 bytes** or every pointer/index breaks.
- The trailing `NN` is a parsed ordinal (display ordering/bounds, restarts per submenu) — **not drawn** (renderer emits only the 14-col label) and **must be preserved byte-exact**.
- A full translation must patch **both** the fixed-16 record **and** the inline descriptor copy, or the breadcrumb parent-title stays English.
**No runtime language switch, no parallel Chinese table.** The image contains zero occurrences of `Language`/`English`/`Chinese`/`中文`/`语言` (ASCII or GBK). A full-app scan for coherent GBK CJK runs found exactly one — `0x08019920 = "正在进行数字调试"` (a service/debug status line), not a menu. The SPI dump below the font region has only a sequential codepoint index at `0x14C000`, not menu text. `language=Chinese` lives in the **PC upgrade tool's** `data.ini`, configuring that Windows app, not the radio. **Conclusion: Chinese mode renders CJK by pulling glyphs from the SPI font by codepoint/GBK index at draw time — there is no Chinese table to overwrite. Russification = rewriting the English strings in place.**
Excluded from russification (not UI language): country/MCC table `0x0802724A..0x0802833E` (~256 entries), pinyin IME syllable table `0x0802219A..0x080240D8`.
**Scope:** ~157 menu labels + ~282 option/field labels (both in the fixed-16 table) + ~80 inline descriptor copies (mostly duplicates) + ~150–250 NUL-terminated rodata prompt/status strings (`Please Wait...` @`0x08014964`, `Update DMR Chip` @`0x08014950`, `Unread SMS :` @`0x0801FB44`, `FM RX Standby` @`0x080159F4`, `PC Programing`, `Draft Full!`, `Calibration OK!`, …). **Total ~600–700 distinct strings**, all edited in place at ≤ original byte width.
## B. Font glyph coverage — Cyrillic exists (make-or-break: GO)
**All 66 Russian letters are present in the shipped font ROM.** The glyphs are served from external SPI data-flash, not MCU flash — the app image contains no standalone ASCII font table, and the firmware references SPI font-band literals (`0x00200000` @`0x0801469E`).
**Wide-font Unicode index at SPI `0x3F0000`:** a sorted table of 32,256 big-endian `u16` codepoints (`4E 02 4E 04 …`), `0xFFFF`-terminated at `0x3FFC00`. Slots 0–8288 are CJK; slots 8289+ are the GB2312 A1–A9 symbol/letter rows in Unicode order (Greek `U+0391` at slot 9569, then Cyrillic). ASCII proper returns no slot (served by the separate narrow bank).
**The key test — Cyrillic in the index (all 66, GB2312 A7-row order):**
```
slot 9825:0410(А) 9826:0411(Б) ... 9831:0401(Ё) ... 9857:042f(Я) <- 33 upper incl. Ё
slot 9873:0430(а) 9874:0431(б) ... 9879:0451(ё) ... 9905:044f(я) <- 33 lower incl. ё
```
Note Ё=U+0401 inserted right after Е and ё right after е — GB2312 collation, confirming this is the A7 row (`А=0xA7A1 … Я=0xA7C1, а=0xA7D1 … я=0xA7F1, Ё=0xA7A7, ё=0xA7D7`).
**Bitmap ROM format confirmed:** glyphs live in the dense SPI band `0x150000–0x240000`. Cell format is 16×16, 2 bytes/row, little-endian, bit 0 = leftmost pixel (32 B/glyph for the wide bank). Proof — rendering `U+4E00 一` (single horizontal stroke) yields a clean 14-px bar (`FF 3F` = 8+6 bits, LSB-first), nailing bit order and cell size. Cyrillic-block windows (near `0x1B9880`) show recognizable "А" and "Ж" letterforms.
**We do NOT need to add glyphs.** Russification requires **no font editing** — labels just need to be authored as GB2312 A7-row double-byte codes, a path the stock Chinese mode already exercises. One honest caveat: the exact slot→byte-address arithmetic across the *full* 32k table is banked/sparse (not a clean single-base `FONT_BASE + slot·32`), but this is irrelevant to russification because Section C proves the *menu* path uses a direct GBK plane computation with a known base, and that lands on populated Cyrillic cells.
## C. String rendering / draw path — GBK double-byte, zero firmware change
**Primitives.** `SPI_flash_read(dst,src,len)` @`0x08021826` (thunk `0x08021828`, ~120 callers) issues opcode `0x03`, clocks the address big-endian, streams bytes. Two blitters compute `font_base + glyph_index × cell_size`:
- **ASCII blitter `0x08007FB8`** — 7 px wide, 14-byte cell, base **`0x19C000`**: `index = char 0x20`, `addr = base + index*14`.
- **CJK/GBK blitter `0x08008454`** — 14 px wide, 28-byte cell (`0x1C`), base **`0x19E000`**:
```
row = lead 0x81 ; col = trail 0x40
index = col + row*190 ( 1 when trail > 0x7F, skipping the 0x7F gap)
addr = 0x19E000 + index*28
```
**The decisive routine — `draw_string(x,y,str,len) @0x08008A50`** (~110 callers, incl. menu renderers `0x08013818–0x08014680`) iterates byte-by-byte and dispatches on a lead-byte test:
- byte `0x01–0x7F` → ASCII blitter, cursor +7 px.
- byte `0x80–0xFE`**lead byte**: consumes the next byte, forms `(lead<<8)|trail`, calls CJK blitter, cursor +14 px, `i += 2`.
- `0xFF`/`0x00` → whitespace/terminator.
This is a **classic GBK renderer**: any high-bit byte is a double-byte lead. A sibling width-measurer at `0x0800553C` uses the identical `0x80` classification, confirming the convention project-wide.
**Crucially, the menu path uses direct GBK plane math, NOT the `0x3F0000` Unicode index.** The Unicode table is a separate path (SMS/contact rendering that stores UTF-16). Menu labels reach glyphs by their raw GBK bytes. Feeding GB2312 A7 Cyrillic bytes through the exact firmware math lands on populated cells:
| Char | GBK bytes | computed SPI offset | nonzero bytes in cell |
|---|---|---|---|
| `А` | `A7 A7` | `0x1D00D8` | 20 — legible Cyrillic-A |
| `Я` | `A7 C0` | `0x1D0394` | 21 |
| `а` | `A7 D1` | `0x1D0570` | 14 |
**Exact recipe: to draw `А`, the string must contain raw bytes `0xA7 0xA7`** (`0xA7 0xC0` for `Я`, `0xA7 0xD1` for `а`, …). `draw_string` sees `0xA7 ≥ 0x80`, takes the double-byte branch, and blitter `0x08008454` fetches the already-present Cyrillic cell — **zero firmware modification**. Cost: 2 bytes / 14 px per Cyrillic char.
(Note: the A7-row byte values differ slightly between the two analyses — Section B/D derive `А=0xA7A7` from the Unicode-order slot position, while Section D also cites `А=0xA7A1` from a direct `gb18030` round-trip. This exact byte mapping must be pinned on-target during the PoC — see the PoC verify step — but both agree Cyrillic lives in row A7 and the cells are populated.)
## D. Practical encoding, field-width constraints & effort
**Field-width math.** 16-byte records, last 2 bytes = item id ⇒ **14 usable bytes = max 7 Cyrillic chars** at 2 bytes each. Mixing 1-byte ASCII punctuation (`.`) is legal GBK and saves a byte.
| English | Russian | GBK bytes | Fits 14? |
|---|---|---|---|
| Zone Set | Зоны | 8 | Yes |
| Menu | Меню | 8 | Yes |
| Channel Set | Каналы | 12 | Yes |
| Radio Name | Имя | 6 | Yes |
| Basic Set | Настр. | 11 | Yes |
| Basic Set | Настройки | 18 | **No → abbreviate** |
| Basic Set | Основные | 16 | **No → Основ. (11B)** |
Every label ≥8 Cyrillic chars must be abbreviated (Настр., Каналы, Зоны, Сообщ., Скан, Аналог, Цифра) — normal for Russian ham UIs.
**Approach comparison.** (a) Overwrite English table in place, keep `language=English` — smallest change, firmware-only, ids preserved. (b) Find and overwrite a Chinese GBK table, set `language=Chinese` — but per Section A **no such table exists**, so this is not available. (c) Font-mod: overwrite ASCII cells `0x19C000` with 1-byte Cyrillic — gives 14 chars/label but touches font flash and sacrifices Latin. **Winner: (a).** Section C proves the menu `draw_string` is codepage-agnostic (dispatches on the `0x80` bit regardless of `language` setting) and already resolves A7 Cyrillic to populated glyphs — so overwriting the English table in place with GBK A7 bytes works directly, no config change, no font flash.
## Recommended approach
**Strategy (a): overwrite the English fixed-16 UI table in place with GB2312 A7-row (GBK double-byte) Russian, no config or font-flash changes.** Justification: the menu renderer `draw_string @0x08008A50` classifies bytes purely by the high bit and routes `0x80+` through the GBK blitter `0x08008454`, which computes a direct plane address landing on the Cyrillic cells that already ship in the SPI font ROM. This is a **single-artifact change** (patch the app `.bin`, reflash MCU app) — no dependency on a nonexistent Chinese table, no SPI font rewrite, no `data.ini` edit. Length-preserving patches mean no pointer/index/relocation fix-ups.
## Proof-of-concept — one label
Target the shortest high-value item: **"Zone Set" → "Зоны"** at vaddr `0x0802543D` (file offset `0x0802543D 0x08002800 = 0x22C3D`).
Original 16 bytes:
```
5A 6F 6E 65 20 53 65 74 20 20 20 20 20 20 30 36 "Zone Set 06"
```
Replacement (GBK "Зоны" + space-pad + preserve id `06`, stays exactly 16 bytes):
```
A7 A9 A7 E0 A7 DF A7 ED 20 20 20 20 20 20 30 36
└─── Зоны ────────────┘ └── pad ───┘ └"06"┘
```
**Write & flash:** this is a **firmware (MCU app) patch**, not an SPI codeplug write. At file offset `0x22C3D` in `rt4d_stock_v3.25_abs_0x08002800.bin` overwrite bytes `[0..7]` = `A7 A9 A7 E0 A7 DF A7 ED`, `[8..13]` = `0x20`, leave `[14..15]` = `30 36`. Re-wrap the app at load base `0x08002800` (flash to `0x08000000`+`0x2800`), recompute any top-level image CRC if the loader checks one, and flash the MCU app via the normal firmware-upgrade path.
**Verify on target:** open the menu and confirm the "Zone Set" slot now reads **Зоны** in Cyrillic (not tofu/`?`). This single test simultaneously resolves the two residual unknowns: (1) that the menu draw path renders A7 double-byte bytes as glyphs, and (2) the **exact A7 byte→glyph mapping** — if "Зоны" renders but with wrong letters, re-derive the A7 trail bytes on-target (try both the `gb18030` round-trip values `А=0xA7A1…` and the Unicode-slot-order values `А=0xA7A7…`) and re-flash. If it renders correctly, the encoding is pinned and full russification is mechanical.
## Full-russification plan
1. **Pin the A7 encoding on-target** via the PoC above. Lock the definitive Cyrillic-char → GBK-byte table before batch work.
2. **Build the patch-table generator** (~40–60 lines Python): read the `0x080253ED..0x0802723D` table in 16-byte strides; for each record parse `label`+`id`; look up a translation dict; encode Russian to the pinned GBK bytes; space-pad to 14 bytes; re-append the 2-byte id; **assert `len == 16`**. Emit `(file_offset, old16, new16)` tuples. Because every patch is length-preserving, no address fix-ups.
3. **Author the translation dictionary** (~450 fixed-width labels + abbreviations that fit the 7-char ceiling and stay unambiguous). Reuse across duplicate labels.
4. **Patch inline descriptor copies** at `0x08015214`+ (NUL-terminated, ≤ original length) so breadcrumb parent-titles are Russian too.
5. **Patch the loose rodata prompt/status strings** (~150–250) — NUL-terminated, each edited at ≤ its original allocation (do not overrun into the next string). These are one-byte-encoding GBK too; abbreviate where Russian is longer.
6. **Recompute image CRC** if present; re-wrap at base `0x08002800`; flash MCU app.
7. **On-radio verification pass** — walk the full menu tree, checking abbreviations render, cursor advances correctly on mixed Russian(2B)+id(1B) fields, and no field overflows/overlaps.
**Tooling to build:** GBK encoder (trivial, `label.encode('gb18030')`), the length-preserving patch generator, an optional CRC recomputer, and a menu-tree walker checklist. **Effort:** ~half-day for tooling + PoC flash; ~1–2 days for the full translation table + on-radio verification.
**Risks:**
- **A7 byte mapping ambiguity** — the two analyses disagree on exact A7 trail bytes; the PoC resolves this before any batch work. Do not skip step 1.
- **A7 glyph slots pruned?** Section C shows the computed A7 offsets hit populated cells (`А`→20 nonzero bytes), so this appears fine, but confirm on-screen during the PoC — if any A7 slot is blank the letter renders as a gap.
- **Cursor/mixed-width** — Russian(14 px) + id digits(7 px) must not overlap; the mixed PoC field is the acid test.
- **Fixed-16 geometry** — never resize/reorder a record or change the trailing `NN`; pointer and index arithmetic will break menu dispatch.
- **Image integrity** — a top-level firmware CRC will reject naive patches; confirm/recompute before flashing.
## RT-4D Menu Russification — Feasibility & Plan
**Verdict: YES (in-place) — every Russian letter already has a shipped glyph and the stock GBK double-byte render path draws it with zero firmware modification; the only real work is rewriting labels in place and abbreviating any that exceed 7 Cyrillic chars.**
## Why (the three gating facts)
1. **Cyrillic glyphs present? YES.** All 66 modern-Russian letters (33+33, incl. Ё/ё) exist in the shipped SPI font ROM, addressed as GB2312 row A7 (`0xA7A1..0xA7F1`). Confirmed two ways: the Unicode codepoint index at SPI `0x3F0000` lists `U+0410..U+044F` + `U+0401/U+0451` contiguously (slots 9825–9905), and feeding the raw GBK bytes through the firmware's own glyph-address math lands on populated bitmaps (`А`=`A7A7``0x1D00D8`, 20 nonzero bytes).
2. **Render supports double-byte? YES.** `draw_string @0x08008A50` classifies any byte `≥0x80` as a GBK lead byte, consumes the trail byte, forms `(lead<<8)|trail`, and blits a 14-px cell — this is the same classic GBK path the stock Chinese mode uses. No modification needed.
3. **Fields fit? PARTIALLY — but workably.** Records are a hard 16 bytes with the last 2 reserved for the item id, leaving **14 usable bytes = 7 Cyrillic chars** (2 bytes each). Short labels (`Зоны`, `Меню`, `Каналы`, `Имя`) fit natively; labels needing ≥8 Cyrillic chars must be abbreviated (`Настр.`, `Основ.`) — standard practice for Russian ham-radio UIs and not a blocker.
The single most important constraint: records are reached by `base + 16·index` and via 80 hard literal-pool pointers, so **every patch must stay exactly 16 bytes — no resize, no reorder.**
---
## A. Menu / UI string tables & the language system
**The `0x080253ED` table is the entire on-screen UI text pool**, a contiguous array of fixed 16-byte records spanning `0x080253ED → 0x0802723D` (7,760 bytes, 480 records). It starts cleanly after code/literal-pool at `0x080252CD..0x080253EC` and ends where the country/MCC table begins (`0x0802724A`). Verified head:
```
0x80253ed 42 61 73 69 63 20 53 65 74 20 20 20 20 20 30 31 |Basic Set 01|
0x80253fd 4b 65 79 20 44 65 66 69 6e 65 20 20 20 20 30 32 |Key Define 02|
0x802544d 4d 65 73 73 61 67 65 20 20 20 20 20 20 20 30 37 |Message 07|
0x802545d 52 61 64 69 6f 20 4e 61 6d 65 20 20 20 20 30 31 |Radio Name 01| <- submenu restarts at 01
```
It interleaves two 16-byte record kinds, both translatable:
| Kind | Count | Unique | Examples | Suffix |
|---|---|---|---|---|
| Menu items (label + `NN` ordinal) | 181 | 157 | `Basic Set␠␠␠␠␠01`, `Scan Direction15`, `Personal ID␠␠␠01` | 2 ASCII digits |
| Option / enum-value / field labels | 299 | 282 | `Off`, `High`, `Slot 1`, `FM`/`AM`/`SSB`, `Show Caller Info`, `Callsign :`, `Unicode`/`GBK`/`None` | none |
**String model — pointer-indexed, not a packed blob.** A menu-descriptor table at `0x08015214`+ holds 80 literal-pool pointers, each targeting a 16-byte record at `base + 16·index` (all 16-aligned): `lit@0x0801521c → 0x080253ED` (rec #0), `lit@0x08015238 → 0x0802545D` (rec #7), etc. Each descriptor is `[RAM state word 0x2000xxxx][pointer to fixed-16 title record][NUL-terminated inline ASCII copy of the parent label]`. Consequences:
- A record's text = its 16 bytes; its identity = its index `(vaddr 0x080253ED)/16`. **Records must stay exactly 16 bytes** or every pointer/index breaks.
- The trailing `NN` is a parsed ordinal (display ordering/bounds, restarts per submenu) — **not drawn** (renderer emits only the 14-col label) and **must be preserved byte-exact**.
- A full translation must patch **both** the fixed-16 record **and** the inline descriptor copy, or the breadcrumb parent-title stays English.
**No runtime language switch, no parallel Chinese table.** The image contains zero occurrences of `Language`/`English`/`Chinese`/`中文`/`语言` (ASCII or GBK). A full-app scan for coherent GBK CJK runs found exactly one — `0x08019920 = "正在进行数字调试"` (a service/debug status line), not a menu. The SPI dump below the font region has only a sequential codepoint index at `0x14C000`, not menu text. `language=Chinese` lives in the **PC upgrade tool's** `data.ini`, configuring that Windows app, not the radio. **Conclusion: Chinese mode renders CJK by pulling glyphs from the SPI font by codepoint/GBK index at draw time — there is no Chinese table to overwrite. Russification = rewriting the English strings in place.**
Excluded from russification (not UI language): country/MCC table `0x0802724A..0x0802833E` (~256 entries), pinyin IME syllable table `0x0802219A..0x080240D8`.
**Scope:** ~157 menu labels + ~282 option/field labels (both in the fixed-16 table) + ~80 inline descriptor copies (mostly duplicates) + ~150–250 NUL-terminated rodata prompt/status strings (`Please Wait...` @`0x08014964`, `Update DMR Chip` @`0x08014950`, `Unread SMS :` @`0x0801FB44`, `FM RX Standby` @`0x080159F4`, `PC Programing`, `Draft Full!`, `Calibration OK!`, …). **Total ~600–700 distinct strings**, all edited in place at ≤ original byte width.
## B. Font glyph coverage — Cyrillic exists (make-or-break: GO)
**All 66 Russian letters are present in the shipped font ROM.** The glyphs are served from external SPI data-flash, not MCU flash — the app image contains no standalone ASCII font table, and the firmware references SPI font-band literals (`0x00200000` @`0x0801469E`).
**Wide-font Unicode index at SPI `0x3F0000`:** a sorted table of 32,256 big-endian `u16` codepoints (`4E 02 4E 04 …`), `0xFFFF`-terminated at `0x3FFC00`. Slots 0–8288 are CJK; slots 8289+ are the GB2312 A1–A9 symbol/letter rows in Unicode order (Greek `U+0391` at slot 9569, then Cyrillic). ASCII proper returns no slot (served by the separate narrow bank).
**The key test — Cyrillic in the index (all 66, GB2312 A7-row order):**
```
slot 9825:0410(А) 9826:0411(Б) ... 9831:0401(Ё) ... 9857:042f(Я) <- 33 upper incl. Ё
slot 9873:0430(а) 9874:0431(б) ... 9879:0451(ё) ... 9905:044f(я) <- 33 lower incl. ё
```
Note Ё=U+0401 inserted right after Е and ё right after е — GB2312 collation, confirming this is the A7 row (`А=0xA7A1 … Я=0xA7C1, а=0xA7D1 … я=0xA7F1, Ё=0xA7A7, ё=0xA7D7`).
**Bitmap ROM format confirmed:** glyphs live in the dense SPI band `0x150000–0x240000`. Cell format is 16×16, 2 bytes/row, little-endian, bit 0 = leftmost pixel (32 B/glyph for the wide bank). Proof — rendering `U+4E00 一` (single horizontal stroke) yields a clean 14-px bar (`FF 3F` = 8+6 bits, LSB-first), nailing bit order and cell size. Cyrillic-block windows (near `0x1B9880`) show recognizable "А" and "Ж" letterforms.
**We do NOT need to add glyphs.** Russification requires **no font editing** — labels just need to be authored as GB2312 A7-row double-byte codes, a path the stock Chinese mode already exercises. One honest caveat: the exact slot→byte-address arithmetic across the *full* 32k table is banked/sparse (not a clean single-base `FONT_BASE + slot·32`), but this is irrelevant to russification because Section C proves the *menu* path uses a direct GBK plane computation with a known base, and that lands on populated Cyrillic cells.
## C. String rendering / draw path — GBK double-byte, zero firmware change
**Primitives.** `SPI_flash_read(dst,src,len)` @`0x08021826` (thunk `0x08021828`, ~120 callers) issues opcode `0x03`, clocks the address big-endian, streams bytes. Two blitters compute `font_base + glyph_index × cell_size`:
- **ASCII blitter `0x08007FB8`** — 7 px wide, 14-byte cell, base **`0x19C000`**: `index = char 0x20`, `addr = base + index*14`.
- **CJK/GBK blitter `0x08008454`** — 14 px wide, 28-byte cell (`0x1C`), base **`0x19E000`**:
```
row = lead 0x81 ; col = trail 0x40
index = col + row*190 ( 1 when trail > 0x7F, skipping the 0x7F gap)
addr = 0x19E000 + index*28
```
**The decisive routine — `draw_string(x,y,str,len) @0x08008A50`** (~110 callers, incl. menu renderers `0x08013818–0x08014680`) iterates byte-by-byte and dispatches on a lead-byte test:
- byte `0x01–0x7F` → ASCII blitter, cursor +7 px.
- byte `0x80–0xFE`**lead byte**: consumes the next byte, forms `(lead<<8)|trail`, calls CJK blitter, cursor +14 px, `i += 2`.
- `0xFF`/`0x00` → whitespace/terminator.
This is a **classic GBK renderer**: any high-bit byte is a double-byte lead. A sibling width-measurer at `0x0800553C` uses the identical `0x80` classification, confirming the convention project-wide.
**Crucially, the menu path uses direct GBK plane math, NOT the `0x3F0000` Unicode index.** The Unicode table is a separate path (SMS/contact rendering that stores UTF-16). Menu labels reach glyphs by their raw GBK bytes. Feeding GB2312 A7 Cyrillic bytes through the exact firmware math lands on populated cells:
| Char | GBK bytes | computed SPI offset | nonzero bytes in cell |
|---|---|---|---|
| `А` | `A7 A7` | `0x1D00D8` | 20 — legible Cyrillic-A |
| `Я` | `A7 C0` | `0x1D0394` | 21 |
| `а` | `A7 D1` | `0x1D0570` | 14 |
**Exact recipe: to draw `А`, the string must contain raw bytes `0xA7 0xA7`** (`0xA7 0xC0` for `Я`, `0xA7 0xD1` for `а`, …). `draw_string` sees `0xA7 ≥ 0x80`, takes the double-byte branch, and blitter `0x08008454` fetches the already-present Cyrillic cell — **zero firmware modification**. Cost: 2 bytes / 14 px per Cyrillic char.
(Note: the A7-row byte values differ slightly between the two analyses — Section B/D derive `А=0xA7A7` from the Unicode-order slot position, while Section D also cites `А=0xA7A1` from a direct `gb18030` round-trip. This exact byte mapping must be pinned on-target during the PoC — see the PoC verify step — but both agree Cyrillic lives in row A7 and the cells are populated.)
## D. Practical encoding, field-width constraints & effort
**Field-width math.** 16-byte records, last 2 bytes = item id ⇒ **14 usable bytes = max 7 Cyrillic chars** at 2 bytes each. Mixing 1-byte ASCII punctuation (`.`) is legal GBK and saves a byte.
| English | Russian | GBK bytes | Fits 14? |
|---|---|---|---|
| Zone Set | Зоны | 8 | Yes |
| Menu | Меню | 8 | Yes |
| Channel Set | Каналы | 12 | Yes |
| Radio Name | Имя | 6 | Yes |
| Basic Set | Настр. | 11 | Yes |
| Basic Set | Настройки | 18 | **No → abbreviate** |
| Basic Set | Основные | 16 | **No → Основ. (11B)** |
Every label ≥8 Cyrillic chars must be abbreviated (Настр., Каналы, Зоны, Сообщ., Скан, Аналог, Цифра) — normal for Russian ham UIs.
**Approach comparison.** (a) Overwrite English table in place, keep `language=English` — smallest change, firmware-only, ids preserved. (b) Find and overwrite a Chinese GBK table, set `language=Chinese` — but per Section A **no such table exists**, so this is not available. (c) Font-mod: overwrite ASCII cells `0x19C000` with 1-byte Cyrillic — gives 14 chars/label but touches font flash and sacrifices Latin. **Winner: (a).** Section C proves the menu `draw_string` is codepage-agnostic (dispatches on the `0x80` bit regardless of `language` setting) and already resolves A7 Cyrillic to populated glyphs — so overwriting the English table in place with GBK A7 bytes works directly, no config change, no font flash.
## Recommended approach
**Strategy (a): overwrite the English fixed-16 UI table in place with GB2312 A7-row (GBK double-byte) Russian, no config or font-flash changes.** Justification: the menu renderer `draw_string @0x08008A50` classifies bytes purely by the high bit and routes `0x80+` through the GBK blitter `0x08008454`, which computes a direct plane address landing on the Cyrillic cells that already ship in the SPI font ROM. This is a **single-artifact change** (patch the app `.bin`, reflash MCU app) — no dependency on a nonexistent Chinese table, no SPI font rewrite, no `data.ini` edit. Length-preserving patches mean no pointer/index/relocation fix-ups.
## Proof-of-concept — one label
Target the shortest high-value item: **"Zone Set" → "Зоны"** at vaddr `0x0802543D` (file offset `0x0802543D 0x08002800 = 0x22C3D`).
Original 16 bytes:
```
5A 6F 6E 65 20 53 65 74 20 20 20 20 20 20 30 36 "Zone Set 06"
```
Replacement (GBK "Зоны" + space-pad + preserve id `06`, stays exactly 16 bytes):
```
A7 A9 A7 E0 A7 DF A7 ED 20 20 20 20 20 20 30 36
└─── Зоны ────────────┘ └── pad ───┘ └"06"┘
```
**Write & flash:** this is a **firmware (MCU app) patch**, not an SPI codeplug write. At file offset `0x22C3D` in `rt4d_stock_v3.25_abs_0x08002800.bin` overwrite bytes `[0..7]` = `A7 A9 A7 E0 A7 DF A7 ED`, `[8..13]` = `0x20`, leave `[14..15]` = `30 36`. Re-wrap the app at load base `0x08002800` (flash to `0x08000000`+`0x2800`), recompute any top-level image CRC if the loader checks one, and flash the MCU app via the normal firmware-upgrade path.
**Verify on target:** open the menu and confirm the "Zone Set" slot now reads **Зоны** in Cyrillic (not tofu/`?`). This single test simultaneously resolves the two residual unknowns: (1) that the menu draw path renders A7 double-byte bytes as glyphs, and (2) the **exact A7 byte→glyph mapping** — if "Зоны" renders but with wrong letters, re-derive the A7 trail bytes on-target (try both the `gb18030` round-trip values `А=0xA7A1…` and the Unicode-slot-order values `А=0xA7A7…`) and re-flash. If it renders correctly, the encoding is pinned and full russification is mechanical.
## Full-russification plan
1. **Pin the A7 encoding on-target** via the PoC above. Lock the definitive Cyrillic-char → GBK-byte table before batch work.
2. **Build the patch-table generator** (~40–60 lines Python): read the `0x080253ED..0x0802723D` table in 16-byte strides; for each record parse `label`+`id`; look up a translation dict; encode Russian to the pinned GBK bytes; space-pad to 14 bytes; re-append the 2-byte id; **assert `len == 16`**. Emit `(file_offset, old16, new16)` tuples. Because every patch is length-preserving, no address fix-ups.
3. **Author the translation dictionary** (~450 fixed-width labels + abbreviations that fit the 7-char ceiling and stay unambiguous). Reuse across duplicate labels.
4. **Patch inline descriptor copies** at `0x08015214`+ (NUL-terminated, ≤ original length) so breadcrumb parent-titles are Russian too.
5. **Patch the loose rodata prompt/status strings** (~150–250) — NUL-terminated, each edited at ≤ its original allocation (do not overrun into the next string). These are one-byte-encoding GBK too; abbreviate where Russian is longer.
6. **Recompute image CRC** if present; re-wrap at base `0x08002800`; flash MCU app.
7. **On-radio verification pass** — walk the full menu tree, checking abbreviations render, cursor advances correctly on mixed Russian(2B)+id(1B) fields, and no field overflows/overlaps.
**Tooling to build:** GBK encoder (trivial, `label.encode('gb18030')`), the length-preserving patch generator, an optional CRC recomputer, and a menu-tree walker checklist. **Effort:** ~half-day for tooling + PoC flash; ~1–2 days for the full translation table + on-radio verification.
**Risks:**
- **A7 byte mapping ambiguity** — the two analyses disagree on exact A7 trail bytes; the PoC resolves this before any batch work. Do not skip step 1.
- **A7 glyph slots pruned?** Section C shows the computed A7 offsets hit populated cells (`А`→20 nonzero bytes), so this appears fine, but confirm on-screen during the PoC — if any A7 slot is blank the letter renders as a gap.
- **Cursor/mixed-width** — Russian(14 px) + id digits(7 px) must not overlap; the mixed PoC field is the acid test.
- **Fixed-16 geometry** — never resize/reorder a record or change the trailing `NN`; pointer and index arithmetic will break menu dispatch.
- **Image integrity** — a top-level firmware CRC will reject naive patches; confirm/recompute before flashing.
**Safety note (calibration / app recovery):** all patches target the **MCU application region only** (`0x08002800`+). Do **not** touch the SPI data-flash — it holds the font ROM *and* the radio's calibration/codeplug; a bad SPI write can destroy factory RF calibration. Before flashing, dump and archive the current MCU app and the full 4 MB SPI (`radio-spi-dump.bin` already serves as the SPI baseline) so you can restore. Keep the stock app image on hand to reflash via the normal upgrade path if a patched app fails to boot. Since strategy (a) never rewrites the font or codeplug, the blast radius is limited to the app image, which is recoverable through the standard firmware-upgrade flow.
+228
Просмотреть файл
@@ -0,0 +1,228 @@
# 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.
+314
Просмотреть файл
@@ -0,0 +1,314 @@
# 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-код в прошивке
```bash
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]`:
```c
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`:
```c
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` — индикация активности приёма, и она же рулит звуком:
```c
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`, обязательно вызывая оригинал.
+84
Просмотреть файл
@@ -0,0 +1,84 @@
# Разбор кастомной прошивки REFV beta42 (DualTachyon)
Файл: `refw/rt-4d-refw-beta42.bin` — 251 904 Б (`0x3D800`, ровно размер app-региона).
Собственная, написанная **с нуля** реализация прошивки RT-4D. Используется здесь как
**независимая перепроверка** наших выводов по железу.
## 1. Структура образа
| Параметр | REFV beta42 | Сток V3.25 |
|---|---|---|
| База загрузки | `0x08002800` | `0x08002800` |
| Начальный SP | `0x2000BFF0` | `0x2000AE48` |
| Reset | `0x080029E5` | `0x08002AC1` |
| Объём кода | ~137 КБ (до `0x21AAF`), дальше `0xFF` | ~152 КБ |
| Таблица векторов | минимальная (почти все → `0x080029E1/E3`) | полная, 81 обработчик |
Брендинг: `"RT-4D REFW v0.42"` @`0x0801E83A`, `"Dual Tachyon"` @`0x0801E84B`.
## 2. Перепроверка железа — совпало полностью ✅
Перепись периферии REFV (по literal-пулам) совпадает с нашим разбором стока:
| Периферия | Ссылок в REFV | Назначение (наш вывод по стоку) |
|---|---:|---|
| GPIOA `0x40020000` | 54 | основные линии управления |
| RCC `0x40023800` | 27 | тактирование |
| ADC1 `0x40012000` | 21 | батарея |
| GPIOB / GPIOF / GPIOC | 19 / 14 / 7 | периферийные линии |
| TIM1 `0x40010000` | 14 | генерация тона/бипа |
| SPI2 `0x40003800` | 10 | SPI-флеш |
| **USART6** `0x40011400` | 9 | **CPS-линк** |
| TIM9 `0x40014000` | 9 | таймер |
| DAC `0x40007400` | 6 | аудио-тракт |
| **USART3** `0x40004800` | 6 | **линк к DMR-баузбенду FM100B** |
**Совпадения по конкретным битам GPIO:**
| Бит | REFV | Сток | Смысл |
|---|---|---|---|
| **GPIOA bit2** | `movs r2,#4; str r2,[GPIOA+0x18]` @`0x08003FCC` | SCR@`0x08014D44`, CLR@`0x0801A836` | **усилитель звука (AF MUTE4898 → LN4898 EN)** |
| GPIOA bit10 | CLR@`0x080064F6`, SCR@`0x08006516` | CLR@`0x080047B8`, SCR@`0x080047C8` | band-select / PA |
| GPIOA bit13/bit14 | активно используются (в т.ч. TOGR) | то же | служебные линии |
⇒ Вывод по аудио (**GPIOA бит 2 = вкл/выкл УНЧ**) подтверждён двумя независимыми прошивками
и принципиальной схемой. Регистры AT32: `SCR +0x18` (вкл), `CLR +0x28` (выкл), `TOGR +0x2C`.
## 3. Протокол баузбенда
REFV работает с FM100B через тот же **USART3** и то же обрамление кадра (`0x68 …`):
immediate `#0x68` встречается в 12+ местах построения пакетов.
Реализован аккуратный HAL поверх USART:
| vaddr (REFV) | роль |
|---|---|
| `0x08003D30` | конфигурация UART |
| `0x08003E56` | чтение DR |
| `0x08003E5C` | проверка флага статуса (маски `0x20` RXNE, `0x80` TC) |
| `0x080199EA`, `0x0801A056`, `0x0801A0AE` | обёртки приёма/передачи для FM100B |
## 4. Что интересного есть в REFV (по строкам)
- **Обновление прошивки самого DMR-модуля**: `"Upgrade DMR FW"`, `"Update DMR Chip"`,
`"Initialising DMR"`, `"DMR FW ERROR!"`, `"DMR FW returned "` — т.е. реализован протокол
прошивки FM100B по USART3 (самая глубокая форма «рулить бекеном»).
- **Talker Alias** (`"Talker Alias On/Off"`), `"Channel Alias"`.
- **Зоны**: `"Zone Setup"`, `"Zone selected"`, `"Zone is empty!"`, `"not in zone mode"`, `"Zone- CH-"`.
- **Скан**: `"Scan Start/End"`, `"Scan Duration/Dwell/Return/Continue"`, `"channels to scan"`.
- **Хоткеи**: `"Extra Hotkey"`, `"0 Hotkey"``"9 Hotkey"`.
- **Мониторинг/сервис**: `"Monitor success!/failed!"`, `"Being Monitored"`, `"Check Failed"`,
`"Command Failed"`, `"repeater failed"`, `"Calibration OK!"`, `"Calibration"`.
- **Версии**: `"Main Firmware"`, `"DMR Firmware"`, `"Flash Type"` — отображение версий обеих
прошивок и типа флеш-микросхемы.
- Прочее: `"Dual Standby On/Off"`, `"Dual Display"`, `"DMR Remote Kill/Stun"`,
`"DMR Time Slot: 1/2"`, `"DMR Encrypt: Off"`, `"Not DMR Channel!"`, `"DMR compatible"`.
## 5. Как это использовать
1. **Верификация** — любой наш вывод по железу можно проверить, поискав тот же регистр/бит в REFV.
2. **Протокол FM100B** — у REFV есть код прошивки баузбенда; при разборе команд обновления
можно получить полный список сервисных команд FM100B.
3. **Ориентир по фичам** — список выше показывает, что реально достижимо на этом железе.
> REFV распространяется только бинарём (исходников нет). Здесь он используется исключительно
> для сверки/исследования собственной работы.
+134
Просмотреть файл
@@ -0,0 +1,134 @@
# Спектроанализатор REFV (DualTachyon) — где он лежит
Разбор по образу `refw/rt-4d-refw-beta42.bin`
(251 904 Б, md5 `c6ab8306b5512b21b1f49fda8df2b1f5`, база загрузки `0x08002800`).
Именно эту реализацию хвалят пользователи. Ниже — как до неё добраться и из чего она состоит.
---
## 1. Как спектр вызывается
Спектр **не является пунктом меню**. Это **функция назначаемой клавиши № 22**
`Analog Spectrum` в списке функций горячих клавиш (`0x080213A9`, записи по 16 байт).
> В стоке та же функция есть под номером **21** — REFV сдвинул нумерацию, вставив
> `Bandwidth Toggle` на позицию 15.
Диспетчер функций клавиш: **`TBH @0x080094EC`**, 45 ветвей (`cmp #44` перед таблицей),
таблица с `0x080094F0`. Ветка 22 → **`0x08009CAA`**.
## 2. Точка входа `0x08009CAA`
```c
if (mode == 3 || mode == 4) return; // в этих режимах спектр запрещён
bl 0x08010ADC; // подготовка
// [area*5 + 0x84] — режим области:
// 0=Frequency, 1=Channel, 2=Zone
if (area_mode == 2) bl 0x08015318; // ← спектр ПО ЗОНЕ
else bl 0x080139E4; // ← ОБЫЧНЫЙ спектр
goto 0x08009BC0; // общий выход
```
Это ровно совпадает с официальным описанием REFV: «переключитесь в режим VFO или канала,
если нужен обычный спектр; из зоны он сканирует эту зону».
## 3. Обычный спектр — `0x080139E4` (инициализация)
```
0x080139E4 push {r4-r8,lr}
0x080139E8 bl 0x08014888 ; подготовка подсистемы
... ; сброс флагов состояния
0x08013A2C mla r2, #0x30, chan, r3 ; запись канала = 48 байт
0x08013A30 ldr [r2+5] -> [r4+5] ; копирование частоты RX/TX
0x08013A38 ldr [r2+9] -> [r4+9] ; в рабочий VFO
... ; полоса/режим: bfi в [r4], [r4+4], [r4+0xe]
0x08013A86 bl 0x08011164 ; применить настройки ВЧ-тракта
0x08013AA6 bl 0x080136EC ; подготовка экрана
0x08013ABA bl 0x08013944 ; отрисовка шапки
0x08013AC8 bl 0x08015978 ; печать числа (r1=2 разряда)
0x08013AF8 ite eq / 'W' : 'N' ; метка полосы Wide/Narrow
0x08013B1C bl 0x0801C62C ; запуск
0x08013B22 movs r2,#0x0B
0x08013B24 strb r2,[0x200009B0] ; ← ЭКРАН = 11
0x08013B26 pop
```
**Ключевой вывод: спектр — это экран № 11.** Архитектура та же, что в стоке
(`g_screen`, значения 0…11), только переменная в REFV живёт по адресу **`0x200009B0`**.
## 4. Модуль спектра целиком
| Адрес | Роль |
|---|---|
| `0x08009CAA` | точка входа с горячей клавиши, выбор обычный/зонный |
| **`0x080139E4`** | **инициализация обычного спектра**, ставит `экран = 11` |
| **`0x08013BA4`** | **тик**: автомат на 4 состояния, счётчик `[база+0x3E] & 3` |
| **`0x08013BFC`** | **обработчик клавиш** спектра, `cmp r0,#0x10` + `TBH @0x08013C20` (17 клавиш) |
| `0x08013598` | шаг развёртки (состояние 1) |
| `0x08012FA0` | рабочий обработчик (состояния 2 и 3) |
| `0x0801307C` | ветка при `состояние > 1` |
| `0x080135E8` | помощник (вызывается из `0x0801449C`) |
| `0x080136EC`, `0x08013944`, `0x08015978` | подготовка экрана, шапка, печать чисел |
| `0x08014888` | инициализация подсистемы |
| **`0x08015318`** | **спектр по зоне** — отдельная реализация |
### Автомат тика `0x08013BA4`
```c
state = (state + 1) & 3; // [база+0x3E]
switch (state) {
case 1: flags[1]=flags[2]=0; bl 0x08013598; break; // шаг развёртки
case 2: flag=0; goto 0x08012FA0; // обработка
case 3: flag=1; goto 0x08012FA0;
default: goto 0x0800A57C; // пометить перерисовку
}
```
Развёртка размазана по тикам главного цикла — экран не блокируется, поэтому спектр
и ощущается отзывчивым.
### Спектр по зоне `0x08015318`
Перебирает **200 каналов** зоны (`cmp r6,#0xC8`), запись зоны — **0x1BD байт**:
```c
for (i = 0; i < 200; i++) {
ch = zone[i].channel; // halfword, отбрасывается если >= 0x400
if (!(zone.scanlist_bitmap[i>>3] >> (i&7) & 1)) continue; // битовая карта scan-list
read_channel(sp, 0x30*ch + 0x4000); // запись канала 48 байт из SPI
// разбор полей: частота ubfx(…,8,12), CTCSS, полоса, режим
// укладка в массив с шагом 28 байт
}
```
Отсюда и фича «зонный спектр показывает живую информацию о субтонах».
## 5. Прочие детали
- **Перерисовка**: `0x0800A5FC``TBB` на 12 экранов (`cmp #0x0B`), каждая ветка делает
`memset(флаги_экрана, 1, N)`. Для экрана 11 — 16 элементов по адресу `0x200000A9`.
- **Идиом связки экранов** встречается в 6 местах:
`if (screen == 11 || (prev == 11 && (screen & ~4) == 8))` — экраны 8 и 12
накладываются поверх спектра (меню/подсказки).
## 6. Что даёт REFV сверх стока (полная таблица UI, 338 записей)
Новые функции горячих клавиш: `Bandwidth Toggle`, `Talker Alias`, `RX AM/FM/**SSB** SW`,
`DMR DCDM Switch`, `DMR TS/CC Switch`, `DMR Key Select`, `Next Zone`, `DTMF List`,
`Freq Monitor`, `Last Call Info`, `Address Book`, `Contacts List`.
Новое в меню: верхнеуровневое **`Extra`**, `Extra hotkey` + `0…9 Hotkey`,
`Edit Scan List` в зонах, `SMS Templates`, `Time Calibrate`, `AM/FM/SSB RX` в канале,
`Last Channel`, `Skip Scan`, `Radio Info`, `Flash Type`.
## 7. Как это перенести к нам
Прямой перенос бинарного кода невозможен — REFV написан с нуля, у него своя карта
памяти и свои структуры (запись канала 48 Б, запись зоны 0x1BD Б, экран в `0x200009B0`).
Переносится **архитектура**:
1. Спектр — отдельный экран в общей диспетчеризации, а не модальный цикл.
2. Развёртка разбита на состояния по тикам, поэтому UI не подвисает.
3. Вход — функция горячей клавиши, а не пункт меню.
4. Отрисовка перерисовывает только помеченные элементы (массив флагов).
В нашей прошивке всё это ложится на уже найденные точки: хук `0x080207E6` (отрисовка) и
`0x08020816` (клавиши) в `ui_tick_normal @0x080207DC`, диспетчеризация по
`g_screen @0x200008B3`. Обязательно **вызывая оригинальный обработчик** — см.
`docs/firmware-parts.md`.
+509
Просмотреть файл
@@ -0,0 +1,509 @@
# REFV beta42 — полный дамп таблиц строк
## Главная таблица (меню + значения опций) (461 записей)
- значение @0x08020BF0: `hift Main DM`
- значение @0x08020C00: `R ID Custom`
- значение @0x08020C10: `DMR ID FM`
- значение @0x08020C20: ` AM`
- значение @0x08020C30: ` SSB`
- значение @0x08020C40: ` 6 digit`
- значение @0x08020C50: `s 8 digit`
- значение @0x08020C60: `s One Sta`
- значение @0x08020C70: `tion All Ran`
- значение @0x08020C80: `ge Frequen`
- значение @0x08020C90: `cy Mode Channel`
- значение @0x08020CA0: ` Mode Impolit`
- значение @0x08020CB0: `e Carrier`
- значение @0x08020CC0: ` Match CC Matc`
- значение @0x08020CD0: `h Slot 1`
- значение @0x08020CE0: ` Slot 2`
- значение @0x08020CF0: ` Low`
- значение @0x08020D00: ` High`
- значение @0x08020D10: ` Off`
- значение @0x08020D20: ` Carrier`
- значение @0x08020D30: ` Match CTCSS/D`
- значение @0x08020D40: `CS Match Off`
- значение @0x08020D50: ` 1`
- значение @0x08020D60: ` 2`
- значение @0x08020D70: ` 3`
- значение @0x08020D80: ` 4`
- значение @0x08020D90: ` 5`
- значение @0x08020DA0: ` 6`
- значение @0x08020DB0: ` 7`
- значение @0x08020DC0: ` 8`
- значение @0x08020DD0: ` Wide`
- значение @0x08020DE0: ` Narrow`
- значение @0x08020DF0: ` Standar`
- значение @0x08020E00: `d Encrypt`
- значение @0x08020E10: ` 1 Encrypt`
- значение @0x08020E20: ` 2 Encrypt`
- значение @0x08020E30: ` 3 Mute Co`
- значение @0x08020E40: `de ARC`
**меню @0x08020E50** (2 пунктов):
- `28` AES 1
- `56` AES 2
- значение @0x08020E70: ` Private`
- значение @0x08020E80: ` Group`
- значение @0x08020E90: ` Off`
- значение @0x08020EA0: ` TX Star`
- значение @0x08020EB0: `t TX End`
- значение @0x08020EC0: ` TX Star`
- значение @0x08020ED0: `t & End 30 ms`
- значение @0x08020EE0: ` 40 ms`
- значение @0x08020EF0: ` 50 ms`
- значение @0x08020F00: ` 60 ms`
- значение @0x08020F10: ` 70 ms`
- значение @0x08020F20: ` 80 ms`
- значение @0x08020F30: ` 90 ms`
- значение @0x08020F40: ` 100 ms`
- значение @0x08020F50: ` 110 ms`
- значение @0x08020F60: ` 120 ms`
- значение @0x08020F70: ` 130 ms`
- значение @0x08020F80: ` 140 ms`
- значение @0x08020F90: ` 150 ms`
- значение @0x08020FA0: ` 160 ms`
- значение @0x08020FB0: ` 170 ms`
- значение @0x08020FC0: ` 180 ms`
- значение @0x08020FD0: ` 190 ms`
- значение @0x08020FE0: ` 200 ms`
- значение @0x08020FF0: ` 210 ms`
- значение @0x08021000: ` 220 ms`
- значение @0x08021010: ` 230 ms`
- значение @0x08021020: ` 240 ms`
- значение @0x08021030: ` 250 ms`
- значение @0x08021040: ` 260 ms`
- значение @0x08021050: ` 270 ms`
- значение @0x08021060: ` 280 ms`
- значение @0x08021070: ` 290 ms`
- значение @0x08021080: ` 300 ms`
- значение @0x08021090: ` 310 ms`
- значение @0x080210A0: ` 320 ms`
- значение @0x080210B0: ` 330 ms`
- значение @0x080210C0: ` 340 ms`
- значение @0x080210D0: ` 350 ms`
- значение @0x080210E0: ` 360 ms`
- значение @0x080210F0: ` 370 ms`
- значение @0x08021100: ` 380 ms`
- значение @0x08021110: ` 390 ms`
- значение @0x08021120: ` 400 ms`
- значение @0x08021130: ` 0 ms`
- значение @0x08021140: ` 100 ms`
- значение @0x08021150: ` 200 ms`
- значение @0x08021160: ` 300 ms`
- значение @0x08021170: ` 400 ms`
- значение @0x08021180: ` 500 ms`
- значение @0x08021190: ` 600 ms`
- значение @0x080211A0: ` 700 ms`
- значение @0x080211B0: ` 800 ms`
- значение @0x080211C0: ` 900 ms`
- значение @0x080211D0: ` 1000 ms`
- значение @0x080211E0: ` 1100 ms`
- значение @0x080211F0: ` 1200 ms`
- значение @0x08021200: ` 1300 ms`
- значение @0x08021210: ` 1400 ms`
- значение @0x08021220: ` 1500 ms`
- значение @0x08021230: ` 1600 ms`
- значение @0x08021240: ` 1700 ms`
- значение @0x08021250: ` 1800 ms`
- значение @0x08021260: ` 1900 ms`
- значение @0x08021270: ` 2000 ms`
- значение @0x08021280: ` 18-64`
**меню @0x08021290** (12 пунктов):
- `36` MHz 64-1
- `74` MHz 136-1
- `40` MHz 174-2
- `20` MHz 240-3
- `00` MHz 320-4
- `80` MHz 400-4
- `60` MHz 480-5
- `20` MHz 560-6
- `20` MHz 840-9
- `00` MHz 920-1
- `40` 0MHz <2
- `40` MHz >2
- значение @0x08021350: ` MHz Off`
- значение @0x08021360: ` Roger B`
- значение @0x08021370: `eep 1 Roger B`
- значение @0x08021380: `eep 2 Send Ra`
- значение @0x08021390: `dio Name None`
- значение @0x080213A0: ` Monitor`
- значение @0x080213B0: `(Analog) H/L Pow`
- значение @0x080213C0: `er Dual St`
- значение @0x080213D0: `andby TX Prio`
- значение @0x080213E0: `rity Scannin`
- значение @0x080213F0: `g Backlig`
- значение @0x08021400: `ht SwitchRoger B`
- значение @0x08021410: `eep FM Radi`
- значение @0x08021420: `o Talkaro`
- значение @0x08021430: `und Alarm`
- значение @0x08021440: ` Freq De`
- значение @0x08021450: `tect CTCSS/D`
- значение @0x08021460: `CS Scan Send Si`
- значение @0x08021470: `ngle ToneStatus`
- значение @0x08021480: `Query Remote`
- значение @0x08021490: `Monitor Bandwid`
- значение @0x080214A0: `th ToggleRemote`
- значение @0x080214B0: `Stun Remote`
- значение @0x080214C0: `Kill Remote`
- значение @0x080214D0: `Wake Up Online`
- значение @0x080214E0: `Check Talker`
- значение @0x080214F0: `Alias RX AM/F`
- значение @0x08021500: `M/SSB SW Analog`
- значение @0x08021510: `Spectrum SQ`
- значение @0x08021520: ` Frequen`
- значение @0x08021530: `cy Step DA Swit`
- значение @0x08021540: `ch(VFO) NOAA Mo`
- значение @0x08021550: `de Save Ch`
- значение @0x08021560: `annel New SMS`
- значение @0x08021570: ` Jump To`
- значение @0x08021580: ` SMS MenuBrightn`
- значение @0x08021590: `ess Analog`
- значение @0x080215A0: `VOX Zone Se`
- значение @0x080215B0: `lect Promisc`
- значение @0x080215C0: `uous ModeDMR DCD`
- значение @0x080215D0: `M Switch DMR TS`
- значение @0x080215E0: `Switch DMR CC`
- значение @0x080215F0: `Switch DMR Key`
- значение @0x08021600: ` Select Jump To`
- значение @0x08021610: ` RX List Address`
- значение @0x08021620: ` Book Contact`
- значение @0x08021630: `s List Next Zo`
- значение @0x08021640: `ne DTMF Li`
- значение @0x08021650: `st Freq Mo`
- значение @0x08021660: `nitor Last Ca`
- значение @0x08021670: `ll Info Unicode`
- значение @0x08021680: ` GBK`
- значение @0x08021690: ` Hytera`
- значение @0x080216A0: ` Motorol`
- значение @0x080216B0: `a Cancel`
- значение @0x080216C0: ` Confirm`
- значение @0x080216D0: ` 0.42 Du`
- значение @0x080216E0: `alTachyonShow Ch`
- значение @0x080216F0: `annel No.Show Fr`
- значение @0x08021700: `equency Show Al`
- значение @0x08021710: `ias Frequen`
- значение @0x08021720: `cy Mode Channel`
- значение @0x08021730: ` Mode Zone Mo`
- значение @0x08021740: `de Area A`
- значение @0x08021750: ` Main Ar`
- значение @0x08021760: `ea Local A`
- значение @0x08021770: `larm Remote`
- значение @0x08021780: `Alarm Local +`
- значение @0x08021790: ` Remote Origina`
- значение @0x080217A0: `l ChannelCurrent`
- значение @0x080217B0: ` Channel Last Ch`
- значение @0x080217C0: `annel Off`
- значение @0x080217D0: ` 1 : 1`
- значение @0x080217E0: ` 1 : 2`
- значение @0x080217F0: ` 1 : 3`
- значение @0x08021800: ` Off`
- значение @0x08021810: ` Talkaro`
- значение @0x08021820: `und Invert`
- значение @0x08021830: `FrequencyOff`
- значение @0x08021840: ` 5 s`
- значение @0x08021850: ` 10 s`
- значение @0x08021860: ` 15 s`
- значение @0x08021870: ` 30 s`
- значение @0x08021880: ` 45 s`
- значение @0x08021890: ` 60 s`
- значение @0x080218A0: ` 75 s`
- значение @0x080218B0: ` 90 s`
- значение @0x080218C0: ` 105 s`
- значение @0x080218D0: ` 120 s`
- значение @0x080218E0: ` 135 s`
- значение @0x080218F0: ` 150 s`
- значение @0x08021900: ` 165 s`
- значение @0x08021910: ` 180 s`
- значение @0x08021920: ` 195 s`
- значение @0x08021930: ` 210 s`
- значение @0x08021940: ` 225 s`
- значение @0x08021950: ` 240 s`
- значение @0x08021960: ` 255 s`
- значение @0x08021970: ` 270 s`
- значение @0x08021980: ` 285 s`
- значение @0x08021990: ` 300 s`
- значение @0x080219A0: ` 315 s`
- значение @0x080219B0: ` 330 s`
- значение @0x080219C0: ` 345 s`
- значение @0x080219D0: ` 360 s`
- значение @0x080219E0: ` 375 s`
- значение @0x080219F0: ` 390 s`
- значение @0x08021A00: ` 405 s`
- значение @0x08021A10: ` 420 s`
- значение @0x08021A20: ` 435 s`
- значение @0x08021A30: ` 450 s`
- значение @0x08021A40: ` 465 s`
- значение @0x08021A50: ` 480 s`
- значение @0x08021A60: ` 495 s`
- значение @0x08021A70: ` 510 s`
- значение @0x08021A80: ` 525 s`
- значение @0x08021A90: ` 540 s`
- значение @0x08021AA0: ` 555 s`
- значение @0x08021AB0: ` 570 s`
- значение @0x08021AC0: ` 585 s`
- значение @0x08021AD0: ` 600 s`
- значение @0x08021AE0: ` 0.25 kH`
- значение @0x08021AF0: `z 1.25 kH`
- значение @0x08021B00: `z 2.5 kHz`
- значение @0x08021B10: ` 5 kHz`
- значение @0x08021B20: ` 6.25 kH`
- значение @0x08021B30: `z 10 kHz`
- значение @0x08021B40: ` 12.5 kH`
- значение @0x08021B50: `z 20 kHz`
- значение @0x08021B60: ` 25 kHz`
- значение @0x08021B70: ` 50 kHz`
- значение @0x08021B80: ` 100 kHz`
- значение @0x08021B90: ` 500 kHz`
- значение @0x08021BA0: ` 1 MHz`
- значение @0x08021BB0: ` 5 MHz`
- значение @0x08021BC0: ` Edit`
- значение @0x08021BD0: ` Busy`
- значение @0x08021BE0: ` Off`
- значение @0x08021BF0: ` On`
- значение @0x08021C00: ` Contact`
- значение @0x08021C10: `s 01Dial No`
- значение @0x08021C20: `. 02Send`
- значение @0x08021C30: ` 01Save`
- значение @0x08021C40: ` 02Send`
- значение @0x08021C50: ` 01Save`
- значение @0x08021C60: ` 02Delete`
- значение @0x08021C70: ` 03Resend`
- значение @0x08021C80: ` 01Forward`
- значение @0x08021C90: ` 02Delete`
- значение @0x08021CA0: ` 03Reply`
- значение @0x08021CB0: ` 01Forward`
- значение @0x08021CC0: ` 02Delete`
- значение @0x08021CD0: ` 03Edit Na`
- значение @0x08021CE0: `me 01Edit Ty`
- значение @0x08021CF0: `pe 02Edit ID`
- значение @0x08021D00: ` 03Save`
- значение @0x08021D10: ` 04Delete`
- значение @0x08021D20: ` 05Edit Fr`
- значение @0x08021D30: `eq 01Set As`
- значение @0x08021D40: `Current02Edit Na`
- значение @0x08021D50: `me 01Set As`
- значение @0x08021D60: `Current02Send`
- значение @0x08021D70: ` 01Save`
- значение @0x08021D80: ` 02Edit Na`
- значение @0x08021D90: `me 01Edit Ty`
- значение @0x08021DA0: `pe 02Edit Ke`
- значение @0x08021DB0: `y 03Save`
- значение @0x08021DC0: ` 04Edit Na`
- значение @0x08021DD0: `me 01Edit Me`
- значение @0x08021DE0: `mber 02Save`
- значение @0x08021DF0: ` 03Clear`
- значение @0x08021E00: ` 04Edit Na`
- значение @0x08021E10: `me 01Edit Ty`
- значение @0x08021E20: `pe 02Edit ID`
- значение @0x08021E30: ` 03Save`
- значение @0x08021E40: ` 04Edit Na`
- значение @0x08021E50: `me 01Edit Ch`
- значение @0x08021E60: `annels 02Edit Sc`
- значение @0x08021E70: `an List03Contact`
- значение @0x08021E80: `s List 01New Con`
- значение @0x08021E90: `tact 02Work Mo`
- значение @0x08021EA0: `de 01RX Stan`
- значение @0x08021EB0: `dby 02Scan Mo`
- значение @0x08021EC0: `de 03Area`
- значение @0x08021ED0: ` 04Channel`
- значение @0x08021EE0: ` 05New SMS`
- значение @0x08021EF0: ` 01Inbox`
- значение @0x08021F00: ` 02Outbox`
- значение @0x08021F10: ` 03Drafts`
- значение @0x08021F20: ` 04SMS Tem`
- значение @0x08021F30: `plates 05Clear A`
- значение @0x08021F40: `ll SMS 06SMS For`
- значение @0x08021F50: `mat 07SMS Fon`
- значение @0x08021F60: `t 08SMS Pro`
- значение @0x08021F70: `mpt 09DMR DCD`
- значение @0x08021F80: `M 01DMR Slo`
- значение @0x08021F90: `t 02Color C`
- значение @0x08021FA0: `ode 03Contact`
- значение @0x08021FB0: `s 04RX Grou`
- значение @0x08021FC0: `p 05Encrypt`
- значение @0x08021FD0: `ion 06Busy Lo`
- значение @0x08021FE0: `ck 07TX Powe`
- значение @0x08021FF0: `r 08Scan Li`
- значение @0x08022000: `st 09TOT`
- значение @0x08022010: ` 10Channel`
- значение @0x08022020: ` Alias 11Offset`
- значение @0x08022030: `Freq 12Set RX`
- значение @0x08022040: `Freq 13Set TX`
- значение @0x08022050: `Freq 14Promisc`
- значение @0x08022060: `uous 15Custom`
- значение @0x08022070: `DMR ID 16DMR ID`
- значение @0x08022080: `Select 17RX/TX L`
- значение @0x08022090: `imit 18CTCSS/D`
- значение @0x080220A0: `CS 01RX CTCS`
- значение @0x080220B0: `S/DCS 02TX CTCS`
- значение @0x080220C0: `S/DCS 03Set RX`
- значение @0x080220D0: `Freq 04Set TX`
- значение @0x080220E0: `Freq 05DCS Enc`
- значение @0x080220F0: `rypt 06Mute Co`
- значение @0x08022100: `de 07Band Wi`
- значение @0x08022110: `dth 08Tail To`
- значение @0x08022120: `ne 09Scrambl`
- значение @0x08022130: `er 10Busy Lo`
- значение @0x08022140: `ck 11TX Powe`
- значение @0x08022150: `r 12Skip Sc`
- значение @0x08022160: `an 13TOT`
- значение @0x08022170: ` 14Channel`
- значение @0x08022180: ` Alias 15Offset`
- значение @0x08022190: `Freq 16AM/FM/S`
- значение @0x080221A0: `SB RX 17RX/TX L`
- значение @0x080221B0: `imit 18Radio D`
- значение @0x080221C0: `MR ID 01Start T`
- значение @0x080221D0: `one 02End Ton`
- значение @0x080221E0: `e 03Group H`
- значение @0x080221F0: `old 04Single`
- значение @0x08022200: `Hold 05SQ Leve`
- значение @0x08022210: `l 06MIC Gai`
- значение @0x08022220: `n 07SPK Gai`
- значение @0x08022230: `n 08TX Deno`
- значение @0x08022240: `ise 09RX Deno`
- значение @0x08022250: `ise 10Contact`
- значение @0x08022260: `s Setup11Group L`
- значение @0x08022270: `ist Set12Encrypt`
- значение @0x08022280: `ion Set13Send DT`
- значение @0x08022290: `MF 14Caller`
- значение @0x080222A0: `Keep 15Call Lo`
- значение @0x080222B0: `g 16Clear A`
- значение @0x080222C0: `ll Log 17Address`
- значение @0x080222D0: ` Book 18SQ Leve`
- значение @0x080222E0: `l 01TX Star`
- значение @0x080222F0: `t Tone 02TX End`
- значение @0x08022300: `Tone 03Single`
- значение @0x08022310: `Tone 04Tone Ti`
- значение @0x08022320: `mer 05MIC Gai`
- значение @0x08022330: `n 06SPK Gai`
- значение @0x08022340: `n 07Glitch`
- значение @0x08022350: `TH 08Detect`
- значение @0x08022360: `Range 09Repeate`
- значение @0x08022370: `r Delay10DTMF De`
- значение @0x08022380: `lay 11DTMF In`
- значение @0x08022390: `terval 12DTMF Du`
- значение @0x080223A0: `ration 13DTMF Mo`
- значение @0x080223B0: `de 14DTMF Li`
- значение @0x080223C0: `st 15DTMF Di`
- значение @0x080223D0: `splay 16DTMF TX`
- значение @0x080223E0: ` Gain 17DTMF RX`
- значение @0x080223F0: ` TH 18DTMF Co`
- значение @0x08022400: `ntrol 19Time Ca`
- значение @0x08022410: `librate20RSSI Re`
- значение @0x08022420: `fresh 21VOX`
- значение @0x08022430: ` 22VOX Del`
- значение @0x08022440: `ay 23VOX Thr`
- значение @0x08022450: `eshold 24Short T`
- значение @0x08022460: `ail 25Seconda`
- значение @0x08022470: `ry PTT 01Side Ke`
- значение @0x08022480: `y 1 S 02Side Ke`
- значение @0x08022490: `y 1 L 03Side Ke`
- значение @0x080224A0: `y 2 S 04Side Ke`
- значение @0x080224B0: `y 2 L 050 Press`
- значение @0x080224C0: ` Long 061 Press`
- значение @0x080224D0: ` Long 072 Press`
- значение @0x080224E0: ` Long 083 Press`
- значение @0x080224F0: ` Long 094 Press`
- значение @0x08022500: ` Long 105 Press`
- значение @0x08022510: ` Long 116 Press`
- значение @0x08022520: ` Long 127 Press`
- значение @0x08022530: ` Long 138 Press`
- значение @0x08022540: ` Long 149 Press`
- значение @0x08022550: ` Long 15Green K`
- значение @0x08022560: `ey L 16Extra h`
- значение @0x08022570: `otkey 170 Hotke`
- значение @0x08022580: `y 181 Hotke`
- значение @0x08022590: `y 192 Hotke`
- значение @0x080225A0: `y 203 Hotke`
- значение @0x080225B0: `y 214 Hotke`
- значение @0x080225C0: `y 225 Hotke`
- значение @0x080225D0: `y 236 Hotke`
- значение @0x080225E0: `y 247 Hotke`
- значение @0x080225F0: `y 258 Hotke`
- значение @0x08022600: `y 269 Hotke`
- значение @0x08022610: `y 27Radio N`
- значение @0x08022620: `ame 01Key Bee`
- значение @0x08022630: `p 02Lock Ti`
- значение @0x08022640: `mer 03Backlig`
- значение @0x08022650: `ht 04Light T`
- значение @0x08022660: `imer 05Brightn`
- значение @0x08022670: `ess 06Menu Ex`
- значение @0x08022680: `it 07Dual St`
- значение @0x08022690: `andby 08TX Prio`
- значение @0x080226A0: `rity 09Frequen`
- значение @0x080226B0: `cy Step10Talkaro`
- значение @0x080226C0: `und 11Save Mo`
- значение @0x080226D0: `de 12Scan Du`
- значение @0x080226E0: `ration 13Scan En`
- значение @0x080226F0: `d 14Scan Co`
- значение @0x08022700: `ntinue 15Scan Dw`
- значение @0x08022710: `ell 16Scan Re`
- значение @0x08022720: `turn 17Alarm T`
- значение @0x08022730: `ype 18Main PT`
- значение @0x08022740: `T TX 19Area A`
- значение @0x08022750: `Mode 20Area A`
- значение @0x08022760: `Show 21Area A`
- значение @0x08022770: `Zone 22Area B`
- значение @0x08022780: `Mode 23Area B`
- значение @0x08022790: `Show 24Area B`
- значение @0x080227A0: `Zone 25Save Ch`
- значение @0x080227B0: `annel 26Delete`
- значение @0x080227C0: `Channel27LCD Con`
- значение @0x080227D0: `trast 28Freq. D`
- значение @0x080227E0: `igits 29Reverse`
- значение @0x080227F0: ` CH Dir30Scan St`
- значение @0x08022800: `art 31Scan En`
- значение @0x08022810: `d 32Initial`
- значение @0x08022820: `ization33Radio I`
- значение @0x08022830: `nfo 34Basic S`
- значение @0x08022840: `etup 01Key Def`
- значение @0x08022850: `ine 02Analog`
- значение @0x08022860: `Setup 03Digital`
- значение @0x08022870: ` Setup 04Channel`
- значение @0x08022880: ` Setup 05Zone Se`
- значение @0x08022890: `tup 06Message`
- значение @0x080228A0: `s 07FM Radi`
- значение @0x080228B0: `o 08Extra`
## Дополнительная таблица (36 записей)
- значение @0x08020980: `(((((((((PPPPPPP`
- значение @0x08020990: `PPPPPPPPP#######`
- значение @0x080209A0: `#########KKKKKKK`
- значение @0x080209B0: `KKKKKKKKKOff`
- значение @0x080209C0: ` Side 1`
- значение @0x080209D0: ` Side 2`
- значение @0x080209E0: ` Off`
- значение @0x080209F0: ` VFO B`
- значение @0x08020A00: ` Opposit`
- значение @0x08020A10: `e VFO RX + TX`
- значение @0x08020A20: ` allowed Only RX`
- значение @0x08020A30: ` allowed Only TX`
- значение @0x08020A40: ` allowed Off`
- значение @0x08020A50: ` On whil`
- значение @0x08020A60: `e locked On alwa`
- значение @0x08020A70: `ys 400 ms`
- значение @0x08020A80: ` 600 ms`
- значение @0x08020A90: ` 800 ms`
- значение @0x08020AA0: ` 1000ms`
- значение @0x08020AB0: ` Always`
- значение @0x08020AC0: `on Off aft`
- значение @0x08020AD0: `er 5 s TX Back`
- значение @0x08020AE0: `light 01Voltage`
- значение @0x08020AF0: ` Show 02Live Su`
- значение @0x08020B00: `b Tone 03Sub Ton`
- значение @0x08020B10: `e PTT 04TOT War`
- значение @0x08020B20: `ning 05DMR Sca`
- значение @0x08020B30: `n Speed06PTT Loc`
- значение @0x08020B40: `k 07Show Zo`
- значение @0x08020B50: `ne CH 08Talker`
- значение @0x08020B60: `Alias 09 60 ms`
- значение @0x08020B70: ` 100 ms`
- значение @0x08020B80: ` 150 ms`
- значение @0x08020B90: ` 200 ms`
- значение @0x08020BA0: ` Tail To`
- значение @0x08020BB0: `ne Off 55Hz No`
+1 -1
Просмотреть файл
@@ -213,7 +213,7 @@ screen→handler map. This is the exact hook point for a rewritten UI router** (
`void boot_key_scan(void)` — at power-on reads `keypad_decode()` directly (not through KeyEv) and matches
combos: `0x11`, `0x0A` (→ enters a special mode: `0x080149D4`/`0x0801492C`, PC-programming/init screen),
`0x0F`, etc. This implements "hold `*`/`#`/side at power-on" entries. Uses the same key codes as §2.
`0x0F`, etc. This implements the hold-key-at-power-on entries (flash mode = **PTT held at power-on**). Uses the same key codes as §2.
---