Piloter 16 LEDs avec le port Série d'un ESP32 à l'aide d'un MCP23017

Bonjour Bonjour !

J’ai récupéré plusieurs MCP23017 dans un ancien FABLAB qui est en train de fermer/changer.
j’ai envie d’apprendre à les utiliser.

mon idée : Piloter une LED parmi 16 LEDs, j’envoie dans le port Série de mon ESP32 (ESP32 WROOM montée sur une carte pas juste le micropross). J’envoie un deux chiffre X1X2.

  • X1 = 0 ou 1 pour sélectionner le port GPAx ou GPBx (x étant le numéro de la sortie entre 0 et 7)
  • X2 = 0, 1, 2, 3, 4, 5, 6 ou 7 pour sélectionner la sortie.
    Exemple : 12 = GPB2 / 00 = GPA0 … etc

le tout en utilisant le MCP23017 donc, j’ai le montage suivant :

avec la version du MCP2307 suivant :

lien RS de ma commande avec datasheet : le MCP23017 que j'ai et sa datasheet

J’ai chercher sur d’autres sujets de ce forum et d’autres tuto en ligne et à l’aide de ça et de chatGPT que mon chère jpBricole m’a plusieurs fois conseillé pour du code j’ai fait ceci :

/*

CODE pour allumer une LED sur demande dans le port série avec un MCP23017




Dans l'ordre il faut : 




- initialiser l'I2C

- configurer les ports GPA0...7 et GPB0...7 en sorties

- maintenir un etat méémoire des sorties

- lire une commande du port série de 2 caractères

- décoder la commande (X1X2) : X1 = port GPA ou GPB / X2 = sortie 0 à 7

- allumer la LED voulu en fonction de la commande




Adresse de registre utilisée (BANK = 0): 

- Direction port A : IODIRA = 0x00

- Direction port B : IODIRB = 0x01

- GPIO port A : GPIOA = 0x12

- GPIO port B : GPIOB = 0x13

*/

#include <Wire.h>




#define MCP23017_Address 0x20 // A changer en fonction de l'adresse du MCP (scanner I2C si besoin)




// Registre du MCP23017 (BANK = 0)

#define IODIRA 0x00

#define IODIRB 0x01

#define GPIOA 0x12

#define GPIOB 0x13




uint8_t PortA_state = 0x00; // Etat des sorties GPA

uint8_t PortB_state = 0x00; // Etat des sorties GPB




// Ecrire un registre MCP23017

void mcpWrite(uint8_t reg, uint8_t value) {

  Wire.beginTransmission(MCP23017_Address);

  Wire.write(reg);

  Wire.write(value);

  Wire.endTransmission();

}




void setup() {

  Serial.begin(115200);

  Wire.begin(21, 22); // SDA, SCL sur l'ESP32




  // Configurer GPA et GPB en sortie

  mcpWrite(IODIRA, 0x00); // GPA0...7 en sortie

  mcpWrite(IODIRB, 0x00); // GPB0...7 en sortie




  // Etteindre toutes les LEDs au départ

  mcpWrite(GPIOA, PortA_state);

  mcpWrite(GPIOB, PortB_state);




  Serial.println("MCP initialisé et pret");

  Serial.println("Entrez une valeur de 00 à 07 ou de 10 à 17");

}




void loop() {

  if (Serial.available() >= 2) {

    // 2 copie de ce qui est lue dans le port serie

    char c1 = Serial.read();

    while (!isDigit(c1)) c1 = Serial.read();

    char c2 = Serial.read();

    while (!isDigit(c2)) c2 = Serial.read();




    // Ignorer les sauts de lignes

    if (c1 == '\n' || c1 == '\r') {

      return;

    }




    int port = c1 - '0';

    int bit = c2 - '0';




    // Verification si ce qui est écrit est bien entre 00 et 07 et entre 10 et 17

    if ((port == 0 || port == 1) && (bit >= 0 && bit <= 7)) {

      // Tout éteindre

      PortA_state = 0x00;

      PortB_state = 0x00;




      if (port == 0) {

        // On est sur GPAx

        PortA_state = (1 << bit);

      }

      else {

        // On est sur GPBx

        PortB_state = (1 << bit);

      }




    mcpWrite(GPIOA, PortA_state);

    mcpWrite(GPIOB, PortB_state);




    Serial.print("LED allumée : ");

    Serial.print(port == 0 ? "GPA" : "GPB");

    Serial.println(bit);

    }

    else {

      Serial.println("Commande invalide (00 à 07 ou 10 à 17)");

    }

  }

  /*// Test simple : allumer GPA0

  mcpWrite(GPIOA, 0x01);  // GPA0 = HIGH

  mcpWrite(GPIOB, 0x00);*/

}

Les 3 dernières lignes de code (celles commentées) servaient uniquement à tester le bon fonctionnement de mon mcp23017… justement… dans mon port Série j’ai bien :

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)

configsip: 0, SPIWP:0xee

clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00

mode:DIO, clock div:1

load:0x3fff0030,len:4980

load:0x40078000,len:16612

load:0x40080400,len:3480

entry 0x400805b4

MCP initialisé et pret

Entrez une valeur de 00 à 07 ou de 10 à 17

LED allumée : GPA0

LED allumée : GPA3

OR… rien ne s’allume…
Mes théories: je n’ai pas de pull-up pour mon I2C mais je ne sais pas comment dimensionner une résistance pour de l’I2C… j’aimerais bien apprendre à le faire et surtout apprendre l’emplacement de où elle se met…
(PS : tout est en 3,3V dans mon montage).

Merci d’avance, et sorry si c’est pas clair ou mal expliqué, je suis seulement un petit électronicien à en devenir je l’espère (LOL) en BUT GEII spé AII (traduction : Génie électrique et informatique industrielle spécialité automatisme et informatique industrielle).
J’ai mon assos de mini atelier pour étudiants pour chacun et je teste des trucs débile pour en apprendre plus, on apprend de l’absurde.

Si quelqu’un à une solution magique je prend mais surtout j’aimerais bien des explications pour apprendre !

Les pullups sont indispensable pour un bon fonctionnement du bus I2C dont les sorties sont en collecteurs ouverts
Tout est là
UM10204.pdf (1.3 MB)

Il est bon de traiter le retour de la méthode endTransmission() qui t'indique si la transmission s'est bien déroulée.
https://docs.arduino.cc/language-reference/en/functions/communication/wire/endTransmission/

Bonjour louisjaffeux

Est ce que ton ESP32 "voit" ton MCP23017?
Pour le savoir, charges le programme de scanner i2C, tu dois voir, dans la console, ceci:

Périphérique I2C trouvé à l'adresse 0x20

Le programme:

#include <Wire.h>

void setup() {
	Serial.begin(115200);
	while (!Serial) {
		delay(10);
	}

	Serial.println("\nScanner I2C ESP32");

	// Initialisation I2C (SDA, SCL)
	Wire.begin(21, 22);


	Serial.println("Scan en cours...");

}

void loop() {
	byte error, address;
	int nDevices = 0;
		
	for (address = 1; address < 127; address++) {
		Wire.beginTransmission(address);
		error = Wire.endTransmission();

		if (error == 0) {
			Serial.print("Périphérique I2C trouvé à l'adresse 0x");
			if (address < 16) Serial.print("0");
			Serial.println(address, HEX);

			nDevices++;
		}
		else if (error == 4) {
			Serial.print("Erreur inconnue à l'adresse 0x");
			if (address < 16) Serial.print("0");
			Serial.println(address, HEX);
		}
	}

	if (nDevices == 0)
	Serial.println("Aucun périphérique I2C trouvé");
	else

	delay(3000); // Nouvelle recherche toutes les 5 secondes
}

Pour les résistances de PULLUP, mets 4,7K ça devrai aller.

A+
jpbbricole

pour trouver le 0x20 j’avais un code équivalent, au cas ou je le remet ici :

/*
Code pour I2C Scanner
*/

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}

void loop() {
  byte error, address;
  int nDevices;

  Serial.println("Scann...");

  nDevices = 0;
  for (address = 1; address < 127; address++){
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("Système I2C trouvé à l'adresse : 0x");
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.println(" !");

      nDevices++;
    }
    else if (error == 4) {
      Serial.print("Erreur inconnue à l'adresse : 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0) Serial.println("pas de système I2C trouvé\n");
  else Serial.println("fini\n");

  delay(7000);
}

et il me renvoyais :

Scann...
Système I2C trouvé à l'adresse : 0x20 !
fini

donc mon mcp23017 est bien reconnu.

je vais regarder ceci, merci pour la documentation.
Si j’ai bien compris je devrais au debut de ma loop mettre : Wire.beginTransmission(); et à la fin : Wire.endTransmission(); c’est ça ? ou alors j’ai mal compris

Dans tout les cas je vais essayer avec la résistance de 4.7kOhm et surtout où la brancher, je ne sais pas brancher de pullup et où la mettre je vais regarder ça et tester cela

Merci à vous deux^^

j’ai fait ceci (j’avais plus que deux résistance de 2.7kOhm ou alors des 3.3kOhm, j’ai mis 2 2.7k en série donc qui s’additionne.
mais rien n’y fais… problème de code ou de montage ?

Unu autre solution consisterai a utiliser une bibliothèque comme celle-ci:


et faire l'exemple PortCopy:

Cet exemple lit l'état des ports de 8 à 15 et reporte ces états sut les ports de 0 à 7.

merci je vais regarder cela ^^

Puisque tu es un futur électronicien, on va parler électronique. :grinning_face:

Tous les composants I2C sont à collecteur ouvert.
C'est-à-dire que leur collecteur n'est relié à rien.
Pour rendre le montage fonctionnel il faut :

  • que tous les collecteurs SDA ,dune part, et SCL, d'autre part, soient reliés ensemble → c'est pour cela que la résistance de charge de collecteur n'est pas sur la puce.
  • que le point commun des collecteurs soit relié au Vcc par une résistance pour qu'un courant puisse circuler. D'aucuns utilisent le terme de "pull-up", un électronicien parle de résistance de charge de collecteur.

Remarques :

  1. Ce principe génial forme un "OU câblé" gratuit et extensible à volonté.
    "Ou câblé" signifie qu'il n'y a pas besoin d'ajouter une porte logique OU. Le maître comme chaque esclave I2C peut prendre la ligne pour questionner ou pour répondre.

  2. Il peut y avoir plusieurs résistances en plusieurs endroits du circuit -> elles se retrouvent en parallèle, ce n'est pas gênant. On trouve souvent une 10k par module I2C. S'il y a 10 modules, le transistor qui est passant ne verra plus qu 10k/10 = 1kohms, il faut juste qu'il supporte le courant.

  3. L'I2c a été prévue pour relier des circuits intégrés sur une seule carte, donc sur des courtes distances. Le principe des "ou câblés" est sensible à la capacité.
    Cas délicats :
    Je les signale pour ton information, mais tu ne vas pas mettre 5 m entre le micro et le MCP, et dans ce cas, on utiliserait des CI spécialisés.
    Quand il y a trop de modules esclaves ou que les distances sont trop longues, la conséquence immédiate est que la capacité augmente, le temps de montée des signaux peut devenir trop important par rapport à la période de l'horloge et cela fini par ne plus fonctionner. Le premier réflexe est de diminuer la résistance "équivalente" de charge de collecteur. Ce qui diminue le produit RC.
    La limite basse pour la résistance équivalente est de ne pas dépasser le courant max dans les collecteurs ouverts → voir les datasheets.

    Cas le plus fréquent :
    En général chaque module est équipé avec 10 k sur SDA et 10 k sur SCL et cela convient sans prise de tête qu'il y ait un ou plusieurs modules I2C.

  4. Emplacement idéal pour les résistances :
    Les liaisons I2C sont bidirectionnelles. Il n'est pas possible de faire des liaisons adaptées.
    Il n'y a malheureusement pas d'emplacement idéal.
    C'est pour cela que la fréquence de I2C est faible : horloge à 100 kHz ou 400 kHz contre 40 MHz avec le SPI.

Alors…

Tout fonctionne comme je le veux, l’erreur était… attention… roulement de tambour… badabamtadam : mon fil SCL branché sur la pin TX0 de ma carte ESP32…

un décalage lorsque j’avais entre plusieurs manip, refait ma breadbord.

Vrais soucis par contre, j’avais laissé, A0, A1 et A3 (du mcp23017) dans le vide pour laisser l’adresse à 0x20. mais j’ai remarqué avec le scanner I2C que l’adresse variais toute seule entre 0x20 et 0x25, au pif, souvent 0x20 mais de temps en temps, hop elle changeais toute seule, j’ai donc relié les 3 broches Ax au GND et la l’adresse est fixé à 0x20. et tout fonctionne !

PAR CONTRE ! cette histoire m’auras aidé à comprendre l’I2C vraiment et les résistances de charge de collecteur ici (“pull-up” ducoup) ! merci ^^

j’ai mis deux 10kOhm entre mon 3.3V et mes pins SDA et SCL.
Je vais maintenant essayer avec 32 LEDs et 2 mcp23017 en m’aidant des exemples
Merci à vous @68tjs @jpbbricole @fdufnews