Integrating the recorder
One source file for the core, one more for an RTOS adapter, a handful of defines. The steps per adapter, with the build fragments taken from the example projects.
Bare metal
Compile core/ViewAlyzer.c (plus core/viewalyzer_cobs.c only for CUSTOM_TRANSPORT), add core/ to the include path, define the knobs:
set(VA_TRANSPORT RAM_BUFFER CACHE STRING "ARM_ITM | JLINK_RTT | RAM_BUFFER | CUSTOM_TRANSPORT")
target_sources(${PROJECT_NAME} PRIVATE ${VA_RECORDER}/core/ViewAlyzer.c)
target_include_directories(${PROJECT_NAME} PRIVATE ${VA_RECORDER}/core)
add_definitions(
-DVA_ENABLED=1
-DVA_RTOS_SELECT=VA_RTOS_NONE
-DVA_DEVICE_HEADER=stm32g4xx.h
-DVA_TRANSPORT=${VA_TRANSPORT})
There is no tick hook to install and no VA_Tick(): the recorder timestamps from the DWT cycle counter (or the timer you give it) and needs only the housekeeping call in your main loop.
#include "ViewAlyzer.h"
enum { TRACE_SINE = 1, TRACE_TICK = 2, TRACE_LED = 3, TRACE_WORKLOAD = 4, TRACE_LOG = 5 };
enum { EVENT_WORK = 1 };
void SysTick_Handler(void)
{
VA_LogISRStart(VA_ISR_ID_SYSTICK);
HAL_IncTick();
VA_LogISREnd(VA_ISR_ID_SYSTICK);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
VA_Init(SystemCoreClock);
VA_RegisterUserTrace(TRACE_SINE, "Sine Wave", VA_USER_TYPE_GRAPH);
VA_RegisterUserTrace(TRACE_TICK, "Tick Counter", VA_USER_TYPE_GRAPH);
VA_RegisterUserTrace(TRACE_LED, "LED Toggle", VA_USER_TYPE_TOGGLE);
VA_RegisterUserTrace(TRACE_WORKLOAD, "Workload", VA_USER_TYPE_BAR);
VA_RegisterUserEvent(EVENT_WORK, "Work Block");
for (uint32_t step = 0;; step++) {
bool traced = (step & 255u) == 0u; /* trace one pass in 256 */
if (traced) VA_EVENT_START(EVENT_WORK);
int32_t env = process_sample(step);
if (traced) VA_EVENT_END(EVENT_WORK);
if (traced) {
VA_LogTrace(TRACE_SINE, sine_lookup(step));
VA_LogTrace(TRACE_TICK, (int32_t) HAL_GetTick());
VA_LogTrace(TRACE_WORKLOAD, env);
}
if (led_changed) VA_LogToggle(TRACE_LED, led_state);
VA_TickOverflowCheck();
}
}
This is baremetal/Nucleo_G474_VA in the examples repository; it ships with nucleo_g474_rambuf.vacf and nucleo_g474_swo.vacf for the two transports it builds.
Cortex-M0/M0+/M23 (no DWT, no ITM): VA_TIMESTAMP_SOURCE=CUSTOM_TIMER with a free-running timer, and RAM_BUFFER or RTT. baremetal/Nucleo_C031_VA is the whole recipe in four lines of va_config.h (CUSTOM_TIMER, VA_TIMER_BITS 16, RAM_BUFFER, VA_RAMBUF_SIZE 4096u) and a 1 MHz TIM3 handed to VA_Init.
If you enable VA_TRANSPORT_BUFFERED=1 for ITM, RTT or custom output, also call VA_Drain() in the main loop. The default RAM transport is drained by the host. See Scheduling the drain.
FreeRTOS
Compile core/ViewAlyzer.c and freertos/VA_Adapter_FreeRTOS.c. Both core/ and freertos/ must be on the include path of the kernel as well as the application, and the defines must reach every translation unit, so use a directory-wide add_definitions():
add_definitions(
-DVA_ENABLED=1
-DVA_RTOS_SELECT=VA_RTOS_FREERTOS
-DVA_DEVICE_HEADER=stm32g474xx.h
-DVA_CONFIG_HEADER=va_config.h
-DVA_TRANSPORT=RAM_BUFFER -DVA_RAMBUF_SIZE=16384u
-DVA_MAX_TASKS=24)
include_directories(${VA_RECORDER}/core ${VA_RECORDER}/freertos)
target_sources(${PROJECT_NAME} PRIVATE
${VA_RECORDER}/core/ViewAlyzer.c
${VA_RECORDER}/freertos/VA_Adapter_FreeRTOS.c)
In FreeRTOSConfig.h, enable the trace facility and include the hook header at the very end, one of the two depending on the kernel version:
#define configUSE_TRACE_FACILITY 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1 /* stack usage lanes */
#define INCLUDE_xSemaphoreGetMutexHolder 1 /* mutex contention */
/* last lines of FreeRTOSConfig.h */
#include "ViewAlyzerFreeRTOSHook_V10_4_Plus.h" /* FreeRTOS 10.4 and later, 11.x included */
/* #include "ViewAlyzerFreeRTOSHook.h" */ /* FreeRTOS 9.0 to 10.3 */
The hook header installs the trace* macros for task switches, creation and deletion, queues, semaphores, mutexes, notifications, event groups, timers, delays and suspends, malloc/free and tickless idle (configUSE_TICKLESS_IDLE=1 for the sleep lanes). Including both headers is a compile error; SMP kernels are refused.
Then, in your code:
int main(void)
{
HAL_Init();
SystemClock_Config();
VA_Init(SystemCoreClock); /* tasks created earlier are registered lazily */
VA_RegisterUserTrace(42, "Sine Wave", VA_USER_TYPE_GRAPH);
VA_RegisterUserEvent(45, "Custom Event");
VA_RegisterHeap(1, "AppPool", 4096u);
xTaskCreate(housekeeping_task, "house", 256, NULL, tskIDLE_PRIORITY + 1, NULL);
vTaskStartScheduler();
}
static void housekeeping_task(void *arg)
{
for (;;) {
VA_TickOverflowCheck(); /* 32-bit timer service */
vTaskDelay(pdMS_TO_TICKS(100));
}
}
Note: keep periodic service running during quiet periods for timer rollover and idle attachment requests. The example uses a 32-bit timer; a 16-bit timer at 1 MHz needs a shorter interval, such as 10 ms. Default RAM metadata capture requires the matching ELF and does not need VA_EmitSetupBundle(). With metadata disabled, this same service call handles setup retransmission.
freertos/Nucleo_G474_VA is the reference: va_config.h with an opt-in category list, nucleo_g474_freertos_rambuf.vacf and nucleo_g474_freertos_swo.vacf.
For optional buffered output, arrange regular VA_Drain() calls through an application task or FreeRTOS's vApplicationIdleHook(). The trace-hook headers do not install that idle callback. Budget draining and the custom send callback on the chosen task's stack. The drain guide shows the hook configuration and explains when to use a service task instead.
Zephyr
The recorder is a Zephyr module; no CMake edits in your application. Point ZEPHYR_EXTRA_MODULES (or a west.yml project) at the recorder's zephyr/ directory and enable it in prj.conf:
CONFIG_VIEWALYZER=y
CONFIG_TRACING_USER=y # required: the module rides Zephyr's tracing_user hooks
CONFIG_VIEWALYZER_TRANSPORT_RAMBUF=y # or _ITM / _RTT
CONFIG_VIEWALYZER_MAX_TASKS=28
CONFIG_VIEWALYZER_MAX_TASK_NAME_LEN=24
CONFIG_VIEWALYZER depends on CPU_CORTEX_M and selects TRACING. The RAM buffer is the default transport on every supported core. On Cortex-M0/M0+/M23, CONFIG_VIEWALYZER_TS_CUSTOM_TIMER with CONFIG_VIEWALYZER_TIMER_BITS selects the timebase. Every recorder knob has a CONFIG_VIEWALYZER_* twin; Configuration lists them.
#include "ViewAlyzer.h"
#include "VA_Adapter_Zephyr.h"
int main(void)
{
k_msleep(1000); /* optional: time to attach a debugger */
VA_Init(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC);
VA_Zephyr_RegisterExistingThreads(); /* main, idle, the workers created before us */
VA_RegisterUserTrace(43, "Tick Counter", VA_USER_TYPE_GRAPH);
VA_RegisterUserEvent(45, "Calc Event");
VA_LogString(1, "System started");
while (1) {
VA_LogTrace(43, k_uptime_get_32());
VA_TickOverflowCheck();
k_msleep(50);
}
}
Threads, mutexes, semaphores, message queues, k_event, k_work (submit, schedule, cancel), k_timer and the kernel heaps are traced by the module through the tracing hooks; nothing to add. zephyr/STM32-Zephyr-VA-Demo in the examples repository exercises all of it on a Nucleo-G474RE, with .vacf files for SWO, RAM buffer and RTT.
With CONFIG_VIEWALYZER_BUFFERED=y for ITM or RTT, also call VA_Drain() from an application thread. The module does not create a drain worker. Size that thread's stack for its application work and draining; choose a service interval that can keep up with the trace rate. See Scheduling the drain. The default RAM transport needs no drain call.
Checking the integration
- The host's capture log prints
Control block at 0x... (resolved from ELF symbol _VA_RAMBUF)for a RAM buffer, or the ITM/RTT stream statistics; a capture that ends with 0 events is an error (empty_capture) and names the likely cause. VA_Initrefusing to start shows asERR:TS_*on the host: the timestamp source is not counting.- RAM metadata attachment fails: supply the matching ELF, check metadata capacity, and keep
VA_TickOverflowCheck()running while the target is quiet. With metadata disabled, missing names can indicate that the periodic setup bundle has not been serviced yet. - Buffered capture is empty or stops: confirm an unmasked thread/main loop calls
VA_Drain()regularly, the sink accepts bytes, and the caller has enough stack. CheckVA_GetBufferStats()for queued bytes and ring losses. lost_eventsin the capture summary: the transport cannot keep up; a biggerVA_RAMBUF_SIZE, a lower logging rate, or fewer categories.