Outils pour utilisateurs

Outils du site


Panneau latéral

Navigation

Plan auto

raspberry_pi:mcp23017_ajout_gpio

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²C1). 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 2), et 3$ pour le MCP23017 3). 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 :

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 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 :  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 : Configurer le bus I2C sur le Raspberry Pi.

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 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 le github de Adafruit, pour télécharger deux fichiers :

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 :

python
#!/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 < <img src='http://forum.pcinpact.com/public/style_emoticons/<#EMO_DIR#>/lunettes1.gif' class='bbc_emoticon' alt='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 < <img src='http://forum.pcinpact.com/public/style_emoticons/<#EMO_DIR#>/lunettes1.gif' class='bbc_emoticon' alt='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 < <img src='http://forum.pcinpact.com/public/style_emoticons/<#EMO_DIR#>/lunettes1.gif' class='bbc_emoticon' alt='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 << <img src='http://forum.pcinpact.com/public/style_emoticons/<#EMO_DIR#>/lunettes1.gif' class='bbc_emoticon' alt='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 << <img src='http://forum.pcinpact.com/public/style_emoticons/<#EMO_DIR#>/lunettes1.gif' class='bbc_emoticon' alt='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 >> <img src='http://forum.pcinpact.com/public/style_emoticons/<#EMO_DIR#>/lunettes1.gif' class='bbc_emoticon' alt='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 :

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 analogiques plutôt que numériques comme c'est le cas ici, il nous faudra utiliser un convertisseur analogique-numérique. On pourra par exemple 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.

1)
Page Wikipedia sur le bus I2CI2C
2)
MCP33008 chez Adafruit : https://www.adafruit.com/products/593
3)
MCP23008 chez Adafruit : https://www.adafruit.com/products/732
raspberry_pi/mcp23017_ajout_gpio.txt · Dernière modification: 15/04/2015 17:54 par sky99