Skip to content

Commit e9bd23b

Browse files
committed
feat(usb_device_uvc): support deinitialization
closes #575
1 parent 15c264b commit e9bd23b

File tree

9 files changed

+140
-23
lines changed

9 files changed

+140
-23
lines changed

components/usb/usb_device_uvc/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# ChangeLog
22

3+
## v1.2.0 2025-8-5
4+
5+
* Support deinitialization.
6+
* Fix image format setting during initialization stage.
7+
38
## v1.1.3 2025-7-21
49

510
* Fix incorrect `_stridx` in UVC descriptors when using dual cameras.

components/usb/usb_device_uvc/idf_component.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "1.1.3"
1+
version: "1.2.0"
22
targets:
33
- esp32s2
44
- esp32s3
@@ -13,7 +13,7 @@ issues: https://github.com/espressif/esp-iot-solution/issues
1313
dependencies:
1414
idf: ">=5.0"
1515
espressif/tinyusb:
16-
version: ">=0.17.0~2"
16+
version: ">=0.18.0~1"
1717
cmake_utilities: "*"
1818
examples:
1919
- path: ../../../examples/usb/device/usb_webcam

components/usb/usb_device_uvc/include/usb_device_uvc.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -89,11 +89,12 @@ esp_err_t uvc_device_init(void);
8989

9090
/**
9191
* @brief Deinitialize the UVC device
92-
* @note This function is not implemented yet because tinyusb does not support deinitialization
92+
*
9393
* @return ESP_OK on success
94-
* ESP_FAIL if the UVC device could not be deinitialized
94+
* ESP_ERR_INVALID_STATE if the UVC device is not initialized, or event group is NULL
95+
*
9596
*/
96-
//esp_err_t uvc_device_deinit(void);
97+
esp_err_t uvc_device_deinit(void);
9798

9899
#ifdef __cplusplus
99100
}

components/usb/usb_device_uvc/test_apps/main/usb_device_uvc_test.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -22,6 +22,7 @@ static const char *TAG = "usb_device_uvc_test";
2222
#define UVC_MAX_FRAMESIZE_SIZE (60*1024)
2323
#define WIDTH CONFIG_UVC_CAM1_FRAMESIZE_WIDTH
2424
#define HEIGHT CONFIG_UVC_CAM1_FRAMESIZE_HEIGT
25+
#define TEST_COUNT (15)
2526

2627
extern const unsigned char jpg_start[] asm("_binary_esp_1280_720_jpg_start");
2728
extern const unsigned char jpg_end[] asm("_binary_esp_1280_720_jpg_end");
@@ -85,9 +86,12 @@ TEST_CASE("usb_device_uvc_test", "[usb_device_uvc]")
8586
};
8687
uvc_device_config(0, &config);
8788
uvc_device_init();
88-
while (1) {
89+
for (int i = 0; i < TEST_COUNT; i++) {
8990
vTaskDelay(1000 / portTICK_PERIOD_MS);
91+
ESP_LOGI(TAG, "UVC Device Test: %d", i);
9092
}
93+
uvc_device_deinit();
94+
free(uvc_buffer);
9195
}
9296

9397
static size_t before_free_8bit;

components/usb/usb_device_uvc/usb_device_uvc.c

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
#include <string.h>
77
#include <inttypes.h>
88
#include "freertos/FreeRTOS.h"
99
#include "freertos/task.h"
10+
#include "freertos/event_groups.h"
1011
#include "esp_timer.h"
1112
#include "esp_check.h"
1213
#if CONFIG_TINYUSB_RHPORT_HS
@@ -25,13 +26,24 @@ static const char *TAG = "usbd_uvc";
2526
#define UVC_CAM_NUM 1
2627
#endif
2728

29+
#define TUSB_EVENT_EXIT (1<<0)
30+
#define TUSB_EVENT_EXIT_DONE (1<<1)
31+
#define UVC1_EVENT_EXIT (1<<2)
32+
#define UVC1_EVENT_EXIT_DONE (1<<3)
33+
#if CONFIG_UVC_SUPPORT_TWO_CAM
34+
#define UVC2_EVENT_EXIT (1<<4)
35+
#define UVC2_EVENT_EXIT_DONE (1<<5)
36+
#endif
37+
2838
typedef struct {
2939
usb_phy_handle_t phy_hdl;
3040
bool uvc_init[UVC_CAM_NUM];
3141
uvc_format_t format[UVC_CAM_NUM];
3242
uvc_device_config_t user_config[UVC_CAM_NUM];
3343
TaskHandle_t uvc_task_hdl[UVC_CAM_NUM];
44+
TaskHandle_t tusb_task_hdl;
3445
uint32_t interval_ms[UVC_CAM_NUM];
46+
EventGroupHandle_t event_group;
3547
} uvc_device_t;
3648

3749
static uvc_device_t s_uvc_device;
@@ -58,8 +70,15 @@ static inline uint32_t get_time_millis(void)
5870
static void tusb_device_task(void *arg)
5971
{
6072
while (1) {
73+
EventBits_t uxBits = xEventGroupGetBits(s_uvc_device.event_group);
74+
if (uxBits & TUSB_EVENT_EXIT) {
75+
ESP_LOGI(TAG, "TUSB task exit");
76+
break;
77+
}
6178
tud_task();
6279
}
80+
xEventGroupSetBits(s_uvc_device.event_group, TUSB_EVENT_EXIT_DONE);
81+
vTaskDelete(NULL);
6382
}
6483

6584
void tud_mount_cb(void)
@@ -110,6 +129,12 @@ static void video_task(void *arg)
110129
uvc_fb_t *pic = NULL;
111130

112131
while (1) {
132+
EventBits_t uxBits = xEventGroupGetBits(s_uvc_device.event_group);
133+
if (uxBits & UVC1_EVENT_EXIT) {
134+
ESP_LOGI(TAG, "UVC task exit");
135+
break;
136+
}
137+
113138
if (!tud_video_n_streaming(0, 0)) {
114139
already_start = 0;
115140
frame_num = 0;
@@ -160,6 +185,9 @@ static void video_task(void *arg)
160185
tud_video_n_frame_xfer(0, 0, (void *)uvc_buffer, frame_len);
161186
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
162187
}
188+
189+
xEventGroupSetBits(s_uvc_device.event_group, UVC1_EVENT_EXIT_DONE);
190+
vTaskDelete(NULL);
163191
}
164192

165193
#if CONFIG_UVC_SUPPORT_TWO_CAM
@@ -175,6 +203,12 @@ static void video_task2(void *arg)
175203
uvc_fb_t *pic = NULL;
176204

177205
while (1) {
206+
EventBits_t uxBits = xEventGroupGetBits(s_uvc_device.event_group);
207+
if (uxBits & UVC2_EVENT_EXIT) {
208+
ESP_LOGI(TAG, "UVC2 task exit");
209+
break;
210+
}
211+
178212
if (!tud_video_n_streaming(1, 0)) {
179213
already_start = 0;
180214
frame_num = 0;
@@ -225,6 +259,9 @@ static void video_task2(void *arg)
225259
tud_video_n_frame_xfer(1, 0, (void *)uvc_buffer, frame_len);
226260
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
227261
}
262+
263+
xEventGroupSetBits(s_uvc_device.event_group, UVC2_EVENT_EXIT_DONE);
264+
vTaskDelete(NULL);
228265
}
229266
#endif
230267

@@ -283,26 +320,38 @@ esp_err_t uvc_device_init(void)
283320
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[1], ESP_ERR_INVALID_STATE, TAG, "uvc device 1 not init, if not use, please disable CONFIG_UVC_SUPPORT_TWO_CAM");
284321
#endif
285322

286-
#ifdef CONFIG_FORMAT_MJPEG_CAM1
323+
#if CONFIG_FORMAT_MJPEG_CAM1
287324
s_uvc_device.format[0] = UVC_FORMAT_JPEG;
325+
#elif CONFIG_FORMAT_H264_CAM1
326+
s_uvc_device.format[0] = UVC_FORMAT_H264;
288327
#endif
289328

290329
#if CONFIG_UVC_SUPPORT_TWO_CAM
291-
#ifdef CONFIG_FORMAT_MJPEG_CAM2
330+
#if CONFIG_FORMAT_MJPEG_CAM2
292331
s_uvc_device.format[1] = UVC_FORMAT_JPEG;
332+
#elif CONFIG_FORMAT_H264_CAM2
333+
s_uvc_device.format[1] = UVC_FORMAT_H264;
293334
#endif
294335
#endif
295336

337+
s_uvc_device.event_group = xEventGroupCreate();
338+
if (s_uvc_device.event_group == NULL) {
339+
ESP_LOGE(TAG, "Failed to create event group");
340+
return ESP_FAIL;
341+
}
342+
296343
// init device stack on configured roothub port
297344
usb_phy_init();
298345
bool usb_init = tusb_init();
299346
if (!usb_init) {
300347
ESP_LOGE(TAG, "USB Device Stack Init Fail");
348+
vEventGroupDelete(s_uvc_device.event_group);
349+
s_uvc_device.event_group = NULL;
301350
return ESP_FAIL;
302351
}
303352

304353
BaseType_t core_id = (CONFIG_UVC_TINYUSB_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_TINYUSB_TASK_CORE;
305-
xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", 4096, NULL, CONFIG_UVC_TINYUSB_TASK_PRIORITY, NULL, core_id);
354+
xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", 4096, NULL, CONFIG_UVC_TINYUSB_TASK_PRIORITY, &s_uvc_device.tusb_task_hdl, core_id);
306355
#if (CFG_TUD_VIDEO)
307356
core_id = (CONFIG_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM1_TASK_CORE;
308357
xTaskCreatePinnedToCore(video_task, "UVC", 4096, NULL, CONFIG_UVC_CAM1_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[0], core_id);
@@ -314,3 +363,58 @@ esp_err_t uvc_device_init(void)
314363
ESP_LOGI(TAG, "UVC Device Start, Version: %d.%d.%d", USB_DEVICE_UVC_VER_MAJOR, USB_DEVICE_UVC_VER_MINOR, USB_DEVICE_UVC_VER_PATCH);
315364
return ESP_OK;
316365
}
366+
367+
esp_err_t uvc_device_deinit(void)
368+
{
369+
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[0], ESP_ERR_INVALID_STATE, TAG, "uvc device 0 not init");
370+
#if CONFIG_UVC_SUPPORT_TWO_CAM
371+
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[1], ESP_ERR_INVALID_STATE, TAG, "uvc device 1 not init, if not use, please disable CONFIG_UVC_SUPPORT_TWO_CAM");
372+
#endif
373+
ESP_RETURN_ON_FALSE(s_uvc_device.event_group != NULL, ESP_ERR_INVALID_STATE, TAG, "event group is NULL");
374+
375+
// Stop UVC tasks first
376+
xEventGroupSetBits(s_uvc_device.event_group, UVC1_EVENT_EXIT);
377+
xEventGroupWaitBits(s_uvc_device.event_group, UVC1_EVENT_EXIT_DONE, pdTRUE, pdTRUE, portMAX_DELAY);
378+
#if CONFIG_UVC_SUPPORT_TWO_CAM
379+
xEventGroupSetBits(s_uvc_device.event_group, UVC2_EVENT_EXIT);
380+
xEventGroupWaitBits(s_uvc_device.event_group, UVC2_EVENT_EXIT_DONE, pdTRUE, pdTRUE, portMAX_DELAY);
381+
#endif
382+
383+
// Call user stop callbacks
384+
if (s_uvc_device.user_config[0].stop_cb) {
385+
s_uvc_device.user_config[0].stop_cb(s_uvc_device.user_config[0].cb_ctx);
386+
}
387+
#if CONFIG_UVC_SUPPORT_TWO_CAM
388+
if (s_uvc_device.user_config[1].stop_cb) {
389+
s_uvc_device.user_config[1].stop_cb(s_uvc_device.user_config[1].cb_ctx);
390+
}
391+
#endif
392+
393+
// Stop TinyUSB task
394+
xEventGroupSetBits(s_uvc_device.event_group, TUSB_EVENT_EXIT);
395+
EventBits_t bits = xEventGroupWaitBits(s_uvc_device.event_group, TUSB_EVENT_EXIT_DONE, pdTRUE, pdTRUE, pdMS_TO_TICKS(5000));
396+
if (!(bits & TUSB_EVENT_EXIT_DONE)) {
397+
ESP_LOGW(TAG, "TinyUSB task exit timeout (5s), force delete");
398+
if (s_uvc_device.tusb_task_hdl) {
399+
vTaskDelete(s_uvc_device.tusb_task_hdl);
400+
s_uvc_device.tusb_task_hdl = NULL;
401+
}
402+
}
403+
404+
// Clean up event group
405+
vEventGroupDelete(s_uvc_device.event_group);
406+
s_uvc_device.event_group = NULL;
407+
408+
// Teardown USB stack
409+
tusb_teardown();
410+
if (s_uvc_device.phy_hdl) {
411+
usb_del_phy(s_uvc_device.phy_hdl);
412+
s_uvc_device.phy_hdl = NULL;
413+
}
414+
415+
// Reset initialization flags
416+
memset(s_uvc_device.uvc_init, 0, sizeof(s_uvc_device.uvc_init));
417+
418+
ESP_LOGI(TAG, "UVC Device Deinit");
419+
return ESP_OK;
420+
}

examples/usb/device/usb_dual_uvc_device/CMakeLists.txt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ endif()
2828
set(OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/spiffs")
2929
file(MAKE_DIRECTORY ${OUTPUT_DIR})
3030

31-
file(GLOB AVI_FILES "${OUTPUT_DIR}/*.avi")
32-
if(NOT AVI_FILES)
33-
file(DOWNLOAD ${URL} ${OUTPUT_DIR}/p4_introduce.avi)
34-
message(STATUS "File downloaded from ${URL} to: ${OUTPUT_DIR}/p4_introduce.avi")
35-
else()
36-
message(STATUS "AVI file(s) already exist(s) in the output directory: ${OUTPUT_DIR}")
31+
set(OUTPUT_FILE "${OUTPUT_DIR}/p4_introduce.avi")
32+
33+
if(EXISTS ${OUTPUT_FILE})
34+
file(REMOVE ${OUTPUT_FILE})
35+
message(STATUS "Old AVI file removed: ${OUTPUT_FILE}")
3736
endif()
37+
38+
file(DOWNLOAD ${URL} ${OUTPUT_FILE})
39+
message(STATUS "File downloaded from ${URL} to: ${OUTPUT_FILE}")

examples/usb/device/usb_dual_uvc_device/main/idf_component.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ targets:
55
- esp32p4
66
dependencies:
77
espressif/esp32_s3_usb_otg:
8-
version: "^1.5.1"
8+
version: "^2.0.0"
99
rules:
1010
- if: "target in [esp32s3]"
1111
usb_device_uvc:
12-
version: "1.1.*"
12+
version: "1.2.*"
1313
override_path: "../../../../../components/usb/usb_device_uvc"
1414
avi_player:
1515
version: "2.*"
1616
override_path: "../../../../../components/avi_player"
17-
lvgl/lvgl: #temp to workaround bsp issue
18-
version: "9.2.0"

examples/usb/device/usb_lcd_display/main/idf_component.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ dependencies:
88
esp_lcd_usb_display:
99
version: "*"
1010
override_path: "../../../../../components/display/lcd/esp_lcd_usb_display"
11+
usb_device_uvc:
12+
version: "*"
13+
override_path: "../../../../../components/usb/usb_device_uvc"

examples/usb/device/usb_webcam/main/idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ dependencies:
1717
override_path: "../eyes_show"
1818
cmake_utilities: "*"
1919
usb_device_uvc:
20-
version: "1.1.*"
20+
version: "1.2.*"
2121
override_path: "../../../../../components/usb/usb_device_uvc"

0 commit comments

Comments
 (0)