Skip to content

Commit b87dabf

Browse files
authored
Added dev_cdc example (#561)
* 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 <[email protected]> * Added dev_cdc to README + minor changes to the example Signed-off-by: paulober <[email protected]> * move new example into USB device section of README * Add license header to dev_cdc example Signed-off-by: paulober <[email protected]> --------- Signed-off-by: paulober <[email protected]>
1 parent cf34686 commit b87dabf

File tree

6 files changed

+359
-0
lines changed

6 files changed

+359
-0
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,14 @@ App|Description
431431
---|---
432432
[dev_lowlevel](usb/device/dev_lowlevel) | A USB Bulk loopback implemented with direct access to the USB hardware (no TinyUSB)
433433

434+
#### Custom CDC with SDK stdio
435+
436+
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.
437+
438+
App|Description
439+
---|---
440+
[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.
441+
434442
### USB Host
435443

436444
All the USB host examples come directly from the TinyUSB host examples directory [here](https://github.com/hathach/tinyusb/tree/master/examples/host).

usb/device/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ set(TINYUSB_LWIP_PATH ${PICO_LWIP_PATH})
66
# Some examples use this, and we need to set this here due to a bug in the TinyUSB CMake config
77
set(TOP ${PICO_TINYUSB_PATH})
88
add_subdirectory(${PICO_TINYUSB_PATH}/examples/device tinyusb_device_examples)
9+
add_subdirectory_exclude_platforms(dev_cdc)
910
add_subdirectory_exclude_platforms(dev_hid_composite)
1011
add_subdirectory_exclude_platforms(dev_lowlevel)

usb/device/dev_cdc/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
cmake_minimum_required(VERSION 3.13)
2+
3+
add_executable(dev_cdc)
4+
5+
target_sources(dev_cdc PUBLIC
6+
${CMAKE_CURRENT_LIST_DIR}/main.c
7+
${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c
8+
)
9+
10+
# Make sure TinyUSB can find tusb_config.h
11+
target_include_directories(dev_cdc PUBLIC
12+
${CMAKE_CURRENT_LIST_DIR})
13+
14+
# In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_device
15+
# for TinyUSB device support and tinyusb_board for the additional board support library used by the example
16+
target_link_libraries(dev_cdc PUBLIC pico_stdlib pico_unique_id tinyusb_device tinyusb_board)
17+
18+
pico_enable_stdio_usb(dev_cdc 1)
19+
pico_add_extra_outputs(dev_cdc)
20+
21+
# add url via pico_set_program_url
22+
example_auto_set_url(dev_cdc)

usb/device/dev_cdc/main.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <stdlib.h>
8+
#include <bsp/board_api.h>
9+
#include <tusb.h>
10+
11+
#include <pico/stdio.h>
12+
13+
void custom_cdc_task(void);
14+
15+
int main(void)
16+
{
17+
// Initialize TinyUSB stack
18+
board_init();
19+
tusb_init();
20+
21+
// TinyUSB board init callback after init
22+
if (board_init_after_tusb) {
23+
board_init_after_tusb();
24+
}
25+
26+
// let pico sdk use the first cdc interface for std io
27+
stdio_init_all();
28+
29+
// main run loop
30+
while (1) {
31+
// TinyUSB device task | must be called regurlarly
32+
tud_task();
33+
34+
// custom tasks
35+
custom_cdc_task();
36+
}
37+
38+
// indicate no error
39+
return 0;
40+
}
41+
42+
void custom_cdc_task(void)
43+
{
44+
// polling CDC interfaces if wanted
45+
46+
// Check if CDC interface 0 (for pico sdk stdio) is connected and ready
47+
48+
if (tud_cdc_n_connected(0)) {
49+
// print on CDC 0 some debug message
50+
printf("Connected to CDC 0\n");
51+
sleep_ms(5000); // wait for 5 seconds
52+
}
53+
}
54+
55+
// callback when data is received on a CDC interface
56+
void tud_cdc_rx_cb(uint8_t itf)
57+
{
58+
// allocate buffer for the data in the stack
59+
uint8_t buf[CFG_TUD_CDC_RX_BUFSIZE];
60+
61+
printf("RX CDC %d\n", itf);
62+
63+
// read the available data
64+
// | IMPORTANT: also do this for CDC0 because otherwise
65+
// | you won't be able to print anymore to CDC0
66+
// | next time this function is called
67+
uint32_t count = tud_cdc_n_read(itf, buf, sizeof(buf));
68+
69+
// check if the data was received on the second cdc interface
70+
if (itf == 1) {
71+
// process the received data
72+
buf[count] = 0; // null-terminate the string
73+
// now echo data back to the console on CDC 0
74+
printf("Received on CDC 1: %s\n", buf);
75+
76+
// and echo back OK on CDC 1
77+
tud_cdc_n_write(itf, (uint8_t const *) "OK\r\n", 4);
78+
tud_cdc_n_write_flush(itf);
79+
}
80+
}

usb/device/dev_cdc/tusb_config.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
// TODO: why not #pragma once?
8+
#ifndef _TUSB_CONFIG_H_
9+
#define _TUSB_CONFIG_H_
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
#endif
14+
15+
#define CFG_TUSB_MCU (OPT_MCU_RP2040)
16+
#define CFG_TUSB_OS (OPT_OS_PICO)
17+
#define CFG_TUSB_DEBUG (0)
18+
19+
#define CFG_TUD_ENABLED (1)
20+
21+
// Legacy RHPORT configuration
22+
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED)
23+
#ifndef BOARD_TUD_RHPORT
24+
#define BOARD_TUD_RHPORT (0)
25+
#endif
26+
// end legacy RHPORT
27+
28+
//------------------------
29+
// DEVICE CONFIGURATION //
30+
//------------------------
31+
32+
// Enable 2 CDC classes
33+
#define CFG_TUD_CDC (2)
34+
// Set CDC FIFO buffer sizes
35+
#define CFG_TUD_CDC_RX_BUFSIZE (64)
36+
#define CFG_TUD_CDC_TX_BUFSIZE (64)
37+
#define CFG_TUD_CDC_EP_BUFSIZE (64)
38+
39+
#ifndef CFG_TUD_ENDPOINT0_SIZE
40+
#define CFG_TUD_ENDPOINT0_SIZE (64)
41+
#endif
42+
43+
#ifdef __cplusplus
44+
}
45+
#endif
46+
47+
#endif /* _TUSB_CONFIG_H_ */

usb/device/dev_cdc/usb_descriptors.c

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/**
2+
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include <tusb.h>
8+
#include <bsp/board_api.h>
9+
10+
// set some example Vendor and Product ID
11+
// the board will use to identify at the host
12+
#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
13+
#define CDC_EXAMPLE_VID 0xCafe
14+
// use _PID_MAP to generate unique PID for each interface
15+
#define CDC_EXAMPLE_PID (0x4000 | _PID_MAP(CDC, 0))
16+
// set USB 2.0
17+
#define CDC_EXAMPLE_BCD 0x0200
18+
19+
// defines a descriptor that will be communicated to the host
20+
tusb_desc_device_t const desc_device = {
21+
.bLength = sizeof(tusb_desc_device_t),
22+
.bDescriptorType = TUSB_DESC_DEVICE,
23+
.bcdUSB = CDC_EXAMPLE_BCD,
24+
25+
.bDeviceClass = TUSB_CLASS_MISC, // CDC is a subclass of misc
26+
.bDeviceSubClass = MISC_SUBCLASS_COMMON, // CDC uses common subclass
27+
.bDeviceProtocol = MISC_PROTOCOL_IAD, // CDC uses IAD
28+
29+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, // 64 bytes
30+
31+
.idVendor = CDC_EXAMPLE_VID,
32+
.idProduct = CDC_EXAMPLE_PID,
33+
.bcdDevice = 0x0100, // Device release number
34+
35+
.iManufacturer = 0x01, // Index of manufacturer string
36+
.iProduct = 0x02, // Index of product string
37+
.iSerialNumber = 0x03, // Index of serial number string
38+
39+
.bNumConfigurations = 0x01 // 1 configuration
40+
};
41+
42+
// called when host requests to get device descriptor
43+
uint8_t const *tud_descriptor_device_cb(void);
44+
45+
enum {
46+
ITF_NUM_CDC_0 = 0,
47+
ITF_NUM_CDC_0_DATA,
48+
ITF_NUM_CDC_1,
49+
ITF_NUM_CDC_1_DATA,
50+
ITF_NUM_TOTAL
51+
};
52+
53+
// total length of configuration descriptor
54+
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN)
55+
56+
// define endpoint numbers
57+
#define EPNUM_CDC_0_NOTIF 0x81 // notification endpoint for CDC 0
58+
#define EPNUM_CDC_0_OUT 0x02 // out endpoint for CDC 0
59+
#define EPNUM_CDC_0_IN 0x82 // in endpoint for CDC 0
60+
61+
#define EPNUM_CDC_1_NOTIF 0x84 // notification endpoint for CDC 1
62+
#define EPNUM_CDC_1_OUT 0x05 // out endpoint for CDC 1
63+
#define EPNUM_CDC_1_IN 0x85 // in endpoint for CDC 1
64+
65+
// configure descriptor (for 2 CDC interfaces)
66+
uint8_t const desc_configuration[] = {
67+
// config descriptor | how much power in mA, count of interfaces, ...
68+
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x80, 100),
69+
70+
// CDC 0: Communication Interface - TODO: get 64 from tusb_config.h
71+
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, 4, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 64),
72+
// CDC 0: Data Interface
73+
//TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0_DATA, 4, 0x01, 0x02),
74+
75+
// CDC 1: Communication Interface - TODO: get 64 from tusb_config.h
76+
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1, 4, EPNUM_CDC_1_NOTIF, 8, EPNUM_CDC_1_OUT, EPNUM_CDC_1_IN, 64),
77+
// CDC 1: Data Interface
78+
//TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1_DATA, 4, 0x03, 0x04),
79+
};
80+
81+
// called when host requests to get configuration descriptor
82+
uint8_t const * tud_descriptor_configuration_cb(uint8_t index);
83+
84+
// more device descriptor this time the qualifier
85+
tusb_desc_device_qualifier_t const desc_device_qualifier = {
86+
.bLength = sizeof(tusb_desc_device_t),
87+
.bDescriptorType = TUSB_DESC_DEVICE,
88+
.bcdUSB = CDC_EXAMPLE_BCD,
89+
90+
.bDeviceClass = TUSB_CLASS_CDC,
91+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
92+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
93+
94+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
95+
.bNumConfigurations = 0x01,
96+
.bReserved = 0x00
97+
};
98+
99+
// called when host requests to get device qualifier descriptor
100+
uint8_t const* tud_descriptor_device_qualifier_cb(void);
101+
102+
// String descriptors referenced with .i... in the descriptor tables
103+
104+
enum {
105+
STRID_LANGID = 0, // 0: supported language ID
106+
STRID_MANUFACTURER, // 1: Manufacturer
107+
STRID_PRODUCT, // 2: Product
108+
STRID_SERIAL, // 3: Serials
109+
STRID_CDC_0, // 4: CDC Interface 0
110+
STRID_CDC_1, // 5: CDC Interface 1
111+
};
112+
113+
// array of pointer to string descriptors
114+
char const *string_desc_arr[] = {
115+
// switched because board is little endian
116+
(const char[]) { 0x09, 0x04 }, // 0: supported language is English (0x0409)
117+
"Raspberry Pi", // 1: Manufacturer
118+
"Pico (2)", // 2: Product
119+
NULL, // 3: Serials (null so it uses unique ID if available)
120+
"Pico SDK stdio" // 4: CDC Interface 0
121+
"Custom CDC", // 5: CDC Interface 1,
122+
"RPiReset" // 6: Reset Interface
123+
};
124+
125+
// buffer to hold the string descriptor during the request | plus 1 for the null terminator
126+
static uint16_t _desc_str[32 + 1];
127+
128+
// called when host request to get string descriptor
129+
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid);
130+
131+
// --------------------------------------------------------------------+
132+
// IMPLEMENTATION
133+
// --------------------------------------------------------------------+
134+
135+
uint8_t const *tud_descriptor_device_cb(void)
136+
{
137+
return (uint8_t const *)&desc_device;
138+
}
139+
140+
uint8_t const* tud_descriptor_device_qualifier_cb(void)
141+
{
142+
return (uint8_t const *)&desc_device_qualifier;
143+
}
144+
145+
uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
146+
{
147+
// avoid unused parameter warning and keep function signature consistent
148+
(void)index;
149+
150+
return desc_configuration;
151+
}
152+
153+
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
154+
{
155+
// TODO: check lang id
156+
(void) langid;
157+
size_t char_count;
158+
159+
// Determine which string descriptor to return
160+
switch (index) {
161+
case STRID_LANGID:
162+
memcpy(&_desc_str[1], string_desc_arr[STRID_LANGID], 2);
163+
char_count = 1;
164+
break;
165+
166+
case STRID_SERIAL:
167+
// try to read the serial from the board
168+
char_count = board_usb_get_serial(_desc_str + 1, 32);
169+
break;
170+
171+
default:
172+
// COPYRIGHT NOTE: Based on TinyUSB example
173+
// Windows wants utf16le
174+
175+
// Determine which string descriptor to return
176+
if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) {
177+
return NULL;
178+
}
179+
180+
// Copy string descriptor into _desc_str
181+
const char *str = string_desc_arr[index];
182+
183+
char_count = strlen(str);
184+
size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
185+
// Cap at max char
186+
if (char_count > max_count) {
187+
char_count = max_count;
188+
}
189+
190+
// Convert ASCII string into UTF-16
191+
for (size_t i = 0; i < char_count; i++) {
192+
_desc_str[1 + i] = str[i];
193+
}
194+
break;
195+
}
196+
197+
// First byte is the length (including header), second byte is string type
198+
_desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (char_count * 2 + 2));
199+
200+
return _desc_str;
201+
}

0 commit comments

Comments
 (0)