Yet Another WebIOPi+
 All Classes Namespaces Files Functions Variables Macros Pages
pca9555.py
Go to the documentation of this file.
1 # Copyright 2014 Andreas Riegg - t-h-i-n-x.net
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 # Changelog
16 #
17 # 1.0 2014/08/20 Initial release.
18 # 1.1 2014/09/30 Added PCA9535 support and remarks on PCF8575.
19 #
20 # Config parameters
21 #
22 # - slave 7 bit I2C slave address
23 #
24 # Usage remarks
25 #
26 # - Digital I/O channels are bound to the GPIOPort class
27 # - This driver is derived as copy and modify from the PCA9698 driver. Future
28 # releases may include a merge with the PCA9698 driver if appropriate
29 # - Reading of output channels retrieves OPx register values
30 # - Some peformance optimizations have been incorporated due to the high
31 # number of channels in order to avoid excessive I2C bus calls
32 # - The polarity inversion and interrupt masking functions are currently
33 # not suppoprted
34 # - This driver also supports the PCA9535, but this has not been tested so far
35 # - This driver does definitely NOT work for the PCF8575 as this chip has no
36 # I2C registers to access the I/O ports via specific register addresses
37 #
38 
39 from webiopi.utils.types import toint
40 from webiopi.devices.i2c import I2C
41 from webiopi.devices.digital import GPIOPort
42 
44 
45 #---------- Constants and definitons ----------
46 
47  FUNCTIONS = [] # Needed for performance improvements
48  INPUTMASK = 0 # dito
49 
50  # I2C Registers (currently unused ones are commented out)
51  IP0 = 0x00 # IP0 input port (read only) register address
52  # IP1 is sequential on 0x01
53  OP0 = 0x02 # OP0 output port (write/read) register address
54  # OP1 is sequential on 0x03
55  #PI0 = 0x04 # PI0 polarity inversion (write/read) register address
56  # PI1 is sequential on 0x05
57  CP0 = 0x06 # CP0 I/O configuration register address
58  # CP1 is sequential on 0x07
59  # direction control values are input=1 output=0
60 
61  # Constants
62  CHANNELS = 16 # This chip has 16 I/O ports organized as
63  BANKS = 2 # 2 banks (@8bit each)
64  OP_DEFAULT = 0xFF # Output port values default to 1
65  CP_DEFAULT = 0xFF # Port configuration registers default to 1 (input)
66 
67 
68 #---------- Class initialisation ----------
69 
70  def __init__(self, slave=0x20):
71  I2C.__init__(self, toint(slave))
72  GPIOPort.__init__(self, self.CHANNELS)
73  self.reset()
74 
75 
76 #---------- Abstraction framework contracts ----------
77 
78  def __str__(self):
79  return "PCA9555(slave=0x%02X)" % self.slave
80 
81 
82 #---------- GPIOPort abstraction related methods ----------
83 
84  def __digitalRead__(self, channel):
85  if self.FUNCTIONS[channel] == self.OUT:
86  # Read output latches for output ports
87  reg_base = self.OP0
88  else:
89  reg_base = self.IP0
90  (addr, mask) = self.__getChannel__(reg_base, channel)
91  d = self.readRegister(addr)
92  return (d & mask) == mask
93 
94  def __digitalWrite__(self, channel, value):
95  (addr, mask) = self.__getChannel__(self.OP0, channel)
96  d = self.readRegister(addr)
97  if value:
98  d |= mask
99  else:
100  d &= ~mask
101  self.writeRegister(addr, d)
102 
103  def __getFunction__(self, channel):
104  return self.FUNCTIONS[channel]
105 
106  def __setFunction__(self, channel, value):
107  if not value in [self.IN, self.OUT]:
108  raise ValueError("Requested function not supported")
109 
110  (addr, mask) = self.__getChannel__(self.CP0, channel)
111  d = self.readRegister(addr)
112  if value == self.IN:
113  d |= mask
114  else:
115  d &= ~mask
116  self.writeRegister(addr, d)
117 
118  self.FUNCTIONS[channel] = value
119  self.__updateInputMask__()
120 
121  def __portRead__(self):
122  # Read all IP_ and OP_ and mix them together according to the FUNCTIONS list
123  ipdata = self.readRegisters(self.IP0, self.BANKS)
124  ipvalue = 0
125  for i in range(self.BANKS):
126  ipvalue |= ipdata[i] << 8*i
127 
128  opdata = self.readRegisters(self.OP0, self.BANKS)
129  opvalue = 0
130  for i in range(self.BANKS):
131  opvalue |= opdata[i] << 8*i
132 
133  (ipmask, opmask) = self.__getFunctionMasks__()
134  return (ipvalue & ipmask) | (opvalue & opmask)
135 
136  def __portWrite__(self, value):
137  data = bytearray(self.BANKS)
138  for i in range(self.BANKS):
139  data[i] = (value >> 8*i) & 0xFF
140  self.writeRegisters(self.OP0, data)
141 
142 
143 #---------- Device features ----------
144 
145  def reset(self):
146  self.__resetFunctions__()
147  self.__resetOutputs__()
148 
149 
150 #---------- Local helpers ----------
151 
153  # Default is to have all ports as input
154  self.writeRegisters(self.CP0, bytearray([self.CP_DEFAULT for i in range (self.BANKS)]))
155  self.FUNCTIONS = [self.IN for i in range(self.CHANNELS)]
156  self.__updateInputMask__()
157 
158  def __resetOutputs__(self):
159  # Default is to have all output latches set to OP_DEFAULT
160  self.writeRegisters(self.OP0, bytearray([self.OP_DEFAULT for i in range (self.BANKS)]))
161 
162  def __getAddress__(self, register, channel=0):
163  # Registers (8bit) are in increasing sequential order
164  return register + int(channel / 8)
165 
166  def __getChannel__(self, register, channel):
167  self.checkDigitalChannel(channel)
168  addr = self.__getAddress__(register, channel)
169  mask = 1 << (channel % 8)
170  return (addr, mask)
171 
173  channels = self.digitalChannelCount
174  self.INPUTMASK = 0
175  for i in range (channels):
176  if self.FUNCTIONS[i] == self.IN:
177  self.INPUTMASK |= 1 << i
178 
180  channels = self.digitalChannelCount
181  inputMask = self.INPUTMASK
182  return (inputMask, (~inputMask & ((1 << channels) - 1) ))
183  # & expression is necessary to avoid 2's complement problems with using
184  # ~ on very large numbers
185 
186 class PCA9535(PCA9555):
187 
188 #---------- Class initialisation ----------
189 
190  def __init__(self, slave=0x20):
191  PCA9555.__init__(self, toint(slave))
192 
193 
194 #---------- Abstraction framework contracts ----------
195 
196  def __str__(self):
197  return "PCA9535(slave=0x%02X)" % self.slave
198