From fd786a52698a4bf4bd0e07389725f1dbc66c6040 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Tue, 25 Apr 2023 12:45:57 +0200 Subject: [PATCH 1/7] chore: add premlinenary CDC interfaces --- micropython/usbd/cdc.py | 86 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 micropython/usbd/cdc.py diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py new file mode 100644 index 000000000..4f490decc --- /dev/null +++ b/micropython/usbd/cdc.py @@ -0,0 +1,86 @@ +# MicroPython USB CDC module +# MIT license; Copyright (c) 2022 Martin Fischer +from .device import ( + USBInterface, + get_usbdevice +) +from .utils import ( + endpoint_descriptor, + EP_OUT_FLAG +) +from micropython import const +import ustruct + +_DEV_CLASS_MISC = const(0xef) +_CS_DESC_TYPE = const(0x24) # CS Interface type communication descriptor +_ITF_ASSOCIATION_DESC_TYPE = const(0xb) # Interface Association descriptor + +# CDC control interface definitions +_CDC_ITF_CONTROL_CLASS = const(2) +_CDC_ITF_CONTROL_SUBCLASS = const(2) # Abstract Control Mode +_CDC_ITF_CONTROL_PROT = const(0) # no protocol + +# CDC data interface definitions +_CDC_ITF_DATA_CLASS = const(0xa) +_CDC_ITF_DATA_SUBCLASS = const(0) +_CDC_ITF_DATA_PROT = const(0) # no protocol + + +def setup_CDC_device(): + # CDC is a composite device, consisting of multiple interfaces + # (CDC control and CDC data) + # therefore we have to make sure that the association descriptor + # is set and that it associates both interfaces to the logical cdc class + usb_device = get_usbdevice() + usb_device.device_class = _DEV_CLASS_MISC + usb_device.device_subclass = 2 + usb_device.device_protocol = 1 # Itf association descriptor + + +class CDCControlInterface(USBInterface): + # Implements the CDC Control Interface + + def __init__(self, interface_str): + super().__init__(_CDC_ITF_CONTROL_CLASS, _CDC_ITF_CONTROL_SUBCLASS, + _CDC_ITF_CONTROL_PROT) + + def get_itf_descriptor(self, num_eps, itf_idx, str_idx): + # CDC needs a Interface Association Descriptor (IAD) + # first interface is zero, two interfaces in total + desc = ustruct.pack(" Date: Thu, 8 Jun 2023 14:24:59 +0200 Subject: [PATCH 2/7] usbd: Add cdc example and a basic read function. --- micropython/usbd/cdc.py | 31 ++++++++++++++++++++++++++----- micropython/usbd/cdc_example.py | 14 ++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 micropython/usbd/cdc_example.py diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py index 4f490decc..62a5a29c9 100644 --- a/micropython/usbd/cdc.py +++ b/micropython/usbd/cdc.py @@ -10,6 +10,7 @@ ) from micropython import const import ustruct +import time _DEV_CLASS_MISC = const(0xef) _CS_DESC_TYPE = const(0x24) # CS Interface type communication descriptor @@ -69,18 +70,38 @@ def get_endpoint_descriptors(self, ep_addr, str_idx): class CDCDataInterface(USBInterface): # Implements the CDC Data Interface - def __init__(self, interface_str): + def __init__(self, interface_str, timeout=1): super().__init__(_CDC_ITF_DATA_CLASS, _CDC_ITF_DATA_SUBCLASS, _CDC_ITF_DATA_PROT) + self.rx_buf = bytearray(256) + self.mv_buf = memoryview(self.rx_buf) + self.rx_done = False + self.rx_nbytes = 0 + self.timeout = timeout def get_endpoint_descriptors(self, ep_addr, str_idx): # XXX OUT = 0x00 but is defined as 0x80? self.ep_in = (ep_addr + 2) | EP_OUT_FLAG self.ep_out = (ep_addr + 2) & ~EP_OUT_FLAG - print("cdc in={} out={}".format(self.ep_in, self.ep_out)) # one IN / OUT Endpoint e_out = endpoint_descriptor(self.ep_out, "bulk", 64, 0) e_in = endpoint_descriptor(self.ep_in, "bulk", 64, 0) - desc = e_out + e_in - return (desc, [], (self.ep_out, self.ep_in)) - + return (e_out + e_in, [], (self.ep_out, self.ep_in)) + + def write(self, data): + super().submit_xfer(self.ep_in, data) + + def read(self, nbytes=0): + # XXX PoC.. When returning, it should probably + # copy it to a ringbuffer instead of leaving it here + super().submit_xfer(self.ep_out, self.rx_buf, self._cb_rx) + now = time.time() + self.rx_done = False + self.rx_nbytes = 0 + while ((time.time() - now) < self.timeout) and not self.rx_done: + time.sleep_ms(10) + return bytes(self.mv_buf[:self.rx_nbytes]) if self.rx_done else None + + def _cb_rx(self, ep, res, num_bytes): + self.rx_done = True + self.rx_nbytes = num_bytes diff --git a/micropython/usbd/cdc_example.py b/micropython/usbd/cdc_example.py new file mode 100644 index 000000000..f2edc91c8 --- /dev/null +++ b/micropython/usbd/cdc_example.py @@ -0,0 +1,14 @@ +from usbd import device, cdc + +ud = device.get_usbdevice() +cdc.setup_CDC_device() +ctrl_cdc = cdc.CDCControlInterface('') +data_cdc = cdc.CDCDataInterface('') +ud.add_interface(ctrl_cdc) +ud.add_interface(data_cdc) +ud.reenumerate() + +# sending something over CDC +data_cdc.write(b'Hello World') +# receiving something.. +print(data_cdc.read(10)) From 9244f235c7ae83617a0b12afa8f156fce9d48454 Mon Sep 17 00:00:00 2001 From: linted Date: Fri, 14 Jul 2023 17:10:47 -0400 Subject: [PATCH 3/7] changed EP_OUT_FLAG to the new EP_IN_FLAG --- micropython/usbd/cdc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py index 62a5a29c9..32b59f433 100644 --- a/micropython/usbd/cdc.py +++ b/micropython/usbd/cdc.py @@ -6,7 +6,7 @@ ) from .utils import ( endpoint_descriptor, - EP_OUT_FLAG + EP_IN_FLAG ) from micropython import const import ustruct @@ -63,8 +63,8 @@ def get_itf_descriptor(self, num_eps, itf_idx, str_idx): return desc, strs def get_endpoint_descriptors(self, ep_addr, str_idx): - self.ep_in = endpoint_descriptor((ep_addr + 1) | EP_OUT_FLAG, "interrupt", 8, 16) - return (self.ep_in, [], ((ep_addr+1) | EP_OUT_FLAG,)) + self.ep_in = endpoint_descriptor((ep_addr + 1) | EP_IN_FLAG, "interrupt", 8, 16) + return (self.ep_in, [], ((ep_addr+1) | EP_IN_FLAG,)) class CDCDataInterface(USBInterface): @@ -81,8 +81,8 @@ def __init__(self, interface_str, timeout=1): def get_endpoint_descriptors(self, ep_addr, str_idx): # XXX OUT = 0x00 but is defined as 0x80? - self.ep_in = (ep_addr + 2) | EP_OUT_FLAG - self.ep_out = (ep_addr + 2) & ~EP_OUT_FLAG + self.ep_in = (ep_addr + 2) | EP_IN_FLAG + self.ep_out = (ep_addr + 2) & ~EP_IN_FLAG # one IN / OUT Endpoint e_out = endpoint_descriptor(self.ep_out, "bulk", 64, 0) e_in = endpoint_descriptor(self.ep_in, "bulk", 64, 0) From e255093c4c8648e9243bfac70e5223c4af1655ac Mon Sep 17 00:00:00 2001 From: linted Date: Sun, 16 Jul 2023 15:02:13 -0400 Subject: [PATCH 4/7] Fixed a ustruct.pack issue causing the control interface to missreport which data interface it controls. Rebased onto the updated main feature branch. --- micropython/usbd/cdc.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py index 32b59f433..4cbfef8bb 100644 --- a/micropython/usbd/cdc.py +++ b/micropython/usbd/cdc.py @@ -44,13 +44,14 @@ class CDCControlInterface(USBInterface): def __init__(self, interface_str): super().__init__(_CDC_ITF_CONTROL_CLASS, _CDC_ITF_CONTROL_SUBCLASS, _CDC_ITF_CONTROL_PROT) + self.ep_in = None def get_itf_descriptor(self, num_eps, itf_idx, str_idx): # CDC needs a Interface Association Descriptor (IAD) # first interface is zero, two interfaces in total desc = ustruct.pack(" Date: Sun, 23 Jul 2023 14:55:21 -0400 Subject: [PATCH 5/7] changed the recv methods to actually be able to get messages from the host consistently --- micropython/usbd/cdc.py | 43 ++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py index 4cbfef8bb..d6294d584 100644 --- a/micropython/usbd/cdc.py +++ b/micropython/usbd/cdc.py @@ -8,6 +8,7 @@ endpoint_descriptor, EP_IN_FLAG ) +from .midi import RingBuf from micropython import const import ustruct import time @@ -76,12 +77,14 @@ def __init__(self, interface_str, timeout=1): super().__init__(_CDC_ITF_DATA_CLASS, _CDC_ITF_DATA_SUBCLASS, _CDC_ITF_DATA_PROT) self.rx_buf = bytearray(256) - self.mv_buf = memoryview(self.rx_buf) - self.rx_done = False - self.rx_nbytes = 0 - self.timeout = timeout + # self.mv_buf = memoryview(self.rx_buf) + # self.rx_done = False + # self.rx_nbytes = 0 + # self.timeout = timeout + self.rb = RingBuf(256) self.ep_in = None self.ep_out = None + self.read_cd_started = False def get_endpoint_descriptors(self, ep_addr, str_idx): # XXX OUT = 0x00 but is defined as 0x80? @@ -95,17 +98,25 @@ def get_endpoint_descriptors(self, ep_addr, str_idx): def write(self, data): self.submit_xfer(self.ep_in, data) - def read(self, nbytes=0): - # XXX PoC.. When returning, it should probably - # copy it to a ringbuffer instead of leaving it here - super().submit_xfer(self.ep_out, self.rx_buf, self._cb_rx) - now = time.time() - self.rx_done = False - self.rx_nbytes = 0 - while ((time.time() - now) < self.timeout) and not self.rx_done: - time.sleep_ms(10) - return bytes(self.mv_buf[:self.rx_nbytes]) if self.rx_done else None + # read nbytes or until stop char is found + def read(self, nbytes=1, stop=None): + if self.read_cd_started == False: + self._start_rx_cb() + self.read_cd_started = True + res = bytearray() + for i in range(nbytes): + nxt = self.rb.get() + if nxt == None: + break + res.append(nxt) + if nxt == ord(stop): + break + return res + + def _start_rx_cb(self): + self.submit_xfer(self.ep_out, self.rx_buf, self._cb_rx) def _cb_rx(self, ep, res, num_bytes): - self.rx_done = True - self.rx_nbytes = num_bytes + for i in range(0, num_bytes): + self.rb.put(self.rx_buf[i]) + self.submit_xfer(self.ep_out, self.rx_buf, self._cb_rx) From 586b0540687d0fe1e1e4214842ed09b6e79d18d3 Mon Sep 17 00:00:00 2001 From: linted Date: Mon, 24 Jul 2023 23:28:41 -0700 Subject: [PATCH 6/7] Added check to ensure that read couldn't start before we received our ep_out --- micropython/usbd/cdc.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py index d6294d584..a0180dc44 100644 --- a/micropython/usbd/cdc.py +++ b/micropython/usbd/cdc.py @@ -100,7 +100,11 @@ def write(self, data): # read nbytes or until stop char is found def read(self, nbytes=1, stop=None): + if self.read_cd_started == False: + if self.ep_out == None: + # ep_out hasn't been set yet, so we can't start recv'ing + return b'' # TODO: better output then just an empty message? self._start_rx_cb() self.read_cd_started = True res = bytearray() From e28c943a5b78ae58f4f5fc0009718417e2e2294a Mon Sep 17 00:00:00 2001 From: linted Date: Wed, 2 Aug 2023 20:22:17 -0700 Subject: [PATCH 7/7] added check to ensure usb is setup before trying to send a message --- micropython/usbd/cdc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/micropython/usbd/cdc.py b/micropython/usbd/cdc.py index 0df36817e..016500e99 100644 --- a/micropython/usbd/cdc.py +++ b/micropython/usbd/cdc.py @@ -100,6 +100,9 @@ def get_endpoint_descriptors(self, ep_addr, str_idx): return (e_out + e_in, [], (self.ep_out, self.ep_in)) def write(self, data): + # we are just going to ignore any sends before it's time + if self.ep_in == None: + return self.submit_xfer(self.ep_in, data) # read nbytes or until stop char is found