mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-28 16:46:31 +03:00
Merge branch 'feat/esp_trace_example' into 'master'
esp_trace example library integration See merge request espressif/esp-idf!48605
This commit is contained in:
@@ -33,11 +33,12 @@ end
|
||||
%% =======================
|
||||
subgraph PRIMARY["🔌 PUBLIC INTERFACE"]
|
||||
api["- esp_trace.h
|
||||
- esp_trace_init()
|
||||
- esp_trace_record()
|
||||
- esp_trace_write()
|
||||
- esp_trace_start()
|
||||
- esp_trace_stop()
|
||||
- esp_trace_flush()
|
||||
- esp_trace_print()"]
|
||||
- esp_trace_is_host_connected()
|
||||
- esp_trace_get_link_type()"]
|
||||
end
|
||||
|
||||
%% wiring: App uses API (labels land on the short pre-edges to api_in)
|
||||
@@ -203,7 +204,7 @@ idf_component_register(
|
||||
)
|
||||
```
|
||||
|
||||
This means you can directly use both the trace library APIs (e.g., SystemView) and `esp_trace` APIs (like `esp_trace_get_user_params()`, `esp_trace_is_host_connected()`, etc.) without explicitly declaring the dependency.
|
||||
This means you can directly use both the trace library APIs (e.g., SystemView) and `esp_trace` APIs (like `esp_trace_get_user_params()`, `esp_trace_is_host_connected()`, `esp_trace_start()` / `esp_trace_stop()` / `esp_trace_flush()`, etc.) without explicitly declaring the dependency.
|
||||
|
||||
### When Using Standalone Apptrace
|
||||
|
||||
@@ -226,6 +227,20 @@ The `esp_trace` component supports integration of external trace libraries throu
|
||||
- **Transport Adapters**: Handle the physical transport layer (e.g., JTAG, UART)
|
||||
- **Encoder Adapters**: Handle the trace encoding/formatting (e.g., SystemView, custom formats)
|
||||
|
||||
> ⚠️ **Reentrancy constraint for adapter runtime callbacks**
|
||||
>
|
||||
> Encoder and transport callbacks invoked from the hot path — `write`, `flush` / `flush_nolock`, `read`, `take_lock` / `give_lock`, `panic_handler` — run from inside FreeRTOS trace hooks (and from ISR context for `traceISR_ENTER` / `traceISR_EXIT`). They are also called while the encoder's lock is held.
|
||||
>
|
||||
> Do **not** call FreeRTOS / IDF APIs that themselves trigger trace hooks from these callbacks. Anything that would emit a `trace*()` macro re-enters the tracing path: it can recurse into your own encoder, deadlock on the encoder's non-recursive spinlock, or call a task-only API from ISR context.
|
||||
>
|
||||
> Specifically avoid:
|
||||
> - Task APIs: `vTaskDelay`, `vTaskSuspend`, `xTaskNotify*`, anything that yields.
|
||||
> - Queue / semaphore / mutex APIs: `xQueueSend/Receive`, `xSemaphoreTake/Give`, `xQueueSemaphoreTake`.
|
||||
> - Stream / message buffer APIs.
|
||||
> - Heap allocations that may take an internal mutex.
|
||||
>
|
||||
> Safe building blocks for adapter code: lock-free or spinlock-only primitives (e.g. `esp_trace_lock_*`, `esp_trace_rb_*`), low-level peripheral register access, atomic operations, and `esp_rom_*` helpers. Do any heavier work (FreeRTOS APIs, allocations) only at adapter `init()` time, before the trace session is in steady state.
|
||||
|
||||
### Creating a Transport Adapter
|
||||
|
||||
Transport adapters provide the physical communication layer for trace data.
|
||||
@@ -407,4 +422,5 @@ For detailed usage instructions, see:
|
||||
Examples demonstrating trace usage can be found in:
|
||||
- `examples/system/app_trace_basic/` - Basic application tracing
|
||||
- `examples/system/sysview_tracing/` - SystemView tracing example
|
||||
- `examples/system/esp_trace/` - Minimal template for integrating an external trace library (encoder + FreeRTOS hooks + vtable lock)
|
||||
- `examples/system/sysview_tracing_heap_log/` - SystemView heap and log tracing example
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -100,6 +100,27 @@ esp_trace_handle_t esp_trace_get_active_handle(void);
|
||||
*/
|
||||
esp_err_t esp_trace_write(esp_trace_handle_t handle, const void *data, size_t size, unsigned long tmo);
|
||||
|
||||
/**
|
||||
* @brief Resume trace event emission on the active session.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_trace_start(void);
|
||||
|
||||
/**
|
||||
* @brief Pause trace event emission on the active session.
|
||||
*
|
||||
* @return ESP_OK on success, otherwise see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_trace_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Flush pending trace data through the encoder
|
||||
*
|
||||
* @return ESP_OK on success, otherwise see esp_err_t
|
||||
*/
|
||||
esp_err_t esp_trace_flush(void);
|
||||
|
||||
/**
|
||||
* @brief Check if the host is connected
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -21,7 +21,13 @@ typedef struct esp_trace_transport esp_trace_transport_t;
|
||||
/**
|
||||
* @brief Encoder Virtual Table
|
||||
*
|
||||
* Defines the interface for trace encoders (libraries)
|
||||
* Defines the interface for trace encoders (libraries).
|
||||
*
|
||||
* @warning Runtime callbacks (write, flush, take_lock, give_lock, panic_handler)
|
||||
* must not call FreeRTOS / IDF APIs that themselves emit trace hooks
|
||||
* (e.g. vTaskDelay, xQueue*, xSemaphore*) — doing so re-enters the
|
||||
* tracing path and can deadlock on the encoder lock or crash in ISR
|
||||
* context.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
@@ -49,8 +55,20 @@ typedef struct {
|
||||
*/
|
||||
void (*panic_handler)(esp_trace_encoder_t *enc, const void *info);
|
||||
|
||||
/** @brief Resume trace event emission */
|
||||
esp_err_t (*start)(esp_trace_encoder_t *enc);
|
||||
|
||||
/** @brief Pause trace event emission */
|
||||
esp_err_t (*stop)(esp_trace_encoder_t *enc);
|
||||
|
||||
/** @brief Flush pending trace data through the encoder */
|
||||
esp_err_t (*flush)(esp_trace_encoder_t *enc);
|
||||
|
||||
/**
|
||||
* @brief Take encoder lock
|
||||
* @brief Take encoder lock.
|
||||
* Callers should pass ESP_TRACE_TMO_INFINITE unless they explicitly
|
||||
* check the return value — pairing a failed take with give_lock()
|
||||
* causes a spinlock owner-mismatch assert.
|
||||
* @param enc Encoder instance
|
||||
* @param tmo Timeout in microseconds
|
||||
* @return Lock state (for recursive locking) or 0 on failure
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -34,6 +34,11 @@ typedef enum {
|
||||
*
|
||||
* Defines the interface for trace transports.
|
||||
*
|
||||
* @warning Runtime callbacks (read, write, flush_nolock, panic_handler) must
|
||||
* not call FreeRTOS / IDF APIs that themselves emit trace hooks
|
||||
* (e.g. vTaskDelay, xQueue*, xSemaphore*) — they are invoked from
|
||||
* inside the encoder's lock and from ISR context, so re-entering
|
||||
* the tracing path can deadlock or assert.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -53,8 +53,9 @@ static esp_err_t esp_trace_create(const esp_trace_open_params_t *params)
|
||||
const esp_trace_encoder_vtable_t *enc_vt = esp_trace_find_encoder(params->encoder_name);
|
||||
const esp_trace_transport_vtable_t *tp_vt = esp_trace_find_transport(params->transport_name);
|
||||
|
||||
if (!enc_vt || !tp_vt) {
|
||||
ESP_EARLY_LOGE(TAG, "Encoder '%s' or transport '%s' not found", params->encoder_name, params->transport_name);
|
||||
// Encoder must be found but transport is optional
|
||||
if (!enc_vt) {
|
||||
ESP_EARLY_LOGE(TAG, "Encoder '%s' not found", params->encoder_name);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ static esp_err_t esp_trace_init(const esp_trace_open_params_t *params)
|
||||
portENTER_CRITICAL(&s_init_lock);
|
||||
|
||||
/* Setup transport first (encoder depends on it) */
|
||||
if (h->transport.vt->init) {
|
||||
if (h->transport.vt && h->transport.vt->init) {
|
||||
err = h->transport.vt->init(&h->transport, params->transport_cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_EARLY_LOGE(TAG, "Transport open failed: %d", err);
|
||||
@@ -150,9 +151,51 @@ esp_err_t esp_trace_write(esp_trace_handle_t h, const void *data, size_t size, u
|
||||
return h->encoder.vt->write(&h->encoder, data, size, tmo);
|
||||
}
|
||||
|
||||
esp_err_t esp_trace_start(void)
|
||||
{
|
||||
esp_trace_handle_t h = s_active_handle;
|
||||
if (!h) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!h->encoder.vt->start) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return h->encoder.vt->start(&h->encoder);
|
||||
}
|
||||
|
||||
esp_err_t esp_trace_stop(void)
|
||||
{
|
||||
esp_trace_handle_t h = s_active_handle;
|
||||
if (!h) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!h->encoder.vt->stop) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return h->encoder.vt->stop(&h->encoder);
|
||||
}
|
||||
|
||||
esp_err_t esp_trace_flush(void)
|
||||
{
|
||||
esp_trace_handle_t h = s_active_handle;
|
||||
if (!h) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!h->encoder.vt->flush) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
return h->encoder.vt->flush(&h->encoder);
|
||||
}
|
||||
|
||||
bool esp_trace_is_host_connected(esp_trace_handle_t h)
|
||||
{
|
||||
if (!h || !h->transport.vt->is_host_connected) {
|
||||
if (!h || !h->transport.vt || !h->transport.vt->is_host_connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -161,7 +204,7 @@ bool esp_trace_is_host_connected(esp_trace_handle_t h)
|
||||
|
||||
esp_trace_link_types_t esp_trace_get_link_type(esp_trace_handle_t h)
|
||||
{
|
||||
if (!h || !h->transport.vt->get_link_type) {
|
||||
if (!h || !h->transport.vt || !h->transport.vt->get_link_type) {
|
||||
return ESP_TRACE_LINK_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -183,7 +226,7 @@ void esp_trace_panic_handler(const void *info)
|
||||
h->encoder.vt->panic_handler(&h->encoder, info);
|
||||
}
|
||||
|
||||
if (h->transport.vt->panic_handler) {
|
||||
if (h->transport.vt && h->transport.vt->panic_handler) {
|
||||
h->transport.vt->panic_handler(&h->transport, info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,9 +557,27 @@ Good instructions on how to install, configure, and visualize data in Impulse fr
|
||||
|
||||
If you have problems with visualization (no data is shown or strange behaviors of zoom action are observed), you can try to delete current signal hierarchy and double-click on the necessary file or port. Eclipse will ask you to create a new signal hierarchy.
|
||||
|
||||
Application Examples
|
||||
""""""""""""""""""""
|
||||
|
||||
- :example:`system/sysview_tracing` demonstrates how to trace FreeRTOS task and system events using SEGGER SystemView.
|
||||
- :example:`system/sysview_tracing_heap_log` demonstrates heap allocation tracing alongside SystemView events.
|
||||
|
||||
.. _app_trace-gcov-source-code-coverage:
|
||||
|
||||
Gcov (Source Code Coverage)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In ESP-IDF projects, code coverage analysis using gcov can be done with the help of `espressif/esp_gcov <https://components.espressif.com/components/espressif/esp_gcov>`_ managed component.
|
||||
|
||||
.. _app_trace-integrating-a-custom-trace-library:
|
||||
|
||||
Integrating a Custom Trace Library
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``esp_trace`` component exposes a stable extension point (``CONFIG_ESP_TRACE_LIB_EXTERNAL``) for plugging in a third-party trace recorder without patching ESP-IDF. An external component provides an encoder adapter (registered via ``ESP_TRACE_REGISTER_ENCODER()``) and a slim ``esp_trace_freertos_impl.h`` that injects the desired FreeRTOS trace hooks. The encoder vtable also offers optional ``start`` / ``stop`` / ``flush`` and ``take_lock`` / ``give_lock`` entries dispatched from the public :cpp:func:`esp_trace_start`, :cpp:func:`esp_trace_stop`, :cpp:func:`esp_trace_flush` API.
|
||||
|
||||
Application Examples
|
||||
""""""""""""""""""""
|
||||
|
||||
- :example:`system/esp_trace` is a minimal copy-paste template that wires up an external encoder, demonstrates the FreeRTOS trace-hook include-chain contract, and covers cross-core serialization through the encoder lock.
|
||||
|
||||
@@ -557,9 +557,27 @@ Start 子命令语法:
|
||||
|
||||
如果你在可视化方面遇到了问题(未显示数据或者缩放操作异常),可以尝试删除当前的信号层次结构,再双击必要的文件或端口。Eclipse 会请求创建新的信号层次结构。
|
||||
|
||||
应用示例
|
||||
""""""""
|
||||
|
||||
- :example:`system/sysview_tracing` 演示如何使用 SEGGER SystemView 记录 FreeRTOS 任务与系统事件。
|
||||
- :example:`system/sysview_tracing_heap_log` 演示如何在记录 SystemView 事件的同时,对堆内存分配进行跟踪。
|
||||
|
||||
.. _app_trace-gcov-source-code-coverage:
|
||||
|
||||
Gcov(源代码覆盖率)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
在 ESP-IDF 项目中,可以借助 `espressif/esp_gcov <https://components.espressif.com/components/espressif/esp_gcov>`_ 托管组件使用 gcov 进行代码覆盖率分析。
|
||||
|
||||
.. _app_trace-integrating-a-custom-trace-library:
|
||||
|
||||
集成自定义跟踪库
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
``esp_trace`` 组件提供了稳定的扩展点 (``CONFIG_ESP_TRACE_LIB_EXTERNAL``),允许在不修改 ESP-IDF 的情况下接入第三方跟踪记录器。外部组件需提供一个编码器适配器(通过 ``ESP_TRACE_REGISTER_ENCODER()`` 注册)以及一个轻量的 ``esp_trace_freertos_impl.h``,用于注入所需的 FreeRTOS 跟踪钩子。编码器虚表还提供可选的 ``start`` / ``stop`` / ``flush`` 及 ``take_lock`` / ``give_lock`` 入口,由公共 API :cpp:func:`esp_trace_start`、:cpp:func:`esp_trace_stop`、:cpp:func:`esp_trace_flush` 调度。
|
||||
|
||||
应用示例
|
||||
""""""""
|
||||
|
||||
- :example:`system/esp_trace` 是一个最简的复制粘贴模板,演示如何接入外部编码器、说明 FreeRTOS 跟踪钩子头文件的包含链约束,以及通过编码器锁实现多核序列化。
|
||||
|
||||
@@ -63,6 +63,24 @@ examples/system/esp_timer:
|
||||
depends_components:
|
||||
- esp_timer
|
||||
|
||||
examples/system/esp_trace:
|
||||
disable:
|
||||
- if: SOC_USB_SERIAL_JTAG_SUPPORTED != 1
|
||||
reason: example transport is USB Serial JTAG
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32h4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32s31"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- freertos
|
||||
|
||||
examples/system/eventfd:
|
||||
disable:
|
||||
- if: SOC_GPTIMER_SUPPORTED != 1 and (IDF_TARGET != "esp32" and (NIGHTLY_RUN != "1" or IDF_TARGET == "linux"))
|
||||
|
||||
8
examples/system/esp_trace/CMakeLists.txt
Normal file
8
examples/system/esp_trace/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(esp_trace_example)
|
||||
207
examples/system/esp_trace/README.md
Normal file
207
examples/system/esp_trace/README.md
Normal file
@@ -0,0 +1,207 @@
|
||||
| Supported Targets | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# ESP Trace External Library Integration Example
|
||||
|
||||
This example shows the **minimal** set of files and configuration needed to plug a third-party trace library into the [`esp_trace`](../../../components/esp_trace) component using the public `CONFIG_ESP_TRACE_LIB_EXTERNAL` extension point. It is meant as a copy-paste starting point for vendors and users who want to integrate their own trace recorder (e.g. Percepio TraceRecorder, a custom CTF emitter, a printf-style logger, …) without patching ESP-IDF itself.
|
||||
|
||||
The example covers:
|
||||
|
||||
* How to expose a custom **encoder** to `esp_trace` via `ESP_TRACE_REGISTER_ENCODER()`.
|
||||
* How to provide an **`esp_trace_freertos_impl.h`** header that injects your trace hooks into FreeRTOS without breaking the `FreeRTOSConfig.h` include chain.
|
||||
* How to wire everything up in **CMake** so the registration is not stripped by the linker, and so your header is visible to the FreeRTOS kernel.
|
||||
* How to override the trace session parameters from the application via **`esp_trace_get_user_params()`**.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Hardware Required
|
||||
|
||||
By default this example targets devices with built-in USB Serial JTAG (ESP32-C3/C5/C6/C61/H2/P4/S3, …). For other transports, see [Changing the Transport](#changing-the-transport).
|
||||
|
||||
You only need a development board and a USB cable.
|
||||
|
||||
### Configure the Project
|
||||
|
||||
```
|
||||
idf.py set-target <esp_target>
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
The defaults in [`sdkconfig.defaults`](sdkconfig.defaults) already enable everything the example needs:
|
||||
|
||||
```ini
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_EXTERNAL=y # use an external encoder
|
||||
CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y # transport: built-in USB-Serial-JTAG
|
||||
CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y # timestamp source
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y # free up USB-Serial-JTAG for trace
|
||||
```
|
||||
|
||||
`CONFIG_ESP_CONSOLE_SECONDARY_NONE=y` is required so the USB-Serial-JTAG peripheral is not claimed by the secondary console — otherwise the transport option is not selectable.
|
||||
|
||||
### Build, Flash, and Monitor
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
You should see the app start up and create a task. Whatever trace bytes your encoder produces will be emitted over the configured transport — in this example, the encoder writes a short string to the transport on every task switch.
|
||||
|
||||
(To exit the serial monitor, type `Ctrl-]`.)
|
||||
|
||||
## Project Layout
|
||||
|
||||
```
|
||||
esp_trace/
|
||||
├── CMakeLists.txt
|
||||
├── sdkconfig.defaults
|
||||
├── main/
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── app_main.c # overrides esp_trace_get_user_params()
|
||||
└── components/
|
||||
└── ext_trace_lib/ # the external trace library component
|
||||
├── CMakeLists.txt # WHOLE_ARCHIVE + freertos include trick
|
||||
├── include/
|
||||
│ ├── esp_trace_freertos_impl.h # entry point pulled in by FreeRTOSConfig.h
|
||||
│ └── trace_FreeRTOS.h # trace*() macros + forward declarations
|
||||
└── src/
|
||||
├── adapter_encoder_ext_trace_lib.c # vtable + ESP_TRACE_REGISTER_ENCODER()
|
||||
└── trace_FreeRTOS.c # hook implementations (may include FreeRTOS.h)
|
||||
```
|
||||
|
||||
## How the Integration Works
|
||||
|
||||
### 1. Selecting the external library
|
||||
|
||||
`CONFIG_ESP_TRACE_LIB_EXTERNAL=y` tells `esp_trace` that the encoder lives in a separate component. Internally, `CONFIG_ESP_TRACE_LIB_NAME` resolves to `"ext"`. You can either:
|
||||
|
||||
* register your encoder under that default name — `ESP_TRACE_REGISTER_ENCODER("ext", &vt);` — and the system picks it up automatically, **or**
|
||||
* register under any name you like (this example uses `"ext_trace_lib"`) and override the session parameters at runtime via `esp_trace_get_user_params()`. See [`main/app_main.c`](main/app_main.c):
|
||||
|
||||
```c
|
||||
esp_trace_open_params_t esp_trace_get_user_params(void)
|
||||
{
|
||||
esp_trace_open_params_t trace_params = {
|
||||
.core_cfg = NULL,
|
||||
.encoder_name = "ext_trace_lib",
|
||||
.encoder_cfg = NULL,
|
||||
.transport_name = "usb_serial_jtag",
|
||||
.transport_cfg = NULL,
|
||||
};
|
||||
return trace_params;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Providing the FreeRTOS trace hooks
|
||||
|
||||
`esp_trace`'s public header [`esp_trace_freertos.h`](../../../components/esp_trace/include/esp_trace_freertos.h) is included from `FreeRTOSConfig.h`. When `CONFIG_ESP_TRACE_LIB_EXTERNAL=y` is set, it pulls in **your** `esp_trace_freertos_impl.h`:
|
||||
|
||||
```c
|
||||
#if CONFIG_ESP_TRACE_LIB_EXTERNAL
|
||||
#include "esp_trace_freertos_impl.h"
|
||||
#endif
|
||||
```
|
||||
|
||||
The example splits the contract into two files:
|
||||
|
||||
* [`esp_trace_freertos_impl.h`](components/ext_trace_lib/include/esp_trace_freertos_impl.h) — a one-line shim that pulls in `trace_FreeRTOS.h`.
|
||||
* [`trace_FreeRTOS.h`](components/ext_trace_lib/include/trace_FreeRTOS.h) — defines only the `trace*()` macros this example actually hooks into, plus forward declarations of the helper functions called from them. **No FreeRTOS includes.** Anything left undefined here falls back to FreeRTOS's own empty default (every trace macro is guarded by `#ifndef traceXXX / #define traceXXX() / #endif` in `freertos/FreeRTOS.h`), so you only need to declare what you actually intercept. Trace macros are allowed to reference FreeRTOS identifiers like `pxTCB` or `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.
|
||||
|
||||
The actual hook implementation lives in [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) and is free to `#include "freertos/FreeRTOS.h"`. By the time a `.c` file is compiled, `FreeRTOSConfig.h` has been fully parsed.
|
||||
|
||||
### 3. Registering the encoder
|
||||
|
||||
[`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c) implements the encoder vtable (`init`, `write`, `panic_handler`) and registers it at link time:
|
||||
|
||||
```c
|
||||
ESP_TRACE_REGISTER_ENCODER("ext_trace_lib", &s_ext_trace_lib_vt);
|
||||
```
|
||||
|
||||
The registration places a descriptor into a dedicated linker section that `esp_trace_core` scans during startup. Because nothing in the application references that descriptor directly, the linker would normally garbage-collect it — `WHOLE_ARCHIVE TRUE` in the component's `CMakeLists.txt` prevents that.
|
||||
|
||||
### 4. CMake setup
|
||||
|
||||
[`components/ext_trace_lib/CMakeLists.txt`](components/ext_trace_lib/CMakeLists.txt) shows the two pieces of CMake plumbing every external trace library needs:
|
||||
|
||||
```cmake
|
||||
if(CONFIG_ESP_TRACE_LIB_EXTERNAL)
|
||||
idf_component_register(SRC_DIRS ${src_dirs}
|
||||
INCLUDE_DIRS ${include_dirs}
|
||||
PRIV_REQUIRES esp_trace
|
||||
WHOLE_ARCHIVE TRUE) # keep ESP_TRACE_REGISTER_* symbols
|
||||
|
||||
# Expose esp_trace_freertos_impl.h to the freertos component
|
||||
idf_component_get_property(freertos_lib freertos COMPONENT_LIB)
|
||||
target_include_directories(${freertos_lib} INTERFACE ${include_dirs})
|
||||
else()
|
||||
idf_component_register(PRIV_REQUIRES esp_trace)
|
||||
endif()
|
||||
```
|
||||
|
||||
The second `target_include_directories(...)` call is what makes `esp_trace_freertos_impl.h` resolvable from inside the FreeRTOS kernel's translation units.
|
||||
|
||||
## Changing the Transport
|
||||
|
||||
The example defaults to USB Serial JTAG. To use a different transport, edit `sdkconfig.defaults` (or run `idf.py menuconfig` → *Component config → ESP Trace Configuration → Trace transport*):
|
||||
|
||||
| Transport | Config | Notes |
|
||||
| --- | --- | --- |
|
||||
| USB Serial JTAG | `CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y` | Default. Requires `ESP_CONSOLE_SECONDARY_NONE=y`. |
|
||||
| apptrace over JTAG | `CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y` + `CONFIG_APPTRACE_DEST_JTAG=y` | Needs OpenOCD on the host to drain the buffer. |
|
||||
| apptrace over UART | `CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y` + `CONFIG_APPTRACE_DEST_UART=y` | Pick a UART different from the console. |
|
||||
| External transport | `CONFIG_ESP_TRACE_TRANSPORT_EXTERNAL=y` | Another component must register a transport with `ESP_TRACE_REGISTER_TRANSPORT(...)`. |
|
||||
| None | `CONFIG_ESP_TRACE_TRANSPORT_NONE=y` | Useful if your library streams data over its own channel and just needs the FreeRTOS hooks. |
|
||||
|
||||
Don't forget to update the `transport_name` field in `esp_trace_get_user_params()` to match (e.g. `"apptrace"`, `"usb_serial_jtag"`, or your custom transport's registered name).
|
||||
|
||||
## What the Demo Emits
|
||||
|
||||
`encode()` in [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) writes one line per trace event in the form:
|
||||
|
||||
```
|
||||
[+ 9933 us] ISR_IN irq=63
|
||||
[+ 29 us] ISR_IN irq=57
|
||||
[+ 21 us] ISR_YIELD
|
||||
[+ 16 us] ISR_OUT
|
||||
[+ 1234 us] Q_CREATE q=0x3fc8a210
|
||||
[+ 167 us] TASK_IN producer
|
||||
[+ 54 us] Q_SEND q=0x3fc8a210
|
||||
[+ 32 us] TASK_IN consumer
|
||||
```
|
||||
|
||||
The leading number is the time elapsed since the previous traced event (microseconds when `CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER` is selected). Eight FreeRTOS hooks are wired up — see the *active hooks* block at the top of [`trace_FreeRTOS.h`](components/ext_trace_lib/include/trace_FreeRTOS.h). The rest stay as no-ops (FreeRTOS still expects every `trace*()` macro to be defined).
|
||||
|
||||
`ISR_OUT` vs `ISR_YIELD` reflects how FreeRTOS leaves the interrupt: `ISR_OUT` when the handler returns to the interrupted task without scheduling, `ISR_YIELD` when it calls `portYIELD_FROM_ISR()` (triggering `traceISR_EXIT_TO_SCHEDULER`). On a busy SMP target the yield path dominates; on a mostly-idle single-core target the plain `ISR_OUT` path does.
|
||||
|
||||
Because the transport is USB-Serial-JTAG and the console is on UART (`CONFIG_ESP_CONSOLE_SECONDARY_NONE=y`), `idf.py monitor` shows ESP-IDF logs while the trace stream is on a separate USB endpoint — open it in any serial terminal (`screen /dev/cu.usbmodem...`, picocom, etc.) to read the output above.
|
||||
|
||||
## Runtime Control — `esp_trace_start` / `_stop` / `_flush`
|
||||
|
||||
[`esp_trace.h`](../../../components/esp_trace/include/esp_trace.h) exposes three generic lifecycle calls that dispatch to the active encoder's vtable. The application uses only the public API — it never reaches into the external library:
|
||||
|
||||
```c
|
||||
esp_trace_start(); // resume emission (also resets the delta baseline)
|
||||
// ... do stuff ...
|
||||
esp_trace_stop(); // pause emission
|
||||
esp_trace_flush(); // drain transport buffers
|
||||
```
|
||||
|
||||
In this example the library boots with `s_enabled = false`, so nothing is emitted until `app_main()` calls `esp_trace_start()`. The trailing pair `esp_trace_flush(); esp_trace_stop();` makes sure the last events reach the host before the trace channel goes silent. Adapter wiring lives in [`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c) (`start` / `stop` / `flush` callbacks); flush forwards to the transport's `flush_nolock`.
|
||||
|
||||
## Cross-Core Serialization
|
||||
|
||||
`encode()` wraps its body in the encoder's `take_lock` / `give_lock` vtable entries (an `esp_trace_lock_t` allocated in the adapter's `init()`). See [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) and [`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c).
|
||||
|
||||
## Other `esp_trace` Helpers
|
||||
|
||||
Beyond what this example uses, [`esp_trace.h`](../../../components/esp_trace/include/esp_trace.h) and [`esp_trace_util.h`](../../../components/esp_trace/include/esp_trace_util.h) also expose:
|
||||
|
||||
* `esp_trace_is_host_connected()` — gate expensive work when no host is listening.
|
||||
* `esp_trace_get_link_type()` — returns `ESP_TRACE_LINK_DEBUG_PROBE`, `_UART`, or `_USB_SERIAL_JTAG`.
|
||||
* `esp_trace_rb_*()` — power-of-2, FreeRTOS-free ring buffer for trace hot paths.
|
||||
* `esp_trace_tmo_init/check()` — cooperative timeouts for flush loops.
|
||||
|
||||
## See Also
|
||||
|
||||
* [`components/esp_trace/README.md`](../../../components/esp_trace/README.md) — full architecture overview and adapter API reference.
|
||||
* [`examples/system/sysview_tracing`](../sysview_tracing) — a production-grade integration of SEGGER SystemView built on the same extension points.
|
||||
@@ -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);
|
||||
}
|
||||
3
examples/system/esp_trace/main/CMakeLists.txt
Normal file
3
examples/system/esp_trace/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
REQUIRES ext_trace_lib
|
||||
INCLUDE_DIRS "")
|
||||
76
examples/system/esp_trace/main/app_main.c
Normal file
76
examples/system/esp_trace/main/app_main.c
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_trace.h"
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
esp_trace_open_params_t esp_trace_get_user_params(void)
|
||||
{
|
||||
esp_trace_open_params_t trace_params = {
|
||||
.core_cfg = NULL,
|
||||
.encoder_name = "ext_trace_lib",
|
||||
.encoder_cfg = NULL,
|
||||
.transport_name = "usb_serial_jtag",
|
||||
.transport_cfg = NULL,
|
||||
};
|
||||
return trace_params;
|
||||
}
|
||||
|
||||
static QueueHandle_t s_q;
|
||||
|
||||
/* Producer: sends a counter value to s_q every 50 ms. Generates Q_SEND
|
||||
* trace events and unblocks the consumer (driving TASK_IN switches). */
|
||||
static void producer(void *arg)
|
||||
{
|
||||
uint32_t v = 0;
|
||||
while (1) {
|
||||
xQueueSend(s_q, &v, portMAX_DELAY);
|
||||
v++;
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/* Consumer: blocks on s_q indefinitely. Each receive wakes this task and
|
||||
* fires a TASK_IN trace event when the scheduler switches us in. */
|
||||
static void consumer(void *arg)
|
||||
{
|
||||
uint32_t v;
|
||||
while (1) {
|
||||
xQueueReceive(s_q, &v, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Start of trace session");
|
||||
|
||||
// Wait some time for host to be ready
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
|
||||
esp_trace_start();
|
||||
|
||||
s_q = xQueueCreate(4, sizeof(uint32_t));
|
||||
|
||||
xTaskCreatePinnedToCore(producer, "producer", 2048, NULL, 5, NULL, 0);
|
||||
xTaskCreatePinnedToCore(consumer, "consumer", 2048, NULL, 5, NULL, portNUM_PROCESSORS - 1);
|
||||
|
||||
// 1 second delay is enough to generate the expected number of trace events.
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
esp_trace_stop();
|
||||
esp_trace_flush();
|
||||
|
||||
ESP_LOGI(TAG, "End of trace session");
|
||||
}
|
||||
115
examples/system/esp_trace/pytest_esp_trace.py
Normal file
115
examples/system/esp_trace/pytest_esp_trace.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import os.path
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import serial
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
# Matches lines emitted by encode() in trace_FreeRTOS.c, e.g.
|
||||
# [+ 12345 us] TASK_IN producer
|
||||
# [+ 12 us] Q_SEND q=0x3fc8a210
|
||||
TRACE_LINE_RE = re.compile(r'^\[\+\s*\d+ us\] ([A-Z_]+)\s*(.*)$')
|
||||
|
||||
# Minimum number of occurrences each event type must reach within the capture window.
|
||||
EXPECTED_MIN_COUNTS = {
|
||||
'TASK_CREATE': 2, # producer + consumer
|
||||
'Q_CREATE': 1, # xQueueCreate in app_main
|
||||
'TASK_IN': 5, # producer/consumer + idle task
|
||||
'Q_SEND': 5, # producer sends every 50 ms
|
||||
'Q_RECEIVE': 5, # consumer receives every send
|
||||
'ISR_IN': 5, # FreeRTOS systick
|
||||
}
|
||||
|
||||
ISR_EXIT_MIN = 5
|
||||
|
||||
# Fraction of malformed lines we tolerate before failing.
|
||||
MAX_MALFORMED_RATIO = 0.05
|
||||
|
||||
|
||||
def _validate_trace_data(trace_log_path: str) -> None:
|
||||
"""Validate the human-readable trace log produced by ext_trace_lib."""
|
||||
with open(trace_log_path, encoding='utf-8', errors='replace') as f:
|
||||
lines = [line.rstrip() for line in f if line.strip()]
|
||||
|
||||
assert lines, f'No trace data captured in {trace_log_path}'
|
||||
|
||||
counts: dict[str, int] = {}
|
||||
create_names: set[str] = set()
|
||||
malformed = 0
|
||||
|
||||
for line in lines:
|
||||
m = TRACE_LINE_RE.match(line)
|
||||
if not m:
|
||||
malformed += 1
|
||||
continue
|
||||
evt, detail = m.group(1), m.group(2).strip()
|
||||
counts[evt] = counts.get(evt, 0) + 1
|
||||
if evt == 'TASK_CREATE':
|
||||
create_names.add(detail)
|
||||
|
||||
ratio = malformed / len(lines)
|
||||
assert ratio <= MAX_MALFORMED_RATIO, (
|
||||
f'Too many malformed lines: {malformed}/{len(lines)} '
|
||||
f'({ratio:.1%} > {MAX_MALFORMED_RATIO:.0%}). Likely USJ TX ring overflow; '
|
||||
f'bump CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE or slow down the producer.'
|
||||
)
|
||||
|
||||
for evt, minimum in EXPECTED_MIN_COUNTS.items():
|
||||
seen = counts.get(evt, 0)
|
||||
assert seen >= minimum, f'Expected at least {minimum} {evt} events, got {seen}'
|
||||
|
||||
isr_exits = counts.get('ISR_OUT', 0) + counts.get('ISR_YIELD', 0)
|
||||
assert isr_exits >= ISR_EXIT_MIN, (
|
||||
f'Expected at least {ISR_EXIT_MIN} ISR_OUT+ISR_YIELD events, '
|
||||
f'got {isr_exits} (ISR_OUT={counts.get("ISR_OUT", 0)}, '
|
||||
f'ISR_YIELD={counts.get("ISR_YIELD", 0)})'
|
||||
)
|
||||
|
||||
assert 'producer' in create_names, f'producer task not seen in TASK_CREATE: {create_names}'
|
||||
assert 'consumer' in create_names, f'consumer task not seen in TASK_CREATE: {create_names}'
|
||||
|
||||
|
||||
def _capture_trace(ser: serial.Serial, trace_log_path: str, capture_s: float = 5.0) -> None:
|
||||
"""Capture trace output from the USB-Serial-JTAG endpoint."""
|
||||
ser.reset_input_buffer()
|
||||
with open(trace_log_path, 'w+b') as f:
|
||||
end_time = time.time() + capture_s
|
||||
while time.time() < end_time:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
f.write(ser.read(ser.in_waiting))
|
||||
except serial.SerialTimeoutException:
|
||||
assert False, 'Timeout reached while reading from serial port, exiting...'
|
||||
|
||||
# Drain anything still in flight after the capture window.
|
||||
time.sleep(0.2)
|
||||
end_time = time.time() + 1.0
|
||||
last_data_time = time.time()
|
||||
while time.time() < end_time and (time.time() - last_data_time) <= 0.3:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
f.write(ser.read(ser.in_waiting))
|
||||
last_data_time = time.time()
|
||||
except serial.SerialTimeoutException:
|
||||
assert False, 'Timeout reached while reading from serial port, exiting...'
|
||||
|
||||
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target'])
|
||||
@pytest.mark.parametrize('config', [pytest.param('default')], indirect=True)
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4', 'esp32s31'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_esp_trace_ext_lib_usj(dut: IdfDut) -> None:
|
||||
dut.expect('Start of trace session', timeout=5)
|
||||
|
||||
time.sleep(1) # wait for USJ port to be ready
|
||||
usj_port = '/dev/serial_ports/ttyACM-esp32'
|
||||
ser = serial.Serial(usj_port, baudrate=1000000, timeout=10)
|
||||
trace_log_path = os.path.join(dut.logdir, 'ext_trace.log')
|
||||
|
||||
_capture_trace(ser, trace_log_path)
|
||||
_validate_trace_data(trace_log_path)
|
||||
6
examples/system/esp_trace/sdkconfig.defaults
Normal file
6
examples/system/esp_trace/sdkconfig.defaults
Normal file
@@ -0,0 +1,6 @@
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_EXTERNAL=y
|
||||
CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y
|
||||
CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE=32768
|
||||
@@ -123,8 +123,6 @@ KNOWN_MISSING = {
|
||||
'system/gcov',
|
||||
'system/gdbstub',
|
||||
'system/rt_mqueue',
|
||||
'system/sysview_tracing',
|
||||
'system/sysview_tracing_heap_log',
|
||||
'system/ulp/lp_core/build_system',
|
||||
'system/ulp/lp_core/build_system/main/ulp',
|
||||
'system/ulp/lp_core/debugging/main/ulp',
|
||||
|
||||
Reference in New Issue
Block a user