88import struct
99from micropython import const
1010
11- from .device import USBInterface , get_usbdevice
11+ from .device import USBInterface , get
1212from .utils import (
1313 Buffer ,
1414 split_bmRequestType ,
8787_MP_STREAM_POLL_HUP = const (0x10 )
8888
8989
90- class CDC (io .IOBase ):
90+ class CDC (io .IOBase , USBInterface ):
9191 # USB CDC serial device class, designed to resemble machine.UART
9292 # with some additional methods.
9393 #
94- # This is a standalone class, instead of a USBInterface subclass, because
95- # CDC consists of multiple interfaces (CDC control and CDC data) and also
96- # so it can derive from io.IOBase.
94+ # Relies on multiple inheritance so it can be an io.IOBase for stream
95+ # functions and also a USBInterface (actually an Interface Association
96+ # Descriptor holding two interfaces.)
9797 def __init__ (self , ** kwargs ):
98+ # io.IOBase has no __init__()
99+ USBInterface .__init__ (self )
100+
98101 # For CDC to work, the device class must be set to Interface Association
99- usb_device = get_usbdevice ()
102+ # TODO: Fix this hack by adding a callback from _USBDevice.init() that lets
103+ # all the USBInterface instances tweak the device class if needed
104+ usb_device = get ()
100105 usb_device .device_class = _DEV_CLASS_MISC
101106 usb_device .device_subclass = 2
102107 usb_device .device_protocol = 1 # Itf association descriptor
103108
104- self ._ctrl = CDCControlInterface ()
105- self ._data = CDCDataInterface ()
106- # The data interface *must* be added immediately after the control interface
107- usb_device .add_interface (self ._ctrl )
108- usb_device .add_interface (self ._data )
109+ # Callbacks for particular control changes initiated by the host
110+ self .break_cb = None # Host sent a "break" condition
111+ self .line_state_cb = None
112+ self .line_coding_cb = None
113+
114+ self ._line_state = 0 # DTR & RTS
115+ # Set a default line coding of 115200/8N1
116+ self ._line_coding = bytearray (b"\x00 \xc2 \x01 \x00 \x00 \x00 \x08 " )
117+
118+ self ._wb = () # Optional write Buffer (IN endpoint), set by CDC.init()
119+ self ._rb = () # Optional read Buffer (OUT endpoint), set by CDC.init()
120+ self ._timeout = 1000 # set from CDC.init() as well
121+
122+ # one control interface endpoint, two data interface endpoints
123+ self .ep_c_in = self .ep_d_in = self .ep_d_out = None
124+
125+ self ._c_itf = None # Number of control interface, data interface is one more
109126
110127 self .init (** kwargs )
111128
@@ -121,7 +138,7 @@ def init(
121138 # code, the USB host sets them.)
122139 struct .pack_into (
123140 "<LBBB" ,
124- self ._ctrl . _line_coding ,
141+ self ._line_coding ,
125142 0 ,
126143 baudrate ,
127144 _STOP_BITS_REPR .index (str (stop )),
@@ -135,44 +152,41 @@ def init(
135152 if not (txbuf and rxbuf ):
136153 raise ValueError # Buffer sizes are required
137154
138- self ._data ._timeout = timeout
139- self ._data ._wb = Buffer (txbuf )
140- self ._data ._rb = Buffer (rxbuf )
141-
142- def is_open (self ):
143- return self ._ctrl .is_open ()
155+ self ._timeout = timeout
156+ self ._wb = Buffer (txbuf )
157+ self ._rb = Buffer (rxbuf )
144158
145159 ###
146160 ### Line State & Line Coding State property getters
147161 ###
148162
149163 @property
150164 def rts (self ):
151- return bool (self ._ctrl . _line_state & _LINE_STATE_RTS )
165+ return bool (self ._line_state & _LINE_STATE_RTS )
152166
153167 @property
154168 def dtr (self ):
155- return bool (self ._ctrl . _line_state & _LINE_STATE_DTR )
169+ return bool (self ._line_state & _LINE_STATE_DTR )
156170
157171 # Line Coding Representation
158172 # Byte 0-3 Byte 4 Byte 5 Byte 6
159173 # dwDTERate bCharFormat bParityType bDataBits
160174
161175 @property
162176 def baudrate (self ):
163- return struct .unpack ("<LBBB" , self ._ctrl . _line_coding )[0 ]
177+ return struct .unpack ("<LBBB" , self ._line_coding )[0 ]
164178
165179 @property
166180 def stop_bits (self ):
167- return _STOP_BITS_REPR [self ._ctrl . _line_coding [4 ]]
181+ return _STOP_BITS_REPR [self ._line_coding [4 ]]
168182
169183 @property
170184 def parity (self ):
171- return _PARITY_BITS_REPR [self ._ctrl . _line_coding [5 ]]
185+ return _PARITY_BITS_REPR [self ._line_coding [5 ]]
172186
173187 @property
174188 def data_bits (self ):
175- return self ._ctrl . _line_coding [6 ]
189+ return self ._line_coding [6 ]
176190
177191 def __repr__ (self ):
178192 return f"{ self .baudrate } /{ self .data_bits } { self .parity } { self .stop_bits } rts={ self .rts } dtr={ self .dtr } "
@@ -182,60 +196,24 @@ def __repr__(self):
182196 ###
183197
184198 def set_break_cb (self , cb ):
185- self ._ctrl . break_cb = cb
199+ self .break_cb = cb
186200
187201 def set_line_state_cb (self , cb ):
188- self ._ctrl . line_state_cb = cb
202+ self .line_state_cb = cb
189203
190204 def set_line_coding_cb (self , cb ):
191- self ._ctrl . line_coding_cb = cb
205+ self .line_coding_cb = cb
192206
193207 ###
194- ### io.IOBase stream implementation
208+ ### USB Interface Implementation
195209 ###
196210
197- def read (self , size = - 1 ):
198- return self ._data .read (size )
199-
200- def readinto (self , b ):
201- return self ._data .readinto (b )
202-
203- def write (self , buf ):
204- return self ._data .write (buf )
205-
206- def flush (self ):
207- # a C implementation of this exists in stream.c, but it's not in io.IOBase
208- # and can't immediately be called from here (AFAIK)
209- r = self .ioctl (_MP_STREAM_FLUSH , 0 )
210- if r :
211- raise OSError (r )
212-
213- def ioctl (self , req , arg ):
214- return self ._data .ioctl (req , arg )
215-
216-
217- class CDCControlInterface (USBInterface ):
218- # Implements the CDC Control Interface
219-
220- def __init__ (self ):
221- super ().__init__ ()
222-
223- # Callbacks for particular changes initiated by the host
224- self .break_cb = None # Host sent a "break" condition
225- self .line_state_cb = None
226- self .line_coding_cb = None
227-
228- self ._line_state = 0 # DTR & RTS
229- # Set a default line coding of 115200/8N1
230- self ._line_coding = bytearray (b"\x00 \xc2 \x01 \x00 \x00 \x00 \x08 " )
231-
232- self .ep_in = None # Set when enumeration happens
233-
234- def descriptor_config_cb (self , desc , itf_num , ep_num ):
235- # CDC needs a Interface Association Descriptor (IAD) connecting the Control & Data interfaces
211+ def descriptor_config (self , desc , itf_num , ep_num , strs ):
212+ # CDC needs a Interface Association Descriptor (IAD) wrapping two interfaces: Control & Data interfaces
236213 desc .interface_assoc (itf_num , 2 , _INTERFACE_CLASS_CDC , _INTERFACE_SUBCLASS_CDC )
237214
238215 # Now add the Control interface descriptor
216+ self ._c_itf = itf_num
239217 desc .interface (itf_num , _CDC_CONTROL_EP_NUM , _INTERFACE_CLASS_CDC , _INTERFACE_SUBCLASS_CDC )
240218
241219 # Append the CDC class-specific interface descriptor
@@ -280,14 +258,46 @@ def descriptor_config_cb(self, desc, itf_num, ep_num):
280258 itf_num + 1 , # bSubordinateInterface0 (data class itf number)
281259 )
282260
283- # Descriptor for a single endpoint
284- self .ep_in = ep_num | EP_IN_FLAG
285- desc .endpoint (self .ep_in , "interrupt" , 8 , 16 )
261+ # Single control IN endpoint (currently unused in this implementation)
262+ self .ep_c_in = ep_num | EP_IN_FLAG
263+ desc .endpoint (self .ep_c_in , "interrupt" , 8 , 16 )
264+
265+ # Now add the data interface
266+ desc .interface (
267+ itf_num + 1 ,
268+ _CDC_DATA_EP_NUM ,
269+ _CDC_ITF_DATA_CLASS ,
270+ _CDC_ITF_DATA_SUBCLASS ,
271+ _CDC_ITF_DATA_PROT ,
272+ )
273+
274+ # Two data endpoints, bulk OUT and IN
275+ self .ep_d_out = ep_num + 1
276+ self .ep_d_in = (ep_num + 1 ) | EP_IN_FLAG
277+ desc .endpoint (self .ep_d_out , "bulk" , _BULK_EP_LEN , 0 )
278+ desc .endpoint (self .ep_d_in , "bulk" , _BULK_EP_LEN , 0 )
279+
280+ def num_itfs (self ):
281+ return 2
282+
283+ def num_eps (self ):
284+ return 2 # total after masking out EP_IN_FLAG
285+
286+ def handle_open (self ):
287+ super ().handle_open ()
288+ # kick off any transfers that may have queued while the device was not open
289+ self ._rd_xfer ()
290+ self ._wr_xfer ()
286291
287292 def handle_interface_control_xfer (self , stage , request ):
288293 # Handle class-specific interface control transfers
289- bmRequestType , bRequest , wValue , _ , wLength = struct .unpack ("BBHHH" , request )
294+ bmRequestType , bRequest , wValue , wIndex , wLength = struct .unpack ("BBHHH" , request )
290295 recipient , req_type , req_dir = split_bmRequestType (bmRequestType )
296+
297+ # Only for the control interface
298+ if wIndex != self ._c_itf :
299+ return False
300+
291301 if stage == STAGE_SETUP :
292302 if req_type == REQ_TYPE_CLASS :
293303 if bRequest == _SET_LINE_CODING_REQ :
@@ -317,34 +327,34 @@ def handle_interface_control_xfer(self, stage, request):
317327
318328 return True
319329
330+ def _wr_xfer (self ):
331+ # Submit a new data IN transfer from the _wb buffer, if needed
332+ if self .is_open () and not self .xfer_pending (self .ep_d_in ) and self ._wb .readable ():
333+ self .submit_xfer (self .ep_d_in , self ._wb .pend_read (), self ._wr_cb )
320334
321- class CDCDataInterface (USBInterface ):
322- # Implements the CDC Data Interface
323-
324- def __init__ (self ):
325- super ().__init__ ()
326-
327- self ._wb = () # Optional write Buffer (IN endpoint), set by CDC.init()
328- self ._rb = () # Optional read Buffer (OUT endpoint), set by CDC.init()
329- self ._timeout = 1000 # set from CDC.init() as well
335+ def _wr_cb (self , ep , res , num_bytes ):
336+ # Whenever a data IN transfer ends
337+ if res == 0 :
338+ self ._wb .finish_read (num_bytes )
339+ self ._wr_xfer ()
330340
331- self .ep_in = self .ep_out = None # Set when enumeration happens
341+ def _rd_xfer (self ):
342+ # Keep an active data OUT transfer to read data from the host,
343+ # whenever the receive buffer has room for new data
344+ if self .is_open () and not self .xfer_pending (self .ep_d_out ) and self ._rb .writable ():
345+ # Can only submit up to the endpoint length per transaction, otherwise we won't
346+ # get any transfer callback until the full transaction completes.
347+ self .submit_xfer (self .ep_d_out , self ._rb .pend_write (_BULK_EP_LEN ), self ._rd_cb )
332348
333- def descriptor_config_cb (self , desc , itf_num , ep_num ):
334- # Add the standard interface descriptor
335- desc .interface (
336- itf_num ,
337- _CDC_DATA_EP_NUM ,
338- _CDC_ITF_DATA_CLASS ,
339- _CDC_ITF_DATA_SUBCLASS ,
340- _CDC_ITF_DATA_PROT ,
341- )
349+ def _rd_cb (self , ep , res , num_bytes ):
350+ # Whenever a data OUT transfer ends
351+ if res == 0 :
352+ self ._rb .finish_write (num_bytes )
353+ self ._rd_xfer ()
342354
343- # Two endpoints, bulk OUT and IN
344- self .ep_out = ep_num
345- self .ep_in = ep_num | EP_IN_FLAG
346- desc .endpoint (self .ep_out , "bulk" , _BULK_EP_LEN , 0 )
347- desc .endpoint (self .ep_in , "bulk" , _BULK_EP_LEN , 0 )
355+ ###
356+ ### io.IOBase stream implementation
357+ ###
348358
349359 def write (self , buf ):
350360 # use a memoryview to track how much of 'buf' we've written so far
@@ -365,36 +375,6 @@ def write(self, buf):
365375 if time .ticks_diff (time .ticks_ms (), start ) > self ._timeout :
366376 return len (buf ) - len (mv )
367377
368- def _wr_xfer (self ):
369- # Submit a new IN transfer from the _wb buffer, if needed
370- if self .is_open () and not self .xfer_pending (self .ep_in ) and self ._wb .readable ():
371- self .submit_xfer (self .ep_in , self ._wb .pend_read (), self ._wr_cb )
372-
373- def _wr_cb (self , ep , res , num_bytes ):
374- # Whenever an IN transfer ends
375- if res == 0 :
376- self ._wb .finish_read (num_bytes )
377- self ._wr_xfer ()
378-
379- def _rd_xfer (self ):
380- # Keep an active OUT transfer to read data from the host,
381- # whenever the receive buffer has room for new data
382- if self .is_open () and not self .xfer_pending (self .ep_out ) and self ._rb .writable ():
383- # Can only submit up to the endpoint length per transaction, otherwise we won't
384- # get any transfer callback until the full transaction completes.
385- self .submit_xfer (self .ep_out , self ._rb .pend_write (_BULK_EP_LEN ), self ._rd_cb )
386-
387- def _rd_cb (self , ep , res , num_bytes ):
388- if res == 0 :
389- self ._rb .finish_write (num_bytes )
390- self ._rd_xfer ()
391-
392- def handle_open (self ):
393- super ().handle_open ()
394- # kick off any transfers that may have queued while the device was not open
395- self ._rd_xfer ()
396- self ._wr_xfer ()
397-
398378 def read (self , size ):
399379 start = time .ticks_ms ()
400380
@@ -457,3 +437,10 @@ def ioctl(self, req, arg):
457437 return 0
458438
459439 return _MP_EINVAL
440+
441+ def flush (self ):
442+ # a C implementation of this exists in stream.c, but it's not in io.IOBase
443+ # and can't immediately be called from here (AFAIK)
444+ r = self .ioctl (_MP_STREAM_FLUSH , 0 )
445+ if r :
446+ raise OSError (r )
0 commit comments