Programmer une mémoire Flash (EEPROM) 32 broches avec un Arduino
Pour un projet, que vous verrez dans mon prochain article, j’avais besoin d’utiliser de la mémoire flash mais je n’avais pas de programmateur.
Souhaitant proposer une solution facilement accessible (sans devoir acheter un programmateur à 50€ minimum), je me suis documenté pour programmer ma mémoire flash avec le matériel déjà en ma possession et qu’on retrouve chez beaucoup de bidouilleurs.
J’avais le choix entre un Raspberry Pi
et un Arduino Uno
.
J’ai une grosse préférence pour l’Arduino. De plus, il s’avère que les mémoires que je dois programmer fonctionnent sous une tension de 5V, ce qui est le cas de mon Arduino Uno
.
Le Raspberry Pi aurait compliqué les choses avec les 3.3V des GPIO.
D’ailleurs assurez vous de pas raccorder une mémoire 3.3V sur du 5V au risque de la griller.
C’est donc l’Arduino Uno
qui me servira de programmateur d’EEPROM !
Solutions existantes
Il est important de préciser que je veux programmer des mémoires de 512Ko via une interface parallèle
ce qui implique d’avoir des mémoires flash avec au moins 32 broches (voir ci-dessous pour les détails).
Evidemment je suis pas le premier à me dire qu’on peut programmer des EEPROM avec un Arduino Uno
et j’ai effectivement trouvé deux ou trois projets à ce sujet sur internet.
Ces derniers sont soit foireux, soit leur code me plait pas, soit ils sont incomplets … J’ai donc décidé de faire ma propre version que je vais vous partager ici !
Comment lire et écrire une mémoire flash ?
Comme évoqué ci-dessus, je vais utiliser des mémoires avec une interface parallèle
et 32 broches.
Une puce mémoire avec interface parallèle
possède un certain nombre d’entrées allant de A0
à AMS
(Most signifiant address), A0
étant donc le bit de poids faible et AMS
le bit de poids fort.
Ces entrées correspondent à l’adresse mémoire ciblée et leur nombre va donc dépendre de la taille de la mémoire.
Par exemple, pour 512Ko on aura 19 entrées, donc de A0
à A18
, car il faut 19 bits pour atteindre toutes les adresses mémoire : 512Ko = 524288 octets (oui je devrais écrire Kio pour être exact … mais on se comprend ^^), vu qu’on commence à l’adresse 0, la dernière adresse sera 524287
ce qui nécessite exactement 19 bits.
Ensuite on trouve les 8 broches DQ0
à DQ7
qui sont des sorties en mode lecture et des entrées en mode écriture !
Lors de la lecture, elles vont prendre la valeur de la donnée contenue à l’adresse mémoire ciblée.
Et inversement, lors de l’écriture, l’adresse mémoire ciblée va prendre la valeur qu’on enverra sur ces broches.
Le mode va dépendre de l’état des 3 entrées suivantes : CE#
(Chip Enable), OE#
(Output Enable), WE#
(Write Enable)
Ces entrées sont activées à l’état bas (et donc désactivées à l’état haut), d’ailleurs on peut constater cela via la notation #
.
La lecture
Le mode lecture est le plus simple car il suffit de désactiver WE#
puis d’activer CE#
et OE#
pour obtenir directement l’octet de l’adresse ciblée par les entrées A0
à AMS
.
Voici le diagramme représentant la lecture d’une mémoire flash SST39SF040
(valable pour beaucoup d’autres mémoires) :
Les seules limitations sont donc les temps d’attente entre l’assignation de l’adresse mémoire et l’accès aux données.
Ces temps vont varier suivant les modèles, on parle de quelques dizaines de nanosecondes, mais il s’agit d’une durée minimale, on peut très bien attendre plus longtemps pour être certain d’accéder aux données.
L’écriture
Pour l’écriture c’est plus délicat car il faut suivre une procédure pour réellement écrire dans la mémoire.
En effet, il ne suffit pas d’activer WE#
avec l’adresse et l’octet souhaités !
La procédure à suivre s’appelle une séquence qui correspond à l’envoi de certaines données à certaines adresses précises pour basculer dans le mode souhaité.
Ces données de séquence ne sont pas écrites dans la mémoire (il n’y a donc pas de corruption de données en mémoire, heureusement).
Via différentes séquences on peut ainsi exécuter quelques commandes sur la mémoire, comme :
- Ecrire une donnée
- Effacer une adresse mémoire
- Effacer l’ensemble de la mémoire
- Récupérer l’identifiant du fabricant et le modèle
- Certaines mémoires proposent aussi un mode verrouillage (pour bloquer l’écriture)
Ces séquences sont disponibles dans le datasheet
de chaque mémoire !
Le datasheet
est un document indispensable en électronique, il regroupe toutes les informations techniques ainsi que des exemples d’utilisation (de temps en temps) pour chaque composant.
Une simple recherche suffit à trouver un datasheet
en utilisant tout simplement les mots clés datasheet REFERENCE
(REFERENCE étant évidemment la référence du composant que vous recherchez).
Concernant les mémoires qui m’intéressent pour mon projet, je retrouve quasiment toujours les mêmes séquences.
Pour le moment j’ai trouvé seulement 2 variantes. Dans tous les cas il suffit de lire la documentation pour savoir quelle séquence utiliser pour votre mémoire et adapter le programme au besoin (voir plus bas).
Par exemple, voilà les séquences disponibles pour la mémoire SST39SF040
:
Et enfin le diagramme représentant l’écriture :
Comme pour la lecture, il est nécessaire de respecter certains timing.
Ceci dit, l’Arduino Uno
n’est pas très rapide et la majorité des opérations peuvent passer sans gérer les timing si le programme n’est pas très optimisé, mais il est préférable de les gérer pour pas avoir de surprise.
Les composants pour fabriquer votre propre programmateur d’EEPROM
Faisons les comptes des broches :
19
pour l’adresse8
pour les données3
pour les contrôles
30
broches qu’on va devoir manipuler (les 2 restantes correspondent à l’alimentation).
L’Arduino Uno
possède 14
entrées/sorties numériques et 6
entrées analogiques.
On peut déjà oublier 2
entrées/sortie numériques (pin 0 et pin 1) car elles sont utilisées pour la communication série (dont on aura besoin pour communiquer avec le PC).
Heureusement on peut utiliser les 6
entrées analogique comme entrée/sortie numérique !
Ce qui fait un total de 18
entrées/sorties exploitables avec l’Arduino Uno
!
Ce n’est donc pas suffisant mais il existe au moins deux solutions très simples à ce problème :
- 2 x
74HC595
: Registre à décalage 8 bits (voir ci-dessous pour plus de détails) - 2 x
74HC574
: Bascule 8 bits
Personnellement j’ai choisi la première solution mais les deux sont tout à fait viables pour ajouter des sorties.
Les registres à décalage ont cependant un très gros avantage car on peut en ajouter autant qu’on veut à la suite sans avoir besoin de sacrifier une sortie de l’Arduino à chaque nouvelle puce (contrairement aux bascules qui doivent être pilotées indépendamment les unes des autres).
D’ailleurs, petite info intéressante à ce sujet, on retrouve généralement des registres à décalage dans les bandeaux de led paramétrables. Si le bandeau permet de contrôler n’importe quelle led indépendamment des autres alors il y a de fortes chances pour que chaque led possède un registre à décalage.
C’est bien beau toutes ces entrées/sorties mais il faut les raccorder aux broches de la mémoire flash.
Pour faire cela tout en pouvant insérer/retirer la puce mémoire facilement, il faut utiliser soit un socket, soit une breadboard.
L’utilisation d’un socket implique la conception d’un circuit imprimé (enfin il est toujours possible de faire ça en l’air évidemment, mais c’est pas très propre/fiable).
Donc si vous avez une plaque de prototypage ou la possibilité de faire votre propre PCB, vous pouvez étudier cette solution, sinon ça sera une breadboard !
Il est également possible de faire fabriquer un circuit via des sites comme JLCPCB, PCBWay etc…
J’ai ce qu’il faut pour faire des circuits imprimés simple face du coup je pars sur la solution socket afin de faire quelque chose d’un peu plus propre qu’un simple prototype.
D’ailleurs, en parlant de socket, il est temps d’aborder le sujet du package (boitier) de la puce ! Afin de pouvoir la manipuler facilement j’ai choisi du DIP32
.
Dans une seconde version je prévois l’utilisation du PLCC32
qui peut également être manipulé facilement avec la pince qui va bien.
Donc, pour résumer, les composants nécessaires sont :
- 1 x
Arduino Uno
(ou autre avec au moins autant de pins que leUno
et la bonne tension) - 2 x
74HC595
- 1 x
Socket DIP32
ouBreadboad
(si vous avez ou non la possibilité de faire un PCB) - Au moins une mémoire flash en boitier
DIP32
, dans mon cas ça sera une39SF040
(pour le boitierPLCC32
j’ai desAM29F040
)
Vous pouvez trouver l’ensemble de ces composants sur internet pour vraiment pas cher. Si vous n’êtes pas trop pressés vous trouverez votre bonheur sur des sites comme Aliexpress ou Ebay.
Explications registre à décalage 74HC595
Ce registre à décalage possède 16 broches dont 9 sont des sorties (les broches de QA
à QH
ainsi que QH'
).
Lors d’un cycle, chaque sortie va prendre la valeur de la sortie précédente, excepté QA
qui prendra la valeur de l’entrée SER
, et QH'
qui est en quelque sorte une copie de QH
(et prendra donc la valeur de QG
).
Le mieux pour illustrer ces cycles, c’est encore de regarder le diagramme disponible dans le datasheet du composant :
Ce diagramme montre bien la progression d’un bit envoyé initialement via l’entrée SER
puis propagé dans chaque sortie l’une après l’autre à chaque cycle.
Ceci dit, ce diagramme pourrait laisser penser qu’il faut absolument jongler avec les deux horloges SRCLK
et RCLK
afin d’attribuer tous les bits du registre, mais c’est faux !
En effet, pour constater cela il faut regarder un autre diagramme, le diagramme logique :
On peut constater que pour 1 bit il y a 2 registres différents, le premier permet de gérer le décalage (colonne de gauche sur le diagramme), et le second stockera la valeur pour la sortie (colonne de droite).
Le premier est géré par l’horloge SRCLK
et le second par RCLK
.
En clair, ça veut dire qu’on peut simplifier la gestion des horloges pour configurer l’ensemble des registres à décalage en utilisant uniquement l’horloge SRCLK
pour définir tous les bits, puis une fois qu’ils sont tous configurés on a plus qu’à faire un coup d’horloge RCLK
pour envoyer les valeurs sur la sortie.
Un autre point intéressant, la sortie QH'
n’est pas liée à RCLK
ni à OE#
, on peut donc raccorder autant de puces qu’on le souhaite en série sans avoir besoin de faire un cycle RCLK
entre chaque puce et sans dépendre de l’état de OE#
!
Donc en reliant la sortie QH'
d’une puce à l’entrée SER
d’une autre puce, on permet aux données de passer d’une puce à l’autre.
Il reste juste à relier toutes les broches communes à toutes les puces ensemble : VCC
, GND
, RCLK
, SRCLK
Les deux dernière broche, OE#
et SRCLR
, sont également communes à toutes les puces.
OE#
permet d’activer les sorties, dans le cas présent elles seront toujours actives, pour cela il faut mettre cette broche à l’état bas.
SRCLR
est un simple reset pour réinitialiser les registres à zéro. Ici c’est inutile, on va donc laisser cette broche à l’état haut (le reset est fait au passage à l’état bas).
Le schéma électronique
On a tout vu dans les chapitres précédents.
Le schéma consiste donc tout simplement à raccorder les registres à décalage ensemble, relier leurs sorties sur les entrées A0
à AMS
de la mémoire, relier les alimentations puis apporter tout le reste sur des connecteurs pour y raccorder l’Arduino
.
Sur le schéma j’ai directement inséré l’Arduino Uno
comme pour réaliser un shield arduino (ça permet aussi de voir les raccordements à faire ^^).
Mais lors de la fabrication du circuit, je n’ai pas fait de shield arduino car c’était trop délicat à faire avec un circuit simple face (et pour tout vous dire, j’ai foiré ma tentative de shield …).
Je suis donc parti sur un simple PCB avec des connecteurs classiques en 2.54mm (standard qu’on retrouve un peu partout, Arduino, breadboard etc…).
J’ai fait le choix d’utiliser seulement 2 registres à décalage, ce qui fait 16 sorties, je dois donc gérer les 3 dernières (A16
, A17
et A18
) directement avec les sorties de l’Arduino
(donc sur un connecteur).
Aperçu de mon prototype
Comme précisé précédemment, je peux fabriquer uniquement des circuits imprimés à une face, ce qui n’est pas suffisant pour un tel projet si on veut faire ça proprement.
En effet, il faudra forcément croiser certaines liaisons pour un tel montage, ce qui nécessite d’avoir au moins deux couches de cuivre.
J’ai donc fait comme je pouvais avec une couche et j’ai fini les liaisons manquantes avec quelques morceaux de fil.
C’est donc un prototype loin d’être parfait (c’est un peu le rôle d’un prototype d’ailleurs) et je préfère ne pas partager les fichiers de cette conception car j’estime que ce n’est pas assez propre pour l’être.
(Il est cependant possible que je fasse évoluer ce projet en faisant un beau PCB 2 couches, mais ça sera pas avant 2021 je pense).
Mais voilà quand même un aperçu :
Le software pour gérer la programmation de la mémoire
Il y a donc deux parties à coder, une pour l’Arduino
(en C), l’autre pour le PC (en Python).
Afin de faciliter l’utilisation, j’ai fait en sorte de pouvoir accéder à toutes les commandes sur l’Arduino
sans utiliser le client en python sur le PC.
Il est donc possible de manipuler la mémoire flash via un simple moniteur série
comme celui proposé dans l’IDE Arduino
ou tout autre client dans le genre comme Putty
.
C’était surtout pratique pour les tests, mais désormais je n’utilise plus que mon client en python !
Arduino
ATTENTION : Comme je l’ai déjà précisé dans l’introduction de cet article, j’ai des mémoires qui fonctionnent sous une tension de 5V et mon Arduino Uno
est également en 5V.
Il est TRES IMPORTANT de vous assurer que votre mémoire et votre Arduino fonctionnement avec la même tension (pas seulement au niveau de l’alimentation mais aussi au niveau des sorties de l’Arduino) !
J’ai fait deux version, une optimisée et compatible uniquement Uno
et Nano
(et normalement Due
aussi), ainsi qu’une pour les autres cartes avec assez de pins comme Mega
, Yun
ou Micro
.
La version pour les autres cartes fonctionne aussi sur Uno
et Nano
mais je trouve le code vraiment moche et clairement pas optimisé.
La seule différence étant la manipulation des registres des ports de l’Arduino
sur la version optimisée alors que l’autre version utilise les fonctions Arduino
pour faire ces manipulations (fonctions vraiment lourdes en temps d’exécution, et il faut bien l’avouer, c’est moche).
Le problème c’est que ces registres ne sont pas les mêmes sur toutes les cartes et ils sont même dans le désordre sur certaines …
D’ailleurs, c’est surtout le fait que ce soit dans le désordre qui est problématique car dans le cas contraire j’aurais pu mapper les bons registres suivant la carte sélectionnée lors de la compilation …
Pour plus de détails sur l’utilisation des registres pour manipuler les pins de l’Arduino
:
- Arduino Port Registers
- Pinout Arduino (il faut voir la valeur
Port Pin
de chaque pin pour savoir sur quel registre ils sont)
Je ne vais pas rentrer dans les détails du code ici.
Toutes les instructions sont disponibles dans le readme du projet : Arduino Flash Programmer
PC
Bien qu’il soit optionnel, c’est beaucoup plus simple d’utiliser un petit client en ligne de commande pour gérer la communication avec l’Arduino
plutôt que d’ouvrir un moniteur série et envoyer toutes les commandes manuellement.
Surtout si il faut envoyer un certain nombre de données (comme pour écrire un fichier entier dans la mémoire).
Il y a donc rien d’extraordinaire dans ce code, c’est juste une gestion des arguments avec quelques petites validations, puis une communication série avec l’Arduino
.
La configuration du port série est celle de l’Arduino
par défaut, à savoir 8 data bits, no parity, 1 stop bit avec une vitesse de 115200 baud.
Comme pour le code Arduino
, toutes les infos sont dans le readme : Arduino Flash Programmer
Conclusion
Je suis loin d’avoir traité tout le sujet de la programmation de mémoire flash mais ça reste une bonne initiation je pense :)
En tout cas ce projet peut clairement évoluer, il est donc possible qu’il y ait une évolution et donc un nouvel article (c’est même fort probable) !
D’ailleurs, mon prochain article sera indirectement lié puisqu’il s’agira d’une mise en pratique concrète afin de réaliser un autre projet.
Sources et inspirations
- Code du projet
- AllDataSheet (Bah oui, sans datasheet on fait pas grand chose ^^)
- MEEPROMMER (Un projet similaire qui m’a inspiré)
- Arduino Port Registers
- Pinout Arduino