Skip to content

Commit 7968c70

Browse files
committed
Update the PCAL9554 functions.
All functions checked with hardware and appear to work properly.
1 parent 840e24b commit 7968c70

File tree

3 files changed

+275
-34
lines changed

3 files changed

+275
-34
lines changed

i2c_expanders/PCA9554.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,20 @@ def __init__(self, i2c, address=_PCA9554_ADDRESS, reset=True):
7070
self.maxpins = 7
7171
self.capability = _enable_bit(0x00, Capability.INVERT_POL)
7272
if reset:
73-
# Reset to all inputs with no pull-ups and no inverted polarity.
74-
self.iodir = 0xFF # Set all IOs to inputs
75-
self.ipol = 0x00 # Set polatiry inversion off for all pins
73+
self.reset_to_defaults()
74+
75+
def reset_to_defaults(self):
76+
"""Reset all registers to their default state. This is also
77+
done with a power cycle, but it can be called by software here.
78+
79+
:return: Nothing.
80+
"""
81+
# TODO: Should I make some sort of 'register' class to
82+
# handle memory addresses and default states?
83+
# Input port register is read only.
84+
self.gpio = 0xFF
85+
self.ipol = 0x00
86+
self.iodir = 0xFF
7687

7788
@property
7889
def gpio(self):

i2c_expanders/PCAL9554.py

Lines changed: 235 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
#
55
# SPDX-License-Identifier: MIT
66

7-
# pylint: disable=too-many-public-methods
7+
# pylint: disable=too-many-public-methods, duplicate-code
8+
# Note: there is a bit of duplicated code between this and the PCAL9555. This code is duplicated
9+
# for these two expanders, but may not be if other expanders are added to this library. I
10+
# therefore want to keep is separate in these two classes. The line above disables the
11+
# pylint check for this.
812

9-
# TODO: mostly a copy/paste from the PCAL9555, make sure this stuff is still true here.
1013
"""
1114
`PCAL9554`
1215
====================================================
@@ -77,16 +80,191 @@ def __init__(self, i2c, address=_PCAL9554_ADDRESS, reset=True):
7780
super().__init__(
7881
i2c, address, False
7982
) # This initializes the PCA9554 compatible registers.
80-
self._capability = (
83+
self.capability = (
8184
_enable_bit(0x00, Capability.PULL_UP)
8285
| _enable_bit(0x00, Capability.PULL_DOWN)
8386
| _enable_bit(0x00, Capability.INVERT_POL)
84-
| _enable_bit(0x00, Capability.DRIVE_MODE)
8587
) # TODO: This device does not really have a capability to set drive mode the way
8688
# digitalio is expecting. I should probably not set this here.
8789
if reset:
8890
self.reset_to_defaults()
8991

92+
def set_int_pin(self, pin, latch=False):
93+
"""Enable interrupt on a pin. Interrupts are generally triggered by any state change
94+
of the pin. There is an exception to this, see info below on latching for details.
95+
96+
:param pin: Pin number to modify.
97+
:param latch: Set to True to enable latching on this interrupt. Defaults to False.
98+
:return: Nothing.
99+
"""
100+
# Validate inputs
101+
self._validate_pin(pin)
102+
if not isinstance(latch, (bool)):
103+
raise ValueError("latch must be True or False")
104+
105+
self.irq_mask = _clear_bit(self.irq_mask, pin)
106+
107+
if latch:
108+
self.input_latch = _enable_bit(self.input_latch, pin)
109+
else:
110+
self.input_latch = _clear_bit(self.input_latch, pin)
111+
112+
def clear_int_pin(self, pin):
113+
"""Disable interrupts on a pin.
114+
115+
:param pin: Pin number to modify.
116+
:return: Nothing.
117+
"""
118+
self._validate_pin(pin)
119+
self.irq_mask = _enable_bit(self.irq_mask, pin)
120+
121+
def get_int_pins(self):
122+
"""Returns a list of pins causing an interrupt. It is possible for multiple pins
123+
to be causing an interrupt. Calling this function will not clear the interrupt state.
124+
125+
:return: Returns a list of pin numbers.
126+
"""
127+
output = []
128+
reg = self.irq_status
129+
for i in range(self.maxpins):
130+
if ((reg >> i) & 1) == 1:
131+
output.append(i)
132+
return output
133+
134+
def set_int_latch(self, pin):
135+
"""Set the interrupt on 'pin' to latching operation. Note this does not enable
136+
or disable the interrupt or clear the interrupt state.
137+
138+
:param pin: Pin number to modify.
139+
:return: Nothing.
140+
"""
141+
self._validate_pin(pin)
142+
self.input_latch = _enable_bit(self.input_latch, pin)
143+
144+
def clear_int_latch(self, pin):
145+
"""Set the interrupt on 'pin' to non-latching operation. Note this does not enable
146+
or disable the interrupt. This does not clear the interrupt state.
147+
148+
:param pin: Pin number to modify.
149+
:return: Nothing.
150+
"""
151+
self._validate_pin(pin)
152+
self.input_latch = _clear_bit(self.input_latch, pin)
153+
154+
"""Interrupt latch behavior
155+
By default (non-latched) if an interrupt enabled pin changes state, but changes back before the GPIO state register is read, the interrupt state
156+
will be cleared. Setting the interrupt latch will cause the device to latch on a state change of the input pin. With latching enabled, on a state
157+
change to the pin, the interrupt pin will be asserted and will not deassert until the input register is read. The value read from the input register
158+
will be the value that caused the interrupt, not nessecarially the current value of the pin. If the pin changed state, but changed back before the
159+
input register was read, the changed state will be what is returned in the register. The state change back to the original state will not trigger
160+
another interrupt as long as it happens before the input register is read. If the input register is read before the pin state changes back to the
161+
original value, both state changes will cause an interrupt.
162+
"""
163+
164+
def get_pupd(self, pin):
165+
"""Checks the state of a pin to see if pull up/down is enabled.
166+
167+
:param pin: Pin number to check.
168+
:return: Returns 'digitalio.Pull.UP', 'digitalio.Pull.DOWN' or 'None'
169+
to indicate the state of the pin.
170+
"""
171+
self._validate_pin(pin)
172+
# The else statements here are extaneous, but without them, it is harder to tell
173+
# what the code is doing. Disable pylint for that error here only.
174+
if _get_bit(self.pupd_en, pin):
175+
if _get_bit(self.pupd_sel, pin): # pylint: disable=no-else-return
176+
return digitalio.Pull.UP
177+
else:
178+
return digitalio.Pull.DOWN
179+
else:
180+
return None
181+
182+
def set_pupd(self, pin, status):
183+
"""Sets the state of the pull up/down resistors on a pin.
184+
185+
:param pin: Pin number to modify.
186+
:param status: The new state of the pull up/down resistors. Should be one of
187+
'digitalio.Pull.UP', 'digitalio.Pull.DOWN' or 'None'.
188+
:return: Nothing.
189+
"""
190+
self._validate_pin(pin)
191+
192+
if status is None:
193+
self.pupd_en = _clear_bit(self.pupd_en, pin)
194+
return
195+
196+
self.pupd_en = _enable_bit(self.pupd_en, pin)
197+
198+
if status == digitalio.Pull.UP:
199+
self.pupd_sel = _enable_bit(self.pupd_sel, pin)
200+
elif status == digitalio.Pull.DOWN:
201+
self.pupd_sel = _clear_bit(self.pupd_sel, pin)
202+
else:
203+
raise ValueError("Expected UP, DOWN, or None for pull state.")
204+
205+
def set_output_drive(self, pin, drive):
206+
"""Sets the output drive strength of a pin.
207+
208+
:param pin: Pin number to modify.
209+
:param drive: The drive strength value to set. See the class 'Drive_strength'
210+
for valid values to set.
211+
:return: Nothing.
212+
"""
213+
self._validate_pin(pin)
214+
# Check inputs to make sure they are valid
215+
if (drive > 3) or (drive < 0):
216+
raise ValueError("Invalid drive strength value.")
217+
218+
loc = pin * 2 # Bit location in the register
219+
val = drive << loc # Value to set shifted to the proper location
220+
mask = ~(3 << loc) & 0xFFFF # Mask to clear the two bits we need to set.
221+
222+
self.out_drive = ((self.out_drive) & (mask)) | val
223+
224+
def get_output_drive(self, pin):
225+
"""Reads the drive strength value of the given pin.
226+
227+
:param pin: Pin number to check.
228+
:return: The current drive strength. Return values are shown in the
229+
'Drive_strength' class
230+
"""
231+
self._validate_pin(pin)
232+
233+
val = self.out_drive
234+
loc = pin * 2 # Bit location in the register
235+
return (val >> loc) & 0x03
236+
237+
def set_drive_mode(self, mode):
238+
"""Configures the output drive of the entire output bank. Sets the outputs to either
239+
open drain or push-pull. Note that this is not a per-pin setting. All pins are set to the
240+
same mode.
241+
242+
:param mode: The mode to set. Should be one of either 'digitalio.DriveMode.PUSH_PULL'
243+
or 'digitalio.DriveMode.OPEN_DRAIN'.
244+
245+
:return: Nothing.
246+
"""
247+
248+
if mode == digitalio.DriveMode.PUSH_PULL:
249+
self.out_port_config = _clear_bit(self.out_port_config, 0)
250+
elif mode == digitalio.DriveMode.OPEN_DRAIN:
251+
self.out_port_config = _enable_bit(self.out_port_config, 0)
252+
else:
253+
raise ValueError(
254+
"Invalid drive mode. It should be either 'digitalio.DriveMode.PUSH_PULL' "
255+
"or 'digitalio.DriveMode.OPEN_DRAIN'."
256+
)
257+
258+
def get_drive_mode(self):
259+
"""Returns the drive mode of the output bank. This is the drive mode of all pins.
260+
261+
:return: The drive mode. Either 'digitalio.DriveMode.PUSH_PULL'
262+
or 'digitalio.DriveMode.OPEN_DRAIN'.
263+
"""
264+
if _get_bit(self.out_port_config, 0) == 0x01:
265+
return digitalio.DriveMode.OPEN_DRAIN
266+
return digitalio.DriveMode.PUSH_PULL
267+
90268
def reset_to_defaults(self):
91269
"""Reset all registers to their default state. This is also
92270
done with a power cycle, but it can be called by software here.
@@ -97,16 +275,38 @@ def reset_to_defaults(self):
97275
# memory addresses and default states?
98276
# Input port and interrupt status registers are read only.
99277
self.gpio = 0xFF
100-
self.ipol = 0x0000
278+
self.ipol = 0x00
101279
self.iodir = 0xFF
102280

103-
# self.out0_drive = 0xFFFF
104-
# self.out1_drive = 0xFFFF
105-
# self.input_latch = 0x0000
106-
# self.pupd_en = 0xFFFF
107-
# self.pupd_sel = 0xFFFF
108-
# self.irq_mask = 0xFFFF
109-
# self.out_port_config = 0x00
281+
self.out_drive = 0xFFFF
282+
self.input_latch = 0x00
283+
self.pupd_en = 0x00 # TODO: 0xFF for PCAL9554, 0x00 for PCAL9538
284+
self.pupd_sel = 0xFF
285+
self.irq_mask = 0xFF
286+
self.out_port_config = 0x00
287+
288+
""" Low level register access. These functions directly set or read the values of the
289+
registers on the device. In general, you should not need to call these
290+
functions directly.
291+
"""
292+
293+
@property
294+
def out_drive(self):
295+
"""Output drive strength of pins 0-7."""
296+
return self._read_u16le(_PCAL9554_OUTPUT_DRIVE_1)
297+
298+
@out_drive.setter
299+
def out_drive(self, val):
300+
self._write_u16le(_PCAL9554_OUTPUT_DRIVE_1, val)
301+
302+
@property
303+
def input_latch(self):
304+
"""Sets latching or non-latching interrupts per pin."""
305+
return self._read_u8(_PCAL9554_INPUT_LATCH)
306+
307+
@input_latch.setter
308+
def input_latch(self, val):
309+
self._write_u8(_PCAL9554_INPUT_LATCH, val)
110310

111311
@property
112312
def pupd_en(self):
@@ -126,21 +326,30 @@ def pupd_sel(self):
126326
def pupd_sel(self, val):
127327
self._write_u8(_PCAL9554_PUPD_SEL, val)
128328

129-
# Enable interrupt on a pin. Interrupts are triggered by any state change of the pin.
130-
def set_int_pin(self, pin):
131-
"""Set interrupt pin"""
132-
self._write_u8(
133-
_PCAL9554_IRQ_MASK, _clear_bit(self._read_u8(_PCAL9554_IRQ_MASK), pin)
134-
)
329+
@property
330+
def irq_mask(self):
331+
"""Masks or unmasks pins for generating interrupts."""
332+
return self._read_u8(_PCAL9554_IRQ_MASK)
135333

136-
# Disable interrupts on a pin.
137-
def clear_int_pin(self, pin):
138-
"""Clear interrupt pin"""
139-
self._write_u8(
140-
_PCAL9554_IRQ_MASK, _enable_bit(self._read_u8(_PCAL9554_IRQ_MASK), pin)
141-
)
334+
@irq_mask.setter
335+
def irq_mask(self, val):
336+
self._write_u8(_PCAL9554_IRQ_MASK, val)
142337

143338
@property
144-
def get_int_status(self):
145-
"""Get interrupt status"""
339+
def irq_status(self):
340+
"""Indicates which pin caused an interrupt."""
146341
return self._read_u8(_PCAL9554_IRQ_STATUS)
342+
343+
@irq_status.setter
344+
def irq_status(self, val):
345+
# Regsiter is read only
346+
pass
347+
348+
@property
349+
def out_port_config(self):
350+
"""Sets output banks to open drain or push-pull operation."""
351+
return self._read_u8(_PCAL9554_OUTPUT_PORT_CONFIG)
352+
353+
@out_port_config.setter
354+
def out_port_config(self, val):
355+
self._write_u8(_PCAL9554_OUTPUT_PORT_CONFIG, val)

i2c_expanders/PCAL9555.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
#
55
# SPDX-License-Identifier: MIT
66

7-
# pylint: disable=too-many-public-methods
7+
# pylint: disable=too-many-public-methods, duplicate-code
8+
9+
# Note: there is a bit of duplicated code between this and the PCAL9554. This code is duplicated
10+
# for these two expanders, but may not be if other expanders are added to this library. I
11+
# therefore want to keep is separate in these two classes. The line above disables the
12+
# pylint check for this.
13+
814

915
"""
1016
`PCAL9555`
@@ -147,7 +153,7 @@ def get_int_pins(self):
147153
"""
148154
output = []
149155
reg = self.irq_status
150-
for i in range(15):
156+
for i in range(self.maxpins):
151157
if ((reg >> i) & 1) == 1:
152158
output.append(i)
153159
return output
@@ -300,6 +306,21 @@ def set_drive_mode(self, bank, mode):
300306
"or 'digitalio.DriveMode.OPEN_DRAIN'."
301307
)
302308

309+
def get_drive_mode(self, bank):
310+
"""Reads the output drive of an output bank. All pins in bank 0 (pins 0-7) or
311+
1 (pins 8-15) are set to the same mode.
312+
313+
:param bank: The bank to set. Should be 0 or 1.
314+
:return: The drive mode. Either 'digitalio.DriveMode.PUSH_PULL'
315+
or 'digitalio.DriveMode.OPEN_DRAIN'.
316+
"""
317+
if (bank > 1) or (bank < 0):
318+
raise ValueError("Bank should be either 0 (pins 0-7) or 1 (pins 8-15).")
319+
320+
if _get_bit(self.out_port_config, bank) == 0x01:
321+
return digitalio.DriveMode.OPEN_DRAIN
322+
return digitalio.DriveMode.PUSH_PULL
323+
303324
def reset_to_defaults(self):
304325
"""Reset all registers to their default state. This is also
305326
done with a power cycle, but it can be called by software here.
@@ -378,8 +399,7 @@ def irq_mask(self):
378399

379400
@irq_mask.setter
380401
def irq_mask(self, val):
381-
# This register is read only.
382-
pass
402+
self._write_u16le(_PCAL9555_IRQ_MASK_0, val)
383403

384404
@property
385405
def irq_status(self):
@@ -388,7 +408,8 @@ def irq_status(self):
388408

389409
@irq_status.setter
390410
def irq_status(self, val):
391-
self._write_u16le(_PCAL9555_IRQ_STATUS_0, val)
411+
# This register is read only.
412+
pass
392413

393414
@property
394415
def out_port_config(self):

0 commit comments

Comments
 (0)