Skip to content

Commit 0a55cfb

Browse files
authored
Merge pull request #4126 from FiriaCTO/webusb
WebUSB serial support (compile-time option, currently defaulted to OFF)
2 parents f6e881e + 1352938 commit 0a55cfb

File tree

12 files changed

+407
-10
lines changed

12 files changed

+407
-10
lines changed

WEBUSB_README.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors)
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
# WebUSB Serial Support
8+
9+
To date, this has only been tested on one port (esp32s2), on one board (espressif_kaluga_1).
10+
11+
## What it does
12+
13+
If you have ever used CircuitPython on a platform with a graphical LCD display, you have probably
14+
already seen multiple "consoles" in use (although the LCD console is "output only").
15+
16+
New compile-time option CIRCUITPY_USB_VENDOR enables an additional "console" that can be used in
17+
parallel with the original (CDC) serial console.
18+
19+
Web pages that support the WebUSB standard can connect to the "vendor" interface and activate
20+
this WebUSB serial console at any time.
21+
22+
You can type into either console, and CircuitPython output is sent to all active consoles.
23+
24+
One example of a web page you can use to test drive this feature can be found at:
25+
26+
https://adafruit.github.io/Adafruit_TinyUSB_Arduino/examples/webusb-serial/index.html
27+
28+
## How to enable
29+
30+
Update your platform's mpconfigboard.mk file to enable and disable specific types of USB interfaces.
31+
32+
CIRCUITPY_USB_HID = xxx
33+
CIRCUITPY_USB_MIDI = xxx
34+
CIRCUITPY_USB_VENDOR = xxx
35+
36+
On at least some of the hardware platforms, the maximum number of USB endpoints is fixed.
37+
For example, on the ESP32S2, you must pick only one of the above 3 interfaces to be enabled.
38+
39+
Original espressif_kaluga_1 mpconfigboard.mk settings:
40+
41+
CIRCUITPY_USB_HID = 1
42+
CIRCUITPY_USB_MIDI = 0
43+
CIRCUITPY_USB_VENDOR = 0
44+
45+
Settings to enable WebUSB instead:
46+
47+
CIRCUITPY_USB_HID = 0
48+
CIRCUITPY_USB_MIDI = 0
49+
CIRCUITPY_USB_VENDOR = 1
50+
51+
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.
52+
53+
## Implementation Notes
54+
55+
CircuitPython uses the tinyusb library.
56+
57+
The tinyusb library already has support for WebUSB serial.
58+
The tinyusb examples already include a "WebUSB serial" example.
59+
60+
Sidenote - The use of the term "vendor" instead of "WebUSB" was done to match tinyusb.
61+
62+
Basically, this feature was ported into CircuitPython by pulling code snippets out of the
63+
tinyusb example, and putting them where they best belonged in the CircuitPython codebase.
64+
65+
There was one complication:
66+
67+
tinyusb uses C preprocessor macros to define things like USB descriptors.
68+
69+
CircuitPython uses a Python program (tools/gen_usb_descriptor.py) to create USB descriptors (etc.)
70+
using "helper objects" from another repo (adafruit_usb_descriptor). This means some of the example
71+
code had to be adapted to the new programing model, and gen_usb_descriptor gained new command-line
72+
options to control the generated code.
73+
74+
The generated files go into the "build" directory, look for autogen_usb_descriptor.c and
75+
genhdr/autogen_usb_descriptor.h.
76+
77+
78+
Also worth pointing out - the re-use of the CDC connect/disconnect mechanism is not actually part
79+
of the WebUSB standard, it's more of "common idiom". We make use of it here because we need to know
80+
when we should be paying attention to the WebUSB serial interface, and when we should ignore it..
81+
82+
## Possible future work areas
83+
84+
The current code uses the existing Python infrastructure to create the Interface descriptor, but
85+
simply outputs the code snippets from the original tinyusb demo code to create the WEBUSB_URL,
86+
BOS, and MS_OS_20 descriptors. I suppose additional work could be done to add these to the
87+
adafruit_usb_descriptor project, and then gen_usb_descriptor.py could be modified to make use
88+
of them.
89+
90+
Program gen_usb_descriptor.py creates objects for most interface types, regardless of whether or
91+
not they are actually enabled. This increases the size of a generated string table. I made the
92+
new vendor-interface-related code not do this (because some of the ARM platforms would no longer
93+
build), but I did not go back and do this for the other interface types (CDC, MIDI, HID, etc.)
94+
Some FLASH savings are probably possible if this is done.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Full Table of Contents
4646
../BUILDING
4747
../CODE_OF_CONDUCT
4848
../license.rst
49+
../WEBUSB_README
4950

5051
Indices and tables
5152
==================

ports/esp32s2/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ LIBS += -lm
164164
endif
165165

166166
# TinyUSB defines
167-
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
167+
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
168+
CFLAGS += -DCFG_TUD_MSC_BUFSIZE=4096 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_MIDI_TX_BUFSIZE=128
169+
CFLAGS += -DCFG_TUD_VENDOR_RX_BUFSIZE=128 -DCFG_TUD_VENDOR_TX_BUFSIZE=128
168170

169171

170172
######################################

ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ CIRCUITPY_ESP_FLASH_MODE=dio
1414
CIRCUITPY_ESP_FLASH_FREQ=80m
1515
CIRCUITPY_ESP_FLASH_SIZE=4MB
1616

17+
# We only have enough endpoints available in hardware to
18+
# enable ONE of these at a time.
19+
CIRCUITPY_USB_MIDI = 1
20+
CIRCUITPY_USB_HID = 0
21+
CIRCUITPY_USB_VENDOR = 0
22+
1723
CIRCUITPY_MODULE=wrover

ports/esp32s2/mpconfigport.mk

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ CIRCUITPY_I2CPERIPHERAL = 0
2929
CIRCUITPY_ROTARYIO = 1
3030
CIRCUITPY_NVM = 1
3131
# We don't have enough endpoints to include MIDI.
32-
CIRCUITPY_USB_MIDI = 0
32+
CIRCUITPY_USB_MIDI ?= 0
33+
CIRCUITPY_USB_HID ?= 1
34+
# We have borrowed the VENDOR nomenclature from tinyusb. VENDOR AKA WEBUSB
35+
CIRCUITPY_USB_VENDOR ?= 0
3336
CIRCUITPY_WIFI = 1
3437
CIRCUITPY_WATCHDOG ?= 1
3538
CIRCUITPY_ESPIDF = 1

py/circuitpy_defns.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ endif
292292
ifeq ($(CIRCUITPY_USB_MIDI),1)
293293
SRC_PATTERNS += usb_midi/%
294294
endif
295+
ifeq ($(CIRCUITPY_USB_VENDOR),1)
296+
SRC_PATTERNS += usb_vendor/%
297+
endif
295298
ifeq ($(CIRCUITPY_USTACK),1)
296299
SRC_PATTERNS += ustack/%
297300
endif

py/circuitpy_mpconfig.mk

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ CFLAGS += -DCIRCUITPY_USB_HID=$(CIRCUITPY_USB_HID)
288288
CIRCUITPY_USB_MIDI ?= 1
289289
CFLAGS += -DCIRCUITPY_USB_MIDI=$(CIRCUITPY_USB_MIDI)
290290

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

supervisor/shared/serial.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ busio_uart_obj_t debug_uart;
4747
byte buf_array[64];
4848
#endif
4949

50+
#if CIRCUITPY_USB_VENDOR
51+
bool tud_vendor_connected(void);
52+
#endif
53+
5054
void serial_early_init(void) {
5155
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
5256
debug_uart.base.type = &busio_uart_type;
@@ -66,6 +70,12 @@ void serial_init(void) {
6670
}
6771

6872
bool serial_connected(void) {
73+
#if CIRCUITPY_USB_VENDOR
74+
if (tud_vendor_connected()) {
75+
return true;
76+
}
77+
#endif
78+
6979
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
7080
return true;
7181
#else
@@ -74,6 +84,14 @@ bool serial_connected(void) {
7484
}
7585

7686
char serial_read(void) {
87+
#if CIRCUITPY_USB_VENDOR
88+
if (tud_vendor_connected() && tud_vendor_available() > 0) {
89+
char tiny_buffer;
90+
tud_vendor_read(&tiny_buffer, 1);
91+
return tiny_buffer;
92+
}
93+
#endif
94+
7795
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
7896
if (tud_cdc_connected() && tud_cdc_available() > 0) {
7997
return (char) tud_cdc_read_char();
@@ -88,6 +106,12 @@ char serial_read(void) {
88106
}
89107

90108
bool serial_bytes_available(void) {
109+
#if CIRCUITPY_USB_VENDOR
110+
if (tud_vendor_connected() && tud_vendor_available() > 0) {
111+
return true;
112+
}
113+
#endif
114+
91115
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
92116
return common_hal_busio_uart_rx_characters_available(&debug_uart) || (tud_cdc_available() > 0);
93117
#else
@@ -104,6 +128,12 @@ void serial_write_substring(const char* text, uint32_t length) {
104128
common_hal_terminalio_terminal_write(&supervisor_terminal, (const uint8_t*) text, length, &errcode);
105129
#endif
106130

131+
#if CIRCUITPY_USB_VENDOR
132+
if (tud_vendor_connected()) {
133+
tud_vendor_write(text, length);
134+
}
135+
#endif
136+
107137
uint32_t count = 0;
108138
while (count < length && tud_cdc_connected()) {
109139
count += tud_cdc_write(text + count, length - count);

supervisor/shared/usb/tusb_config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
#define CFG_TUD_MSC 1
6969
#define CFG_TUD_HID CIRCUITPY_USB_HID
7070
#define CFG_TUD_MIDI CIRCUITPY_USB_MIDI
71+
#define CFG_TUD_VENDOR CIRCUITPY_USB_VENDOR
7172
#define CFG_TUD_CUSTOM_CLASS 0
7273

7374
/*------------------------------------------------------------------*/

supervisor/shared/usb/usb.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@
3737

3838
#include "tusb.h"
3939

40+
#if CIRCUITPY_USB_VENDOR
41+
#include "genhdr/autogen_usb_descriptor.h"
42+
43+
// The WebUSB support being conditionally added to this file is based on the
44+
// tinyusb demo examples/device/webusb_serial.
45+
46+
extern const tusb_desc_webusb_url_t desc_webusb_url;
47+
48+
static bool web_serial_connected = false;
49+
#endif
50+
51+
52+
4053
// Serial number as hex characters. This writes directly to the USB
4154
// descriptor.
4255
extern uint16_t usb_serial_number[1 + COMMON_HAL_MCU_PROCESSOR_UID_LENGTH * 2];
@@ -145,6 +158,62 @@ void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
145158
}
146159
}
147160

161+
#if CIRCUITPY_USB_VENDOR
162+
//--------------------------------------------------------------------+
163+
// WebUSB use vendor class
164+
//--------------------------------------------------------------------+
165+
166+
bool tud_vendor_connected(void)
167+
{
168+
return web_serial_connected;
169+
}
170+
171+
// Invoked when a control transfer occurred on an interface of this class
172+
// Driver response accordingly to the request and the transfer stage (setup/data/ack)
173+
// return false to stall control endpoint (e.g unsupported request)
174+
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
175+
{
176+
// nothing to with DATA & ACK stage
177+
if (stage != CONTROL_STAGE_SETUP ) return true;
178+
179+
switch (request->bRequest)
180+
{
181+
case VENDOR_REQUEST_WEBUSB:
182+
// match vendor request in BOS descriptor
183+
// Get landing page url
184+
return tud_control_xfer(rhport, request, (void*) &desc_webusb_url, desc_webusb_url.bLength);
185+
186+
case VENDOR_REQUEST_MICROSOFT:
187+
if ( request->wIndex == 7 )
188+
{
189+
// Get Microsoft OS 2.0 compatible descriptor
190+
uint16_t total_len;
191+
memcpy(&total_len, desc_ms_os_20+8, 2);
192+
193+
return tud_control_xfer(rhport, request, (void*) desc_ms_os_20, total_len);
194+
} else
195+
{
196+
return false;
197+
}
198+
199+
case 0x22:
200+
// Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to
201+
// connect and disconnect.
202+
web_serial_connected = (request->wValue != 0);
203+
204+
// response with status OK
205+
return tud_control_status(rhport, request);
206+
207+
default:
208+
// stall unknown request
209+
return false;
210+
}
211+
212+
return true;
213+
}
214+
#endif CIRCUITPY_USB_VENDOR
215+
216+
148217
#if MICROPY_KBD_EXCEPTION
149218

150219
/**

supervisor/supervisor.mk

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ else
102102
shared-module/usb_midi/PortOut.c
103103
endif
104104

105+
ifeq ($(CIRCUITPY_USB_VENDOR), 1)
106+
SRC_SUPERVISOR += \
107+
lib/tinyusb/src/class/vendor/vendor_device.c
108+
endif
109+
105110
CFLAGS += -DUSB_AVAILABLE
106111
endif
107112

@@ -119,13 +124,22 @@ ifndef USB_INTERFACE_NAME
119124
USB_INTERFACE_NAME = "CircuitPython"
120125
endif
121126

127+
# In the following URL, don't include the https:// prefix.
128+
# It gets added automatically.
129+
ifndef USB_WEBUSB_URL
130+
USB_WEBUSB_URL = "circuitpython.org"
131+
endif
132+
122133
USB_DEVICES_COMPUTED := CDC,MSC
123134
ifeq ($(CIRCUITPY_USB_MIDI),1)
124135
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),AUDIO
125136
endif
126137
ifeq ($(CIRCUITPY_USB_HID),1)
127138
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),HID
128139
endif
140+
ifeq ($(CIRCUITPY_USB_VENDOR),1)
141+
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),VENDOR
142+
endif
129143
USB_DEVICES ?= "$(USB_DEVICES_COMPUTED)"
130144

131145
ifndef USB_HID_DEVICES
@@ -198,6 +212,12 @@ USB_DESCRIPTOR_ARGS = \
198212
--output_c_file $(BUILD)/autogen_usb_descriptor.c\
199213
--output_h_file $(BUILD)/genhdr/autogen_usb_descriptor.h
200214

215+
ifeq ($(CIRCUITPY_USB_VENDOR), 1)
216+
USB_DESCRIPTOR_ARGS += \
217+
--vendor_ep_num_out 0 --vendor_ep_num_in 0 \
218+
--webusb_url $(USB_WEBUSB_URL)
219+
endif
220+
201221
ifeq ($(USB_RENUMBER_ENDPOINTS), 0)
202222
USB_DESCRIPTOR_ARGS += --no-renumber_endpoints
203223
endif

0 commit comments

Comments
 (0)