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_GRAPH | A line over time (the default choice for a sensor or a computed value) |
VA_USER_TYPE_BAR | A bar per sample |
VA_USER_TYPE_GAUGE | The latest value on a dial |
VA_USER_TYPE_COUNTER | A monotonic count |
VA_USER_TYPE_TABLE | A row per sample |
VA_USER_TYPE_HISTOGRAM | A distribution of the values |
VA_USER_TYPE_TOGGLE | A two-level lane; log with VA_LogToggle |
VA_USER_TYPE_ISR | Not 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_HEADERundefined: 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_TIMERand a RAM buffer or RTT. VA_BUFFER_SIZEnot a power of two, or buffered mode combined withRAM_BUFFER.- Both FreeRTOS hook headers included, or FreeRTOS SMP (not supported).
VA_RegisterTransportSendin a non-custom build.