initial working MCP2221

This commit is contained in:
caternuson 2019-11-20 21:00:09 -08:00
parent 8a4686cbc7
commit 103b2f2455
10 changed files with 397 additions and 0 deletions

View file

@ -0,0 +1,9 @@
from adafruit_blinka.microcontroller.mcp2221 import pin
G0 = pin.G0
G1 = pin.G1
G2 = pin.G2
G3 = pin.G3
SCL = pin.SCL
SDA = pin.SDA

View file

@ -0,0 +1,24 @@
from adafruit_blinka.microcontroller.mcp2221.pin import Pin
from .mcp2221 import mcp2221
class I2C:
def __init__(self, *, baudrate=100000):
mcp2221.i2c_configure(baudrate)
def scan(self):
return mcp2221.i2c_scan()
def writeto(self, address, buffer, *, start=0, end=None, stop=True):
mcp2221.i2c_writeto(address, buffer, start=start, end=end)
def readfrom_into(self, address, buffer, *, start=0, end=None, stop=True):
mcp2221.i2c_readfrom_into(address, buffer, start=start, end=end)
def writeto_then_readfrom(self, address, buffer_out, buffer_in, *,
out_start=0, out_end=None,
in_start=0, in_end=None, stop=False):
mcp2221.i2c_writeto_then_readfrom(address, buffer_out, buffer_in,
out_start=out_start, out_end=out_end,
in_start=in_start, in_end=in_end)

View file

@ -0,0 +1,227 @@
import time
import hid
class MCP2221:
VID = 0x04D8
PID = 0x00DD
GP_GPIO = 0b000
GP_DEDICATED = 0b001
GP_ALT0 = 0b010
GP_ALT1 = 0b011
GP_ALT2 = 0b100
def __init__(self):
self._hid = hid.device()
self._hid.open(MCP2221.VID, MCP2221.PID)
def _hid_xfer(self, report, response=True):
# first byte is report ID, which =0 for MCP2221
# remaing bytes = 64 byte report data
# https://github.com/libusb/hidapi/blob/083223e77952e1ef57e6b77796536a3359c1b2a3/hidapi/hidapi.h#L185
self._hid.write(b'\0' + report + b'\0'*(64-len(report)))
if response:
# return is 64 byte response report
return self._hid.read(64)
#----------------------------------------------------------------
# MISC
#----------------------------------------------------------------
def gp_get_mode(self, pin):
return self._hid_xfer(bytes([0x61]))[22+pin] & 0x07
def gp_set_mode(self, pin, mode):
# get current settings
current = self._hid_xfer(bytes([0x61]))
# empty report, this is safe since 0's = no change
report = bytearray([0x60]+[0]*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
# and make it so
self._hid_xfer(report)
def _pretty_report(self, report):
print(" 0 1 2 3 4 5 6 7 8 9")
index = 0
for row in range(7):
print("{} : ".format(row), end='')
for _ in range(10):
print("{:02x} ".format(report[index]), end='')
index += 1
if index > 63:
break
print()
def _status_dump(self):
self._pretty_report(self._hid_xfer(bytes([0x10])))
def _sram_dump(self):
self._pretty_report(self._hid_xfer(bytes([0x61])))
def _reset(self):
self._hid_xfer(b'\x70\xAB\xCD\xEF', response=False)
time.sleep(1)
self._hid.open(MCP2221.VID, MCP2221.PID)
#----------------------------------------------------------------
# GPIO
#----------------------------------------------------------------
def gpio_set_direction(self, pin, mode):
report = bytearray([0x50]+[0]*63) # empty set GPIO report
offset = 4 * (pin + 1)
report[offset] = 0x01 # set pin direction
report[offset+1] = mode # to this
self._hid_xfer(report)
def gpio_set_pin(self, pin, value):
report = bytearray([0x50]+[0]*63) # empty set GPIO report
offset = 2 + 4 * pin
report[offset] = 0x01 # set pin value
report[offset+1] = value # to this
self._hid_xfer(report)
def gpio_get_pin(self, pin):
resp = self._hid_xfer(bytes([0x51]))
offset = 2 + 2 * pin
if resp[offset] == 0xEE:
raise RuntimeError("Pin is not set for GPIO operation.")
else:
return resp[offset]
#----------------------------------------------------------------
# I2C
#
# cribbed from the C driver:
# define RESP_ERR_NOERR 0x00
# define RESP_ADDR_NACK 0x25
# define RESP_READ_ERR 0x7F
# define RESP_READ_COMPL 0x55
# define RESP_I2C_IDLE 0x00
# define RESP_I2C_START_TOUT 0x12
# define RESP_I2C_RSTART_TOUT 0x17
# define RESP_I2C_WRADDRL_TOUT 0x23
# define RESP_I2C_WRADDRL_WSEND 0x21
# define RESP_I2C_WRADDRL_NACK 0x25
# define RESP_I2C_WRDATA_TOUT 0x44
# define RESP_I2C_RDDATA_TOUT 0x52
# define RESP_I2C_STOP_TOUT 0x62
#----------------------------------------------------------------
def i2c_configure(self, baudrate=100000):
self._hid_xfer(bytes([0x10, # set parameters
0x00, # don't care
0x00, # no effect
0x20, # next byte is clock divider
12000000 // baudrate - 3]))
def i2c_writeto(self, address, buffer, *, start=0, end=None):
end = end if end else len(buffer)
self._hid_xfer(bytes([0x90, # i2c write data
end - start & 0xFF, # xfer length lo byte
end - start >> 8 & 0xFF, # xfer length hi byte
address << 1]) + # i2c slave address
buffer[start:end]) # user data to be sent
def i2c_readfrom_into(self, address, buffer, *, start=0, end=None):
end = end if end else len(buffer)
retries = 0
while retries < 5:
#
# why does this require two xfers?
#
# 1. tell it we want to read
self._hid_xfer(bytes([0x91, # i2c read data
end - start & 0xFF, # xfer length lo byte
end - start >> 8 & 0xFF, # xfer length hi byte
address << 1 | 0x01])) # i2c slave address
# 2. and then actually read
response = self._hid_xfer(bytes([0x40]))
# check for success
if response[1] == 0x00:
break
retries += 1
if retries >= 5:
raise RuntimeError("I2C read error, max retries reached.")
# move data into buffer
for i in range(end - start):
buffer[start + i] = response[4 + i]
def i2c_writeto_then_readfrom(self, address, out_buffer, in_buffer, *,
out_start=0, out_end=None,
in_start=0, in_end=None):
out_end = out_end if out_end else len(buffer_out)
in_end = in_end if in_end else len(buffer_in)
self._hid_xfer(bytes([0x94, # i2c write data no stop
out_end - out_start & 0xFF, # xfer length lo byte
out_end - out_start >> 8 & 0xFF, # xfer length hi byte
address << 1]) + # i2c slave address
out_buffer[out_start:out_end]) # user data to be sent
retries = 5
while retries < 5:
#
# why does this require two xfers?
#
# 1. tell it we want to read
self._hid_xfer(bytes([0x93, # i2c read data repeated start
in_end - in_start & 0xFF, # xfer length lo byte
in_end - in_start >> 8 & 0xFF, # xfer length hi byte
address << 1 | 0x01])) # i2c slave address
# 2. and then actually read
response = self._hid_xfer(bytes([0x40]))
# check for success
if response[1] == 0x00:
break
retries += 1
if retries >= 5:
raise RuntimeError("I2C read error, max retries reached.")
# move data into buffer
for i in range(in_end - in_start):
in_buffer[in_start + i] = response[4 + i]
def i2c_scan(self, *, start=0, end=0x79):
found = []
for addr in range(start, end+1):
# try a write
self.i2c_writeto(addr, b'\x00')
# store if success
if self._hid_xfer(b'\x10')[8] == 0x00:
found.append(addr)
# cancel and continue
self._hid_xfer(b'\x10\x00\x10')
return found
#----------------------------------------------------------------
# ADC
#----------------------------------------------------------------
def adc_configure(self, vref=0):
report = bytearray([0x60]+[0]*63)
report[5] = 1 << 7 | (vref & 0b111)
self._hid_xfer(report)
def adc_read(self, pin):
resp = self._hid_xfer(bytes([0x10]))
return resp[49 + 2 * pin] << 8 | resp[48 + 2 * pin]
#----------------------------------------------------------------
# DAC
#----------------------------------------------------------------
def dac_configure(self, vref=0):
report = bytearray([0x60]+[0]*63)
report[3] = 1 << 7 | (vref & 0b111)
self._hid_xfer(report)
def dac_write(self, pin, value):
report = bytearray([0x60]+[0]*63)
report[4] = 1 << 7 | (value & 0b11111)
self._hid_xfer(report)
mcp2221 = MCP2221()

View file

@ -0,0 +1,74 @@
from .mcp2221 import mcp2221
class Pin:
"""A basic Pin class for use with MCP2221."""
# pin modes
OUT = 0
IN = 1
ADC = 2
DAC = 3
# pin values
LOW = 0
HIGH = 1
def __init__(self, pin_id=None):
self.id = pin_id
self._mode = None
def init(self, mode=IN, pull=None):
if self.id is None:
raise RuntimeError("Can not init a None type pin.")
if mode in (Pin.IN, Pin.OUT):
mcp2221.gp_set_mode(self.id, mcp2221.GP_GPIO)
mcp2221.gpio_set_direction(self.id, mode)
elif mode == Pin.ADC:
mcp2221.gp_set_mode(self.id, mcp2221.GP_ALT0)
mcp2221.adc_configure()
elif mode == Pin.DAC:
mcp2221.gp_set_mode(self.id, mcp2221.GP_ALT1)
mcp2221.dac_configure()
else:
raise ValueError("Incorrect pin mode: {}".format(mode))
self._mode = mode
def value(self, val=None):
# Digital In / Out
if self._mode in (Pin.IN, Pin.OUT):
# digital read
if val is None:
return mcp2221.gpio_get_pin(self.id)
# digital write
elif val in (Pin.LOW, Pin.HIGH):
mcp2221.gpio_set_pin(self.id, val)
# nope
else:
raise ValueError("Invalid value for pin.")
# Analog In
elif self._mode == Pin.ADC:
if val is None:
# MCP2221 ADC is 10 bit, scale to 16 bit per CP API
return mcp2221.adc_read(self.id) * 64
else:
# read only
raise AttributeError("'AnalogIn' object has no attribute 'value'")
# Analog Out
elif self._mode == Pin.DAC:
if val is None:
# write only
raise AttributeError("unreadable attribute")
else:
# scale 16 bit value to MCP2221 5 bit DAC (yes 5 bit)
mcp2221.dac_write(self.id, val // 2048)
else:
raise RuntimeError("No action for mode {} with value {}".format(self._mode, val))
# create pin instances for each pin
G0 = Pin(0)
G1 = Pin(1)
G2 = Pin(2)
G3 = Pin(3)
SCL = Pin()
SDA = Pin()

52
src/analogio.py Normal file
View file

@ -0,0 +1,52 @@
"""
`analogio` - Analog input and output control
=================================================
See `CircuitPython:analogio` in CircuitPython for more details.
* Author(s): Carter Nelson
"""
from adafruit_blinka.agnostic import board_id, detector
# pylint: disable=ungrouped-imports,wrong-import-position
if detector.board.microchip_mcp2221:
from adafruit_blinka.microcontroller.mcp2221.pin import Pin
else:
raise NotImplementedError("analogio not supported for this board.")
from adafruit_blinka import ContextManaged
class AnalogIn(ContextManaged):
def __init__(self, pin):
self._pin = Pin(pin.id)
self._pin.init(mode=Pin.ADC)
@property
def value(self):
return self._pin.value()
@value.setter
def value(self, value):
# emulate what CircuitPython does
raise AttributeError("'AnalogIn' object has no attribute 'value'")
def deinit(self):
del self._pin
class AnalogOut(ContextManaged):
def __init__(self, pin):
self._pin = Pin(pin.id)
self._pin.init(mode=Pin.DAC)
@property
def value(self):
# emulate what CircuitPython does
raise AttributeError("unreadable attribute")
@value.setter
def value(self, value):
self._pin.value(value)
def deinit(self):
del self._pin

View file

@ -100,6 +100,9 @@ elif board_id == ap_board.DRAGONBOARD_410C:
elif board_id == ap_board.FTDI_FT232H:
from adafruit_blinka.board.ftdi_ft232h import *
elif board_id == ap_board.MICROCHIP_MCP2221:
from adafruit_blinka.board.microchip_mcp2221 import *
elif "sphinx" in sys.modules:
pass

View file

@ -21,6 +21,10 @@ class I2C(Lockable):
from adafruit_blinka.microcontroller.ft232h.i2c import I2C
self._i2c = I2C()
return
if detector.board.microchip_mcp2221:
from adafruit_blinka.microcontroller.mcp2221.i2c import I2C
self._i2c = I2C()
return
elif detector.board.any_embedded_linux:
from adafruit_blinka.microcontroller.generic_linux.i2c import I2C as _I2C
else:

View file

@ -37,6 +37,8 @@ elif detector.board.ftdi_ft232h:
from adafruit_blinka.microcontroller.ft232h.pin import Pin
elif detector.chip.STM32:
from machine import Pin
elif detector.board.microchip_mcp2221:
from adafruit_blinka.microcontroller.mcp2221.pin import Pin
from adafruit_blinka import Enum, ContextManaged
class DriveMode(Enum):

View file

@ -34,5 +34,7 @@ elif chip_id == ap_chip.IMX8MX:
from adafruit_blinka.microcontroller.nxp_imx8m.pin import *
elif chip_id == ap_chip.FT232H:
from adafruit_blinka.microcontroller.ft232h.pin import *
elif chip_id == ap_chip.MCP2221:
from adafruit_blinka.microcontroller.mcp2221.pin import *
else:
raise NotImplementedError("Microcontroller not supported: ", chip_id)