Skip to content

Commit 64e4145

Browse files
committed
Basic USB host support and keyboard workflow
Connects up read, write and ctrl_transfer to TinyUSB. USB Host support is available on iMX RT and RP2040. Fixes micropython#6527 (imx) and fixes micropython#5986 (rp2).
1 parent 95535a8 commit 64e4145

File tree

32 files changed

+812
-63
lines changed

32 files changed

+812
-63
lines changed

.gitmodules

+4
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,7 @@
341341
[submodule "frozen/Adafruit_CircuitPython_Wave"]
342342
path = frozen/Adafruit_CircuitPython_Wave
343343
url = http://github.com/adafruit/Adafruit_CircuitPython_Wave.git
344+
[submodule "ports/raspberrypi/lib/Pico-PIO-USB"]
345+
path = ports/raspberrypi/lib/Pico-PIO-USB
346+
url = https://github.com/tannewt/Pico-PIO-USB.git
347+
branch = circuitpython_usb_host

lib/tinyusb

Submodule tinyusb updated 462 files

locale/circuitpython.pot

+5-4
Original file line numberDiff line numberDiff line change
@@ -471,12 +471,17 @@ msgstr ""
471471
msgid "All channels in use"
472472
msgstr ""
473473

474+
#: ports/raspberrypi/common-hal/usb_host/Port.c
475+
msgid "All dma channels in use"
476+
msgstr ""
477+
474478
#: ports/atmel-samd/common-hal/audioio/AudioOut.c
475479
msgid "All event channels in use"
476480
msgstr ""
477481

478482
#: ports/raspberrypi/common-hal/picodvi/Framebuffer.c
479483
#: ports/raspberrypi/common-hal/rp2pio/StateMachine.c
484+
#: ports/raspberrypi/common-hal/usb_host/Port.c
480485
msgid "All state machines in use"
481486
msgstr ""
482487

@@ -1950,10 +1955,6 @@ msgstr ""
19501955
msgid "Size not supported"
19511956
msgstr ""
19521957

1953-
#: ports/raspberrypi/common-hal/alarm/SleepMemory.c
1954-
msgid "Sleep Memory not available"
1955-
msgstr ""
1956-
19571958
#: shared-bindings/alarm/SleepMemory.c shared-bindings/memorymap/AddressRange.c
19581959
#: shared-bindings/nvm/ByteArray.c
19591960
msgid "Slice and value different lengths."

ports/mimxrt10xx/Makefile

+16-9
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,19 @@ include $(TOP)/py/mkrules.mk
229229
print-%:
230230
@echo $* = $($*)
231231

232-
ifeq ($(CHIP_FAMILY), MIMXRT1062)
233-
PYOCD_TARGET = mimxrt1060
234-
endif
235-
236-
# Flash using pyocd
237-
PYOCD_OPTION ?=
238-
flash: $(BUILD)/firmware.hex
239-
pyocd flash -t $(PYOCD_TARGET) $(PYOCD_OPTION) $<
240-
pyocd reset -t $(PYOCD_TARGET)
232+
# Flash using jlink
233+
define jlink_script
234+
halt
235+
loadfile $^
236+
r
237+
go
238+
exit
239+
endef
240+
export jlink_script
241+
242+
JLINKEXE = JLinkExe
243+
flash-jlink: $(BUILD)/firmware.elf
244+
@echo "$$jlink_script" > $(BUILD)/firmware.jlink
245+
$(JLINKEXE) -device $(CHIP_FAMILY)xxx5A -if swd -JTAGConf -1,-1 -speed auto -CommandFile $(BUILD)/firmware.jlink
246+
247+
flash: flash-jlink

ports/mimxrt10xx/boards/imxrt1060_evk/mpconfigboard.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121

2222
// Put host on the first USB so that right angle OTG adapters can fit. This is
2323
// the right port when looking at the board.
24-
#define CIRCUITPY_USB_DEVICE_INSTANCE 1
25-
#define CIRCUITPY_USB_HOST_INSTANCE 0
24+
#define CIRCUITPY_USB_DEVICE_INSTANCE 0
25+
#define CIRCUITPY_USB_HOST_INSTANCE 1

ports/mimxrt10xx/linking/common.ld

+7
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ SECTIONS
7272
*(EXCLUDE_FILE(
7373
*fsl_flexspi.o
7474
*dcd_ci_hs.o
75+
*ehci.o
7576
*tusb_fifo.o
7677
*usbd.o
7778
*string0.o
@@ -88,6 +89,11 @@ SECTIONS
8889
We try to only keep USB interrupt related functions. */
8990
*dcd_ci_hs.o(.text.process_*_request .text.dcd_edpt* .text.dcd_init .text.dcd_set_address)
9091
*usbd.o(.text.process_*_request .text.process_[gs]et* .text.tud_* .text.usbd_* .text.configuration_reset .text.invoke_*)
92+
*ehci.o(.text.hcd_edpt* .text.hcd_setup* .text.ehci_init* .text.hcd_port* .text.hcd_device* .text.qtd_init* .text.list_remove*)
93+
94+
/* Less critical portions of the runtime. */
95+
*runtime.o(.text.mp_import* .text.mp_resume* .text.mp_make_raise* .text.mp_init)
96+
*gc.o(.text.gc_never_free .text.gc_make_long_lived)
9197

9298
/* Anything marked cold/unlikely should be in flash. */
9399
*(.text.unlikely.*)
@@ -146,6 +152,7 @@ SECTIONS
146152
*(.itcm.*)
147153
*fsl_flexspi.o(.text*)
148154
*dcd_ci_hs.o(.text*)
155+
*ehci.o(.text*)
149156
*tusb_fifo.o(.text*)
150157
*py/objboundmeth.o(.text*)
151158
*py/objtype.o(.text*)

ports/raspberrypi/Makefile

+13-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ INC += \
148148
CFLAGS += -DRASPBERRYPI -DPICO_ON_DEVICE=1 -DPICO_NO_BINARY_INFO=0 -DPICO_TIME_DEFAULT_ALARM_POOL_DISABLED=0 -DPICO_DIVIDER_CALL_IDIV0=0 -DPICO_DIVIDER_CALL_LDIV0=0 -DPICO_DIVIDER_HARDWARE=1 -DPICO_DOUBLE_ROM=1 -DPICO_FLOAT_ROM=1 -DPICO_MULTICORE=1 -DPICO_BITS_IN_RAM=0 -DPICO_DIVIDER_IN_RAM=0 -DPICO_DOUBLE_PROPAGATE_NANS=0 -DPICO_DOUBLE_IN_RAM=0 -DPICO_MEM_IN_RAM=0 -DPICO_FLOAT_IN_RAM=0 -DPICO_FLOAT_PROPAGATE_NANS=1 -DPICO_NO_FLASH=0 -DPICO_COPY_TO_RAM=0 -DPICO_DISABLE_SHARED_IRQ_HANDLERS=0 -DPICO_NO_BI_BOOTSEL_VIA_DOUBLE_RESET=0 -DDVI_1BPP_BIT_REVERSE=0
149149
OPTIMIZATION_FLAGS ?= -O3
150150
# TinyUSB defines
151-
CFLAGS += -DTUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX=1 -DCFG_TUSB_MCU=OPT_MCU_RP2040 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_CDC_RX_BUFSIZE=256 -DCFG_TUD_MIDI_TX_BUFSIZE=128 -DCFG_TUD_CDC_TX_BUFSIZE=256 -DCFG_TUD_MSC_BUFSIZE=1024
151+
CFLAGS += -DCFG_TUSB_OS=OPT_OS_PICO -DTUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX=1 -DCFG_TUSB_MCU=OPT_MCU_RP2040 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_CDC_RX_BUFSIZE=256 -DCFG_TUD_MIDI_TX_BUFSIZE=128 -DCFG_TUD_CDC_TX_BUFSIZE=256 -DCFG_TUD_MSC_BUFSIZE=1024
152152

153153
# option to override default optimization level, set in boards/$(BOARD)/mpconfigboard.mk
154154
CFLAGS += $(OPTIMIZATION_FLAGS)
@@ -258,6 +258,18 @@ SRC_C += \
258258
$(SRC_CYW43) \
259259
$(SRC_LWIP) \
260260

261+
262+
ifeq ($(CIRCUITPY_USB_HOST), 1)
263+
SRC_C += \
264+
lib/tinyusb/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c \
265+
lib/Pico-PIO-USB/src/pio_usb.c \
266+
lib/Pico-PIO-USB/src/pio_usb_host.c \
267+
lib/Pico-PIO-USB/src/usb_crc.c \
268+
269+
INC += \
270+
-isystem lib/Pico-PIO-USB/src
271+
endif
272+
261273
ifeq ($(CIRCUITPY_PICODVI),1)
262274
SRC_C += \
263275
bindings/picodvi/__init__.c \

ports/raspberrypi/boards/adafruit_feather_rp2040_usb_host/board.c

+14
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,18 @@
2626

2727
#include "supervisor/board.h"
2828

29+
#include "shared-bindings/digitalio/DigitalInOut.h"
30+
#include "shared-bindings/usb_host/Port.h"
31+
2932
// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here.
33+
34+
usb_host_port_obj_t _host_port;
35+
digitalio_digitalinout_obj_t _host_power;
36+
37+
void board_init(void) {
38+
common_hal_digitalio_digitalinout_construct(&_host_power, &pin_GPIO18);
39+
common_hal_digitalio_digitalinout_never_reset(&_host_power);
40+
common_hal_digitalio_digitalinout_switch_to_output(&_host_power, true, DRIVE_MODE_PUSH_PULL);
41+
42+
common_hal_usb_host_port_construct(&_host_port, &pin_GPIO16, &pin_GPIO17);
43+
}

ports/raspberrypi/boards/adafruit_feather_rp2040_usb_host/mpconfigboard.h

+5
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@
1212

1313
#define DEFAULT_UART_BUS_RX (&pin_GPIO1)
1414
#define DEFAULT_UART_BUS_TX (&pin_GPIO0)
15+
16+
#define CIRCUITPY_CONSOLE_UART_RX DEFAULT_UART_BUS_RX
17+
#define CIRCUITPY_CONSOLE_UART_TX DEFAULT_UART_BUS_TX
18+
19+
#define CIRCUITPY_USB_HOST_INSTANCE 1

ports/raspberrypi/common-hal/rp2pio/StateMachine.h

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int chann
9898
void rp2pio_statemachine_reset_ok(PIO pio, int sm);
9999
void rp2pio_statemachine_never_reset(PIO pio, int sm);
100100

101+
uint8_t rp2pio_statemachine_find_pio(int program_size, int sm_count);
102+
101103
extern const mp_obj_type_t rp2pio_statemachine_type;
102104

103105
#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_RP2PIO_STATEMACHINE_H
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "bindings/rp2pio/StateMachine.h"
28+
#include "shared-bindings/microcontroller/Pin.h"
29+
#include "shared-bindings/microcontroller/Processor.h"
30+
#include "shared-bindings/usb_host/Port.h"
31+
#include "supervisor/usb.h"
32+
33+
#include "src/common/pico_time/include/pico/time.h"
34+
#include "src/rp2040/hardware_structs/include/hardware/structs/mpu.h"
35+
#include "src/rp2_common/cmsis/stub/CMSIS/Device/RaspberryPi/RP2040/Include/RP2040.h"
36+
#include "src/rp2_common/hardware_dma/include/hardware/dma.h"
37+
#include "src/rp2_common/pico_multicore/include/pico/multicore.h"
38+
39+
#include "py/runtime.h"
40+
41+
#include "tusb.h"
42+
43+
#include "lib/Pico-PIO-USB/src/pio_usb.h"
44+
#include "lib/Pico-PIO-USB/src/pio_usb_configuration.h"
45+
46+
#include "supervisor/serial.h"
47+
48+
bool usb_host_init;
49+
50+
STATIC PIO pio_instances[2] = {pio0, pio1};
51+
volatile bool _core1_ready = false;
52+
53+
static void __not_in_flash_func(core1_main)(void) {
54+
// The MPU is reset before this starts.
55+
SysTick->LOAD = (uint32_t)((common_hal_mcu_processor_get_frequency() / 1000) - 1UL);
56+
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
57+
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // Processor clock.
58+
SysTick_CTRL_ENABLE_Msk;
59+
60+
// Turn off flash access. After this, it will hard fault. Better than messing
61+
// up CIRCUITPY.
62+
MPU->CTRL = MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
63+
MPU->RNR = 6; // 7 is used by pico-sdk stack protection.
64+
MPU->RBAR = XIP_MAIN_BASE | MPU_RBAR_VALID_Msk;
65+
MPU->RASR = MPU_RASR_XN_Msk | // Set execute never and everything else is restricted.
66+
MPU_RASR_ENABLE_Msk |
67+
(0x1b << MPU_RASR_SIZE_Pos); // Size is 0x10000000 which masks up to SRAM region.
68+
MPU->RNR = 7;
69+
70+
_core1_ready = true;
71+
72+
while (true) {
73+
pio_usb_host_frame();
74+
if (tuh_task_event_ready()) {
75+
// Queue the tinyusb background task.
76+
usb_background_schedule();
77+
}
78+
// Wait for systick to reload.
79+
while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == 0) {
80+
}
81+
}
82+
}
83+
84+
STATIC uint8_t _sm_free_count(uint8_t pio_index) {
85+
PIO pio = pio_instances[pio_index];
86+
uint8_t free_count = 0;
87+
for (size_t j = 0; j < NUM_PIO_STATE_MACHINES; j++) {
88+
if (!pio_sm_is_claimed(pio, j)) {
89+
free_count++;
90+
}
91+
}
92+
return free_count;
93+
}
94+
95+
STATIC bool _has_program_room(uint8_t pio_index, uint8_t program_size) {
96+
PIO pio = pio_instances[pio_index];
97+
pio_program_t program_struct = {
98+
.instructions = NULL,
99+
.length = program_size,
100+
.origin = -1
101+
};
102+
return pio_can_add_program(pio, &program_struct);
103+
}
104+
105+
void common_hal_usb_host_port_construct(usb_host_port_obj_t *self, const mcu_pin_obj_t *dp, const mcu_pin_obj_t *dm) {
106+
if (dp->number + 1 != dm->number) {
107+
raise_ValueError_invalid_pins();
108+
}
109+
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
110+
pio_cfg.skip_alarm_pool = true;
111+
pio_cfg.pin_dp = dp->number;
112+
pio_cfg.pio_tx_num = 0;
113+
pio_cfg.pio_rx_num = 1;
114+
// PIO with room for 22 instructions
115+
// PIO with room for 31 instructions and two free SM.
116+
if (!_has_program_room(pio_cfg.pio_tx_num, 22) || _sm_free_count(pio_cfg.pio_tx_num) < 1 ||
117+
!_has_program_room(pio_cfg.pio_rx_num, 31) || _sm_free_count(pio_cfg.pio_rx_num) < 2) {
118+
mp_raise_RuntimeError(translate("All state machines in use"));
119+
}
120+
pio_cfg.tx_ch = dma_claim_unused_channel(false); // DMA channel
121+
if (pio_cfg.tx_ch < 0) {
122+
mp_raise_RuntimeError(translate("All dma channels in use"));
123+
}
124+
125+
PIO tx_pio = pio_instances[pio_cfg.pio_tx_num];
126+
pio_cfg.sm_tx = pio_claim_unused_sm(tx_pio, false);
127+
PIO rx_pio = pio_instances[pio_cfg.pio_rx_num];
128+
pio_cfg.sm_rx = pio_claim_unused_sm(rx_pio, false);
129+
pio_cfg.sm_eop = pio_claim_unused_sm(rx_pio, false);
130+
131+
// Unclaim everything so that the library can.
132+
dma_channel_unclaim(pio_cfg.tx_ch);
133+
pio_sm_unclaim(tx_pio, pio_cfg.sm_tx);
134+
pio_sm_unclaim(rx_pio, pio_cfg.sm_rx);
135+
pio_sm_unclaim(rx_pio, pio_cfg.sm_eop);
136+
137+
// Set all of the state machines to never reset.
138+
rp2pio_statemachine_never_reset(tx_pio, pio_cfg.sm_tx);
139+
rp2pio_statemachine_never_reset(rx_pio, pio_cfg.sm_rx);
140+
rp2pio_statemachine_never_reset(rx_pio, pio_cfg.sm_eop);
141+
142+
common_hal_never_reset_pin(dp);
143+
common_hal_never_reset_pin(dm);
144+
145+
// Core 1 will run the SOF interrupt directly.
146+
_core1_ready = false;
147+
multicore_launch_core1(core1_main);
148+
while (!_core1_ready) {
149+
}
150+
151+
tuh_configure(TUH_OPT_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &pio_cfg);
152+
tuh_init(TUH_OPT_RHPORT);
153+
154+
self->init = true;
155+
usb_host_init = true;
156+
}
157+
158+
void common_hal_usb_host_port_deinit(usb_host_port_obj_t *self) {
159+
self->init = false;
160+
usb_host_init = false;
161+
}
162+
163+
bool common_hal_usb_host_port_deinited(usb_host_port_obj_t *self) {
164+
return !self->init;
165+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2023 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#pragma once
28+
29+
#include "py/obj.h"
30+
31+
typedef struct {
32+
mp_obj_base_t base;
33+
bool init;
34+
} usb_host_port_obj_t;
35+
36+
// Cheater state so that the usb module knows if it should return the TinyUSB
37+
// state.
38+
extern bool usb_host_init;

0 commit comments

Comments
 (0)