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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 274 additions & 0 deletions
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

Lines changed: 30 additions & 0 deletions
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);
Lines changed: 57 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy