+# 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
MCP2221_RETRY_MAX = 50
MCP2221_MAX_I2C_DATA_LEN = 60
MASK_ADDR_NACK = 0x40
-# pylint: enable=bad-whitespace
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"""
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)
# ----------------------------------------------------------------
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
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
# 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()
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(
# yay chunk sent!
while self._i2c_state() == RESP_I2C_PARTIALDATA:
time.sleep(0.001)
+ if not buffer:
+ break
start += chunk
retries = 0
# 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
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)
# pylint: enable=too-many-arguments
- def i2c_configure(self, baudrate=100000):
+ def _i2c_configure(self, baudrate=100000):
"""Configure I2C"""
self._hid_xfer(
bytes(
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