Skip to content

Commit 50bddc1

Browse files
committed
pbio/drv/usb: Switch to serial.
The previous WebUSB driver was a promising start, but it was not reliable. Messages could get stuck and the hub would not know if the app was disconnected. Bidirectional traffic was slow and prone to lockups. This commit replaces the USB drivers on all platforms with a standard serial USB device. We will be able to use this with Web Serial on most systems. Instead of manually subscribing and unsubscribing to keep track of the app connection state, we can use the DTR signal which is asserted on connect and deasserted on disconnect, even when the browser tab is abrubtly closed. Since serial is handled by the OS rather than our host application, in-flight messages don't get stuck if the host app is not reading them, which was part of the reason we had lockups before. This should also make it possible to use it with RFCOMM so we can add Bluetooth support for EV3 and NXT with relatively little changes. Finally, it may allow us to align the SPIKE Prime update procedure with the official firmware, for a more streamlined approach. Although the medium is a serial stream, we keep the same packetized event messages as before, much like we do for BLE. Frames are encoded with COBS with a 0x00 delimiter between messages, which makes it easy to get back in sync. The pull request for this change discusses further work.
1 parent 34f8acf commit 50bddc1

20 files changed

Lines changed: 1212 additions & 1139 deletions

File tree

lib/pbio/drv/usb/stm32_usbd/usbd_desc.c

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ pbdrv_usb_dev_desc_union_t USBD_DeviceDesc = {
8282
.s = {
8383
.bLength = sizeof(pbdrv_usb_dev_desc_t),
8484
.bDescriptorType = DESC_TYPE_DEVICE,
85-
.bcdUSB = 0x0210, /* 2.1.0 (for BOS support) */
86-
.bDeviceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS,
87-
.bDeviceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS,
88-
.bDeviceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL,
85+
.bcdUSB = 0x0200, /* 2.0.0 */
86+
.bDeviceClass = USB_CLASS_MISC,
87+
.bDeviceSubClass = USB_MISC_SUBCLASS_COMMON,
88+
.bDeviceProtocol = USB_MISC_PROTOCOL_IAD,
8989
.bMaxPacketSize0 = USB_MAX_EP0_SIZE,
9090
.idVendor = PBDRV_CONFIG_USB_VID,
9191
.idProduct = PBDRV_CONFIG_USB_PID,
@@ -221,21 +221,13 @@ static uint8_t *USBD_Pybricks_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint1
221221
return (uint8_t *)USBD_StringSerial;
222222
}
223223

224-
static uint8_t *USBD_Pybricks_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) {
225-
/* Prevent unused argument(s) compilation warning */
226-
UNUSED(speed);
227-
228-
*length = sizeof(pbdrv_usb_bos_desc_set.s);
229-
return (uint8_t *)&pbdrv_usb_bos_desc_set;
230-
}
231-
232224
USBD_DescriptorsTypeDef USBD_Pybricks_Desc = {
233225
.GetDeviceDescriptor = USBD_Pybricks_DeviceDescriptor,
234226
.GetLangIDStrDescriptor = USBD_Pybricks_LangIDStrDescriptor,
235227
.GetManufacturerStrDescriptor = USBD_Pybricks_ManufacturerStrDescriptor,
236228
.GetProductStrDescriptor = USBD_Pybricks_ProductStrDescriptor,
237229
.GetSerialStrDescriptor = USBD_Pybricks_SerialStrDescriptor,
238-
.GetBOSDescriptor = USBD_Pybricks_BOSDescriptor,
230+
.GetBOSDescriptor = NULL,
239231
};
240232

241233
void USBD_Pybricks_Desc_Init(void) {

lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.c

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,10 @@
3636
*/
3737

3838
/* Includes ------------------------------------------------------------------*/
39-
#include <pbio/protocol.h>
40-
#include <pbio/version.h>
41-
4239
#include "usbd_ctlreq.h"
4340
#include "usbd_pybricks.h"
4441

4542
#include "../usb_ch9.h"
46-
#include "../usb_common_desc.h"
4743

4844

4945
/** @addtogroup STM32_USB_DEVICE_LIBRARY
@@ -116,7 +112,14 @@ USBD_ClassTypeDef USBD_Pybricks_ClassDriver =
116112
/* USB Pybricks device Configuration Descriptor */
117113
typedef struct PBDRV_PACKED {
118114
pbdrv_usb_conf_desc_t conf_desc;
119-
pbdrv_usb_iface_desc_t iface_desc;
115+
pbdrv_usb_iad_desc_t iad;
116+
pbdrv_usb_iface_desc_t comm_iface;
117+
pbdrv_usb_cdc_header_desc_t cdc_header;
118+
pbdrv_usb_cdc_call_mgmt_desc_t cdc_call_mgmt;
119+
pbdrv_usb_cdc_acm_desc_t cdc_acm;
120+
pbdrv_usb_cdc_union_desc_t cdc_union;
121+
pbdrv_usb_ep_desc_t cmd_ep;
122+
pbdrv_usb_iface_desc_t data_iface;
120123
pbdrv_usb_ep_desc_t ep_out;
121124
pbdrv_usb_ep_desc_t ep_in;
122125
} pbdrv_usb_stm32_conf_t;
@@ -128,21 +131,80 @@ static pbdrv_usb_stm32_conf_union_t USBD_Pybricks_CfgDesc = {
128131
.bLength = sizeof(pbdrv_usb_conf_desc_t),
129132
.bDescriptorType = DESC_TYPE_CONFIGURATION,
130133
.wTotalLength = sizeof(pbdrv_usb_stm32_conf_t),
131-
.bNumInterfaces = 1,
134+
.bNumInterfaces = 2,
132135
.bConfigurationValue = 1,
133136
.iConfiguration = 0,
134137
.bmAttributes = USB_CONF_DESC_BM_ATTR_MUST_BE_SET,
135138
.bMaxPower = 250, /* 500mA (number of 2mA units) */
136139
},
137-
.iface_desc = {
140+
/* Interface Association: groups the comm and data interfaces into one
141+
* CDC ACM function. */
142+
.iad = {
143+
.bLength = sizeof(pbdrv_usb_iad_desc_t),
144+
.bDescriptorType = DESC_TYPE_INTERFACE_ASSOCIATION,
145+
.bFirstInterface = 0,
146+
.bInterfaceCount = 2,
147+
.bFunctionClass = USB_CLASS_CDC,
148+
.bFunctionSubClass = USB_CDC_SUBCLASS_ACM,
149+
.bFunctionProtocol = USB_CDC_PROTOCOL_AT,
150+
.iFunction = 0,
151+
},
152+
/* Communication interface */
153+
.comm_iface = {
138154
.bLength = sizeof(pbdrv_usb_iface_desc_t),
139155
.bDescriptorType = DESC_TYPE_INTERFACE,
140156
.bInterfaceNumber = 0,
141157
.bAlternateSetting = 0,
158+
.bNumEndpoints = 1,
159+
.bInterfaceClass = USB_CLASS_CDC,
160+
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
161+
.bInterfaceProtocol = USB_CDC_PROTOCOL_AT,
162+
.iInterface = 0,
163+
},
164+
.cdc_header = {
165+
.bFunctionLength = sizeof(pbdrv_usb_cdc_header_desc_t),
166+
.bDescriptorType = USB_CDC_CS_INTERFACE,
167+
.bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_HEADER,
168+
.bcdCDC = 0x0110,
169+
},
170+
.cdc_call_mgmt = {
171+
.bFunctionLength = sizeof(pbdrv_usb_cdc_call_mgmt_desc_t),
172+
.bDescriptorType = USB_CDC_CS_INTERFACE,
173+
.bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_CALL_MGMT,
174+
.bmCapabilities = 0x00,
175+
.bDataInterface = 1,
176+
},
177+
.cdc_acm = {
178+
.bFunctionLength = sizeof(pbdrv_usb_cdc_acm_desc_t),
179+
.bDescriptorType = USB_CDC_CS_INTERFACE,
180+
.bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_ACM,
181+
.bmCapabilities = 0x02,
182+
},
183+
.cdc_union = {
184+
.bFunctionLength = sizeof(pbdrv_usb_cdc_union_desc_t),
185+
.bDescriptorType = USB_CDC_CS_INTERFACE,
186+
.bDescriptorSubtype = USB_CDC_FUNC_SUBTYPE_UNION,
187+
.bControlInterface = 0,
188+
.bSubordinateInterface0 = 1,
189+
},
190+
.cmd_ep = {
191+
.bLength = sizeof(pbdrv_usb_ep_desc_t),
192+
.bDescriptorType = DESC_TYPE_ENDPOINT,
193+
.bEndpointAddress = USBD_PYBRICKS_CMD_EP,
194+
.bmAttributes = PBDRV_USB_EP_TYPE_INTR,
195+
.wMaxPacketSize = USBD_PYBRICKS_CMD_PACKET_SIZE,
196+
.bInterval = 16,
197+
},
198+
/* Data interface */
199+
.data_iface = {
200+
.bLength = sizeof(pbdrv_usb_iface_desc_t),
201+
.bDescriptorType = DESC_TYPE_INTERFACE,
202+
.bInterfaceNumber = 1,
203+
.bAlternateSetting = 0,
142204
.bNumEndpoints = 2,
143-
.bInterfaceClass = PBIO_PYBRICKS_USB_DEVICE_CLASS,
144-
.bInterfaceSubClass = PBIO_PYBRICKS_USB_DEVICE_SUBCLASS,
145-
.bInterfaceProtocol = PBIO_PYBRICKS_USB_DEVICE_PROTOCOL,
205+
.bInterfaceClass = USB_CLASS_CDC_DATA,
206+
.bInterfaceSubClass = 0,
207+
.bInterfaceProtocol = 0,
146208
.iInterface = 0,
147209
},
148210
.ep_out = {
@@ -185,12 +247,28 @@ static USBD_StatusTypeDef USBD_Pybricks_Init(USBD_HandleTypeDef *pdev, uint8_t c
185247

186248
pdev->pClassData = &hPybricks;
187249

250+
/* Default line coding reported to the host: 115200 baud, 1 stop bit, no
251+
* parity, 8 data bits. This is inert: USB does not transfer data at this
252+
* rate (it is not a real UART). CDC ACM just requires valid line coding to
253+
* be stored and echoed back on GET_LINE_CODING. */
254+
hPybricks.LineCoding[0] = 0x00;
255+
hPybricks.LineCoding[1] = 0xC2;
256+
hPybricks.LineCoding[2] = 0x01;
257+
hPybricks.LineCoding[3] = 0x00;
258+
hPybricks.LineCoding[4] = 0x00;
259+
hPybricks.LineCoding[5] = 0x00;
260+
hPybricks.LineCoding[6] = 0x08;
261+
hPybricks.CmdOpCode = 0xFFU;
262+
188263
(void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_IN_EP, USBD_EP_TYPE_BULK, USBD_PYBRICKS_MAX_PACKET_SIZE);
189264
pdev->ep_in[USBD_PYBRICKS_IN_EP & 0xFU].is_used = 1U;
190265

191266
(void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_OUT_EP, USBD_EP_TYPE_BULK, USBD_PYBRICKS_MAX_PACKET_SIZE);
192267
pdev->ep_out[USBD_PYBRICKS_OUT_EP & 0xFU].is_used = 1U;
193268

269+
(void)USBD_LL_OpenEP(pdev, USBD_PYBRICKS_CMD_EP, USBD_EP_TYPE_INTR, USBD_PYBRICKS_CMD_PACKET_SIZE);
270+
pdev->ep_in[USBD_PYBRICKS_CMD_EP & 0xFU].is_used = 1U;
271+
194272
/* Init physical Interface components */
195273
((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->Init();
196274

@@ -217,6 +295,10 @@ static USBD_StatusTypeDef USBD_Pybricks_DeInit(USBD_HandleTypeDef *pdev, uint8_t
217295
(void)USBD_LL_CloseEP(pdev, USBD_PYBRICKS_OUT_EP);
218296
pdev->ep_out[USBD_PYBRICKS_OUT_EP & 0xFU].is_used = 0U;
219297

298+
/* Close command EP */
299+
(void)USBD_LL_CloseEP(pdev, USBD_PYBRICKS_CMD_EP);
300+
pdev->ep_in[USBD_PYBRICKS_CMD_EP & 0xFU].is_used = 0U;
301+
220302
/* DeInit physical Interface components */
221303
if (pdev->pClassData != NULL) {
222304
((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->DeInit();
@@ -235,31 +317,45 @@ static USBD_StatusTypeDef USBD_Pybricks_DeInit(USBD_HandleTypeDef *pdev, uint8_t
235317
*/
236318
static USBD_StatusTypeDef USBD_Pybricks_Setup(USBD_HandleTypeDef *pdev,
237319
USBD_SetupReqTypedef *req) {
320+
USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData;
238321
uint8_t ifalt = 0U;
239322
uint16_t status_info = 0U;
240323
USBD_StatusTypeDef ret = USBD_OK;
241324

242325
switch (req->bmRequest & USB_REQ_TYPE_MASK)
243326
{
244327
case USB_REQ_TYPE_CLASS:
245-
ret = ((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->ReadCharacteristic(pdev, req);
246-
break;
247-
248-
case USB_REQ_TYPE_VENDOR:
328+
if (hPybricks == NULL) {
329+
USBD_CtlError(pdev, req);
330+
ret = USBD_FAIL;
331+
break;
332+
}
249333
switch (req->bRequest)
250334
{
251-
case PBDRV_USB_VENDOR_REQ_MS_20:
252-
(void)USBD_CtlSendData(pdev,
253-
(uint8_t *)&pbdrv_usb_ms_20_desc_set,
254-
MIN(sizeof(pbdrv_usb_ms_20_desc_set.s), req->wLength));
335+
case USB_CDC_REQ_SET_LINE_CODING:
336+
/* Receive the line coding into the handle. The value is
337+
* accepted but not acted on (the link is not a real UART). */
338+
if (req->wLength == USB_CDC_LINE_CODING_SIZE) {
339+
hPybricks->CmdOpCode = (uint8_t)req->bRequest;
340+
hPybricks->CmdLength = USB_CDC_LINE_CODING_SIZE;
341+
(void)USBD_CtlPrepareRx(pdev, hPybricks->LineCoding, USB_CDC_LINE_CODING_SIZE);
342+
} else {
343+
USBD_CtlError(pdev, req);
344+
ret = USBD_FAIL;
345+
}
255346
break;
256347

257-
case PBDRV_USB_VENDOR_REQ_WEBUSB:
258-
if ((req->wValue == PBDRV_USB_WEBUSB_LANDING_PAGE_URL_IDX) && (req->wIndex == WEBUSB_REQ_GET_URL)) {
259-
(void)USBD_CtlSendData(pdev,
260-
(uint8_t *)&pbdrv_usb_webusb_landing_page,
261-
MIN(pbdrv_usb_webusb_landing_page.s.bLength, req->wLength));
262-
}
348+
case USB_CDC_REQ_GET_LINE_CODING:
349+
(void)USBD_CtlSendData(pdev, hPybricks->LineCoding,
350+
MIN(USB_CDC_LINE_CODING_SIZE, req->wLength));
351+
break;
352+
353+
case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
354+
/* The DTR bit indicates whether a host application has
355+
* opened the port. This is the USB analog of a BLE host
356+
* subscribing to notifications. */
357+
((USBD_Pybricks_ItfTypeDef *)pdev->pUserData[pdev->classId])->SetControlLineState(
358+
(req->wValue & USB_CDC_CONTROL_LINE_STATE_DTR) != 0U);
263359
break;
264360

265361
default:
@@ -388,6 +484,14 @@ static USBD_StatusTypeDef USBD_Pybricks_DataOut(USBD_HandleTypeDef *pdev, uint8_
388484
* @retval status
389485
*/
390486
static USBD_StatusTypeDef USBD_Pybricks_EP0_RxReady(USBD_HandleTypeDef *pdev) {
487+
USBD_Pybricks_HandleTypeDef *hPybricks = pdev->pClassData;
488+
489+
if (hPybricks != NULL && hPybricks->CmdOpCode != 0xFFU) {
490+
/* SET_LINE_CODING data has been received into hPybricks->LineCoding.
491+
* Nothing to do; the value is stored for GET_LINE_CODING. */
492+
hPybricks->CmdOpCode = 0xFFU;
493+
}
494+
391495
return USBD_OK;
392496
}
393497

lib/pbio/drv/usb/stm32_usbd/usbd_pybricks.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ extern "C" {
2525
#endif
2626

2727
/* Includes ------------------------------------------------------------------*/
28+
#include <stdbool.h>
29+
2830
#include "usbd_ioreq.h"
2931

32+
#include "../usb_ch9.h"
33+
3034
/** @addtogroup STM32_USB_DEVICE_LIBRARY
3135
* @{
3236
*/
@@ -38,8 +42,10 @@ extern "C" {
3842

3943
#define USBD_PYBRICKS_IN_EP 0x81U /* EP1 for data IN */
4044
#define USBD_PYBRICKS_OUT_EP 0x01U /* EP1 for data OUT */
45+
#define USBD_PYBRICKS_CMD_EP 0x82U /* EP2 for CDC notifications */
4146

4247
#define USBD_PYBRICKS_MAX_PACKET_SIZE 64U
48+
#define USBD_PYBRICKS_CMD_PACKET_SIZE 8U
4349

4450
/**
4551
* @}
@@ -59,7 +65,7 @@ typedef struct
5965
USBD_StatusTypeDef (*DeInit)(void);
6066
USBD_StatusTypeDef (*Receive)(uint8_t *Buf, uint32_t Len);
6167
USBD_StatusTypeDef (*TransmitCplt)(uint8_t *Buf, uint32_t Len, uint8_t epnum);
62-
USBD_StatusTypeDef (*ReadCharacteristic)(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
68+
USBD_StatusTypeDef (*SetControlLineState)(bool dtr);
6369
} USBD_Pybricks_ItfTypeDef;
6470

6571

@@ -69,6 +75,9 @@ typedef struct
6975
uint8_t *TxBuffer;
7076
uint32_t RxLength;
7177
uint32_t TxLength;
78+
uint8_t CmdOpCode;
79+
uint8_t CmdLength;
80+
uint8_t LineCoding[USB_CDC_LINE_CODING_SIZE];
7281
} USBD_Pybricks_HandleTypeDef;
7382

7483

0 commit comments

Comments
 (0)