Files
uv-k5-v3-emulator/harness/sim_record.c
T
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

132 lines
3.2 KiB
C

#include "sim_record.h"
#include <stdio.h>
#include <string.h>
#include "sim_clock.h"
#define SIM_MAX_EVENTS 4096
#define SIM_MAX_TEXT 1024
static SIM_Event_t s_events[SIM_MAX_EVENTS];
static unsigned s_event_count;
static char s_text[SIM_MAX_TEXT];
static unsigned s_text_len;
static bool s_verbose;
void SIM_RecordReset(void)
{
s_event_count = 0;
s_text_len = 0;
s_text[0] = '\0';
}
void SIM_RecordSetVerbose(bool verbose)
{
s_verbose = verbose;
}
static void push(SIM_EventKind_t kind, char ch)
{
if (s_event_count >= SIM_MAX_EVENTS)
return;
s_events[s_event_count].kind = kind;
s_events[s_event_count].at_ms = SIM_ClockNow();
s_events[s_event_count].ch = ch;
s_event_count++;
if (s_verbose) {
const char *name = kind == SIM_EV_CARRIER_ON ? "carrier on"
: kind == SIM_EV_CARRIER_OFF ? "carrier off"
: "char";
if (kind == SIM_EV_CHAR)
fprintf(stderr, "%6u ms %s '%c'\n", SIM_ClockNow(), name, ch);
else
fprintf(stderr, "%6u ms %s\n", SIM_ClockNow(), name);
}
}
void SIM_RecordCarrier(bool on)
{
push(on ? SIM_EV_CARRIER_ON : SIM_EV_CARRIER_OFF, 0);
}
void SIM_RecordChar(char ch)
{
push(SIM_EV_CHAR, ch);
if (s_text_len + 1 < SIM_MAX_TEXT) {
s_text[s_text_len++] = ch;
s_text[s_text_len] = '\0';
}
}
void SIM_RecordDebug(const char *text, unsigned int size)
{
if (s_verbose && text != NULL && size > 0)
fprintf(stderr, "%6u ms [dbg] %.*s", SIM_ClockNow(), (int)size, text);
}
const char *SIM_RecordedText(void)
{
return s_text;
}
// Walks the event list pairing each CARRIER_ON with the following CARRIER_OFF.
// Returns the duration of element `index`, or 0 when it does not exist.
static bool element_span(unsigned index, uint32_t *start, uint32_t *end)
{
unsigned seen = 0;
for (unsigned i = 0; i < s_event_count; i++) {
if (s_events[i].kind != SIM_EV_CARRIER_ON)
continue;
for (unsigned j = i + 1; j < s_event_count; j++) {
if (s_events[j].kind == SIM_EV_CARRIER_ON)
break; // unterminated; treat as incomplete
if (s_events[j].kind == SIM_EV_CARRIER_OFF) {
if (seen == index) {
*start = s_events[i].at_ms;
*end = s_events[j].at_ms;
return true;
}
seen++;
break;
}
}
}
return false;
}
unsigned int SIM_RecordedElementCount(void)
{
unsigned n = 0;
uint32_t a, b;
while (element_span(n, &a, &b))
n++;
return n;
}
uint32_t SIM_RecordedElementMs(unsigned int index)
{
uint32_t start, end;
return element_span(index, &start, &end) ? end - start : 0;
}
uint32_t SIM_RecordedGapMs(unsigned int index)
{
uint32_t s0, e0, s1, e1;
if (!element_span(index, &s0, &e0) || !element_span(index + 1, &s1, &e1))
return 0;
return s1 - e0;
}
unsigned int SIM_RecordedEventCount(void)
{
return s_event_count;
}
const SIM_Event_t *SIM_RecordedEvent(unsigned int index)
{
return index < s_event_count ? &s_events[index] : NULL;
}