Michael BOUVY
CTO E-commerce

Microchip MCP23008 : multiplexage 8-bits I2C

atmega328 arduino i2c mux microchip mcp23008
Published on 2012/12/08

Lors de la réalisation de circuits à base de microcontrôleurs (Arduino Uno avec µC ATMega 328 par exemple), il arrive très fréquemment d'être limités par le nombre de pins d'entrées/sorties, et particulièrement dans le cas d'accessoires communiquant en parallèle (keypad, écran LCD, etc.), utilisant ainsi de nombreux pins sur nos µC.

Une des solutions afin de réduire significativement le nombre de pins nécessaires est d'utiliser un "port expander" afin de multiplexer les signaux : nous parlerons ici du MCP23008 de Microchip, fonctionnant sur le bus I2C.

Microchip MCP23008

Caractéristiques principales

  MCP23008
Pins 18
Tension de fonctionnement 1.8 à 5.5V
Bus I2C
Entrées / sorties 8
Débit maximal 1700 kb/s

Connexion à votre microcontrôleur

Pin MCP23008 Pin Arduino Uno Pin ATMega328P
1 (I2C clock) 5 28
2 (I2C data) 4 27
3, 4, 5 (address) GND GND
6 (power), 18 (reset) VCC VCC
9 GND GND
10 à 17 (GP0 à GP7) I/O pin I/O pin
Attention à bien utiliser la même tension entre votre µC et le MCP23008, ou un convertisseur de niveau.

Mapping pins Microchip MCP23008

Librairie Adafruit MCP23008

Si vous souhaitez utiliser le MCP23008 avec votre Arduino (testé avec un Uno) ou µC ATMega avec bootloader Arduino, Adafruit met à disposition sur GitHub une librairie prête à l'emploi, à installer dans votre dossier "libraries" de l'IDE Arduino. Cette librairie est fournie avec 2 exemples : button et toggle. Son utilisation est très simple, voici un exemple (inspiré de "toggle") pour faire clignoter en alternance 2 LED connectées aux E/S GP0 (pin 10) et GP1 (pin 11) du MCP23008 :


Interruptions

Comme nous venons de le voir, il est très simple d'utiliser le MCP23008 pour gérer des sorties. Dans le cas de l'utilisation des pins GPIO en tant qu'entrées, deux cas de figure se présentent :

  • Vous souhaitez lire des états à intervalles réguliers ou lors d'une action en particulier
  • Vous souhaitez détecter un changement d'état, pour gérer un keypad ou des boutons par exemple

Dans le premier cas, il vous suffira d'utiliser quand nécessaire la méthode digitalRead de la librairie Adafruit MCP23008. Dans le second cas, vous serez obligé de lire constamment l'état du ou des pins afin de détecter un changement d'état : votre programme ne fera donc que ça, comme le montre l'exemple ci-dessous :

#include <Wire.h>
#include "Adafruit_MCP23008.h"

Adafruit_MCP23008 mcp;
volatile int pinState = 0;
void setup() { 
  mcp.begin();      // Utilisation de l'adresse "0" par défaut 
  mcp.pinMode(0, INPUT); // Pin GP0 en entrée
  pinState = digitalRead(0); // On initialise la variable d'état du pin GP0 
}

void loop() { 
  if(mcp.digitalRead(0) != pinState) {
    // do something ... 
    pinState = !pinState;
  } 
  delay(5);
}
A noter que dans le cas de pins définis en entrée sur votre MCP23008, il est possible de les "tirer vers un état haut" à la manière d'une résistance de pull-up, avec la méthode pullUp.

Si le programme réalisait d'autre opérations au moment où l'on presse un bouton par exemple, il se pourrait qu'un changement d'état ne soit pas "capturé" par mcp.digitalRead(0). Une des solutions à ce problème est d'utiliser le mécanisme d'interruptions présent dans le MCP23008 (pin 8 / INT). Malheureusement, la librairie Adafruit ne supporte pas (encore) les interruptions ; vous pourrez toutefois télécharger à la fin de cet article une version modifiée de cette librairie gérant les interruptions. Les registres suivants (sur 8 bits) sont utilisés dans le MCP23008 pour gérer les interruptions :

  • GPINTEN (0x02) : permet de définir si les interruptions sont activées (1) ou non (0) sur un pin
MCP23008 GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0
Valeur par défaut 0 0 0 0 0 0 0 0
Mode R/W R/W R/W R/W R/W R/W R/W R/W

Ainsi, si l'on souhaite activer la détection des interruptions sur le pin GP0, il faudra définir le registre à : 0b00000001 soit 0x01. A noter que ce les registres DEFVAL et INCON doivent également être définis pour les pins définis à 1 dans GPINTEN (détection des interruptions activée).

  • INTCON (0x04) : définit si l'interruption doit être déclenchée par un changement d'état d'un pin par rapport à une valeur par défaut dans DEFVAL (1) ou par rapport à son état précédent (0)
MCP23008 GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0
Valeur par défaut 0 0 0 0 0 0 0 0
Mode R/W R/W R/W R/W R/W R/W R/W R/W
  • DEFVAL (0x03) : utile quand INTCON est à 1 pour un pin donné, définit l'état par défaut d'un pin
MCP23008 GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0
Valeur par défaut 0 0 0 0 0 0 0 0
Mode R/W R/W R/W R/W R/W R/W R/W R/W
  • IOCON (0x05) : registre de configuration du MCP23008 ; le bit intéressant ici est le 1 (INTPOL) : s'il est défini à 1, le pin INT sera "active-high", s'il est à 0, le pin INT sera "active-low", ce qui correspond à l'état sur ce pin quand une interruption sera déclenchée.
MCP23008 - - SEQOP DISSLW HAEN ODR INTPOL -
Valeur par défaut - - 0 0 0 0 0 -
Mode - - R/W R/W R/W R/W R/W -

Attention, par défaut le pin INT est "active-low" et sera donc à un état haut tant qu'une interruption n'est pas envoyée. Si vous décidez de changer ce paramètre à 1, le pin INT au démarrage du MCP23008 sera tout de même durant quelques millisecondes en état haut (active-low) avant de passer en état bas (active-high). Si vous écoutez une interruption de type "CHANGE" sur votre Arduino/µC, celle-ci sera alors déclenchée. Une solution consiste à "attacher l'interruption" (attachInterrupt()) qu'après avoir configuré le MCP23008 dans son setup().

  • INTF (0x07) : ce registre indique quels pins (actifs [1] dans GPINTEN) ont déclenché une interruption
MCP23008 GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0
Mode R R R R R R R R
  • INTCAP (0x08) : ce registre est une capture de l'état des pins (1 = haut, 0 = bas) lors du déclenchement d'une interruption
MCP23008 GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0
Mode R R R R R R R R

Les registres INTF et INTCAP sont "nettoyés" (= remis à 0) lors de la lecture du registre INTCAP ou du registre GPIO. Pour plus de détails sur ce point précis, n'hésitez pas à vous référer à la page 19 de la datasheet (lien ci-dessous).

Un exemple concret

Prenons un cas concret : vous souhaitez générer une interruption lors de passage à l'état haut bas (0) du pin GP0 (qui sera donc par défaut à l'état haut) du MCP23008. Les valeurs des différents registres seront les suivantes :

  • GPINTEN = 00000001 = 0x01 = 1 : on active les interruptions pour le pin GP0 (bit 0 mis à 1)
  • INTCON = 00000001 = 0x01 = 1 : l'interruption sera déclenchée par un changement d'état par rapport à la valeur par défaut
  • DEFVAL = 00000001 = 0x01 = 1 : la valeur par défaut de GP0 est 1 (état haut)
Lorsque GP0 passera à un état bas, une interruption sera déclenchée et nous aurons :
  • INTF = 00000001 = 0x01 = 1  : c'est le pin GP0 qui a déclenché l'interruption
  • INTCAP = 00000000 = 0x00 = 0 : le pin GP0 (comme tous les autres) était à l'état 0 (bas) au moment de l'interruption

Version modifiée de la librairie Adafruit

La librairie fournie par Adafruit n'offrant malheureusement pas le support des interruptions pour le MCP23008, j'y ai apporté quelques modifications dans cette version. Outre les méthodes standard de la librairie d'origine, voici les méthodes disponibles pour gérer les interruptions :

void Adafruit_MCP23008::pinIntPolarity(uint8_t p);

Définition de l'état actif  (active-state) du pin INT du MCP23008. uint8_t p : polarité (1 = HIGH, 0 = LOW)

 
void Adafruit_MCP23008::pinInt(uint8_t p, uint8_t m);

Activation des interruptions sur des pins du MCP23008. uint8_t p : port (0 à 7) uint8_t m : mode (1 = actif, 0 = inactif)

 
void Adafruit_MCP23008::pinIntControl(uint8_t p, uint8_t c);

Comparaison de l'état d'un pin avec son état par défaut (DEFVAL) ou sa valeur précédente. uint8_t p : port (0 à 7) uint8_t c : control mode (1 = DEFVAL, 0 = valeur précédente)

 
void Adafruit_MCP23008::pinIntDefval(uint8_t p, uint8_t v);

Valeur par défaut du pin pour comparaison. uint8_t p : port (0 à 7) uint8_t v : valeur (1 = HIGH, 0 = LOW)

 
uint8_t Adafruit_MCP23008::readIntFlag(void);

Lecture du registre INTF indiquant le/les pin(s) ayant déclenché l'interruption. Cette méthode ne retourne pas le n° du pin (0 à 7) mais la valeur du registre.

 
uint8_t Adafruit_MCP23008::readIntCapture(void);

Lecture du registre INTCAP indiquant l'état des pins au moment de l'interruption.

Liens utiles

Michael BOUVY

I'm Michael BOUVY, CTO and co-founder of Click&Mortar, a digital agency based in Paris, France, specialized in e-commerce.

Over the last years, I've worked as an Engineering Manager and CTO for brands like Zadig&Voltaire and Maisons du Monde.

With more than 10 years experience in e-commerce platforms, I'm always looking for new challenges, feel free to get in touch!