feat(esp_trace): add example to demonstrate external lib integration

This commit is contained in:
Erhan Kurubas
2026-06-01 09:25:47 +03:00
parent fbf57aeba1
commit 2d2d200ebd
18 changed files with 885 additions and 4 deletions
@@ -0,0 +1,23 @@
set(src_dirs
"src"
)
set(include_dirs
"include"
)
set(priv_requires
"esp_trace"
)
if(CONFIG_ESP_TRACE_LIB_EXTERNAL)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs}
PRIV_REQUIRES ${priv_requires}
WHOLE_ARCHIVE TRUE)
idf_component_get_property(freertos_lib freertos COMPONENT_LIB)
target_include_directories(${freertos_lib} INTERFACE ${include_dirs})
else()
idf_component_register(PRIV_REQUIRES ${priv_requires})
endif()
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "trace_FreeRTOS.h"
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* External Trace Library - FreeRTOS trace hooks
*
* This header is pulled in (via esp_trace_freertos_impl.h) from FreeRTOSConfig.h
* Do NOT include any FreeRTOS header here — keep this file restricted to:
* - forward declarations of the C functions called by the macros below,
* - the trace*() macro definitions themselves.
*
* Macros are allowed to reference FreeRTOS identifiers (pxTCB, xTicksToWait, ...)
* by name — they are resolved later, when the macro is expanded inside the
* FreeRTOS kernel .c files where those names are already in scope.
*
* Only the trace*() macros this example actually hooks are defined here.
* Anything left undefined falls back to FreeRTOS's own empty default (see
* the #ifndef guards in freertos/FreeRTOS.h).
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Forward declaration so we can pass an encoder pointer from the adapter to
* init_trace_lib() without pulling in esp_trace_port_encoder.h. */
typedef struct esp_trace_encoder esp_trace_encoder_t;
void init_trace_lib(esp_trace_encoder_t *enc);
void trace_lib_start(void);
void trace_lib_stop(void);
/* Hook implementations — defined in trace_FreeRTOS.c.
* Kept void*-typed to avoid depending on FreeRTOS types in this header. */
void trace_lib_task_switched_in(void);
void trace_lib_task_create(void *pxNewTCB);
void trace_lib_isr_enter(uint32_t irq);
void trace_lib_isr_exit(void);
void trace_lib_isr_exit_to_scheduler(void);
void trace_lib_queue_send(void *pxQueue);
void trace_lib_queue_receive(void *pxQueue);
void trace_lib_queue_create(void *pxNewQueue);
#ifdef __cplusplus
}
#endif
/* ------------------------------------------------------------------ *
* Active hooks (forwarded to trace_FreeRTOS.c)
* ------------------------------------------------------------------ */
#define traceTASK_SWITCHED_IN() trace_lib_task_switched_in()
#define traceTASK_CREATE(pxNewTCB) trace_lib_task_create(pxNewTCB)
#define traceISR_ENTER(n) trace_lib_isr_enter(n)
#define traceISR_EXIT() trace_lib_isr_exit()
#define traceISR_EXIT_TO_SCHEDULER() trace_lib_isr_exit_to_scheduler()
#define traceQUEUE_SEND(pxQueue) trace_lib_queue_send(pxQueue)
#define traceQUEUE_RECEIVE(pxQueue) trace_lib_queue_receive(pxQueue)
#define traceQUEUE_CREATE(pxNewQueue) trace_lib_queue_create(pxNewQueue)
/* All other trace*() macros fall back to FreeRTOS's default empty defines
* (#ifndef ... #define ... empty in freertos/FreeRTOS.h); no need to list
* them here. Add a mapping above when you want to hook one. */
@@ -0,0 +1,141 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_trace_types.h"
#include "esp_trace_registry.h"
#include "esp_trace_port_encoder.h"
#include "esp_trace_port_transport.h"
#include "esp_trace_util.h"
#include "trace_FreeRTOS.h"
typedef struct {
esp_trace_lock_t lock;
} ext_trace_lib_ctx_t;
/**
* @brief Initializes ext_trace_lib encoder.
* This function is called for each core.
* Adapter implementations do NOT need their own multi-core protection. Core does it for them.
*
* @param enc Pointer to the encoder structure. Must not be NULL.
* @param enc_cfg Pointer to the encoder configuration. Can be NULL for defaults.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*/
static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg)
{
(void)enc_cfg;
// Ensure the encoder is initialized only once unless something todo for both cores
static bool initialized = false;
if (!enc) {
return ESP_ERR_INVALID_ARG;
}
if (initialized) {
return ESP_OK;
}
ext_trace_lib_ctx_t *ctx = heap_caps_calloc(1, sizeof(*ctx),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (!ctx) {
return ESP_ERR_NO_MEM;
}
esp_trace_lock_init(&ctx->lock);
enc->ctx = ctx;
init_trace_lib(enc);
initialized = true;
return ESP_OK;
}
static esp_err_t write(esp_trace_encoder_t *enc, const void *data, size_t size, uint32_t tmo)
{
if (!enc || !data || size == 0) {
return ESP_ERR_INVALID_ARG;
}
if (!enc->tp || !enc->tp->vt->write) {
return ESP_ERR_NOT_SUPPORTED;
}
return enc->tp->vt->write(enc->tp, data, size, tmo);
}
static esp_err_t start(esp_trace_encoder_t *enc)
{
(void)enc;
trace_lib_start();
return ESP_OK;
}
static esp_err_t stop(esp_trace_encoder_t *enc)
{
(void)enc;
trace_lib_stop();
return ESP_OK;
}
static esp_err_t flush(esp_trace_encoder_t *enc)
{
if (!enc || !enc->tp || !enc->tp->vt->flush_nolock) {
return ESP_ERR_NOT_SUPPORTED;
}
return enc->tp->vt->flush_nolock(enc->tp);
}
/**
* @brief Panic handler
*
* Called during system panic to finalize encoder state.
*
* @param enc Pointer to the encoder structure. Must not be NULL.
* @param info Panic information
*/
static void panic_handler(esp_trace_encoder_t *enc, const void *info)
{
(void)info;
flush(enc);
}
static unsigned int take_lock(esp_trace_encoder_t *enc, uint32_t tmo_us)
{
if (!enc || !enc->ctx) {
return 0;
}
ext_trace_lib_ctx_t *ctx = enc->ctx;
esp_trace_lock_take(&ctx->lock, tmo_us);
return ctx->lock.int_state;
}
static void give_lock(esp_trace_encoder_t *enc, unsigned int int_state)
{
if (!enc || !enc->ctx) {
return;
}
ext_trace_lib_ctx_t *ctx = enc->ctx;
ctx->lock.int_state = int_state;
esp_trace_lock_give(&ctx->lock);
}
static const esp_trace_encoder_vtable_t s_ext_trace_lib_vt = {
.init = init,
.write = write,
.panic_handler = panic_handler,
.start = start,
.stop = stop,
.flush = flush,
.take_lock = take_lock,
.give_lock = give_lock,
};
ESP_TRACE_REGISTER_ENCODER("ext_trace_lib", &s_ext_trace_lib_vt);
@@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* SPDX-License-Identifier: Apache-2.0
*
* Implementations of the trace*() hooks declared in trace_FreeRTOS.h.
*
* Each hook encodes a single human-readable line that can be observed directly
* in any serial monitor — no decoder is needed:
*
* [+ 123 us] TASK_IN Task 1
* [+ 1000 us] ISR_IN irq=5
*
* The leading number is the time elapsed since the previous traced event.
*
*/
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "trace_FreeRTOS.h"
#include "esp_trace.h"
#include "esp_trace_port_encoder.h"
#include "esp_trace_util.h"
static esp_trace_handle_t s_esp_trace_handle = NULL;
static esp_trace_encoder_t *s_enc = NULL;
static uint32_t s_ts_freq_hz = 1000000; /* default assume 1 MHz */
static uint32_t s_last_ts = 0;
static volatile bool s_enabled = false;
void init_trace_lib(esp_trace_encoder_t *enc)
{
s_esp_trace_handle = esp_trace_get_active_handle();
s_enc = enc;
uint32_t freq = esp_trace_timestamp_init();
if (freq != 0) {
s_ts_freq_hz = freq;
}
s_last_ts = esp_trace_timestamp_get();
}
void trace_lib_start(void)
{
if (!s_esp_trace_handle) {
return;
}
s_last_ts = esp_trace_timestamp_get();
s_enabled = true;
}
void trace_lib_stop(void)
{
s_enabled = false;
}
/* Encode one trace line and write it through. */
static void encode(const char *type, const char *detail)
{
if (!s_enabled || !s_esp_trace_handle || !s_enc) {
return;
}
unsigned int int_state = s_enc->vt->take_lock(s_enc, ESP_TRACE_TMO_INFINITE);
uint32_t now = esp_trace_timestamp_get();
uint32_t delta = now - s_last_ts; /* uint32 modular subtraction handles wrap */
s_last_ts = now;
uint32_t delta_us = (s_ts_freq_hz == 1000000)
? delta
: (uint32_t)((uint64_t)delta * 1000000ULL / s_ts_freq_hz);
char line[96];
int n = snprintf(line, sizeof(line), "[+%7lu us] %-12s %s\n",
(unsigned long)delta_us, type, detail ? detail : "");
if (n > 0) {
if (n >= (int)sizeof(line)) {
n = (int)sizeof(line) - 1;
}
esp_trace_write(s_esp_trace_handle, line, (size_t)n, 0);
}
s_enc->vt->give_lock(s_enc, int_state);
}
void trace_lib_task_switched_in(void)
{
TaskHandle_t h = xTaskGetCurrentTaskHandle();
encode("TASK_IN", h ? pcTaskGetName(h) : "?");
}
void trace_lib_task_create(void *pxNewTCB)
{
if (!pxNewTCB) {
encode("TASK_CREATE", "(null)");
return;
}
encode("TASK_CREATE", pcTaskGetName((TaskHandle_t)pxNewTCB));
}
void trace_lib_isr_enter(uint32_t irq)
{
char d[24];
snprintf(d, sizeof(d), "irq=%lu", (unsigned long)irq);
encode("ISR_IN", d);
}
void trace_lib_isr_exit(void)
{
encode("ISR_OUT", "");
}
void trace_lib_isr_exit_to_scheduler(void)
{
encode("ISR_YIELD", "");
}
void trace_lib_queue_send(void *pxQueue)
{
char d[24];
snprintf(d, sizeof(d), "q=%p", pxQueue);
encode("Q_SEND", d);
}
void trace_lib_queue_receive(void *pxQueue)
{
char d[24];
snprintf(d, sizeof(d), "q=%p", pxQueue);
encode("Q_RECEIVE", d);
}
void trace_lib_queue_create(void *pxNewQueue)
{
char d[24];
snprintf(d, sizeof(d), "q=%p", pxNewQueue);
encode("Q_CREATE", d);
}