Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions usb/device/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
23 changes: 23 additions & 0 deletions usb/device/dev_cdc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
74 changes: 74 additions & 0 deletions usb/device/dev_cdc/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <stdlib.h>
#include <bsp/board_api.h>
#include <tusb.h>

#include <pico/stdio.h>

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that custom_cdc_task is called from within the main loop, does this sleep here mean that once CDC 0 is connected, tud_task will only get called once every 5 seconds? 🤔 (and doesn't tud_task need to get called more often than that in order to keep the USB connection alive?)

}
}

// 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);
}
}
42 changes: 42 additions & 0 deletions usb/device/dev_cdc/tusb_config.h
Original file line number Diff line number Diff line change
@@ -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_ */
194 changes: 194 additions & 0 deletions usb/device/dev_cdc/usb_descriptors.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#include <tusb.h>
#include <bsp/board_api.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 (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;
}