====== Ajouter des GPIO au Raspberry pi en utilisant une puce MCP23017 ou MCP23008 ====== Si le nombre de GPIO disponibles sur le Raspberry Pi ne vous convient pas, pas d'inquiétude, il est possible d'en rajouter. La puce MCP23017 permet d'ajouter 16 GPIO , et la puce MCP23008 permet d'en rajouter 8. Dans les deux cas, ces puces se connectent au Raspberry Pi sur des GPIO "spéciaux", dédiés au protocole //I²C//((Page Wikipedia sur le bus I2C[[wpfr>I2C]])). Ce qui est formidable, c'est que même ainsi, vos broches I²C restent disponibles, grâce à un système d'adressage. Il est ainsi possible de connecter d'autres puces sur les broches I²C en chaînant celles ci avec notre MCP23017 ou MCP23008. Pour cela, on utilisera un système d'adressage que nous verrons plus tard. Ces deux puces coûtent environ 2%%$%% pour le MCP23008 ((MCP33008 chez Adafruit : [[https://www.adafruit.com/products/593]])), et 3%%$%% pour le MCP23017 ((MCP23008 chez Adafruit : [[https://www.adafruit.com/products/732]])). A moins d'avoir des contraintes d'espace (le MCP23008 se présente sous forme d'une puce à 16 broches, en 2*8, alors que la MCP23017 est une puce à 28 broches, en 2*14) sur votre montage, autant prendre des MCP23017. :) Voici les spécifications des puces : * [[http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf|Datasheet du MCP23017]]; * [[http://ww1.microchip.com/downloads/en/DeviceDoc/21919e.pdf|Datasheet du MCP23008]]. A noter que la lecture des spécifications n'est pas du tout nécessaire pour l'utilisation de cette puce, je mettrai tout le nécessaire dans ce tutoriel. Toutefois, il peut être utile d'avoir ces fichiers sous la main, par exemple pour les schémas de la disposition des broches de la puce, ou pour y dénicher davantage d'informations. Le câblage des deux puces ne sera pas identique, mais il est assez facile de passer de l'un à l'autre. Je montrerai le câblage des MCP23017 dans un premier temps, et des MCP23008 dans une mise à jour ultérieure. En revanche, le code et tout le reste seront similaires. Un point intéressant à noter est que ces puces ont de nombreuses fonctionnalités, comme des résistances "//pull-up//", qui nous simplifieront la vie pour lire des boutons poussoir, par exemple! En outre, chaque GPIO de la puce peut fournir 25mA, contre 16mA pour les GPIO du Raspberry, ce qui ne suffit pas pour alimenter pleinement une DEL par exemple (20mA pour les DEL standard), sans compter que le Raspberry ne peut fournir plus de 50mA sur le total des GPIO! Ici, nous pourrons donc alimenter pleinement 16 LED sans problème. Enfin, un dernier point intéressant, c'est que ces puces sont solides. Je ne vous garantis pas qu'elles sont protégées contre les mauvaises manipulations, je n'ai pas fouillé dans les spécifications pour vérifier. Mais en pratique, sur les miennes, j'ai fait plein de bêtises, en ayant câblé des choses à l'envers, mis le +3.3V d'alimentation sur une mauvaise broche, au point d'avoir eu deux puces brûlantes. Malgré cela, une fois le bon câblage fait, les puces se sont mises à fonctionner comme si de rien n'était Utiliser ces puces peut s'avérer intéressant même si vous n'avez pas besoin de plus de GPIO, ne serait-ce que pour protéger le Raspberry pi, car les GPIO "natifs" sont eux très fragiles, et n'ont aucune protection. On peut facilement griller des GPIO ou même un Pi entier en faisant de mauvais branchements. Ici, en cas de mauvaise manipulation, au pire, vous grillez une puce qui coûte 3$, et qu'il suffit de changer pour continuer à utiliser votre système. ===== Câblage du MCP23017 ===== Passons maintenant à la pratique. Dans un premier temps, je tiens à signaler que je n'ai pas fait ce tutoriel en partant du néant, j'ai utilisé le [[http://learn.adafruit.com/mcp230xx-gpio-expander-on-the-raspberry-pi|tutoriel d'Adafruit sur l'utilisation du MCP23017]](en) pour câbler ma première puce. Maintenant j'écris ce tutoriel en essayant d'ajouter ce qui m'a manqué dans celui d'Adafruit. Voici le schéma du câblage : {{ http://nagashur.com/blog/wp-content/uploads/2013/02/mcp23017_cablage1.png?600 | câblage du MCP23017 vers un Raspberry Pi}} Le MCP23017 est alimenté par du 3.3V, qui se connecte à la 6ème broche en partant du bas, à gauche. La masse du Raspberry sera connecté à la 5ème broche en partant du bas, à gauche. Le GPIO SDA (second en en partant du haut, colonne de gauche sur le Pi) se connecte à la seconde broche en partant du bas, à gauche, du MCP23017. Enfin,le GPIO SCL (troisième broche en partant du haut, colonne de gauche sur le Pi) se connecte à la 3ème broche en partant du bas, à gauche sur le MCP23017. Sur la colonne de droite du MCP23017, vous constaterez que la 4eme broche en partant du bas est connectée au +3.3V. Cette broche est la broche NON-Reset. Elle est à 3.3V, soit un 1 logique, ce qui veut dire que reset est à 0. Si vous souhaitez faire un reset du MCP23017, vous pouvez toujours connecter cette broche à un GPIO, mais pensez à définir la valeur à 1 pour le fonctionnement normal. Les trois broches du bas, dans la colonne de droite du MCP23017 servent à coder l'adresse de la puce, sur 3 bits, donc 8 adresses possibles. Si la broche donnée est sur le +3.3V, alors le bit concerné est à 1. Si la broche est sur le 0V, alors le bit concerné est à 0. Ici, l'adresse est donc 000. Le premier bit de l'adresse est celui tout en bas à droite, le second est le deuxième en partant du bas, à droite, et le troisième bit d'adresse est le troisième en partant du bas, à droite. Si vous branchez une autre puce I2C, il faudra qu'elle aie une autre adresse. Si ce n'est pas possible pour l'autre puce (adresse soudée par exemple) il est toujours possible de changer celle du MCP23017 ou du MCP23008. Si vous utilisez plusieurs MCP23017 ou MCP23008, il faudra s'assurer d'avoir des adresses différentes pour chaque puce. Maintenant, vous disposez d'une puce utilisable, et les 8 broches du haut, à gauche comme à droite, sont les GPIO. Il conviendra maintenant de déterminer la première broche, en haut, à gauche le MCP23017, à droite le MCP23008. Sur le MCP23017, la broche 0 est donc la 7ème en partant du bas. Les numéros de GPIO vont croissant jusqu'au haut de la puce, (en haut à droite c'est la broche 7), et décroissent sur les 8 premières broches de la colonne de gauche (tout en haut à gauche, c'est le GPIO 8, et la 8eme broche en partant du haut sur la colonne de gauche est le GPIO 15). ===== Configuration de Raspbian ===== Configurons maintenant Raspbian pour gérer le protocole I²C. Un tutoriel complet et illustré est dédié à la configuration de l'I2C, et est maintenant disponible : [[raspberry_pi:i2c_setup]]. Si vous utilisez Occidentalis, la distribution est déjà prête de base, et vous n'avez rien à faire de plus. En revanche, avec Raspbian, il faudra faire quelques petites opérations, plus détaillées sur le [[http://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c|tutoriel d'Adafruit sur la configuration I²C de Raspbian]]. Voyons rapidement ce qu'il faut faire. Nous commencerons par éditer le fichier /etc/modules pour y ajouter les modules noyeau nécéssaires à l'utilisation du protocole I²C. Pour cela, il suffira d'éditer en root ce fichier : sudo nano /etc/modules Ajoutez à la fin du fichier les deux lignes suivantes : i2c-bcm2708 i2c-dev Pour que les modifications prennent effet, il faudra redemarrer, et les pilotes seront chargés au démarrage. Une autre solution est d'utiliser **modprobe** : sudo modprobe i2c-bcm2708 sudo modprobe i2c-dev Cela aura pour effet de charger immédiatement les modules, sans avoir à redémarrer. Si jamais toutefois cela ne fonctionne pas, essayez de redémarrer: sudo reboot now Si jamais vous avez des problèmes, essayez de mettre à jour Raspbian avant de ré-essayer: sudo apt-get upgrade sudo apt-get update Sur ma version de raspbian, je n'ai pas eu de problèmes, mais le tuto d'Adafruit précise que vous pouvez avoir un fichier ///etc/modprobe.d/raspi_blacklist.conf//. Si c'est le cas, éditez ce fichier : sudo nano /etc/modprobe.d/raspi_blacklist.conf Modifiez alors les lignes suivantes : blacklist spi-bcm2708 blacklist i2c-bcm2708 Commentez les en ajoutant un %%#%% devant, pour obtenir ceci : #blacklist spi-bcm2708 #blacklist i2c-bcm2708 Dans tous les cas, avec nano, pour sauvegarder, tapez CTRL+x, et ensuite o ou y pour confirmer la sauvegarde. Nous allons maintenant installer des outils pour tester/vérifier les périphériques I2C: sudo apt-get install python-smbus sudo apt-get install i2c-tools Il devient dès lors possible, d'utiliser **i2cdetect** pour voir si la puce est détectée. Si vous avez les anciens Raspberry (versions 256Mo, principalement), il faudra taper : sudo i2cdetect -y 0 Sur la plupart des Raspberry pi, il faudra en revanche faire ceci : sudo i2cdetect -y 1 La raison est que le bus I²C utilisé sur les GPIO est devenu le bus 1 au lieu du bus 0 depuis les rev2. Il doit y avoir un autre sous système du Pi qui utilise le bus 0 quelque part sur la carte. Si tout à fonctionné , vous verrez un écran affichant de nombreuses lignes principalement remplies de tirets, et dans l'ensemble une suite de chiffre, qui correspond à l'adresse de votre puce (MCP23008/23017). Si vous avez câblé avec les 3 bits d'adresse à 0, vous verrez 20 dans la matrice, et pas 40 ou 70. Si vous ne voyez rien, c'est qu'il y a un problème de câblage, vérifiez les broches d'adresse, la broche "non-reset" qui doit être connectée au 3.3V et non à la masse, le +3.3v et la masse de la puce, et enfin les prises SDA et SCL, qu'on inverse parfois (ça m'est arrivé plusieurs fois) ===== Le code ===== Passons maintenant à une application pratique, avec du code. Nous allons faire clignoter une LED en utilisant un GPIO du MCP23017. Pour cela, nous n'allons pas implémenter le protocole I2C, mais utiliser le code d'Adafruit. Rendez vous sur [[https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/tree/master/Adafruit_MCP230xx|le github de Adafruit]], pour télécharger deux fichiers : * [[https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_MCP230xx/Adafruit_I2C.py|Adafruit_I2C.py]] * [[https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_MCP230xx/Adafruit_MCP230xx.py|Adafruit_MCP230xx.py]] Le premier est la classe qui permet d'utiliser le protocole I2C. Le second est une classe permettant d'utiliser le MCP23017 pour "faire des trucs". En pratique, le code d'exemple donné permet de lire la valeur d'un bouton, et également de faire clignoter une DEL. Il y a quelques petites modifications à apporter pour adapter le code à vos besoins, je vais les détailler ci dessous : #!/usr/bin/python from Adafruit_I2C import Adafruit_I2C import smbus import time MCP23017_IODIRA = 0x00 MCP23017_IODIRB = 0x01 MCP23017_GPIOA = 0x12 MCP23017_GPIOB = 0x13 MCP23017_GPPUA = 0x0C MCP23017_GPPUB = 0x0D MCP23017_OLATA = 0x14 MCP23017_OLATB = 0x15 MCP23008_GPIOA = 0x09 MCP23008_GPPUA = 0x06 MCP23008_OLATA = 0x0A class Adafruit_MCP230XX(object): OUTPUT = 0 INPUT = 1 def __init__(self, address, num_gpios, busnum = 0): assert num_gpios >= 0 and num_gpios <= 16, "Number of GPIOs must be between 0 and 16" self.i2c = Adafruit_I2C(address=address, bus=smbus.SMBus(busnum)) self.address = address self.num_gpios = num_gpios # set defaults if num_gpios <= 8: self.i2c.write8(MCP23017_IODIRA, 0xFF) # all inputs on port A self.direction = self.i2c.readU8(MCP23017_IODIRA) self.i2c.write8(MCP23008_GPPUA, 0x00) elif num_gpios > 8 and num_gpios <= 16: self.i2c.write8(MCP23017_IODIRA, 0xFF) # all inputs on port A self.i2c.write8(MCP23017_IODIRB, 0xFF) # all inputs on port B self.direction = self.i2c.readU8(MCP23017_IODIRA) self.direction |= self.i2c.readU8(MCP23017_IODIRB) << 8 self.i2c.write8(MCP23017_GPPUA, 0x00) self.i2c.write8(MCP23017_GPPUB, 0x00) def _changebit(self, bitmap, bit, value): assert value == 1 or value == 0, "Value is %s must be 1 or 0" % value if value == 0: return bitmap & ~(1 << bit) elif value == 1: return bitmap | (1 << bit) def _readandchangepin(self, port, pin, value, currvalue = None): assert pin >= 0 and pin < self.num_gpios, "Pin number %s is invalid, only 0-%s are valid" % (pin, self.num_gpios) #assert self.direction & (1 << pin) == 0, "Pin %s not set to output" % pin if not currvalue: currvalue = self.i2c.readU8(port) newvalue = self._changebit(currvalue, pin, value) self.i2c.write8(port, newvalue) return newvalue def pullup(self, pin, value): if self.num_gpios <= 8: return self._readandchangepin(MCP23008_GPPUA, pin, value) if self.num_gpios <= 16: if (pin < 8)/>/>/>/>: return self._readandchangepin(MCP23017_GPPUA, pin, value) else: return self._readandchangepin(MCP23017_GPPUB, pin-8, value) # Set pin to either input or output mode def config(self, pin, mode): if self.num_gpios <= 8: self.direction = self._readandchangepin(MCP23017_IODIRA, pin, mode) if self.num_gpios <= 16: if (pin < 8)/>/>/>/>: self.direction = self._readandchangepin(MCP23017_IODIRA, pin, mode) else: self.direction = self._readandchangepin(MCP23017_IODIRB, pin-8, mode) return self.direction def output(self, pin, value): # assert self.direction & (1 << pin) == 0, "Pin %s not set to output" % pin if self.num_gpios <= 8: self.outputvalue = self._readandchangepin(MCP23008_GPIOA, pin, value, self.i2c.readU8(MCP23008_OLATA)) if self.num_gpios <= 16: if (pin < 8)/>/>/>/>: self.outputvalue = self._readandchangepin(MCP23017_GPIOA, pin, value, self.i2c.readU8(MCP23017_OLATA)) else: self.outputvalue = self._readandchangepin(MCP23017_GPIOB, pin-8, value, self.i2c.readU8(MCP23017_OLATB)) return self.outputvalue self.outputvalue = self._readandchangepin(MCP23017_IODIRA, pin, value, self.outputvalue) return self.outputvalue def input(self, pin): assert pin >= 0 and pin < self.num_gpios, "Pin number %s is invalid, only 0-%s are valid" % (pin, self.num_gpios) assert self.direction & (1 << pin) != 0, "Pin %s not set to input" % pin if self.num_gpios <= 8: value = self.i2c.readU8(MCP23008_GPIOA) elif self.num_gpios > 8 and self.num_gpios <= 16: value = self.i2c.readU16(MCP23017_GPIOA) temp = value >> 8 value <<= 8 value |= temp return value & (1 << pin) def readU8(self): result = self.i2c.readU8(MCP23008_OLATA) return(result) def readS8(self): result = self.i2c.readU8(MCP23008_OLATA) if (result > 127): result -= 256 return result def readU16(self): assert self.num_gpios >= 16, "16bits required" lo = self.i2c.readU8(MCP23017_OLATA) hi = self.i2c.readU8(MCP23017_OLATB) return((hi << 8)/>/>/>/> | lo) def readS16(self): assert self.num_gpios >= 16, "16bits required" lo = self.i2c.readU8(MCP23017_OLATA) hi = self.i2c.readU8(MCP23017_OLATB) if (hi > 127): hi -= 256 return((hi << 8)/>/>/>/> | lo) def write8(self, value): self.i2c.write8(MCP23008_OLATA, value) def write16(self, value): assert self.num_gpios >= 16, "16bits required" self.i2c.write8(MCP23017_OLATA, value & 0xFF) self.i2c.write8(MCP23017_OLATB, (value >> 8)/>/>/>/> & 0xFF) # RPi.GPIO compatible interface for MCP23017 and MCP23008 class MCP230XX_GPIO(object): OUT = 0 IN = 1 BCM = 0 BOARD = 0 def __init__(self, busnum, address, num_gpios): self.chip = Adafruit_MCP230XX(busnum, address, num_gpios) def setmode(self, mode): # do nothing pass def setup(self, pin, mode): self.chip.config(pin, mode) def input(self, pin): return self.chip.input(pin) def output(self, pin, value): self.chip.output(pin, value) def pullup(self, pin, value): self.chip.pullup(pin, value) if __name__ == '__main__': #definissez la variable adress comme étant ce que vous avez réglé avec les broches adresse, selon la valeur retournée par i2cdetect !! #définissez num_gpios à 16 pour le MCP23017, et à 8 pour le MCP23008 !! #définissez busnum à 0 pour les anciens raspberry pi, et busnum à 1 pour les nouveaux !! mcp = Adafruit_MCP230XX(address = 0x20, num_gpios = 16, busnum = 1) # *************************************************** # Set num_gpios to 8 for MCP23008 or 16 for MCP23017! # If you have a new Pi you may also need to add: # busnum = 1 # *************************************************** # définition des broches 0, 1 et 2 en sortie (on peut définir les broches 0 à 15 de cette façon) mcp.config(0, mcp.OUTPUT) mcp.config(1, mcp.OUTPUT) mcp.config(2, mcp.OUTPUT) # On définit la broche 3 en entrée avec la résistance pull-up intégrée d'activée mcp.pullup(3, 1) # on affiche le résultat de la lecture. mcp.input(3) lit la broche 3. print "%d: %x" % (3, mcp.input(3) >> 3) while (True): mcp.output(0, 1) # broche 0 à high time.sleep(0.5) mcp.output(0, 0) # broche 0 à low time.sleep(0.5) Le passage important à vérifier est : mcp = Adafruit_MCP230XX(address = 0x20, num_gpios = 16, busnum = 1) busnum doit être à 1 pour les Raspberry de 512mo et toutes les versions 2 et +, sinon, pour les anciens pi (256mo pour un modèle B), la valeur de //busnum// doit être à 0. L'adresse doit correspondre à ce que i2cdetect retourne, avec 0x devant. Enfin, si vous avez un MCP23008, il faudra mettre num_gpios=8 au lieu de num_gpios=16. Pour le reste, j'ai remplacé les commentaires du main par des commentaires en français, ça devrait être assez explicite. Il ne reste plus qu'à lancer : sudo python blink.py Voici le schéma du montage correspondant : {{ http://nagashur.com/blog/wp-content/uploads/2013/02/mcp23017_cablage2_led.png?600 }} ===== Conclusions ===== Nous pouvons maintenant ajouter 8 à 16 GPIO par puce, jusqu'à un maximum de 7 puces (au delà, on aura des collisions d'adresses, celles ci étant sur 3 bits, et l'adresse 00 étant réservée). Si l'on alimente la puce en 3.3V depuis une source plus puissante que le rail 3.3v du Raspberry pi, on peut également augmenter la puissance disponible en sortie des GPIO. Il convient de noter que ces entrées-sorties sont numériques, on ne peut donc s'interfacer qu'avec des systèmes numériques. Toutefois, si l'on souhaite ajouter des entrées [[wpfr>Analogique|analogiques]] plutôt que [[wpfr>Numérique|numériques]] comme c'est le cas ici, il nous faudra utiliser un convertisseur analogique-numérique. On pourra par exemple [[raspberry_pi:mcp3008_lecture_analogique|utiliser un MCP3008 pour ajouter 8 entrées analogiques au Raspberry pi]] et ainsi lire la valeur d'un potentiomètre ou d'un capteur analogique.