usb: cdc support, streams redirection; ci, examples upd

tusb: cdc, tasks encapsulation, callbacks api, multiple interfaces
examples: added serial interface and usb console
ci: reimplemented cmake/make test in python with ignore lists

IDF-578
This commit is contained in:
Andrei Gramakov 2020-08-06 14:41:32 +02:00
parent b08c2885d8
commit 84aa1c0cc3
42 changed files with 2207 additions and 1804 deletions

View File

@ -1,37 +1,64 @@
idf_component_register(REQUIRES esp_rom freertos soc driver)
idf_component_register(REQUIRES esp_rom freertos vfs soc)
if(CONFIG_USB_ENABLED)
idf_component_get_property( FREERTOS_ORIG_INCLUDE_PATH freertos ORIG_INCLUDE_PATH)
target_compile_options(${COMPONENT_TARGET} INTERFACE
"-DCFG_TUSB_MCU=OPT_MCU_ESP32_S2"
)
### variables ###
#################
set(compile_options
"-DCFG_TUSB_MCU=OPT_MCU_ESP32S2"
"-DCFG_TUSB_DEBUG=${CONFIG_USB_DEBUG_LEVEL}"
"-Wno-type-limits" # needed for the vanila tinyusb with turned off classes
)
idf_component_get_property(FREERTOS_ORIG_INCLUDE_PATH freertos
ORIG_INCLUDE_PATH)
set(includes_private
# tusb:
"${COMPONENT_DIR}/tinyusb/hw/bsp/"
"${COMPONENT_DIR}/tinyusb/src/"
"${COMPONENT_DIR}/tinyusb/src/device"
# espressif:
"${COMPONENT_DIR}/additions/include_private"
)
target_include_directories(${COMPONENT_TARGET} INTERFACE
"${FREERTOS_ORIG_INCLUDE_PATH}"
# espressif:
"${COMPONENT_DIR}/port/esp32s2/include/"
"${COMPONENT_DIR}/port/common/include"
# tusb:
"${COMPONENT_DIR}/tinyusb/hw/bsp/"
"${COMPONENT_DIR}/tinyusb/src/"
"${COMPONENT_DIR}/tinyusb/src/device"
)
set(includes_public
# tusb:
"${FREERTOS_ORIG_INCLUDE_PATH}"
"${COMPONENT_DIR}/tinyusb/src/"
# espressif:
"${COMPONENT_DIR}/additions/include")
set(srcs
# espressif:
"${COMPONENT_DIR}/additions/src/descriptors_control.c"
"${COMPONENT_DIR}/additions/src/tinyusb.c"
"${COMPONENT_DIR}/additions/src/tusb_tasks.c"
"${COMPONENT_DIR}/additions/src/usb_descriptors.c"
# tusb:
"${COMPONENT_DIR}/tinyusb/src/portable/espressif/esp32s2/dcd_esp32s2.c"
"${COMPONENT_DIR}/tinyusb/src/class/cdc/cdc_device.c"
"${COMPONENT_DIR}/tinyusb/src/class/hid/hid_device.c"
"${COMPONENT_DIR}/tinyusb/src/class/midi/midi_device.c"
"${COMPONENT_DIR}/tinyusb/src/class/msc/msc_device.c"
"${COMPONENT_DIR}/tinyusb/src/common/tusb_fifo.c"
"${COMPONENT_DIR}/tinyusb/src/device/usbd_control.c"
"${COMPONENT_DIR}/tinyusb/src/device/usbd.c"
"${COMPONENT_DIR}/tinyusb/src/tusb.c")
# cdc stuff if turned on
if(CONFIG_USB_CDC_ENABLED)
list(APPEND srcs
"${COMPONENT_DIR}/additions/src/cdc.c"
"${COMPONENT_DIR}/additions/src/tusb_cdc_acm.c"
"${COMPONENT_DIR}/additions/src/tusb_console.c"
"${COMPONENT_DIR}/additions/src/vfs_tinyusb.c")
endif()
### tinyusb lib ###
###################
add_library(tinyusb STATIC ${srcs})
target_include_directories(
tinyusb
PUBLIC ${includes_public}
PRIVATE ${includes_private})
target_compile_options(tinyusb PRIVATE ${compile_options})
target_link_libraries(${COMPONENT_TARGET} INTERFACE tinyusb)
target_sources(${COMPONENT_TARGET} INTERFACE
# espressif:
"${COMPONENT_DIR}/port/common/src/descriptors_control.c"
"${COMPONENT_DIR}/port/common/src/usb_descriptors.c"
"${COMPONENT_DIR}/port/common/src/usbd.c"
"${COMPONENT_DIR}/port/esp32s2/src/device_controller_driver.c"
"${COMPONENT_DIR}/port/esp32s2/src/tinyusb.c"
# tusb:
"${COMPONENT_DIR}/tinyusb/src/common/tusb_fifo.c"
"${COMPONENT_DIR}/tinyusb/src/device/usbd_control.c"
"${COMPONENT_DIR}/tinyusb/src/class/msc/msc_device.c"
"${COMPONENT_DIR}/tinyusb/src/class/cdc/cdc_device.c"
"${COMPONENT_DIR}/tinyusb/src/class/hid/hid_device.c"
"${COMPONENT_DIR}/tinyusb/src/class/midi/midi_device.c"
"${COMPONENT_DIR}/tinyusb/src/tusb.c"
)
endif()

View File

@ -9,19 +9,31 @@ menu "TinyUSB"
help
Adds support for TinyUSB
config USB_DEBUG
bool "Debug mode"
default n
menu "USB task configuration"
depends on USB_ENABLED
help
Debug mode
config USB_DO_NOT_CREATE_TASK
bool "Do not create a TinyUSB task"
default n
help
This option allows to not create the FreeRTOS task during the driver initialization. User will have
to handle TinyUSB events manually
config USB_TASK_PRIORITY
int "Set a priority of the TinyUSB task"
default 5
depends on !USB_DO_NOT_CREATE_TASK
help
User can change the priority of the main task according the application needs
endmenu
menu "Descriptor configuration"
depends on USB_ENABLED
config USB_DESC_USE_ESPRESSIF_VID
bool "VID: Use an Espressif's default value"
default y
depends on USB_ENABLED
help
Long description
@ -35,7 +47,6 @@ menu "TinyUSB"
config USB_DESC_USE_DEFAULT_PID
bool "PID: Use a default PID assigning"
default y
depends on USB_ENABLED
help
Default TinyUSB PID assigning uses values 0x4000...0x4007
@ -49,31 +60,78 @@ menu "TinyUSB"
config USB_DESC_BCDDEVICE
hex "bcdDevice"
default 0x0100
depends on USB_ENABLED
help
Version of the firmware of the USB device
config USB_DESC_MANUFACTURER_STRING
string "Manufacturer"
default "Espressif Systems"
depends on USB_ENABLED
help
Name of the manufacturer of the USB device
config USB_DESC_PRODUCT_STRING
string "Product"
default "Espressif Device"
depends on USB_ENABLED
help
Name of the USB device
config USB_DESC_SERIAL_STRING
string "Serial string"
default "123456"
depends on USB_ENABLED
help
Specify serial number of the USB device
config USB_DESC_CDC_STRING
string "CDC Device String"
default "Espressif CDC Device"
depends on USB_CDC_ENABLED
help
Specify name of the CDC device
config USB_DESC_MSC_STRING
string "MSC Device String"
default "Espressif MSC Device"
depends on USB_MSC_ENABLED
help
Specify name of the MSC device
config USB_DESC_HID_STRING
string "HID Device String"
default "Espressif HID Device"
depends on USB_HID_ENABLED
help
Specify name of the HID device
endmenu
config USB_CDC_ENABLED
bool "Enable USB Serial (CDC) TinyUSB driver"
default n
depends on USB_ENABLED
help
Enable USB Serial (CDC) TinyUSB driver.
config USB_CDC_RX_BUFSIZE
int "CDC FIFO size of RX"
default 64
depends on USB_CDC_ENABLED
help
CDC FIFO size of RX
config USB_CDC_TX_BUFSIZE
int "CDC FIFO size of TX"
default 64
depends on USB_CDC_ENABLED
help
CDC FIFO size of TX
config USB_DEBUG_LEVEL
int "TinyUSB log level (0-3)"
default 0
range 0 3
depends on USB_ENABLED
help
Define amount of log output from TinyUSB
endmenu

View File

@ -15,10 +15,11 @@
#pragma once
#include <stdbool.h>
#include "descriptors_control.h"
#include "board.h"
#include "tusb.h"
#include "tusb_option.h"
#include "tusb_config.h"
#include "tinyusb_types.h"
#ifdef __cplusplus
extern "C" {
@ -66,6 +67,9 @@ extern "C" {
# endif
#endif
/**
* @brief Configuration structure of the tinyUSB core
*/
typedef struct {
tusb_desc_device_t *descriptor;
char **string_descriptor;
@ -75,6 +79,7 @@ typedef struct {
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config);
// TODO esp_err_t tinyusb_driver_uninstall(void); (IDF-1474)
#ifdef __cplusplus
}
#endif

View File

@ -1,4 +1,4 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -14,17 +14,19 @@
#pragma once
#include "tusb.h"
#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
#ifdef __cplusplus
extern "C" {
#endif
#define USB_ESPRESSIF_VID 0x303A
#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 7
typedef enum{
TINYUSB_USBDEV_0,
} tinyusb_usbdev_t;
typedef char *tusb_desc_strarray_device_t[USB_STRING_DESCRIPTOR_ARRAY_SIZE];
tusb_desc_device_t descriptor_tinyusb;
tusb_desc_strarray_device_t descriptor_str_tinyusb;
tusb_desc_device_t descriptor_kconfig;
tusb_desc_strarray_device_t descriptor_str_kconfig;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,197 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "tusb.h"
#include "tinyusb.h"
/**
* @brief CDC ports available to setup
*/
typedef enum{
TINYUSB_CDC_ACM_0 = 0x0
}tinyusb_cdcacm_itf_t;
/* Callbacks and events
********************************************************************* */
/**
* @brief Data provided to the input of the `callback_rx_wanted_char` callback
*/
typedef struct {
char wanted_char;
} cdcacm_event_rx_wanted_char_data_t;
/**
* @brief Data provided to the input of the `callback_line_state_changed` callback
*/
typedef struct {
bool dtr;
bool rts;
} cdcacm_event_line_state_changed_data_t;
/**
* @brief Data provided to the input of the `line_coding_changed` callback
*/
typedef struct {
cdc_line_coding_t const *p_line_coding;
} cdcacm_event_line_coding_changed_data_t;
/**
* @brief Types of CDC ACM events
*/
typedef enum {
CDC_EVENT_RX,
CDC_EVENT_RX_WANTED_CHAR,
CDC_EVENT_LINE_STATE_CHANGED,
CDC_EVENT_LINE_CODING_CHANGED
} cdcacm_event_type_t;
/**
* @brief Describes an event passing to the input of a callbacks
*/
typedef struct {
cdcacm_event_type_t type;
union {
cdcacm_event_rx_wanted_char_data_t rx_wanted_char_data;
cdcacm_event_line_state_changed_data_t line_state_changed_data;
cdcacm_event_line_coding_changed_data_t line_coding_changed_data;
};
} cdcacm_event_t;
/**
* @brief CDC-ACM callback type
*/
typedef void(*tusb_cdcacm_callback_t)(int itf, cdcacm_event_t *event);
/*********************************************************************** Callbacks and events*/
/* Other structs
********************************************************************* */
/**
* @brief Configuration structure for CDC-ACM
*/
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< Usb device to set up */
tinyusb_cdcacm_itf_t cdc_port; /*!< CDC port */
size_t rx_unread_buf_sz; /*!< Amount of data that can be passed to the AMC at once */
tusb_cdcacm_callback_t callback_rx; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_rx_wanted_char; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_line_state_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_line_coding_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
} tinyusb_config_cdcacm_t;
/*********************************************************************** Other structs*/
/* Public functions
********************************************************************* */
/**
* @brief Initialize CDC ACM. Initialization will be finished with
* the `tud_cdc_line_state_cb` callback
*
* @param cfg - init configuration structure
* @return esp_err_t
*/
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg);
/**
* @brief Register a callback invoking on CDC event. If the callback had been
* already registered, it will be overwritten
*
* @param itf - number of a CDC object
* @param event_type - type of registered event for a callback
* @param callback - callback function
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type,
tusb_cdcacm_callback_t callback);
/**
* @brief Unregister a callback invoking on CDC event.
*
* @param itf - number of a CDC object
* @param event_type - type of registered event for a callback
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type);
/**
* @brief Sent one character to a write buffer
*
* @param itf - number of a CDC object
* @param ch - character to send
* @return size_t - amount of queued bytes
*/
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch);
/**
* @brief Write data to write buffer from a byte array
*
* @param itf - number of a CDC object
* @param in_buf - a source array
* @param in_size - size to write from arr_src
* @return size_t - amount of queued bytes
*/
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, uint8_t *in_buf, size_t in_size);
/**
* @brief Send all data from a write buffer. Use `tinyusb_cdcacm_write_queue` to add data to the buffer
*
* @param itf - number of a CDC object
* @param timeout_ticks - waiting until flush will be considered as failed
* @return esp_err_t - ESP_OK if (timeout_ticks > 0) and and flush was successful,
* ESP_ERR_TIMEOUT if timeout occurred3 or flush was successful with (timeout_ticks == 0)
* ESP_FAIL if flush was unsuccessful
*/
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks);
/**
* @brief Read a content to the array, and defines it's size to the sz_store
*
* @param itf - number of a CDC object
* @param out_buf - to this array will be stored the object from a CDC buffer
* @param out_buf_sz - size of buffer for results
* @param rx_data_size - to this address will be stored the object's size
* @return esp_err_t ESP_OK, ESP_FAIL or ESP_ERR_INVALID_STATE
*/
esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size);
/**
* @brief Check if the ACM initialized
*
* @param itf - number of a CDC object
* @return true or false
*/
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf);
/*********************************************************************** Public functions*/
#ifdef __cplusplus
}
#endif

View File

@ -25,26 +25,20 @@
*/
#pragma once
#include "tusb_option.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
#define OPT_MCU_ESP32_S2 900 // TODO remove after rebase to the last TUSB (IDF-1473)
#define CFG_TUSB_MCU OPT_MCU_ESP32_S2
/* */
/* COMMON CONFIGURATION */
/* */
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
#define CFG_TUSB_OS OPT_OS_FREERTOS
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
#define CFG_TUSB_DEBUG CONFIG_USB_DEBUG
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
@ -60,15 +54,33 @@ extern "C" {
# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4)
#endif
/* */
/* DRIVER CONFIGURATION */
/* */
//--------------------------------------------------------------------
// DEVICE CONFIGURATION
//--------------------------------------------------------------------
#define CFG_TUD_MAINTASK_SIZE 4096
#define CFG_TUD_ENDOINT0_SIZE 64
//------kconfig adaptor part -------//
// CDC FIFO size of TX and RX
#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_USB_CDC_RX_BUFSIZE
#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_USB_CDC_TX_BUFSIZE
// MSC Buffer size of Device Mass storage:
#define CFG_TUD_MSC_BUFSIZE CONFIG_USB_MSC_BUFSIZE
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_BUFSIZE CONFIG_USB_HID_BUFSIZE
#define CFG_TUD_CDC CONFIG_USB_CDC_ENABLED
#define CFG_TUD_MSC CONFIG_USB_MSC_ENABLED
#define CFG_TUD_HID CONFIG_USB_HID_ENABLED
#define CFG_TUD_MIDI CONFIG_USB_MIDI_ENABLED
#define CFG_TUD_CUSTOM_CLASS CONFIG_USB_CUSTOM_CLASS_ENABLED
/* */
/* KCONFIG */
/* */
#ifndef CONFIG_USB_CDC_ENABLED
# define CONFIG_USB_CDC_ENABLED 0
#endif
@ -89,26 +101,6 @@ extern "C" {
# define CONFIG_USB_CUSTOM_CLASS_ENABLED 0
#endif
//------------- CLASS -------------//
#define CFG_TUD_CDC CONFIG_USB_CDC_ENABLED
#define CFG_TUD_MSC CONFIG_USB_MSC_ENABLED
#define CFG_TUD_HID CONFIG_USB_HID_ENABLED
#define CFG_TUD_MIDI CONFIG_USB_MIDI_ENABLED
#define CFG_TUD_CUSTOM_CLASS CONFIG_USB_CUSTOM_CLASS_ENABLED
// CDC FIFO size of TX and RX
#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_USB_CDC_RX_BUFSIZE
#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_USB_CDC_TX_BUFSIZE
// MSC Buffer size of Device Mass storage:
#define CFG_TUD_MSC_BUFSIZE CONFIG_USB_MSC_BUFSIZE
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_BUFSIZE CONFIG_USB_HID_BUFSIZE
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,33 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_err.h"
/**
* @brief Redirect output to the USB serial
* @param cdc_intf - interface number of TinyUSB's CDC
*
* @return esp_err_t - ESP_OK, ESP_FAIL or an error code
*/
esp_err_t esp_tusb_init_console(int cdc_intf);
/**
* @brief Switch log to the default output
* @param cdc_intf - interface number of TinyUSB's CDC
*
* @return esp_err_t
*/
esp_err_t esp_tusb_deinit_console(int cdc_intf);

View File

@ -0,0 +1,44 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief This API starts a task with a wrapper function of tud_task and default task parameters.
*
* The wrapper function basically wraps tud_task and some log. Default parameters: stack size and priority as configured, argument = NULL,
* not pinned to any core.
* If you have more requirements for this task, you can create your own task which calls tud_task as the last step.
*
* @return ESP_OK or ESP_FAIL
*/
esp_err_t tusb_run_task(void);
/**
* @brief Stops a FreeRTOS task with @ref tusb_device_task
*
* @return ESP_OK or ESP_FAIL
*/
esp_err_t tusb_stop_task(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,42 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Register TinyUSB CDC at VFS with path
* @param cdc_intf - interface number of TinyUSB's CDC
* @param path - path where the CDC will be registered, `/dev/tusb_cdc` will be used if left NULL.
*
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path);
/**
* @brief Unregister TinyUSB CDC from VFS
* @param path - path where the CDC will be unregistered if NULL will be used `/dev/tusb_cdc`
*
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t esp_vfs_tusb_cdc_unregister(char const *path);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,99 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "tusb.h"
#include "tinyusb_types.h"
/* CDC classification
********************************************************************* */
typedef enum {
TINYUSB_CDC_DATA = 0x00,
} cdc_data_sublcass_type_t; // CDC120 specification
/* Note:other classification is represented in the file components\tinyusb\tinyusb\src\class\cdc\cdc.h */
/*********************************************************************** CDC classification*/
/* Structs
********************************************************************* */
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< USB device to set up */
tusb_class_code_t cdc_class; /*!< CDC device class : Communications or Data device */
union {
cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: AMC, ECM, etc. */
cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/
} cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */
} tinyusb_config_cdc_t; /*!< Main configuration structure of a CDC device */
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< USB device used for the instance */
tusb_class_code_t type;
union {
cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: AMC, ECM, etc. */
cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/
} cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */
void *subclass_obj; /*!< Dynamically allocated subclass specific object */
} esp_tusb_cdc_t;
/*********************************************************************** Structs*/
/* Functions
********************************************************************* */
/**
* @brief Initializing CDC basic object
* @param itf - number of a CDC object
* @param cfg - CDC configuration structure
*
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg);
/**
* @brief De-initializing CDC. Clean its objects
* @param itf - number of a CDC object
* @return esp_err_t ESP_OK, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE
*
*/
esp_err_t tinyusb_cdc_deinit(int itf);
/**
* @brief Checks if the CDC initialized and ready to interaction
*
* @return true or false
*/
bool tinyusb_cdc_initialized(int itf);
/**
* @brief Return interface of a CDC device
*
* @param itf_num
* @return esp_tusb_cdc_t* pointer to the interface or (NULL) on error
*/
esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num);
/*********************************************************************** Functions*/
#ifdef __cplusplus
}
#endif

View File

@ -56,7 +56,7 @@ enum {
};
enum {
CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN + CFG_TUD_MSC * TUD_MSC_DESC_LEN +
TUSB_DESC_TOTAL_LEN = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN + CFG_TUD_MSC * TUD_MSC_DESC_LEN +
CFG_TUD_HID * TUD_HID_DESC_LEN
};

View File

@ -14,36 +14,20 @@
#pragma once
#include "tusb.h"
#include "tinyusb_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
// Espressif
#include "driver/periph_ctrl.h"
#include "freertos/xtensa_api.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "soc/dport_reg.h"
#include "soc/usb_periph.h"
#include "tusb_config.h"
// TinyUSB
#include "tusb_option.h"
#include "descriptors_control.h"
#include "device/dcd.h"
#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
extern tusb_desc_device_t descriptor_tinyusb;
extern tusb_desc_strarray_device_t descriptor_str_tinyusb;
#define USB_EP_DIRECTIONS 2
#define USB_MAX_EP_NUM 16
typedef struct {
uint8_t *buffer;
uint16_t total_len;
uint16_t queued_len;
uint16_t max_size;
bool short_packet;
} xfer_ctl_t;
extern tusb_desc_device_t descriptor_kconfig;
extern tusb_desc_strarray_device_t descriptor_str_kconfig;
#ifdef __cplusplus
}

View File

@ -0,0 +1,162 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include "esp_err.h"
#include "esp_log.h"
#include "tusb.h"
#include "cdc.h"
#include "sdkconfig.h"
static const char *TAG = "tusb_cdc";
#define ESP_RETURN_ON_ERROR(x) do {esp_err_t r = (x); if (r != ESP_OK) return r;} while(0)
#define CDC_INTF_NUM CFG_TUD_CDC // number of cdc blocks
static esp_tusb_cdc_t *cdc_obj[CDC_INTF_NUM] = {};
/* Common CDC functions
********************************************************************* */
bool tinyusb_cdc_initialized(int itf)
{
return (cdc_obj[itf] != NULL);
}
static esp_err_t cdc_interface_check(int itf)
{
if (tinyusb_cdc_initialized(itf)) {
return ESP_OK;
} else {
ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
return ESP_ERR_INVALID_STATE;
}
}
/**
* @brief
*
* @param itf
* @param expected_inited
* @param expected_type use -1 if you don't care
* @return esp_err_t
*/
static esp_err_t cdc_obj_check(int itf, bool expected_inited, tusb_class_code_t expected_type)
{
bool inited = (cdc_obj[itf] != NULL);
if (expected_inited != inited) {
ESP_LOGE(TAG, "Wrong state of the interface. Expected state: %s",
expected_inited ? "initialized" : "not initialized");
return ESP_ERR_INVALID_STATE;
}
if (inited && (expected_type != -1) && !(cdc_obj[itf]->type == expected_type)) {
ESP_LOGE(TAG, "Wrong type of the interface. Should be : 0x%x (tusb_class_code_t)", expected_type);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num)
{
if (cdc_interface_check(itf_num) != ESP_OK) {
return NULL;
}
return cdc_obj[itf_num];
}
/*********************************************************************** Common CDC functions*/
/* CDC class funcs
********************************************************************* */
static esp_err_t tusb_cdc_comm_init(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1));
cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t));
if (cdc_obj[itf] != NULL) {
cdc_obj[itf]->type = TUSB_CLASS_CDC;
ESP_LOGD(TAG, "CDC Comm class initialized");
return ESP_OK;
} else {
ESP_LOGE(TAG, "CDC Comm initialization error");
return ESP_FAIL;
}
}
static esp_err_t tusb_cdc_deinit_comm(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC));
free(cdc_obj[itf]);
cdc_obj[itf] = NULL;
return ESP_OK;
}
static esp_err_t tusb_cdc_data_init(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, TUSB_CLASS_CDC_DATA));
cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t));
if (cdc_obj[itf] != NULL) {
cdc_obj[itf]->type = TUSB_CLASS_CDC_DATA;
ESP_LOGD(TAG, "CDC Data class initialized");
return ESP_OK;
} else {
ESP_LOGE(TAG, "CDC Data initialization error");
return ESP_FAIL;
}
}
static esp_err_t tusb_cdc_deinit_data(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC_DATA));
free(cdc_obj[itf]);
cdc_obj[itf] = NULL;
return ESP_OK;
}
/*********************************************************************** CDC class funcs*/
/* CDC initialization
********************************************************************* */
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg)
{
ESP_LOGD(TAG, "CDC initialization...");
if (itf != 0) {
ESP_LOGE(TAG, "There is not CDC no.%d", itf);
return ESP_ERR_INVALID_ARG;
}
if (cfg->cdc_class == TUSB_CLASS_CDC) {
ESP_RETURN_ON_ERROR(tusb_cdc_comm_init(itf));
cdc_obj[itf]->cdc_subclass.comm_subclass = cfg->cdc_subclass.comm_subclass;
} else {
ESP_RETURN_ON_ERROR(tusb_cdc_data_init(itf));
cdc_obj[itf]->cdc_subclass.data_subclass = cfg->cdc_subclass.data_subclass;
}
cdc_obj[itf]->usb_dev = cfg->usb_dev;
return ESP_OK;
}
esp_err_t tinyusb_cdc_deinit(int itf)
{
if (itf != 0) {
ESP_LOGE(TAG, "There is not CDC no.%d", itf);
return ESP_ERR_INVALID_ARG;
}
if (cdc_obj[itf]->type == TUSB_CLASS_CDC) {
ESP_RETURN_ON_ERROR(tusb_cdc_deinit_comm(itf));
} else if (cdc_obj[itf]->type == TUSB_CLASS_CDC_DATA) {
ESP_RETURN_ON_ERROR(tusb_cdc_deinit_data(itf));
} else {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGD(TAG, "De-initialized");
return ESP_OK;
}
/*********************************************************************** CDC initialization*/

View File

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "descriptors_control.h"
#include "esp_log.h"
#include "descriptors_control.h"
static const char *TAG = "TUSB:descriptors_control";
static const char *TAG = "tusb_desc";
static tusb_desc_device_t s_descriptor;
static char *s_str_descriptor[USB_STRING_DESCRIPTOR_ARRAY_SIZE];
#define MAX_DESC_BUF_SIZE 32
#if CFG_TUD_HID //HID Report Descriptor
uint8_t const desc_hid_report[] = {
@ -29,7 +29,7 @@ uint8_t const desc_hid_report[] = {
uint8_t const desc_configuration[] = {
// interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
# if CFG_TUD_CDC
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
@ -73,42 +73,41 @@ uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
return desc_configuration;
}
static uint16_t _desc_str[32];
static uint16_t _desc_str[MAX_DESC_BUF_SIZE];
/**
* @brief Invoked when received GET STRING DESCRIPTOR request.
* Application returns pointer to descriptor, whose contents must exist long
* enough for transfer to complete
*
* @param index
* @return uint16_t const*
*/
uint16_t const *tud_descriptor_string_cb(uint8_t index)
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void) langid;
uint8_t chr_count;
if (index == 0) {
if ( index == 0) {
memcpy(&_desc_str[1], s_str_descriptor[0], 2);
chr_count = 1;
} else {
// Convert ASCII string into UTF-16
if (index >= sizeof(s_str_descriptor) /
sizeof(s_str_descriptor[0])) {
if ( index >= sizeof(s_str_descriptor) / sizeof(s_str_descriptor[0]) ) {
return NULL;
}
const char *str = s_str_descriptor[index];
// Cap at max char
chr_count = strlen(str);
if (chr_count > 31) {
chr_count = 31;
if ( chr_count > MAX_DESC_BUF_SIZE - 1 ) {
chr_count = MAX_DESC_BUF_SIZE - 1;
}
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// first byte is len, second byte is string type
_desc_str[0] = TUD_DESC_STR_HEADER(chr_count);
// first byte is length (including header), second byte is string type
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2);
return _desc_str;
}

View File

@ -12,14 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "esp_rom_gpio.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#include "esp_rom_gpio.h"
#include "esp32s2/rom/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "hal/gpio_ll.h"
#include "hal/usb_hal.h"
#include "soc/gpio_periph.h"
#include "soc/usb_periph.h"
#include "tinyusb.h"
#include "descriptors_control.h"
#include "tusb.h"
#include "tusb_tasks.h"
#include "sdkconfig.h"
const static char *TAG = "TinyUSB";
static void configure_pins(usb_hal_context_t *usb)
{
@ -34,7 +44,7 @@ static void configure_pins(usb_hal_context_t *usb)
esp_rom_gpio_connect_out_signal(iopin->pin, iopin->func, false, false);
} else {
esp_rom_gpio_connect_in_signal(iopin->pin, iopin->func, false);
if ((iopin->pin != GPIO_MATRIX_CONST_ZERO_INPUT) && (iopin->pin != GPIO_MATRIX_CONST_ONE_INPUT)) {
if ((iopin->pin != GPIO_FUNC_IN_LOW) && (iopin->pin != GPIO_FUNC_IN_HIGH)) {
gpio_ll_input_enable(&GPIO, iopin->pin);
}
}
@ -62,7 +72,9 @@ static void configure_pins(usb_hal_context_t *usb)
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
{
tusb_desc_device_t *descriptor;
int res;
char **string_descriptor;
ESP_LOGI(TAG, "Driver installation...");
periph_module_reset(PERIPH_USB_MODULE);
periph_module_enable(PERIPH_USB_MODULE);
@ -90,6 +102,18 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
tusb_set_descriptor(descriptor,
string_descriptor);
ESP_ERROR_CHECK(tusb_init());
res = tusb_init();
if (res != TUSB_ERROR_NONE) {
ESP_LOGE(TAG, "Can't initialize the TinyUSB stack. TinyUSB error: %d", res);
return res;
}
#if !CONFIG_USB_DO_NOT_CREATE_TASK
res = tusb_run_task();
if (res != ESP_OK) {
ESP_LOGE(TAG, "Can't create the TinyUSB task.");
return res;
}
#endif
ESP_LOGI(TAG, "Driver installed");
return ESP_OK;
}

View File

@ -0,0 +1,412 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include "esp_err.h"
#include "esp_log.h"
#include "tusb.h"
#include "tusb_cdc_acm.h"
#include "cdc.h"
#include "sdkconfig.h"
#define ESP_RETURN_ON_ERROR(x) do{esp_err_t r = (x); if (r != ESP_OK) return r;} while(0)
#define RX_UNREADBUF_SZ_DEFAULT 64 // buffer storing all unread RX data
typedef struct {
bool initialized;
size_t rx_unread_buf_sz;
RingbufHandle_t rx_unread_buf;
uint8_t *rx_tfbuf;
tusb_cdcacm_callback_t callback_rx;
tusb_cdcacm_callback_t callback_rx_wanted_char;
tusb_cdcacm_callback_t callback_line_state_changed;
tusb_cdcacm_callback_t callback_line_coding_changed;
xTimerHandle flush_timer;
} esp_tusb_cdcacm_t; /*!< CDC_AMC object */
static const char *TAG = "tusb_cdc_acm";
static inline esp_tusb_cdcacm_t *get_acm(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
if (cdc_inst == NULL) {
return (esp_tusb_cdcacm_t *)NULL;
}
return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj);
}
/* TinyUSB callbacks
********************************************************************* */
/* Invoked when cdc when line state changed e.g connected/disconnected */
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (dtr && rts) { // connected
if (acm != NULL) {
ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf);
} else {
ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf);
return;
}
} else { // disconnected
if (acm != NULL) {
ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf);
} else {
return;
}
}
if (acm) {
tusb_cdcacm_callback_t cb = acm->callback_line_state_changed;
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_LINE_STATE_CHANGED,
.line_state_changed_data = {
.dtr = dtr,
.rts = rts
}
};
cb(itf, &event);
}
}
}
/* Invoked when CDC interface received data from host */
void tud_cdc_rx_cb(uint8_t itf)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
if (!acm->rx_unread_buf) {
ESP_LOGE(TAG, "There is no RX buffer created");
abort();
}
} else {
tud_cdc_n_read_flush(itf); // we have no place to store data, so just drop it
return;
}
while (tud_cdc_n_available(itf)) {
int read_res = tud_cdc_n_read( itf,
acm->rx_tfbuf,
CONFIG_USB_CDC_RX_BUFSIZE );
int res = xRingbufferSend(acm->rx_unread_buf,
acm->rx_tfbuf,
read_res, 0);
if (res != pdTRUE) {
ESP_LOGW(TAG, "The unread buffer is too small, the data has been lost");
} else {
ESP_LOGV(TAG, "Sent %d bytes to the buffer", read_res);
}
}
if (acm) {
tusb_cdcacm_callback_t cb = acm->callback_rx;
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_RX
};
cb(itf, &event);
}
}
}
// Invoked when line coding is change via SET_LINE_CODING
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
tusb_cdcacm_callback_t cb = acm->callback_line_coding_changed;
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_LINE_CODING_CHANGED,
.line_coding_changed_data = {
.p_line_coding = p_line_coding,
}
};
cb(itf, &event);
}
} else {
return;
}
}
// Invoked when received `wanted_char`
void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
tusb_cdcacm_callback_t cb = acm->callback_rx_wanted_char;
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_RX_WANTED_CHAR,
.rx_wanted_char_data = {
.wanted_char = wanted_char,
}
};
cb(itf, &event);
}
} else {
return;
}
}
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type,
tusb_cdcacm_callback_t callback)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
switch (event_type) {
case CDC_EVENT_RX:
acm->callback_rx = callback;
return ESP_OK;
case CDC_EVENT_RX_WANTED_CHAR:
acm->callback_rx_wanted_char = callback;
return ESP_OK;
case CDC_EVENT_LINE_STATE_CHANGED:
acm->callback_line_state_changed = callback;
return ESP_OK;
case CDC_EVENT_LINE_CODING_CHANGED:
acm->callback_line_coding_changed = callback;
return ESP_OK;
default:
ESP_LOGE(TAG, "Wrong event type");
return ESP_ERR_INVALID_ARG;
}
} else {
ESP_LOGE(TAG, "CDC-ACM is not initialized");
return ESP_ERR_INVALID_STATE;
}
}
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (!acm) {
ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
return ESP_ERR_INVALID_STATE;
}
switch (event_type) {
case CDC_EVENT_RX:
acm->callback_rx = NULL;
return ESP_OK;
case CDC_EVENT_RX_WANTED_CHAR:
acm->callback_rx_wanted_char = NULL;
return ESP_OK;
case CDC_EVENT_LINE_STATE_CHANGED:
acm->callback_line_state_changed = NULL;
return ESP_OK;
case CDC_EVENT_LINE_CODING_CHANGED:
acm->callback_line_coding_changed = NULL;
return ESP_OK;
default:
ESP_LOGE(TAG, "Wrong event type");
return ESP_ERR_INVALID_ARG;
}
}
/*********************************************************************** TinyUSB callbacks*/
/* CDC-ACM
********************************************************************* */
esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (!acm) {
ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
return ESP_ERR_INVALID_STATE;
}
uint8_t *buf = xRingbufferReceiveUpTo(acm->rx_unread_buf, rx_data_size, 0, out_buf_sz);
if (buf) {
memcpy(out_buf, buf, *rx_data_size);
vRingbufferReturnItem(acm->rx_unread_buf, (void *)buf);
return ESP_OK;
} else {
ESP_LOGE(TAG, "Failed to receive item");
return ESP_ERR_NO_MEM;
}
}
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch)
{
if (!get_acm(itf)) { // non-initialized
return 0;
}
return tud_cdc_n_write_char(itf, ch);
}
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, uint8_t *in_buf, size_t in_size)
{
if (!get_acm(itf)) { // non-initialized
return 0;
}
return tud_cdc_n_write(itf, in_buf, in_size);
}
static inline bool timer_isactive(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
return xTimerIsTimerActive(acm->flush_timer);
}
static inline esp_err_t timer_start(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
xTimerChangePeriod(acm->flush_timer, timeout_ticks, 0); // set the timer
if (!xTimerIsTimerActive(acm->flush_timer)) {
if (xTimerStart(acm->flush_timer, 0) != pdPASS) { // start
ESP_LOGE(TAG, "Can't start the timer");
return ESP_FAIL;
}
}
return ESP_OK;
}
static inline void timer_stop(tinyusb_cdcacm_itf_t itf)
{
if (timer_isactive(itf)) {
xTimerStop(get_acm(itf)->flush_timer, 0);
}
}
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
{
if (!get_acm(itf)) { // non-initialized
return ESP_FAIL;
}
if (!timeout_ticks) { // if no timeout - nonblocking mode
int res = tud_cdc_n_write_flush(itf);
if (!res) {
return ESP_FAIL;
} else {
if (tud_cdc_n_write_available(itf)) {
return ESP_FAIL;
}
}
return ESP_ERR_TIMEOUT;
} else { // if timeout use timer
ESP_RETURN_ON_ERROR(timer_start(itf, timeout_ticks));
while (1) { // loop until success or until the time runs out
if (!tud_cdc_n_write_available(itf)) { // if nothing to write - nothing to flush
break;
}
if (tud_cdc_n_write_flush(itf)) { // Success
break;
}
if (!timer_isactive(itf)) { // Time is up
ESP_LOGW(TAG, "Flush failed");
return ESP_ERR_TIMEOUT;
}
vTaskDelay(1);
}
timer_stop(itf);
return ESP_OK;
}
}
static void flush_timer_cb(xTimerHandle pxTimer)
{
ESP_LOGV(TAG, "flush_timer stopped");
xTimerStop(pxTimer, 0);
}
static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t));
if (!cdc_inst->subclass_obj) {
return ESP_FAIL;
} else {
return ESP_OK;
}
}
static void free_obj(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
free(cdc_inst->subclass_obj);
cdc_inst->subclass_obj = NULL;
}
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg)
{
int itf = (int)cfg->cdc_port;
/* Creating a CDC object */
const tinyusb_config_cdc_t cdc_cfg = {
.usb_dev = cfg->usb_dev,
.cdc_class = TUSB_CLASS_CDC,
.cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL
};
ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg));
ESP_RETURN_ON_ERROR(alloc_obj(itf));
esp_tusb_cdcacm_t *acm = get_acm(itf);
/* Callbacks setting up*/
if (cfg->callback_rx) {
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, cfg->callback_rx);
}
if (cfg->callback_rx_wanted_char) {
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX_WANTED_CHAR, cfg->callback_rx_wanted_char);
}
if (cfg->callback_line_state_changed) {
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_STATE_CHANGED, cfg->callback_line_state_changed);
}
if (cfg->callback_line_coding_changed) {
tinyusb_cdcacm_register_callback( itf, CDC_EVENT_LINE_CODING_CHANGED, cfg->callback_line_coding_changed);
}
/* SW timer*/
acm->flush_timer = xTimerCreate(
"flush_timer", 10 / portTICK_PERIOD_MS, pdTRUE, (void *)itf, flush_timer_cb);
/* Buffers */
acm->rx_tfbuf = malloc(CONFIG_USB_CDC_RX_BUFSIZE);
if (!acm->rx_tfbuf) {
ESP_LOGE(TAG, "Creation buffer error");
free_obj(itf);
return ESP_ERR_NO_MEM;
}
acm->rx_unread_buf_sz = cfg->rx_unread_buf_sz == 0 ? RX_UNREADBUF_SZ_DEFAULT : cfg->rx_unread_buf_sz;
acm->rx_unread_buf = xRingbufferCreate(acm->rx_unread_buf_sz, RINGBUF_TYPE_BYTEBUF);
if (acm->rx_unread_buf == NULL) {
ESP_LOGE(TAG, "Creation buffer error");
free_obj(itf);
return ESP_ERR_NO_MEM;
} else {
ESP_LOGD(TAG, "Comm Initialized buff:%d bytes", cfg->rx_unread_buf_sz);
return ESP_OK;
}
}
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
return true;
} else {
return false;
}
}
/*********************************************************************** CDC-ACM*/

View File

@ -0,0 +1,142 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdio_ext.h>
#include "esp_log.h"
#include "cdc.h"
#include "tusb_console.h"
#include "tinyusb.h"
#include "vfs_tinyusb.h"
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
static const char *TAG = "tusb_console";
typedef struct {
FILE *in;
FILE *out;
FILE *err;
} console_handle_t;
static console_handle_t con;
/**
* @brief Reopen standard streams using a new path
*
* @param f_in - pointer to a pointer holding a file for in or NULL to don't change stdin
* @param f_out - pointer to a pointer holding a file for out or NULL to don't change stdout
* @param f_err - pointer to a pointer holding a file for err or NULL to don't change stderr
* @param path - mount point
* @return esp_err_t ESP_FAIL or ESP_OK
*/
static esp_err_t redirect_std_streams_to(FILE **f_in, FILE **f_out, FILE **f_err, const char *path)
{
if (f_in) {
*f_in = freopen(path, "r", stdin);
if (*f_in == NULL) {
ESP_LOGE(TAG, "Failed to reopen in!");
return ESP_FAIL;
}
}
if (f_out) {
*f_out = freopen(path, "w", stdout);
if (*f_out == NULL) {
ESP_LOGE(TAG, "Failed to reopen out!");
return ESP_FAIL;
}
}
if (f_err) {
*f_err = freopen(path, "w", stderr);
if (*f_err == NULL) {
ESP_LOGE(TAG, "Failed to reopen err!");
return ESP_FAIL;
}
}
return ESP_OK;
}
/**
* @brief Restore output to default
*
* @param f_in - pointer to a pointer of an in file updated with `redirect_std_streams_to` or NULL to don't change stdin
* @param f_out - pointer to a pointer of an out file updated with `redirect_std_streams_to` or NULL to don't change stdout
* @param f_err - pointer to a pointer of an err file updated with `redirect_std_streams_to` or NULL to don't change stderr
* @return esp_err_t ESP_FAIL or ESP_OK
*/
static esp_err_t restore_std_streams(FILE **f_in, FILE **f_out, FILE **f_err)
{
const char *default_uart_dev = "/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM);
if (f_in) {
stdin = freopen(default_uart_dev, "r", *f_in);
if (stdin == NULL) {
ESP_LOGE(TAG, "Failed to reopen stdin!");
return ESP_FAIL;
}
}
if (f_out) {
stdout = freopen(default_uart_dev, "w", *f_out);
if (stdout == NULL) {
ESP_LOGE(TAG, "Failed to reopen stdout!");
return ESP_FAIL;
}
}
if (f_err) {
stderr = freopen(default_uart_dev, "w", *f_err);
if (stderr == NULL) {
ESP_LOGE(TAG, "Failed to reopen stderr!");
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t esp_tusb_init_console(int cdc_intf)
{
if (!tinyusb_cdc_initialized(cdc_intf)) {
ESP_LOGE(TAG, "Can't init the console because TinyUSB's CDC is not initialized!");
return ESP_ERR_INVALID_STATE;
}
/* Registering TUSB at VFS */
int res = esp_vfs_tusb_cdc_register(cdc_intf, NULL);
if (res != ESP_OK) {
return res;
}
res = redirect_std_streams_to(&con.in, &con.out, &con.err, "/dev/tusb_cdc");
if (res != ESP_OK) {
return res;
}
return ESP_OK;
}
esp_err_t esp_tusb_deinit_console(int cdc_intf)
{
if (!tinyusb_cdc_initialized(cdc_intf)) {
ESP_LOGE(TAG, "Can't deinit the console because TinyUSB's CDC is not initialized!");
return ESP_ERR_INVALID_STATE;
}
int res = restore_std_streams(&con.in, &con.out, &con.err);
if (res != ESP_OK) {
return res;
}
esp_vfs_tusb_cdc_unregister(NULL);
return ESP_OK;
}

View File

@ -0,0 +1,62 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "hal/usb_hal.h"
#include "tinyusb.h"
#include "tusb_tasks.h"
#include "sdkconfig.h"
const static char *TAG = "tusb_tsk";
static TaskHandle_t s_tusb_tskh;
/**
* @brief This top level thread processes all usb events and invokes callbacks
*/
static void tusb_device_task(void *arg)
{
ESP_LOGD(TAG, "tinyusb task started");
while (1) { // RTOS forever loop
tud_task();
}
}
esp_err_t tusb_run_task(void)
{
// Create a task for tinyusb device stack:
xTaskCreate(tusb_device_task, "tinyUSB: main task", CFG_TUD_MAINTASK_SIZE, NULL, CONFIG_USB_TASK_PRIORITY, &s_tusb_tskh);
if (!s_tusb_tskh) {
return ESP_FAIL;
} else {
return ESP_OK;
}
}
esp_err_t tusb_stop_task(void)
{
if ( s_tusb_tskh != NULL ) {
vTaskDelete(s_tusb_tskh);
} else {
ESP_LOGE(TAG, "tinyusb task is not started");
return ESP_FAIL;
}
if (s_tusb_tskh) {
return ESP_FAIL;
} else {
return ESP_OK;
}
}

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sdkconfig.h"
#include "usb_descriptors.h"
#include "sdkconfig.h"
#define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | _PID_MAP(MIDI, 3))
@ -45,7 +45,8 @@ tusb_desc_device_t descriptor_tinyusb = {
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
.bNumConfigurations = 0x01
};
tusb_desc_strarray_device_t descriptor_str_tinyusb = {
// array of pointer to string descriptors
@ -97,7 +98,8 @@ tusb_desc_device_t descriptor_kconfig = {
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
.bNumConfigurations = 0x01
};
tusb_desc_strarray_device_t descriptor_str_kconfig = {
// array of pointer to string descriptors
@ -125,4 +127,4 @@ tusb_desc_strarray_device_t descriptor_str_kconfig = {
#endif
};
/* End of Kconfig driven Descriptor */
/* End of Kconfig driven Descriptor */

View File

@ -0,0 +1,296 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "vfs_tinyusb.h"
#include "sdkconfig.h"
const static char *TAG = "tusb_vfs";
#define VFS_TUSB_MAX_PATH 16
#define VFS_TUSB_PATH_DEFAULT "/dev/tusb_cdc"
// Token signifying that no character is available
#define NONE -1
#define FD_CHECK(fd, ret_val) do { \
if ((fd) != 0) { \
errno = EBADF; \
return (ret_val); \
} \
} while (0)
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CRLF
#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CR
#else
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_LF
#endif
#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CRLF
#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CR
#else
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_LF
#endif
typedef struct {
_lock_t write_lock;
_lock_t read_lock;
esp_line_endings_t tx_mode; // Newline conversion mode when transmitting
esp_line_endings_t rx_mode; // Newline conversion mode when receiving
uint32_t flags;
char vfs_path[VFS_TUSB_MAX_PATH];
int cdc_intf;
} vfs_tinyusb_t;
static vfs_tinyusb_t s_vfstusb;
static esp_err_t apply_path(char const *path)
{
if (path != NULL) {
size_t path_len = strlen(path) + 1;
if (path_len > VFS_TUSB_MAX_PATH) {
ESP_LOGE(TAG, "The path is too long; maximum is %d characters", VFS_TUSB_MAX_PATH);
return ESP_ERR_INVALID_ARG;
}
strncpy(s_vfstusb.vfs_path, path, path_len);
} else {
strncpy(s_vfstusb.vfs_path,
VFS_TUSB_PATH_DEFAULT,
strlen(VFS_TUSB_PATH_DEFAULT) + 1);
}
ESP_LOGV(TAG, "Path is set to `%s`", s_vfstusb.vfs_path);
return ESP_OK;
}
/**
* @brief Fill s_vfstusb
*
* @param cdc_intf - interface of tusb for registration
* @param path - a path where the CDC will be registered
* @return esp_err_t ESP_OK or ESP_ERR_INVALID_ARG
*/
static esp_err_t vfstusb_init(int cdc_intf, char const *path)
{
s_vfstusb.cdc_intf = cdc_intf;
s_vfstusb.tx_mode = DEFAULT_TX_MODE;
s_vfstusb.rx_mode = DEFAULT_RX_MODE;
return apply_path(path);
}
/**
* @brief Clear s_vfstusb to default values
*/
static void vfstusb_deinit(void)
{
memset(&s_vfstusb, 0, sizeof(s_vfstusb));
}
static int tusb_open(const char *path, int flags, int mode)
{
(void) mode;
(void) path;
s_vfstusb.flags = flags | O_NONBLOCK; // for now only non-blocking mode is implemented
return 0;
}
static ssize_t tusb_write(int fd, const void *data, size_t size)
{
FD_CHECK(fd, -1);
size_t written_sz = 0;
const char *data_c = (const char *)data;
_lock_acquire(&(s_vfstusb.write_lock));
for (size_t i = 0; i < size; i++) {
int c = data_c[i];
/* handling the EOL */
if (c == '\n' && s_vfstusb.tx_mode != ESP_LINE_ENDINGS_LF) {
if (tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, '\r')) {
written_sz++;
} else {
break; // can't write anymore
}
if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CR) {
continue;
}
}
/* write a char */
if (tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, c)) {
written_sz++;
} else {
break; // can't write anymore
}
}
tinyusb_cdcacm_write_flush(s_vfstusb.cdc_intf, 0);
_lock_release(&(s_vfstusb.write_lock));
return written_sz;
}
static int tusb_close(int fd)
{
FD_CHECK(fd, -1);
return 0;
}
static ssize_t tusb_read(int fd, void *data, size_t size)
{
FD_CHECK(fd, -1);
char *data_c = (char *) data;
size_t received = 0;
_lock_acquire(&(s_vfstusb.read_lock));
int cm1 = NONE;
int c = NONE;
while (received < size) {
cm1 = c; // store the old char
int c = tud_cdc_n_read_char(0); // get a new one
if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CR) {
if (c == '\r') {
c = '\n';
}
} else if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CR) {
if ((c == '\n') & (cm1 == '\r')) {
--received; // step back
c = '\n';
}
}
if ( c == NONE) { // if data ends
break;
}
data_c[received] = (char) c;
++received;
if (c == '\n') {
break;
}
}
_lock_release(&(s_vfstusb.read_lock));
if (received > 0) {
return received;
}
errno = EWOULDBLOCK;
return -1;
}
static int tusb_fstat(int fd, struct stat *st)
{
FD_CHECK(fd, -1);
st->st_mode = S_IFCHR;
return 0;
}
static int tusb_fcntl(int fd, int cmd, int arg)
{
FD_CHECK(fd, -1);
int result = 0;
switch (cmd) {
case F_GETFL:
result = s_vfstusb.flags;
break;
case F_SETFL:
s_vfstusb.flags = arg;
break;
default:
result = -1;
errno = ENOSYS;
break;
}
return result;
}
esp_err_t esp_vfs_tusb_cdc_unregister(char const *path)
{
ESP_LOGD(TAG, "Unregistering TinyUSB driver");
int res;
if (path == NULL) { // NULL means using the default path for unregistering: VFS_TUSB_PATH_DEFAULT
res = strcmp(s_vfstusb.vfs_path, VFS_TUSB_PATH_DEFAULT);
} else {
res = strcmp(s_vfstusb.vfs_path, path);
}
if (res) {
res = ESP_ERR_INVALID_ARG;
ESP_LOGE(TAG, "There is no TinyUSB driver registerred to the path '%s' (err: 0x%x)", s_vfstusb.vfs_path, res);
return res;
}
res = esp_vfs_unregister(s_vfstusb.vfs_path);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Can't unregister TinyUSB driver from '%s' (err: 0x%x)", s_vfstusb.vfs_path, res);
} else {
ESP_LOGD(TAG, "Unregistered TinyUSB driver");
vfstusb_deinit();
}
return res;
}
esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path)
{
ESP_LOGD(TAG, "Registering TinyUSB CDC driver");
int res;
if (!tusb_cdc_acm_initialized(cdc_intf)) {
ESP_LOGE(TAG, "TinyUSB CDC#%d is not initialized", cdc_intf);
return ESP_ERR_INVALID_STATE;
}
res = vfstusb_init(cdc_intf, path);
if (res != ESP_OK) {
return res;
}
esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
.close = &tusb_close,
.fcntl = &tusb_fcntl,
.fstat = &tusb_fstat,
.open = &tusb_open,
.read = &tusb_read,
.write = &tusb_write,
};
res = esp_vfs_register(s_vfstusb.vfs_path, &vfs, NULL);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Can't register TinyUSB driver (err: %x)", res);
} else {
ESP_LOGD(TAG, "TinyUSB CDC registered (%s)", s_vfstusb.vfs_path);
}
return res;
}

View File

@ -1,830 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org),
* 2020 Espressif Systems (Shanghai) Co. Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* This file is part of the TinyUSB stack.
*/
#include "tusb_option.h"
#if TUSB_OPT_DEVICE_ENABLED
#include "tusb.h"
#include "usbd.h"
#include "device/usbd_pvt.h"
#include "dcd.h"
#include "esp_log.h"
static const char *TAG = "TUSB:device";
#ifndef CFG_TUD_TASK_QUEUE_SZ
#define CFG_TUD_TASK_QUEUE_SZ 16
#endif
//--------------------------------------------------------------------+
// Device Data
//--------------------------------------------------------------------+
typedef struct {
struct TU_ATTR_PACKED {
volatile uint8_t connected : 1;
volatile uint8_t configured : 1;
volatile uint8_t suspended : 1;
uint8_t remote_wakeup_en : 1; // enable/disable by host
uint8_t remote_wakeup_support : 1; // configuration descriptor's attribute
uint8_t self_powered : 1; // configuration descriptor's attribute
};
uint8_t ep_busy_map[2]; // bit mask for busy endpoint
uint8_t ep_stall_map[2]; // bit map for stalled endpoint
uint8_t itf2drv[16]; // map interface number to driver (0xff is invalid)
uint8_t ep2drv[8][2]; // map endpoint to driver ( 0xff is invalid )
} usbd_device_t;
static usbd_device_t _usbd_dev = {0};
//--------------------------------------------------------------------+
// Class Driver
//--------------------------------------------------------------------+
typedef struct {
uint8_t class_code;
void (*init)(void);
bool (*open)(uint8_t rhport, tusb_desc_interface_t const *desc_intf, uint16_t *p_length);
bool (*control_request)(uint8_t rhport, tusb_control_request_t const *request);
bool (*control_request_complete)(uint8_t rhport, tusb_control_request_t const *request);
bool (*xfer_cb)(uint8_t rhport, uint8_t ep_addr, xfer_result_t, uint32_t);
void (*sof)(uint8_t rhport);
void (*reset)(uint8_t);
} usbd_class_driver_t;
static usbd_class_driver_t const usbd_class_drivers[] = {
#if CFG_TUD_CDC
{
.class_code = TUSB_CLASS_CDC,
.init = cdcd_init,
.open = cdcd_open,
.control_request = cdcd_control_request,
.control_request_complete = cdcd_control_request_complete,
.xfer_cb = cdcd_xfer_cb,
.sof = NULL,
.reset = cdcd_reset
},
#endif
#if CFG_TUD_MSC
{
.class_code = TUSB_CLASS_MSC,
.init = mscd_init,
.open = mscd_open,
.control_request = mscd_control_request,
.control_request_complete = mscd_control_request_complete,
.xfer_cb = mscd_xfer_cb,
.sof = NULL,
.reset = mscd_reset
},
#endif
#if CFG_TUD_HID
{
.class_code = TUSB_CLASS_HID,
.init = hidd_init,
.open = hidd_open,
.control_request = hidd_control_request,
.control_request_complete = hidd_control_request_complete,
.xfer_cb = hidd_xfer_cb,
.sof = NULL,
.reset = hidd_reset
},
#endif
#if CFG_TUD_MIDI
{
.class_code = TUSB_CLASS_AUDIO,
.init = midid_init,
.open = midid_open,
.control_request = midid_control_request,
.control_request_complete = midid_control_request_complete,
.xfer_cb = midid_xfer_cb,
.sof = NULL,
.reset = midid_reset
},
#endif
#if CFG_TUD_CUSTOM_CLASS
{
.class_code = TUSB_CLASS_VENDOR_SPECIFIC,
.init = cusd_init,
.open = cusd_open,
.control_request = cusd_control_request,
.control_request_complete = cusd_control_request_complete,
.xfer_cb = cusd_xfer_cb,
.sof = NULL,
.reset = cusd_reset
},
#endif
};
enum {
USBD_CLASS_DRIVER_COUNT = TU_ARRAY_SZIE(usbd_class_drivers)
};
//--------------------------------------------------------------------+
// DCD Event
//--------------------------------------------------------------------+
// Event queue
// OPT_MODE_DEVICE is used by OS NONE for mutex (disable usb isr)
OSAL_QUEUE_DEF(OPT_MODE_DEVICE, _usbd_qdef, CFG_TUD_TASK_QUEUE_SZ, dcd_event_t);
static osal_queue_t _usbd_q;
//--------------------------------------------------------------------+
// Prototypes
//--------------------------------------------------------------------+
static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const *p_desc, uint16_t desc_len, uint8_t driver_id);
static bool process_control_request(uint8_t rhport, tusb_control_request_t const *p_request);
static bool process_set_config(uint8_t rhport, uint8_t cfg_num);
static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const *p_request);
void usbd_control_reset(uint8_t rhport);
bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
void usbd_control_set_complete_callback(bool (*fp)(uint8_t, tusb_control_request_t const *));
//--------------------------------------------------------------------+
// Application API
//--------------------------------------------------------------------+
bool tud_mounted(void)
{
return _usbd_dev.configured;
}
bool tud_suspended(void)
{
return _usbd_dev.suspended;
}
bool tud_remote_wakeup(void)
{
// only wake up host if this feature is supported and enabled and we are suspended
TU_VERIFY(_usbd_dev.suspended && _usbd_dev.remote_wakeup_support && _usbd_dev.remote_wakeup_en);
dcd_remote_wakeup(TUD_OPT_RHPORT);
return true;
}
//--------------------------------------------------------------------+
// USBD Task
//--------------------------------------------------------------------+
bool usbd_init(void)
{
tu_varclr(&_usbd_dev);
// Init device queue & task
ESP_LOGV(TAG, "Init device queue & task...");
_usbd_q = osal_queue_create(&_usbd_qdef);
TU_ASSERT(_usbd_q != NULL);
ESP_LOGV(TAG, "Init device queue & task: Done");
// Init class drivers
# if USBD_CLASS_DRIVER_COUNT
for (uint8_t i = 0; i < USBD_CLASS_DRIVER_COUNT; i++) {
usbd_class_drivers[i].init();
}
# endif
// Init device controller driver
ESP_LOGV(TAG, "dcd_init...");
dcd_init(TUD_OPT_RHPORT);
ESP_LOGV(TAG, "dcd_init: Done");
ESP_LOGV(TAG, "dcd_int_enable...");
dcd_int_enable(TUD_OPT_RHPORT);
ESP_LOGV(TAG, "dcd_int_enable: Done");
return true;
}
static void usbd_reset(uint8_t rhport)
{
tu_varclr(&_usbd_dev);
memset(_usbd_dev.itf2drv, 0xff, sizeof(_usbd_dev.itf2drv)); // invalid mapping
memset(_usbd_dev.ep2drv, 0xff, sizeof(_usbd_dev.ep2drv)); // invalid mapping
usbd_control_reset(rhport);
# if USBD_CLASS_DRIVER_COUNT
for (uint8_t i = 0; i < USBD_CLASS_DRIVER_COUNT; i++) {
if (usbd_class_drivers[i].reset) {
usbd_class_drivers[i].reset(rhport);
}
}
# endif
}
/* USB Device Driver task
* This top level thread manages all device controller event and delegates events to class-specific drivers.
* This should be called periodically within the mainloop or rtos thread.
*
@code
int main(void)
{
application_init();
tusb_init();
while(1) // the mainloop
{
application_code();
tud_task(); // tinyusb device task
}
}
@endcode
*/
void tud_task(void)
{
// Skip if stack is not initialized
bool tusb_ready = tusb_inited();
if (!tusb_ready) {
ESP_LOGV(TAG, "is not ready");
return;
}
ESP_LOGV(TAG, "started");
// Loop until there is no more events in the queue
while (1) {
dcd_event_t event;
volatile bool ev = osal_queue_receive(_usbd_q, &event);
if (!ev) {
ESP_LOGV(TAG, "USB EVENT ...empty...");
return;
}
ESP_LOGV(TAG, "USB EVENT: %u", event.event_id);
switch (event.event_id) {
case DCD_EVENT_BUS_RESET:
ESP_LOGV(TAG, "USB EVENT bus_reset");
usbd_reset(event.rhport);
break;
case DCD_EVENT_UNPLUGGED:
ESP_LOGV(TAG, "USB EVENT unplugged");
usbd_reset(event.rhport);
// invoke callback
if (tud_umount_cb) {
tud_umount_cb();
}
break;
case DCD_EVENT_SETUP_RECEIVED:
ESP_LOGV(TAG, "USB EVENT setup_received");
// Mark as connected after receiving 1st setup packet.
// But it is easier to set it every time instead of wasting time to check then set
_usbd_dev.connected = 1;
// Process control request
if (!process_control_request(event.rhport, &event.setup_received)) {
// Failed -> stall both control endpoint IN and OUT
dcd_edpt_stall(event.rhport, 0);
dcd_edpt_stall(event.rhport, 0 | TUSB_DIR_IN_MASK);
}
break;
case DCD_EVENT_XFER_COMPLETE:
// Only handle xfer callback in ready state
// if (_usbd_dev.connected && !_usbd_dev.suspended)
ESP_LOGV(TAG, "USB EVENT xfer_complete");
{
// Invoke the class callback associated with the endpoint address
uint8_t const ep_addr = event.xfer_complete.ep_addr;
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
_usbd_dev.ep_busy_map[dir] = (uint8_t)tu_bit_clear(_usbd_dev.ep_busy_map[dir], epnum);
if (0 == tu_edpt_number(ep_addr)) {
// control transfer DATA stage callback
usbd_control_xfer_cb(event.rhport, ep_addr, event.xfer_complete.result, event.xfer_complete.len);
} else {
uint8_t const drv_id = _usbd_dev.ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)];
# if USBD_CLASS_DRIVER_COUNT
TU_ASSERT(drv_id < USBD_CLASS_DRIVER_COUNT, );
# endif
usbd_class_drivers[drv_id].xfer_cb(event.rhport, ep_addr, event.xfer_complete.result, event.xfer_complete.len);
}
}
break;
case DCD_EVENT_SUSPEND:
ESP_LOGV(TAG, "USB EVENT suspend");
if (tud_suspend_cb) {
tud_suspend_cb(_usbd_dev.remote_wakeup_en);
}
break;
case DCD_EVENT_RESUME:
ESP_LOGV(TAG, "USB EVENT resume");
if (tud_resume_cb) {
tud_resume_cb();
}
break;
case DCD_EVENT_SOF:
ESP_LOGV(TAG, "USB EVENT sof");
# if USBD_CLASS_DRIVER_COUNT
for (uint8_t i = 0; i < USBD_CLASS_DRIVER_COUNT; i++) {
if (usbd_class_drivers[i].sof) {
usbd_class_drivers[i].sof(event.rhport);
}
}
# endif
break;
case USBD_EVENT_FUNC_CALL:
ESP_LOGV(TAG, "USB EVENT func_call");
if (event.func_call.func) {
event.func_call.func(event.func_call.param);
}
break;
default:
ESP_LOGV(TAG, "USB EVENT unknown");
TU_BREAKPOINT();
break;
}
}
}
//--------------------------------------------------------------------+
// Control Request Parser & Handling
//--------------------------------------------------------------------+
// This handles the actual request and its response.
// return false will cause its caller to stall control endpoint
static bool process_control_request(uint8_t rhport, tusb_control_request_t const *p_request)
{
usbd_control_set_complete_callback(NULL);
switch (p_request->bmRequestType_bit.recipient) {
//------------- Device Requests e.g in enumeration -------------//
case TUSB_REQ_RCPT_DEVICE:
if (TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type) {
// Non standard request is not supported
TU_BREAKPOINT();
return false;
}
switch (p_request->bRequest) {
case TUSB_REQ_SET_ADDRESS:
ESP_LOGV(TAG, "TUSB_REQ_SET_ADDRESS");
// Depending on mcu, status phase could be sent either before or after changing device address
// Therefore DCD must include zero-length status response
dcd_set_address(rhport, (uint8_t)p_request->wValue);
return true; // skip status
break;
case TUSB_REQ_GET_CONFIGURATION: {
ESP_LOGV(TAG, "TUSB_REQ_GET_CONFIGURATION");
uint8_t cfgnum = _usbd_dev.configured ? 1 : 0;
usbd_control_xfer(rhport, p_request, &cfgnum, 1);
}
break;
case TUSB_REQ_SET_CONFIGURATION: {
ESP_LOGV(TAG, "TUSB_REQ_SET_CONFIGURATION");
uint8_t const cfg_num = (uint8_t)p_request->wValue;
dcd_set_config(rhport, cfg_num);
_usbd_dev.configured = cfg_num ? 1 : 0;
if (cfg_num) {
TU_ASSERT(process_set_config(rhport, cfg_num));
}
usbd_control_status(rhport, p_request);
}
break;
case TUSB_REQ_GET_DESCRIPTOR:
ESP_LOGV(TAG, "TUSB_REQ_GET_DESCRIPTOR");
TU_VERIFY(process_get_descriptor(rhport, p_request));
break;
case TUSB_REQ_SET_FEATURE:
ESP_LOGV(TAG, "TUSB_REQ_SET_FEATURE");
// Only support remote wakeup for device feature
TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue);
// Host may enable remote wake up before suspending especially HID device
_usbd_dev.remote_wakeup_en = true;
usbd_control_status(rhport, p_request);
break;
case TUSB_REQ_CLEAR_FEATURE:
ESP_LOGV(TAG, "TUSB_REQ_CLEAR_FEATURE");
// Only support remote wakeup for device feature
TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue);
// Host may disable remote wake up after resuming
_usbd_dev.remote_wakeup_en = false;
usbd_control_status(rhport, p_request);
break;
case TUSB_REQ_GET_STATUS: {
ESP_LOGV(TAG, "TUSB_REQ_GET_STATUS");
// Device status bit mask
// - Bit 0: Self Powered
// - Bit 1: Remote Wakeup enabled
uint16_t status = (_usbd_dev.self_powered ? 1 : 0) | (_usbd_dev.remote_wakeup_en ? 2 : 0);
usbd_control_xfer(rhport, p_request, &status, 2);
}
break;
// Unknown/Unsupported request
default:
TU_BREAKPOINT();
return false;
}
break;
//------------- Class/Interface Specific Request -------------//
case TUSB_REQ_RCPT_INTERFACE: {
uint8_t const itf = tu_u16_low(p_request->wIndex);
uint8_t const drvid = _usbd_dev.itf2drv[itf];
# if USBD_CLASS_DRIVER_COUNT
TU_VERIFY(drvid < USBD_CLASS_DRIVER_COUNT);
# endif
usbd_control_set_complete_callback(usbd_class_drivers[drvid].control_request_complete);
// stall control endpoint if driver return false
return usbd_class_drivers[drvid].control_request(rhport, p_request);
}
break;
//------------- Endpoint Request -------------//
case TUSB_REQ_RCPT_ENDPOINT:
// Non standard request is not supported
TU_VERIFY(TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type);
switch (p_request->bRequest) {
case TUSB_REQ_GET_STATUS: {
uint16_t status = usbd_edpt_stalled(rhport, tu_u16_low(p_request->wIndex)) ? 0x0001 : 0x0000;
usbd_control_xfer(rhport, p_request, &status, 2);
}
break;
case TUSB_REQ_CLEAR_FEATURE:
if (TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue) {
usbd_edpt_clear_stall(rhport, tu_u16_low(p_request->wIndex));
}
usbd_control_status(rhport, p_request);
break;
case TUSB_REQ_SET_FEATURE:
if (TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue) {
usbd_edpt_stall(rhport, tu_u16_low(p_request->wIndex));
}
usbd_control_status(rhport, p_request);
break;
// Unknown/Unsupported request
default:
TU_BREAKPOINT();
return false;
}
break;
// Unknown recipient
default:
TU_BREAKPOINT();
return false;
}
return true;
}
// Process Set Configure Request
// This function parse configuration descriptor & open drivers accordingly
static bool process_set_config(uint8_t rhport, uint8_t cfg_num)
{
tusb_desc_configuration_t const *desc_cfg = (tusb_desc_configuration_t const *)tud_descriptor_configuration_cb(cfg_num - 1); // index is cfg_num-1
TU_ASSERT(desc_cfg != NULL && desc_cfg->bDescriptorType == TUSB_DESC_CONFIGURATION);
// Parse configuration descriptor
_usbd_dev.remote_wakeup_support = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP) ? 1 : 0;
_usbd_dev.self_powered = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_SELF_POWERED) ? 1 : 0;
// Parse interface descriptor
uint8_t const *p_desc = ((uint8_t const *)desc_cfg) + sizeof(tusb_desc_configuration_t);
uint8_t const *desc_end = ((uint8_t const *)desc_cfg) + desc_cfg->wTotalLength;
while (p_desc < desc_end) {
// Each interface always starts with Interface or Association descriptor
if (TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc)) {
p_desc = tu_desc_next(p_desc); // ignore Interface Association
} else {
TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc));
tusb_desc_interface_t *desc_itf = (tusb_desc_interface_t *)p_desc;
// Check if class is supported
uint8_t drv_id = 0;
# if USBD_CLASS_DRIVER_COUNT
for (; drv_id < USBD_CLASS_DRIVER_COUNT; drv_id++) {
if (usbd_class_drivers[drv_id].class_code == desc_itf->bInterfaceClass) {
break;
}
}
# endif
// Interface number must not be used already TODO alternate interface
TU_ASSERT(0xff == _usbd_dev.itf2drv[desc_itf->bInterfaceNumber]);
_usbd_dev.itf2drv[desc_itf->bInterfaceNumber] = drv_id;
uint16_t itf_len = 0;
TU_ASSERT(usbd_class_drivers[drv_id].open(rhport, desc_itf, &itf_len));
TU_ASSERT(itf_len >= sizeof(tusb_desc_interface_t));
mark_interface_endpoint(_usbd_dev.ep2drv, p_desc, itf_len, drv_id);
p_desc += itf_len; // next interface
}
}
// invoke callback
if (tud_mount_cb) {
tud_mount_cb();
}
return true;
}
// Helper marking endpoint of interface belongs to class driver
static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const *p_desc, uint16_t desc_len, uint8_t driver_id)
{
uint16_t len = 0;
while (len < desc_len) {
if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
uint8_t const ep_addr = ((tusb_desc_endpoint_t const *)p_desc)->bEndpointAddress;
ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = driver_id;
}
len += tu_desc_len(p_desc);
p_desc = tu_desc_next(p_desc);
}
}
// return descriptor's buffer and update desc_len
static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const *p_request)
{
tusb_desc_type_t const desc_type = (tusb_desc_type_t)tu_u16_high(p_request->wValue);
uint8_t const desc_index = tu_u16_low(p_request->wValue);
switch (desc_type) {
case TUSB_DESC_DEVICE:
return usbd_control_xfer(rhport, p_request, (void *)tud_descriptor_device_cb(), sizeof(tusb_desc_device_t));
break;
case TUSB_DESC_CONFIGURATION: {
tusb_desc_configuration_t const *desc_config = (tusb_desc_configuration_t const *)tud_descriptor_configuration_cb(desc_index);
return usbd_control_xfer(rhport, p_request, (void *)desc_config, desc_config->wTotalLength);
}
break;
case TUSB_DESC_STRING:
// String Descriptor always uses the desc set from user
if (desc_index == 0xEE) {
// The 0xEE index string is a Microsoft USB extension.
// It can be used to tell Windows what driver it should use for the device !!!
return false;
} else {
uint8_t const *desc_str = (uint8_t const *)tud_descriptor_string_cb(desc_index);
TU_ASSERT(desc_str);
// first byte of descriptor is its size
return usbd_control_xfer(rhport, p_request, (void *)desc_str, desc_str[0]);
}
break;
case TUSB_DESC_DEVICE_QUALIFIER:
return false;
break;
default:
return false;
}
return true;
}
//--------------------------------------------------------------------+
// DCD Event Handler
//--------------------------------------------------------------------+
void dcd_event_handler(dcd_event_t const *event, bool in_isr)
{
switch (event->event_id) {
case DCD_EVENT_BUS_RESET:
osal_queue_send(_usbd_q, event, in_isr);
break;
case DCD_EVENT_UNPLUGGED:
_usbd_dev.connected = 0;
_usbd_dev.configured = 0;
_usbd_dev.suspended = 0;
osal_queue_send(_usbd_q, event, in_isr);
break;
case DCD_EVENT_SOF:
// nothing to do now
break;
case DCD_EVENT_SUSPEND:
// NOTE: When plugging/unplugging device, the D+/D- state are unstable and can accidentally meet the
// SUSPEND condition ( Idle for 3ms ). Some MCUs such as SAMD doesn't distinguish suspend vs disconnect as well.
// We will skip handling SUSPEND/RESUME event if not currently connected
if (_usbd_dev.connected) {
_usbd_dev.suspended = 1;
osal_queue_send(_usbd_q, event, in_isr);
}
break;
case DCD_EVENT_RESUME:
if (_usbd_dev.connected) {
_usbd_dev.suspended = 0;
osal_queue_send(_usbd_q, event, in_isr);
}
break;
case DCD_EVENT_SETUP_RECEIVED:
osal_queue_send(_usbd_q, event, in_isr);
break;
case DCD_EVENT_XFER_COMPLETE:
// skip zero-length control status complete event, should DCD notify us.
if ((0 == tu_edpt_number(event->xfer_complete.ep_addr)) && (event->xfer_complete.len == 0)) {
break;
}
osal_queue_send(_usbd_q, event, in_isr);
TU_ASSERT(event->xfer_complete.result == XFER_RESULT_SUCCESS, );
break;
// Not an DCD event, just a convenient way to defer ISR function should we need to
case USBD_EVENT_FUNC_CALL:
osal_queue_send(_usbd_q, event, in_isr);
break;
default:
break;
}
}
// helper to send bus signal event
void dcd_event_bus_signal(uint8_t rhport, dcd_eventid_t eid, bool in_isr)
{
dcd_event_t event = {
.rhport = rhport,
.event_id = eid,
};
dcd_event_handler(&event, in_isr);
}
// helper to send setup received
void dcd_event_setup_received(uint8_t rhport, uint8_t const *setup, bool in_isr)
{
dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SETUP_RECEIVED};
memcpy(&event.setup_received, setup, 8);
dcd_event_handler(&event, in_isr);
}
// helper to send transfer complete event
void dcd_event_xfer_complete(uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr)
{
dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_XFER_COMPLETE};
event.xfer_complete.ep_addr = ep_addr;
event.xfer_complete.len = xferred_bytes;
event.xfer_complete.result = result;
dcd_event_handler(&event, in_isr);
}
//--------------------------------------------------------------------+
// Helper
//--------------------------------------------------------------------+
// Parse consecutive endpoint descriptors (IN & OUT)
bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const *p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t *ep_out, uint8_t *ep_in)
{
for (int i = 0; i < ep_count; i++) {
tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *)p_desc;
TU_VERIFY(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && xfer_type == desc_ep->bmAttributes.xfer);
TU_ASSERT(dcd_edpt_open(rhport, desc_ep));
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
(*ep_in) = desc_ep->bEndpointAddress;
} else {
(*ep_out) = desc_ep->bEndpointAddress;
}
p_desc = tu_desc_next(p_desc);
}
return true;
}
// Helper to defer an isr function
void usbd_defer_func(osal_task_func_t func, void *param, bool in_isr)
{
dcd_event_t event = {
.rhport = 0,
.event_id = USBD_EVENT_FUNC_CALL,
};
event.func_call.func = func;
event.func_call.param = param;
dcd_event_handler(&event, in_isr);
}
//--------------------------------------------------------------------+
// USBD Endpoint API
//--------------------------------------------------------------------+
bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)
{
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
TU_VERIFY(dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes));
_usbd_dev.ep_busy_map[dir] = (uint8_t)tu_bit_set(_usbd_dev.ep_busy_map[dir], epnum);
return true;
}
bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr)
{
(void)rhport;
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
return tu_bit_test(_usbd_dev.ep_busy_map[dir], epnum);
}
void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
{
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
dcd_edpt_stall(rhport, ep_addr);
_usbd_dev.ep_stall_map[dir] = (uint8_t)tu_bit_set(_usbd_dev.ep_stall_map[dir], epnum);
}
void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
{
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
dcd_edpt_clear_stall(rhport, ep_addr);
_usbd_dev.ep_stall_map[dir] = (uint8_t)tu_bit_clear(_usbd_dev.ep_stall_map[dir], epnum);
}
bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr)
{
(void)rhport;
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
return tu_bit_test(_usbd_dev.ep_stall_map[dir], epnum);
}
#endif

View File

@ -1,725 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2018 Scott Shawcroft, 2019 William D. Jones for Adafruit Industries
* Copyright (c) 2019 Ha Thach (tinyusb.org)
* Additions Copyright (c) 2020, Espressif Systems (Shanghai) Co. Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* This file is part of the TinyUSB stack.
*/
#include "device_controller_driver.h"
#include "esp_rom_sys.h"
static const char *TAG = "TUSB:DCD";
static intr_handle_t usb_ih;
static volatile TU_ATTR_ALIGNED(4) uint32_t _setup_packet[6];
static uint8_t s_setup_phase = 0; /* 00 - got setup,
01 - got done setup,
02 - setup cmd sent*/
#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
static xfer_ctl_t xfer_status[USB_MAX_EP_NUM][USB_EP_DIRECTIONS];
static inline void readyfor1setup_pkg(int ep_num)
{
USB0.out_ep_reg[ep_num].doeptsiz |= (1 << USB_SUPCNT0_S); // doeptsiz 29:30 will decremented on every setup received
}
// Setup the control endpoint 0.
static void bus_reset(void)
{
for (int ep_num = 0; ep_num < USB_OUT_EP_NUM; ep_num++) {
USB0.out_ep_reg[ep_num].doepctl |= USB_DO_SNAK0_M; // DOEPCTL0_SNAK
}
USB0.dcfg &= ~USB_DEVADDR_M; // reset address
// Peripheral FIFO architecture
//
// --------------- 320 ( 1280 bytes )
// | IN FIFO 3 |
// --------------- y + x + 16 + GRXFSIZ
// | IN FIFO 2 |
// --------------- x + 16 + GRXFSIZ
// | IN FIFO 1 |
// --------------- 16 + GRXFSIZ
// | IN FIFO 0 |
// --------------- GRXFSIZ
// | OUT FIFO |
// | ( Shared ) |
// --------------- 0
//
// FIFO sizes are set up by the following rules (each word 32-bits):
// All EP OUT shared a unique OUT FIFO which uses (based on page 1354 of Rev 17 of reference manual):
// * 10 locations in hardware for setup packets + setup control words
// (up to 3 setup packets).
// * 2 locations for OUT endpoint control words.
// * 16 for largest packet size of 64 bytes. ( TODO Highspeed is 512 bytes)
// * 1 location for global NAK (not required/used here).
//
// It is recommended to allocate 2 times the largest packet size, therefore
// Recommended value = 10 + 1 + 2 x (16+2) = 47 --> Let's make it 50
USB0.grstctl |= 0x10 << USB_TXFNUM_S; // fifo 0x10,
USB0.grstctl |= USB_TXFFLSH_M; // Flush fifo
USB0.grxfsiz = 50;
USB0.gintmsk = USB_MODEMISMSK_M |
USB_SOFMSK_M |
USB_RXFLVIMSK_M |
USB_ERLYSUSPMSK_M |
USB_USBSUSPMSK_M |
USB_USBRSTMSK_M |
USB_ENUMDONEMSK_M |
USB_IEPINTMSK_M |
USB_OEPINTMSK_M |
USB_RESETDETMSK_M |
USB_DISCONNINTMSK_M;
USB0.daintmsk |= USB_OUTEPMSK0_M | USB_INEPMSK0_M;
USB0.doepmsk |= USB_SETUPMSK_M | USB_XFERCOMPLMSK;
USB0.diepmsk |= USB_TIMEOUTMSK_M | USB_DI_XFERCOMPLMSK_M;
USB0.gnptxfsiz = 16 << USB_NPTXFDEP_S; // Control IN uses FIFO 0 with 64 bytes ( 16 32-bit word )
readyfor1setup_pkg(0);
}
static void enum_done_processing(void)
{
ESP_EARLY_LOGV(TAG, "dcd_int_handler - Speed enumeration done! Sending DCD_EVENT_BUS_RESET then");
// On current silicon on the Full Speed core, speed is fixed to Full Speed.
// However, keep for debugging and in case Low Speed is ever supported.
uint32_t enum_spd = (USB0.dsts >> USB_ENUMSPD_S) & (USB_ENUMSPD_V);
// Maximum packet size for EP 0 is set for both directions by writing DIEPCTL
if (enum_spd == 0x03) { // Full-Speed (PHY on 48 MHz)
USB0.in_ep_reg[0].diepctl &= ~USB_D_MPS0_V; // 64 bytes
USB0.in_ep_reg[0].diepctl &= ~USB_D_STALL0_M; // clear Stall
xfer_status[0][TUSB_DIR_OUT].max_size = 64;
xfer_status[0][TUSB_DIR_IN].max_size = 64;
} else {
USB0.in_ep_reg[0].diepctl |= USB_D_MPS0_V; // 8 bytes
USB0.in_ep_reg[0].diepctl &= ~USB_D_STALL0_M; // clear Stall
xfer_status[0][TUSB_DIR_OUT].max_size = 8;
xfer_status[0][TUSB_DIR_IN].max_size = 8;
}
USB0.gintmsk |= USB_SOFMSK_M; // SOF unmask
}
/*------------------------------------------------------------------*/
/* Controller API
*------------------------------------------------------------------*/
void dcd_init(uint8_t rhport)
{
ESP_LOGV(TAG, "DCD init - Start");
// A. Disconnect
ESP_LOGV(TAG, "DCD init - Soft DISCONNECT and Setting up");
USB0.dctl |= USB_SFTDISCON_M; // Soft disconnect
// B. Programming DCFG
/* If USB host misbehaves during status portion of control xfer
(non zero-length packet), send STALL back and discard. Full speed. */
USB0.dcfg |= USB_NZSTSOUTHSHK_M | // NonZero .... STALL
(3 << 0); // dev speed: fullspeed 1.1 on 48 mhz // TODO no value in usb_reg.h (IDF-1476)
USB0.gahbcfg |= USB_NPTXFEMPLVL_M | USB_GLBLLNTRMSK_M; // Global interruptions ON
USB0.gusbcfg |= USB_FORCEDEVMODE_M; // force devmode
USB0.gotgctl &= ~(USB_BVALIDOVVAL_M | USB_BVALIDOVEN_M | USB_VBVALIDOVVAL_M); //no overrides
#ifdef CONFIG_IDF_TARGET_ESP32S2BETA // needed for beta chip only
//C. chip 7.2.2 hack
ESP_LOGV(TAG, "DCD init - chip ESP32-S2 beta hack");
USB0.gotgctl = (0 << USB_BVALIDOVVAL_S); //B override value
esp_rom_delay_us(20);
USB0.gotgctl = (0 << USB_BVALIDOVVAL_S) | (1 << USB_BVALIDOVEN_S); //B override value & enable
esp_rom_delay_us(20);
#endif
// C. Setting SNAKs, then connect
for (int n = 0; n < USB_OUT_EP_NUM; n++) {
USB0.out_ep_reg[n].doepctl |= USB_DO_SNAK0_M; // DOEPCTL0_SNAK
}
ESP_LOGV(TAG, "DCD init - Soft CONNECT");
USB0.dctl &= ~USB_SFTDISCON_M; // Connect
// D. Interruption masking
USB0.gintmsk = 0; //mask all
USB0.gotgint = ~0U; //clear OTG ints
USB0.gintsts = ~0U; //clear pending ints
USB0.gintmsk = USB_MODEMISMSK_M |
USB_SOFMSK_M |
USB_RXFLVIMSK_M |
USB_ERLYSUSPMSK_M |
USB_USBSUSPMSK_M |
USB_USBRSTMSK_M |
USB_ENUMDONEMSK_M |
USB_RESETDETMSK_M |
USB_DISCONNINTMSK_M;
esp_rom_delay_us(100);
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
{
(void)rhport;
ESP_LOGV(TAG, "DCD init - Set address : %u", dev_addr);
USB0.dcfg |= ((dev_addr & USB_DEVADDR_V) << USB_DEVADDR_S);
// Response with status after changing device address
dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0);
}
void dcd_set_config(uint8_t rhport, uint8_t config_num)
{
(void)rhport;
(void)config_num;
// Nothing to do
}
void dcd_remote_wakeup(uint8_t rhport)
{
(void)rhport;
}
/*------------------------------------------------------------------*/
/* DCD Endpoint port
*------------------------------------------------------------------*/
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt)
{
ESP_LOGV(TAG, "DCD endpoint opened");
(void)rhport;
usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]);
usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]);
uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress);
uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
// Unsupported endpoint numbers/size.
if ((desc_edpt->wMaxPacketSize.size > 64) || (epnum > 3)) {
return false;
}
xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir);
xfer->max_size = desc_edpt->wMaxPacketSize.size;
if (dir == TUSB_DIR_OUT) {
out_ep[epnum].doepctl |= USB_USBACTEP0_M |
desc_edpt->bmAttributes.xfer << USB_EPTYPE0_S |
desc_edpt->wMaxPacketSize.size << USB_MPS0_S;
USB0.daintmsk |= (1 << (16 + epnum));
} else {
// Peripheral FIFO architecture (Rev18 RM 29.11)
//
// --------------- 320 ( 1280 bytes )
// | IN FIFO 3 |
// --------------- y + x + 16 + GRXFSIZ
// | IN FIFO 2 |
// --------------- x + 16 + GRXFSIZ
// | IN FIFO 1 |
// --------------- 16 + GRXFSIZ
// | IN FIFO 0 |
// --------------- GRXFSIZ
// | OUT FIFO |
// | ( Shared ) |
// --------------- 0
//
// Since OUT FIFO = 50, FIFO 0 = 16, average of FIFOx = (312-50-16) / 3 = 82 ~ 80
in_ep[epnum].diepctl |= USB_D_USBACTEP1_M |
(epnum - 1) << USB_D_TXFNUM1_S |
desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S |
(desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) |
desc_edpt->wMaxPacketSize.size << 0;
USB0.daintmsk |= (1 << (0 + epnum));
// Both TXFD and TXSA are in unit of 32-bit words
uint16_t const fifo_size = 80;
uint32_t const fifo_offset = (USB0.grxfsiz & USB_NPTXFDEP_V) + 16 + fifo_size * (epnum - 1);
USB0.dieptxf[epnum - 1] = (80 << USB_NPTXFDEP_S) | fifo_offset;
}
return true;
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)
{
(void)rhport;
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir);
xfer->buffer = buffer;
xfer->total_len = total_bytes;
xfer->queued_len = 0;
xfer->short_packet = false;
uint16_t num_packets = (total_bytes / xfer->max_size);
uint8_t short_packet_size = total_bytes % xfer->max_size;
// Zero-size packet is special case.
if (short_packet_size > 0 || (total_bytes == 0)) {
num_packets++;
}
ESP_LOGV(TAG, "Transfer <-> EP%i, %s, pkgs: %i, bytes: %i",
epnum, ((dir == TUSB_DIR_IN) ? "USB0.HOST (in)" : "HOST->DEV (out)"),
num_packets, total_bytes);
// IN and OUT endpoint xfers are interrupt-driven, we just schedule them
// here.
if (dir == TUSB_DIR_IN) {
// A full IN transfer (multiple packets, possibly) triggers XFRC.
int bytes2fifo_left = total_bytes;
uint32_t val; // 32 bit val from 4 buff addresses
USB0.in_ep_reg[epnum].diepint = ~0U; // clear all ints
USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes;
USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK
while (bytes2fifo_left > 0) { // TODO move it to ep_in_handle (IDF-1475)
/* ATTENTION! In cases when CFG_TUD_ENDOINT0_SIZE, CFG_TUD_CDC_EPSIZE, CFG_TUD_MIDI_EPSIZE or
CFG_TUD_MSC_BUFSIZE < 4 next line can be a cause of an error.*/
val = (*(buffer + 3) << 24) |
(*(buffer + 2) << 16) |
(*(buffer + 1) << 8) |
(*(buffer + 0) << 0);
ESP_LOGV(TAG, "Transfer 0x%08x -> FIFO%d", val, epnum);
USB0.fifo[epnum][0] = val; //copy and next buffer address
buffer += 4;
bytes2fifo_left -= 4;
}
// USB0.dtknqr4_fifoemptymsk |= (1 << epnum);
} else {
// Each complete packet for OUT xfers triggers XFRC.
USB0.out_ep_reg[epnum].doeptsiz = USB_PKTCNT0_M |
((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S);
USB0.out_ep_reg[epnum].doepctl |= USB_EPENA0_M | USB_CNAK0_M;
}
return true;
}
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
{
(void)rhport;
usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]);
usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]);
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_IN) {
// Only disable currently enabled non-control endpoint
if ((epnum == 0) || !(in_ep[epnum].diepctl & USB_D_EPENA1_M)) {
in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M);
} else {
// Stop transmitting packets and NAK IN xfers.
in_ep[epnum].diepctl |= USB_DI_SNAK1_M;
while ((in_ep[epnum].diepint & USB_DI_SNAK1_M) == 0)
;
// Disable the endpoint. Note that both SNAK and STALL are set here.
in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M |
USB_D_EPDIS1_M);
while ((in_ep[epnum].diepint & USB_D_EPDISBLD0_M) == 0)
;
in_ep[epnum].diepint = USB_D_EPDISBLD0_M;
}
// Flush the FIFO, and wait until we have confirmed it cleared.
USB0.grstctl |= ((epnum - 1) << USB_TXFNUM_S);
USB0.grstctl |= USB_TXFFLSH_M;
while ((USB0.grstctl & USB_TXFFLSH_M) != 0)
;
} else {
// Only disable currently enabled non-control endpoint
if ((epnum == 0) || !(out_ep[epnum].doepctl & USB_EPENA0_M)) {
out_ep[epnum].doepctl |= USB_STALL0_M;
} else {
// Asserting GONAK is required to STALL an OUT endpoint.
// Simpler to use polling here, we don't use the "B"OUTNAKEFF interrupt
// anyway, and it can't be cleared by user code. If this while loop never
// finishes, we have bigger problems than just the stack.
USB0.dctl |= USB_SGOUTNAK_M;
while ((USB0.gintsts & USB_GOUTNAKEFF_M) == 0)
;
// Ditto here- disable the endpoint. Note that only STALL and not SNAK
// is set here.
out_ep[epnum].doepctl |= (USB_STALL0_M | USB_EPDIS0_M);
while ((out_ep[epnum].doepint & USB_EPDISBLD0_M) == 0)
;
out_ep[epnum].doepint = USB_EPDISBLD0_M;
// Allow other OUT endpoints to keep receiving.
USB0.dctl |= USB_CGOUTNAK_M;
}
}
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
{
(void)rhport;
usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]);
usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]);
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_IN) {
in_ep[epnum].diepctl &= ~USB_D_STALL1_M;
uint8_t eptype = (in_ep[epnum].diepctl & USB_D_EPTYPE1_M) >> USB_D_EPTYPE1_S;
// Required by USB spec to reset DATA toggle bit to DATA0 on interrupt
// and bulk endpoints.
if (eptype == 2 || eptype == 3) {
in_ep[epnum].diepctl |= USB_DI_SETD0PID1_M;
}
} else {
out_ep[epnum].doepctl &= ~USB_STALL1_M;
uint8_t eptype = (out_ep[epnum].doepctl & USB_EPTYPE1_M) >> USB_EPTYPE1_S;
// Required by USB spec to reset DATA toggle bit to DATA0 on interrupt
// and bulk endpoints.
if (eptype == 2 || eptype == 3) {
out_ep[epnum].doepctl |= USB_DO_SETD0PID1_M;
}
}
}
/*------------------------------------------------------------------*/
static void receive_packet(xfer_ctl_t *xfer, /* usb_out_endpoint_t * out_ep, */ uint16_t xfer_size)
{
ESP_EARLY_LOGV(TAG, "USB - receive_packet");
uint32_t *rx_fifo = USB0.fifo[0];
// See above TODO
// uint16_t remaining = (out_ep->DOEPTSIZ & UsbDOEPTSIZ_XFRSIZ_Msk) >> UsbDOEPTSIZ_XFRSIZ_Pos;
// xfer->queued_len = xfer->total_len - remaining;
uint16_t remaining = xfer->total_len - xfer->queued_len;
uint16_t to_recv_size;
if (remaining <= xfer->max_size) {
// Avoid buffer overflow.
to_recv_size = (xfer_size > remaining) ? remaining : xfer_size;
} else {
// Room for full packet, choose recv_size based on what the microcontroller
// claims.
to_recv_size = (xfer_size > xfer->max_size) ? xfer->max_size : xfer_size;
}
uint8_t to_recv_rem = to_recv_size % 4;
uint16_t to_recv_size_aligned = to_recv_size - to_recv_rem;
// Do not assume xfer buffer is aligned.
uint8_t *base = (xfer->buffer + xfer->queued_len);
// This for loop always runs at least once- skip if less than 4 bytes
// to collect.
if (to_recv_size >= 4) {
for (uint16_t i = 0; i < to_recv_size_aligned; i += 4) {
uint32_t tmp = (*rx_fifo);
base[i] = tmp & 0x000000FF;
base[i + 1] = (tmp & 0x0000FF00) >> 8;
base[i + 2] = (tmp & 0x00FF0000) >> 16;
base[i + 3] = (tmp & 0xFF000000) >> 24;
}
}
// Do not read invalid bytes from RX FIFO.
if (to_recv_rem != 0) {
uint32_t tmp = (*rx_fifo);
uint8_t *last_32b_bound = base + to_recv_size_aligned;
last_32b_bound[0] = tmp & 0x000000FF;
if (to_recv_rem > 1) {
last_32b_bound[1] = (tmp & 0x0000FF00) >> 8;
}
if (to_recv_rem > 2) {
last_32b_bound[2] = (tmp & 0x00FF0000) >> 16;
}
}
xfer->queued_len += xfer_size;
// Per USB spec, a short OUT packet (including length 0) is always
// indicative of the end of a transfer (at least for ctl, bulk, int).
xfer->short_packet = (xfer_size < xfer->max_size);
}
static void transmit_packet(xfer_ctl_t *xfer, volatile usb_in_endpoint_t *in_ep, uint8_t fifo_num)
{
ESP_EARLY_LOGV(TAG, "USB - transmit_packet");
uint32_t *tx_fifo = USB0.fifo[0];
uint16_t remaining = (in_ep->dieptsiz & 0x7FFFFU) >> USB_D_XFERSIZE0_S;
xfer->queued_len = xfer->total_len - remaining;
uint16_t to_xfer_size = (remaining > xfer->max_size) ? xfer->max_size : remaining;
uint8_t to_xfer_rem = to_xfer_size % 4;
uint16_t to_xfer_size_aligned = to_xfer_size - to_xfer_rem;
// Buffer might not be aligned to 32b, so we need to force alignment
// by copying to a temp var.
uint8_t *base = (xfer->buffer + xfer->queued_len);
// This for loop always runs at least once- skip if less than 4 bytes
// to send off.
if (to_xfer_size >= 4) {
for (uint16_t i = 0; i < to_xfer_size_aligned; i += 4) {
uint32_t tmp = base[i] | (base[i + 1] << 8) |
(base[i + 2] << 16) | (base[i + 3] << 24);
(*tx_fifo) = tmp;
}
}
// Do not read beyond end of buffer if not divisible by 4.
if (to_xfer_rem != 0) {
uint32_t tmp = 0;
uint8_t *last_32b_bound = base + to_xfer_size_aligned;
tmp |= last_32b_bound[0];
if (to_xfer_rem > 1) {
tmp |= (last_32b_bound[1] << 8);
}
if (to_xfer_rem > 2) {
tmp |= (last_32b_bound[2] << 16);
}
(*tx_fifo) = tmp;
}
}
static void read_rx_fifo(void)
{
// Pop control word off FIFO (completed xfers will have 2 control words,
// we only pop one ctl word each interrupt).
volatile uint32_t ctl_word = USB0.grxstsp;
uint8_t pktsts = (ctl_word & USB_PKTSTS_M) >> USB_PKTSTS_S;
uint8_t epnum = (ctl_word & USB_CHNUM_M) >> USB_CHNUM_S;
uint16_t bcnt = (ctl_word & USB_BCNT_M) >> USB_BCNT_S;
switch (pktsts) {
case 0x01: // Global OUT NAK (Interrupt)
ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Global OUT NAK");
break;
case 0x02: { // Out packet recvd
ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Out packet");
xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
receive_packet(xfer, bcnt);
}
break;
case 0x03: // Out packet done (Interrupt)
ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Out packet done");
break;
case 0x04: // Setup packet done (Interrupt)
if (s_setup_phase == 0) { // only if setup is started
s_setup_phase = 1;
ESP_EARLY_LOGV(TAG, "TUSB IRQ - setup_phase 1"); //finished
ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX : Setup packet done");
}
break;
case 0x06: { // Setup packet recvd
s_setup_phase = 0;
ESP_EARLY_LOGV(TAG, "TUSB IRQ - setup_phase 0"); // new setup process
// For some reason, it's possible to get a mismatch between
// how many setup packets were received versus the location
// of the Setup packet done word. This leads to situations
// where stale setup packets are in the RX FIFO that were received
// after the core loaded the Setup packet done word. Workaround by
// only accepting one setup packet at a time for now.
_setup_packet[0] = (USB0.grxstsp);
_setup_packet[1] = (USB0.grxstsp);
ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX : Setup packet : 0x%08x 0x%08x",
_setup_packet[0], _setup_packet[1]);
}
break;
default: // Invalid, do something here, like breakpoint?
break;
}
}
static void handle_epout_ints(void)
{
// GINTSTS will be cleared with DAINT == 0
// DAINT for a given EP clears when DOEPINTx is cleared.
// DOEPINT will be cleared when DAINT's out bits are cleared.
for (int n = 0; n < USB_OUT_EP_NUM; n++) {
xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_OUT);
if (USB0.daint & (1 << (16 + n))) {
// SETUP packet Setup Phase done.
if ((USB0.out_ep_reg[n].doepint & USB_SETUP0_M)) {
USB0.out_ep_reg[n].doepint |= USB_STUPPKTRCVD0_M | USB_SETUP0_M; // clear
if (s_setup_phase == 1) { // only if setup is done, but not handled
s_setup_phase = 2;
ESP_EARLY_LOGV(TAG, "TUSB IRQ - setup_phase 2"); // sending to a handling queue
ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP OUT - Setup Phase done (irq-s 0x%08x)", USB0.out_ep_reg[n].doepint);
dcd_event_setup_received(0, (uint8_t *)&_setup_packet[0], true);
}
readyfor1setup_pkg(0);
}
// OUT XFER complete (single packet).q
if (USB0.out_ep_reg[n].doepint & USB_XFERCOMPL0_M) {
ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP OUT - XFER complete (single packet)");
USB0.out_ep_reg[n].doepint = USB_XFERCOMPL0_M;
// Transfer complete if short packet or total len is transferred
if (xfer->short_packet || (xfer->queued_len == xfer->total_len)) {
xfer->short_packet = false;
dcd_event_xfer_complete(0, n, xfer->queued_len, XFER_RESULT_SUCCESS, true);
} else {
// Schedule another packet to be received.
USB0.out_ep_reg[n].doeptsiz = USB_PKTCNT0_M |
((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S);
USB0.out_ep_reg[n].doepctl |= USB_EPENA0_M | USB_CNAK0_M;
}
}
}
}
}
static void handle_epin_ints(void)
{
// GINTSTS will be cleared with DAINT == 0
// DAINT for a given EP clears when DIEPINTx is cleared.
// IEPINT will be cleared when DAINT's out bits are cleared.
for (uint32_t n = 0; n < USB_IN_EP_NUM; n++) {
xfer_ctl_t *xfer = &xfer_status[n][TUSB_DIR_IN];
if (USB0.daint & (1 << (0 + n))) {
ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP IN %u", n);
// IN XFER complete (entire xfer).
if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) {
ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER complete!");
USB0.in_ep_reg[n].diepint = USB_D_XFERCOMPL0_M;
// USB0.dtknqr4_fifoemptymsk &= ~(1 << n); // Turn off TXFE b/c xfer inactive.
dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
}
// XFER FIFO empty
if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) {
ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER FIFO empty!");
USB0.in_ep_reg[n].diepint = USB_D_TXFEMP0_M;
transmit_packet(xfer, &USB0.in_ep_reg[n], n);
}
}
}
}
static void dcd_int_handler(void)
{
uint32_t int_status = USB0.gintsts;
uint32_t int_msk = USB0.gintmsk;
if (int_status & USB_DISCONNINT_M) {
ESP_EARLY_LOGV(TAG, "dcd_int_handler - disconnected");
USB0.gintsts = USB_DISCONNINT_M;
}
if (int_status & USB_USBRST_M) {
ESP_EARLY_LOGV(TAG, "dcd_int_handler - reset");
USB0.gintsts = USB_USBRST_M;
bus_reset();
}
if (int_status & USB_RESETDET_M) {
ESP_EARLY_LOGV(TAG, "dcd_int_handler - reset while suspend");
USB0.gintsts = USB_RESETDET_M;
bus_reset();
}
if (int_status & USB_ENUMDONE_M) {
// ENUMDNE detects speed of the link. For full-speed, we
// always expect the same value. This interrupt is considered
// the end of reset.
USB0.gintsts = USB_ENUMDONE_M;
enum_done_processing();
dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true);
}
if (int_status & USB_SOF_M) {
USB0.gintsts = USB_SOF_M;
dcd_event_bus_signal(0, DCD_EVENT_SOF, true); // do nothing actually
}
if ((int_status & USB_RXFLVI_M) & (int_msk & USB_RXFLVIMSK_M)) {
ESP_EARLY_LOGV(TAG, "dcd_int_handler - rx!");
read_rx_fifo();
}
// OUT endpoint interrupt handling.
if (int_status & USB_OEPINT_M) {
ESP_EARLY_LOGV(TAG, "dcd_int_handler - OUT endpoint!");
handle_epout_ints();
}
// IN endpoint interrupt handling.
if (int_status & USB_IEPINT_M) {
ESP_EARLY_LOGV(TAG, "dcd_int_handler - IN endpoint!");
handle_epin_ints();
}
// Without handling
USB0.gintsts |= USB_CURMOD_INT_M |
USB_MODEMIS_M |
USB_OTGINT_M |
USB_NPTXFEMP_M |
USB_GINNAKEFF_M |
USB_GOUTNAKEFF |
USB_ERLYSUSP_M |
USB_USBSUSP_M |
USB_ISOOUTDROP_M |
USB_EOPF_M |
USB_EPMIS_M |
USB_INCOMPISOIN_M |
USB_INCOMPIP_M |
USB_FETSUSP_M |
USB_PTXFEMP_M;
}
void dcd_int_enable(uint8_t rhport)
{
(void)rhport;
esp_intr_alloc(ETS_USB_INTR_SOURCE, ESP_INTR_FLAG_LOWMED, (intr_handler_t)dcd_int_handler, NULL, &usb_ih);
}
void dcd_int_disable(uint8_t rhport)
{
(void)rhport;
esp_intr_free(usb_ih);
}

@ -1 +1 @@
Subproject commit 28f89e13473d40637574bcbfe4142633b39899fd
Subproject commit a2ba3dccccf94022d31e939fa2ce4dca5f0a34f0

View File

@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tusb_console)

View File

@ -0,0 +1,87 @@
| Supported Targets | ESP32-S2 |
| ----------------- | -------- |
# TinyUSB Sample Descriptor
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to set up ESP32-S2 chip to get log output via Serial Device connection
As a USB stack, a TinyUSB component is used.
## How to use example
### Hardware Required
- Any board with the ESP32-S2 chip with USB connectors or with exposed USB's D+ and D- (DATA+/DATA-) pins.
If the board has no USB connector, but has the pins connect pins directly to the host (e.g. with DIY cable from any USB connection cable)
```
ESP32-S2 BOARD USB CONNECTOR (type A)
--
| || VCC
[GPIO 19] --------> | || D-
[GPIO 20] --------> | || D+
| || GND
--
```
You can also use power from the USB connector.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Serial Connection
After program's start and getting of the message of readiness (`Serial device is ready to connect`) you can connect to the board using any serial port terminal application (e.g. CoolTerm).
Note: if you want to send data to the target see how to implement it via `tud_cdc_rx_cb` at the `tusb_serial_device` example.
## Example Output
After the flashing you should see the output at idf monitor:
```
I (340) example: USB initialization
I (340) TinyUSB: Driver installation...
I (340) TinyUSB - Descriptors Control: Setting of a descriptor:
.bDeviceClass = 239
.bDeviceSubClass = 2,
.bDeviceProtocol = 1,
.bMaxPacketSize0 = 64,
.idVendor = 0x0000303a,
.idProduct = 0x00004001,
.bcdDevice = 0x00000100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
I (373) TinyUSB: Driver installed
I (373) example: USB initialization DONE
I (383) example: log -> UART
example: print -> stdout
example: print -> stderr
...
```
Other log will be printed to USB:
```
I (5382) example: log -> USB
example: print -> stdout
example: print -> stderr
...
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "tusb_console_main.c"
INCLUDE_DIRS .)

View File

@ -0,0 +1,58 @@
/* USB Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
// DESCRIPTION:
// This example contains minimal code to make ESP32-S2 based device
// recognizable by USB-host devices as a USB Serial Device printing output from
// the application.
#include <stdio.h>
#include <stdlib.h>
#include <sys/reent.h>
#include "esp_log.h"
#include "esp_vfs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "tusb_console.h"
#include "sdkconfig.h"
static const char *TAG = "example";
void app_main(void)
{
/* Setting TinyUSB up */
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t tusb_cfg = { 0 }; // the configuration uses default values
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
tinyusb_config_cdcacm_t amc_cfg = { 0 }; // the configuration uses default values
ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
while (1) {
ESP_LOGI(TAG, "log -> UART");
vTaskDelay(1000 / portTICK_PERIOD_MS);
fprintf(stdout, "example: print -> stdout\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
fprintf(stderr, "example: print -> stderr\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_tusb_init_console(TINYUSB_CDC_ACM_0); // log to usb
ESP_LOGI(TAG, "log -> USB");
vTaskDelay(1000 / portTICK_PERIOD_MS);
fprintf(stdout, "example: print -> stdout\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
fprintf(stderr, "example: print -> stderr\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_tusb_deinit_console(TINYUSB_CDC_ACM_0); // log to uart
}
}

View File

@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="esp32s2"
CONFIG_USB_ENABLED=y
CONFIG_USB_CDC_ENABLED=y

View File

@ -7,14 +7,27 @@
This example is demonstrating how to set up ESP32-S2 chip to work as a Generic USB Device with a user-defined descriptor. You can specify a manufacturer, device's name, ID and other USB-devices parameters responsible for identification by host.
As a USB stack, a TinyUSB component is used.
## How to use example
### Hardware Required
- Any board with the ESP32-S2 chip
- Any board with the ESP32-S2 chip with USB connectors or with exposed USB's D+ and D- (DATA+/DATA-) pins.
If the board has no USB connector, but has the pins connect pins directly to the host (e.g. with DIY cable from any USB connection cable)
```
ESP32-S2 BOARD USB CONNECTOR (type A)
--
| || VCC
[GPIO 19] --------> | || D-
[GPIO 20] --------> | || D+
| || GND
--
```
You can also use power from the USB connector.
### Configure the project
@ -26,13 +39,13 @@ For the manual descriptor's configuration use the default example's settings and
#### Menuconfig
If you want to set up the desctiptor using Menuconfig UI:
If you want to set up the descriptor using Menuconfig UI:
1. Execute in the terminal from the example's directory: `idf.py menuconfig`
2. Turn off `Set up a USB descriptor manually in code` parameter at `Example Configuration`
3. Folow to `Component config -> TinyUSB -> Descriptor configuration` for all available configurations.
3. Follow `Component config -> TinyUSB -> Descriptor configuration` for all available configurations.
### Build and Flash
@ -53,21 +66,20 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
After the flashing you should see the output:
```
I (314) example: USB initialization
I (314) TUSB:descriptors_control: Setting of a descriptor:
I (349) TinyUSB: Driver installation...
I (349) TinyUSB - Descriptors Control: Setting of a descriptor:
.bDeviceClass = 0
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = 0x0000303a,
.idProduct = 0x00004000,
.bcdDevice = 0x00000100,
.idProduct = 0x00003000,
.bcdDevice = 0x00000101,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
I (344) example: USB initialization DONE
I (354) example: USB task started
I (389) TinyUSB: Driver installed
I (389) example: USB initialization DONE
```

View File

@ -1,2 +1,2 @@
idf_component_register(SRCS "tusb_sample_descriptor.c"
INCLUDE_DIRS . ${COMPONENT_DIR})
idf_component_register(SRCS "tusb_sample_descriptor_main.c"
INCLUDE_DIRS .)

View File

@ -9,26 +9,16 @@
#include <stdlib.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "tinyusb.h"
#include "sdkconfig.h"
static const char *TAG = "example";
// USB Device Driver task
// This top level thread processes all usb events and invokes callbacks
static void usb_device_task(void *param) {
(void)param;
ESP_LOGI(TAG, "USB task started");
while (1) {
tud_task(); // RTOS forever loop
}
}
void app_main(void) {
void app_main(void)
{
ESP_LOGI(TAG, "USB initialization");
#if CONFIG_EXAMPLE_MANUAL_DESC
@ -49,7 +39,8 @@ void app_main(void) {
.iProduct = 0x02, // see string_descriptor[2] bellow
.iSerialNumber = 0x03, // see string_descriptor[3] bellow
.bNumConfigurations = 0x01};
.bNumConfigurations = 0x01
};
tusb_desc_strarray_device_t my_string_descriptor = {
// array of pointer to string descriptors
@ -77,8 +68,4 @@ void app_main(void) {
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
// Create a task for tinyusb device stack:
xTaskCreate(usb_device_task, "usbd", 4096, NULL, 5, NULL);
return;
}

View File

@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tusb_serial_device)

View File

@ -0,0 +1,79 @@
| Supported Targets | ESP32-S2 |
| ----------------- | -------- |
# TinyUSB Sample Descriptor
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to set up ESP32-S2 chip to work as a USB Serial Device.
As a USB stack, a TinyUSB component is used.
## How to use example
### Hardware Required
- Any board with the ESP32-S2 chip with USB connectors or with exposed USB's D+ and D- (DATA+/DATA-) pins.
If the board has no USB connector, but has the pins connect pins directly to the host (e.g. with DIY cable from any USB connection cable)
```
ESP32-S2 BOARD USB CONNECTOR (type A)
--
| || VCC
[GPIO 19] --------> | || D-
[GPIO 20] --------> | || D+
| || GND
--
```
You can also use power from the USB connector.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Serial Connection
After program's start and getting of the message of readiness (`Serial device is ready to connect`) you can connect to the board using any serial port terminal application (e.g. CoolTerm).
## Example Output
After the flashing you should see the output:
```
I (346) example: USB initialization
I (346) TinyUSB: Driver installation...
I (346) TinyUSB - Descriptors Control: Setting of a descriptor:
.bDeviceClass = 239
.bDeviceSubClass = 2,
.bDeviceProtocol = 1,
.bMaxPacketSize0 = 64,
.idVendor = 0x0000303a,
.idProduct = 0x00004001,
.bcdDevice = 0x00000100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
I (362) TinyUSB: Driver installed
I (362) example: USB initialization DONE
I (922) example: Line state changed! dtr:0, rst:0
```
Let's try to send a string "espressif" and get the return string in your console on PC:
```
I (18346) example: Got data (9 bytes): espressif
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "tusb_serial_device_main.c"
INCLUDE_DIRS .)

View File

@ -0,0 +1,74 @@
/* USB Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
// DESCRIPTION:
// This example contains minimal code to make ESP32-S2 based device
// recognizable by USB-host devices as a USB Serial Device.
#include <stdint.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "sdkconfig.h"
static const char *TAG = "example";
static uint8_t buf[CONFIG_USB_CDC_RX_BUFSIZE + 1];
void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event)
{
/* initialization */
size_t rx_size = 0;
/* read */
esp_err_t ret = tinyusb_cdcacm_read(itf, buf, CONFIG_USB_CDC_RX_BUFSIZE, &rx_size);
if (ret == ESP_OK) {
buf[rx_size] = '\0';
ESP_LOGI(TAG, "Got data (%d bytes): %s", rx_size, buf);
} else {
ESP_LOGE(TAG, "Read error");
}
/* write back */
tinyusb_cdcacm_write_queue(itf, buf, rx_size);
tinyusb_cdcacm_write_flush(itf, portMAX_DELAY);
}
void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
{
int dtr = event->line_state_changed_data.dtr;
int rst = event->line_state_changed_data.rts;
ESP_LOGI(TAG, "Line state changed! dtr:%d, rst:%d", dtr, rst);
}
void app_main(void)
{
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t tusb_cfg = {}; // the configuration using default values
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
tinyusb_config_cdcacm_t amc_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = &tinyusb_cdc_rx_callback, // the first way to register a callback
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL
};
ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg));
/* the second way to register a callback */
ESP_ERROR_CHECK(tinyusb_cdcacm_register_callback(
TINYUSB_CDC_ACM_0,
CDC_EVENT_LINE_STATE_CHANGED,
&tinyusb_cdc_line_state_changed_callback));
ESP_LOGI(TAG, "USB initialization DONE");
}

View File

@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="esp32s2"
CONFIG_USB_ENABLED=y
CONFIG_USB_CDC_ENABLED=y

View File

@ -0,0 +1,6 @@
components/
common_components/
cxx/experimental/experimental_cpp_component/
main/
build_system/cmake/
mb_example_common/

View File

@ -0,0 +1,2 @@
build_system/cmake
temp_

View File

@ -0,0 +1,120 @@
#!/usr/bin/env python
import os
import sys
import pprint
import json
import subprocess
# =============================================================================
# Service funcs
# =============================================================================
def _build_path(path, *paths):
return str(os.path.normpath(os.path.join(path, *paths)).replace("\\", "/"))
def _unify_paths(path_list):
return [_build_path(p) for p in path_list]
def _exclude_by_pat_list(path_list, ignore_list):
print("- Applying ignore list")
path_list_res = list(path_list)
for ign in ignore_list:
if len(ign.strip()):
for p in path_list:
if p.find(ign) != -1:
try:
path_list_res.remove(p)
except ValueError:
pass
return path_list_res
def _file2linelist(path):
with open(path) as f:
lines = [line.rstrip() for line in f]
return [str(line) for line in lines]
# =============================================================================
# Test funcs
# =============================================================================
def get_idf_path(path, *paths):
IDF_PATH = os.getenv('IDF_PATH')
return _build_path(IDF_PATH, path, *paths)
def _get_apps(target, build_system):
print("- Getting paths of apps")
output = subprocess.check_output(
sys.executable + " " + os.getenv('IDF_PATH') +
"/tools/find_apps.py -p examples --recursive --target %s --build-system %s"
% (target, build_system),
shell=True)
o_list = output.split("\n")
json_list = []
for j in o_list:
if j:
json_list.append(json.loads(j))
app_paths = []
for j in json_list:
app_paths.append(j['app_dir'])
return _unify_paths(app_paths)
def get_apps(target, build_system, ignorelist):
apps = _get_apps(target, build_system)
if len(ignorelist):
return _exclude_by_pat_list(apps, ignorelist)
else:
return apps
def get_cmake_ignore_list():
print("- Getting CMake ignore list")
return _file2linelist(
get_idf_path("tools", "ci",
"check_examples_cmake_make-cmake_ignore.txt"))
def get_make_ignore_list():
print("- Getting Make ignore list")
return _file2linelist(
get_idf_path("tools", "ci",
"check_examples_cmake_make-make_ignore.txt"))
def diff(first, second):
print("- Comparing...")
first = set(first)
second = set(second)
res = list(first - second) + list(second - first)
return res
def main():
cmake_ignore = get_cmake_ignore_list()
make_ignore = get_make_ignore_list()
cmakes = get_apps("esp32", "cmake", cmake_ignore)
makes = get_apps("esp32", "make", make_ignore)
res = diff(cmakes, makes)
if len(res):
pp = pprint.PrettyPrinter(indent=4)
print(
"[ ERROR ] Some projects are not containing Make and Cmake project files:"
)
pp.pprint(res)
raise ValueError("Test is not passed")
else:
print("[ DONE ]")
if __name__ == "__main__":
main()

View File

@ -1,71 +0,0 @@
#!/usr/bin/env bash
# While we support GNU Make & CMake together, check the same examples are present for both. But only for ESP32
echo "- Getting paths of CMakeLists and Makefiles"
IFS=
CMAKELISTS=$( find ${IDF_PATH}/examples/ -type f -name CMakeLists.txt | grep -v "/components/" | grep -v "/common_components/" | grep -v "/cxx/experimental/experimental_cpp_component/" | grep -v "/main/" | grep -v "/build_system/cmake/" | grep -v "/mb_example_common/" | sort -n)
MAKEFILES=$( find ${IDF_PATH}/examples/ -type f -name Makefile | grep -v "/build_system/cmake/" | sort -n)
echo " [DONE]"
echo "- Building the ignore list"
IGNORE_LIST=
while read line
do
STL=$(grep "set[(]SUPPORTED_TARGETS" $line)
if test -n $STL # if specified SUPPORTED_TARGETS
then
WO_ESP32=$(grep -v -w "esp32" <<< $STL)
if test -n "$WO_ESP32" # if not specified esp32 at SUPPORTED_TARGETS
then # then consider that the example should not have a Makefile
PATH2IGNORE=$(/usr/bin/dirname $line)
echo " Adding to ignore $PATH2IGNORE"
IGNORE_LIST="$IGNORE_LIST$PATH2IGNORE
"
fi
fi
done <<< $CMAKELISTS
IGNORE_LIST=$(grep -v -e '^$' <<< $IGNORE_LIST) # remove empty lines
echo -e " [DONE] - Ignore list:\n$IGNORE_LIST\n"
echo "- Applying the Ignore list"
while read line
do
echo $line
CMAKELISTS=$(echo $CMAKELISTS | grep -v $line)
MAKEFILES=$(echo $MAKEFILES | grep -v $line)
done <<< $IGNORE_LIST
echo " [DONE]"
echo "- Getting paths of examples"
while read line
do
new_path="$(/usr/bin/dirname $line)"
CMAKE_EXAMPLE_PATHS="$CMAKE_EXAMPLE_PATHS$new_path
"
done <<< $CMAKELISTS
CMAKE_EXAMPLE_PATHS=$(grep -v -e '^$' <<< $CMAKE_EXAMPLE_PATHS) # remove empty lines
while read line
do
new_path="$(/usr/bin/dirname $line)"
MAKE_EXAMPLE_PATHS="$MAKE_EXAMPLE_PATHS$new_path
"
done <<< $MAKEFILES
MAKE_EXAMPLE_PATHS=$(grep -v -e '^$' <<< $MAKE_EXAMPLE_PATHS) # remove empty lines
echo " [DONE]"
echo "- Analysing matches"
MISMATCH=$(comm -3 <(echo "$MAKE_EXAMPLE_PATHS") <(echo "$CMAKE_EXAMPLE_PATHS"))
if [ -n "$MISMATCH" ]; then
echo " [ERROR] Some examples are not in both CMake and GNU Make:"
echo "$MISMATCH"
exit 1
fi
echo " [DONE] Example lists match"
exit 0

View File

@ -46,7 +46,7 @@ check_examples_cmake_make:
- /^release\/v/
- /^v\d+\.\d+(\.\d+)?($|-)/
script:
- tools/ci/check_examples_cmake_make.sh
- python ${IDF_PATH}/tools/ci/check_examples_cmake_make.py
check_rom_api_header:
extends: .check_job_template_with_filter

View File

@ -39,7 +39,7 @@ tools/ci/check-executable.sh
tools/ci/check-line-endings.sh
tools/ci/check_build_warnings.py
tools/ci/check_deprecated_kconfigs.py
tools/ci/check_examples_cmake_make.sh
tools/ci/check_examples_cmake_make.py
tools/ci/check_examples_rom_header.sh
tools/ci/check_idf_version.sh
tools/ci/check_readme_links.py