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));
}
标签:usb,记录,itf,void,tu,rx,ESP32S3,callback,test
From: https://blog.csdn.net/qq_28695769/article/details/141054887