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+
2838typedef 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
3749static uvc_device_t s_uvc_device ;
@@ -58,8 +70,15 @@ static inline uint32_t get_time_millis(void)
5870static 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
6584void 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+ }
0 commit comments