Stock TYT-family codeplugs
The vendor firmware of the UV380 family (a two-region codeplug with 3000 channels) and of the 256 KB family (one 256 KB codeplug image) keeps its codeplug in the radio's SPI flash, in a layout of its own. RadioFoundry doesn't use that layout: it keeps the open-firmware layout (open-firmware-cps.md 7) in the same flash. A backup taken right after RadioFoundry was installed over the stock firmware therefore still holds the stock codeplug. This spec covers how the Codeplug Editor tells the two layouts apart (section 2) and the stock records it converts (sections 3-4).
(T) marks a value checked with qdmr's dmrconf 0.15.1 on files: dmrconf encode run on YAML test files with one
field changed at a time and the binaries compared, dmrconf decode run on binaries changed one byte at a time, and a
SPI flash dump of a UV380 family radio that dmrconf decode reads as a codeplug when its two regions are put in a
DfuSe file.
Code: site/static/js/codeplug/stock_tyt.js (the records), site/static/js/codeplug/detect.js (section 2),
site/static/js/editor/files.js (the editor's Open), site/static/js/flasher/codeplug.js (the Tools page), tests in
site/tests/stock.test.mjs.
1. Where the codeplug is
Addresses in this spec are SPI flash addresses. dmrconf's binary for these radios is a DfuSe file whose element addresses are the same addresses (T).
| Family | dmrconf keys | Regions |
|---|---|---|
| UV380 | uv390, md2017 and the keys of the radios sold under other names |
0x002000-0x03FFFF and 0x110000-0x19FFFF |
| 256 KB | the family's one key | 0x002000-0x03FFFF (the 256 KB codeplug image) |
Radios sold under two names write the same bytes apart from the programming time (T). uv390 and md2017 write the
same records at the same addresses as the base model, plus tables this spec doesn't cover (T).
RadioFoundry's flash map on these radios puts its emulated EEPROM, settings ring, journal and second DMR ID database area at addresses that overlap the stock tables (settings-store.md 1). Until the user writes a RadioFoundry codeplug and an ID database, those areas still hold the stock bytes.
Files that carry the same codeplug:
| File | Content |
|---|---|
SPI flash dump (16 MB, in a backup .zip as spi-flash.bin) |
Every address as above |
256 KB family CPS file (.rdt, 262709 bytes) |
A 549-byte header, the 256 KB image from address 0, a 16-byte trailer |
| 256 KB family raw image (262144 bytes) | The 256 KB image from address 0 |
UV380 family CPS file (.rdt, 852533 bytes) |
dmrconf requires this size (T). Where its regions sit in the file is not known: a 549-byte header, the image from 0, then 0x110000-0x19FFFF did not decode. The editor reports the file as not supported |
2. Telling the layouts apart
The editor decides from the bytes, in this order. A file can hold both layouts in part: a radio that moved to RadioFoundry and was programmed keeps stock bytes wherever RadioFoundry hasn't written. The open-firmware layout wins when it has evidence.
-
Open-firmware evidence. Each record the open-firmware layout marks as in use counts as good or bad:
- the general settings (open-firmware-cps.md 7.3.2): good when the DMR ID is valid BCD and not 0 and the callsign is a valid name; bad when the callsign field holds bytes that aren't a name;
- every channel whose in-use bit is set (7.3.3): good when both frequencies are valid BCD and the name is valid; a record of all 0xFF bytes is erased flash and doesn't count;
- every zone whose in-use bit is set (7.3.4) and whose record isn't all 0xFF, every contact that decodes (7.3.5), every RX group list whose count is set (7.3.6): good when the name is valid and not empty.
A valid name is Latin-1 text (0x20-0x7E, 0xA0-0xFE) up to the first 0x00 or 0xFF, with only 0x00 or 0xFF after it. A UTF-16 name, as the stock layout stores them, fails: its second byte is 0x00 and printable bytes follow. The layout is the open-firmware one when there is at least one good record and at most one bad record for every four good ones.
-
Stock evidence. The channel table of each family (section 3.2) is checked: a record is in use when its first name character is not 0 and its mode (byte 0 bits 0-1) is 1 or 2. An in-use record is good when both frequencies are valid BCD and the name is valid UTF-16 (each character 0x0020 or above and not 0xFFFF, 0x0000 after the first 0x0000). The UV380 table is checked first, then the 256 KB table. Each needs at least one good record and at most one bad one for every four good ones.
-
Blank. No record in use in either layout: an erased or empty codeplug. The editor opens it as an empty open-firmware codeplug.
-
Anything else is a codeplug in a layout the editor doesn't know.
A UV380 family .rdt file is recognized by its size alone (section 1) and reported as not supported.
The stock dump above has no open-firmware evidence (its general settings are erased) and 16 good stock channels. A RadioFoundry codeplug image of the same radio has good open-firmware records and no stock channels.
3. Records
All multi-byte numbers are little endian. Text is UTF-16LE, padded with 0x0000. Frequencies and tones use the same encodings as the open-firmware layout (open-firmware-cps.md 7.3.3): a frequency is 8 BCD digits of 10 Hz units in a LE 32-bit word, and a tone is a LE 16-bit word, 0xFFFF for none, BCD tenths of a Hz for CTCSS (67.0 Hz = 0x0670), or bit 15 set for DCS with the three octal digits in the low 12 bits and bit 14 set when inverted (D754I = 0xC754) (T).
3.1 General settings (0x2000)
| Address | Size | Field |
|---|---|---|
| 0x2040 | 20 | Boot text line 1, 10 characters |
| 0x2054 | 20 | Boot text line 2, 10 characters |
| 0x2084 | 3 | The radio's DMR ID, a 24-bit number |
| 0x20B0 | 32 | Radio name, 16 characters |
dmrconf also writes the programming time near 0x2002 as BCD, so two encodes of one file differ there (T).
3.2 Tables
| Table | UV380 family | 256 KB family | Record size | In use when (T: dmrconf decode skips the others) |
|---|---|---|---|---|
| Channels | 0x110000, 3000 | 0x01EE00, 1000 | 64 | the name is not empty (section 2 adds the mode and frequency checks) |
| Contacts | 0x140000, 10000 | 0x005F80, 1000 | 36 | the name is not empty and the call type is not 0 |
| RX group lists | 0x00EC20, 250 | 0x00EC20, 250 | 96 | the name is not empty |
| Zones | 0x0149E0, 250 | 0x0149E0, 250 | 64 | the name is not empty |
| Zone extensions | 0x031000, 250 | none | 224 | belongs to the zone with the same index |
Records refer to each other by 1-based index into the table; 0 means none. A reference to a record that isn't in use is dropped (dmrconf leaves such a zone member out, T). A name that isn't valid UTF-16 (section 2) counts as empty, so erased flash (0xFF) reads as unused.
RadioFoundry's settings ring and sector journal (0x011000-0x01AFFF, settings-store.md 1) lie inside the
RX group list and zone tables of both families. Until RadioFoundry claims the area, the stock bytes there are intact. The claim
erases it, and RadioFoundry then writes its own records. The area counts as claimed when a 256-byte slot from 0x011000 to
0x018FFF starts with the settings magic "OSET", or 0x019000 or 0x01A000 starts with a journal magic ("OJNL", "OJN2").
A claimed area holds no stock records: list and zone records that overlap it are skipped, and the conversion reports
it (note settings_area). A backup taken before the claim keeps them.
3.3 Channel (64 bytes)
| Offset | Bits | Field |
|---|---|---|
| 0x00 | 0-1 | Mode: 1 FM, 2 DMR. Unused records hold 3, or 1 with an empty name |
| 0x00 | 3 | FM bandwidth: 1 = 25 kHz, 0 = 12.5 kHz |
| 0x01 | 1 | RX only |
| 0x01 | 2-3 | Time slot: 1 = TS1, 2 = TS2 |
| 0x01 | 4-7 | Color code, 0-15 |
| 0x04 | 4 | VOX |
| 0x04 | 5 | Power: 1 high, 0 low (dmrconf writes Low and Mid both as 0) |
| 0x04 | 6-7 | Admit criterion: 0 always, 1 channel free, 2 CTCSS/DCS (FM), 3 color code (DMR) |
| 0x06 | 16 | TX contact, an index into the contacts |
| 0x08 | 8 | TX timeout in 15 s units; 0 = off |
| 0x0A | 8 | Scan list index |
| 0x0C | 8 | RX group list index |
| 0x0F | 8 | 0xFF in every record seen |
| 0x10 | 32 | RX frequency |
| 0x14 | 32 | TX frequency |
| 0x18 | 16 | RX tone (decode) |
| 0x1A | 16 | TX tone (encode) |
| 0x20 | 32 bytes | Name, 16 characters |
The FM squelch level is not stored per channel: changing qdmr's channel squelch changes no byte (T).
3.4 Contact (36 bytes)
| Offset | Size | Field |
|---|---|---|
| 0x00 | 3 | DMR ID or talkgroup, a 24-bit number; 0xFFFFFF for all call |
| 0x03 | bits 0-1 | Call type: 1 group, 2 private, 3 all call; 0 unused |
| 0x03 | bit 5 | Ring (call alert tone) |
| 0x04 | 32 | Name, 16 characters |
Unused records hold FF FF FF C0 and a zero name (T).
3.5 RX group list (96 bytes)
A 16-character name (32 bytes), then 32 contact indexes (16 bits each); the first 0 ends the list. dmrconf writes only group call contacts into a list (T).
3.6 Zone (64 bytes) and zone extension (224 bytes)
A zone record is a 16-character name (32 bytes), then the first 16 channel indexes of its A list. On the UV380 family, the extension with the same index holds 48 more A channels (offset 0x00, to 64), then 64 B channels (offset 0x60). The first 0 ends each list. The 256 KB family has 16 channels per zone and no B list: dmrconf writes a longer or two-list zone as two zones (T).
4. Conversion to the open-firmware layout
The Codeplug Editor and the Tools page build an open-firmware codeplug from a stock one
(stock_tyt.js importStock), the same way a qdmr import fills the editing model (qdmr-yaml.md).
| Stock | Open-firmware |
|---|---|
| Radio name, DMR ID, boot text | Radio tab. The name is cut to 8 characters |
| Channels | Channels, numbered in table order. Power high = level "High" (9), low = "Low" (3). Squelch is the radio's default. Bandwidth only for FM |
| Contacts | Contacts with time slot override off. The ring flag is dropped |
| RX group lists | RX group lists, 32 contacts each at most |
| Zones | A zone with a B list becomes two zones, "Name A" and "Name B", as for a qdmr import |
| Admit criterion, scan lists, everything else | Not converted. Scan lists are reported |
Names keep their characters; characters outside Latin-1 become '?' when the record is written. The open-firmware
layout holds 1024 channels, 1024 contacts, 76 RX group lists and 68 zones. When a table has more, the first records
in table order are kept, except that contacts and RX group lists that a kept channel or list refers to are kept
first. Each loss is reported with the qdmr import's note codes (too_many_channels, shortened, bad_frequency,
scan_lists and the others).
5. Open points
- The 256 KB family addresses are from dmrconf's binary for that family only. No SPI flash dump of that family has been checked, so whether the vendor firmware keeps the image at SPI address 0 is unverified.
- The UV380 family
.rdtlayout (section 1). - Power levels between low and high, and the meaning of channel bytes not listed in 3.3.