]> Repositories - Adafruit_Blinka-hackapet.git/blobdiff - src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py
Merge pull request #540 from ezio-melotti/close-mcp2221
[Adafruit_Blinka-hackapet.git] / src / adafruit_blinka / microcontroller / mcp2221 / mcp2221.py
index 4d1fee8278d0eefd834e62af169de32d7ee78c64..5ff027ab2803df4957508ee388cef9016c1f80e8 100644 (file)
@@ -1,18 +1,22 @@
+# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
 """Chip Definition for MCP2221"""
 
 import os
 import time
+import atexit
+
 import hid
 
 # Here if you need it
 MCP2221_HID_DELAY = float(os.environ.get("BLINKA_MCP2221_HID_DELAY", 0))
-# Use to set delay between reset and device reopen
+# Use to set delay between reset and device reopen. if negative, don't reset at all
 MCP2221_RESET_DELAY = float(os.environ.get("BLINKA_MCP2221_RESET_DELAY", 0.5))
 
 # from the C driver
 # http://ww1.microchip.com/downloads/en/DeviceDoc/mcp2221_0_1.tar.gz
 # others (???) determined during driver developement
-# pylint: disable=bad-whitespace
 RESP_ERR_NOERR = 0x00
 RESP_ADDR_NACK = 0x25
 RESP_READ_ERR = 0x7F
@@ -35,7 +39,6 @@ RESP_I2C_WRITINGNOSTOP = 0x45  # ???
 MCP2221_RETRY_MAX = 50
 MCP2221_MAX_I2C_DATA_LEN = 60
 MASK_ADDR_NACK = 0x40
-# pylint: enable=bad-whitespace
 
 
 class MCP2221:
@@ -53,7 +56,22 @@ class MCP2221:
     def __init__(self):
         self._hid = hid.device()
         self._hid.open(MCP2221.VID, MCP2221.PID)
-        self._reset()
+        # make sure the device gets closed before exit
+        atexit.register(self.close)
+        if MCP2221_RESET_DELAY >= 0:
+            self._reset()
+        self._gp_config = [0x07] * 4  # "don't care" initial value
+        for pin in range(4):
+            self.gp_set_mode(pin, self.GP_GPIO)  # set to GPIO mode
+            self.gpio_set_direction(pin, 1)  # set to INPUT
+
+    def close(self):
+        """Close the hid device. Does nothing if the device is not open."""
+        self._hid.close()
+
+    def __del__(self):
+        # try to close the device before destroying the instance
+        self.close()
 
     def _hid_xfer(self, report, response=True):
         """Perform HID Transfer"""
@@ -76,21 +94,21 @@ class MCP2221:
 
     def gp_set_mode(self, pin, mode):
         """Set Current Pin Mode"""
-        # get current settings
-        current = self._hid_xfer(b"\x61")
+        # already set to that mode?
+        mode &= 0x07
+        if mode == (self._gp_config[pin] & 0x07):
+            return
+        # update GP mode for pin
+        self._gp_config[pin] = mode
         # empty report, this is safe since 0's = no change
         report = bytearray(b"\x60" + b"\x00" * 63)
         # set the alter GP flag byte
         report[7] = 0xFF
-        # each pin can be set individually
-        # but all 4 get set at once, so we need to
-        # transpose current settings
-        report[8] = current[22]  # GP0
-        report[9] = current[23]  # GP1
-        report[10] = current[24]  # GP2
-        report[11] = current[25]  # GP3
-        # then change only the one
-        report[8 + pin] = mode & 0x07
+        # add GP setttings
+        report[8] = self._gp_config[0]
+        report[9] = self._gp_config[1]
+        report[10] = self._gp_config[2]
+        report[11] = self._gp_config[3]
         # and make it so
         self._hid_xfer(report)
 
@@ -132,6 +150,12 @@ class MCP2221:
     # ----------------------------------------------------------------
     def gpio_set_direction(self, pin, mode):
         """Set Current GPIO Pin Direction"""
+        if mode:
+            # set bit 3 for INPUT
+            self._gp_config[pin] |= 1 << 3
+        else:
+            # clear bit 3 for OUTPUT
+            self._gp_config[pin] &= ~(1 << 3)
         report = bytearray(b"\x50" + b"\x00" * 63)  # empty set GPIO report
         offset = 4 * (pin + 1)
         report[offset] = 0x01  # set pin direction
@@ -140,6 +164,12 @@ class MCP2221:
 
     def gpio_set_pin(self, pin, value):
         """Set Current GPIO Pin Value"""
+        if value:
+            # set bit 4
+            self._gp_config[pin] |= 1 << 4
+        else:
+            # clear bit 4
+            self._gp_config[pin] &= ~(1 << 4)
         report = bytearray(b"\x50" + b"\x00" * 63)  # empty set GPIO report
         offset = 2 + 4 * pin
         report[offset] = 0x01  # set pin value
@@ -174,7 +204,7 @@ class MCP2221:
             # bus release will need "a few hundred microseconds"
             time.sleep(0.001)
 
-    # pylint: disable=too-many-arguments
+    # pylint: disable=too-many-arguments,too-many-branches
     def _i2c_write(self, cmd, address, buffer, start=0, end=None):
         if self._i2c_state() != 0x00:
             self._i2c_cancel()
@@ -183,7 +213,7 @@ class MCP2221:
         length = end - start
         retries = 0
 
-        while (end - start) > 0:
+        while (end - start) > 0 or not buffer:
             chunk = min(end - start, MCP2221_MAX_I2C_DATA_LEN)
             # write out current chunk
             resp = self._hid_xfer(
@@ -208,6 +238,8 @@ class MCP2221:
             # yay chunk sent!
             while self._i2c_state() == RESP_I2C_PARTIALDATA:
                 time.sleep(0.001)
+            if not buffer:
+                break
             start += chunk
             retries = 0
 
@@ -252,7 +284,7 @@ class MCP2221:
 
         # and now the read part
         while (end - start) > 0:
-            for retry in range(MCP2221_RETRY_MAX):
+            for _ in range(MCP2221_RETRY_MAX):
                 # the actual read
                 resp = self._hid_xfer(b"\x40")
                 # check for success
@@ -270,6 +302,8 @@ class MCP2221:
                     continue
                 if resp[2] in (RESP_READ_COMPL, RESP_READ_PARTIAL):
                     break
+            else:
+                raise RuntimeError("I2C read error: max retries reached.")
 
             # move data into buffer
             chunk = min(end - start, 60)
@@ -279,7 +313,7 @@ class MCP2221:
 
     # pylint: enable=too-many-arguments
 
-    def i2c_configure(self, baudrate=100000):
+    def _i2c_configure(self, baudrate=100000):
         """Configure I2C"""
         self._hid_xfer(
             bytes(
@@ -310,7 +344,7 @@ class MCP2221:
         out_start=0,
         out_end=None,
         in_start=0,
-        in_end=None
+        in_end=None,
     ):
         """Write data from buffer_out to an address and then
         read data from an address and into buffer_in