X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/blobdiff_plain/a3770186a94117be56a8d9a8038a2d540bd2884c..64755adc8a8c0dbfd792d3eb60d02fd033be530e:/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py diff --git a/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py b/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py index 4d1fee8..8e76cc7 100644 --- a/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py +++ b/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py @@ -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) @@ -115,6 +133,7 @@ class MCP2221: def _reset(self): self._hid_xfer(b"\x70\xAB\xCD\xEF", response=False) + self._hid.close() time.sleep(MCP2221_RESET_DELAY) start = time.monotonic() while time.monotonic() - start < 5: @@ -132,6 +151,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 +165,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 +205,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 +214,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 +239,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 @@ -215,7 +248,7 @@ class MCP2221: for _ in range(MCP2221_RETRY_MAX): status = self._i2c_status() if status[20] & MASK_ADDR_NACK: - raise RuntimeError("I2C slave address was NACK'd") + raise OSError("I2C slave address was NACK'd") usb_cmd_status = status[8] if usb_cmd_status == 0: break @@ -252,7 +285,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 +303,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 +314,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 +345,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