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:
- 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).
- 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.
- 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_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.