Elrindel/Flob

  • Accueil
  • Github
  • Liens
  • Bin Converter

Programmer une mémoire Flash (EEPROM) 32 broches avec un Arduino

Publié le : 18 Jul 2020 Catégories : DIY

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

39SF040 Read Diagram

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 :

39SF040 Sequences

Et enfin le diagramme représentant l’écriture :

39SF040 Write Diagram

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’adresse
  • 8 pour les données
  • 3 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 :

  1. 2 x 74HC595 : Registre à décalage 8 bits (voir ci-dessous pour plus de détails)
  2. 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 le Uno et la bonne tension)
  • 2 x 74HC595
  • 1 x Socket DIP32 ou Breadboad (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 une 39SF040 (pour le boitier PLCC32 j’ai des AM29F040)

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 :

74HC595 Timing Diagram

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 :

74HC595 Logic Diagram

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…).

Arduino Flash Programmer Schéma

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 :

Arduino Flash Programmer Prototype Arduino Flash Programmer Prototype Arduino Flash Programmer Prototype

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