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.
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(); /* thread context, once a second is plenty */
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
Note: the housekeeping call matters more on an RTOS than on bare metal: every kernel hook runs under masked interrupts, where the setup bundle cannot be sent, so a host that attaches after boot relies on this call to receive the task names. Any low-priority task with a loop will do.
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.
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. On Cortex-M0/M0+/M23 boards the RAM buffer is the default transport and 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.
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.- Task names missing on an RTOS:
VA_TickOverflowCheck()is not being called from thread context, or the capture started beforeVA_Initand the bundle has not been re-emitted yet (2 s by default). lost_eventsin the capture summary: the transport cannot keep up; a biggerVA_RAMBUF_SIZE, a lower logging rate, or fewer categories.