Files
uv-k5-v3-emulator/assets
mckero c0a09827ed UV-K5 V3 emulator: QEMU machine for the PY32F071
Adds a QEMU machine for the Puya PY32F071 (Cortex-M0+) so Quansheng UV-K5 V3
firmware can run on a PC. The firmware boots to its main loop in about five
seconds and the LCD contents are readable.

Register layouts come from the vendor CMSIS header shipped with the firmware
rather than guesswork. Modelled: RCC, GPIO, ADC, both SPI controllers, DMA1 and
the PY25Q16 flash; everything else answers through a logging catch-all, which is
how the next thing worth modelling gets identified.

Seven things had to be right before it would boot, each found by watching where
the firmware stopped: flash aliased at the application offset, clock ready bits,
self-clearing ADC calibration, SPI transfer flags, DMA-driven flash reads,
SysTick poll acceleration, and the bit-banged transceiver bus idling low.

SysTick needs explanation. SYSTICK_DelayUs polls the counter and accumulates
differences; under emulation a register read costs far more relative to guest
time, so a measured 120 ms delay would have taken about 7.7 hours. Lowering the
clock does not help because the bottleneck is loop iterations, not counter speed.
Reporting a value that runs ahead of the real counter does, via a new poll-boost
property on SysTick. Guest time therefore runs fast during delays: fine for
exercising menus and control flow, wrong for judging signal timing.

Also includes the host build of the CW timing chain (harness, stubs, shim,
tests), which compiles app/cwkeyer.c and app/cwmacro.c unmodified against stub
drivers with a virtual clock and scripted paddle input.

Known gap: keypad rows reach the firmware's scan and KEYBOARD_Poll returns the
right key code, but the UI does not react yet.

Not modelled, and not intended to be: radio behaviour. The transceiver chip has
no public datasheet, so keying envelopes and emissions need real hardware.
2026-08-27 14:59:21 +01:00
..

Simulator assets

calibration.bin — 512 bytes

A calibration dump from the user's own radio. Loaded into the virtual SPI flash at physical 0x010000, which is where App/driver/eeprom_compat.c maps the 512-byte calibration block (_MK_MAPPING(0x010000, 0x00B000, 0x00B200)).

Without it the firmware takes error branches in the frequency and power paths, so the machine would boot into a state that does not represent the real radio.

Verified contents

Checked against the offsets SETTINGS_LoadCalibration() actually reads:

Offset Field Value Sanity
0x000–0x0BF per-band TX power curves 10 ascending bytes per row, 0xFF padding plausible power steps
0x0C0 gEEPROM_RSSI_CALIB[3] 110, 120, 130, 140 ascending
0x0C8 gEEPROM_RSSI_CALIB[0] 180, 190, 200, 210 ascending
0x140 gBatteryCalibration (6×u16) 1426, 1978, 2125, 2155, 2271, 2600 strictly ascending, matches a Li-ion curve

47% of the file is 0xFF, consistent with a real dump: each power row uses 10 of its 16 bytes and the remainder is erased flash.

The file name the user supplied said "not necessarily accurate"; the structure above is self-consistent, so it is being treated as usable. If the emulated radio later shows implausible power or battery readings, this is the first thing to re-dump.

How to refresh

Export from a real radio with UV Studio (Dump Calib), then replace this file.