mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-02 10:00:57 +02:00
Merge branch 'feat/add_cherryusb_demo' into 'master'
feat(usb): add CherryUSB serial device and host example See merge request espressif/esp-idf!39467
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# The following 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.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(cherryusb_serial_device)
|
@@ -0,0 +1,85 @@
|
||||
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | -------- | -------- | -------- |
|
||||
|
||||
# CherryUSB Serial Device Example
|
||||
|
||||
Starts a CDC Device, and sends back any received data from the host.
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## How to use example
|
||||
|
||||
This example can also be configured to act as double serial device.
|
||||
Run `idf.py menuconfig` and in `Example Configuration → Enable usb cdc acm two channel` enable.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
Any ESP board that have USB-OTG supported.
|
||||
|
||||
#### Pin Assignment
|
||||
|
||||
_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
|
||||
|
||||
See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments).
|
||||
|
||||
### 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.
|
||||
|
||||
## Example output
|
||||
|
||||
When running, the example will print the following output:
|
||||
|
||||
```
|
||||
I (271) main_task: Started on CPU0
|
||||
I (291) main_task: Calling app_main()
|
||||
[I/USB] cherryusb, version: v1.4.3
|
||||
[I/USB] ========== dwc2 udc params ==========
|
||||
[I/USB] CID:00000000
|
||||
[I/USB] GSNPSID:4f54400a
|
||||
[I/USB] GHWCFG1:00000000
|
||||
[I/USB] GHWCFG2:224dd930
|
||||
[I/USB] GHWCFG3:00c804b5
|
||||
[I/USB] GHWCFG4:d3f0a030
|
||||
[I/USB] dwc2 fsphy type:1, hsphy type:0, dma support:2
|
||||
[I/USB] dwc2 has 7 endpoints and dfifo depth(32-bit words) is 200, default config: 7 endpoints
|
||||
[I/USB] =================================
|
||||
[I/USB] fifo0 size:0010, offset:0050
|
||||
[I/USB] fifo1 size:0010, offset:0060
|
||||
[I/USB] fifo2 size:0010, offset:0070
|
||||
[I/USB] fifo3 size:0010, offset:0080
|
||||
[I/USB] fifo4 size:0010, offset:0090
|
||||
[I/USB] fifo5 size:0010, offset:00a0
|
||||
[I/USB] fifo6 size:0010, offset:00b0
|
||||
I (391) device_cdc_main: usb cdc acm init done
|
||||
I (721) device_cdc_main: intf:0, dtr:0
|
||||
I (721) device_cdc_main: intf:0, rts:0
|
||||
I (2981) device_cdc_main: intf:0, dtr:0
|
||||
I (2981) device_cdc_main: intf:0, rts:0
|
||||
I (2981) device_cdc_main: intf:0, dtr:0
|
||||
I (2981) device_cdc_main: intf:0, rts:0
|
||||
I (2981) device_cdc_main: intf:0, dtr:0
|
||||
I (2981) device_cdc_main: intf:0, rts:0
|
||||
I (2991) device_cdc_main: intf:0, dtr:0
|
||||
I (2991) device_cdc_main: intf:0, rts:0
|
||||
I (2991) device_cdc_main: intf:0, dtr:0
|
||||
I (2991) device_cdc_main: intf:0, rts:0
|
||||
I (3001) device_cdc_main: intf:0, dtr:0
|
||||
I (3001) device_cdc_main: intf:0, rts:0
|
||||
I (6011) device_cdc_main: actual out len:13
|
||||
I (6021) device_cdc_main: actual in len:13
|
||||
I (10411) device_cdc_main: actual out len:13
|
||||
I (10421) device_cdc_main: actual in len:13
|
||||
I (11111) device_cdc_main: intf:0, dtr:0
|
||||
I (11111) device_cdc_main: intf:0, rts:0
|
||||
```
|
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "device_cdc_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
)
|
@@ -0,0 +1,38 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_USB_DEVICE_RHPORT
|
||||
prompt "USB Device Peripheral"
|
||||
default EXAMPLE_USB_DEVICE_RHPORT_HS if IDF_TARGET_ESP32P4
|
||||
default EXAMPLE_USB_DEVICE_RHPORT_FS
|
||||
help
|
||||
Allows set the USB Peripheral Controller for USB device.
|
||||
|
||||
- High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed)
|
||||
- Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed)
|
||||
|
||||
config EXAMPLE_USB_DEVICE_RHPORT_HS
|
||||
bool "OTG2.0"
|
||||
depends on IDF_TARGET_ESP32P4
|
||||
config EXAMPLE_USB_DEVICE_RHPORT_FS
|
||||
bool "OTG1.1"
|
||||
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
bool "Enable usb cdc acm two channel"
|
||||
default n
|
||||
|
||||
config EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
|
||||
bool "Perform init deinit of cherryusb stack in a loop"
|
||||
default n
|
||||
|
||||
config EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
|
||||
bool "set usb read buffer size to MPS"
|
||||
default y
|
||||
help
|
||||
If this option is disabled, a larger receive buffer will be configured for DMA at a time,
|
||||
which may improve efficiency a little. However, when the host sends data with a length that
|
||||
is an integer multiple of MPS, the host may not send ZLP, resulting in failure to report
|
||||
the receipt of data in a timely manner.
|
||||
|
||||
endmenu
|
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "usbd_core.h"
|
||||
#include "usbd_cdc_acm.h"
|
||||
|
||||
static char *TAG = "device_cdc_main";
|
||||
|
||||
#ifndef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
#define CDC_ACM_CHANNEL_NUM 1
|
||||
#else
|
||||
#define CDC_ACM_CHANNEL_NUM 2
|
||||
#endif
|
||||
|
||||
#define INTERFACES_NUM (2 * CDC_ACM_CHANNEL_NUM)
|
||||
|
||||
#define NOTIFY_USB_CONFIGURED_BIT BIT0
|
||||
#define NOTIFY_EXIT_BIT BIT1
|
||||
#define NOTIFY_EP_BIT BIT2
|
||||
|
||||
static TaskHandle_t s_task_handle = NULL;
|
||||
|
||||
/*!< endpoint address */
|
||||
#define CDC_IN_EP 0x81
|
||||
#define CDC_OUT_EP 0x01
|
||||
#define CDC_INT_EP 0x83
|
||||
#if CDC_ACM_CHANNEL_NUM >= 2
|
||||
#define CDC_IN_EP1 0x82
|
||||
#define CDC_OUT_EP1 0x02
|
||||
#define CDC_INT_EP1 0x84
|
||||
#endif
|
||||
|
||||
#define USBD_VID 0xFFFF
|
||||
#define USBD_PID 0xFFFF
|
||||
#define USBD_MAX_POWER 100
|
||||
#define USBD_LANGID_STRING 1033
|
||||
|
||||
/*!< config descriptor size */
|
||||
#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN * CDC_ACM_CHANNEL_NUM)
|
||||
|
||||
#define CDC_FS_MAX_MPS 64
|
||||
#define CDC_HS_MAX_MPS 512
|
||||
|
||||
#ifdef CONFIG_USB_HS
|
||||
#define CDC_MAX_MPS CDC_HS_MAX_MPS
|
||||
#else
|
||||
#define CDC_MAX_MPS CDC_FS_MAX_MPS
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t rx_addr;
|
||||
uint8_t tx_addr;
|
||||
bool rx_busy_flag;
|
||||
bool tx_busy_flag;
|
||||
uint32_t read_len;
|
||||
uint32_t write_len;
|
||||
} ep_status_t;
|
||||
|
||||
static ep_status_t s_ep_status[CDC_ACM_CHANNEL_NUM] = {
|
||||
{ CDC_OUT_EP, CDC_IN_EP, false, false, 0, 0 },
|
||||
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
{ CDC_OUT_EP1, CDC_IN_EP1, false, false, 0, 0 },
|
||||
#endif
|
||||
};
|
||||
|
||||
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
|
||||
#define WRITE_BUFFER_SIZE ALIGN_UP(ALIGN_UP(2048, CDC_MAX_MPS), CONFIG_USB_ALIGN_SIZE)
|
||||
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
|
||||
#define READ_BUFFER_SIZE ALIGN_UP(CDC_MAX_MPS, CONFIG_USB_ALIGN_SIZE)
|
||||
#else
|
||||
#define READ_BUFFER_SIZE WRITE_BUFFER_SIZE
|
||||
#endif
|
||||
|
||||
static uint32_t s_mps = CDC_MAX_MPS;
|
||||
static DRAM_DMA_ALIGNED_ATTR uint8_t read_buffer[CDC_ACM_CHANNEL_NUM][READ_BUFFER_SIZE];
|
||||
static DRAM_DMA_ALIGNED_ATTR uint8_t write_buffer[CDC_ACM_CHANNEL_NUM][WRITE_BUFFER_SIZE];
|
||||
|
||||
static const uint8_t device_descriptor[] = {
|
||||
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01)
|
||||
};
|
||||
|
||||
static const uint8_t config_descriptor_fs[] = {
|
||||
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTERFACES_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
|
||||
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_FS_MAX_MPS, 0x02),
|
||||
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
CDC_ACM_DESCRIPTOR_INIT(0x02, CDC_INT_EP1, CDC_OUT_EP1, CDC_IN_EP1, CDC_FS_MAX_MPS, 0x02),
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_USB_HS
|
||||
static const uint8_t config_descriptor_hs[] = {
|
||||
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTERFACES_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
|
||||
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_HS_MAX_MPS, 0x02),
|
||||
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
CDC_ACM_DESCRIPTOR_INIT(0x02, CDC_INT_EP1, CDC_OUT_EP1, CDC_IN_EP1, CDC_HS_MAX_MPS, 0x02),
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
static const uint8_t device_quality_descriptor[] = {
|
||||
///////////////////////////////////////
|
||||
/// device qualifier descriptor
|
||||
///////////////////////////////////////
|
||||
0x0a,
|
||||
USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x40,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
|
||||
static const char *string_descriptors[] = {
|
||||
(const char[])
|
||||
{
|
||||
0x09, 0x04
|
||||
}, /* Langid */
|
||||
"CherryUSB", /* Manufacturer */
|
||||
"CherryUSB CDC MULTI DEMO", /* Product */
|
||||
"2025060518", /* Serial Number */
|
||||
};
|
||||
|
||||
static const uint8_t *device_descriptor_callback(uint8_t speed)
|
||||
{
|
||||
return device_descriptor;
|
||||
}
|
||||
|
||||
static const uint8_t *config_descriptor_callback(uint8_t speed)
|
||||
{
|
||||
#ifdef CONFIG_USB_HS
|
||||
if (speed >= USB_SPEED_HIGH) {
|
||||
s_mps = CDC_HS_MAX_MPS;
|
||||
return config_descriptor_hs;
|
||||
}
|
||||
s_mps = CDC_FS_MAX_MPS;
|
||||
#endif
|
||||
return config_descriptor_fs;
|
||||
}
|
||||
|
||||
static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
|
||||
{
|
||||
return device_quality_descriptor;
|
||||
}
|
||||
|
||||
static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
|
||||
{
|
||||
if (index > 3) {
|
||||
return NULL;
|
||||
}
|
||||
return string_descriptors[index];
|
||||
}
|
||||
|
||||
const struct usb_descriptor cdc_descriptor = {
|
||||
.device_descriptor_callback = device_descriptor_callback,
|
||||
.config_descriptor_callback = config_descriptor_callback,
|
||||
.device_quality_descriptor_callback = device_quality_descriptor_callback,
|
||||
.string_descriptor_callback = string_descriptor_callback
|
||||
};
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
void usbd_event_handler(uint8_t busid, uint8_t event)
|
||||
{
|
||||
switch (event) {
|
||||
case USBD_EVENT_RESET:
|
||||
break;
|
||||
case USBD_EVENT_CONNECTED:
|
||||
break;
|
||||
case USBD_EVENT_DISCONNECTED:
|
||||
break;
|
||||
case USBD_EVENT_RESUME:
|
||||
break;
|
||||
case USBD_EVENT_SUSPEND:
|
||||
break;
|
||||
case USBD_EVENT_CONFIGURED: {
|
||||
BaseType_t HPTaskAwoken = pdFALSE;
|
||||
xTaskNotifyFromISR(s_task_handle, NOTIFY_USB_CONFIGURED_BIT, eSetBits, &HPTaskAwoken);
|
||||
if (HPTaskAwoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case USBD_EVENT_SET_REMOTE_WAKEUP:
|
||||
break;
|
||||
case USBD_EVENT_CLR_REMOTE_WAKEUP:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
static void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
|
||||
{
|
||||
ESP_EARLY_LOGI(TAG, "ep 0x%02X actual out len:%d", ep, nbytes);
|
||||
BaseType_t HPTaskAwoken = pdFALSE;
|
||||
for (size_t i = 0; i < CDC_ACM_CHANNEL_NUM; i++) {
|
||||
ep_status_t *ep_status = &s_ep_status[i];
|
||||
if (ep_status->rx_addr == ep) {
|
||||
if (nbytes > 0) {
|
||||
if (ep_status->write_len == 0) {
|
||||
memcpy(write_buffer[i], read_buffer[i], nbytes);
|
||||
ep_status->write_len = nbytes;
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
|
||||
usbd_ep_start_read(0, ep, read_buffer[i], s_mps);
|
||||
#else
|
||||
usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[0]));
|
||||
#endif
|
||||
} else {
|
||||
ep_status->read_len = nbytes;
|
||||
ep_status->rx_busy_flag = false;
|
||||
}
|
||||
xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken);
|
||||
} else {
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
|
||||
usbd_ep_start_read(0, ep, read_buffer[i], s_mps);
|
||||
#else
|
||||
usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[i]));
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (HPTaskAwoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
static void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
|
||||
{
|
||||
BaseType_t HPTaskAwoken = pdFALSE;
|
||||
ESP_EARLY_LOGI(TAG, "ep 0x%02X actual in len:%d", ep, nbytes);
|
||||
if ((nbytes % s_mps) == 0 && nbytes) {
|
||||
usbd_ep_start_write(0, ep, NULL, 0);
|
||||
} else {
|
||||
for (size_t i = 0; i < CDC_ACM_CHANNEL_NUM; i++) {
|
||||
ep_status_t *ep_status = &s_ep_status[i];
|
||||
if (ep_status->tx_addr == ep) {
|
||||
ep_status->write_len = 0;
|
||||
ep_status->tx_busy_flag = false;
|
||||
xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (HPTaskAwoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
struct usbd_endpoint cdc_out_ep = {
|
||||
.ep_addr = CDC_OUT_EP,
|
||||
.ep_cb = usbd_cdc_acm_bulk_out
|
||||
};
|
||||
|
||||
struct usbd_endpoint cdc_in_ep = {
|
||||
.ep_addr = CDC_IN_EP,
|
||||
.ep_cb = usbd_cdc_acm_bulk_in
|
||||
};
|
||||
|
||||
struct usbd_interface intf0;
|
||||
struct usbd_interface intf1;
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
struct usbd_endpoint cdc_out_ep1 = {
|
||||
.ep_addr = CDC_OUT_EP1,
|
||||
.ep_cb = usbd_cdc_acm_bulk_out
|
||||
};
|
||||
|
||||
struct usbd_endpoint cdc_in_ep1 = {
|
||||
.ep_addr = CDC_IN_EP1,
|
||||
.ep_cb = usbd_cdc_acm_bulk_in
|
||||
};
|
||||
|
||||
struct usbd_interface intf2;
|
||||
struct usbd_interface intf3;
|
||||
#endif
|
||||
|
||||
static void cdc_acm_init(void)
|
||||
{
|
||||
usbd_desc_register(0, &cdc_descriptor);
|
||||
|
||||
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf0));
|
||||
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf1));
|
||||
usbd_add_endpoint(0, &cdc_out_ep);
|
||||
usbd_add_endpoint(0, &cdc_in_ep);
|
||||
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
|
||||
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf2));
|
||||
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf3));
|
||||
usbd_add_endpoint(0, &cdc_out_ep1);
|
||||
usbd_add_endpoint(0, &cdc_in_ep1);
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_USB_DEVICE_RHPORT_HS
|
||||
usbd_initialize(0, ESP_USB_HS0_BASE, usbd_event_handler);
|
||||
#else
|
||||
usbd_initialize(0, ESP_USB_FS0_BASE, usbd_event_handler);
|
||||
#endif
|
||||
}
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)
|
||||
{
|
||||
ESP_EARLY_LOGI(TAG, "intf:%u, dtr:%d", intf, dtr);
|
||||
}
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
void usbd_cdc_acm_set_rts(uint8_t busid, uint8_t intf, bool rts)
|
||||
{
|
||||
ESP_EARLY_LOGI(TAG, "intf:%u, rts:%d", intf, rts);
|
||||
}
|
||||
|
||||
void cdc_acm_task(void *arg)
|
||||
{
|
||||
uint32_t notify_value = 0;
|
||||
ESP_LOGI(TAG, "usb cdc acm task start");
|
||||
// Initialize the USB driver and CDC interface
|
||||
cdc_acm_init();
|
||||
ESP_LOGI(TAG, "usb cdc acm init done");
|
||||
|
||||
while (1) {
|
||||
xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
|
||||
if (notify_value & NOTIFY_EXIT_BIT) {
|
||||
break;
|
||||
}
|
||||
if (notify_value & NOTIFY_USB_CONFIGURED_BIT) {
|
||||
/* setup first out ep read transfer */
|
||||
for (size_t i = 0; i < sizeof(s_ep_status) / sizeof(s_ep_status[0]); i++) {
|
||||
ep_status_t *ep_status = &s_ep_status[i];
|
||||
ep_status->write_len = 0;
|
||||
ep_status->tx_busy_flag = false;
|
||||
|
||||
ep_status->read_len = 0;
|
||||
ep_status->rx_busy_flag = true;
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
|
||||
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], s_mps);
|
||||
#else
|
||||
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[0]));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(s_ep_status) / sizeof(s_ep_status[0]); i++) {
|
||||
ep_status_t *ep_status = &s_ep_status[i];
|
||||
if (ep_status->tx_busy_flag == false) {
|
||||
if (ep_status->write_len == 0 && ep_status->read_len > 0) {
|
||||
memcpy(&write_buffer[i][0], &read_buffer[i][0], ep_status->read_len);
|
||||
ep_status->write_len = ep_status->read_len;
|
||||
ep_status->read_len = 0;
|
||||
}
|
||||
if (ep_status->write_len > 0) {
|
||||
ep_status->tx_busy_flag = true;
|
||||
usbd_ep_start_write(0, ep_status->tx_addr, write_buffer[i], ep_status->write_len);
|
||||
}
|
||||
}
|
||||
if (ep_status->rx_busy_flag == false) {
|
||||
if (ep_status->read_len == 0) {
|
||||
ep_status->rx_busy_flag = true;
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
|
||||
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], s_mps);
|
||||
#else
|
||||
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[i]));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usbd_deinitialize(0);
|
||||
ESP_LOGW(TAG, "task exit");
|
||||
s_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
|
||||
while (1)
|
||||
#endif
|
||||
{
|
||||
xTaskCreatePinnedToCore(&cdc_acm_task, "cdc_acm_task", 1024 * 3, NULL, 10, &s_task_handle, 0);
|
||||
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
ESP_LOGW(TAG, "Deinit usb after %d seconds...", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
ESP_LOGW(TAG, "Notify deinit usb");
|
||||
xTaskNotify(s_task_handle, NOTIFY_EXIT_BIT, eSetBits);
|
||||
while (s_task_handle) {
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
cherry-embedded/cherryusb: 1.5.2~1
|
@@ -0,0 +1,4 @@
|
||||
CONFIG_CHERRYUSB=y
|
||||
CONFIG_CHERRYUSB_DEVICE=y
|
||||
CONFIG_CHERRYUSB_DEVICE_DWC2_ESP=y
|
||||
CONFIG_CHERRYUSB_DEVICE_CDC_ACM=y
|
@@ -0,0 +1,7 @@
|
||||
# The following 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.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(cherryusb_host)
|
73
examples/peripherals/usb/host/cherryusb_host/README.md
Normal file
73
examples/peripherals/usb/host/cherryusb_host/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | -------- | -------- | -------- |
|
||||
|
||||
# CherryUSB Host Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use the CherryUSB host driver. Currently, this example supports communication with HID devices (such as Keyboard and Mouse), serial port devices (such as CDC_ACM, CH34x, CP210x, PL2303, FTDI FT23x/FT423x devices) and MSC (Mass Storage Class).
|
||||
|
||||
## How to use example
|
||||
|
||||
By default, all drivers supported by the routine are enabled. If you need to trim or disable unnecessary drivers, please disable the corresponding driver configuration.
|
||||
Run `idf.py menuconfig` and in `Component config → CherryUSB Configuration → Enable usb host mode`, Uncheck the drivers you don’t need.
|
||||
|
||||
### Hardware Required
|
||||
* Development board with USB-OTG support
|
||||
* A USB cable for Power supply and programming
|
||||
* USB OTG Cable
|
||||
|
||||
#### Pin Assignment
|
||||
|
||||
Follow instruction in [examples/usb/README.md](../../../README.md) for specific hardware setup.
|
||||
|
||||
### 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.
|
||||
|
||||
## Example output
|
||||
|
||||
When running, the example will print the following output:
|
||||
|
||||
```
|
||||
I (264) main_task: Started on CPU0
|
||||
I (304) main_task: Calling app_main()
|
||||
[I/USB] cherryusb, version: v1.5.0
|
||||
[I/USB] ========== dwc2 hcd params ==========
|
||||
[I/USB] CID:00000000
|
||||
[I/USB] GSNPSID:4f54400a
|
||||
[I/USB] GHWCFG1:00000000
|
||||
[I/USB] GHWCFG2:215fffd0
|
||||
[I/USB] GHWCFG3:03805eb5
|
||||
[I/USB] GHWCFG4:dff1a030
|
||||
[I/USB] dwc2 has 16 channels and dfifo depth(32-bit words) is 896
|
||||
I (334) HOST: Init usb
|
||||
I (334) main_task: Returned from app_main()
|
||||
[I/usbh_hub] New low-speed device on Bus 0, Hub 1, Port 1 connected
|
||||
[I/usbh_core] New device found,idVendor:413c,idProduct:2113,bcdDevice:0110
|
||||
[I/usbh_core] The device has 1 bNumConfigurations
|
||||
[I/usbh_core] The device has 2 interfaces
|
||||
[I/usbh_core] Enumeration success, start loading class driver
|
||||
[I/usbh_core] Loading hid class driver
|
||||
[I/usbh_hid] Ep=81 Attr=03 Mps=8 Interval=10 Mult=00
|
||||
[I/usbh_hid] Register HID Class:/dev/input0
|
||||
I (1054) HID: intf 0, SubClass 1, Protocol 1
|
||||
[I/usbh_core] Loading hid class driver
|
||||
[W/usbh_hid] Do not support set idle
|
||||
[I/usbh_hid] Ep=82 Attr=03 Mps=3 Interval=10 Mult=00
|
||||
[I/usbh_hid] Register HID Class:/dev/input1
|
||||
I (1074) HID: intf 1, SubClass 0, Protocol 0
|
||||
|
||||
Keyboard
|
||||
xiaodou
|
||||
```
|
@@ -0,0 +1,35 @@
|
||||
set(srcs "main.c")
|
||||
|
||||
if(CONFIG_CHERRYUSB_HOST_HID)
|
||||
list(APPEND srcs "hid.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_CHERRYUSB_HOST_CDC_ACM OR CONFIG_CHERRYUSB_HOST_FTDI OR CONFIG_CHERRYUSB_HOST_CH34X OR
|
||||
CONFIG_CHERRYUSB_HOST_CP210X OR CONFIG_CHERRYUSB_HOST_PL2303)
|
||||
list(APPEND srcs "cdc_acm.c")
|
||||
set(CHERRYUSB_HOST_CDC_ACM_INCLUDE TRUE)
|
||||
endif()
|
||||
|
||||
if(CONFIG_CHERRYUSB_HOST_MSC)
|
||||
list(APPEND srcs "msc.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES esp_timer fatfs esp_ringbuf
|
||||
)
|
||||
|
||||
if(CONFIG_CHERRYUSB_HOST_HID)
|
||||
# Make sure the definitions in hid.c are linked correctly
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_hid")
|
||||
endif()
|
||||
|
||||
if(CHERRYUSB_HOST_CDC_ACM_INCLUDE)
|
||||
# Make sure the definitions in cdc_acm.c are linked correctly
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_cdc_acm")
|
||||
endif()
|
||||
|
||||
if(CONFIG_CHERRYUSB_HOST_MSC)
|
||||
# Make sure the definitions in msc.c are linked correctly
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_msc")
|
||||
endif()
|
@@ -0,0 +1,38 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_USB_HOST_RHPORT
|
||||
prompt "USB Host Peripheral"
|
||||
default EXAMPLE_USB_HOST_RHPORT_HS if IDF_TARGET_ESP32P4
|
||||
default EXAMPLE_USB_HOST_RHPORT_FS
|
||||
help
|
||||
Allows set the USB Peripheral Controller for USB host.
|
||||
|
||||
- High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed)
|
||||
- Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed)
|
||||
|
||||
config EXAMPLE_USB_HOST_RHPORT_HS
|
||||
bool "OTG2.0"
|
||||
depends on IDF_TARGET_ESP32P4
|
||||
config EXAMPLE_USB_HOST_RHPORT_FS
|
||||
bool "OTG1.1"
|
||||
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_HAL_USE_ESP32_S3_USB_OTG
|
||||
bool "Use dev kit ESP32-S3-USB-OTG"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
default y
|
||||
|
||||
config EXAMPLE_FORMAT_IF_MOUNT_FAILED
|
||||
bool "Format the card if mount failed"
|
||||
depends on CHERRYUSB_HOST_MSC
|
||||
default n
|
||||
help
|
||||
If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if
|
||||
the mount has failed.
|
||||
|
||||
config EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
|
||||
bool "Perform init deinit of cherryusb stack in a loop"
|
||||
default n
|
||||
|
||||
endmenu
|
341
examples/peripherals/usb/host/cherryusb_host/main/cdc_acm.c
Normal file
341
examples/peripherals/usb/host/cherryusb_host/main/cdc_acm.c
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "usbh_core.h"
|
||||
|
||||
#if CONFIG_CHERRYUSB_HOST_CDC_ACM
|
||||
#include "usbh_cdc_acm.h"
|
||||
#endif
|
||||
#if CONFIG_CHERRYUSB_HOST_FTDI
|
||||
#include "usbh_ftdi.h"
|
||||
#endif
|
||||
#if CONFIG_CHERRYUSB_HOST_CH34X
|
||||
#include "usbh_ch34x.h"
|
||||
#endif
|
||||
#if CONFIG_CHERRYUSB_HOST_CP210X
|
||||
#include "usbh_cp210x.h"
|
||||
#endif
|
||||
#if CONFIG_CHERRYUSB_HOST_PL2303
|
||||
#include "usbh_pl2303.h"
|
||||
#endif
|
||||
|
||||
static char *TAG = "CDC_ACM";
|
||||
|
||||
#define RINGBUF_SIZE 1024
|
||||
static RingbufHandle_t s_recv_ringbuf = NULL;
|
||||
static TaskHandle_t s_printf_task_handle = NULL;
|
||||
|
||||
typedef enum {
|
||||
SERIAL_TYPE_CDC_ACM = 0,
|
||||
SERIAL_TYPE_FTDI,
|
||||
SERIAL_TYPE_CH34X,
|
||||
SERIAL_TYPE_CP210X,
|
||||
SERIAL_TYPE_PL2303,
|
||||
} serial_type_t;
|
||||
|
||||
typedef struct {
|
||||
serial_type_t type;
|
||||
struct usbh_hubport *hport;
|
||||
|
||||
struct usbh_urb *bulkout_urb;
|
||||
struct usb_endpoint_descriptor *bulkout;
|
||||
uint8_t *out_buffer;
|
||||
|
||||
struct usbh_urb *bulkin_urb;
|
||||
struct usb_endpoint_descriptor *bulkin;
|
||||
uint8_t *in_buffer;
|
||||
} serial_t;
|
||||
|
||||
static void free_serial_buffer(serial_t *serial);
|
||||
static esp_err_t serial_start_in(serial_t *serial);
|
||||
|
||||
void ld_include_cdc_acm(void)
|
||||
{
|
||||
}
|
||||
|
||||
#define alloc_serial_buffer(serial_class, serial_type, out_serial) \
|
||||
{ \
|
||||
out_serial = heap_caps_calloc(1, sizeof(serial_t), MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); \
|
||||
if (out_serial) { \
|
||||
out_serial->type = serial_type; \
|
||||
out_serial->hport = serial_class->hport; \
|
||||
out_serial->bulkout_urb = &serial_class->bulkout_urb; \
|
||||
out_serial->bulkout = serial_class->bulkout; \
|
||||
out_serial->out_buffer = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, out_serial->bulkout->wMaxPacketSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); \
|
||||
out_serial->bulkin_urb = &serial_class->bulkin_urb; \
|
||||
out_serial->bulkin = serial_class->bulkin; \
|
||||
out_serial->in_buffer = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, out_serial->bulkin->wMaxPacketSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); \
|
||||
if(out_serial->out_buffer == NULL || out_serial->in_buffer == NULL) { \
|
||||
free_serial_buffer(out_serial); \
|
||||
out_serial = NULL; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
static void free_serial_buffer(serial_t *serial)
|
||||
{
|
||||
if (serial == NULL) {
|
||||
return;
|
||||
}
|
||||
if (serial->out_buffer) {
|
||||
heap_caps_free(serial->out_buffer);
|
||||
}
|
||||
if (serial->in_buffer) {
|
||||
heap_caps_free(serial->in_buffer);
|
||||
}
|
||||
heap_caps_free(serial);
|
||||
}
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
static void serial_in_cb(void *arg, int nbytes)
|
||||
{
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
serial_t *serial = (serial_t *)arg;
|
||||
uint8_t *data = serial->in_buffer;
|
||||
if (nbytes < 0) {
|
||||
return;
|
||||
}
|
||||
if (serial->type == SERIAL_TYPE_FTDI) {
|
||||
if (nbytes <= 2) {
|
||||
serial_start_in(serial);
|
||||
return;
|
||||
}
|
||||
//FTDI Skip the first two bytes (header)
|
||||
data += 2;
|
||||
nbytes -= 2;
|
||||
}
|
||||
|
||||
if (s_recv_ringbuf) {
|
||||
if (xRingbufferSendFromISR(s_recv_ringbuf, data, nbytes, &xTaskWoken) != pdTRUE) {
|
||||
ESP_LOGD(TAG, "Ringbuffer send failed");
|
||||
}
|
||||
}
|
||||
if (xTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
serial_start_in(serial);
|
||||
}
|
||||
|
||||
static esp_err_t serial_start_in(serial_t *serial)
|
||||
{
|
||||
int ret;
|
||||
usbh_bulk_urb_fill(serial->bulkin_urb, serial->hport, serial->bulkin, serial->in_buffer, serial->bulkin->wMaxPacketSize, 0, serial_in_cb, serial);
|
||||
ret = usbh_submit_urb(serial->bulkin_urb);
|
||||
if (ret != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t serial_out(serial_t *serial, uint8_t *data, size_t len, uint32_t timeout)
|
||||
{
|
||||
int ret;
|
||||
if (len > serial->bulkout->wMaxPacketSize) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
memcpy(serial->out_buffer, data, len);
|
||||
usbh_bulk_urb_fill(serial->bulkout_urb, serial->hport, serial->bulkout, serial->out_buffer, len, timeout, NULL, NULL);
|
||||
ret = usbh_submit_urb(serial->bulkout_urb);
|
||||
if (ret != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void usbh_cdc_acm_printf_task(void *arg)
|
||||
{
|
||||
while (1) {
|
||||
size_t length = 0;
|
||||
char *data = (char *)xRingbufferReceive(s_recv_ringbuf, &length, portMAX_DELAY);
|
||||
if (data == NULL) {
|
||||
continue;
|
||||
}
|
||||
ESP_LOGI(TAG, "Data received");
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, length, ESP_LOG_INFO);
|
||||
vRingbufferReturnItem(s_recv_ringbuf, (void *)data);
|
||||
fflush(stdout);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void creat_printf_task(void)
|
||||
{
|
||||
if (s_recv_ringbuf == NULL) {
|
||||
s_recv_ringbuf = xRingbufferCreate(RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (s_recv_ringbuf == NULL) {
|
||||
ESP_LOGE(TAG, "ringbuf create failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (s_printf_task_handle == NULL) {
|
||||
xTaskCreate(usbh_cdc_acm_printf_task, "usbh_cdc_acm_printf_task", 4096, NULL, 5, &s_printf_task_handle);
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_CHERRYUSB_HOST_CDC_ACM
|
||||
|
||||
void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class)
|
||||
{
|
||||
serial_t *serial;
|
||||
alloc_serial_buffer(cdc_acm_class, SERIAL_TYPE_CDC_ACM, serial);
|
||||
if (serial == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
return;
|
||||
}
|
||||
creat_printf_task();
|
||||
cdc_acm_class->user_data = serial;
|
||||
|
||||
struct cdc_line_coding linecoding = {
|
||||
.dwDTERate = 115200,
|
||||
.bDataBits = 8,
|
||||
.bParityType = 0,
|
||||
.bCharFormat = 0,
|
||||
};
|
||||
usbh_cdc_acm_set_line_coding(cdc_acm_class, &linecoding);
|
||||
|
||||
serial_start_in(serial);
|
||||
const char data[] = "CDC: Hello, world!\r\n";
|
||||
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
|
||||
}
|
||||
|
||||
void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class)
|
||||
{
|
||||
free_serial_buffer(cdc_acm_class->user_data);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_CHERRYUSB_HOST_FTDI
|
||||
void usbh_ftdi_run(struct usbh_ftdi *ftdi_class)
|
||||
{
|
||||
serial_t *serial;
|
||||
alloc_serial_buffer(ftdi_class, SERIAL_TYPE_FTDI, serial);
|
||||
if (serial == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
return;
|
||||
}
|
||||
creat_printf_task();
|
||||
ftdi_class->user_data = serial;
|
||||
|
||||
struct cdc_line_coding linecoding = {
|
||||
.dwDTERate = 115200,
|
||||
.bDataBits = 8,
|
||||
.bParityType = 0,
|
||||
.bCharFormat = 0,
|
||||
};
|
||||
usbh_ftdi_set_line_coding(ftdi_class, &linecoding);
|
||||
|
||||
serial_start_in(serial);
|
||||
const char data[] = "FTDI: Hello, world!\r\n";
|
||||
|
||||
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
|
||||
}
|
||||
|
||||
void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class)
|
||||
{
|
||||
free_serial_buffer(ftdi_class->user_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_CHERRYUSB_HOST_CH34X
|
||||
void usbh_ch34x_run(struct usbh_ch34x *ch34x_class)
|
||||
{
|
||||
serial_t *serial;
|
||||
alloc_serial_buffer(ch34x_class, SERIAL_TYPE_CH34X, serial);
|
||||
if (serial == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
return;
|
||||
}
|
||||
creat_printf_task();
|
||||
ch34x_class->user_data = serial;
|
||||
|
||||
struct cdc_line_coding linecoding = {
|
||||
.dwDTERate = 115200,
|
||||
.bDataBits = 8,
|
||||
.bParityType = 0,
|
||||
.bCharFormat = 0,
|
||||
};
|
||||
usbh_ch34x_set_line_coding(ch34x_class, &linecoding);
|
||||
|
||||
serial_start_in(serial);
|
||||
const char data[] = "CH34x: Hello, world!\r\n";
|
||||
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
|
||||
}
|
||||
|
||||
void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class)
|
||||
{
|
||||
free_serial_buffer(ch34x_class->user_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_CHERRYUSB_HOST_CP210X
|
||||
void usbh_cp210x_run(struct usbh_cp210x *cp210x_class)
|
||||
{
|
||||
serial_t *serial;
|
||||
alloc_serial_buffer(cp210x_class, SERIAL_TYPE_CP210X, serial);
|
||||
if (serial == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
return;
|
||||
}
|
||||
creat_printf_task();
|
||||
cp210x_class->user_data = serial;
|
||||
|
||||
struct cdc_line_coding linecoding = {
|
||||
.dwDTERate = 115200,
|
||||
.bDataBits = 8,
|
||||
.bParityType = 0,
|
||||
.bCharFormat = 0,
|
||||
};
|
||||
usbh_cp210x_set_line_coding(cp210x_class, &linecoding);
|
||||
|
||||
serial_start_in(serial);
|
||||
const char data[] = "CP201x: Hello, world!\r\n";
|
||||
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
|
||||
}
|
||||
|
||||
void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class)
|
||||
{
|
||||
free_serial_buffer(cp210x_class->user_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_CHERRYUSB_HOST_PL2303
|
||||
void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class)
|
||||
{
|
||||
serial_t *serial;
|
||||
alloc_serial_buffer(pl2303_class, SERIAL_TYPE_PL2303, serial);
|
||||
if (serial == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
return;
|
||||
}
|
||||
creat_printf_task();
|
||||
pl2303_class->user_data = serial;
|
||||
|
||||
struct cdc_line_coding linecoding = {
|
||||
.dwDTERate = 115200,
|
||||
.bDataBits = 8,
|
||||
.bParityType = 0,
|
||||
.bCharFormat = 0,
|
||||
};
|
||||
usbh_pl2303_set_line_coding(pl2303_class, &linecoding);
|
||||
|
||||
serial_start_in(serial);
|
||||
const char data[] = "PL2303: Hello, world!\r\n";
|
||||
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
|
||||
}
|
||||
|
||||
void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class)
|
||||
{
|
||||
free_serial_buffer(pl2303_class->user_data);
|
||||
}
|
||||
#endif
|
580
examples/peripherals/usb/host/cherryusb_host/main/hid.c
Normal file
580
examples/peripherals/usb/host/cherryusb_host/main/hid.c
Normal file
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "usbh_core.h"
|
||||
#include "usbh_hid.h"
|
||||
|
||||
static char *TAG = "HID";
|
||||
|
||||
typedef struct {
|
||||
bool is_active;
|
||||
esp_timer_handle_t timer;
|
||||
uint8_t *buffer;
|
||||
} hid_int_in_t;
|
||||
|
||||
/**
|
||||
* @brief Key event
|
||||
*/
|
||||
typedef struct {
|
||||
enum key_state {
|
||||
KEY_STATE_PRESSED = 0x00,
|
||||
KEY_STATE_RELEASED = 0x01
|
||||
} state;
|
||||
uint8_t modifier;
|
||||
uint8_t key_code;
|
||||
} key_event_t;
|
||||
|
||||
/**
|
||||
* @brief hid msg
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t protocol;
|
||||
uint16_t len;
|
||||
uint8_t data[64];
|
||||
} hid_msg_t;
|
||||
|
||||
#define QUEUE_LEN 10
|
||||
static QueueHandle_t s_msg_queue = NULL;
|
||||
static TaskHandle_t s_msg_task_handle = NULL;
|
||||
|
||||
/* Main char symbol for ENTER key */
|
||||
#define KEYBOARD_ENTER_MAIN_CHAR '\r'
|
||||
/* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */
|
||||
#define KEYBOARD_ENTER_LF_EXTEND 1
|
||||
/* When set to 1, numbers entered from the numeric keypad while ALT is pressed will be escaped */
|
||||
#define KEYBOARD_ENTER_ALT_ESCAPE 1
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
static bool is_ansi = false;
|
||||
static unsigned int alt_code = 0;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Scancode to ascii table
|
||||
*/
|
||||
const uint8_t keycode2ascii [57][2] = {
|
||||
{0, 0}, /* HID_KEY_NO_PRESS */
|
||||
{0, 0}, /* HID_KEY_ROLLOVER */
|
||||
{0, 0}, /* HID_KEY_POST_FAIL */
|
||||
{0, 0}, /* HID_KBD_USAGE_ERRUNDEF */
|
||||
{'a', 'A'}, /* HID_KEY_A */
|
||||
{'b', 'B'}, /* HID_KEY_B */
|
||||
{'c', 'C'}, /* HID_KEY_C */
|
||||
{'d', 'D'}, /* HID_KEY_D */
|
||||
{'e', 'E'}, /* HID_KEY_E */
|
||||
{'f', 'F'}, /* HID_KEY_F */
|
||||
{'g', 'G'}, /* HID_KEY_G */
|
||||
{'h', 'H'}, /* HID_KEY_H */
|
||||
{'i', 'I'}, /* HID_KEY_I */
|
||||
{'j', 'J'}, /* HID_KEY_J */
|
||||
{'k', 'K'}, /* HID_KEY_K */
|
||||
{'l', 'L'}, /* HID_KEY_L */
|
||||
{'m', 'M'}, /* HID_KEY_M */
|
||||
{'n', 'N'}, /* HID_KEY_N */
|
||||
{'o', 'O'}, /* HID_KEY_O */
|
||||
{'p', 'P'}, /* HID_KEY_P */
|
||||
{'q', 'Q'}, /* HID_KEY_Q */
|
||||
{'r', 'R'}, /* HID_KEY_R */
|
||||
{'s', 'S'}, /* HID_KEY_S */
|
||||
{'t', 'T'}, /* HID_KEY_T */
|
||||
{'u', 'U'}, /* HID_KEY_U */
|
||||
{'v', 'V'}, /* HID_KEY_V */
|
||||
{'w', 'W'}, /* HID_KEY_W */
|
||||
{'x', 'X'}, /* HID_KEY_X */
|
||||
{'y', 'Y'}, /* HID_KEY_Y */
|
||||
{'z', 'Z'}, /* HID_KEY_Z */
|
||||
{'1', '!'}, /* HID_KEY_1 */
|
||||
{'2', '@'}, /* HID_KEY_2 */
|
||||
{'3', '#'}, /* HID_KEY_3 */
|
||||
{'4', '$'}, /* HID_KEY_4 */
|
||||
{'5', '%'}, /* HID_KEY_5 */
|
||||
{'6', '^'}, /* HID_KEY_6 */
|
||||
{'7', '&'}, /* HID_KEY_7 */
|
||||
{'8', '*'}, /* HID_KEY_8 */
|
||||
{'9', '('}, /* HID_KEY_9 */
|
||||
{'0', ')'}, /* HID_KEY_0 */
|
||||
{KEYBOARD_ENTER_MAIN_CHAR, KEYBOARD_ENTER_MAIN_CHAR}, /* HID_KEY_ENTER */
|
||||
{0, 0}, /* HID_KEY_ESC */
|
||||
{'\b', 0}, /* HID_KEY_DEL */
|
||||
{0, 0}, /* HID_KEY_TAB */
|
||||
{' ', ' '}, /* HID_KEY_SPACE */
|
||||
{'-', '_'}, /* HID_KEY_MINUS */
|
||||
{'=', '+'}, /* HID_KEY_EQUAL */
|
||||
{'[', '{'}, /* HID_KEY_OPEN_BRACKET */
|
||||
{']', '}'}, /* HID_KEY_CLOSE_BRACKET */
|
||||
{'\\', '|'}, /* HID_KEY_BACK_SLASH */
|
||||
{'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH
|
||||
{';', ':'}, /* HID_KEY_COLON */
|
||||
{'\'', '"'}, /* HID_KEY_QUOTE */
|
||||
{'`', '~'}, /* HID_KEY_TILDE */
|
||||
{',', '<'}, /* HID_KEY_LESS */
|
||||
{'.', '>'}, /* HID_KEY_GREATER */
|
||||
{'/', '?'} /* HID_KBD_USAGE_QUESTION */
|
||||
};
|
||||
|
||||
void ld_include_hid(void)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard print char symbol
|
||||
*
|
||||
* @param[in] key_char Keyboard char to stdout
|
||||
*/
|
||||
static inline void hid_keyboard_print_char(unsigned int key_char)
|
||||
{
|
||||
if (!!key_char) {
|
||||
putchar(key_char);
|
||||
#if (KEYBOARD_ENTER_LF_EXTEND)
|
||||
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
|
||||
putchar('\n');
|
||||
}
|
||||
#endif // KEYBOARD_ENTER_LF_EXTEND
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes new line depending on report output protocol type
|
||||
*
|
||||
* @param[in] proto Current protocol to output
|
||||
*/
|
||||
static void hid_print_new_device_report_header(int proto)
|
||||
{
|
||||
static int prev_proto_output = -1;
|
||||
|
||||
if (prev_proto_output != proto) {
|
||||
prev_proto_output = proto;
|
||||
printf("\r\n");
|
||||
if (proto == HID_PROTOCOL_MOUSE) {
|
||||
printf("Mouse\r\n");
|
||||
} else if (proto == HID_PROTOCOL_KEYBOARD) {
|
||||
printf("Keyboard\r\n");
|
||||
} else {
|
||||
printf("Generic\r\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard modifier verification for capitalization application (right or left shift)
|
||||
*
|
||||
* @param[in] modifier
|
||||
* @return true Modifier was pressed (left or right shift)
|
||||
* @return false Modifier was not pressed (left or right shift)
|
||||
*
|
||||
*/
|
||||
static inline bool hid_keyboard_is_modifier_shift(uint8_t modifier)
|
||||
{
|
||||
if (((modifier & HID_MODIFIER_LSHIFT) == HID_MODIFIER_LSHIFT) ||
|
||||
((modifier & HID_MODIFIER_RSHIFT) == HID_MODIFIER_RSHIFT)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
/**
|
||||
* @brief HID Keyboard modifier verification for capitalization application (right or left alt)
|
||||
*
|
||||
* @param[in] modifier
|
||||
* @return true Modifier was pressed (left or right alt)
|
||||
* @return false Modifier was not pressed (left or right alt)
|
||||
*
|
||||
*/
|
||||
static inline bool hid_keyboard_is_modifier_alt(uint8_t modifier)
|
||||
{
|
||||
if (((modifier & HID_MODIFIER_LALT) == HID_MODIFIER_LALT) ||
|
||||
((modifier & HID_MODIFIER_RALT) == HID_MODIFIER_RALT)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard alt code process(Called when ALT is pressed)
|
||||
*
|
||||
* @param[in] key_code Entered key value
|
||||
* @return true Key values that qualify for ALT escape processing
|
||||
* @return false Key values that do not comply with ALT escape processing
|
||||
*
|
||||
*/
|
||||
static inline bool hid_keyboard_alt_code_processing(uint8_t key_code)
|
||||
{
|
||||
if ((key_code < HID_KBD_USAGE_KPD1) || (key_code > HID_KBD_USAGE_KPD0)) {
|
||||
return false;
|
||||
}
|
||||
if (key_code == HID_KBD_USAGE_KPD0) {
|
||||
if (alt_code == 0) {
|
||||
is_ansi = true;
|
||||
return true;
|
||||
}
|
||||
/* Note: Since the keyboard code 0 of the numeric keypad is not keyboard code 1 minus 1, the
|
||||
* conversion is performed here to facilitate subsequent calculations of the input numbers.
|
||||
*/
|
||||
key_code = HID_KBD_USAGE_KPD1 - 1;
|
||||
}
|
||||
alt_code = alt_code * 10 + (key_code - (HID_KBD_USAGE_KPD1 - 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard alt code process complete(Called when ALT is not pressed)
|
||||
*/
|
||||
static inline void hid_keyboard_alt_code_process_complete(void)
|
||||
{
|
||||
if (alt_code > 0) {
|
||||
alt_code = alt_code & 0xff;
|
||||
if (is_ansi || alt_code == 0) {
|
||||
char utf8_buffer[8] = { 0 };
|
||||
if (alt_code == 0) {
|
||||
alt_code = 0x100;
|
||||
}
|
||||
//ANSI is processed as UTF8
|
||||
if (alt_code <= 0x7F) {
|
||||
utf8_buffer[0] = (char)alt_code;
|
||||
} else {
|
||||
utf8_buffer[0] = 0xC0 | ((alt_code >> 6) & 0x1F);
|
||||
utf8_buffer[1] = 0x80 | (alt_code & 0x3F);
|
||||
}
|
||||
printf("%s", utf8_buffer);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
hid_keyboard_print_char(alt_code);
|
||||
}
|
||||
alt_code = 0;
|
||||
}
|
||||
is_ansi = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard get char symbol from key code
|
||||
*
|
||||
* @param[in] modifier Keyboard modifier data
|
||||
* @param[in] key_code Keyboard key code
|
||||
* @param[in] key_char Pointer to key char data
|
||||
*
|
||||
* @return true Key scancode converted successfully
|
||||
* @return false Key scancode unknown
|
||||
*/
|
||||
static inline bool hid_keyboard_get_char(uint8_t modifier,
|
||||
uint8_t key_code,
|
||||
unsigned char *key_char)
|
||||
{
|
||||
uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0;
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
if (hid_keyboard_is_modifier_alt(modifier)) {
|
||||
if (hid_keyboard_alt_code_processing(key_code)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((key_code >= HID_KBD_USAGE_A) && (key_code <= HID_KBD_USAGE_QUESTION)) {
|
||||
*key_char = keycode2ascii[key_code][mod];
|
||||
} else {
|
||||
// All other key pressed
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Key Event. Key event with the key code, state and modifier.
|
||||
*
|
||||
* @param[in] key_event Pointer to Key Event structure
|
||||
*
|
||||
*/
|
||||
static void key_event_callback(key_event_t *key_event)
|
||||
{
|
||||
unsigned char key_char;
|
||||
|
||||
hid_print_new_device_report_header(HID_PROTOCOL_KEYBOARD);
|
||||
|
||||
if (KEY_STATE_PRESSED == key_event->state) {
|
||||
if (hid_keyboard_get_char(key_event->modifier,
|
||||
key_event->key_code, &key_char)) {
|
||||
|
||||
hid_keyboard_print_char(key_char);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Key buffer scan code search.
|
||||
*
|
||||
* @param[in] src Pointer to source buffer where to search
|
||||
* @param[in] key Key scancode to search
|
||||
* @param[in] length Size of the source buffer
|
||||
*/
|
||||
static inline bool key_found(const uint8_t *const src,
|
||||
uint8_t key,
|
||||
unsigned int length)
|
||||
{
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
if (src[i] == key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void usbh_hid_keyboard_report_callback(void *arg, int nbytes)
|
||||
{
|
||||
struct usb_hid_kbd_report *kb_report = (struct usb_hid_kbd_report *)arg;
|
||||
if (nbytes < sizeof(struct usb_hid_kbd_report)) {
|
||||
return;
|
||||
}
|
||||
static uint8_t prev_keys[sizeof(kb_report->key)] = { 0 };
|
||||
key_event_t key_event;
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
if (!hid_keyboard_is_modifier_alt(kb_report->modifier)) {
|
||||
hid_keyboard_alt_code_process_complete();
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < sizeof(kb_report->key); i++) {
|
||||
|
||||
// key has been released verification
|
||||
if (prev_keys[i] > HID_KBD_USAGE_ERRUNDEF &&
|
||||
!key_found(kb_report->key, prev_keys[i], sizeof(kb_report->key))) {
|
||||
key_event.key_code = prev_keys[i];
|
||||
key_event.modifier = 0;
|
||||
key_event.state = KEY_STATE_RELEASED;
|
||||
key_event_callback(&key_event);
|
||||
}
|
||||
|
||||
// key has been pressed verification
|
||||
if (kb_report->key[i] > HID_KBD_USAGE_ERRUNDEF &&
|
||||
!key_found(prev_keys, kb_report->key[i], sizeof(kb_report->key))) {
|
||||
key_event.key_code = kb_report->key[i];
|
||||
key_event.modifier = kb_report->modifier;
|
||||
key_event.state = KEY_STATE_PRESSED;
|
||||
key_event_callback(&key_event);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(prev_keys, &kb_report->key, sizeof(kb_report->key));
|
||||
}
|
||||
|
||||
static void usbh_hid_mouse_report_callback(void *arg, int nbytes)
|
||||
{
|
||||
struct usb_hid_mouse_report *mouse_report = (struct usb_hid_mouse_report *)arg;
|
||||
|
||||
// At least 3 bytes are reported, followed by optional data
|
||||
if (nbytes < 3) {
|
||||
return;
|
||||
}
|
||||
static int x_pos = 0;
|
||||
static int y_pos = 0;
|
||||
|
||||
// Calculate absolute position from displacement
|
||||
x_pos += (int8_t)mouse_report->xdisp;
|
||||
y_pos += (int8_t)mouse_report->ydisp;
|
||||
|
||||
hid_print_new_device_report_header(HID_PROTOCOL_MOUSE);
|
||||
|
||||
printf("X: %06d\tY: %06d\t|%c|%c|\n",
|
||||
x_pos, y_pos,
|
||||
((mouse_report->buttons & HID_MOUSE_INPUT_BUTTON_LEFT) ? 'o' : ' '),
|
||||
((mouse_report->buttons & HID_MOUSE_INPUT_BUTTON_RIGHT) ? 'o' : ' '));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void usbh_hid_generic_report_callback(void *arg, int nbytes)
|
||||
{
|
||||
char *data = arg;
|
||||
hid_print_new_device_report_header(HID_PROTOCOL_NONE);
|
||||
for (int i = 0; i < nbytes; i++) {
|
||||
printf("%02X", data[i]);
|
||||
}
|
||||
putchar('\r');
|
||||
}
|
||||
|
||||
//Note: This callback is in the interrupt context
|
||||
static void usbh_hid_callback(void *arg, int nbytes)
|
||||
{
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
struct usbh_hid *hid_class = (struct usbh_hid *)arg;
|
||||
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
|
||||
|
||||
if (nbytes <= 0) {
|
||||
hid_intin->is_active = false;
|
||||
return;
|
||||
}
|
||||
uint8_t sub_class = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceSubClass;
|
||||
uint8_t protocol = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceProtocol;
|
||||
|
||||
if (s_msg_queue) {
|
||||
hid_msg_t msg;
|
||||
if (nbytes <= sizeof(msg.data)) {
|
||||
msg.protocol = HID_PROTOCOL_NONE;
|
||||
if (sub_class == HID_SUBCLASS_BOOTIF) {
|
||||
if (protocol == HID_PROTOCOL_KEYBOARD) {
|
||||
msg.protocol = HID_PROTOCOL_KEYBOARD;
|
||||
} else if (protocol == HID_PROTOCOL_MOUSE) {
|
||||
msg.protocol = HID_PROTOCOL_MOUSE;
|
||||
}
|
||||
}
|
||||
msg.len = nbytes;
|
||||
memcpy(msg.data, hid_intin->buffer, nbytes);
|
||||
if (xQueueSendFromISR(s_msg_queue, &msg, &xTaskWoken) != pdTRUE) {
|
||||
ESP_EARLY_LOGD(TAG, "msg queue full");
|
||||
}
|
||||
} else {
|
||||
ESP_EARLY_LOGD(TAG, "nbytes(%d) > sizeof(msg.data)", nbytes);
|
||||
}
|
||||
|
||||
}
|
||||
hid_intin->is_active = false;
|
||||
if (xTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
//Note: If the dispatch_method of esp_timer is ESP_TIMER_ISR, the callback is in the interrupt context.
|
||||
static void intin_timer_cb(void *arg)
|
||||
{
|
||||
int ret;
|
||||
struct usbh_hid *hid_class = (struct usbh_hid *)arg;
|
||||
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
|
||||
if (hid_intin->is_active) {
|
||||
return;
|
||||
}
|
||||
usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_intin->buffer, hid_class->intin->wMaxPacketSize, 0,
|
||||
usbh_hid_callback, hid_class);
|
||||
|
||||
hid_intin->is_active = true;
|
||||
ret = usbh_submit_urb(&hid_class->intin_urb);
|
||||
if (ret != 0) {
|
||||
if (ret == -USB_ERR_NOTCONN) {
|
||||
esp_timer_stop(hid_intin->timer);
|
||||
return;
|
||||
}
|
||||
hid_intin->is_active = false;
|
||||
ESP_EARLY_LOGE(TAG, "usbh_submit_urb failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void usbh_hid_msg_task(void *arg)
|
||||
{
|
||||
hid_msg_t msg;
|
||||
while (1) {
|
||||
BaseType_t err = xQueueReceive(s_msg_queue, &msg, portMAX_DELAY);
|
||||
if (err != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
if (msg.protocol == HID_PROTOCOL_KEYBOARD) {
|
||||
usbh_hid_keyboard_report_callback(msg.data, msg.len);
|
||||
} else if (msg.protocol == HID_PROTOCOL_MOUSE) {
|
||||
usbh_hid_mouse_report_callback(msg.data, msg.len);
|
||||
} else {
|
||||
usbh_hid_generic_report_callback(msg.data, msg.len);
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void creat_msg_task(void)
|
||||
{
|
||||
if (s_msg_queue == NULL) {
|
||||
s_msg_queue = xQueueCreate(QUEUE_LEN, sizeof(hid_msg_t));
|
||||
if (s_msg_queue == NULL) {
|
||||
ESP_LOGE(TAG, "ringbuf create failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (s_msg_task_handle == NULL) {
|
||||
xTaskCreate(usbh_hid_msg_task, "usbh_hid_msg_task", 4096, NULL, 5, &s_msg_task_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void usbh_hid_run(struct usbh_hid *hid_class)
|
||||
{
|
||||
int ret;
|
||||
esp_err_t err;
|
||||
uint8_t sub_class = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceSubClass;
|
||||
uint8_t protocol = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceProtocol;
|
||||
ESP_LOGI(TAG, "intf %u, SubClass %u, Protocol %u", hid_class->intf, sub_class, protocol);
|
||||
|
||||
if (sub_class == HID_SUBCLASS_BOOTIF) {
|
||||
ret = usbh_hid_set_protocol(hid_class, HID_PROTOCOL_BOOT);
|
||||
if (ret < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
creat_msg_task();
|
||||
|
||||
if (hid_class->intin == NULL) {
|
||||
ESP_LOGW(TAG, "no intin ep desc");
|
||||
return;
|
||||
}
|
||||
|
||||
hid_int_in_t *hid_intin = heap_caps_calloc(1, sizeof(hid_int_in_t), MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
|
||||
if (hid_intin == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
return;
|
||||
}
|
||||
hid_intin->buffer = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, hid_class->intin->wMaxPacketSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
if (hid_intin->buffer == NULL) {
|
||||
ESP_LOGW(TAG, "Malloc failed");
|
||||
goto error;
|
||||
}
|
||||
hid_intin->is_active = false;
|
||||
esp_timer_create_args_t timer_cfg = {
|
||||
.callback = intin_timer_cb,
|
||||
.arg = hid_class,
|
||||
#if CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
|
||||
.dispatch_method = ESP_TIMER_ISR,
|
||||
#else
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
#endif
|
||||
.name = "intin timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
err = esp_timer_create(&timer_cfg, &hid_intin->timer);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "timer create failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
hid_class->user_data = hid_intin;
|
||||
|
||||
esp_timer_start_periodic(hid_intin->timer, USBH_GET_URB_INTERVAL(hid_class->intin->bInterval, hid_class->hport->speed));
|
||||
|
||||
return;
|
||||
error:
|
||||
if (hid_intin->buffer) {
|
||||
heap_caps_free(hid_intin->buffer);
|
||||
}
|
||||
heap_caps_free(hid_intin);
|
||||
}
|
||||
|
||||
void usbh_hid_stop(struct usbh_hid *hid_class)
|
||||
{
|
||||
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
|
||||
if (hid_intin) {
|
||||
esp_timer_stop(hid_intin->timer);
|
||||
esp_timer_delete(hid_intin->timer);
|
||||
heap_caps_free(hid_intin->buffer);
|
||||
heap_caps_free(hid_intin);
|
||||
}
|
||||
ESP_LOGW(TAG, "hid stop");
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
cherry-embedded/cherryusb: 1.5.2~1
|
65
examples/peripherals/usb/host/cherryusb_host/main/main.c
Normal file
65
examples/peripherals/usb/host/cherryusb_host/main/main.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_HAL_USE_ESP32_S3_USB_OTG
|
||||
#include "driver/gpio.h"
|
||||
#define BOOST_EN 13
|
||||
#define DEV_VBUS_EN 12
|
||||
#define LIMIT_EN 17
|
||||
#define USB_SEL 18
|
||||
#endif
|
||||
|
||||
#include "usbh_core.h"
|
||||
#include "usbh_hid.h"
|
||||
|
||||
static char *TAG = "HOST";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if CONFIG_EXAMPLE_HAL_USE_ESP32_S3_USB_OTG
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_down_en = 0,
|
||||
.pull_up_en = 0,
|
||||
.pin_bit_mask = (1ULL << BOOST_EN) | (1ULL << DEV_VBUS_EN) | (1ULL << LIMIT_EN) | (1ULL << USB_SEL),
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(BOOST_EN, 0);
|
||||
gpio_set_level(DEV_VBUS_EN, 1);
|
||||
gpio_set_level(LIMIT_EN, 1);
|
||||
gpio_set_level(USB_SEL, 1);
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
|
||||
while (1)
|
||||
#endif
|
||||
{
|
||||
#if CONFIG_EXAMPLE_USB_HOST_RHPORT_HS
|
||||
usbh_initialize(0, ESP_USB_HS0_BASE);
|
||||
#else
|
||||
usbh_initialize(0, ESP_USB_FS0_BASE);
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Init usb");
|
||||
|
||||
#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
ESP_LOGW(TAG, "Deinit usb after %d seconds...", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
ESP_LOGW(TAG, "Deinit usb");
|
||||
usbh_deinitialize(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
404
examples/peripherals/usb/host/cherryusb_host/main/msc.c
Normal file
404
examples/peripherals/usb/host/cherryusb_host/main/msc.c
Normal file
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "ffconf.h"
|
||||
#include "ff.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
|
||||
#include "usbh_core.h"
|
||||
#include "usbh_msc.h"
|
||||
|
||||
static char *TAG = "MSC";
|
||||
|
||||
#define DRIVE_STR_LEN 3
|
||||
|
||||
typedef struct msc_host_vfs {
|
||||
uint8_t pdrv;
|
||||
FATFS *fs;
|
||||
char base_path[0];
|
||||
} msc_host_vfs_t;
|
||||
|
||||
static struct usbh_msc *s_mscs[FF_VOLUMES] = { NULL };
|
||||
|
||||
#define WAIT_BUFFER_TIMEOUT_MS 8000
|
||||
static SemaphoreHandle_t s_buff_mux = NULL;
|
||||
static size_t s_buff_size = 0;
|
||||
static uint8_t *s_buff = NULL;
|
||||
|
||||
void ld_include_msc(void)
|
||||
{
|
||||
}
|
||||
|
||||
static DSTATUS usb_disk_initialize(BYTE pdrv)
|
||||
{
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
static DSTATUS usb_disk_status(BYTE pdrv)
|
||||
{
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
static uint8_t *get_buffer(size_t size)
|
||||
{
|
||||
if (xSemaphoreTake(s_buff_mux, WAIT_BUFFER_TIMEOUT_MS / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "wait buffer timeout");
|
||||
return NULL;
|
||||
}
|
||||
if (s_buff_size < size) {
|
||||
if (s_buff) {
|
||||
heap_caps_free(s_buff);
|
||||
}
|
||||
s_buff = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
if (s_buff == NULL) {
|
||||
s_buff_size = 0;
|
||||
ESP_LOGW(TAG, "no mem");
|
||||
xSemaphoreGive(s_buff_mux);
|
||||
return NULL;
|
||||
}
|
||||
s_buff_size = size;
|
||||
}
|
||||
return s_buff;
|
||||
}
|
||||
|
||||
static void free_buffer(void)
|
||||
{
|
||||
xSemaphoreGive(s_buff_mux);
|
||||
}
|
||||
|
||||
static void check_free_buffer(void)
|
||||
{
|
||||
uint8_t *buff;
|
||||
if (s_buff == NULL || xSemaphoreTake(s_buff_mux, 0) != pdTRUE) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(s_mscs) / sizeof(s_mscs[0]); i++) {
|
||||
if (s_mscs[i] != NULL) {
|
||||
xSemaphoreGive(s_buff_mux);
|
||||
return;
|
||||
}
|
||||
}
|
||||
buff = s_buff;
|
||||
s_buff = NULL;
|
||||
s_buff_size = 0;
|
||||
xSemaphoreGive(s_buff_mux);
|
||||
if (buff) {
|
||||
heap_caps_free(buff);
|
||||
ESP_LOGI(TAG, "free msc buffer");
|
||||
}
|
||||
}
|
||||
|
||||
static DRESULT usb_disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
struct usbh_msc *msc_class;
|
||||
assert(pdrv < FF_VOLUMES);
|
||||
|
||||
msc_class = s_mscs[pdrv];
|
||||
assert(msc_class);
|
||||
if (sector >= msc_class->blocknum - count) {
|
||||
ESP_LOGW(TAG, "%s: sector 0x%"PRIX32" out of range", __FUNCTION__, (uint32_t)sector);
|
||||
return RES_PARERR;
|
||||
}
|
||||
|
||||
uint8_t *dma_buff = buff;
|
||||
size_t len = msc_class->blocksize * count;
|
||||
|
||||
if (((uint32_t)dma_buff & (CONFIG_USB_ALIGN_SIZE - 1)) || (len & (CONFIG_USB_ALIGN_SIZE - 1))) {
|
||||
dma_buff = get_buffer(len);
|
||||
if (dma_buff == NULL) {
|
||||
return RES_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = usbh_msc_scsi_read10(msc_class, sector, dma_buff, count);
|
||||
if (dma_buff != buff) {
|
||||
if (ret == 0) {
|
||||
memcpy(buff, dma_buff, len);
|
||||
}
|
||||
free_buffer();
|
||||
}
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(TAG, "usbh_msc_scsi_read10 failed (%d)", ret);
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
static DRESULT usb_disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
|
||||
{
|
||||
struct usbh_msc *msc_class;
|
||||
assert(pdrv < FF_VOLUMES);
|
||||
|
||||
msc_class = s_mscs[pdrv];
|
||||
assert(msc_class);
|
||||
if (sector >= msc_class->blocknum - count) {
|
||||
ESP_LOGW(TAG, "%s: sector 0x%"PRIX32" out of range", __FUNCTION__, (uint32_t)sector);
|
||||
return RES_PARERR;
|
||||
}
|
||||
|
||||
const uint8_t *dma_buff = buff;
|
||||
size_t len = msc_class->blocksize * count;
|
||||
|
||||
if (((uint32_t)dma_buff & (CONFIG_USB_ALIGN_SIZE - 1)) || (len & (CONFIG_USB_ALIGN_SIZE - 1))) {
|
||||
dma_buff = get_buffer(len);
|
||||
if (dma_buff == NULL) {
|
||||
return RES_ERROR;
|
||||
}
|
||||
memcpy((uint8_t *)dma_buff, buff, len);
|
||||
}
|
||||
|
||||
int ret = usbh_msc_scsi_write10(msc_class, sector, dma_buff, count);
|
||||
if (dma_buff != buff) {
|
||||
free_buffer();
|
||||
}
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "usbh_msc_scsi_write10 failed (%d)", ret);
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
static DRESULT usb_disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
|
||||
{
|
||||
struct usbh_msc *msc_class;
|
||||
assert(pdrv < FF_VOLUMES);
|
||||
|
||||
msc_class = s_mscs[pdrv];
|
||||
assert(msc_class);
|
||||
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
return RES_OK;
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *) buff) = msc_class->blocknum;
|
||||
return RES_OK;
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *) buff) = msc_class->blocksize;
|
||||
return RES_OK;
|
||||
case GET_BLOCK_SIZE:
|
||||
return RES_ERROR;
|
||||
}
|
||||
return RES_ERROR;
|
||||
}
|
||||
|
||||
void ff_diskio_register_msc(BYTE pdrv, struct usbh_msc *msc_class)
|
||||
{
|
||||
assert(pdrv < FF_VOLUMES);
|
||||
|
||||
static const ff_diskio_impl_t usb_disk_impl = {
|
||||
.init = &usb_disk_initialize,
|
||||
.status = &usb_disk_status,
|
||||
.read = &usb_disk_read,
|
||||
.write = &usb_disk_write,
|
||||
.ioctl = &usb_disk_ioctl
|
||||
};
|
||||
s_mscs[pdrv] = msc_class;
|
||||
ff_diskio_register(pdrv, &usb_disk_impl);
|
||||
}
|
||||
|
||||
BYTE ff_diskio_get_pdrv_disk(const struct usbh_msc *msc_class)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (msc_class == s_mscs[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
static esp_err_t msc_host_format(struct usbh_msc *msc_class, size_t allocation_size)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE((msc_class != NULL && msc_class->user_data != NULL), ESP_ERR_INVALID_ARG, TAG, "");
|
||||
void *workbuf = NULL;
|
||||
const size_t workbuf_size = 4096;
|
||||
msc_host_vfs_t *vfs = (msc_host_vfs_t *)msc_class->user_data;
|
||||
|
||||
char drive[DRIVE_STR_LEN] = {(char)('0' + vfs->pdrv), ':', 0};
|
||||
ESP_RETURN_ON_FALSE((workbuf = ff_memalloc(workbuf_size)), ESP_ERR_NO_MEM, TAG, "");
|
||||
|
||||
// Valid value of cluster size is between sector_size and 128 * sector_size.
|
||||
size_t cluster_size = MIN(MAX(allocation_size, msc_class->blocksize), 128 * msc_class->blocksize);
|
||||
|
||||
ESP_LOGW(TAG, "Formatting card, allocation unit size=%d", cluster_size);
|
||||
f_mount(0, drive, 0);
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
FRESULT err = f_mkfs(drive, FM_ANY | FM_SFD, cluster_size, workbuf, workbuf_size);
|
||||
#else
|
||||
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, cluster_size};
|
||||
FRESULT err = f_mkfs(drive, &opt, workbuf, workbuf_size);
|
||||
#endif
|
||||
|
||||
free(workbuf);
|
||||
if (err != FR_OK || (err = f_mount(vfs->fs, drive, 0)) != FR_OK) {
|
||||
ESP_LOGE(TAG, "Formatting failed with error: %d", err);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t msc_host_vfs_register(struct usbh_msc *msc_class,
|
||||
const char *base_path,
|
||||
const esp_vfs_fat_mount_config_t *mount_config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE((msc_class != NULL && msc_class->user_data == NULL && base_path != NULL && mount_config != NULL), ESP_ERR_INVALID_ARG, TAG, "");
|
||||
|
||||
FATFS *fs = NULL;
|
||||
BYTE pdrv;
|
||||
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "the maximum count of volumes is already mounted");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
msc_host_vfs_t *vfs = malloc(sizeof(msc_host_vfs_t) + strlen(base_path) + 1);
|
||||
ESP_RETURN_ON_FALSE(vfs != NULL, ESP_ERR_NO_MEM, TAG, "");
|
||||
|
||||
ff_diskio_register_msc(pdrv, msc_class);
|
||||
char drive[DRIVE_STR_LEN] = {(char)('0' + pdrv), ':', 0};
|
||||
strcpy(vfs->base_path, base_path);
|
||||
vfs->pdrv = pdrv;
|
||||
|
||||
ret = esp_vfs_fat_register(base_path, drive, mount_config->max_files, &fs);
|
||||
ESP_GOTO_ON_ERROR(ret, fail, TAG, "Failed to register filesystem, error=%s", esp_err_to_name(ret));
|
||||
vfs->fs = fs;
|
||||
|
||||
msc_class->user_data = vfs;
|
||||
if (f_mount(fs, drive, 1) != FR_OK) {
|
||||
if ((!mount_config->format_if_mount_failed) || msc_host_format(msc_class, mount_config->allocation_unit_size) != ESP_OK) {
|
||||
ret = ESP_FAIL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
msc_class->user_data = NULL;
|
||||
if (fs) {
|
||||
f_mount(NULL, drive, 0);
|
||||
}
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
ff_diskio_unregister(pdrv);
|
||||
s_mscs[pdrv] = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t msc_host_vfs_unregister(struct usbh_msc *msc_class)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE((msc_class != NULL && ff_diskio_get_pdrv_disk(msc_class) != 0XFF), ESP_ERR_INVALID_ARG, TAG, "");
|
||||
msc_host_vfs_t *vfs = (msc_host_vfs_t *)msc_class->user_data;
|
||||
msc_class->user_data = NULL;
|
||||
|
||||
char drive[DRIVE_STR_LEN] = {(char)('0' + vfs->pdrv), ':', 0};
|
||||
f_mount(NULL, drive, 0);
|
||||
ff_diskio_unregister(vfs->pdrv);
|
||||
s_mscs[vfs->pdrv] = NULL;
|
||||
esp_vfs_fat_unregister_path(vfs->base_path);
|
||||
heap_caps_free(vfs);
|
||||
check_free_buffer();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t s_example_write_file(const char *path, const char *data)
|
||||
{
|
||||
ESP_LOGI(TAG, "Opening file %s", path);
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (fd < 0) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
write(fd, data, strlen(data));
|
||||
close(fd);
|
||||
ESP_LOGI(TAG, "File written");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t s_example_read_file(const char *path)
|
||||
{
|
||||
ESP_LOGI(TAG, "Reading file %s", path);
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
char line[64];
|
||||
size_t len;
|
||||
|
||||
ESP_LOGI(TAG, "Read from file:");
|
||||
do {
|
||||
len = read(fd, line, sizeof(line));
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, line, len, ESP_LOG_WARN);
|
||||
} while (len == sizeof(line));
|
||||
close(fd);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void usbh_msc_run(struct usbh_msc *msc_class)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (s_buff_mux == NULL) {
|
||||
s_buff_mux = xSemaphoreCreateMutex();
|
||||
if (s_buff_mux == NULL) {
|
||||
ESP_LOGE(TAG, "create mutex fail");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ret = usbh_msc_scsi_init(msc_class);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "scsi_init error,ret:%d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
|
||||
.format_if_mount_failed = true,
|
||||
#else
|
||||
.format_if_mount_failed = false,
|
||||
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 4 * 1024
|
||||
};
|
||||
ESP_LOGI(TAG, "Mounting filesystem");
|
||||
|
||||
if (msc_host_vfs_register(msc_class, "/usb", &mount_config) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "msc_host_vfs_register fail");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Filesystem mounted");
|
||||
|
||||
const char *file_hello = "/usb/hello.txt";
|
||||
const char data[] = "Hello, world!\n";
|
||||
ret = s_example_write_file(file_hello, data);
|
||||
if (ret != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ret = s_example_read_file(file_hello);
|
||||
if (ret != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void usbh_msc_stop(struct usbh_msc *msc_class)
|
||||
{
|
||||
msc_host_vfs_unregister(msc_class);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 6.0.0 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
|
||||
CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
|
||||
CONFIG_CHERRYUSB=y
|
||||
CONFIG_CHERRYUSB_HOST=y
|
||||
CONFIG_CHERRYUSB_HOST_DWC2_ESP=y
|
||||
CONFIG_CHERRYUSB_HOST_CDC_ACM=y
|
||||
CONFIG_CHERRYUSB_HOST_HID=y
|
||||
CONFIG_CHERRYUSB_HOST_MSC=y
|
||||
CONFIG_CHERRYUSB_HOST_FTDI=y
|
||||
CONFIG_CHERRYUSB_HOST_CH34X=y
|
||||
CONFIG_CHERRYUSB_HOST_CP210X=y
|
||||
CONFIG_CHERRYUSB_HOST_PL2303=y
|
@@ -83,6 +83,13 @@ typedef struct {
|
||||
#define KEYBOARD_ENTER_MAIN_CHAR '\r'
|
||||
/* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */
|
||||
#define KEYBOARD_ENTER_LF_EXTEND 1
|
||||
/* When set to 1, numbers entered from the numeric keypad while ALT is pressed will be escaped */
|
||||
#define KEYBOARD_ENTER_ALT_ESCAPE 1
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
static bool is_ansi = false;
|
||||
static unsigned int alt_code = 0;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Scancode to ascii table
|
||||
@@ -147,6 +154,24 @@ const uint8_t keycode2ascii [57][2] = {
|
||||
{'/', '?'} /* HID_KEY_SLASH */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard print char symbol
|
||||
*
|
||||
* @param[in] key_char Keyboard char to stdout
|
||||
*/
|
||||
static inline void hid_keyboard_print_char(unsigned int key_char)
|
||||
{
|
||||
if (!!key_char) {
|
||||
putchar(key_char);
|
||||
#if (KEYBOARD_ENTER_LF_EXTEND)
|
||||
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
|
||||
putchar('\n');
|
||||
}
|
||||
#endif // KEYBOARD_ENTER_LF_EXTEND
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes new line depending on report output protocol type
|
||||
*
|
||||
@@ -187,6 +212,81 @@ static inline bool hid_keyboard_is_modifier_shift(uint8_t modifier)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
/**
|
||||
* @brief HID Keyboard modifier verification for capitalization application (right or left alt)
|
||||
*
|
||||
* @param[in] modifier
|
||||
* @return true Modifier was pressed (left or right alt)
|
||||
* @return false Modifier was not pressed (left or right alt)
|
||||
*
|
||||
*/
|
||||
static inline bool hid_keyboard_is_modifier_alt(uint8_t modifier)
|
||||
{
|
||||
if (((modifier & HID_LEFT_ALT) == HID_LEFT_ALT) ||
|
||||
((modifier & HID_RIGHT_ALT) == HID_RIGHT_ALT)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard alt code process(Called when ALT is pressed)
|
||||
*
|
||||
* @param[in] key_code Entered key value
|
||||
* @return true Key values that qualify for ALT escape processing
|
||||
* @return false Key values that do not comply with ALT escape processing
|
||||
*
|
||||
*/
|
||||
static inline bool hid_keyboard_alt_code_processing(uint8_t key_code)
|
||||
{
|
||||
if ((key_code < HID_KEY_KEYPAD_1) || (key_code > HID_KEY_KEYPAD_0)) {
|
||||
return false;
|
||||
}
|
||||
if (key_code == HID_KEY_KEYPAD_0) {
|
||||
if (alt_code == 0) {
|
||||
is_ansi = true;
|
||||
return true;
|
||||
}
|
||||
/* Note: Since the keyboard code 0 of the numeric keypad is not keyboard code 1 minus 1, the
|
||||
* conversion is performed here to facilitate subsequent calculations of the input numbers.
|
||||
*/
|
||||
key_code = HID_KEY_KEYPAD_1 - 1;
|
||||
}
|
||||
alt_code = alt_code * 10 + (key_code - (HID_KEY_KEYPAD_1 - 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard alt code process complete(Called when ALT is not pressed)
|
||||
*/
|
||||
static inline void hid_keyboard_alt_code_process_complete(void)
|
||||
{
|
||||
if (alt_code > 0) {
|
||||
alt_code = alt_code & 0xff;
|
||||
if (is_ansi || alt_code == 0) {
|
||||
char utf8_buffer[8] = { 0 };
|
||||
if (alt_code == 0) {
|
||||
alt_code = 0x100;
|
||||
}
|
||||
//ANSI is processed as UTF8
|
||||
if (alt_code <= 0x7F) {
|
||||
utf8_buffer[0] = (char)alt_code;
|
||||
} else {
|
||||
utf8_buffer[0] = 0xC0 | ((alt_code >> 6) & 0x1F);
|
||||
utf8_buffer[1] = 0x80 | (alt_code & 0x3F);
|
||||
}
|
||||
printf("%s", utf8_buffer);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
hid_keyboard_print_char(alt_code);
|
||||
}
|
||||
alt_code = 0;
|
||||
}
|
||||
is_ansi = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard get char symbol from key code
|
||||
*
|
||||
@@ -203,6 +303,16 @@ static inline bool hid_keyboard_get_char(uint8_t modifier,
|
||||
{
|
||||
uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0;
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
if (hid_keyboard_is_modifier_alt(modifier)) {
|
||||
// ALT modifier is still pressed
|
||||
if (hid_keyboard_alt_code_processing(key_code)) {
|
||||
// ALT code processed, no need to go further
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_SLASH)) {
|
||||
*key_char = keycode2ascii[key_code][mod];
|
||||
} else {
|
||||
@@ -213,24 +323,6 @@ static inline bool hid_keyboard_get_char(uint8_t modifier,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard print char symbol
|
||||
*
|
||||
* @param[in] key_char Keyboard char to stdout
|
||||
*/
|
||||
static inline void hid_keyboard_print_char(unsigned int key_char)
|
||||
{
|
||||
if (!!key_char) {
|
||||
putchar(key_char);
|
||||
#if (KEYBOARD_ENTER_LF_EXTEND)
|
||||
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
|
||||
putchar('\n');
|
||||
}
|
||||
#endif // KEYBOARD_ENTER_LF_EXTEND
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Key Event. Key event with the key code, state and modifier.
|
||||
*
|
||||
@@ -289,6 +381,12 @@ static void hid_host_keyboard_report_callback(const uint8_t *const data, const i
|
||||
static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = { 0 };
|
||||
key_event_t key_event;
|
||||
|
||||
#if KEYBOARD_ENTER_ALT_ESCAPE
|
||||
if (!hid_keyboard_is_modifier_alt(kb_report->modifier.val)) {
|
||||
hid_keyboard_alt_code_process_complete();
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
|
||||
|
||||
// key has been released verification
|
||||
|
Reference in New Issue
Block a user