Radioddity DB25-D PRO hardware

This page holds the facts a firmware needs to bring up the DB25-D PRO mobile: SoC, memory, pins, the two RF chips, display, storage, calibration, the PC link and the update path. The survey entry, with sources for the model and FCC facts, is in radios.md. The HR_C7000 itself (UARTs, timers, ADC, DAC, PIC, GPIO) is in hardware-dm32.md and its modem in modem-hrc7000.md; this page only says where the DB25-D differs.

Values marked (measured) were checked on a DB25-D PRO (firmware 909E.D6x.EARSAB.020) on 2026-10-16 or later, or on the SDR bench. (datasheet) is from a chip data sheet or the HR_C7000 user guide. (unverified) is not confirmed on a radio or in a datasheet.

Section Contents
1-2 Models, memory, clocks
3 Pin map: buses, RF switching, keys, hand microphone
4 RF chain: the two RF chips, register values, TX and RX sequences, DMR path
5-6 Display, storage, calibration
7 PC link, update and recovery
8 Codec
9-10 How RadioFoundry uses it, open questions

1. Models and batches

Item Finding
SoC One HR_C7000 (CK803S core), marked "HR_C7000 D56323MP1"
Boards (PRO) Control "CDR300UV-MCU-BT-AI-TDA-1.0", display "C7DR300UV-TFT-51M-FD-2.0", RX "C7DR300UV-RX-BT-AD-3.0", TX "C7DR300UV-TX-RD35-BT-AD-3.5"
One image for every board The firmware identifies both RF chips at boot (section 4.2) and writes two letters after the "D" of its version string
Batch codes D64, D66, D6T: FD6818 main transceiver (2021-2023). DBB: two BK4819, the PRO
Minimum vendor firmware on a PRO The 2024-05-09 build is the first that knows the BK4819. Older vendor firmware must never be loaded on a PRO

2. Memory and image

Item Finding
Program flash XIP at 0x03000000. Boya, 2 MB (JEDEC ID 68 40 15), 4 KB sectors. QE (status register 2 bit 1) is set: the application sets it before it switches the controller to quad mode (measured)
Loader 0x03000000-0x0300BFFF (48 KB, 36 KB used), the vendor IAP loader. It's never in an update package (measured)
Application Linked at 0x0300C000; the loader jumps to its first byte. Raw image: no header, no checksum, no encryption. On a normal power-on the loader starts it only if its first 4 bytes are 2C 11 01 C0 (the entry of the vendor application, lrw r1, ...; mtcr r1, psr), else it stays in update mode; right after an update it starts it without looking (measured)
RadioFoundry's layout The first stage (boot/db25d, boot/README.md) in the 4 KB sector at 0x0300C000, carrying that signature; the application from 0x0300D000 (measured)
Clocks The loader leaves the PLL at 72 MHz (24 MHz × 24 / 2 / 4), APB 36 MHz, on both paths into the application. The vendor application never writes the clock manager, so it runs on those. RadioFoundry re-programs the PLL to the DM-32's 168 MHz CPU / 84 MHz APB (section 2.1) (measured)
Start-up state PSR 0x80000100, SP 0x50000, VBR 0x10000, cache set as on the DM-32
SRAM 0x10000-0x57FFF as on the DM-32. The vendor application uses 0x10000-0x525E0
Modem SRAM The vendor application copies its modem and vocoder tables to 0x18000800-0x18004F90

2.1 Clocks in RadioFoundry

Item Setting
Switch DB25D_FULL_CLOCK=1 (default, board.mk): CPU / AHB 168 MHz, APB 84 MHz as on the DM-32 (hardware-dm32.md 1.1). DB25D_FULL_CLOCK=0: no PLL change, 72 / 36 MHz as the loader leaves them. Both builds define HRC_LOADER_CPU_HZ = 72 MHz, which the stub hand-over uses (measured)
Limits The user guide (4.2.2.2) gives CPU and AHB up to 192 MHz, APB up to 96 MHz (typical 120 / 60). 168 / 84 is inside them. The NAND takes 133 MHz, the ST7735S writes at up to 15 MHz (datasheet)
Start-up The loader runs the CPU from the BPLL, which must not be reprogrammed under it. hrc_clock_init (RAM code, interrupts off, before the vector table is set up) saves CLK_MGR 0x010 / 0x024 / 0x028, sets the SoC clock select (CLK_MGR_CTRL bit 3) to the oscillator, clears the BPLL configure bit (bit 1), writes the 24 MHz / 1 × 28 / 2 / 2 word, USB / SDIO dividers and AHB:APB = 1:2 as the DM-32 does, sets bit 1 again, waits 100 us for the lock flag, then clears bit 3. The APLL (modem clock, bit 0) is left alone. If the PLL never locks within 2 s, the saved words go back. The XIP flash is switched to quad mode at divider 2 before, as on the DM-32; its SCLK follows the AHB clock (measured)
Timers, UART, SSI All derive from HRC_APB_HZ at build time: timer 2 (microseconds), the tick, the one-shots, UART divisor 46 for 115200 (0.9 % slow; 20 at 36 MHz is 2.3 % slow), NAND SPI0 divider 4 (21 MHz, 9 MHz at 36), display SPI2 divider 6 (14 MHz, 6 MHz at 36) (measured)
Bit-banged display Each phase of the clock line is written hrc_bitbang_repeat(APB) times (3 at 84 MHz, 2 at 36), so the phases stay over about 30 ns. Used only when the ADC 2 strap reads 50 or more (3.1) (measured)
Not clock dependent The BK4819 3-wire bus, key scan, APC ramp and TX settling use hal_delay_us (timer 2). ADC conversions run from the 24 MHz oscillator (user guide clock tree: oscillator / 10); the ADC and UART wait loops are counted, and are shorter in wall time at 168 MHz, still several ms (measured)
Modem The modem and codec clocks come from the APLL and the oscillator, not the BPLL (user guide clock tree), and hrc_clock_init doesn't touch them; the DMR code's assumptions are the DM-32's, which are 168 / 84 (datasheet)
Hand-over enter_stub and hrc_soft_reset (the REBOOT path) call hrc_clock_restore_loader() first: SoC clock to the oscillator, the saved CLK_MGR words back, lock, SoC clock to the PLL, so the loader (which finds the clock manager as it left it, a soft reset only resets the CPU side) and the runtime stub see the clocks they expect (the universal stub measures timer 2 anyway and gets the rates in CONFIG, 7; it measured 35.6 MHz). Before it the UART drains; after it the divisor is set for 36 MHz (20). A power cycle resets the clock manager (hard reset), and the first stage (boot/db25d) runs before any RadioFoundry code, so the power-on stage catch always sees the loader's clocks even if RadioFoundry hangs at 168 MHz (measured)

Check the clock on a radio: the debug link's uptime_ms has to advance one second per second (timer 2 counts APB cycles, so a wrong APB shows at once) and the host link has to answer at 115200 (the UART divisor assumes 84 MHz).

3. Pin map

GPIO index as on the DM-32: 0-31 PTA, 32-63 PTB, 64-95 PTC. Levels are "high = on" unless stated.

3.1 Buses

Bus Pins Protocol
Display PTC2 (66) reset, PTC3 (67) CS, PTC4 (68) SCL, PTC6 (70) D/C (low = command); data on PTC7 (71) or PTC8 (72) 4-wire serial, 8 bits MSB first, data set while SCL is low and taken on the rising edge. ADC channel 2 is a board strap. Reading ≥ 50: bit-banged GPIO, data on PTC7. Below 50: the SPI2 controller (0x140E0000), SCLK on PTC4 (IO_DIPLEX2 [6:5] = 1) and MOSI on PTC8 (IO_DIPLEX2 [12:11] = 1); CS, D/C and reset stay GPIO. The owner's radio reads about 2 (SPI2) (measured)
SPI NAND SPI0 (0x140A0000), CS PTA18 (18) by GPIO Mode 0, 8-bit frames, BAUDR 4 (21 MHz)
RF chips SCLK PTB16 (48), SDIO PTB24 (56) bidirectional, SEN main PTB25 (57), SEN LO PTC20 (84) 3-wire, as the DM-32's FD6818: 8-bit address (bit 7 = read), 16 data bits, MSB first. One lock around every access
RF chip lines PTB27 (59) input with interrupt; PTB26 (58) input ? (interrupt / sub-audio detect, status)
FM broadcast (RDA5802N) Bit-banged I2C, SCL PTA7 (7), SDA PTA8 (8), 7-bit address 0x11
RTC SoC I2C2, address 0x70
PC link UART1 (0x14040000), RXD PTA9 (9), TXD PTA10 (10), 115200 8N1. The vendor application's debug output goes to the same port. RadioFoundry's host port, where a CPU fault's trace lines appear (crash-log.md 2.3)
GPS UART2 (0x14050000, interrupt source 11), 9600 8N1 receive only; RXD pad PTA11 (IO_DIPLEX0 bit 11 = 0, HR_C7000 user guide 4.4.4.4); power PTB17 (49), high = on, confirmed on the radio 2026-10-17 (RadioFoundry's single constant is GPS_POWER_ON_LEVEL in boards/db25d/pins.h). Module: the green daughter board (CC1157G) (measured)

The DM-32's programming cable is on UART0. The DB25-D never uses UART0.

3.2 RF switching and the PA

Pin Use
PTC19 (83) RX front end, on in both RX paths
PTB4 (36) / PTA25 (25) RX path VHF / UHF
PTB3 (35) / PTA26 (26) TX path (PA select) VHF / UHF
PTA23 (23) High in RX, low in TX
PTB2 (34), PTA27 (27) Low in RX. TX supply, one pin per band, raised 200 us into TX: UHF PTA27 high with PTB2 driven low, VHF PTB2 high with PTA27 driven low
DAC A RX front-end tuning: the calibration value for the RX frequency, written at each receive set-up (6.4)
DAC B PA power control. Ramped over 180 steps of 4 us at the start and end of each burst
DAC C 1762 (0x06E2) at init, as on the DM-32; 1780 at each receive set-up
DAC init IO_DIPLEX2 bits 27–29 cleared (all three outputs on their pins), PD_MODE_EN = 0, PD_CTRL = 0 (all channels on), every data register 0, then DAC C

To TX: RX paths off, 50 us, tune, the page's TX-on delay, PA select of the band (UHF PTA26 high and PTB3 low, VHF the reverse) with PTA23 low, 200 us, the band's TX supply, then the PA ramp up (4.4). To RX: the main chip to its RX state, the page's TX-off delay, the ramp down, both supplies and both PA selects low, PTA23 high, 300 us, the retune, the RX path of the band, 200 us.

Transmitter safe state (board_rf_safe, crash-log.md 8), at every fault, reset and stub entry and first in main(): TX supplies PTA27 and PTB2 low, PA selects PTA26 and PTB3 low, DAC B 0 and powered down, PTA23 high; on the fault and reset paths also the modem's FM_PTT and LAYER2_TXRX_CTRL 0. The first stage (boot/db25d) drives the same pins (not the DAC) right after the power latch; boards/db25d/tests/bootDb25dTest.c (test_tx_off) tests it. The BK4819s keep their state until the next start. Found on the radio (2026-10-21): a fault while transmitting, then the soft reset, left the PA keyed at about 15 W of supply draw with the radio stopped before the display was lit, until a power cycle; that fault is why board_rf_safe exists. The fault trace goes out on UART1, and was checked on the radio on 2026-10-21. A watchdog restart reports the cause warm.

3.3 Keys, encoder, power, LEDs, audio

Pin Use
PTB18, PTB20, PTB21, PTB22, PTB23 (50, 52-55) Key matrix drive lines, idle high, driven low one at a time
PTB9, PTB10, PTB11 (41-43) Key matrix sense lines, low = closed. PTB9 and PTB10 also carry one key each wired straight to ground (low with every drive line high)
PTA9 (9) Also read as a GPIO by the key code: the mic connector's line that carries PTT and the radio's RXD. RadioFoundry counts a low PTA9 as PTT only 50 ms or more after the last good UART byte and held low that way for 20 ms (drivers/input/sharedptt, gotchas 16.2), then on three scans in a row; the diagnostics line mic ends with ign N, the low scans ignored. The three-scan filter alone let debug link polling key the radio; with the gate, 30 s of polling gave ign 5 and no PTT press (2026-10-19, measured)
PTC11 (75) Hand mic PTT: high at idle, low while held. The mic's digit and P keys change neither PTA9 nor PTC11 at a 10 ms scan; how they reach the radio is open (measured)
PTB5 / PTB8 (37 / 40) Rotary encoder A / B, GPIO B interrupt
PTB12 (44) Power key sense, low = pressed
PTB13 (45) Power latch: set at start-up, cleared to switch off
PTA16 (16) LCD backlight: PWM1 output (IO_DIPLEX0 bit 18 clear) (datasheet)
PTA17 (17) Unused, set high at init
PTB0, PTB1 (32, 33) Status LEDs, high = on: PTB0 green, PTB1 red (measured)
PTB14, PTB15 (46, 47) Audio path selects: audio on = PTB14 high, PTB15 low; off = PTB14 low, PTB15 high
PTA15, PTA24 (15, 24) Speaker enables, low = on: PTA24 the body speaker, PTA15 the mic speaker. Both high when audio is off. IO_DIPLEX1 bits 11 and 12 set (GPIO). PTA15 low also darkens the LCD backlight on the owner's radio, so RadioFoundry keeps it high and drives the body speaker only (measured)
PTC18 (82) Speaker source: high next to every main-chip AF on and side tone (analog squelch open, tone burst), low for the modem's audio (DMR call start, beeps, call end). Taken to select the main chip's AF (high) or the modem's line out (low) for the amplifier. RadioFoundry drives it low in DMR, high otherwise
PTA19 (19) FM broadcast audio enable
ADC 2 Board ID: display bus mode
ADC 6 RSSI (squelch and channel busy)
ADC 0, ADC 7 Neither is shown to be the supply, and no supply-voltage read is known. ADC 7 is a hysteresis input (below 465 / above 527) that sets a GPIO of the LO BK4819; its source is unknown (unverified). At a 13.5 V supply: ADC 0 reads 0, ADC 2 about 0, ADC 6 594 (no signal), ADC 7 742 (measured)

Speaker volume. The BK4819 init (table in 4.3) writes register 0x48 as 0xB3BF and then 0xB3B3 near the end, so the AF DAC gain nibble ends at 3. Neither ADC 0 nor ADC 7 is a volume knob, and the VOL/SQL key may be a squelch key rather than volume (unverified). RadioFoundry gives the radio a volume of its own: 16 steps (RADIO_VOLUME_MAX) on the AF DAC gain nibble (bits 3:0, the public BK4819 meaning), with the other fields of 0x48 left at 0xB3B3's upper bits. Step 0 is mute (register 0x47 to its mute word, and the chip's output off); steps 1..16 give nibble 0..15, the table being nibble = (step - 1) * DB25D_VOL_GAIN_TOP / 15 in radio_db25d.c (DB25D_VOL_GAIN_TOP is 15; lower it if the top steps distort). Nibble 3 is step 4; the default step 8 gives nibble 7. The dB per nibble step, and whether the top steps distort the speaker, are not measured (unverified). The debug link can set a step (radio-features.md 4.2). In DMR the chip's output is the modem's input (4.5): it gets the digital gain word 0xB174 (nibble 4) and the volume step comes back when the radio returns to FM.

The front keys are power (PTB12), P1, P2, P3, VOL/SQL, TONE/T.SEL, B/E and the encoder push; there's no separate Menu/OK key (measured). Their matrix positions, found by pressing each key on the radio, with the key code the vendor firmware uses for the same position:

Drive \ sense PTB9 PTB10 PTB11
PTB18 - (9) - (10) - (11)
PTB20 - (15) - (16) - (17)
PTB21 - (27) P1 (24) P2 (25)
PTB22 P3 (26) VOL/SQL (28) TONE/T.SEL (29)
PTB23 B/E (6) Encoder push (5) - (17)
Direct (every drive line high) - (25) - (24) none

Row PTB18 appears twice in the key scan, so key codes 12-14 can't occur. The hand mic's keys don't use the matrix (3.4).

3.4 Hand microphone

The 16-key hand mic has its own MCU. Its PTT is a plain contact on PTC11 (3.3). Its keys are serial: while a key is held the mic sends three ASCII bytes, "K" and the key number as two digits, on PTA9 at 115200 baud 8N1, every few milliseconds (a frame takes about 300 us). Nothing is sent when the key is let go. PTA9 is also the PC link's RXD, so the host UART receives the frames, and the mic and the PC cable can't be used at the same time (measured).

Key 0-9 * (DWN) # (UP) P4 P5 P6 P7
Number 00-09 10 11 12 13 14 18

Numbers 15-17 weren't seen (measured).

4. RF chain

4.1 Architecture (PRO)

Superheterodyne with a 51.550 MHz IF (manual section 18.2). One BK4819 is the receiver and transmitter ("main"), the other is the local oscillator ("LO"):

State Main BK4819 LO BK4819
RX on f Tuned to 51.550 MHz, RX state Synthesizer at f + 51.550 MHz (high side): frequency in 0x38 / 0x39, state 0x30 = TX enable value so its output stage drives the mixer, level in 0x36
TX on f Tuned to f, TX state Off (0x30 = 0, 0x37 = 0x1D00)

Analog FM is modulated and demodulated by the main BK4819, not by the HR_C7000, which stays out of its FM modem mode.

4.2 Chip identification

The firmware reads register 0 of each chip at boot and writes the letters into its version string:

Register 0 Main transceiver (letter) LO (letter)
0x6818 (FD6818) '6' '6'
0x7813 (unverified) '4' '4'
0x1846 'A' -
0x1100 (AT1141 PLL) - 'T'
0x4819 (BK4819) 'B' 'B'
other soft reset and read again same

4.3 BK4819 registers (address: value, in order)

The register meanings are the BK4819's public ones; the values are what the vendor firmware writes.

Register Meaning
0x00 bit 15 soft reset
0x30 Enables. Main chip (bit 9 set in every state): 0x0200 idle, 0xBFF1 RX, 0xC3FE TX (VCO calibration, PLL / VCO, PA gain, mic ADC, TX DSP), 0xC3FA TX with the mic ADC (bit 2) off for a tone, 0x0302 during init. LO chip (bit 9 never set): 0x0000 idle, 0xC1FE its carrier-only transmitter stage
0x36 PA bias and gain. Both chips: 0xBF (level 15 of 15), written once at detection / init and never changed, in TX too: the main chip drives the PA at 0xBF
0x37 Power-up of the LDOs, bandgap and crystal (0x1D0F normal, 0x1D00 off)
0x38 / 0x39 Frequency, 10 Hz units, low / high 16 bits
0x43 Receive filter bandwidth: 0x3028 and 0x4048 (wide / narrow) (unverified). The channel set writes 0x30 = 0x0200, 0x52 = 0x028F, then 0x43 and 0x4F together: 0x3028 with 0x3332, or 0x4048 with 0x3D3C
0x47 AF output select. The vendor word is 0x6042 | select << 8 | (1 on a digital channel): 0x6042 muted, 0x6142 FM audio on, 0x6342 side tone, 0x6143 AF on in digital. RadioFoundry's FM uses 0x6140 (bit 1 clear, heard working)
0x65 / 0x67 Noise / RSSI indicators (as the FD6818, hardware-dm32.md 8.4)

Main chip init: 00:8000, 00:0000, 33:0003, 34:0000, 35:0000, 37:1D0F, 47:6042, 13:03BE, 12:037B, 11:027B, 10:007A, 14:0019, 49:2A38, 7B:8420, 7D:E950, 48:B3BF, 1F:5454, 3E:A037, 3F:07EE, 2A:4F18, 28:6B00, 53:C074, 40:352A, 29:AB24, 4B:7126, 4E:6F15, 4F:3332, 27:A430, 48:B3B3, 37:1D9F, 30:0302, 47:6042, 30:0200, 33:0000, 30:0200, 47:6042, 30:0200, 37:1D0F, 7D:E34A.

LO chip init: the same up to 37:1D9F, except 4F:2F2E; then 30:0302, 47:6042, 30:0000, 33:0000, 30:0000, 47:6042, 30:0000, 37:1D0F, 51:0000, 40:0000, 73 |= 0x0010 (read, bit 4 set, written back: AFC off), 47:6943, 7E:3006, 43:4048, 47:6042, then 36:00BF: an output level of 15 on a 0-15 scale, written as level x 63 / 15 with bit 7 set.

LO tune to f_LO: 30:0000, 38 / 39 = f_LO / 10 Hz, 37:1D0F, 30:0000, 30:C1FE.

LO off (in TX, and while idle): 30:0000 only. 0x37, 0x36 and the frequency stay.

Analog mode set, main chip (on entering an analog channel): 2A:4F18, 73 bit 4 cleared (AFC on; a digital channel sets it), 47 select 0 (6042), 7E:302E, 2B:C000, 50:3B18. The mode set doesn't restore 0x27, 0x40, 0x43, 0x48 or 0x51 after a digital channel; the channel set rewrites 0x43 / 0x4F, the TX and sub-audio set 0x40 / 0x51, and 0x27 = 0x0430 and 0x48 = 0xB174 stay. RadioFoundry puts back the values it had.

Digital mode set, main chip (on entering a digital channel, 4.5): 2A:4711, 51:0000, 73 bit 4 set (AFC off), 52:028F, 47:6943, 7E:3006, 43:4444, 40:0000, 27:0430, 2B:8707, 48:B174, 47:6143.

4.4 FM transmit

The main chip transmits on f itself (4.1). The RF record carries the PA drive, the modulation pair and the two TX delays per frequency (6.4); only the PA drive and the delays act on the BK4819 path. The modulation pair and the TX center go to the C7000 modem's DMR registers, and the modem is never in FM mode (4.1), so analog deviation on this board comes from the chip's own registers: 0x40 (written before each transmission, 0x34D0 or 0x34A8 with a sub-audio tone), microphone gain 0x7D = 0xE34A, the same for 12.5 and 25 kHz (unverified).

TX on f (the order the radio's firmware uses; RadioFoundry differs where noted):

Step Action
0 Still receiving: main chip 0x40 = 0x34D0 and 0x51 = 0x1050 with no sub-audio; with CTCSS 0x40 = 0x34A8, 0x51 = 0x9045 and 0x07 = Hz x 20.64888 truncated (100.0 Hz = 2064); with DCS 0x40 = 0x34A8, 0x07 = 0x0AD7, 0x51 and the code (4.7)
1 RadioFoundry only: main chip AF muted, then the "audio off" pins: PTB14 low, PTB15 high, PTA24 high (3.3). The first RadioFoundry build raised PTB14 here and the speaker popped at key-up (measured); whether this order removes the pop and still passes the microphone is not yet checked
2 RX front end (PTC19), UHF RX (PTA25) and VHF RX (PTB4) off
3 50 us
4 Main chip: 38 / 39 = f / 10 Hz (truncated), 37:1D0F, 30:0200, 30:C3FE (RadioFoundry writes the idle word before the frequency, not after). Then the LO chip 30:0000 only. 0x36 stays 0xBF
5 The page's TX-on delay for f (1 us UHF, 1000 us VHF on the first unit). There is no other wait. RadioFoundry adds 3 ms (TX_SETTLE_MS) for the VCO calibration: no measurement says it can go, and a PA that comes up before the PLL has settled puts a carrier off frequency
6 DAC B powered up at 0 (RadioFoundry only; the vendor firmware leaves it on), PA select of the band (UHF PTA26 high, PTB3 low; VHF PTB3 high, PTA26 low), PTA23 low, 200 us, the band's single TX supply (UHF PTA27 high, PTB2 low; VHF PTB2 high, PTA27 low)
7 DAC B ramps from 0 to the drive in 180 steps of 4 us

TX end: the main chip to its RX state on the TX frequency (47:6042, 50:3B18, 70:0, 37:1D0F, 30:0200, 30:BFF1), the page's TX-off delay (1700 us UHF, 1600 us VHF), the ramp back down, DAC B 0, both TX supplies and both PA selects low, PTA23 high, 300 us. Then the receive set-up (4.1): the main chip back to the IF, the LO re-enabled (37:1D0F, 30:0000, 30:C1FE, its frequency not rewritten), 300 us, the RX path of the band, 200 us. RadioFoundry then powers DAC B down. 0x36 and 0x40 stay as they are (0x40 only matters in TX, unverified).

The ramp is a 180-entry table in the radio's firmware; RadioFoundry uses a smoothstep over the same 180 steps. DB25D_TX_DRIVE_PCT defaults to 100 (the page's value, the PA measured working); the high and low power levels are table 0x045 and 0x056.

FM TX tone (tx_tone / tx_tone_key: the 1750 Hz burst, roger beep, CW ID, a test tone). Single tone, in TX with the main chip at 0xC3FE:

Step Registers
Tone on 47:6342 (AF select 3, the side tone), 70:B000 (tone 1 on, gain 0x30 in bits 14:8), 50:BB18, 37:1D0F, 30:0200, 30:C3FA (microphone ADC off), 71 = Hz x 10.3244 truncated (1750 Hz = 18067), 50:3B18
Key up (silent carrier) 70:0; the chip stays at 0xC3FA, so the microphone is still out (RadioFoundry's choice, unverified)
Key down 70 as for tone on (RadioFoundry's choice, unverified)
Tone off 47:6042, 50:3B18, 70:0, 37:1D0F, 30:0200, 30:C3FE, 40:34A8
End of the transmission The same without 30:C3FE: it ends in 30:BFF1 (TX end above), so a tone never survives a transmission

0x50 bit 15 is not left set after the sequence; whether that bit mutes the tone as well as the microphone is unverified, so RadioFoundry doesn't keep it.

DTMF (tx_dtmf, radio-features.md 3.7; bk4819_dtmf_start). One digit: as tone on, with 70:9090 (tone 1 and tone 2 on, gain 0x10 each), 71 = the low tone's word and 72 = the high tone's (Hz x 10.32444 rounded, 1633 Hz = 16860) and 40:348A, at 0xC3FA. RadioFoundry retunes 71 / 72 for the next digit without leaving 0xC3FA; tone off is the single tone's (40 back to 34A8). On air (SDR bench): about 1.6 kHz peak deviation at 12.5 kHz and 3.3 kHz at 25 kHz; the tones go through the microphone's pre-emphasis, so the high tone reads 3.6 to 7.0 dB over the low one on the carrier and +0.7 to +2.3 dB after a receiver's de-emphasis (radio-features.md 3.7).

The level is DB25D_TX_TONE_GAIN (radio_db25d.c, default 0x30). Measure the deviation of the 1750 Hz burst on the SDR bench (rfoundry_debug.py burst on, FM channel) and scale the gain; the target is about 60 % of the nominal deviation (1.5 kHz on 12.5 kHz). The first on-air run of the earlier sequence (0xC1FE, 0x50 bit 15 set) gave no modulation; the corrections above are not yet measured on air.

4.5 DMR: the main chip and the modem

RadioFoundry's DMR (CONFIG_DMR=y, on by default; boards/db25d/dmr_db25d.c) receives and transmits (DB25D_DMR_TX, on by default, below). The HR_C7000 runs the DMR layer 2 as on the DM-32UV (modem-hrc7000.md). This section is the main chip's digital set-up and what RadioFoundry does.

Channel type. Byte 6 of the channel record: 2 is digital (the BK4819 digital set below, no bandwidth, sub-audio or squelch, the modem's WORK_MODE bit 7 cleared, the DMR microphone gain from the settings), 1 is analog (bandwidth, sub-audio, squelch) (names unverified).

Sequence for a digital channel (the receive set-up of 6.4, in order):

Step Action
1 DAC B 0, both TX supplies low, RX path pins (3.2)
2 Modem SIG_CENTER and RF_MOD_BIAS_CTRL from the page (4.6), DAC A = RX tune, DAC C = 1780
3 Main chip: 30:0200, 52:028F, then the digital mode set (only when the previous channel was analog): 2A:4711, 51:0000, 73 bit 4 set (AFC off), 52:028F, 47:6943, 7E:3006, 43:4444, 40:0000, 27:0430, 2B:8707, 48:B174, 47:6143
4 Main chip RX tune as for FM: 38 / 39 = 51.55 MHz, 37:1D0F, 30:0200, 30:BFF1
5 LO tune to f + 51.55 MHz (4.3), RX path of the band, main chip GPIO 0x33 band bit (VHF only)

Nothing on the LO chip changes for digital. No switch pin moves for receive; the only audio pins are the speaker ones below.

Modem. The DMR initialization runs once, at start-up, in every mode. The vendor firmware's values and RadioFoundry's:

Register Vendor firmware RadioFoundry
RF_MODE (0x104) Built by read-modify-write from the reset value 0x10E0: b7 cleared, b25 and b24 set (AF receive with averaging), AF gain 0x4C (4.75) in b23:16, TX mode 3, RX mode 0, b15 = a flag the RF record build sets (the LO is high side; the start-up builds a record before this, so b15 = 1), b11 set (dual receive; cleared only for a channel value of 2 or 3, unverified), b31 / b29 only in a test mode. Result 0x034C9860 Same word (DB25D_DMR_RF_MODE). The DM-32UV's is 0x03269860: only the gain differs
RF_CONTROL (0x110) Reset 0x00040A0A ORed with 0x1512: 0x00041F1A Same
DAC_CONTROL (0x070) Reset ORed with b31, b5 cleared: 0x80000016 0x8000001B (b2 and the reserved b3 / b0 differ)
ADC_CONTROL, 0x168, RF_RECV_DELAY, RX / TX_IF_FREQ, THRESHOLD, sync words, CRC masks, LAYER2_SLOT_PRE, interrupt mask As modem-hrc7000.md 4 (0x41C3 ORed, 0x14, 0x36B0, 0xBB800, 0x0978786F, ...) Same
Sync enables (b31 of the sync words) MS and BS; TDMA 1 / 2 only for the direct-mode channel value All four
DEV_LIMITER Never written Its reset value

No other AF level, DC offset or deviation register is written for receive.

The modem's input. The main chip's AF output (0x47 select 1, AF DAC gain 0x48) goes to the modem's AF ADC (the only AF left on in digital; nothing mutes 0x47 on a digital channel, unverified). AF level: 0x48 = 0xB174 (nibble 4, RX gain field 0x17), the AF filters 0x2B = 0x8707.

Speaker. Decoded voice leaves the modem's codec line out. The call start drives PTC18 low and runs the audio-on pins (PTB14 high, PTB15 low, speaker enables, 3.3); the call end runs audio off. The CPU-fed speaker buffer is not used; beeps go through the prompt path (AUDIO_CONTROL b7). PTC18 is high for the main chip's audio (3.3).

RadioFoundry (bk4819_dmr_rx_enter, db25d_dmr_enter, tests/host/bk4819Test.c):

Item RadioFoundry
Main chip The digital set (4.3), in its order, on entering DMR. Leaving: the analog set, then 0x27 and 0x48 as they were
AF gain DB25D_DMR_AF_GAIN (default 4) over the 0xB174 word, whatever the volume step. A volume of 0 entering DMR left the modem with no input before this
AF output 0x47 = 0x6143 for as long as the radio is in DMR
RF_MODE DB25D_DMR_RF_MODE (default 0x034C9860), written after the DMR entry
PTC18 Low in DMR, high in FM; DB25D_DMR_SPK_SWITCH=1 keeps it high
Speaker in DMR The PCM stream (db25d_speaker_pcm, from the modem's playback callback) turns the amplifier on and PTC18 low, as audio() does for an open squelch. The radio core never opens the squelch path for DMR voice, so before this every call played into a muted amplifier; beeps went silent the same way
Voice-access slot HRC7000_RX_NEXT_SLOT_XOR=0 (the driver's default since 2026-10-22; it was 1): the flag (modem-hrc7000.md 5.3) belongs on the slot LAYER2_STATUS b30 shows at this board's RX timeslot interrupt. With 1 the chip never wrote received voice to its voice RAM: every frame was the same stale bytes, decoded to silence (peak 6 of 32767) with every counter clean. With 0, bench speech decodes with peaks near 16,000 (measured, 2026-10-17)

Radio results before the digital set (bench MS voice, 461.4625 MHz, CC 2): RF_MODE 0x03269860 gave no sync; 0x03261860 (b15 clear) gave sync, the right color code and slot, but no usable burst; a larger AF gain held sync on noise. The PHASE_OUT range read exactly 32 times the RF_MODE gain field (saturated), so on this board it doesn't measure the AF level (measured).

With the set and word above (2026-10-16, same bench call, 191 bursts): 180 voice bursts, 8 LC headers, 3 terminators, CC 2, no CRC errors, the call decoded (source, talkgroup 201, group), and 540 of 540 AMBE frames with none lost or concealed (measured). With the BK4819 digital set, b15 set is right; b15 clear (0x034C1860) syncs but deframes no usable burst.

The diagnostics page (CONFIG_DIAG) lists the modem counters after the TX line 11 and the storage lines 12-15 (from line 12 when CONFIG_NAND_STORAGE is off): ticks, slot interrupts, bursts by type, CRC errors, and last the codec in the slot with the AF phase range (HR_C7000 PHASE_OUT, 0x128).

DMR transmit. On by default (DB25D_DMR_TX=1); DB25D_DMR_TX=0 builds receive only, and then tx() refuses in DMR mode. Checked on the SDR bench 2026-10-16 (measured): a 3 s key-up on 461.4625 MHz, low power into a dummy load, decoded by the bench modem as one call from the radio's ID to TG 201 (3 LC headers, 54 voice bursts, terminator, 162 AMBE frames with 12 FEC bits corrected); the diagnostics line showed 57 bursts keyed and ended, each ended by the RF RX interrupt, none late. Analog FM transmit is 4.4. How a burst is keyed:

Item Sequence
4FSK path Single-point modulation on the reference oscillator. The modem runs in two-point mode (RF_MODE b6:5 = 3) but only its MOD1 output is used: RF_MOD_BIAS_CTRL b15:8 (MOD2 amplitude) is 0, b7:0 (MOD1 amplitude) is the page's modulation byte for the TX frequency (6.4), and MOD1 drives the control pin of the TCXO both BK4819s run from (the DC level of 4.6). The HR_C7000 user guide describes this case (single-point modulation with an RF chip that has no modulation input). The deviation therefore scales with the carrier frequency, which is why the page holds the modulation byte per frequency point (datasheet)
RF_MODE Built bit by bit at DMR set-up: bit 7 (TX symbol mapping inverted) cleared, bits 25 and 24 set (AF receive with averaging), AF gain field 0x4C, TX mode 3, RX mode 0, bit 15 (RX IQ inverted) set when the LO is on the high side (always here), bit 11 (dual receive) set except for two values of a setting. With the reset value 0x10E0 that gives 0x034C9860. The DM-32UV's 0x03269860 has bit 7 set and AF gain 0x26 (datasheet)
RF_CONTROL The reset value (0x00040A0A) OR-ed with 0x1500 and 0x12: 0x00041F1A, the DM-32UV's value (RF TX interrupt 3.1 ms before the burst, RF RX interrupt 2.6 ms before the next slot). With one value of a setting byte, 0x00041A1A instead (datasheet)
Main chip, digital channel The channel set (0x30 = 0x0200, 0x52 = 0x028F) calls the mode routine with "digital" when the channel's type is 2: 0x2A = 0x4711, 0x51 = 0, 0x73 bit 4 set (AFC off), 0x52 = 0x028F, 0x47 = 0x6943, 0x7E = 0x3006, 0x43 = 0x4444, 0x40 = 0x0000 (TX deviation off), 0x27 = 0x0430, 0x2B = 0x8707, 0x48 = 0xB174, 0x47 = 0x6143 (AF on, bit 0 = digital). Only on a change of mode. The receive table above is the same set
Main chip in TX 0x30 = 0xC3FE (microphone ADC on), 0x36 = 0xBF as always. With 0x40 = 0 the microphone doesn't reach the carrier: the BK4819 sends a plain carrier that the TCXO modulates
Per-burst switch Every burst runs the whole analog TX sequence. The RF TX and RF RX interrupt handlers only post to a task ("rfint", priority 15 of the RTOS) and record which one fired. On a digital channel the task runs TX on (3.2: RX paths off, 50 us, main chip to f, LO 0x30 = 0, the page's TX-on delay, PA select, 200 us, TX supply, DAC B ramp up) at the RF TX interrupt, and TX off (the page's TX-off delay, ramp down, supplies and PA selects off, PTA23 high, 300 us, main chip to the IF and the LO on again, 300 us, the RX path, 200 us) at the RF RX interrupt. So the radio receives between its bursts, on the RX frequency
Burst timing TX on starts 3.1 ms before the slot; with the UHF page values (1 us TX-on delay) the PA is fully up about 1.1 ms later. TX off starts at the RF RX interrupt, 30.5 ms after the RF TX interrupt; with the page's 1700 us TX-off delay (UHF) the ramp down starts 32.2 ms after the RF TX interrupt and ends about 0.2 ms before the next slot. Waits of 50 us and more are timer waits that let other tasks run; shorter ones spin
Retune per burst Main chip: 0x38 / 0x39 = f, 0x37 = 0x1D0F, 0x30 = 0x0200, 0x30 = 0xC3FE; back: 0x38 / 0x39 = 51.55 MHz, 0x37, 0x30 = 0x0200, 0x30 = 0xBFF1. LO: 0x30 = 0 at TX on; 0x37 = 0x1D0F, 0x30 = 0, 0x30 = 0xC1FE at TX off, its frequency not rewritten
PA keying DAC B ramp (180 steps of 4 us) and the band's single TX supply pin inside the burst sequence, from the page's PA table for the power level. No PA timer: the timer the DM-32UV uses for its PA one-shot isn't used for that here
Modem bias SIG_CENTER and RF_MOD_BIAS_CTRL as in 4.6 and 6.4, written at every receive set-up from the channel's RF record, which holds the modulation byte for the TX frequency
Mic and vocoder The microphone reaches the modem's codec ADC with no pin to switch: PTB14 / PTB15 appear only in the audio on / off routines (3.3), never in a TX path. Mic gain (GCR_ADCL) on a digital channel comes from a setting (0 dB when unset); an analog channel sets +12 dB. WORK_MODE bit 7 cleared (DMR modulator)
Analog PTT routine Not used for a digital channel: the RF follows the bursts only (unverified)

RadioFoundry (boards/db25d/dmr_db25d.c, radio_db25d.c; tests in tests/host/bk4819Test.c and boards/db25d/tests/db25dCalTest.c):

Step RadioFoundry
tx() in DMR mode Checks band and page as FM does, keeps the TX plan (PA drive, TX-on and TX-off delays), writes RF_MOD_BIAS_CTRL with the modulation byte for the TX frequency, main chip 0x40 = 0 and 0x51 = 0 (bk4819_dmr_tx_prepare), powers DAC B at 0, clears RF_MODE bit 7 (make DB25D_DMR_TX_INVERT=1 sets it, to try the other 4FSK polarity) and arms. The RF stays receiving until the first burst
RF TX interrupt The glue's rf_switch(true) records the time and wakes the RF task (priority above every other task), which runs the TX on sequence (db25d_burst_on)
End of a burst Woken by the modem's RF RX interrupt (enabled while armed) or by the engine's rf_switch(false) at the first slot tick 20 ms after the RF TX interrupt, whichever comes first. The task then waits until 30.5 ms + the page's TX-off delay after the RF TX interrupt (db25d_dmr_hold_us) and runs the TX off sequence (db25d_burst_off). A wake-up after that point ramps down at once and counts as late
Waits Below 50 us a spin; from 50 us TIMER_BOARD (timer 5) while the task sleeps
tx_off(), rx(), leaving DMR Disarm: TX off at once (no hold) if a burst is on, wait for the task, DAC B powered down, then the normal receive set-up
PA one-shot The glue still arms it per burst; the board's hook does nothing
Diagnostics The line after the codec line: b<on>/<off> rx<RF RX interrupts> <R/T/A> L<late>/<last late us>. R, T or A says what woke the last TX off. rx that stays at 0 while transmitting means the RF RX interrupt doesn't fire in TX, and the slot tick ends the bursts; L counts the ramp downs that started after that point

Answered by the bench check above: 0x40 = 0 with the rest of the analog set gives a carrier the modem's 4FSK decodes, the polarity with RF_MODE bit 7 clear is right, and the RF RX interrupt ends every TX burst. Deviation with the page's modulation byte (2026-10-17, 461.4625 MHz, low power, SDR bench, instantaneous frequency inside the bursts): the four symbol levels at -1970, -653, +676 and +1970 Hz around the carrier, so the outer deviation is 101 % of the standard's 1944 Hz and the outer / inner ratio 2.97 (3.00 ideal); carrier 187 Hz high (0.4 ppm) (measured).

The receive and transmit checks are at the end of section 9.

4.6 Frequency reference: the modem's bias

On the owner's radio both receive and transmit read 23 ppm low (10.6 kHz at 461.4625 MHz) and DAC C changes nothing. The C7000 modem's DC bias registers are written at every receive set-up, in FM as well as DMR: SIG_CENTER 0x11000108 and RF_MOD_BIAS_CTRL 0x11000114 as in 6.4. The BK4819s have no VCO input, so the modem's two-point modulation puts its low-frequency half on the TCXO's control pin and this DC level sets the TCXO frequency; both chips share that TCXO, so receive and transmit move together (unverified; matches the measurement). The factory tunes the TX center byte with the radio transmitting, and nothing else on the page trims the frequency. RadioFoundry powers the modem (hrc7000_power_on_init) at init in every build and writes both words at init and at every rx().

Page TX center c (0x43F) m 0x11000108 0x11000114 (mod byte 0x21 at 461.4625 MHz)
106 (owner's radio) 489 (0x1E9) 0x6A6AE9E9 0x01E90021

make BOARD=db25d DB25D_TX_CENTER=N replaces c, to sweep it on the radio: a few ppm per step is expected.

4.7 DCS

The main chip sends and detects DCS itself, with the code as the 23-bit Golay word (code bits first, subaudio_dcs_word) in two writes to 0x08:

Register Value
0x07 0x0AD7 (the 134.4 bit/s rate) while the code is set; 0x4AD7 after it on receive
0x08 word bits 11:0, then bits 22:12 with bit 15 set (D023: 0x0813, 0x8763; D754: 0x09EC, 0x820F)
0x51 0x8030 normal, 0xA030 inverted (bit 15 on, bit 13 invert, bit 12 = 0 selects DCS, the rest the gain)
0x40 0x34A8 on transmit, as with CTCSS
0x0C received: bit 14 the code as set, bit 15 its inverse (bit 10 CTCSS)
Order Steps
TX (4.4 step 0) 0x40, 0x07 = 0x0AD7, 0x51, 0x08 twice
RX 0x07 = 0x0AD7, 0x51, 0x08 twice, 0x07 = 0x4AD7

The receive chain inverts the audio: the LO sits above the signal (f + 51.55 MHz, 4.1), so the IF spectrum is turned over and the deviation's sign flips. A normal code arrives as its inverse. RadioFoundry sets 0x51 for the code as sent and reads 0x0C's other bit: a normal code sets bit 15, an inverted one bit 14 (bk4819_t.rx_inverted). On the SDR bench another radio's D023N and D023I opened the matching setting only, and D754 and CTCSS settings stayed shut; the radio's D023N went out as D023N (measured). The diagnostics page's rssi line shows 0x0C as c (hex), polled with the RSSI.

At the end of a DCS transmission 0x52 = 0x828F is written, then, after 300 ms, 0x028F. 0x52 bit 15 is the sub-audio tail generator (as on the FD6818, datasheet): the chip sends 134.4 Hz in place of the code, the DCS turn-off. RadioFoundry does the same through the core's tail (radio-features.md 3.3a; bk4819_tx_tail_start / _end; the end of the transmission writes 0x028F back if a tail is still on). On the SDR bench: D023N, then 134.4 Hz for 0.30 s up to the carrier's end (measured).

5. Display

Item Finding
Panel 1.77" color TFT, ST7735S-class controller, 160 x 128 landscape (MADCTL 0x60)
Reset high 10 ms, low 10 ms, high 130 ms
Init ST7735 frame-rate and power commands as RadioFoundry's st7735 type 1 except VCOM (C5 = 0x12) and the first positive gamma pair (E0: 04 22 ...)
Backlight PWM1 (0x140C0020) on PTA16 at 1 kHz: PER = 24,000, FP = on-time. Which clock the PWM counts is not established: the user guide says only "system clock" and a 54 MHz maximum, so 24,000 is 1 kHz from a 24 MHz clock and 1.5 kHz from the loader's APB; RadioFoundry keeps PER = 24,000, so the rate is 1 kHz to 3.5 kHz whichever it is. Levels 0-6 give 10, 25, 40, 55, 70, 85, 100 % duty; 0 only when switched off. Set-up: CFG bits 5, 8, 9, RPT 5, PER, FP, then CFG bit 2 (load) and bit 0 (enable). The light also needs PTA15 high (3.3) (measured)

6. Storage and calibration

Item Finding
NAND Kioxia TC58CVG1S3HRAIJ, 2 Gbit SPI NAND: 2 KB pages, 128 KB blocks. Standard SPI NAND opcodes (0x9F ID, 0x13 page read, 0x06, 0x84, 0x0F / 0xC0 status, 0xD8 erase). The vendor firmware builds a bad-block table at boot (6.5) (datasheet)
Calibration A 2 KB page at NAND 0x280000 (block 20) starting with "TUNE", and a copy in program flash at 0x031FE000 (4 KB sector). The vendor firmware repairs one copy from the other at boot
Calibration layout 6.1 – 6.4
Vendor firmware NAND areas 0x40000, 0x110000, 0x3C0000, 0x1100000, 0x2980000 (codeplug and databases, unverified)

Rule: RadioFoundry reads both calibration copies and never writes them. It never writes program flash at 0x031FE000 or NAND block 20.

6.1 Page header and the two copies

Offset Size Field
0x000 4 Magic "TUNE"
0x004 4 Save counter, u32 little-endian. The vendor tuning mode adds 1 on every save

At boot the vendor firmware reads 8 bytes of each copy. If both carry "TUNE", the copy with the higher counter wins and is copied over the other. If only one does, it's copied over the other. If neither does, the radio runs untuned ("No tune"). There's no checksum. RadioFoundry applies the same choice when it reads, without writing anything back.

6.2 Bands and frequency points

The page has two 1 KB halves with the same layout: 0x000–0x3FF for UHF and 0x400–0x7FF for VHF. The half is picked from the band of the frequency in use. Offsets below are UHF; add 0x400 for VHF. The few fields that exist only once are marked.

Most tables hold one value per frequency point, 8 points per band:

Point 1 2 3 4 5 6 7 8
UHF (MHz) 403.175 414.175 425.175 436.625 447.775 458.775 469.825 478.825
VHF (MHz) 136.125 142.125 146.125 152.125 156.125 162.125 166.125 173.125

A per-point table is one leading byte, then 8 entries in point order, so entry p (1–8) of size n starts at table + 1 + n·(p − 1). The leading byte isn't used by any per-point lookup.

Interpolation: for f at or below point 1, use point 1. Otherwise take the first point p ≥ 2 at or above f and interpolate linearly between points p − 1 and p, rounding half up. Above point 8 (478.825–480 MHz, 173.125–174 MHz), the vendor firmware reads past the table into the next one. RadioFoundry uses point 8 there instead.

The squelch table works by segment: 8 segments per band. VHF: below 142, 142–146, 146–152, 152–156, 156–162, 162–166, 166–173, from 173 MHz. UHF: below 410, then 10 MHz steps (410–420 … 460–470), from 470 MHz. A segment's upper edge belongs to the next segment.

6.3 Fields

Offset (UHF) Size Content Used for
0x02C 1 + 8 × u16 RX front-end tuning, per point DAC A, written when the receiver is set up for a frequency. The tuning voltage of the receive band-pass filter (unverified)
0x43E (VHF half only) 7 Global bytes, both bands. Byte 0x43F is the TX center (128 = mid-scale, as on the DM-32); the other six aren't traced Modem SIG_CENTER and RF_MOD_BIAS_CTRL, 6.4
0x045 1 + 8 × u16 PA drive, power level 1 (H, unverified), per point. DAC B value: the APC ramp's end value TX power
0x056 1 + 8 × u16 PA drive, every other power level (L, unverified), per point TX power
0x067 1 + 8 × (u8, u8) + 1 Modulation, per point. The first byte of each pair goes to the modem (6.4). The second is read but then cleared before use TX modulation level
0x09B 1 + 8 × u8 A per-point value capped at 15, written into the RF chip driver's state (use not traced). The leading byte is read on its own and kept with the channel's RF parameters ?
0x0AE 1 + 8 × (u16, u16) TX timing in µs, per point: the delay between tuning the transmitter and selecting the PA, and the delay at TX end before the ramp down. A value above 2000 counts as 0 TX switching
0x0CF 1 + 8 × 9 × u16 Squelch, per segment: one value for each squelch level 1–9 (level 0 doesn't use the table) RF chip squelch, 6.4
0x160, 0x1B1 1 + 8 × 5 × u16 each Five values for each of 8 ranges: up to point 2, then between points (2–3 … 7–8), then above point 8. No interpolation. Two variants chosen by channel mode (use not traced) ?
0x202 / 0x206 / 0x207 / 0x208 / 0x20B 4 / 1 / 1 / 3 / ? Small items the tuning mode reads and writes. 0x20B is in the UHF half only (use not traced) ?

The tuning mode moves each table as a unit (the sizes above), plus 198 bytes from the start of each half. Everything not listed is unused.

6.4 What the values drive

Output Value
PA drive (DAC B, 12 bit) The interpolated 0x045 or 0x056 value for the TX frequency, chosen by power level. The APC ramp runs up to it at TX start and down from it at TX end, as on the DM-32
RX front end (DAC A) The interpolated 0x02C value for the RX frequency
DAC C 1780 when receive is set up, not from the page. It has no effect on the frequency (measured); the TCXO trim is the modem bias below (4.6) (measured)
SIG_CENTER (modem 0x108) Written at every receive set-up, FM and DMR (4.6). (c << 24) or (c << 16) or ((m & 0xFF) << 8) or (m & 0xFF), with c = the TX center byte and m below
RF_MOD_BIAS_CTRL (modem 0x114) ((m & 0x3FF) << 16) or a. m = round((1.228 + c × 0.422 / 128) × 1023 / 3.3): 381 at c = 0, 512 at c = 128, close to the DM-32's c + 384. a = the interpolated first byte of 0x067
BK4819 squelch (reg 0x78) For level n (1–9), v = low byte of entry n of the segment's 0x0CF table: v is the open threshold in dB below 0 dBm at the main chip. Register = (o << 8) or (o − 4), o = 2 × (160 − v), i.e. open at −v dBm and close 2 dB lower. Level 0 uses v = 136
Untuned radio No page: the vendor firmware keeps running, values unknown

RadioFoundry's use: the reader (boards/db25d/calibration_db25d.c) copies the chosen page into RAM at boot and never writes either copy. The program-flash copy is memory-mapped, so it's read directly: it is a plain byte copy from 0x031FE000. The NAND copy is read through the SPI NAND driver (6.5): its 8-byte header first, the whole page only when it wins, and a NAND page that can't be read gives way to the flash copy. Interpolation is the shared calibration curve (core/radio/cal_curve.h). Receive sets DAC A and DAC C from it now. TX power comes from 0x045 ("H") or 0x056 ("L"), and TX refuses when the page is missing or the PA value for the frequency is 0 or above the DAC's range. On the first unit's page, table 0x045 is the higher DAC value on both bands (UHF 1430-2001 against 999-1193, VHF 916-1512 against 590-928), so it is the high level; that a higher DAC B value is more output depends on the APC loop and isn't measured (unverified). The RSSI that RadioFoundry shows stays the chip's own reading: the page has no RSSI offset, only squelch thresholds (unverified).

6.5 NAND access

How the NAND is driven (RadioFoundry's read side follows this) and what the data sheet adds:

Step Sequence
Bus SPI0, IO_DIPLEX1 bits 3–8 cleared (SCLK / MOSI / MISO to the SPI master), CS PTA18 by GPIO, CTRLR0 7, BAUDR 4
ID 0x9F, one dummy byte, then 0x98 (Kioxia), 0xEB (device), 0x40 (organization: 2 KB pages, 128 KB blocks) (datasheet)
Organization (2048 + 64) bytes × 64 pages × 2048 blocks. At least 2008 good blocks over the chip's life; blocks 0–7 are good when shipped (datasheet)
Page read 0x13 and a 3-byte page number (17 bits), then 0x0F 0xC0 (status) until bit 0 (busy) clears: 180 µs at most. The vendor firmware remembers the last page read and doesn't read it again
Read from cache 0x03, a 2-byte column (12 bits; the vendor firmware masks 13), one dummy byte, then data. Byte address = page × 2048 + column (datasheet)
Status (0xC0) Bit 0 busy, 1 write enabled, 2 erase failed, 3 program failed. Bits 4–5 ECC: 0 clean, 1 corrected, 2 uncorrectable, 3 corrected at or above the bit-flip threshold (feature 0x10) (datasheet)
ECC On by default (feature 0xB0 bit 4): 8 bits corrected per 512 + 16 bytes. Spare bytes 2048–2111 are readable, the parity at 2112–2175 isn't (datasheet)
Block lock Every block locked at power-up (feature 0xA0); reads don't need it cleared, program and erase do (datasheet)
ID pages Feature 0xB0 bit 6 (IDR_E) set, then a page read: page 0 is the unique ID, page 1 the parameter page (data sheet 4.12, 4.14); clear the bit after. The vendor firmware reads 256 bytes of page 0 at boot (the unique ID) (datasheet)
Unique ID Page 0 with IDR_E: 16 copies of 32 bytes, each the 16-byte ID followed by its complement. RadioFoundry reads it at boot (spi_nand_read_uid, the first copy whose complement matches) as the radio's UID for the debug link's INFO_CHIP (debuglink.md 3.9) (datasheet)
Spare bytes The vendor firmware keeps its own block marks at spare offset 4 (column 2052)
Bad blocks Factory-bad blocks read 0x00 in every page; the mark is judged from the data whatever the ECC status says (datasheet)
Vendor bad-block skip The vendor firmware builds a bad-block list at boot and, for some address ranges, skips bad blocks when it maps an address to a page. Which ranges, and whether that ever moves block 20, is unverified

The write side, from the data sheet:

Step Sequence
Unlock 0x1F 0xA0 0x00 before a program or erase; 0x1F 0xA0 0x38 (every block locked, the power-up value) after it (datasheet)
Program 0x06 (write enable), 0x02 and a 2-byte column with data (clears the rest of the buffer to 0xFF), 0x10 and the 3-byte page number, then 0x0F 0xC0 until not busy: 500 µs at most. Pages of a block are programmed in order (datasheet)
Internal data move 0x13 of the source page, then 0x06, 0x84 and a column with data (keeps the rest of the buffer), 0x10 to the new page. Needs feature 0xB0 bit 1 (HSE) clear. The page's ECC parity is computed again (datasheet)
Erase 0x06, 0xD8 and the 3-byte page number of the block, then status until not busy: 7 ms at most (datasheet)
Partial programs The chip allows 4 per page, but with ECC on each 512 + 16 byte unit takes one; RadioFoundry programs every page once (datasheet)
While busy Only 0x0F (status) and the reset commands may be sent (datasheet)
Vendor configuration The vendor firmware writes feature 0xB0 = 0x14

RadioFoundry's driver is drivers/flash/spi_nand (generic). It takes the geometry from the board and treats a block whose first page's first spare byte isn't 0xFF as bad. The DB25-D reads the calibration copy at its physical address: if a skip ever moved it, its header won't say "TUNE" and the program-flash copy is used. With CONFIG_NAND_STORAGE the driver also programs and erases: at probe it writes feature 0xB0 = 0x10 (ECC on, HSE off for the internal data move) and 0xA0 = 0x38, unlocks only around each operation, and refuses any program or erase outside RadioFoundry's range (6.6). One lock serializes every NAND user (calibration read, storage).

6.6 RadioFoundry storage on the NAND

RadioFoundry keeps its own data in physical blocks 1920-1951 (32 blocks, 4 MB), through drivers/flash/nand_sectors (nand-sectors.md), which turns the region into 4 KB sectors that behave like NOR flash. Why these blocks are safe from the vendor firmware and the calibration:

Area Where
Calibration Block 20
Vendor data (codeplug, contacts, resources) Logical addresses up to about 0x5000000: block 640, a few blocks higher after the bad-block skip
Vendor scratch blocks The top 10 good blocks of the chip: 2038-2047 on a chip with no bad blocks, never below about 1998 with the most bad blocks the data sheet allows (40) (datasheet)
RadioFoundry 1920-1951: far above the vendor data, below the lowest place the scratch blocks can reach (unverified)

The vendor firmware's use of this range is unverified: only its logical data area and its scratch blocks are known. What makes the range safe is the layer's foreign-data guard (nand-sectors.md 4): a block whose first or last page reads back clean, isn't erased and carries no RadioFoundry record makes the mount fail. Then nothing in the range is programmed or erased, the radio runs without stored settings, and the diagnostics page names the block. Clearing it is the owner's call. RadioFoundry never programs or erases outside the range, and never touches the factory bad blocks in it. The layer uses spare bytes 8-23 of each page and leaves 0-7 alone (the factory mark and the vendor marks at 4-5). A block the vendor firmware marked bad at bytes 4-5 counts as foreign.

Logical map (the core's storage_map_t, settings-store.md): 608 sectors, 2.375 MB.

Item Logical address Size
Open-firmware codeplug, EEPROM part 0x000000 64 KB
Settings ring 0x011000 32 KB
Sector journal 0x019000 4 KB
Open-firmware codeplug, flash part 0x020000 1 MB
Native codeplug 0x120000 1 MB
Staging 0x220000 256 KB
Calibration None: read from block 20 and the program flash (6.1)

Codeplug writes (CONFIG_CPS_OPENFW, section 9): the CPS shim commits one 4 KB sector at a time with hal_flash_t.replace (nand_sectors_replace): one record of two pages, atomic, with no journal and no erase first. The core's journaled rewrite (core/storage, settings-store.md 4) written for NOR would take about 11 page programs per sector here. The host test (tests/host/nandSectorsTest.c) counts them:

Write Page programs Block erases (32-block region)
One sector, replace 2
One sector, journaled erase and program 10.9
What qdmr transfers (33 sectors: EEPROM 0x80-0x6060 and 0x7500-0xB000, custom data, codeplug 0x9B000-0xAEE60) 66 about 1
The whole open-firmware image (the 64 KB EEPROM part and the 1 MB flash part, 271 sectors) 542 8 to 10, with garbage collection

A sector whose content doesn't change programs nothing. The calibration can't be reached: the layer works only inside blocks 1920-1951, so neither NAND block 20 nor the program flash copy is ever written, and the CPS window in the image (flash image 0x10000, wire 0x30000) is plain stored data that nothing reads. Speed on the radio is not measured; the UART (115200 baud, 32-byte blocks) is the limit, not the NAND: a qdmr write is about 20 s.

The region holds the device twice over (each sector can need two copies while garbage collection runs) plus the reserve. RAM: 2 bytes per sector and 6 per block, about 1.5 KB in all, no page buffer. The mount reads every programmed page's spare area and the data of every current record, so it takes longer as more data is stored.

The diagnostics page (CONFIG_DIAG) shows the storage on lines 12-15, after the TX line 11 and before any DMR lines:

Line Shows
nst u<used> f<free> bad<bad> Blocks holding data, free, bad. nst foreign <block> n<count>: the physical block holding data RadioFoundry didn't write, and how many such blocks; nothing was touched and the radio runs without stored settings. nst none: no storage for another reason (line 13 says which)
nst w<writes> e<erases> gc<blocks> x<verify fails> Records written, block erases, blocks emptied by garbage collection, pages that read back wrong
nst st<status> Without storage, line 13 shows this instead: the mount status (0 never mounted, no NAND; 2 bad set-up; 3 foreign data)
nst t<torn> r<retired> #<seq> Torn pages the mount set aside, blocks retired this session, the next sequence number
set <state> #<seq> s<saves> f<failures> Where the settings came from (ram, dflt, load, upgr, frgn), the settings record's sequence, saves and failed saves this session
Item Finding
Connectors RJ-45 mic: 1 power off, 2 PTT / radio RXD, 3 mic, 4 shield, 5 radio TXD, 6 ground, 7 +8 V, 8 speaker. 3.3 V logic. The side K1 jack carries the same serial pair
Cable USB-serial (Prolific PL2303 originally; FTDI accepted from firmware 909E.D4.EARSAB.018)
Vendor PC protocol UART1, 115200. Identifies as "+DRS-300UV" / "+136-480MHZ+". 2 KB packets with an additive checksum
IAP loader entry Radio off, cable in, hold P1 and press power: red LED, blank display with backlight. The vendor "IAP" tool sends the raw .bin; the radio turns off when done. The loader also stays in update mode when the application lacks the signature (2) (measured)
IAP protocol UART1, 115200. The loader prints its NAND state and "IAPReady". Host: Erase (Erase + 6 spaces) + u32 BE address 0 + u32 BE N = length / 2048; the loader answers its version string, then "Erase ok" about 1.5 s later. Then N + 1 raw 2048-byte pages, the last padded with 0xFF, each answered "kyd". About 1.4 s after the last page: ChecksumI + u32 BE additive sum of every byte sent, and the loader starts the application at once. It erases only as far as the image reaches (measured)
Stuck in update mode White (blank, backlit) screen and a solid red LED at every power-on without P1 held: the application at 0x0300C000 no longer starts with 2C 11 01 C0 (RadioFoundry's first stage was overwritten, or a write was interrupted), so the loader stays in update mode. Nothing answers the debug link, USMART or the first stage's window. The loader still answers the IAP protocol: Flash Read + address 0 + count 1 gives its version string ("Read_2M_V1.08B..."), a page, ChecksumR and "IAPReady", and changes nothing. The web flasher uses that as a read-only probe (never Erase). Fix: write the install image (rfoundry_db25d_install.bin, stage and application, starts 2C 11 01 C0) through the IAP route; the checksum matches and the radio starts RadioFoundry. No P1 is needed in this state; holding P1 at power-on also reaches it (measured)
Loader reads Flash Read + u32 BE address + u32 BE page count, then one Read per 2048-byte page and one more for ChecksumR + u32 BE: reads the NAND, not the program flash (measured)
Update loader family Not the serial update protocol keyed by a model string that other HR_C7000 radios use (tools/serial_loader.py --probe checks for it without writing; hardware-dm32.md 13.2); the loader speaks the IAP protocol above instead (measured)
Loader rewrite The vendor application accepts writes to program flash at 0x03000000 / 0x03001000 (the vendor ".flash" loader update). A bad transfer there can erase the loader
JTAG PTA0-PTA4 are the SoC's JTAG pins; the pads are unverified
ROM serial boot The HR_C7000 boot ROM offers a download on UART0 at reset. Where UART0 goes on this board is unverified
Open-firmware CPS The same UART1 link speaks the open-firmware CPS protocol (open-firmware-cps.md, core/cps) next to the debug link; the host link sorts them by the first byte (docs/dev/cps-and-codeplug.md). Section 9 (measured)
Web flasher site/static/js/flasher/usmart.js, iap.js and iapui.js load the universal stub through the vendor firmware's USMART console (Stub from the vendor firmware, below), then back up and install through the stub as on a radio that runs RadioFoundry. The IAP loader above is the recovery route: a reinstall, or a backup put back, when nothing answers on the cable. The vendor firmware has no boot ROM window (that exists only once RadioFoundry's first stage is installed) (measured)
Web flasher profile boards/db25d/board.toml: update family serial-iap (the IAP loader above), stock_console = "usmart" for the vendor builds it knows (018 and 020), a 2 MB program flash with no SPI data flash (the NAND is not read), the first stage's size and signature from board.mk (BOOT_SIZE, BOOT_SIGNATURE). The first stage is the only way into the stub from a radio that runs RadioFoundry without answering. In the IAP recovery route the page installs the first stage, RadioFoundry and the codec slot (rfoundry_db25d_install.bin with the slot merged in), which must start with the first stage's signature, or restores a backup's application area and slot. The site's tests (site/tests/iap.test.mjs) run the console stub load and the IAP write against simulated radios (measured on a radio: the sequences in this table)
USMART console UART1, the same port. A command is one frame in one write: @AT, one filler byte (not CR or LF), the text, CR LF; each byte must follow within 20 ms, and an unframed line is ignored. read_addr(0xADDR) answers read_addr(0XADDR)=0XVALUE;, write_addr(0xADDR,0xVALUE) echoes write_addr(0XADDR,0XVALUE);; the application's own log lines follow on the same port. The console's command table is copied to RAM 0x18000840 at boot; its cmd_rec entry (+8, 0x18000848) is called through the table for every frame except 01 and DMRCALL. The version string EARSAB.0NN is at 0x03079F3D (018) and 0x0307DCCD (020)
Stub at power-on RadioFoundry's first stage offers the boot ROM's handshake on UART1 for about 0.5 s at every power-on and takes blocks below 0x4E000. The universal stub loads at 0x10000 there as on the other radios: stubtool PORT --wait 120 --board db25d --write-app IMAGE, then a power cycle (older stubs: add --legacy-stub --load-base 0x30000). The radio programming utility's Recover at Power-On does the same (boot/README.md) (measured)
Stub from a running RadioFoundry CONFIG_STUB_ENTRY is on: stubtool --rfoundry-enter asks the firmware for its stub span (debug link STUB INFO) and loads the universal stub there (RadioFoundry restores the loader's clocks before the jump, section 2.1). The span is 0x30000-0x4FFFF in a build without DMR (HRC_STUB_BASE in config.mk; boards/db25d/fm_only.ld keeps RadioFoundry's RAM below it), 0x1D000-0x2EFFF with CONFIG_DMR=y (8). The stub copies itself from the load address to 0x30000 and runs there (in a DMR build from 0x1D000; in a build without DMR it is already there), so one command (stub/db25d_update.sh IMAGE PORT) installs a DMR or a non-DMR image from either kind of build. The tool reads board= from VERSION and sends the DB25-D profile in CONFIG (power latch PTB13, power key PTB12, UART1 only, NAND), and checks the echo before writing. --legacy-stub and --rfoundry-stub FILE load a position-dependent stub instead, refused if linked for another span; the power-on stage catch recovers a hang. --boot reset restarts into RadioFoundry: the soft reset leaves the power latch on, and the loader goes through the stage again. No keys or power cycle for an update (measured)
Stub RAM The universal stub runs at 0x30000-0x37978 on every route (image 7,004 B, .bss to 0x36974, 4 KB stack; stub/README.md, RAM placement). Deepest stack use measured on this radio: 252 B, NAND reads included. Its SPI NAND reads are read only: stubtool --nand F --nand-range A:B, --verify-nand F (measured)
Stub from the vendor firmware (018, 020) Run on a radio with 018 (2026-10-18): stub loaded and started, 2 MB program flash read in 68 s; 020 not yet. RAM at the hand-over: 018 code and data 0x10000-0x345C4, .bss to 0x525E0, interrupt stack to 0x535E0, heap to 0x53DE0; 020 .bss to 0x4F150, interrupt stack to 0x50150, heap to 0x50950. Both: VBR 0x10000, start-up stack top 0x50000, modem tables and the console's state in the modem SRAM at 0x18000800 and up. The free window both share is 0x54000-0x58000. The console writes the universal stub there word by word (write_addr, little endian, unpatched), checks it with read_addr, and starts it by writing 0x00054000 to cmd_rec (0x18000848) and sending one more frame; that word is never read back, since the read would start it too. The stub masks interrupts, hands the program flash back from execute-in-place, and copies itself to 0x30000 (the vendor .bss, dead once interrupts are off; the 7,004-byte source at 0x54000 doesn't overlap). Then stubtool PORT --attach --board db25d. The vendor firmware leaves the loader's clocks and uses UART1 as RadioFoundry does, so the same profile applies; the host measures the clock. An address inside the interrupt stack (020: 0x4F150-0x50150) is not safe for the load

8. Codec

The HR_C7000 vocoder library (V1.9.6) runs AMBE+2 in software on the CK803S and the frames pass through the CPU. RadioFoundry hosts it as on the DM-32UV (codec-dm32.md): the user's machine carves the codec out of the vendor firmware file or the radio's program flash into a slot in the image, and the firmware copies it to the board's codec window at the top of RAM at boot and moves the addresses its codec map lists (codec-placement.md). No codec bytes are in the repository or in a release.

8.1 Vendor firmware files

The 2024-05-09 build is the first a PRO can run (1), and older images must never be loaded on one, so no profile exists for them. Three 018 files are supported, all plain images linked at 0x0300C000 (no header, no container, no key), and the 020 build from a read of the radio's program flash. The three 018 files carry the same codec code, built from the same library, with different literal pool words.

File Size sha256 Profile id
DB25-D PRO 909E.Dx.EARSAB.018_2025-01-21.bin 846,700 341536cbac0c1b2471f8198ebc456946e5fe026d96d153df14d6aa5566304f8b db25d-2501
DB25-D PRO 909E.Dx.EARSAB.018_2024-09-23.bin 845,900 f2a4b214720b971b58cdadcc53d44695e1462b75296afc41f2a0b53a704d36b4 db25d-2409
2024-05-09 build (909E.D6x.EARSAB.018) 843,944 9e199f54cd6545d153d39542ca5be3ac1076ee215eb93ca630a83cbdbbff4606 db25d-2405
909E.DBB.EARSAB.020, a 1 MB program flash read (stub/stubtool.py --progmem) 1,048,576 none: found by the codec's own hashes db25d-020

The 020 build carries a different compile of the codec, and its profile works from a read of the radio's program flash. A radio can run any supported codec whatever its own firmware version, because the codec is plain code and tables. The 020 build:

020
Code start (the two signature words) flash 0x030C142C
Code Another compile: 32 of the 19,144 words match 018's at the same offset. Most 32-byte runs of the 018 code are there, shifted by 0x4F0, 0x430 and other amounts
Code run address 0x180B8, inferred: the RAM image starts at flash 0x030B9374 (its first 256 bytes are 018's, and occur once), and 020's start-up copy is in the 4 KB the read lacks
Tables Byte-identical to 018's (same sha256), at flash 0x0307E760

0x180B8 lay inside RadioFoundry's RAM in the layout from before the move. With the codec copied to the board's window (8.3), the run address only sets the offset added to the three words its codec map lists. The 020 code is 78,172 bytes, the largest of the four builds, and sizes the window.

The web flasher finds the codec by content: the two signature words, then the SHA-256 of the code and the tables against every profile, wherever an image or a backup holds them. A vendor firmware file not known by its own SHA-256 is placed as the radio would hold it and searched the same way, so a re-release that carries a known codec unchanged works. When the signature is there but no profile matches, the page says that the radio's codec is a build RadioFoundry can't use and asks for a supported file; the backup is not affected.

8.2 Where the codec is

2025-01-21 2024-09-23 2024-05-09
Code: file offset / flash address 0xB5C58 / 0x030C1C58 0xB592C / 0x030C192C 0xB5130 / 0x030C1130
Code: length 0x12B20 (76,576) same same
Code: run address (RAM) 0x1CEB0 0x1CEB0 0x1CEB0
Tables: flash address 0x0307A8EC 0x0307A614 0x0307A150
Tables: length 0x2D50 (11,600) same same
Tables: run address (modem SRAM) 0x180014AC 0x180014B4 0x180014B0
First two code words 0x152514D8, 0x74C776CD same same
sha256 of the carved tables e1e4358224... same same

Layout facts:

  • The codec is the code and two table groups (752 + 10,844 bytes, as in codec-dm32.md); the tables are byte-identical in all three 018 files and sit 4 bytes apart here. All 58 distinct modem SRAM addresses the codec's literal pools hold lie inside the table window.
  • The code ends at RAM 0x2F9D0 (run address 0x1CEB0 plus the length), and no function reachable from the entry points calls anything outside the window.
  • The entry points, as offsets from the code start, are the DM-32 046 build's except the FEC init: decode 0x0, encode 0x260, decoder init 0xB4, decoder set 0x84, encoder init 0x314, encoder set 0x2E4, FEC init 0x1DCC. The init sequence (the state offsets, the 14 encoder parameters, the order of the calls, the first decode) is the one platform/hrc7000/vocoder.c runs.
  • A read of the radio's program flash lacks the first 4 KB; that doesn't matter here, because the 018 codec lies far above it and the 020 codec is a separate profile (above).

The code's literal pools hold three absolute codec addresses and 58 table addresses, so the code runs only at these addresses (as the DM-32UV's). The build reserves them. The codec window is one block at the top of RAM (codec-placement.md), so the 020 build's run address, which would lie inside RadioFoundry's RAM, is no obstacle.

8.3 Build and flash

Step What
Codec file Put one of the files above in codecs/ (any name; the build recognizes it by sha256)
Build make BOARD=db25d. Next to rfoundry_db25d_install.bin (first stage + application) it writes build/db25d/rfoundry_db25d_codec.bin: the same, padded with 0xFF to 0x030E8000, then the 128-byte slot header, the code and the tables. With no supported file in codecs/ the build says so and writes only the plain images
Slot 0x030E8000..0x03100000 (96 KB; 88,316 bytes used for 018, 89,912 for 020): the version 2 header of platform/hrc7000/codec_slot.h, the code and tables as the vendor firmware has them, and the relocation list. tools/hrc7000_codec.py --board db25d --stock FILE writes it from a vendor firmware file or a program flash read
Flash The merged image goes where _install.bin goes (the loader's update mode or stubtool --write-app, 7): it is the stage, the application and the slot in one file. The loader writes as far as the image reaches, about 1 MB, so the first flash takes a while; stubtool --write-app writes only the sectors that changed
Check The debug link's VERSION reports codec=db25d-2501 (or -2409, -2405, -020) when the slot is valid, absent when it's built with CONFIG_DMR=y and the slot is missing, damaged or version 1
RAM CONFIG_DMR=y links boards/db25d/memory.ld: one region for RadioFoundry, 0x10000..0x44E00 (216,576 B: the vectors, the slot engine, the core's CPU state, data, bss with the RTOS heap, the start-up stack), and the codec window 0x44E00..0x58000 (78,336 B) at the top. The tables go to modem SRAM 0x180014AC..0x18004360, which the script leaves out. The runtime stub window is 0x45000..0x58000 (inside the codec window, after the codec is parked; HRC_STUB_BASE in config.mk). The universal stub loads there and copies itself to 0x30000, which is RadioFoundry's RAM and dead after the jump (7). Without DMR the span is 0x30000 (boards/db25d/fm_only.ld)

9. How RadioFoundry uses it (make BOARD=db25d)

State on a radio:

Part State
Platform platform/hrc7000, image at 0x0300C000 behind the IAP loader; the host port on UART1
RF drivers/rf/bk4819: main and LO on one 3-wire bus. FM receive by the main chip at the IF with the LO at f + 51.55 MHz. RX sub-audio: CTCSS and DCS from the chip's detector (4.7)
Calibration Both copies are read at boot, the higher save counter wins (6.1, 6.4); DAC A and DAC C are set from it for receive. The diagnostics page shows the copy used, its save counter and the PA values at 446 and 146 MHz, and a line with the PA drive for the tuned frequency
NAND drivers/flash/spi_nand on SPI0 (6.5). Programs and erases only inside RadioFoundry's range (6.6). The diagnostics page shows its ID and the page reads ECC had to correct
TX Analog FM (4.4) and DMR (4.5), both measured on the SDR bench with the frequency trim of 4.6. Refused without a valid calibration page or outside the bands. The diagnostics page shows the PA drive for the tuned frequency, low / high
Display st7735 on a 4-wire serial bus: SPI2 or bit-banged, chosen by the ADC 2 strap
Power No battery (CAP_NO_BATTERY): a mobile on a fixed 13.8 V supply with no supply reading (3.3). The core shows no battery icon, percentage, Tools > Battery page, Battery saver or Low battery warning setting, and has no battery monitor, so it never warns or refuses to transmit for the battery. hal_power_t has no battery_mv or battery_pct. The diagnostics page shows the raw ADC channels
Keys Matrix and encoder positions checked on the radio. VOL/SQL is ACT_VOLUME: the volume screen, then the encoder (radio-features.md 4.2)
Volume No knob (hal_power_t.volume() is -1): the stored step (0..16) goes to hal_radio_t.volume() and sets the BK4819 AF DAC gain (3.3); the debug link sets it too (op 30). Checked on the radio: the steps change the level and step 0 is silent
Storage CONFIG_NAND_STORAGE (on): settings kept in NAND blocks 1920-1951 through drivers/flash/nand_sectors (6.6). On the owner's radio the region mounted clean (no foreign data, no bad blocks) and the VFO frequency survived a chip reset
Codeplug over the cable CONFIG_CPS_OPENFW (on): the open-firmware CPS on the host link (UART1, 115200) over the codeplug areas of 6.6, at the STM32 radios' addresses (EEPROM part at 0, flash part at 0x20000, no --offset). Radio info reports type 6; no MCU ROM or flash security register area; no calibration window. tools/cps_client.py --port /dev/ttyUSB1 read / write / verify, qdmr should work as well (not tried). The channels and zones come from the stored codeplug (core/codeplug), which the radio re-reads at the end of a session. Host-tested on a simulated NAND with power cuts; read, write and verify run on the owner's radio (read 63 s, write 76 s). CONFIG_APRS_RX and CONFIG_APRS_IGATE are off (no FM receive PCM on this radio)
Codec Profiles for three 018 vendor firmware files and the 020 program flash read (8); make merges one into rfoundry_db25d_codec.bin when its file is in codecs/. On the owner's radio (2026-10-19), with the code moved to the codec window: the 020 read and the 2024-09-23 file each give VERSION codec=db25d-020 / db25d-2409 and decode a bench voice call with every one of its 234 AMBE frames
DMR CONFIG_DMR=y (default on): the shared HR_C7000 glue of the DM-32UV with the main BK4819's digital set (4.5). Receive works on the radio: a bench voice call decodes with every AMBE frame, and a bench SMS arrives (4.5). Speaker audio for DMR not yet heard. Transmit (DB25D_DMR_TX=1, default on): the per-burst TX sequence from an RF task (4.5), checked on the SDR bench; DB25D_DMR_TX=0 builds receive only. Builds clean with every DMR feature, the network ones included (the next rows)
Hotspot CONFIG_HOTSPOT=y (default on): the MMDVM host port on UART1, simplex. The core's hotspot drives the same hal_dmr_modem_t as a call (tx_begin, tx_queue, tx_end) and keys through hal_radio_t.tx() in DMR mode, so it runs on the per-burst TX path of 4.5 (the dmrrf RF task) with nothing added to the board: whole calls and data go out as the host sends them, and a retune (rx()) disarms the keying until the hotspot arms it again. A split RX / TX frequency pair works as a call's does (each burst tunes to the TX frequency, then back). Not offered: the split-duplex roles (CONFIG_HOTSPOT_SPLIT=n: the driver has no continuous BS outbound, so the core would refuse the role) and SFR / tx_listen per-slot switching (CONFIG_SFR=n). Checked on the radio with tools/bench/hotspot_cases.py against the SDR bench modem (frequency 461.4625 MHz, color code 2): GET_VERSION, SET_FREQ, SET_CONFIG, SET_MODE and GET_STATUS answer (status DMR, TS2 space 22); all 13 cases pass (rf_voice8, rf_late, rf_long, rf_lost, rf_b2b, rf_csbk, rf_data_unconf, rf_data_conf for uplink frames; net_voice, net_voice_ctl, net_data, net_data_conf, net_b2b for calls sent from the host and heard on air). The radio is back on its channel within 25 s of the last frame
DMR VFO CONFIG_VFO_DMR=y follows the default: the core's DMR VFO (dmr-voice.md 2.1; the VFO quick menu's Mode tile switches it FM / DMR). On the radio (2026-10-17, debug link): mode, color code, slot and a group or private target set and kept across leaving the VFO and a restart; at 461.4625 MHz CC 2 TS 1 TG 201 a bench call received with every voice burst (120 voice, 8 headers, 3 terminators), and a 4 s key-up decoded by the bench modem as a group call to TG 201 (72 voice bursts, 216 AMBE frames). A record written by an older build keeps the color code its FM VFO stored (often 0) until the user sets one
IP link CONFIG_NET_PPP=y (default on): PPP on the host port's UART1 (board_t.netif is hostlink_ppp_netif, as on the DM-32UV), and with it CONFIG_DMR_IP_GATEWAY and the services on it: UDP compression, DMR confirmed data, TCP (3 connections, TCP_CONNS), KISS, HTTP (gzip), rigctl, APRS-IS, mDNS, TFTP, SNMP and the control station. USRP is off (CONFIG_NET_USRP=n): its 8 kHz 16-bit PCM is 128 kbit/s plus headers, more than the 115200 baud host port carries, and a heard call started the stream. Checked on the radio with pppd and tools/rfoundry_ppp.sh: LCP and IPCP come up, ping 30 ms, 5/5; HTTP 200 (also gzip); rigctl f answers the channel frequency over TCP 4532; SNMP sysName.0 answers; the debug link over UDP answers; 48 HTTP requests in six parallel loops all succeed with no PPP errors and no TCP retransmits (six clients against three connection slots: tcp.no_slot counted 7 SYNs that found no slot, and curl retried them); rfoundry_ppp.sh --stop sends the LCP Terminate and the debug link answers on the serial port again. Found on the radio: with the USRP stream on, a bench DMR call heard while PPP was up wedged the host link task (send_rx() in core/usbaudio/usrp_net.c never left its loop, because a frame takes 30 ms to clock out of the UART and holds 20 ms of audio); the radio stopped answering LCP and the debug link until a power cycle. send_rx() now gives up after 20 ms of sending per poll (host test usrpNetTest). Not checked: mDNS, TFTP, APRS-IS, KISS and the control station; a DMR call heard while PPP is up, after the fix
RAM Main RAM (above the codec window, 165,424 B) was 139,952 B used with DMR alone and is 150,080 B with the network and hotspot, USRP off (15.3 KB free; 155,704 B with USRP, 9.5 KB free); RAM_LO (52,912 B) went from 19,024 B to 28,584 B used (24 KB free). The network's buffers sit in RAM_LO (.ram_lo, as RFOUNDRY_CPU_RAM marks them). The RTOS heap is 40 KB, the DM-32UV's, and holds the task stacks. Costs from the ci builds: IP gateway and PPP 15.0 KB main + 6.7 KB RAM_LO; TCP 5.0 KB + 3.4 KB; USRP 5.6 KB; SNMP 3.2 KB; HTTP 2.5 KB; hotspot 2.6 KB + 2.8 KB; roaming 2.2 KB + 0.9 KB; the rest under 1 KB each
GPS CAP_GPS, reported once the module is heard. platform/hrc7000/gps_hrc.c (shared with the DM-32UV, hardware-dm32.md 12) powers it in board_init, listens 5 s on UART2 for one checksum-valid sentence, feeds every sentence to the APRS position source, and powers it off again when nothing is heard. Once heard, the module is powered only while Settings > Location > GPS is on (board_t.gps_enable = hrc_gps_enable, applied at start-up and on each change). The diagnostics page line gps yes (or gps no) `GOOD/BAD bBYTES on
Left off CONFIG_SFR (per-slot RX / TX retune not timed on this radio), CONFIG_APRS_RX and CONFIG_APRS_IGATE (no FM receive PCM), USB and USB network (no data port), broadcast FM (not wired), air band AM (no air band receive), FONT_XL and AUDIO_PROBE
Older batches (FD6818 main) Not driven (the chips don't identify as BK4819)

DMR receive check (passed on the owner's radio, 4.5): build with the vendor firmware file in codecs/ and make BOARD=db25d; flash rfoundry_db25d_codec.bin; with a DMR repeater or the SDR bench's DMR source on a channel of the zone, read the diagnostics page. In order: the codec line shows db25d-... and a non-zero tag; the sync counters move (the AF phase range isn't a level check on this board, 4.5; if nothing syncs, try the AF gain and RF_MODE knobs of 4.5); ts and si climb (the modem has slot timing); then V / H / T counts rise on a talk burst. Voice and the speaker route come last. stubtool --rfoundry-enter loads the stub into a DMR build's 0x1D000 span by itself (7).

DMR transmit check (passed on the owner's radio, 4.5; on a dummy load or into the SDR bench): key a DMR channel on the low power level. On the diagnostics page b (bursts keyed) and its pair (bursts ended) climb by 16 or 17 a second and stay equal after the key-up; L stays 0 or small. On the SDR bench: bursts in one timeslot, carrier on frequency, the ramp ending before the other slot, then sync and the voice header decoded. No sync with a clean carrier: try DB25D_DMR_TX_INVERT=1. Low deviation: compare the page's modulation byte (6.3, 0x067) with the value for the same frequency in the radio's own calibration.

10. Open questions

# Question Needs the radio?
1 Firmware version in Device Info (expect 909E.DBB...) Yes, menu only
2 Program flash part and size; JTAG and UART0 pads Yes, photos of the control board
3 Panel size and which bus mode the PRO uses (ADC 2) Yes
4 Calibration page: the 0x09B / 0x160 / 0x1B1 / 0x2xx items (6.3). Real values are known: 0x045 is the higher DAC value, so the high level; that this is more output needs a power measurement A power meter or the SDR bench
5 Can the vendor CPS read back the calibration page (a backup path)? Yes, serial capture of a CPS read
6 Any model check in the IAP loader (the protocol is in 7) A foreign image sent through it
7 Hand-mic key protocol on RJ-45 pin 2 Yes, logic analyzer
8 Hand mic key numbers 15-17, if any Yes
9 Band assignment of the RF switch pins (UHF = PTB3 / PTB4 is by analogy) No, then a meter
10 Supply voltage: no ADC channel known (3.3). ADC 7 read at two supply voltages would show whether it follows the supply No, then a meter
11 Which NAND address ranges the vendor bad-block skip covers (6.5) Yes
12 Answered (4.5): the digital set with RF_MODE 0x034C9860 decodes every frame -
13 The modem's input is the main chip's AF output (4.5, decodes on the radio). The voice output's speaker route (PTC18 low) still needs a listen Yes, a listen
14 Answered (4.5): single-point modulation on the TCXO, the per-burst sequence, decoded on the SDR bench; deviation 101 % of nominal at 461.4625 MHz -
15 The 020 codec's windows, entry points and a RAM layout for its run address (a separate compile at 0x030C142C, 8.1) A 020 vendor firmware file, or a read of 0x0300C000-0x0300CFFF

View the source of this page