Utiliser une EEPROM externe avec un Arduino🔗
Posted by Médéric Ribreux 🗓 In blog/ Electronic/
Introduction
J'ai retrouvé une vieille eeprom que j'avais récupérée en désossant un vieil écran CRT. C'est une LC21B de ST Microelectronics, plus précisément une ST24LC21B.
Je me suis dit que ça serait chouette de travailler un peu à une étude de son fonctionnement avec un Arduino, ça fait bien longtemps que je n'ai pas utilisé ce dernier.
La datasheet de cette EEPROM montre qu'elle ne fait que 1Kbits soit 1024bits et non 1024 octets. La capacité de l'EEPROM est donc de 128 octets ce qui est très faible (mais mieux que rien).
A l'origine, cette EEPROM servait à stocker les paramètres VESA des moniteurs (sans doute les données d'EDID). Elle est donc principalement faite pour être lue via le câblage VGA par la carte graphique. Mais, à la base, c'est une EEPROM alors on peut mettre ce qu'on veut dedans.
Mais ce qui est sans doute plus important c'est qu'elle est capable de fonctionner via le BUS I2C. Ce sera donc un bon moyen de voir comment lire et écrire des bits via ce mode de communication avec l'Arduino.
I2C
I2C est un standard de communication sur deux fils créé par Philips. Il est utilisable par l'Arduino via la bibliothèque Wire que nous allons utiliser.
Pour résumer, I2C utilise deux ports analogiques spécifiques de votre Arduino. Ces deux ports correspondent aux ports SDA (Serial Data) et SCL (Serial clock). Chaque périphérique I2C dispose d'une adresse (un peu comme une adresse MAC) et répond quand un maître (l'Arduino dans notre cas) y fait appel.
Après, on peut lui envoyer des commandes qui auront une signification propre au périphérique (une EEPROM n'a rien à voir avec un capteur de température par exemple). I2C s'occupe juste des communications, pas de la manière dont fonctionne le périphérique.
Branchements et spécifications
Sur l'Arduino
J'ai un vieil Arduino (un duemilanove) mais je constate que le dernier Arduino correspondant à la gamme (le Uno rev3) n'a pas trop évolué au niveau des grandes lignes des spécifications. On retrouve les mêmes composants, la même quantité de RAM, de Flash, de ports, etc.
Ce qui nous intéresse, dans notre cas, ce sont les ports de l'Arduino que nous allons utiliser pour la communication I2C:
- Analog 4: SDA
- Analog 5: SCL
Sur l'EEPROM
Nous avons les connexions suivantes:
- VCC (+5v): 8
- GND/VSS : 4
- SDA: 5
- SCL: 6
- VCLK: 7
En lisant la doc de l'EEPROM, on se rend compte que VCLK est un port qui permet d'autoriser ou non l'écriture dans l'EEPROM. S'il est haut (5V), alors l'écriture est autorisée. Sinon, même en envoyant des ordres d'écriture, l'EEPROM ne verra pas son contenu modifié.
Par ailleurs, nous avons besoin de connaître l'adresse unique I2C de l'EEPROM. En consultant la documentation, on se rend compte qu'elle vaut 1010000x. Le x de la fin varie suivant le mode écriture/lecture de l'EEPROM. Ainsi, si vous souhaitez lire son contenu, il faudra s'adresser à 10100001 soit 0x51. Si vous souhaitez écrire dans l'EEPROM, il faudra s'adresser à 10100000 soit 0X50.
Schéma
Au final, voici l'aspect du schéma électronique (que j'ai réalisé avec Kicad):
En plus du fait de relier les bons ports entre l'Arduino et l'EEPROM, on trouve quelques composants en plus:
- Les deux résistances en 4.7kOhms sur SDA et SCL sont des résistances de pull-up. Elles servent à maintenir l'état haut du bus lorsqu'il n'y a pas de communication, le tout avec un courant assez faible.
- La résistance de 4.7kOhms sur VCLK sert à limiter le courant entrant sur le port numérique en sortie de l'Arduino. D'après les spécifications de l'EEPROM, le courant max (Icc) doit être de 2mA lorsque Vcc vaut 5V. Si vous faites un branchement en direct, certes, il y aura du 5V en sortie, mais il n'y aura rien pour limiter le courant. Vous risquez donc d'envoyer un flux de courant d'environ 40mA (le max d'une sortie numérique d'un ATMega) ce qui grillera votre EEPROM. Si on prend la moitié de la valeur de Icc, soit 1mA, la valeur de résistance sera de R = U/I soit 5/0.001 soit 5kOhms. Mettre une troisième résistance de 4.7KOhms rentre dans les tolérances (on est à 1.06mA). Pour ma part, j'utilise le port numérique 13, ça porte chance !
En plus du schéma théorique, voilà le schéma sur une breadboard, ça peut toujours servir pour le débutant:
Code
Passons maintenant à la partie logicielle. Dans mon cas, j'ai réalisé une espèce de programme interactif qui permet d'envoyer des commandes à l'Arduino via le port série (qui passe par l'USB). Cela me permet de réaliser des opérations à la demande sur l'EEPROM.
Je me suis arrêté quand j'ai obtenu un résultat significatif:
- Savoir lire le contenu complet de l'EEPROM.
- Savoir écrire un octet à une adresse spécifique de l'EEPROM.
- Exploiter la fonction d'écriture massive de l'EEPROM.
Voici à quoi ça ressemble:
/* * Use an old ST24LC21B EEPROM with I2C to read and store values. * I2C address for EEPROM: * - 1010.0001 for reading: 0x51 * - 1010.0000 for writing: 0x50 (c) 2018 by Médéric Ribreux *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * *************************************************************************** */ // on inclut la bibliothèque Wire qui fait très bien son travail avec I2C. #include <Wire.h> /* Quelques constantes globales */ #define writeID 0x50 // L'adresse I2C d'écriture. #define readID 0x51 // L'adresse I2C de lecture. #define romSize 128 // La taille de l'EEPROM en bytes. #define writePin 13 // Le port qui contrôle le mode écriture. /* Variables globales */ byte addr = 0; unsigned char eeprom[romSize]; // un tableau qui capture le contenu de l'EEPROM. String command; // Une chaîne de caractères pour gérer nos commandes. // Affecte ou non la protection en écriture via le port dédié void setWriteProtection(bool state) { if (state == false) { digitalWrite(writePin, HIGH); } else { digitalWrite(writePin, LOW); } } // Sélectionne une adresse d'octet dans l'EEPROM (Random Access). byte selectAddr(byte address) { byte state = 0; Wire.beginTransmission(writeID); Wire.write(address); state = Wire.endTransmission(); return(state); } // Dump de l'EEPROM void dump() { byte block = 0; byte status = 0; byte offset = 0; status = selectAddr(0x0); if (status!=0) { Serial.println("Problème d'adressage"); } else { for(block=0;block<=4;block++) { Wire.requestFrom(readID, 32, true); while(Wire.available()) { eeprom[offset] = Wire.read(); offset = offset + 1; } } } } // Test d'écriture void writeInMemory(byte offset, byte value) { byte state = 0; // on lève la protection en écriture digitalWrite(writePin, HIGH); delay(500); // On se place à l'offset Wire.beginTransmission(writeID); Wire.write(offset); Wire.write(value); state = Wire.endTransmission(true); if (state != 0) { Serial.print("Erreur d'écriture: "); Serial.println(state, DEC); } // on remet la protection en écriture delay(500); digitalWrite(writePin, LOW); } // Écriture massive void zeroMemory() { byte state = 0; byte page = 0; unsigned char theZero[8] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}; // on lève la protection en écriture digitalWrite(writePin, HIGH); delay(500); // On écrit séquentiellement for(page=0;page<=15;page++) { Wire.beginTransmission(writeID); Wire.write(page*8); Wire.write(theZero, 8); state = Wire.endTransmission(true); delay(100); if (state != 0) { Serial.print("Erreur d'écriture page: "); Serial.print(page); Serial.print(" :"); Serial.println(state, DEC); } } // on remet la protection en écriture delay(500); digitalWrite(writePin, LOW); } // Configuration de l'Arduino void setup() { pinMode(writePin, OUTPUT); setWriteProtection(true); Serial.begin(9600); Wire.begin(); // On lance le bus I2C Serial.println("\nI2C EEPROM Player"); delay(5000); } // Fonction principale void loop() { byte i = 0; // Un mode interactif if (Serial.available() > 0) { command = Serial.readStringUntil('\n'); Serial.print("Your command:"); Serial.println(command); } // On gère la signification du truc if (command.equals("dump")) { command = ""; Serial.println("Dumping…"); // On dumpe dump(); } if (command.equals("print")) { Serial.println("Printing…"); command = ""; for(i=0;i<=127;i++) { Serial.print(eeprom[i], HEX); Serial.print("|"); } Serial.println(" "); } if (command.equals("write")) { command = ""; // on effectue une écriture de base Serial.println("Writing in EEPROM…"); writeInMemory(0x9, 0xFE); } if (command.equals("flush")) { command = ""; // on effectue une écriture de base Serial.println("Flushing data…"); memset(eeprom,0,sizeof(eeprom)); } if (command.equals("zero")) { command = ""; // on effectue une écriture de base Serial.println("Zeroing memory…"); zeroMemory(); } delay(4000); }
Compiler et mettre le code sur Arduino
En 2018, les bonnes recettes n'ont toujours pas changé: on a toujours besoin d'AVRdude et du paquet arduino-mk pour envoyer des choses vers l'Arduino.
Voici le Makefile que j'utilise:
# Makefile for lc21b ## Directories ARDUINO_DIR = /usr/share/arduino ARDMK_DIR = /usr/share/arduino ARDUINO_LIBS = Wire ## Arduino Board configuration MCU = atmega328p F_CPU = 16000000 ARDUINO_PORT = /dev/ttyUSB* ## Avrdude configuration AVRDUDE_ARD_PROGRAMMER = arduino AVRDUDE_ARD_BAUDRATE = 57600 ## Le nom de notre programme TARGET = lc21b include /usr/share/arduino/Arduino.mk
Ensuite, un simple make
suffit pour compiler le code. Pour l'upload, j'utilise make upload
.
Utilisation
Ensuite, il faut voir ce que retourne l'Arduino par le port série:
$ screen -L /dev/ttyUSB0
Vous pouvez ensuite taper vos commandes:
dump
permet de capturer le contenu de l'EEPROM dans une variable.print
affiche la capture de l'EEPROM.write
écrit à l'adresse 0x9 la valeur FE (à vous de modifier cette valeur).zero
permet de mettre à zéro toute l'EEPROM.
Conclusions
Bon, j'ai bien aimé faire mumuse avec cette EEPROM. Ok, elle n'a pas beaucoup de stockage mais c'est parfait pour la lecture de données par l'Arduino qui n'a pas non plus beaucoup de mémoire. D'un point de vue didactique, c'est plutôt simple.
La bibliothèque Arduino fait vraiment bien son job: je n'ai jamais dû lire les specs du protocole I2C ni faire d'opérations bas niveau pour exploiter le bus.
Vous me direz que je suis bien avancé: je sais utiliser un circuit intégré qui n'est plus produit depuis au moins 10 ans. Mais sachez que le fonctionnement sera quasi identique pour les derniers produits du moment. En effet, si je prends la M24C02, elle a pratiquement les mêmes spécifications et elle utilise les mêmes ports. Je peux substituer la ST24LCB1 de mon circuit par la M24C02 et tout marchera sans changer une virgule au code !
Ce qui est aussi dingue, c'est que cette EEPROM est assez ancienne. Je dirai qu'elle avait au moins 20 ans lors de la rédaction de cet article et les données dessus n'avaient pas bougé d'un poil de bit. Ce qui est encore plus dingue c'est que je l'ai dessoudée "à la main" pour la récupérer, avec tous les risques de surchauffe que ça comporte et qu'elle est parfaitement opérationnelle car j'arrive à tout lire et à tout écrire sans erreur.
En conclusion, je vous invite à désosser les circuits imprimés de vos rebus électroniques et à les conserver pour le futur. Ça peut toujours vous servir pour un circuit ou pour apprendre. Comme en plus vous n'aurez rien dépensé pour l'avoir, vous n'aurez pas trop de regrets lorsque, à la suite d'une mauvaise manipulation ou mauvais branchement, vous verrez un nuage de fumée en sortir !
PS: Ce composant est fait par ST Microelectronics, un vaillant fondeur de puces électroniques qui est français à l'origine. Cette multinationale embauche encore environ 10000 personnes en France. Souvent oubliée dans le monde de l'électronique qui présente souvent des acteurs américains, chinois ou japonais, ST est un acteur important de l'électronique qui emploie des vrais gens en France. Si vous avez de l'électronique à faire, je vous invite à utiliser les produits ST qui sont à la fois très bien faits, pas forcément chers à l'achat et qui profitent (un peu) à l'économie française !