Cours:TPS 2103 tp mcp23017
Sommaire
matériel
- carte arduino nano
- shield arduino nano
- shield i2c
- plaque à essais
- fils M-M
- 2 bps
- 2 leds 5mm
- 1 résistance 10k
- 2 résistances 330
GPIO expander
Lorsque le nombre d'entrées/sorties nécessaire dépasse la quantité disponible sur le µcontrôleur utilisé, il est possible d'utiliser un périphérique/composant pour en ajouter.
On parle de GPIO (General Purpose Input Output / Entrée-Sortie à usage générique) expander
Le mcp23017 que nous allons utiliser (cf datasheet plus haut), possède une liaison i2c (Inter Integrated Circuit Bus), aussi nommé TWI (Two Wire Interface).
Pour rappel, le bus i2c est une liaison série, synchrone, half-duplex, qui nécessite 2 fils SDA (données) et SCL (l'horloge), en plus de la masse.
Identification des adresses i2c
Le principe de connexion sur le bus i2c est représenté sur la figure ci-dessous.
Les résistances de pull-up sont indispensables, elles imposent la tension représentant le niveau logique 1.
Pour votre culture, d'un point de vue logique, nous avons :
- un niveau logique 0 dominant
- un niveau logique 1 récessif
c'est à dire que si plusieurs composants veulent imposer des niveaux logiques différents sur les lignes SDA et SCL, le niveau logique 0 l'emporte.
L'adresse des composants i2c est codée sur 7 bits, donc 2^7 adresses possibles. En pratique certaines adresses sont réservées, se référer par ex à la page wikipedia pour plus de détails : https://fr.wikipedia.org/wiki/I2C
Nous utiliserons ce "shiledI2c", au format d'une carte arduino, qui comporte 2 composants I2c (mcp23017 et mcp3424).
Le bus i2c est présent sur le connecteur rouge. Il suffit de relier la carte que nous programmons avec une nappe sur ce connecteur pour l'utiliser.
Attention, le cavalier bleu permet de choisir la tension d'alimentation (3,3V ou 5V). Par ex:
- arduino uno : 5v
- raspberry pi : 3.3V
Il est au format d’une carte Arduino UNO, on peut l’utiliser avec une carte Arduino sous 5 V ou avec une carte Raspberry Pi sous 3,3V. Il faut placer le cavalier bleu suivant la carte que l’on utilise. Ici le cavalier est en position Arduino. Les 16 ports d’extension sont sur les connecteurs en haut de la carte sur la photo (17..2). Les switchs de marque Omron A6FR permettent de régler l’adresse du GPIO expander MCP23017. Pour relier ce Shield avec la carte du SAé 1 on utilise une nappe connectée sur chaque connecteur rouge de chaque carte. Les connecteurs sont munis de détrompeur.
Trouver dans la datasheet les différentes adresses possibles du composant MCP23017 et compléter le document réponse
Les GPIOs du MCP23017 sont séparés en 2 (PORTA / PORTB) comportant chacun 8 broches. Combien de MCP23017 peut-on utiliser sur le même bus i2c, et donc combien d'entrées/sorties au maximum peut-on ajouter de cette façon ?
Utiliser le programme I2cScanner pour lister les adresses des 2 composants reliés sur le bus I2c
En déduire
- Les valeurs des bits A2/A1/A0 pour le mcp23017
- l'adresse du CAN i2c (MCP3424)
https://playground.arduino.cc/Main/I2cScanner/
Les registres du mcp23017
Comme la plupart des composants i2c, nous devons lire/écrire des valeurs dans des registres, principe vu dans le td i2c registres.
Attention : les registres des targets i2c ne sont pas réinitialisées lorsque le µcontrôleur redémarre. Pour les réinitialiser vous devez couper l'alimentation du/des composants. (par ex en débranchant le cordon usb)
La correspondance adresses<=>registre est modifiable selon 2 configurations pour le mcp23017 (cf datasheet page 12). On utilisera le mode par défaut :
Adresse | Nom du registre | Valeur par défaut | description |
---|---|---|---|
0x00 | IODIRA | 0b11111111 | PORTA : broche en entrée (1) ou sortie (0) |
0x01 | IODIRB | 0b11111111 | PORTB : idem |
0x02 | IPOLA | 0b00000000 | PORTA : si à 1, inverse la valeur des entrées |
0x03 | IPOLB | 0b00000000 | PORTB : idem |
0x04 | GPINTENA | 0b00000000 | PORTA : si à 1, un changement d'état de l'entrée active la sortie d'interruption |
0x05 | GPINTENB | 0b00000000 | PORTB : idem |
0x06 | DEFVALA | 0b00000000 | PORTA : valeur de comparaison des entées pour générer une interruption (cf INTCONA) |
0x07 | DEFVALB | 0b00000000 | PORTB : idem |
0x08 | INTCONA | 0b00000000 | PORTA : si 1, génère une interruption si l'entrée est différente de la valeur de comparaison (DEFVALA) |
0x09 | INTCONB | 0b00000000 | PORTB : si à 0, génère une interruption si la valeur de l'entrée change |
0x0A | IOCON | 0b00000000 | registre général de configuration |
0x0B | IOCON | 0b00000000 | registre général de configuration |
0x0C | GPPUA | 0b00000000 | PORTA : activer(1) ou désactiver(0) la résistance de pull-up (100k) |
0x0D | GPPUB | 0b00000000 | PORTB : idem |
0x0E | INTFA | 0b00000000 | PORTA : indique (bit à 1) quelle(s) broches ont générées l'interruption |
0x0F | INTFB | 0b00000000 | PORTB : idem |
0x10 | INTCAPA | 0b00000000 | PORTA : mémorise la valeur du PORT au moment de l'interruption |
0x11 | INTCAPB | 0b00000000 | PORTB : idem |
0x12 | GPIOA | 0b00000000 | PORTA : modifier/lire l'état des broches |
0x13 | GPIOB | 0b00000000 | PORTB : idem |
0x14 | OLATA | 0b00000000 | PORTA : OUTPUT LATCH REGISTER, modifier/lire l'état des latch |
0x15 | OLATB | 0b00000000 | PORTB : idem
|
lecture d'une entrée
Comme indiqué dans le tableau précédent, toutes les GPIOs sont configurées en entrée.
La correspondance entre la sérigraphie de la carte et le numéro de GPIO est le suivant :
- broche numérotée 2 -> GPB0
- broche numérotée 3 -> GPB1
- ...
- broche numérotée 9 -> GPB7
- broche numérotée 10 -> GPA0
- broche numérotée 11 -> GPA1
- ...
- broche numérotée 17 -> GPA7
On connecte un bouton avec sa résistance de tirage sur la GPA2.
Faire en sorte que la LED1 (cf schéma ci-contre) s'allume si le bouton est appuyé
Rque : Vous pouvez utiliser la fonction donnée en bas de TP pour afficher la valeur des registres.
On ajoute un 2ème bouton sur GPB6, sans mettre de résistance de tirage, il faudra utiliser celle du mcp23017.
Compléter votre programme pour que la LED2 s'allume en fonction de l'état du 2ème bouton
gestion des sorties
On ajoute 2 leds sur les gpios du mcp23017, sur les broches GPA7 et GPB7.
Pensez aux résistances pour régler la valeur du courant dans les leds (environ 10mA)
Associer l'état d'un bouton à chaque led
utilisation de l'interruption du MCP23017
Lorsqu'on souhaite exécuter une action lors du changement d'état d'une entrée, les 2 solutions sont :
- par scrutation
- on observe en permanence l'état de l'entrée
- la charge processeur est très importante
- par interruption
- un signal nous préviens d'un changement
- la charge processeur est très faible
Le mcp23017 possède 1 sortie d'interruption par port :
- INTA, pour les GPIOs du port A (broche 0 du shield i2c)
- INTB, pour les GPIOs du port B (broche 1 du shield i2c)
Ces 2 sorties sont configurables de différentes façon (cf registre IOCON page 21).
Par défaut la sortie d'interruption est à l'état HAUT et passe à l'état BAS lorsqu'une entrée change d'état.
Sur le shield arduinoNano que nous utilisons, les 2 broches associées aux INT0 et INT1 ne sont pas disponibles. Nous allons utiliser les broches PD6 et PD7 (ECHO et TRIG sur le schéma de la carte).
Relier ces 2 broches sur les broches centrales du connecteur pour le module Ultrason. (connecteur noir à 4 broches)
Le code suivant permet d'autoriser l'interruption de changement d'état associée aux broches PD6(PCINT22) et PD7 (PCINT23) :
ISR(PCINT2_vect) // Interruption sur changement d'état
{
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// attention, ne pas utiliser les fonctions i2c dans l'interruption !!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
void setup()
{
...
cli();
PCMSK2 |=(1<<PCINT23)|(1<<PCINT22);
PCICR |=(1<<PCIE2);
sei();
readI2cReg(mcp23017Ad,GPIOB); // permet d'acquitter une interruption en attente
readI2cReg(mcp23017Ad,GPIOA); // permet d'acquitter une interruption en attente
...
}
Ecrire un programme qui utilise les interruption du MCP23017 pour qu'à l'appui sur l'un des boutons les 2 leds s'allument, et s'éteignent à l'appui sur l'autre bouton.
Rque: vous pouvez utiliser les registres
- INTCAP(A/B) qui mémorise l'état des entrées lors de l'interruption
- GPIO(A/B) qui donne la valeur actuelle des entrées
Pour aller plus loin
Vous pouvez essayer de reprendre le TP sur le clavier matriciel en le connectant directement au GPIO expander.
Ressources
Adresses des registres
#define IODIRA 0x00
#define IODIRB 0x01
#define IPOLA 0x02
#define IPOLB 0x03
#define GPINTENA 0x04
#define GPINTENB 0x05
#define DEFVALA 0x06
#define DEFVALB 0x07
#define INTCONA 0x08
#define INTCONB 0x09
#define IOCON1 0x0A
#define IOCON2 0x0B
#define GPPUA 0x0C
#define GPPUB 0x0D
#define INTFA 0x0E
#define INTFB 0x0F
#define INTCAPA 0x10
#define INTCAPB 0x11
#define GPIOA 0x12
#define GPIOB 0x13
#define OLATA 0x14
#define OLATB 0x15
Affichage des registres du mcp23017
Voici une fonction pour afficher la valeur des registres du MCP23017. Il faut passer en paramètre de la fonction l'adresse i2c du composant.
void serialPrintRegistresMCP23017(uint8_t targetAddress)
{
const uint8_t nbRegister = 22;
const char* nameRegister[nbRegister] ={ "IODIRA","IODIRB","IPOLA","IPOLB","GPINTENA","GPINTENB","DEFVALA",
"DEFVALB","INTCONA","INTCONB","IOCON","IOCON","GPPUA","GPPUB",
"INTFA","INTFB","INTCAPA","INTCAPB","GPIOA","GPIOB","OLATA","OLATB"
};
Serial.print("Registres MCP23017, @:");
Serial.println(targetAddress);
Wire.beginTransmission(targetAddress);
Wire.write((uint8_t)0x00); // register pointer to zero
Wire.endTransmission();
// attention, ne fonctionne que si le pointeur de registre
// est configuré pour une incrémentation automatique dans le mcp23017
Wire.requestFrom(targetAddress, nbRegister); // lecture de tous les registres
uint8_t regAddress=0;
while(Wire.available()) // slave may send less than requested
{
uint8_t regValue = Wire.read(); // receive a byte as number
Serial.print((String)"Register : ");
Serial.print(regAddress ,HEX);
int length = strlen(nameRegister[regAddress]) ; // longueur du nom du registre
Serial.print((String) "\t" + nameRegister[regAddress]);
// Format d'affichage suivant longeur du nom du registre
if(length>7) {
Serial.print(" ");
Serial.println(regValue);
}
else {
Serial.print("\t ");
Serial.println(regValue);
}
regAddress++;
}
Serial.write('\n');
}
Ecrire une valeur dans un registre
void writeI2cReg(uint8_t targetAddress, uint8_t regAddress, uint8_t regValue)
{
Wire.beginTransmission(targetAddress); // start transmitting
Wire.write(regAddress); // sends @ registre
Wire.write(regValue); // sends value registre
Wire.endTransmission(); // stop transmitting
}
Lire une valeur dans un registre
uint8_t readI2cReg(uint8_t targetAddress, uint8_t regAddress)
{
uint8_t regValue=0;
Wire.beginTransmission(targetAddress); // start transmitting
Wire.write(regAddress); // sends @ registre
Wire.endTransmission(); // stop transmitting
Wire.requestFrom(targetAddress,(uint8_t) 1);
while (Wire.available()) regValue = Wire.read();
return regValue;
}