Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebUSB serial support (compile-time option, currently defaulted to OFF) #4126

Merged
merged 23 commits into from
Feb 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
84d7a0d
Initial steps, mostly via clone and modify.
FiriaCTO Jan 21, 2021
9ce33a5
Now generates the WebUSB URL Descriptor. Still need to generate the V…
FiriaCTO Jan 22, 2021
fbfb7b6
Most of the code we need has been pulled in from the tinyusb webusb_s…
FiriaCTO Jan 26, 2021
b152c65
Making WebUSB default to off (build unbreakage, caught by CI)
FiriaCTO Jan 27, 2021
1b03150
BOS and MS_OS_2.0 descriptors have been added. Still more descriptor …
FiriaCTO Jan 27, 2021
63f9b12
Partially hard-coded VENDOR descriptor has been added. First build to…
FiriaCTO Jan 29, 2021
e1618c2
Default WebUSB to OFF because other ports are not ready for it yet
FiriaCTO Feb 1, 2021
817ca39
ITF_NUM_VENDOR is now automatically computed at run-time
FiriaCTO Feb 2, 2021
1a9e4f5
Restored accidentally deleted character
FiriaCTO Feb 2, 2021
e792839
Moving control of USB compile-time options down to the BOARD level
FiriaCTO Feb 2, 2021
00926b6
Trying to keep two ARM builds from overflowing their FLASH
FiriaCTO Feb 3, 2021
215a56c
Merge branch 'main' into webusb (trying to get CI back online)
FiriaCTO Feb 3, 2021
f206773
Code cleanup and added a WEBUSB_README file
FiriaCTO Feb 4, 2021
4d90f19
(I could have sworn I deleted that line...)
FiriaCTO Feb 4, 2021
3bf8ef4
Making WEBUSB_README.md standalone for now
FiriaCTO Feb 4, 2021
87fe58f
Added WEBUSB_README to the table of contents (required by CI)
FiriaCTO Feb 4, 2021
cf9a00d
A little more content into WEBUSB_README
FiriaCTO Feb 4, 2021
3a952e9
Fixing whitespace
FiriaCTO Feb 4, 2021
7a17c75
File ending
FiriaCTO Feb 4, 2021
e72069e
Fix typo in comment
FiriaCTO Feb 4, 2021
c4a795b
Changed URL
FiriaCTO Feb 4, 2021
b6ce29b
Merge branch 'webusb' of https://github.com/FiriaCTO/circuitpython in…
FiriaCTO Feb 4, 2021
1352938
Changed URL
FiriaCTO Feb 4, 2021
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
94 changes: 94 additions & 0 deletions WEBUSB_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!--
SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors)

SPDX-License-Identifier: MIT
-->

# WebUSB Serial Support

To date, this has only been tested on one port (esp32s2), on one board (espressif_kaluga_1).

## What it does

If you have ever used CircuitPython on a platform with a graphical LCD display, you have probably
already seen multiple "consoles" in use (although the LCD console is "output only").

New compile-time option CIRCUITPY_USB_VENDOR enables an additional "console" that can be used in
parallel with the original (CDC) serial console.

Web pages that support the WebUSB standard can connect to the "vendor" interface and activate
this WebUSB serial console at any time.

You can type into either console, and CircuitPython output is sent to all active consoles.

One example of a web page you can use to test drive this feature can be found at:

https://adafruit.github.io/Adafruit_TinyUSB_Arduino/examples/webusb-serial/index.html

## How to enable

Update your platform's mpconfigboard.mk file to enable and disable specific types of USB interfaces.

CIRCUITPY_USB_HID = xxx
CIRCUITPY_USB_MIDI = xxx
CIRCUITPY_USB_VENDOR = xxx

On at least some of the hardware platforms, the maximum number of USB endpoints is fixed.
For example, on the ESP32S2, you must pick only one of the above 3 interfaces to be enabled.

Original espressif_kaluga_1 mpconfigboard.mk settings:

CIRCUITPY_USB_HID = 1
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_USB_VENDOR = 0

Settings to enable WebUSB instead:

CIRCUITPY_USB_HID = 0
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_USB_VENDOR = 1

Notice that to enable VENDOR on ESP32-S2, we had to give up HID. There may be platforms that can have both, or even all three.

## Implementation Notes

CircuitPython uses the tinyusb library.

The tinyusb library already has support for WebUSB serial.
The tinyusb examples already include a "WebUSB serial" example.

Sidenote - The use of the term "vendor" instead of "WebUSB" was done to match tinyusb.

Basically, this feature was ported into CircuitPython by pulling code snippets out of the
tinyusb example, and putting them where they best belonged in the CircuitPython codebase.

There was one complication:

tinyusb uses C preprocessor macros to define things like USB descriptors.

CircuitPython uses a Python program (tools/gen_usb_descriptor.py) to create USB descriptors (etc.)
using "helper objects" from another repo (adafruit_usb_descriptor). This means some of the example
code had to be adapted to the new programing model, and gen_usb_descriptor gained new command-line
options to control the generated code.

The generated files go into the "build" directory, look for autogen_usb_descriptor.c and
genhdr/autogen_usb_descriptor.h.


Also worth pointing out - the re-use of the CDC connect/disconnect mechanism is not actually part
of the WebUSB standard, it's more of "common idiom". We make use of it here because we need to know
when we should be paying attention to the WebUSB serial interface, and when we should ignore it..

## Possible future work areas

The current code uses the existing Python infrastructure to create the Interface descriptor, but
simply outputs the code snippets from the original tinyusb demo code to create the WEBUSB_URL,
BOS, and MS_OS_20 descriptors. I suppose additional work could be done to add these to the
adafruit_usb_descriptor project, and then gen_usb_descriptor.py could be modified to make use
of them.

Program gen_usb_descriptor.py creates objects for most interface types, regardless of whether or
not they are actually enabled. This increases the size of a generated string table. I made the
new vendor-interface-related code not do this (because some of the ARM platforms would no longer
build), but I did not go back and do this for the other interface types (CDC, MIDI, HID, etc.)
Some FLASH savings are probably possible if this is done.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Full Table of Contents
../BUILDING
../CODE_OF_CONDUCT
../license.rst
../WEBUSB_README

Indices and tables
==================
Expand Down
4 changes: 3 additions & 1 deletion ports/esp32s2/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ LIBS += -lm
endif

# TinyUSB defines
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_ESP32S2 -DCFG_TUSB_OS=OPT_OS_FREERTOS -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_CDC_TX_BUFSIZE=1024 -DCFG_TUD_MSC_BUFSIZE=4096 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_MIDI_TX_BUFSIZE=128
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_ESP32S2 -DCFG_TUSB_OS=OPT_OS_FREERTOS -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_CDC_TX_BUFSIZE=1024
CFLAGS += -DCFG_TUD_MSC_BUFSIZE=4096 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_MIDI_TX_BUFSIZE=128
CFLAGS += -DCFG_TUD_VENDOR_RX_BUFSIZE=128 -DCFG_TUD_VENDOR_TX_BUFSIZE=128


######################################
Expand Down
6 changes: 6 additions & 0 deletions ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ CIRCUITPY_ESP_FLASH_MODE=dio
CIRCUITPY_ESP_FLASH_FREQ=80m
CIRCUITPY_ESP_FLASH_SIZE=4MB

# We only have enough endpoints available in hardware to
# enable ONE of these at a time.
CIRCUITPY_USB_MIDI = 1
CIRCUITPY_USB_HID = 0
CIRCUITPY_USB_VENDOR = 0

CIRCUITPY_MODULE=wrover
5 changes: 4 additions & 1 deletion ports/esp32s2/mpconfigport.mk
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ CIRCUITPY_I2CPERIPHERAL = 0
CIRCUITPY_ROTARYIO = 1
CIRCUITPY_NVM = 1
# We don't have enough endpoints to include MIDI.
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_USB_MIDI ?= 0
CIRCUITPY_USB_HID ?= 1
# We have borrowed the VENDOR nomenclature from tinyusb. VENDOR AKA WEBUSB
CIRCUITPY_USB_VENDOR ?= 0
CIRCUITPY_WIFI = 1
CIRCUITPY_WATCHDOG ?= 1
CIRCUITPY_ESPIDF = 1
Expand Down
3 changes: 3 additions & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ endif
ifeq ($(CIRCUITPY_USB_MIDI),1)
SRC_PATTERNS += usb_midi/%
endif
ifeq ($(CIRCUITPY_USB_VENDOR),1)
SRC_PATTERNS += usb_vendor/%
endif
ifeq ($(CIRCUITPY_USTACK),1)
SRC_PATTERNS += ustack/%
endif
Expand Down
6 changes: 6 additions & 0 deletions py/circuitpy_mpconfig.mk
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ CFLAGS += -DCIRCUITPY_USB_HID=$(CIRCUITPY_USB_HID)
CIRCUITPY_USB_MIDI ?= 1
CFLAGS += -DCIRCUITPY_USB_MIDI=$(CIRCUITPY_USB_MIDI)

# Defaulting this to OFF initially because it has only been tested on a
# limited number of platforms, and the other platforms do not have this
# setting in their mpconfigport.mk and/or mpconfigboard.mk files yet.
CIRCUITPY_USB_VENDOR ?= 0
CFLAGS += -DCIRCUITPY_USB_VENDOR=$(CIRCUITPY_USB_VENDOR)

CIRCUITPY_PEW ?= 0
CFLAGS += -DCIRCUITPY_PEW=$(CIRCUITPY_PEW)

Expand Down
30 changes: 30 additions & 0 deletions supervisor/shared/serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ busio_uart_obj_t debug_uart;
byte buf_array[64];
#endif

#if CIRCUITPY_USB_VENDOR
bool tud_vendor_connected(void);
#endif

void serial_early_init(void) {
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
debug_uart.base.type = &busio_uart_type;
Expand All @@ -66,6 +70,12 @@ void serial_init(void) {
}

bool serial_connected(void) {
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected()) {
return true;
}
#endif

#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
return true;
#else
Expand All @@ -74,6 +84,14 @@ bool serial_connected(void) {
}

char serial_read(void) {
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected() && tud_vendor_available() > 0) {
char tiny_buffer;
tud_vendor_read(&tiny_buffer, 1);
return tiny_buffer;
}
#endif

#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
if (tud_cdc_connected() && tud_cdc_available() > 0) {
return (char) tud_cdc_read_char();
Expand All @@ -88,6 +106,12 @@ char serial_read(void) {
}

bool serial_bytes_available(void) {
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected() && tud_vendor_available() > 0) {
return true;
}
#endif

#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
return common_hal_busio_uart_rx_characters_available(&debug_uart) || (tud_cdc_available() > 0);
#else
Expand All @@ -104,6 +128,12 @@ void serial_write_substring(const char* text, uint32_t length) {
common_hal_terminalio_terminal_write(&supervisor_terminal, (const uint8_t*) text, length, &errcode);
#endif

#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected()) {
tud_vendor_write(text, length);
}
#endif

uint32_t count = 0;
while (count < length && tud_cdc_connected()) {
count += tud_cdc_write(text + count, length - count);
Expand Down
1 change: 1 addition & 0 deletions supervisor/shared/usb/tusb_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#define CFG_TUD_MSC 1
#define CFG_TUD_HID CIRCUITPY_USB_HID
#define CFG_TUD_MIDI CIRCUITPY_USB_MIDI
#define CFG_TUD_VENDOR CIRCUITPY_USB_VENDOR
#define CFG_TUD_CUSTOM_CLASS 0

/*------------------------------------------------------------------*/
Expand Down
69 changes: 69 additions & 0 deletions supervisor/shared/usb/usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@

#include "tusb.h"

#if CIRCUITPY_USB_VENDOR
#include "genhdr/autogen_usb_descriptor.h"

// The WebUSB support being conditionally added to this file is based on the
// tinyusb demo examples/device/webusb_serial.

extern const tusb_desc_webusb_url_t desc_webusb_url;

static bool web_serial_connected = false;
#endif



// Serial number as hex characters. This writes directly to the USB
// descriptor.
extern uint16_t usb_serial_number[1 + COMMON_HAL_MCU_PROCESSOR_UID_LENGTH * 2];
Expand Down Expand Up @@ -145,6 +158,62 @@ void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
}
}

#if CIRCUITPY_USB_VENDOR
//--------------------------------------------------------------------+
// WebUSB use vendor class
//--------------------------------------------------------------------+

bool tud_vendor_connected(void)
{
return web_serial_connected;
}

// Invoked when a control transfer occurred on an interface of this class
// Driver response accordingly to the request and the transfer stage (setup/data/ack)
// return false to stall control endpoint (e.g unsupported request)
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
{
// nothing to with DATA & ACK stage
if (stage != CONTROL_STAGE_SETUP ) return true;

switch (request->bRequest)
{
case VENDOR_REQUEST_WEBUSB:
// match vendor request in BOS descriptor
// Get landing page url
return tud_control_xfer(rhport, request, (void*) &desc_webusb_url, desc_webusb_url.bLength);

case VENDOR_REQUEST_MICROSOFT:
if ( request->wIndex == 7 )
{
// Get Microsoft OS 2.0 compatible descriptor
uint16_t total_len;
memcpy(&total_len, desc_ms_os_20+8, 2);

return tud_control_xfer(rhport, request, (void*) desc_ms_os_20, total_len);
} else
{
return false;
}

case 0x22:
// Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to
// connect and disconnect.
web_serial_connected = (request->wValue != 0);

// response with status OK
return tud_control_status(rhport, request);

default:
// stall unknown request
return false;
}

return true;
}
#endif CIRCUITPY_USB_VENDOR


#if MICROPY_KBD_EXCEPTION

/**
Expand Down
20 changes: 20 additions & 0 deletions supervisor/supervisor.mk
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ else
shared-module/usb_midi/PortOut.c
endif

ifeq ($(CIRCUITPY_USB_VENDOR), 1)
SRC_SUPERVISOR += \
lib/tinyusb/src/class/vendor/vendor_device.c
endif

CFLAGS += -DUSB_AVAILABLE
endif

Expand All @@ -119,13 +124,22 @@ ifndef USB_INTERFACE_NAME
USB_INTERFACE_NAME = "CircuitPython"
endif

# In the following URL, don't include the https:// prefix.
# It gets added automatically.
ifndef USB_WEBUSB_URL
USB_WEBUSB_URL = "circuitpython.org"
endif

USB_DEVICES_COMPUTED := CDC,MSC
ifeq ($(CIRCUITPY_USB_MIDI),1)
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),AUDIO
endif
ifeq ($(CIRCUITPY_USB_HID),1)
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),HID
endif
ifeq ($(CIRCUITPY_USB_VENDOR),1)
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),VENDOR
endif
USB_DEVICES ?= "$(USB_DEVICES_COMPUTED)"

ifndef USB_HID_DEVICES
Expand Down Expand Up @@ -198,6 +212,12 @@ USB_DESCRIPTOR_ARGS = \
--output_c_file $(BUILD)/autogen_usb_descriptor.c\
--output_h_file $(BUILD)/genhdr/autogen_usb_descriptor.h

ifeq ($(CIRCUITPY_USB_VENDOR), 1)
USB_DESCRIPTOR_ARGS += \
--vendor_ep_num_out 0 --vendor_ep_num_in 0 \
--webusb_url $(USB_WEBUSB_URL)
endif

ifeq ($(USB_RENUMBER_ENDPOINTS), 0)
USB_DESCRIPTOR_ARGS += --no-renumber_endpoints
endif
Expand Down
Loading