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 any VA_Register* call**: registrations made earlier are wiped and never re-emitted.

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);

The recorder's housekeeping call. Your code calls it; nothing inside the recorder or the adapters does, and nothing ever did. It does three things:

  1. Keeps the 64-bit time extension in step. Timestamps on the wire are 32-bit; the recorder extends them by noticing when the counter wraps, and it can only notice a wrap by reading the counter at least once per wrap period. Every logged event does that read, so a busy system needs nothing else. Across a quiet gap longer than the wrap period, time is silently lost unless something reads the counter: this call does. Wrap periods: a 32-bit DWT at 170 MHz wraps every 25 s (call every 1 to 10 s); a 16-bit custom timer at 1 MHz wraps every 65 ms (call every pass of the main loop, or from a periodic timer).
  2. Re-arms a stalled ITM pipe (ITM builds only, at most once a second), so a host that starts draining the SWO pin after the firmware started is picked up.
  3. Services a pending setup bundle from thread context. On an RTOS every kernel hook runs under masked interrupts, where the bundle cannot be sent; a host that attaches late may otherwise never receive the task and object names.

Call it from the main loop on bare metal, from a housekeeping thread on an RTOS (any low-priority loop is fine), or from a periodic timer. It is cheap and safe to call far more often than needed.

Warning: the FreeRTOS and Zephyr adapters need this call as much as bare metal does. Without it, a capture that starts after boot can show unnamed tasks, and a trace with idle stretches longer than the wrap period jumps in time.

VA_EmitSetupBundle

void VA_EmitSetupBundle(void);

Re-emits the sync marker and every setup packet (names, types, ranges, OS info). The recorder does this by itself every VA_AUTO_SETUP_INTERVAL_MS (2000 ms) so a host can attach at any moment; call it yourself when you want the bundle right now, for instance right after registering a batch of objects at run time.

VA_Drain

void VA_Drain(void);

Buffered mode only (VA_TRANSPORT_BUFFERED=1): flushes the RAM ring to the wire. Call it from the idle task or the main loop. A no-op macro in every other build.

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 void (*VA_TransportSendFn)(const uint8_t *data, uint32_t length);

Custom transport builds: hands the recorder the function that ships a COBS-framed packet. 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.

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.