From 4c19b7d1607e8e0ba3e065b5cda1c927436a0461 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:14:16 +0100 Subject: [PATCH 1/4] Added dev_cdc example It demonstrates how to use TinyUSB with a CDC interface while using the Pico SDK stdio USB. Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- usb/device/CMakeLists.txt | 1 + usb/device/dev_cdc/CMakeLists.txt | 23 ++++ usb/device/dev_cdc/main.c | 74 ++++++++++ usb/device/dev_cdc/tusb_config.h | 42 ++++++ usb/device/dev_cdc/usb_descriptors.c | 194 +++++++++++++++++++++++++++ 5 files changed, 334 insertions(+) create mode 100644 usb/device/dev_cdc/CMakeLists.txt create mode 100644 usb/device/dev_cdc/main.c create mode 100644 usb/device/dev_cdc/tusb_config.h create mode 100644 usb/device/dev_cdc/usb_descriptors.c diff --git a/usb/device/CMakeLists.txt b/usb/device/CMakeLists.txt index 125773e54..78cc8c648 100644 --- a/usb/device/CMakeLists.txt +++ b/usb/device/CMakeLists.txt @@ -6,5 +6,6 @@ set(TINYUSB_LWIP_PATH ${PICO_LWIP_PATH}) # Some examples use this, and we need to set this here due to a bug in the TinyUSB CMake config set(TOP ${PICO_TINYUSB_PATH}) add_subdirectory(${PICO_TINYUSB_PATH}/examples/device tinyusb_device_examples) +add_subdirectory_exclude_platforms(dev_cdc) add_subdirectory_exclude_platforms(dev_hid_composite) add_subdirectory_exclude_platforms(dev_lowlevel) diff --git a/usb/device/dev_cdc/CMakeLists.txt b/usb/device/dev_cdc/CMakeLists.txt new file mode 100644 index 000000000..7dd2c6327 --- /dev/null +++ b/usb/device/dev_cdc/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.13) + +add_executable(dev_cdc) + +target_sources(dev_cdc PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/main.c + ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c + ) + +# Make sure TinyUSB can find tusb_config.h +target_include_directories(dev_cdc PUBLIC + ${CMAKE_CURRENT_LIST_DIR}) + +# In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_device +# for TinyUSB device support and tinyusb_board for the additional board support library used by the example +target_link_libraries(dev_cdc PUBLIC pico_stdlib pico_unique_id tinyusb_device tinyusb_board) + +pico_enable_stdio_usb(dev_cdc 1) +pico_enable_stdio_usb(dev_cdc 2) +pico_add_extra_outputs(dev_cdc) + +# add url via pico_set_program_url +example_auto_set_url(dev_cdc) diff --git a/usb/device/dev_cdc/main.c b/usb/device/dev_cdc/main.c new file mode 100644 index 000000000..d2c80d949 --- /dev/null +++ b/usb/device/dev_cdc/main.c @@ -0,0 +1,74 @@ +#include +#include +#include + +#include + +void custom_cdc_task(void); + +int main(void) +{ + // Initialize TinyUSB stack + board_init(); + tusb_init(); + + // TinyUSB board init callback after init + if (board_init_after_tusb) { + board_init_after_tusb(); + } + + // let pico sdk use the first cdc interface for std io + stdio_init_all(); + + // main run loop + while (1) { + // TinyUSB device task | must be called regurlarly + tud_task(); + + // custom tasks + custom_cdc_task(); + } + + // indicate no error + return 0; +} + +void custom_cdc_task(void) +{ + // polling CDC interfaces if wanted + + // Check if CDC interface 0 (for pico sdk stdio) is connected and ready + + if (tud_cdc_n_connected(0)) { + // print on CDC 0 some debug message + printf("Connected to CDC 0\n"); + sleep_ms(5000); // wait for 5 seconds + } +} + +// 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); + } +} diff --git a/usb/device/dev_cdc/tusb_config.h b/usb/device/dev_cdc/tusb_config.h new file mode 100644 index 000000000..92132c9b9 --- /dev/null +++ b/usb/device/dev_cdc/tusb_config.h @@ -0,0 +1,42 @@ +// TODO: why not #pragma once? + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#define CFG_TUSB_MCU (OPT_MCU_RP2040) +#define CFG_TUSB_OS (OPT_OS_PICO) +#define CFG_TUSB_DEBUG (0) + +#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_ */ diff --git a/usb/device/dev_cdc/usb_descriptors.c b/usb/device/dev_cdc/usb_descriptors.c new file mode 100644 index 000000000..342f82846 --- /dev/null +++ b/usb/device/dev_cdc/usb_descriptors.c @@ -0,0 +1,194 @@ +#include +#include + +// 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 (0x4000 | _PID_MAP(CDC, 0)) +// set USB 2.0 +#define CDC_EXAMPLE_BCD 0x0200 + +// 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 = 0x01, // Index of manufacturer string + .iProduct = 0x02, // Index of product string + .iSerialNumber = 0x03, // 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_TOTAL +}; + +// total length of configuration descriptor +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_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, 4, 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, 4, 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), +}; + +// 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); + +// 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 +}; + +// 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) + "CDC Interface 0" // 4: CDC Interface 0 + "CDC Interface 1", // 5: CDC Interface 1 +}; + +// 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; +} From da554f49b9bbfc6f221eef094980552b64faf69e Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:34:48 +0100 Subject: [PATCH 2/4] Added dev_cdc to README + minor changes to the example Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- README.md | 10 ++++++++++ usb/device/dev_cdc/CMakeLists.txt | 1 - usb/device/dev_cdc/usb_descriptors.c | 5 +++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6809a1fc..7f7f150fb 100644 --- a/README.md +++ b/README.md @@ -440,6 +440,16 @@ At the time of writing, there is only one dual example available: - tinyusb_dual_host_hid_to_device_cdc +### Custom CDC with SDK stdio + +This example demonstrates how to use the TinyUSB CDC device library to create two USB serial ports, and assign one of them to the SDK for stdio. + +App|Description +---|--- +[dev_cdc](usb/device/dev_cdc) | A USB CDC device example with two serial ports, one of which is used for stdio + +The example exposes two serial ports over USB to the host. The first port is used for stdio, and the second port is used for a simple echo loopback. You can connect to the second port and send some characters, and they will be echoed back on the first port while you will receive a "OK\r\n" message on the second port indicating that the data was received. + ### Watchdog App|Description diff --git a/usb/device/dev_cdc/CMakeLists.txt b/usb/device/dev_cdc/CMakeLists.txt index 7dd2c6327..0ec4adda3 100644 --- a/usb/device/dev_cdc/CMakeLists.txt +++ b/usb/device/dev_cdc/CMakeLists.txt @@ -16,7 +16,6 @@ target_include_directories(dev_cdc PUBLIC target_link_libraries(dev_cdc PUBLIC pico_stdlib pico_unique_id tinyusb_device tinyusb_board) pico_enable_stdio_usb(dev_cdc 1) -pico_enable_stdio_usb(dev_cdc 2) pico_add_extra_outputs(dev_cdc) # add url via pico_set_program_url diff --git a/usb/device/dev_cdc/usb_descriptors.c b/usb/device/dev_cdc/usb_descriptors.c index 342f82846..3744714dd 100644 --- a/usb/device/dev_cdc/usb_descriptors.c +++ b/usb/device/dev_cdc/usb_descriptors.c @@ -111,8 +111,9 @@ char const *string_desc_arr[] = { "Raspberry Pi", // 1: Manufacturer "Pico (2)", // 2: Product NULL, // 3: Serials (null so it uses unique ID if available) - "CDC Interface 0" // 4: CDC Interface 0 - "CDC Interface 1", // 5: CDC Interface 1 + "Pico SDK stdio" // 4: CDC Interface 0 + "Custom CDC", // 5: CDC Interface 1, + "RPiReset" // 6: Reset Interface }; // buffer to hold the string descriptor during the request | plus 1 for the null terminator From bf4a25a6434494b104edbe1e5d4622349392d8c1 Mon Sep 17 00:00:00 2001 From: Graham Sanderson Date: Sun, 24 Nov 2024 11:43:14 -0600 Subject: [PATCH 3/4] move new example into USB device section of README --- README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7f7f150fb..eb1e3b97b 100644 --- a/README.md +++ b/README.md @@ -418,6 +418,14 @@ App|Description ---|--- [dev_lowlevel](usb/device/dev_lowlevel) | A USB Bulk loopback implemented with direct access to the USB hardware (no TinyUSB) +#### Custom CDC with SDK stdio + +This example demonstrates how to use the TinyUSB CDC device library to create two USB serial ports, and assign one of them to the SDK for stdio. + +App|Description +---|--- +[dev_cdc](usb/device/dev_cdc) | A USB CDC device example with two serial ports, one of which is used for stdio. The example exposes two serial ports over USB to the host. The first port is used for stdio, and the second port is used for a simple echo loopback. You can connect to the second port and send some characters, and they will be echoed back on the first port while you will receive a "OK\r\n" message on the second port indicating that the data was received. + ### USB Host All the USB host examples come directly from the TinyUSB host examples directory [here](https://github.com/hathach/tinyusb/tree/master/examples/host). @@ -440,16 +448,6 @@ At the time of writing, there is only one dual example available: - tinyusb_dual_host_hid_to_device_cdc -### Custom CDC with SDK stdio - -This example demonstrates how to use the TinyUSB CDC device library to create two USB serial ports, and assign one of them to the SDK for stdio. - -App|Description ----|--- -[dev_cdc](usb/device/dev_cdc) | A USB CDC device example with two serial ports, one of which is used for stdio - -The example exposes two serial ports over USB to the host. The first port is used for stdio, and the second port is used for a simple echo loopback. You can connect to the second port and send some characters, and they will be echoed back on the first port while you will receive a "OK\r\n" message on the second port indicating that the data was received. - ### Watchdog App|Description From 8627b91938c975c47079fce80de998da1a32df6d Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 27 Nov 2024 01:49:11 +0100 Subject: [PATCH 4/4] Add license header to dev_cdc example Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- usb/device/dev_cdc/main.c | 6 ++++++ usb/device/dev_cdc/tusb_config.h | 7 ++++++- usb/device/dev_cdc/usb_descriptors.c | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/usb/device/dev_cdc/main.c b/usb/device/dev_cdc/main.c index d2c80d949..71236b024 100644 --- a/usb/device/dev_cdc/main.c +++ b/usb/device/dev_cdc/main.c @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + #include #include #include diff --git a/usb/device/dev_cdc/tusb_config.h b/usb/device/dev_cdc/tusb_config.h index 92132c9b9..cc985a6b1 100644 --- a/usb/device/dev_cdc/tusb_config.h +++ b/usb/device/dev_cdc/tusb_config.h @@ -1,5 +1,10 @@ -// TODO: why not #pragma once? +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +// TODO: why not #pragma once? #ifndef _TUSB_CONFIG_H_ #define _TUSB_CONFIG_H_ diff --git a/usb/device/dev_cdc/usb_descriptors.c b/usb/device/dev_cdc/usb_descriptors.c index 3744714dd..5240bf93e 100644 --- a/usb/device/dev_cdc/usb_descriptors.c +++ b/usb/device/dev_cdc/usb_descriptors.c @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + #include #include