DM-32UV: hosting the AMBE+2 codec

Rule: the radio runs exactly the codec its own vendor firmware shipped. The user's machine extracts it from a firmware file the user supplies. No codec bytes go in this repository or in released binaries. The shared mechanism is in codec-placement.md; this page is the format of the DM-32UV's codec in those files.

Section Contents
Vendor firmware files The two files the facts below are measured on
Where the codec is Addresses, lengths and hashes in each file
Entry points, position dependence, calling convention What the codec needs from its host
Vendor firmware versions The other builds and their addresses
C. Codec state init tables The init sequence
Hosting How RadioFoundry places it

Vendor firmware files

file size sha256 header
DM32.01.01.049.bin 869688 03107b6f600e7fa95105af8324cff349c0f17fe30db249c14d11e79baa8da97a 256-byte BFUV32-V2 ... END
DM32.00.01.046HRVocoder.bin 890516 f8484c13c994e920649d7b7bc2b98a98a76cfd0836865e5995a00627fc0c2887 same

The files aren't encrypted. The image is linked at 0x0300C000 (XIP GD25Q80 at 0x03000000), so file offset = address - 0x0300C000 + 0x100.

Where the codec is

The firmware's start-up copies a RAM code block from flash to RAM 0x10000 (049: flash 0x030BFBA0, length 0x1F098; 046: flash 0x030BF904, length 0x24490). The codec is part of that block. Its data tables are in the modem RAM image, which the start-up copies to 0x18000800 (049: from flash 0x03071F80, 0x43FC bytes; 046: from 0x03071D60, 0x439C bytes).

049 046 HRVocoder
codec code, RAM (run address) 0x00016CDC..0x0002B2DC 0x0001B838..0x0002F914
length 0x14600 0x140DC
flash address / file offset 0x030C687C / 0xBA97C 0x030CB13C / 0xBF23C
first two words (signature) 0x152514D8, 0x74C776CD same
sha256 of the carved code 355ca906...6ceeafd1 02743cf4...5c5e64fb
other code inside the range (not codec, never called by it) 0x17270..0x1732C, 0x1CDBC..0x1D4C8, 0x1E8BC..0x1FE64 0x21AEC..0x230A8
codec tables, modem RAM 0x18000EA8..0x18003E20 0x18000E48..0x18003DC0
tables: flash address / file offset / length 0x03072628 / 0x66728 / 0x2F78 0x030723A8 / 0x664A8 / 0x2F78
sha256 of the carved tables 10684c33...4eb0b0e1 cd5a4d27...3a270ef803

The table window holds two codec object groups (049: 0x18000EA8..0x18001198 and 0x180013C4..0x18003E20) with other data between them. Copying the whole window verbatim is simpler than splitting it, and it does no harm.

Entry points (offsets from the codec start)

function offset 049 RAM 046 RAM prologue
decode +0x0000 0x16CDC 0x1B838 push r4-r11, r15
encode +0x0260 0x16F3C 0x1BA98 push r4-r11, r15
encode ECC 049 +0x1C60, 046 +0x1E34 0x1893C 0x1D66C
decoder init +0x00B4 0x16D90 0x1B8EC
decoder set (2 params) +0x0084 0x16D60 0x1B8BC
encoder init +0x0314 0x16FF0 0x1BB4C
encoder set (2 params) +0x02E4 0x16FC0 0x1BB1C
FEC/ECC state init 049 +0x1BF8, 046 +0x1DCC 0x188D4 0x1D604

The ECC and FEC init offsets differ between versions because 049 links extra code before them.

Self-containment and position dependence

  • 152 functions are reachable from decode, encode and ECC. They make no calls outside the codec range and have no literals pointing at app RAM, flash or peripherals. The only indirect calls are jsr r12 and jsr r2, and those use pointers the codec stores itself.
  • The codec does reference the modem RAM tables above (049: 58 distinct addresses at 107 sites).
  • Branches and calls are PC-relative. Exactly 3 literal-pool words hold absolute codec addresses. In 049 they are the pool words at RAM 0x187B0, 0x187B4 and 0x19B68, holding 0x19540, 0x22754 and 0x26CAC. 046 has the same 3.
  • So the code runs unmodified only at its run address, with the tables at their modem RAM addresses. To move the contiguous block by a constant delta, patch those 3 words; RadioFoundry does (Hosting, step 2). To move the tables, you would patch every modem RAM literal.

Calling convention

C-SKY V2 ABI: arguments in r0-r3, then the stack; r4-r11 and r15 are callee-saved; the return value is in r0.

  • decode(r0 = wave out (80 samples), r1 = 80, r2 = bit buffer (49 x u16), r3, sp+0, sp+4, sp+8 = decoder state)
  • encode(r0 = bit buffer, r1, r2 = wave in, r3 = 80, sp+0, sp+4 = 0/1 (first/second half), sp+8, sp+12 = encoder state)
  • ECC(r0 = r1 = bit buffer, r2, r3 = ECC state)

The vendor wrapper passes values it keeps in the state blocks:

  • decode: r3 = D[0x1166], sp+0 = a flags word.
  • encode: r1 = E[0x1736], sp+0 = the flags word (a literal 0x800 in three calls, E[0x172E] = 0x1840 in the other three), sp+8 = E[0x173A] (0x2000 after init).
  • ECC: r2 = E[0x1738].

The encode flags word is a set of feature bits, one value on every call of a transmission (0x800, or 0x1840 = 0x1000 | 0x800 | 0x40). "0x1840 on the first call, then 0x800" behaves as 0x800: no noise suppression and no tone frames.

platform/hrc7000/vocoder.c (the HR_C7000 boards) passes one word on every call from drivers/vocoder/ambe2_flags.h: 0x800, plus 0x40 for noise reduction, 0x1000 for tone frames and 0x2000 for AGC (0x1840 by default).

Vendor firmware versions

Every DM-32UV build from 01.01.032 to 01.01.049 carries the same codec: the same length (0x14600), the same run address (0x16CDC), the same entry points (the 049 column above) and byte-identical tables (10684c33...). Inside the codec ranges the code differs only in its table pointers. The rest of the differences fall in the other code that lies inside the window but isn't codec. Each version links the codec to its own flash addresses, so tools/hrc7000_codec.py keys each file by its SHA-256 and finds the codec in a program flash read by content, then by address.

Build Code, flash Tables, flash Tables, modem RAM Same code as
01.01.032 0x030C74B4 0x03071908 0x18000EA8
01.01.037 0x030C5F08 0x03071D68 0x18000EA8 039, 040
01.01.039 0x030C6414 0x03072228 0x18000EA8 037, 040
01.01.040 0x030C6424 0x03072238 0x18000EA8 037, 039
01.01.046 0x030C65B0 0x03072378 0x18000EA8 01.02.046
01.02.046 0x030C6590 0x03072358 0x18000EA8 01.01.046
01.01.047 0x030C68B8 0x03072658 0x18000EA8 049
01.L01.048 0x030C4444 0x03070468 0x18000EA8
01.01.049 0x030C687C 0x03072628 0x18000EA8 047
01.01.049 NRF and fanti builds 0x030C7914, 0x030C7954 0x03072E80, 0x03072EC0 0x18000EB0 each other

Rules for the addresses:

Value Rule
Run address The three self-referencing pool words (section "Self-containment and position dependence") give it. Every build has 0x16CDC
Table RAM address Every table literal in the codec ranges moves by the same constant against 049: none for 032 to 049, +8 for the NRF and fanti builds (81 literals).
Table window The 64-byte head CRC (TABLES_HEAD_CRC), then the SHA-256

The NRF and fanti tables end at 0x18003E28, 8 bytes above the 049 window, so the DM-32UV linker script reserves modem SRAM up to 0x18003E28. The slot's version tag is the build number (32 to 49), and the host reports it as dm32-<three digits>.

C. Codec state init tables

The codec has no precomputed state arrays in the file: the state is built at run time with the codec's own init functions. The 049 init routine (RAM 0x162A8) uses a decoder state D (049: 0x456C0) and an encoder state E (049: 0x48160), with the ECC state at E+0x16E0 and the encode bit buffer at E+0x1898. It runs these steps in order:

  1. It sets these 16-bit parameters:

    field value
    E[0x172C] 0x1840
    E[0x173A] 0x2000
    E[0x173C] 128
    E[0x173E] -20
    E[0x1740] -22
    E[0x1742] 20
    E[0x1744] -10
    E[0x1746] -40
    E[0x1748] 0x4000
    E[0x174A] 3276
    E[0x174C] 3276
    E[0x174E] 1310
    E[0x1750] 10
    E[0x1752] 20
    D[0x1152] 0
    D[0x116A] 128
    D[0x116C] -20
  2. It calls the init functions:

    • encoder init(E, 1, 1)
    • FEC init(E+0x16E0, 1)
    • encoder set(E, E[0x173C], E[0x173E])
    • decoder init(D, 1)
    • FEC init(D+0xFD0, 1)
  3. It runs one decode with r0 = D+0x12AE, r2 = D+0x1354, r3 = 0, sp = {8, 0, D}.

  4. It calls decoder set(D, D[0x116A], D[0x116C]).

046 does the same (D = 0x4AAA8, E = 0x4D548). RadioFoundry calls these functions (platform/hrc7000/vocoder.c). The state blocks can live anywhere, because they're passed by pointer. Minimum sizes from the accesses are about D >= 0x1400 and E >= 0x1900; the exact sizes are open.

Hosting

How RadioFoundry runs it (codec-placement.md, "HR_C7000 radios"):

  1. At the user's machine, tools/hrc7000_codec.py (or the web flasher) checks the sha256 of the vendor firmware file, carves the code and the table window, and writes them unchanged into the codec slot at 0x030E8000..0x03100000, after a 128-byte version 2 header and before the relocation list: the offsets of the 3 self-referencing words, from the build's codec map. The slot is load-only data in XIP flash after the application.
  2. At boot, platform/hrc7000/vocoder.c copies the code to the board's codec window at the top of SRAM (0x43A00..0x58000), adds (window start - run address) to each listed word, checks the first two words, and copies the tables to their modem RAM address (049: 0x18000EA8).
  3. Memory map (boards/dm32/memory.ld): one RAM region for RadioFoundry from 0x10000 to the codec window. The modem RAM window 0x18000E48..0x18003E28 (the union of the builds' tables) is reserved.
  4. Versions: every DM-32UV build with a codec map (six maps in tools/rfoundry/rfoundry/codec_maps/dm32/) runs in the same window, since only the 3 words depend on the run address.

Open items

  • On the radio (2026-10-19) the 049 codec in the window decodes on-air speech with every frame (234 of 234, 78 of 78 bursts) (measured). Audio quality against the vendor firmware has not been compared.
  • Exact sizes of the D/E state blocks; whether the codec writes to its modem RAM tables (the boot copy covers both cases).

View the source of this page