diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 6beb320527e43..8e3379252549e 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2968,6 +2968,10 @@ msgstr "" msgid "destination buffer must be an array of type 'H' for bit_depth = 16" msgstr "" +#: shared-bindings/usb/cdc_host/__init__.c +msgid "device must be a usb.core.Device object" +msgstr "" + #: py/objdict.c msgid "dict update sequence has wrong length" msgstr "" diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index fa90481e648ea..1f2dac4c3ecee 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -785,6 +785,8 @@ SRC_SHARED_MODULE_ALL = \ usb/__init__.c \ usb/core/__init__.c \ usb/core/Device.c \ + usb/cdc_host/Serial.c \ + usb/cdc_host/__init__.c \ usb/util/__init__.c \ ustack/__init__.c \ vectorio/Circle.c \ diff --git a/shared-bindings/usb/__init__.c b/shared-bindings/usb/__init__.c index ea05229984c97..4ee289960ac57 100644 --- a/shared-bindings/usb/__init__.c +++ b/shared-bindings/usb/__init__.c @@ -11,6 +11,7 @@ #include "shared-bindings/usb/__init__.h" #include "shared-bindings/usb/core/__init__.h" #include "shared-bindings/usb/util/__init__.h" +#include "shared-bindings/usb/cdc_host/__init__.h" #include "supervisor/usb.h" //| """PyUSB-compatible USB host API @@ -23,6 +24,7 @@ static mp_rom_map_elem_t usb_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb) }, { MP_ROM_QSTR(MP_QSTR_core), MP_OBJ_FROM_PTR(&usb_core_module) }, { MP_ROM_QSTR(MP_QSTR_util), MP_OBJ_FROM_PTR(&usb_util_module) }, + { MP_ROM_QSTR(MP_QSTR_cdc_host), MP_OBJ_FROM_PTR(&usb_cdc_host_module) }, }; static MP_DEFINE_CONST_DICT(usb_module_globals, usb_module_globals_table); diff --git a/shared-bindings/usb/cdc_host/Serial.c b/shared-bindings/usb/cdc_host/Serial.c new file mode 100644 index 0000000000000..a018b471ed2e9 --- /dev/null +++ b/shared-bindings/usb/cdc_host/Serial.c @@ -0,0 +1,274 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#include "py/stream.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/stream.h" + +#include "shared-bindings/usb/cdc_host/Serial.h" + +#include "tusb.h" +#include "class/cdc/cdc_host.h" + +//| class Serial: +//| """Receives cdc commands over USB""" +//| +//| def __init__(self, device: Device) -> None: +//| """You cannot create an instance of `usb_cdc.Serial`. +//| The available instances are in the ``usb_cdc.serials`` tuple.""" +//| ... +//| +//| def read(self, size: int = -1) -> bytes: +//| """Read at most ``size`` bytes. If ``size`` exceeds the internal buffer size, +//| only the bytes in the buffer will be read. If ``size`` is not specified or is ``-1``, +//| read as many bytes as possible, until the timeout expires. +//| If `timeout` is > 0 or ``None``, and fewer than ``size`` bytes are available, +//| keep waiting until the timeout expires or ``size`` bytes are available. +//| +//| If no bytes are read, return ``b''``. This is unlike, say, `busio.UART.read()`, which +//| would return ``None``. +//| +//| :return: Data read +//| :rtype: bytes""" +//| ... +//| +//| def readinto(self, buf: WriteableBuffer) -> int: +//| """Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. If `timeout` +//| is > 0 or ``None``, keep waiting until the timeout expires or ``len(buf)`` +//| bytes are available. +//| +//| :return: number of bytes read and stored into ``buf`` +//| :rtype: int""" +//| ... +//| +//| def readline(self, size: int = -1) -> Optional[bytes]: +//| r"""Read a line ending in a newline character ("\\n"), including the newline. +//| Return everything readable if no newline is found and ``timeout`` is 0. +//| Return ``None`` in case of error. +//| +//| This is a binary stream: the newline character "\\n" cannot be changed. +//| If the host computer transmits "\\r" it will also be included as part of the line. +//| +//| :param int size: maximum number of characters to read. ``-1`` means as many as possible. +//| :return: the line read +//| :rtype: bytes or None""" +//| ... +//| +//| def readlines(self) -> List[Optional[bytes]]: +//| """Read multiple lines as a list, using `readline()`. +//| +//| .. warning:: If ``timeout`` is ``None``, +//| `readlines()` will never return, because there is no way to indicate end of stream. +//| +//| :return: a list of the line read +//| :rtype: list""" +//| ... +//| +//| def write(self, buf: ReadableBuffer) -> int: +//| """Write as many bytes as possible from the buffer of bytes. +//| +//| :return: the number of bytes written +//| :rtype: int""" +//| ... +//| +//| def flush(self) -> None: +//| """Force out any unwritten bytes, waiting until they are written.""" +//| ... +//| + +static mp_uint_t usb_host_cdc_serial_read_stream(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + byte *buf = buf_in; + + if (size == 0) { + return 0; + } + + return common_hal_usb_host_cdc_serial_read(self, buf, size, errcode); +} + +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) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + const byte *buf = buf_in; + + return common_hal_usb_host_cdc_serial_write(self, buf, size, errcode); +} + +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) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t ret = 0; + switch (request) { + case MP_STREAM_POLL: { + mp_uint_t flags = arg; + ret = 0; + if ((flags & MP_STREAM_POLL_RD) && common_hal_usb_host_cdc_serial_get_in_waiting(self) > 0) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && common_hal_usb_host_cdc_serial_get_out_waiting(self) < CFG_TUH_CDC_TX_BUFSIZE) { + ret |= MP_STREAM_POLL_WR; + } + break; + } + + case MP_STREAM_FLUSH: + common_hal_usb_host_cdc_serial_flush(self); + ret = 0; + break; + + default: + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +// connected property +//| connected: bool +//| """True if this Serial object represents a mounted CDC device +//| and the remote device is asserting DTR (Data Terminal Ready). (read-only) +//| """ +static mp_obj_t usb_host_cdc_serial_get_connected(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(common_hal_usb_host_cdc_serial_get_connected(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_connected_obj, usb_host_cdc_serial_get_connected); + +MP_PROPERTY_GETTER(usb_host_cdc_serial_connected_obj, + (mp_obj_t)&usb_host_cdc_serial_get_connected_obj); + +// in_waiting property +//| in_waiting: int +//| """Returns the number of bytes waiting to be read from the +//| CDC device's input buffer. (read-only)""" +static mp_obj_t usb_host_cdc_serial_get_in_waiting(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(common_hal_usb_host_cdc_serial_get_in_waiting(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_in_waiting_obj, usb_host_cdc_serial_get_in_waiting); + +MP_PROPERTY_GETTER(usb_host_cdc_serial_in_waiting_obj, + (mp_obj_t)&usb_host_cdc_serial_get_in_waiting_obj); + +// out_waiting property +//| out_waiting: int +//| """Returns the number of bytes waiting to be written to the +//| CDC device's output buffer. (read-only)""" +static mp_obj_t usb_host_cdc_serial_get_out_waiting(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(common_hal_usb_host_cdc_serial_get_out_waiting(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_out_waiting_obj, usb_host_cdc_serial_get_out_waiting); + +MP_PROPERTY_GETTER(usb_host_cdc_serial_out_waiting_obj, + (mp_obj_t)&usb_host_cdc_serial_get_out_waiting_obj); + +// reset_input_buffer method +//| def reset_input_buffer(self) -> None: +//| """Clears any unread bytes from the input buffer.""" +//| ... +static mp_obj_t usb_host_cdc_serial_reset_input_buffer(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_reset_input_buffer(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_reset_input_buffer_obj, usb_host_cdc_serial_reset_input_buffer); + +// reset_output_buffer method +//| def reset_output_buffer(self) -> None: +//| """Clears any unwritten bytes from the output buffer.""" +//| ... +static mp_obj_t usb_host_cdc_serial_reset_output_buffer(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_reset_output_buffer(self); + return mp_const_none; // Standard method returns None +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_reset_output_buffer_obj, usb_host_cdc_serial_reset_output_buffer); + +// timeout property +//| timeout: Optional[float] +//| """The read timeout value in seconds. `None means wait indefinitely.//| 0 means non-blocking. Positive value is the timeout in seconds.""" +static mp_obj_t usb_host_cdc_serial_get_timeout(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_float_t timeout = common_hal_usb_host_cdc_serial_get_timeout(self); + return (timeout < 0.0f) ? mp_const_none : mp_obj_new_float(timeout); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_timeout_obj, usb_host_cdc_serial_get_timeout); + +static mp_obj_t usb_host_cdc_serial_set_timeout(mp_obj_t self_in, mp_obj_t timeout_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_set_timeout(self, + timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(timeout_in)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(usb_host_cdc_serial_set_timeout_obj, usb_host_cdc_serial_set_timeout); + +MP_PROPERTY_GETSET(usb_host_cdc_serial_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_get_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_set_timeout_obj); + +// write_timeout property +//| write_timeout: Optional[float] +//| """The write timeout value in seconds. `None means wait indefinitely.//| 0 means non-blocking. Positive value is the timeout in seconds.""" +static mp_obj_t usb_host_cdc_serial_get_write_timeout(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_float_t write_timeout = common_hal_usb_host_cdc_serial_get_write_timeout(self); + return (write_timeout < 0.0f) ? mp_const_none : mp_obj_new_float(write_timeout); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_write_timeout_obj, usb_host_cdc_serial_get_write_timeout); + +static mp_obj_t usb_host_cdc_serial_set_write_timeout(mp_obj_t self_in, mp_obj_t write_timeout_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_set_write_timeout(self, + write_timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(write_timeout_in)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(usb_host_cdc_serial_set_write_timeout_obj, usb_host_cdc_serial_set_write_timeout); + +MP_PROPERTY_GETSET(usb_host_cdc_serial_write_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_get_write_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_set_write_timeout_obj); + + +static const mp_rom_map_elem_t usb_host_cdc_serial_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)}, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)}, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + + { MP_ROM_QSTR(MP_QSTR_in_waiting), MP_ROM_PTR(&usb_host_cdc_serial_in_waiting_obj) }, + { MP_ROM_QSTR(MP_QSTR_out_waiting), MP_ROM_PTR(&usb_host_cdc_serial_out_waiting_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&usb_host_cdc_serial_reset_input_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_output_buffer), MP_ROM_PTR(&usb_host_cdc_serial_reset_output_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_timeout), MP_ROM_PTR(&usb_host_cdc_serial_timeout_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_timeout), MP_ROM_PTR(&usb_host_cdc_serial_write_timeout_obj) }, + + { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_host_cdc_serial_connected_obj) }, + + // TODO: Add baudrate, data_bits, parity, stop_bits properties/methods. +}; +static MP_DEFINE_CONST_DICT(usb_host_cdc_serial_locals_dict, usb_host_cdc_serial_locals_dict_table); + +static const mp_stream_p_t usb_host_cdc_serial_stream_p = { + .read = usb_host_cdc_serial_read_stream, + .write = usb_host_cdc_serial_write_stream, + .ioctl = usb_host_cdc_serial_ioctl_stream, + .is_text = false, + .pyserial_read_compatibility = true, + .pyserial_readinto_compatibility = true, + .pyserial_dont_return_none_compatibility = true, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + usb_cdc_host_serial_type, + MP_QSTR_Serial, + MP_TYPE_FLAG_ITER_IS_ITERNEXT | MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + locals_dict, &usb_host_cdc_serial_locals_dict, + iter, mp_stream_unbuffered_iter, + protocol, &usb_host_cdc_serial_stream_p + ); diff --git a/shared-bindings/usb/cdc_host/Serial.h b/shared-bindings/usb/cdc_host/Serial.h new file mode 100644 index 0000000000000..ac41e6190b854 --- /dev/null +++ b/shared-bindings/usb/cdc_host/Serial.h @@ -0,0 +1,30 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/usb/cdc_host/Serial.h" + +extern const mp_obj_type_t usb_cdc_host_serial_type; + +size_t common_hal_usb_host_cdc_serial_read(usb_cdc_host_serial_obj_t *self, uint8_t *data, size_t len, int *errcode); +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); + +uint32_t common_hal_usb_host_cdc_serial_get_in_waiting(usb_cdc_host_serial_obj_t *self); +uint32_t common_hal_usb_host_cdc_serial_get_out_waiting(usb_cdc_host_serial_obj_t *self); + +void common_hal_usb_host_cdc_serial_reset_input_buffer(usb_cdc_host_serial_obj_t *self); +uint32_t common_hal_usb_host_cdc_serial_reset_output_buffer(usb_cdc_host_serial_obj_t *self); + +uint32_t common_hal_usb_host_cdc_serial_flush(usb_cdc_host_serial_obj_t *self); + +bool common_hal_usb_host_cdc_serial_get_connected(usb_cdc_host_serial_obj_t *self); + +mp_float_t common_hal_usb_host_cdc_serial_get_timeout(usb_cdc_host_serial_obj_t *self); +void common_hal_usb_host_cdc_serial_set_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t timeout); + +mp_float_t common_hal_usb_host_cdc_serial_get_write_timeout(usb_cdc_host_serial_obj_t *self); +void common_hal_usb_host_cdc_serial_set_write_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t write_timeout); diff --git a/shared-bindings/usb/cdc_host/__init__.c b/shared-bindings/usb/cdc_host/__init__.c new file mode 100644 index 0000000000000..3bcdb9ebd0260 --- /dev/null +++ b/shared-bindings/usb/cdc_host/__init__.c @@ -0,0 +1,57 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/objmodule.h" +#include "py/runtime.h" + +#include "shared-bindings/usb/cdc_host/Serial.h" +#include "shared-bindings/usb/core/Device.h" + +#include "tusb.h" +#include "class/cdc/cdc_host.h" + +static mp_obj_t usb_cdc_host_find(mp_obj_t device_in, mp_obj_t interface_in) { + if (!mp_obj_is_type(device_in, &usb_core_device_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("device must be a usb.core.Device object")); + } + + usb_core_device_obj_t *device_obj = MP_OBJ_TO_PTR(device_in); + uint8_t daddr = device_obj->device_address; + + mp_int_t interface_num = mp_obj_get_int(interface_in); + uint8_t cdc_idx = tuh_cdc_itf_get_index(daddr, (uint8_t)interface_num); + + if (cdc_idx == TUSB_INDEX_INVALID_8) { + return mp_const_none; + } + + usb_cdc_host_serial_obj_t *serial_obj = mp_obj_malloc(usb_cdc_host_serial_obj_t, &usb_cdc_host_serial_type); + serial_obj->idx = cdc_idx; + serial_obj->timeout = -1.0f; + serial_obj->write_timeout = -1.0f; + + return MP_OBJ_FROM_PTR(serial_obj); +} + +static MP_DEFINE_CONST_FUN_OBJ_2(usb_cdc_host_find_obj, usb_cdc_host_find); + +static const mp_rom_map_elem_t usb_cdc_host_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usb_dot_cdc_host) }, + { MP_ROM_QSTR(MP_QSTR_Serial), MP_ROM_PTR(&usb_cdc_host_serial_type) }, + { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&usb_cdc_host_find_obj) }, +}; + +static MP_DEFINE_CONST_DICT(usb_cdc_host_module_globals, usb_cdc_host_module_globals_table); + +const mp_obj_module_t usb_cdc_host_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&usb_cdc_host_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_usb_dot_cdc_host, usb_cdc_host_module); diff --git a/shared-bindings/usb/cdc_host/__init__.h b/shared-bindings/usb/cdc_host/__init__.h new file mode 100644 index 0000000000000..260aec4bace2d --- /dev/null +++ b/shared-bindings/usb/cdc_host/__init__.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/usb/cdc_host/__init__.h" + +extern const mp_obj_module_t usb_cdc_host_module; diff --git a/shared-module/usb/cdc_host/Serial.c b/shared-module/usb/cdc_host/Serial.c new file mode 100644 index 0000000000000..089cc86d3e7c9 --- /dev/null +++ b/shared-module/usb/cdc_host/Serial.c @@ -0,0 +1,119 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#include "shared/runtime/interrupt_char.h" +#include "shared-bindings/usb/cdc_host/Serial.h" +#include "shared-module/usb/cdc_host/Serial.h" +#include "supervisor/shared/tick.h" + +#include "tusb.h" +#include "class/cdc/cdc_host.h" +#include "py/stream.h" + +size_t common_hal_usb_host_cdc_serial_read(usb_cdc_host_serial_obj_t *self, uint8_t *data, size_t len, int *errcode) { + if (!tuh_cdc_mounted(self->idx)) { + *errcode = ENODEV; + return 0; + } + + size_t total_read = tuh_cdc_read(self->idx, data, len); + *errcode = 0; + return total_read; +} + +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) { + if (!tuh_cdc_mounted(self->idx)) { + *errcode = ENODEV; + return 0; + } + + size_t total_written = tuh_cdc_write(self->idx, data, len); + *errcode = 0; // Success + return total_written; +} + + +uint32_t common_hal_usb_host_cdc_serial_get_in_waiting(usb_cdc_host_serial_obj_t *self) { + if (!tuh_cdc_mounted(self->idx)) { + return 0; + } + return tuh_cdc_read_available(self->idx); +} + +uint32_t common_hal_usb_host_cdc_serial_get_out_waiting(usb_cdc_host_serial_obj_t *self) { + if (!tuh_cdc_mounted(self->idx)) { + return 0; + } + uint32_t available_space = tuh_cdc_write_available(self->idx); + if (available_space > CFG_TUH_CDC_TX_BUFSIZE) { + return 0; + } + return CFG_TUH_CDC_TX_BUFSIZE - available_space; +} + +void common_hal_usb_host_cdc_serial_reset_input_buffer(usb_cdc_host_serial_obj_t *self) { + if (tuh_cdc_mounted(self->idx)) { + tuh_cdc_read_clear(self->idx); + } +} + +uint32_t common_hal_usb_host_cdc_serial_reset_output_buffer(usb_cdc_host_serial_obj_t *self) { + uint32_t bytes_cleared = 0; + if (tuh_cdc_mounted(self->idx)) { + bytes_cleared = common_hal_usb_host_cdc_serial_get_out_waiting(self); + tuh_cdc_write_clear(self->idx); + } + return bytes_cleared; +} + +uint32_t common_hal_usb_host_cdc_serial_flush(usb_cdc_host_serial_obj_t *self) { + if (!tuh_cdc_mounted(self->idx)) { + return 0; + } + + uint64_t start_ticks = supervisor_ticks_ms64(); + uint64_t timeout_ticks = (self->write_timeout < 0) ? 0 : float_to_uint64(self->write_timeout * 1000); + + uint32_t initial_waiting = common_hal_usb_host_cdc_serial_get_out_waiting(self); + + while (common_hal_usb_host_cdc_serial_get_out_waiting(self) > 0) { + tuh_cdc_write_flush(self->idx); + + if (!(self->write_timeout < 0 || self->write_timeout > 0)) { + return initial_waiting - common_hal_usb_host_cdc_serial_get_out_waiting(self); + } + + if (self->write_timeout > 0) { + if (supervisor_ticks_ms64() - start_ticks >= timeout_ticks) { + return initial_waiting - common_hal_usb_host_cdc_serial_get_out_waiting(self); + } + } + + RUN_BACKGROUND_TASKS; + } + + return initial_waiting; +} + +bool common_hal_usb_host_cdc_serial_get_connected(usb_cdc_host_serial_obj_t *self) { + return tuh_cdc_mounted(self->idx) && tuh_cdc_connected(self->idx); +} + +mp_float_t common_hal_usb_host_cdc_serial_get_timeout(usb_cdc_host_serial_obj_t *self) { + return self->timeout; +} + +void common_hal_usb_host_cdc_serial_set_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t timeout) { + self->timeout = timeout; +} + +mp_float_t common_hal_usb_host_cdc_serial_get_write_timeout(usb_cdc_host_serial_obj_t *self) { + return self->write_timeout; +} + +void common_hal_usb_host_cdc_serial_set_write_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t write_timeout) { + self->write_timeout = write_timeout; +} diff --git a/shared-module/usb/cdc_host/Serial.h b/shared-module/usb/cdc_host/Serial.h new file mode 100644 index 0000000000000..2e6c6272ef6e6 --- /dev/null +++ b/shared-module/usb/cdc_host/Serial.h @@ -0,0 +1,16 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + mp_float_t timeout; // Read timeout (s). <0 means block forever + mp_float_t write_timeout; // Write timeout (s). <0 means block forever + uint8_t idx; // TinyUSB CDC interface index +} usb_cdc_host_serial_obj_t; diff --git a/shared-module/usb/cdc_host/__init__.c b/shared-module/usb/cdc_host/__init__.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/shared-module/usb/cdc_host/__init__.h b/shared-module/usb/cdc_host/__init__.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index ce39925f45b22..de9eca5501604 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -185,7 +185,7 @@ extern "C" { // 2 hubs so we can support "7 port" hubs which have two internal hubs. #define CFG_TUH_HUB 2 -#define CFG_TUH_CDC 0 +#define CFG_TUH_CDC 2 #define CFG_TUH_MSC 0 #define CFG_TUH_VENDOR 0 #define CFG_TUH_API_EDPT_XFER 1 diff --git a/supervisor/supervisor.mk b/supervisor/supervisor.mk index 0ecc6ea3acfe6..3eb1f3388b31d 100644 --- a/supervisor/supervisor.mk +++ b/supervisor/supervisor.mk @@ -185,6 +185,7 @@ ifeq ($(CIRCUITPY_TINYUSB),1) SRC_SUPERVISOR += \ lib/tinyusb/src/host/hub.c \ lib/tinyusb/src/host/usbh.c \ + lib/tinyusb/src/class/cdc/cdc_host.c \ endif 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