BKPT LabsDOCS/VIEWALYZER RECORDER/RECORDER API REFERENCE VIEWALYZER · FIRMWARE RECORDER
VIEWALYZER · RECORDER API

Recorder API reference

Everything a firmware author calls, from core/ViewAlyzer.h. Session functions are always compiled. Every other function belongs to a VA_TRACE_* category (see Configuration); when the category is off, the function becomes a macro that discards its arguments without evaluating them, so calls keep compiling and cost nothing. With VA_ENABLED=0 the whole API compiles out the same way.

Identifiers are uint8_t. Each registry (user traces, user events, GPIOs, heap gauges) has its own id space and its own size knob (VA_MAX_USER_TRACES and friends, default 16). Names are copied into a fixed wire field of VA_MAX_TASK_NAME_LEN bytes (16 including the terminator); a string literal that does not fit is a compile-time error through VA_ASSERT_NAME_FITS, a pointer is truncated at run time.

Session

VA_Init

void VA_Init(uint32_t cpu_freq);                                          /* DWT_CYCCNT timestamps (default) */
void VA_Init(uint32_t cpu_freq, VA_TimestampFn ts_fn, uint32_t tick_hz);  /* VA_TIMESTAMP_SOURCE=CUSTOM_TIMER */

Starts the recorder: resets every registry, probes the timestamp source, emits the sync marker and the setup bundle. Call it once, after clocks and the transport are up and before registering objects or user channels: those registrations are reset by initialization. Register a custom transport callback before VA_Init.

cpu_freq is the core clock in Hz; the host derives seconds from it. With the default DWT source the cycle counter is the timebase. With CUSTOM_TIMER the second form takes your tick function (typedef uint32_t (*VA_TimestampFn)(void), returning a free-running counter of VA_TIMER_BITS bits) and its rate; the timer must already be running.

VA_Init checks that the source is counting and refuses to start when it is not, reporting ERR:TS_* to the host: a DWT that a vendor left out, or a timer that was not started, shows up at the first capture instead of as an empty trace.

/* Cortex-M0+ with no DWT: a 16-bit timer as the timebase (VA_TIMER_BITS 16) */
static uint32_t tick_read(void) { return TIM3->CNT; }

TIM3->PSC = SystemCoreClock / 1000000u - 1u;   /* 1 MHz */
TIM3->ARR = 0xFFFF;
TIM3->CR1 |= TIM_CR1_CEN;
VA_Init(SystemCoreClock, tick_read, 1000000u);

VA_TickOverflowCheck

void VA_TickOverflowCheck(void);

Periodic recorder service. Every timestamped event automatically handles a counter rollover. Call VA_TickOverflowCheck() during quiet periods too, so no complete timer wrap goes unobserved.

With RAM metadata enabled, this call also services host attachment requests when no events are emitted and sends small time/sequence checkpoints during quiet periods. No full setup bundle is retransmitted. Trace events can also service pending attachment requests.

With metadata disabled, the call services due setup bundles and RAM-buffer host-attach detection. ITM builds also use it to re-arm a stalled output pipe.

Call it from the main loop or an application thread with interrupts enabled. Neither the recorder nor the RTOS adapters install a background service task. A 100 ms interval is suitable for typical 32-bit timers; use, for example, 10 ms for a 16-bit timer at 1 MHz, which wraps every 65.5 ms. Account for scheduling delays: the longest gap must be shorter than the timer wrap period. Frequent service also lets an idle target answer attachment requests promptly.

VA_EmitSetupBundle

void VA_EmitSetupBundle(void);

With VA_METADATA=1, services a pending host checkpoint request; it does not send a full setup bundle. Applications do not need to call it for attachment.

With VA_METADATA=0, emits the sync marker and all setup packets. Automatic retransmission uses VA_AUTO_SETUP_INTERVAL_MS (default 2000 ms), serviced by VA_TickOverflowCheck() or an eligible thread-context recorder call. The RAM transport can also request a bundle when a host begins draining the ring. Call this function manually only when you need an immediate full bundle.

VA_Drain

void VA_Drain(void);

Buffered mode only (VA_TRANSPORT_BUFFERED=1): offers at most VA_DRAIN_MAX_BYTES (default 256) to the transport. Call repeatedly from an unmasked idle task or main loop. Unaccepted bytes remain queued; a partial write ends the call. ISR, interrupt-masked and overlapping calls do nothing. Direct builds do nothing.

The application schedules this call; neither RTOS adapter installs a drain worker. It and the custom send callback use the calling task's stack. See Scheduling the drain for the FreeRTOS idle hook, Zephyr thread and stack-sizing guidance. The default RAM_BUFFER transport does not need this call.

VA_GetBufferStats

VA_BufferStats_t VA_GetBufferStats(void);

Returns queuedBytes, droppedPackets and droppedBytes. Loss totals count whole packets rejected by the staging ring, saturate at UINT32_MAX, and reset only at VA_Init. Reads and drains do not clear them. Custom byte counts include COBS framing. Direct and disabled builds return zeros. Stop producers and drains before reinitializing the recorder.

VA_IsInit

bool VA_IsInit(void);

True once VA_Init succeeded.

VA_SnapshotFreeze

void VA_SnapshotFreeze(void);

Stops writes to the post-mortem ring so its current window survives whatever happens next. Call it from your fault or assert handler; any context is safe, and everything logged before the call is already in the ring. Meaningful with VA_RAMBUF_MODE_WRAP or VA_SNAPSHOT, a no-op macro otherwise, so the call always compiles. The host reads the frozen window with viewalyzer-cli snapshot (see Transports and snapshots).

void HardFault_Handler(void)
{
    VA_SnapshotFreeze();     /* the last N KB of events survive the reset */
    fault_log_and_reset();
}

VA_RegisterTransportSend

void VA_RegisterTransportSend(VA_TransportSendFn sendFn);   /* VA_TRANSPORT=CUSTOM_TRANSPORT only */
typedef uint32_t (*VA_TransportSendFn)(const uint8_t *data, uint32_t length);

Custom transport builds: registers the writer for the COBS-framed byte stream. Declared only in those builds, so a call in another build is a compile error rather than a silent no-op. Call it before VA_Init.

Return the number of leading bytes accepted, from zero through length, without blocking. Accepted bytes must be sent or copied into owned storage before returning; do not retain the temporary pointer for DMA. Buffered calls contain arbitrary portions of the byte stream and retry unaccepted bytes. Direct calls contain a whole COBS frame, require all-or-none acceptance, and can run with interrupts masked. Direct callbacks must not call recorder APIs or traced services. Replace the callback only while producers and drains are stopped; a NULL callback accepts no bytes.

Values

Category VA_TRACE_USER_VALUES. Register a channel, then log to it; the type tells the host how to draw the channel.

void VA_RegisterUserTrace(uint8_t id, const char *name, VA_UserTraceType_t type);
void VA_LogTrace(uint8_t id, int32_t value);
void VA_LogTraceFloat(uint8_t id, float value);
void VA_LogToggle(uint8_t id, bool state);        /* TOGGLE_LOW / TOGGLE_HIGH */
TYPE DRAWN AS
VA_USER_TYPE_GRAPHA line over time (the default choice for a sensor or a computed value)
VA_USER_TYPE_BARA bar per sample
VA_USER_TYPE_GAUGEThe latest value on a dial
VA_USER_TYPE_COUNTERA monotonic count
VA_USER_TYPE_TABLEA row per sample
VA_USER_TYPE_HISTOGRAMA distribution of the values
VA_USER_TYPE_TOGGLEA two-level lane; log with VA_LogToggle
VA_USER_TYPE_ISRNot a value: names an interrupt for the ISR lanes (below)
VA_RegisterUserTrace(TRACE_TEMP, "Temp C x10", VA_USER_TYPE_GRAPH);
VA_RegisterUserTrace(TRACE_RMS,  "RMS",        VA_USER_TYPE_GRAPH);
VA_RegisterUserTrace(TRACE_LED,  "LED",        VA_USER_TYPE_TOGGLE);

VA_LogTrace(TRACE_TEMP, temp_c_x10);          /* int32 */
VA_LogTraceFloat(TRACE_RMS, rms);             /* float, drawn with its fraction */
VA_LogToggle(TRACE_LED, TOGGLE_HIGH);

A value is one 11-byte packet on the wire (type, sequence, id, 32-bit timestamp, 32-bit value), so a channel logged at 1 kHz costs 11 KB/s of transport. Log at the rate the signal deserves, not at the loop rate.

Event spans

Category VA_TRACE_USER_EVENTS. A named span with a start and an end, drawn as a block on its own lane; nested spans are fine.

void VA_RegisterUserEvent(uint8_t id, const char *name);
void VA_LogEvent(uint8_t id, bool state);         /* USER_EVENT_START / USER_EVENT_END */
#define VA_EVENT_START(id)
#define VA_EVENT_END(id)
VA_RegisterUserEvent(EVENT_FFT, "FFT");

VA_EVENT_START(EVENT_FFT);
fft_run(buf);
VA_EVENT_END(EVENT_FFT);

Markers and strings

Category VA_TRACE_STRINGS.

void VA_LogString(uint8_t id, const char *msg);

Sends a text marker (up to VA_MAX_LOG_STRING_LEN bytes, 100 by default; longer strings are cut). The id groups markers into a lane. The packet is built on the stack, so keep the call out of threads with a tiny stack.

VA_LogString(1, "System started");
VA_LogString(2, "MedPrio mutex timeout");

Interrupts

Category VA_TRACE_ISRS. Wrap the body of a handler; the host draws an ISR lane with entry and exit, preemption of tasks, and per-ISR statistics. Names come through VA_RegisterUserTrace with the VA_USER_TYPE_ISR type. Two ids are predefined: VA_ISR_ID_SYSTICK (1) and VA_ISR_ID_PENDSV (2); pick your own for the rest.

void VA_LogISRStart(uint8_t isrId);
void VA_LogISREnd(uint8_t isrId);
#define ISR_ID_ADC 3

VA_RegisterUserTrace(ISR_ID_ADC, "ADC1_2", VA_USER_TYPE_ISR);

void SysTick_Handler(void)
{
    VA_LogISRStart(VA_ISR_ID_SYSTICK);
    HAL_IncTick();
    VA_LogISREnd(VA_ISR_ID_SYSTICK);
}

void ADC1_2_IRQHandler(void)
{
    VA_LogISRStart(ISR_ID_ADC);
    adc_drain();
    VA_LogISREnd(ISR_ID_ADC);
}

On FreeRTOS and Zephyr the kernel's own interrupts (the tick, PendSV) are not traced automatically either; add the two calls to the handlers you care about.

GPIO, counters, heap gauge

void VA_RegisterGPIO(uint8_t id, const char *name);       /* VA_TRACE_GPIO */
void VA_LogGPIO(uint8_t id, bool state);

void VA_LogCounter(uint8_t id, uint32_t value);           /* VA_TRACE_COUNTERS */

void VA_RegisterHeap(uint8_t id, const char *name, uint32_t totalSize);   /* VA_TRACE_HEAP_METRICS */
void VA_LogHeap(uint8_t id, uint32_t usedBytes);

VA_LogGPIO gives a pin its own two-level lane without a logic analyser. VA_LogCounter is for monotonic counts (packets, errors). The heap gauge is manual and works on bare metal too: register the pool with its capacity, then report the bytes in use whenever it changes; the host draws usage against capacity. On FreeRTOS and Zephyr the kernel heaps are traced by the adapter without these calls.

VA_RegisterHeap(1, "AppPool", 4096u);
VA_LogHeap(1, pool_used_bytes());

Zephyr only

void VA_Zephyr_RegisterExistingThreads(void);   /* zephyr/VA_Adapter_Zephyr.h */

Emits the setup packets for the threads that were created before VA_Init ran (the main thread, the idle thread, drivers' workers). Call it once, right after VA_Init.

What you never call

ViewAlyzer.h also declares the adapter entry points (va_taskswitchedin, va_logQueueObject*, va_logWorkArm, va_logTimerArm, va_logHeapAlloc and the rest). The FreeRTOS hook header and the Zephyr tracing shim call these from the kernel; application code does not.

Compile-time guards you may hit

  • VA_DEVICE_HEADER undefined: the only mandatory knob (-DVA_DEVICE_HEADER=stm32g474xx.h, bare token).
  • DWT or ITM selected on a Cortex-M0/M0+/M23: those cores have neither; use VA_TIMESTAMP_SOURCE=CUSTOM_TIMER and a RAM buffer or RTT.
  • VA_BUFFER_SIZE not a power of two, or buffered mode combined with RAM_BUFFER.
  • Both FreeRTOS hook headers included, or FreeRTOS SMP (not supported).
  • VA_RegisterTransportSend in a non-custom build.