Skip to content

Commit 0a40338

Browse files
committed
mcp23009e: add driver for the I/O expender
1 parent 6069baa commit 0a40338

17 files changed

Lines changed: 2236 additions & 0 deletions

lib/mcp23009e/.gitkeep

Whitespace-only changes.

lib/mcp23009e/README.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# MicroPython library for MCP23009E I/O Expander
2+
3+
This library provides a complete driver to control the MCP23009E I/O expander on the STeaMi board.
4+
5+
## Features
6+
7+
-**Full GPIO control**: Configure pins as input/output, read/write levels
8+
-**Pull-up resistors**: Built-in pull-up support
9+
-**Interrupt support**: Hardware interrupts with callback system
10+
-**Pin-compatible API**: `MCP23009Pin` class compatible with `machine.Pin`
11+
-**Register access**: Low-level register access for advanced usage
12+
13+
## Quick Start
14+
15+
### Basic Usage with MCP23009E class
16+
17+
```python
18+
from machine import I2C, Pin
19+
from mcp23009e import MCP23009E
20+
from mcp23009e.const import *
21+
22+
# Initialize I2C and reset pin
23+
bus = I2C(1)
24+
reset = Pin("RST_EXPANDER", Pin.OUT)
25+
26+
# Create driver instance
27+
mcp = MCP23009E(bus, address=MCP23009_I2C_ADDR, reset_pin=reset)
28+
29+
# Configure a GPIO as input with pull-up
30+
mcp.setup(7, MCP23009_DIR_INPUT, pullup=MCP23009_PULLUP)
31+
32+
# Read the level
33+
level = mcp.get_level(7)
34+
print(f"GPIO 7 level: {level}")
35+
36+
# Configure a GPIO as output
37+
mcp.setup(0, MCP23009_DIR_OUTPUT)
38+
mcp.set_level(0, MCP23009_LOGIC_HIGH)
39+
```
40+
41+
### Pin-Compatible API with MCP23009Pin
42+
43+
The `MCP23009Pin` class provides a **machine.Pin-compatible** interface:
44+
45+
```python
46+
from machine import I2C, Pin
47+
from mcp23009e import MCP23009E, MCP23009Pin
48+
from mcp23009e.const import *
49+
50+
# Initialize
51+
bus = I2C(1)
52+
reset = Pin("RST_EXPANDER", Pin.OUT)
53+
mcp = MCP23009E(bus, address=MCP23009_I2C_ADDR, reset_pin=reset)
54+
55+
# Create a Pin object for a button
56+
btn = MCP23009Pin(mcp, 7, MCP23009Pin.IN, MCP23009Pin.PULL_UP)
57+
print(f"Button state: {btn.value()}")
58+
59+
# Create a Pin object for a LED
60+
led = MCP23009Pin(mcp, 0, MCP23009Pin.OUT)
61+
led.on() # Turn on
62+
led.off() # Turn off
63+
led.toggle() # Toggle state
64+
```
65+
66+
### Interrupts
67+
68+
```python
69+
from machine import I2C, Pin
70+
from mcp23009e import MCP23009E, MCP23009Pin
71+
from mcp23009e.const import *
72+
73+
# Initialize with interrupt pin
74+
bus = I2C(1)
75+
reset = Pin("RST_EXPANDER", Pin.OUT)
76+
interrupt = Pin("INT_EXPANDER", Pin.IN)
77+
mcp = MCP23009E(bus, address=MCP23009_I2C_ADDR, reset_pin=reset, interrupt_pin=interrupt)
78+
79+
# Create pin and configure interrupt
80+
btn = MCP23009Pin(mcp, 7, MCP23009Pin.IN, MCP23009Pin.PULL_UP)
81+
82+
def callback(pin):
83+
print(f"Button state changed: {pin.value()}")
84+
85+
btn.irq(handler=callback, trigger=MCP23009Pin.IRQ_FALLING | MCP23009Pin.IRQ_RISING)
86+
```
87+
88+
## Examples
89+
90+
The library includes several examples:
91+
92+
- `buttons.py` - Simple button reading with polling
93+
- `test_basic.py` - Basic driver functionality tests
94+
- `test_interrupts.py` - Interrupt system demonstration
95+
- `test_pin.py` - MCP23009Pin class usage examples
96+
- `test_pin_irq.py` - Pin-compatible interrupt examples
97+
98+
Run examples with [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html):
99+
100+
```sh
101+
mpremote mount . run examples/buttons.py
102+
mpremote mount . run examples/test_pin.py
103+
```
104+
105+
## API Reference
106+
107+
### MCP23009E Class
108+
109+
#### Constructor
110+
```python
111+
MCP23009E(i2c, address, reset_pin, interrupt_pin=None)
112+
```
113+
114+
#### Main Methods
115+
- `setup(gpx, direction, pullup, polarity)` - Configure a GPIO
116+
- `set_level(gpx, level)` - Set output level
117+
- `get_level(gpx)` - Read input level
118+
- `interrupt_on_change(gpx, callback)` - Register change callback
119+
- `interrupt_on_falling(gpx, callback)` - Register falling edge callback
120+
- `interrupt_on_raising(gpx, callback)` - Register rising edge callback
121+
- `disable_interrupt(gpx)` - Disable interrupts on a GPIO
122+
123+
### MCP23009Pin Class
124+
125+
#### Constructor
126+
```python
127+
MCP23009Pin(mcp, pin_number, mode=-1, pull=-1, value=None)
128+
```
129+
130+
#### Methods (machine.Pin compatible)
131+
- `init(mode, pull, value)` - (Re)configure the pin
132+
- `value(x=None)` - Get or set pin value
133+
- `on()` - Set pin high
134+
- `off()` - Set pin low
135+
- `toggle()` - Toggle pin state
136+
- `irq(handler, trigger)` - Configure interrupt
137+
- `mode(mode=None)` - Get or set mode
138+
- `pull(pull=None)` - Get or set pull configuration
139+
140+
#### Constants
141+
- `MCP23009Pin.IN` / `MCP23009Pin.OUT` - Pin modes
142+
- `MCP23009Pin.PULL_UP` - Pull-up configuration
143+
- `MCP23009Pin.IRQ_FALLING` / `MCP23009Pin.IRQ_RISING` - Interrupt triggers
144+
145+
### MCP23009ActiveLowPin Class
146+
147+
Special pin class for **active-low configurations** (LEDs connected between VCC and GPIO).
148+
149+
The MCP23009E can sink more current (25mA) than it can source (~1mA). For LEDs and similar loads, use this configuration:
150+
151+
```
152+
3.3V → [LED] → [Resistor 220-330Ω] → GPIO
153+
```
154+
155+
With `MCP23009ActiveLowPin`, the logic is automatically inverted:
156+
- `led.on()` → GPIO LOW → LED lights up
157+
- `led.off()` → GPIO HIGH → LED turns off
158+
159+
#### Example
160+
161+
```python
162+
from mcp23009e import MCP23009E, MCP23009ActiveLowPin
163+
164+
mcp = MCP23009E(bus, address=MCP23009_I2C_ADDR, reset_pin=reset)
165+
166+
# Create an active-low LED
167+
led = MCP23009ActiveLowPin(mcp, 0)
168+
led.on() # LED lights up (GPIO goes LOW)
169+
led.off() # LED turns off (GPIO goes HIGH)
170+
led.toggle() # Toggle LED state
171+
```
172+
173+
The API is identical to `MCP23009Pin` - just use `MCP23009ActiveLowPin` instead!

lib/mcp23009e/examples/buttons.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Exemple simple d'utilisation du MCP23009E pour lire les boutons du D-PAD
3+
Version avec lecture en polling (sans interruption)
4+
"""
5+
6+
from time import sleep
7+
from machine import I2C, Pin
8+
9+
from mcp23009e import MCP23009E
10+
from mcp23009e.const import *
11+
12+
# Configuration I2C
13+
bus = I2C(1)
14+
15+
# Configuration du pin de reset
16+
reset = Pin("RST_EXPANDER", Pin.OUT)
17+
18+
# Créer l'instance du driver
19+
mcp = MCP23009E(bus, address=MCP23009_I2C_ADDR, reset_pin=reset)
20+
21+
print("=================================")
22+
print("Test des boutons MCP23009E")
23+
print("=================================\n")
24+
25+
# Configuration des boutons
26+
btn_mapping = {
27+
MCP23009_BTN_UP: "UP",
28+
MCP23009_BTN_DOWN: "DOWN",
29+
MCP23009_BTN_LEFT: "LEFT",
30+
MCP23009_BTN_RIGHT: "RIGHT",
31+
}
32+
33+
print("Configuration des boutons...")
34+
for btn_pin in btn_mapping.keys():
35+
mcp.setup(btn_pin, MCP23009_DIR_INPUT, pullup=MCP23009_PULLUP)
36+
print("✓ Configuration terminée\n")
37+
38+
print("Appuyez sur les boutons du D-PAD (Ctrl+C pour arrêter)...")
39+
print("=" * 50)
40+
41+
try:
42+
last_states = {}
43+
while True:
44+
# Lire l'état de tous les boutons
45+
for btn_pin, btn_name in btn_mapping.items():
46+
level = mcp.get_level(btn_pin)
47+
48+
# Afficher uniquement les changements d'état
49+
if btn_pin not in last_states or last_states[btn_pin] != level:
50+
if level == MCP23009_LOGIC_LOW:
51+
print(f"Bouton {btn_name} APPUYÉ")
52+
else:
53+
print(f"Bouton {btn_name} RELÂCHÉ")
54+
last_states[btn_pin] = level
55+
56+
sleep(0.05) # Petite pause pour éviter de saturer le bus I2C
57+
58+
except KeyboardInterrupt:
59+
print("\n\nTest terminé!")

lib/mcp23009e/examples/i2c_scan.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Script de scan I2C pour identifier tous les périphériques connectés
3+
Utile pour trouver l'adresse correcte du MCP23009E
4+
"""
5+
from time import sleep
6+
from machine import I2C,Pin
7+
8+
reset = Pin("RST_EXPANDER")
9+
reset.init(Pin.OUT)
10+
reset.value(0)
11+
sleep(1)
12+
reset.value(1)
13+
14+
15+
16+
print("=" * 60)
17+
print("Scanner I2C - Recherche des périphériques")
18+
print("=" * 60)
19+
print()
20+
21+
# Tester les différents bus I2C disponibles
22+
# Sur STM32, généralement I2C(1) et I2C(2) sont disponibles
23+
for bus_num in [0, 1]:
24+
try:
25+
print(f"Scan du bus I2C({bus_num})...")
26+
i2c = I2C(bus_num)
27+
28+
print(f" Configuration: {i2c}")
29+
30+
# Scanner toutes les adresses possibles (0x08 à 0x77)
31+
devices = i2c.scan()
32+
33+
if devices:
34+
print(f" ✓ {len(devices)} périphérique(s) trouvé(s):")
35+
for addr in devices:
36+
print(f" - 0x{addr:02X} (décimal: {addr})")
37+
38+
# Indiquer si c'est potentiellement le MCP23009E
39+
if addr == 0x40:
40+
print(" → Pourrait être le MCP23009E (adresse par défaut)")
41+
elif addr in range(0x20, 0x28):
42+
print(" → Pourrait être un MCP23xxx")
43+
else:
44+
print(f" ✗ Aucun périphérique trouvé sur I2C({bus_num})")
45+
46+
print()
47+
48+
except Exception as e:
49+
print(f" ✗ Erreur lors du scan I2C({bus_num}): {e}")
50+
print()
51+
52+
print("=" * 60)
53+
print("Scan terminé!")
54+
print()
55+
print("Notes:")
56+
print(" - L'adresse par défaut du MCP23009E est 0x40 (décimal 64)")
57+
print(" - Les MCP23xxx standards utilisent 0x20-0x27")
58+
print(" - Vérifiez le schéma de votre carte pour confirmer l'adresse")
59+
print("=" * 60)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
Exemple de test basique pour le driver MCP23009E
3+
Ce script teste les fonctionnalités de base : configuration GPIO, lecture/écriture
4+
"""
5+
6+
from time import sleep
7+
from machine import I2C, Pin
8+
9+
from mcp23009e import MCP23009E
10+
from mcp23009e.const import *
11+
12+
# Configuration I2C (à adapter selon votre carte)
13+
# Sur STeaMi, l'I2C1 est généralement utilisé
14+
bus = I2C(1)
15+
16+
# IMPORTANT : Le pin RST_EXPANDER n'a pas de pull-up, il DOIT être initialisé
17+
# sinon le MCP23009E reste en état reset et ne répond pas sur I2C
18+
reset = Pin("RST_EXPANDER", Pin.OUT)
19+
20+
mcp = MCP23009E(bus, address=MCP23009_I2C_ADDR, reset_pin=reset)
21+
22+
print("=================================")
23+
print("Test du driver MCP23009E")
24+
print("=================================\n")
25+
26+
# Test 1 : Lecture des registres par défaut
27+
print("Test 1 : Lecture des registres initiaux")
28+
print(f" IODIR : 0x{mcp.get_iodir():02X} (devrait être 0xFF - toutes entrées)")
29+
print(f" GPPU : 0x{mcp.get_gppu():02X} (devrait être 0x00 - pas de pull-up)")
30+
print(f" GPINTEN: 0x{mcp.get_gpinten():02X} (devrait être 0x00 - pas d'interruptions)")
31+
print(f" IOCON : 0x{mcp.get_iocon().get_register_value():02X} (devrait être 0x00)")
32+
33+
print()
34+
35+
# Test 2 : Configuration d'un GPIO en entrée avec pull-up
36+
print("Test 2 : Configuration GPIO 7 en entrée avec pull-up")
37+
mcp.setup(7, MCP23009_DIR_INPUT, pullup=MCP23009_PULLUP)
38+
print(f" IODIR après setup: 0x{mcp.get_iodir():02X}")
39+
print(f" GPPU après setup : 0x{mcp.get_gppu():02X}")
40+
print()
41+
42+
# Test 3 : Test d'un GPIO en sortie
43+
print("\nTest 3 : Configuration GPIO en sortie")
44+
for i in MCP23009_GPIOS:
45+
mcp.setup(i, MCP23009_DIR_OUTPUT)
46+
print(f" LED {i}: HIGH")
47+
mcp.set_level(i, MCP23009_LOGIC_HIGH)
48+
sleep(0.5)
49+
print(f" LED {i}: LOW")
50+
mcp.set_level(i, MCP23009_LOGIC_LOW)
51+
sleep(0.5)
52+
print()
53+
54+
# Test 4 : Lecture du niveau logique des boutons du D-PAD
55+
print("Test 4 : Lecture des boutons du D-PAD")
56+
print(" (Les boutons sont normalement HIGH, appuyés = LOW)")
57+
btn_names = {
58+
MCP23009_BTN_UP: "UP",
59+
MCP23009_BTN_DOWN: "DOWN",
60+
MCP23009_BTN_LEFT: "LEFT",
61+
MCP23009_BTN_RIGHT: "RIGHT",
62+
}
63+
64+
for btn_pin, btn_name in btn_names.items():
65+
# Configurer tous les boutons en entrée avec pull-up
66+
mcp.setup(btn_pin, MCP23009_DIR_INPUT, pullup=MCP23009_PULLUP)
67+
68+
print("\nAppuyez sur les boutons du D-PAD (Ctrl+C pour arrêter)...")
69+
print("=" * 50)
70+
71+
try:
72+
while True:
73+
# Lire l'état de tous les boutons
74+
states = []
75+
for btn_pin, btn_name in btn_names.items():
76+
level = mcp.get_level(btn_pin)
77+
if level == MCP23009_LOGIC_LOW: # Bouton appuyé
78+
states.append(btn_name)
79+
80+
if states:
81+
print(f"Boutons appuyés: {', '.join(states)}")
82+
83+
sleep(0.1)
84+
85+
except KeyboardInterrupt:
86+
print("\n\nTest terminé!")
87+
88+
89+
print("\n=================================")
90+
print("Tests terminés avec succès!")
91+
print("=================================")

0 commit comments

Comments
 (0)