initial commit
This commit is contained in:
commit
b44ce7bd77
2716 files changed
+559005
No files matched your search
@@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(tinyusb_host_examples C CXX ASM)
|
||||
family_initialize_project(tinyusb_host_examples ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# family_add_subdirectory will filter what to actually add based on selected FAMILY
|
||||
set(EXAMPLE_LIST
|
||||
bare_api
|
||||
cdc_msc_hid
|
||||
cdc_msc_hid_freertos
|
||||
device_info
|
||||
hid_controller
|
||||
midi_rx
|
||||
midi2_host
|
||||
msc_file_explorer
|
||||
msc_file_explorer_freertos
|
||||
)
|
||||
|
||||
foreach (example ${EXAMPLE_LIST})
|
||||
family_add_subdirectory(${example})
|
||||
endforeach ()
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(bare_api C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE += \
|
||||
src/main.c
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,47 @@
|
||||
# Host: Bare API
|
||||
|
||||
A USB host example that drives the TinyUSB host stack through its low-level API directly, without the higher-level class-driver application layer.
|
||||
|
||||
## What it does
|
||||
|
||||
- Enumerates any attached device and, on mount, fetches and prints the full device descriptor (VID/PID, USB version, class, max packet size, etc.) over the debug UART.
|
||||
- Reads and prints the manufacturer, product, and serial-number string descriptors (UTF-16 to UTF-8).
|
||||
- Fetches and walks the configuration descriptor with a small hand-written parser.
|
||||
- For any HID interface found, opens its interrupt IN endpoint with the raw endpoint API (`tuh_edpt_open` / `tuh_edpt_xfer`) and continuously prints the incoming HID reports as raw hex bytes.
|
||||
- Blinks the board LED once per second.
|
||||
|
||||
## Requirements
|
||||
|
||||
The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
#define CFG_TUH_API_EDPT_XFER 1
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Plug any USB device (a HID keyboard/mouse works well) into the board's USB host port. On attach you should see `Device attached` followed by the full device/string/configuration descriptor dump on the debug UART. If the device exposes a HID interface, its interrupt-IN reports are printed as hex as they arrive (e.g. when you press a key or move the mouse).
|
||||
@@ -0,0 +1,32 @@
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32C5
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "class/hid/hid.h"
|
||||
|
||||
// English
|
||||
#define LANGUAGE_ID 0x0409
|
||||
#define BUF_COUNT 4
|
||||
|
||||
|
||||
CFG_TUH_MEM_SECTION tusb_desc_device_t desc_device;
|
||||
|
||||
CFG_TUH_MEM_SECTION uint8_t buf_pool[BUF_COUNT][64];
|
||||
uint8_t buf_owner[BUF_COUNT] = { 0 }; // device address that owns buffer
|
||||
|
||||
CFG_TUH_MEM_SECTION uint16_t temp_buf[128]; // temp buffer for string descriptor
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void);
|
||||
void print_devinfo_task(void);
|
||||
|
||||
static void print_utf16(uint16_t *temp_buf, size_t buf_len);
|
||||
static void print_one_device(uint8_t daddr);
|
||||
void parse_config_descriptor(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg);
|
||||
|
||||
uint8_t* get_hid_buf(uint8_t daddr);
|
||||
void free_hid_buf(uint8_t daddr);
|
||||
|
||||
// One flag per possible device address — set in tuh_mount_cb (host task) and
|
||||
// cleared by print_devinfo_task (main loop) once the descriptors are printed.
|
||||
static volatile bool need_devinfo[CFG_TUH_DEVICE_MAX + 1];
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Bare API Example\r\n");
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
while (1) {
|
||||
// tinyusb host task
|
||||
tuh_task();
|
||||
print_devinfo_task();
|
||||
led_blinking_task();
|
||||
}
|
||||
}
|
||||
|
||||
/*------------- TinyUSB Callbacks -------------*/
|
||||
|
||||
// Invoked when device is mounted (configured). Runs in the host task — keep
|
||||
// it minimal. The descriptor fetching/printing happens in print_devinfo_task()
|
||||
// below where the sync helpers are safe (different context).
|
||||
void tuh_mount_cb(uint8_t daddr) {
|
||||
printf("Device attached, address = %d\r\n", daddr);
|
||||
if (daddr < TU_ARRAY_SIZE(need_devinfo)) {
|
||||
need_devinfo[daddr] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when device is unmounted (bus reset/unplugged)
|
||||
void tuh_umount_cb(uint8_t daddr) {
|
||||
printf("Device removed, address = %d\r\n", daddr);
|
||||
if (daddr < TU_ARRAY_SIZE(need_devinfo)) {
|
||||
need_devinfo[daddr] = false;
|
||||
}
|
||||
free_hid_buf(daddr);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Print device info task — serialises descriptor fetching across all
|
||||
// mounted devices via sync helpers. Sync calls are safe here because this
|
||||
// runs in the main loop (outside any host-stack callback context).
|
||||
//--------------------------------------------------------------------+
|
||||
void print_devinfo_task(void) {
|
||||
for (uint8_t daddr = 1; daddr < TU_ARRAY_SIZE(need_devinfo); daddr++) {
|
||||
if (need_devinfo[daddr]) {
|
||||
need_devinfo[daddr] = false;
|
||||
print_one_device(daddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print_one_device(uint8_t daddr) {
|
||||
// Get Device Descriptor
|
||||
if (XFER_RESULT_SUCCESS != tuh_descriptor_get_device_sync(daddr, &desc_device, 18)) {
|
||||
printf("Failed to get device descriptor\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Device %u: ID %04x:%04x\r\n", daddr, desc_device.idVendor, desc_device.idProduct);
|
||||
printf("Device Descriptor:\r\n");
|
||||
printf(" bLength %u\r\n" , desc_device.bLength);
|
||||
printf(" bDescriptorType %u\r\n" , desc_device.bDescriptorType);
|
||||
printf(" bcdUSB %04x\r\n" , desc_device.bcdUSB);
|
||||
printf(" bDeviceClass %u\r\n" , desc_device.bDeviceClass);
|
||||
printf(" bDeviceSubClass %u\r\n" , desc_device.bDeviceSubClass);
|
||||
printf(" bDeviceProtocol %u\r\n" , desc_device.bDeviceProtocol);
|
||||
printf(" bMaxPacketSize0 %u\r\n" , desc_device.bMaxPacketSize0);
|
||||
printf(" idVendor 0x%04x\r\n" , desc_device.idVendor);
|
||||
printf(" idProduct 0x%04x\r\n" , desc_device.idProduct);
|
||||
printf(" bcdDevice %04x\r\n" , desc_device.bcdDevice);
|
||||
|
||||
printf(" iManufacturer %u ", desc_device.iManufacturer);
|
||||
if (XFER_RESULT_SUCCESS == tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, temp_buf, sizeof(temp_buf))) {
|
||||
print_utf16(temp_buf, TU_ARRAY_SIZE(temp_buf));
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
printf(" iProduct %u ", desc_device.iProduct);
|
||||
if (XFER_RESULT_SUCCESS == tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, temp_buf, sizeof(temp_buf))) {
|
||||
print_utf16(temp_buf, TU_ARRAY_SIZE(temp_buf));
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
printf(" iSerialNumber %u ", desc_device.iSerialNumber);
|
||||
if (XFER_RESULT_SUCCESS == tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, temp_buf, sizeof(temp_buf))) {
|
||||
print_utf16(temp_buf, TU_ARRAY_SIZE(temp_buf));
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
printf(" bNumConfigurations %u\r\n", desc_device.bNumConfigurations);
|
||||
|
||||
// Get configuration descriptor with sync API
|
||||
if (XFER_RESULT_SUCCESS == tuh_descriptor_get_configuration_sync(daddr, 0, temp_buf, sizeof(temp_buf))) {
|
||||
parse_config_descriptor(daddr, (tusb_desc_configuration_t *) temp_buf);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Configuration Descriptor
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// count total length of an interface
|
||||
uint16_t count_interface_total_len(tusb_desc_interface_t const* desc_itf, uint8_t itf_count, uint16_t max_len);
|
||||
|
||||
void open_hid_interface(uint8_t daddr, tusb_desc_interface_t const *desc_itf, uint16_t max_len);
|
||||
|
||||
// simple configuration parser to open and listen to HID Endpoint IN
|
||||
void parse_config_descriptor(uint8_t dev_addr, tusb_desc_configuration_t const *desc_cfg) {
|
||||
uint8_t const *desc_end = ((uint8_t const *) desc_cfg) + tu_le16toh(desc_cfg->wTotalLength);
|
||||
uint8_t const *p_desc = tu_desc_next(desc_cfg);
|
||||
|
||||
// parse each interfaces
|
||||
while (p_desc < desc_end) {
|
||||
uint8_t assoc_itf_count = 1;
|
||||
|
||||
// Class will always starts with Interface Association (if any) and then Interface descriptor
|
||||
if (TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc)) {
|
||||
tusb_desc_interface_assoc_t const *desc_iad = (tusb_desc_interface_assoc_t const *) p_desc;
|
||||
assoc_itf_count = desc_iad->bInterfaceCount;
|
||||
|
||||
p_desc = tu_desc_next(p_desc);// next to Interface
|
||||
}
|
||||
|
||||
// must be interface from now
|
||||
if (TUSB_DESC_INTERFACE != tu_desc_type(p_desc)) { return; }
|
||||
tusb_desc_interface_t const *desc_itf = (tusb_desc_interface_t const *) p_desc;
|
||||
|
||||
uint16_t const drv_len = count_interface_total_len(desc_itf, assoc_itf_count, (uint16_t) (desc_end - p_desc));
|
||||
|
||||
// probably corrupted descriptor
|
||||
if (drv_len < sizeof(tusb_desc_interface_t)) { return; }
|
||||
|
||||
// only open and listen to HID endpoint IN
|
||||
if (desc_itf->bInterfaceClass == TUSB_CLASS_HID) {
|
||||
open_hid_interface(dev_addr, desc_itf, drv_len);
|
||||
}
|
||||
|
||||
// next Interface or IAD descriptor
|
||||
p_desc += drv_len;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t count_interface_total_len(tusb_desc_interface_t const *desc_itf, uint8_t itf_count, uint16_t max_len) {
|
||||
uint8_t const *p_desc = (uint8_t const *) desc_itf;
|
||||
uint16_t len = 0;
|
||||
|
||||
while (itf_count--) {
|
||||
// Next on interface desc
|
||||
len += tu_desc_len(desc_itf);
|
||||
p_desc = tu_desc_next(p_desc);
|
||||
|
||||
while (len < max_len) {
|
||||
// return on IAD regardless of itf count
|
||||
if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION) { return len; }
|
||||
|
||||
if ((tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) &&
|
||||
((tusb_desc_interface_t const *) p_desc)->bAlternateSetting == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
len += tu_desc_len(p_desc);
|
||||
p_desc = tu_desc_next(p_desc);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// HID Interface
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void hid_report_received(tuh_xfer_t* xfer);
|
||||
|
||||
void open_hid_interface(uint8_t daddr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) {
|
||||
// len = interface + hid + n*endpoints
|
||||
uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) +
|
||||
desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t));
|
||||
|
||||
// corrupted descriptor
|
||||
if (max_len < drv_len) { return; }
|
||||
|
||||
uint8_t const *p_desc = (uint8_t const *) desc_itf;
|
||||
|
||||
// HID descriptor
|
||||
p_desc = tu_desc_next(p_desc);
|
||||
tusb_hid_descriptor_hid_t const *desc_hid = (tusb_hid_descriptor_hid_t const *) p_desc;
|
||||
if (HID_DESC_TYPE_HID != desc_hid->bDescriptorType) { return; }
|
||||
|
||||
// Endpoint descriptor
|
||||
p_desc = tu_desc_next(p_desc);
|
||||
tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc;
|
||||
|
||||
for (int i = 0; i < desc_itf->bNumEndpoints; i++) {
|
||||
if (TUSB_DESC_ENDPOINT != desc_ep->bDescriptorType) { return; }
|
||||
|
||||
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
|
||||
if (!tuh_edpt_open(daddr, desc_ep)) {
|
||||
return; // skip if failed to open endpoint
|
||||
}
|
||||
|
||||
uint8_t *buf = get_hid_buf(daddr);
|
||||
if (!buf) {
|
||||
return;// out of memory
|
||||
}
|
||||
|
||||
tuh_xfer_t xfer = {
|
||||
.daddr = daddr,
|
||||
.ep_addr = desc_ep->bEndpointAddress,
|
||||
.buflen = 64,
|
||||
.buffer = buf,
|
||||
.complete_cb = hid_report_received,
|
||||
.user_data = (uintptr_t) buf,// since buffer is not available in callback, use user data to store the buffer
|
||||
};
|
||||
|
||||
// submit transfer for this EP
|
||||
tuh_edpt_xfer(&xfer);
|
||||
|
||||
printf("Listen to [dev %u: ep %02x]\r\n", daddr, desc_ep->bEndpointAddress);
|
||||
}
|
||||
|
||||
p_desc = tu_desc_next(p_desc);
|
||||
desc_ep = (tusb_desc_endpoint_t const *) p_desc;
|
||||
}
|
||||
}
|
||||
|
||||
void hid_report_received(tuh_xfer_t *xfer) {
|
||||
// Note: not all field in xfer is available for use (i.e filled by tinyusb stack) in callback to save sram
|
||||
// For instance, xfer->buffer is NULL. We have used user_data to store buffer when submitted callback
|
||||
uint8_t *buf = (uint8_t *) xfer->user_data;
|
||||
|
||||
if (xfer->result == XFER_RESULT_SUCCESS) {
|
||||
printf("[dev %u: ep %02x] HID Report:", xfer->daddr, xfer->ep_addr);
|
||||
for (uint32_t i = 0; i < xfer->actual_len; i++) {
|
||||
if (i % 16 == 0) {
|
||||
printf("\r\n ");
|
||||
}
|
||||
printf("%02X ", buf[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
// continue to submit transfer, with updated buffer
|
||||
// other field remain the same
|
||||
xfer->buflen = 64;
|
||||
xfer->buffer = buf;
|
||||
|
||||
tuh_edpt_xfer(xfer);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Buffer helper
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// get an buffer from pool
|
||||
uint8_t *get_hid_buf(uint8_t daddr) {
|
||||
for (size_t i = 0; i < BUF_COUNT; i++) {
|
||||
if (buf_owner[i] == 0) {
|
||||
buf_owner[i] = daddr;
|
||||
return buf_pool[i];
|
||||
}
|
||||
}
|
||||
|
||||
// out of memory, increase BUF_COUNT
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// free all buffer owned by device
|
||||
void free_hid_buf(uint8_t daddr) {
|
||||
for (size_t i = 0; i < BUF_COUNT; i++) {
|
||||
if (buf_owner[i] == daddr) buf_owner[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void) {
|
||||
const uint32_t interval_ms = 1000;
|
||||
static uint32_t start_ms = 0;
|
||||
|
||||
static bool led_state = false;
|
||||
|
||||
// Blink every interval ms
|
||||
if (tusb_time_millis_api() - start_ms < interval_ms) {
|
||||
return; // not enough time
|
||||
}
|
||||
start_ms += interval_ms;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state;// toggle
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// String Descriptor Helper
|
||||
//--------------------------------------------------------------------+
|
||||
static void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) {
|
||||
// TODO: Check for runover.
|
||||
(void) utf8_len;
|
||||
// Get the UTF-16 length out of the data itself.
|
||||
|
||||
for (size_t i = 0; i < utf16_len; i++) {
|
||||
uint16_t chr = utf16[i];
|
||||
if (chr < 0x80) {
|
||||
*utf8++ = chr & 0xffu;
|
||||
} else if (chr < 0x800) {
|
||||
*utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F));
|
||||
*utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F));
|
||||
} else {
|
||||
// TODO: Verify surrogate.
|
||||
*utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F));
|
||||
*utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F));
|
||||
*utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F));
|
||||
}
|
||||
// TODO: Handle UTF-16 code points that take two entries.
|
||||
}
|
||||
}
|
||||
|
||||
// Count how many bytes a utf-16-le encoded string will take in utf-8.
|
||||
static int _count_utf8_bytes(const uint16_t *buf, size_t len) {
|
||||
size_t total_bytes = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint16_t chr = buf[i];
|
||||
if (chr < 0x80) {
|
||||
total_bytes += 1;
|
||||
} else if (chr < 0x800) {
|
||||
total_bytes += 2;
|
||||
} else {
|
||||
total_bytes += 3;
|
||||
}
|
||||
// TODO: Handle UTF-16 code points that take two entries.
|
||||
}
|
||||
return (int) total_bytes;
|
||||
}
|
||||
|
||||
static void print_utf16(uint16_t *buf, size_t buf_len) {
|
||||
if ((buf[0] & 0xff) == 0) return;// empty
|
||||
size_t utf16_len = ((buf[0] & 0xff) - 2) / sizeof(uint16_t);
|
||||
size_t utf8_len = (size_t) _count_utf8_bytes(buf + 1, utf16_len);
|
||||
_convert_utf16le_to_utf8(buf + 1, utf16_len, (uint8_t *) buf, sizeof(uint16_t) * buf_len);
|
||||
((uint8_t *) buf)[utf8_len] = '\0';
|
||||
|
||||
printf("%s", (char *) buf);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
// only hub class is enabled
|
||||
#define CFG_TUH_HUB 1
|
||||
|
||||
// max device support (excluding hub device)
|
||||
// 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
// Max endpoint per device
|
||||
#define CFG_TUH_ENDPOINT_MAX 8
|
||||
|
||||
// Enable tuh_edpt_xfer() API
|
||||
#define CFG_TUH_API_EDPT_XFER 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(cdc_msc_hid C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/cdc_app.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/hid_app.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE = \
|
||||
src/cdc_app.c \
|
||||
src/hid_app.c \
|
||||
src/main.c \
|
||||
src/msc_app.c \
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,59 @@
|
||||
# Host: CDC/MSC/HID
|
||||
|
||||
A USB host example that enumerates CDC serial, Mass Storage, and HID devices and reports their activity over the debug UART.
|
||||
|
||||
## What it does
|
||||
|
||||
- CDC (`CFG_TUH_CDC`): bidirectionally bridges the debug console and the attached CDC serial device — bytes typed on the debug UART are written to the device, and bytes received from the device are echoed back to the UART. Also supports common USB-serial adapters (FTDI, CP210x, CH34x, PL2303). On mount it prints the interface info and line coding (set to 115200 8N1 on enumeration).
|
||||
- MSC (`CFG_TUH_MSC`): on mount, issues a SCSI Inquiry and prints the drive's vendor/product/revision strings and its capacity (block count, block size, total MB).
|
||||
- HID (`CFG_TUH_HID`): receives reports and prints keyboard keystrokes as ASCII, mouse button state and cursor movement, and any other (generic) report as raw hex.
|
||||
- Blinks the board LED once per second.
|
||||
|
||||
## Requirements
|
||||
|
||||
The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_CDC 1
|
||||
#define CFG_TUH_CDC_FTDI 1
|
||||
#define CFG_TUH_CDC_CP210X 1
|
||||
#define CFG_TUH_CDC_CH34X 1
|
||||
#define CFG_TUH_CDC_PL2303 1
|
||||
#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX)
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_HID_EPIN_BUFSIZE 64
|
||||
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
|
||||
#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM (CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS)
|
||||
#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## FreeRTOS variant
|
||||
|
||||
A FreeRTOS build is in `examples/host/cdc_msc_hid_freertos` — identical CDC/MSC/HID host behavior, running the host stack in a FreeRTOS task with a software-timer LED.
|
||||
|
||||
## How to use
|
||||
|
||||
Plug a USB keyboard, mouse, flash drive, or serial device into the board's USB host port (a USB hub also works, so several can be connected at once). Watch the debug UART: keystrokes and mouse movement appear as you use the input devices, an attached flash drive prints its inquiry/size info, and for a serial device you can type into the debug console to forward characters to it and see its output echoed back.
|
||||
@@ -0,0 +1,32 @@
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32C5
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2025 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef TUSB_TINYUSB_EXAMPLES_APP_H
|
||||
#define TUSB_TINYUSB_EXAMPLES_APP_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void cdc_app_task(void);
|
||||
void hid_app_task(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022, Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.h"
|
||||
#include "bsp/board_api.h"
|
||||
#include "app.h"
|
||||
|
||||
static size_t console_read(uint8_t *buf, size_t bufsize) {
|
||||
size_t count = 0;
|
||||
while (count < bufsize) {
|
||||
const int ch = board_getchar();
|
||||
if (ch < 0) {
|
||||
break;
|
||||
}
|
||||
buf[count] = (uint8_t) ch;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static size_t console_write(const uint8_t *buf, size_t bufsize) {
|
||||
// Use board_uart_write directly for non-blocking behavior.
|
||||
// board_putchar -> sys_write has a blocking retry loop that causes UART RX overrun.
|
||||
int wr = board_uart_write(buf, (int) bufsize);
|
||||
return (wr > 0) ? (size_t) wr : 0;
|
||||
}
|
||||
|
||||
// forward from console to usbh
|
||||
static void console_to_usbh(uint8_t idx) {
|
||||
uint8_t buf[64];
|
||||
size_t count = console_read(buf, sizeof(buf));
|
||||
if (count > 0) {
|
||||
tuh_cdc_write(idx, buf, count);
|
||||
}
|
||||
}
|
||||
|
||||
void cdc_app_task(void) {
|
||||
const uint8_t idx = 0;
|
||||
|
||||
// Bidirectional forwarding: console <-> host cdc interfaces
|
||||
if (!tuh_cdc_mounted(idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// usbh -> uart
|
||||
uint8_t buf[64];
|
||||
uint32_t count = tuh_cdc_read(idx, buf, sizeof(buf));
|
||||
uint32_t wr = 0;
|
||||
|
||||
do {
|
||||
// uart write is slow, while waiting forward uart -> usbh else uart rx can be overflow
|
||||
if (count) {
|
||||
wr += console_write(buf + wr, count - wr);
|
||||
}
|
||||
console_to_usbh(idx);
|
||||
} while (wr < count);
|
||||
|
||||
tuh_cdc_write_flush(idx);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when a device with CDC interface is mounted
|
||||
// idx is index of cdc interface in the internal pool.
|
||||
void tuh_cdc_mount_cb(uint8_t idx) {
|
||||
tuh_itf_info_t itf_info = {0};
|
||||
tuh_cdc_itf_get_info(idx, &itf_info);
|
||||
|
||||
printf("CDC Interface is mounted: address = %u, itf_num = %u\r\n", itf_info.daddr,
|
||||
itf_info.desc.bInterfaceNumber);
|
||||
|
||||
// If CFG_TUH_CDC_LINE_CODING_ON_ENUM is defined, line coding will be set by tinyusb stack
|
||||
// while eneumerating new cdc device
|
||||
cdc_line_coding_t line_coding = {0};
|
||||
if (tuh_cdc_get_line_coding_local(idx, &line_coding)) {
|
||||
printf(" Baudrate: %" PRIu32 ", Stop Bits : %u\r\n", line_coding.bit_rate, line_coding.stop_bits);
|
||||
printf(" Parity : %u, Data Width: %u\r\n", line_coding.parity, line_coding.data_bits);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when a device with CDC interface is unmounted
|
||||
void tuh_cdc_umount_cb(uint8_t idx) {
|
||||
tuh_itf_info_t itf_info = {0};
|
||||
tuh_cdc_itf_get_info(idx, &itf_info);
|
||||
|
||||
printf("CDC Interface is unmounted: address = %u, itf_num = %u\r\n", itf_info.daddr,
|
||||
itf_info.desc.bInterfaceNumber);
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2021, Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
#define MAX_REPORT 4
|
||||
|
||||
static uint8_t const keycode2ascii[128][2] = {HID_KEYCODE_TO_ASCII};
|
||||
|
||||
// Each HID instance can has multiple reports
|
||||
static struct {
|
||||
uint8_t report_count;
|
||||
tuh_hid_report_info_t report_info[MAX_REPORT];
|
||||
} hid_info[CFG_TUH_HID];
|
||||
|
||||
static void process_kbd_report(hid_keyboard_report_t const *report);
|
||||
static void process_mouse_report(hid_mouse_report_t const *report);
|
||||
static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len);
|
||||
|
||||
void hid_app_task(void) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when device with hid interface is mounted
|
||||
// Report descriptor is also available for use. tuh_hid_parse_report_descriptor()
|
||||
// can be used to parse common/simple enough descriptor.
|
||||
// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped
|
||||
// therefore report_desc = NULL, desc_len = 0
|
||||
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
|
||||
printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
|
||||
|
||||
// Interface protocol (hid_interface_protocol_enum_t)
|
||||
const char *protocol_str[] = {"None", "Keyboard", "Mouse"};
|
||||
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||
|
||||
printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]);
|
||||
|
||||
// By default, host stack will use boot protocol on supported interface.
|
||||
// Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser)
|
||||
if (itf_protocol == HID_ITF_PROTOCOL_NONE) {
|
||||
hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len);
|
||||
printf("HID has %u reports \r\n", hid_info[instance].report_count);
|
||||
}
|
||||
|
||||
// request to receive report
|
||||
// tuh_hid_report_received_cb() will be invoked when report is available
|
||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||
printf("Error: cannot request to receive report\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when device with hid interface is un-mounted
|
||||
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
|
||||
printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
|
||||
}
|
||||
|
||||
// Invoked when received report from device via interrupt endpoint
|
||||
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||
|
||||
switch (itf_protocol) {
|
||||
case HID_ITF_PROTOCOL_KEYBOARD:
|
||||
TU_LOG2("HID receive boot keyboard report\r\n");
|
||||
process_kbd_report((hid_keyboard_report_t const *) report);
|
||||
break;
|
||||
|
||||
case HID_ITF_PROTOCOL_MOUSE:
|
||||
TU_LOG2("HID receive boot mouse report\r\n");
|
||||
process_mouse_report((hid_mouse_report_t const *) report);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Generic report requires matching ReportID and contents with previous parsed report info
|
||||
process_generic_report(dev_addr, instance, report, len);
|
||||
break;
|
||||
}
|
||||
|
||||
// continue to request to receive report
|
||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||
printf("Error: cannot request to receive report\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Keyboard
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// look up new key in previous keys
|
||||
static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) {
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (report->keycode[i] == keycode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void process_kbd_report(hid_keyboard_report_t const *report) {
|
||||
static hid_keyboard_report_t prev_report = {0, 0, {0}};// previous report to check key released
|
||||
|
||||
//------------- example code ignore control (non-printable) key affects -------------//
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (report->keycode[i]) {
|
||||
if (find_key_in_report(&prev_report, report->keycode[i])) {
|
||||
// exist in previous report means the current key is holding
|
||||
} else {
|
||||
// not existed in previous report means the current key is pressed
|
||||
bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT);
|
||||
uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0];
|
||||
putchar(ch);
|
||||
if (ch == '\r') {
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
#ifndef __ICCARM__ // TODO IAR doesn't support stream control ?
|
||||
fflush(stdout);// flush right away, else nanolib will wait for newline
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// TODO example skips key released
|
||||
}
|
||||
|
||||
prev_report = *report;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Mouse
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void cursor_movement(int8_t x, int8_t y, int8_t wheel) {
|
||||
printf("(%d %d %d)\r\n", x, y, wheel);
|
||||
}
|
||||
|
||||
static void process_mouse_report(hid_mouse_report_t const *report) {
|
||||
static hid_mouse_report_t prev_report = {0};
|
||||
|
||||
// button state
|
||||
uint8_t button_changed_mask = report->buttons ^ prev_report.buttons;
|
||||
if (button_changed_mask & report->buttons) {
|
||||
printf(" %c%c%c ",
|
||||
report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-',
|
||||
report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-',
|
||||
report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-');
|
||||
}
|
||||
|
||||
// cursor movement
|
||||
cursor_movement(report->x, report->y, report->wheel);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Generic Report
|
||||
//--------------------------------------------------------------------+
|
||||
static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||
(void) dev_addr;
|
||||
(void) len;
|
||||
|
||||
uint8_t const rpt_count = hid_info[instance].report_count;
|
||||
tuh_hid_report_info_t *rpt_info_arr = hid_info[instance].report_info;
|
||||
tuh_hid_report_info_t *rpt_info = NULL;
|
||||
|
||||
if (rpt_count == 1 && rpt_info_arr[0].report_id == 0) {
|
||||
// Simple report without report ID as 1st byte
|
||||
rpt_info = &rpt_info_arr[0];
|
||||
} else {
|
||||
// Composite report, 1st byte is report ID, data starts from 2nd byte
|
||||
uint8_t const rpt_id = report[0];
|
||||
|
||||
// Find report id in the array
|
||||
for (uint8_t i = 0; i < rpt_count; i++) {
|
||||
if (rpt_id == rpt_info_arr[i].report_id) {
|
||||
rpt_info = &rpt_info_arr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
report++;
|
||||
len--;
|
||||
}
|
||||
|
||||
if (!rpt_info) {
|
||||
printf("Couldn't find report info !\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// For complete list of Usage Page & Usage checkout src/class/hid/hid.h. For examples:
|
||||
// - Keyboard : Desktop, Keyboard
|
||||
// - Mouse : Desktop, Mouse
|
||||
// - Gamepad : Desktop, Gamepad
|
||||
// - Consumer Control (Media Key) : Consumer, Consumer Control
|
||||
// - System Control (Power key) : Desktop, System Control
|
||||
// - Generic (vendor) : 0xFFxx, xx
|
||||
if (rpt_info->usage_page == HID_USAGE_PAGE_DESKTOP) {
|
||||
switch (rpt_info->usage) {
|
||||
case HID_USAGE_DESKTOP_KEYBOARD:
|
||||
TU_LOG2("HID receive keyboard report\r\n");
|
||||
// Assume keyboard follow boot report layout
|
||||
process_kbd_report((hid_keyboard_report_t const *) report);
|
||||
break;
|
||||
|
||||
case HID_USAGE_DESKTOP_MOUSE:
|
||||
TU_LOG2("HID receive mouse report\r\n");
|
||||
// Assume mouse follow boot report layout
|
||||
process_mouse_report((hid_mouse_report_t const *) report);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("report[%u] ", rpt_info->report_id);
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
printf("%02X ", report[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTOTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void);
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host CDC MSC HID Example\r\n");
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
|
||||
// FeatherWing MAX3421E use MAX3421E's GPIO0 for VBUS enable
|
||||
enum { IOPINS1_ADDR = 20u << 3, /* 0xA0 */ };
|
||||
tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
// tinyusb host task
|
||||
tuh_task();
|
||||
|
||||
led_blinking_task();
|
||||
cdc_app_task();
|
||||
hid_app_task();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void tuh_mount_cb(uint8_t dev_addr) {
|
||||
// application set-up
|
||||
printf("A device with address %u is mounted\r\n", dev_addr);
|
||||
}
|
||||
|
||||
void tuh_umount_cb(uint8_t dev_addr) {
|
||||
// application tear-down
|
||||
printf("A device with address %u is unmounted \r\n", dev_addr);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void) {
|
||||
const uint32_t interval_ms = 1000;
|
||||
static uint32_t start_ms = 0;
|
||||
|
||||
static bool led_state = false;
|
||||
|
||||
// Blink every interval ms
|
||||
if (tusb_time_millis_api() - start_ms < interval_ms) {
|
||||
return;// not enough time
|
||||
}
|
||||
start_ms += interval_ms;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "tusb.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
static scsi_inquiry_resp_t inquiry_resp;
|
||||
|
||||
static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data) {
|
||||
msc_cbw_t const* cbw = cb_data->cbw;
|
||||
msc_csw_t const* csw = cb_data->csw;
|
||||
|
||||
if (csw->status != 0) {
|
||||
printf("Inquiry failed\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Print out Vendor ID, Product ID and Rev
|
||||
printf("%.8s %.16s %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev);
|
||||
|
||||
// Get capacity of device
|
||||
uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
|
||||
uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
|
||||
|
||||
printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
|
||||
block_count, block_size, block_count / ((1024 * 1024) / block_size));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------- IMPLEMENTATION -------------//
|
||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device is mounted\r\n");
|
||||
|
||||
uint8_t const lun = 0;
|
||||
tuh_msc_inquiry(dev_addr, lun, &inquiry_resp, inquiry_complete_cb, 0);
|
||||
}
|
||||
|
||||
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
printf("A MassStorage device is unmounted\r\n");
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
// Increase task event queue to handle rapid bulk transfer completions
|
||||
#define CFG_TUH_TASK_QUEUE_SZ 64
|
||||
|
||||
#define CFG_TUH_HUB 1 // number of supported hubs
|
||||
#define CFG_TUH_CDC 1 // number of supported CDC devices. also activates CDC ACM
|
||||
#define CFG_TUH_CDC_FTDI 1 // FTDI Serial. FTDI is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_CDC_CP210X 1 // CP210x Serial. CP210X is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_CDC_CH34X 1 // CH340 or CH341 Serial. CH34X is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_CDC_PL2303 1 // PL2303 Serial. PL2303 is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX) // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_VENDOR 0
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
//------------- HID -------------//
|
||||
#define CFG_TUH_HID_EPIN_BUFSIZE 64
|
||||
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
|
||||
|
||||
//------------- CDC -------------//
|
||||
|
||||
// Set Line Control state on enumeration/mounted:
|
||||
// DTR ( bit 0), RTS (bit 1)
|
||||
#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM (CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS)
|
||||
|
||||
// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t
|
||||
// bit rate = 115200, 1 stop bit, no parity, 8 bit data width
|
||||
#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
@@ -0,0 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(cdc_msc_hid_freertos C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/cdc_app.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/hid_app.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} freertos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
RTOS = freertos
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE = \
|
||||
src/cdc_app.c \
|
||||
src/hid_app.c \
|
||||
src/main.c \
|
||||
src/msc_app.c \
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,28 @@
|
||||
family:espressif
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32C5
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1,3 @@
|
||||
mcu:CH32F20X
|
||||
board:lpcxpresso54114
|
||||
mcu:FT90X
|
||||
@@ -0,0 +1,6 @@
|
||||
# This file is for ESP-IDF only
|
||||
idf_component_register(SRCS "cdc_app.c" "hid_app.c" "main.c" "msc_app.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES boards tinyusb_src)
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format)
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2025 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef TUSB_TINYUSB_EXAMPLES_APP_H
|
||||
#define TUSB_TINYUSB_EXAMPLES_APP_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void cdc_app_init(void);
|
||||
void hid_app_init(void);
|
||||
void msc_app_init(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2022, Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.h"
|
||||
#include "bsp/board_api.h"
|
||||
#include "app.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CDC_STACK_SIZE 2048
|
||||
#else
|
||||
#define CDC_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2)
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StackType_t cdc_stack[CDC_STACK_SIZE];
|
||||
StaticTask_t cdc_taskdef;
|
||||
#endif
|
||||
|
||||
static void cdc_app_task(void* param);
|
||||
|
||||
void cdc_app_init(void) {
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
(void) xTaskCreateStatic(cdc_app_task, "cdc", CDC_STACK_SIZE, NULL, configMAX_PRIORITIES-2, cdc_stack, &cdc_taskdef);
|
||||
#else
|
||||
(void) xTaskCreate(cdc_app_task, "cdc", CDC_STACK_SIZE, NULL, configMAX_PRIORITIES-2, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
// helper
|
||||
static size_t console_read(uint8_t *buf, size_t bufsize) {
|
||||
size_t count = 0;
|
||||
while (count < bufsize) {
|
||||
int ch = board_getchar();
|
||||
if (ch <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf[count] = (uint8_t) ch;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void cdc_app_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
uint8_t buf[64 + 1]; // +1 for extra null character
|
||||
uint32_t const bufsize = sizeof(buf) - 1;
|
||||
|
||||
while (1) {
|
||||
uint32_t count = console_read(buf, bufsize);
|
||||
buf[count] = 0;
|
||||
|
||||
if (count) {
|
||||
// loop over all mounted interfaces
|
||||
for (uint8_t idx = 0; idx < CFG_TUH_CDC; idx++) {
|
||||
if (tuh_cdc_mounted(idx)) {
|
||||
// console --> cdc interfaces
|
||||
tuh_cdc_write(idx, buf, count);
|
||||
tuh_cdc_write_flush(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when received new data
|
||||
void tuh_cdc_rx_cb(uint8_t idx) {
|
||||
uint8_t buf[64 + 1]; // +1 for extra null character
|
||||
uint32_t const bufsize = sizeof(buf) - 1;
|
||||
|
||||
// forward cdc interfaces -> console
|
||||
uint32_t count = tuh_cdc_read(idx, buf, bufsize);
|
||||
buf[count] = 0;
|
||||
|
||||
printf("%s", (char *) buf);
|
||||
}
|
||||
|
||||
void tuh_cdc_mount_cb(uint8_t idx) {
|
||||
tuh_itf_info_t itf_info = { 0 };
|
||||
tuh_cdc_itf_get_info(idx, &itf_info);
|
||||
|
||||
printf("CDC Interface is mounted: address = %u, itf_num = %u\r\n", itf_info.daddr, itf_info.desc.bInterfaceNumber);
|
||||
|
||||
#ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM
|
||||
// CFG_TUH_CDC_LINE_CODING_ON_ENUM must be defined for line coding is set by tinyusb in enumeration
|
||||
// otherwise you need to call tuh_cdc_set_line_coding() first
|
||||
cdc_line_coding_t line_coding = { 0 };
|
||||
if (tuh_cdc_get_local_line_coding(idx, &line_coding)) {
|
||||
printf(" Baudrate: %" PRIu32 ", Stop Bits : %u\r\n", line_coding.bit_rate, line_coding.stop_bits);
|
||||
printf(" Parity : %u, Data Width: %u\r\n", line_coding.parity, line_coding.data_bits);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void tuh_cdc_umount_cb(uint8_t idx) {
|
||||
tuh_itf_info_t itf_info = { 0 };
|
||||
tuh_cdc_itf_get_info(idx, &itf_info);
|
||||
|
||||
printf("CDC Interface is unmounted: address = %u, itf_num = %u\r\n", itf_info.daddr, itf_info.desc.bInterfaceNumber);
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2021, Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// If your host terminal support ansi escape code such as TeraTerm
|
||||
// it can be use to simulate mouse cursor movement within terminal
|
||||
#define USE_ANSI_ESCAPE 0
|
||||
|
||||
#define MAX_REPORT 4
|
||||
|
||||
static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII };
|
||||
|
||||
// Each HID instance can has multiple reports
|
||||
static struct {
|
||||
uint8_t report_count;
|
||||
tuh_hid_report_info_t report_info[MAX_REPORT];
|
||||
} hid_info[CFG_TUH_HID];
|
||||
|
||||
static void process_kbd_report(hid_keyboard_report_t const *report);
|
||||
static void process_mouse_report(hid_mouse_report_t const *report);
|
||||
static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len);
|
||||
|
||||
void hid_app_init(void) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when device with hid interface is mounted
|
||||
// Report descriptor is also available for use. tuh_hid_parse_report_descriptor()
|
||||
// can be used to parse common/simple enough descriptor.
|
||||
// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped
|
||||
// therefore report_desc = NULL, desc_len = 0
|
||||
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
|
||||
printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
|
||||
|
||||
// Interface protocol (hid_interface_protocol_enum_t)
|
||||
const char *protocol_str[] = { "None", "Keyboard", "Mouse" };
|
||||
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||
|
||||
printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]);
|
||||
|
||||
// By default host stack will use activate boot protocol on supported interface.
|
||||
// Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser)
|
||||
if (itf_protocol == HID_ITF_PROTOCOL_NONE) {
|
||||
hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT,
|
||||
desc_report, desc_len);
|
||||
printf("HID has %u reports \r\n", hid_info[instance].report_count);
|
||||
}
|
||||
|
||||
// request to receive report
|
||||
// tuh_hid_report_received_cb() will be invoked when report is available
|
||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||
printf("Error: cannot request to receive report\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when device with hid interface is un-mounted
|
||||
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
|
||||
printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
|
||||
}
|
||||
|
||||
// Invoked when received report from device via interrupt endpoint
|
||||
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||
|
||||
switch (itf_protocol) {
|
||||
case HID_ITF_PROTOCOL_KEYBOARD:
|
||||
TU_LOG2("HID receive boot keyboard report\r\n");
|
||||
process_kbd_report((hid_keyboard_report_t const *) report);
|
||||
break;
|
||||
|
||||
case HID_ITF_PROTOCOL_MOUSE:
|
||||
TU_LOG2("HID receive boot mouse report\r\n");
|
||||
process_mouse_report((hid_mouse_report_t const *) report);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Generic report requires matching ReportID and contents with previous parsed report info
|
||||
process_generic_report(dev_addr, instance, report, len);
|
||||
break;
|
||||
}
|
||||
|
||||
// continue to request to receive report
|
||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||
printf("Error: cannot request to receive report\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Keyboard
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// look up new key in previous keys
|
||||
static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) {
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (report->keycode[i] == keycode) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void process_kbd_report(hid_keyboard_report_t const *report) {
|
||||
static hid_keyboard_report_t prev_report = { 0, 0, { 0 } }; // previous report to check key released
|
||||
|
||||
//------------- example code ignore control (non-printable) key affects -------------//
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (report->keycode[i]) {
|
||||
if (find_key_in_report(&prev_report, report->keycode[i])) {
|
||||
// exist in previous report means the current key is holding
|
||||
} else {
|
||||
// not existed in previous report means the current key is pressed
|
||||
bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT);
|
||||
uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0];
|
||||
putchar(ch);
|
||||
if (ch == '\r') putchar('\n'); // added new line for enter key
|
||||
|
||||
#ifndef __ICCARM__ // TODO IAR doesn't support stream control ?
|
||||
fflush(stdout); // flush right away, else nanolib will wait for newline
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// TODO example skips key released
|
||||
}
|
||||
|
||||
prev_report = *report;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Mouse
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void cursor_movement(int8_t x, int8_t y, int8_t wheel) {
|
||||
#if USE_ANSI_ESCAPE
|
||||
// Move X using ansi escape
|
||||
if ( x < 0) {
|
||||
printf(ANSI_CURSOR_BACKWARD(%d), (-x)); // move left
|
||||
}else if ( x > 0) {
|
||||
printf(ANSI_CURSOR_FORWARD(%d), x); // move right
|
||||
}
|
||||
|
||||
// Move Y using ansi escape
|
||||
if ( y < 0) {
|
||||
printf(ANSI_CURSOR_UP(%d), (-y)); // move up
|
||||
}else if ( y > 0) {
|
||||
printf(ANSI_CURSOR_DOWN(%d), y); // move down
|
||||
}
|
||||
|
||||
// Scroll using ansi escape
|
||||
if (wheel < 0) {
|
||||
printf(ANSI_SCROLL_UP(%d), (-wheel)); // scroll up
|
||||
}else if (wheel > 0) {
|
||||
printf(ANSI_SCROLL_DOWN(%d), wheel); // scroll down
|
||||
}
|
||||
|
||||
printf("\r\n");
|
||||
#else
|
||||
printf("(%d %d %d)\r\n", x, y, wheel);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void process_mouse_report(hid_mouse_report_t const *report) {
|
||||
static hid_mouse_report_t prev_report = { 0 };
|
||||
|
||||
//------------- button state -------------//
|
||||
uint8_t button_changed_mask = report->buttons ^ prev_report.buttons;
|
||||
if (button_changed_mask & report->buttons) {
|
||||
printf(" %c%c%c ",
|
||||
report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-',
|
||||
report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-',
|
||||
report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-');
|
||||
}
|
||||
|
||||
//------------- cursor movement -------------//
|
||||
cursor_movement(report->x, report->y, report->wheel);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Generic Report
|
||||
//--------------------------------------------------------------------+
|
||||
static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||
(void) dev_addr;
|
||||
(void) len;
|
||||
|
||||
uint8_t const rpt_count = hid_info[instance].report_count;
|
||||
tuh_hid_report_info_t *rpt_info_arr = hid_info[instance].report_info;
|
||||
tuh_hid_report_info_t *rpt_info = NULL;
|
||||
|
||||
if (rpt_count == 1 && rpt_info_arr[0].report_id == 0) {
|
||||
// Simple report without report ID as 1st byte
|
||||
rpt_info = &rpt_info_arr[0];
|
||||
} else {
|
||||
// Composite report, 1st byte is report ID, data starts from 2nd byte
|
||||
uint8_t const rpt_id = report[0];
|
||||
|
||||
// Find report id in the array
|
||||
for (uint8_t i = 0; i < rpt_count; i++) {
|
||||
if (rpt_id == rpt_info_arr[i].report_id) {
|
||||
rpt_info = &rpt_info_arr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
report++;
|
||||
len--;
|
||||
}
|
||||
|
||||
if (!rpt_info) {
|
||||
printf("Couldn't find report info !\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// For complete list of Usage Page & Usage checkout src/class/hid/hid.h. For examples:
|
||||
// - Keyboard : Desktop, Keyboard
|
||||
// - Mouse : Desktop, Mouse
|
||||
// - Gamepad : Desktop, Gamepad
|
||||
// - Consumer Control (Media Key) : Consumer, Consumer Control
|
||||
// - System Control (Power key) : Desktop, System Control
|
||||
// - Generic (vendor) : 0xFFxx, xx
|
||||
if (rpt_info->usage_page == HID_USAGE_PAGE_DESKTOP) {
|
||||
switch (rpt_info->usage) {
|
||||
case HID_USAGE_DESKTOP_KEYBOARD:
|
||||
TU_LOG1("HID receive keyboard report\r\n");
|
||||
// Assume keyboard follow boot report layout
|
||||
process_kbd_report((hid_keyboard_report_t const *) report);
|
||||
break;
|
||||
|
||||
case HID_USAGE_DESKTOP_MOUSE:
|
||||
TU_LOG1("HID receive mouse report\r\n");
|
||||
// Assume mouse follow boot report layout
|
||||
process_mouse_report((hid_mouse_report_t const *) report);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define USBH_STACK_SIZE 4096
|
||||
#else
|
||||
// Increase stack size when debug log is enabled
|
||||
#define USBH_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 4 : 2))
|
||||
#endif
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTOTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
/* Blink pattern
|
||||
* - 250 ms : device not mounted
|
||||
* - 1000 ms : device mounted
|
||||
* - 2500 ms : device is suspended
|
||||
*/
|
||||
enum {
|
||||
BLINK_NOT_MOUNTED = 250,
|
||||
BLINK_MOUNTED = 1000,
|
||||
BLINK_SUSPENDED = 2500,
|
||||
};
|
||||
|
||||
// static timer & task
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StaticTimer_t blinky_tmdef;
|
||||
|
||||
StackType_t usb_host_stack[USBH_STACK_SIZE];
|
||||
StaticTask_t usb_host_taskdef;
|
||||
#endif
|
||||
|
||||
TimerHandle_t blinky_tm;
|
||||
|
||||
static void led_blinky_cb(TimerHandle_t xTimer);
|
||||
static void usb_host_task(void* param);
|
||||
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host CDC MSC HID with FreeRTOS Example\r\n");
|
||||
|
||||
// Create soft timer for blinky, task for tinyusb stack
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
blinky_tm = xTimerCreateStatic(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb, &blinky_tmdef);
|
||||
xTaskCreateStatic(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES-1, usb_host_stack, &usb_host_taskdef);
|
||||
#else
|
||||
blinky_tm = xTimerCreate(NULL, pdMS_TO_TICKS(BLINK_NOT_MOUNTED), true, NULL, led_blinky_cb);
|
||||
xTaskCreate(usb_host_task, "usbd", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES-1, NULL);
|
||||
#endif
|
||||
|
||||
xTimerStart(blinky_tm, 0);
|
||||
|
||||
// only start scheduler for non-espressif mcu
|
||||
#ifndef ESP_PLATFORM
|
||||
vTaskStartScheduler();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void app_main(void) {
|
||||
main();
|
||||
}
|
||||
#endif
|
||||
|
||||
// USB Host task
|
||||
// This top level thread process all usb events and invoke callbacks
|
||||
static void usb_host_task(void *param) {
|
||||
(void) param;
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
|
||||
if (!tusb_init(BOARD_TUH_RHPORT, &host_init)) {
|
||||
printf("Failed to init USB Host Stack\r\n");
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
|
||||
// FeatherWing MAX3421E use MAX3421E's GPIO0 for VBUS enable
|
||||
enum { IOPINS1_ADDR = 20u << 3, /* 0xA0 */ };
|
||||
tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
|
||||
#endif
|
||||
|
||||
cdc_app_init();
|
||||
hid_app_init();
|
||||
msc_app_init();
|
||||
|
||||
// RTOS forever loop
|
||||
while (1) {
|
||||
// put this thread to waiting state until there is new events
|
||||
tuh_task();
|
||||
|
||||
// following code only run if tuh_task() process at least 1 event
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void tuh_mount_cb(uint8_t dev_addr) {
|
||||
// application set-up
|
||||
printf("A device with address %d is mounted\r\n", dev_addr);
|
||||
}
|
||||
|
||||
void tuh_umount_cb(uint8_t dev_addr) {
|
||||
// application tear-down
|
||||
printf("A device with address %d is unmounted \r\n", dev_addr);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// BLINKING TASK
|
||||
//--------------------------------------------------------------------+
|
||||
static void led_blinky_cb(TimerHandle_t xTimer) {
|
||||
(void) xTimer;
|
||||
static bool led_state = false;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
// define the buffer to be place in USB/DMA memory with correct alignment/cache line size
|
||||
CFG_TUH_MEM_SECTION static struct {
|
||||
TUH_EPBUF_TYPE_DEF(scsi_inquiry_resp_t, inquiry);
|
||||
} scsi_resp;
|
||||
|
||||
void msc_app_init(void) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
static bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const *cb_data) {
|
||||
msc_cbw_t const *cbw = cb_data->cbw;
|
||||
msc_csw_t const *csw = cb_data->csw;
|
||||
|
||||
if (csw->status != 0) {
|
||||
printf("Inquiry failed\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Print out Vendor ID, Product ID and Rev
|
||||
printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id, scsi_resp.inquiry.product_rev);
|
||||
|
||||
// Get capacity of device
|
||||
uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
|
||||
uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
|
||||
|
||||
printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
|
||||
block_count, block_size, block_count / ((1024 * 1024) / block_size));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------- IMPLEMENTATION -------------//
|
||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device is mounted\r\n");
|
||||
|
||||
uint8_t const lun = 0;
|
||||
tuh_msc_inquiry(dev_addr, lun, &scsi_resp.inquiry, inquiry_complete_cb, 0);
|
||||
}
|
||||
|
||||
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
printf("A MassStorage device is unmounted\r\n");
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_FREERTOS
|
||||
#endif
|
||||
|
||||
// Espressif IDF requires "freertos/" prefix in include path
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CFG_TUSB_OS_INC_PATH freertos/
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 1 // number of supported hubs
|
||||
#define CFG_TUH_CDC 1 // number of supported CDC devices. also activates CDC ACM
|
||||
#define CFG_TUH_CDC_FTDI 1 // FTDI Serial. FTDI is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_CDC_CP210X 1 // CP210x Serial. CP210X is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_CDC_CH34X 1 // CH340 or CH341 Serial. CH34X is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_CDC_PL2303 1 // PL2303 Serial. PL2303 is not part of CDC class, only to re-use CDC driver API
|
||||
#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX) // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_VENDOR 0
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
//------------- HID -------------//
|
||||
#define CFG_TUH_HID_EPIN_BUFSIZE 64
|
||||
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
|
||||
|
||||
//------------- CDC -------------//
|
||||
|
||||
// Set Line Control state on enumeration/mounted:
|
||||
// DTR ( bit 0), RTS (bit 1)
|
||||
#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM (CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS)
|
||||
|
||||
// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t
|
||||
// bit rate = 115200, 1 stop bit, no parity, 8 bit data width
|
||||
#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
@@ -0,0 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(device_info C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE += \
|
||||
src/main.c
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,45 @@
|
||||
# Host: Device Info
|
||||
|
||||
A USB host example that enumerates any attached device and prints its descriptor information over the debug UART. No class drivers are enabled — only the hub driver, so it works with any kind of device.
|
||||
|
||||
## What it does
|
||||
|
||||
- On mount of any device, fetches and prints the full device descriptor: VID/PID, USB version, device class/subclass/protocol, max packet size, bcdDevice, and number of configurations.
|
||||
- Reads and prints the manufacturer, product, and serial-number string descriptors (UTF-16 to UTF-8), falling back to a placeholder serial when none is present.
|
||||
- Blinks the board LED, with a faster pattern while no device is mounted.
|
||||
- Builds on either the bare main loop or a FreeRTOS task, depending on `CFG_TUSB_OS`.
|
||||
|
||||
## Requirements
|
||||
|
||||
The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Plug any USB device into the board's USB host port. The debug UART prints the device's `ID vvvv:pppp`, serial number, and the decoded device descriptor (a hub lets you attach several devices, each printed in turn). Unplugging the device prints a removal message.
|
||||
@@ -0,0 +1,33 @@
|
||||
family:espressif
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32C5
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,4 @@
|
||||
# This file is for ESP-IDF only
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES boards tinyusb_src)
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Host example will get device descriptors of attached devices and print it out via uart/rtt (logger) as follows:
|
||||
* Device 1: ID 046d:c52f SN 11223344
|
||||
Device Descriptor:
|
||||
bLength 18
|
||||
bDescriptorType 1
|
||||
bcdUSB 0200
|
||||
bDeviceClass 0
|
||||
bDeviceSubClass 0
|
||||
bDeviceProtocol 0
|
||||
bMaxPacketSize0 8
|
||||
idVendor 0x046d
|
||||
idProduct 0xc52f
|
||||
bcdDevice 2200
|
||||
iManufacturer 1 Logitech
|
||||
iProduct 2 USB Receiver
|
||||
iSerialNumber 0
|
||||
bNumConfigurations 1
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
|
||||
// English
|
||||
#define LANGUAGE_ID 0x0409
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
enum {
|
||||
BLINK_NOT_MOUNTED = 250,
|
||||
BLINK_MOUNTED = 1000,
|
||||
BLINK_SUSPENDED = 2500,
|
||||
};
|
||||
static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
|
||||
|
||||
// Declare for buffer for usb transfer, may need to be in USB/DMA section and
|
||||
// multiple of dcache line size if dcache is enabled (for some ports).
|
||||
CFG_TUH_MEM_SECTION struct {
|
||||
TUH_EPBUF_TYPE_DEF(tusb_desc_device_t, device);
|
||||
TUH_EPBUF_DEF(serial, 64*sizeof(uint16_t));
|
||||
TUH_EPBUF_DEF(buf, 128*sizeof(uint16_t));
|
||||
} desc;
|
||||
|
||||
void led_blinking_task(void* param);
|
||||
void print_devinfo_task(void* param);
|
||||
static void print_utf16(uint16_t* temp_buf, size_t buf_len);
|
||||
|
||||
// One flag per possible device address — set by tuh_mount_cb (host task) and
|
||||
// cleared by print_devinfo_task (separate task / main loop) once the device's
|
||||
// descriptor info has been printed.
|
||||
static volatile bool need_devinfo[CFG_TUH_DEVICE_MAX + 1];
|
||||
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
void init_freertos_task(void);
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Main
|
||||
//--------------------------------------------------------------------
|
||||
static void init_tinyusb(void) {
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
board_init();
|
||||
printf("TinyUSB Device Info Example\r\n");
|
||||
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
init_freertos_task();
|
||||
#else
|
||||
board_delay(100); // wait for uart to be ready
|
||||
init_tinyusb();
|
||||
while (1) {
|
||||
tuh_task(); // tinyusb host task
|
||||
print_devinfo_task(NULL);
|
||||
led_blinking_task(NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*------------- TinyUSB Callbacks -------------*/
|
||||
|
||||
// Invoked when device is mounted (configured). Runs in the host task — keep
|
||||
// it minimal. The actual descriptor fetching/printing happens in
|
||||
// print_devinfo_task() below, which runs in a different context (main loop
|
||||
// on OS_NONE / dedicated task on RTOS) where the sync helpers are safe.
|
||||
void tuh_mount_cb(uint8_t daddr) {
|
||||
blink_interval_ms = BLINK_MOUNTED;
|
||||
if (daddr < TU_ARRAY_SIZE(need_devinfo)) {
|
||||
need_devinfo[daddr] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when device is unmounted (bus reset/unplugged)
|
||||
void tuh_umount_cb(uint8_t daddr) {
|
||||
blink_interval_ms = BLINK_NOT_MOUNTED;
|
||||
if (daddr < TU_ARRAY_SIZE(need_devinfo)) {
|
||||
need_devinfo[daddr] = false;
|
||||
}
|
||||
printf("Device removed, address = %d\r\n", daddr);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Print device info task — serialises descriptor fetching across all
|
||||
// mounted devices using the sync helpers. Sync calls are safe here because
|
||||
// this task runs outside the host-task callback context (main loop on
|
||||
// OS_NONE / dedicated FreeRTOS task on RTOS).
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void print_one_device(uint8_t daddr) {
|
||||
// Get Device Descriptor
|
||||
uint8_t xfer_result = tuh_descriptor_get_device_sync(daddr, &desc.device, 18);
|
||||
if (XFER_RESULT_SUCCESS != xfer_result) {
|
||||
printf("Failed to get device descriptor\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Device %u: ID %04x:%04x SN ", daddr, desc.device.idVendor, desc.device.idProduct);
|
||||
|
||||
xfer_result = XFER_RESULT_FAILED;
|
||||
if (desc.device.iSerialNumber != 0) {
|
||||
xfer_result = tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, desc.serial, sizeof(desc.serial));
|
||||
}
|
||||
if (XFER_RESULT_SUCCESS != xfer_result) {
|
||||
uint16_t* serial = (uint16_t*)(uintptr_t) desc.serial;
|
||||
serial[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * 1 + 2));
|
||||
serial[1] = '0';
|
||||
serial[2] = 0;
|
||||
}
|
||||
print_utf16((uint16_t*)(uintptr_t) desc.serial, sizeof(desc.serial)/2);
|
||||
printf("\r\n");
|
||||
|
||||
printf("Device Descriptor:\r\n");
|
||||
printf(" bLength %u\r\n", desc.device.bLength);
|
||||
printf(" bDescriptorType %u\r\n", desc.device.bDescriptorType);
|
||||
printf(" bcdUSB %04x\r\n", desc.device.bcdUSB);
|
||||
printf(" bDeviceClass %u\r\n", desc.device.bDeviceClass);
|
||||
printf(" bDeviceSubClass %u\r\n", desc.device.bDeviceSubClass);
|
||||
printf(" bDeviceProtocol %u\r\n", desc.device.bDeviceProtocol);
|
||||
printf(" bMaxPacketSize0 %u\r\n", desc.device.bMaxPacketSize0);
|
||||
printf(" idVendor 0x%04x\r\n", desc.device.idVendor);
|
||||
printf(" idProduct 0x%04x\r\n", desc.device.idProduct);
|
||||
printf(" bcdDevice %04x\r\n", desc.device.bcdDevice);
|
||||
|
||||
printf(" iManufacturer %u ", desc.device.iManufacturer);
|
||||
if (desc.device.iManufacturer != 0) {
|
||||
if (XFER_RESULT_SUCCESS == tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, desc.buf, sizeof(desc.buf))) {
|
||||
print_utf16((uint16_t*)(uintptr_t) desc.buf, sizeof(desc.buf)/2);
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
printf(" iProduct %u ", desc.device.iProduct);
|
||||
if (desc.device.iProduct != 0) {
|
||||
if (XFER_RESULT_SUCCESS == tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, desc.buf, sizeof(desc.buf))) {
|
||||
print_utf16((uint16_t*)(uintptr_t) desc.buf, sizeof(desc.buf)/2);
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
printf(" iSerialNumber %u ", desc.device.iSerialNumber);
|
||||
printf("%s\r\n", (char*)desc.serial); // serial is already UTF-8
|
||||
printf(" bNumConfigurations %u\r\n", desc.device.bNumConfigurations);
|
||||
}
|
||||
|
||||
void print_devinfo_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
while (1) {
|
||||
#endif
|
||||
for (uint8_t daddr = 1; daddr < TU_ARRAY_SIZE(need_devinfo); daddr++) {
|
||||
if (need_devinfo[daddr]) {
|
||||
need_devinfo[daddr] = false;
|
||||
print_one_device(daddr);
|
||||
}
|
||||
}
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// String Descriptor Helper
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void _convert_utf16le_to_utf8(const uint16_t* utf16, size_t utf16_len, uint8_t* utf8, size_t utf8_len) {
|
||||
// TODO: Check for runover.
|
||||
(void) utf8_len;
|
||||
// Get the UTF-16 length out of the data itself.
|
||||
|
||||
for (size_t i = 0; i < utf16_len; i++) {
|
||||
uint16_t chr = utf16[i];
|
||||
if (chr < 0x80) {
|
||||
*utf8++ = chr & 0xffu;
|
||||
} else if (chr < 0x800) {
|
||||
*utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F));
|
||||
*utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F));
|
||||
} else {
|
||||
// TODO: Verify surrogate.
|
||||
*utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F));
|
||||
*utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F));
|
||||
*utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F));
|
||||
}
|
||||
// TODO: Handle UTF-16 code points that take two entries.
|
||||
}
|
||||
}
|
||||
|
||||
// Count how many bytes a utf-16-le encoded string will take in utf-8.
|
||||
static int _count_utf8_bytes(const uint16_t* buf, size_t len) {
|
||||
size_t total_bytes = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint16_t chr = buf[i];
|
||||
if (chr < 0x80) {
|
||||
total_bytes += 1;
|
||||
} else if (chr < 0x800) {
|
||||
total_bytes += 2;
|
||||
} else {
|
||||
total_bytes += 3;
|
||||
}
|
||||
// TODO: Handle UTF-16 code points that take two entries.
|
||||
}
|
||||
return (int) total_bytes;
|
||||
}
|
||||
|
||||
static void print_utf16(uint16_t* temp_buf, size_t buf_len) {
|
||||
if ((temp_buf[0] & 0xff) == 0) return; // empty
|
||||
size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t);
|
||||
size_t utf8_len = (size_t) _count_utf8_bytes(temp_buf + 1, utf16_len);
|
||||
_convert_utf16le_to_utf8(temp_buf + 1, utf16_len, (uint8_t*) temp_buf, sizeof(uint16_t) * buf_len);
|
||||
((uint8_t*) temp_buf)[utf8_len] = '\0';
|
||||
|
||||
printf("%s", (char*) temp_buf);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void* param) {
|
||||
(void) param;
|
||||
static uint32_t start_ms = 0;
|
||||
static bool led_state = false;
|
||||
|
||||
while (1) {
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
vTaskDelay(blink_interval_ms / portTICK_PERIOD_MS);
|
||||
#else
|
||||
if (tusb_time_millis_api() - start_ms < blink_interval_ms) {
|
||||
return; // not enough time
|
||||
}
|
||||
#endif
|
||||
|
||||
start_ms += blink_interval_ms;
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// FreeRTOS
|
||||
//--------------------------------------------------------------------+
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
|
||||
#define BLINKY_STACK_SIZE configMINIMAL_STACK_SIZE
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define USB_STACK_SIZE 4096
|
||||
#else
|
||||
// Increase stack size when debug log is enabled
|
||||
#define USB_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
|
||||
#endif
|
||||
|
||||
|
||||
// static task
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StackType_t blinky_stack[BLINKY_STACK_SIZE];
|
||||
StaticTask_t blinky_taskdef;
|
||||
|
||||
StackType_t usb_stack[USB_STACK_SIZE];
|
||||
StaticTask_t usb_taskdef;
|
||||
|
||||
StackType_t devinfo_stack[USB_STACK_SIZE];
|
||||
StaticTask_t devinfo_taskdef;
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void app_main(void) {
|
||||
main();
|
||||
}
|
||||
#endif
|
||||
|
||||
void usb_host_task(void *param) {
|
||||
(void) param;
|
||||
board_delay(100); // wait for uart to be ready
|
||||
init_tinyusb();
|
||||
while (1) {
|
||||
tuh_task();
|
||||
}
|
||||
}
|
||||
|
||||
void init_freertos_task(void) {
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
xTaskCreateStatic(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, blinky_stack, &blinky_taskdef);
|
||||
xTaskCreateStatic(usb_host_task, "usbh", USB_STACK_SIZE, NULL, configMAX_PRIORITIES-1, usb_stack, &usb_taskdef);
|
||||
xTaskCreateStatic(print_devinfo_task, "devinfo", USB_STACK_SIZE, NULL, configMAX_PRIORITIES-2, devinfo_stack, &devinfo_taskdef);
|
||||
#else
|
||||
xTaskCreate(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, NULL);
|
||||
xTaskCreate(usb_host_task, "usbh", USB_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
xTaskCreate(print_devinfo_task, "devinfo", USB_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, NULL);
|
||||
#endif
|
||||
|
||||
// only start scheduler for non-espressif mcu
|
||||
#ifndef ESP_PLATFORM
|
||||
vTaskStartScheduler();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
// Espressif IDF requires "freertos/" prefix in include path
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CFG_TUSB_OS_INC_PATH freertos/
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
// only hub class is enabled
|
||||
#define CFG_TUH_HUB 1
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(hid_controller C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/hid_app.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE += \
|
||||
src/hid_app.c \
|
||||
src/main.c
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,48 @@
|
||||
# Host: HID Controller
|
||||
|
||||
A USB host example that reads a USB game controller / gamepad (a HID device) and prints its inputs over the debug UART.
|
||||
|
||||
## What it does
|
||||
|
||||
- Enumerates HID devices (`CFG_TUH_HID`) and, on mount, prints the device's VID/PID.
|
||||
- Decodes reports from explicitly supported controllers — Sony DualShock 4 and a few compatible PS4 pads (Hori FC4, Hori PS4 Mini, ASW GG xrd) — printing the joystick axes (x, y, z, rz), D-pad direction, and pressed buttons (Square/Cross/Circle/Triangle, L1/R1/L2/R2, Share/Option/L3/R3, PS, touchpad click). Output is only printed when the report changes meaningfully.
|
||||
- Sends a periodic rumble output report back to the DualShock 4, with the motor intensities driven by the L2/R2 analog triggers.
|
||||
- Blinks the board LED once per second.
|
||||
|
||||
Note: events are only shown for the explicitly supported controllers above; other HID devices enumerate but their reports are not decoded.
|
||||
|
||||
## Requirements
|
||||
|
||||
The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 0
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX)
|
||||
#define CFG_TUH_HID_EP_BUFSIZE 64
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Plug a supported USB game controller (e.g. a Sony DualShock 4) into the board's USB host port. Move the sticks and press buttons — the changes are printed on the debug UART, and squeezing the L2/R2 triggers makes the controller rumble.
|
||||
@@ -0,0 +1,30 @@
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2025 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef TUSB_TINYUSB_EXAMPLES_APP_H
|
||||
#define TUSB_TINYUSB_EXAMPLES_APP_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void hid_app_task(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2021, Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
/* From https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
____________________________ __
|
||||
/ [__ZL__] [__ZR__] \ |
|
||||
/ [__ TL __] [__ TR __] \ | Front Triggers
|
||||
__/________________________________\__ __|
|
||||
/ _ \ |
|
||||
/ /\ __ (N) \ |
|
||||
/ || __ |MO| __ _ _ \ | Main Pad
|
||||
| <===DP===> |SE| |ST| (W) -|- (E) | |
|
||||
\ || ___ ___ _ / |
|
||||
/\ \/ / \ / \ (S) /\ __|
|
||||
/ \________ | LS | ____ | RS | ________/ \ |
|
||||
| / \ \___/ / \ \___/ / \ | | Control Sticks
|
||||
| / \_____/ \_____/ \ | __|
|
||||
| / \ |
|
||||
\_____/ \_____/
|
||||
|
||||
|________|______| |______|___________|
|
||||
D-Pad Left Right Action Pad
|
||||
Stick Stick
|
||||
|
||||
|_____________|
|
||||
Menu Pad
|
||||
|
||||
Most gamepads have the following features:
|
||||
- Action-Pad 4 buttons in diamonds-shape (on the right side) NORTH, SOUTH, WEST and EAST.
|
||||
- D-Pad (Direction-pad) 4 buttons (on the left side) that point up, down, left and right.
|
||||
- Menu-Pad Different constellations, but most-times 2 buttons: SELECT - START.
|
||||
- Analog-Sticks provide freely moveable sticks to control directions, Analog-sticks may also
|
||||
provide a digital button if you press them.
|
||||
- Triggers are located on the upper-side of the pad in vertical direction. The upper buttons
|
||||
are normally named Left- and Right-Triggers, the lower buttons Z-Left and Z-Right.
|
||||
- Rumble Many devices provide force-feedback features. But are mostly just simple rumble motors.
|
||||
*/
|
||||
|
||||
// Sony DS4 report layout detail https://www.psdevwiki.com/ps4/DS4-USB
|
||||
typedef struct TU_ATTR_PACKED
|
||||
{
|
||||
uint8_t x, y, z, rz; // joystick
|
||||
|
||||
struct {
|
||||
uint8_t dpad : 4; // (hat format, 0x08 is released, 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW)
|
||||
uint8_t square : 1; // west
|
||||
uint8_t cross : 1; // south
|
||||
uint8_t circle : 1; // east
|
||||
uint8_t triangle : 1; // north
|
||||
};
|
||||
|
||||
struct {
|
||||
uint8_t l1 : 1;
|
||||
uint8_t r1 : 1;
|
||||
uint8_t l2 : 1;
|
||||
uint8_t r2 : 1;
|
||||
uint8_t share : 1;
|
||||
uint8_t option : 1;
|
||||
uint8_t l3 : 1;
|
||||
uint8_t r3 : 1;
|
||||
};
|
||||
|
||||
struct {
|
||||
uint8_t ps : 1; // playstation button
|
||||
uint8_t tpad : 1; // track pad click
|
||||
uint8_t counter : 6; // +1 each report
|
||||
};
|
||||
|
||||
uint8_t l2_trigger; // 0 released, 0xff fully pressed
|
||||
uint8_t r2_trigger; // as above
|
||||
|
||||
// uint16_t timestamp;
|
||||
// uint8_t battery;
|
||||
//
|
||||
// int16_t gyro[3]; // x, y, z;
|
||||
// int16_t accel[3]; // x, y, z
|
||||
|
||||
// there is still lots more info
|
||||
|
||||
} sony_ds4_report_t;
|
||||
|
||||
typedef struct TU_ATTR_PACKED {
|
||||
// First 16 bits set what data is pertinent in this structure (1 = set; 0 = not set)
|
||||
uint8_t set_rumble : 1;
|
||||
uint8_t set_led : 1;
|
||||
uint8_t set_led_blink : 1;
|
||||
uint8_t set_ext_write : 1;
|
||||
uint8_t set_left_volume : 1;
|
||||
uint8_t set_right_volume : 1;
|
||||
uint8_t set_mic_volume : 1;
|
||||
uint8_t set_speaker_volume : 1;
|
||||
uint8_t set_flags2;
|
||||
|
||||
uint8_t reserved;
|
||||
|
||||
uint8_t motor_right;
|
||||
uint8_t motor_left;
|
||||
|
||||
uint8_t lightbar_red;
|
||||
uint8_t lightbar_green;
|
||||
uint8_t lightbar_blue;
|
||||
uint8_t lightbar_blink_on;
|
||||
uint8_t lightbar_blink_off;
|
||||
|
||||
uint8_t ext_data[8];
|
||||
|
||||
uint8_t volume_left;
|
||||
uint8_t volume_right;
|
||||
uint8_t volume_mic;
|
||||
uint8_t volume_speaker;
|
||||
|
||||
uint8_t other[9];
|
||||
} sony_ds4_output_report_t;
|
||||
|
||||
static bool ds4_mounted = false;
|
||||
static uint8_t ds4_dev_addr = 0;
|
||||
static uint8_t ds4_instance = 0;
|
||||
static uint8_t motor_left = 0;
|
||||
static uint8_t motor_right = 0;
|
||||
|
||||
// check if device is Sony DualShock 4
|
||||
static inline bool is_sony_ds4(uint8_t dev_addr)
|
||||
{
|
||||
uint16_t vid, pid;
|
||||
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
||||
|
||||
return ( (vid == 0x054c && (pid == 0x09cc || pid == 0x05c4)) // Sony DualShock4
|
||||
|| (vid == 0x0f0d && pid == 0x005e) // Hori FC4
|
||||
|| (vid == 0x0f0d && pid == 0x00ee) // Hori PS4 Mini (PS4-099U)
|
||||
|| (vid == 0x1f4f && pid == 0x1002) // ASW GG xrd controller
|
||||
);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void hid_app_task(void)
|
||||
{
|
||||
if (ds4_mounted)
|
||||
{
|
||||
const uint32_t interval_ms = 200;
|
||||
static uint32_t start_ms = 0;
|
||||
|
||||
uint32_t current_time_ms = tusb_time_millis_api();
|
||||
if ( current_time_ms - start_ms >= interval_ms)
|
||||
{
|
||||
start_ms = current_time_ms;
|
||||
|
||||
sony_ds4_output_report_t output_report = {0};
|
||||
output_report.set_rumble = 1;
|
||||
output_report.motor_left = motor_left;
|
||||
output_report.motor_right = motor_right;
|
||||
tuh_hid_send_report(ds4_dev_addr, ds4_instance, 5, &output_report, sizeof(output_report));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when device with hid interface is mounted
|
||||
// Report descriptor is also available for use. tuh_hid_parse_report_descriptor()
|
||||
// can be used to parse common/simple enough descriptor.
|
||||
// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped
|
||||
// therefore report_desc = NULL, desc_len = 0
|
||||
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
|
||||
{
|
||||
(void)desc_report;
|
||||
(void)desc_len;
|
||||
uint16_t vid, pid;
|
||||
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
||||
|
||||
printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
|
||||
printf("VID = %04x, PID = %04x\r\n", vid, pid);
|
||||
|
||||
// Sony DualShock 4 [CUH-ZCT2x]
|
||||
if ( is_sony_ds4(dev_addr) )
|
||||
{
|
||||
if (!ds4_mounted)
|
||||
{
|
||||
ds4_dev_addr = dev_addr;
|
||||
ds4_instance = instance;
|
||||
motor_left = 0;
|
||||
motor_right = 0;
|
||||
ds4_mounted = true;
|
||||
}
|
||||
// request to receive report
|
||||
// tuh_hid_report_received_cb() will be invoked when report is available
|
||||
if ( !tuh_hid_receive_report(dev_addr, instance) )
|
||||
{
|
||||
printf("Error: cannot request to receive report\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when device with hid interface is un-mounted
|
||||
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
|
||||
{
|
||||
printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
|
||||
if (ds4_mounted && ds4_dev_addr == dev_addr && ds4_instance == instance)
|
||||
{
|
||||
ds4_mounted = false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if different than 2
|
||||
static inline bool diff_than_2(uint8_t x, uint8_t y) {
|
||||
return (x - y > 2) || (y - x > 2);
|
||||
}
|
||||
|
||||
// check if 2 reports are different enough
|
||||
static bool diff_report(sony_ds4_report_t const* rpt1, sony_ds4_report_t const* rpt2) {
|
||||
bool result;
|
||||
|
||||
// x, y, z, rz must different than 2 to be counted
|
||||
result = diff_than_2(rpt1->x, rpt2->x) || diff_than_2(rpt1->y , rpt2->y ) ||
|
||||
diff_than_2(rpt1->z, rpt2->z) || diff_than_2(rpt1->rz, rpt2->rz);
|
||||
|
||||
// check the rest with mem compare
|
||||
result |= memcmp(&rpt1->rz + 1, &rpt2->rz + 1, sizeof(sony_ds4_report_t)-6);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void process_sony_ds4(uint8_t const* report, uint16_t len)
|
||||
{
|
||||
(void)len;
|
||||
const char* dpad_str[] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW", "none" };
|
||||
|
||||
// previous report used to compare for changes
|
||||
static sony_ds4_report_t prev_report = { 0 };
|
||||
|
||||
uint8_t const report_id = report[0];
|
||||
report++;
|
||||
len--;
|
||||
|
||||
// all buttons state is stored in ID 1
|
||||
if (report_id == 1)
|
||||
{
|
||||
sony_ds4_report_t ds4_report;
|
||||
memcpy(&ds4_report, report, sizeof(ds4_report));
|
||||
|
||||
// counter is +1, assign to make it easier to compare 2 report
|
||||
prev_report.counter = ds4_report.counter;
|
||||
|
||||
// only print if changes since it is polled ~ 5ms
|
||||
// Since count+1 after each report and x, y, z, rz fluctuate within 1 or 2
|
||||
// We need more than memcmp to check if report is different enough
|
||||
if ( diff_report(&prev_report, &ds4_report) )
|
||||
{
|
||||
printf("(x, y, z, rz) = (%u, %u, %u, %u)\r\n", ds4_report.x, ds4_report.y, ds4_report.z, ds4_report.rz);
|
||||
printf("DPad = %s ", dpad_str[ds4_report.dpad]);
|
||||
|
||||
if (ds4_report.square ) printf("Square ");
|
||||
if (ds4_report.cross ) printf("Cross ");
|
||||
if (ds4_report.circle ) printf("Circle ");
|
||||
if (ds4_report.triangle ) printf("Triangle ");
|
||||
|
||||
if (ds4_report.l1 ) printf("L1 ");
|
||||
if (ds4_report.r1 ) printf("R1 ");
|
||||
if (ds4_report.l2 ) printf("L2 ");
|
||||
if (ds4_report.r2 ) printf("R2 ");
|
||||
|
||||
if (ds4_report.share ) printf("Share ");
|
||||
if (ds4_report.option ) printf("Option ");
|
||||
if (ds4_report.l3 ) printf("L3 ");
|
||||
if (ds4_report.r3 ) printf("R3 ");
|
||||
|
||||
if (ds4_report.ps ) printf("PS ");
|
||||
if (ds4_report.tpad ) printf("TPad ");
|
||||
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
// The left and right triggers control the intensity of the left and right rumble motors
|
||||
motor_left = ds4_report.l2_trigger;
|
||||
motor_right = ds4_report.r2_trigger;
|
||||
|
||||
prev_report = ds4_report;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when received report from device via interrupt endpoint
|
||||
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||
if (is_sony_ds4(dev_addr)) {
|
||||
process_sony_ds4(report, len);
|
||||
}
|
||||
|
||||
// continue to request to receive report
|
||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||
printf("Error: cannot request to receive report\r\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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 example current worked and tested with following controller
|
||||
* - Sony DualShock 4 [CUH-ZCT2x] VID = 0x054c, PID = 0x09cc
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void);
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host HID Controller Example\r\n");
|
||||
printf("Note: Events only displayed for explicit supported controllers\r\n");
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
while (1) {
|
||||
// tinyusb host task
|
||||
tuh_task();
|
||||
led_blinking_task();
|
||||
hid_app_task();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void) {
|
||||
const uint32_t interval_ms = 1000;
|
||||
static uint32_t start_ms = 0;
|
||||
|
||||
static bool led_state = false;
|
||||
|
||||
// Blink every interval ms
|
||||
if ( tusb_time_millis_api() - start_ms < interval_ms) return; // not enough time
|
||||
start_ms += interval_ms;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 0
|
||||
#define CFG_TUH_CDC 0
|
||||
#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX) // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||
#define CFG_TUH_MSC 0
|
||||
#define CFG_TUH_VENDOR 0
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
//------------- HID -------------//
|
||||
|
||||
#define CFG_TUH_HID_EP_BUFSIZE 64
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
@@ -0,0 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(midi2_host C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE += \
|
||||
src/main.c
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,45 @@
|
||||
# Host: MIDI 2.0
|
||||
|
||||
A minimal USB host example that receives MIDI from a connected USB-MIDI device and prints it over the debug UART, using the USB-MIDI 2.0 host driver (UMP).
|
||||
|
||||
## What it does
|
||||
|
||||
- Enumerates USB-MIDI devices (`CFG_TUH_MIDI2`) and, on mount, prints the negotiated protocol (MIDI 1.0 / MIDI 2.0) and the number of RX/TX cables.
|
||||
- Receives Universal MIDI Packets (UMP) and prints each one, decoding common Channel Voice messages — Note On/Off, Control Change, Program Change, Channel Pressure, Pitch Bend — for both MIDI 2.0 and MIDI 1.0 message types, and falling back to a raw hex dump for anything else.
|
||||
|
||||
## Requirements
|
||||
|
||||
The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_MIDI2 CFG_TUH_DEVICE_MAX
|
||||
#define CFG_TUH_MIDI2_RX_BUFSIZE 512
|
||||
#define CFG_TUH_MIDI2_TX_BUFSIZE 512
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Plug a USB-MIDI device (keyboard, controller, or interface) into the board's USB host port. On attach the debug UART prints the mount/descriptor info; then playing notes or moving controls on the device prints the decoded MIDI messages.
|
||||
@@ -0,0 +1,34 @@
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:ESP32P4
|
||||
mcu:ESP32S2
|
||||
mcu:ESP32S3
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2026 Saulo Verissimo
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Minimal USB-MIDI 2.0 host example.
|
||||
// Receives UMP from any MIDI 2.0 device, prints each packet to stdout.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "class/midi/midi2_host.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// State
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
TU_ATTR_UNUSED static uint8_t midi2_idx = 0xFF;
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// UMP printer - shows MT and word(s) in hex; decodes Channel Voice
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void print_ump(const uint32_t* words, uint8_t wc) {
|
||||
uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F);
|
||||
uint8_t group = (uint8_t)((words[0] >> 24) & 0x0F);
|
||||
|
||||
if (mt == 0x4 && wc >= 2) {
|
||||
// MIDI 2.0 Channel Voice
|
||||
uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F);
|
||||
uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F);
|
||||
uint8_t data1 = (uint8_t)((words[0] >> 8) & 0x7F);
|
||||
switch (status) {
|
||||
case 0x9:
|
||||
printf("[g%u ch%u] M2 NoteOn n=%u vel=%04X attr=%04X\r\n",
|
||||
group, channel, data1,
|
||||
(unsigned)((words[1] >> 16) & 0xFFFF),
|
||||
(unsigned)(words[1] & 0xFFFF));
|
||||
break;
|
||||
case 0x8:
|
||||
printf("[g%u ch%u] M2 NoteOff n=%u vel=%04X\r\n",
|
||||
group, channel, data1,
|
||||
(unsigned)((words[1] >> 16) & 0xFFFF));
|
||||
break;
|
||||
case 0xB:
|
||||
printf("[g%u ch%u] M2 CC#%u = %08lX\r\n",
|
||||
group, channel, data1, (unsigned long)words[1]);
|
||||
break;
|
||||
case 0xC:
|
||||
printf("[g%u ch%u] M2 ProgChg %u\r\n",
|
||||
group, channel, (unsigned)((words[1] >> 24) & 0x7F));
|
||||
break;
|
||||
case 0xD:
|
||||
printf("[g%u ch%u] M2 ChanPress %08lX\r\n",
|
||||
group, channel, (unsigned long)words[1]);
|
||||
break;
|
||||
case 0xE:
|
||||
printf("[g%u ch%u] M2 PitchBend %08lX\r\n",
|
||||
group, channel, (unsigned long)words[1]);
|
||||
break;
|
||||
default:
|
||||
printf("[g%u ch%u] M2 status=0x%X w0=%08lX w1=%08lX\r\n",
|
||||
group, channel, status,
|
||||
(unsigned long)words[0], (unsigned long)words[1]);
|
||||
break;
|
||||
}
|
||||
} else if (mt == 0x2 && wc == 1) {
|
||||
// MIDI 1.0 Channel Voice
|
||||
uint8_t status = (uint8_t)((words[0] >> 16) & 0xFF);
|
||||
uint8_t data1 = (uint8_t)((words[0] >> 8) & 0x7F);
|
||||
uint8_t data2 = (uint8_t)(words[0] & 0x7F);
|
||||
printf("[g%u] M1 %02X %02X %02X\r\n", group, status, data1, data2);
|
||||
} else {
|
||||
printf("UMP MT=0x%X wc=%u w0=%08lX\r\n",
|
||||
mt, wc, (unsigned long)words[0]);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MIDI 2.0 Host Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) {
|
||||
(void)idx;
|
||||
printf("MIDI2 descriptor: %s, RX cables=%u TX cables=%u\r\n",
|
||||
d->protocol_version ? "MIDI 2.0" : "MIDI 1.0",
|
||||
d->rx_cable_count, d->tx_cable_count);
|
||||
}
|
||||
|
||||
void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) {
|
||||
midi2_idx = idx;
|
||||
printf("MIDI2 mounted: idx=%u protocol=%s\r\n",
|
||||
idx, m->protocol_version ? "MIDI 2.0" : "MIDI 1.0");
|
||||
}
|
||||
|
||||
void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) {
|
||||
(void)xferred_bytes;
|
||||
|
||||
uint32_t words[16];
|
||||
while (1) {
|
||||
uint32_t n = tuh_midi2_ump_read(idx, words, 16);
|
||||
if (n == 0) break;
|
||||
|
||||
uint32_t i = 0;
|
||||
while (i < n) {
|
||||
uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F);
|
||||
uint8_t wc = midi2_ump_word_count(mt);
|
||||
if (i + wc > n) break;
|
||||
print_ump(&words[i], wc);
|
||||
i += wc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) {
|
||||
(void)idx; (void)xferred_bytes;
|
||||
}
|
||||
|
||||
void tuh_midi2_umount_cb(uint8_t idx) {
|
||||
(void)idx;
|
||||
midi2_idx = 0xFF;
|
||||
printf("MIDI2 unmounted\r\n");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Main
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("\r\nTinyUSB Host MIDI 2.0 Example\r\n");
|
||||
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO,
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
while (1) {
|
||||
tuh_task();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2026 Saulo Verissimo
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CFG_TUSB_OS_INC_PATH freertos/
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------+
|
||||
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
// USB-MIDI 2.0 Host
|
||||
#define CFG_TUH_MIDI2 CFG_TUH_DEVICE_MAX
|
||||
#define CFG_TUH_MIDI2_RX_BUFSIZE 512
|
||||
#define CFG_TUH_MIDI2_TX_BUFSIZE 512
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(midi_rx C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE += \
|
||||
src/main.c
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,44 @@
|
||||
# Host: MIDI Receive
|
||||
|
||||
A USB host example that receives MIDI from a connected USB-MIDI device and prints the incoming bytes over the debug UART.
|
||||
|
||||
## What it does
|
||||
|
||||
- Enumerates USB-MIDI devices (`CFG_TUH_MIDI`) and, on mount, prints the interface index, device address, and the number of RX/TX cables.
|
||||
- On each received MIDI packet, reads the stream and prints the cable number followed by the raw MIDI bytes as hex.
|
||||
- Blinks the board LED once per second.
|
||||
|
||||
## Requirements
|
||||
|
||||
The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_MIDI CFG_TUH_DEVICE_MAX
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Plug a USB-MIDI device (keyboard, controller, or interface) into the board's USB host port. On attach the debug UART prints the mount info; then playing notes or moving controls prints lines like `Cable 0 rx: 90 3C 7F` with the raw MIDI bytes.
|
||||
@@ -0,0 +1,35 @@
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:ESP32P4
|
||||
mcu:ESP32S2
|
||||
mcu:ESP32S3
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32C5
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// STATIC GLOBALS DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTOTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void);
|
||||
void midi_host_rx_task(void);
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host MIDI Example\r\n");
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
while (1) {
|
||||
tuh_task();
|
||||
led_blinking_task();
|
||||
midi_host_rx_task();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void) {
|
||||
const uint32_t interval_ms = 1000;
|
||||
static uint32_t start_ms = 0;
|
||||
|
||||
static bool led_state = false;
|
||||
|
||||
// Blink every interval ms
|
||||
if (tusb_time_millis_api() - start_ms < interval_ms) return;// not enough time
|
||||
start_ms += interval_ms;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state;// toggle
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MIDI host receive task
|
||||
//--------------------------------------------------------------------+
|
||||
void midi_host_rx_task(void) {
|
||||
// nothing to do, we just print out received data in callback
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when device with MIDI interface is mounted.
|
||||
void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) {
|
||||
printf("MIDI Interface Index = %u, Address = %u, Number of RX cables = %u, Number of TX cables = %u\r\n",
|
||||
idx, mount_cb_data->daddr, mount_cb_data->rx_cable_count, mount_cb_data->tx_cable_count);
|
||||
}
|
||||
|
||||
// Invoked when device with hid interface is un-mounted
|
||||
void tuh_midi_umount_cb(uint8_t idx) {
|
||||
printf("MIDI Interface Index = %u is unmounted\r\n", idx);
|
||||
}
|
||||
|
||||
void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) {
|
||||
if (xferred_bytes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buffer[48];
|
||||
uint8_t cable_num = 0;
|
||||
uint32_t bytes_read = tuh_midi_stream_read(idx, &cable_num, buffer, sizeof(buffer));
|
||||
|
||||
printf("Cable %u rx: ", cable_num);
|
||||
for (uint32_t i = 0; i < bytes_read; i++) {
|
||||
printf("%02X ", buffer[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) {
|
||||
(void) idx;
|
||||
(void) xferred_bytes;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
// Espressif IDF requires "freertos/" prefix in include path
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CFG_TUSB_OS_INC_PATH freertos/
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 1
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_MIDI CFG_TUH_DEVICE_MAX
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(msc_file_explorer C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c
|
||||
${TOP}/lib/fatfs/source/ff.c
|
||||
${TOP}/lib/fatfs/source/ffsystem.c
|
||||
${TOP}/lib/fatfs/source/ffunicode.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${TOP}/lib/fatfs/source
|
||||
${TOP}/lib/embedded-cli
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} noos)
|
||||
|
||||
# Suppress warnings on fatfs
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
set_source_files_properties(${TOP}/lib/fatfs/source/ff.c PROPERTIES
|
||||
COMPILE_OPTIONS "-Wno-conversion;-Wno-cast-qual"
|
||||
)
|
||||
endif ()
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
FATFS_PATH = lib/fatfs/source
|
||||
|
||||
INC += \
|
||||
src \
|
||||
$(TOP)/$(FATFS_PATH) \
|
||||
$(TOP)/lib/embedded-cli \
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE = \
|
||||
src/main.c \
|
||||
src/msc_app.c \
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
# FatFS source
|
||||
SRC_C += \
|
||||
$(FATFS_PATH)/ff.c \
|
||||
$(FATFS_PATH)/ffsystem.c \
|
||||
$(FATFS_PATH)/ffunicode.c \
|
||||
|
||||
# suppress warning caused by fatfs
|
||||
CFLAGS += -Wno-error=cast-qual
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,127 @@
|
||||
# MSC File Explorer
|
||||
|
||||
This host example implements an interactive command-line file browser for USB Mass Storage devices.
|
||||
When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like
|
||||
CLI is presented over the board's serial console.
|
||||
|
||||
## Features
|
||||
|
||||
- Automatic mount/unmount of USB storage devices
|
||||
- FAT12/16/32 filesystem support via FatFS
|
||||
- Interactive CLI with command history
|
||||
- Read speed benchmarking with `dd`
|
||||
- Support for up to 4 simultaneous USB storage devices (via hub)
|
||||
|
||||
## Supported Commands
|
||||
|
||||
| Command | Usage | Description |
|
||||
|---------|--------------------|------------------------------------------------------|
|
||||
| help | `help` | Print list of available commands |
|
||||
| cat | `cat <file>` | Print file contents to the console |
|
||||
| cd | `cd <dir>` | Change current working directory |
|
||||
| cp | `cp <src> <dest>` | Copy a file |
|
||||
| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) |
|
||||
| ls | `ls [dir]` | List directory contents |
|
||||
| pwd | `pwd` | Print current working directory |
|
||||
| mkdir | `mkdir <dir>` | Create a directory |
|
||||
| mv | `mv <src> <dest>` | Rename/move a file or directory |
|
||||
| rm | `rm <file>` | Remove a file |
|
||||
|
||||
## Configuration
|
||||
|
||||
Notable `tusb_config.h` settings:
|
||||
|
||||
```c
|
||||
#define CFG_TUH_ENABLED 1
|
||||
#define CFG_TUH_HUB 1
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
#define CFG_TUH_MSC_MAXLUN 4
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
CMake:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake -DBOARD=raspberry_pi_pico ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Make:
|
||||
|
||||
```bash
|
||||
make BOARD=raspberry_pi_pico all
|
||||
```
|
||||
|
||||
## FreeRTOS variant
|
||||
|
||||
A FreeRTOS build is in `examples/host/msc_file_explorer_freertos` — identical MSC FatFS file-explorer CLI, running the host stack and CLI as FreeRTOS tasks.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash the firmware to your board.
|
||||
2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud.
|
||||
3. Plug a USB flash drive into the board's USB host port.
|
||||
4. The device is auto-mounted and the prompt appears:
|
||||
|
||||
```
|
||||
TinyUSB MSC File Explorer Example
|
||||
|
||||
Device connected
|
||||
Vendor : Kingston
|
||||
Product : DataTraveler 2.0
|
||||
Rev : 1.0
|
||||
Capacity: 1.9 GB
|
||||
|
||||
0:/> _
|
||||
```
|
||||
|
||||
### Browsing Files
|
||||
|
||||
```
|
||||
0:/> ls
|
||||
----a 1234 readme.txt
|
||||
d---- 0 photos
|
||||
d---- 0 docs
|
||||
|
||||
0:/> cd photos
|
||||
0:/photos> ls
|
||||
----a 520432 vacation.jpg
|
||||
----a 312088 family.png
|
||||
|
||||
0:/> cat readme.txt
|
||||
Hello from USB drive!
|
||||
```
|
||||
|
||||
### Copying and Moving Files
|
||||
|
||||
```
|
||||
0:/> cp readme.txt backup.txt
|
||||
0:/> mv backup.txt docs/backup.txt
|
||||
```
|
||||
|
||||
### Measuring Read Speed
|
||||
|
||||
```
|
||||
0:/> dd
|
||||
Reading 1024 sectors...
|
||||
Data speed: 823 KB/s
|
||||
```
|
||||
|
||||
### Multiple Devices
|
||||
|
||||
When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to
|
||||
navigate between them:
|
||||
|
||||
```
|
||||
0:/> cd 1:
|
||||
1:/> ls
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
This example is part of the TinyUSB HIL (Hardware-in-the-Loop) test suite. The HIL test
|
||||
automatically flashes, runs the example, and verifies MSC enumeration and file operations
|
||||
against a known USB drive.
|
||||
@@ -0,0 +1,32 @@
|
||||
family:hpmicro
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:CH32V20X
|
||||
mcu:KINETIS_KL
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RAXXX
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32C5
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1 @@
|
||||
board:lpcxpresso54114
|
||||
@@ -0,0 +1,313 @@
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Configurations of FatFs Module
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 80386 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_READONLY 0
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 0
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 0
|
||||
/* This option switches f_mkfs(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FASTSEEK 0
|
||||
/* This option switches fast seek feature. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 0
|
||||
/* This option switches attribute control API functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 0
|
||||
/* This option switches volume label API functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 0
|
||||
/* This option switches f_forward(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 0
|
||||
#define FF_PRINT_LLI 0
|
||||
#define FF_PRINT_FLOAT 0
|
||||
#define FF_STRF_ENCODE 0
|
||||
/* FF_USE_STRFUNC switches string API functions, f_gets(), f_putc(), f_puts() and
|
||||
/ f_printf().
|
||||
/
|
||||
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion.
|
||||
/
|
||||
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
|
||||
/ makes f_printf() support floating point argument. These features want C99 or later.
|
||||
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
|
||||
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||
/ to be read/written via those functions.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP
|
||||
/ 1: Unicode in UTF-16LE
|
||||
/ 2: Unicode in UTF-16BE
|
||||
/ 3: Unicode in UTF-8
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_CODE_PAGE 437
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect code page setting can cause a file open failure.
|
||||
/
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
/ 0 - Include all code pages above and configured by f_setcp()
|
||||
*/
|
||||
|
||||
|
||||
#define FF_USE_LFN 1
|
||||
#define FF_MAX_LFN 255
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#define FF_LFN_UNICODE 0
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||
/
|
||||
/ Also behavior of string I/O functions will be affected by this option.
|
||||
/ When LFN is not enabled, this option has no effect. */
|
||||
|
||||
|
||||
#define FF_LFN_BUF 255
|
||||
#define FF_SFN_BUF 12
|
||||
/* This set of options defines size of file name members in the FILINFO structure
|
||||
/ which is used to read out directory items. These values should be sufficient for
|
||||
/ the file names to read. The maximum possible length of the read file name depends
|
||||
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||
|
||||
|
||||
#define FF_FS_RPATH 2
|
||||
/* This option configures support for relative path feature.
|
||||
/
|
||||
/ 0: Disable relative path and remove related API functions.
|
||||
/ 1: Enable relative path and dot names. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_PATH_DEPTH 10
|
||||
/* This option defines maximum depth of directory in the exFAT volume. It is NOT
|
||||
/ relevant to FAT/FAT32 volume.
|
||||
/ For example, FF_PATH_DEPTH = 3 will able to follow a path "/dir1/dir2/dir3/file"
|
||||
/ but a sub-directory in the dir3 will not able to be followed and set current
|
||||
/ directory.
|
||||
/ The size of filesystem object (FATFS) increases FF_PATH_DEPTH * 24 bytes.
|
||||
/ When FF_FS_EXFAT == 0 or FF_FS_RPATH == 0, this option has no effect.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_VOLUMES 4
|
||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 0
|
||||
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drive. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table is needed as:
|
||||
/
|
||||
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
|
||||
*/
|
||||
|
||||
|
||||
#define FF_MULTI_PARTITION 0
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this feature is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ will be available. */
|
||||
|
||||
|
||||
#define FF_MIN_SS 512
|
||||
#define FF_MAX_SS 512
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk, but a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
|
||||
/ configured for variable sector size mode and disk_ioctl() needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_LBA64 0
|
||||
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
|
||||
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||
|
||||
|
||||
#define FF_MIN_GPT 0x10000000
|
||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and
|
||||
/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 0
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable this feature, also CTRL_TRIM command should be implemented to
|
||||
/ the disk_ioctl(). */
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_TINY 0
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is reduced FF_MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||
|
||||
|
||||
#define FF_FS_EXFAT 0
|
||||
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
|
||||
#define FF_FS_NORTC 1
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2025
|
||||
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
|
||||
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
|
||||
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
|
||||
/ to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_CRTIME 0
|
||||
/* This option enables(1)/disables(0) the timestamp of the file created. When
|
||||
/ set 1, the file created time is available in FILINFO structure. */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 0
|
||||
/* If you need to know the correct free space on the FAT32 volume, set bit 0 of
|
||||
/ this option, and f_getfree() on the first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_FS_LOCK 0
|
||||
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
|
||||
#define FF_FS_REENTRANT 0
|
||||
#define FF_FS_TIMEOUT 1000
|
||||
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk(), are always not re-entrant. Only file/directory access to
|
||||
/ the same volume is under control of this featuer.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
|
||||
/ must be added to the project. Samples are available in ffsystem.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*--- End of configuration options ---*/
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
|
||||
#include "msc_app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void);
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host MassStorage Explorer Example\r\n");
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUH_RHPORT, &host_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
msc_app_init();
|
||||
|
||||
while (1) {
|
||||
// tinyusb host task
|
||||
tuh_task();
|
||||
|
||||
msc_app_task();
|
||||
led_blinking_task();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void tuh_mount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
}
|
||||
|
||||
void tuh_umount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void) {
|
||||
const uint32_t interval_ms = 1000;
|
||||
static uint32_t start_ms = 0;
|
||||
|
||||
static bool led_state = false;
|
||||
|
||||
// Blink every interval ms
|
||||
if (tusb_time_millis_api() - start_ms < interval_ms) return; // not enough time
|
||||
start_ms += interval_ms;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include "tusb.h"
|
||||
#include "bsp/board_api.h"
|
||||
|
||||
#include "ff.h"
|
||||
#include "diskio.h"
|
||||
|
||||
// lib/embedded-cli
|
||||
#define EMBEDDED_CLI_IMPL
|
||||
#include "embedded_cli.h"
|
||||
|
||||
#include "msc_app.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
//------------- embedded-cli -------------//
|
||||
#define CLI_BUFFER_SIZE 512
|
||||
#define CLI_RX_BUFFER_SIZE 16
|
||||
#define CLI_CMD_BUFFER_SIZE 64
|
||||
#define CLI_HISTORY_SIZE 32
|
||||
#define CLI_BINDING_COUNT 9
|
||||
|
||||
static EmbeddedCli *_cli;
|
||||
static CLI_UINT cli_buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)];
|
||||
|
||||
//------------- Elm Chan FatFS -------------//
|
||||
static CFG_TUH_MEM_SECTION FATFS fatfs[CFG_TUH_DEVICE_MAX]; // for simplicity only support 1 LUN per device
|
||||
static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX];
|
||||
|
||||
static CFG_TUH_MEM_SECTION FIL file1, file2;
|
||||
|
||||
#ifndef CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE
|
||||
#define CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE 4096
|
||||
#endif
|
||||
static CFG_TUH_MEM_SECTION uint8_t rw_buf[CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE];
|
||||
|
||||
// define the buffer to be place in USB/DMA memory with correct alignment/cache line size
|
||||
CFG_TUH_MEM_SECTION static struct {
|
||||
TUH_EPBUF_TYPE_DEF(scsi_inquiry_resp_t, inquiry);
|
||||
} scsi_resp;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
//
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
bool cli_init(void);
|
||||
|
||||
bool msc_app_init(void) {
|
||||
for (size_t i = 0; i < CFG_TUH_DEVICE_MAX; i++) {
|
||||
_disk_busy[i] = false;
|
||||
}
|
||||
|
||||
// disable stdout buffered for echoing typing command
|
||||
#ifndef __ICCARM__ // TODO IAR doesn't support stream control ?
|
||||
setbuf(stdout, NULL);
|
||||
#endif
|
||||
|
||||
cli_init();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void msc_app_task(void) {
|
||||
if (!_cli) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ch = board_getchar();
|
||||
if (ch > 0) {
|
||||
while (ch > 0) {
|
||||
embeddedCliReceiveChar(_cli, (char)ch);
|
||||
ch = board_getchar();
|
||||
}
|
||||
embeddedCliProcess(_cli);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
//
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
|
||||
const msc_cbw_t *cbw = cb_data->cbw;
|
||||
const msc_csw_t *csw = cb_data->csw;
|
||||
|
||||
if (csw->status != 0) {
|
||||
printf("Inquiry failed\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Print out Vendor ID, Product ID and Rev
|
||||
printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id,
|
||||
scsi_resp.inquiry.product_rev);
|
||||
|
||||
// Get capacity of device
|
||||
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
|
||||
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
|
||||
|
||||
printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
|
||||
block_count, block_size, block_count / ((1024 * 1024) / block_size));
|
||||
|
||||
// For simplicity: we only mount 1 LUN per device
|
||||
const uint8_t drive_num = dev_addr - 1;
|
||||
char drive_path[3] = "0:";
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
if (f_mount(&fatfs[drive_num], drive_path, 1) != FR_OK) {
|
||||
printf("mount failed\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// change to newly mounted drive
|
||||
f_chdrive(drive_path);
|
||||
FRESULT rc = f_chdir("/");
|
||||
if (rc != FR_OK) {
|
||||
printf("chdir failed: %d\r\n", rc);
|
||||
}
|
||||
|
||||
// print the drive label
|
||||
// char label[34];
|
||||
// if ( FR_OK == f_getlabel(drive_path, label, NULL) )
|
||||
// {
|
||||
// puts(label);
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------- IMPLEMENTATION -------------//
|
||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device (addr = %u) is mounted\r\n", dev_addr);
|
||||
|
||||
const uint8_t lun = 0;
|
||||
tuh_msc_inquiry(dev_addr, lun, &scsi_resp.inquiry, inquiry_complete_cb, 0);
|
||||
}
|
||||
|
||||
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device is unmounted\r\n");
|
||||
|
||||
const uint8_t drive_num = dev_addr - 1;
|
||||
char drive_path[3] = "0:";
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
f_unmount(drive_path);
|
||||
|
||||
// if ( phy_disk == f_get_current_drive() )
|
||||
// { // active drive is unplugged --> change to other drive
|
||||
// for(uint8_t i=0; i<CFG_TUH_DEVICE_MAX; i++)
|
||||
// {
|
||||
// if ( disk_is_ready(i) )
|
||||
// {
|
||||
// f_chdrive(i);
|
||||
// cli_init(); // refractor, rename
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// DiskIO
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void wait_for_disk_io(BYTE pdrv) {
|
||||
while (_disk_busy[pdrv]) {
|
||||
tuh_task();
|
||||
}
|
||||
}
|
||||
|
||||
static bool disk_io_complete(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
|
||||
(void)dev_addr;
|
||||
(void)cb_data;
|
||||
_disk_busy[dev_addr - 1] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
uint8_t dev_addr = pdrv + 1;
|
||||
return tuh_msc_mounted(dev_addr) ? 0 : STA_NODISK;
|
||||
}
|
||||
|
||||
DSTATUS disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
(void)pdrv;
|
||||
return 0; // nothing to do
|
||||
}
|
||||
|
||||
DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
BYTE *buff, /* Data buffer to store read data */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to read */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_read10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#if FF_FS_READONLY == 0
|
||||
|
||||
DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
const BYTE *buff, /* Data to be written */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to write */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_write10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||
BYTE cmd, /* Control code */
|
||||
void *buff /* Buffer to send/receive control data */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
// nothing to do since we do blocking
|
||||
return RES_OK;
|
||||
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *)buff) = (DWORD)tuh_msc_get_block_count(dev_addr, lun);
|
||||
return RES_OK;
|
||||
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *)buff) = (WORD)tuh_msc_get_block_size(dev_addr, lun);
|
||||
return RES_OK;
|
||||
|
||||
case GET_BLOCK_SIZE:
|
||||
*((DWORD *)buff) = 1; // erase block size in units of sector size
|
||||
return RES_OK;
|
||||
|
||||
default:
|
||||
return RES_PARERR;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// CLI Commands
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context);
|
||||
|
||||
static void cli_write_char(EmbeddedCli *cli, char c) {
|
||||
(void)cli;
|
||||
putchar((int)c);
|
||||
}
|
||||
|
||||
bool cli_init(void) {
|
||||
EmbeddedCliConfig *config = embeddedCliDefaultConfig();
|
||||
config->cliBuffer = cli_buffer;
|
||||
config->cliBufferSize = CLI_BUFFER_SIZE;
|
||||
config->rxBufferSize = CLI_RX_BUFFER_SIZE;
|
||||
config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
|
||||
config->historyBufferSize = CLI_HISTORY_SIZE;
|
||||
config->maxBindingCount = CLI_BINDING_COUNT;
|
||||
|
||||
TU_ASSERT(embeddedCliRequiredSize(config) <= CLI_BUFFER_SIZE);
|
||||
|
||||
_cli = embeddedCliNew(config);
|
||||
TU_ASSERT(_cli != NULL);
|
||||
|
||||
_cli->writeChar = cli_write_char;
|
||||
|
||||
embeddedCliAddBinding(_cli,
|
||||
(CliCommandBinding){"cat", "Usage: cat [FILE]...\r\n\tConcatenate FILE(s) to standard output..",
|
||||
true, NULL, cli_cmd_cat});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"cd", "Usage: cd [DIR]...\r\n\tChange the current directory to DIR.",
|
||||
true, NULL, cli_cmd_cd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"cp", "Usage: cp SOURCE DEST\r\n\tCopy SOURCE to DEST.", true, NULL,
|
||||
cli_cmd_cp});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"dd", "Usage: dd [COUNT]\r\n\t" "Read COUNT sectors (default 1024) and report speed.", true, NULL,
|
||||
cli_cmd_dd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"ls",
|
||||
"Usage: ls [DIR]...\r\n\tList information about the FILEs (the "
|
||||
"current directory by default).",
|
||||
true, NULL, cli_cmd_ls});
|
||||
|
||||
embeddedCliAddBinding(_cli,
|
||||
(CliCommandBinding){"pwd", "Usage: pwd\r\n\tPrint the name of the current working directory.",
|
||||
true, NULL, cli_cmd_pwd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"mkdir",
|
||||
"Usage: mkdir DIR...\r\n\tCreate the DIRECTORY(ies), if they do not "
|
||||
"already exist..",
|
||||
true, NULL, cli_cmd_mkdir});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"mv", "Usage: mv SOURCE DEST...\r\n\tRename SOURCE to DEST.", true,
|
||||
NULL, cli_cmd_mv});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"rm", "Usage: rm [FILE]...\r\n\tRemove (unlink) the FILE(s).", true,
|
||||
NULL, cli_cmd_rm});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint32_t count = 1024; // default sectors to read
|
||||
if (embeddedCliGetTokenCount(args) >= 1) {
|
||||
count = (uint32_t)atoi(embeddedCliGetToken(args, 1));
|
||||
if (count == 0) {
|
||||
count = 1024;
|
||||
}
|
||||
}
|
||||
|
||||
// find first mounted MSC device
|
||||
uint8_t dev_addr = 0;
|
||||
for (uint8_t i = 1; i <= CFG_TUH_DEVICE_MAX; i++) {
|
||||
if (tuh_msc_mounted(i)) {
|
||||
dev_addr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dev_addr == 0) {
|
||||
printf("no MSC device mounted\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t lun = 0;
|
||||
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, lun);
|
||||
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, lun);
|
||||
if (count > block_count) {
|
||||
count = block_count;
|
||||
}
|
||||
|
||||
const uint16_t sectors_per_xfer = (uint16_t)(sizeof(rw_buf) / block_size);
|
||||
const uint32_t xfer_count = (count + sectors_per_xfer - 1) / sectors_per_xfer;
|
||||
|
||||
printf("dd: reading %" PRIu32 " sectors (%" PRIu32 " bytes), %u sectors/xfer ...\r\n",
|
||||
count, count * block_size, sectors_per_xfer);
|
||||
|
||||
const uint32_t start_ms = tusb_time_millis_api();
|
||||
const uint8_t pdrv = dev_addr - 1;
|
||||
|
||||
for (uint32_t i = 0; i < count; i += sectors_per_xfer) {
|
||||
const uint16_t n = (uint16_t)((count - i < sectors_per_xfer) ? (count - i) : sectors_per_xfer);
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_read10(dev_addr, lun, rw_buf, i, n, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
}
|
||||
|
||||
const uint32_t elapsed_ms = tusb_time_millis_api() - start_ms;
|
||||
const uint32_t total_data = count * block_size;
|
||||
// each SCSI transaction has 31-byte CBW + data + 13-byte CSW
|
||||
const uint32_t total_bus = total_data + xfer_count * (31 + 13);
|
||||
|
||||
if (elapsed_ms > 0) {
|
||||
const uint32_t data_kbs = total_data / elapsed_ms; // KB/s (bytes/ms = KB/s)
|
||||
const uint32_t bus_kbs = total_bus / elapsed_ms;
|
||||
printf("dd: %" PRIu32 " bytes in %" PRIu32 " ms = %" PRIu32 " KB/s (bus %" PRIu32 " KB/s)\r\n",
|
||||
total_data, elapsed_ms, data_kbs, bus_kbs);
|
||||
} else {
|
||||
printf("dd: %" PRIu32 " bytes in <1 ms\r\n", total_data);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// need at least 1 argument
|
||||
if (argc == 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < argc; i++) {
|
||||
FIL *fi = &file1;
|
||||
const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
|
||||
|
||||
if (FR_OK != f_open(fi, fpath, FA_READ)) {
|
||||
printf("%s: No such file or directory\r\n", fpath);
|
||||
} else {
|
||||
UINT count = 0;
|
||||
while ((FR_OK == f_read(fi, rw_buf, sizeof(rw_buf), &count)) && (count > 0)) {
|
||||
for (UINT c = 0; c < count; c++) {
|
||||
const uint8_t ch = rw_buf[c];
|
||||
if (isprint(ch) || iscntrl(ch)) {
|
||||
putchar(ch);
|
||||
} else {
|
||||
putchar('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_close(fi);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc != 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = args;
|
||||
|
||||
if (FR_OK != f_chdir(dpath)) {
|
||||
printf("%s: No such file or directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
if (argc != 2) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *src = embeddedCliGetToken(args, 1);
|
||||
const char *dst = embeddedCliGetToken(args, 2);
|
||||
|
||||
FIL *f_src = &file1;
|
||||
FIL *f_dst = &file2;
|
||||
|
||||
if (FR_OK != f_open(f_src, src, FA_READ)) {
|
||||
printf("cannot stat '%s': No such file or directory\r\n", src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FR_OK != f_open(f_dst, dst, FA_WRITE | FA_CREATE_ALWAYS)) {
|
||||
printf("cannot create '%s'\r\n", dst);
|
||||
return;
|
||||
} else {
|
||||
UINT rd_count = 0;
|
||||
while ((FR_OK == f_read(f_src, rw_buf, sizeof(rw_buf), &rd_count)) && (rd_count > 0)) {
|
||||
UINT wr_count = 0;
|
||||
|
||||
if (FR_OK != f_write(f_dst, rw_buf, rd_count, &wr_count)) {
|
||||
printf("cannot write to '%s'\r\n", dst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_close(f_src);
|
||||
f_close(f_dst);
|
||||
}
|
||||
|
||||
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc > 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = ".";
|
||||
if (argc) {
|
||||
dpath = args;
|
||||
}
|
||||
|
||||
DIR dir;
|
||||
if (FR_OK != f_opendir(&dir, dpath)) {
|
||||
printf("cannot access '%s': No such file or directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
|
||||
FILINFO fno;
|
||||
while ((f_readdir(&dir, &fno) == FR_OK) && (fno.fname[0] != 0)) {
|
||||
if (fno.fname[0] != '.') // ignore . and .. entry
|
||||
{
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
// directory
|
||||
printf("/%s\r\n", fno.fname);
|
||||
} else {
|
||||
printf("%-40s", fno.fname);
|
||||
if (fno.fsize < 1024) {
|
||||
printf("%" PRIu32 " B\r\n", fno.fsize);
|
||||
} else {
|
||||
printf("%" PRIu32 " KB\r\n", fno.fsize / 1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
}
|
||||
|
||||
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
if (argc != 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char path[256];
|
||||
if (FR_OK != f_getcwd(path, sizeof(path))) {
|
||||
printf("cannot get current working directory\r\n");
|
||||
}
|
||||
|
||||
puts(path);
|
||||
}
|
||||
|
||||
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc != 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = args;
|
||||
|
||||
if (FR_OK != f_mkdir(dpath)) {
|
||||
printf("%s: cannot create this directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
if (argc != 2) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *src = embeddedCliGetToken(args, 1);
|
||||
const char *dst = embeddedCliGetToken(args, 2);
|
||||
|
||||
if (FR_OK != f_rename(src, dst)) {
|
||||
printf("cannot mv %s to %s\r\n", src, dst);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// need at least 1 argument
|
||||
if (argc == 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < argc; i++) {
|
||||
const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
|
||||
|
||||
if (FR_OK != f_unlink(fpath)) {
|
||||
printf("cannot remove '%s': No such file or directory\r\n", fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2025 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MSC_APP_H
|
||||
#define MSC_APP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool msc_app_init(void);
|
||||
void msc_app_task(void);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 1 // number of supported hubs
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_CDC 0
|
||||
#define CFG_TUH_HID 0 // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||
#define CFG_TUH_VENDOR 0
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
//------------- MSC -------------//
|
||||
#define CFG_TUH_MSC_MAXLUN 4 // typical for most card reader
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(msc_file_explorer_freertos C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c
|
||||
${TOP}/lib/fatfs/source/ff.c
|
||||
${TOP}/lib/fatfs/source/ffsystem.c
|
||||
${TOP}/lib/fatfs/source/ffunicode.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${TOP}/lib/fatfs/source
|
||||
${TOP}/lib/embedded-cli
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example with FreeRTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} freertos)
|
||||
|
||||
# Suppress warnings on fatfs
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
set_source_files_properties(${TOP}/lib/fatfs/source/ff.c PROPERTIES
|
||||
COMPILE_OPTIONS "-Wno-conversion;-Wno-cast-qual"
|
||||
)
|
||||
endif ()
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
RTOS = freertos
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
FATFS_PATH = lib/fatfs/source
|
||||
|
||||
INC += \
|
||||
src \
|
||||
$(TOP)/$(FATFS_PATH) \
|
||||
$(TOP)/lib/embedded-cli \
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE = \
|
||||
src/main.c \
|
||||
src/msc_app.c \
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
# FatFS source
|
||||
SRC_C += \
|
||||
$(FATFS_PATH)/ff.c \
|
||||
$(FATFS_PATH)/ffsystem.c \
|
||||
$(FATFS_PATH)/ffunicode.c \
|
||||
|
||||
# suppress warning caused by fatfs
|
||||
CFLAGS += -Wno-error=cast-qual
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
@@ -0,0 +1,28 @@
|
||||
family:espressif
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
@@ -0,0 +1,4 @@
|
||||
mcu:CH32F20X
|
||||
board:lpcxpresso54114
|
||||
mcu:FT90X
|
||||
board:stm32h7s3nucleo
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# This file is for ESP-IDF only
|
||||
set(FATFS_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../lib/fatfs/source)
|
||||
set(EMBEDDED_CLI_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../lib/embedded-cli)
|
||||
|
||||
idf_component_register(
|
||||
SRCS "main.c" "msc_app.c"
|
||||
${FATFS_DIR}/ff.c
|
||||
${FATFS_DIR}/ffsystem.c
|
||||
${FATFS_DIR}/ffunicode.c
|
||||
INCLUDE_DIRS "." ${FATFS_DIR} ${EMBEDDED_CLI_DIR}
|
||||
REQUIRES boards tinyusb_src)
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format)
|
||||
@@ -0,0 +1,313 @@
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Configurations of FatFs Module
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 80386 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_READONLY 0
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 0
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 0
|
||||
/* This option switches f_mkfs(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FASTSEEK 0
|
||||
/* This option switches fast seek feature. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 0
|
||||
/* This option switches attribute control API functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 0
|
||||
/* This option switches volume label API functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 0
|
||||
/* This option switches f_forward(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 0
|
||||
#define FF_PRINT_LLI 0
|
||||
#define FF_PRINT_FLOAT 0
|
||||
#define FF_STRF_ENCODE 0
|
||||
/* FF_USE_STRFUNC switches string API functions, f_gets(), f_putc(), f_puts() and
|
||||
/ f_printf().
|
||||
/
|
||||
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion.
|
||||
/
|
||||
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
|
||||
/ makes f_printf() support floating point argument. These features want C99 or later.
|
||||
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
|
||||
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||
/ to be read/written via those functions.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP
|
||||
/ 1: Unicode in UTF-16LE
|
||||
/ 2: Unicode in UTF-16BE
|
||||
/ 3: Unicode in UTF-8
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_CODE_PAGE 437
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect code page setting can cause a file open failure.
|
||||
/
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
/ 0 - Include all code pages above and configured by f_setcp()
|
||||
*/
|
||||
|
||||
|
||||
#define FF_USE_LFN 1
|
||||
#define FF_MAX_LFN 255
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#define FF_LFN_UNICODE 0
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||
/
|
||||
/ Also behavior of string I/O functions will be affected by this option.
|
||||
/ When LFN is not enabled, this option has no effect. */
|
||||
|
||||
|
||||
#define FF_LFN_BUF 255
|
||||
#define FF_SFN_BUF 12
|
||||
/* This set of options defines size of file name members in the FILINFO structure
|
||||
/ which is used to read out directory items. These values should be sufficient for
|
||||
/ the file names to read. The maximum possible length of the read file name depends
|
||||
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||
|
||||
|
||||
#define FF_FS_RPATH 2
|
||||
/* This option configures support for relative path feature.
|
||||
/
|
||||
/ 0: Disable relative path and remove related API functions.
|
||||
/ 1: Enable relative path and dot names. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_PATH_DEPTH 10
|
||||
/* This option defines maximum depth of directory in the exFAT volume. It is NOT
|
||||
/ relevant to FAT/FAT32 volume.
|
||||
/ For example, FF_PATH_DEPTH = 3 will able to follow a path "/dir1/dir2/dir3/file"
|
||||
/ but a sub-directory in the dir3 will not able to be followed and set current
|
||||
/ directory.
|
||||
/ The size of filesystem object (FATFS) increases FF_PATH_DEPTH * 24 bytes.
|
||||
/ When FF_FS_EXFAT == 0 or FF_FS_RPATH == 0, this option has no effect.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_VOLUMES 4
|
||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 0
|
||||
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drive. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table is needed as:
|
||||
/
|
||||
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
|
||||
*/
|
||||
|
||||
|
||||
#define FF_MULTI_PARTITION 0
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this feature is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ will be available. */
|
||||
|
||||
|
||||
#define FF_MIN_SS 512
|
||||
#define FF_MAX_SS 512
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk, but a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
|
||||
/ configured for variable sector size mode and disk_ioctl() needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_LBA64 0
|
||||
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
|
||||
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||
|
||||
|
||||
#define FF_MIN_GPT 0x10000000
|
||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and
|
||||
/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 0
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable this feature, also CTRL_TRIM command should be implemented to
|
||||
/ the disk_ioctl(). */
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_TINY 0
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is reduced FF_MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||
|
||||
|
||||
#define FF_FS_EXFAT 0
|
||||
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
|
||||
#define FF_FS_NORTC 1
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2025
|
||||
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
|
||||
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
|
||||
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
|
||||
/ to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_CRTIME 0
|
||||
/* This option enables(1)/disables(0) the timestamp of the file created. When
|
||||
/ set 1, the file created time is available in FILINFO structure. */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 0
|
||||
/* If you need to know the correct free space on the FAT32 volume, set bit 0 of
|
||||
/ this option, and f_getfree() on the first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_FS_LOCK 0
|
||||
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
|
||||
#define FF_FS_REENTRANT 0
|
||||
#define FF_FS_TIMEOUT 1000
|
||||
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk(), are always not re-entrant. Only file/directory access to
|
||||
/ the same volume is under control of this featuer.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
|
||||
/ must be added to the project. Samples are available in ffsystem.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*--- End of configuration options ---*/
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#ifdef ESP_PLATFORM
|
||||
// ESP-IDF need "freertos/" prefix in include path.
|
||||
// CFG_TUSB_OS_INC_PATH should be defined accordingly.
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#else
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "timers.h"
|
||||
#endif
|
||||
|
||||
#include "msc_app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
#ifdef ESP_PLATFORM
|
||||
#define USBH_STACK_SIZE 4096
|
||||
#else
|
||||
// Increase stack size when debug log is enabled.
|
||||
#define USBH_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 4 : 3))
|
||||
#endif
|
||||
|
||||
enum {
|
||||
BLINK_MOUNTED = 1000,
|
||||
};
|
||||
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StaticTimer_t blinky_tmdef;
|
||||
|
||||
StackType_t usb_host_stack[USBH_STACK_SIZE];
|
||||
StaticTask_t usb_host_taskdef;
|
||||
#endif
|
||||
|
||||
TimerHandle_t blinky_tm;
|
||||
|
||||
static void led_blinky_cb(TimerHandle_t xTimer);
|
||||
static void usb_host_task(void* param);
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host MassStorage Explorer FreeRTOS Example\r\n");
|
||||
|
||||
// Create soft timer for blinky and task for TinyUSB host stack.
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
blinky_tm = xTimerCreateStatic(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb, &blinky_tmdef);
|
||||
xTaskCreateStatic(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, usb_host_stack,
|
||||
&usb_host_taskdef);
|
||||
#else
|
||||
blinky_tm = xTimerCreate(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb);
|
||||
xTaskCreate(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
#endif
|
||||
|
||||
xTimerStart(blinky_tm, 0);
|
||||
|
||||
// only start scheduler for non-espressif mcu
|
||||
#ifndef ESP_PLATFORM
|
||||
vTaskStartScheduler();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void app_main(void) {
|
||||
main();
|
||||
}
|
||||
#endif
|
||||
|
||||
// USB Host task
|
||||
// This top-level thread processes all USB events and invokes callbacks.
|
||||
static void usb_host_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
|
||||
if (!tusb_init(BOARD_TUH_RHPORT, &host_init)) {
|
||||
printf("Failed to init USB Host Stack\r\n");
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
|
||||
// FeatherWing MAX3421E uses MAX3421E GPIO0 for VBUS enable.
|
||||
enum { IOPINS1_ADDR = 20u << 3 };
|
||||
tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
|
||||
#endif
|
||||
|
||||
if (!msc_app_init()) {
|
||||
printf("Failed to init MSC app\r\n");
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
// TinyUSB host task.
|
||||
tuh_task();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void tuh_mount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
}
|
||||
|
||||
void tuh_umount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
static void led_blinky_cb(TimerHandle_t xTimer) {
|
||||
(void) xTimer;
|
||||
static bool led_state = false;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include "tusb.h"
|
||||
#include "bsp/board_api.h"
|
||||
#ifdef ESP_PLATFORM
|
||||
// ESP-IDF need "freertos/" prefix in include path.
|
||||
// CFG_TUSB_OS_INC_PATH should be defined accordingly.
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#else
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "timers.h"
|
||||
#endif
|
||||
|
||||
#include "ff.h"
|
||||
#include "diskio.h"
|
||||
|
||||
// lib/embedded-cli
|
||||
#define EMBEDDED_CLI_IMPL
|
||||
#include "embedded_cli.h"
|
||||
|
||||
#include "msc_app.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
//------------- embedded-cli -------------//
|
||||
#define CLI_BUFFER_SIZE 512
|
||||
#define CLI_RX_BUFFER_SIZE 16
|
||||
#define CLI_CMD_BUFFER_SIZE 64
|
||||
#define CLI_HISTORY_SIZE 32
|
||||
#define CLI_BINDING_COUNT 9
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define MSC_APP_STACK_SIZE 4096
|
||||
#else
|
||||
#define MSC_APP_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 3 : 2))
|
||||
#endif
|
||||
|
||||
static EmbeddedCli *_cli;
|
||||
static CLI_UINT cli_buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)];
|
||||
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StackType_t msc_app_stack[MSC_APP_STACK_SIZE];
|
||||
StaticTask_t msc_app_taskdef;
|
||||
#endif
|
||||
|
||||
//------------- Elm Chan FatFS -------------//
|
||||
static CFG_TUH_MEM_SECTION FATFS fatfs[CFG_TUH_DEVICE_MAX]; // for simplicity only support 1 LUN per device
|
||||
static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX];
|
||||
static volatile bool _mount_pending[CFG_TUH_DEVICE_MAX];
|
||||
|
||||
static CFG_TUH_MEM_SECTION FIL file1, file2;
|
||||
|
||||
#ifndef CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE
|
||||
#define CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE 4096
|
||||
#endif
|
||||
static CFG_TUH_MEM_SECTION uint8_t rw_buf[CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE];
|
||||
|
||||
// define the buffer to be place in USB/DMA memory with correct alignment/cache line size
|
||||
CFG_TUH_MEM_SECTION static struct {
|
||||
TUH_EPBUF_TYPE_DEF(scsi_inquiry_resp_t, inquiry);
|
||||
} scsi_resp;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
//
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static bool cli_init(void);
|
||||
static void msc_app_task(void* param);
|
||||
static void process_pending_mount(void);
|
||||
|
||||
bool msc_app_init(void) {
|
||||
for (size_t i = 0; i < CFG_TUH_DEVICE_MAX; i++) {
|
||||
_disk_busy[i] = false;
|
||||
_mount_pending[i] = false;
|
||||
}
|
||||
|
||||
// disable stdout buffered for echoing typing command
|
||||
#ifndef __ICCARM__ // TODO IAR doesn't support stream control ?
|
||||
setbuf(stdout, NULL);
|
||||
#endif
|
||||
|
||||
cli_init();
|
||||
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
TaskHandle_t task_hdl = xTaskCreateStatic(msc_app_task, "msc", MSC_APP_STACK_SIZE, NULL,
|
||||
configMAX_PRIORITIES - 2, msc_app_stack, &msc_app_taskdef);
|
||||
TU_ASSERT(task_hdl != NULL);
|
||||
#else
|
||||
TU_ASSERT(xTaskCreate(msc_app_task, "msc", MSC_APP_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, NULL) == pdPASS);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void msc_app_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
while (1) {
|
||||
process_pending_mount();
|
||||
|
||||
if (!_cli) {
|
||||
vTaskDelay(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
int ch = board_getchar();
|
||||
if (ch > 0) {
|
||||
while (ch > 0) {
|
||||
embeddedCliReceiveChar(_cli, (char) ch);
|
||||
ch = board_getchar();
|
||||
}
|
||||
embeddedCliProcess(_cli);
|
||||
}
|
||||
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void process_pending_mount(void) {
|
||||
for (uint8_t drive_num = 0; drive_num < CFG_TUH_DEVICE_MAX; drive_num++) {
|
||||
if (!_mount_pending[drive_num]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_mount_pending[drive_num] = false;
|
||||
|
||||
const uint8_t dev_addr = drive_num + 1;
|
||||
if (!tuh_msc_mounted(dev_addr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char drive_path[3] = "0:";
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
if (f_mount(&fatfs[drive_num], drive_path, 1) != FR_OK) {
|
||||
printf("mount failed\r\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
f_chdrive(drive_path);
|
||||
FRESULT rc = f_chdir("/");
|
||||
if (rc != FR_OK) {
|
||||
printf("chdir failed: %d\r\n", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
//
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
|
||||
const msc_cbw_t *cbw = cb_data->cbw;
|
||||
const msc_csw_t *csw = cb_data->csw;
|
||||
|
||||
if (csw->status != 0) {
|
||||
printf("Inquiry failed\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Print out Vendor ID, Product ID and Rev
|
||||
printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id,
|
||||
scsi_resp.inquiry.product_rev);
|
||||
|
||||
// Get capacity of device
|
||||
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
|
||||
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
|
||||
|
||||
printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
|
||||
block_count, block_size, block_count / ((1024 * 1024) / block_size));
|
||||
|
||||
// For simplicity: we only mount 1 LUN per device
|
||||
const uint8_t drive_num = dev_addr - 1;
|
||||
_mount_pending[drive_num] = true;
|
||||
|
||||
// print the drive label
|
||||
// char label[34];
|
||||
// if ( FR_OK == f_getlabel(drive_path, label, NULL) )
|
||||
// {
|
||||
// puts(label);
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------- IMPLEMENTATION -------------//
|
||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device (addr = %u) is mounted\r\n", dev_addr);
|
||||
|
||||
const uint8_t lun = 0;
|
||||
tuh_msc_inquiry(dev_addr, lun, &scsi_resp.inquiry, inquiry_complete_cb, 0);
|
||||
}
|
||||
|
||||
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device is unmounted\r\n");
|
||||
|
||||
const uint8_t drive_num = dev_addr - 1;
|
||||
char drive_path[3] = "0:";
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
_mount_pending[drive_num] = false;
|
||||
|
||||
f_unmount(drive_path);
|
||||
|
||||
// if ( phy_disk == f_get_current_drive() )
|
||||
// { // active drive is unplugged --> change to other drive
|
||||
// for(uint8_t i=0; i<CFG_TUH_DEVICE_MAX; i++)
|
||||
// {
|
||||
// if ( disk_is_ready(i) )
|
||||
// {
|
||||
// f_chdrive(i);
|
||||
// cli_init(); // refractor, rename
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// DiskIO
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void wait_for_disk_io(BYTE pdrv) {
|
||||
while (_disk_busy[pdrv]) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool disk_io_complete(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
|
||||
(void)dev_addr;
|
||||
(void)cb_data;
|
||||
_disk_busy[dev_addr - 1] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
uint8_t dev_addr = pdrv + 1;
|
||||
return tuh_msc_mounted(dev_addr) ? 0 : STA_NODISK;
|
||||
}
|
||||
|
||||
DSTATUS disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
(void)pdrv;
|
||||
return 0; // nothing to do
|
||||
}
|
||||
|
||||
DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
BYTE *buff, /* Data buffer to store read data */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to read */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_read10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#if FF_FS_READONLY == 0
|
||||
|
||||
DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
const BYTE *buff, /* Data to be written */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to write */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_write10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||
BYTE cmd, /* Control code */
|
||||
void *buff /* Buffer to send/receive control data */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
// nothing to do since we do blocking
|
||||
return RES_OK;
|
||||
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *)buff) = (DWORD)tuh_msc_get_block_count(dev_addr, lun);
|
||||
return RES_OK;
|
||||
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *)buff) = (WORD)tuh_msc_get_block_size(dev_addr, lun);
|
||||
return RES_OK;
|
||||
|
||||
case GET_BLOCK_SIZE:
|
||||
*((DWORD *)buff) = 1; // erase block size in units of sector size
|
||||
return RES_OK;
|
||||
|
||||
default:
|
||||
return RES_PARERR;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// CLI Commands
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context);
|
||||
|
||||
static void cli_write_char(EmbeddedCli *cli, char c) {
|
||||
(void)cli;
|
||||
putchar((int)c);
|
||||
}
|
||||
|
||||
bool cli_init(void) {
|
||||
EmbeddedCliConfig *config = embeddedCliDefaultConfig();
|
||||
config->cliBuffer = cli_buffer;
|
||||
config->cliBufferSize = CLI_BUFFER_SIZE;
|
||||
config->rxBufferSize = CLI_RX_BUFFER_SIZE;
|
||||
config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
|
||||
config->historyBufferSize = CLI_HISTORY_SIZE;
|
||||
config->maxBindingCount = CLI_BINDING_COUNT;
|
||||
|
||||
TU_ASSERT(embeddedCliRequiredSize(config) <= CLI_BUFFER_SIZE);
|
||||
|
||||
_cli = embeddedCliNew(config);
|
||||
TU_ASSERT(_cli != NULL);
|
||||
|
||||
_cli->writeChar = cli_write_char;
|
||||
|
||||
embeddedCliAddBinding(_cli,
|
||||
(CliCommandBinding){"cat", "Usage: cat [FILE]...\r\n\tConcatenate FILE(s) to standard output..",
|
||||
true, NULL, cli_cmd_cat});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"cd", "Usage: cd [DIR]...\r\n\tChange the current directory to DIR.",
|
||||
true, NULL, cli_cmd_cd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"cp", "Usage: cp SOURCE DEST\r\n\tCopy SOURCE to DEST.", true, NULL,
|
||||
cli_cmd_cp});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"dd", "Usage: dd [COUNT]\r\n\t" "Read COUNT sectors (default 1024) and report speed.", true, NULL,
|
||||
cli_cmd_dd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"ls",
|
||||
"Usage: ls [DIR]...\r\n\tList information about the FILEs (the "
|
||||
"current directory by default).",
|
||||
true, NULL, cli_cmd_ls});
|
||||
|
||||
embeddedCliAddBinding(_cli,
|
||||
(CliCommandBinding){"pwd", "Usage: pwd\r\n\tPrint the name of the current working directory.",
|
||||
true, NULL, cli_cmd_pwd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"mkdir",
|
||||
"Usage: mkdir DIR...\r\n\tCreate the DIRECTORY(ies), if they do not "
|
||||
"already exist..",
|
||||
true, NULL, cli_cmd_mkdir});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"mv", "Usage: mv SOURCE DEST...\r\n\tRename SOURCE to DEST.", true,
|
||||
NULL, cli_cmd_mv});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"rm", "Usage: rm [FILE]...\r\n\tRemove (unlink) the FILE(s).", true,
|
||||
NULL, cli_cmd_rm});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint32_t count = 1024; // default sectors to read
|
||||
if (embeddedCliGetTokenCount(args) >= 1) {
|
||||
count = (uint32_t)atoi(embeddedCliGetToken(args, 1));
|
||||
if (count == 0) {
|
||||
count = 1024;
|
||||
}
|
||||
}
|
||||
|
||||
// find first mounted MSC device
|
||||
uint8_t dev_addr = 0;
|
||||
for (uint8_t i = 1; i <= CFG_TUH_DEVICE_MAX; i++) {
|
||||
if (tuh_msc_mounted(i)) {
|
||||
dev_addr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dev_addr == 0) {
|
||||
printf("no MSC device mounted\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t lun = 0;
|
||||
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, lun);
|
||||
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, lun);
|
||||
if (count > block_count) {
|
||||
count = block_count;
|
||||
}
|
||||
|
||||
const uint16_t sectors_per_xfer = (uint16_t)(sizeof(rw_buf) / block_size);
|
||||
const uint32_t xfer_count = (count + sectors_per_xfer - 1) / sectors_per_xfer;
|
||||
|
||||
printf("dd: reading %" PRIu32 " sectors (%" PRIu32 " bytes), %u sectors/xfer ...\r\n",
|
||||
count, count * block_size, sectors_per_xfer);
|
||||
|
||||
const uint32_t start_ms = tusb_time_millis_api();
|
||||
const uint8_t pdrv = dev_addr - 1;
|
||||
bool submit_failed = false;
|
||||
|
||||
for (uint32_t i = 0; i < count; i += sectors_per_xfer) {
|
||||
const uint16_t n = (uint16_t)((count - i < sectors_per_xfer) ? (count - i) : sectors_per_xfer);
|
||||
_disk_busy[pdrv] = true;
|
||||
|
||||
if (!tuh_msc_read10(dev_addr, lun, rw_buf, i, n, disk_io_complete, 0)) {
|
||||
_disk_busy[pdrv] = false;
|
||||
printf("dd: failed to submit read at sector %" PRIu32 " (%u sectors)\r\n", i, n);
|
||||
submit_failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
wait_for_disk_io(pdrv);
|
||||
}
|
||||
|
||||
if (submit_failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t elapsed_ms = tusb_time_millis_api() - start_ms;
|
||||
const uint32_t total_data = count * block_size;
|
||||
// each SCSI transaction has 31-byte CBW + data + 13-byte CSW
|
||||
const uint32_t total_bus = total_data + xfer_count * (31 + 13);
|
||||
|
||||
if (elapsed_ms > 0) {
|
||||
const uint32_t data_kbs = total_data / elapsed_ms; // KB/s (bytes/ms = KB/s)
|
||||
const uint32_t bus_kbs = total_bus / elapsed_ms;
|
||||
printf("dd: %" PRIu32 " bytes in %" PRIu32 " ms = %" PRIu32 " KB/s (bus %" PRIu32 " KB/s)\r\n",
|
||||
total_data, elapsed_ms, data_kbs, bus_kbs);
|
||||
} else {
|
||||
printf("dd: %" PRIu32 " bytes in <1 ms\r\n", total_data);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// need at least 1 argument
|
||||
if (argc == 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < argc; i++) {
|
||||
FIL *fi = &file1;
|
||||
const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
|
||||
|
||||
if (FR_OK != f_open(fi, fpath, FA_READ)) {
|
||||
printf("%s: No such file or directory\r\n", fpath);
|
||||
} else {
|
||||
UINT count = 0;
|
||||
while ((FR_OK == f_read(fi, rw_buf, sizeof(rw_buf), &count)) && (count > 0)) {
|
||||
for (UINT c = 0; c < count; c++) {
|
||||
const uint8_t ch = rw_buf[c];
|
||||
if (isprint(ch) || iscntrl(ch)) {
|
||||
putchar(ch);
|
||||
} else {
|
||||
putchar('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_close(fi);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc != 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = args;
|
||||
|
||||
if (FR_OK != f_chdir(dpath)) {
|
||||
printf("%s: No such file or directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
if (argc != 2) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *src = embeddedCliGetToken(args, 1);
|
||||
const char *dst = embeddedCliGetToken(args, 2);
|
||||
|
||||
FIL *f_src = &file1;
|
||||
FIL *f_dst = &file2;
|
||||
|
||||
if (FR_OK != f_open(f_src, src, FA_READ)) {
|
||||
printf("cannot stat '%s': No such file or directory\r\n", src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FR_OK != f_open(f_dst, dst, FA_WRITE | FA_CREATE_ALWAYS)) {
|
||||
printf("cannot create '%s'\r\n", dst);
|
||||
f_close(f_src);
|
||||
return;
|
||||
} else {
|
||||
UINT rd_count = 0;
|
||||
while ((FR_OK == f_read(f_src, rw_buf, sizeof(rw_buf), &rd_count)) && (rd_count > 0)) {
|
||||
UINT wr_count = 0;
|
||||
|
||||
if (FR_OK != f_write(f_dst, rw_buf, rd_count, &wr_count)) {
|
||||
printf("cannot write to '%s'\r\n", dst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_close(f_src);
|
||||
f_close(f_dst);
|
||||
}
|
||||
|
||||
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc > 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = ".";
|
||||
if (argc) {
|
||||
dpath = args;
|
||||
}
|
||||
|
||||
DIR dir;
|
||||
if (FR_OK != f_opendir(&dir, dpath)) {
|
||||
printf("cannot access '%s': No such file or directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
|
||||
FILINFO fno;
|
||||
while ((f_readdir(&dir, &fno) == FR_OK) && (fno.fname[0] != 0)) {
|
||||
if (fno.fname[0] != '.') // ignore . and .. entry
|
||||
{
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
// directory
|
||||
printf("/%s\r\n", fno.fname);
|
||||
} else {
|
||||
printf("%-40s", fno.fname);
|
||||
if (fno.fsize < 1024) {
|
||||
printf("%" PRIu32 " B\r\n", fno.fsize);
|
||||
} else {
|
||||
printf("%" PRIu32 " KB\r\n", fno.fsize / 1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
}
|
||||
|
||||
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
if (argc != 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char path[256];
|
||||
if (FR_OK != f_getcwd(path, sizeof(path))) {
|
||||
printf("cannot get current working directory\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
puts(path);
|
||||
}
|
||||
|
||||
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc != 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = args;
|
||||
|
||||
if (FR_OK != f_mkdir(dpath)) {
|
||||
printf("%s: cannot create this directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
if (argc != 2) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *src = embeddedCliGetToken(args, 1);
|
||||
const char *dst = embeddedCliGetToken(args, 2);
|
||||
|
||||
if (FR_OK != f_rename(src, dst)) {
|
||||
printf("cannot mv %s to %s\r\n", src, dst);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// need at least 1 argument
|
||||
if (argc == 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < argc; i++) {
|
||||
const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
|
||||
|
||||
if (FR_OK != f_unlink(fpath)) {
|
||||
printf("cannot remove '%s': No such file or directory\r\n", fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2025 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MSC_APP_H
|
||||
#define MSC_APP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool msc_app_init(void);
|
||||
|
||||
#endif
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_FREERTOS
|
||||
#endif
|
||||
|
||||
// Espressif IDF requires "freertos/" prefix in include path
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CFG_TUSB_OS_INC_PATH freertos/
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
/* 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.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 1 // number of supported hubs
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_CDC 0
|
||||
#define CFG_TUH_HID 0 // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||
#define CFG_TUH_VENDOR 0
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
//------------- MSC -------------//
|
||||
#define CFG_TUH_MSC_MAXLUN 4 // typical for most card reader
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
Reference in new issue
Block a user