Dans un micro-contrôleur tel que l'ATmega328 qui est inclus dans un Arduino UNO, il existe deux types de mémoire: la mémoire persistante et la mémoire volatile. Comme leur nom l'indique, la première n'est pas effacée lorsque la carte est mise hors-tension, alors que la seconde l'est. L'architecture du micro-contrôleur est organisée autour de trois mémoire d'usages différents:
- la mémoire Flash dans laquelle est sauvegardée le programme lui-même. C'est une mémoire persistante. Heureusement, sinon, il faudrait reprogammer l'arduino à chaque allumage... Par contre, elle n'est accessible qu'au moment de la programmation.
- le mémoire RAM qui est volatile. C'est dans cette zone que les variables d'exécution du programme sont stockées. Par exemple, le
i d'une bouclefor() . - la mémoire EEPROM qui est persistante et accessible lors de l'exécution du programme. Elle est particulièrement pratique lorsque vient le temps de sauvegarder des états modifiés par le programme. Une simple analogie: l'adresse d'un décodeur ou une variable de configuration (CV) dans un décodeur DCC.
- 32ko de mémoire Flash
- 2ko de mémoire RAM
- 1ko de mémoire EEPROM
Nous avons vu précédemment une structure de programme particulière: le tableau. Afin de vous remémorer ce concept, vous pouvez vous référer à l'exemple du phare côtier dans lequel nous avions défini un tableau de valeurs d'intensité
Par analogie, nous définirons implicitement la zone de mémoire EEPROM comme un tableau de valeurs
Accéder à un élément du tableau est bien simple (nous l'avons d'ailleurs déjà vu en partie dans les exemples précédents). Définissons une tableau:
De manière similaire, l'écriture d'un élément dans le tableau s'effectue ainsi:
Voyons ce que cela donne dans un petit programme...
byte tab[ SIZE ];
byte e;
void setup()
{
for ( int i = 0; i < SIZE; i++ )
{
tab[ i ] = 0;
}
}
void loop()
{
e = tab[ 10 ];
tab[ 12 ] = 50;
}
Vous êtes d'ores-et-déjà familier avec les concepts simples de programmation. Nous ne nous étendrons donc pas sur ce code, mais nous résumerons ces lignes ainsi:
- le tableau
tab est construit avec une tailleSIZE soit1024 éléments de typebyte . - dans la fonction
setup() , nous initialisons le contenu de toutes les cases du tableau avec la valeur0 grâce à une bouclefor() . - dans la fonction principale
loop() , la valeur de l'élément d'index10 est luee = tab[ 10 ] puis celui de valeur50 est enregistré à l'index12 en effectuanttab[ 12 ] = 50 .
Comme nous l'avons vu au chapitre concernant les librairies, les fonctionnalités liées à la gestion de l'EEPROM ne sont pas incluses par défaut dans un programme. Donc, la première étape est d'inclure celle-ci par la directive
Une fois fait, il ne reste plus qu'à accéder à cette zone mémoire à la manière d'un tableau en appelant les méthodes
byte e;
void setup()
{
for ( int i = 0; i < SIZE; i++ )
{
EEPROM.write( i, 0 );
}
}
void loop()
{
e = EEPROM.read( 10 );
EEPROM.write( 12, 50 );
}
Mais le gros avantage désormais est que le contenu est persistant même si l'Arduino n'est plus alimenté contrairement au premier exemple... Ceci-dit, les instructions effectuées ici ne sont pas d'un grand intérêt. Passons à un exemple concret.
Cet exemple va regrouper plusieurs concepts vus dans les chapitres précédents:
- le bouton-poussoir ou l'utilisation des entrées
- le contrôle d'un servo-moteur
- et bien évidemment la mémoire EEPROM
- de positionner un aiguillage à l'aide de deux boutons poussoirs
- de visualiser la position à l'aide de deux LEDs
- et enfin de conserver cette information si le contrôleur (le réseau) est éteint.
Et voilà le code source:
#include <EEPROM.h>
int boutonGauche = 8;
int boutonDroit = 9;
int ledRouge = 2;
int ledVerte = 3;
int commande = 7;
Servo moteur;
#define ADR_POS 0
#define POS_GAUCHE 90
#define POS_DROITE 45
void setup()
{
pinMode( boutonGauche, INPUT );
digitalWrite( boutonGauche, HIGH );
pinMode( boutonDroit, INPUT );
digitalWrite( boutonDroit, HIGH );
pinMode( ledRouge, OUTPUT );
pinMode( ledVerte, OUTPUT );
moteur.attach( commande );
if ( EEPROM.read( ADR_POS ) != POS_GAUCHE
&& EEPROM.read( ADR_POS ) != POS_DROITE )
{
EEPROM.write( ADR_POS, POS_GAUCHE );
}
}
void loop()
{
if ( digitalRead( boutonGauche ) == LOW )
{
while ( digitalRead( boutonGauche ) == LOW )
{
delay( 20 );
}
if ( EEPROM.read( ADR_POS ) == POS_DROITE )
{
for ( int i = POS_DROITE; i <= POS_GAUCHE; ++i )
{
moteur.write( i );
delay( 20 );
}
EEPROM.write( ADR_POS, POS_GAUCHE );
}
}
else if ( digitalRead( boutonDroit ) == LOW )
{
while ( digitalRead( boutonDroit ) == LOW )
{
delay( 20 );
}
if ( EEPROM.read( ADR_POS ) == POS_GAUCHE )
{
for ( int i = POS_GAUCHE; i >= POS_DROITE; --i )
{
moteur.write( i );
delay( 20 );
}
EEPROM.write( ADR_POS, POS_DROITE );
}
}
if ( EEPROM.read( ADR_POS ) == POS_GAUCHE )
{
digitalWrite( ledVerte, HIGH );
digitalWrite( ledRouge, LOW );
}
else
{
digitalWrite( ledVerte, LOW );
digitalWrite( ledRouge, HIGH );
}
}
Je ne rentrerai pas dans les détails puisque toutes les parties importantes de ce code ont été vues dans les chapitres précédents. Bien évidemment, il n'y a pas qu'une seule façon de faire... J'ai développé ici un code simple et clair qui permettra au lecteur débutant de comprendre les grandes étapes de ce programme. Quelques mots rapidement:
- on définit l'adresse mémoire EEPROM à laquelle on désire sauvegarder la position du moteur
#define ADR_POS 0 . Ici, le premier des 1024 bytes de la zone mémoire - dans la fonction
setup() , on initialise la valeur à mettre en mémoire EEPROM car la première fois, il y a n'importe quelle valeur... Le test est simple à comprendre: si la valeur lue à cette adresse en EEPROM ne correspond ni à la position gauche ni à la position droite (en fait, il faut lire: "si la valeur actuelle est différente de la position gauche ET la valeur actuelle est différente de la position droite", alors écrit "une valeur par défaut" dans la case mémoire EEPROM d'adresse 0. Ici nous avons fait le choix de la position gauche, mais cela aurait pu être l'autre... - dans la fonction
loop() principale, il y a deux grandes étapes: d'une part la détection d'un évènement (bouton pressé): si le "bouton pressé ne correspond pas à la position courante du moteur alors actionner le moteur", ce qui basculera les rails dans la bonne position. D'autre part, les LEDs sont mises à jour en fonction de la position courante du moteur.
Remarque importante: en écrivant ce code, j'ai trouvé un bug dans la librairie
La plupart du temps, il y a suffisamment de mémoire EEPROM dans l'Arduino pour y stocker quelques données persistantes. Mais il peut être nécessaire d'ajouter un peu d'espace... Par exemple dans le cas d'un automate, si on veut enregistrer un script pour chaque locomotive, la logique du réseau et quelques informations de signalisation, 1ko devient vite un facteur limitant.
D'où l'intérêt de ce composant: le 24LC256. Il s'agit d'une EEPROM avec laquelle l'Arduino communique grâce au bus I2C. Il existe plusieurs tailles de stockage. Ici, cette version donne accès à 256kbit ce qui est correspond à 32ko.
Le branchement est simple:
www.datasheetdir.com |
Pin 8: Vcc vers +5V
Pin 4: Vss vers GND
Le bus I2C:
Pin 5: SDA vers A4 (Arduino Uno)
Pin 6: SCL vers A5 (Arduino Uno)
La protection en écriture:
Pin 7: WP vers GND pour autoriser l'écriture ou +5V pour protéger le contenu.
Enfin, l'identificateur:
Pin 1, 2 et 3: A0, A1 et A2. Le triplet de bits permet de former une valeur de 0 à 7 (b000, b001, b010, ... b111). Cette valeur est à ajouter à l'adresse de base 0x50, identifiant une mémoire EEPROM de ce type. Ainsi, dans le programme, on accédera au device I2C avec l'adresse 0x50 + 0 ou 0x50 + 1 ou 0x50 + 2, etc... Cela permet d'ajouter plusieurs puces identiques sur le bus I2C et les différencier par leur adresse respective. Dans le cas présent, on définie ce device par l'identificateur 0, donc les 3 pins A0, A1 et A2 sont connectés au GND.
Maintenant, le code... Tout d'abord, il faut communiquer avec ce device par le bus I2C. Nous allons donc inclure la librairie
#define ADDR 0x50
Deux fonctions bien utiles. Tout d'abord,
{
Wire.beginTransmission( ADDR );
Wire.write( (unsigned int)( ( address & 0xFF00 ) >> 8 ) );
Wire.write( (unsigned int)( address & 0x00FF ) );
Wire.write( (uint8_t)value );
Wire.endTransmission();
delay( 3 );
}
byte EEread( unsigned int address )
{
byte data = 0xFF;
Wire.beginTransmission( ADDR );
Wire.write( (unsigned int)( ( address & 0xFF00 ) >> 8 ) );
Wire.write( (unsigned int)( address & 0x00FF ) );
Wire.endTransmission();
Wire.requestFrom( ADDR, 1 );
data = Wire.read();
return data;
}
Le code n'est pas très compliqué. Dans les deux cas, on ouvre un canal de transmission en direction de l'adresse choisie, par un appel à la méthode
Ensuite, on transmet l'information. Tout d'abord l'adresse de la case mémoire puis la valeur à stocker dans cette case. Cette adresse étant sur 16 bits, il faut envoyer cette valeur en deux octets distincts: d'une part l'octet de poids fort
Quelques mots sur ces deux instructions.
La dernière instruction de transmission est
Les données effectives ont été transmises, il ne reste plus qu'à clore la transmission:
Un point important concernant la fonction
Passons maintenant à la seconde fonction:
Voilà, rien de bien sorcier... Pour que tout cela puisse fonctionner, il ne faut pas oublier d'initialiser la classe
Il ne vous reste plus qu'à écrire vos données vers l'EEPROM et les relire au besoin. Dernier point, cette mémoire est non volatile, donc tant que vous ne les effacez pas, elles seront toujours là au prochain allumage de votre circuit!
Bonjour,
RépondreEffacerPeux ton rajouter d'autres aiguillages sur le meme programme ?
Si oui combien et comment ?
D'avance merci