Utiliser une EEPROM externe avec un Arduino🔗

Posted by Médéric Ribreux 🗓 In blog/ Electronic/

#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:

Sur l'EEPROM

Nous avons les connexions suivantes:

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):

Un schéma entre Arduino et ST24LC21B
Un schéma entre Arduino et ST24LC21B

En plus du fait de relier les bons ports entre l'Arduino et l'EEPROM, on trouve quelques composants en plus:

En plus du schéma théorique, voilà le schéma sur une breadboard, ça peut toujours servir pour le débutant:

Une vue de connexion entre Arduino et ST24LC21B
Une vue de connexion entre Arduino et ST24LC21B

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:

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:

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 !

Références