Initial project setup

This commit is contained in:
2025-12-13 11:59:11 +02:00
commit 3218e6039f
2176 changed files with 355321 additions and 0 deletions

2
.clangd Normal file
View File

@@ -0,0 +1,2 @@
CompileFlags:
Remove: [-f*, -m*]

13
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@@ -0,0 +1,21 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build/
sdkconfig
sdkconfig.old

23
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "${config:idf.toolsPath}/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${config:idf.espIdfPath}/components/**",
"${config:idf.espIdfPathWin}/components/**",
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${config:idf.espIdfPath}/components",
"${config:idf.espIdfPathWin}/components",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

15
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

19
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"C_Cpp.intelliSenseEngine": "default",
"idf.espIdfPath": "/Users/tarassivas/esp/v5.5.1/esp-idf",
"idf.pythonInstallPath": "/Users/tarassivas/radioconda/bin/python",
"idf.openOcdConfigs": [
"board/esp32s3-builtin.cfg"
],
"idf.port": "/dev/tty.BLTH",
"idf.toolsPath": "/Users/tarassivas/.espressif",
"idf.customExtraVars": {
"IDF_TARGET": "esp32s3"
},
"clangd.path": "/Users/tarassivas/.espressif/tools/esp-clang/esp-19.1.2_20250312/esp-clang/bin/clangd",
"clangd.arguments": [
"--background-index",
"--query-driver=/Users/tarassivas/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc",
"--compile-commands-dir=${workspaceFolder}/build"
]
}

8
CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
# 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.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(watch-watch)

58
README.md Normal file
View File

@@ -0,0 +1,58 @@
# watch-watch
watch-watch — вбудована система на ESP32-S3 для нагляду за п’ятьма силовими модулями на базі Raspberry Pi 5. ESP32-S3 керує DC/DC перетворювачами через сигнали `EN`, а також надає USB CLI інтерфейс для налаштування та діагностики без окремого UART.
## Основні можливості
- **Керування каналами живлення**: 5 незалежних ліній `EN` (GPIO 2, 4, 5, 18, 19), які можна увімкнути, вимкнути або перемкнути з коду чи CLI.
- **Послідовний автотест**: у `app_main` реалізовано базову логіку — канали вмикаються по черзі з інтервалом 4 с, що дозволяє перевірити всі DC/DC.
- **Нативний USB-CLI**: ESP32-S3 підключається до Raspberry Pi 5 по USB і стає CDC ACM пристроєм; командний інтерфейс дозволяє керувати каналами та дивитись стан у реальному часі.
- **Модульна архітектура**: окремі компоненти `dcdc_controller` і `usb_cdc_cli` спрощують розширення (телеметрія, автоматизація, протоколи зв’язку).
## Структура проєкту
```
├── main
│ ├── main.c // базова логіка, ініціалізація модулів
│ ├── dcdc_controller.c/.h // API керування DC/DC каналами
│ ├── usb_cdc_cli.c/.h // TinyUSB CLI для Raspberry Pi 5
│ └── idf_component.yml // залежність на espressif/esp_tinyusb
├── sdkconfig // конфігурація (опції TinyUSB увімкнені)
└── README.md // цей файл
```
## GPIO-призначення каналів
| Канал | GPIO | Призначення |
|-------|------|-----------------------|
| 0 | 2 | Модуль живлення #1 |
| 1 | 4 | Модуль живлення #2 |
| 2 | 5 | Модуль живлення #3 |
| 3 | 18 | Модуль живлення #4 |
| 4 | 19 | Модуль живлення #5 |
> Піни можна змінити в `main/dcdc_controller.c`, масив `s_dcdc_gpio_map`.
## USB CDC CLI
Після підключення ESP32-S3 до Raspberry Pi 5 з’являється USB-пристрій (CDC ACM). У CLI доступні команди:
| Команда | Опис |
|------------------|------------------------------------|
| `help` | довідка по командам |
| `status` | поточний стан усіх каналів |
| `enable <n>` | увімкнути канал `n` (0..4) |
| `disable <n>` | вимкнути канал `n` |
| `toggle <n>` | перемкнути канал `n` |
CLI з’являється після того, як Raspberry Pi встановить DTR (наприклад, через `screen`, `picocom` або власний скрипт).
## Збірка та конфігурація
1. Встановіть ESP-IDF v5.5.1 (шлях `IDF_PATH` має вказувати на `/Users/tarassivas/esp/v5.5.1/esp-idf`).
2. Один раз виконайте `idf.py reconfigure`, щоб підвантажити залежності з `idf_component.yml`.
3. Переконайтеся, що в `sdkconfig` увімкнено:
- `CONFIG_TINYUSB_CDC_ENABLED=y`
- `CONFIG_TINYUSB_CDC_RX_BUFSIZE=128` (або інше значення за вашим сценарієм).
4. Зберіть проєкт: `idf.py build`, прошийте `idf.py flash`, переглядайте лог `idf.py monitor`.
## Подальший розвиток
1. Додати протокол обміну з Raspberry Pi (наприклад, командний набір через USB CLI або окреме IPC).
2. Включити зворотній зв’язок: вимір напруги/струму, датчики температури для кожного DC/DC.
3. Розширити CLI (макроси, сценарії, логування станів) та інтегрувати з продакшн-скриптами.

39
dependencies.lock Normal file
View File

@@ -0,0 +1,39 @@
dependencies:
espressif/esp_tinyusb:
component_hash: 6a50305bc61c7a361da8c0833642be824e92dacb0a6001719a832a4e96e471bf
dependencies:
- name: idf
require: private
version: '>=5.0'
- name: espressif/tinyusb
registry_url: https://components.espressif.com
require: public
version: '>=0.14.2'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.6~2
espressif/tinyusb:
component_hash: 5ea9d3b6d6b0734a0a0b3491967aa0e1bece2974132294dbda5dd2839b247bfa
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
targets:
- esp32s2
- esp32s3
- esp32p4
- esp32h4
version: 0.19.0~2
idf:
source:
type: idf
version: 5.5.1
direct_dependencies:
- espressif/esp_tinyusb
manifest_hash: 5a05c0273068b434734dee09d2c2ea1da5ef6181b054b4a9d51531ebb931282d
target: esp32s3
version: 2.0.0

4
main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "main.c"
"dcdc_controller.c"
"usb_cdc_cli.c"
INCLUDE_DIRS ".")

106
main/dcdc_controller.c Normal file
View File

@@ -0,0 +1,106 @@
#include "dcdc_controller.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char *TAG = "dcdc";
static const gpio_num_t s_dcdc_gpio_map[DCDC_CHANNEL_COUNT] = {
GPIO_NUM_2,
GPIO_NUM_4,
GPIO_NUM_5,
GPIO_NUM_18,
GPIO_NUM_19,
};
static bool s_initialized;
static bool s_channel_state[DCDC_CHANNEL_COUNT];
static bool dcdc_is_channel_valid(dcdc_channel_t channel)
{
return channel >= DCDC_CHANNEL_0 && channel < DCDC_CHANNEL_COUNT;
}
esp_err_t dcdc_init(void)
{
if (s_initialized) {
return ESP_OK;
}
gpio_config_t cfg = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pin_bit_mask = 0,
};
for (int i = 0; i < DCDC_CHANNEL_COUNT; ++i) {
cfg.pin_bit_mask = 1ULL << s_dcdc_gpio_map[i];
esp_err_t err = gpio_config(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Не вдалося налаштувати GPIO %d (канал %d): %s",
(int)s_dcdc_gpio_map[i], i, esp_err_to_name(err));
return err;
}
ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_set_level(s_dcdc_gpio_map[i], 0));
s_channel_state[i] = false;
}
s_initialized = true;
ESP_LOGI(TAG, "DCDC контролер ініціалізовано, каналів: %d", DCDC_CHANNEL_COUNT);
return ESP_OK;
}
esp_err_t dcdc_set_state(dcdc_channel_t channel, bool enabled)
{
if (!dcdc_is_channel_valid(channel)) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = gpio_set_level(s_dcdc_gpio_map[channel], enabled ? 1 : 0);
if (err == ESP_OK) {
s_channel_state[channel] = enabled;
ESP_LOGI(TAG, "Канал %d -> %s", channel, enabled ? "ON" : "OFF");
}
return err;
}
esp_err_t dcdc_enable(dcdc_channel_t channel)
{
return dcdc_set_state(channel, true);
}
esp_err_t dcdc_disable(dcdc_channel_t channel)
{
return dcdc_set_state(channel, false);
}
esp_err_t dcdc_toggle(dcdc_channel_t channel)
{
if (!dcdc_is_channel_valid(channel)) {
return ESP_ERR_INVALID_ARG;
}
return dcdc_set_state(channel, !s_channel_state[channel]);
}
bool dcdc_get_state(dcdc_channel_t channel)
{
if (!dcdc_is_channel_valid(channel)) {
return false;
}
return s_channel_state[channel];
}
size_t dcdc_channel_count(void)
{
return DCDC_CHANNEL_COUNT;
}
gpio_num_t dcdc_get_gpio(dcdc_channel_t channel)
{
if (!dcdc_is_channel_valid(channel)) {
return GPIO_NUM_NC;
}
return s_dcdc_gpio_map[channel];
}

25
main/dcdc_controller.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "driver/gpio.h"
#include "esp_err.h"
#define DCDC_CHANNEL_COUNT 5
typedef enum {
DCDC_CHANNEL_0 = 0,
DCDC_CHANNEL_1,
DCDC_CHANNEL_2,
DCDC_CHANNEL_3,
DCDC_CHANNEL_4,
} dcdc_channel_t;
esp_err_t dcdc_init(void);
esp_err_t dcdc_set_state(dcdc_channel_t channel, bool enabled);
esp_err_t dcdc_enable(dcdc_channel_t channel);
esp_err_t dcdc_disable(dcdc_channel_t channel);
esp_err_t dcdc_toggle(dcdc_channel_t channel);
bool dcdc_get_state(dcdc_channel_t channel);
size_t dcdc_channel_count(void);
gpio_num_t dcdc_get_gpio(dcdc_channel_t channel);

3
main/idf_component.yml Normal file
View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb: "^1"

42
main/main.c Normal file
View File

@@ -0,0 +1,42 @@
#include <stddef.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "dcdc_controller.h"
#include "usb_cdc_cli.h"
static const char *TAG = "watch-watch";
void app_main(void)
{
if (dcdc_init() != ESP_OK) {
ESP_LOGE(TAG, "Помилка ініціалізації DCDC контролера");
return;
}
if (usb_cdc_cli_init() != ESP_OK) {
ESP_LOGW(TAG, "USB CDC CLI недоступний");
} else {
ESP_LOGI(TAG, "USB CDC CLI запущено");
}
ESP_LOGI(TAG, "Початок послідовного ввімкнення каналів з інтервалом 4 с");
const TickType_t on_time = pdMS_TO_TICKS(4000);
size_t prev_channel = DCDC_CHANNEL_COUNT - 1;
while (true) {
for (size_t ch = 0; ch < dcdc_channel_count(); ++ch) {
if (prev_channel != ch) {
dcdc_disable(prev_channel);
}
ESP_LOGI(TAG, "-> Ввімкнення каналу %d", (int)ch);
dcdc_enable(ch);
vTaskDelay(on_time);
prev_channel = ch;
}
}
}

296
main/usb_cdc_cli.c Normal file
View File

@@ -0,0 +1,296 @@
#include "usb_cdc_cli.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "dcdc_controller.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#define CLI_LINE_MAX_LEN 128
#define CLI_QUEUE_LEN 8
#ifndef CONFIG_TINYUSB_CDC_RX_BUFSIZE
#define CONFIG_TINYUSB_CDC_RX_BUFSIZE 64
#endif
typedef struct {
size_t length;
uint8_t data[CONFIG_TINYUSB_CDC_RX_BUFSIZE];
} usb_cli_msg_t;
static const char *TAG = "usb_cli";
static QueueHandle_t s_cli_queue;
static bool s_cli_initialized;
static bool s_host_ready;
static void usb_cli_task(void *arg);
static void usb_cli_process_line(char *line);
static void usb_cli_prompt(void);
static void usb_cli_write_raw(const char *data, size_t len)
{
if (!s_host_ready || len == 0) {
return;
}
if (tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (uint8_t *)data, len) == ESP_OK) {
esp_err_t err = tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "CDC flush error: %s", esp_err_to_name(err));
}
}
}
static void usb_cli_printf(const char *fmt, ...)
{
if (!s_host_ready) {
return;
}
char buffer[192];
va_list args;
va_start(args, fmt);
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
if (len > 0) {
if (len >= (int)sizeof(buffer)) {
len = (int)sizeof(buffer) - 1;
}
usb_cli_write_raw(buffer, len);
}
}
static const char *PROMPT = "\r\nwatch> ";
static char *usb_cli_trim(char *str)
{
while (*str && isspace((unsigned char)*str)) {
str++;
}
char *end = str + strlen(str);
while (end > str && isspace((unsigned char)*(end - 1))) {
*(--end) = '\0';
}
return str;
}
static bool usb_cli_parse_channel(const char *token, dcdc_channel_t *channel)
{
if (!token) {
return false;
}
char *end = NULL;
long val = strtol(token, &end, 10);
if (end == token || val < 0 || val >= DCDC_CHANNEL_COUNT) {
return false;
}
*channel = (dcdc_channel_t)val;
return true;
}
static void usb_cli_print_status(void)
{
usb_cli_printf("\r\nСтан каналів:\r\n");
for (size_t i = 0; i < dcdc_channel_count(); ++i) {
dcdc_channel_t ch = (dcdc_channel_t)i;
usb_cli_printf(" - CH%u: %s (GPIO %d)\r\n",
(unsigned)i,
dcdc_get_state(ch) ? "ON" : "OFF",
(int)dcdc_get_gpio(ch));
}
}
static void usb_cli_prompt(void)
{
usb_cli_write_raw(PROMPT, strlen(PROMPT));
}
static void usb_cli_handle_switch(const char *cmd, char *args)
{
dcdc_channel_t channel;
char *ctx = NULL;
char *channel_str = strtok_r(args, " ", &ctx);
if (!usb_cli_parse_channel(channel_str, &channel)) {
usb_cli_printf("\r\nНевірний номер каналу\r\n");
return;
}
esp_err_t err = ESP_OK;
if (strcasecmp(cmd, "enable") == 0) {
err = dcdc_enable(channel);
} else if (strcasecmp(cmd, "disable") == 0) {
err = dcdc_disable(channel);
} else {
err = dcdc_toggle(channel);
}
if (err != ESP_OK) {
usb_cli_printf("\r\nПомилка керування каналом: %s\r\n", esp_err_to_name(err));
} else {
usb_cli_printf("\r\nКанал %d -> %s\r\n", channel,
dcdc_get_state(channel) ? "ON" : "OFF");
}
}
static void usb_cli_process_line(char *line)
{
char *trimmed = usb_cli_trim(line);
if (*trimmed == '\0') {
return;
}
char *save_ptr = NULL;
char *cmd = strtok_r(trimmed, " ", &save_ptr);
if (!cmd) {
return;
}
if (strcasecmp(cmd, "help") == 0) {
usb_cli_printf(
"\r\nДоступні команди:\r\n"
" help - показати довідку\r\n"
" status - стан всіх каналів\r\n"
" enable <n> - увімкнути канал n\r\n"
" disable <n> - вимкнути канал n\r\n"
" toggle <n> - перемкнути канал n\r\n");
} else if (strcasecmp(cmd, "status") == 0) {
usb_cli_print_status();
} else if (strcasecmp(cmd, "enable") == 0 ||
strcasecmp(cmd, "disable") == 0 ||
strcasecmp(cmd, "toggle") == 0) {
usb_cli_handle_switch(cmd, save_ptr);
} else {
usb_cli_printf("\r\nНевідома команда '%s'\r\n", cmd);
}
}
static void usb_cli_task(void *arg)
{
usb_cli_msg_t msg;
char line[CLI_LINE_MAX_LEN];
size_t line_len = 0;
while (xQueueReceive(s_cli_queue, &msg, portMAX_DELAY)) {
for (size_t i = 0; i < msg.length; ++i) {
char ch = (char)msg.data[i];
if (ch == '\r' || ch == '\n') {
if (line_len > 0) {
line[line_len] = '\0';
usb_cli_process_line(line);
line_len = 0;
}
usb_cli_prompt();
} else if (ch == 0x7F || ch == '\b') {
if (line_len > 0) {
line_len--;
}
} else if (isprint((unsigned char)ch)) {
if (line_len < CLI_LINE_MAX_LEN - 1) {
line[line_len++] = ch;
} else {
usb_cli_printf("\r\nРядок занадто довгий\r\n");
line_len = 0;
usb_cli_prompt();
}
}
}
}
}
static void usb_cdc_rx_callback(int itf, cdcacm_event_t *event)
{
if (!s_cli_queue) {
return;
}
usb_cli_msg_t msg = { 0 };
size_t rx_size = 0;
esp_err_t ret = tinyusb_cdcacm_read(itf, msg.data, sizeof(msg.data), &rx_size);
if (ret == ESP_OK && rx_size > 0) {
msg.length = rx_size;
if (xQueueSend(s_cli_queue, &msg, 0) != pdTRUE) {
ESP_LOGW(TAG, "CLI queue overflow, втрачено %u байт", (unsigned)rx_size);
}
}
}
static void usb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
{
const bool dtr = event->line_state_changed_data.dtr;
const bool rts = event->line_state_changed_data.rts;
ESP_LOGI(TAG, "CDC лінія %d змінилась: DTR=%d RTS=%d", itf, dtr, rts);
s_host_ready = dtr;
if (s_host_ready) {
usb_cli_printf("\r\nwatch-watch CLI готовий. Надрукуйте 'help'.\r\n");
usb_cli_prompt();
}
}
esp_err_t usb_cdc_cli_init(void)
{
if (s_cli_initialized) {
return ESP_OK;
}
s_cli_queue = xQueueCreate(CLI_QUEUE_LEN, sizeof(usb_cli_msg_t));
if (!s_cli_queue) {
return ESP_ERR_NO_MEM;
}
if (xTaskCreate(usb_cli_task, "usb_cli", 4096, NULL, 5, NULL) != pdPASS) {
vQueueDelete(s_cli_queue);
s_cli_queue = NULL;
return ESP_ERR_NO_MEM;
}
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif
};
ESP_RETURN_ON_ERROR(tinyusb_driver_install(&tusb_cfg), TAG, "TinyUSB init failed");
const tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
.callback_rx = usb_cdc_rx_callback,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL,
};
ESP_RETURN_ON_ERROR(tusb_cdc_acm_init(&acm_cfg), TAG, "CDC init failed");
ESP_RETURN_ON_ERROR(
tinyusb_cdcacm_register_callback(TINYUSB_CDC_ACM_0,
CDC_EVENT_LINE_STATE_CHANGED,
usb_cdc_line_state_changed_callback),
TAG,
"Line state callback error");
s_cli_initialized = true;
ESP_LOGI(TAG, "USB CDC CLI ініціалізовано");
return ESP_OK;
}

11
main/usb_cdc_cli.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include "esp_err.h"
/**
* @brief Ініціалізує USB CDC CLI, підключений до Raspberry Pi 5.
*
* Модуль запускає TinyUSB CDC ACM та фонове завдання, яке читає рядки з
* USB-консолі та надає команди для керування DC/DC каналами.
*/
esp_err_t usb_cdc_cli_init(void);

View File

@@ -0,0 +1 @@
6a50305bc61c7a361da8c0833642be824e92dacb0a6001719a832a4e96e471bf

View File

@@ -0,0 +1,107 @@
## 1.7.6~2
- esp_tinyusb: Added support for IDF 6.0 after removal of the USB component
## 1.7.6~1
- esp_tinyusb: Added documentation to README.md
## 1.7.6
- MSC: Fixed the possibility to use SD/MMC storage with large capacity (more than 4 GB)
## 1.7.5
- esp_tinyusb: Provide forward compatibility with IDF 6.0
## 1.7.4
- MSC: WL Sector runtime check during spiflash init (fix for build time error check)
## 1.7.3 [yanked]
- MSC: Improved transfer speed to SD cards and SPI flash
## 1.7.2
- esp_tinyusb: Fixed crash on logging from ISR
- PHY: Fixed crash with external_phy=true configuration
## 1.7.1
- NCM: Changed default NTB config to decrease DRAM memory usage (fix for DRAM overflow on ESP32S2)
## 1.7.0 [yanked]
- NCM: Added possibility to configure NCM Transfer Blocks (NTB) via menuconfig
- esp_tinyusb: Added option to select TinyUSB peripheral on esp32p4 via menuconfig (USB_PHY_SUPPORTS_P4_OTG11 in esp-idf is required)
- esp_tinyusb: Fixed uninstall tinyusb driver with not default task configuration
## 1.6.0
- CDC-ACM: Fixed memory leak on deinit
- esp_tinyusb: Added Teardown
## 1.5.0
- esp_tinyusb: Added DMA mode option to tinyusb DCD DWC2 configuration
- esp_tinyusb: Changed the default affinity mask of the task to CPU1
## 1.4.5
- CDC-ACM: Fixed memory leak at VFS unregister
- Vendor specific: Provided default configuration
## 1.4.4
- esp_tinyusb: Added HighSpeed and Qualifier device descriptors in tinyusb configuration
- CDC-ACM: Removed MIN() definition if already defined
- MSC: Fixed EP size selecting in default configuration descriptor
## 1.4.3
- esp_tinyusb: Added ESP32P4 support (HS only)
## 1.4.2
- MSC: Fixed maximum files open
- Added uninstall function
## 1.4.0
- MSC: Fixed integer overflows
- CDC-ACM: Removed intermediate RX ringbuffer
- CDC-ACM: Increased default FIFO size to 512 bytes
- CDC-ACM: Fixed Virtual File System binding
## 1.3.0
- Added NCM extension
## 1.2.1 - 1.2.2
- Minor bugfixes
## 1.2.0
- Added MSC extension for accessing SPI Flash on memory card https://github.com/espressif/idf-extra-components/commit/a8c00d7707ba4ceeb0970c023d702c7768dba3dc
## 1.1.0
- Added support for NCM, ECM/RNDIS, DFU and Bluetooth TinyUSB drivers https://github.com/espressif/idf-extra-components/commit/79f35c9b047b583080f93a63310e2ee7d82ef17b
## 1.0.4
- Cleaned up string descriptors handling https://github.com/espressif/idf-extra-components/commit/046cc4b02f524d5c7e3e56480a473cfe844dc3d6
## 1.0.2 - 1.0.3
- Minor bugfixes
## 1.0.1
- CDC-ACM: Return ESP_OK if there is nothing to flush https://github.com/espressif/idf-extra-components/commit/388ff32eb09aa572d98c54cb355f1912ce42707c
## 1.0.0
- Initial version based on [esp-idf v4.4.3](https://github.com/espressif/esp-idf/tree/v4.4.3/components/tinyusb)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
set(srcs
"descriptors_control.c"
"tinyusb.c"
"usb_descriptors.c"
)
set(priv_req "")
if(${IDF_VERSION_MAJOR} LESS 6)
list(APPEND priv_req "usb")
endif()
if(NOT CONFIG_TINYUSB_NO_DEFAULT_TASK)
list(APPEND srcs "tusb_tasks.c")
endif() # CONFIG_TINYUSB_NO_DEFAULT_TASK
if(CONFIG_TINYUSB_CDC_ENABLED)
list(APPEND srcs
"cdc.c"
"tusb_cdc_acm.c"
)
if(CONFIG_VFS_SUPPORT_IO)
list(APPEND srcs
"tusb_console.c"
"vfs_tinyusb.c"
)
endif() # CONFIG_VFS_SUPPORT_IO
endif() # CONFIG_TINYUSB_CDC_ENABLED
if(CONFIG_TINYUSB_MSC_ENABLED)
list(APPEND srcs
tusb_msc_storage.c
)
endif() # CONFIG_TINYUSB_MSC_ENABLED
if(CONFIG_TINYUSB_NET_MODE_NCM)
list(APPEND srcs
tinyusb_net.c
)
endif() # CONFIG_TINYUSB_NET_MODE_NCM
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "include_private"
PRIV_REQUIRES ${priv_req}
REQUIRES fatfs vfs
)
# Determine whether tinyusb is fetched from component registry or from local path
idf_build_get_property(build_components BUILD_COMPONENTS)
if(tinyusb IN_LIST build_components)
set(tinyusb_name tinyusb) # Local component
else()
set(tinyusb_name espressif__tinyusb) # Managed component
endif()
# Pass tusb_config.h from this component to TinyUSB
idf_component_get_property(tusb_lib ${tinyusb_name} COMPONENT_LIB)
target_include_directories(${tusb_lib} PRIVATE "include")

View File

@@ -0,0 +1,379 @@
menu "TinyUSB Stack"
config TINYUSB_DEBUG_LEVEL
int "TinyUSB log level (0-3)"
default 1
range 0 3
help
Specify verbosity of TinyUSB log output.
choice TINYUSB_RHPORT
prompt "USB Peripheral"
default TINYUSB_RHPORT_HS if IDF_TARGET_ESP32P4
default TINYUSB_RHPORT_FS
help
Allows set the USB Peripheral Controller for TinyUSB.
- High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed)
- Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed)
config TINYUSB_RHPORT_HS
bool "OTG2.0"
depends on IDF_TARGET_ESP32P4
config TINYUSB_RHPORT_FS
bool "OTG1.1"
endchoice
menu "TinyUSB DCD"
choice TINYUSB_MODE
prompt "DCD Mode"
default TINYUSB_MODE_DMA
help
TinyUSB DCD DWC2 Driver supports two modes: Slave mode (based on IRQ) and Buffer DMA mode.
config TINYUSB_MODE_SLAVE
bool "Slave/IRQ"
config TINYUSB_MODE_DMA
bool "Buffer DMA"
endchoice
endmenu # "TinyUSB DCD"
menu "TinyUSB task configuration"
config TINYUSB_NO_DEFAULT_TASK
bool "Do not create a TinyUSB task"
default n
help
This option allows to not create the FreeRTOS task during the driver initialization.
User will have to handle TinyUSB events manually.
config TINYUSB_TASK_PRIORITY
int "TinyUSB task priority"
default 5
depends on !TINYUSB_NO_DEFAULT_TASK
help
Set the priority of the default TinyUSB main task.
config TINYUSB_TASK_STACK_SIZE
int "TinyUSB task stack size (bytes)"
default 4096
depends on !TINYUSB_NO_DEFAULT_TASK
help
Set the stack size of the default TinyUSB main task.
choice TINYUSB_TASK_AFFINITY
prompt "TinyUSB task affinity"
default TINYUSB_TASK_AFFINITY_CPU1 if !FREERTOS_UNICORE
default TINYUSB_TASK_AFFINITY_NO_AFFINITY
depends on !TINYUSB_NO_DEFAULT_TASK
help
Allows setting TinyUSB tasks affinity, i.e. whether the task is pinned to
CPU0, pinned to CPU1, or allowed to run on any CPU.
config TINYUSB_TASK_AFFINITY_NO_AFFINITY
bool "No affinity"
config TINYUSB_TASK_AFFINITY_CPU0
bool "CPU0"
config TINYUSB_TASK_AFFINITY_CPU1
bool "CPU1"
depends on !FREERTOS_UNICORE
endchoice
config TINYUSB_TASK_AFFINITY
hex
default FREERTOS_NO_AFFINITY if TINYUSB_TASK_AFFINITY_NO_AFFINITY
default 0x0 if TINYUSB_TASK_AFFINITY_CPU0
default 0x1 if TINYUSB_TASK_AFFINITY_CPU1
config TINYUSB_INIT_IN_DEFAULT_TASK
bool "Initialize TinyUSB stack within the default TinyUSB task"
default n
depends on !TINYUSB_NO_DEFAULT_TASK
help
Run TinyUSB stack initialization just after starting the default TinyUSB task.
This is especially useful in multicore scenarios, when we need to pin the task
to a specific core and, at the same time initialize TinyUSB stack
(i.e. install interrupts) on the same core.
endmenu # "TinyUSB task configuration"
menu "Descriptor configuration"
comment "You can provide your custom descriptors via tinyusb_driver_install()"
config TINYUSB_DESC_USE_ESPRESSIF_VID
bool "VID: Use Espressif's vendor ID"
default y
help
Enable this option, USB device will use Espressif's vendor ID as its VID.
This is helpful at product develop stage.
config TINYUSB_DESC_CUSTOM_VID
hex "VID: Custom vendor ID"
default 0x1234
depends on !TINYUSB_DESC_USE_ESPRESSIF_VID
help
Custom Vendor ID.
config TINYUSB_DESC_USE_DEFAULT_PID
bool "PID: Use a default PID assigned to TinyUSB"
default y
help
Default TinyUSB PID assigning uses values 0x4000...0x4007.
config TINYUSB_DESC_CUSTOM_PID
hex "PID: Custom product ID"
default 0x5678
depends on !TINYUSB_DESC_USE_DEFAULT_PID
help
Custom Product ID.
config TINYUSB_DESC_BCD_DEVICE
hex "bcdDevice"
default 0x0100
help
Version of the firmware of the USB device.
config TINYUSB_DESC_MANUFACTURER_STRING
string "Manufacturer name"
default "Espressif Systems"
help
Name of the manufacturer of the USB device.
config TINYUSB_DESC_PRODUCT_STRING
string "Product name"
default "Espressif Device"
help
Name of the USB device.
config TINYUSB_DESC_SERIAL_STRING
string "Serial string"
default "123456"
help
Serial number of the USB device.
config TINYUSB_DESC_CDC_STRING
depends on TINYUSB_CDC_ENABLED
string "CDC Device String"
default "Espressif CDC Device"
help
Name of the CDC device.
config TINYUSB_DESC_MSC_STRING
depends on TINYUSB_MSC_ENABLED
string "MSC Device String"
default "Espressif MSC Device"
help
Name of the MSC device.
endmenu # "Descriptor configuration"
menu "Massive Storage Class (MSC)"
config TINYUSB_MSC_ENABLED
bool "Enable TinyUSB MSC feature"
default n
help
Enable TinyUSB MSC feature.
config TINYUSB_MSC_BUFSIZE
depends on TINYUSB_MSC_ENABLED
int "MSC FIFO size"
default 512 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 8192 if IDF_TARGET_ESP32P4
range 64 8192 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
range 64 32768 if IDF_TARGET_ESP32P4
help
MSC FIFO size, in bytes.
config TINYUSB_MSC_MOUNT_PATH
depends on TINYUSB_MSC_ENABLED
string "Mount Path"
default "/data"
help
MSC Mount Path of storage.
menu "TinyUSB FAT Format Options"
choice TINYUSB_FAT_FORMAT_TYPE
prompt "FatFS Format Type"
default TINYUSB_FAT_FORMAT_ANY
help
Select the FAT filesystem type used when formatting storage.
config TINYUSB_FAT_FORMAT_ANY
bool "FM_ANY - Automatically select from FAT12/FAT16/FAT32"
config TINYUSB_FAT_FORMAT_FAT
bool "FM_FAT - Allow only FAT12/FAT16"
config TINYUSB_FAT_FORMAT_FAT32
bool "FM_FAT32 - Force FAT32 only"
config TINYUSB_FAT_FORMAT_EXFAT
bool "FM_EXFAT - Force exFAT (requires exFAT enabled)"
endchoice
config TINYUSB_FAT_FORMAT_SFD
bool "FM_SFD - Use SFD (no partition table)"
default n
help
Format as a Super Floppy Disk (no partition table).
This is typical for USB flash drives and small volumes.
endmenu
endmenu # "Massive Storage Class"
menu "Communication Device Class (CDC)"
config TINYUSB_CDC_ENABLED
bool "Enable TinyUSB CDC feature"
default n
help
Enable TinyUSB CDC feature.
config TINYUSB_CDC_COUNT
int "CDC Channel Count"
default 1
range 1 2
depends on TINYUSB_CDC_ENABLED
help
Number of independent serial ports.
config TINYUSB_CDC_RX_BUFSIZE
depends on TINYUSB_CDC_ENABLED
int "CDC FIFO size of RX channel"
default 512
range 64 10000
help
CDC FIFO size of RX channel.
config TINYUSB_CDC_TX_BUFSIZE
depends on TINYUSB_CDC_ENABLED
int "CDC FIFO size of TX channel"
default 512
help
CDC FIFO size of TX channel.
endmenu # "Communication Device Class"
menu "Musical Instrument Digital Interface (MIDI)"
config TINYUSB_MIDI_COUNT
int "TinyUSB MIDI interfaces count"
default 0
range 0 2
help
Setting value greater than 0 will enable TinyUSB MIDI feature.
endmenu # "Musical Instrument Digital Interface (MIDI)"
menu "Human Interface Device Class (HID)"
config TINYUSB_HID_COUNT
int "TinyUSB HID interfaces count"
default 0
range 0 4
help
Setting value greater than 0 will enable TinyUSB HID feature.
endmenu # "HID Device Class (HID)"
menu "Device Firmware Upgrade (DFU)"
choice TINYUSB_DFU_MODE
prompt "DFU mode"
default TINYUSB_DFU_MODE_NONE
help
Select which DFU driver you want to use.
config TINYUSB_DFU_MODE_DFU
bool "DFU"
config TINYUSB_DFU_MODE_DFU_RUNTIME
bool "DFU Runtime"
config TINYUSB_DFU_MODE_NONE
bool "None"
endchoice
config TINYUSB_DFU_BUFSIZE
depends on TINYUSB_DFU_MODE_DFU
int "DFU XFER BUFFSIZE"
default 512
help
DFU XFER BUFFSIZE.
endmenu # Device Firmware Upgrade (DFU)
menu "Bluetooth Host Class (BTH)"
config TINYUSB_BTH_ENABLED
bool "Enable TinyUSB BTH feature"
default n
help
Enable TinyUSB BTH feature.
config TINYUSB_BTH_ISO_ALT_COUNT
depends on TINYUSB_BTH_ENABLED
int "BTH ISO ALT COUNT"
default 0
help
BTH ISO ALT COUNT.
endmenu # "Bluetooth Host Device Class"
menu "Network driver (ECM/NCM/RNDIS)"
choice TINYUSB_NET_MODE
prompt "Network mode"
default TINYUSB_NET_MODE_NONE
help
Select network driver you want to use.
config TINYUSB_NET_MODE_ECM_RNDIS
bool "ECM/RNDIS"
config TINYUSB_NET_MODE_NCM
bool "NCM"
config TINYUSB_NET_MODE_NONE
bool "None"
endchoice
config TINYUSB_NCM_OUT_NTB_BUFFS_COUNT
int "Number of NCM NTB buffers for reception side"
depends on TINYUSB_NET_MODE_NCM
default 3
range 1 6
help
Number of NTB buffers for reception side.
Can be increased to improve performance and stability with the cost of additional RAM requirements.
Helps to mitigate "tud_network_can_xmit: request blocked" warning message when running NCM device.
config TINYUSB_NCM_IN_NTB_BUFFS_COUNT
int "Number of NCM NTB buffers for transmission side"
depends on TINYUSB_NET_MODE_NCM
default 3
range 1 6
help
Number of NTB buffers for transmission side.
Can be increased to improve performance and stability with the cost of additional RAM requirements.
Helps to mitigate "tud_network_can_xmit: request blocked" warning message when running NCM device.
config TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE
int "NCM NTB Buffer size for reception size"
depends on TINYUSB_NET_MODE_NCM
default 3200
range 1600 10240
help
Size of NTB buffers on the reception side. The minimum size used by Linux is 2048 bytes.
NTB buffer size must be significantly larger than the MTU (Maximum Transmission Unit).
The typical default MTU size for Ethernet is 1500 bytes, plus an additional packet overhead.
To improve performance, the NTB buffer size should be large enough to fit multiple MTU-sized
frames in a single NTB buffer and it's length should be multiple of 4.
config TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE
int "NCM NTB Buffer size for transmission size"
depends on TINYUSB_NET_MODE_NCM
default 3200
range 1600 10240
help
Size of NTB buffers on the transmission side. The minimum size used by Linux is 2048 bytes.
NTB buffer size must be significantly larger than the MTU (Maximum Transmission Unit).
The typical default MTU size for Ethernet is 1500 bytes, plus an additional packet overhead.
To improve performance, the NTB buffer size should be large enough to fit multiple MTU-sized
frames in a single NTB buffer and it's length should be multiple of 4.
endmenu # "Network driver (ECM/NCM/RNDIS)"
menu "Vendor Specific Interface"
config TINYUSB_VENDOR_COUNT
int "TinyUSB Vendor specific interfaces count"
default 0
range 0 2
help
Setting value greater than 0 will enable TinyUSB Vendor specific feature.
endmenu # "Vendor Specific Interface"
endmenu # "TinyUSB Stack"

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,166 @@
# Espressif's additions to TinyUSB
[![Component Registry](https://components.espressif.com/components/espressif/esp_tinyusb/badge.svg)](https://components.espressif.com/components/espressif/esp_tinyusb)
This component adds features to TinyUSB that help users with integrating TinyUSB with their ESP-IDF application.
It contains:
* Configuration of USB device and string descriptors
* USB Serial Device (CDC-ACM) with optional Virtual File System support
* Input and output streams through USB Serial Device. This feature is available only when Virtual File System support is enabled.
* Other USB classes (MIDI, MSC, HID…) support directly via TinyUSB
* VBUS monitoring for self-powered devices
* SPI Flash or sd-card access via MSC USB device Class.
## How to use?
This component is distributed via [IDF component manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). Just add `idf_component.yml` file to your main component with the following content:
``` yaml
## IDF Component Manager Manifest File
dependencies:
esp_tinyusb: "~1.0.0"
```
Or simply run:
```
idf.py add-dependency esp_tinyusb~1.0.0
```
## Documentation
Hardware-related documentation could be found in [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_device.html).
### Device Stack Structure
The Device Stack is built on top of TinyUSB and provides:
- Custom USB descriptor support
- Serial device (CDC-ACM) support
- Standard stream redirection through the serial device
- Storage media support (SPI-Flash and SD-Card) for USB MSC Class
- A dedicated task for TinyUSB servicing
### Configuration Options
Configure the Device Stack using `menuconfig`:
- TinyUSB log verbosity
- Device Stack task options
- Default device/string descriptor options
- Class-specific options
### Descriptor Configuration
Configure USB descriptors using the `tinyusb_config_t` structure:
- `device_descriptor`
- `string_descriptor`
- `configuration_descriptor` (full-speed)
- For high-speed devices: `fs_configuration_descriptor`, `hs_configuration_descriptor`, `qualifier_descriptor`
If any descriptor field is set to `NULL`, default descriptors (based on menuconfig) are used.
### Installation
Install the Device Stack by calling `tinyusb_driver_install` with a `tinyusb_config_t` structure. Members set to `0` or `NULL` use default values.
```c
const tinyusb_config_t partial_init = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif
};
```
### Self-Powered Device
Self-powered devices must monitor VBUS voltage. Use a GPIO pin with a voltage divider or comparator to detect VBUS state. Set `self_powered = true` and assign the VBUS monitor GPIO in `tinyusb_config_t`.
### USB Serial Device (CDC-ACM)
If enabled, initialize the USB Serial Device with `tusb_cdc_acm_init` and a `tinyusb_config_cdcacm_t` structure:
```c
const tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = NULL,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL
};
tusb_cdc_acm_init(&acm_cfg);
```
Redirect standard I/O streams to USB with `esp_tusb_init_console` and revert with `esp_tusb_deinit_console`.
### USB Mass Storage Device (MSC)
If enabled, initialize storage media for MSC:
**SPI-Flash Example:**
```c
static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle) {
// ... partition and mount logic ...
}
storage_init_spiflash(&wl_handle);
const tinyusb_msc_spiflash_config_t config_spi = {
.wl_handle = wl_handle
};
tinyusb_msc_storage_init_spiflash(&config_spi);
```
**SD-Card Example:**
```c
static esp_err_t storage_init_sdmmc(sdmmc_card_t **card) {
// ... SDMMC host and slot config ...
}
storage_init_sdmmc(&card);
const tinyusb_msc_sdmmc_config_t config_sdmmc = {
.card = card
};
tinyusb_msc_storage_init_sdmmc(&config_sdmmc);
```
### MSC Performance Optimization
- **Single-buffer approach:** Buffer size is set via `CONFIG_TINYUSB_MSC_BUFSIZE`.
- **Performance:** SD cards offer higher throughput than internal SPI flash due to architectural constraints.
**Performance Table (ESP32-S3):**
| FIFO Size | Read Speed | Write Speed |
|-----------|------------|-------------|
| 512 B | 0.566 MB/s | 0.236 MB/s |
| 8192 B | 0.925 MB/s | 0.928 MB/s |
**Performance Table (ESP32-P4):**
| FIFO Size | Read Speed | Write Speed |
|-----------|------------|-------------|
| 512 B | 1.174 MB/s | 0.238 MB/s |
| 8192 B | 4.744 MB/s | 2.157 MB/s |
| 32768 B | 5.998 MB/s | 4.485 MB/s |
**Performance Table (ESP32-S2, SPI Flash):**
| FIFO Size | Write Speed |
|-----------|-------------|
| 512 B | 5.59 KB/s |
| 8192 B | 21.54 KB/s |
**Note:** Internal SPI flash is for demonstration only; use SD cards or external flash for higher performance.
## Examples
You can find examples in [ESP-IDF on GitHub](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device).

View File

@@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "tusb.h"
#include "cdc.h"
#define CDC_INTF_NUM CFG_TUD_CDC // number of cdc blocks
static esp_tusb_cdc_t *cdc_obj[CDC_INTF_NUM] = {};
static const char *TAG = "tusb_cdc";
esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num)
{
if (itf_num >= CDC_INTF_NUM || itf_num < 0) {
return NULL;
}
return cdc_obj[itf_num];
}
static esp_err_t cdc_obj_check(int itf, bool expected_inited, tusb_class_code_t expected_type)
{
esp_tusb_cdc_t *this_itf = tinyusb_cdc_get_intf(itf);
bool inited = (this_itf != NULL);
ESP_RETURN_ON_FALSE(expected_inited == inited, ESP_ERR_INVALID_STATE, TAG, "Wrong state of the interface. Expected state: %s", expected_inited ? "initialized" : "not initialized");
ESP_RETURN_ON_FALSE(!(inited && (expected_type != -1) && !(this_itf->type == expected_type)), ESP_ERR_INVALID_STATE, TAG, "Wrong type of the interface. Should be : 0x%x (tusb_class_code_t)", expected_type);
return ESP_OK;
}
static esp_err_t tusb_cdc_comm_init(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed");
cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t));
if (cdc_obj[itf] != NULL) {
cdc_obj[itf]->type = TUSB_CLASS_CDC;
ESP_LOGD(TAG, "CDC Comm class initialized");
return ESP_OK;
} else {
ESP_LOGE(TAG, "CDC Comm initialization error");
return ESP_FAIL;
}
}
static esp_err_t tusb_cdc_deinit_comm(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC), TAG, "cdc_obj_check failed");
free(cdc_obj[itf]);
cdc_obj[itf] = NULL;
return ESP_OK;
}
static esp_err_t tusb_cdc_data_init(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed");
cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t));
if (cdc_obj[itf] != NULL) {
cdc_obj[itf]->type = TUSB_CLASS_CDC_DATA;
ESP_LOGD(TAG, "CDC Data class initialized");
return ESP_OK;
} else {
ESP_LOGE(TAG, "CDC Data initialization error");
return ESP_FAIL;
}
}
static esp_err_t tusb_cdc_deinit_data(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed");
free(cdc_obj[itf]);
cdc_obj[itf] = NULL;
return ESP_OK;
}
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed");
ESP_LOGD(TAG, "Init CDC %d", itf);
if (cfg->cdc_class == TUSB_CLASS_CDC) {
ESP_RETURN_ON_ERROR(tusb_cdc_comm_init(itf), TAG, "tusb_cdc_comm_init failed");
cdc_obj[itf]->cdc_subclass.comm_subclass = cfg->cdc_subclass.comm_subclass;
} else {
ESP_RETURN_ON_ERROR(tusb_cdc_data_init(itf), TAG, "tusb_cdc_data_init failed");
cdc_obj[itf]->cdc_subclass.data_subclass = cfg->cdc_subclass.data_subclass;
}
cdc_obj[itf]->usb_dev = cfg->usb_dev;
return ESP_OK;
}
esp_err_t tinyusb_cdc_deinit(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, -1), TAG, "cdc_obj_check failed");
ESP_LOGD(TAG, "Deinit CDC %d", itf);
if (cdc_obj[itf]->type == TUSB_CLASS_CDC) {
ESP_RETURN_ON_ERROR(tusb_cdc_deinit_comm(itf), TAG, "tusb_cdc_deinit_comm failed");
} else if (cdc_obj[itf]->type == TUSB_CLASS_CDC_DATA) {
ESP_RETURN_ON_ERROR(tusb_cdc_deinit_data(itf), TAG, "tusb_cdc_deinit_data failed");
} else {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}

View File

@@ -0,0 +1,294 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_err.h"
#include "descriptors_control.h"
#include "usb_descriptors.h"
#define MAX_DESC_BUF_SIZE 32 // Max length of string descriptor (can be extended, USB supports lengths up to 255 bytes)
static const char *TAG = "tusb_desc";
// =============================================================================
// STRUCTS
// =============================================================================
/**
* @brief Descriptor pointers for tinyusb descriptor requests callbacks
*
*/
typedef struct {
const tusb_desc_device_t *dev; /*!< Pointer to device descriptor */
union {
const uint8_t *cfg; /*!< Pointer to FullSpeed configuration descriptor when device one-speed only */
const uint8_t *fs_cfg; /*!< Pointer to FullSpeed configuration descriptor when device support HighSpeed */
};
#if (TUD_OPT_HIGH_SPEED)
const uint8_t *hs_cfg; /*!< Pointer to HighSpeed configuration descriptor */
const tusb_desc_device_qualifier_t *qualifier; /*!< Pointer to Qualifier descriptor */
uint8_t *other_speed; /*!< Pointer for other speed configuration descriptor */
#endif // TUD_OPT_HIGH_SPEED
const char *str[USB_STRING_DESCRIPTOR_ARRAY_SIZE]; /*!< Pointer to array of UTF-8 strings */
int str_count; /*!< Number of descriptors in str */
} tinyusb_descriptor_config_t;
static tinyusb_descriptor_config_t s_desc_cfg;
// =============================================================================
// CALLBACKS
// =============================================================================
/**
* @brief Invoked when received GET DEVICE DESCRIPTOR.
* Descriptor contents must exist long enough for transfer to complete
*
* @return Pointer to device descriptor
*/
uint8_t const *tud_descriptor_device_cb(void)
{
assert(s_desc_cfg.dev);
return (uint8_t const *)s_desc_cfg.dev;
}
/**
* @brief Invoked when received GET CONFIGURATION DESCRIPTOR.
* Descriptor contents must exist long enough for transfer to complete
*
* @param[in] index Index of required configuration
* @return Pointer to configuration descriptor
*/
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
(void)index; // Unused, this driver supports only 1 configuration
assert(s_desc_cfg.cfg);
#if (TUD_OPT_HIGH_SPEED)
// HINT: cfg and fs_cfg are union, no need to assert(fs_cfg)
assert(s_desc_cfg.hs_cfg);
// Return configuration descriptor based on Host speed
return (TUSB_SPEED_HIGH == tud_speed_get())
? s_desc_cfg.hs_cfg
: s_desc_cfg.fs_cfg;
#else
return s_desc_cfg.cfg;
#endif // TUD_OPT_HIGH_SPEED
}
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
* Descriptor contents must exist long enough for transfer to complete
* If not highspeed capable stall this request
*/
uint8_t const *tud_descriptor_device_qualifier_cb(void)
{
assert(s_desc_cfg.qualifier);
return (uint8_t const *)s_desc_cfg.qualifier;
}
/**
* @brief Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request
* Descriptor contents must exist long enough for transfer to complete
* Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
*/
uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index)
{
assert(s_desc_cfg.other_speed);
const uint8_t *other_speed = (TUSB_SPEED_HIGH == tud_speed_get())
? s_desc_cfg.fs_cfg
: s_desc_cfg.hs_cfg;
memcpy(s_desc_cfg.other_speed,
other_speed,
((tusb_desc_configuration_t *)other_speed)->wTotalLength);
((tusb_desc_configuration_t *)s_desc_cfg.other_speed)->bDescriptorType = TUSB_DESC_OTHER_SPEED_CONFIG;
return s_desc_cfg.other_speed;
}
#endif // TUD_OPT_HIGH_SPEED
/**
* @brief Invoked when received GET STRING DESCRIPTOR request
*
* @param[in] index Index of required descriptor
* @param[in] langid Language of the descriptor
* @return Pointer to UTF-16 string descriptor
*/
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void) langid; // Unused, this driver supports only one language in string descriptors
assert(s_desc_cfg.str);
uint8_t chr_count;
static uint16_t _desc_str[MAX_DESC_BUF_SIZE];
if (index == 0) {
memcpy(&_desc_str[1], s_desc_cfg.str[0], 2);
chr_count = 1;
} else {
if (index >= USB_STRING_DESCRIPTOR_ARRAY_SIZE) {
ESP_LOGW(TAG, "String index (%u) is out of bounds, check your string descriptor", index);
return NULL;
}
if (s_desc_cfg.str[index] == NULL) {
ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index);
return NULL;
}
const char *str = s_desc_cfg.str[index];
chr_count = strnlen(str, MAX_DESC_BUF_SIZE - 1); // Buffer len - header
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// First byte is length in bytes (including header), second byte is descriptor type (TUSB_DESC_STRING)
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2);
return _desc_str;
}
// =============================================================================
// Driver functions
// =============================================================================
esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config)
{
esp_err_t ret = ESP_FAIL;
assert(config);
const char **pstr_desc;
// Flush descriptors control struct
memset(&s_desc_cfg, 0x00, sizeof(tinyusb_descriptor_config_t));
// Parse configuration and save descriptors's pointer
// Select Device Descriptor
if (config->device_descriptor == NULL) {
ESP_LOGW(TAG, "No Device descriptor provided, using default.");
s_desc_cfg.dev = &descriptor_dev_default;
} else {
s_desc_cfg.dev = config->device_descriptor;
}
// Select FullSpeed configuration descriptor
if (config->configuration_descriptor == NULL) {
// Default configuration descriptor must be provided for the following classes
#if (CFG_TUD_HID > 0 || CFG_TUD_MIDI > 0 || CFG_TUD_ECM_RNDIS > 0 || CFG_TUD_DFU > 0 || CFG_TUD_DFU_RUNTIME > 0 || CFG_TUD_BTH > 0)
ESP_GOTO_ON_FALSE(config->configuration_descriptor, ESP_ERR_INVALID_ARG, fail, TAG, "Configuration descriptor must be provided for this device");
#else
ESP_LOGW(TAG, "No FullSpeed configuration descriptor provided, using default.");
s_desc_cfg.cfg = descriptor_fs_cfg_default;
#endif
} else {
s_desc_cfg.cfg = config->configuration_descriptor;
}
#if (TUD_OPT_HIGH_SPEED)
// High Speed
if (config->hs_configuration_descriptor == NULL) {
// Default configuration descriptor must be provided for the following classes
#if (CFG_TUD_HID > 0 || CFG_TUD_MIDI > 0 || CFG_TUD_ECM_RNDIS > 0 || CFG_TUD_DFU > 0 || CFG_TUD_DFU_RUNTIME > 0 || CFG_TUD_BTH > 0)
ESP_GOTO_ON_FALSE(config->hs_configuration_descriptor, ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed configuration descriptor must be provided for this device");
#else
ESP_LOGW(TAG, "No HighSpeed configuration descriptor provided, using default.");
s_desc_cfg.hs_cfg = descriptor_hs_cfg_default;
#endif
} else {
s_desc_cfg.hs_cfg = config->hs_configuration_descriptor;
}
// HS and FS cfg desc should be equal length
ESP_GOTO_ON_FALSE(((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength ==
((tusb_desc_configuration_t *)s_desc_cfg.fs_cfg)->wTotalLength,
ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed and FullSpeed configuration descriptors must be same length");
// Qualifier Descriptor
if (config->qualifier_descriptor == NULL) {
ESP_GOTO_ON_FALSE((s_desc_cfg.dev == &descriptor_dev_default), ESP_ERR_INVALID_ARG, fail, TAG, "Qualifier descriptor must be present (Device Descriptor not default).");
// Get default qualifier if device descriptor is default
ESP_LOGW(TAG, "No Qulifier descriptor provided, using default.");
s_desc_cfg.qualifier = &descriptor_qualifier_default;
} else {
s_desc_cfg.qualifier = config->qualifier_descriptor;
}
// Other Speed buffer allocate
s_desc_cfg.other_speed = calloc(1, ((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength);
ESP_GOTO_ON_FALSE(s_desc_cfg.other_speed, ESP_ERR_NO_MEM, fail, TAG, "Other speed memory allocation error");
#endif // TUD_OPT_HIGH_SPEED
// Select String Descriptors and count them
if (config->string_descriptor == NULL) {
ESP_LOGW(TAG, "No String descriptors provided, using default.");
pstr_desc = descriptor_str_default;
while (descriptor_str_default[++s_desc_cfg.str_count] != NULL);
} else {
pstr_desc = config->string_descriptor;
s_desc_cfg.str_count = (config->string_descriptor_count != 0)
? config->string_descriptor_count
: 8; // '8' is for backward compatibility with esp_tinyusb v1.0.0. Do NOT remove!
}
ESP_GOTO_ON_FALSE(s_desc_cfg.str_count <= USB_STRING_DESCRIPTOR_ARRAY_SIZE, ESP_ERR_NOT_SUPPORTED, fail, TAG, "String descriptors exceed limit");
memcpy(s_desc_cfg.str, pstr_desc, s_desc_cfg.str_count * sizeof(pstr_desc[0]));
ESP_LOGI(TAG, "\n"
"┌─────────────────────────────────┐\n"
"│ USB Device Descriptor Summary │\n"
"├───────────────────┬─────────────┤\n"
"│bDeviceClass │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bDeviceSubClass │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bDeviceProtocol │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bMaxPacketSize0 │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│idVendor │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│idProduct │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│bcdDevice │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iManufacturer │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iProduct │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iSerialNumber │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│bNumConfigurations │ %-#10x │\n"
"└───────────────────┴─────────────┘",
s_desc_cfg.dev->bDeviceClass, s_desc_cfg.dev->bDeviceSubClass,
s_desc_cfg.dev->bDeviceProtocol, s_desc_cfg.dev->bMaxPacketSize0,
s_desc_cfg.dev->idVendor, s_desc_cfg.dev->idProduct, s_desc_cfg.dev->bcdDevice,
s_desc_cfg.dev->iManufacturer, s_desc_cfg.dev->iProduct, s_desc_cfg.dev->iSerialNumber,
s_desc_cfg.dev->bNumConfigurations);
return ESP_OK;
fail:
#if (TUD_OPT_HIGH_SPEED)
free(s_desc_cfg.other_speed);
#endif // TUD_OPT_HIGH_SPEED
return ret;
}
void tinyusb_set_str_descriptor(const char *str, int str_idx)
{
assert(str_idx < USB_STRING_DESCRIPTOR_ARRAY_SIZE);
s_desc_cfg.str[str_idx] = str;
}
void tinyusb_free_descriptors(void)
{
#if (TUD_OPT_HIGH_SPEED)
assert(s_desc_cfg.other_speed);
free(s_desc_cfg.other_speed);
#endif // TUD_OPT_HIGH_SPEED
}

View File

@@ -0,0 +1,13 @@
dependencies:
idf: '>=5.0'
tinyusb:
public: true
version: '>=0.14.2'
description: Espressif's additions to TinyUSB
documentation: https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_device.html
repository: git://github.com/espressif/esp-usb.git
repository_info:
commit_sha: c0be948c1acc6ee1f7ef00ab183c571971fccefd
path: device/esp_tinyusb
url: https://github.com/espressif/esp-usb/tree/master/device/esp_tinyusb
version: 1.7.6~2

View File

@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "tusb.h"
#include "tinyusb_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuration structure of the TinyUSB core
*
* USB specification mandates self-powered devices to monitor USB VBUS to detect connection/disconnection events.
* If you want to use this feature, connected VBUS to any free GPIO through a voltage divider or voltage comparator.
* The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port).
* The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge).
*/
typedef struct {
union {
const tusb_desc_device_t *device_descriptor; /*!< Pointer to a device descriptor. If set to NULL, the TinyUSB device will use a default device descriptor whose values are set in Kconfig */
const tusb_desc_device_t *descriptor __attribute__((deprecated)); /*!< Alias to `device_descriptor` for backward compatibility */
};
const char **string_descriptor; /*!< Pointer to array of string descriptors. If set to NULL, TinyUSB device will use a default string descriptors whose values are set in Kconfig */
int string_descriptor_count; /*!< Number of descriptors in above array */
bool external_phy; /*!< Should USB use an external PHY */
union {
struct {
const uint8_t *configuration_descriptor; /*!< Pointer to a configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */
};
#if (TUD_OPT_HIGH_SPEED)
struct {
const uint8_t *fs_configuration_descriptor; /*!< Pointer to a FullSpeed configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */
};
};
const uint8_t *hs_configuration_descriptor; /*!< Pointer to a HighSpeed configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */
const tusb_desc_device_qualifier_t *qualifier_descriptor; /*!< Pointer to a qualifier descriptor */
#else
};
#endif // TUD_OPT_HIGH_SPEED
bool self_powered; /*!< This is a self-powered USB device. USB VBUS must be monitored. */
int vbus_monitor_io; /*!< GPIO for VBUS monitoring. Ignored if not self_powered. */
} tinyusb_config_t;
/**
* @brief This is an all-in-one helper function, including:
* 1. USB device driver initialization
* 2. Descriptors preparation
* 3. TinyUSB stack initialization
* 4. Creates and start a task to handle usb events
*
* @note Don't change Custom descriptor, but if it has to be done,
* Suggest to define as follows in order to match the Interface Association Descriptor (IAD):
* bDeviceClass = TUSB_CLASS_MISC,
* bDeviceSubClass = MISC_SUBCLASS_COMMON,
*
* @param config tinyusb stack specific configuration
* @retval ESP_ERR_INVALID_ARG Install driver and tinyusb stack failed because of invalid argument
* @retval ESP_FAIL Install driver and tinyusb stack failed because of internal error
* @retval ESP_OK Install driver and tinyusb stack successfully
*/
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config);
/**
* @brief This is an all-in-one helper function, including:
* 1. Stops the task to handle usb events
* 2. TinyUSB stack tearing down
* 2. Freeing resources after descriptors preparation
* 3. Deletes USB PHY
*
* @retval ESP_FAIL Uninstall driver or tinyusb stack failed because of internal error
* @retval ESP_OK Uninstall driver, tinyusb stack and USB PHY successfully
*/
esp_err_t tinyusb_driver_uninstall(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "tinyusb_types.h"
#include "esp_err.h"
#include "sdkconfig.h"
#if (CONFIG_TINYUSB_NET_MODE_NONE != 1)
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief On receive callback type
*/
typedef esp_err_t (*tusb_net_rx_cb_t)(void *buffer, uint16_t len, void *ctx);
/**
* @brief Free Tx buffer callback type
*/
typedef void (*tusb_net_free_tx_cb_t)(void *buffer, void *ctx);
/**
* @brief On init callback type
*/
typedef void (*tusb_net_init_cb_t)(void *ctx);
/**
* @brief ESP TinyUSB NCM driver configuration structure
*/
typedef struct {
uint8_t mac_addr[6]; /*!< MAC address. Must be 6 bytes long. */
tusb_net_rx_cb_t on_recv_callback; /*!< TinyUSB receive data callbeck */
tusb_net_free_tx_cb_t free_tx_buffer; /*!< User function for freeing the Tx buffer.
* - could be NULL, if user app is responsible for freeing the buffer
* - must be used in asynchronous send mode
* - is only called if the used tinyusb_net_send...() function returns ESP_OK
* - in sync mode means that the packet was accepted by TinyUSB
* - in async mode means that the packet was queued to be processed in TinyUSB task
*/
tusb_net_init_cb_t on_init_callback; /*!< TinyUSB init network callback */
void *user_context; /*!< User context to be passed to any of the callback */
} tinyusb_net_config_t;
/**
* @brief Initialize TinyUSB NET driver
*
* @param[in] usb_dev USB device to use
* @param[in] cfg Configuration of the driver
* @return esp_err_t
*/
esp_err_t tinyusb_net_init(tinyusb_usbdev_t usb_dev, const tinyusb_net_config_t *cfg);
/**
* @brief TinyUSB NET driver send data synchronously
*
* @note It is possible to use sync and async send interchangeably.
* This function needs some synchronization primitives, so using sync mode (even once) uses more heap
*
* @param[in] buffer USB send data
* @param[in] len Send data len
* @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback
* @param[in] timeout Send data len
* @return ESP_OK on success == packet has been consumed by tusb and would be eventually freed
* by free_tx_buffer() callback (if non null)
* ESP_ERR_TIMEOUT on timeout
* ESP_ERR_INVALID_STATE if tusb not initialized, ESP_ERR_NO_MEM on alloc failure
*/
esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout);
/**
* @brief TinyUSB NET driver send data asynchronously
*
* @note If using asynchronous sends, you must free the buffer using free_tx_buffer() callback.
* @note It is possible to use sync and async send interchangeably.
* @note Async flavor of the send is useful when the USB stack runs faster than the caller,
* since we have no control over the transmitted packets, if they get accepted or discarded.
*
* @param[in] buffer USB send data
* @param[in] len Send data len
* @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback
* @return ESP_OK on success == packet has been consumed by tusb and will be freed
* by free_tx_buffer() callback (if non null)
* ESP_ERR_INVALID_STATE if tusb not initialized
*/
esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg);
#endif // (CONFIG_TINYUSB_NET_MODE_NONE != 1)
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define USB_ESPRESSIF_VID 0x303A
typedef enum {
TINYUSB_USBDEV_0,
} tinyusb_usbdev_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "tinyusb_types.h"
#include "class/cdc/cdc.h"
#if (CONFIG_TINYUSB_CDC_ENABLED != 1)
#error "TinyUSB CDC driver must be enabled in menuconfig"
#endif
/**
* @brief CDC ports available to setup
*/
typedef enum {
TINYUSB_CDC_ACM_0 = 0x0,
TINYUSB_CDC_ACM_1,
TINYUSB_CDC_ACM_MAX
} tinyusb_cdcacm_itf_t;
/* Callbacks and events
********************************************************************* */
/**
* @brief Data provided to the input of the `callback_rx_wanted_char` callback
*/
typedef struct {
char wanted_char; /*!< Wanted character */
} cdcacm_event_rx_wanted_char_data_t;
/**
* @brief Data provided to the input of the `callback_line_state_changed` callback
*/
typedef struct {
bool dtr; /*!< Data Terminal Ready (DTR) line state */
bool rts; /*!< Request To Send (RTS) line state */
} cdcacm_event_line_state_changed_data_t;
/**
* @brief Data provided to the input of the `line_coding_changed` callback
*/
typedef struct {
cdc_line_coding_t const *p_line_coding; /*!< New line coding value */
} cdcacm_event_line_coding_changed_data_t;
/**
* @brief Types of CDC ACM events
*/
typedef enum {
CDC_EVENT_RX,
CDC_EVENT_RX_WANTED_CHAR,
CDC_EVENT_LINE_STATE_CHANGED,
CDC_EVENT_LINE_CODING_CHANGED
} cdcacm_event_type_t;
/**
* @brief Describes an event passing to the input of a callbacks
*/
typedef struct {
cdcacm_event_type_t type; /*!< Event type */
union {
cdcacm_event_rx_wanted_char_data_t rx_wanted_char_data; /*!< Data input of the `callback_rx_wanted_char` callback */
cdcacm_event_line_state_changed_data_t line_state_changed_data; /*!< Data input of the `callback_line_state_changed` callback */
cdcacm_event_line_coding_changed_data_t line_coding_changed_data; /*!< Data input of the `line_coding_changed` callback */
};
} cdcacm_event_t;
/**
* @brief CDC-ACM callback type
*/
typedef void(*tusb_cdcacm_callback_t)(int itf, cdcacm_event_t *event);
/*********************************************************************** Callbacks and events*/
/* Other structs
********************************************************************* */
/**
* @brief Configuration structure for CDC-ACM
*/
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< Usb device to set up */
tinyusb_cdcacm_itf_t cdc_port; /*!< CDC port */
size_t rx_unread_buf_sz __attribute__((deprecated("This parameter is not used any more. Configure RX buffer in menuconfig.")));
tusb_cdcacm_callback_t callback_rx; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_rx_wanted_char; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_line_state_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_line_coding_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
} tinyusb_config_cdcacm_t;
/*********************************************************************** Other structs*/
/* Public functions
********************************************************************* */
/**
* @brief Initialize CDC ACM. Initialization will be finished with
* the `tud_cdc_line_state_cb` callback
*
* @param[in] cfg Configuration structure
* @return esp_err_t
*/
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg);
/**
* @brief De-initialize CDC ACM.
*
* @param[in] itf Index of CDC interface
* @return esp_err_t
*/
esp_err_t tusb_cdc_acm_deinit(int itf);
/**
* @brief Register a callback invoking on CDC event. If the callback had been
* already registered, it will be overwritten
*
* @param[in] itf Index of CDC interface
* @param[in] event_type Type of registered event for a callback
* @param[in] callback Callback function
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type,
tusb_cdcacm_callback_t callback);
/**
* @brief Unregister a callback invoking on CDC event
*
* @param[in] itf Index of CDC interface
* @param[in] event_type Type of registered event for a callback
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type);
/**
* @brief Sent one character to a write buffer
*
* @param[in] itf Index of CDC interface
* @param[in] ch Character to send
* @return size_t - amount of queued bytes
*/
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch);
/**
* @brief Write data to write buffer
*
* @param[in] itf Index of CDC interface
* @param[in] in_buf Data
* @param[in] in_size Data size in bytes
* @return size_t - amount of queued bytes
*/
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size);
/**
* @brief Flush data in write buffer of CDC interface
*
* Use `tinyusb_cdcacm_write_queue` to add data to the buffer
*
* WARNING! TinyUSB can block output Endpoint for several RX callbacks, after will do additional flush
* after the each transfer. That can leads to the situation when you requested a flush, but it will fail until
* one of the next callbacks ends.
* SO USING OF THE FLUSH WITH TIMEOUTS IN CALLBACKS IS NOT RECOMMENDED - YOU CAN GET A LOCK FOR THE TIMEOUT
*
* @param[in] itf Index of CDC interface
* @param[in] timeout_ticks Transfer timeout. Set to zero for non-blocking mode
* @return - ESP_OK All data flushed
* - ESP_ERR_TIMEOUT Time out occurred in blocking mode
* - ESP_NOT_FINISHED The transfer is still in progress in non-blocking mode
*/
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks);
/**
* @brief Receive data from CDC interface
*
* @param[in] itf Index of CDC interface
* @param[out] out_buf Data buffer
* @param[in] out_buf_sz Data buffer size in bytes
* @param[out] rx_data_size Number of bytes written to out_buf
* @return esp_err_t ESP_OK, ESP_FAIL or ESP_ERR_INVALID_STATE
*/
esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size);
/**
* @brief Check if the CDC interface is initialized
*
* @param[in] itf Index of CDC interface
* @return - true Initialized
* - false Not Initialized
*/
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf);
/*********************************************************************** Public functions*/
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org),
* SPDX-FileContributor: 2020-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2019 Ha Thach (tinyusb.org),
* Additions Copyright (c) 2020, Espressif Systems (Shanghai) PTE LTD
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#pragma once
#include "tusb_option.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CONFIG_TINYUSB_CDC_ENABLED
# define CONFIG_TINYUSB_CDC_ENABLED 0
#endif
#ifndef CONFIG_TINYUSB_CDC_COUNT
# define CONFIG_TINYUSB_CDC_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_MSC_ENABLED
# define CONFIG_TINYUSB_MSC_ENABLED 0
#endif
#ifndef CONFIG_TINYUSB_HID_COUNT
# define CONFIG_TINYUSB_HID_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_MIDI_COUNT
# define CONFIG_TINYUSB_MIDI_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_VENDOR_COUNT
# define CONFIG_TINYUSB_VENDOR_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_NET_MODE_ECM_RNDIS
# define CONFIG_TINYUSB_NET_MODE_ECM_RNDIS 0
#endif
#ifndef CONFIG_TINYUSB_NET_MODE_NCM
# define CONFIG_TINYUSB_NET_MODE_NCM 0
#endif
#ifndef CONFIG_TINYUSB_DFU_MODE_DFU
# define CONFIG_TINYUSB_DFU_MODE_DFU 0
#endif
#ifndef CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME
# define CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME 0
#endif
#ifndef CONFIG_TINYUSB_BTH_ENABLED
# define CONFIG_TINYUSB_BTH_ENABLED 0
# define CONFIG_TINYUSB_BTH_ISO_ALT_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_DEBUG_LEVEL
# define CONFIG_TINYUSB_DEBUG_LEVEL 0
#endif
#ifdef CONFIG_TINYUSB_RHPORT_HS
# define CFG_TUSB_RHPORT1_MODE OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED
#else
# define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED
#endif
// ------------------------------------------------------------------------
// DCD DWC2 Mode
// ------------------------------------------------------------------------
#define CFG_TUD_DWC2_SLAVE_ENABLE 1 // Enable Slave/IRQ by default
// ------------------------------------------------------------------------
// DMA & Cache
// ------------------------------------------------------------------------
#ifdef CONFIG_TINYUSB_MODE_DMA
// DMA Mode has a priority over Slave/IRQ mode and will be used if hardware supports it
#define CFG_TUD_DWC2_DMA_ENABLE 1 // Enable DMA
#if CONFIG_CACHE_L1_CACHE_LINE_SIZE
// To enable the dcd_dcache clean/invalidate/clean_invalidate calls
# define CFG_TUD_MEM_DCACHE_ENABLE 1
#define CFG_TUD_MEM_DCACHE_LINE_SIZE CONFIG_CACHE_L1_CACHE_LINE_SIZE
// NOTE: starting with esp-idf v5.3 there is specific attribute present: DRAM_DMA_ALIGNED_ATTR
# define CFG_TUSB_MEM_SECTION __attribute__((aligned(CONFIG_CACHE_L1_CACHE_LINE_SIZE))) DRAM_ATTR
#else
# define CFG_TUD_MEM_CACHE_ENABLE 0
# define CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) DRAM_ATTR
#endif // CONFIG_CACHE_L1_CACHE_LINE_SIZE
#endif // CONFIG_TINYUSB_MODE_DMA
#define CFG_TUSB_OS OPT_OS_FREERTOS
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
* e.g
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
*/
#ifndef CFG_TUSB_MEM_SECTION
# define CFG_TUSB_MEM_SECTION
#endif
#ifndef CFG_TUSB_MEM_ALIGN
# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4)
#endif
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
// Debug Level
#define CFG_TUSB_DEBUG CONFIG_TINYUSB_DEBUG_LEVEL
#define CFG_TUSB_DEBUG_PRINTF esp_rom_printf // TinyUSB can print logs from ISR, so we must use esp_rom_printf()
// CDC FIFO size of TX and RX
#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE
#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE
// MSC Buffer size of Device Mass storage
#define CFG_TUD_MSC_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE
// MIDI macros
#define CFG_TUD_MIDI_EP_BUFSIZE 64
#define CFG_TUD_MIDI_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
#define CFG_TUD_MIDI_RX_BUFSIZE 64
#define CFG_TUD_MIDI_TX_BUFSIZE 64
// Vendor FIFO size of TX and RX
#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
// DFU macros
#define CFG_TUD_DFU_XFER_BUFSIZE CONFIG_TINYUSB_DFU_BUFSIZE
// Number of BTH ISO alternatives
#define CFG_TUD_BTH_ISO_ALT_COUNT CONFIG_TINYUSB_BTH_ISO_ALT_COUNT
// Enabled device class driver
#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_COUNT
#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED
#define CFG_TUD_HID CONFIG_TINYUSB_HID_COUNT
#define CFG_TUD_MIDI CONFIG_TINYUSB_MIDI_COUNT
#define CFG_TUD_VENDOR CONFIG_TINYUSB_VENDOR_COUNT
#define CFG_TUD_ECM_RNDIS CONFIG_TINYUSB_NET_MODE_ECM_RNDIS
#define CFG_TUD_NCM CONFIG_TINYUSB_NET_MODE_NCM
#define CFG_TUD_DFU CONFIG_TINYUSB_DFU_MODE_DFU
#define CFG_TUD_DFU_RUNTIME CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME
#define CFG_TUD_BTH CONFIG_TINYUSB_BTH_ENABLED
// NCM NET Mode NTB buffers configuration
#define CFG_TUD_NCM_OUT_NTB_N CONFIG_TINYUSB_NCM_OUT_NTB_BUFFS_COUNT
#define CFG_TUD_NCM_IN_NTB_N CONFIG_TINYUSB_NCM_IN_NTB_BUFFS_COUNT
#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE CONFIG_TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE
#define CFG_TUD_NCM_IN_NTB_MAX_SIZE CONFIG_TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
/**
* @brief Redirect output to the USB serial
* @param cdc_intf - interface number of TinyUSB's CDC
*
* @return esp_err_t - ESP_OK, ESP_FAIL or an error code
*/
esp_err_t esp_tusb_init_console(int cdc_intf);
/**
* @brief Switch log to the default output
* @param cdc_intf - interface number of TinyUSB's CDC
*
* @return esp_err_t
*/
esp_err_t esp_tusb_deinit_console(int cdc_intf);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,190 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include "esp_err.h"
#include "wear_levelling.h"
#include "esp_vfs_fat.h"
#if SOC_SDMMC_HOST_SUPPORTED
#include "driver/sdmmc_host.h"
#endif
/**
* @brief Data provided to the input of the `callback_mount_changed` and `callback_premount_changed` callback
*/
typedef struct {
bool is_mounted; /*!< Flag if storage is mounted or not */
} tinyusb_msc_event_mount_changed_data_t;
/**
* @brief Types of MSC events
*/
typedef enum {
TINYUSB_MSC_EVENT_MOUNT_CHANGED, /*!< Event type AFTER mount/unmount operation is successfully finished */
TINYUSB_MSC_EVENT_PREMOUNT_CHANGED /*!< Event type BEFORE mount/unmount operation is started */
} tinyusb_msc_event_type_t;
/**
* @brief Describes an event passing to the input of a callbacks
*/
typedef struct {
tinyusb_msc_event_type_t type; /*!< Event type */
union {
tinyusb_msc_event_mount_changed_data_t mount_changed_data; /*!< Data input of the callback */
};
} tinyusb_msc_event_t;
/**
* @brief MSC callback that is delivered whenever a specific event occurs.
*/
typedef void(*tusb_msc_callback_t)(tinyusb_msc_event_t *event);
#if SOC_SDMMC_HOST_SUPPORTED
/**
* @brief Configuration structure for sdmmc initialization
*
* User configurable parameters that are used while
* initializing the sdmmc media.
*/
typedef struct {
sdmmc_card_t *card; /*!< Pointer to sdmmc card configuration structure */
tusb_msc_callback_t callback_mount_changed; /*!< Pointer to the function callback that will be delivered AFTER mount/unmount operation is successfully finished */
tusb_msc_callback_t callback_premount_changed; /*!< Pointer to the function callback that will be delivered BEFORE mount/unmount operation is started */
const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */
} tinyusb_msc_sdmmc_config_t;
#endif
/**
* @brief Configuration structure for spiflash initialization
*
* User configurable parameters that are used while
* initializing the SPI Flash media.
*/
typedef struct {
wl_handle_t wl_handle; /*!< Pointer to spiflash wera-levelling handle */
tusb_msc_callback_t callback_mount_changed; /*!< Pointer to the function callback that will be delivered AFTER mount/unmount operation is successfully finished */
tusb_msc_callback_t callback_premount_changed; /*!< Pointer to the function callback that will be delivered BEFORE mount/unmount operation is started */
const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */
} tinyusb_msc_spiflash_config_t;
/**
* @brief Register storage type spiflash with tinyusb driver
*
* @param config pointer to the spiflash configuration
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NO_MEM, if there was no memory to allocate storage components;
* - ESP_ERR_NOT_SUPPORTED, if wear leveling sector size CONFIG_WL_SECTOR_SIZE is bigger than
* the tinyusb MSC buffer size CONFIG_TINYUSB_MSC_BUFSIZE
*/
esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t *config);
#if SOC_SDMMC_HOST_SUPPORTED
/**
* @brief Register storage type sd-card with tinyusb driver
*
* @param config pointer to the sd card configuration
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NO_MEM, if there was no memory to allocate storage components;
*/
esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *config);
#endif
/**
* @brief Deregister storage with tinyusb driver and frees the memory
*
*/
void tinyusb_msc_storage_deinit(void);
/**
* @brief Register a callback invoking on MSC event. If the callback had been
* already registered, it will be overwritten
*
* @param event_type - type of registered event for a callback
* @param callback - callback function
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_msc_register_callback(tinyusb_msc_event_type_t event_type,
tusb_msc_callback_t callback);
/**
* @brief Unregister a callback invoking on MSC event.
*
* @param event_type - type of registered event for a callback
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_msc_unregister_callback(tinyusb_msc_event_type_t event_type);
/**
* @brief Mount the storage partition locally on the firmware application.
*
* Get the available drive number. Register spi flash partition.
* Connect POSIX and C standard library IO function with FATFS.
* Mounts the partition.
* This API is used by the firmware application. If the storage partition is
* mounted by this API, host (PC) can't access the storage via MSC.
* When this function is called from the tinyusb callback functions, care must be taken
* so as to make sure that user callbacks must be completed within a
* specific time. Otherwise, MSC device may re-appear again on Host.
*
* @param base_path path prefix where FATFS should be registered
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NOT_FOUND if the maximum count of volumes is already mounted
* - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered;
*/
esp_err_t tinyusb_msc_storage_mount(const char *base_path);
/**
* @brief Unmount the storage partition from the firmware application.
*
* Unmount the partition. Unregister diskio driver.
* Unregister the SPI flash partition.
* Finally, Un-register FATFS from VFS.
* After this function is called, storage device can be seen (recognized) by host (PC).
* When this function is called from the tinyusb callback functions, care must be taken
* so as to make sure that user callbacks must be completed within a specific time.
* Otherwise, MSC device may not appear on Host.
*
* @return esp_err_t
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
*/
esp_err_t tinyusb_msc_storage_unmount(void);
/**
* @brief Get number of sectors in storage media
*
* @return usable size, in bytes
*/
uint32_t tinyusb_msc_storage_get_sector_count(void);
/**
* @brief Get sector size of storage media
*
* @return sector count
*/
uint32_t tinyusb_msc_storage_get_sector_size(void);
/**
* @brief Get status if storage media is exposed over USB to Host
*
* @return bool
* - true, if the storage media is exposed to Host
* - false, if the stoarge media is mounted on application (not exposed to Host)
*/
bool tinyusb_msc_storage_in_use_by_usb_host(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief This helper function creates and starts a task which wraps `tud_task()`.
*
* The wrapper function basically wraps tud_task and some log.
* Default parameters: stack size and priority as configured, argument = NULL, not pinned to any core.
* If you have more requirements for this task, you can create your own task which calls tud_task as the last step.
*
* @retval ESP_OK run tinyusb main task successfully
* @retval ESP_FAIL run tinyusb main task failed of internal error or initialization within the task failed when TINYUSB_INIT_IN_DEFAULT_TASK=y
* @retval ESP_FAIL initialization within the task failed if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK is enabled
* @retval ESP_ERR_INVALID_STATE tinyusb main task has been created before
*/
esp_err_t tusb_run_task(void);
/**
* @brief This helper function stops and destroys the task created by `tusb_run_task()`
*
* @retval ESP_OK stop and destroy tinyusb main task successfully
* @retval ESP_ERR_INVALID_STATE tinyusb main task hasn't been created yet
*/
esp_err_t tusb_stop_task(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_vfs_common.h" // For esp_line_endings_t definitions
#ifdef __cplusplus
extern "C" {
#endif
#define VFS_TUSB_MAX_PATH 16
#define VFS_TUSB_PATH_DEFAULT "/dev/tusb_cdc"
/**
* @brief Register TinyUSB CDC at VFS with path
*
* Know limitation:
* In case there are multiple CDC interfaces in the system, only one of them can be registered to VFS.
*
* @param[in] cdc_intf Interface number of TinyUSB's CDC
* @param[in] path Path where the CDC will be registered, `/dev/tusb_cdc` will be used if left NULL.
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path);
/**
* @brief Unregister TinyUSB CDC from VFS
*
* @param[in] path Path where the CDC will be unregistered if NULL will be used `/dev/tusb_cdc`
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t esp_vfs_tusb_cdc_unregister(char const *path);
/**
* @brief Set the line endings to sent
*
* This specifies the conversion between newlines ('\n', LF) on stdout and line
* endings sent:
*
* - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF
* - ESP_LINE_ENDINGS_CR: convert LF to CR
* - ESP_LINE_ENDINGS_LF: no modification
*
* @param[in] mode line endings to send
*/
void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode);
/**
* @brief Set the line endings expected to be received
*
* This specifies the conversion between line endings received and
* newlines ('\n', LF) passed into stdin:
*
* - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF
* - ESP_LINE_ENDINGS_CR: convert CR to LF
* - ESP_LINE_ENDINGS_LF: no modification
*
* @param[in] mode line endings expected
*/
void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "tusb.h"
#include "tinyusb_types.h"
/* CDC classification
********************************************************************* */
typedef enum {
TINYUSB_CDC_DATA = 0x00,
} cdc_data_sublcass_type_t; // CDC120 specification
/* Note:other classification is represented in the file components\tinyusb\tinyusb\src\class\cdc\cdc.h */
/*********************************************************************** CDC classification*/
/* Structs
********************************************************************* */
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< USB device to set up */
tusb_class_code_t cdc_class; /*!< CDC device class : Communications or Data device */
union {
cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */
cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/
} cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */
} tinyusb_config_cdc_t; /*!< Main configuration structure of a CDC device */
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< USB device used for the instance */
tusb_class_code_t type;
union {
cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */
cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/
} cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */
void *subclass_obj; /*!< Dynamically allocated subclass specific object */
} esp_tusb_cdc_t;
/*********************************************************************** Structs*/
/* Functions
********************************************************************* */
/**
* @brief Initializing CDC basic object
* @param itf - number of a CDC object
* @param cfg - CDC configuration structure
*
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg);
/**
* @brief De-initializing CDC. Clean its objects
* @param itf - number of a CDC object
* @return esp_err_t ESP_OK, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE
*
*/
esp_err_t tinyusb_cdc_deinit(int itf);
/**
* @brief Return interface of a CDC device
*
* @param itf_num
* @return esp_tusb_cdc_t* pointer to the interface or (NULL) on error
*/
esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num);
/*********************************************************************** Functions*/
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "tinyusb.h"
#ifdef __cplusplus
extern "C" {
#endif
#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 8 // Max 8 string descriptors for a device. LANGID, Manufacturer, Product, Serial number + 4 user defined
/**
* @brief Parse tinyusb configuration and prepare the device configuration pointer list to configure tinyusb driver
*
* @attention All descriptors passed to this function must exist for the duration of USB device lifetime
*
* @param[in] config tinyusb stack specific configuration
* @retval ESP_ERR_INVALID_ARG Default configuration descriptor is provided only for CDC, MSC and NCM classes
* @retval ESP_ERR_NO_MEM Memory allocation error
* @retval ESP_OK Descriptors configured without error
*/
esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config);
/**
* @brief Set specific string descriptor
*
* @attention The descriptor passed to this function must exist for the duration of USB device lifetime
*
* @param[in] str UTF-8 string
* @param[in] str_idx String descriptor index
*/
void tinyusb_set_str_descriptor(const char *str, int str_idx);
/**
* @brief Free memory allocated during tinyusb_set_descriptors
*
*/
void tinyusb_free_descriptors(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "tusb.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Device descriptor generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own device descriptor via tinyusb_driver_install() call
*/
extern const tusb_desc_device_t descriptor_dev_default;
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief Qualifier Device descriptor generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own descriptor via tinyusb_driver_install() call
*/
extern const tusb_desc_device_qualifier_t descriptor_qualifier_default;
#endif // TUD_OPT_HIGH_SPEED
/**
* @brief Array of string descriptors generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own descriptor via tinyusb_driver_install() call
*/
extern const char *descriptor_str_default[];
/**
* @brief FullSpeed configuration descriptor generated from Kconfig
* This descriptor is used by default.
* The user can provide their own FullSpeed configuration descriptor via tinyusb_driver_install() call
*/
extern const uint8_t descriptor_fs_cfg_default[];
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief HighSpeed Configuration descriptor generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own HighSpeed configuration descriptor via tinyusb_driver_install() call
*/
extern const uint8_t descriptor_hs_cfg_default[];
#endif // TUD_OPT_HIGH_SPEED
uint8_t tusb_get_mac_string_id(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,2 @@
supplier: 'Organization: Espressif Systems (Shanghai) CO LTD'
originator: 'Organization: Espressif Systems (Shanghai) CO LTD'

View File

@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.22)
project(libusb_test
LANGUAGES C
)
add_executable(libusb_test libusb_test.c)
target_link_libraries(libusb_test -lusb-1.0)

View File

@@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>
#define TINYUSB_VENDOR 0x303A
#define TINYUSB_PRODUCT 0x4002
#define DESC_TYPE_DEVICE_QUALIFIER 0x06
#define DESC_TYOE_OTHER_SPEED_CONFIG 0x07
// Buffer for descriptor data
unsigned char buffer[512] = { 0 };
// USB Other Speed Configuration Descriptor
typedef struct __attribute__ ((packed))
{
uint8_t bLength ; ///< Size of descriptor
uint8_t bDescriptorType ; ///< Other_speed_Configuration Type
uint16_t wTotalLength ; ///< Total length of data returned
uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration
uint8_t bConfigurationValue ; ///< Value to use to select configuration
uint8_t iConfiguration ; ///< Index of string descriptor
uint8_t bmAttributes ; ///< Same as Configuration descriptor
uint8_t bMaxPower ; ///< Same as Configuration descriptor
} desc_other_speed_t;
// USB Device Qualifier Descriptor
typedef struct __attribute__ ((packed))
{
uint8_t bLength ; ///< Size of descriptor
uint8_t bDescriptorType ; ///< Device Qualifier Type
uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00)
uint8_t bDeviceClass ; ///< Class Code
uint8_t bDeviceSubClass ; ///< SubClass Code
uint8_t bDeviceProtocol ; ///< Protocol Code
uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed
uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations
uint8_t bReserved ; ///< Reserved for future use, must be zero
} desc_device_qualifier_t;
// printf helpers
static void _print_device_qulifier_desc(unsigned char *buffer, int length);
static void _print_other_speed_desc(unsigned char *buffer, int length);
//
// MAIN
//
int main()
{
libusb_context *context = NULL;
int rc = 0;
rc = libusb_init(&context);
assert(rc == 0);
libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(context,
TINYUSB_VENDOR,
TINYUSB_PRODUCT);
if (dev_handle != NULL) {
printf("TinyUSB Device has been found\n");
// Test Qualifier Descriprtor
// 1. Get Qualifier Descriptor
// 2. print descriptor data
rc = libusb_get_descriptor(dev_handle, DESC_TYPE_DEVICE_QUALIFIER, 0, buffer, 512);
_print_device_qulifier_desc(buffer, rc);
// Test Other Speed Descriptor
// 1. Get Other Speed Descriptor
// 2. print descriptor data
rc = libusb_get_descriptor(dev_handle, DESC_TYOE_OTHER_SPEED_CONFIG, 0, buffer, 512);
_print_other_speed_desc(buffer, rc);
libusb_close(dev_handle);
} else {
printf("TinyUSB Device has NOT been found\n");
}
libusb_exit(context);
}
// =============================================================================
static void _print_device_qulifier_desc(unsigned char *buffer, int length)
{
assert(buffer);
desc_device_qualifier_t *qualifier_desc = (desc_device_qualifier_t *) buffer;
printf("========= Device Qualifier ========== \n");
printf("\t bLength: %d \n", qualifier_desc->bLength);
printf("\t bDescriptorType: %d (%#x)\n", qualifier_desc->bDescriptorType, qualifier_desc->bDescriptorType);
printf("\t bcdUSB: %d (%#x) \n", qualifier_desc->bcdUSB, qualifier_desc->bcdUSB);
printf("\t bDeviceClass: %d (%#x) \n", qualifier_desc->bDeviceClass, qualifier_desc->bDeviceClass);
printf("\t bDeviceSubClass: %d \n", qualifier_desc->bDeviceSubClass);
printf("\t bDeviceProtocol: %d \n", qualifier_desc->bDeviceProtocol);
printf("\t bMaxPacketSize0: %d \n", qualifier_desc->bMaxPacketSize0);
printf("\t bNumConfigurations: %d \n", qualifier_desc->bNumConfigurations);
}
static void _print_other_speed_desc(unsigned char *buffer, int length)
{
assert(buffer);
desc_other_speed_t *other_speed = (desc_other_speed_t *) buffer;
printf("============ Other Speed ============ \n");
printf("\t bLength: %d \n", other_speed->bLength);
printf("\t bDescriptorType: %d (%#x) \n", other_speed->bDescriptorType, other_speed->bDescriptorType);
printf("\t wTotalLength: %d \n", other_speed->wTotalLength);
printf("\t bNumInterfaces: %d \n", other_speed->bNumInterfaces);
printf("\t bConfigurationValue: %d \n", other_speed->bConfigurationValue);
printf("\t iConfiguration: %d \n", other_speed->iConfiguration);
printf("\t bmAttributes: %d (%#x) \n", other_speed->bmAttributes, other_speed->bmAttributes);
printf("\t bMaxPower: %d (%#x) \n", other_speed->bMaxPower, other_speed->bMaxPower);
}

View File

@@ -0,0 +1,131 @@
# CI target runner setup
To allow a Docker container, running on a CI target runner, to access USB devices connected to the CI target runner, some modifications must be made.
In our case, it's an `RPI` target runner.
The main idea comes from this response on [stackoverflow](https://stackoverflow.com/a/66427245/19840830). The same approach is also recommended in the official Docker [documentation](https://docs.docker.com/reference/cli/docker/container/run/#device-cgroup-rule)
### Following changes shall be made on a CI target runner
- [`UDEV rules`](#udev-rules)
- [`Docker tty script`](#docker-tty-script)
- [`Logging`](#logging)
- [`Running a docker container`](#running-a-docker-container)
- [`GitHub CI target runner setup`](#github-ci-target-runner-setup)
- [`GitLab CI target runner setup`](#gitlab-ci-target-runner-setup)
## UDEV rules
- This UDEV rule will trigger a `docker_tty.sh` script every time a USB device is connected, disconnected, or enumerated by the host machine (CI target runner)
- Location: `/etc/udev/rules.d/99-docker-tty.rules`
- `99-docker-tty.rules` file content:
``` sh
ACTION=="add", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'added' '%E{DEVNAME}' '%M' '%m'"
ACTION=="remove", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'removed' '%E{DEVNAME}' '%M' '%m'"
```
## Docker tty script
- This `.sh` script, triggered by the UDEV rule above, will propagate USB devices to a running Docker container.
- Location: `/usr/local/bin/docker_tty.sh`
- `docker_tty.sh` file content:
``` sh
#!/usr/bin/env bash
# Log the USB event with parameters
echo "USB event: $1 $2 $3 $4" >> /tmp/docker_tty.log
# Find a running Docker container (using the first one found)
docker_name=$(docker ps --format "{{.Names}}" | head -n 1)
# Check if a container was found
if [ ! -z "$docker_name" ]; then
if [ "$1" == "added" ]; then
docker exec -u 0 "$docker_name" mknod $2 c $3 $4
docker exec -u 0 "$docker_name" chmod -R 777 $2
echo "Adding $2 to Docker container $docker_name" >> /tmp/docker_tty.log
else
docker exec -u 0 "$docker_name" rm $2
echo "Removing $2 from Docker container $docker_name" >> /tmp/docker_tty.log
fi
else
echo "No running Docker containers found." >> /tmp/docker_tty.log
fi
```
### Making the script executable
Don't forget to make the created script executable:
``` sh
root@~$ chmod +x /usr/local/bin/docker_tty.sh
```
## Logging
- The `docker_tty.sh` script logs information about the USB devices it processes.
- Location: `/tmp/docker_tty.log`
- Example of a log from the `docker_tty.log` file, showing a flow of the `pytest_usb_device.py` test
```
USB event: added /dev/ttyACM0 166 0
USB event: added /dev/ttyACM1 166 1
Adding /dev/ttyACM0 to Docker container d5e5c774174b435b8befea864f8fcb7f_python311bookworm_6a975d
Adding /dev/ttyACM1 to Docker container d5e5c774174b435b8befea864f8fcb7f_python311bookworm_6a975d
USB event: removed /dev/ttyACM0 166 0
USB event: removed /dev/ttyACM1 166 1
```
## Running a docker container
### Check Major and Minor numbers of connected devices
Check the Major and Minor numbers assigned by the Linux kernel to devices that you want the Docker container to access.
In our case, we want to access `/dev/ttyUSB0`, `/dev/ttyACM0` and `/dev/ttyACM1`
`/dev/ttyUSB0`: Major 188, Minor 0
``` sh
peter@BrnoRPIG007:~ $ ls -l /dev/ttyUSB0
crw-rw-rw- 1 root dialout 188, 0 Nov 12 11:08 /dev/ttyUSB0
```
`/dev/ttyACM0` and `/dev/ttyACM1`: Major 166, Minor 0 (1)
``` sh
peter@BrnoRPIG007:~ $ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 Nov 13 10:26 /dev/ttyACM0
peter@BrnoRPIG007:~ $ ls -l /dev/ttyACM1
crw-rw---- 1 root dialout 166, 1 Nov 13 10:26 /dev/ttyACM1
```
### Run a docker container
Run a Docker container with the following extra options:
``` sh
docker run --device-cgroup-rule='c 188:* rmw' --device-cgroup-rule='c 166:* rmw' --privileged ..
```
- `--device-cgroup-rule='c 188:* rmw'`: allow access to `ttyUSBx` (Major 188, all Minors)
- `--device-cgroup-rule='c 166:* rmw'`: allow access to `ttyACMx` (Major 166, all Minors)
## GitHub CI target runner setup
To apply these changes to a GitHub target runner a `.yml` file used to run a Docker container for pytest must be modified. The Docker container is then run with the following options:
``` yaml
container:
image: python:3.11-bookworm
options: --privileged --device-cgroup-rule="c 188:* rmw" --device-cgroup-rule="c 166:* rmw"
```
## GitLab CI target runner setup
To apply these changes to a GitLab runner the `config.toml` file located at `/etc/gitlab-runner/config.toml` on each GitLab target runner must be modified.
According to GitLab's [documentation](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersdocker-section) the `[runners.docker]` section of the `config.toml` file should include the `device_cgroup_rules` parameter:
``` toml
[runners.docker]
...
device_cgroup_rules = ["c 188:* rmw", "c 166:* rmw"]
```

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_cdc)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,152 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "unity.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "vfs_tinyusb.h"
#define VFS_PATH "/dev/usb-cdc1"
static const tusb_desc_device_t cdc_device_descriptor = {
.bLength = sizeof(cdc_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_ESPRESSIF_VID,
.idProduct = 0x4002,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN;
static const uint8_t cdc_desc_configuration[] = {
TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)),
TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, (TUD_OPT_HIGH_SPEED ? 512 : 64)),
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event)
{
}
/**
* @brief TinyUSB CDC testcase
*
* This is not a 'standard' testcase, as it never exits. The testcase runs in a loop where it echoes back all the data received.
*
* - Init TinyUSB with standard CDC device and configuration descriptors
* - Init 2 CDC-ACM interfaces
* - Map CDC1 to Virtual File System
* - In a loop: Read data from CDC0 and CDC1. Echo received data back
*
* Note: CDC0 appends 'novfs' to echoed data, so the host (test runner) can easily determine which port is which.
*/
TEST_CASE("tinyusb_cdc", "[esp_tinyusb][cdc]")
{
// Install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &cdc_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = cdc_desc_configuration,
.hs_configuration_descriptor = cdc_desc_configuration,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = cdc_desc_configuration,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = &tinyusb_cdc_rx_callback,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL
};
// Init CDC 0
TEST_ASSERT_FALSE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0));
TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_init(&acm_cfg));
TEST_ASSERT_TRUE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0));
// Init CDC 1
acm_cfg.cdc_port = TINYUSB_CDC_ACM_1;
acm_cfg.callback_rx = NULL;
TEST_ASSERT_FALSE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_1));
TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_init(&acm_cfg));
TEST_ASSERT_TRUE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_1));
// Install VFS to CDC 1
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_tusb_cdc_register(TINYUSB_CDC_ACM_1, VFS_PATH));
esp_vfs_tusb_cdc_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF);
esp_vfs_tusb_cdc_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
FILE *cdc = fopen(VFS_PATH, "r+");
TEST_ASSERT_NOT_NULL(cdc);
uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1];
while (true) {
size_t b = fread(buf, 1, sizeof(buf), cdc);
if (b > 0) {
printf("Intf VFS, RX %d bytes\n", b);
//ESP_LOG_BUFFER_HEXDUMP("test", buf, b, ESP_LOG_INFO);
fwrite(buf, 1, b, cdc);
}
vTaskDelay(1);
size_t rx_size = 0;
int itf = 0;
ESP_ERROR_CHECK(tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size));
if (rx_size > 0) {
printf("Intf %d, RX %d bytes\n", itf, rx_size);
// Add 'novfs' to reply so the host can identify the port
strcpy((char *)&buf[rx_size - 2], "novfs\r\n");
tinyusb_cdcacm_write_queue(itf, buf, rx_size + sizeof("novfs") - 1);
tinyusb_cdcacm_write_flush(itf, 0);
}
}
}
#endif

View File

@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
from time import sleep
from serial import Serial, SerialException
from serial.tools.list_ports import comports
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_device_cdc(dut) -> None:
'''
Running the test locally:
1. Build the testa app for your DUT (ESP32-S2 or S3)
2. Connect you DUT to your test runner (local machine) with USB port and flashing port
3. Run `pytest --target esp32s3`
Test procedure:
1. Run the test on the DUT
2. Expect 2 Virtual COM Ports in the system
3. Open both comports and send some data. Expect echoed data
'''
dut.expect_exact('Press ENTER to see the list of tests.')
dut.write('[cdc]')
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
sleep(2) # Some time for the OS to enumerate our USB device
# Find devices with Espressif TinyUSB VID/PID
s = []
ports = comports()
for port, _, hwid in ports:
if '303A:4002' in hwid:
s.append(port)
if len(s) != 2:
raise Exception('TinyUSB COM port not found')
try:
with Serial(s[0]) as cdc0:
with Serial(s[1]) as cdc1:
# Write dummy string and check for echo
cdc0.write('text\r\n'.encode())
res = cdc0.readline()
assert b'text' in res
if b'novfs' in res:
novfs_cdc = cdc0
vfs_cdc = cdc1
cdc1.write('text\r\n'.encode())
res = cdc1.readline()
assert b'text' in res
if b'novfs' in res:
novfs_cdc = cdc1
vfs_cdc = cdc0
# Write more than MPS, check that the transfer is not divided
novfs_cdc.write(bytes(100))
dut.expect_exact("Intf 0, RX 100 bytes")
# Write more than RX buffer, check correct reception
novfs_cdc.write(bytes(600))
transfer_len1 = int(dut.expect(r'Intf 0, RX (\d+) bytes')[1].decode())
transfer_len2 = int(dut.expect(r'Intf 0, RX (\d+) bytes')[1].decode())
assert transfer_len1 + transfer_len2 == 600
# The VFS is setup for CRLF RX and LF TX
vfs_cdc.write('text\r\n'.encode())
res = vfs_cdc.readline()
assert b'text\n' in res
return
except SerialException as e:
print(f"SerialException occurred: {e}")
raise
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise

View File

@@ -0,0 +1,18 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_MSC_ENABLED=n
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=2
CONFIG_TINYUSB_HID_COUNT=0
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_configuration_descriptor)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,277 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_err.h"
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "config_test";
#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000
#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000
// ========================= TinyUSB descriptors ===============================
// Here we need to create dual CDC device, to match the CONFIG_TINYUSB_CDC_COUNT from sdkconfig.defaults
static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN;
static const uint8_t test_fs_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_CDC * 2, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 64),
#if CFG_TUD_CDC > 1
TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 64),
#endif
};
#if (TUD_OPT_HIGH_SPEED)
static const uint8_t test_hs_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 512),
TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 512),
};
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
// ========================== Private logic ====================================
SemaphoreHandle_t wait_mount = NULL;
/**
* @brief Creates test additional resources.
*
* Is called before start test to create/init internal resources.
*/
static bool __test_init(void)
{
wait_mount = xSemaphoreCreateBinary();
return (wait_mount != NULL);
}
/**
* @brief Indicates device connection event.
*
* Is called in tud_mount callback.
*/
static void __test_conn(void)
{
if (wait_mount) {
xSemaphoreGive(wait_mount);
}
}
/**
* @brief Awaits device connection event.
*
* Is used for waiting the event in test logic.
* Timeout could be configured via TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS define.
*/
static esp_err_t __test_wait_conn(void)
{
if (!wait_mount) {
return ESP_ERR_INVALID_STATE;
}
return ( xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS))
? ESP_OK
: ESP_ERR_TIMEOUT );
}
/**
* @brief Releases test resources.
*
* Is called in the end of the test to release internal resources.
*/
static void __test_release(void)
{
if (wait_mount) {
vSemaphoreDelete(wait_mount);
}
}
/**
* @brief One round of setup configuration for TinyUSB.
*
* Steps:
* 1. Init internal resources
* 2. Installs TinyUSB with provided configuration
* 3. Waits for the connection event from the Host
* 4. Gives some extra time for Host to configure the device (configures via TEARDOWN_DEVICE_DETACH_DELAY_MS)
* 5. Uninstall TinyUSB
* 6. Release internal resources
*
* Is called in the end of the test to release internal resources.
*/
static void __test_tinyusb_set_config(const tinyusb_config_t *tusb_cfg)
{
TEST_ASSERT_EQUAL(true, __test_init());
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(tusb_cfg));
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
__test_release();
}
// ========================== Callbacks ========================================
/**
* @brief TinyUSB callback for device mount.
*
* @note
* For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver.
* For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor.
*/
void tud_mount_cb(void)
{
ESP_LOGD(TAG, "%s", __FUNCTION__);
__test_conn();
}
// ============================= Tests =========================================
/**
* @brief TinyUSB Configuration test case.
*
* Verifies:
* Configuration without specifying any parameters.
* Configuration & descriptors are provided by esp_tinyusb wrapper.
*/
TEST_CASE("descriptors_config_all_default", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = NULL,
.configuration_descriptor = NULL,
#if (CONFIG_TINYUSB_RHPORT_HS)
.hs_configuration_descriptor = NULL,
#endif // CONFIG_TINYUSB_RHPORT_HS
};
// Install TinyUSB driver
__test_tinyusb_set_config(&tusb_cfg);
}
/**
* @brief TinyUSB Configuration test case.
*
* Verifies:
* Configuration with specifying only device descriptor.
* For High-speed qualifier descriptor as well.
*/
TEST_CASE("descriptors_config_device", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.configuration_descriptor = NULL,
#if (CONFIG_TINYUSB_RHPORT_HS)
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = &device_qualifier,
#endif // CONFIG_TINYUSB_RHPORT_HS
};
__test_tinyusb_set_config(&tusb_cfg);
}
/**
* @brief TinyUSB Configuration test case.
*
* Verifies:
* Configuration with specifying device & configuration descriptors.
*
* For High-speed:
* - HS configuration descriptor is not provided by user (legacy compatibility) and default HS config descriptor is using when possible.
* - Qualifier descriptor.
*/
TEST_CASE("descriptors_config_device_and_config", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.configuration_descriptor = test_fs_configuration_descriptor,
#if (CONFIG_TINYUSB_RHPORT_HS)
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = &device_qualifier,
#endif // CONFIG_TINYUSB_RHPORT_HS
};
__test_tinyusb_set_config(&tusb_cfg);
}
#if (CONFIG_IDF_TARGET_ESP32P4)
/**
* @brief TinyUSB High-speed Configuration test case.
*
* Verifies:
* Configuration with specifying device & HS configuration descriptor only.
* FS configuration descriptor is not provided by user (legacy compatibility) and default configuration descriptor for FS is using when possible.
*/
TEST_CASE("descriptors_config_device_and_hs_config_only", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.configuration_descriptor = NULL,
.hs_configuration_descriptor = test_hs_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
};
__test_tinyusb_set_config(&tusb_cfg);
}
/**
* @brief TinyUSB High-speed Configuration test case.
*
* Verifies:
* Configuration with specifying all descriptors by user.
*/
TEST_CASE("descriptors_config_all_configured", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.fs_configuration_descriptor = test_fs_configuration_descriptor,
.hs_configuration_descriptor = test_hs_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
};
__test_tinyusb_set_config(&tusb_cfg);
}
#endif // CONFIG_IDF_TARGET_ESP32P4
#endif // SOC_USB_OTG_SUPPORTED

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_device_configuration(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='config')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=2
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_dconn_detection)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_rom_gpio.h"
#include "soc/gpio_sig_map.h"
#include "unity.h"
#include "tinyusb.h"
#include "tusb_tasks.h"
#define DEVICE_DETACH_TEST_ROUNDS 10
#define DEVICE_DETACH_ROUND_DELAY_MS 1000
#if (CONFIG_IDF_TARGET_ESP32P4)
#define USB_SRP_BVALID_IN_IDX USB_SRP_BVALID_PAD_IN_IDX
#endif // CONFIG_IDF_TARGET_ESP32P4
/* TinyUSB descriptors
********************************************************************* */
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static unsigned int dev_mounted = 0;
static unsigned int dev_umounted = 0;
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
/**
* @attention Tests relying on this callback only pass on Linux USB Host!
*
* This callback is issued after SetConfiguration command from USB Host.
* However, Windows issues SetConfiguration only after a USB driver was assigned to the device.
* So in case you are implementing a Vendor Specific class, or your device has 0 interfaces, this callback is not issued on Windows host.
*/
printf("%s\n", __FUNCTION__);
dev_mounted++;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
printf("%s\n", __FUNCTION__);
dev_umounted++;
}
/**
* @brief TinyUSB Disconnect Detection test case
*
* This is specific artificial test for verifying the disconnection detection event.
* Normally, this event comes as a result of detaching USB device from the port and disappearing the VBUS voltage.
* In this test case, we use GPIO matrix and connect the signal to the ZERO or ONE constant inputs.
* Connection to constant ONE input emulates the attachment to the USB Host port (appearing VBUS).
* Connection to constant ZERO input emulates the detachment from the USB Host port (removing VBUS).
*
* Test logic:
* - Install TinyUSB Device stack without any class
* - In cycle:
* - Emulate the detachment, get the tud_umount_cb(), increase the dev_umounted value
* - Emulate the attachment, get the tud_mount_cb(), increase the dev_mounted value
* - Verify that dev_umounted == dev_mounted
* - Verify that dev_mounted == DEVICE_DETACH_TEST_ROUNDS, where DEVICE_DETACH_TEST_ROUNDS - amount of rounds
* - Uninstall TinyUSB Device stack
*
*/
TEST_CASE("dconn_detection", "[esp_tinyusb][dconn]")
{
unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS;
// Install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
dev_mounted = 0;
dev_umounted = 0;
while (rounds--) {
// LOW to emulate disconnect USB device
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false);
vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS));
// HIGH to emulate connect USB device
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_SRP_BVALID_IN_IDX, false);
vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS));
}
// Verify
TEST_ASSERT_EQUAL(dev_umounted, dev_mounted);
TEST_ASSERT_EQUAL(DEVICE_DETACH_TEST_ROUNDS, dev_mounted);
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
}
#endif // SOC_USB_OTG_SUPPORTED

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
#@pytest.mark.usb_device Disable in CI: unavailable teardown for P4
def test_usb_device_dconn_detection(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='dconn')

View File

@@ -0,0 +1,18 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_MSC_ENABLED=n
CONFIG_TINYUSB_CDC_ENABLED=n
CONFIG_TINYUSB_CDC_COUNT=0
CONFIG_TINYUSB_HID_COUNT=0
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_default_task_without_init)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "default_task";
SemaphoreHandle_t wait_mount = NULL;
#define TEARDOWN_DEVICE_DELAY_MS 1000
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief TinyUSB Task specific testcase
*
* Scenario:
* 1. Install TinyUSB driver
* 2. Wait tud_mount_cb() until TUSB_DEVICE_DELAY_MS
* 3. Wait TUSB_DEVICE_DELAY_MS
* 4. Teardown TinyUSB
* 5. Release resources
*/
TEST_CASE("tinyusb_default_task_with_init", "[esp_tinyusb][tusb_task]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_default_task_with_init(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='tusb_task')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB
CONFIG_TINYUSB_NO_DEFAULT_TASK=n
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=n
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_default_task_with_init)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "default_task_with_init";
SemaphoreHandle_t wait_mount = NULL;
#define TEARDOWN_DEVICE_DELAY_MS 1000
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief TinyUSB Task specific testcase
*
* Scenario:
* 1. Install TinyUSB driver
* 2. Wait tud_mount_cb() until TUSB_DEVICE_DELAY_MS
* 3. Wait TUSB_DEVICE_DELAY_MS
* 4. Teardown TinyUSB
* 5. Release resources
*/
TEST_CASE("tinyusb_default_task_with_init", "[esp_tinyusb][tusb_task]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_default_task_with_init(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='tusb_task')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB
CONFIG_TINYUSB_NO_DEFAULT_TASK=n
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_external_task_without_init)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,144 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "external_task";
static SemaphoreHandle_t wait_mount = NULL;
static TaskHandle_t s_test_tusb_tskh;
#define TUSB_DEVICE_DELAY_MS 1000
#define TUSB_EXTERNAL_TASK_SIZE 4096
#define TUSB_EXTERNAL_TASK_PRIO 5
#define TUSB_EXTERNAL_TASK_AFFINITY 0x7FFFFFFF /* FREERTOS_NO_AFFINITY */
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief This top level thread processes all usb events and invokes callbacks
*/
static void test_tusb_external_task(void *arg)
{
ESP_LOGD(TAG, "External TinyUSB task started");
while (1) { // RTOS forever loop
tud_task();
}
}
/**
* @brief TinyUSB Task specific testcase
*
* Scenario:
* 1. Install TinyUSB driver
* 2. Create external TinyUSB task for tud_task()
* 3. Wait tud_mount_cb() until TUSB_DEVICE_DELAY_MS
* 4. Wait TUSB_DEVICE_DELAY_MS
* 5. Teardown TinyUSB
* 6. Release resources
*
* @note If run the task before installing the tinyusb driver, the external task will lead to cpu starvation.
*/
TEST_CASE("tinyusb_external_task", "[esp_tinyusb][tusb_task]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Create an external task for tinyusb device stack
xTaskCreate(test_tusb_external_task,
"TinyUSB",
TUSB_EXTERNAL_TASK_SIZE,
NULL,
TUSB_EXTERNAL_TASK_PRIO,
&s_test_tusb_tskh);
TEST_ASSERT_NOT_NULL(s_test_tusb_tskh);
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown
vTaskDelay(pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vTaskDelete(s_test_tusb_tskh);
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_external_task_internal_init(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='tusb_task')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB
CONFIG_TINYUSB_NO_DEFAULT_TASK=y
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=n
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_teardown_device)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
static const char *TAG = "teardown";
SemaphoreHandle_t wait_mount = NULL;
#define TEARDOWN_DEVICE_INIT_DELAY_MS 1000
#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000
#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000
#define TEARDOWN_AMOUNT 10
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief TinyUSB Teardown specific testcase
*
* Scenario:
* 1. Install TinyUSB device without any class
* 2. Wait SetConfiguration() (tud_mount_cb)
* 3. If attempts == 0 goto step 8
* 4. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
* 5. Uninstall TinyUSB device
* 6. Wait TEARDOWN_DEVICE_INIT_DELAY_MS
* 7. Decrease attempts by 1, goto step 3
* 8. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
* 9. Uninstall TinyUSB device
*/
TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown routine
int attempts = TEARDOWN_AMOUNT;
while (attempts--) {
// Keep device attached
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_INIT_DELAY_MS));
// Reconnect
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
ESP_LOGD(TAG, "mounted");
}
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
import subprocess
from time import sleep, time
class DeviceNotFoundError(Exception):
"""Custom exception for device not found within the timeout period."""
pass
def tusb_dev_in_list(vid, pid):
try:
output = subprocess.check_output(["lsusb"], text=True)
search_string = f"{vid}:{pid}"
return search_string in output
except Exception as e:
print(f"Error while executing lsusb: {e}")
raise
def wait_tusb_dev_appeared(vid, pid, timeout):
start_time = time()
while True:
if tusb_dev_in_list(vid, pid):
return True
if time() - start_time > timeout:
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} not found within {timeout} seconds.")
sleep(0.5)
def wait_tusb_dev_removed(vid, pid, timeout):
start_time = time()
while True:
if not tusb_dev_in_list(vid, pid):
return True
if time() - start_time > timeout:
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} wasn't removed within {timeout} seconds.")
sleep(0.5)
def tusb_device_teardown(iterations, timeout):
TUSB_VID = "303a" # Espressif TinyUSB VID
TUSB_PID = "4002" # Espressif TinyUSB VID
for i in range(iterations):
# Wait until the device is present
print(f"Waiting for device ...")
wait_tusb_dev_appeared(TUSB_VID, TUSB_PID, timeout)
print("Device detected.")
# Wait until the device is removed
print("Waiting for the device to be removed...")
wait_tusb_dev_removed(TUSB_VID, TUSB_PID, timeout)
print("Device removed.")
print("Monitoring completed.")
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_teardown_device(dut: IdfDut) -> None:
dut.expect_exact('Press ENTER to see the list of tests.')
dut.write('[teardown]')
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
sleep(2) # Some time for the OS to enumerate our USB device
try:
tusb_device_teardown(10, 10) # Teardown tusb device: amount, timeout
except DeviceNotFoundError as e:
print(f"Error: {e}")
raise
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=1
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# 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.
set(COMPONENTS main)
project(test_app_vendor_specific)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
// We don't check memory leaks here because we cannot uninstall TinyUSB yet
unity_run_menu();
}

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "vendor_test";
char buffer_in[64];
#if (TUSB_VERSION_MINOR >= 17)
void tud_vendor_rx_cb(uint8_t itf, uint8_t const *buffer, uint16_t bufsize)
#else
void tud_vendor_rx_cb(uint8_t itf)
#endif // TUSB_VERSION_MINOR
{
ESP_LOGI(TAG, "tud_vendor_rx_cb(itf=%d)", itf);
int available = tud_vendor_n_available(itf);
int read = tud_vendor_n_read(itf, buffer_in, available);
ESP_LOGI(TAG, "actual read: %d. buffer message: %s", read, buffer_in);
}
// Invoked when a control transfer occurred on an interface of this class
// Driver response accordingly to the request and the transfer stage (setup/data/ack)
// return false to stall control endpoint (e.g unsupported request)
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request)
{
// nothing to with DATA & ACK stage
if (stage != CONTROL_STAGE_SETUP) {
return true;
}
// stall unknown request
return false;
}
/**
* @brief TinyUSB Vendor specific testcase
*/
TEST_CASE("tinyusb_vendor", "[esp_tinyusb][vendor]")
{
// Install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = NULL,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
}
#endif

View File

@@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
import usb.core
import usb.util
from time import sleep
def find_interface_by_index(device, interface_index):
'''
Function to find the interface by index
'''
for cfg in device:
for intf in cfg:
if intf.bInterfaceNumber == interface_index:
return intf
return None
def send_data_to_intf(VID, PID, interface_index):
'''
Find a device, its interface and dual BULK endpoints
Send some data to it
'''
# Find the USB device by VID and PID
dev = usb.core.find(idVendor=VID, idProduct=PID)
if dev is None:
raise ValueError("Device not found")
# Find the interface by index
intf = find_interface_by_index(dev, interface_index)
if intf is None:
raise ValueError(f"Interface with index {interface_index} not found")
if intf:
def ep_read(len):
try:
return ep_in.read(len, 100)
except:
return None
def ep_write(buf):
try:
ep_out.write(buf, 100)
except:
pass
maximum_packet_size = 64
ep_in = usb.util.find_descriptor(intf, custom_match = \
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
ep_out = usb.util.find_descriptor(intf, custom_match = \
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
#print(ep_in)
#print(ep_out)
buf = "IF{}\n".format(interface_index).encode('utf-8')
ep_write(bytes(buf))
ep_read(maximum_packet_size)
else:
print("NOT found")
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
#@pytest.mark.usb_device Disable in CI, for now, not possible to run this test in Docker container
def test_usb_device_vendor(dut: IdfDut) -> None:
'''
Running the test locally:
1. Build the test app for your DUT
2. Connect you DUT to your test runner (local machine) with USB port and flashing port
3. Run `pytest --target esp32s3`
Important note: On Windows you must manually assign a driver the device, otherwise it will never be configured.
On Linux this is automatic
Test procedure:
1. Run the test on the DUT
2. Expect 2 Vendor specific interfaces in the system
3. Send some data to it, check log output
'''
dut.run_all_single_board_cases(group='vendor')
sleep(2) # Wait until the device is enumerated
VID = 0x303A # Replace with your device's Vendor ID
PID = 0x4040 # Replace with your device's Product ID
send_data_to_intf(VID, PID, 0)
dut.expect_exact('vendor_test: actual read: 4. buffer message: IF0')
send_data_to_intf(VID, PID, 1)
dut.expect_exact('vendor_test: actual read: 4. buffer message: IF1')

View File

@@ -0,0 +1,15 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_VENDOR_COUNT=2
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_private/usb_phy.h"
#include "tinyusb.h"
#include "descriptors_control.h"
#include "tusb.h"
#include "tusb_tasks.h"
const static char *TAG = "TinyUSB";
static usb_phy_handle_t phy_hdl;
// For the tinyusb component without tusb_teardown() implementation
#ifndef tusb_teardown
# define tusb_teardown() (true)
#endif // tusb_teardown
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
{
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL");
// Configure USB PHY
usb_phy_config_t phy_conf = {
.controller = USB_PHY_CTRL_OTG,
.otg_mode = USB_OTG_MODE_DEVICE,
#if (USB_PHY_SUPPORTS_P4_OTG11)
.otg_speed = (TUD_OPT_HIGH_SPEED) ? USB_PHY_SPEED_HIGH : USB_PHY_SPEED_FULL,
#else
#if (CONFIG_IDF_TARGET_ESP32P4 && CONFIG_TINYUSB_RHPORT_FS)
#error "USB PHY for OTG1.1 is not supported, please update your esp-idf."
#endif // IDF_TARGET_ESP32P4 && CONFIG_TINYUSB_RHPORT_FS
#endif // USB_PHY_SUPPORTS_P4_OTG11
};
/*
Following ext. PHY IO configuration is here to provide compatibility with IDFv5.x releases,
where ext. PHY IOs were mapped to predefined GPIOs.
In reality, ESP32-S2 and ESP32-S3 can map ext. PHY IOs to any GPIOs.
This option is implemented in esp_tinyusb v2.0.0 and later.
*/
usb_phy_ext_io_conf_t ext_io_conf;
// Use memset to be compatible with IDF < 5.4.1 where suspend_n_io_num and fs_edge_sel_io_num were added
memset(&ext_io_conf, -1, sizeof(usb_phy_ext_io_conf_t));
#if CONFIG_IDF_TARGET_ESP32S2
ext_io_conf.vp_io_num = 33;
ext_io_conf.vm_io_num = 34;
ext_io_conf.rcv_io_num = 35;
ext_io_conf.oen_io_num = 36;
ext_io_conf.vpo_io_num = 37;
ext_io_conf.vmo_io_num = 38;
#elif CONFIG_IDF_TARGET_ESP32S3
ext_io_conf.vp_io_num = 42;
ext_io_conf.vm_io_num = 41;
ext_io_conf.rcv_io_num = 21;
ext_io_conf.oen_io_num = 40;
ext_io_conf.vpo_io_num = 39;
ext_io_conf.vmo_io_num = 38;
#endif // IDF_TARGET_ESP32S3
if (config->external_phy) {
phy_conf.target = USB_PHY_TARGET_EXT;
phy_conf.ext_io_conf = &ext_io_conf;
/*
There is a bug in esp-idf that does not allow device speed selection
when External PHY is used.
Remove this when proper fix is implemented in IDF-11144
*/
phy_conf.otg_speed = USB_PHY_SPEED_UNDEFINED;
} else {
phy_conf.target = USB_PHY_TARGET_INT;
}
// OTG IOs config
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->vbus_monitor_io);
if (config->self_powered) {
phy_conf.otg_io_conf = &otg_io_conf;
}
ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed");
// Descriptors config
ESP_RETURN_ON_ERROR(tinyusb_set_descriptors(config), TAG, "Descriptors config failed");
// Init
#if !CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
ESP_RETURN_ON_FALSE(tusb_init(), ESP_FAIL, TAG, "Init TinyUSB stack failed");
#endif
#if !CONFIG_TINYUSB_NO_DEFAULT_TASK
ESP_RETURN_ON_ERROR(tusb_run_task(), TAG, "Run TinyUSB task failed");
#endif
ESP_LOGI(TAG, "TinyUSB Driver installed");
return ESP_OK;
}
esp_err_t tinyusb_driver_uninstall(void)
{
#if !CONFIG_TINYUSB_NO_DEFAULT_TASK
ESP_RETURN_ON_ERROR(tusb_stop_task(), TAG, "Unable to stop TinyUSB task");
#endif // !CONFIG_TINYUSB_NO_DEFAULT_TASK
ESP_RETURN_ON_FALSE(tusb_teardown(), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB");
tinyusb_free_descriptors();
ESP_RETURN_ON_ERROR(usb_del_phy(phy_hdl), TAG, "Unable to delete PHY");
return ESP_OK;
}

Some files were not shown because too many files have changed in this diff Show More