0、前期准备

1、会使用idf开发环境

2、懂得kconfig

1、知识储备

1.1 概述

​ TingUSB是一个开源的跨平台的USB主机/设备的usb协议栈,常用在mcu开发平台,由于不采用动态分配内存以及阻塞所有中断事件,将中断事件

要处理的事情都放在,非中断函数中处理,因此该usb栈内存设计非常安全、线程非常安全。

1.2 功能架构

​ ESP32S3内部集成了一个USB OTG外设,可配置成主机模式(host)或者设备模式(device),符合usb2.0协议规范。支持全速模式(12Mbit/s)和低速模式(1.5Mbit/s),还支持主机协商协议(HNP)和会话请求协议(SRP)。

设备模式特性

  • 端点 0 永远存在(双向控制,由 EP0 IN 和 EP0 OUT 组成)
  • 6 个附加端点 (1 ~ 6),可配置为 IN 或 OUT
  • 最多 5 个 IN 端点同时工作(包括 EP0 IN)
  • 所有 OUT 端点共享一个 RX FIFO
  • 每个 IN 端点都有专用的 TX FIF
  • 主机模式特性

  • 8 个通道(管道) – 由 IN 与 OUT 两个通道组成的一个控制管道,因为 IN 和 OUT 必须分开处理。仅支持控制传输类型。 – 其余 7 个管道可被配置为 IN 或OUT,支持批量、同步、中断中的任意传输类型。

  • 所有通道共用一个 RX FIFO、一个非周期性 TX FIFO、和一个周期性 TX FIFO。每个 FIFO 大小可配置。

  • 1.4 ESP32S3的tinyUSB使用配置(设备模式)流程介绍

    1、使用idf.py新建工程

    2、导入tinyUSB组件,导入命令如下:

    idf.py add-dependency "leeebo/tinyusb_src^0.16.0~2"
    

    3、设置usb的配置描述符并且注册usb驱动

    以下对tinyusb_config_t 结构体进行说明

    typedef struct {
        union {
            const tusb_desc_device_t *device_descriptor;//设备描述符
            const tusb_desc_device_t *descriptor  __attribute__((deprecated));
        };
        const char **string_descriptor;//字符描述
        int string_descriptor_count; //字符描述的数量
        bool external_phy;//是否使用外部phy,一般为false
        const uint8_t *configuration_descriptor; //配置描述符
        bool self_powered;//是否自供电
        int vbus_monitor_io;//自供电电源检测脚
    } tinyusb_config_t;
    

    使用例子

    tinyusb_config_t ubs_cfg = {
        .device_descriptor = NULL,
        .string_descriptor = NULL,
        .external_phy = false,
        .configuration_descriptor = NULL,
    };
    //一般为空即可,如果默认为空就会使用系统默认配置
    //注册驱动
    tinyusb_driver_install(&ubs_cfg);
    

    4、初始化usb设备,并且注册相应的驱动

    1.5 介绍如何使用ttyACM

    以下是参考源码

    #include "esp_log.h"
    #include "tinyusb.h"
    #include "tusb_cdc_acm.h"
    #include "tusb_test.h"
    
    void acm_rx_callback(int itf, cdcacm_event_t* event) {
        size_t rx_size = 0;
        esp_err_t err = tinyusb_cdcacm_read(itf, acm_buff, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
        if (err == ESP_OK) {
            ESP_LOG_BUFFER_HEXDUMP(TAG, acm_buff, rx_size, ESP_LOG_INFO);
        } else {
            ESP_LOGE(TAG, "read error(%s:%s)", __FILE__, __func__);
        }
    }
    
    void app_main(void) {
    
    	tinyusb_config_t ubs_cfg = {
            .device_descriptor = NULL,
            .string_descriptor = NULL,
            .external_phy = false,
            .configuration_descriptor = NULL,
        };
        
        ESP_LOGI(TAG, "acm initialzationning ...");
    
        ESP_ERROR_CHECK(tinyusb_driver_install(&ubs_cfg));
    
        tinyusb_config_cdcacm_t acm_cfg = {
            .usb_dev = TINYUSB_USBDEV_0,
            .cdc_port = TINYUSB_CDC_ACM_0,
            .rx_unread_buf_sz = 64,
            .callback_rx = acm_rx_callback,
            .callback_rx_wanted_char = NULL,
            .callback_line_coding_changed = NULL,
            .callback_line_state_changed = NULL,
        };
    
    
        ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));
        ESP_ERROR_CHECK(tinyusb_cdcacm_register_callback(TINYUSB_CDC_ACM_0, CDC_EVENT_LINE_STATE_CHANGED,
                                                         &line_state_changed_callback));
    }
    

    到此esp32s3的使用介绍完毕。

    以上是直接使用官方写好的usb设备驱动,但是如果想自定义自己的usb外设的话,可以参考以下步骤:

    番外

    介绍如何注册自定义的esp32s3 usb设备

    1、新建components文件,将espressif__ esp_tinyusb 和 espressif__tinyusb两个组件由managed_components拷贝到components,不然在编译的时候,如果有修改过组件的内容会导致编译不过,或者修改的内容会被覆盖

    2、在espressif__tinyusb/src/class新建一个文件夹,名字取自定义usb设备名,如test

    3、在test文件夹中新建test_device.c和test_device.h两个设备文件,内容可参考cdc_acm

    test_device.c内容参考如下:

    #include "tusb_option.h"
    
    #if (CFG_TUD_ENABLED && CFG_TUD_TEST)
    #include "device/usbd.h"
    #include "device/usbd_pvt.h"
    
    #include "test_device.h"
    
    enum
    {
      BULK_PACKET_SIZE = (TUD_OPT_HIGH_SPEED ? 512 : 64)
    };
    
    /* 该结构体的用途:用于作为通讯节点 */
    typedef struct {
        uint8_t ep_in;
        uint8_t ep_out;
    
        tu_fifo_t rx_ff;
        tu_fifo_t tx_ff;
    
        uint8_t rx_ff_buf[CFG_TUD_TEXT_RX_BUFSIZE];
        uint8_t tx_ff_buf[CFG_TUD_TEXT_TX_BUFSIZE];
    
        OSAL_MUTEX_DEF(rx_ff_mutex);
        OSAL_MUTEX_DEF(tx_ff_mutex);
    
        CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_TEXT_EP_BUFSIZE];
        CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_TEXT_EP_BUFSIZE];
    
    } test_interface_t;
    
    CFG_TUSB_MEM_SECTION tu_static test_interface_t _test_itf;
    
    static bool prep_out_transaction(test_interface_t* p_test) {
        uint8_t const rhport = 0;
        uint16_t available = tu_fifo_remaining(_test_itf.rx_ff);
        TU_VERIFY(available >= sizeof(p_test->epout_buf));
    
        // claim endpoint
        //判断端点是否在使用
        TU_VERIFY(usbd_edpt_claim(rhport, p_test->ep_out));
    
        // fifo can be changed before endpoint is claimed
        available = tu_fifo_remaining(p_test->rx_ff);
    
        if (available >= sizeof(p_test->epout_buf)) {
            return usbd_edpt_xfer(rhport, p_test->ep_out, p_test->epout_buf, sizeof(p_test->epout_buf));
        } else {
            //释放端点
            usbd_edpt_release(rhport, p_test->ep_out);
            return false;
        }
    }
    
    void test_init(void) {
    
        tu_memclr(&_test_itf, sizeof(_test_itf));
        //配置数据传输管道
        tu_fifo_config(_test_itf.rx_ff, p_test->rx_ff_buf, TU_ARRAY_SIZE(p_test->rx_ff_buf), 1, false);
        tu_fifo_config(_test_itf.tx_ff, p_test->tx_ff_buf, TU_ARRAY_SIZE(p_test->tx_ff_buf), 1, true);
        //创建管道锁
        tu_fifo_config_mutex(_test_itf.rx_ff, NULL, osal_mutex_create(_test_itf.rx_ff_mutex));
        tu_fifo_config_mutex(_test_itf.tx_ff, osal_mutex_create(_test_itf.tx_ff_mutex), NULL);
    }
    void test_reset(uint8_t rhport) {
        
        tu_memclr(&_test_itf, sizeof(uint8_t)*2);
        
        //清空管道
        tu_fifo_clear(_test_itf.rx_ff);
        tu_fifo_clear(_test_itf.tx_ff);
    
        tu_fifo_set_overwritable(_test_itf.tx_ff, true);
    }
    
    uint16_t test_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16_t max_len) {
        test_interface_t* p_test = NULL;
        uint16_t drv_len = 0;
    
        TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass, 0);
    
        if (_test_itf.ep_in == 0) {
            p_test = &_test_itf;
        }
    
        TU_ASSERT(p_test, 0);
    
        uint8_t const* p_desc = (uint8_t const*)itf_desc;
        drv_len = tu_desc_len(p_desc);
    
        /* Interface */
        if (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) {
            /* Endpoint Descriptor */
            p_desc = tu_desc_next(p_desc);
    
            TU_ASSERT(
                usbd_open_edpt_pair(rhport, p_desc, itf_desc->bNumEndpoints, TUSB_XFER_BULK, _test_itf.ep_out, _test_itf.ep_in),
                0);
            drv_len += itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t);
        }
    
         prep_out_transaction(p_test);
    
        return drv_len;
    }
    
    
    bool test_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) {
        if (ep_addr == _test_itf.ep_out) {
    
            tu_fifo_write_n(_test_itf.rx_ff, _test_itf.epout_buf, (uint16_t)xferred_bytes);
    
            if (tud_test_rx_cb && !tu_fifo_empty(_test_itf.rx_ff)) {
                tud_test_rx_cb();
            }
    
            prep_out_transaction(&_test_itf);
        } else if (ep_addr == _test_itf.ep_in) {
            if ( tud_text_tx_complete_cb ) tud_text_tx_complete_cb();//调用回调
            if ( 0 == tud_test_write_flush())
            {
                if ( !tu_fifo_count(_test_itf.tx_ff) && xferred_bytes && (0 == (xferred_bytes & (BULK_PACKET_SIZE-1))) )
                {
                    if ( usbd_edpt_claim(rhport, _test_itf.ep_in) )
                    {
                    	usbd_edpt_xfer(rhport, _test_itf.ep_in, NULL, 0);
                    }
                }
            }
        }
        return true;
    }
    
    bool test_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request){
        return true;
    }
    
    // app api
    uint32_t tud_test_read(void* buffer, uint32_t bufsize) {
        uint32_t num_read = tu_fifo_read_n(&_test_itf.rx_ff, buffer, (uint16_t)bufsize);
        prep_out_transaction(&_test_itf);
        return num_read;
    }
    
    bool tud_test_peek(uint8_t* chr) {
        return tu_fifo_peek(&_test_itf.rx_ff, chr);
    }
    
    void tud_test_read_flush(void) {
        tu_fifo_clear(_test_itf.rx_ff);
        prep_out_transaction(&_test_itf);
    }
    
    uint32_t tu_test_write(void*buffer,uint32_t bufsize){
        test_interface_t* p_test = &_test_itf;
        uint16_t ret = tu_fifo_write_n(_test_itf.tx_ff,buffer,(uint16_t)bufsize);
        if ((tu_fifo_count(_test_itf.tx_ff) >= BULK_PACKET_SIZE) || ((CFG_TUD_TEXT_TX_BUFSIZE < BULK_PACKET_SIZE) && tu_fifo_full(_test_itf.tx_ff)))
        {
            tud_test_write_flush();
        }
        
        return ret;
    }
    
    uint32_t tud_test_write_flush(void){
      // Skip if usb is not ready yet
      TU_VERIFY(tud_ready(), 0);
      // No data to send
      if ( !tu_fifo_count(_test_itf.tx_ff) ) return 0;
      uint8_t const rhport = 0;
      // Claim the endpoint
      TU_VERIFY( usbd_edpt_claim(rhport, _test_itf.ep_in), 0 );
      // Pull data from FIFO
      uint16_t const count = tu_fifo_read_n(_test_itf.tx_ff, _test_itf.epin_buf, sizeof(_test_itf.epin_buf));
      if (count)
      {
        TU_ASSERT( usbd_edpt_xfer(rhport, _test_itf.ep_in, _test_itf.epin_buf, count), 0 );
        return count;
      }else
      {
        // Release endpoint since we don't make any transfer
        // Note: data is dropped if terminal is not connected
        usbd_edpt_release(rhport,_test_itf.ep_in);
        return 0;
      }
    }
    
    uint32_t tud_test_write_available(void)
    {
      return tu_fifo_remaining(&_test_itf.tx_ff);
    }
    
    bool tud_test_write_clear(void)
    {
      return tu_fifo_clear(&_test_itf.tx_ff);
    }
    
    #endif
    
    

    test_device.h内容参考如下:

    #ifndef _TUSB_TEST_DEVICE_H_
    #define _TUSB_TEST_DEVICE_H_
    
    #include "common/tusb_common.h"
    
    #ifdef __cplusplus
     extern "C" {
    #endif
    
    void     test_init            (void);
    void     test_reset           (uint8_t rhport);
    uint16_t test_open            (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
    bool     test_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
    bool     test_xfer_cb         (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
    
    //callback
    TU_ATTR_WEAK void tud_test_rx_cb(void);
    TU_ATTR_WEAK void tud_test_tx_complete_cb(void);
    
    // App API
    uint32_t tud_test_read(void* buffer, uint32_t bufsize);
    bool tud_test_peek(uint8_t* chr);
    void tud_test_read_flush(void);
    
    uint32_t tu_test_write(void*buffer,uint32_t bufsize);
    uint32_t tud_test_write_flush(void);
    uint32_t tud_test_write_available(void);
    bool tud_test_write_clear(void);
    
    #ifdef __cplusplus
     }
    #endif
    
    #endif /* _TUSB_TEST_DEVICE_H_ */
    

    4、在tu_static usbd_class_driver_t const _usbd_driver 数组中注册自定义设备

    tu_static usbd_class_driver_t const _usbd_driver[] =
    {
    ...
      #if CFG_TUD_TEST
      {
        DRIVER_NAME("TEST")
        .init             = test_init,
        .reset            = test_reset,
        .open             = test_open,
        .control_xfer_cb  = test_control_xfer_cb,
        .xfer_cb          = test_xfer_cb,
        .sof              = NULL
      },
      #endif
    ...
    }
    

    5、在espressif__ esp_tinyusb/include/tusb_config.h文件中添加自定义设备配置

    // TEST EP Size
    #define CFG_TUD_TEST_EP_BUFSIZE      64
    #define CFG_TUD_TEST_RX_BUFSIZE      CONFIG_TINYUSB_TEST_RX_BUFSIZE
    #define CFG_TUD_TEST_TX_BUFSIZE      CONFIG_TINYUSB_TEST_TX_BUFSIZE
    
    #define CFG_TUD_TEST                 CONFIG_TINYUSB_TEST_ENABLED
    

    6、在espressif__ esp_tinyusb/include中新建tusb_test.h文件,内容可参考tusb_cdc_acm.h

    内容如下

    #pragma once
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    #include <stdint.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/semphr.h"
    #include "freertos/timers.h"
    #include "tusb.h"
    #include "tinyusb_types.h"
    
    #if (CONFIG_TINYUSB_TEST_ENABLED != 1)
    #error "TinyUSB TEST driver must be enabled in menuconfig"
    #endif
    
    #define EPNUM_TEST_IN 0x81
    #define EPNUM_TEST_OUT 0x01
    
    #define TUSB_CLASS_TEST 0xFF
    
    #define TUD_TEST_DESC_LEN (8 + 9 + 7 + 7)
    // Interface number, string index, EP Out & EP In address, EP size
    #define TUD_TEST_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \
        /* Interface */ \
        8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 1, TUSB_CLASS_TEST, 0, 0, 0,  \
        9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_TEST, 0, 0, _stridx,     \
        7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,  \
        7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0
    
    
    
    typedef enum{
        TEST_EVENT_RX,
        TEST_EVENT_TX_COMPLETE
    }test_event_type_t;
    
    typedef void (* tusb_test_callback_t)(void);
    
    typedef struct{
        tusb_test_callback_t callback_rx;
        tusb_test_callback_t callback_tx_complete_callback;
    }tinyusb_config_test_t;
    
    esp_err_t tinyusb_test_register_callback(test_event_type_t _event_type,tusb_test_callback_t _callback);
    esp_err_t tinyusb_test_read(uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size);
    
    
    #ifdef __cplusplus
    extern }
    #endif
    
    
    

    7、在espressif__ esp_tinyusb/中新建tusb_test.c文件,内容可参考tusb_cdc_acm.c

    内容如下:

    #include "tusb_test.h"
    #include "esp_check.h"
    #include "esp_err.h"
    #include "esp_log.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "tusb.h"
    
    #include "sdkconfig.h"
    
    static portMUX_TYPE test_lock = portMUX_INITIALIZER_UNLOCKED;
    #define TEST_ENTER_CRITICAL() portENTER_CRITICAL(&test_lock)
    #define TEST_EXIT_CRITICAL() portEXIT_CRITICAL(&test_lock)
    
    static const char* TAG = "tusb_test";
    
    static tinyusb_config_test_t _test_cfg;
    
    esp_err_t tinyusb_test_read(uint8_t* out_buf, size_t out_buf_sz, size_t* rx_data_size) {
        *rx_data_size = tud_test_read(out_buf, out_buf_sz);
        return ESP_OK;
    }
    
    esp_err_t tinyusb_test_register_callback(test_event_type_t _event_type, tusb_test_callback_t _callback) {
        switch (_event_type) {
            case test_EVENT_RX:
                TEST_ENTER_CRITICAL();
                _test_cfg.callback_rx = _callback;
                TEST_EXIT_CRITICAL();
                return ESP_OK;
            case test_EVENT_TX_COMPLETE:
                TEST_ENTER_CRITICAL();
                _test_cfg.callback_tx_complete_callback = _callback;
                TEST_EXIT_CRITICAL();
                return ESP_OK;
    
            default:
                ESP_LOGE(TAG, "test Callback fun is NULL");
                return ESP_FAIL;
        }
    }
    
    void tud_test_rx_cb(void) {
        if (_test_cfg.callback_rx) {
            _test_cfg.callback_rx();
        }
    }
    
    void tud_test_tx_complete_cb(void) {
        if (_test_cfg.callback_tx_complete_callback) {
            _test_cfg.callback_tx_complete_callback();
        }
    }
    
    void tinyusb_test_relay_out_point(void){
        tud_test_relay_out_point();
    }
    

    到此设备新加完毕

    使用例子:

    #include "esp_log.h"
    #include "tinyusb.h"
    #include "tusb_test.h"
    
    static uint8_t test_buff[CONFIG_TINYUSB_TEXT_RX_BUFSIZE + 1];
    
    void test_rx_callback(int itf, cdcacm_event_t* event) {
        size_t rx_size = 0;
        esp_err_t err = tinyusb_test_read(itf, acm_buff, CONFIG_TINYUSB_TEXT_RX_BUFSIZE, &rx_size);
        if (err == ESP_OK) {
            ESP_LOG_BUFFER_HEXDUMP(TAG, test_buff, rx_size, ESP_LOG_INFO);
        } else {
            ESP_LOGE(TAG, "read error(%s:%s)", __FILE__, __func__);
        }
    }
    
    void app_main(void) {
    
    	tinyusb_config_t ubs_cfg = {
            .device_descriptor = NULL,
            .string_descriptor = NULL,
            .external_phy = false,
            .configuration_descriptor = NULL,
        };
        
        ESP_LOGI(TAG, "test initialzationning ...");
    
        ESP_ERROR_CHECK(tinyusb_driver_install(&ubs_cfg));
    	ESP_ERROR_CHECK(tinyusb_test_register_callback(DCM_EVENT_RX, test_rx_callback));
    }
    

    作者:MagicKingC

    物联沃分享整理
    物联沃-IOTWORD物联网 » ESP32S3 USB使用详解

    发表回复