#!/usr/bin/env python # Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from struct import * from array import array as Array import time REG_IODIR = 0x00 REG_IPOL = 0x01 REG_GPINTEN = 0x02 REG_DEFVAL = 0x03 REG_INTCON = 0x04 REG_IOCON = 0x05 REG_GPPU = 0x06 REG_INTF = 0x07 REG_INTCAP = 0x08 REG_GPIO = 0x09 REG_OLAT = 0x0a ACK = 0 class Mcp23009: def __init__(self, address): self.i2cBus = None # the write address self.i2cAddress = address & 0xFE def setup(self, i2cBus): self.i2cBus = i2cBus # I was dumb and put the LEDs to GND even though the IO expander is OD # outputs :(. They will never work. Ignore them for now. # make buttons, USB power outputs (OD), leave accessory PU and LEDS as is. curIODIR = unpack('>B', self._i2cRead8BitReg(REG_IODIR))[0] & 0xEF self._i2cWrite8BitReg(REG_IODIR, curIODIR) self._i2cWrite8BitReg(REG_GPPU, 0x10) def _i2cWrite8BitReg(self, regAddress, regValue): self.i2cBus.Start() writeString = pack('>BBB', self.i2cAddress, regAddress, regValue) self.i2cBus.Write(writeString) if self.i2cBus.GetAck() != ACK: print "NO ACK RECEIVED w0" #self.i2cBus.Stop() #raise Exception("No ack received for command string %s" % writeString) self.i2cBus.Stop() def _i2cRead8BitReg(self, regAddress): self.i2cBus.Start() writeString = pack('>BB', self.i2cAddress, regAddress) self.i2cBus.Write(writeString) if self.i2cBus.GetAck() != ACK: print "NO ACK RECEIVED r1" self.i2cBus.Start() writeString = pack('B', (self.i2cAddress + 0x01)) self.i2cBus.Write(writeString) if self.i2cBus.GetAck() != ACK: print "NO ACK RECEIVED r3" self.i2cBus.SendNacks() data = self.i2cBus.Read(1) self.i2cBus.SendAcks() self.i2cBus.Stop() return data def setButtons(self, back=False, up=False, down=False, select=False): # read the current GPIO register and mask out the buttons curGPIO = unpack('>B',self._i2cRead8BitReg(REG_GPIO))[0] | 0x0f print "Before - setButton: 0x%x" % curGPIO if back: curGPIO &= 0xf7 if up: curGPIO &= 0xfb if select: curGPIO &= 0xfd if down: curGPIO &= 0xfe print "After - setButton: 0x%x" % curGPIO self._i2cWrite8BitReg(REG_GPIO, curGPIO) def configureGPIODirection(self, gpio_mask, as_output=True): # The mask tells us what IOs we would like to be an output or input gpiodir = unpack('>B', self._i2cRead8BitReg(REG_IODIR))[0] if as_output: new_gpiodir = gpiodir & ~gpio_mask # 1 == input, 0 == output else: new_gpiodir = gpiodir | gpio_mask if gpiodir != new_gpiodir: self._i2cWrite8BitReg(REG_IODIR, new_gpiodir) gpiodir = unpack('>B', self._i2cRead8BitReg(REG_IODIR))[0] print "REG_IODIR = 0x%x" % gpiodir def setUsbChargeEn(self, chargeEnable=False): usb_en_mask = 0x10 self.configureGPIODirection(usb_en_mask) # read the current GPIO register and mask out the USB V+ En curGPIO = unpack('>B', self._i2cRead8BitReg(REG_GPIO))[0] & 0xEF print "Before - setUsbChargeEn: 0x%x" % curGPIO if chargeEnable: curGPIO |= usb_en_mask print "After - setUsbChargeEn 0x%x" % curGPIO self._i2cWrite8BitReg(REG_GPIO, curGPIO) def setAccessoryPullup(self, pullupEnable=False): acc_en_mask = 0x20 self.configureGPIODirection(acc_en_mask) # read the current GPIO register curGPIO = unpack('>B', self._i2cRead8BitReg(REG_GPIO))[0] is_enabled = (curGPIO & acc_en_mask) == 0 # Are we requesting a change in state? if (is_enabled != pullupEnable): curGPIO &= ~acc_en_mask # Clear the current setting if not pullupEnable: # The ACC_PU_EN is active low curGPIO |= acc_en_mask self._i2cWrite8BitReg(REG_GPIO, curGPIO) curGPIO = unpack('>B', self._i2cRead8BitReg(REG_GPIO))[0] print "REG_GPIO = 0x%x" % curGPIO def reset(self): # this is the reset sequence self._i2cWrite8BitReg(REG_IODIR, 0xFF) self._i2cWrite8BitReg(REG_IPOL, 0x00) self._i2cWrite8BitReg(REG_GPINTEN, 0x00) self._i2cWrite8BitReg(REG_DEFVAL, 0x00) self._i2cWrite8BitReg(REG_INTCON, 0x00) self._i2cWrite8BitReg(REG_IOCON, 0x00) self._i2cWrite8BitReg(REG_GPPU, 0x00) self._i2cWrite8BitReg(REG_INTF, 0x00) self._i2cWrite8BitReg(REG_INTCAP, 0x00) self._i2cWrite8BitReg(REG_GPIO, 0x00) self._i2cWrite8BitReg(REG_OLAT, 0x00) def readRegs(self): for reg in range(0,11): print "%x: %x" %(reg, unpack('>B',self._i2cRead8BitReg(reg))[0])