From de197e3c0f2ea42fb8a86f1f8d82605d4e204d6d Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Thu, 14 May 2026 09:38:05 +0200 Subject: [PATCH 1/4] change(esp_trace): allow init without transport(none) --- components/esp_trace/src/core/esp_trace_core.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/esp_trace/src/core/esp_trace_core.c b/components/esp_trace/src/core/esp_trace_core.c index 13fd193e8cb..885b807b040 100644 --- a/components/esp_trace/src/core/esp_trace_core.c +++ b/components/esp_trace/src/core/esp_trace_core.c @@ -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); @@ -152,7 +153,7 @@ esp_err_t esp_trace_write(esp_trace_handle_t h, const void *data, size_t size, u 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 +162,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 +184,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); } } From 4c88c77f9ca500abeff45d8ff5a3bba26be30d8f Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 15 May 2026 15:55:47 +0200 Subject: [PATCH 2/4] feat(esp_trace): extend encoder interface Add esp_trace_flush, esp_trace_start and esp_trace_stop APIs --- components/esp_trace/README.md | 9 ++-- components/esp_trace/include/esp_trace.h | 23 +++++++++- .../include/esp_trace_port_encoder.h | 16 ++++++- .../esp_trace/src/core/esp_trace_core.c | 42 +++++++++++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/components/esp_trace/README.md b/components/esp_trace/README.md index 45196dd0985..47564a2983d 100644 --- a/components/esp_trace/README.md +++ b/components/esp_trace/README.md @@ -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 diff --git a/components/esp_trace/include/esp_trace.h b/components/esp_trace/include/esp_trace.h index 5ed7ccb3247..d17f7f60738 100644 --- a/components/esp_trace/include/esp_trace.h +++ b/components/esp_trace/include/esp_trace.h @@ -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 * diff --git a/components/esp_trace/include/esp_trace_port_encoder.h b/components/esp_trace/include/esp_trace_port_encoder.h index 2aa7bd8b9f6..59a603ad0fe 100644 --- a/components/esp_trace/include/esp_trace_port_encoder.h +++ b/components/esp_trace/include/esp_trace_port_encoder.h @@ -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 */ @@ -49,8 +49,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 diff --git a/components/esp_trace/src/core/esp_trace_core.c b/components/esp_trace/src/core/esp_trace_core.c index 885b807b040..7fd81c5a544 100644 --- a/components/esp_trace/src/core/esp_trace_core.c +++ b/components/esp_trace/src/core/esp_trace_core.c @@ -151,6 +151,48 @@ 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 || !h->transport.vt->is_host_connected) { From 41d425bf05383a923a59e9a86e976cabceb066f0 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 15 May 2026 14:55:02 +0200 Subject: [PATCH 3/4] feat(esp_trace): add example to demonstrate external lib integration --- components/esp_trace/README.md | 15 ++ .../include/esp_trace_port_encoder.h | 8 +- .../include/esp_trace_port_transport.h | 7 +- docs/en/api-guides/app_trace.rst | 18 ++ docs/zh_CN/api-guides/app_trace.rst | 18 ++ examples/system/.build-test-rules.yml | 18 ++ examples/system/esp_trace/CMakeLists.txt | 8 + examples/system/esp_trace/README.md | 207 ++++++++++++++++++ .../components/ext_trace_lib/CMakeLists.txt | 23 ++ .../include/esp_trace_freertos_impl.h | 9 + .../ext_trace_lib/include/trace_FreeRTOS.h | 68 ++++++ .../src/adapter_encoder_ext_trace_lib.c | 141 ++++++++++++ .../ext_trace_lib/src/trace_FreeRTOS.c | 147 +++++++++++++ examples/system/esp_trace/main/CMakeLists.txt | 3 + examples/system/esp_trace/main/app_main.c | 76 +++++++ examples/system/esp_trace/pytest_esp_trace.py | 115 ++++++++++ examples/system/esp_trace/sdkconfig.defaults | 6 + tools/ci/check_examples_documented.py | 2 - 18 files changed, 885 insertions(+), 4 deletions(-) create mode 100644 examples/system/esp_trace/CMakeLists.txt create mode 100644 examples/system/esp_trace/README.md create mode 100644 examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt create mode 100644 examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h create mode 100644 examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h create mode 100644 examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c create mode 100644 examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c create mode 100644 examples/system/esp_trace/main/CMakeLists.txt create mode 100644 examples/system/esp_trace/main/app_main.c create mode 100644 examples/system/esp_trace/pytest_esp_trace.py create mode 100644 examples/system/esp_trace/sdkconfig.defaults diff --git a/components/esp_trace/README.md b/components/esp_trace/README.md index 47564a2983d..7ec47c6bf8d 100644 --- a/components/esp_trace/README.md +++ b/components/esp_trace/README.md @@ -227,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. @@ -408,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 diff --git a/components/esp_trace/include/esp_trace_port_encoder.h b/components/esp_trace/include/esp_trace_port_encoder.h index 59a603ad0fe..5f1365d51e6 100644 --- a/components/esp_trace/include/esp_trace_port_encoder.h +++ b/components/esp_trace/include/esp_trace_port_encoder.h @@ -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 { /** diff --git a/components/esp_trace/include/esp_trace_port_transport.h b/components/esp_trace/include/esp_trace_port_transport.h index 39d435e9242..8385ce4a396 100644 --- a/components/esp_trace/include/esp_trace_port_transport.h +++ b/components/esp_trace/include/esp_trace_port_transport.h @@ -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 { /** diff --git a/docs/en/api-guides/app_trace.rst b/docs/en/api-guides/app_trace.rst index c3d6456b171..5c4818c121c 100644 --- a/docs/en/api-guides/app_trace.rst +++ b/docs/en/api-guides/app_trace.rst @@ -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 `_ 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. diff --git a/docs/zh_CN/api-guides/app_trace.rst b/docs/zh_CN/api-guides/app_trace.rst index b4aa69dd8da..ed71462c971 100644 --- a/docs/zh_CN/api-guides/app_trace.rst +++ b/docs/zh_CN/api-guides/app_trace.rst @@ -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 `_ 托管组件使用 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 跟踪钩子头文件的包含链约束,以及通过编码器锁实现多核序列化。 diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 2879eafb491..41388e9c4be 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -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")) diff --git a/examples/system/esp_trace/CMakeLists.txt b/examples/system/esp_trace/CMakeLists.txt new file mode 100644 index 00000000000..dd56d133980 --- /dev/null +++ b/examples/system/esp_trace/CMakeLists.txt @@ -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) diff --git a/examples/system/esp_trace/README.md b/examples/system/esp_trace/README.md new file mode 100644 index 00000000000..861d11752bc --- /dev/null +++ b/examples/system/esp_trace/README.md @@ -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 +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. diff --git a/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt b/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt new file mode 100644 index 00000000000..ce4555c4eb5 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt @@ -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() diff --git a/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h b/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h new file mode 100644 index 00000000000..fc365434365 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "trace_FreeRTOS.h" diff --git a/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h b/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h new file mode 100644 index 00000000000..c49ca59bf49 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/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 + +#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. */ diff --git a/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c b/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c new file mode 100644 index 00000000000..976704b44e4 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#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); diff --git a/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c b/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c new file mode 100644 index 00000000000..5e88bf6df37 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c @@ -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 +#include +#include + +#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); +} diff --git a/examples/system/esp_trace/main/CMakeLists.txt b/examples/system/esp_trace/main/CMakeLists.txt new file mode 100644 index 00000000000..f9cd30438ad --- /dev/null +++ b/examples/system/esp_trace/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "app_main.c" + REQUIRES ext_trace_lib + INCLUDE_DIRS "") diff --git a/examples/system/esp_trace/main/app_main.c b/examples/system/esp_trace/main/app_main.c new file mode 100644 index 00000000000..28d7c07aa27 --- /dev/null +++ b/examples/system/esp_trace/main/app_main.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include "sdkconfig.h" + +#include +#include + +#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"); +} diff --git a/examples/system/esp_trace/pytest_esp_trace.py b/examples/system/esp_trace/pytest_esp_trace.py new file mode 100644 index 00000000000..e5bc2a0af85 --- /dev/null +++ b/examples/system/esp_trace/pytest_esp_trace.py @@ -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) diff --git a/examples/system/esp_trace/sdkconfig.defaults b/examples/system/esp_trace/sdkconfig.defaults new file mode 100644 index 00000000000..f90dbb5d285 --- /dev/null +++ b/examples/system/esp_trace/sdkconfig.defaults @@ -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 diff --git a/tools/ci/check_examples_documented.py b/tools/ci/check_examples_documented.py index a7606957b4a..5faeceb0e65 100644 --- a/tools/ci/check_examples_documented.py +++ b/tools/ci/check_examples_documented.py @@ -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', From a6f111e65c42ae1bc67f560e06d37eb874f54003 Mon Sep 17 00:00:00 2001 From: renpeiying Date: Tue, 26 May 2026 10:54:45 +0800 Subject: [PATCH 4/4] docs: Update CN trans --- docs/zh_CN/api-guides/app_trace.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh_CN/api-guides/app_trace.rst b/docs/zh_CN/api-guides/app_trace.rst index ed71462c971..2d4965cc10d 100644 --- a/docs/zh_CN/api-guides/app_trace.rst +++ b/docs/zh_CN/api-guides/app_trace.rst @@ -560,8 +560,8 @@ Start 子命令语法: 应用示例 """""""" -- :example:`system/sysview_tracing` 演示了如何使用 SEGGER SystemView 跟踪 FreeRTOS 任务和系统事件。 -- :example:`system/sysview_tracing_heap_log` 演示了如何在 SystemView 事件的同时跟踪堆内存分配。 +- :example:`system/sysview_tracing` 演示如何使用 SEGGER SystemView 记录 FreeRTOS 任务与系统事件。 +- :example:`system/sysview_tracing_heap_log` 演示如何在记录 SystemView 事件的同时,对堆内存分配进行跟踪。 .. _app_trace-gcov-source-code-coverage: