Skip to content

Commit 6dca07a

Browse files
committed
feature(esp_tinyusb): Added CPU load test case in runtime_config test
1 parent 5ac149b commit 6dca07a

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "soc/soc_caps.h"
8+
9+
#if SOC_USB_OTG_SUPPORTED
10+
11+
#include <stdio.h>
12+
#include <string.h>
13+
#include "freertos/FreeRTOS.h"
14+
#include "freertos/task.h"
15+
#include "esp_log.h"
16+
#include "esp_err.h"
17+
#include "unity.h"
18+
#include "tinyusb.h"
19+
#include "tinyusb_default_config.h"
20+
#include "device_handling.h"
21+
22+
// Enable this, if you need to see the stat for all tasks
23+
#define STAT_CONFIG_ALL_TASKS 0
24+
// Increase this if test_cpu_load_init or test_cpu_load_measure fail due to insufficient array size
25+
#define STAT_CONFIG_ARRAY_SIZE_OFFSET 5
26+
27+
// TinyUSB task related variables
28+
const static char *TINYUSB_TASK_NAME = "TinyUSB";
29+
static uint32_t _tinyusb_run_time = 0;
30+
static uint32_t _tinyusb_cpu_load = 0;
31+
32+
// Arrays and variables for CPU load measurement
33+
static TaskStatus_t *start_array = NULL;
34+
static TaskStatus_t *end_array = NULL;
35+
static UBaseType_t start_array_size;
36+
static UBaseType_t end_array_size;
37+
38+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
39+
static configRUN_TIME_COUNTER_TYPE start_run_time;
40+
static configRUN_TIME_COUNTER_TYPE end_run_time;
41+
#else
42+
static uint32_t start_run_time;
43+
static uint32_t end_run_time;
44+
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
45+
46+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
47+
#define CPU_AMOUNT_OF_CORES CONFIG_FREERTOS_NUMBER_OF_CORES
48+
#else
49+
#define CPU_AMOUNT_OF_CORES portNUM_PROCESSORS
50+
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
51+
52+
/**
53+
* @brief Initialize CPU load measurement
54+
*/
55+
static void test_cpu_load_init(void)
56+
{
57+
printf("Starting TinyUSB load measurement test...\n");
58+
59+
// Allocate array to store current task states
60+
start_array_size = uxTaskGetNumberOfTasks() + STAT_CONFIG_ARRAY_SIZE_OFFSET;
61+
start_array = malloc(sizeof(TaskStatus_t) * start_array_size);
62+
TEST_ASSERT_NOT_NULL_MESSAGE(start_array, "Insufficient memory to allocate internal arrays");
63+
64+
// Get current task states
65+
start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time);
66+
TEST_ASSERT_MESSAGE(start_array_size != 0, "Insufficient array size for uxTaskGetSystemState. Try increasing STAT_CONFIG_ARRAY_SIZE_OFFSET");
67+
}
68+
69+
/**
70+
* @brief Measure CPU load since initialization
71+
*
72+
* Note: test_cpu_load_init() must be called before this function
73+
*/
74+
static void test_cpu_load_measure(void)
75+
{
76+
end_array_size = uxTaskGetNumberOfTasks() + STAT_CONFIG_ARRAY_SIZE_OFFSET;
77+
end_array = malloc(sizeof(TaskStatus_t) * end_array_size);
78+
TEST_ASSERT_NOT_NULL_MESSAGE(end_array, "Insufficient memory to allocate internal arrays");
79+
80+
// Get post delay task states
81+
end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time);
82+
TEST_ASSERT_MESSAGE(end_array_size != 0, "Insufficient array size for uxTaskGetSystemState. Try increasing STAT_CONFIG_ARRAY_SIZE_OFFSET");
83+
84+
// Calculate total_elapsed_time in units of run time stats clock period.
85+
uint32_t total_elapsed_time = (end_run_time - start_run_time);
86+
TEST_ASSERT_MESSAGE(total_elapsed_time != 0, "Delay duration too short");
87+
88+
#if (STAT_CONFIG_ALL_TASKS)
89+
printf("\n%-20s %10s %8s\n", "Name", "Run time", "CPU load");
90+
#endif // STAT_CONFIG_ALL_TASKS
91+
// Print TinyUSB statistics only
92+
for (int i = 0; i < start_array_size; i++) {
93+
int k = -1;
94+
for (int j = 0; j < end_array_size; j++) {
95+
if (start_array[i].xHandle == end_array[j].xHandle) {
96+
k = j;
97+
// Mark that task have been matched by overwriting their handles
98+
start_array[i].xHandle = NULL;
99+
end_array[j].xHandle = NULL;
100+
break;
101+
}
102+
}
103+
// Check if matching task found
104+
if (k >= 0) {
105+
uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter;
106+
uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CPU_AMOUNT_OF_CORES);
107+
#if (STAT_CONFIG_ALL_TASKS)
108+
printf("%-20.20s %10" PRIu32 " %7" PRIu32 "%%\n",
109+
start_array[i].pcTaskName, // left-aligned, max 20 chars
110+
task_elapsed_time, // right-aligned, width 10
111+
percentage_time); // right-aligned, width 7 + '%' char
112+
#endif // STAT_CONFIG_ALL_TASKS
113+
// Save the TinyUSB task stats for test validation
114+
if (strcmp(start_array[i].pcTaskName, TINYUSB_TASK_NAME) == 0) {
115+
_tinyusb_run_time = task_elapsed_time;
116+
_tinyusb_cpu_load = percentage_time;
117+
}
118+
}
119+
}
120+
#if (STAT_CONFIG_ALL_TASKS)
121+
// Print unmatched tasks
122+
for (int i = 0; i < start_array_size; i++) {
123+
if (start_array[i].xHandle != NULL) {
124+
printf("%-20.20s %10s\n",
125+
start_array[i].pcTaskName, // left-aligned, max 20 chars
126+
"Deleted"); // right-aligned, width 10
127+
}
128+
}
129+
for (int i = 0; i < end_array_size; i++) {
130+
if (end_array[i].xHandle != NULL) {
131+
printf("%-20.20s %10s\n",
132+
end_array[i].pcTaskName, // left-aligned, max 20 chars
133+
"Created"); // right-aligned, width 10
134+
}
135+
}
136+
#endif // STAT_CONFIG_ALL_TASKS
137+
free(start_array);
138+
free(end_array);
139+
printf("CPU load measurement test completed.\n");
140+
}
141+
142+
/**
143+
* @brief Test TinyUSB CPU load measurement
144+
*
145+
* Scenario:
146+
* - Install TinyUSB driver with default configuration
147+
* - wait for device connection
148+
* - measure CPU load
149+
* - uninstall driver
150+
* - show results
151+
*/
152+
TEST_CASE("[CPU load] Install & Uninstall, default configuration", "[cpu_load]")
153+
{
154+
#if (!CONFIG_FREERTOS_UNICORE)
155+
// Allow other core to finish initialization
156+
vTaskDelay(pdMS_TO_TICKS(100));
157+
#endif // (!CONFIG_FREERTOS_UNICORE)
158+
159+
// Install TinyUSB driver
160+
tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler);
161+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
162+
163+
// Initialize CPU load measurement
164+
test_cpu_load_init();
165+
166+
// Wait for the device to be mounted and enumerated by the Host
167+
test_device_wait();
168+
printf("\t -> Device connected\n");
169+
170+
// Measure CPU load
171+
test_cpu_load_measure();
172+
173+
// Uninstall TinyUSB driver
174+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
175+
176+
// Show results
177+
printf("TinyUSB Run time: %" PRIu32 " ticks\n", _tinyusb_run_time);
178+
printf("TinyUSB CPU load: %" PRIu32 " %%\n", _tinyusb_cpu_load);
179+
}
180+
181+
#endif // SOC_USB_OTG_SUPPORTED

device/esp_tinyusb/test_apps/runtime_config/pytest_runtime_config.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,39 @@ def test_usb_device_runtime_config(dut: IdfDut) -> None:
2020

2121
for periph in peripherals:
2222
dut.run_all_single_board_cases(group=periph)
23+
24+
# The threshold values for TinyUSB Task Run time (in cycles) for different targets
25+
TASK_RUN_TIME_LIMITS = {
26+
'esp32s2': 5000,
27+
'esp32s3': 3000,
28+
'esp32p4': 1800,
29+
}
30+
31+
def _get_run_time_th(target: str) -> int:
32+
assert target in TASK_RUN_TIME_LIMITS
33+
return TASK_RUN_TIME_LIMITS.get(target)
34+
35+
@pytest.mark.esp32s2
36+
@pytest.mark.esp32s3
37+
@pytest.mark.esp32p4
38+
@pytest.mark.usb_device
39+
def test_cpu_load_task_stat_print(dut: IdfDut) -> None:
40+
'''
41+
Test to verify that Run time and CPU load measurement for TinyUSB task is working.
42+
This test runs only on runtime_config test app.
43+
44+
Test procedure:
45+
1. Run the test on the DUT
46+
2. Expect to see TinyUSB task CPU load printed in the output
47+
3. Expect TinyUSB task CPU load to be not greater than 0%
48+
'''
49+
dut.expect_exact('Press ENTER to see the list of tests.')
50+
dut.write('[cpu_load]')
51+
dut.expect_exact('Starting TinyUSB load measurement test...')
52+
dut.expect_exact('CPU load measurement test completed.')
53+
54+
line = dut.expect(r'TinyUSB Run time: (\d+) ticks')
55+
run_time = int(line.group(1))
56+
run_time_max = _get_run_time_th(dut.target)
57+
58+
assert 0 < run_time < run_time_max, f'Unexpected TinyUSB Run time: {run_time}'

device/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ CONFIG_TINYUSB_CDC_COUNT=2
55
# Disable watchdogs, they'd get triggered during unity interactive menu
66
# CONFIG_ESP_TASK_WDT_INIT is not set
77

8+
# For CPU load measurement
9+
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
10+
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
11+
812
# Run-time checks of Heap and Stack
913
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
1014
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y

0 commit comments

Comments
 (0)