Esta página aún no está traducida; se muestra en inglés.
TYT MD-380 hardware (MD-390, Retevis RT3, and the "G" GPS models)
The TYT MD-380, the MD-390 (the same board in an IP67 case) and the Retevis RT3 are one board family: an STM32F405VGT6 MCU, an HR_C5000 DMR baseband and codec chip, no RF transceiver chip but a Skyworks SKY72310 fractional-N synthesizer, separate receive and transmit VCOs, a GT3136 FM IF receiver, a discrete PA with an analog power loop, a 160 x 128 color LCD on the FSMC 8-bit bus, a SPI NOR flash, a 3-row keypad matrix sharing its columns with the LCD data bus, a 16-position channel switch, an analog volume knob with the power switch, and USB full speed on the MCU's OTG_FS core. Each radio is single band, UHF or VHF. The "G" models (MD-380G, MD-390G, RT3 GPS) add a GPS module on a UART.
Conventions: register values are hex. HR_C5000 registers are written as reg = value on its configuration page
(page 0; the driver keeps the HR_C6000's page numbers and maps them, section 4.3). SKY72310 registers are its 4-bit
addresses. A value is marked (unverified) where no datasheet and no measurement on a radio confirms it.
md380-bringup.md is the order in which a radio confirms or corrects the open facts,
and section 17 lists every open question.
1. MCU, clocks, memory
1.1 Part
STM32F405VGT6, LQFP100: Cortex-M4F, 1 MB flash, 192 KB RAM (128 KB SRAM, 64 KB CCM). Read-out protection level 1 on production radios (unverified).
1.2 Clocks
8 MHz crystal on HSE (unverified). RadioFoundry runs the DM-1701's clock plan: PLL M 4, N 72, P 2 (72 MHz SYSCLK), Q 3 (48 MHz
USB), AHB 72 MHz, APB1 36 MHz, APB2 72 MHz; If HSE does not start as a crystal, board_early_init retries it in bypass mode (the DM-1701
needed bypass) before staying on HSI. LSE: a 32.768 kHz crystal is assumed (unverified).
1.3 Internal flash layout
| Sectors | Addresses | Use |
|---|---|---|
| 0-2 (3 x 16 KB) | 0x08000000-0x0800BFFF | TYT bootloader, never written (unverified) |
| 3-7 | 0x0800C000-0x0807FFFF | RadioFoundry application, 464 KB in one piece (boards/md380/memory.ld) |
| 8-11 | see codec-md380.md | The codec as the merge places it. From D002.032: the table windows at their original addresses (sectors 8, 10, 11), the code moved to 0x080E0000 (sector 11). From D35.14 / S35.14: the main table window at its original address (from sector 9 into sector 10), the code right after it, the small ones in sector 11. Then the descriptor at 0x080FFF80 |
The application starts at 0x0800C000 with its vector table there (VECT_TAB_OFFSET 0xC000) (unverified).
boards/md380/memory.ld asserts that the image ends before 0x08080000. The default UHF image takes 320,236 bytes with link time
optimization, on by default here: feature-matrix.md. The manufacturer's firmware keeps
the codec code at 0x08050C44 (sectors 6 and 7), so the merge moves it.
1.4 RAM
128 KB SRAM for data and every DMA buffer (minus 4 bytes at the top, as the DM-1701 keeps them for the boot request
word); 64 KB CCM for CPU-only state (RFOUNDRY_CPU_RAM: the task stacks and the slot engine, as on the DM-1701, a76da0d;
main RAM use went from 89 % to 68 % of the default build with that move).
2. Pin assignments
The pin tables come from public schematic notes (from the RT3 / MD-380 service schematic) that we cannot name. Nothing independent backs them, so every pin is (unverified) unless another basis is cited. The notes warn that the
schematic "is known to be not correct" in places. Where the notes are unclear the pin is marked (unverified). boards/md380/pins.h holds the same map.
2.1 Port A
| Pin | Function | Notes |
|---|---|---|
| PA0 | ADC1 IN0: volume knob position | (unverified) |
| PA1 | ADC1 IN1: battery voltage | (unverified) |
| PA2 | ADC1 IN2: QT_DQT_IN, sub-audio tone input | (unverified); RadioFoundry decodes CTCSS / DCS in software, sampling it with CONFIG_SUBTONE (section 6.4) |
| PA3 | ADC1 IN3: VOX, mic level | (unverified) |
| PA4 | DAC1: APC / TV (PA power reference in TX, front-end tuning in RX) | (unverified); the double use (unverified) |
| PA5 | DAC2: MOD2_BIAS (reference oscillator bias) | (unverified); value from 11.1 |
| PA6 | Keypad row K1 | (unverified) |
| PA7 | POW_C, power latch | (unverified) |
| PA8 | SAVE: enable of the 5C rail (U404), high = on (5.7) | schematic; output, driven high |
| PA9 | VCOVCC_SW: RX or TX VCO supply, high = receive VCO, low = transmit VCO | bench |
| PA10 | DMR_SW: receiver IF to the HR_C5000 | (unverified); low in TX, high in RX 2.6 |
| PA11 / PA12 | USB OTG_FS D- / D+ | (unverified) |
| PA13 | W/N_SW, IF bandwidth; also SWDIO | (unverified); polarity (unverified) |
| PA14 | MICPWR_SW, microphone bias; also SWCLK | (unverified) |
| PA15 | I2S3 WS | (unverified) |
2.2 Port B
| Pin | Function | Notes |
|---|---|---|
| PB0 | ADC1 IN8: RSSI | (unverified) |
| PB1 | ADC1 IN9: BUSY, the receiver's noise squelch level | (unverified); polarity (unverified) |
| PB2 | FM_SW, FM receive path select | high in FM receive (measured on RadioFoundry) |
| PB3 / PB4 / PB5 | SPI1 SCK / MISO / MOSI, SPI flash (AF5) | (unverified) |
| PB6 / PB7 | I2C1 SCL / SDA | (unverified); nothing on it is known; unused |
| PB8 | SPK_C, speaker mute | high = muted (measured on RadioFoundry) |
| PB9 | AFCO, audio amplifier enable | high = on (measured on RadioFoundry) |
| PB10 / PB11 | Channel switch ECN2 / ECN3 | (unverified) |
| PB12 | HR_C5000 voice port chip select | 2.6 |
| PB13 / PB14 / PB15 | SPI2 SCK / MISO / MOSI, HR_C5000 voice port (AF5) | 2.6 |
2.3 Port C
| Pin | Function | Notes |
|---|---|---|
| PC0 | HR_C5000 timeslot interrupt (EXTI0) | (unverified) |
| PC1 | HR_C5000 system interrupt (EXTI1) | (unverified) |
| PC2 | HR_C5000 RF TX interrupt (EXTI2) | (unverified) |
| PC3 | ADC1 IN13: 2T/5T (FM discriminator audio) | (unverified); unused |
| PC4 | RF_APC_SW, PA power loop on | (unverified) |
| PC5 | 5TC, transmitter supply | (unverified) |
| PC6 | LAMP, LCD backlight (TIM8 CH1 PWM, AF3) | (unverified); PWM is our choice: 40 kHz, far above the FM transmit audio band (#330) |
| PC7 | CTC/DCS_OUT, sub-audio tone output (PWM) | (unverified); TIM3 CH2 (AF2) with CONFIG_SUBTONE (section 6.4), otherwise released (open drain, high): it tunes the reference (5.2) |
| PC8 | BEEP (TIM8 CH3) | (unverified); unused |
| PC9 | 5RC, receiver supply | (unverified) |
| PC13 | HR_C5000 configuration clock, idles low (section 4.2) | the bus pin (not PE3) |
| PC10 / PC11 / PC12 | I2S3 CK / ext SD / SD | (unverified); direction of the two data lines (unverified) |
| PC14 / PC15 | LSE crystal | (unverified) |
2.4 Port D
| Pin | Function | Notes |
|---|---|---|
| PD0 / PD1 | FSMC D2 / D3 (LCD data, keypad columns) | (unverified) |
| PD2 | Keypad row K2 | (unverified) |
| PD3 | Keypad row K3 | (unverified); carries the side keys (unverified) |
| PD4 / PD5 | FSMC NOE / NWE | (unverified) |
| PD6 | LCD chip select | (unverified) |
| PD7 | SPI flash chip select | (unverified) |
| PD8 | GPS module power on the G models; listed as a second flash chip select on others | 13.7 |
| PD9 | USART3 RX (AF7): GPS NMEA in on the G models; listed as a flash chip select on others | (unverified) |
| PD10 | SKY72310 PS, out-of-lock, open collector: pulled up, high = locked | (unverified); pull-up |
| PD11 | SKY72310 chip select | (unverified) |
| PD12 | FSMC A17, LCD RS (data / command) | (unverified) |
| PD13 | LCD reset | (unverified) |
| PD14 / PD15 | FSMC D0 / D1 | (unverified) |
2.5 Port E
| Pin | Function | Notes |
|---|---|---|
| PE0 | Green LED (RX) | (unverified) |
| PE1 | Red LED (TX) | (unverified) |
| PE2 | HR_C5000 configuration port chip select | (unverified) |
| PE3 | HR_C5000 SDO -> MCU (input, pull-up) | bench |
| PE4 | SKY72310 clock (idles high) | bench |
| PE5 | MCU -> HR_C5000 SDI, also the SKY72310 data | bench |
| PE6 | DMR_SLEEP, HR_C5000 power down (high = down) | (unverified); polarity as the HR_C6000 (unverified) |
| PE7-PE10 | FSMC D4-D7 | (unverified) |
| PE11 | PTT, active low | (unverified) |
| PE12 | External PTT (accessory jack), active low | (unverified) |
| PE13 | FM_MUTE | high = FM audio through, low = muted (measured on RadioFoundry) |
| PE14 / PE15 | Channel switch ECN0 / ECN1 | (unverified) |
2.6 Reset-time levels and the SWD pins
board_early_init sets, before anything slow: the power latch (PA7) high; the HR_C5000 chip select and power
down high and its clock low; the SKY72310 clock high; the SKY72310 chip select high; the transmitter supply, APC loop and receiver supply low and VCO select high (the receive VCO);
DMR_SW high; speaker muted, amplifier off, FM muted; LEDs off; keypad rows low; the flash chip select high. PD8 and
PD9 are left as inputs (section 13). These are safe choices (unverified).
Transmitter safe state (board_rf_safe, crash-log.md 8), at every fault, reset and stub entry and first
in main(): APC_EN (PC4) and 5TC (PC5) low, VCO_SEL (PA9) high (the receive VCO), the APC DAC (PA4, DAC1 channel 1) 0
when its clock is on. The SKY72310 and HR_C5000 keep their state until the next start.
PA13 and PA14 are the SWD pins. The radio needs them as the IF bandwidth switch and the microphone supply, so
radio_md380.c takes them over at radio init; a debugger then has to connect under reset. Build with
MD380_DEFS=-DMD380_KEEP_SWD to leave them alone: the microphone is then
unpowered and the bandwidth switch stays as the hardware pulls it.
3. Interrupts, DMA, timers
Our allocation, following the DM-1701 (boards/dm1701), since the same MCU pins carry the same HR_C5000 lines:
| Source | Use | Priority |
|---|---|---|
| EXTI0 / EXTI1 / EXTI2 (PC0-PC2) | HR_C5000 timeslot / system / RF TX interrupts (falling edge) | 5 |
| TIM7 | 30 ms one-shot after the RF TX interrupt: the burst's PA off (section 5.6) | 5 |
| DMA1 Stream0 ch0 / Stream5 ch2 | I2S3 receive / I2S3ext transmit (PCM, section 6.2) | 6 |
| DMA2 Stream4 ch0 | ADC1 continuous scan (circular; with CONFIG_SUBTONE half and full transfer interrupts feed the sub-audio detector, section 6.4) |
7 with CONFIG_SUBTONE, else none |
| USART3 | GPS NMEA receive (RXNE interrupt into a ring buffer) | 7 |
| TIM8 | LCD backlight PWM, CH1 on PC6 | - |
| TIM3 | sub-audio tone PWM, CH2 on PC7, only with CONFIG_SUBTONE and only while a tone is sent (section 6.4) |
6 |
| OTG_FS | USB (platform) | platform |
FreeRTOS's configMAX_SYSCALL_INTERRUPT_PRIORITY is 4 (platform/stm32f4/FreeRTOSConfig.h; even so that a part with
3 priority bits can hold it, gotchas.md 13.11), and no interrupt uses 4. The modem ISRs at 5 notify the
DMR task, and a masked section at the syscall level makes every configuration-port and synthesizer transaction
atomic.
4. HR_C5000
4.1 Overview
The HR_C5000 is the HR_C6000's predecessor in the same family: DMR layer 1/2 baseband, two-point modulation outputs,
an IF input from the receiver, and an audio codec with I2S. RadioFoundry has a driver of its own for it,
drivers/modem/hrc5000, which shares only the chip-independent code (drivers/modem/hrc_common: the bus accessors and
the DMR slot engine) with the HR_C6000 driver. The shared engine reaches the chip through a description struct
(hrc_chip_t); the MD-380 build links no HR_C6000 code. The HR_C5000 user manual (HR_C5000 用户手册 V2.0, Chinese,
35 pages, datasheets/C5000/hrc5000.pdf) is the source for the register names and reset values in 4.6. It has no
register map for 0x25, 0x26, 0x37, 0x39, 0x3D, 0x41, 0x45, 0x47, 0x48 or 0xB9-0xBB; those values are (unverified) until a radio
confirms them.
4.2 Configuration port and the synthesizer bus
Bit-banged SPI on PE2 (CS), PC13 (clock), PE5 (MCU -> chip) and PE3 (chip -> MCU) bench. Unlike the HR_C6000 on the DM-1701, the clock idles LOW. Per bit: clock high, set MOSI, clock low (the chip samples MOSI on this falling edge), waits, then samples MISO. The chip drives the next MISO bit only after the falling edge: sampled at once, every read comes one bit late (0x10 read back 0x40 after 0x80 was written, and the LC 00 00 00 00 00 63 30 5A 5E read 80 00 00 00 00 31 98 2D 2F) on air 2026-10-18. Register reads in logs taken before that fix (issue 3, issue 66, up to 2026-10-18) are shifted right by one bit. On any other pin choice every read returned 0xFF. A transaction is 3 bytes for a register write (page, register, value) and 2 bytes plus the data for a read (0x80 | page, register), with CS low around the whole frame. A few cycles per half bit is enough; speed is not a constraint.
The SKY72310 has its own clock (PE4) and chip select (PD11); only the data line (PE5) is shared with the HR_C5000
bench. Its clock idles high: the MCU sets the data bit while the clock is high, drops the clock, raises it again,
and the chip latches the bit on the rising edge. Because PE5 is shared, a transaction on either chip is one masked
section (md380_rfbus_lock), so a modem ISR's register access never lands inside a synthesizer word or the reverse.
4.3 Differences from the HR_C6000
The chip description in hrc5000.c carries these; the HR_C6000 description in hrc6000.c carries the other column.
| Item | HR_C6000 | HR_C5000 | Status |
|---|---|---|---|
| Configuration page | 4 | 0 | bench |
| Reg 0x10 in DMR | 0x6E | 0x6B simplex, 0x4F on a repeater channel; RepeaterSlot (b0) stays 1 and the engine picks the slot from the TC (next row) | manual: b2 ISRepeater = repeated operation, b0 RepeaterSlot. On air through an MMDVM duplex hotspot (issue 108): 0x4F with the engine's slot choice lands every call on the channel's slot, both slots, 2026-10-21. 0x6E / 0x6F (timeslot mode, RepeaterSlot 0 / 1) did not steer the slot: 6 key-ups each went out 2,1,1,1,1,1 and 1,1,1,1,2,1 (2026-10-20) |
| Own DMR ID 0x14-0x16 | big endian | 0x14 holds the low byte | manual (LocalSreAddress L, M, H) |
| TX start / stop | access policy and vocoder control written | tx_begin / tx_end hooks absent |
(unverified) the vocoder runs on the MCU, so 0x22 (EncodeStart) and 0x60 are not used for voice |
| Reg 0x5F (sync / slot report) | read at every burst | absent: DMO / RMO come from the channel | (unverified) not in the manual's table |
| Embedded LC LCSS | chip | MCU writes it in 0x50 for every voice burst: A-F = 0x08 0x19 0x2B 0x3B 0x4A 0x58 | (unverified) |
| CRC_MCU_Control (0x40 bit 3) | present | absent: for CSBK, MBC header and data header write 10 bytes and the chip appends the CRC | on air 2026-10-20: CSBKs and data from the MD-380 decode with good CRC on a DM-32UV and an MMDVM hotspot |
| CACH TC report | 0x52 bit 2, refreshed in received slots | 0x52 bit 2 too (the manual calls bits 3-0 reserved), but it changes only in slots opened for receive, and 1 means TS2 is starting | per-tick probe of a hotspot downlink, 2026-10-21: with receive open in both slots the bit alternates every tick (0x55555555 over 32 ticks); with one slot closed it stops changing. That froze the engine's lock (it closed the other slot once locked) and every repeater key-up was rejected or landed on either slot. The chip flag tc_last_rx keeps both slots open; the engine reads the bit only after two received slots in a row (gotchas 1.10), so the first read after our own transmission is skipped. Unconfirmed on this chip: whether that first read is frozen or already right. Polarity from the calls: read as the slot that had ended, every call landed on the other slot |
| Mic gain | 0xE4 = 0x20 + gain | 0x0F = raw value (C5000_MIC_GAIN in radio_md380.c) |
(unverified) |
DMR set-up (hrc5000_set_mode_dmr) writes 0x06 before the reset-release block (0x00 = 0xFF) and FM after it (unverified);
it runs once per mode change; per-burst switching (hrc5000_dmr_path) touches only the
RF side:
- TX: 0x0D = 0x8C, 0x0E = 0x44, mic gain, 0x37 = 0xC2, 0x25 = 0x0E, 0x26 = 0xFE (RX IF and audio off), 0x45 / 0x46 = the calibrated DMR modulation pair, 0x35 = 0x1E, 0x3F = 0x04.
- RX: 0x0E = 0x44, mic gain, 0x37 = 0xC2, 0x25 = 0x0E, 0x26 = 0xFD.
VHF radios swap I and Q with 0x01 = 0xF0 after the init. The manual gives 0x01 as the TX and RX IQ/QI bits; which value the receiver needs is still to be confirmed on a VHF radio (unverified).
FM (hrc5000_set_mode_fm_rx, hrc5000_set_mode_fm_tx) uses its own tables. The TX deviation register 0x35 takes 0x30
(narrow) or 0x3C (wide) in FM and 0x1E in DMR; the narrow / wide mapping is inferred (unverified). CTCSS and DTMF are not
implemented for this chip yet.
4.4 Voice port
Hardware SPI2 (PB13-PB15, AF5) with chip select PB12, commands 0x03 (write) and 0x83 (read) followed by 0x00 and 27 bytes (one voice burst's payload) bench. RadioFoundry uses SPI mode 2 at 1.125 MHz on air 2026-10-18. At 4.5 MHz transmit voice decodes on another radio, but received voice reads back with the first bits of every byte wrong: the C0 words check only two bits off their place and C1 never does. At 1.125 MHz the three frames are in place and C1 checks too; the first data byte after the command still has a few bit errors that the Golay codes correct (unverified).
The voice RAM is 32 bytes: a longer read wraps. A received burst is in it as the three 72-bit AMBE+2 air frames in a
row (the HR_C6000 layout, core/dmr/ambe/ambeFec.h), but only while the decoder runs (4.8).
4.5 Reference oscillator
The calibration's reference tune byte t (section 11) sets regs 0x47 = 0x04 = 0x7F - t, 0x48 = 0x00 if t < 0x80
else 0x03, and DAC2 = t x 4 + 0x600 bench. hrc5000_set_ref() and hrc5000_ref_dac(); the stored tune is
re-applied by every full mode set-up. The user's reference trim adds to DAC2 (5.2, "Carrier offset"). The 2-2.5 kHz
error two radios showed was not this mapping but PC7 held low (5.2) (unverified until a radio confirms the fix).
4.6 Power and start-up
PE6 low wakes the chip; RadioFoundry waits 12 ms before the first access. hrc5000_power_on then programs the clock
(0x0A, 0x0B, 0x0C, 0xB9-0xBB) and enters FM receive. After init the driver reads 0x10 back for the diagnostics page
(bring-up stage 4).
4.7 Checked against the HR_C5000 user manual
The manual's register table (section 2, "参数配置") runs from 0x00 to 0x90 with reset values. What it says about the registers this driver writes:
| Register | Manual | Driver value | Status |
|---|---|---|---|
| 0x0A | CLKOutDiv, the output clock divider on CKOut (reset 0x02) | 0x80, then 0x00, in the clock step | Not a clock source switch; the effect is unknown (unverified) |
| 0x0B | PLLM (reset 0x0A) | 0x28 | : with XTAL 29.4912 MHz, M = 40 |
| 0x0C | PLLBP bit 7 (0 = PLL, reset 0x82 = bypass), PLL SLEEP bit 6, PLLDO bits 5-4, PLLN bits 3-0 | 0x33 | : N = 3, DO = 3 (divide by 8), PLL on. 29.4912 x 40 / 3 / 8 = 49.152 MHz, the manual's system clock |
| 0xB9-0xBB | not in the register table | 0x32, 0x22, 0x11 | Not confirmed. The manual gives the system clocks as fixed from 49.152 MHz: DMR 9.8304 MHz, FM 8.192 MHz, CodeC 12.288 MHz, external CodeC 24.576 MHz (unverified) |
| 0x18 / 0x19 | section 6 says the system clock is set here; the table defines them as group address registers | not written | Contradiction in the manual; unresolved (unverified) |
| 0x00 | active-low resets: bit 7 DMR protocol, bit 6 PHY, bit 5 codec, bit 4 FM, bit 3 vocoder, bit 2 UDC/DDC, bit 1 I2S, bit 0 CodeC (0 = reset) | 0x28 | DMR frames go out with DMR protocol and PHY at 0, so 0 is a running block here and the manual's polarity looks inverted (unverified) |
| 0x01 | bit 7 RFTransIQMode, bit 6 RFRecvIQMode (0 = IQ, 1 = QI) | 0xB0 (UHF), 0xF0 (VHF) | UHF: TX QI, RX IQ. VHF: both QI (unverified) |
| 0x10 | ModulatorMode bit 7 (0 DMR, 1 FM), ISRepeater bit 2 | 0x6B simplex, 0x4F repeater, 0x80 FM | |
| 0x11 | LocalChanMode bit 7 (1 = valid) | 0x80 | |
| 0x14-0x16 | LocalSreAddress L, M, H (low byte first) | DMR ID, low byte first | |
| 0x1F | LocalEMB, colour code in bits 7-4 | 0x10 (CC 1) | |
| 0x06 | Vocoder638 bit 7 (0 = CMX638 interface off); bit 0 LocalVocoderControl (1 = the CPU controls the vocoder switch) | 0x21 | Consistent; the other bits are not described (unverified) |
| 0x0D | Codec Ctrl, 0xC8 reset: bit 7 MCU controls the codec signals | 0x8C | Bit 7 set; the other bits unconfirmed (unverified) |
| 0x0E | HPoutEn, bit 7 (0 = off, reset 0x40) | 0x44 | Headphone output off; the value stays until a test shows otherwise (unverified) |
| 0x0F | ADLinVol (reset 0xB8) | 0xC8 | In range; the function is not confirmed (unverified) |
| 0x1D, 0x1E | LocalUnaddress and LocalBroadCast, receive addresses in bits 7-4 | 0xFF and 0xF1 | Reads as address 0xF; confirm on air (unverified) |
| 0x21 / 0x22 | access policy; EncodeStart bit 7, EncodeStop bit 6 | not written by default | See 4.3 (unverified) |
| 0x83, 0x87 | InterClear (write 1 to clear), SendStopMask | 0xFF, 0x00 | for the clear |
| 0x30-0x33 | BCLK / LRCK counts for the external codec I2S: Mclk / BCLK and Mclk / LRCK (reset 0) | 0x00 0x17 0x02 0xFF, from the 0x11 block | 24 and 768 counts, so BCLK 512 kHz and LRCK 16 kHz from a 12.288 MHz Mclk, or 256 kHz and 8 kHz from 6.144 MHz. Which Mclk the board gives is not known (unverified) |
| 0x36 | not in the manual. The HR_C6000's manual has bit 7 = external codec BCLK / LRCK off | 0xF0 from the 0x11 block; hrc5000_i2s_clock() writes 0x70 (bit 7 clear) while a PCM stream runs |
See 4.8 (unverified) |
Not in the manual: 0x25, 0x26, 0x36, 0x37, 0x39, 0x3D, 0x41, 0x45, 0x47, 0x48 and 0xB9-0xBB. Their values stay unconfirmed (unverified) until they are checked on a radio.
4.8 I2S clocks and the receive interrupts
Tester logs: issue 3.
| Fact | Basis |
|---|---|
Before any RadioFoundry write the chip's I2S pins toggle (the reset state has the clocks running); after the set-ups they are driven but do not move (ck 0 ws 0) |
tester logs |
| Every set-up's 0x11 block writes 0x36 = 0xF0. On the HR_C6000 bit 7 of 0x36 turns the external-codec BCLK / LRCK off; the C5000 has the same pins (17-20, BCLK / LRCK / ADCDAT / DACDAT) | HR_C6000 manual |
| RadioFoundry never wrote 0x36 after the block, so with the same bit meaning the clocks stay off. The reset register tries (0x28, 0x2A, 0x3F, 0xFF) changed nothing | tester logs |
The driver writes 0x36 = 0x00 while a PCM stream runs and puts 0xF0 back when it stops. Clearing bit 7 alone (0x70)
left the clocks stopped; with 0x00 they run and DMR transmit voice decodes on another radio (issue 66, 2026-10-17).
On receive the PCM doesn't reach the speaker yet: with every value from 0x00 to 0x60 the speaker plays only a faint
TDMA buzz, which sounds like the receiver's audio passed through (4.9)
(hrc5000_i2s_clock(), called from audio_md380.c); a full set-up re-applies the state, since its block rewrites 0x36.
The value is a hypothesis (unverified): the bit is not in the C5000 manual, and bits 6-4 are unknown. -DHRC5000_I2S_CTL_ON=<v>
tries another value (0x00, 0x30). With the clocks running, the diagnostics line i2s ck N ws N (MD380_I2S_PROBE)
gives the rates. The Mclk arithmetic in 4.7 says 16 kHz or 8 kHz for WS; the STM32 side assumes 8 kHz with 32-bit
channels, so a 16 kHz WS means the sample rate or the channel length needs changing (unverified).
Interrupts (manual section 7, with the sub-status registers):
| Item | Fact |
|---|---|
| 0x82 flags | bit 7 send request rejected, bit 6 send start (sub-status 0x84, mask 0x85), bit 5 send end (0x86, mask 0x87), bit 4 late access, bit 3 data received (0x51 gives type and CRC), bit 2 information received (sub-status 0x90: 0 = the whole message passed its check, 1 = failed), bit 1 abnormal exit, bit 0 physical-layer receive. Only the system interrupt needs the write-1 clear in 0x83 |
| 0x51 | bits 7-4 data type, bit 3 PI, bit 2 CRC (0 = good), bits 1-0 sync class (0 none, 1 voice, 2 data, 3 RC) |
| 0x52 | bits 7-4 received color code, bits 3-0 reserved; the slot (timecode) is read from bit 2 |
| Pins | TIME_SLOT_INTER every 30 ms in layer 2, used by the CPU to decide whether and what to send in the next slot; RF_RX_INTER / RF_TX_INTER the receive / transmit switch; SYS_INTER the system interrupt |
| Voice | The CPU reads and writes the voice port (SPI2 on this board) at times it derives from the captured start and end of a call; voice is not announced per burst by the system interrupt |
The tester's logs fit this: flag 0x04 (information) carries the sync-class reports, 0x08 (data) the others, and 0x52 reads 0x08 or 0x18 because the color code is only valid in a report that decoded a burst with one (a voice sync burst has none). Whether the engine can take a received voice call from these reports alone, or must run its voice reads from the 30 ms tick as the manual describes, is open (unverified).
4.9 DMR receive
The HR_C5000 doesn't report each received burst: there is no voice class report per burst as on the HR_C6000. With the reads fixed (4.2), a call shows as on air 2026-10-18, against an AnyTone AT-D878UV:
| Event | What the chip gives |
|---|---|
| Voice LC header, terminator | System interrupt 0x08 with 0x51 = 0x12 / 0x22 (type, class 2) and the LC in page 2 from 0x00 |
| Voice | Nothing per burst. The voice RAM changes at every tick, the other slot's ticks included |
| Late entry, information | 0x10 and 0x04: the LC of the superframe |
The manual's receive flow: with the CPU in charge of the vocoder (0x06 bit 0, which RadioFoundry sets), the CPU starts the decoder after the voice LC header or late entry interrupt by writing 0x22 bit 5 (DecodeStart), and stops it at the terminator or loss of sync (bit 4, DecodeStop). Without DecodeStart the voice RAM never holds received voice (0 of 324 frames checked at any bit offset); with it, the read at the tick after the call's slot holds that slot's burst.
The slot engine does this for chips with rx_voice_read (hrc_chip_t): a header, late entry or information report
starts the decoder (0x22 = 0x27, bits 2-0 keeping the 0x11 block's 0x07), the voice port is read at every tick, the
first read whose frames check (two of three C0 words with at most one error) locks the slot, and that slot's reads
go to the stack as bursts A to F. A read that hasn't changed since the last tick, or has no frame that checks, is a
lost burst; six in a row end the reception (0x22 = 0x17). The port is read at every tick, the other slot's too: a
silent talker's bursts are the same bytes every time, so only a port that stopped moving is a lost burst. A read
compared with this slot's last burst lost every burst of a silent call after the first (gotchas 1.11).
That the port moves at the other slot's tick on a direct channel with nothing on that slot comes from the on-air
table above; if it doesn't, the same bytes stay lost bursts. The diagnostics line tr c b same bad counts receptions,
bursts and lost reads by cause. The chip's voice class reports are ignored, and its 25 ms report delay doesn't count
as out of step.
So on this chip an empty drx trace shows only that no read was taken. It doesn't show that the chip went quiet.
A reception lost mid-call costs more than its gap (drx and log times of a bench run, two gaps alike;
gotchas 1.12):
| Step | Time after the last burst | What happens |
|---|---|---|
| Six lost reads | 360 ms | DecodeStop. Nothing is read until the next chip interrupt |
| 13 ticks without a burst | 420 ms | rs l: the receiver restarts, which drops the chip's sync |
| Hunting for sync | about 660 ms | The timeslot interrupts stop for about 240 ms, so the tick count falls behind the air |
| Late entry report | about 1 s | The chip has assembled the LC. The first read after it is burst F (the trace's slot counts fit that, and the bursts are numbered from A, so they stay one off until the call ends) |
A burst is in the port one tick after its slot's report, so the read at a tick holds the burst of the slot that started two ticks before. The engine takes this as fixed: the read is named, and given its slot counter, from the tick before it. It used to learn the relation from the first read that checked after a header, which with traffic on both slots can be the other call's (issue 279). With traffic on both slots both phases hold bursts that check, so the search for the call's phase is limited:
| Rule | Detail |
|---|---|
| A header started the read | Lock only the header's phase. A late entry report while searching drops that limit |
Repeater channel (rx_lock_slot 1 or 2) |
Lock only a phase the CACH TC names as the channel's slot, never one with the slot unknown. A header is kept unless the TC names its phase the other slot |
| A lock whose phase the TC names the other slot | Search again, without the header's phase. The burst index goes on counting one per frame, so a lock back on the same phase keeps A-F |
| The slot unknown while locked (a receiver restart) | The lock stays; its bursts carry no slot number until the TC locks again |
| The call's own bursts skipped while its slot is unknown | Counted, so the index stays A-F. After a quiet-period restart that costs the first bursts of a call (two in the host test, about 150 ms until the TC locks) |
All of these are host-tested (hrc5000Test test_two_slots: a call on each slot, a restart mid-call, a TC wrong
from the start, both slots heard with rx_lock_slot 0). On air (the SDR bench sending repeater bursts
with the CACH on both slots): the reads alternate as above (phase 0 TS1's frames, phase 1 TS2's), and the wanted call
plays with the other slot already talking (77 of 78 bursts; 0 before the fix).
The embedded LC isn't per slot (page 2, modem-hrc6000.md 5.2). On the same bench run, the other
call's LC was in page 2 at the wanted call's F: the core took it for a new call, and the wanted call went quiet with
the other call's source shown (23 of 78 bursts when the wanted call started first). The engine keeps each slot's
call LC and holds back an embedded LC that is the other slot's call's (the rules are in modem-hrc6000.md 5.2).
Host-tested (test_two_slots, the LC, alias and mixed LC cases: page 2 gets a call's LC or alias block the
tick after its F). On air (the SDR bench, a call on each slot, 78 bursts sent to the wanted slot, two
runs each):
| Case | Before | After |
|---|---|---|
| Wanted call first, the other starts beside it | 23 | 78, 78 (both slots) |
| Other call first, wanted call with its headers | 75-77, TS2 once 13-19 | 73-77 (both slots) |
| Wanted call joined mid-stream (no header) beside the other, TS1 | 0 played | 56-57 |
| Single call: headers, late entry, no header | 77-78, 38, 68 | the same |
Left as they were: both calls joined at once after a channel switch (0-20 bursts) and TS2 joined mid-stream beside a call (0) play as before.
On air with this (2026-10-18): calls are taken with the caller and talk group, and 393 frames reached the codec with
2 bit errors in all. The decoded audio is speech (peak 3521 of 32767) and streams to the chip (aud blk climbing),
but the speaker plays only a faint buzz (4.8). Leads, not checked: 0x0E (HPoutEn, bit 7; RadioFoundry writes 0x44, which
leaves it off), 0x37 (DAC gain), the I2S data line directions and sample rate (unknown 17).
4.10 Codec and audio routing
DMR receive audio plays with the LINEOUT values below (on air, 2026-10-19: our test, not authoritative). This section keeps the manual's facts apart from what our tests showed and what we only suspect.
From the user manual:
| Item | Fact |
|---|---|
| Audio pins | 2 HPOUT, 10 LINEOUT, 5-8 MIC2 / MIC1 differential inputs, 11 MICBIAS |
| External-codec I2S | 17 ADCDAT (in), 18 BCLK (out), 19 LRCK (out), 20 DACDAT (out): for an external codec |
| SPI ports | SPI0 = pins 53-57 (the configuration port; the chip is the slave). SPI1 = pins 33-36, "generic vocoder interface" (the voice port; slave). SPI2 = pins 37-43, "codec and vocoder interface" (C_CS and C_SCLK are outputs: the chip drives it) |
| SPI0 command, bits 2-0 | 000 configuration registers, 001 auxiliary table, 010 information data, 011 tone data, 100 CMX638, 101 AMBE3000; bit 7 = read |
| Tone data | the CPU writes the codec over SPI0 with command 0x03, address 0x00 and two bytes; the required write interval is missing from the manual's text |
| 0x06 | bits 7-6 vocoder interface (AMBE3000 / CMX638), 5 DMRFrom (1 = from SPI1), 4 VocoderFrom, 3 SPIFrom, 2 CodeCMode (0 internal, 1 external), 1 OpenMusic, 0 CPU vocoder control |
| MUX4 (codec source) | 0x06 bit 1 = 1: SPI >> codec (highest priority); else 0x10 bit 7 = 1 (FM): codec <> FM; else (DMR): codec <> vocoder |
| 0x0D | bit 7 CPU controls the codec, 6 suspend, 5 sleep, 4 Switch2 (K2), 3 anti-pop; reset 0xC8 |
| 0x0E | bit 7 HPoutEn, 6 HPMute, 5-4 HP volume (0 / 2 / 4 / 6 dB), 3 LineOutEn, 2 Mic1En, 1 Mic2En, 0 Switch1; reset 0x40 |
| 0x0F | bits 7-3 ADLinVol (+12 to -34.5 dB in 1.5 dB steps), 2-1 MicVol (0 / 6 / 12 / 20 dB) |
The driver writes 0x0E = 0x44 (headphone muted, line out off, mic 1 on) and 0x06 = 0x21 in DMR.
What the tests showed (on air, 2026-10-19; not authoritative). The full log is in the feature/md380_follow_up
pull request. Each test started from a power-on, and register reads confirmed the writes took effect.
- Tone data works in FM: a 1 kHz square wave (16-bit, MSB first, one sample every 125 us) played cleanly with 0x0E = 0xBD, which needs bit 2 (Mic1En) and bit 0 (Switch1) set.
- In DMR, no HPOUT-side setting played the MCU's samples, by I2S or as tone data. With 0x0E bit 2 set, DMR mode feeds the codec's input to the speaker (acoustic feedback). FM_SW and FM_MUTE both high plays the receiver's 4FSK as a buzz.
- 0x06 bit 2 = 1 (external codec) during a call stops the voice decoding until the next set-up.
- During a received call the chip sends the MCU a slow ramp near 0x55F8 over I2S (the mic converter's level), never the samples the MCU sent.
- A handheld transmitting a few feet away puts a TDMA buzz into the audio amplifier: faint pulsing in a test can be that, not a routing result.
The fix (our test, on air 2026-10-19; not authoritative). The speaker amplifier is fed from the codec's LINEOUT
(pin 10, 0x0E bit 3), not HPOUT. The tests above never had 0x0D bit 4 (Switch2) and 0x0E bit 3 (LineOutEn) set together
while a call played. hrc5000_speaker_voice() now writes, when a DMR PCM stream opens the route: 0x0D = 0x9C, 0x0E = 0x4C
(HPMute stays set, HPoutEn stays off, Mic1En on) and 0x37 = 0xC2, with 0x36 = 0x00 already written; it writes 0x0D = 0x8C and
0x0E = 0x44 back when the route closes, which should also end the buzz after a call. -DHRC5000_SPK_VOICE=0 leaves it out.
On an MD-380 (UHF, D002.032 codec) against an AT-D878UV on a 70 cm test frequency, TS1, with the build at 17d89001 plus the codec
move: received speech plays clear from the speaker and transmit is clear (one 10 s call: 498 frames, 10 bit errors, 3
concealed, 0 lost). Why both bits are needed together isn't in the manual (unverified). Not yet checked with it: the buzz after
a call or after our own transmit, the key beep in DMR and FM, and FM receive.
5. RF
5.1 Bands and power
One band per radio: UHF 400-480 MHz or VHF 136-174 MHz, 1 W and 5 W (unverified). RadioFoundry builds one image per band
(make BOARD=md380 BAND=uhf|vhf, default UHF); the image's band table refuses the other band. Which band a
radio is can't be read from the hardware by any method we know (the calibration's frequency list, 11.2, is a
candidate for a run-time check).
5.2 SKY72310 synthesizer
Skyworks SKY72310-11 (unverified as the part fitted to this radio). From the data sheet:
- Serial interface: 16-bit words, MSB first: 4 address bits then 12 data bits, latched when CS goes high. Write only.
- Registers: 0 main divider (N_reg = floor(N) - 32, 9 bits, 6..505 in fractional mode); 1 dividend MSB (10 bits); 2 dividend LSB (8 bits); 5 reference divider (R - 1, 5 bits); 6 phase detector gain (5 bits, 0.125-1 mA/rad); 7 power down and Mux_out (bit 0 full power down, 1 synthesizer power down, 2 integer-N, 3 10-bit modulator, 8:6 Mux_out source, 9 tristate); 8 modulation control; 9 modulation data; 14 delta-sigma (0x080, bit 5 signed mode).
- N = f_vco / f_pd with f_pd = f_osc / R <= 50 MHz; the 18-bit dividend is round(2^18 x frac(N)) in unsigned mode.
- The driver uses the signed mode (register 14 bit 5): N_reg = round(N) - 32 and a two's complement dividend for a fraction of -0.5..+0.5. Sent unsigned, every channel whose fraction was over one half came out one phase detector step (8.4 MHz) low with lock detect high: at 443.7, 444.0-444.5, 435 and 450 MHz the radio received nothing, at 440, 445 and 446 MHz it did (DM-32UV as the sender, 2026-10-20, issue 108). Either the unsigned-mode write doesn't take or the part runs signed; signed encoding is right in both cases on air.
- A new ratio takes effect on the dividend MSB write: order divider, LSB, MSB.
- Lock detect high = locked (on the Mux_out / LD pin, wired to PD10 (unverified)). The pin is the PS output, open collector (unverified): the MCU's pull-up gives the high (5.7).
drivers/rf/sky72310 implements this and tests/host/sky72310Test.c checks it against a model of the chip. The
board picks the smallest reference divider that keeps N in the fractional range (2 or 3 at 16.8 MHz), the phase
detector gain at its maximum (the loop filter was designed for a value that is unknown) and Mux_out = the
oscillator. The lock detect output's configuration isn't in our copy of the data sheet (unverified).
Carrier offset (issue 108). One MD-380 UHF measured about +1.9 kHz high at 448.700 MHz (an MMDVM hotspot's DMR BER test decoded it only from 448.70175 to 448.70200 MHz, BER 0.14-0.28 %; a DM-32UV measured the same way sat within 50 Hz of 448.700). Hotspot modems (ADF7021) decode only about +-250 Hz around the carrier, so that radio never woke a duplex hotspot. A second MD-380 (reference tune 0x72) measured 2-2.5 kHz high on a service monitor, on FM too.
Bench, 2026-10-28, the first radio (reference tune t = 0x51), with test builds that changed one control each. The receive error is the center of the band a DM-32UV's DMR is decoded in as the MD-380's VFO steps 250 Hz at a time; the transmit error is from the hotspot's MMDVMCal BER test:
| Build | DAC2 | 0x47 / 0x48 | Receive | Transmit |
|---|---|---|---|---|
| as in 4.5 | 0x744 | 0x2E / 0x00 | +2.0 kHz | +2.0 kHz |
| register sign as the HR_C6000's (t - 128) | 0x744 | 0xD1 / 0x03 | -0.75 kHz | -1.0 to -1.5 kHz |
| DAC2 fixed | 0x600 | 0x2E / 0x00 | -3.25 kHz | no clean decode |
| DAC2 fixed | 0xA00 | 0x2E / 0x00 | none within +-9 kHz (about +11 kHz) | no clean decode |
- Receive and transmit are off by the same amount, so the error is in the shared 16.8 MHz reference (about 4.5 ppm), not in the synthesizer's divide ratios.
- DAC2 moves the reference about 16.2 Hz a count at 448.7 MHz (36.1 ppb), higher = higher. The HR_C5000 register moves it about 31 Hz a step.
- The mapping in 4.5 lands this radio 2 kHz high, and flipping the register's sign overshoots to about -1 kHz.
Cause: CTC/DCS_OUT (PC7) held low [schematic, page 6]. The 16.8 MHz VCTCXO X601 clocks the SKY72310 and, through the Q605 tripler, the second LO, so receive and transmit share it. Its VC pin is driven by U603 (NJM2904) amplifier B:
| U603 B input | Signal | Path |
|---|---|---|
| Inverting (pin 6) | MOD2, from the HR_C5000 | R645 100K |
| Inverting (pin 6) | CTC/DCS_OUT, PC7 | R641 560K |
| Non-inverting (pin 5) | MOD2_BIAS, DAC2 (PA5) | R605 15K, R615 22K |
| Feedback | R644 100K, C678 100 pF | |
| Output (pin 7) | X601 VC (pin 1) | R646 4K7, C640 3.3 nF to ground |
Any DC on PC7 moves the reference, at -100K / 560K = -0.18 V per volt at the amplifier's output. RadioFoundry used to drive PC7 push-pull low whenever no tone was sent, which raises the non-inverting gain from 2 to 2.18: the same as DAC2 about 0.18 / 2.18 x 0x744 = 150 counts high, about +2.5 kHz at 448.7 MHz, in receive and transmit alike. That fits both radios (+2.0 and +2-2.5 kHz) in size and sign (this is an estimate: the bias divider past R615 isn't fully traced). PC7 now idles released (open drain, high), so the branch carries no current (unverified on a radio).
While a tone is sent the PWM drives PC7 push-pull around 50 % duty (about 1.65 V mean), so a channel with CTCSS or DCS may still transmit off by some hundreds of Hz (unverified; 6.4).
Before that fix, the user's trim corrected it: hal_radio_t.ref_trim (radio-features.md 6.1) adds ppb / 36.1 counts to
DAC2 (rounded), at once and at every rx(). A trim of -2000 Hz at 448.7 MHz (-4.5 ppm) put the first radio's transmit
within about 150 Hz (hotspot BER 0.05 %, four cold wake-ups on alternating slots). MD380_TX_OFFSET_HZ (radio_md380.c, a build define, default 0)
still shifts the transmit frequency alone; with the trim it isn't needed.
5.3 Frequency plan
Assumed from the MD-9600, which has the same TCXO and synthesizer and whose notes give the IF [radios.md MD-9600 section]:
| Item | Value | Tag |
|---|---|---|
| Reference | 16.8 MHz TCXO | , from the MD-9600 |
| First IF | 49.95 MHz | , from the MD-9600 |
| UHF receive | VCO = f - 49.95 MHz (LO below) | (unverified) |
| VHF receive | VCO = 2 x (f + 49.95 MHz), divided by 2 (LO above) | (unverified) |
| UHF transmit | VCO = f | (unverified) |
| VHF transmit | VCO = 2 x f | (unverified) |
radio_md380.c keeps the plan in MD380_TCXO_HZ, MD380_IF_HZ, MD380_RX_LO_ABOVE and MD380_VCO_MULT. A wrong
plan shows at once on the bench: no lock (diagnostics "pll UNLK") or a carrier off by a fixed amount (bring-up stage 4).
5.4 Receive chain and switching
Receive: VCOVCC_SW selects the RX VCO, the synthesizer is tuned, 5RC (PC9) powers the receiver, DAC1 sets the front-end tuning voltage from the calibration (11.2), the GT3136 IF goes to the HR_C5000 with DMR_SW (PA10) high, and FM_SW (PB2) is high in FM receive (6.1). W/N_SW (PA13) sets the IF filter for 12.5 or 25 kHz (unverified).
Transmit: 5RC off, DMR_SW low, VCOVCC_SW to the TX VCO, the synthesizer to the TX frequency and locked, then 5TC (PC5), DAC1 = APC drive, RF_APC_SW (PC4) on (unverified for the pins, order ours). FM TX waits for lock detect (5 ms at most) and refuses to key without it.
5.5 RSSI, noise, squelch
RSSI is an analog level on PB0 and the receiver's noise level ("BUSY") on PB1 (unverified). Neither curve is known (unverified). RadioFoundry reports rssi_dbm = -130 + raw x 90 / 4095 and noise = raw / 16 as placeholders, and shows both raw values on the diagnostics page so a bench session with a signal generator can fit them.
Measured on RadioFoundry (UHF, 446.000 MHz, raw ADC, a second radio nearby; 2026-10-14):
| RSSI (IN8) | BUSY (IN9) | |
|---|---|---|
| no signal | 690-760 | 1340-1620 |
| FM carrier, shown as -71 dBm | 2720-2732 | 39-81 |
BUSY falls with a signal (larger = more noise). With the USB programming cable in the accessory jack the receiver doesn't detect a signal at all (RSSI stays near the no-signal level): read these values with the cable out (a log line read back afterwards), or by ear.
Roaming reads this RSSI (the IF chain's level on an ADC) as the site signal (dmr-roaming.md section 3).
5.6 PA and power control
DAC1 is the APC reference, scaled from the calibration's power rows (11.2). Two levels, 1 W and 5 W. TX is refused without an accepted calibration table (no safe uncalibrated drive is known).
DMR: the PA follows the bursts. The RF TX interrupt keys it (APC_EN); a TIM7 one-shot drops it 30 ms later 3.4, and the slot engine's PA-off at the next timeslot interrupt stays as the fallback.
The radio has one synthesizer, so it can't receive while it is set up to transmit. The switch to TX (receiver off, the HR_C5000's transmit set, the TX VCO locked, TX_5V up) happens before the first burst, never at it: done in the RF TX interrupt, the first burst went out unlocked and a one-burst wake-up never reached a repeater. When:
| Start | Switch to TX |
|---|---|
| Own timing (simplex, the repeater wake-up) | at the modem's tx_begin, or at radio tx() if that comes later |
| Aligned to a repeater's slots | at the engine's tx_slot_found, two ticks before the first burst. Until then the receiver stays on: the engine needs the downlink's TC to find the slot (4, the CACH TC row) |
The synthesizer returns to RX at the engine's TX done, on every channel. On a split channel the radio then hears the
repeater's answer to its wake-up. On simplex the receive LO is 49.95 MHz from the carrier, and a confirmed data
response comes in the next frame, 30 ms after our last burst. Left on TX until tx_off() (the radio task), the radio
missed nearly every such response (a receive CRC error instead), and the TX VCO kept leaking the carrier about 34 dB
below the burst while TX_5V discharged (about 20 ms). rx() then keeps the synthesizer where TX done put it.
5.7 The 5C rail (SAVE)
PA8 ("SAVE") is the enable of U404 (XC6204B502, CE active high), the regulator for the 5C rail; 5C feeds the
SKY72310's 3.3 V regulator U602 (XC6204B332, CE tied to its input) and part of the receive chain [schematic,
page 4]. PA8 is an output, driven high. board.c drives it high at start-up.
Measured on a UHF MD-380 with no signal: the raw RSSI reading (ADC1 IN8) went from 246 with PA8 left as an input to 640-720 with it high, and the RSSI follows a received carrier. PD10 (PS, pulled up) reads 1 with SAVE low (the synthesizer unpowered, PS floating) and 0 with SAVE high (powered, not yet programmed: out of lock).
6. Audio
6.1 Paths
FM receive audio and DMR voice both reach the amplifier through the HR_C5000 in RadioFoundry's model: FM through its feed-through (0x36) behind FM_MUTE (PE13), DMR through its codec DAC; SPK_C (PB8) mutes the speaker, AFCO (PB9) turns the amplifier on (the routing (unverified)). Measured on RadioFoundry with a pin scan (the 16 levels of FM_MUTE, FM_SW, SPK_C and AFCO, monitor held on an FM channel): noise only with FM_MUTE high, FM_SW high, SPK_C low and AFCO high. With those levels a received FM call plays clearly. The GT3136's discriminator audio also reaches the MCU on PC3, which RadioFoundry doesn't use. Our tests (2026-10-19, not authoritative) found the HR_C5000's codec output reaching the speaker with FM_SW and FM_MUTE low; DMR voice through it doesn't work yet (4.10).
The volume knob sets the level in the analog path; the MCU only sees its position on PA0. The board therefore has no
CAP_VOLUME_POT and hal_power_t.volume() returns -1. There is no hal_radio_t.volume either: the MCU has no way to set
the speaker level (the HR_C5000's feed-through gain isn't used for it, and the knob acts after it), so the debug link's
volume operation (RADIO op 30) answers "unavailable" (radio-features.md 4.2).
6.2 I2S PCM
I2S3 full duplex, slave to the HR_C5000's codec: WS PA15, CK PC10, SD PC12 (chip -> MCU) and ext SD PC11 (MCU ->
chip), Philips, 16-bit samples in 32-bit channels, 8 kHz (unverified for the pins; format and directions copied from the
DM-1701 (unverified)). audio_md380.c is the DM-1701's driver. Which HR_C5000 port these lines reach (the external-codec
pins or SPI2, the codec / vocoder port) is open (4.10).
6.3 Microphone
Bias from MICPWR_SW (PA14), on whenever the radio runs (section 2.6). Mic level on PA3 for VOX (unverified).
6.4 Sub-audio
CTCSS / DCS are decoded in software from QT_DQT_IN (PA2) and generated as PWM on
CTC/DCS_OUT (PC7) (unverified). RadioFoundry does the same behind CONFIG_SUBTONE (on by default, 3.3 KB of
flash). CTCSS 100.0 Hz works on air both ways (tester, 2026-10-19). DCS receive works on air (D023N, 2026-11-02). DCS
transmit went out inverted (D023N decoded as D023I by another radio, 2026-11-02), so MD380_SUBTONE_TX_INVERT is on
by default. At MD380_SUBTONE_PERCENT 30 the deviation is 0.60 kHz peak for CTCSS 100.0 Hz and about 0.72 kHz for DCS
(SDR, 2026-11-02), inside the usual 0.5 to 0.75 kHz. Without CONFIG_SUBTONE, channels with a tone open on carrier and send no tone.
The FM TX tones (1750 Hz, roger beep) and APRS AFSK are not written (CONFIG_FM_TX_TONES, CONFIG_APRS_RX in
config.mk).
The DSP is chip independent (drivers/rf/subaudio, host-tested on synthetic signals); boards/md380/subtone_md380.c
and power_md380.c are the glue.
Receive. PA2 is one more rank of the ADC1 scan that power_md380.c already runs (IN2 after the temperature
sensor, 480-cycle sampling, ADC clock PCLK2 / 8). Seven ranks give a pass every 383 us, so the sub-audio is sampled at
PCLK2 / 8 / (7 x 492) = 2613 Hz (computed at run time). The DMA stream's half and full transfer interrupts (DMA2
Stream4, priority 7) hand each new sample to the detector; the ring grows from 8 to 16 scans, so the averaged RSSI
and battery readings cover 6 ms instead of 3 ms. The detector (subaudio_rx.h):
| CTCSS | DCS | |
|---|---|---|
| Method | Goertzel at the tone and 3 Hz either side, over overlapping 0.2 s windows, a result every 0.1 s | slicer with peak-tracked threshold, clock recovery on the edges, 23-bit register against the Golay word |
| Opens | tone holds 30 % of the AC power, beats both guard bins by 1 dB, amplitude at least 8 counts; two results in a row | word matches with at most 2 bit errors, twice 23 bits (+-1) apart |
| Closes | three bad results in a row (0.3 s); a 150 ms phase reversal at the end of a transmission is ridden out | no match for three words (0.5 s) |
| Tested | all 50 standard tones, neighbors rejected, +-1 Hz sender error, noise only for 60 s | all 104 codes both polarities, wrong code and polarity rejected, +-1 % bit rate |
status().tone_ok is the detector's verdict (true for a channel without an RX tone); the core debounces it and gates
the squelch (radio-features.md). An inverted code is the same bits inverted on the wire; if the analog path inverts
the waveform, MD380_SUBTONE_RX_INVERT flips it back.
Transmit. TIM3 CH2 on PC7 (AF2) is a 10-bit PWM at 17.6 kHz (the timer clock divided by 4 x 1024). Its update
interrupt (priority 6, running only while a tone is sent) computes a new duty every fourth period, 4.4 kHz: a sine
from a 65-point table for CTCSS, the 23-bit word at 134.4 baud with a light smoothing for DCS. The level ramps up over
20 ms; MD380_SUBTONE_PERCENT (default 30) sets the swing as a share of the duty range. tx() starts it after the PA is
on, tx_off() stops it, and the pin is released again (open drain, high): PC7 sums into the reference
oscillator's tuning, so a low there pulls the carrier (5.2). TIM3 and DMA2 Stream4's interrupt are the only
resources added (section 3). The DCS turn-off tail (radio-features.md 3.3a) switches the generator to a 134.4 Hz
sine at the same swing (subaudio_tx_tail, with the interrupt held off for the switch); not yet checked on a radio.
Unverified, check on a radio (docs/testing/md380-bringup.md 5 and 6):
- That PC7 reaches the modulator and where (summing into the HR_C5000's modulation input or the VCO line), and the deviation 30 % gives. CTCSS wants 0.5 to 0.75 kHz of deviation.
- The PWM's own filter: the carrier is 17.6 kHz and the tone at most 254.1 Hz, so any RC corner under about 1 kHz works.
- The signal on PA2: its level (
MD380_SUBTONE_MIN_AMP, default 8 counts of 4096) and its polarity for DCS. - That the ADC rate really is 2613 Hz (the 480-cycle sampling is the existing scan's) and that PA2 is not loaded by the longer sampling.
- A DCS transmission ends without the 134.4 Hz turn-off burst (not written); the receiving squelch tail may be longer than with a hardware decoder.
7. Display
160 x 128 color TFT on the FSMC 8-bit bus (NE1), command at 0x60000000 and data at 0x60040000 (A17 = RS on PD12),
chip select PD6, reset PD13: the DM-1701's wiring (unverified). The controller differs between radios and is detected (below). RadioFoundry drives it with drivers/display/st7735 with
safe-area insets of 1 px at the top and 2 px on the left (the case edge crowds them; right and bottom are close but usable; seen on the radio, 2026-10-16). Backlight: LAMP (PC6), TIM8 CH1
PWM at 1 kHz (unverified for the pin; PWM ours).
Panel type: display_md380.c picks the st7735 driver's type (drivers/display/st7735/st7735.h) at start-up, from
the controller's own ID and the factory byte (panel_md380.c):
- After a hardware reset, before any init, it reads the display ID: command 0x04 returns a dummy byte and three ID bytes, 0xDA, 0xDB and 0xDC a dummy byte and one each. Reads use a slower FSMC strobe than writes (address setup 15, data 60 HCLK cycles).
- It reads the factory byte: SPI flash security register 3, byte 0x1D. Many radios carry 0xFF there because the register was wiped, so an erased byte says nothing.
- The controller ID chooses the init, the byte (where programmed) only the orientation:
| Controller ID | Controller | Factory byte | Type |
|---|---|---|---|
00 91 xx |
GalaxyCore GC9xxx (the unlock-variant init) | 0xFF, unread, or bit 0 = 1 | 3 (normal) |
| bit 0 = 0 | 2 (flipped) | ||
7C xx xx |
Sitronix ST7735 class | 1 | 1 (rotated 180 degrees) |
| anything else | 0 (normal) | ||
| anything else | unknown | programmed | its low two bits |
| 0xFF or unread | 0 |
Bit 0 gives a GC9xxx's orientation in both stock layouts: stock D3.008 to D13.20 tested only bit 0 (0 = flipped GC9xxx, 1 = normal); D3.32 and later use all four types (2 = flipped, 3 = normal GC9xxx).
Stock sets the same color order for every type, so all are driven in RGB order (force_rgb).
One radio (2026-10-27): ID 00 91 02 from both commands on every start (probably a GC9102), factory byte 0xFF, so
type 3; picture right side up with correct colors. Its panel also showed a picture with the ST7735 init (type 0),
which is why a picture alone doesn't identify the controller. Untested: an ST7735 panel, and types 1 and 2.
| Knob or key | What |
|---|---|
MD380_PANEL_TYPE (build, MD380_DEFS) |
overrides the detected type |
MD380_PANEL_MADCTL_XOR (build) |
orientation bits flipped on top of the type's, default 0 |
display.panel_controller (debug link link section) |
gc9xxx, st7735 or unknown |
display.panel_source (same) |
what chose the type: id, factory, default or build |
display.panel_type (same) |
the type in use |
display.panel_id, display.panel_id_reg (same) |
the ID bytes in hex from 0x04 and from 0xDA-0xDC |
display.panel_raw (same) |
the factory byte in hex, none when it couldn't be read |
boards/md380/tests/md380PanelTest.c checks the rules. boards/md380/hil/test_md380_panel.py checks the radio:
both ID reads agree, the controller is known, the type follows the rules. An unknown controller fails it: add its ID
to panel_md380.c.
8. LEDs
Green (RX) on PE0, red (TX) on PE1, active high (unverified).
9. Keypad, side keys, channel switch, PTT
9.1 Matrix
Rows K1 PA6, K2 PD2, K3 PD3, driven high one at a time; columns are the LCD data lines D0-D7, read under the LCD bus lock as on the DM-1701 (unverified for the pins). The key positions are assumed to be the MD-UV380's, the nearest documented relative (unverified):
| Row | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 |
|---|---|---|---|---|---|---|---|---|
| K1 | 1 | 2 | 3 | 4 | 5 | 6 | 0 | * |
| K2 | Green (menu) | Up | Down | 7 | 8 | 9 | # | Red (back) |
| K3 | - | - | - | - | - | - | SK2 | SK1 |
The diagnostics page shows the raw row bits ("keys rr rr rr"), so one press of each key on a radio fills in this table.
9.2 Channel switch
A 16-position switch with four code lines ECN0-ECN3 on PE14, PE15, PB10, PB11 (unverified), not a quadrature encoder.
input_md380.c reads the code at every 10 ms scan and reports position changes as encoder steps (the shorter way
round across the stop). A code counts after three equal scans (30 ms): the contacts bounce. Pulled up, closed contacts
reading low (MD380_CH_ACTIVE_HIGH flips it). The code is a Gray code in its own line order. Read on RadioFoundry's diagnostics
page ("ch x", after the polarity), one detent at a time clockwise (bench, 2026-10-16):
| Position | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Code | 0 | 8 | A | 2 | 3 | B | 9 | 1 | 5 | D | F | 7 | 6 | E | C | 4 |
Each step changes one line, so a bouncing contact reads as the next or previous position, never a jump.
9.3 PTT
PTT on PE11 and the accessory jack's PTT on PE12, both active low with pull-ups (unverified).
10. Power and analog inputs
10.1 Power switch and latch
The volume knob carries the power switch. It doesn't switch the supply: POW_C (PA7) is a latch the firmware holds (unverified),
and the switch breaks the battery sense line (and the volume pot's supply). Measured on a UHF MD-380 with
the radio on USB: the raw battery reading (ADC1 IN1) is 3341-3351 (8.3 V) with the knob on and 3-4 with it off, at once;
the volume reading falls from about 130-155 to 2-4. The radio stays on with the knob off, and with switch_on() always
on it only showed the battery running down. switch_on() now reads the knob from the battery line (radio-features.md
5.5, core/radio/power_switch.c, as on the DM-1701), and power-off drops the latch.
10.2 Battery
7.4 V 2S Li-ion pack (unverified). ADC1 IN1 through a divider: RadioFoundry assumes 40.3 counts per 0.1 V
(MD380_BATT_COUNTS_X10) (unverified). Low 6.2 V and critical 6.0 V are RadioFoundry's choices, as on the DM-1701.
10.3 ADC scan
ADC1 scans IN0 (volume), IN1 (battery), IN3 (mic), IN8 (RSSI), IN9 (noise) and IN16 (temperature sensor) continuously at
480 cycles each into a DMA ring of 8 scans; readings are averages over the ring (power_md380.c).
11. Calibration
11.1 Location
512 bytes in the SPI flash's security registers 1 and 2 (flash addresses 0x1000 and 0x2000, 256 bytes each) 11.1. A third 256-byte block must not be read into a 512-byte buffer 11.1; RadioFoundry reads exactly two. Read only: RadioFoundry never writes them.
11.2 Layout
Single band, 9 points per row:
| Offset | Content |
|---|---|
| 0x009 | Reference oscillator tune t (section 4.5) |
| 0x010-0x018 | TX power, high (5 W), per point |
| 0x020-0x028 | TX power, low (1 W), per point |
| 0x030-0x038 | Receiver front-end tuning, per point |
| 0x130-0x138 / 0x140-0x148 | DMR modulation pair (0x45 / 0x46) |
| 0x170-0x178 / 0x180-0x188 | FM modulation pair |
| 0x1B0-0x1F7 | Frequency list: 9 entries of 8 bytes, two 4-byte BCD frequencies each, least significant byte first, 10 Hz units |
| 0x1B2-0x1B3 | Band low end: the upper half of the first frequency, 4 BCD digits in 100 kHz units (0x4010 = 401.0 MHz) |
| 0x1F2-0x1F3 | Band high end, the same from the last entry (0x4799 = 479.9 MHz) |
Only the even points (0, 2, 4, 6, 8) of a row are used. Each odd point is the mean of its neighbors, rounded down, whatever the flash holds there. That holds for every row from 0x010 to 0x180 except 0x0E0. The rest of the frequency list is not used: on the radios seen, the first frequencies run 401.035, 411.035 ... 471.035, 479.995 MHz, close to the points below but not the same.
calibration_md380.c accepts the table when the power rows are neither erased nor zero and low < high at every point,
and no tuning or modulation row is erased. If the band ends are not BCD, out of band or not ascending, it uses
400.0-470.0 MHz (UHF) or 136.0-174.0 MHz (VHF) instead and says so ("factory/grid").
11.3 Interpolation
The nine points split the band by halving, in 100 kHz units with integer halves: point 0 and 8 are the band ends, point 4 the middle of those, points 2 and 6 the middles of the halves, and the odd points the middles between. For 401.0-479.9 MHz: 4010, 4108, 4207, 4305, 4404, 4502, 4601, 4700, 4799.
The channel frequency is taken in 100 kHz units, truncated (TX frequency for the power rows, RX frequency for the tuning row). The value is linear between the two neighboring points, truncated toward the lower point's value, and clamped to the first and last point outside them. Power and tuning bytes go to the 12-bit DAC as (b x 255) >> 4 (b x 255 in its left-aligned register).
12. SPI flash
12.1 Device and bus
Winbond W25Q128FV, 16 MB, on SPI1 (PB3-PB5, AF5) with chip select PD7, mode 0, 4.5 MHz (unverified). Some
early VHF radios have a 1 MB part (unverified). flash_md380.c accepts any W25Q (JEDEC type 0x40) and probes the
size.
12.2 Map
On a 16 MB part RadioFoundry uses the DM-1701's map (settings 0x011000, journal 0x019000, open-firmware codeplug 0x000000 and 0x020000-0xE00000, native area 0xE00000, TFTP staging 0xF00000), so the open-firmware CPS works the same. The TYT codeplug at 0 is overwritten by the first open-firmware write: read it out with the manufacturer's CPS first if it matters (md380-migration.md does that, converts it with qdmr and writes it back). There is no calibration window in the main array (11.1). On a smaller part the core gets no areas: settings live in RAM and nothing is written. The CPS still reads the chip (area 1, up to its end) and the radio info gives its size, so a backup saves all of it; the Codeplug Editor and the flasher's channel step say that the radio stores no codeplug.
13. GPS
13.1 Module
The G models carry a GPS module whose NMEA output goes to the MCU's USART3 (unverified). 9600 baud is assumed (unverified).
13.2 Detection
The same board ships with and without the module, and PD8 (the module's power switch on G models) is listed as a
second flash chip select on others 13.7. So nothing drives PD8 until the module has been heard:
gps_md380.c enables USART3 RX on PD9 at start-up and listens for 5 s. One checksum-valid sentence (any talker, up to
82 characters plus CR LF; drivers/gps/nmea_line) makes the module present: PD8 is then held at the level it already
had, and every sentence goes to the APRS position source (aprs_nmea, CONFIG_APRS). Nothing heard: the UART is
switched off and board.gps_present() stays false, so board_has_gps() hides the GPS features. UART errors are
counted and cleared in the interrupt, so one framing error never stops reception ( 13.7).
14. Bootloader, firmware images, codec
14.1 Firmware file
TYT ships the application as an encrypted "OutSecurityBin" container, the same family as the DM-1701's. The supported file:
| Item | Value |
|---|---|
| File | MD-380-D2.32(AD).bin (TYT "Firmware 2.32") |
| SHA-256 | e1a1a11a84d9afd2d2ceed61be8395e32a8034785abd0be0236478f7d8609f43 |
| Size | 0xF2E00 bytes |
| Model string | "JST51" |
| Segment count word (0x7C) | 0xFFFFFFFF |
| Segment table | one entry: 0x0800C000, 0xF2C00 |
| Payload | 0x100 + 0xF2C00, then 0x100 trailing bytes |
rfoundry/stockfw.parse_container reads this single-segment form (count 0xFFFFFFFF = one segment in the first
table entry).
14.2 Encryption and key
The payload is XOR-encrypted with a 1024-byte key held in the bootloader (unverified). The key's SHA-256 is
5254139c6b596d9413d106e6b7c0685ec245d69555cb28df98f061d6b879c4cd and its first bytes 2E DF 40 B5 (tools/stm32_dfu.py md380 model). It can be derived from the D2.32 firmware file and checked by that hash: the plain column vote is wrong in a third of the columns, and a byte-pair refinement learned from the file
fixes them (codec-md380.md, "Key recovery"; rfoundry/stockfw.derive_key). A key file still works
(--key).
14.3 Bootloader and DFU
Entered by holding PTT and the upper side key while switching on (unverified). USB DFU 0483:DF11 with TYT's
non-standard commands; the same erase-by-sector and block-write protocol as the DM-1701's loader (unverified). Flash a RadioFoundry
image with tools/stm32_dfu.py --model md380 --key KEY rfoundry_md380.bin. The image is plain, starts at 0x0800C000, and the
tool encrypts it for the loader. rfoundry knows the board (boards/md380/board.toml: release stable, an update writes
sectors 3-7 and keeps the codec).
Switched on normally, the original firmware is also a DFU device with the same IDs. The flashers tell it from the bootloader by alt 0's layout and refuse it (hardware-dm1701.md 15.1). The MD-380's bootloader, read on a radio (2026-11-02): manufacturer and string 1 "AnyRoad Technology", product "Digital Radio in USB mode", serial "00000000010C", bcdDevice 0x0200, one DFU interface with two alternate settings:
| Alt | Layout |
|---|---|
| 0 | @Internal Flash /0x0800C000/01*016Kg,01*064Kg,07*128Kg (the application area only) |
| 1 | @SPI Flash Memory /0x00000000/16*064Kg |
boards/md380/board.toml declares alt 0 ([flash.dfu] layout), so the flashers accept only that device.
The same radio switched on normally, running a patched build of the original firmware (2026-11-02): 0483:DF11,
bcdDevice 0x0200, serial "00000000010C", alt 0 @Internal Flash /0x08000000/03*016Kg, alt 1 as in the bootloader.
String 1 and the manufacturer read "@00000004 : ffffffff" and the product "Patched MD380", which the patch may have
changed; the unpatched firmware's strings are not known (?). Alt 0 is the MD-9600's normal-mode layout, so either rule
refuses it.
14.4 The codec
The AMBE+2 codec is inside the manufacturer's application: the same library as the
DM-1701's, at other addresses. In the D002.032 file: code window 0x08050C44+0x10884, table window 0x0808FC64+0xAFF8 and
four small table windows in sectors 10 and 11. The merge moves the code to 0x080E0000, keeps the table windows at
their original addresses and writes a descriptor at 0x080FFF80; the codec runs in place from there. The D35.14 (MD-380)
and S35.14 (MD-390) files work too: they carry the MD-UV380 family's codec, placed next to its tables in sectors 9 and
10 (untested on the radio). Entry points,
calling convention, state sizes, the placement and the extraction recipe are in codec-md380.md.
stockfw.PROFILES has the profiles, the codec maps (rfoundry/codec_maps/md380/, one per build) the rewrites,
boards/md380/memory.ld keeps the application out of sectors 8-11, vocoder_md380.c checks and hosts the codec, and
make BOARD=md380 merges it when the firmware file is in codecs/. The debug link reports the codec as
md380-d2032-s11; a build from before the move reports md380-d2032. Without the codec merged the radio is FM only.
15. USB
OTG_FS device on PA11 / PA12 (unverified). RadioFoundry enumerates with the STM32F4 radios' IDs (platform/stm32f4/platform.h) and the product
name "MD-380" (usb_md380.c); the bootloader's DFU device is separate (14.3).
The open-firmware CPS shim reports radio type 7 and serves the MCU ROM from 0x08000000, 1 MB (boards/md380/board.c,
cps-protocol.md 2.2).
16. Variants
| Radio | Difference |
|---|---|
| MD-380 | the base board |
| MD-390 | the same electronics in an IP67 case |
| Retevis RT3 | a rebadged MD-380 |
| MD-380G / MD-390G / RT3 GPS | GPS module on USART3, power on PD8 (section 13) |
| UHF / VHF | separate boards: different RF parts and VCOs, one RadioFoundry image each |
| 1 MB flash | some early VHF radios; RadioFoundry stores nothing on them |
(unverified) for all rows.
17. Unknowns that need a radio
Each with how to settle it; the stage numbers are md380-bringup.md's.
| # | Unknown | How to settle it on a radio |
|---|---|---|
| 1 | HSE crystal or external clock | Stage 1: the radio boots on HSE (USB enumerates); if only bypass works, record it |
| 2 | Display controller, orientation, colors | Stage 2: the type is detected (section 7); if the boot screen is wrong, note the display.panel_* keys and try MD380_PANEL_TYPE / MD380_PANEL_MADCTL_XOR |
| 3 | Key matrix positions, side keys on K3 | Stage 3: press each key, read "keys" on the diagnostics page, correct section 9.1 and input_md380.c |
| 4 | Channel switch code polarity and order | Settled 2026-10-16: a Gray code, table in 9.2 |
| 5 | Volume knob on PA0 vs the TX LED | Stage 3: "vol" on the diagnostics page follows the knob, and the red LED lights on PE1 |
| 6 | Battery divider | Stage 3: compare "bat raw" against a meter; set MD380_BATT_COUNTS_X10 |
| 7 | Power switch wiring (direct or sensed) | Stage 3: switch off with the knob: does the radio die at once? |
| 8 | Flash size and JEDEC ID | Stage 3: "flash" line |
| 9 | Calibration layout and scaling | Stage 4: "cal" must say factory; read both security registers over the open-firmware CPS protocol (area 10, which md380_cps_security_read serves) and compare the rows with what the manufacturer's CPS shows |
| 10 | Frequency plan (TCXO, IF, LO side, VCO multiplier per band) | Stage 4: "pll lock" on every channel; an SDR on a 25 kHz-spaced pair of channels shows the receive LO leak and TX carrier |
| 11 | SKY72310 bus timing and lock detect polarity | Stage 4: "pll lock" with a correct carrier; a scope on PD10 while retuning |
| 12 | W/N_SW and DMR_SW polarities (FM_SW, FM_MUTE, SPK_C and AFCO measured, 6.1) | Stage 4/5: receive a known FM signal; invert one pin's level at a time in radio_md380.c (one rebuild each, batched) until IF, audio and bandwidth are right |
| 13 | RSSI and noise curves | Stage 5: signal generator steps, record "rssi" and "busy", fit status() |
| 14 | HR_C5000 configuration port timing, page mapping, reg 0x10 read-back | Stage 4: "C5000 10=" shows a sane value after init |
| 15 | FM TX deviation and modulation balance on the C5000 | Stage 6: TX into a dummy load and an SDR: deviation and frequency against a reference |
| 16 | APC scaling (table x 16) and the two power levels | Stage 6: power meter on 1 W and 5 W at band edges and center |
| 17 | I2S data line directions and format | Stage 7: DMR receive with the codec merged (after 14.4); a silent or noisy speaker means a direction or channel problem |
| 18 | RF TX interrupt lead time and the 30 ms PA hold | Stage 8: scope the PA supply (PC5) against the burst on an SDR |
| 19 | VHF I / Q swap (0x01 = 0xF0) | Stage 8 on a VHF radio: DMR receive and transmit against a known radio |
| 20 | GPS UART pin, baud rate, power pin polarity | Stage 9: on a G model "gps yes" with counts rising; on a non-G model "gps no" and no flash errors |
| 21 | Codec windows in D002.032 | Located and verified on a host (14.4); stage 7 checks voice on the radio |
| 22 | Whether the bootloader key can be derived from a firmware file | Done (14.2): derived from D2.32 and checked by its hash |
18. Datasheets
Skyworks SKY72310-11 data sheet 203639L (https://www.skyworksinc.com/Products/PLLs-Synthesizers-VCOs/SKY72310-11), STMicroelectronics STM32F405xx data sheet and RM0090, Hangzhou Hongrui HR_C5000 user manual (Chinese: pins, SPI ports, configuration registers, audio routing), and gotchas.md 2.6, 3.3, 3.4, 5.2, 11.1, 13.7.
19. Sizes
Flash: the table in feature-matrix.md and the per-feature figures in feature-matrix.md. Measured 2026-10-19 with LTO: default UHF image 320,236 B of 475,136 B (464 KB), 107,120 B of 131,068 B SRAM, 59,640 B of 64 KB CCM; maximal 327,064 B flash.
CONFIG_NET_WEBCTL follows CONFIG_NET_HTTP_GZIP on this radio (boards/md380/config.mk): web control's files,
stored uncompressed, need more flash than the default image leaves free. With gzip off the radio serves the plain
status page.
20. Facts moved from the shared specs
- Settings and storage: on a 16 MB part the core's map is settings 0x011000 (32 KB, 8 sectors), journal 0x019000, open-firmware EEPROM 0x000000, open-firmware flash 0x020000 up to 0xE00000, native area 0xE00000 (1 MB), TFTP staging 0xF00000 (256 KB). There is no calibration window.
- Roaming reads the IF chain level on an ADC as the site signal (dmr-roaming.md 3).
- USB: the STM32F405 OTG_FS has three IN endpoints besides EP0, so serial and network are alternatives, not a composite (usb-network.md).
board_rf_safepins: APC_EN and TX_5V (5TC) low, VCO_SEL high (receive VCO), APC DAC (DAC1 channel 1) 0.- The G models use USART3 RX on PD9, so there is no UART for the fault trace. Store and restart cause of the crash log are unverified on the radio.
- The volume knob's switch only breaks the battery sense line: raw ADC reading 3345 at 8.3 V, 3 with the knob off.
- The DCS turn-off tail is the generator's 134.4 Hz sine (
subaudio_tx_tail), not yet measured. There is no FM TX tone path yet, so AFSK TX is not written (boards/md380/config.mk). Without a calibration the board refuses to transmit. - Release:
[status] release = "stable", both band images (rfoundry-md380-<version>.binUHF,rfoundry-md380-vhf-<version>.bin). Application sectors 3-7 (464 KB), codec sectors 8-11 (D002.032: 8, 10, 11; 35.14: 9-11), so an update keeps the codec. Profilemd380-d2032-s11(a build from before the move reportsmd380-d2032).tools/md380_migrate.pyflashes it and keeps the codeplug.