tinyusb --- 轻量级的开源USB协议栈
TinyUSB是一个轻量级的开源USB协议栈,适用于嵌入式系统,支持主机端和设备端的USB协议。
移植需要实现的函数:
tud_init() ---> usbd_app_driver_get_cb()

#include <errno.h> #include <semaphore.h> #include "esp_log.h" #include "class/vendor/vendor_device.h" #include "device/usbd.h" #include "device/usbd_pvt.h" #include "mtp.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "common/tusb_types.h" #include "fifo.h" #include "logCache.h" static const char *TAG = "USB_GADGET"; // vendor default is 0 #define DEFAULT_ITF 0 #define MAX_DESC_BUF_SIZE 32 #define USB_STRING_DESCRIPTOR_ARRAY_SIZE 8 #define USB_WRITE_BUF (2 * 1024 * 1024) #define RX_FF_BUF_SIZE (1 * 1024 * 1024) #define TX_FF_BUF_SIZE (1 * 1024 * 1024) #define BULK_EP_SIZE (512) typedef struct { uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; uint8_t ep_notif; FIFO rx_ff_fifo; FIFO tx_ff_fifo; // Endpoint Transfer buffer CFG_TUSB_MEM_ALIGN uint8_t epout_buf[BULK_EP_SIZE]; CFG_TUSB_MEM_ALIGN uint8_t epin_buf[BULK_EP_SIZE]; } mtp_interface_t; sem_t g_tx_sem; sem_t g_rx_sem; mtp_interface_t g_mtp_interface; // ============================================================================= // CALLBACKS from tintUsb // ============================================================================= void tud_mount_cb(void) { ESP_LOGI(TAG, "Mount"); // 挂载 } // Invoked when device is unmounted void tud_umount_cb(void) { ESP_LOGI(TAG, "UN-Mount"); // 卸载 } /** * @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) { static const tusb_desc_device_t dev = { .bLength = sizeof(dev), .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0200, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers .idProduct = 0xFEF6, .bcdDevice = 0x100, .iManufacturer = 0x01, .iProduct = 0x02, .iSerialNumber = 0x03, .bNumConfigurations = 0x01 }; return (uint8_t const *)&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 */ static uint8_t msc_fs_configuration_desc[] = { // Config number, interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, 1, 0, 39, TUSB_DESC_CONFIG_ATT_SELF_POWERED, 2), // Interface 9, TUSB_DESC_INTERFACE, 0, 0, 3, TUSB_CLASS_IMAGE, 0x01, 0x01, 4, // ep IN INTERRUPT 7, TUSB_DESC_ENDPOINT, 0x84, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(64), 01, // ep IN Bulk 7, TUSB_DESC_ENDPOINT, 0x81, TUSB_XFER_BULK, U16_TO_U8S_LE(64), 0, // ep OUT Bulk 7, TUSB_DESC_ENDPOINT, 0x02, TUSB_XFER_BULK, U16_TO_U8S_LE(64), 0, }; #if (TUD_OPT_HIGH_SPEED) static uint8_t msc_hs_configuration_desc[] = { // Config number, interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, 1, 0, 39, TUSB_DESC_CONFIG_ATT_SELF_POWERED, 2), // Interface 9, TUSB_DESC_INTERFACE, 0, 0, 3, TUSB_CLASS_IMAGE, 0x01, 0x01, 4, // ep IN INTERRUPT 7, TUSB_DESC_ENDPOINT, 0x84, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(64), 01, // ep IN Bulk 7, TUSB_DESC_ENDPOINT, 0x81, TUSB_XFER_BULK, U16_TO_U8S_LE(512), 0, // ep OUT Bulk 7, TUSB_DESC_ENDPOINT, 0x02, TUSB_XFER_BULK, U16_TO_U8S_LE(512), 0, }; #endif uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { (void)index; // Unused, this driver supports only 1 configuration #if (TUD_OPT_HIGH_SPEED) // Return configuration descriptor based on Host speed return (TUSB_SPEED_HIGH == tud_speed_get()) ? (uint8_t const *)(&msc_hs_configuration_desc) : (uint8_t const *)(&msc_fs_configuration_desc); #else return (uint8_t const *)(&msc_fs_configuration_desc); #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) { static const tusb_desc_device_qualifier_t device_qualifier = { .bLength = sizeof(tusb_desc_device_qualifier_t), .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, .bcdUSB = 0x0200, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .bNumConfigurations = 0x01, .bReserved = 0 }; return (uint8_t const *)(&device_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) { return (TUSB_SPEED_HIGH == tud_speed_get()) ? (uint8_t const *)(&msc_fs_configuration_desc) : (uint8_t const *)(&msc_hs_configuration_desc); } #endif // TUD_OPT_HIGH_SPEED static char const *string_desc_arr[USB_STRING_DESCRIPTOR_ARRAY_SIZE] = { (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) "OBSBOT", // 1: Manufacturer "OBSBOT MTP", // 2: Product "123456", // 3: Serials "MTP", // 4. MTP }; /** * @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 uint8_t chr_count; static uint16_t _desc_str[MAX_DESC_BUF_SIZE]; if (index == 0) { memcpy(&_desc_str[1], string_desc_arr[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 (string_desc_arr[index] == NULL) { ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index); return NULL; } const char *str = string_desc_arr[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; } static void _prep_out_transaction() { uint8_t const rhport = 0; // claim endpoint TU_VERIFY(usbd_edpt_claim(rhport, g_mtp_interface.ep_out), ); // Prepare for incoming data but only allow what we can store in the ring buffer. int max_read = fifo_getAvailableSpace(&g_mtp_interface.rx_ff_fifo); if (max_read >= BULK_EP_SIZE) { usbd_edpt_xfer(rhport, g_mtp_interface.ep_out, g_mtp_interface.epout_buf, BULK_EP_SIZE); } else { // Release endpoint since we don't make any transfer usbd_edpt_release(rhport, g_mtp_interface.ep_out); } } static bool g_tx_fifo_task_run = false; static void mtp_tx_fifo_task(void *arg) { uint8_t rhport = 0; ESP_LOGI(TAG, "mtp_tx_fifo_task task started"); TU_VERIFY( usbd_edpt_claim(rhport, g_mtp_interface.ep_in), ); while (g_tx_fifo_task_run) { // RTOS forever loop if (fifo_is_empty(&g_mtp_interface.tx_ff_fifo) == true) { sem_wait(&g_tx_sem); } else { s8 data_type = peek_fifo_data_type(&g_mtp_interface.tx_ff_fifo); if (data_type == 1 && usbd_edpt_busy(rhport, g_mtp_interface.ep_in)) { sem_wait(&g_tx_sem); } else if (data_type == 2 && usbd_edpt_busy(rhport, g_mtp_interface.ep_notif)) { sem_wait(&g_tx_sem); } else { u8 ep = 0; if (data_type == 1) { ep = g_mtp_interface.ep_in; } else { ep = g_mtp_interface.ep_notif; } int size = 0; uint8_t *buffer = (uint8_t*)fifo_dequeue(&g_mtp_interface.tx_ff_fifo, &size); if (buffer) { char *string = hex_to_string((char *)buffer, size); RM_LOGD("send usb data[%d]=%s", size, string); free(string); TU_ASSERT( usbd_edpt_xfer(rhport, ep, buffer, size), ); free(buffer); } } } } usbd_edpt_release(rhport, g_mtp_interface.ep_in); ESP_LOGI(TAG, "mtp_tx_fifo_task task end"); vTaskDelete(NULL); } static void mtp_rx_fifo_task(void *arg) { ESP_LOGI(TAG, "mtp_rx_fifo_task task started"); while (g_tx_fifo_task_run) { // RTOS forever loop if (fifo_is_empty(&g_mtp_interface.rx_ff_fifo)) { sem_wait(&g_rx_sem); } else { int size = 0; uint8_t *buffer = (uint8_t*)fifo_dequeue(&g_mtp_interface.rx_ff_fifo, &size); if (buffer) { mtp_process_in_packet(buffer, size, 0); free(buffer); } } } vTaskDelete(NULL); } void mtp_gadget_init(void) { ESP_LOGI(TAG, "mtp_gadget_init"); memset(&g_mtp_interface, 0, sizeof(g_mtp_interface)); fifo_create(&(g_mtp_interface.rx_ff_fifo), RX_FF_BUF_SIZE); fifo_create(&(g_mtp_interface.tx_ff_fifo), TX_FF_BUF_SIZE); sem_init(&g_tx_sem, 0, 0); sem_init(&g_rx_sem, 0, 0); g_tx_fifo_task_run = true; xTaskCreatePinnedToCore(mtp_tx_fifo_task, "mtp_tx_fifo", 8 * 1024, NULL, 5, NULL, 0); xTaskCreatePinnedToCore(mtp_rx_fifo_task, "mtp_rx_fifo", 8 * 1024, NULL, 5, NULL, 0); } void mtp_gadget_reset(uint8_t rhport) { ESP_LOGI(TAG, "mtp_gadget_reset"); (void) rhport; fifo_clear(&g_mtp_interface.rx_ff_fifo); fifo_clear(&g_mtp_interface.tx_ff_fifo); } uint16_t mtp_gadget_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) { uint8_t const * p_desc = tu_desc_next(desc_itf); uint8_t const * desc_end = p_desc + max_len; g_mtp_interface.itf_num = desc_itf->bInterfaceNumber; if (desc_itf->bNumEndpoints) { // skip non-endpoint descriptors while ( (TUSB_DESC_ENDPOINT != tu_desc_type(p_desc)) && (p_desc < desc_end) ) { p_desc = tu_desc_next(p_desc); } { tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; TU_ASSERT( usbd_edpt_open(rhport, desc_ep), 0 ); g_mtp_interface.ep_notif = desc_ep->bEndpointAddress; p_desc = tu_desc_next(p_desc); } // Open endpoint pair with usbd helper TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, desc_itf->bNumEndpoints - 1, TUSB_XFER_BULK, &g_mtp_interface.ep_out, &g_mtp_interface.ep_in), 0); p_desc += (desc_itf->bNumEndpoints - 1) * sizeof(tusb_desc_endpoint_t); // Prepare for incoming data if (g_mtp_interface.ep_out) { _prep_out_transaction(); } } return (uint16_t) ((uintptr_t) p_desc - (uintptr_t) desc_itf); } bool mtp_gadget_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; RM_LOGW("stage %u, type %d, bRequest %d", stage, request->bmRequestType_bit.type, request->bRequest); // stall unknown request return false; } bool mtp_gadget_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) rhport; (void) result; if ( ep_addr == g_mtp_interface.ep_out ) { // Receive new data fifo_enqueue(&g_mtp_interface.rx_ff_fifo, g_mtp_interface.epout_buf, xferred_bytes, 1); // RM_LOGI("%lu recv usb data %lu", xTaskGetTickCount() * portTICK_PERIOD_MS, xferred_bytes); // RM_LOGI("recv usb data %lu", xferred_bytes); sem_post(&g_rx_sem); _prep_out_transaction(); } else if ( ep_addr == g_mtp_interface.ep_in ) { // 发送完成回调函数 RM_LOGI("send usb bulk data success %lu", xferred_bytes); sem_post(&g_tx_sem); } else if ( ep_addr == g_mtp_interface.ep_notif ) { RM_LOGI("send usb int data success %lu", xferred_bytes); sem_post(&g_tx_sem); } return true; } tu_static usbd_class_driver_t const mtp_usbd_driver = { #if CFG_TUSB_DEBUG >= 2 .name = "MTP", #endif .init = mtp_gadget_init, .reset = mtp_gadget_reset, .open = mtp_gadget_open, .control_xfer_cb = mtp_gadget_control_xfer_cb, .xfer_cb = mtp_gadget_xfer_cb, .sof = NULL }; usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) { *driver_count = 1; return &mtp_usbd_driver; } uint32_t Usb_gadget_write(int itf, void *buffer, int size, bool flush, u8 data_type) { (void)flush; do { if (fifo_getAvailableSpace(&g_mtp_interface.tx_ff_fifo) >= size) { fifo_enqueue(&g_mtp_interface.tx_ff_fifo, buffer, size, data_type); sem_post(&g_tx_sem); // RM_LOGI("sem_post %d", size); break; } else { ESP_LOGW(TAG, "write block need size:%d, free:%d", size, fifo_getAvailableSpace(&g_mtp_interface.tx_ff_fifo)); vTaskDelay(20); } } while(g_tx_fifo_task_run); return 0; } uint32_t Usb_gadget_init(void) { ESP_LOGI(TAG, "%s %d\n", __func__, __LINE__); return 0; }
部分 API 解释:
设置接收端点的缓存:
usbd_edpt_xfer(rhport, g_mtp_interface.ep_out, g_mtp_interface.epout_buf, BULK_EP_SIZE);
向指定端点发送数据:
usbd_edpt_xfer(rhport, g_mtp_interface.ep_in, buffer, size)

浙公网安备 33010602011771号