Skip to content

Commit f967a76

Browse files
committed
Add CDC host
1 parent ba69a59 commit f967a76

File tree

13 files changed

+517
-1
lines changed

13 files changed

+517
-1
lines changed

locale/circuitpython.pot

+4
Original file line numberDiff line numberDiff line change
@@ -2968,6 +2968,10 @@ msgstr ""
29682968
msgid "destination buffer must be an array of type 'H' for bit_depth = 16"
29692969
msgstr ""
29702970

2971+
#: shared-bindings/usb/cdc_host/__init__.c
2972+
msgid "device must be a usb.core.Device object"
2973+
msgstr ""
2974+
29712975
#: py/objdict.c
29722976
msgid "dict update sequence has wrong length"
29732977
msgstr ""

py/circuitpy_defns.mk

+2
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,8 @@ SRC_SHARED_MODULE_ALL = \
785785
usb/__init__.c \
786786
usb/core/__init__.c \
787787
usb/core/Device.c \
788+
usb/cdc_host/Serial.c \
789+
usb/cdc_host/__init__.c \
788790
usb/util/__init__.c \
789791
ustack/__init__.c \
790792
vectorio/Circle.c \

shared-bindings/usb/__init__.c

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "shared-bindings/usb/__init__.h"
1212
#include "shared-bindings/usb/core/__init__.h"
1313
#include "shared-bindings/usb/util/__init__.h"
14+
#include "shared-bindings/usb/cdc_host/__init__.h"
1415
#include "supervisor/usb.h"
1516

1617
//| """PyUSB-compatible USB host API
@@ -23,6 +24,7 @@ static mp_rom_map_elem_t usb_module_globals_table[] = {
2324
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb) },
2425
{ MP_ROM_QSTR(MP_QSTR_core), MP_OBJ_FROM_PTR(&usb_core_module) },
2526
{ MP_ROM_QSTR(MP_QSTR_util), MP_OBJ_FROM_PTR(&usb_util_module) },
27+
{ MP_ROM_QSTR(MP_QSTR_cdc_host), MP_OBJ_FROM_PTR(&usb_cdc_host_module) },
2628
};
2729

2830
static MP_DEFINE_CONST_DICT(usb_module_globals, usb_module_globals_table);

shared-bindings/usb/cdc_host/Serial.c

+274
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: 2025 rianadon
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include "py/stream.h"
8+
#include "py/objproperty.h"
9+
#include "py/runtime.h"
10+
#include "py/stream.h"
11+
12+
#include "shared-bindings/usb/cdc_host/Serial.h"
13+
14+
#include "tusb.h"
15+
#include "class/cdc/cdc_host.h"
16+
17+
//| class Serial:
18+
//| """Receives cdc commands over USB"""
19+
//|
20+
//| def __init__(self, device: Device) -> None:
21+
//| """You cannot create an instance of `usb_cdc.Serial`.
22+
//| The available instances are in the ``usb_cdc.serials`` tuple."""
23+
//| ...
24+
//|
25+
//| def read(self, size: int = -1) -> bytes:
26+
//| """Read at most ``size`` bytes. If ``size`` exceeds the internal buffer size,
27+
//| only the bytes in the buffer will be read. If ``size`` is not specified or is ``-1``,
28+
//| read as many bytes as possible, until the timeout expires.
29+
//| If `timeout` is > 0 or ``None``, and fewer than ``size`` bytes are available,
30+
//| keep waiting until the timeout expires or ``size`` bytes are available.
31+
//|
32+
//| If no bytes are read, return ``b''``. This is unlike, say, `busio.UART.read()`, which
33+
//| would return ``None``.
34+
//|
35+
//| :return: Data read
36+
//| :rtype: bytes"""
37+
//| ...
38+
//|
39+
//| def readinto(self, buf: WriteableBuffer) -> int:
40+
//| """Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. If `timeout`
41+
//| is > 0 or ``None``, keep waiting until the timeout expires or ``len(buf)``
42+
//| bytes are available.
43+
//|
44+
//| :return: number of bytes read and stored into ``buf``
45+
//| :rtype: int"""
46+
//| ...
47+
//|
48+
//| def readline(self, size: int = -1) -> Optional[bytes]:
49+
//| r"""Read a line ending in a newline character ("\\n"), including the newline.
50+
//| Return everything readable if no newline is found and ``timeout`` is 0.
51+
//| Return ``None`` in case of error.
52+
//|
53+
//| This is a binary stream: the newline character "\\n" cannot be changed.
54+
//| If the host computer transmits "\\r" it will also be included as part of the line.
55+
//|
56+
//| :param int size: maximum number of characters to read. ``-1`` means as many as possible.
57+
//| :return: the line read
58+
//| :rtype: bytes or None"""
59+
//| ...
60+
//|
61+
//| def readlines(self) -> List[Optional[bytes]]:
62+
//| """Read multiple lines as a list, using `readline()`.
63+
//|
64+
//| .. warning:: If ``timeout`` is ``None``,
65+
//| `readlines()` will never return, because there is no way to indicate end of stream.
66+
//|
67+
//| :return: a list of the line read
68+
//| :rtype: list"""
69+
//| ...
70+
//|
71+
//| def write(self, buf: ReadableBuffer) -> int:
72+
//| """Write as many bytes as possible from the buffer of bytes.
73+
//|
74+
//| :return: the number of bytes written
75+
//| :rtype: int"""
76+
//| ...
77+
//|
78+
//| def flush(self) -> None:
79+
//| """Force out any unwritten bytes, waiting until they are written."""
80+
//| ...
81+
//|
82+
83+
static mp_uint_t usb_host_cdc_serial_read_stream(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
84+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
85+
byte *buf = buf_in;
86+
87+
if (size == 0) {
88+
return 0;
89+
}
90+
91+
return common_hal_usb_host_cdc_serial_read(self, buf, size, errcode);
92+
}
93+
94+
static mp_uint_t usb_host_cdc_serial_write_stream(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
95+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
96+
const byte *buf = buf_in;
97+
98+
return common_hal_usb_host_cdc_serial_write(self, buf, size, errcode);
99+
}
100+
101+
static mp_uint_t usb_host_cdc_serial_ioctl_stream(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) {
102+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
103+
mp_uint_t ret = 0;
104+
switch (request) {
105+
case MP_STREAM_POLL: {
106+
mp_uint_t flags = arg;
107+
ret = 0;
108+
if ((flags & MP_STREAM_POLL_RD) && common_hal_usb_host_cdc_serial_get_in_waiting(self) > 0) {
109+
ret |= MP_STREAM_POLL_RD;
110+
}
111+
if ((flags & MP_STREAM_POLL_WR) && common_hal_usb_host_cdc_serial_get_out_waiting(self) < CFG_TUH_CDC_TX_BUFSIZE) {
112+
ret |= MP_STREAM_POLL_WR;
113+
}
114+
break;
115+
}
116+
117+
case MP_STREAM_FLUSH:
118+
common_hal_usb_host_cdc_serial_flush(self);
119+
ret = 0;
120+
break;
121+
122+
default:
123+
*errcode = MP_EINVAL;
124+
ret = MP_STREAM_ERROR;
125+
}
126+
return ret;
127+
}
128+
129+
// connected property
130+
//| connected: bool
131+
//| """True if this Serial object represents a mounted CDC device
132+
//| and the remote device is asserting DTR (Data Terminal Ready). (read-only)
133+
//| """
134+
static mp_obj_t usb_host_cdc_serial_get_connected(mp_obj_t self_in) {
135+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
136+
return mp_obj_new_bool(common_hal_usb_host_cdc_serial_get_connected(self));
137+
}
138+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_connected_obj, usb_host_cdc_serial_get_connected);
139+
140+
MP_PROPERTY_GETTER(usb_host_cdc_serial_connected_obj,
141+
(mp_obj_t)&usb_host_cdc_serial_get_connected_obj);
142+
143+
// in_waiting property
144+
//| in_waiting: int
145+
//| """Returns the number of bytes waiting to be read from the
146+
//| CDC device's input buffer. (read-only)"""
147+
static mp_obj_t usb_host_cdc_serial_get_in_waiting(mp_obj_t self_in) {
148+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
149+
return mp_obj_new_int(common_hal_usb_host_cdc_serial_get_in_waiting(self));
150+
}
151+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_in_waiting_obj, usb_host_cdc_serial_get_in_waiting);
152+
153+
MP_PROPERTY_GETTER(usb_host_cdc_serial_in_waiting_obj,
154+
(mp_obj_t)&usb_host_cdc_serial_get_in_waiting_obj);
155+
156+
// out_waiting property
157+
//| out_waiting: int
158+
//| """Returns the number of bytes waiting to be written to the
159+
//| CDC device's output buffer. (read-only)"""
160+
static mp_obj_t usb_host_cdc_serial_get_out_waiting(mp_obj_t self_in) {
161+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
162+
return mp_obj_new_int(common_hal_usb_host_cdc_serial_get_out_waiting(self));
163+
}
164+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_out_waiting_obj, usb_host_cdc_serial_get_out_waiting);
165+
166+
MP_PROPERTY_GETTER(usb_host_cdc_serial_out_waiting_obj,
167+
(mp_obj_t)&usb_host_cdc_serial_get_out_waiting_obj);
168+
169+
// reset_input_buffer method
170+
//| def reset_input_buffer(self) -> None:
171+
//| """Clears any unread bytes from the input buffer."""
172+
//| ...
173+
static mp_obj_t usb_host_cdc_serial_reset_input_buffer(mp_obj_t self_in) {
174+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
175+
common_hal_usb_host_cdc_serial_reset_input_buffer(self);
176+
return mp_const_none;
177+
}
178+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_reset_input_buffer_obj, usb_host_cdc_serial_reset_input_buffer);
179+
180+
// reset_output_buffer method
181+
//| def reset_output_buffer(self) -> None:
182+
//| """Clears any unwritten bytes from the output buffer."""
183+
//| ...
184+
static mp_obj_t usb_host_cdc_serial_reset_output_buffer(mp_obj_t self_in) {
185+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
186+
common_hal_usb_host_cdc_serial_reset_output_buffer(self);
187+
return mp_const_none; // Standard method returns None
188+
}
189+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_reset_output_buffer_obj, usb_host_cdc_serial_reset_output_buffer);
190+
191+
// timeout property
192+
//| timeout: Optional[float]
193+
//| """The read timeout value in seconds. `None means wait indefinitely.//| 0 means non-blocking. Positive value is the timeout in seconds."""
194+
static mp_obj_t usb_host_cdc_serial_get_timeout(mp_obj_t self_in) {
195+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
196+
mp_float_t timeout = common_hal_usb_host_cdc_serial_get_timeout(self);
197+
return (timeout < 0.0f) ? mp_const_none : mp_obj_new_float(timeout);
198+
}
199+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_timeout_obj, usb_host_cdc_serial_get_timeout);
200+
201+
static mp_obj_t usb_host_cdc_serial_set_timeout(mp_obj_t self_in, mp_obj_t timeout_in) {
202+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
203+
common_hal_usb_host_cdc_serial_set_timeout(self,
204+
timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(timeout_in));
205+
return mp_const_none;
206+
}
207+
MP_DEFINE_CONST_FUN_OBJ_2(usb_host_cdc_serial_set_timeout_obj, usb_host_cdc_serial_set_timeout);
208+
209+
MP_PROPERTY_GETSET(usb_host_cdc_serial_timeout_obj,
210+
(mp_obj_t)&usb_host_cdc_serial_get_timeout_obj,
211+
(mp_obj_t)&usb_host_cdc_serial_set_timeout_obj);
212+
213+
// write_timeout property
214+
//| write_timeout: Optional[float]
215+
//| """The write timeout value in seconds. `None means wait indefinitely.//| 0 means non-blocking. Positive value is the timeout in seconds."""
216+
static mp_obj_t usb_host_cdc_serial_get_write_timeout(mp_obj_t self_in) {
217+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
218+
mp_float_t write_timeout = common_hal_usb_host_cdc_serial_get_write_timeout(self);
219+
return (write_timeout < 0.0f) ? mp_const_none : mp_obj_new_float(write_timeout);
220+
}
221+
MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_write_timeout_obj, usb_host_cdc_serial_get_write_timeout);
222+
223+
static mp_obj_t usb_host_cdc_serial_set_write_timeout(mp_obj_t self_in, mp_obj_t write_timeout_in) {
224+
usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
225+
common_hal_usb_host_cdc_serial_set_write_timeout(self,
226+
write_timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(write_timeout_in));
227+
return mp_const_none;
228+
}
229+
MP_DEFINE_CONST_FUN_OBJ_2(usb_host_cdc_serial_set_write_timeout_obj, usb_host_cdc_serial_set_write_timeout);
230+
231+
MP_PROPERTY_GETSET(usb_host_cdc_serial_write_timeout_obj,
232+
(mp_obj_t)&usb_host_cdc_serial_get_write_timeout_obj,
233+
(mp_obj_t)&usb_host_cdc_serial_set_write_timeout_obj);
234+
235+
236+
static const mp_rom_map_elem_t usb_host_cdc_serial_locals_dict_table[] = {
237+
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
238+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
239+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
240+
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)},
241+
{ MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)},
242+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
243+
244+
{ MP_ROM_QSTR(MP_QSTR_in_waiting), MP_ROM_PTR(&usb_host_cdc_serial_in_waiting_obj) },
245+
{ MP_ROM_QSTR(MP_QSTR_out_waiting), MP_ROM_PTR(&usb_host_cdc_serial_out_waiting_obj) },
246+
{ MP_ROM_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&usb_host_cdc_serial_reset_input_buffer_obj) },
247+
{ MP_ROM_QSTR(MP_QSTR_reset_output_buffer), MP_ROM_PTR(&usb_host_cdc_serial_reset_output_buffer_obj) },
248+
{ MP_ROM_QSTR(MP_QSTR_timeout), MP_ROM_PTR(&usb_host_cdc_serial_timeout_obj) },
249+
{ MP_ROM_QSTR(MP_QSTR_write_timeout), MP_ROM_PTR(&usb_host_cdc_serial_write_timeout_obj) },
250+
251+
{ MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_host_cdc_serial_connected_obj) },
252+
253+
// TODO: Add baudrate, data_bits, parity, stop_bits properties/methods.
254+
};
255+
static MP_DEFINE_CONST_DICT(usb_host_cdc_serial_locals_dict, usb_host_cdc_serial_locals_dict_table);
256+
257+
static const mp_stream_p_t usb_host_cdc_serial_stream_p = {
258+
.read = usb_host_cdc_serial_read_stream,
259+
.write = usb_host_cdc_serial_write_stream,
260+
.ioctl = usb_host_cdc_serial_ioctl_stream,
261+
.is_text = false,
262+
.pyserial_read_compatibility = true,
263+
.pyserial_readinto_compatibility = true,
264+
.pyserial_dont_return_none_compatibility = true,
265+
};
266+
267+
MP_DEFINE_CONST_OBJ_TYPE(
268+
usb_cdc_host_serial_type,
269+
MP_QSTR_Serial,
270+
MP_TYPE_FLAG_ITER_IS_ITERNEXT | MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
271+
locals_dict, &usb_host_cdc_serial_locals_dict,
272+
iter, mp_stream_unbuffered_iter,
273+
protocol, &usb_host_cdc_serial_stream_p
274+
);

shared-bindings/usb/cdc_host/Serial.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: 2025 rianadon
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "shared-module/usb/cdc_host/Serial.h"
10+
11+
extern const mp_obj_type_t usb_cdc_host_serial_type;
12+
13+
size_t common_hal_usb_host_cdc_serial_read(usb_cdc_host_serial_obj_t *self, uint8_t *data, size_t len, int *errcode);
14+
size_t common_hal_usb_host_cdc_serial_write(usb_cdc_host_serial_obj_t *self, const uint8_t *data, size_t len, int *errcode);
15+
16+
uint32_t common_hal_usb_host_cdc_serial_get_in_waiting(usb_cdc_host_serial_obj_t *self);
17+
uint32_t common_hal_usb_host_cdc_serial_get_out_waiting(usb_cdc_host_serial_obj_t *self);
18+
19+
void common_hal_usb_host_cdc_serial_reset_input_buffer(usb_cdc_host_serial_obj_t *self);
20+
uint32_t common_hal_usb_host_cdc_serial_reset_output_buffer(usb_cdc_host_serial_obj_t *self);
21+
22+
uint32_t common_hal_usb_host_cdc_serial_flush(usb_cdc_host_serial_obj_t *self);
23+
24+
bool common_hal_usb_host_cdc_serial_get_connected(usb_cdc_host_serial_obj_t *self);
25+
26+
mp_float_t common_hal_usb_host_cdc_serial_get_timeout(usb_cdc_host_serial_obj_t *self);
27+
void common_hal_usb_host_cdc_serial_set_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t timeout);
28+
29+
mp_float_t common_hal_usb_host_cdc_serial_get_write_timeout(usb_cdc_host_serial_obj_t *self);
30+
void common_hal_usb_host_cdc_serial_set_write_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t write_timeout);
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: 2025 rianadon
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <stdint.h>
8+
9+
#include "py/obj.h"
10+
#include "py/objmodule.h"
11+
#include "py/runtime.h"
12+
13+
#include "shared-bindings/usb/cdc_host/Serial.h"
14+
#include "shared-bindings/usb/core/Device.h"
15+
16+
#include "tusb.h"
17+
#include "class/cdc/cdc_host.h"
18+
19+
static mp_obj_t usb_cdc_host_find(mp_obj_t device_in, mp_obj_t interface_in) {
20+
if (!mp_obj_is_type(device_in, &usb_core_device_type)) {
21+
mp_raise_TypeError(MP_ERROR_TEXT("device must be a usb.core.Device object"));
22+
}
23+
24+
usb_core_device_obj_t *device_obj = MP_OBJ_TO_PTR(device_in);
25+
uint8_t daddr = device_obj->device_address;
26+
27+
mp_int_t interface_num = mp_obj_get_int(interface_in);
28+
uint8_t cdc_idx = tuh_cdc_itf_get_index(daddr, (uint8_t)interface_num);
29+
30+
if (cdc_idx == TUSB_INDEX_INVALID_8) {
31+
return mp_const_none;
32+
}
33+
34+
usb_cdc_host_serial_obj_t *serial_obj = mp_obj_malloc(usb_cdc_host_serial_obj_t, &usb_cdc_host_serial_type);
35+
serial_obj->idx = cdc_idx;
36+
serial_obj->timeout = -1.0f;
37+
serial_obj->write_timeout = -1.0f;
38+
39+
return MP_OBJ_FROM_PTR(serial_obj);
40+
}
41+
42+
static MP_DEFINE_CONST_FUN_OBJ_2(usb_cdc_host_find_obj, usb_cdc_host_find);
43+
44+
static const mp_rom_map_elem_t usb_cdc_host_module_globals_table[] = {
45+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usb_dot_cdc_host) },
46+
{ MP_ROM_QSTR(MP_QSTR_Serial), MP_ROM_PTR(&usb_cdc_host_serial_type) },
47+
{ MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&usb_cdc_host_find_obj) },
48+
};
49+
50+
static MP_DEFINE_CONST_DICT(usb_cdc_host_module_globals, usb_cdc_host_module_globals_table);
51+
52+
const mp_obj_module_t usb_cdc_host_module = {
53+
.base = { &mp_type_module },
54+
.globals = (mp_obj_dict_t *)&usb_cdc_host_module_globals,
55+
};
56+
57+
MP_REGISTER_MODULE(MP_QSTR_usb_dot_cdc_host, usb_cdc_host_module);

0 commit comments

Comments
 (0)