Skip to content

Commit f03091c

Browse files
committed
shared/tinyusb: Add USB Host mode support.
This adds support for USB Host mode using TinyUSB, complementing existing USB Device mode support. Key additions: 1. Implement machine.USBHost class for managing USB host functionality 2. Implement USBH_CDC class for communication with CDC devices 3. Documentation and examples for using USB Host mode USB Host mode must be enabled in a port's configuration with MICROPY_HW_USB_HOST (1), and requires that the port calls mp_usbh_task() in its main task loop to process USB events. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent f315a37 commit f03091c

File tree

5 files changed

+1308
-0
lines changed

5 files changed

+1308
-0
lines changed

docs/library/machine.USBHost.rst

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
.. currentmodule:: machine
2+
.. _machine.USBHost:
3+
4+
class USBHost -- USB Host controller
5+
====================================
6+
7+
.. note:: ``machine.USBHost`` is currently only available on select ports with TinyUSB and
8+
hardware USB host controller support.
9+
10+
The USBHost class provides a way to connect to and communicate with USB devices connected to the
11+
microcontroller's USB host port.
12+
13+
Constructors
14+
------------
15+
16+
.. class:: USBHost()
17+
18+
Create a USBHost object.
19+
20+
Methods
21+
-------
22+
23+
.. method:: USBHost.active([value])
24+
25+
With no arguments, get the active state of the USB host.
26+
27+
With ``value`` set to ``True``, enable the USB host.
28+
29+
With ``value`` set to ``False``, disable the USB host.
30+
31+
Example::
32+
33+
# Enable USB host
34+
host = machine.USBHost()
35+
host.active(True)
36+
37+
# Check if USB host is active
38+
print(host.active()) # Returns True
39+
40+
.. method:: USBHost.devices()
41+
42+
Returns a list of connected USB devices.
43+
44+
Each device in the returned list is a USBDevice object with properties for
45+
the device attributes.
46+
47+
Example::
48+
49+
# Get list of connected devices
50+
devices = host.devices()
51+
for dev in devices:
52+
print(f"Device: VID=0x{dev.vid:04x}, PID=0x{dev.pid:04x}")
53+
if dev.manufacturer:
54+
print(f" Manufacturer: {dev.manufacturer}")
55+
if dev.product:
56+
print(f" Product: {dev.product}")
57+
if dev.serial:
58+
print(f" Serial: {dev.serial}")
59+
60+
.. method:: USBHost.cdc_devices()
61+
62+
Returns a list of connected CDC devices (virtual serial ports).
63+
64+
Each device in the returned list is a USBH_CDC object which implements the
65+
stream interface.
66+
67+
Example::
68+
69+
# Get list of CDC devices
70+
cdc_devices = host.cdc_devices()
71+
if cdc_devices:
72+
# Use the first CDC device
73+
cdc = cdc_devices[0]
74+
cdc.write(b'Hello from MicroPython!\r\n')
75+
if cdc.any():
76+
data = cdc.read(64)
77+
print(f"Received: {data}")
78+
79+
.. method:: USBHost.msc_devices()
80+
81+
Returns a list of connected MSC devices (Mass Storage Class, like USB flash drives).
82+
83+
Each device in the returned list is a USBH_MSC object which implements the
84+
block device interface for use with a filesystem.
85+
86+
Example::
87+
88+
# Get list of MSC devices
89+
msc_devices = host.msc_devices()
90+
if msc_devices:
91+
# Use the first MSC device
92+
msc = msc_devices[0]
93+
if msc.is_connected():
94+
# Mount the device as a filesystem
95+
import vfs
96+
vfs.mount(msc, '/usb')
97+
98+
# List files
99+
import os
100+
print(os.listdir('/usb'))
101+
102+
USBDevice class
103+
--------------
104+
105+
.. class:: USBDevice
106+
107+
This class is not meant to be instantiated directly, but is returned by
108+
``USBHost.devices()``.
109+
110+
.. method:: USBDevice.vid
111+
112+
Returns the device's Vendor ID.
113+
114+
.. method:: USBDevice.pid
115+
116+
Returns the device's Product ID.
117+
118+
.. method:: USBDevice.serial
119+
120+
Returns the device's serial number as a string, or ``None`` if not available.
121+
122+
.. method:: USBDevice.manufacturer
123+
124+
Returns the device's manufacturer string, or ``None`` if not available.
125+
126+
.. method:: USBDevice.product
127+
128+
Returns the device's product string, or ``None`` if not available.
129+
130+
USBH_CDC class
131+
-------------
132+
133+
.. class:: USBH_CDC
134+
135+
This class is not meant to be instantiated directly, but is returned by
136+
``USBHost.cdc_devices()``. It implements the :term:`stream` interface.
137+
138+
.. method:: USBH_CDC.is_connected()
139+
140+
Returns ``True`` if the CDC device is still connected, ``False`` otherwise.
141+
142+
.. method:: USBH_CDC.any()
143+
144+
Returns the number of bytes available to read from the CDC device.
145+
146+
.. method:: USBH_CDC.read(size)
147+
148+
Read up to ``size`` bytes from the CDC device. Returns a bytes object with
149+
the data read.
150+
151+
.. method:: USBH_CDC.write(data)
152+
153+
Write ``data`` to the CDC device. Returns the number of bytes written.
154+
155+
.. method:: USBH_CDC.irq(handler=None, trigger=IRQ_RX, hard=False)
156+
157+
Set a callback to be triggered when data is received.
158+
159+
- ``handler`` is the function to call when an event occurs.
160+
- ``trigger`` is the event that triggers the callback.
161+
162+
The handler will be passed the CDC device object.
163+
164+
Example::
165+
166+
def on_rx(cdc):
167+
print(f"Data available: {cdc.any()} bytes")
168+
if cdc.any():
169+
print(f"Received: {cdc.read(64)}")
170+
171+
cdc.irq(handler=on_rx, trigger=cdc.IRQ_RX)
172+
173+
USBH_MSC class
174+
-------------
175+
176+
.. class:: USBH_MSC
177+
178+
This class is not meant to be instantiated directly, but is returned by
179+
``USBHost.msc_devices()``. It implements the block device protocol for use
180+
with filesystem operations.
181+
182+
.. method:: USBH_MSC.is_connected()
183+
184+
Returns ``True`` if the MSC device is still connected, ``False`` otherwise.
185+
186+
.. method:: USBH_MSC.readblocks(block_num, buf, offset=0)
187+
188+
Read data from the specified block into ``buf``.
189+
190+
- ``block_num`` is the block number to read from
191+
- ``buf`` is a buffer to store the data in
192+
- ``offset`` is the offset within the block to start reading from
193+
194+
This method implements part of the block device protocol and is typically
195+
used by the filesystem driver rather than called directly.
196+
197+
.. method:: USBH_MSC.writeblocks(block_num, buf, offset=0)
198+
199+
Write data from ``buf`` to the specified block.
200+
201+
- ``block_num`` is the block number to write to
202+
- ``buf`` is a buffer containing the data to write
203+
- ``offset`` is the offset within the block to start writing to
204+
205+
This method implements part of the block device protocol and is typically
206+
used by the filesystem driver rather than called directly.
207+
208+
.. method:: USBH_MSC.ioctl(cmd, arg)
209+
210+
Control the block device.
211+
212+
- ``cmd`` is the command to perform
213+
- ``arg`` is an optional argument
214+
215+
This method implements part of the block device protocol and is typically
216+
used by the filesystem driver rather than called directly.
217+
218+
Common commands include:
219+
220+
- ``MP_BLOCKDEV_IOCTL_INIT`` - Initialize the device
221+
- ``MP_BLOCKDEV_IOCTL_DEINIT`` - Deinitialize the device
222+
- ``MP_BLOCKDEV_IOCTL_SYNC`` - Synchronize/flush cached data
223+
- ``MP_BLOCKDEV_IOCTL_BLOCK_COUNT`` - Get total block count
224+
- ``MP_BLOCKDEV_IOCTL_BLOCK_SIZE`` - Get block size
225+
226+
Example using the MSC device with a filesystem::
227+
228+
# Mount the MSC device with a FAT filesystem
229+
import vfs
230+
msc = host.msc_devices()[0]
231+
vfs.VfsFat.mkfs(msc) # Format if needed
232+
vfs.mount(msc, '/usb')
233+
234+
# Use the filesystem
235+
import os
236+
os.listdir('/usb')
237+
238+
# Write a file
239+
with open('/usb/test.txt', 'w') as f:
240+
f.write('Hello from MicroPython!')
241+
242+
# Read a file
243+
with open('/usb/test.txt', 'r') as f:
244+
print(f.read())
245+
246+
Constants
247+
---------
248+
249+
.. data:: USBH_CDC.IRQ_RX
250+
251+
Constant used with ``irq()`` method to trigger on data reception.
252+
253+
Hardware Integration
254+
-------------------
255+
256+
USB Host mode requires periodic processing of USB events. This is typically done
257+
in the port's main task loop. For example:
258+
259+
.. code-block:: c
260+
261+
// In the port's main task loop
262+
for (;;) {
263+
#if MICROPY_HW_USB_HOST
264+
// Process USB host events
265+
mp_usbh_task();
266+
#endif
267+
268+
// Other processing
269+
...
270+
}

docs/library/machine.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,4 @@ Classes
271271
machine.SD.rst
272272
machine.SDCard.rst
273273
machine.USBDevice.rst
274+
machine.USBHost.rst

extmod/modmachine.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
#include "extmod/modmachine.h"
3333
#include "shared/runtime/pyexec.h"
3434

35+
#if MICROPY_HW_USB_HOST
36+
#include "shared/tinyusb/mp_usbh.h"
37+
#endif
38+
3539
#if MICROPY_PY_MACHINE_DHT_READINTO
3640
#include "drivers/dht/dht.h"
3741
#endif
@@ -234,6 +238,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = {
234238
#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
235239
{ MP_ROM_QSTR(MP_QSTR_USBDevice), MP_ROM_PTR(&machine_usb_device_type) },
236240
#endif
241+
#if MICROPY_HW_USB_HOST
242+
{ MP_ROM_QSTR(MP_QSTR_USBHost), MP_ROM_PTR(&machine_usb_host_type) },
243+
#endif
237244
#if MICROPY_PY_MACHINE_WDT
238245
{ MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) },
239246
#endif

0 commit comments

Comments
 (0)