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)

 

 

posted @ 2025-03-17 20:54  流水灯  阅读(1190)  评论(0)    收藏  举报