Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions docs/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ Used to avoid "Pystack exhausted" errors when the code can't be reworked to avoi

CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE (integer)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allocates a buffer for incoming isochronous USB data (USB camera, USB audio)
on boards with a ``usb_host.Port``. Without it, isochronous IN transfers return at
most one packet per USB frame, so data arriving while code is busy is lost.
Allocates a buffer for incoming isochronous USB data, such as from a USB camera
read with ``usb.core``, on boards with a ``usb_host.Port``. Without it, isochronous IN
transfers return at most one packet per USB frame, so data arriving while code is busy is lost.
The value is rounded up to the next power of two and limited to the range 2048
to 65536. A typical value for a USB camera is 16384. The buffer is allocated when
the USB host port is created and is never freed.
Expand Down
10 changes: 7 additions & 3 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ msgstr ""
msgid "No pull up found on SDA or SCL; check your wiring"
msgstr ""

#: ports/atmel-samd/common-hal/busio/UART.c shared-bindings/usb/core/Device.c
#: ports/atmel-samd/common-hal/busio/UART.c
#: shared-bindings/usb_host_bulk/InStream.c
msgid "%q must be power of 2"
msgstr ""
Expand Down Expand Up @@ -1346,6 +1346,7 @@ msgstr ""
#: ports/espressif/common-hal/_bleio/Characteristic.c
#: ports/espressif/common-hal/_bleio/Service.c
#: ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c
#: ports/raspberrypi/common-hal/usb_host_bulk/InStream.c
msgid "Too many %q"
msgstr ""

Expand Down Expand Up @@ -1476,7 +1477,7 @@ msgid "%q must be array of type 'H'"
msgstr ""

#: ports/espressif/common-hal/audiobusio/PDMIn.c
#: shared-bindings/audioi2sin/I2SIn.c
#: shared-bindings/audioi2sin/I2SIn.c shared-bindings/usb_host_audio/USBIn.c
msgid "%q must be 8, 16, 24, or 32"
msgstr ""

Expand Down Expand Up @@ -1675,6 +1676,7 @@ msgstr ""
#: shared-bindings/digitalio/DigitalInOut.c shared-bindings/emmcio/EMMC.c
#: shared-bindings/i2cioexpander/IOPin.c shared-bindings/microcontroller/Pin.c
#: shared-module/max3421e/Max3421E.c shared-module/usb/core/Device.c
#: shared-module/usb_host_audio/USBIn.c
msgid "%q in use"
msgstr ""

Expand Down Expand Up @@ -3922,10 +3924,12 @@ msgid "%q must be multiple of 8."
msgstr ""

#: shared-bindings/audiobusio/PDMIn.c shared-bindings/audioi2sin/I2SIn.c
#: shared-bindings/usb_host_audio/USBIn.c
msgid "Cannot record to a file"
msgstr ""

#: shared-bindings/audiobusio/PDMIn.c shared-bindings/audioi2sin/I2SIn.c
#: shared-bindings/usb_host_audio/USBIn.c
msgid "Destination capacity is smaller than destination_length."
msgstr ""

Expand Down Expand Up @@ -3965,7 +3969,7 @@ msgstr ""
msgid "bits_per_sample must be 16"
msgstr ""

#: shared-bindings/audioi2sin/I2SIn.c
#: shared-bindings/audioi2sin/I2SIn.c shared-bindings/usb_host_audio/USBIn.c
#, c-format
msgid "invalid destination buffer, must be an array of type: %c"
msgstr ""
Expand Down
8 changes: 8 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
#include "shared-module/audiofilewriter/AudioFileWriter.h"
#endif

#if CIRCUITPY_USB_HOST_AUDIO
#include "shared-module/usb_host_audio/USBIn.h"
#endif

#if CIRCUITPY_MEMORYMONITOR
#include "shared-module/memorymonitor/__init__.h"
#endif
Expand Down Expand Up @@ -420,6 +424,10 @@ static void cleanup_after_vm(mp_obj_t exception) {
audiofilewriter_reset();
#endif

#if CIRCUITPY_USB_HOST_AUDIO
usb_host_audio_reset();
#endif

// Close user-initiated sockets.
#if CIRCUITPY_SOCKETPOOL
socketpool_user_reset();
Expand Down
3 changes: 3 additions & 0 deletions ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ CFLAGS += -DPIO_USB_HOST_ISOCHRONOUS=1

# USB host bulk input, for SDR devices
CIRCUITPY_USB_HOST_BULK = 1

# USB microphones as audio sources. Needs the isochronous ring above.
CIRCUITPY_USB_HOST_AUDIO = 1
73 changes: 68 additions & 5 deletions ports/raspberrypi/common-hal/usb_host/Port.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,32 @@ usb_host_port_obj_t usb_host_instance;

volatile bool _core1_ready = false;

#if PIO_USB_HOST_ISOCHRONOUS
// The isochronous ring from settings.toml. Like the port, it is never freed.
static uint8_t *_iso_settings_buffer = NULL;
static uint32_t _iso_settings_size = 0;
// A ring claimed for the length of one stream, which replaces the one above
// until it is released.
static uint8_t *_iso_claimed_buffer = NULL;

// The ring size the host controller accepts: a power of two in [2048, 65536].
static uint32_t _iso_ring_size(size_t size) {
uint32_t rounded = 2048;
while (rounded < size && rounded < 65536) {
rounded <<= 1;
}
return rounded;
}

// Hand the host controller another ring. Core 1 may still be in the frame
// where the endpoint stopped streaming into the old ring, so let it finish
// first. False while an endpoint streams into the ring.
static bool _iso_set_ring(uint8_t *buffer, uint32_t size) {
busy_wait_us(2000);
return pio_usb_host_set_iso_ring(buffer, size);
}
#endif

// Core1 posts TinyUSB events while core0 may hold the queue FIFO mutex. The
// contended wait path calls best_effort_wfe_or_timeout() and time_us_64(),
// which normally live in flash, but core1 must not touch flash (see the MPU
Expand Down Expand Up @@ -183,18 +209,16 @@ usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp,
mp_int_t iso_size = 0;
if (settings_get_int("CIRCUITPY_USB_HOST_ISO_BUFFER_SIZE", &iso_size) == SETTINGS_OK
&& iso_size > 0) {
// Round up to a power of two and clamp to [2048, 65536]
uint32_t rounded = 2048;
while (rounded < (uint32_t)iso_size && rounded < 65536) {
rounded <<= 1;
}
uint32_t rounded = _iso_ring_size(iso_size);
// dma_capable=true forces the internal-SRAM heap. Never PSRAM: PSRAM
// is inside the XIP region that core 1's MPU blocks below, and the
// ring is written from core 1. Like the port itself, the buffer is
// never freed; it survives soft reload.
uint8_t *iso_buf = port_malloc(rounded, true);
if (iso_buf != NULL) {
pio_usb_host_set_iso_ring(iso_buf, rounded);
_iso_settings_buffer = iso_buf;
_iso_settings_size = rounded;
} else {
mp_raise_RuntimeError_varg(MP_ERROR_TEXT("Failed to allocate %q buffer"),
MP_QSTR_USB_HOST_ISO_BUFFER);
Expand All @@ -214,6 +238,45 @@ usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp,
return self;
}

bool common_hal_usb_host_port_claim_iso_buffer(size_t size) {
#if PIO_USB_HOST_ISOCHRONOUS
// A ring left claimed because it was still streaming may be free by now.
common_hal_usb_host_port_release_iso_buffer();
if (_iso_claimed_buffer != NULL) {
return false;
}
uint32_t rounded = _iso_ring_size(size);
// Internal SRAM only, for the same reason as the settings.toml ring above.
uint8_t *buffer = port_malloc(rounded, true);
if (buffer == NULL) {
m_malloc_fail(rounded);
}
if (!_iso_set_ring(buffer, rounded)) {
port_free(buffer);
return false;
}
_iso_claimed_buffer = buffer;
return true;
#else
(void)size;
return false;
#endif
}

void common_hal_usb_host_port_release_iso_buffer(void) {
#if PIO_USB_HOST_ISOCHRONOUS
if (_iso_claimed_buffer == NULL) {
return;
}
// When an endpoint still streams into the ring, core 1 keeps writing it, so
// it can't be freed. It stays claimed and the next claim tries again.
if (_iso_set_ring(_iso_settings_buffer, _iso_settings_size)) {
port_free(_iso_claimed_buffer);
_iso_claimed_buffer = NULL;
}
#endif
}

// Not used, but we must define to put this hook into SRAM
void __not_in_flash_func(tuh_event_hook_cb)(uint8_t rhport, uint32_t eventid, bool in_isr) {
(void)rhport;
Expand Down
5 changes: 5 additions & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,9 @@ endif
ifeq ($(CIRCUITPY_USB_HOST),1)
SRC_PATTERNS += usb_host/%
endif
ifeq ($(CIRCUITPY_USB_HOST_AUDIO),1)
SRC_PATTERNS += usb_host_audio/%
endif
ifeq ($(CIRCUITPY_USB_HOST_BULK),1)
SRC_PATTERNS += usb_host_bulk/%
endif
Expand Down Expand Up @@ -877,6 +880,8 @@ SRC_SHARED_MODULE_ALL = \
usb/core/__init__.c \
usb/core/Device.c \
usb/util/__init__.c \
usb_host_audio/USBIn.c \
usb_host_audio/__init__.c \
ustack/__init__.c \
vectorio/Circle.c \
vectorio/Polygon.c \
Expand Down
5 changes: 5 additions & 0 deletions py/circuitpy_mpconfig.mk
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,11 @@ CFLAGS += -DCIRCUITPY_USB_HOST=$(CIRCUITPY_USB_HOST)
CIRCUITPY_PYUSB ?= $(call enable-if-any,$(CIRCUITPY_USB_HOST) $(CIRCUITPY_MAX3421E))
CFLAGS += -DCIRCUITPY_PYUSB=$(CIRCUITPY_PYUSB)

# Needs a host controller that buffers isochronous IN packets, such as
# Pico-PIO-USB with PIO_USB_HOST_ISOCHRONOUS set.
CIRCUITPY_USB_HOST_AUDIO ?= 0
CFLAGS += -DCIRCUITPY_USB_HOST_AUDIO=$(CIRCUITPY_USB_HOST_AUDIO)

# Continuous bulk transfers on PIO USB host ports. Opt-in per board.
CIRCUITPY_USB_HOST_BULK ?= 0
CFLAGS += -DCIRCUITPY_USB_HOST_BULK=$(CIRCUITPY_USB_HOST_BULK)
Expand Down
10 changes: 10 additions & 0 deletions shared-bindings/usb_host/Port.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ extern const mp_obj_type_t usb_host_port_type;
// method to check the internals of the global object against the given arguments
// to determine whether to return the singleton or raise an exception.
usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp, const mcu_pin_obj_t *dm);

// Gives the host controller a buffer of at least size bytes for isochronous IN
// packets, which it then keeps between transfers and hands over as
// length-prefixed records, as usb_host_audio needs. Until it is released it
// replaces the buffer set in settings.toml. False when another isochronous
// stream holds the buffer; raises when out of memory.
bool common_hal_usb_host_port_claim_iso_buffer(size_t size);
// Frees the claimed buffer and puts back the one from settings.toml. Call after
// the stream ended. Never raises.
void common_hal_usb_host_port_release_iso_buffer(void);
Loading
Loading