]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/busio.py
Merge pull request #1002 from makermelissa/main
[Adafruit_Blinka-hackapet.git] / src / busio.py
1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
2 #
3 # SPDX-License-Identifier: MIT
4 """
5 `busio` - Bus protocol support like I2C and SPI
6 =================================================
7
8 See `CircuitPython:busio` in CircuitPython for more details.
9
10 * Author(s): cefn
11 """
12 try:
13     import threading
14 except ImportError:
15     threading = None
16
17 # pylint: disable=unused-import
18 import adafruit_platformdetect.constants.boards as ap_board
19 import adafruit_platformdetect.constants.chips as ap_chip
20 from adafruit_blinka import Enum, Lockable, agnostic
21 from adafruit_blinka.agnostic import board_id, detector
22
23 # pylint: disable=import-outside-toplevel,too-many-branches,too-many-statements
24 # pylint: disable=too-many-arguments,too-many-function-args,too-many-return-statements
25
26
27 class I2C(Lockable):
28     """
29     Busio I2C Class for CircuitPython Compatibility. Used
30     for both MicroPython and Linux.
31
32     NOTE: Frequency has no effect on Linux systems. The argument is only there for compatibility.
33     """
34
35     def __init__(self, scl, sda, frequency=100000):
36         self.init(scl, sda, frequency)
37
38     def init(self, scl, sda, frequency):
39         """Initialization"""
40         self.deinit()
41         if detector.board.ftdi_ft232h:
42             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.i2c import I2C as _I2C
43
44             self._i2c = _I2C(frequency=frequency)
45             return
46         if detector.board.binho_nova:
47             from adafruit_blinka.microcontroller.nova.i2c import I2C as _I2C
48
49             self._i2c = _I2C(frequency=frequency)
50             return
51         if detector.board.microchip_mcp2221:
52             from adafruit_blinka.microcontroller.mcp2221.i2c import I2C as _I2C
53
54             self._i2c = _I2C(frequency=frequency)
55             return
56
57         if detector.board.OS_AGNOSTIC_BOARD:
58             from adafruit_blinka.microcontroller.generic_agnostic_board.i2c import (
59                 I2C as _I2C,
60             )
61
62             self._i2c = _I2C(frequency=frequency)
63             return
64
65         if detector.board.greatfet_one:
66             from adafruit_blinka.microcontroller.nxp_lpc4330.i2c import I2C as _I2C
67
68             self._i2c = _I2C(frequency=frequency)
69             return
70         if detector.board.pico_u2if:
71             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import I2C_Pico as _I2C
72
73             self._i2c = _I2C(scl, sda, frequency=frequency)
74             return
75         if detector.board.feather_u2if:
76             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
77                 I2C_Feather as _I2C,
78             )
79
80             self._i2c = _I2C(scl, sda, frequency=frequency)
81             return
82         if detector.board.feather_can_u2if:
83             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
84                 I2C_Feather_CAN as _I2C,
85             )
86
87             self._i2c = _I2C(scl, sda, frequency=frequency)
88             return
89         if detector.board.feather_epd_u2if:
90             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
91                 I2C_Feather_EPD as _I2C,
92             )
93
94             self._i2c = _I2C(scl, sda, frequency=frequency)
95             return
96         if detector.board.feather_rfm_u2if:
97             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
98                 I2C_Feather_RFM as _I2C,
99             )
100
101             self._i2c = _I2C(scl, sda, frequency=frequency)
102             return
103         if detector.board.qtpy_u2if:
104             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import I2C_QTPY as _I2C
105
106             self._i2c = _I2C(scl, sda, frequency=frequency)
107             return
108         if detector.board.itsybitsy_u2if:
109             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
110                 I2C_ItsyBitsy as _I2C,
111             )
112
113             self._i2c = _I2C(scl, sda, frequency=frequency)
114             return
115         if detector.board.macropad_u2if:
116             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
117                 I2C_MacroPad as _I2C,
118             )
119
120             self._i2c = _I2C(scl, sda, frequency=frequency)
121             return
122         if detector.board.qt2040_trinkey_u2if:
123             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
124                 I2C_QT2040_Trinkey as _I2C,
125             )
126
127             self._i2c = _I2C(scl, sda, frequency=frequency)
128             return
129         if detector.board.kb2040_u2if:
130             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
131                 I2C_KB2040 as _I2C,
132             )
133
134             self._i2c = _I2C(scl, sda, frequency=frequency)
135             return
136         if detector.board.radxa_x4_u2if:
137             from adafruit_blinka.microcontroller.rp2040_u2if.i2c import (
138                 I2C_Radxa_X4 as _I2C,
139             )
140
141             self._i2c = _I2C(scl, sda, frequency=frequency)
142             return
143         if detector.chip.id == ap_chip.RP2040:
144             from adafruit_blinka.microcontroller.rp2040.i2c import I2C as _I2C
145
146             self._i2c = _I2C(scl, sda, frequency=frequency)
147             return
148         if detector.board.any_siemens_iot2000:
149             from adafruit_blinka.microcontroller.am65xx.i2c import I2C as _I2C
150
151             self._i2c = _I2C(frequency=frequency)
152             return
153
154         if detector.board.any_embedded_linux:
155             from adafruit_blinka.microcontroller.generic_linux.i2c import I2C as _I2C
156
157             if frequency == 100000:
158                 frequency = None  # Set to None if default to avoid triggering warning
159         elif detector.board.ftdi_ft2232h:
160             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.i2c import I2C as _I2C
161         elif detector.board.ftdi_ft4232h:
162             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.i2c import I2C as _I2C
163         else:
164             from adafruit_blinka.microcontroller.generic_micropython.i2c import (
165                 I2C as _I2C,
166             )
167         from microcontroller.pin import i2cPorts
168
169         for portId, portScl, portSda in i2cPorts:
170             try:
171                 # pylint: disable=unexpected-keyword-arg
172                 if scl == portScl and sda == portSda:
173                     self._i2c = _I2C(portId, mode=_I2C.MASTER, baudrate=frequency)
174                     break
175                 # pylint: enable=unexpected-keyword-arg
176             except RuntimeError:
177                 pass
178         else:
179             raise ValueError(
180                 f"No Hardware I2C on (scl,sda)={(scl, sda)}\n"
181                 f"Valid I2C ports: {i2cPorts}.\nMake sure I2C is enabled."
182             )
183         if threading is not None:
184             self._lock = threading.RLock()
185
186     def deinit(self):
187         """Deinitialization"""
188         try:
189             del self._i2c
190         except AttributeError:
191             pass
192
193     def __enter__(self):
194         if threading is not None:
195             self._lock.acquire()
196         return self
197
198     def __exit__(self, exc_type, exc_value, traceback):
199         if threading is not None:
200             self._lock.release()
201         self.deinit()
202
203     def scan(self):
204         """Scan for attached devices"""
205         return self._i2c.scan()
206
207     def readfrom_into(self, address, buffer, *, start=0, end=None):
208         """Read from a device at specified address into a buffer"""
209         if start != 0 or end is not None:
210             if end is None:
211                 end = len(buffer)
212             buffer = memoryview(buffer)[start:end]
213         return self._i2c.readfrom_into(address, buffer, stop=True)
214
215     def writeto(self, address, buffer, *, start=0, end=None):
216         """Write to a device at specified address from a buffer"""
217         if isinstance(buffer, str):
218             buffer = bytes([ord(x) for x in buffer])
219         if start != 0 or end is not None:
220             if end is None:
221                 return self._i2c.writeto(address, memoryview(buffer)[start:], stop=True)
222             return self._i2c.writeto(address, memoryview(buffer)[start:end], stop=True)
223         return self._i2c.writeto(address, buffer, stop=True)
224
225     def writeto_then_readfrom(
226         self,
227         address,
228         buffer_out,
229         buffer_in,
230         *,
231         out_start=0,
232         out_end=None,
233         in_start=0,
234         in_end=None,
235         stop=False,
236     ):
237         """ "Write to a device at specified address from a buffer then read
238         from a device at specified address into a buffer
239         """
240         return self._i2c.writeto_then_readfrom(
241             address,
242             buffer_out,
243             buffer_in,
244             out_start=out_start,
245             out_end=out_end,
246             in_start=in_start,
247             in_end=in_end,
248             stop=stop,
249         )
250
251
252 class SPI(Lockable):
253     """
254     Busio SPI Class for CircuitPython Compatibility. Used
255     for both MicroPython and Linux.
256     """
257
258     def __init__(self, clock, MOSI=None, MISO=None):
259         self.deinit()
260         if detector.board.ftdi_ft232h:
261             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.spi import SPI as _SPI
262             from adafruit_blinka.microcontroller.ftdi_mpsse.ft232h.pin import (
263                 SCK,
264                 MOSI,
265                 MISO,
266             )
267
268             self._spi = _SPI()
269             self._pins = (SCK, MOSI, MISO)
270             return
271         if detector.board.binho_nova:
272             from adafruit_blinka.microcontroller.nova.spi import SPI as _SPI
273             from adafruit_blinka.microcontroller.nova.pin import SCK, MOSI, MISO
274
275             self._spi = _SPI(clock)
276             self._pins = (SCK, MOSI, MISO)
277             return
278         if detector.board.greatfet_one:
279             from adafruit_blinka.microcontroller.nxp_lpc4330.spi import SPI as _SPI
280             from adafruit_blinka.microcontroller.nxp_lpc4330.pin import SCK, MOSI, MISO
281
282             self._spi = _SPI()
283             self._pins = (SCK, MOSI, MISO)
284             return
285         if detector.board.pico_u2if:
286             from adafruit_blinka.microcontroller.rp2040_u2if.spi import SPI_Pico as _SPI
287
288             self._spi = _SPI(clock)  # this is really all that's needed
289             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
290             return
291         if detector.board.feather_u2if:
292             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
293                 SPI_Feather as _SPI,
294             )
295
296             self._spi = _SPI(clock)  # this is really all that's needed
297             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
298             return
299         if detector.board.feather_can_u2if:
300             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
301                 SPI_Feather_CAN as _SPI,
302             )
303
304             self._spi = _SPI(clock)  # this is really all that's needed
305             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
306             return
307         if detector.board.feather_epd_u2if:
308             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
309                 SPI_Feather_EPD as _SPI,
310             )
311
312             self._spi = _SPI(clock)  # this is really all that's needed
313             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
314             return
315         if detector.board.feather_rfm_u2if:
316             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
317                 SPI_Feather_RFM as _SPI,
318             )
319
320             self._spi = _SPI(clock)  # this is really all that's needed
321             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
322             return
323         if detector.board.itsybitsy_u2if:
324             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
325                 SPI_ItsyBitsy as _SPI,
326             )
327
328             self._spi = _SPI(clock)  # this is really all that's needed
329             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
330             return
331         if detector.board.macropad_u2if:
332             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
333                 SPI_MacroPad as _SPI,
334             )
335
336             self._spi = _SPI(clock)  # this is really all that's needed
337             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
338             return
339         if detector.board.qtpy_u2if:
340             from adafruit_blinka.microcontroller.rp2040_u2if.spi import SPI_QTPY as _SPI
341
342             self._spi = _SPI(clock)  # this is really all that's needed
343             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
344             return
345         if detector.board.kb2040_u2if:
346             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
347                 SPI_KB2040 as _SPI,
348             )
349
350             self._spi = _SPI(clock)  # this is really all that's needed
351             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
352             return
353         if detector.board.radxa_x4_u2if:
354             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
355                 SPI_Radxa_X4 as _SPI,
356             )
357
358             self._spi = _SPI(clock)  # this is really all that's needed
359             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
360             return
361         if detector.chip.id == ap_chip.RP2040:
362             from adafruit_blinka.microcontroller.rp2040.spi import SPI as _SPI
363
364             self._spi = _SPI(clock, MOSI, MISO)  # Pins configured on instantiation
365             self._pins = (clock, clock, clock)  # These don't matter, they're discarded
366             return
367         if detector.board.any_siemens_iot2000:
368             from adafruit_blinka.microcontroller.am65xx.spi import SPI as _SPI
369
370             self._spi = _SPI(clock)  # this is really all that's needed
371             self._pins = (clock, clock, clock)  # will determine MOSI/MISO from clock
372             return
373         if detector.board.any_embedded_linux:
374             from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI
375         elif detector.board.ftdi_ft2232h:
376             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.spi import SPI as _SPI
377         elif detector.board.ftdi_ft4232h:
378             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.spi import SPI as _SPI
379         elif detector.board.OS_AGNOSTIC_BOARD:
380             from adafruit_blinka.microcontroller.generic_agnostic_board.spi import (
381                 SPI as _SPI,
382             )
383         else:
384             from adafruit_blinka.microcontroller.generic_micropython.spi import (
385                 SPI as _SPI,
386             )
387         from microcontroller.pin import spiPorts
388
389         for portId, portSck, portMosi, portMiso in spiPorts:
390             if (
391                 (clock == portSck)
392                 and MOSI in (portMosi, None)  # Clock is required!
393                 and MISO in (portMiso, None)  # But can do with just output
394             ):  # Or just input
395                 self._spi = _SPI(portId)
396                 self._pins = (portSck, portMosi, portMiso)
397                 break
398         else:
399             raise ValueError(
400                 f"No Hardware SPI on (SCLK, MOSI, MISO)={(clock, MOSI, MISO)}\n"
401                 f"Valid SPI ports: {spiPorts}.\nMake sure SPI is enabled."
402             )
403
404     def configure(self, baudrate=100000, polarity=0, phase=0, bits=8):
405         """Update the configuration"""
406         if detector.board.any_nanopi and detector.chip.id == ap_chip.SUN8I:
407             from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI
408         elif detector.board.ftdi_ft232h:
409             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.spi import (
410                 SPI as _SPI,
411             )
412         elif detector.board.ftdi_ft2232h:
413             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.spi import (
414                 SPI as _SPI,
415             )
416         elif detector.board.ftdi_ft4232h:
417             from adafruit_blinka.microcontroller.ftdi_mpsse.mpsse.spi import (
418                 SPI as _SPI,
419             )
420         elif detector.board.binho_nova:
421             from adafruit_blinka.microcontroller.nova.spi import SPI as _SPI
422         elif detector.board.greatfet_one:
423             from adafruit_blinka.microcontroller.nxp_lpc4330.spi import SPI as _SPI
424         elif detector.board.any_lubancat and detector.chip.id == ap_chip.IMX6ULL:
425             from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI
426         elif detector.board.pico_u2if:
427             from adafruit_blinka.microcontroller.rp2040_u2if.spi import SPI_Pico as _SPI
428         elif detector.board.feather_u2if:
429             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
430                 SPI_Feather as _SPI,
431             )
432         elif detector.board.feather_can_u2if:
433             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
434                 SPI_Feather_CAN as _SPI,
435             )
436         elif detector.board.feather_epd_u2if:
437             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
438                 SPI_Feather_EPD as _SPI,
439             )
440         elif detector.board.feather_rfm_u2if:
441             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
442                 SPI_Feather_RFM as _SPI,
443             )
444         elif detector.board.itsybitsy_u2if:
445             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
446                 SPI_ItsyBitsy as _SPI,
447             )
448         elif detector.board.macropad_u2if:
449             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
450                 SPI_MacroPad as _SPI,
451             )
452         elif detector.board.kb2040_u2if:
453             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
454                 SPI_KB2040 as _SPI,
455             )
456         elif detector.board.qtpy_u2if:
457             from adafruit_blinka.microcontroller.rp2040_u2if.spi import SPI_QTPY as _SPI
458         elif detector.board.radxa_x4_u2if:
459             from adafruit_blinka.microcontroller.rp2040_u2if.spi import (
460                 SPI_Radxa_X4 as _SPI,
461             )
462         elif detector.chip.id == ap_chip.RP2040:
463             from adafruit_blinka.microcontroller.rp2040.spi import SPI as _SPI
464         elif detector.board.any_siemens_iot2000:
465             from adafruit_blinka.microcontroller.am65xx.spi import SPI as _SPI
466             from adafruit_blinka.microcontroller.am65xx.pin import Pin
467         elif detector.board.any_embedded_linux:
468             from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI
469         elif detector.board.OS_AGNOSTIC_BOARD:
470             from adafruit_blinka.microcontroller.generic_agnostic_board.spi import (
471                 SPI as _SPI,
472             )
473         else:
474             from adafruit_blinka.microcontroller.generic_micropython.spi import (
475                 SPI as _SPI,
476             )
477
478         if self._locked:
479             # TODO check if #init ignores MOSI=None rather than unsetting, to save _pinIds attribute
480             self._spi.init(
481                 baudrate=baudrate,
482                 polarity=polarity,
483                 phase=phase,
484                 bits=bits,
485                 firstbit=_SPI.MSB,
486             )
487         else:
488             raise RuntimeError("First call try_lock()")
489
490     def deinit(self):
491         """Deinitialization"""
492         self._spi = None
493         self._pinIds = None
494
495     @property
496     def frequency(self):
497         """Return the baud rate if implemented"""
498         try:
499             return self._spi.frequency
500         except AttributeError as error:
501             raise NotImplementedError(
502                 "Frequency attribute not implemented for this platform"
503             ) from error
504
505     def write(self, buf, start=0, end=None):
506         """Write to the SPI device"""
507         return self._spi.write(buf, start, end)
508
509     def readinto(self, buf, start=0, end=None, write_value=0):
510         """Read from the SPI device into a buffer"""
511         return self._spi.readinto(buf, start, end, write_value=write_value)
512
513     def write_readinto(
514         self, buffer_out, buffer_in, out_start=0, out_end=None, in_start=0, in_end=None
515     ):
516         """Write to the SPI device and read from the SPI device into a buffer"""
517         return self._spi.write_readinto(
518             buffer_out, buffer_in, out_start, out_end, in_start, in_end
519         )
520
521
522 class UART(Lockable):
523     """
524     Busio UART Class for CircuitPython Compatibility. Used
525     for MicroPython and a few other non-Linux boards.
526     """
527
528     class Parity(Enum):
529         """Parity Enumeration"""
530
531         pass  # pylint: disable=unnecessary-pass
532
533     Parity.ODD = Parity()
534     Parity.EVEN = Parity()
535
536     def __init__(
537         self,
538         tx,
539         rx,
540         baudrate=9600,
541         bits=8,
542         parity=None,
543         stop=1,
544         timeout=1000,
545         receiver_buffer_size=64,
546         flow=None,
547     ):
548         if detector.board.any_embedded_linux:
549             raise RuntimeError(
550                 "busio.UART not supported on this platform. Please use pyserial instead."
551             )
552         if detector.board.binho_nova:
553             from adafruit_blinka.microcontroller.nova.uart import UART as _UART
554         elif detector.board.greatfet_one:
555             from adafruit_blinka.microcontroller.nxp_lpc4330.uart import UART as _UART
556         elif detector.chip.id == ap_chip.RP2040:
557             from adafruit_blinka.microcontroller.rp2040.uart import UART as _UART
558         else:
559             from machine import UART as _UART
560
561         from microcontroller.pin import uartPorts
562
563         self.baudrate = baudrate
564
565         if flow is not None:  # default 0
566             raise NotImplementedError(
567                 "Parameter '{}' unsupported on {}".format("flow", agnostic.board_id)
568             )
569
570         # translate parity flag for Micropython
571         if parity is UART.Parity.ODD:
572             parity = 1
573         elif parity is UART.Parity.EVEN:
574             parity = 0
575         elif parity is None:
576             pass
577         else:
578             raise ValueError("Invalid parity")
579
580         if detector.chip.id == ap_chip.RP2040:
581             self._uart = _UART(
582                 tx,
583                 rx,
584                 baudrate=baudrate,
585                 bits=bits,
586                 parity=parity,
587                 stop=stop,
588             )
589         else:
590             # check tx and rx have hardware support
591             for portId, portTx, portRx in uartPorts:  #
592                 if portTx == tx and portRx == rx:
593                     self._uart = _UART(
594                         portId,
595                         baudrate,
596                         bits=bits,
597                         parity=parity,
598                         stop=stop,
599                         timeout=timeout,
600                         read_buf_len=receiver_buffer_size,
601                     )
602                     break
603             else:
604                 raise ValueError(
605                     "No Hardware UART on (tx,rx)={}\nValid UART ports: {}".format(
606                         (tx, rx), uartPorts
607                     )
608                 )
609
610     def deinit(self):
611         """Deinitialization"""
612         if detector.board.binho_nova:
613             self._uart.deinit()
614         self._uart = None
615
616     def read(self, nbytes=None):
617         """Read from the UART"""
618         return self._uart.read(nbytes)
619
620     def readinto(self, buf, nbytes=None):
621         """Read from the UART into a buffer"""
622         return self._uart.readinto(buf, nbytes)
623
624     def readline(self):
625         """Read a line of characters up to a newline character from the UART"""
626         return self._uart.readline()
627
628     def write(self, buf):
629         """Write to the UART from a buffer"""
630         return self._uart.write(buf)