From 1ea377684b76307a4fa4083289fb8d81371d38f5 Mon Sep 17 00:00:00 2001 From: Ulysse Cura Date: Tue, 13 Oct 2026 23:34:57 +0200 Subject: [PATCH] Starting to have functionnal host and device using tu. --- .vscode/c_cpp_properties.json | 18 +- .vscode/tasks.json | 3 +- CMakeLists.txt | 39 +++-- src/hid_app.c | 298 -------------------------------- src/main_device.c | 31 ---- src/main_host.c | 111 ------------ src/msc_app.c | 73 -------- src_device/main.c | 77 +++++++++ src_device/tusb_config.h | 42 +++++ src_device/usb_descriptor.c | 211 ++++++++++++++++++++++ {src => src_host}/cdc_app.c | 66 +++---- src_host/main.c | 86 +++++++++ {src => src_host}/tusb_config.h | 60 +++---- 13 files changed, 506 insertions(+), 609 deletions(-) delete mode 100644 src/hid_app.c delete mode 100644 src/main_device.c delete mode 100644 src/main_host.c delete mode 100644 src/msc_app.c create mode 100644 src_device/main.c create mode 100644 src_device/tusb_config.h create mode 100644 src_device/usb_descriptor.c rename {src => src_host}/cdc_app.c (76%) create mode 100644 src_host/main.c rename {src => src_host}/tusb_config.h (63%) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index cdb4187..002f28a 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,27 +3,13 @@ { "name": "Linux", "intelliSenseMode": "linux-gcc-arm", - "includePath": [ - "${env:PICO_SDK_PATH}/src/**/include/", - "${env:PICO_SDK_PATH}/lib/**/include/", - "${env:PICO_SDK_PATH}/lib/**/src/", - "${env:PICO_SDK_PATH}/lib/tinyusb/hw", - "${workspaceFolder}/build/generated/pico_base/", - "${workspaceFolder}/src/", - "${workspaceFolder}/lib/**/" - ], - "compilerPath": "/usr/bin/arm-none-eabi-gcc", - "cStandard": "c17", "browse": { "path": [ "${workspaceFolder}" ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" + "limitSymbolsToIncludedHeaders": true }, - "defines": [ - "CFG_TUSB_MCU=OPT_MCU_RP2040" - ] + "compileCommands": "build/compile_commands.json" } ], "version": 4 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6fffed5..e2e0372 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,7 +8,8 @@ "command": "cmake", "args": [ "-H.", - "-Bbuild" + "-Bbuild", + "-DCMAKE_EXPORT_COMPILE_COMMANDS=1" ], "group": "build", "presentation": { diff --git a/CMakeLists.txt b/CMakeLists.txt index e41bfd2..08340ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 3.13) # Explicitely define board set(PICO_BOARD pico) -if(NOT DEFINED PICO_BOARD) - add_definitions(-DPICO_BOARD=${PICO_BOARD}) -endif() # Include pico-sdk cmake support include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) @@ -19,21 +16,37 @@ pico_sdk_init() add_executable(Pico_Host) add_executable(Pico_Device) +# Sources target_sources(Pico_Host PUBLIC - src/main_host.c - src/cdc_app.c - src/hid_app.c - src/msc_app.c + src_host/main.c + src_host/cdc_app.c ) target_sources(Pico_Device PUBLIC - src/main_device.c + src_device/main.c + src_device/usb_descriptor.c ) +# Flags +# Enable the reset interface +target_compile_definitions(Pico_Device PUBLIC + PICO_ENABLE_USB_RESET_VIA_VENDOR_INTERFACE=1 + # use SDK MS_OS_20 descriptor, as this doesn't have a custom one + PICO_USB_RESET_SUPPORT_MS_OS_20_DESCRIPTOR=1 + # pass the interface number to the SDK MS_OS_20 descriptor + PICO_USB_RESET_MS_OS_20_DESCRIPTOR_ITF=4 +) + +# Include paths target_include_directories(Pico_Host PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/src/ + ${CMAKE_CURRENT_LIST_DIR}/src_host/ ) +target_include_directories(Pico_Device PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/src_device/ +) + +# Libs target_link_libraries(Pico_Host pico_stdlib tinyusb_board @@ -42,13 +55,17 @@ target_link_libraries(Pico_Host target_link_libraries(Pico_Device pico_stdlib + tinyusb_board + tinyusb_device ) -# Output +pico_enable_stdio_usb(Pico_Device 1) + +# Outputs pico_add_extra_outputs(Pico_Host) pico_add_extra_outputs(Pico_Device) -# Flash target +# Flash targets add_custom_target(Flash_Host DEPENDS Pico_Host COMMAND picotool load -f ${PROJECT_BINARY_DIR}/Pico_Host.uf2 diff --git a/src/hid_app.c b/src/hid_app.c deleted file mode 100644 index 6465d09..0000000 --- a/src/hid_app.c +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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" - -//--------------------------------------------------------------------+ -// 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_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 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 -//--------------------------------------------------------------------+ - -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; - - 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; iusage_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; - } - } -} \ No newline at end of file diff --git a/src/main_device.c b/src/main_device.c deleted file mode 100644 index 16a315e..0000000 --- a/src/main_device.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -int main(void) -{ - stdio_init_all(); - - gpio_init(PICO_DEFAULT_LED_PIN); - gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); - - while(true) - { - static uint64_t last_time = 0; - uint64_t time = time_us_64(); - if(time - last_time > 1000 * 1000) - { - last_time = time; - - static bool led_state = true; - led_state = !led_state; - - gpio_put(PICO_DEFAULT_LED_PIN, led_state); - - printf("led_state : %d\n", led_state); - } - - tight_loop_contents(); - } - - return PICO_OK; -} \ No newline at end of file diff --git a/src/main_host.c b/src/main_host.c deleted file mode 100644 index 8d27aad..0000000 --- a/src/main_host.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include "tusb_config.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF PROTYPES -//--------------------------------------------------------------------+ -void led_blinking_task(void); -extern void cdc_app_task(void); -extern void hid_app_task(void); - -#if CFG_TUH_ENABLED && CFG_TUH_MAX3421 -// API to read/rite MAX3421's register. Implemented by TinyUSB -extern uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr); -extern bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr); -#endif - -/*------------- MAIN -------------*/ -int main(void) { - board_init(); - - printf("TinyUSB Host CDC MSC HID Example\r\n"); - - // init host stack on configured roothub port - const tusb_rhport_init_t rh_init = { - .role = TUSB_ROLE_HOST, - .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, - }; - TU_ASSERT(tuh_rhport_init(BOARD_TUH_RHPORT, &rh_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 %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); -} - -#if TUSB_VERSION_NUMBER > 1800 -// board_millis has been removed from tinyusb. Use tusb_time_millis_api instead -#define board_millis tusb_time_millis_api -#endif - -//--------------------------------------------------------------------+ -// 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 (board_millis() - start_ms < interval_ms) return; // not enough time - start_ms += interval_ms; - - board_led_write(led_state); - led_state = 1 - led_state; // toggle -} \ No newline at end of file diff --git a/src/msc_app.c b/src/msc_app.c deleted file mode 100644 index fbbb443..0000000 --- a/src/msc_app.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 "tusb.h" - -#include -#include - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ -static scsi_inquiry_resp_t inquiry_resp; - -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 rev %.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 " MB\r\n", block_count / ((1024*1024)/block_size)); - printf("Block Count = %" PRIu32 ", Block Size: %" PRIu32 "\r\n", block_count, 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"); -} \ No newline at end of file diff --git a/src_device/main.c b/src_device/main.c new file mode 100644 index 0000000..7c98be8 --- /dev/null +++ b/src_device/main.c @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include + +#define LED_BLINK_DELAY_MS 1000 + +int main(void) +{ + // Initialize TinyUSB stack + board_init(); + tusb_init(); + + // let pico sdk use the first cdc interface for std io + stdio_init_all(); + + uint32_t start_ms = 0; + bool led_state = false; + + // main run loop + while(true) + { + // TinyUSB device task | must be called regurlarly + tud_task(); + + if(board_millis() - start_ms > LED_BLINK_DELAY_MS) + { + board_led_write(led_state); + led_state = ! led_state; + start_ms += LED_BLINK_DELAY_MS; + + if(tud_cdc_n_connected(0)) + { + printf("Connected to CDC 0\n"); + } + + if(tud_cdc_n_connected(1)) + { + printf("Connected to CDC 1\n"); + } + } + } +} + +// callback when data is received on a CDC interface +void tud_cdc_rx_cb(uint8_t itf) +{ + // allocate buffer for the data in the stack + uint8_t buf[CFG_TUD_CDC_RX_BUFSIZE]; + + printf("RX CDC %d\n", itf); + + // read the available data + // | IMPORTANT: also do this for CDC0 because otherwise + // | you won't be able to print anymore to CDC0 + // | next time this function is called + uint32_t count = tud_cdc_n_read(itf, buf, sizeof(buf)); + + // check if the data was received on the second cdc interface + if (itf == 1) { + // process the received data + buf[count] = 0; // null-terminate the string + // now echo data back to the console on CDC 0 + printf("Received on CDC 1: %s\n", buf); + + // and echo back OK on CDC 1 + tud_cdc_n_write(itf, (uint8_t const *) "OK\r\n", 4); + tud_cdc_n_write_flush(itf); + } +} \ No newline at end of file diff --git a/src_device/tusb_config.h b/src_device/tusb_config.h new file mode 100644 index 0000000..9f4ea70 --- /dev/null +++ b/src_device/tusb_config.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#define CFG_TUD_ENABLED (1) + +// Legacy RHPORT configuration +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED) +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT (0) +#endif +// end legacy RHPORT + +//------------------------ +// DEVICE CONFIGURATION // +//------------------------ + +// Enable 2 CDC classes +#define CFG_TUD_CDC (2) +// Set CDC FIFO buffer sizes +#define CFG_TUD_CDC_RX_BUFSIZE (64) +#define CFG_TUD_CDC_TX_BUFSIZE (64) +#define CFG_TUD_CDC_EP_BUFSIZE (64) + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE (64) +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ \ No newline at end of file diff --git a/src_device/usb_descriptor.c b/src_device/usb_descriptor.c new file mode 100644 index 0000000..32c947b --- /dev/null +++ b/src_device/usb_descriptor.c @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "pico/usb_reset.h" + +// set some example Vendor and Product ID +// the board will use to identify at the host +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define CDC_EXAMPLE_VID 0xCafe +// use _PID_MAP to generate unique PID for each interface +#define CDC_EXAMPLE_PID (0x4100 | _PID_MAP(CDC, 0)) +// set to 2.10 so the MS_OS_20_DESCRIPTOR works +#define CDC_EXAMPLE_BCD 0x0210 + +// String descriptors referenced with .i... in the descriptor tables + +enum { + STRID_LANGID = 0, // 0: supported language ID + STRID_MANUFACTURER, // 1: Manufacturer + STRID_PRODUCT, // 2: Product + STRID_SERIAL, // 3: Serials + STRID_CDC_0, // 4: CDC Interface 0 + STRID_CDC_1, // 5: CDC Interface 1 + STRID_RPI_RESET, // 6: Reset Interface +}; + +// array of pointer to string descriptors +char const *string_desc_arr[] = { + // switched because board is little endian + (const char[]) { 0x09, 0x04 }, // 0: supported language is English (0x0409) + "Raspberry Pi", // 1: Manufacturer + "Pico (2)", // 2: Product + NULL, // 3: Serials (null so it uses unique ID if available) + "Pico SDK stdio", // 4: CDC Interface 0 + "Custom CDC", // 5: CDC Interface 1, + "Reset" // 6: Reset Interface +}; + +// defines a descriptor that will be communicated to the host +tusb_desc_device_t const desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = CDC_EXAMPLE_BCD, + + .bDeviceClass = TUSB_CLASS_MISC, // CDC is a subclass of misc + .bDeviceSubClass = MISC_SUBCLASS_COMMON, // CDC uses common subclass + .bDeviceProtocol = MISC_PROTOCOL_IAD, // CDC uses IAD + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, // 64 bytes + + .idVendor = CDC_EXAMPLE_VID, + .idProduct = CDC_EXAMPLE_PID, + .bcdDevice = 0x0100, // Device release number + + .iManufacturer = STRID_MANUFACTURER, // Index of manufacturer string + .iProduct = STRID_PRODUCT, // Index of product string + .iSerialNumber = STRID_SERIAL, // Index of serial number string + + .bNumConfigurations = 0x01 // 1 configuration +}; + +// called when host requests to get device descriptor +uint8_t const *tud_descriptor_device_cb(void); + +enum { + ITF_NUM_CDC_0 = 0, + ITF_NUM_CDC_0_DATA, + ITF_NUM_CDC_1, + ITF_NUM_CDC_1_DATA, + ITF_NUM_RPI_RESET, + ITF_NUM_TOTAL +}; + +static_assert(ITF_NUM_RPI_RESET == PICO_USB_RESET_MS_OS_20_DESCRIPTOR_ITF, "ITF_NUM_RPI_RESET must be equal to the PICO_USB_RESET_MS_OS_20_DESCRIPTOR_ITF set in CMakeLists.txt"); + +// total length of configuration descriptor +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN + TUD_RPI_RESET_DESC_LEN) + +// define endpoint numbers +#define EPNUM_CDC_0_NOTIF 0x81 // notification endpoint for CDC 0 +#define EPNUM_CDC_0_OUT 0x02 // out endpoint for CDC 0 +#define EPNUM_CDC_0_IN 0x82 // in endpoint for CDC 0 + +#define EPNUM_CDC_1_NOTIF 0x84 // notification endpoint for CDC 1 +#define EPNUM_CDC_1_OUT 0x05 // out endpoint for CDC 1 +#define EPNUM_CDC_1_IN 0x85 // in endpoint for CDC 1 + +// configure descriptor (for 2 CDC interfaces) +uint8_t const desc_configuration[] = { + // config descriptor | how much power in mA, count of interfaces, ... + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x80, 100), + + // CDC 0: Communication Interface - TODO: get 64 from tusb_config.h + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, STRID_CDC_0, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 64), + // CDC 0: Data Interface + //TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0_DATA, 4, 0x01, 0x02), + + // CDC 1: Communication Interface - TODO: get 64 from tusb_config.h + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1, STRID_CDC_1, EPNUM_CDC_1_NOTIF, 8, EPNUM_CDC_1_OUT, EPNUM_CDC_1_IN, 64), + // CDC 1: Data Interface + //TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1_DATA, 4, 0x03, 0x04), + + // RPi Reset Interface + TUD_RPI_RESET_DESCRIPTOR(ITF_NUM_RPI_RESET, STRID_RPI_RESET), +}; + +// called when host requests to get configuration descriptor +uint8_t const * tud_descriptor_configuration_cb(uint8_t index); + +// more device descriptor this time the qualifier +tusb_desc_device_qualifier_t const desc_device_qualifier = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = CDC_EXAMPLE_BCD, + + .bDeviceClass = TUSB_CLASS_CDC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// called when host requests to get device qualifier descriptor +uint8_t const* tud_descriptor_device_qualifier_cb(void); + +// buffer to hold the string descriptor during the request | plus 1 for the null terminator +static uint16_t _desc_str[32 + 1]; + +// called when host request to get string descriptor +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid); + +// --------------------------------------------------------------------+ +// IMPLEMENTATION +// --------------------------------------------------------------------+ + +uint8_t const *tud_descriptor_device_cb(void) +{ + return (uint8_t const *)&desc_device; +} + +uint8_t const* tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const *)&desc_device_qualifier; +} + +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + // avoid unused parameter warning and keep function signature consistent + (void)index; + + return desc_configuration; +} + +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + // TODO: check lang id + (void) langid; + size_t char_count; + + // Determine which string descriptor to return + switch (index) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[STRID_LANGID], 2); + char_count = 1; + break; + + case STRID_SERIAL: + // try to read the serial from the board + char_count = board_usb_get_serial(_desc_str + 1, 32); + break; + + default: + // COPYRIGHT NOTE: Based on TinyUSB example + // Windows wants utf16le + + // Determine which string descriptor to return + if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) { + return NULL; + } + + // Copy string descriptor into _desc_str + const char *str = string_desc_arr[index]; + + char_count = strlen(str); + size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + // Cap at max char + if (char_count > max_count) { + char_count = max_count; + } + + // Convert ASCII string into UTF-16 + for (size_t i = 0; i < char_count; i++) { + _desc_str[1 + i] = str[i]; + } + break; + } + + // First byte is the length (including header), second byte is string type + _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (char_count * 2 + 2)); + + return _desc_str; +} \ No newline at end of file diff --git a/src/cdc_app.c b/src_host/cdc_app.c similarity index 76% rename from src/cdc_app.c rename to src_host/cdc_app.c index 25fc339..4b26bdb 100644 --- a/src/cdc_app.c +++ b/src_host/cdc_app.c @@ -24,44 +24,48 @@ * This file is part of the TinyUSB stack. */ -#include "tusb.h" -#include "bsp/board_api.h" +#include +#include -size_t get_console_inputs(uint8_t* buf, size_t bufsize) { - size_t count = 0; - while (count < bufsize) { - int ch = board_getchar(); - if (ch <= 0) break; +size_t get_console_inputs(uint8_t* buf, size_t bufsize) +{ + size_t count = 0; - buf[count] = (uint8_t) ch; - count++; - } + while (count < bufsize) { + int ch = board_getchar(); + if (ch <= 0) break; - return count; -} - -void cdc_app_task(void) { - uint8_t buf[64 + 1]; // +1 for extra null character - uint32_t const bufsize = sizeof(buf) - 1; - - uint32_t count = get_console_inputs(buf, bufsize); - buf[count] = 0; - - // loop over all mounted interfaces - for (uint8_t idx = 0; idx < CFG_TUH_CDC; idx++) { - if (tuh_cdc_mounted(idx)) { - // console --> cdc interfaces - if (count) { - tuh_cdc_write(idx, buf, count); - tuh_cdc_write_flush(idx); - } + buf[count] = (uint8_t) ch; + count++; + } + + return count; +} + +void cdc_app_task(void) +{ + uint8_t buf[64 + 1]; // +1 for extra null character + uint32_t const bufsize = sizeof(buf) - 1; + + uint32_t count = get_console_inputs(buf, bufsize); + buf[count] = 0; + + // loop over all mounted interfaces + for(uint8_t idx = 0; idx < CFG_TUH_CDC; idx++) + { + if(tuh_cdc_mounted(idx)) + { + // console --> cdc interfaces + if(count) + { + tuh_cdc_write(idx, buf, count); + tuh_cdc_write_flush(idx); + } + } } - } } -//--------------------------------------------------------------------+ // TinyUSB callbacks -//--------------------------------------------------------------------+ // Invoked when received new data void tuh_cdc_rx_cb(uint8_t idx) { diff --git a/src_host/main.c b/src_host/main.c new file mode 100644 index 0000000..6b42567 --- /dev/null +++ b/src_host/main.c @@ -0,0 +1,86 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#define LED_BLINK_DELAY_MS 1000 + +extern void cdc_app_task(void); + +/*------------- MAIN -------------*/ +int main(void) { + board_init(); + + gpio_set_function(12, UART_FUNCSEL_NUM(uart0, 12)); + gpio_set_function(13, UART_FUNCSEL_NUM(uart0, 13)); + + printf("TinyUSB Host CDC Example\n"); + + // init host stack on configured roothub port + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_HOST, + .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + + if(!tuh_rhport_init(BOARD_TUH_RHPORT, &rh_init)) + { + printf("Failed to init rhport.\n"); + return PICO_ERROR_GENERIC; + } + + uint32_t start_ms = 0; + bool led_state = false; + + while(true) + { + // tinyusb host task + tuh_task(); + + if(board_millis() - start_ms > LED_BLINK_DELAY_MS) + { + board_led_write(led_state); + led_state = ! led_state; + start_ms += LED_BLINK_DELAY_MS; + } + + cdc_app_task(); + } +} + +// 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); +} diff --git a/src/tusb_config.h b/src_host/tusb_config.h similarity index 63% rename from src/tusb_config.h rename to src_host/tusb_config.h index de3653d..afd47e3 100644 --- a/src/tusb_config.h +++ b/src_host/tusb_config.h @@ -27,7 +27,7 @@ #define _TUSB_CONFIG_H_ #ifdef __cplusplus - extern "C" { +extern "C" { #endif //-------------------------------------------------------------------- @@ -40,11 +40,11 @@ #endif #ifndef CFG_TUSB_OS -#define CFG_TUSB_OS OPT_OS_NONE +#define CFG_TUSB_OS OPT_OS_NONE #endif #ifndef CFG_TUSB_DEBUG -#define CFG_TUSB_DEBUG 0 +#define CFG_TUSB_DEBUG 0 #endif /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. @@ -62,63 +62,49 @@ #define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) #endif -//-------------------------------------------------------------------- -// Host Configuration -//-------------------------------------------------------------------- - -// Enable Host stack -#define CFG_TUH_ENABLED 1 +// 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 + // #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 + // 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 -------------------------- +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED // RHPort number used for host can be defined by board.mk, default to port 0 #ifndef BOARD_TUH_RHPORT -#define BOARD_TUH_RHPORT 0 +#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 +#define BOARD_TUH_MAX_SPEED OPT_MODE_FULL_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 // 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_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 +#define CFG_TUH_HUB 1 // number of supported hubs +#define CFG_TUH_CDC 1 // 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_HID 0 +#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_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 0x03 @@ -129,7 +115,7 @@ #ifdef __cplusplus - } +} #endif #endif /* _TUSB_CONFIG_H_ */ \ No newline at end of file