mirror of
https://github.com/MrUnknownDE/OpenIris-ESPIDF.git
synced 2026-04-11 02:43:43 +02:00
346 lines
10 KiB
C
346 lines
10 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_check.h"
|
|
#if CONFIG_TINYUSB_RHPORT_HS
|
|
#include "soc/hp_sys_clkrst_reg.h"
|
|
#include "soc/hp_system_reg.h"
|
|
#else
|
|
#include "esp_private/usb_phy.h"
|
|
#endif
|
|
#include "tusb.h"
|
|
#include "usb_device_uvc.h"
|
|
|
|
static const char *TAG = "usbd_uvc";
|
|
|
|
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
#define UVC_CAM_NUM 2
|
|
#else
|
|
#define UVC_CAM_NUM 1
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
#if !CONFIG_TINYUSB_RHPORT_HS
|
|
usb_phy_handle_t phy_hdl;
|
|
#endif
|
|
bool uvc_init[UVC_CAM_NUM];
|
|
uvc_format_t format[UVC_CAM_NUM];
|
|
uvc_device_config_t user_config[UVC_CAM_NUM];
|
|
TaskHandle_t uvc_task_hdl[UVC_CAM_NUM];
|
|
uint32_t interval_ms[UVC_CAM_NUM];
|
|
} uvc_device_t;
|
|
|
|
static uvc_device_t s_uvc_device;
|
|
|
|
static void usb_phy_init(void)
|
|
{
|
|
#if !CONFIG_TINYUSB_RHPORT_HS
|
|
// Configure USB PHY
|
|
usb_phy_config_t phy_conf = {
|
|
.controller = USB_PHY_CTRL_OTG,
|
|
.otg_mode = USB_OTG_MODE_DEVICE,
|
|
.target = USB_PHY_TARGET_INT,
|
|
};
|
|
usb_new_phy(&phy_conf, &s_uvc_device.phy_hdl);
|
|
#endif
|
|
}
|
|
|
|
static inline uint32_t get_time_millis(void)
|
|
{
|
|
return (uint32_t)(esp_timer_get_time() / 1000);
|
|
}
|
|
|
|
static void tusb_device_task(void *arg)
|
|
{
|
|
while (1)
|
|
{
|
|
tud_task();
|
|
}
|
|
}
|
|
|
|
void tud_mount_cb(void)
|
|
{
|
|
ESP_LOGI(TAG, "Mount");
|
|
}
|
|
|
|
// Invoked when device is unmounted
|
|
void tud_umount_cb(void)
|
|
{
|
|
ESP_LOGI(TAG, "UN-Mount");
|
|
}
|
|
|
|
void tud_suspend_cb(bool remote_wakeup_en)
|
|
{
|
|
(void)remote_wakeup_en;
|
|
|
|
if (s_uvc_device.user_config[0].stop_cb)
|
|
{
|
|
s_uvc_device.user_config[0].stop_cb(s_uvc_device.user_config[0].cb_ctx);
|
|
}
|
|
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
if (s_uvc_device.user_config[1].stop_cb)
|
|
{
|
|
s_uvc_device.user_config[1].stop_cb(s_uvc_device.user_config[1].cb_ctx);
|
|
}
|
|
#endif
|
|
ESP_LOGI(TAG, "Suspend");
|
|
}
|
|
|
|
// Invoked when usb bus is resumed
|
|
void tud_resume_cb(void)
|
|
{
|
|
ESP_LOGI(TAG, "Resume");
|
|
}
|
|
|
|
#if (CFG_TUD_VIDEO)
|
|
//--------------------------------------------------------------------+
|
|
// USB Video
|
|
//--------------------------------------------------------------------+
|
|
static void video_task(void *arg)
|
|
{
|
|
uint32_t start_ms = 0;
|
|
uint32_t frame_num = 0;
|
|
uint32_t frame_len = 0;
|
|
uint32_t already_start = 0;
|
|
uint32_t tx_busy = 0;
|
|
uint8_t *uvc_buffer = s_uvc_device.user_config[0].uvc_buffer;
|
|
uint32_t uvc_buffer_size = s_uvc_device.user_config[0].uvc_buffer_size;
|
|
uvc_fb_t *pic = NULL;
|
|
|
|
while (1)
|
|
{
|
|
if (!tud_video_n_streaming(0, 0))
|
|
{
|
|
already_start = 0;
|
|
frame_num = 0;
|
|
tx_busy = 0;
|
|
vTaskDelay(1);
|
|
continue;
|
|
}
|
|
|
|
if (!already_start)
|
|
{
|
|
already_start = 1;
|
|
start_ms = get_time_millis();
|
|
}
|
|
|
|
uint32_t cur = get_time_millis();
|
|
if (cur - start_ms < s_uvc_device.interval_ms[0])
|
|
{
|
|
vTaskDelay(1);
|
|
continue;
|
|
}
|
|
|
|
if (tx_busy)
|
|
{
|
|
uint32_t xfer_done = ulTaskNotifyTake(pdTRUE, 1);
|
|
if (xfer_done == 0)
|
|
{
|
|
continue;
|
|
}
|
|
++frame_num;
|
|
tx_busy = 0;
|
|
}
|
|
|
|
start_ms += s_uvc_device.interval_ms[0];
|
|
ESP_LOGD(TAG, "frame %" PRIu32 " taking picture...", frame_num);
|
|
pic = s_uvc_device.user_config[0].fb_get_cb(s_uvc_device.user_config[0].cb_ctx);
|
|
if (pic)
|
|
{
|
|
ESP_LOGD(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGE(TAG, "Failed to capture picture");
|
|
continue;
|
|
}
|
|
|
|
if (pic->len > uvc_buffer_size)
|
|
{
|
|
ESP_LOGW(TAG, "frame size is too big, dropping frame");
|
|
s_uvc_device.user_config[0].fb_return_cb(pic, s_uvc_device.user_config[0].cb_ctx);
|
|
continue;
|
|
}
|
|
frame_len = pic->len;
|
|
memcpy(uvc_buffer, pic->buf, frame_len);
|
|
s_uvc_device.user_config[0].fb_return_cb(pic, s_uvc_device.user_config[0].cb_ctx);
|
|
tx_busy = 1;
|
|
tud_video_n_frame_xfer(0, 0, (void *)uvc_buffer, frame_len);
|
|
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
|
|
}
|
|
}
|
|
|
|
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
static void video_task2(void *arg)
|
|
{
|
|
uint32_t start_ms = 0;
|
|
uint32_t frame_num = 0;
|
|
uint32_t frame_len = 0;
|
|
uint32_t already_start = 0;
|
|
uint32_t tx_busy = 0;
|
|
uint8_t *uvc_buffer = s_uvc_device.user_config[1].uvc_buffer;
|
|
uint32_t uvc_buffer_size = s_uvc_device.user_config[1].uvc_buffer_size;
|
|
uvc_fb_t *pic = NULL;
|
|
|
|
while (1)
|
|
{
|
|
if (!tud_video_n_streaming(1, 0))
|
|
{
|
|
already_start = 0;
|
|
frame_num = 0;
|
|
tx_busy = 0;
|
|
vTaskDelay(1);
|
|
continue;
|
|
}
|
|
|
|
if (!already_start)
|
|
{
|
|
already_start = 1;
|
|
start_ms = get_time_millis();
|
|
}
|
|
|
|
uint32_t cur = get_time_millis();
|
|
if (cur - start_ms < s_uvc_device.interval_ms[1])
|
|
{
|
|
vTaskDelay(1);
|
|
continue;
|
|
}
|
|
|
|
if (tx_busy)
|
|
{
|
|
uint32_t xfer_done = ulTaskNotifyTake(pdTRUE, 1);
|
|
if (xfer_done == 0)
|
|
{
|
|
continue;
|
|
}
|
|
++frame_num;
|
|
tx_busy = 0;
|
|
}
|
|
|
|
start_ms += s_uvc_device.interval_ms[1];
|
|
ESP_LOGD(TAG, "frame %" PRIu32 " taking picture...", frame_num);
|
|
pic = s_uvc_device.user_config[1].fb_get_cb(s_uvc_device.user_config[1].cb_ctx);
|
|
if (pic)
|
|
{
|
|
ESP_LOGD(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGE(TAG, "Failed to capture picture");
|
|
continue;
|
|
}
|
|
|
|
if (pic->len > uvc_buffer_size)
|
|
{
|
|
ESP_LOGW(TAG, "frame size is too big, dropping frame");
|
|
s_uvc_device.user_config[1].fb_return_cb(pic, s_uvc_device.user_config[1].cb_ctx);
|
|
continue;
|
|
}
|
|
frame_len = pic->len;
|
|
memcpy(uvc_buffer, pic->buf, frame_len);
|
|
s_uvc_device.user_config[1].fb_return_cb(pic, s_uvc_device.user_config[1].cb_ctx);
|
|
tx_busy = 1;
|
|
tud_video_n_frame_xfer(1, 0, (void *)uvc_buffer, frame_len);
|
|
ESP_LOGD(TAG, "frame %" PRIu32 " transfer start, size %" PRIu32, frame_num, frame_len);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx)
|
|
{
|
|
(void)ctl_idx;
|
|
(void)stm_idx;
|
|
xTaskNotifyGive(s_uvc_device.uvc_task_hdl[ctl_idx]);
|
|
}
|
|
|
|
int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx,
|
|
video_probe_and_commit_control_t const *parameters)
|
|
{
|
|
(void)ctl_idx;
|
|
(void)stm_idx;
|
|
/* convert unit to ms from 100 ns */
|
|
ESP_LOGI(TAG, "bFrameIndex: %u", parameters->bFrameIndex);
|
|
ESP_LOGI(TAG, "dwFrameInterval: %" PRIu32 "", parameters->dwFrameInterval);
|
|
if (parameters->bFrameIndex > UVC_FRAME_NUM)
|
|
{
|
|
return VIDEO_ERROR_OUT_OF_RANGE;
|
|
}
|
|
s_uvc_device.interval_ms[ctl_idx] = parameters->dwFrameInterval / 10000;
|
|
int frame_index = parameters->bFrameIndex - 1;
|
|
esp_err_t ret = s_uvc_device.user_config[ctl_idx].start_cb(s_uvc_device.format[ctl_idx], UVC_FRAMES_INFO[ctl_idx][frame_index].width,
|
|
UVC_FRAMES_INFO[ctl_idx][frame_index].height, UVC_FRAMES_INFO[ctl_idx][frame_index].rate, s_uvc_device.user_config[ctl_idx].cb_ctx);
|
|
|
|
if (ret != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "camera init failed");
|
|
return VIDEO_ERROR_OUT_OF_RANGE;
|
|
}
|
|
return VIDEO_ERROR_NONE;
|
|
}
|
|
#endif
|
|
|
|
esp_err_t uvc_device_config(int index, uvc_device_config_t *config)
|
|
{
|
|
ESP_RETURN_ON_FALSE(index < UVC_CAM_NUM, ESP_ERR_INVALID_ARG, TAG, "index is invalid");
|
|
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is NULL");
|
|
ESP_RETURN_ON_FALSE(config->start_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "start_cb is NULL");
|
|
ESP_RETURN_ON_FALSE(config->fb_get_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "fb_get_cb is NULL");
|
|
ESP_RETURN_ON_FALSE(config->fb_return_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "fb_return_cb is NULL");
|
|
ESP_RETURN_ON_FALSE(config->stop_cb != NULL, ESP_ERR_INVALID_ARG, TAG, "stop_cb is NULL");
|
|
ESP_RETURN_ON_FALSE(config->uvc_buffer != NULL, ESP_ERR_INVALID_ARG, TAG, "uvc_buffer is NULL");
|
|
ESP_RETURN_ON_FALSE(config->uvc_buffer_size > 0, ESP_ERR_INVALID_ARG, TAG, "uvc_buffer_size is 0");
|
|
|
|
s_uvc_device.user_config[index] = *config;
|
|
s_uvc_device.interval_ms[index] = 1000 / (index == 0 ? UVC_CAM1_FRAME_RATE : UVC_CAM2_FRAME_RATE);
|
|
s_uvc_device.uvc_init[index] = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t uvc_device_init(void)
|
|
{
|
|
ESP_RETURN_ON_FALSE(s_uvc_device.uvc_init[0], ESP_ERR_INVALID_STATE, TAG, "uvc device 0 not init");
|
|
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
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");
|
|
#endif
|
|
|
|
#ifdef CONFIG_FORMAT_MJPEG_CAM1
|
|
s_uvc_device.format[0] = UVC_FORMAT_JPEG;
|
|
#endif
|
|
|
|
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
#ifdef CONFIG_FORMAT_MJPEG_CAM2
|
|
s_uvc_device.format[1] = UVC_FORMAT_JPEG;
|
|
#endif
|
|
#endif
|
|
|
|
// init device stack on configured roothub port
|
|
usb_phy_init();
|
|
bool usb_init = tusb_init();
|
|
if (!usb_init)
|
|
{
|
|
ESP_LOGE(TAG, "USB Device Stack Init Fail");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
BaseType_t core_id = (CONFIG_UVC_TINYUSB_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_TINYUSB_TASK_CORE;
|
|
xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", 4096, NULL, CONFIG_UVC_TINYUSB_TASK_PRIORITY, NULL, core_id);
|
|
#if (CFG_TUD_VIDEO)
|
|
core_id = (CONFIG_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM1_TASK_CORE;
|
|
xTaskCreatePinnedToCore(video_task, "UVC", 4096, NULL, CONFIG_UVC_CAM1_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[0], core_id);
|
|
#if CONFIG_UVC_SUPPORT_TWO_CAM
|
|
core_id = (CONFIG_UVC_CAM2_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_UVC_CAM2_TASK_CORE;
|
|
xTaskCreatePinnedToCore(video_task2, "UVC2", 4096, NULL, CONFIG_UVC_CAM2_TASK_PRIORITY, &s_uvc_device.uvc_task_hdl[1], core_id);
|
|
#endif
|
|
#endif
|
|
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);
|
|
return ESP_OK;
|
|
}
|