Skip to content

Commit 51f4cb2

Browse files
committed
pbdrv/usb_stm32: Implement event subscription
Implement the Pybricks Profile event subscription flag. This saves us from trying to send messages to the host when there is no app running to receive them. Previously, trying to write to stdout would block forever because of this. The 5 millisecond timeout is arbitrary. Wireshark shows that the OUT endpoint is being serviced in less than 1 ms, so this should be plenty of time even if some hosts are an order of magnitude slower. We don't want to make it too big because it will delay user programs for this long and we don't want to mess up someone's control loop too badly.
1 parent 0d927ef commit 51f4cb2

File tree

1 file changed

+53
-22
lines changed

1 file changed

+53
-22
lines changed

lib/pbio/drv/usb/usb_stm32.c

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ static volatile uint32_t usb_response_sz;
4040
static volatile uint32_t usb_status_sz;
4141
static volatile uint32_t usb_stdout_sz;
4242
static volatile bool transmitting;
43+
static volatile bool pbdrv_usb_stm32_is_events_subscribed;
4344

4445
static USBD_HandleTypeDef husbd;
4546
static PCD_HandleTypeDef hpcd;
@@ -237,12 +238,14 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
237238
return PBIO_ERROR_NOT_IMPLEMENTED;
238239
#endif
239240

241+
if (!pbdrv_usb_stm32_is_events_subscribed) {
242+
// If the app hasn't subscribed to events, we can't send stdout.
243+
return PBIO_ERROR_INVALID_OP;
244+
}
245+
240246
uint8_t *ptr = usb_stdout_buf;
241247
uint32_t ptr_len = sizeof(usb_stdout_buf);
242248

243-
// TODO: return PBIO_ERROR_INVALID_OP if app flag is not set. Also need a
244-
// timeout in case the app crashes and doesn't clear the flag on exit.
245-
246249
if (usb_stdout_sz) {
247250
return PBIO_ERROR_AGAIN;
248251
}
@@ -271,6 +274,14 @@ bool pbdrv_usb_stdout_tx_is_idle(void) {
271274
return usb_stdout_sz == 0;
272275
}
273276

277+
static void pbdrv_usb_stm32_reset_tx_state(void) {
278+
usb_response_sz = 0;
279+
usb_status_sz = 0;
280+
usb_stdout_sz = 0;
281+
transmitting = false;
282+
pbdrv_usb_stm32_is_events_subscribed = false;
283+
}
284+
274285
/**
275286
* @brief Pybricks_Itf_Init
276287
* Initializes the Pybricks media low layer
@@ -280,10 +291,7 @@ bool pbdrv_usb_stdout_tx_is_idle(void) {
280291
static USBD_StatusTypeDef Pybricks_Itf_Init(void) {
281292
USBD_Pybricks_SetRxBuffer(&husbd, usb_in_buf);
282293
usb_in_sz = 0;
283-
usb_response_sz = 0;
284-
usb_status_sz = 0;
285-
usb_stdout_sz = 0;
286-
transmitting = false;
294+
pbdrv_usb_stm32_reset_tx_state();
287295

288296
return USBD_OK;
289297
}
@@ -295,6 +303,7 @@ static USBD_StatusTypeDef Pybricks_Itf_Init(void) {
295303
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
296304
*/
297305
static USBD_StatusTypeDef Pybricks_Itf_DeInit(void) {
306+
pbdrv_usb_stm32_reset_tx_state();
298307
return USBD_OK;
299308
}
300309

@@ -389,7 +398,8 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) {
389398
static PBIO_ONESHOT(pwrdn_oneshot);
390399
static bool bcd_busy;
391400
static pbio_pybricks_error_t result;
392-
static struct etimer timer;
401+
static struct etimer status_timer;
402+
static struct etimer transmit_timer;
393403
static uint32_t prev_status_flags = ~0;
394404
static uint32_t new_status_flags;
395405

@@ -410,7 +420,7 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) {
410420

411421
PROCESS_BEGIN();
412422

413-
etimer_set(&timer, 500);
423+
etimer_set(&status_timer, 500);
414424

415425
for (;;) {
416426
PROCESS_WAIT_EVENT();
@@ -442,6 +452,12 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) {
442452

443453
if (usb_in_sz) {
444454
switch (usb_in_buf[0]) {
455+
case USBD_PYBRICKS_OUT_EP_MSG_SUBSCRIBE:
456+
pbdrv_usb_stm32_is_events_subscribed = usb_in_buf[1];
457+
usb_response_buf[0] = USBD_PYBRICKS_IN_EP_MSG_RESPONSE;
458+
pbio_set_uint32_le(&usb_response_buf[1], PBIO_PYBRICKS_ERROR_OK);
459+
usb_response_sz = sizeof(usb_response_buf);
460+
break;
445461
case USBD_PYBRICKS_OUT_EP_MSG_COMMAND:
446462
if (usb_response_sz == 0) {
447463
result = pbsys_command(usb_in_buf + 1, usb_in_sz - 1, PBSYS_COMMAND_TRANSPORT_USB);
@@ -458,6 +474,13 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) {
458474
}
459475

460476
if (transmitting) {
477+
if (etimer_expired(&transmit_timer)) {
478+
// Transmission has taken too long, so reset the state to allow
479+
// new transmissions. This can happen if the host stops reading
480+
// data for some reason.
481+
pbdrv_usb_stm32_reset_tx_state();
482+
}
483+
461484
continue;
462485
}
463486

@@ -467,20 +490,28 @@ PROCESS_THREAD(pbdrv_usb_process, ev, data) {
467490
if (usb_response_sz) {
468491
transmitting = true;
469492
USBD_Pybricks_TransmitPacket(&husbd, usb_response_buf, usb_response_sz);
470-
} else if ((new_status_flags != prev_status_flags) || etimer_expired(&timer)) {
471-
usb_status_buf[0] = USBD_PYBRICKS_IN_EP_MSG_EVENT;
472-
_Static_assert(sizeof(usb_status_buf) + 1 >= PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE,
473-
"size of status report does not match size of event");
474-
usb_status_sz = 1 + pbsys_status_get_status_report(&usb_status_buf[1]);
475-
476-
etimer_restart(&timer);
477-
prev_status_flags = new_status_flags;
493+
} else if (pbdrv_usb_stm32_is_events_subscribed) {
494+
if ((new_status_flags != prev_status_flags) || etimer_expired(&status_timer)) {
495+
usb_status_buf[0] = USBD_PYBRICKS_IN_EP_MSG_EVENT;
496+
_Static_assert(sizeof(usb_status_buf) + 1 >= PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE,
497+
"size of status report does not match size of event");
498+
usb_status_sz = 1 + pbsys_status_get_status_report(&usb_status_buf[1]);
499+
500+
etimer_restart(&status_timer);
501+
prev_status_flags = new_status_flags;
502+
503+
transmitting = true;
504+
USBD_Pybricks_TransmitPacket(&husbd, usb_status_buf, usb_status_sz);
505+
} else if (usb_stdout_sz) {
506+
transmitting = true;
507+
USBD_Pybricks_TransmitPacket(&husbd, usb_stdout_buf, usb_stdout_sz);
508+
}
509+
}
478510

479-
transmitting = true;
480-
USBD_Pybricks_TransmitPacket(&husbd, usb_status_buf, usb_status_sz);
481-
} else if (usb_stdout_sz) {
482-
transmitting = true;
483-
USBD_Pybricks_TransmitPacket(&husbd, usb_stdout_buf, usb_stdout_sz);
511+
if (transmitting) {
512+
// If the FIFO isn't emptied quickly, then there probably isn't an
513+
// app anymore. This timer is used to detect such a condition.
514+
etimer_set(&transmit_timer, 5);
484515
}
485516
}
486517

0 commit comments

Comments
 (0)