Skip to content

Commit 9e9a743

Browse files
committed
libs/driver: Add aw9523 driver.
Signed-off-by: lbuque <[email protected]>
1 parent d257b0e commit 9e9a743

File tree

2 files changed

+317
-0
lines changed

2 files changed

+317
-0
lines changed

m5stack/libs/driver/aw9523.py

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
import machine
6+
import micropython
7+
8+
9+
def _aw9523_closure() -> tuple: # noqa: C901
10+
aw = None
11+
12+
class _AW9523:
13+
"""Micropython driver for AW9523 GPIO expander.
14+
15+
:param I2C i2c: The AW9523 is connected to the i2c bus.
16+
:param int address: The i2c address of the AW9523. Default is 0x59.
17+
:param int irq_pin: The pin number of the interrupt pin. Default is None.
18+
"""
19+
20+
AW9523_REG_CHIPID = 0x10 # ///< Register for hardcode chip ID
21+
AW9523_REG_SOFTRESET = 0x7F # ///< Register for soft resetting
22+
AW9523_REG_INPUT0 = 0x00 # ///< Register for reading input values
23+
AW9523_REG_OUTPUT0 = 0x02 # ///< Register for writing output values
24+
AW9523_REG_CONFIG0 = 0x04 # ///< Register for configuring direction
25+
AW9523_REG_INTENABLE0 = 0x06 # ///< Register for enabling interrupt
26+
AW9523_REG_GCR = 0x11 # ///< Register for general configuration
27+
AW9523_REG_LEDMODE = 0x12 # ///< Register for configuring const current
28+
29+
def __new__(cls, *args, **kwargs) -> "_AW9523":
30+
nonlocal aw
31+
if aw is None:
32+
aw = super().__new__(cls)
33+
aw._initialized = False
34+
return aw
35+
36+
def __init__(self, i2c, address: int = 0x59, irq_pin=None) -> None:
37+
self._i2c = i2c
38+
self._address = address
39+
if self._initialized:
40+
return
41+
42+
uid = self._i2c.readfrom_mem(self._address, 0x10, 1)[0]
43+
if uid != 0x23:
44+
raise ValueError("AW9523 not found")
45+
46+
self._i2c.writeto_mem(self._address, self.AW9523_REG_CONFIG0, b"\xff\xff") # all input
47+
self._reg_bit_on(self.AW9523_REG_GCR, 4) # pull up mode
48+
self._i2c.writeto_mem(
49+
self._address, self.AW9523_REG_INTENABLE0, b"\xff\xff"
50+
) # no interrupt
51+
52+
# clear PI4IOE5V6408 interrupt
53+
try:
54+
self._i2c.readfrom_mem(0x43, 0x13, 1)
55+
except OSError:
56+
pass
57+
58+
if irq_pin is not None:
59+
print("irq pin", irq_pin)
60+
self._irq = machine.Pin(irq_pin, machine.Pin.IN, machine.Pin.PULL_UP)
61+
self._irq.irq(self._irq_pin_handler, machine.Pin.IRQ_FALLING)
62+
63+
self._pin_table = [None for _ in range(16)]
64+
self._last_input_states = [0 for _ in range(16)]
65+
self._initialized = True
66+
67+
def apply(self, pin) -> None:
68+
"""Apply a pin to the pin table.
69+
70+
:param Pin pin: The pin to be applied. The pin is an instance of Pin.
71+
"""
72+
self._pin_table[pin._id] = pin
73+
74+
def unapply(self, pin) -> None:
75+
"""Remove a pin from the pin table.
76+
77+
:param Pin pin: The pin to be removed. The pin is an instance of Pin.
78+
"""
79+
self._pin_table[pin._id] = None
80+
81+
def pin_irq_enable(self, pid: int, en: bool) -> None:
82+
"""enable or disable interrupt for a pin.
83+
84+
:param int pid: The pin number. The pin number is 0-15.
85+
:param bool en: True for enable, False for disable.
86+
"""
87+
register = self.AW9523_REG_INTENABLE0 + (pid >> 3) # 8 bits per register
88+
if en:
89+
self._reg_bit_off(register, pid % 8)
90+
else:
91+
self._reg_bit_on(register, pid % 8)
92+
93+
def _irq_pin_handler(self, pin: machine.Pin) -> None:
94+
"""The interrupt handler for the interrupt pin.
95+
96+
:param Pin pin: The interrupt pin. The pin is an instance of Pin.
97+
"""
98+
# print("irq_handler")
99+
# clear interrupt
100+
# self._i2c.readfrom_mem(0x43, 0x13, 1) # clear PI4IOE5V6408 interrupt
101+
self._i2c.readfrom_mem(self._address, self.AW9523_REG_INPUT0, 2)
102+
103+
# higl level interrupt
104+
for i in range(16):
105+
if self._pin_table[i] is not None:
106+
p = self._pin_table[i]
107+
cur_value = p()
108+
if p._mode != p.IN:
109+
continue
110+
if cur_value == self._last_input_states[i]:
111+
continue
112+
113+
# rising or falling
114+
trigger = (
115+
p.IRQ_RISING if cur_value > self._last_input_states[i] else p.IRQ_FALLING
116+
)
117+
# trigger
118+
if p.handler[trigger] is not None:
119+
micropython.schedule(p.handler[trigger], p)
120+
121+
self._last_input_states[i] = cur_value
122+
123+
def _reg_bit_on(self, reg: int, bit: int) -> None:
124+
"""Turn on a bit in a register.
125+
126+
:param int reg: The register address.
127+
:param int bit: The bit number to turn on. The bit number is 0-7.
128+
"""
129+
print("reg_bit_on", reg, bit)
130+
value = self._i2c.readfrom_mem(self._address, reg, 1)[0]
131+
self._i2c.writeto_mem(self._address, reg, bytes([value | 1 << bit]))
132+
133+
def _reg_bit_off(self, reg: int, bit: int) -> None:
134+
"""Turn off a bit in a register.
135+
136+
:param int reg: The register address.
137+
:param int bit: The bit number to turn off. The bit number is 0-7.
138+
"""
139+
print("reg_bit_off", reg, bit)
140+
value = self._i2c.readfrom_mem(self._address, reg, 1)[0]
141+
self._i2c.writeto_mem(self._address, reg, bytes([value & ~(1 << bit)]))
142+
143+
def set_pin_mode(self, pid: int, mode: int) -> None:
144+
"""Set the mode of a pin.
145+
146+
:param int pid: The pin number. The pin number is 0-15.
147+
:param int mode: The mode of the pin, machine.Pin.IN or machine.Pin.OUT.
148+
"""
149+
register = self.AW9523_REG_CONFIG0 + (pid >> 3)
150+
if mode == machine.Pin.IN:
151+
self._reg_bit_on(register, pid % 8) # input
152+
else:
153+
self._reg_bit_off(register, pid % 8) # output
154+
155+
def get_output_state(self, pid: int) -> int:
156+
"""Get the output state of a pin.
157+
158+
:param int pid: The pin number. The pin number is 0-15.
159+
:return: The output state of the pin. 1 for high, 0 for low.
160+
:rtype: int
161+
"""
162+
register = self.AW9523_REG_OUTPUT0 + (pid >> 3)
163+
value = self._i2c.readfrom_mem(self._address, register, 1)[0]
164+
return value >> (pid % 8) & 0x01
165+
166+
def set_output_state(self, pid: int, state: int) -> None:
167+
"""Set the output state of a pin.
168+
169+
:param int pid: The pin number. The pin number is 0-15.
170+
:param int state: The output state of the pin. 1 for high, 0 for low
171+
"""
172+
register = self.AW9523_REG_OUTPUT0 + (pid >> 3)
173+
if state:
174+
self._reg_bit_on(register, pid % 8) # high
175+
else:
176+
self._reg_bit_off(register, pid % 8) # low
177+
178+
def get_input_state(self, pid: int) -> int:
179+
"""Get the input state of a pin.
180+
181+
:param int pid: The pin number. The pin number is 0-15.
182+
:return: The input state of the pin. 1 for high, 0 for low.
183+
:rtype: int
184+
"""
185+
register = self.AW9523_REG_INPUT0 + (pid >> 3)
186+
value = self._i2c.readfrom_mem(self._address, register, 1)[0]
187+
return value >> (pid % 8) & 0x01
188+
189+
class _Pin:
190+
"""The Pin class for AW9523 GPIO expander.
191+
192+
:param int id: The pin number. The pin number is 0-15.
193+
:param int mode: The mode of the pin, machine.Pin.IN or machine.Pin.OUT. Default is machine.Pin.IN.
194+
:param int value: The initial value of the pin. Default is None.
195+
"""
196+
197+
IN = machine.Pin.IN
198+
OUT = machine.Pin.OUT
199+
200+
IRQ_FALLING = machine.Pin.IRQ_FALLING
201+
IRQ_RISING = machine.Pin.IRQ_RISING
202+
203+
def __init__(self, id, mode: int = IN, value=None) -> None:
204+
nonlocal aw
205+
if aw is None:
206+
raise ValueError("AW9523 not initialized")
207+
self._aw = aw
208+
self._id = id
209+
self._mode = mode
210+
self._aw.set_pin_mode(self._id, self._mode)
211+
if value is not None:
212+
self._aw.set_output_state(self._id, value)
213+
self._aw.apply(self)
214+
self.handler = {self.IRQ_FALLING: None, self.IRQ_RISING: None}
215+
216+
def init(self, mode: int = -1, value=None) -> None:
217+
"""Re-initialise the pin using the given parameters.
218+
219+
:param int mode: The mode of the pin, machine.Pin.IN or machine.Pin.OUT. Default is machine.Pin.IN.
220+
:param int value: The initial value of the pin. Default is None.
221+
"""
222+
self._aw.set_pin_mode(self._id, mode)
223+
if value is not None:
224+
self._aw.set_output_state(self._id, value)
225+
226+
def value(self, *args):
227+
"""
228+
This method allows to set and get the value of the pin, depending on whether
229+
the argument ``args`` is supplied or not.
230+
231+
If the argument is omitted then this method gets the digital logic level of
232+
the pin, returning 0 or 1 corresponding to low and high voltage signals
233+
respectively. The behaviour of this method depends on the mode of the pin:
234+
235+
- ``Pin.IN`` - The method returns the actual input value currently present
236+
on the pin.
237+
- ``Pin.OUT`` - The behaviour and return value of the method is undefined.
238+
239+
If the argument is supplied then this method sets the digital logic level of
240+
the pin. The argument ``args`` can be anything that converts to a boolean.
241+
If it converts to ``True``, the pin is set to state '1', otherwise it is set
242+
to state '0'. The behaviour of this method depends on the mode of the pin:
243+
244+
- ``Pin.IN`` - The value is stored in the output buffer for the pin. The
245+
pin state does not change, it remains in the high-impedance state. The
246+
stored value will become active on the pin as soon as it is changed to
247+
``Pin.OUT`` or ``Pin.OPEN_DRAIN`` mode.
248+
- ``Pin.OUT`` - The output buffer is set to the given value immediately.
249+
250+
When setting the value this method returns ``None``.
251+
"""
252+
return self.__call__(*args)
253+
254+
def __call__(self, *args):
255+
"""
256+
This method allows to set and get the value of the pin, depending on whether
257+
the argument ``args`` is supplied or not.
258+
259+
If the argument is omitted then this method gets the digital logic level of
260+
the pin, returning 0 or 1 corresponding to low and high voltage signals
261+
respectively. The behaviour of this method depends on the mode of the pin:
262+
263+
- ``Pin.IN`` - The method returns the actual input value currently present
264+
on the pin.
265+
- ``Pin.OUT`` - The behaviour and return value of the method is undefined.
266+
267+
If the argument is supplied then this method sets the digital logic level of
268+
the pin. The argument ``args`` can be anything that converts to a boolean.
269+
If it converts to ``True``, the pin is set to state '1', otherwise it is set
270+
to state '0'. The behaviour of this method depends on the mode of the pin:
271+
272+
- ``Pin.IN`` - The value is stored in the output buffer for the pin. The
273+
pin state does not change, it remains in the high-impedance state. The
274+
stored value will become active on the pin as soon as it is changed to
275+
``Pin.OUT`` or ``Pin.OPEN_DRAIN`` mode.
276+
- ``Pin.OUT`` - The output buffer is set to the given value immediately.
277+
278+
When setting the value this method returns ``None``.
279+
"""
280+
if len(args) == 0:
281+
if self._mode == self.IN:
282+
return self._aw.get_input_state(self._id)
283+
else:
284+
return self._aw.get_output_state(self._id)
285+
elif len(args) == 1:
286+
self._aw.set_output_state(self._id, args[0])
287+
288+
def on(self) -> None:
289+
"""Set pin to "1" output level."""
290+
self._aw.set_output_state(self._id, 1)
291+
292+
def off(self) -> None:
293+
"""Set pin to "0" output level."""
294+
self._aw.set_output_state(self._id, 0)
295+
296+
def irq(self, handler=None, trigger=IRQ_FALLING | IRQ_RISING) -> None:
297+
"""Enable interrupt for the pin.
298+
299+
:param function handler: The interrupt handler function.
300+
:param int trigger: The interrupt trigger mode, machine.Pin.IRQ_FALLING or machine.Pin.IRQ_RISING.
301+
"""
302+
self._aw.pin_irq_enable(self._id, True)
303+
self.handler[trigger] = handler
304+
305+
def __del__(self) -> None:
306+
"""De-initialise the pin."""
307+
self._aw.unapply(self)
308+
309+
def deinit(self) -> None:
310+
"""De-initialise the pin."""
311+
self._aw.unapply(self)
312+
313+
return _AW9523, _Pin
314+
315+
316+
AW9523, Pin = _aw9523_closure()

m5stack/libs/driver/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"ads1100.py",
4141
"adxl34x.py",
4242
"atgm336h.py",
43+
"aw9523.py",
4344
"asr650x.py",
4445
"bh1750.py",
4546
"bh1750fvi.py",

0 commit comments

Comments
 (0)