mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
refactor(examples): group trace examples under examples/system/tracing/
Move 6 trace-related examples (app_trace_basic, app_trace_to_plot, esp_trace, gcov, sysview_tracing, sysview_tracing_heap_log) from examples/system/ into a dedicated examples/system/tracing/ subdirectory for better organization. Update all path references across documentation (en + zh_CN), build-test-rules, CI configs and component READMEs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZAGzon27T12sGbDPPbZ8G
This commit is contained in:
committed by
BOT
co-authored by
Claude Opus 5
parent
640d832d2f
commit
c99a63fd18
@@ -0,0 +1,68 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
examples/system/tracing/app_trace_basic:
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32h4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- app_trace
|
||||
|
||||
examples/system/tracing/esp_trace_custom_library:
|
||||
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
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- freertos
|
||||
|
||||
examples/system/tracing/gcov:
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32h4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- app_trace
|
||||
|
||||
examples/system/tracing/sysview_tracing:
|
||||
disable:
|
||||
- if: SOC_GPTIMER_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32h4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- app_trace
|
||||
|
||||
examples/system/tracing/sysview_tracing_heap_log:
|
||||
disable:
|
||||
- if: SOC_GPTIMER_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32h4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- app_trace
|
||||
@@ -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.22)
|
||||
|
||||
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(app_trace_basic)
|
||||
@@ -0,0 +1,73 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# Application Level Tracing Example (Basic)
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use the [Application Level Tracing Library](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/transports.html) (henceforth referred to as **App Trace**) to log messages to a host via JTAG instead of the normal method of logging via UART.
|
||||
|
||||
UART logs are time consuming and can significantly slow down the function that calls it. Therefore, it is generally a bad idea to use UART logs in time-critical functions. Logging to host via JTAG is significantly faster and can be used in time-critical functions. For more details regarding logging to host via JTAG, refer to the [Logging to Host Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/transports.html#app-trace-logging-to-host).
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need a supported target dev board connected to a JTAG adapter, which can come in the following forms:
|
||||
|
||||
* [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp-wrover-kit/index.html) which integrates an on-board JTAG adapter. Ensure that the [required jumpers to enable JTAG are connected](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/get-started-wrover-kit.html#setup-options) on the WROVER-KIT.
|
||||
* ESP32, ESP32-S2 or ESP32-C2 core board (e.g. ESP32-DevKitC, [ESP32-S2-Saola-1](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s2/esp32-s2-saola-1/index.html)) can also work as long as you connect it to an external JTAG adapter (e.g. FT2232H, J-LINK).
|
||||
|
||||
This example will assume that an ESP-WROVER-KIT is used.
|
||||
|
||||
#### Connections:
|
||||
|
||||
1. Connect the JTAG interface to the target board. For details about how to set up JTAG interface, please see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html). Power up both the JTAG debugger and target board.
|
||||
|
||||
2. After connecting JTAG interface, you need to [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
|
||||
|
||||
3. Open a separate terminal window and run telnet by entering the command below. The telnet terminal window is used to feed commands to OpenOCD:
|
||||
|
||||
```bash
|
||||
telnet localhost 4444
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* To enable application tracing, select the `(X) Trace memory` option under `Component config > Application Level Tracing`. This option should have been selected by default.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
**Start App Trace:** In the telnet session window, trigger OpenOCD to start App Trace on the target by entering the command below. This command will collect 9000 bytes of JTAG log data and save them to the file `file://apptrace.log` (note `file://` depends on
|
||||
where OpenOCD was started). Assuming that OpenOCD was started in this example's directory, `apptrace.log` will be saved here as well.
|
||||
|
||||
|
||||
```bash
|
||||
esp apptrace start file://apptrace.log 0 2000 3 0 0
|
||||
```
|
||||
|
||||
**Note:** For more details on OpenOCD commands regarding App Trace, refer to the [OpenOCD Application Level Tracing Commands](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/transports.html#openocd-application-level-tracing-commands)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Unable to flash when OpenOCD is connected to the target
|
||||
|
||||
On ESP32 boards, one likely cause would be an incorrect SPI flash voltage when starting OpenOCD. Suppose a target board/module with a 3.3 V powered SPI flash is being used, but the configuration file (ex. `board/esp32-wrover.cfg` for ESP32) is selected when starting OpenOCD which can set the SPI flash voltage to 1.8 V. In this situation, the SPI flash will not work after OpenOCD connects to the target as OpenOCD has changed the SPI flash voltage. Therefore, you might not be able to flash to the target when OpenOCD is connected.
|
||||
|
||||
To work around this issue, users are suggested to use `board/esp32-wrover.cfg` for ESP32 boards/modules operating with an SPI flash voltage of 1.8 V, and `board/esp-wroom-32.cfg` for 3.3 V. Refer to [ESP32 Dev Kits](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/index.html) and [Set SPI Flash Voltage](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/tips-and-quirks.html#why-to-set-spi-flash-voltage-in-openocd-configuration) for more details.
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_trace_basic_example_main.c"
|
||||
PRIV_REQUIRES esp_trace
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Application Trace Basic Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/uart_pins.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_app_trace.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#if !CONFIG_APPTRACE_DEST_JTAG
|
||||
/* Override default uart config to use console pins as a uart channel */
|
||||
esp_apptrace_config_t esp_apptrace_get_user_params(void)
|
||||
{
|
||||
esp_apptrace_config_t config = APPTRACE_UART_CONFIG_DEFAULT();
|
||||
config.dest_cfg.uart.uart_num = 0;
|
||||
config.dest_cfg.uart.tx_pin_num = U0TXD_GPIO_NUM;
|
||||
config.dest_cfg.uart.rx_pin_num = U0RXD_GPIO_NUM;
|
||||
return config;
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
if (esp_apptrace_get_destination() == ESP_APPTRACE_DEST_JTAG) {
|
||||
ESP_LOGI(TAG, "Waiting for OpenOCD connection");
|
||||
|
||||
while (!esp_apptrace_host_is_connected()) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
} else { // UART
|
||||
// Before sending data, wait for the UART host to be ready
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Sending example data to the host...");
|
||||
|
||||
for (unsigned int cnt = 1; cnt < 51; ++cnt) {
|
||||
char buf[32] = {0};
|
||||
snprintf(buf, sizeof(buf), "Apptrace test data[%d]:%d\n", cnt, cnt * cnt);
|
||||
esp_err_t res = esp_apptrace_write(buf, strlen(buf), ESP_APPTRACE_TMO_INFINITE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
|
||||
}
|
||||
esp_apptrace_flush(1000);
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Done!");
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import os.path
|
||||
import time
|
||||
import typing
|
||||
|
||||
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
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from conftest import OpenOCD
|
||||
|
||||
|
||||
def _test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
time.sleep(1) # Wait for the USJ port to be ready
|
||||
dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5)
|
||||
with openocd_dut.run() as openocd:
|
||||
openocd.write('reset run')
|
||||
dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5)
|
||||
time.sleep(1) # wait for APPTRACE_INIT semihosting call
|
||||
openocd.write('esp apptrace start file://apptrace.log 0 2000 3 0 0')
|
||||
openocd.apptrace_wait_stop()
|
||||
|
||||
search_strings = ['Targets connected.', 'Disconnect targets...']
|
||||
with open(openocd.log_file, encoding='utf-8') as oocd_log: # pylint: disable=protected-access
|
||||
cores = 1 if dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') is True else 2
|
||||
search_strings.append(f'App trace params: from {cores} cores,')
|
||||
for search_str in search_strings:
|
||||
found = False
|
||||
oocd_log.seek(0)
|
||||
for line in oocd_log:
|
||||
if search_str in line:
|
||||
found = True
|
||||
break
|
||||
if found is not True:
|
||||
raise RuntimeError(f'"{search_str}" could not be found in {openocd.log_file}') # pylint: disable=protected-access
|
||||
|
||||
with open('apptrace.log', encoding='utf-8') as apptrace_log:
|
||||
content = apptrace_log.read()
|
||||
for sample_num in range(1, 51):
|
||||
log_str = f'Apptrace test data[{sample_num}]:{sample_num * sample_num}'
|
||||
if log_str not in content:
|
||||
raise RuntimeError('"{}" could not be found in {}'.format(log_str, 'apptrace.log'))
|
||||
|
||||
|
||||
@pytest.mark.jtag
|
||||
@idf_parametrize('config', ['apptrace_jtag'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||
def test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
_test_examples_app_trace_basic(openocd_dut, dut)
|
||||
|
||||
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@idf_parametrize('config', ['apptrace_jtag'], indirect=['config'])
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
_test_examples_app_trace_basic(openocd_dut, dut)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['apptrace_uart'], indirect=['config'])
|
||||
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_examples_app_trace_basic_uart(dut: IdfDut) -> None:
|
||||
dut.serial.close()
|
||||
with serial.Serial(dut.serial.port, baudrate=1000000, timeout=3) as ser:
|
||||
apptrace_log = os.path.join(dut.logdir, 'apptrace_log_uart.txt') # pylint: disable=protected-access
|
||||
with open(apptrace_log, 'w+b') as f:
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
f.write(ser.read(ser.in_waiting))
|
||||
if time.time() - start_time > 5:
|
||||
break
|
||||
except serial.SerialTimeoutException:
|
||||
assert False, 'Timeout reached while reading from serial port, exiting...'
|
||||
|
||||
f.seek(0)
|
||||
content = f.read().decode('utf-8', errors='ignore')
|
||||
for sample_num in range(1, 51):
|
||||
log_str = f'Apptrace test data[{sample_num}]:{sample_num * sample_num}'
|
||||
if log_str not in content:
|
||||
raise RuntimeError('"{}" could not be found in {}'.format(log_str, 'apptrace_log_uart.txt'))
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_APPTRACE_DEST_JTAG=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_CONSOLE_NONE=y
|
||||
# Destination can be selectable on runtime
|
||||
CONFIG_APPTRACE_DEST_ALL=y
|
||||
@@ -0,0 +1,4 @@
|
||||
# Enable application tracing by default
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
@@ -0,0 +1,10 @@
|
||||
# For more information about build system see
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||
# The following five 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.22)
|
||||
|
||||
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(app_trace_to_plot)
|
||||
@@ -0,0 +1,140 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# Application Level Tracing Example (Plotting)
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use the [Application Level Tracing Library](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/transports.html) (henceforth referred to as **App Trace**) to send and plot dummy sensor data to a host via JTAG instead of the normal method of logging via UART.
|
||||
|
||||
UART logs are time consuming and can significantly slow down the function that calls it. Therefore, it is generally a bad idea to use UART logs in time-critical functions. Logging to host via JTAG is significantly faster and can be used in time-critical functions. For more details regarding logging to host via JTAG, refer to the [Logging to Host Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/transports.html#app-trace-logging-to-host).
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need a supported target dev board connected to a JTAG adapter, which can come in the following forms:
|
||||
|
||||
* [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp-wrover-kit/index.html) which integrates an on-board JTAG adapter. Ensure that the [required jumpers to enable JTAG are connected](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/get-started-wrover-kit.html#setup-options) on the WROVER-KIT.
|
||||
* ESP32, ESP32-S2 or ESP32-C2 core board (e.g. ESP32-DevKitC, [ESP32-S2-Saola-1](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s2/esp32-s2-saola-1/index.html)) can also work as long as you connect it to an external JTAG adapter (e.g. FT2232H, J-LINK).
|
||||
- For ESP32-C3 or ESP32-S3, any board with the built-in USB interface (USB_SERIAL_JTAG).
|
||||
|
||||
This example will assume that an ESP-WROVER-KIT is used.
|
||||
|
||||
#### Connections:
|
||||
|
||||
1. Connect the JTAG interface to the target board. For details about how to set up JTAG interface, please see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html). Power up both the JTAG debugger and target board.
|
||||
|
||||
2. To start the tcp socket server, you need to run `read_trace.py` tool under the `esp-idf/examples/system/tracing/app_trace_to_plot` path.
|
||||
|
||||
3. After connecting JTAG interface and starting the tcp socket server, you need to [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
|
||||
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* To enable application tracing, select the `(X) Trace memory` option under `Component config > Application Level Tracing`. This option should have been selected by default.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
**Run Plotting Tool** To plot data and open TCP socket, there is a tool named `read_trace.py` under the `examples/system/tracing/app_trace_to_plot` path. Run this tool in the terminal session with configured IDF development environment by entering the command below. This command opens a TCP socket and plots the given data when OpenOCD triggered to start App Trace. If you are running tool at first time, you need to install dash with `pip install dash` in the same terminal session after running configuring IDF development environment.
|
||||
|
||||
```bash
|
||||
python read_trace.py --plot-config data.json --source tcp://localhost:53535 --output-file data.log
|
||||
```
|
||||
|
||||
**Start App Trace:** Start OpenOCD and App Trace on the target by entering the command below. This command will start OpenOCD and collect all of the bytes from JTAG log data and send them to the tcp socket `tcp://localhost:53535` (note `tcp://` depends on which IP address and port number opened). Assuming that OpenOCD was started in this example's directory, `data.log` will be saved here as well.
|
||||
|
||||
After running the plotting tool and starting apptrace with OpenOCD separately, you need access plotting on related socket address. Default address is `http://127.0.0.1:8055/` and also address can be seen from `read_trace.py` output. You can see the plotting result by accessing the address from browser.
|
||||
|
||||
|
||||
```bash
|
||||
idf.py openocd --openocd-commands "-f board/esp32-wrover-kit-3.3v.cfg -c 'init;reset;esp apptrace start tcp://localhost:53535 0 -1 5'"
|
||||
```
|
||||
|
||||

|
||||
|
||||
**Note:** data.json file is an example for plot config file. It can be changed or modified.
|
||||
|
||||
**Note:** For more details on OpenOCD commands regarding App Trace, refer to the [OpenOCD Application Level Tracing Commands](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/transports.html#openocd-application-level-tracing-commands)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
### Configuration file and data format
|
||||
|
||||
#### General format of json file is:
|
||||
|
||||
```JSON
|
||||
"Subplot Name": {
|
||||
"data_streams"
|
||||
"Name of the sensor": {
|
||||
"id": int
|
||||
"x_axis_data_size": int
|
||||
"x_axis_timestamp": bool
|
||||
"y_axis_data_size": int
|
||||
"data_type": "string"
|
||||
["precision": int]
|
||||
["type": "string"]
|
||||
["mode": "string"]
|
||||
["any_plot_config": Type defined in plotly]
|
||||
}
|
||||
}
|
||||
"xaxis_title": "string",
|
||||
"yaxis_title": "string"
|
||||
}
|
||||
```
|
||||
|
||||
#### Explanations of JSON keys are:
|
||||
|
||||
| JSON Keys | Explanation |
|
||||
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| id | ID value of the incoming packet (e.g. 1, 2, 3) |
|
||||
| x_axis_data_size | Data size of x axis of the incoming packet in bytes (e.g 2, 4, 8) |
|
||||
| x_axis_timestamp | Will x axis data be timestamp value (e.g. true, false) |
|
||||
| y_axis_data_size | Data size of y axis of the incoming packet in bytes (e.g 2, 4, 8) |
|
||||
| data_type | Data type of y axis of the incoming packet (e.g. "int", "float", "double") |
|
||||
| precision | Optional. If y axis data of the incoming packet is a floating point number, you need to send it in integer form by multiplying a power of 10 and adjust this field (e.g. 1, 2, 3) |
|
||||
| type | Optional. Determines the plotting type of the graph (e.g. "scatter", "bar"). Default is "scatter" |
|
||||
| mode | Optional. Determines how values will look like (e.g. "lines", "markers", "lines+markers"). Default is "lines" |
|
||||
| xaxis_title | Title of x axis in the plot (e.g "time") |
|
||||
| yaxis_title | Title of y axis in the plot (e.g "values", "sensor data") |
|
||||
|
||||
**Note:** Plotting works with plotly subplot feature. `id`, `timestamp_flag`, `data_size` and `data_type` are the mandatory elements. In addition to these elements, you can config your plot with [properties are passed to the constructor of the specified trace type](https://plotly.com/python/reference/scatter/). Data transferring is in {'x': ..., 'y': ..., ...} format. Graph types like Pie or Scatter3D won't work. Scatter, histogram, box, line and similar graph types are working.
|
||||
|
||||
#### Data format in microcontroller side is:
|
||||
|
||||
```C
|
||||
struct format {
|
||||
char STX[5] = "esp32",
|
||||
uint32_t id,
|
||||
any_size y_axis_value,
|
||||
any_size x_axis_value
|
||||
char EXT = 0x03,
|
||||
}
|
||||
```
|
||||
**Note** STX and EXT fields are mandatory and constant to check data validity in visualizer side. You cannot change or remove these fields.
|
||||
|
||||
**Note** y_axis_value and x_axis_value could be in any value and different sizes like long, short, and int. The only thing to do is enter the correct data size in the config file.
|
||||
|
||||
**Note:** If you want to send a floating point number, send it in integer form by multiplying a power of 10 and filling the precision field in the config file.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Unable to flash when OpenOCD is connected to the target
|
||||
|
||||
On ESP32 boards, one likely cause would be an incorrect SPI flash voltage when starting OpenOCD. Suppose a target board/module with a 3.3 V powered SPI flash is being used, but the configuration file (ex. `board/esp32-wrover.cfg` for ESP32) is selected when starting OpenOCD which can set the SPI flash voltage to 1.8 V. In this situation, the SPI flash will not work after OpenOCD connects to the target as OpenOCD has changed the SPI flash voltage. Therefore, you might not be able to flash to the target when OpenOCD is connected.
|
||||
|
||||
To work around this issue, users are suggested to use `board/esp32-wrover.cfg` for ESP32 boards/modules operating with an SPI flash voltage of 1.8 V, and `board/esp-wroom-32.cfg` for 3.3 V. Refer to [ESP32 Dev Kits](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/index.html) and [Set SPI Flash Voltage](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/tips-and-quirks.html#why-to-set-spi-flash-voltage-in-openocd-configuration) for more details.
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"//data.json": "Apptrace plotting config file for 'read_trace.py'",
|
||||
"//Summary and usage": "This file is used for apptrace plotting. You can use this json file with '--plot-config' parameter of the 'read_trace.py' file is to configure graphs for visualizing sensor data",
|
||||
"//More information": "To get more information about apptrace plotting and configuration file, please check 'app_trace_to_plot' example in '../examples/system/tracing' and 'read_trace.py' file",
|
||||
"Temperature sensors": {
|
||||
"data_streams" : {
|
||||
"Outside temperature": {
|
||||
"id": 1,
|
||||
"x_axis_data_size": 4,
|
||||
"x_axis_timestamp":true,
|
||||
"y_axis_data_size": 1,
|
||||
"data_type": "int",
|
||||
"opacity": 0.5,
|
||||
"line_color":"crimson",
|
||||
"marker_line_width":2,
|
||||
"marker_size":9,
|
||||
"mode": "lines+markers"
|
||||
},
|
||||
"Engine temperature": {
|
||||
"id": 2,
|
||||
"x_axis_data_size": 4,
|
||||
"x_axis_timestamp":true,
|
||||
"y_axis_data_size": 2,
|
||||
"data_type": "int",
|
||||
"line_color": "#0d0887",
|
||||
"mode": "lines+markers"
|
||||
}
|
||||
},
|
||||
"xaxis_title": "Timestamp",
|
||||
"yaxis_title": "Temperature in Celsius"
|
||||
},
|
||||
"Altitude sensors": {
|
||||
"data_streams" : {
|
||||
"Altitude": {
|
||||
"id": 3,
|
||||
"x_axis_data_size": 4,
|
||||
"x_axis_timestamp":true,
|
||||
"y_axis_data_size": 4,
|
||||
"data_type": "int",
|
||||
"fill": "tozeroy",
|
||||
"fillcolor": "gray",
|
||||
"hovertext": "(in meter)",
|
||||
"type": "scatter",
|
||||
"fillpattern": {
|
||||
"shape": "x"
|
||||
},
|
||||
"error_y": {
|
||||
"type":"percent",
|
||||
"value": 10
|
||||
},
|
||||
"mode": "markers"
|
||||
}
|
||||
},
|
||||
"xaxis_title": "Timestamp",
|
||||
"yaxis_title": "Altitude in meters"
|
||||
},
|
||||
"Pressure sensors": {
|
||||
"data_streams" : {
|
||||
"Pressure": {
|
||||
"id": 4,
|
||||
"x_axis_data_size": 4,
|
||||
"x_axis_timestamp":true,
|
||||
"y_axis_data_size": 8,
|
||||
"data_type": "float",
|
||||
"precision": 2,
|
||||
"type": "bar"
|
||||
}
|
||||
},
|
||||
"xaxis_title": "Timestamp",
|
||||
"yaxis_title": "Pressure in milibar"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_trace_to_plot.c"
|
||||
PRIV_REQUIRES esp_trace
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Application Trace to Plot Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_app_trace.h"
|
||||
#include "esp_trace.h"
|
||||
#include "esp_log.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#define CYCLE_PERIOD 51
|
||||
#define STX "esp32"
|
||||
#define STX_LENGTH 5
|
||||
#define ETX 0x03
|
||||
#define MAX_PACKAGE_SIZE 25
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
uint32_t timestamp;
|
||||
} __attribute__((packed)) package_header_t;
|
||||
|
||||
typedef struct {
|
||||
package_header_t header;
|
||||
int8_t value;
|
||||
} __attribute__((packed)) sensor_data_pkt_t;
|
||||
|
||||
typedef struct {
|
||||
package_header_t header;
|
||||
int16_t value;
|
||||
} __attribute__((packed)) sensor2_data_pkt_t;
|
||||
|
||||
typedef struct {
|
||||
package_header_t header;
|
||||
int32_t value;
|
||||
} __attribute__((packed)) sensor3_data_pkt_t;
|
||||
|
||||
typedef struct {
|
||||
package_header_t header;
|
||||
int64_t value;
|
||||
} __attribute__((packed)) sensor4_data_pkt_t;
|
||||
|
||||
int16_t get_engine_temperature(int cnt)
|
||||
{
|
||||
/* A healhty engine should work between 90 to 105 degree Celsius */
|
||||
const int32_t temp_arr[] = {1, 1, 2, 3, 4, 6, 8, 10, 13, 16, 19, 24, 29, 34, 40, 47, 53,
|
||||
60, 68, 75, 82, 88, 94, 99, 103, 105, 107, 107, 106, 104, 101, 96, 91, 85, 78, 71, 64,
|
||||
57, 50, 43, 37, 31, 26, 21, 17, 14, 11, 9, 7, 5, 4, 3};
|
||||
return temp_arr[cnt];
|
||||
}
|
||||
|
||||
int8_t get_outside_temperature(int cnt)
|
||||
{
|
||||
/* Recorded highest temperature was around 57 degree and lowest was -89 degree Celsius */
|
||||
return (rand() % 146) - 89;
|
||||
}
|
||||
|
||||
int32_t get_altitude(void)
|
||||
{
|
||||
/* Very high altidude is around 4000 meter */
|
||||
return rand() % 4000;
|
||||
}
|
||||
|
||||
int32_t get_pressure(int cnt)
|
||||
{
|
||||
/* Highest pressure recorded around 1.084b and lowest was 0.870b on earth */
|
||||
return (int)(((sin(cnt) / 10) + 1) * 100);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Waiting for OpenOCD connection");
|
||||
while (!esp_apptrace_host_is_connected()) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Sending data to the host...");
|
||||
uint32_t cnt = 0;
|
||||
uint8_t buf[MAX_PACKAGE_SIZE] = {0};
|
||||
sensor_data_pkt_t sensor;
|
||||
sensor2_data_pkt_t sensor2;
|
||||
sensor3_data_pkt_t sensor3;
|
||||
sensor4_data_pkt_t sensor4;
|
||||
|
||||
memcpy(buf, (uint8_t *)&STX, STX_LENGTH);
|
||||
|
||||
while (esp_apptrace_host_is_connected()) {
|
||||
sensor.header.id = 1;
|
||||
sensor.header.timestamp = esp_log_timestamp();
|
||||
sensor.value = get_outside_temperature(cnt);
|
||||
|
||||
buf[STX_LENGTH] = sizeof(sensor);
|
||||
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor, sizeof(sensor));
|
||||
buf[STX_LENGTH + 1 + sizeof(sensor)] = ETX;
|
||||
esp_err_t res = esp_apptrace_write(buf, sizeof(sensor) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
|
||||
break;
|
||||
}
|
||||
/* Delay added for represent the data better on the plot */
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
sensor2.header.id = 2;
|
||||
sensor2.header.timestamp = esp_log_timestamp();
|
||||
sensor2.value = get_engine_temperature(cnt);
|
||||
|
||||
buf[STX_LENGTH] = sizeof(sensor2);
|
||||
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor2, sizeof(sensor2));
|
||||
buf[STX_LENGTH + 1 + sizeof(sensor2)] = ETX;
|
||||
res = esp_apptrace_write(buf, sizeof(sensor2) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
|
||||
break;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
if (cnt % 2 == 0) {
|
||||
sensor3.header.id = 3;
|
||||
sensor3.header.timestamp = esp_log_timestamp();
|
||||
sensor3.value = get_altitude();
|
||||
|
||||
buf[STX_LENGTH] = sizeof(sensor3);
|
||||
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor3, sizeof(sensor3));
|
||||
buf[STX_LENGTH + 1 + sizeof(sensor3)] = ETX;
|
||||
res = esp_apptrace_write(buf, sizeof(sensor3) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
|
||||
break;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
sensor4.header.id = 4;
|
||||
sensor4.header.timestamp = esp_log_timestamp();
|
||||
sensor4.value = get_pressure(cnt);
|
||||
|
||||
buf[STX_LENGTH] = sizeof(sensor4);
|
||||
memcpy(buf + STX_LENGTH + 1, (uint8_t *)&sensor4, sizeof(sensor4));
|
||||
buf[STX_LENGTH + 1 + sizeof(sensor4)] = ETX;
|
||||
res = esp_apptrace_write(buf, sizeof(sensor4) + STX_LENGTH + 1/*sensor pkt len*/ + 1/*ETX len*/, ESP_APPTRACE_TMO_INFINITE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write data to host [0x%x] (%s)", res, esp_err_to_name(res));
|
||||
break;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
esp_apptrace_flush(1000);
|
||||
cnt = (cnt + 1) % CYCLE_PERIOD;
|
||||
}
|
||||
ESP_LOGE(TAG, "Apptrace connection lost");
|
||||
ESP_LOGI(TAG, "Data sent and getting back to the UART...");
|
||||
ESP_LOGI(TAG, "Done!");
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
import espytrace.apptrace
|
||||
except ImportError: # cheat and use IDF's copy of espytrace if available
|
||||
idf_path = os.getenv('IDF_PATH')
|
||||
if not idf_path or not os.path.exists(idf_path):
|
||||
print('IDF not found. Please export idf')
|
||||
raise SystemExit(1)
|
||||
sys.path.insert(0, os.path.join(idf_path, 'tools', 'esp_app_trace'))
|
||||
import espytrace.apptrace
|
||||
|
||||
try:
|
||||
import dash
|
||||
from dash import dcc
|
||||
from dash import html
|
||||
from dash.dependencies import Input
|
||||
from dash.dependencies import Output
|
||||
from plotly.subplots import make_subplots
|
||||
except ImportError:
|
||||
print("Dash not found. Try to run 'pip install dash'")
|
||||
raise SystemExit(1)
|
||||
|
||||
plots: list[Any] = []
|
||||
output_lines: list[Any] = []
|
||||
COMMENT_LINE = '//'
|
||||
|
||||
|
||||
class States(Enum):
|
||||
STX_WAIT = 1
|
||||
LENGTH_WAIT = 2
|
||||
DATA_WAIT = 3
|
||||
ETX_WAIT = 4
|
||||
|
||||
|
||||
app = dash.Dash(__name__)
|
||||
app.layout = html.Div(
|
||||
html.Div(
|
||||
[
|
||||
html.H2('Telemetry Data'),
|
||||
html.Div(id='live-update-data'),
|
||||
dcc.Graph(id='live-update-graph', style={'height': 800}), # Height of the plotting area set to 800px
|
||||
dcc.Interval(
|
||||
id='interval-component',
|
||||
interval=5 * 100, # Graph will be updated every 500 ms
|
||||
n_intervals=0,
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Multiple components can update every time interval gets fired.
|
||||
@app.callback(Output('live-update-graph', 'figure'), Input('interval-component', 'n_intervals'))
|
||||
def update_graph_live(_n: Any) -> Any: # pylint: disable=undefined-argument
|
||||
excluded_keys_for_plot = {
|
||||
'id',
|
||||
'x_axis_data_size',
|
||||
'y_axis_data_size',
|
||||
'data_type',
|
||||
'precision',
|
||||
'x_axis_timestamp',
|
||||
}
|
||||
fig = make_subplots(
|
||||
rows=len(plots), cols=1, vertical_spacing=0.2, subplot_titles=[each_plot['title'] for each_plot in plots]
|
||||
)
|
||||
|
||||
for i, each_plot in enumerate(plots, start=1):
|
||||
for each_subplot in each_plot['plots']:
|
||||
plot_dict = {k: each_subplot[k] for k in each_subplot.keys() - excluded_keys_for_plot}
|
||||
fig.append_trace(plot_dict, i, 1)
|
||||
fig['layout'][f'xaxis{i}']['title'] = each_plot['xaxis_title']
|
||||
fig['layout'][f'yaxis{i}']['title'] = each_plot['yaxis_title']
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
def get_value_from_key(input_id: int, key: str) -> Any:
|
||||
for each_plot in plots:
|
||||
for each_subplot in each_plot['plots']:
|
||||
if each_subplot['id'] == input_id:
|
||||
return each_subplot[key]
|
||||
return None
|
||||
|
||||
|
||||
def parse_data_and_print(packet: bytes, offset_time: Any) -> None:
|
||||
ID_LENGTH = 4
|
||||
x_axis_data_length = 4
|
||||
y_axis_data_length = 4
|
||||
|
||||
input_id = int.from_bytes(packet[:ID_LENGTH], 'little')
|
||||
data_size = get_value_from_key(input_id, 'x_axis_data_size')
|
||||
x_axis_data_length = data_size
|
||||
x_axis_raw_data = int.from_bytes(packet[ID_LENGTH : ID_LENGTH + x_axis_data_length], 'little')
|
||||
is_timestamp = get_value_from_key(input_id, 'x_axis_timestamp')
|
||||
if is_timestamp:
|
||||
x_axis_data = offset_time + datetime.timedelta(seconds=x_axis_raw_data / 1000)
|
||||
else:
|
||||
x_axis_data = float(x_axis_raw_data)
|
||||
data_size = get_value_from_key(input_id, 'y_axis_data_size')
|
||||
y_axis_data_length = data_size
|
||||
y_axis_data = int.from_bytes(
|
||||
packet[x_axis_data_length + ID_LENGTH : x_axis_data_length + ID_LENGTH + y_axis_data_length],
|
||||
'little',
|
||||
signed=True,
|
||||
)
|
||||
if get_value_from_key(input_id, 'data_type') in ['float', 'double']:
|
||||
precision = get_value_from_key(input_id, 'precision')
|
||||
y_axis_data = y_axis_data / (10**precision)
|
||||
|
||||
ct = datetime.datetime.now()
|
||||
str_ctr = ct.strftime('%x-%X.%f')
|
||||
|
||||
output_str = str_ctr + '-> ' + str(packet)
|
||||
output_lines.append(output_str)
|
||||
|
||||
arr = [x_axis_data, y_axis_data]
|
||||
update_specific_graph_list(int(input_id), arr)
|
||||
|
||||
|
||||
class CustomRequestHandler(espytrace.apptrace.TCPRequestHandler):
|
||||
"""
|
||||
Handler for incoming TCP connections
|
||||
"""
|
||||
|
||||
def handle(self) -> None:
|
||||
STX = b'esp32'
|
||||
ETX = b'\x03'
|
||||
STX_LEN = 5
|
||||
|
||||
ETX_LEN = 1
|
||||
DATA_LENGTH_LEN = 1
|
||||
PACKET_TIMEOUT_SECOND = 3
|
||||
|
||||
state = States.STX_WAIT
|
||||
val_to_parse = b''
|
||||
length = 0
|
||||
offset_time = datetime.datetime.now()
|
||||
start_time = datetime.datetime.now()
|
||||
data = b''
|
||||
while not self.server.need_stop:
|
||||
data += self.request.recv(1024)
|
||||
if state == States.STX_WAIT:
|
||||
if len(data) >= STX_LEN and data.find(STX) != -1:
|
||||
data = data[data.find(STX) + STX_LEN :]
|
||||
state = States.LENGTH_WAIT
|
||||
start_time = datetime.datetime.now()
|
||||
else:
|
||||
data = data[-STX_LEN:]
|
||||
if state == States.LENGTH_WAIT:
|
||||
if len(data) > 0:
|
||||
state = States.DATA_WAIT
|
||||
length = int.from_bytes(data[:DATA_LENGTH_LEN], 'little')
|
||||
data = data[DATA_LENGTH_LEN:]
|
||||
if state == States.DATA_WAIT:
|
||||
if len(data) >= length:
|
||||
state = States.ETX_WAIT
|
||||
val_to_parse = data[:length]
|
||||
data = data[length:]
|
||||
if state == States.ETX_WAIT:
|
||||
if len(data) >= ETX_LEN and data[:ETX_LEN] == ETX:
|
||||
state = States.STX_WAIT
|
||||
parse_data_and_print(val_to_parse, offset_time)
|
||||
data = data[ETX_LEN:]
|
||||
if state != States.STX_WAIT and (datetime.datetime.now() - start_time).seconds > PACKET_TIMEOUT_SECOND:
|
||||
print('Packet timed out. Dropping!')
|
||||
state = States.STX_WAIT
|
||||
|
||||
|
||||
def read_json(file_path: str) -> Any:
|
||||
with open(file_path, encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
|
||||
def save_data(file_path: str) -> None:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(output_lines)
|
||||
|
||||
|
||||
def signal_handler(output_file_path: str, reader: Any, sig: Any, frame: Any) -> None:
|
||||
del sig, frame
|
||||
reader.cleanup()
|
||||
if output_file_path is not None:
|
||||
save_data(output_file_path)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def update_specific_graph_list(input_id: int, val: list[Any]) -> None:
|
||||
for each_plot in plots:
|
||||
for each_subplot in each_plot['plots']:
|
||||
if each_subplot['id'] == input_id:
|
||||
each_subplot['x'].append(val[0])
|
||||
each_subplot['y'].append(float(val[1]))
|
||||
return
|
||||
|
||||
|
||||
def check_entry_and_get_struct(entry: dict, data_label: str) -> dict:
|
||||
mandatory_key = 'id'
|
||||
other_keys = {'x': [], 'y': [], 'type': 'scatter'}
|
||||
|
||||
ret_dict = entry
|
||||
if ret_dict.get(mandatory_key) is None:
|
||||
raise KeyError('ID key is missing')
|
||||
|
||||
ret_dict['name'] = data_label
|
||||
for each_key, each_value in other_keys.items():
|
||||
if ret_dict.get(each_key) is None:
|
||||
ret_dict[each_key] = each_value
|
||||
|
||||
return ret_dict
|
||||
|
||||
|
||||
def validate_json(json_file: Any) -> None:
|
||||
mandatory_keys = {
|
||||
'id': int,
|
||||
'x_axis_data_size': int,
|
||||
'y_axis_data_size': int,
|
||||
'data_type': str,
|
||||
'x_axis_timestamp': bool,
|
||||
}
|
||||
for each_plot in json_file:
|
||||
if each_plot.startswith(COMMENT_LINE):
|
||||
continue
|
||||
try:
|
||||
each_subplot = json_file[each_plot]['data_streams']
|
||||
except KeyError:
|
||||
print('data_streams key not found. Aborting')
|
||||
raise SystemExit(1)
|
||||
for each_subplot in json_file[each_plot]['data_streams']:
|
||||
for key, value in mandatory_keys.items():
|
||||
try:
|
||||
val = json_file[each_plot]['data_streams'][each_subplot][key]
|
||||
if not isinstance(val, value):
|
||||
print(
|
||||
f'[{each_plot}][data_streams][{each_subplot}][{key}] '
|
||||
f'expected {mandatory_keys[key]} found {type(val)}'
|
||||
)
|
||||
raise SystemExit(1)
|
||||
except KeyError:
|
||||
print(f'[{each_plot}][data_streams][{each_subplot}][{key}] key not found. Aborting')
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def configure_plots(json_file: Any) -> None:
|
||||
for each_plot in json_file:
|
||||
if each_plot.startswith(COMMENT_LINE):
|
||||
continue
|
||||
data_struct = {
|
||||
'title': each_plot,
|
||||
'plots': [],
|
||||
'xaxis_title': json_file[each_plot]['xaxis_title'],
|
||||
'yaxis_title': json_file[each_plot]['yaxis_title'],
|
||||
}
|
||||
for each_subplot in json_file[each_plot]['data_streams']:
|
||||
subplot_items = json_file[each_plot]['data_streams'][each_subplot]
|
||||
plot_data_struct = check_entry_and_get_struct(subplot_items, each_subplot)
|
||||
data_struct['plots'].append(plot_data_struct)
|
||||
plots.append(data_struct)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description='Apptrace Visualizing Tool')
|
||||
parser.add_argument(
|
||||
'--plot-config',
|
||||
help='Path to json file',
|
||||
required=False,
|
||||
type=str,
|
||||
default=os.path.realpath(os.path.join(os.path.dirname(__file__), 'data.json')),
|
||||
)
|
||||
parser.add_argument('--source', help='Data source path', required=True, type=str)
|
||||
parser.add_argument('--output-file', help='Path to program output file in txt format', type=str)
|
||||
|
||||
args = parser.parse_args()
|
||||
output_file_path = args.output_file
|
||||
json_file_name = args.plot_config
|
||||
data_source = args.source
|
||||
|
||||
json_file = read_json(json_file_name)
|
||||
validate_json(json_file)
|
||||
configure_plots(json_file)
|
||||
reader = espytrace.apptrace.reader_create(data_source, 1, CustomRequestHandler)
|
||||
signal.signal(signal.SIGINT, partial(signal_handler, output_file_path, reader))
|
||||
|
||||
app.run(debug=True, use_reloader=False, port=8055)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,5 @@
|
||||
# Enable application tracing by default
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_APPTRACE_DEST_JTAG=y
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 787 KiB |
@@ -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_custom_library)
|
||||
@@ -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_custom_library/
|
||||
├── 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 read out 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(); // flush 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.
|
||||
+23
@@ -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()
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "trace_FreeRTOS.h"
|
||||
+68
@@ -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. */
|
||||
+141
@@ -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);
|
||||
+147
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
REQUIRES ext_trace_lib
|
||||
INCLUDE_DIRS "")
|
||||
@@ -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");
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
# 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...'
|
||||
|
||||
# Read out 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)
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], 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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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.22)
|
||||
|
||||
# keep this string to detect as project file in CI:
|
||||
#include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
file(TO_NATIVE_PATH "$ENV{IDF_PATH}/tools/cmake/project.cmake" _project_path)
|
||||
|
||||
include(${_project_path})
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(gcov_example)
|
||||
|
||||
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/coverage_report" _coverage_path)
|
||||
|
||||
idf_create_coverage_report(${_coverage_path})
|
||||
idf_clean_coverage_report(${_coverage_path})
|
||||
@@ -0,0 +1,182 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# Blink Example With Coverage Info (Gcov)
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
The following example demonstrates how to compile an ESP-IDF project to generate code coverage data, and how generate a code coverage report using Gcov. Refer to the [esp_gcov component](https://components.espressif.com/components/espressif/esp_gcov/) for more details on the code coverage features supported in ESP-IDF.
|
||||
|
||||
This example implements a simple blink application but with code coverage enabled. The example will demonstrate the following features:
|
||||
* How to add the esp_gcov component dependency
|
||||
* How to compile a project with coverage info enabled
|
||||
* Various methods of dumping code coverage data (e.g. Instant Run-Time Dump and Hard-coded Dump)
|
||||
* How to generate a code coverage report
|
||||
|
||||
## How to use example
|
||||
|
||||
### Component Dependency
|
||||
|
||||
This example uses the esp_gcov component for code coverage functionality. The component is already added as a dependency in the `main/idf_component.yml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
espressif/esp_gcov: ^1
|
||||
```
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need a supported dev board connected to a JTAG adapter, which can come in the following forms:
|
||||
|
||||
* [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp-wrover-kit/index.html) which integrates an on-board JTAG adapter. Ensure that the [required jumpers to enable JTAG are connected](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/get-started-wrover-kit.html#setup-options) on the WROVER-KIT.
|
||||
* ESP core board (e.g. ESP32-DevKitC) can also work as long as you connect it to an external JTAG adapter (e.g. FT2232H, J-LINK).
|
||||
|
||||
This example will assume that that an ESP-WROVER-KIT is used.
|
||||
|
||||
1. Connect the JTAG interface to ESP32 board, and power up both the JTAG and ESP32. For details about how to set up JTAG interface, please see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html).
|
||||
|
||||
2. After connecting JTAG interface, you need to [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
|
||||
|
||||
3. Open a separate terminal window and run telnet by entering the command below. The telnet terminal window is used to feed commands to OpenOCD:
|
||||
|
||||
```bash
|
||||
telnet localhost 4444
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
The example will enable the following options by default:
|
||||
|
||||
* Enable the Application Tracing Module under `Component config -> Application Level Tracing -> Data Destination` by choosing `JTAG`.
|
||||
* Enable GCOV to host interface under `Component config -> GNU Code Coverage -> GCOV to Host Enable`.
|
||||
* Enable OpenOCD Debug Stubs under `Component config -> ESP System Settings -> OpenOCD debug stubs`
|
||||
|
||||
**Note:** With the esp_gcov component, the GCOV configuration options appear under the component's own menu section.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
### 1. Hard-coded Dump
|
||||
|
||||
The example will initially execute two hard-coded dumps. Therefore, when the application outputs `Ready to dump GCOV data...`, users should execute the `esp gcov dump` OpenOCD command. The example should output the following:
|
||||
|
||||
```
|
||||
blink_dummy_func: Counter = 0
|
||||
some_dummy_func: Counter = 0
|
||||
Ready to dump GCOV data...
|
||||
GCOV data have been dumped.
|
||||
blink_dummy_func: Counter = 1
|
||||
some_dummy_func: Counter = 2
|
||||
Ready to dump GCOV data...
|
||||
GCOV data have been dumped.
|
||||
```
|
||||
|
||||
### 2. Instant Run-Time Dump
|
||||
|
||||
After the two hard-coded dumps, the example will continue looping through it's main blink function. Users can call `esp gcov` OpenOCD command to trigger an instant run-time dump. The output should resemble the following:
|
||||
|
||||
```
|
||||
blink_dummy_func: Counter = 2
|
||||
some_dummy_func: Counter = 4
|
||||
blink_dummy_func: Counter = 3
|
||||
some_dummy_func: Counter = 6
|
||||
blink_dummy_func: Counter = 4
|
||||
some_dummy_func: Counter = 8
|
||||
blink_dummy_func: Counter = 5
|
||||
some_dummy_func: Counter = 10
|
||||
blink_dummy_func: Counter = 6
|
||||
some_dummy_func: Counter = 12
|
||||
blink_dummy_func: Counter = 7
|
||||
some_dummy_func: Counter = 14
|
||||
blink_dummy_func: Counter = 8
|
||||
some_dummy_func: Counter = 16
|
||||
blink_dummy_func: Counter = 9
|
||||
some_dummy_func: Counter = 18
|
||||
blink_dummy_func: Counter = 10
|
||||
some_dummy_func: Counter = 20
|
||||
...
|
||||
```
|
||||
|
||||
### Generating Gcovr Report
|
||||
|
||||
After dumping one or more times, a coverage report can be generated by calling `idf.py gcovr-report`. This should result in an HTML code coverage report being generated in the build directory.
|
||||
|
||||
To clean Gcov and report related data from the build directory, call `idf.py cov-data-clean`
|
||||
|
||||
The following log should be output when generating the coverage report:
|
||||
|
||||
```
|
||||
Executing action: gcovr-report
|
||||
Running ninja in directory /home/user/esp/esp-idf/examples/system/gcov/build
|
||||
Executing "ninja gcovr-report"...
|
||||
[1/2] Generating coverage report in: /home/user/esp/esp-idf/examples/system/gcov/build/coverage_report
|
||||
Using gcov: xtensa-esp32-elf-gcov
|
||||
[2/2] cd /home/user/esp/esp-idf/examples/system/gcov/build && gcovr -r /home/user/esp/esp-idf/examples/system/gcov...a-esp32-elf-gcov -s --html-details /home/user/esp/esp-idf/examples/system/gcov/build/coverage_report/html/index.htm
|
||||
lines: 100.0% (27 out of 27)
|
||||
branches: 100.0% (2 out of 2)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### OpenOCD Out of Sync
|
||||
|
||||
If the following log is output when issuing an OpenOCD command via telnet, it could indicate that OpenOCD and the ESP32 are out of sync. This occurs when the ESP32 is externally reset whilst connected to OpenOCD (e.g., by pressing the EN button).
|
||||
|
||||
```
|
||||
Open On-Chip Debugger
|
||||
> esp gcov dump
|
||||
Target halted. PRO_CPU: PC=0x4008AFF4 (active) APP_CPU: PC=0x400E396E
|
||||
Total trace memory: 16384 bytes
|
||||
Connect targets...
|
||||
Target halted. PRO_CPU: PC=0x400D5D74 (active) APP_CPU: PC=0x400E396E
|
||||
timed out while waiting for target halted / 1 - 2
|
||||
Failed to wait halt on bp target (-4)!
|
||||
Failed to halt targets (-4)!
|
||||
Failed to connect to targets (-4)!
|
||||
```
|
||||
|
||||
This issue can be resolved in the following ways:
|
||||
* Reset the board by issuing the `reset` command via telnet
|
||||
* Restart OpenOCD
|
||||
|
||||
### gcovr not found
|
||||
|
||||
gcovr can be installed from the package database of your operating system or directly as a Python package, e.g:
|
||||
|
||||
```
|
||||
python -m pip install gcovr
|
||||
```
|
||||
|
||||
## Using Code Coverage in Your Own Project
|
||||
|
||||
To use code coverage functionality in your own ESP-IDF project, add the esp_gcov component as a dependency in your `idf_component.yml` manifest file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
espressif/esp_gcov: ^1
|
||||
```
|
||||
|
||||
After adding the dependency:
|
||||
|
||||
1. Configure the project using `idf.py menuconfig` to enable the necessary tracing and GCOV options
|
||||
2. Include the esp_gcov header in your code: `#include "esp_gcov.h"`
|
||||
3. Use the coverage dumping functions as demonstrated in this example
|
||||
|
||||
For detailed documentation, refer to the [esp_gcov component repository](https://components.espressif.com/components/espressif/esp_gcov/).
|
||||
@@ -0,0 +1,6 @@
|
||||
idf_component_register(SRCS "some_funcs.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
set_source_files_properties(some_funcs.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
--coverage)
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
void some_dummy_func(void)
|
||||
{
|
||||
static int i;
|
||||
printf("some_dummy_func: Counter = %d\n", i++);
|
||||
i++;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
idf_component_register(SRCS "gcov_example_main.c"
|
||||
"gcov_example_func.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES "sample" "esp_driver_gpio")
|
||||
|
||||
set_source_files_properties(gcov_example_main.c
|
||||
gcov_example_func.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
--coverage)
|
||||
@@ -0,0 +1,16 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
|
||||
|
||||
config BLINK_GPIO
|
||||
int "Blink GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C2
|
||||
default 18 if IDF_TARGET_ESP32S2
|
||||
default 48 if IDF_TARGET_ESP32S3
|
||||
default 5
|
||||
help
|
||||
GPIO number (IOxx) to blink on and off or the RMT signal for the addressable LED.
|
||||
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
void blink_dummy_func(void)
|
||||
{
|
||||
static int i;
|
||||
printf("blink_dummy_func: Counter = %d\n", i++);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_gcov.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
/* Can use project configuration menu (idf.py menuconfig) to choose the GPIO
|
||||
to blink, or you can edit the following line and set a number here.
|
||||
*/
|
||||
#define BLINK_GPIO CONFIG_BLINK_GPIO
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
void blink_dummy_func(void);
|
||||
void some_dummy_func(void);
|
||||
|
||||
#if !CONFIG_APPTRACE_DEST_JTAG
|
||||
#include "soc/uart_pins.h"
|
||||
#include "esp_app_trace.h"
|
||||
/* Override default uart config to use console pins as a uart channel */
|
||||
esp_apptrace_config_t esp_apptrace_get_user_params(void)
|
||||
{
|
||||
esp_apptrace_config_t config = APPTRACE_UART_CONFIG_DEFAULT();
|
||||
config.dest_cfg.uart.uart_num = 0;
|
||||
config.dest_cfg.uart.tx_pin_num = U0TXD_GPIO_NUM;
|
||||
config.dest_cfg.uart.rx_pin_num = U0RXD_GPIO_NUM;
|
||||
return config;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void blink_task(void *pvParameter)
|
||||
{
|
||||
ESP_LOGI(TAG, "Ready for OpenOCD connection");
|
||||
|
||||
// The first two iterations GCOV data are dumped using call to esp_gcov_dump() and OOCD's "esp32 gcov dump" command.
|
||||
// After that they can be dumped using OOCD's "esp32 gcov" command only.
|
||||
int dump_gcov_after = -2;
|
||||
/* Configure the IOMUX register for pad BLINK_GPIO (some pads are
|
||||
muxed to GPIO on reset already, but some default to other
|
||||
functions and need to be switched to GPIO. Consult the
|
||||
Technical Reference for a list of pads and their default
|
||||
functions.)
|
||||
*/
|
||||
gpio_reset_pin(BLINK_GPIO);
|
||||
/* Set the GPIO as a push/pull output */
|
||||
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
|
||||
|
||||
while(1) {
|
||||
/* Blink off (output low) */
|
||||
gpio_set_level(BLINK_GPIO, 0);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
/* Blink on (output high) */
|
||||
gpio_set_level(BLINK_GPIO, 1);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
blink_dummy_func();
|
||||
some_dummy_func();
|
||||
if (dump_gcov_after++ < 0) {
|
||||
// Dump gcov data
|
||||
printf("Ready to dump GCOV data...\n");
|
||||
esp_gcov_dump();
|
||||
printf("GCOV data have been dumped.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
xTaskCreate(&blink_task, "blink_task", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=6.0'
|
||||
# # Put list of dependencies here
|
||||
espressif/esp_gcov: ^1
|
||||
@@ -0,0 +1,232 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from conftest import OpenOCD
|
||||
|
||||
try:
|
||||
from gcov_capture import UartGcovCapture
|
||||
from gcov_capture import get_coverage_data
|
||||
except ImportError:
|
||||
idf_path = os.getenv('IDF_PATH')
|
||||
if not idf_path:
|
||||
raise RuntimeError('IDF_PATH not found. Please run `source $IDF_PATH/export.sh`')
|
||||
esp_app_trace_dir = os.path.join(idf_path, 'tools', 'esp_app_trace')
|
||||
sys.path.insert(0, esp_app_trace_dir)
|
||||
from gcov_capture import UartGcovCapture
|
||||
from gcov_capture import get_coverage_data
|
||||
|
||||
|
||||
def _candidate_target_dirs(binary_path: str, component: str) -> list:
|
||||
"""Per-component CMakeFiles/<target>.dir/ directories used by either build system.
|
||||
|
||||
Build system v1 emits ``__idf_<component>.dir`` (double underscore); Build system
|
||||
v2 emits ``_idf_<component>.dir`` (single underscore). Both candidates are returned
|
||||
so the caller can probe either layout without knowing which build system produced
|
||||
the binary.
|
||||
"""
|
||||
parent = os.path.join(binary_path, 'esp-idf', component, 'CMakeFiles')
|
||||
return [
|
||||
os.path.join(parent, f'__idf_{component}.dir'),
|
||||
os.path.join(parent, f'_idf_{component}.dir'),
|
||||
]
|
||||
|
||||
|
||||
def _resolve_gcda_path(binary_path: str, component: str, basename: str) -> str:
|
||||
"""Return the .gcda path, preferring whichever build-system layout already has the file.
|
||||
|
||||
Before the chip dumps, neither layout has a .gcda yet, so the chip-side FOPEN will
|
||||
create the directory of whichever path the binary embedded. Call this again after
|
||||
the dump to get the materialized path.
|
||||
"""
|
||||
for d in _candidate_target_dirs(binary_path, component):
|
||||
candidate = os.path.join(d, basename)
|
||||
if os.path.isfile(candidate):
|
||||
return candidate
|
||||
# Default to v2 layout; the post-dump call will return the actual path.
|
||||
return os.path.join(_candidate_target_dirs(binary_path, component)[1], basename)
|
||||
|
||||
|
||||
def get_expected_gcda_paths(dut: IdfDut) -> list:
|
||||
"""Get list of expected .gcda file paths for this example."""
|
||||
return [
|
||||
_resolve_gcda_path(dut.app.binary_path, 'main', 'gcov_example_main.c.gcda'),
|
||||
_resolve_gcda_path(dut.app.binary_path, 'main', 'gcov_example_func.c.gcda'),
|
||||
_resolve_gcda_path(dut.app.binary_path, 'sample', 'some_funcs.c.gcda'),
|
||||
]
|
||||
|
||||
|
||||
def prepare_test(dut: IdfDut) -> list:
|
||||
"""Prepare test environment: create both candidate directories and clean stale .gcda."""
|
||||
# Pre-create both build-system layouts so the chip-side FOPEN can write to whichever
|
||||
# the binary embedded, and clean stale .gcda from either layout.
|
||||
for component in ('main', 'sample'):
|
||||
for d in _candidate_target_dirs(dut.app.binary_path, component):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
try:
|
||||
for entry in os.listdir(d):
|
||||
if entry.endswith('.gcda'):
|
||||
try:
|
||||
os.remove(os.path.join(d, entry))
|
||||
print(f'Removed old .gcda file: {entry}')
|
||||
except OSError as e:
|
||||
print(f'Warning: Could not remove {os.path.join(d, entry)}: {e}')
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return get_expected_gcda_paths(dut)
|
||||
|
||||
|
||||
def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
prepare_test(dut)
|
||||
|
||||
def expect_counter_output(loop: int, timeout: int = 10) -> None:
|
||||
dut.expect_exact(
|
||||
[f'blink_dummy_func: Counter = {loop}', f'some_dummy_func: Counter = {loop * 2}'],
|
||||
expect_all=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
def dump_coverage(on_the_fly: bool, expected_counts: dict | None = None) -> None:
|
||||
response = openocd.gcov_dump(on_the_fly=on_the_fly)
|
||||
|
||||
expect_lines = [
|
||||
'Targets connected.',
|
||||
'gcov_example_main.c.gcda',
|
||||
'gcov_example_func.c.gcda',
|
||||
'some_funcs.c.gcda',
|
||||
'Targets disconnected.',
|
||||
]
|
||||
|
||||
for line in response.splitlines():
|
||||
for expect in expect_lines[:]:
|
||||
if expect in line:
|
||||
if expect.endswith('.gcda'): # check file exists
|
||||
file_path = line.split()[3].strip("'")
|
||||
assert os.path.isfile(file_path)
|
||||
|
||||
expect_lines.remove(expect)
|
||||
|
||||
assert len(expect_lines) == 0
|
||||
|
||||
# Verify execution counts if expected and coverage data is available
|
||||
if expected_counts:
|
||||
# Re-resolve gcda paths after the dump so we observe the layout the chip actually used.
|
||||
expected_gcda_paths = get_expected_gcda_paths(dut)
|
||||
coverage = get_coverage_data(dut.app.binary_path, expected_gcda_paths)
|
||||
if coverage: # Only verify if detailed data is available
|
||||
print(f'Coverage data: {coverage}')
|
||||
for func, expected_count in expected_counts.items():
|
||||
actual_count = coverage.get(func)
|
||||
assert actual_count == expected_count, f'Expected {func}={expected_count}, got {actual_count}'
|
||||
else:
|
||||
# Backup verification: ensure .gcda files exist
|
||||
print('Coverage data not available (gcov tool not found)')
|
||||
for gcda_path in expected_gcda_paths:
|
||||
assert os.path.isfile(gcda_path), f'Expected .gcda file not found: {gcda_path}'
|
||||
print('Basic verification passed (all .gcda files exist)')
|
||||
|
||||
time.sleep(1) # Wait for the USJ port to be ready
|
||||
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
|
||||
with openocd_dut.run() as openocd:
|
||||
openocd.write('reset run')
|
||||
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
|
||||
|
||||
expect_counter_output(0)
|
||||
dut.expect('Ready to dump GCOV data...', timeout=5)
|
||||
|
||||
# Test two hard-coded dumps with verification
|
||||
dump_coverage(False, {'gcov_example_func.c:blink_dummy_func': 1, 'some_funcs.c:some_dummy_func': 1})
|
||||
dut.expect('GCOV data have been dumped.', timeout=5)
|
||||
expect_counter_output(1)
|
||||
dut.expect('Ready to dump GCOV data...', timeout=5)
|
||||
|
||||
dump_coverage(False, {'gcov_example_func.c:blink_dummy_func': 2, 'some_funcs.c:some_dummy_func': 2})
|
||||
dut.expect('GCOV data have been dumped.', timeout=5)
|
||||
|
||||
for i in range(2, 6):
|
||||
expect_counter_output(i)
|
||||
|
||||
# Test instant run-time dumps with verification
|
||||
expected_runtime_counts = [
|
||||
{'gcov_example_func.c:blink_dummy_func': 7, 'some_funcs.c:some_dummy_func': 7},
|
||||
{'gcov_example_func.c:blink_dummy_func': 8, 'some_funcs.c:some_dummy_func': 8},
|
||||
{'gcov_example_func.c:blink_dummy_func': 10, 'some_funcs.c:some_dummy_func': 10},
|
||||
]
|
||||
|
||||
for expected in expected_runtime_counts:
|
||||
time.sleep(1)
|
||||
dump_coverage(True, expected)
|
||||
|
||||
|
||||
@pytest.mark.jtag
|
||||
@idf_parametrize('config', ['gcov_jtag'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||
def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
_test_gcov(openocd_dut, dut)
|
||||
|
||||
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@idf_parametrize('config', ['gcov_jtag'], indirect=['config'])
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target'])
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
_test_gcov(openocd_dut, dut)
|
||||
|
||||
|
||||
def _test_gcov_uart(dut: IdfDut) -> None:
|
||||
# Close console port to free up the port for the gcov capture
|
||||
dut.serial.close()
|
||||
|
||||
prepare_test(dut)
|
||||
log_file = os.path.join(dut.logdir, 'gcov_uart.log')
|
||||
uart_port = dut.serial.port
|
||||
baud = dut.app.sdkconfig.get('APPTRACE_UART_BAUDRATE')
|
||||
with UartGcovCapture(port=uart_port, baudrate=baud, log_file=log_file, log_level=1) as uart_capture:
|
||||
uart_capture.run(background=True)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Expected execution counts
|
||||
expected_counts = [
|
||||
{'gcov_example_func.c:blink_dummy_func': 1, 'some_funcs.c:some_dummy_func': 1}, # First dump
|
||||
{'gcov_example_func.c:blink_dummy_func': 2, 'some_funcs.c:some_dummy_func': 2}, # Second dump
|
||||
]
|
||||
|
||||
# Verify each dump
|
||||
for dump_num, expected in enumerate(expected_counts, start=1):
|
||||
if uart_capture.wait_for_fstop(timeout=10.0):
|
||||
# Re-resolve gcda paths after the dump so we observe the layout the chip actually used.
|
||||
expected_gcda_paths = get_expected_gcda_paths(dut)
|
||||
coverage = get_coverage_data(dut.app.binary_path, expected_gcda_paths)
|
||||
|
||||
if coverage: # Only verify details if coverage data is available
|
||||
print(f'Coverage data: {coverage}')
|
||||
for func, expected_count in expected.items():
|
||||
actual_count = coverage.get(func)
|
||||
assert actual_count == expected_count, f'Expected {func}={expected_count}, got {actual_count}'
|
||||
else:
|
||||
# Backup verification: ensure .gcda files exist
|
||||
print('Coverage data not available (gcov tool not found)')
|
||||
for gcda_path in expected_gcda_paths:
|
||||
assert os.path.isfile(gcda_path), f'Expected .gcda file not found: {gcda_path}'
|
||||
print('Basic verification passed (all .gcda files exist)')
|
||||
else:
|
||||
assert False, f'esp_gcov_dump {dump_num} timeout'
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['gcov_uart'], indirect=['config'])
|
||||
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_gcov_uart(dut: IdfDut) -> None:
|
||||
_test_gcov_uart(dut)
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_APPTRACE_DEST_JTAG=y
|
||||
CONFIG_APPTRACE_LOCK_ENABLE=y
|
||||
CONFIG_APPTRACE_ONPANIC_HOST_FLUSH_TMO=-1
|
||||
CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH=0
|
||||
@@ -0,0 +1,5 @@
|
||||
CONFIG_ESP_CONSOLE_NONE=y
|
||||
CONFIG_APPTRACE_DEST_UART=y
|
||||
CONFIG_APPTRACE_DEST_UART_NUM=0
|
||||
CONFIG_APPTRACE_UART_BAUDRATE=1000000
|
||||
CONFIG_APPTRACE_UART_TX_MSG_SIZE=256
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_ESP_GCOV_ENABLE=y
|
||||
@@ -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.22)
|
||||
|
||||
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(sysview_tracing)
|
||||
@@ -0,0 +1,146 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
# Example: Application Level Tracing - SystemView Tracing (sysview_tracing)
|
||||
|
||||
This test code shows how to perform system-wide behavioral analysis of the program using [SEGGER SystemView tool](https://www.segger.com/products/development-tools/systemview/).
|
||||
|
||||
For description of [SystemView tracing](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/sysview.html) please refer to **ESP32 Programming Guide**, section **Application Level Tracing library**. The following example provides practical implementation of this functionality.
|
||||
|
||||
|
||||
## Use Case
|
||||
|
||||
To find the reason of program's misbehaviour it is often necessary to have the whole picture of interacting components (scheduler, IRQs, tasks, semaphores etc.) in the system. In such cases tools which allow to trace the behaviour of the system as a whole can be very useful.
|
||||
Consider the following situation. User program have a timer and a task. Upon every tick the timer sends an event to the task. When task receives event it prints a message. Timer should notify the task 10 times.
|
||||
There can be a problem which causes the task to lose some events.
|
||||
Below is the timer's ISR code:
|
||||
|
||||
```
|
||||
static void example_timer_isr(void *arg)
|
||||
{
|
||||
example_event_data_t *tim_arg = (example_event_data_t *)arg;
|
||||
|
||||
if (tim_arg->thnd != NULL) {
|
||||
if (tim_arg->count++ < 10) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
if (xTaskNotifyFromISR(tim_arg->thnd, tim_arg->count, eSetValueWithOverwrite, &xHigherPriorityTaskWoken) != pdPASS) {
|
||||
ESP_EARLY_LOGE(TAG, "Failed to notify task %p", tim_arg->thnd);
|
||||
} else {
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// re-start timer
|
||||
example_timer_rearm(tim_arg->group, tim_arg->timer);
|
||||
}
|
||||
```
|
||||
|
||||
Below is the task's code:
|
||||
|
||||
```
|
||||
static void example_task(void *p)
|
||||
{
|
||||
example_event_data_t *arg = (example_event_data_t *) p;
|
||||
timer_isr_handle_t inth;
|
||||
|
||||
ESP_LOGI(TAG, "%p: run task", xTaskGetCurrentTaskHandle());
|
||||
|
||||
esp_err_t res = timer_isr_register(arg->group, arg->timer, example_timer_isr, arg, 0, &inth);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%p: failed to register timer ISR", xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
else {
|
||||
res = timer_start(arg->group, arg->timer);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%p: failed to start timer", xTaskGetCurrentTaskHandle());
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {
|
||||
uint32_t event_val;
|
||||
xTaskNotifyWait(0, 0, &event_val, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Task[%p]: received event %d", xTaskGetCurrentTaskHandle(), event_val);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Potential problem can arise in such program because task and timer has no any mechanism to acknowledge the events transfer. Task needs some time to process an event before waiting for the next one. In this case call to `ESP_LOGI` will be the most time consuming part of event processing. Therefore when timer's ISR is called at some rate it can happen that `xTaskNotifyFromISR` gets called several times before task calls `xTaskNotifyWait`. In these conditions some events will be lost. Possible solution for this is to increase timer's tick period or to use some events acknowledgement mechanism.
|
||||
|
||||
Check the full example code [sysview_tracing](main/sysview_tracing.c) that when compiled in dual core mode reproduces the described problem on both cores. Below is the output of example compiled in single core mode. It shows that task misses several events:
|
||||
|
||||
```
|
||||
I (295) example: 0x3ffb6c10: run task
|
||||
I (297) example: Task[0x3ffb6c10]: received event 1
|
||||
I (300) example: Task[0x3ffb6c10]: received event 2
|
||||
I (306) example: Task[0x3ffb6c10]: received event 5
|
||||
I (311) example: Task[0x3ffb6c10]: received event 8
|
||||
I (317) example: Task[0x3ffb6c10]: received event 10
|
||||
```
|
||||
|
||||
|
||||
## Detecting The Reason Of The Problem
|
||||
|
||||
Besides built-in functionality to trace FreeRTOS internal operations SystemView also provides user with means to define its own trace messages and transfer problem-specific data to the host. In this example we extend SystemView with user-defined tracing module in order to
|
||||
make the root cause of the problem more clear in the gathered trace.
|
||||
|
||||
There are two ways to send user-defined info to SystemView:
|
||||
1. Using built-in SystemView messages. This method uses `SEGGER_SYSVIEW_OnUserStart` and `SEGGER_SYSVIEW_OnUserStop` API to indicate that some user event started or stopped. Disadvantge of this way is that those API do not carry any other data except for user event ID. Advantage is that you do not need to write any additional code to use these functions. In SystemView messages sent by means of that API will be shown as `User Start` or `User Stop` with user event ID as only parameter: 0 - for timer and 1 - for task in this example.
|
||||
2. Using custom SystemView messages. This is more flexible way. It implies extending SystemView with user module having its own set of events with any number of parameters (data) in each. For more details on extending see [code of this example](main/sysview_tracing.c) and [SystemView User Manual](https://www.segger.com/downloads/jlink/UM08027).
|
||||
|
||||
To run the example and find out the reason of the problem:
|
||||
|
||||
1. Connect JTAG interface to ESP32 board, power up both JTAG and ESP32. For details how to setup JTAG interface see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html).
|
||||
|
||||
2. [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd). If you are using the [binary distribution of OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#jtag-debugging-setup-openocd) and one of versions of [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp-wrover-kit/index.html), respective command line will look as follows:
|
||||
|
||||
```
|
||||
cd ~/esp/openocd-esp32
|
||||
bin/openocd -s share/openocd/scripts -f board/esp32-wrover-kit-3.3v.cfg
|
||||
```
|
||||
NOTE: In order to run this example you need OpenOCD version `v0.10.0-esp32-20181105` or later.
|
||||
|
||||
3. Compile and load the example. Note to enable application tracing in menuconfig by going to `Component config > Application Level Tracing` and selecting `(X) Trace memory`. Also in order to generate SystemView trace data you need to enable this in `Component config > Application Level Tracing > FreeRTOS SystemView Tracing` by checking `(X) SystemView Tracing Enable`. Also ensure that all SystemView events are enabled there.
|
||||
|
||||
4. If you are going to use custom events enable them by checking `Example Configuration > Use custom SystemView event IDs`. By default SystemView shows only numeric values of IDs and parameters for custom messages in `Events` view. To make them pretty-looking you need to define for them parsing templates in `SYSVIEW_FreeRTOS.txt` which is resided in SystemView installation directory. For this example you can use `SYSVIEW_FreeRTOS.txt` from the project's root directory.
|
||||
|
||||
5. It is useful to use GDB to start and/or stop tracing automatically. To do this you need to prepare special `gdbinit` file:
|
||||
|
||||
```
|
||||
target remote :3333
|
||||
mon reset halt
|
||||
b app_main
|
||||
commands
|
||||
mon esp sysview_mcore start file:///tmp/sysview_example.svdat
|
||||
c
|
||||
end
|
||||
c
|
||||
```
|
||||
|
||||
Using this file GDB will connect to the target, reset it, and start tracing when it hit breakpoint at `app_main`. Trace data will be saved to `/tmp/sysview_example.svdat`. The `esp sysview_mcore` command uses the SEGGER SystemView multi-core format, so a single file holds the trace for both single- and dual-core targets.
|
||||
|
||||
6. Run GDB using the following command from the project root directory:
|
||||
|
||||
```
|
||||
xtensa-esp32-elf-gdb -x gdbinit build/sysview_tracing.elf
|
||||
```
|
||||
|
||||
**Note:** Replace `xtensa-esp32-elf-gdb` with the related gdb tool (e.g. `xtensa-esp32s2-elf-gdb`, `xtensa-esp32s3-elf-gdb` or `riscv32-esp-elf-gdb`) if running the example on different chip.
|
||||
|
||||
7. When program prints the last message, interrupt its execution (e.g. by pressing `CTRL+C`) and type the following command in GDB console to stop tracing:
|
||||
|
||||
```
|
||||
mon esp sysview_mcore stop
|
||||
```
|
||||
|
||||
You can also use another breakpoint to stop tracing and add respective lines to `gdbinit` at step 5.
|
||||
|
||||
8. Open trace data file in SystemView tool. The multi-core format produced by `esp sysview_mcore` requires SEGGER SystemView v3.60 or later.
|
||||
|
||||
9. Right-click on any event in `Events` view and select:
|
||||
|
||||
- `Show User Events only` if you compiled example to use only built-in event.
|
||||
- `Show APIs only` if you compiled example to use custom events.
|
||||
|
||||
10. Now you can navigate over user-defined messages and see when timer `TG1_T0_LEVEL` sends events (`SYSVIEW_EXAMPLE_SEND_EVENT_START/STOP` or `User Start/Stop(0)`) and when task `svtrace0` reads them (`SYSVIEW_EXAMPLE_WAIT_EVENT_START/STOP` or `User Start/Stop(1)`).
|
||||
If you compiled example to use custom events you will also be able to see the values sent by timer and ones actually received by tasks.
|
||||
@@ -0,0 +1,108 @@
|
||||
128 vTaskAllocateMPURegions xTask=%t pxRegions=%u
|
||||
33 vTaskDelete xTaskToDelete=%t
|
||||
34 vTaskDelay xTicksToDelay=%u
|
||||
35 vTaskDelayUntil
|
||||
129 uxTaskPriorityGet xTask=%t
|
||||
56 uxTaskPriorityGetFromISR xTask=%t
|
||||
130 eTaskGetState xTask=%t
|
||||
55 vTaskPrioritySet xTask=%t uxNewPriority=%u
|
||||
36 vTaskSuspend xTaskToSuspend=%t
|
||||
40 vTaskResume xTaskToResume=%t
|
||||
43 xTaskResumeFromISR xTaskToResume=%t
|
||||
131 vTaskStartScheduler
|
||||
132 vTaskEndScheduler
|
||||
133 vTaskSuspendAll
|
||||
134 xTaskResumeAll
|
||||
135 xTaskGetTickCount
|
||||
57 xTaskGetTickCountFromISR
|
||||
136 uxTaskGetNumberOfTasks
|
||||
137 pcTaskGetName xTaskToQuery=%t
|
||||
138 uxTaskGetStackHighWaterMark xTask=%t
|
||||
139 vTaskSetApplicationTaskTag xTask=%t pxHookFunction=%u
|
||||
140 xTaskGetApplicationTaskTag xTask=%t
|
||||
141 vTaskSetThreadLocalStoragePointer xTaskToSet=%T xIndex=%u pvValue=%u
|
||||
142 pvTaskGetThreadLocalStoragePointer xTaskToQuery=%T xIndex=%u
|
||||
143 xTaskCallApplicationTaskHook xTask=%T pvParameter=%u
|
||||
144 xTaskGetIdleTaskHandle
|
||||
145 uxTaskGetSystemState pxTaskStatusArray=%u uxArraySize=%u pulTotalRunTime=%u
|
||||
146 vTaskList pcWriteBuffer=%u
|
||||
147 vTaskGetRunTimeStats pcWriteBuffer=%u
|
||||
44 xTaskGenericNotify xTaskToNotify=%t ulValue=%u eAction=%u pulPreviousNotificationValue=%u
|
||||
45 xTaskGenericNotifyFromISR xTaskToNotify=%t ulValue=%u eAction=%u pulPreviousNotificationValue=%u pxHigherPriorityTaskWoken=%u
|
||||
46 xTaskNotifyWait ulBitsToClearOnEntry=%u ulBitsToClearOnExit=%u pulNotificationValue=%u xTicksToWait=%u
|
||||
38 vTaskNotifyGiveFromISR xTaskToNotify=%t pxHigherPriorityTaskWoken=%u
|
||||
37 ulTaskNotifyTake xClearCountOnExit=%u xTicksToWait=%u
|
||||
148 xTaskNotifyStateClear xTask=%t
|
||||
149 xTaskGetCurrentTaskHandle
|
||||
150 vTaskSetTimeOutState pxTimeOut=%u
|
||||
151 xTaskCheckForTimeOut pxTimeOut=%u pxTicksToWait=%u
|
||||
152 vTaskMissedYield
|
||||
153 xTaskGetSchedulerState
|
||||
39 vTaskPriorityInherit pxMutexHolder=%p
|
||||
42 xTaskPriorityDisinherit pxMutexHolder=%p
|
||||
154 xTaskGenericCreate pxTaskCode=%u pcName=%u usStackDepth=%u pvParameters=%u uxPriority=%u pxCreatedTask=%u puxStackBuffer=%u xRegions=%u
|
||||
155 uxTaskGetTaskNumber xTask=%u
|
||||
156 vTaskSetTaskNumber xTask=%u uxHandle=%u
|
||||
41 vTaskStepTick xTicksToJump=%u
|
||||
157 eTaskConfirmSleepModeStatus
|
||||
158 xTimerCreate pcTimerName=%u xTimerPeriodInTicks=%u uxAutoReload=%u pvTimerID=%u pxCallbackFunction=%u
|
||||
159 pvTimerGetTimerID xTimer=%u
|
||||
160 vTimerSetTimerID xTimer=%u pvNewID=%u
|
||||
161 xTimerIsTimerActive xTimer=%u
|
||||
162 xTimerGetTimerDaemonTaskHandle
|
||||
163 xTimerPendFunctionCallFromISR xFunctionToPend=%u pvParameter1=%u ulParameter2=%u pxHigherPriorityTaskWoken=%u
|
||||
164 xTimerPendFunctionCall xFunctionToPend=%u pvParameter1=%u ulParameter2=%u xTicksToWait=%u
|
||||
165 pcTimerGetName xTimer=%u
|
||||
166 xTimerCreateTimerTask
|
||||
167 xTimerGenericCommand xTimer=%u xCommandID=%u xOptionalValue=%u pxHigherPriorityTaskWoken=%u xTicksToWait=%u
|
||||
53 xQueueGenericSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u xCopyPosition=%u
|
||||
50 xQueuePeekFromISR xQueue=%I pvBuffer=%p
|
||||
49 xQueueGenericReceive xQueue=%I pvBuffer=%p xTicksToWait=%u xJustPeek=%u
|
||||
168 uxQueueMessagesWaiting xQueue=%I
|
||||
169 uxQueueSpacesAvailable xQueue=%I
|
||||
48 vQueueDelete xQueue=%I
|
||||
54 xQueueGenericSendFromISR xQueue=%I pvItemToQueue=%p pxHigherPriorityTaskWoken=%u xCopyPosition=%u
|
||||
61 xQueueGiveFromISR xQueue=%I pxHigherPriorityTaskWoken=%u
|
||||
51 xQueueReceiveFromISR xQueue=%I pvBuffer=%p pxHigherPriorityTaskWoken=%u
|
||||
62 xQueueIsQueueEmptyFromISR xQueue=%I
|
||||
63 xQueueIsQueueFullFromISR xQueue=%I
|
||||
170 uxQueueMessagesWaitingFromISR xQueue=%I
|
||||
171 xQueueAltGenericSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u xCopyPosition=%u
|
||||
172 xQueueAltGenericReceive xQueue=%I pvBuffer=%p xTicksToWait=%u xJustPeeking=%u
|
||||
173 xQueueCRSendFromISR xQueue=%I pvItemToQueue=%p xCoRoutinePreviouslyWoken=%u
|
||||
174 xQueueCRReceiveFromISR xQueue=%I pvBuffer=%p pxTaskWoken=%u
|
||||
175 xQueueCRSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u
|
||||
176 xQueueCRReceive xQueue=%I pvBuffer=%p xTicksToWait=%u
|
||||
177 xQueueCreateMutex ucQueueType=%u
|
||||
178 xQueueCreateCountingSemaphore uxMaxCount=%u uxInitialCount=%u
|
||||
179 xQueueGetMutexHolder xSemaphore=%u
|
||||
180 xQueueTakeMutexRecursive xMutex=%u xTicksToWait=%u
|
||||
181 xQueueGiveMutexRecursive pxMutex=%u
|
||||
52 vQueueAddToRegistry xQueue=%I pcName=%u
|
||||
182 vQueueUnregisterQueue xQueue=%I
|
||||
47 xQueueGenericCreate uxQueueLength=%u uxItemSize=%u ucQueueType=%u
|
||||
183 xQueueCreateSet uxEventQueueLength=%u
|
||||
184 xQueueAddToSet xQueueOrSemaphore=%u xQueueSet=%u
|
||||
185 xQueueRemoveFromSet xQueueOrSemaphore=%u xQueueSet=%u
|
||||
186 xQueueSelectFromSet xQueueSet=%u xTicksToWait=%u
|
||||
187 xQueueSelectFromSetFromISR xQueueSet=%u
|
||||
188 xQueueGenericReset xQueue=%I xNewQueue=%u
|
||||
189 vListInitialise pxList=%u
|
||||
190 vListInitialiseItem pxItem=%u
|
||||
191 vListInsert pxList=%u pxNewListItem=%u
|
||||
192 vListInsertEnd pxList=%u pxNewListItem=%u
|
||||
193 uxListRemove pxItemToRemove=%u
|
||||
194 xEventGroupCreate
|
||||
195 xEventGroupWaitBits xEventGroup=%u uxBitsToWaitFor=%u xClearOnExit=%u xWaitForAllBits=%u xTicksToWait=%u
|
||||
196 xEventGroupClearBits xEventGroup=%u uxBitsToClear=%u
|
||||
58 xEventGroupClearBitsFromISR xEventGroup=%u uxBitsToSet=%u
|
||||
197 xEventGroupSetBits xEventGroup=%u uxBitsToSet=%u
|
||||
59 xEventGroupSetBitsFromISR xEventGroup=%u uxBitsToSet=%u pxHigherPriorityTaskWoken=%u
|
||||
198 xEventGroupSync xEventGroup=%u uxBitsToSet=%u uxBitsToWaitFor=%u xTicksToWait=%u
|
||||
60 xEventGroupGetBitsFromISR xEventGroup=%u
|
||||
199 vEventGroupDelete xEventGroup=%u
|
||||
200 uxEventGroupGetNumber xEventGroup=%u
|
||||
512 SYSVIEW_EXAMPLE_SEND_EVENT_START
|
||||
513 SYSVIEW_EXAMPLE_SEND_EVENT_END evt_val=%u
|
||||
514 SYSVIEW_EXAMPLE_WAIT_EVENT_START
|
||||
515 SYSVIEW_EXAMPLE_WAIT_EVENT_END evt_val=%u
|
||||
@@ -0,0 +1,13 @@
|
||||
set pagination off
|
||||
target remote :3333
|
||||
|
||||
mon reset halt
|
||||
maintenance flush register-cache
|
||||
|
||||
b app_main
|
||||
commands
|
||||
mon esp sysview_mcore start file:///tmp/sysview_example.svdat
|
||||
c
|
||||
end
|
||||
|
||||
c
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "sysview_tracing.c"
|
||||
PRIV_REQUIRES esp_driver_gptimer
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,12 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config USE_CUSTOM_EVENT_ID
|
||||
bool "Use custom SystemView event IDs"
|
||||
default "n"
|
||||
help
|
||||
Use custom IDs for user events. If it is enabled, replace `SYSVIEW_FreeRTOS.txt` in SystemView
|
||||
installation directory with the version from example's root directory.
|
||||
|
||||
By default SYSVIEW_EVTID_MARK_START/STOP are used for tracing purposes of the example.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,7 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=6.0'
|
||||
# # Put list of dependencies here
|
||||
espressif/esp_sysview: ^1
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_trace.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#if !CONFIG_USE_CUSTOM_EVENT_ID
|
||||
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_ID 0
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_ID 1
|
||||
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_START() SEGGER_SYSVIEW_OnUserStart(SYSVIEW_EXAMPLE_SEND_EVENT_ID)
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_END(_val_) SEGGER_SYSVIEW_OnUserStop(SYSVIEW_EXAMPLE_SEND_EVENT_ID)
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_START() SEGGER_SYSVIEW_OnUserStart(SYSVIEW_EXAMPLE_WAIT_EVENT_ID)
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_END(_val_) SEGGER_SYSVIEW_OnUserStop(SYSVIEW_EXAMPLE_WAIT_EVENT_ID)
|
||||
|
||||
#else
|
||||
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_START_ID 0
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_END_ID 1
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_START_ID 2
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_END_ID 3
|
||||
#define SYSVIEW_EXAMPLE_EVENT_MAX 4
|
||||
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_START() example_sysview_event_send(SYSVIEW_EXAMPLE_SEND_EVENT_START_ID, 0)
|
||||
#define SYSVIEW_EXAMPLE_SEND_EVENT_END(_val_) example_sysview_event_send(SYSVIEW_EXAMPLE_SEND_EVENT_END_ID, _val_)
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_START() example_sysview_event_send(SYSVIEW_EXAMPLE_WAIT_EVENT_START_ID, 0)
|
||||
#define SYSVIEW_EXAMPLE_WAIT_EVENT_END(_val_) example_sysview_event_send(SYSVIEW_EXAMPLE_WAIT_EVENT_END_ID, _val_)
|
||||
|
||||
static SEGGER_SYSVIEW_MODULE s_example_sysview_module = {
|
||||
.sModule = "M=Example SystemView User Module",
|
||||
.NumEvents = SYSVIEW_EXAMPLE_EVENT_MAX,
|
||||
};
|
||||
|
||||
static void example_sysview_event_send(uint32_t id, uint32_t val)
|
||||
{
|
||||
U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + SEGGER_SYSVIEW_QUANTA_U32];
|
||||
|
||||
U8 *pPayload = SEGGER_SYSVIEW_PREPARE_PACKET(aPacket);
|
||||
pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, val); // Add the parameter to the packet
|
||||
SEGGER_SYSVIEW_SendPacket(&aPacket[0], pPayload, s_example_sysview_module.EventOffset + id);
|
||||
}
|
||||
|
||||
#endif // !CONFIG_USE_CUSTOM_EVENT_ID
|
||||
|
||||
typedef struct {
|
||||
gptimer_handle_t gptimer;
|
||||
int count;
|
||||
TaskHandle_t thnd;
|
||||
uint64_t period;
|
||||
char task_name[32];
|
||||
} example_event_data_t;
|
||||
|
||||
static bool example_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
example_event_data_t *tim_arg = (example_event_data_t *)user_ctx;
|
||||
bool need_yield = false;
|
||||
|
||||
if (tim_arg->thnd != NULL) {
|
||||
if (tim_arg->count++ < 10) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
SYSVIEW_EXAMPLE_SEND_EVENT_START();
|
||||
if (xTaskNotifyFromISR(tim_arg->thnd, tim_arg->count, eSetValueWithOverwrite, &xHigherPriorityTaskWoken) != pdPASS) {
|
||||
ESP_EARLY_LOGE(TAG, "Failed to notify task %p", tim_arg->thnd);
|
||||
} else {
|
||||
SYSVIEW_EXAMPLE_SEND_EVENT_END(tim_arg->count);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static void example_task(void *p)
|
||||
{
|
||||
uint32_t event_val;
|
||||
example_event_data_t *arg = (example_event_data_t *) p;
|
||||
ESP_LOGI(TAG, "%p: run task", xTaskGetCurrentTaskHandle());
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = arg->period,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
// This task is pinned to a specific core, to the interrupt will also be install to that core
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = example_timer_alarm_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(arg->gptimer, &cbs, arg));
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(arg->gptimer, &alarm_config));
|
||||
ESP_ERROR_CHECK(gptimer_enable(arg->gptimer));
|
||||
ESP_ERROR_CHECK(gptimer_start(arg->gptimer));
|
||||
while (1) {
|
||||
SYSVIEW_EXAMPLE_WAIT_EVENT_START();
|
||||
xTaskNotifyWait(0, 0, &event_val, portMAX_DELAY);
|
||||
SYSVIEW_EXAMPLE_WAIT_EVENT_END(event_val);
|
||||
ESP_LOGI(TAG, "Task[%p]: received event %"PRIu32, xTaskGetCurrentTaskHandle(), event_val);
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_TRACE_TRANSPORT_APPTRACE
|
||||
#include "soc/uart_pins.h"
|
||||
#include "esp_app_trace.h"
|
||||
esp_trace_open_params_t esp_trace_get_user_params(void)
|
||||
{
|
||||
static esp_apptrace_config_t app_trace_config = APPTRACE_CONFIG_DEFAULT();
|
||||
#if CONFIG_APPTRACE_DEST_UART
|
||||
/* Override default values to use console pins as a uart channel */
|
||||
app_trace_config.dest_cfg.uart.tx_pin_num = U0TXD_GPIO_NUM;
|
||||
app_trace_config.dest_cfg.uart.rx_pin_num = U0RXD_GPIO_NUM;
|
||||
#endif
|
||||
|
||||
esp_trace_open_params_t trace_params = {
|
||||
.core_cfg = NULL,
|
||||
.encoder_name = "sysview",
|
||||
.encoder_cfg = NULL,
|
||||
.transport_name = "apptrace",
|
||||
.transport_cfg = &app_trace_config,
|
||||
};
|
||||
return trace_params;
|
||||
}
|
||||
#elif CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG
|
||||
esp_trace_open_params_t esp_trace_get_user_params(void)
|
||||
{
|
||||
esp_trace_open_params_t trace_params = {
|
||||
.core_cfg = NULL,
|
||||
.encoder_name = "sysview",
|
||||
.encoder_cfg = NULL,
|
||||
.transport_name = "usb_serial_jtag",
|
||||
.transport_cfg = NULL,
|
||||
};
|
||||
return trace_params;
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Hello from sysview_tracing example!");
|
||||
|
||||
static example_event_data_t event_data[CONFIG_FREERTOS_NUMBER_OF_CORES];
|
||||
|
||||
#if CONFIG_USE_CUSTOM_EVENT_ID
|
||||
// Currently OpenOCD does not support requesting module info from target. So do the following...
|
||||
// Wait until SystemView module receives START command from host,
|
||||
// after that data can be sent to the host using onboard API,
|
||||
// so user module description does not need to be requested by OpenOCD itself.
|
||||
while (!SEGGER_SYSVIEW_Started()) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
SEGGER_SYSVIEW_RegisterModule(&s_example_sysview_module);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < CONFIG_FREERTOS_NUMBER_OF_CORES; i++) {
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &event_data[i].gptimer));
|
||||
event_data[i].period = 1000000 * (i + 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < CONFIG_FREERTOS_NUMBER_OF_CORES; i++) {
|
||||
sprintf(event_data->task_name, "svtrace%d", i);
|
||||
xTaskCreatePinnedToCore(example_task, event_data->task_name, 4096, &event_data[i], 3, &event_data[i].thnd, i);
|
||||
ESP_LOGI(TAG, "Created task %p", event_data[i].thnd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import os.path
|
||||
import re
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pexpect
|
||||
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
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from conftest import OpenOCD
|
||||
|
||||
|
||||
def _validate_trace_data(trace_log: str, target: str, dual_core: bool = False, is_uart: bool = False) -> None:
|
||||
"""Validate SysView trace data in a single trace log file.
|
||||
|
||||
Args:
|
||||
trace_log: Path to the trace log file
|
||||
target: Target chip name (e.g., 'esp32', 'esp32s3')
|
||||
dual_core: If True, expect a per-core description block for both cores
|
||||
(the ``esp sysview_mcore`` multi-core capture embeds one per core)
|
||||
is_uart: If True, also validate STOP record at end of file
|
||||
"""
|
||||
STOP_EVENT_ID = 0x0B # SYSVIEW_EVTID_TRACE_STOP
|
||||
|
||||
with open(trace_log, 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
for idx in range(2 if dual_core else 1):
|
||||
search_str = f'N=FreeRTOS Application,D={target},C=core{idx},O=FreeRTOS'.encode()
|
||||
assert search_str in content, f'SysView core{idx} trace data not found in {trace_log}'
|
||||
|
||||
# The file must end with a TRACE_STOP record: the STOP event ID
|
||||
# followed by a variable-length timestamp delta. Walk back
|
||||
# over the trailing continuation bytes (0x80 bit set)
|
||||
# to find the event ID, since its offset is not fixed.
|
||||
size = len(content)
|
||||
assert size >= 2, 'Trace file too small to contain STOP record'
|
||||
i = size - 2
|
||||
while i >= 0 and (content[i] & 0x80):
|
||||
i -= 1
|
||||
assert i >= 0 and content[i] == STOP_EVENT_ID, 'STOP record does not start with STOP eventID'
|
||||
|
||||
|
||||
def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None:
|
||||
"""Capture SysView trace data from serial port.
|
||||
|
||||
Args:
|
||||
ser: Serial port instance
|
||||
trace_log_path: Path to save the trace log
|
||||
"""
|
||||
START_CMD = b'\x01'
|
||||
STOP_CMD = b'\x02'
|
||||
STOP_EVENT_ID = 0x0B # SYSVIEW_EVTID_TRACE_STOP
|
||||
|
||||
ser.reset_input_buffer()
|
||||
# Send Start command to start SysView tracing
|
||||
ser.write(START_CMD)
|
||||
|
||||
data = bytearray()
|
||||
|
||||
# Capture for 3 seconds
|
||||
end_time = time.time() + 3.0
|
||||
while time.time() < end_time:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
data += ser.read(ser.in_waiting)
|
||||
except serial.SerialTimeoutException:
|
||||
assert False, 'Timeout reached while reading from serial port, exiting...'
|
||||
|
||||
# Give pending trace data a short window to reach the host before requesting STOP.
|
||||
time.sleep(0.2)
|
||||
|
||||
# Send Stop command
|
||||
ser.write(STOP_CMD)
|
||||
|
||||
# Capture the final data produced by STOP and the transport flush.
|
||||
end_time = time.time() + 3.0
|
||||
last_data_time = time.time()
|
||||
while time.time() < end_time and (time.time() - last_data_time) <= 1.0:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
data += ser.read(ser.in_waiting)
|
||||
last_data_time = time.time()
|
||||
except serial.SerialTimeoutException:
|
||||
assert False, 'Timeout reached while reading from serial port, exiting...'
|
||||
|
||||
# Drop anything after the TRACE_STOP record (e.g. the ROM boot banner printed
|
||||
# on the shared UART when pytest-embedded resets the DUT after the test).
|
||||
stop_pos = data.rfind(STOP_EVENT_ID)
|
||||
if stop_pos != -1:
|
||||
end = stop_pos + 1
|
||||
# Consume the timestamp delta: continuation bytes have the 0x80 bit set,
|
||||
# terminated by a single byte with 0x80 clear.
|
||||
while end < len(data) and (data[end] & 0x80):
|
||||
end += 1
|
||||
if end < len(data):
|
||||
end += 1
|
||||
data = data[:end]
|
||||
|
||||
with open(trace_log_path, 'w+b') as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
def _test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
# Single multi-core capture file (esp sysview_mcore): one file is enough for
|
||||
# both single- and dual-core targets.
|
||||
trace_log = os.path.join(dut.logdir, 'sys_log.svdat') # pylint: disable=protected-access
|
||||
dual_core = not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3'
|
||||
|
||||
# Prepare gdbinit file pointing at this run's capture file
|
||||
gdb_logfile = os.path.join(dut.logdir, 'gdb.txt')
|
||||
gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit')
|
||||
gdbinit = os.path.join(dut.logdir, 'gdbinit')
|
||||
with open(gdbinit_orig) as f_r, open(gdbinit, 'w') as f_w:
|
||||
for line in f_r:
|
||||
if line.startswith('mon esp sysview_mcore start'):
|
||||
f_w.write(f'mon esp sysview_mcore start file://{trace_log}\n')
|
||||
else:
|
||||
f_w.write(line)
|
||||
|
||||
def dut_expect_task_event() -> None:
|
||||
dut.expect(re.compile(rb'example: Task\[0x[0-9A-Fa-f]+\]: received event \d+'), timeout=30)
|
||||
|
||||
time.sleep(1) # Wait for the USJ port to be ready
|
||||
dut.expect_exact('example: Hello from sysview_tracing example!', timeout=5)
|
||||
with (
|
||||
openocd_dut.run() as openocd,
|
||||
open(gdb_logfile, 'w') as gdb_log,
|
||||
pexpect.spawn(
|
||||
f'idf.py -B {dut.app.binary_path} gdb --batch -x {gdbinit}',
|
||||
timeout=60,
|
||||
logfile=gdb_log,
|
||||
encoding='utf-8',
|
||||
codec_errors='ignore',
|
||||
) as p,
|
||||
):
|
||||
p.expect_exact('hit Breakpoint 1, app_main ()')
|
||||
dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect()
|
||||
dut_expect_task_event()
|
||||
|
||||
# Do a sleep while sysview samples are captured.
|
||||
time.sleep(3)
|
||||
openocd.write('esp sysview_mcore stop')
|
||||
openocd.apptrace_wait_stop()
|
||||
|
||||
_validate_trace_data(trace_log, dut.target, dual_core=dual_core)
|
||||
|
||||
|
||||
@pytest.mark.jtag
|
||||
@idf_parametrize('config', ['sysview_jtag'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||
def test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
_test_sysview_tracing_jtag(openocd_dut, dut)
|
||||
|
||||
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@idf_parametrize('config', ['sysview_jtag'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'),
|
||||
indirect=['target'],
|
||||
)
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
def test_sysview_tracing_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
_test_sysview_tracing_jtag(openocd_dut, dut)
|
||||
|
||||
|
||||
def _test_sysview_tracing_uart(dut: IdfDut) -> None:
|
||||
dut.serial.close()
|
||||
time.sleep(2) # Wait for the DUT to reboot
|
||||
with serial.Serial(dut.serial.port, baudrate=dut.app.sdkconfig.get('APPTRACE_UART_BAUDRATE'), timeout=3) as ser:
|
||||
trace_log = os.path.join(dut.logdir, 'sys_log_uart.svdat') # pylint: disable=protected-access
|
||||
_capture_sysview_trace(ser, trace_log)
|
||||
_validate_trace_data(trace_log, dut.target, is_uart=True)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['sysview_uart'], indirect=['config'])
|
||||
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_sysview_tracing_uart(dut: IdfDut) -> None:
|
||||
_test_sysview_tracing_uart(dut)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.xtal_26mhz
|
||||
@idf_parametrize('config', ['sysview_uart_esp32c2_26Mhz'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32c2'], indirect=['target'])
|
||||
def test_sysview_tracing_uart_c2(dut: IdfDut) -> None:
|
||||
_test_sysview_tracing_uart(dut)
|
||||
|
||||
|
||||
@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('sysview_usj')], indirect=True)
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_sysview_tracing_usj_serial(dut: IdfDut) -> None:
|
||||
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 = os.path.join(dut.logdir, 'sys_log_usj.svdat') # pylint: disable=protected-access
|
||||
_capture_sysview_trace(ser, trace_log)
|
||||
_validate_trace_data(trace_log, dut.target, is_uart=True)
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_APPTRACE_DEST_JTAG=y
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_ESP_CONSOLE_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_APPTRACE_DEST_UART=y
|
||||
CONFIG_APPTRACE_DEST_UART_NUM=0
|
||||
CONFIG_APPTRACE_UART_BAUDRATE=1000000
|
||||
CONFIG_USE_CUSTOM_EVENT_ID=y
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_IDF_TARGET="esp32c2"
|
||||
CONFIG_ESP_CONSOLE_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_APPTRACE_DEST_UART=y
|
||||
CONFIG_APPTRACE_UART_BAUDRATE=74880
|
||||
CONFIG_XTAL_FREQ_26=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y
|
||||
CONFIG_USE_CUSTOM_EVENT_ID=y
|
||||
@@ -0,0 +1,19 @@
|
||||
# 1ms tick period
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
# Enable FreeRTOS SystemView Tracing by default
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_EXTERNAL=y
|
||||
CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_ISR_EXIT_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_ISR_TO_SCHED_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_EXEC_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_READY_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_READY_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_CREATE_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_TERMINATE_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_IDLE_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TIMER_ENTER_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TIMER_EXIT_ENABLE=y
|
||||
@@ -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.22)
|
||||
|
||||
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(sysview_tracing_heap_log)
|
||||
@@ -0,0 +1,184 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# SystemView Heap and Log Tracing Example
|
||||
|
||||
Heap memory leaking is quite widespread software bug. IDF provides [heap tracing feature](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/heap_debug.html#heap-tracing) which allows to collect information related to heap operations (allocations/deallocations) and detect potential memory leaks. This feature can be used in two modes: standalone and host-based. In standalone mode collected data are kept on-board, so this mode is limited by available memory in the system. Host-based mode does not have such limitation because collected data are sent to the host and can be analysed there using special tools. One of such tool is SEGGER SystemView. For description of [SystemView tracing feature](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/sysview.html) please refer to **ESP32 Programming Guide**, section **Application Level Tracing library**. SystemView is also can be useful to show log message sent from the target.
|
||||
This example shows how to use this tool and IDF's scripts for host-based heap and log tracing analysis.
|
||||
|
||||
Consider the following situation. User program have two tasks. One task allocates memory and puts obtained addresses into the queue. Another task waits on that queue, reads sent pointers and frees memory. The first task queues only part of the pointers so some of the allocated blocks are not freed and become leaked. Both tasks uses IDF's logging API to report their actions. This example uses IDF's heap tracing module to record allocations and deallocations to detect memory leaks. Both heap tracing records and log messages are redirected to the host.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware and tools required
|
||||
|
||||
This example does not require any special hardware, and can be run on any common development board.
|
||||
This example requires the following tools:
|
||||
1. [OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
|
||||
NOTE: In order to run this example you need OpenOCD version `v0.10.0-esp32-20190313` or later.
|
||||
|
||||
2. [GDB](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#setup-toolchain) can be used to start and/or stop tracing automatically. To do this you need to prepare special GDB command file. Having provided with `gdbinit` file from the example project directory GDB will connect to the target, reset it, start and stop tracing automatically.
|
||||
|
||||
The gdbinit file includes commands to start tracing:
|
||||
```
|
||||
mon esp sysview_mcore start file:///tmp/heap_log.svdat
|
||||
```
|
||||
When program hits breakpoint at `heap_trace_start`, trace data will be saved to `/tmp/heap_log.svdat`. The `esp sysview_mcore` command uses the SEGGER SystemView multi-core format, so a single file holds the trace for both single- and dual-core targets.
|
||||
Tracing will be stopped when program hits breakpoint at `heap_trace_stop`.
|
||||
|
||||
3. [SEGGER SystemView tool](https://www.segger.com/products/development-tools/systemview/). By default SystemView shows only numeric values of IDs and parameters for IDF's heap messages in `Events` view. To make them pretty-looking you need to copy `SYSVIEW_FreeRTOS.txt` from the project's root directory to SystemView installation one.
|
||||
|
||||
### Build and flash
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with serial port name.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
### Collect And View Trace Data
|
||||
|
||||
To run the example and collect trace data:
|
||||
|
||||
1. Run GDB using the following command from the project root directory:
|
||||
|
||||
```
|
||||
xtensa-esp32-elf-gdb -x gdbinit build/sysview_tracing_heap_log.elf
|
||||
```
|
||||
|
||||
2. When program stops at `heap_trace_stop` quit GDB.
|
||||
|
||||
3. Open trace data file in SystemView tool. The multi-core format produced by `esp sysview_mcore` requires SEGGER SystemView v3.60 or later.
|
||||
|
||||
4. Now you can inspect all collected events. Log messages are shown in `Terminal` view.
|
||||
|
||||
5. You can filter out API related and heap events by right-clicking on any item in `Events` view and select `Show APIs only`.
|
||||
|
||||
### Auto-detect Heap Leaks
|
||||
|
||||
Since SystemView tool is mostly intended for OS level analysis. It allows just to inspect custom events' timestamps and parameters. So it can require some efforts to analyse heap operations flow. IDF provides special script to make the life a bit more easy. This script parses SystemView trace file sand reports detected memory leaks. The script also prints found log messages. To run it type the following from the project root directory:
|
||||
|
||||
```
|
||||
$IDF_PATH/tools/esp_app_trace/sysviewtrace_proc.py -p -b build/sysview_tracing_heap_log.elf /tmp/heap_log.svdat
|
||||
```
|
||||
The script auto-detects the multi-core capture format and processes both cores from the single file, so the same command works for single- and dual-core targets.
|
||||
|
||||
Below is the sample scripts output.
|
||||
|
||||
```
|
||||
[0.002272225] HEAP: Allocated 8 bytes @ 0x3ffaff6c from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:85
|
||||
/home/user/projects/esp/esp-idf/components/esp32/cpu_start.c:570
|
||||
|
||||
[0.002307300] HEAP: Allocated 2500 bytes @ 0x3ffb580c from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:804
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:84 (discriminator 2)
|
||||
|
||||
[0.002323775] HEAP: Allocated 356 bytes @ 0x3ffb61d4 from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:809
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:84 (discriminator 2)
|
||||
|
||||
[0.002427700] HEAP: Allocated 120 bytes @ 0x3ffaff78 from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/queue.c:391
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:46
|
||||
|
||||
[0.002471225] HEAP: Allocated 2500 bytes @ 0x3ffb633c from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:804
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:55
|
||||
|
||||
[0.002487725] HEAP: Allocated 356 bytes @ 0x3ffb6d04 from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:809
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:55
|
||||
|
||||
[0.002569725] HEAP: Allocated 2 bytes @ 0x3ffafff4 from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:59
|
||||
/home/user/projects/esp/esp-idf/components/freertos/port.c:145
|
||||
|
||||
[0.002835275] LOG: I (298) example: Task[0x3ffb61d4]: allocated 2 bytes @ 0x3ffafff4
|
||||
[0.002974600] LOG: I (299) example: Task[0x3ffb6d04]: free memory @ 0x3ffafff4
|
||||
|
||||
....
|
||||
|
||||
[2.942891550] LOG: I (3239) example: Task[0x3ffb7840]: allocated 396 bytes @ 0x3ffb9d08
|
||||
[2.943024150] LOG: I (3239) example: Task[0x3ffb6d04]: free memory @ 0x3ffb9c3c
|
||||
[2.943035600] HEAP: Freed bytes @ 0x3ffb9c3c from task "free0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:24 (discriminator 9)
|
||||
/home/user/projects/esp/esp-idf/components/freertos/port.c:145
|
||||
|
||||
[2.943212125] LOG: I (3239) example: Task[0x3ffb83ec]: free memory @ 0x3ffb9d08
|
||||
[2.943223500] HEAP: Freed bytes @ 0x3ffb9d08 from task "free1" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:24 (discriminator 9)
|
||||
/home/user/projects/esp/esp-idf/components/freertos/port.c:145
|
||||
|
||||
[2.943649025] HEAP: Allocated 594 bytes @ 0x3ffb9c3c from task "alloc2" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:59
|
||||
/home/user/projects/esp/esp-idf/components/freertos/port.c:145
|
||||
|
||||
[2.943734250] LOG: I (3240) example: Task[0x3ffb8f28]: allocated 594 bytes @ 0x3ffb9c3c
|
||||
[2.943867850] LOG: I (3240) example: Task[0x3ffb9ad4]: free memory @ 0x3ffb9c3c
|
||||
[2.943879200] HEAP: Freed bytes @ 0x3ffb9c3c from task "free2" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:24 (discriminator 9)
|
||||
/home/user/projects/esp/esp-idf/components/freertos/port.c:145
|
||||
|
||||
[2.972813425] LOG: I (3269) example: Got notify val 2
|
||||
[2.972870400] LOG: I (3269) example: Wait notify 1
|
||||
[2.972932800] LOG: I (3269) example: Got notify val 1
|
||||
[2.972989825] LOG: I (3269) example: Wait notify 2
|
||||
[2.973756125] LOG: I (3270) example: Got notify val 1
|
||||
Processed 13467 events
|
||||
=============== LOG TRACE REPORT ===============
|
||||
Processed 600 log messages.
|
||||
=============== HEAP TRACE REPORT ===============
|
||||
Processed 612 heap events.
|
||||
[0.002272225] HEAP: Allocated 8 bytes @ 0x3ffaff6c from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:85
|
||||
/home/user/projects/esp/esp-idf/components/esp32/cpu_start.c:570
|
||||
|
||||
[0.002307300] HEAP: Allocated 2500 bytes @ 0x3ffb580c from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:804
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:84 (discriminator 2)
|
||||
|
||||
[0.002323775] HEAP: Allocated 356 bytes @ 0x3ffb61d4 from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:809
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:84 (discriminator 2)
|
||||
|
||||
[0.002427700] HEAP: Allocated 120 bytes @ 0x3ffaff78 from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/queue.c:391
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:46
|
||||
|
||||
[0.002471225] HEAP: Allocated 2500 bytes @ 0x3ffb633c from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:804
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:55
|
||||
|
||||
[0.002487725] HEAP: Allocated 356 bytes @ 0x3ffb6d04 from task "alloc0" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:809
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:55
|
||||
|
||||
[0.003102175] HEAP: Allocated 8 bytes @ 0x3ffb6e6c from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:85
|
||||
/home/user/projects/esp/esp-idf/components/esp32/cpu_start.c:570
|
||||
|
||||
....
|
||||
|
||||
[0.003713175] HEAP: Allocated 356 bytes @ 0x3ffb8f28 from task "main" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:809
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:84 (discriminator 2)
|
||||
|
||||
[0.003814375] HEAP: Allocated 120 bytes @ 0x3ffb9090 from task "alloc2" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/queue.c:391
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:46
|
||||
|
||||
[0.003845875] HEAP: Allocated 2500 bytes @ 0x3ffb910c from task "alloc2" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:804
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:55
|
||||
|
||||
[0.003862350] HEAP: Allocated 356 bytes @ 0x3ffb9ad4 from task "alloc2" on core 0 by:
|
||||
/home/user/projects/esp/esp-idf/components/freertos/tasks.c:809
|
||||
/home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/build/../main/sysview_heap_log.c:55
|
||||
|
||||
Found 17520 leaked bytes in 18 blocks.
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
128 vTaskAllocateMPURegions xTask=%t pxRegions=%u
|
||||
33 vTaskDelete xTaskToDelete=%t
|
||||
34 vTaskDelay xTicksToDelay=%u
|
||||
35 vTaskDelayUntil
|
||||
129 uxTaskPriorityGet xTask=%t
|
||||
56 uxTaskPriorityGetFromISR xTask=%t
|
||||
130 eTaskGetState xTask=%t
|
||||
55 vTaskPrioritySet xTask=%t uxNewPriority=%u
|
||||
36 vTaskSuspend xTaskToSuspend=%t
|
||||
40 vTaskResume xTaskToResume=%t
|
||||
43 xTaskResumeFromISR xTaskToResume=%t
|
||||
131 vTaskStartScheduler
|
||||
132 vTaskEndScheduler
|
||||
133 vTaskSuspendAll
|
||||
134 xTaskResumeAll
|
||||
135 xTaskGetTickCount
|
||||
57 xTaskGetTickCountFromISR
|
||||
136 uxTaskGetNumberOfTasks
|
||||
137 pcTaskGetName xTaskToQuery=%t
|
||||
138 uxTaskGetStackHighWaterMark xTask=%t
|
||||
139 vTaskSetApplicationTaskTag xTask=%t pxHookFunction=%u
|
||||
140 xTaskGetApplicationTaskTag xTask=%t
|
||||
141 vTaskSetThreadLocalStoragePointer xTaskToSet=%T xIndex=%u pvValue=%u
|
||||
142 pvTaskGetThreadLocalStoragePointer xTaskToQuery=%T xIndex=%u
|
||||
143 xTaskCallApplicationTaskHook xTask=%T pvParameter=%u
|
||||
144 xTaskGetIdleTaskHandle
|
||||
145 uxTaskGetSystemState pxTaskStatusArray=%u uxArraySize=%u pulTotalRunTime=%u
|
||||
146 vTaskList pcWriteBuffer=%u
|
||||
147 vTaskGetRunTimeStats pcWriteBuffer=%u
|
||||
44 xTaskGenericNotify xTaskToNotify=%t ulValue=%u eAction=%u pulPreviousNotificationValue=%u
|
||||
45 xTaskGenericNotifyFromISR xTaskToNotify=%t ulValue=%u eAction=%u pulPreviousNotificationValue=%u pxHigherPriorityTaskWoken=%u
|
||||
46 xTaskNotifyWait ulBitsToClearOnEntry=%u ulBitsToClearOnExit=%u pulNotificationValue=%u xTicksToWait=%u
|
||||
38 vTaskNotifyGiveFromISR xTaskToNotify=%t pxHigherPriorityTaskWoken=%u
|
||||
37 ulTaskNotifyTake xClearCountOnExit=%u xTicksToWait=%u
|
||||
148 xTaskNotifyStateClear xTask=%t
|
||||
149 xTaskGetCurrentTaskHandle
|
||||
150 vTaskSetTimeOutState pxTimeOut=%u
|
||||
151 xTaskCheckForTimeOut pxTimeOut=%u pxTicksToWait=%u
|
||||
152 vTaskMissedYield
|
||||
153 xTaskGetSchedulerState
|
||||
39 vTaskPriorityInherit pxMutexHolder=%p
|
||||
42 xTaskPriorityDisinherit pxMutexHolder=%p
|
||||
154 xTaskGenericCreate pxTaskCode=%u pcName=%u usStackDepth=%u pvParameters=%u uxPriority=%u pxCreatedTask=%u puxStackBuffer=%u xRegions=%u
|
||||
155 uxTaskGetTaskNumber xTask=%u
|
||||
156 vTaskSetTaskNumber xTask=%u uxHandle=%u
|
||||
41 vTaskStepTick xTicksToJump=%u
|
||||
157 eTaskConfirmSleepModeStatus
|
||||
158 xTimerCreate pcTimerName=%u xTimerPeriodInTicks=%u uxAutoReload=%u pvTimerID=%u pxCallbackFunction=%u
|
||||
159 pvTimerGetTimerID xTimer=%u
|
||||
160 vTimerSetTimerID xTimer=%u pvNewID=%u
|
||||
161 xTimerIsTimerActive xTimer=%u
|
||||
162 xTimerGetTimerDaemonTaskHandle
|
||||
163 xTimerPendFunctionCallFromISR xFunctionToPend=%u pvParameter1=%u ulParameter2=%u pxHigherPriorityTaskWoken=%u
|
||||
164 xTimerPendFunctionCall xFunctionToPend=%u pvParameter1=%u ulParameter2=%u xTicksToWait=%u
|
||||
165 pcTimerGetName xTimer=%u
|
||||
166 xTimerCreateTimerTask
|
||||
167 xTimerGenericCommand xTimer=%u xCommandID=%u xOptionalValue=%u pxHigherPriorityTaskWoken=%u xTicksToWait=%u
|
||||
53 xQueueGenericSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u xCopyPosition=%u
|
||||
50 xQueuePeekFromISR xQueue=%I pvBuffer=%p
|
||||
49 xQueueGenericReceive xQueue=%I pvBuffer=%p xTicksToWait=%u xJustPeek=%u
|
||||
168 uxQueueMessagesWaiting xQueue=%I
|
||||
169 uxQueueSpacesAvailable xQueue=%I
|
||||
48 vQueueDelete xQueue=%I
|
||||
54 xQueueGenericSendFromISR xQueue=%I pvItemToQueue=%p pxHigherPriorityTaskWoken=%u xCopyPosition=%u
|
||||
61 xQueueGiveFromISR xQueue=%I pxHigherPriorityTaskWoken=%u
|
||||
51 xQueueReceiveFromISR xQueue=%I pvBuffer=%p pxHigherPriorityTaskWoken=%u
|
||||
62 xQueueIsQueueEmptyFromISR xQueue=%I
|
||||
63 xQueueIsQueueFullFromISR xQueue=%I
|
||||
170 uxQueueMessagesWaitingFromISR xQueue=%I
|
||||
171 xQueueAltGenericSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u xCopyPosition=%u
|
||||
172 xQueueAltGenericReceive xQueue=%I pvBuffer=%p xTicksToWait=%u xJustPeeking=%u
|
||||
173 xQueueCRSendFromISR xQueue=%I pvItemToQueue=%p xCoRoutinePreviouslyWoken=%u
|
||||
174 xQueueCRReceiveFromISR xQueue=%I pvBuffer=%p pxTaskWoken=%u
|
||||
175 xQueueCRSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u
|
||||
176 xQueueCRReceive xQueue=%I pvBuffer=%p xTicksToWait=%u
|
||||
177 xQueueCreateMutex ucQueueType=%u
|
||||
178 xQueueCreateCountingSemaphore uxMaxCount=%u uxInitialCount=%u
|
||||
179 xQueueGetMutexHolder xSemaphore=%u
|
||||
180 xQueueTakeMutexRecursive xMutex=%u xTicksToWait=%u
|
||||
181 xQueueGiveMutexRecursive pxMutex=%u
|
||||
52 vQueueAddToRegistry xQueue=%I pcName=%u
|
||||
182 vQueueUnregisterQueue xQueue=%I
|
||||
47 xQueueGenericCreate uxQueueLength=%u uxItemSize=%u ucQueueType=%u
|
||||
183 xQueueCreateSet uxEventQueueLength=%u
|
||||
184 xQueueAddToSet xQueueOrSemaphore=%u xQueueSet=%u
|
||||
185 xQueueRemoveFromSet xQueueOrSemaphore=%u xQueueSet=%u
|
||||
186 xQueueSelectFromSet xQueueSet=%u xTicksToWait=%u
|
||||
187 xQueueSelectFromSetFromISR xQueueSet=%u
|
||||
188 xQueueGenericReset xQueue=%I xNewQueue=%u
|
||||
189 vListInitialise pxList=%u
|
||||
190 vListInitialiseItem pxItem=%u
|
||||
191 vListInsert pxList=%u pxNewListItem=%u
|
||||
192 vListInsertEnd pxList=%u pxNewListItem=%u
|
||||
193 uxListRemove pxItemToRemove=%u
|
||||
194 xEventGroupCreate
|
||||
195 xEventGroupWaitBits xEventGroup=%u uxBitsToWaitFor=%u xClearOnExit=%u xWaitForAllBits=%u xTicksToWait=%u
|
||||
196 xEventGroupClearBits xEventGroup=%u uxBitsToClear=%u
|
||||
58 xEventGroupClearBitsFromISR xEventGroup=%u uxBitsToSet=%u
|
||||
197 xEventGroupSetBits xEventGroup=%u uxBitsToSet=%u
|
||||
59 xEventGroupSetBitsFromISR xEventGroup=%u uxBitsToSet=%u pxHigherPriorityTaskWoken=%u
|
||||
198 xEventGroupSync xEventGroup=%u uxBitsToSet=%u uxBitsToWaitFor=%u xTicksToWait=%u
|
||||
60 xEventGroupGetBitsFromISR xEventGroup=%u
|
||||
199 vEventGroupDelete xEventGroup=%u
|
||||
200 uxEventGroupGetNumber xEventGroup=%u
|
||||
|
||||
512 esp_sysview_heap_trace_alloc addr=%p size=%u callers=%x
|
||||
513 esp_sysview_heap_trace_free addr=%p callers=%x
|
||||
@@ -0,0 +1,18 @@
|
||||
set pagination off
|
||||
target remote :3333
|
||||
|
||||
mon reset halt
|
||||
maintenance flush register-cache
|
||||
|
||||
tb heap_trace_start
|
||||
commands
|
||||
mon esp sysview_mcore start file:///tmp/heap_log.svdat
|
||||
c
|
||||
end
|
||||
|
||||
tb heap_trace_stop
|
||||
commands
|
||||
mon esp sysview_mcore stop
|
||||
end
|
||||
|
||||
c
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "sysview_heap_log.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,7 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=6.0'
|
||||
# # Put list of dependencies here
|
||||
espressif/esp_sysview: ^1
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <inttypes.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_app_trace.h"
|
||||
#include "esp_sysview_heap_trace_module.h" // from esp_sysview managed component
|
||||
#include "esp_heap_trace.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_trace.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
// waits on queue for memory addresses and frees memory allocated by 'alloc_task'
|
||||
static void free_task(void *p)
|
||||
{
|
||||
QueueHandle_t queue = (QueueHandle_t)p;
|
||||
while (1) {
|
||||
void *p = NULL;
|
||||
if (xQueueReceive(queue, ( void * )&p, portMAX_DELAY) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to send to queue!");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Task[%p]: free memory @ %p", xTaskGetCurrentTaskHandle(), p);
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct alloc_args {
|
||||
int idx;
|
||||
TaskHandle_t waiter;
|
||||
};
|
||||
|
||||
// allocates memory and puts addresses to the queue
|
||||
static void alloc_task(void *p)
|
||||
{
|
||||
struct alloc_args *task_args = (struct alloc_args *)p;
|
||||
char task_name[20];
|
||||
|
||||
QueueHandle_t queue = xQueueCreate(10, sizeof(void *));
|
||||
if(queue == 0) {
|
||||
ESP_LOGE(TAG, "Failed to create queue!");
|
||||
return;
|
||||
}
|
||||
snprintf(task_name, sizeof(task_name), "free%d", task_args->idx);
|
||||
xTaskCreatePinnedToCore(free_task, task_name, 2500, queue, 5, NULL, CONFIG_FREERTOS_NUMBER_OF_CORES-1);
|
||||
|
||||
// here GDB will stop at breakpoint and execute OpenOCD command to start tracing
|
||||
for(int i = 1; i < 10; i++) {
|
||||
uint32_t sz = 2*i*(task_args->idx + 1);
|
||||
void *p = malloc(sz/2);
|
||||
// WARNING: the previous allocated memory is intentionally not deallocated in order to cause memory leak!
|
||||
p = malloc(sz);
|
||||
ESP_LOGI(TAG, "Task[%p]: allocated %"PRIu32" bytes @ %p", xTaskGetCurrentTaskHandle(), sz, p);
|
||||
if (xQueueSend(queue, ( void * )&p, portMAX_DELAY) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to send to queue!");
|
||||
}
|
||||
vTaskDelay(30/portTICK_PERIOD_MS);
|
||||
}
|
||||
xTaskNotifyGive(task_args->waiter);
|
||||
while(1){
|
||||
vTaskDelay(100/portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
esp_trace_open_params_t esp_trace_get_user_params(void)
|
||||
{
|
||||
esp_trace_open_params_t trace_params = {
|
||||
.core_cfg = NULL,
|
||||
.encoder_name = "sysview",
|
||||
.encoder_cfg = NULL,
|
||||
.transport_name = "apptrace",
|
||||
.transport_cfg = NULL,
|
||||
};
|
||||
return trace_params;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Ready for OpenOCD connection");
|
||||
|
||||
const int num_allocers = 3;
|
||||
char task_name[20];
|
||||
// redirect log messages to the host using SystemView tracing module
|
||||
esp_log_set_vprintf(&esp_sysview_vprintf);
|
||||
// init host-based heap tracing
|
||||
if(heap_trace_init_tohost() != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to init heap trace!");
|
||||
return;
|
||||
}
|
||||
heap_trace_start(HEAP_TRACE_ALL);
|
||||
for (int i = 0; i < num_allocers; i++) {
|
||||
struct alloc_args *task_args = malloc(sizeof(struct alloc_args));
|
||||
if (task_args == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to alloc task args!");
|
||||
heap_trace_stop();
|
||||
return;
|
||||
}
|
||||
task_args->idx = i;
|
||||
task_args->waiter = xTaskGetCurrentTaskHandle();
|
||||
snprintf(task_name, sizeof(task_name), "alloc%d", i);
|
||||
xTaskCreatePinnedToCore(alloc_task, task_name, 2500, task_args, 5, NULL, 0);
|
||||
}
|
||||
for (int i = 0; i < num_allocers; i++) {
|
||||
ESP_LOGI(TAG, "Wait notify %d", i);
|
||||
uint32_t val = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Got notify val %"PRIu32, val);
|
||||
}
|
||||
// here GDB will stop at breakpoint and execute OpenOCD command to stop tracing
|
||||
heap_trace_stop();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import os.path
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pexpect.fdpexpect
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from conftest import OpenOCD
|
||||
|
||||
|
||||
def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||
# Single multi-core capture file (esp sysview_mcore): one file is enough for
|
||||
# both single- and dual-core targets.
|
||||
trace_log = os.path.join(dut.logdir, 'heap_log.svdat') # pylint: disable=protected-access
|
||||
|
||||
# Prepare gdbinit file pointing at this run's capture file
|
||||
gdb_logfile = os.path.join(dut.logdir, 'gdb.txt')
|
||||
gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit')
|
||||
gdbinit = os.path.join(dut.logdir, 'gdbinit')
|
||||
with open(gdbinit_orig) as f_r, open(gdbinit, 'w') as f_w:
|
||||
for line in f_r:
|
||||
if line.startswith('mon esp sysview_mcore start'):
|
||||
f_w.write(f'mon esp sysview_mcore start file://{trace_log}\n')
|
||||
else:
|
||||
f_w.write(line)
|
||||
|
||||
time.sleep(1) # Wait for the USJ port to be ready
|
||||
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
|
||||
with openocd_dut.run() as oocd:
|
||||
if dut.target == 'esp32p4':
|
||||
oocd.write('esp appimage_offset 0x20000')
|
||||
with (
|
||||
open(gdb_logfile, 'w') as gdb_log,
|
||||
pexpect.spawn(
|
||||
f'idf.py -B {dut.app.binary_path} gdb --batch -x {gdbinit}',
|
||||
timeout=60,
|
||||
logfile=gdb_log,
|
||||
encoding='utf-8',
|
||||
codec_errors='ignore',
|
||||
) as p,
|
||||
):
|
||||
# Wait for sysview files to be generated
|
||||
p.expect_exact('Tracing is STOPPED')
|
||||
|
||||
# Process sysview trace log (sysviewtrace_proc.py auto-detects and splits the
|
||||
# multi-core capture, so a single file works for single- and dual-core)
|
||||
command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p', trace_log]
|
||||
with pexpect.spawn(' '.join(command)) as sysviewtrace:
|
||||
sysviewtrace.expect(r'Found \d+ leaked bytes in \d+ blocks.', timeout=120)
|
||||
|
||||
# Validate GDB logs
|
||||
with open(gdb_logfile, encoding='utf-8') as fr: # pylint: disable=protected-access
|
||||
gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno())
|
||||
gdb_pexpect_proc.expect_exact(
|
||||
'Thread 2 "main" hit Temporary breakpoint 1, heap_trace_start (mode_param', timeout=10
|
||||
) # should be (mode_param=HEAP_TRACE_ALL) # TODO GCC-329
|
||||
gdb_pexpect_proc.expect_exact('Thread 2 "main" hit Temporary breakpoint 2, heap_trace_stop ()', timeout=10)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
|
||||
@pytest.mark.jtag
|
||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||
def test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||
_test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'),
|
||||
indirect=['target'],
|
||||
)
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703')
|
||||
def test_examples_sysview_tracing_heap_log_usj(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||
_test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut)
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_APPTRACE_DEST_JTAG=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_APPTRACE_DEST_UART=y
|
||||
CONFIG_APPTRACE_DEST_UART_NUM=1
|
||||
@@ -0,0 +1,28 @@
|
||||
# 1ms tick period
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
# Enable application tracing by default
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_EXTERNAL=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_ESP_TRACE_TS_SOURCE_GPTIMER=y
|
||||
|
||||
# FreeRTOS SystemView Tracing Settings
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_ISR_EXIT_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_ISR_TO_SCHED_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_EXEC_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_READY_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_READY_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_CREATE_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_TERMINATE_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_IDLE_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TIMER_ENTER_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TIMER_EXIT_ENABLE=y
|
||||
# Disable color output in logs
|
||||
CONFIG_LOG_COLORS=n
|
||||
# Enable heap tracing to host
|
||||
CONFIG_HEAP_TRACING_TOHOST=y
|
||||
# For RISC-V targets
|
||||
CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Heap memory debugging
|
||||
#
|
||||
CONFIG_HEAP_TRACING_STACK_DEPTH=10
|
||||
@@ -0,0 +1,2 @@
|
||||
# Increase size of bootloader due to frame pointer. Only overflowed on P4.
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x10000
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Heap memory debugging
|
||||
#
|
||||
CONFIG_HEAP_TRACING_STACK_DEPTH=10
|
||||
Reference in New Issue
Block a user