Programmeur Arduino UNO pour Ordinateur sur Plaque d'Essai 8 bits – Construisez et Codez Votre Système
Composants et Fournitures
![]() |
Arduino UNO
|
× | 1 |
À Propos de ce Projet
Qu'est-ce que ce projet ?
Ce projet est une extension de l'ordinateur sur plaque d'essai 8 bits de Ben Eater. J'ai énormément apprécié suivre ses vidéos sur la construction d'un ordinateur sur plaque d'essai et en construire ma propre version. Si vous n'avez pas suivi les vidéos de Ben, je vous recommande vivement de les regarder avant de lire la suite — ce projet n'aura que peu de sens sans ce contexte.
Une fois l'ordinateur construit, il est vite apparu que si je devais entrer manuellement un programme via des commutateurs DIP à chaque mise sous tension, l'ordinateur ne servirait pas beaucoup. Même l'entrée de seulement 16 octets encore et encore devient vite fastidieuse. De plus, je prévoyais d'étendre la mémoire à 256 octets, ce qui n'aurait d'utilité que si les programmes pouvaient être chargés et sauvegardés.
Que peut-on faire avec ?
- Sauvegarder un programme depuis la RAM de l'ordinateur sur plaque d'essai
- Charger un programme dans la RAM de l'ordinateur sur plaque d'essai
- Exécuter automatiquement un programme sauvegardé au démarrage de l'ordinateur sur plaque d'essai
- Inspecter et modifier le contenu de la mémoire via une connexion série
- Programmer l'ordinateur sur plaque d'essai en langage assembleur
- Désassembler le contenu de la mémoire
- Exécuter pas à pas, instruction par instruction
- Définir des points d'arrêt et exécuter jusqu'au point d'arrêt
Voici une vidéo démontrant ces fonctionnalités. Excusez la qualité vidéo — je ne suis en aucun cas un créateur de contenu vidéo professionnel :
Comment cela fonctionne-t-il ?
J'avais quelques objectifs en tête en réalisant ce projet :
- Matériel minimal requis pour le construire
- Modifications minimales de l'ordinateur sur plaque d'essai lui-même
Au début, j'ai pensé utiliser une EEPROM pour stocker les programmes, puis une logique pour transférer les programmes de l'EEPROM vers la RAM de l'ordinateur sur plaque d'essai. Cependant, concevoir la logique de transfert s'est avéré plus compliqué que je ne pouvais gérer (je suis plus développeur logiciel, même si j'aime travailler près du matériel). Étant un grand fan d'Arduino, j'ai fini par envisager de remplacer la logique de transfert par un Arduino. Il m'a fallu du temps pour me convaincre que l'utilisation d'un Arduino n'était pas de la « triche » (après tout, même l'Arduino UNO est bien plus puissant que l'ordinateur sur plaque d'essai lui-même), mais je suis satisfait du résultat.
Que doit faire l'Arduino ? Il doit pouvoir lire des données depuis la mémoire et écrire des données dans la mémoire. La méthode la plus simple et la moins intrusive (pour l'ordinateur sur plaque d'essai) consiste à utiliser la même interface que le module de mémoire possède déjà : le bus et les signaux de contrôle. Les broches d'E/S numériques de l'Arduino sont bidirectionnelles ; connecter 8 d'entre elles directement au bus permet à l'Arduino de lire et d'écrire sur le bus. Pour faire lire ou écrire le module RAM sur le bus, il suffit de positionner les signaux MI/RI/RO en conséquence. Or, ces signaux sont normalement pilotés par les EEPROM de la logique de contrôle ; faire les piloter par l'Arduino aussi entraînerait des conflits et d'éventuels courts-circuits. Cependant, les EEPROM AT28C16 utilisées par Ben possèdent une entrée Chip Enable (CE) qui met toutes les sorties de données en haute impédance (high-Z), ce qui permet alors à l'Arduino de manipuler les signaux.
Pour lire la RAM, l'Arduino doit :
- Mettre le signal CE de l'EEPROM à l'état haut (désactiver la logique de contrôle)
- Sortir la première adresse à lire sur le bus
- Mettre le signal MI à l'état haut et attendre une transition front montant de l'horloge
- Mettre MI à l'état bas et RO à l'état haut, attendre une transition front montant de l'horloge
- Lire l'octet de données sur le bus
- Répéter à partir de l'étape 2 pour les 16 adresses
- Remettre le signal CE de l'EEPROM à l'état bas (réactiver la logique de contrôle)
L'écriture dans la RAM est très similaire : l'Arduino écrit sur le bus et met RI à l'état haut au lieu de RO. Bien sûr, il y a quelques problèmes techniques à résoudre, mais le mécanisme de base ci-dessus est le cœur de ce projet.
Quelles modifications apporter à l'ordinateur sur plaque d'essai ?
Deux modifications sont nécessaires :
- Ajouter des résistances de tirage vers le bas (pull-down) sur les sorties de données des EEPROM de la logique de contrôle
- Désactiver (ou réinitialiser en permanence) le compteur annulaire pendant que l'EEPROM est désactivée
Résistances de tirage vers le bas (pull-down)
Ces résistances sont nécessaires car une fois l'EEPROM désactivée, les résistances de tirage vers le haut (pull-up) des portes logiques auxquelles les signaux de contrôle (AO, AI, EO, etc.) sont connectés tireront ces signaux à l'état haut. Cela signifierait que plusieurs registres écriraient sur le bus simultanément, causant des conflits.
Les résistances de tirage vers le haut sur les entrées des portes 74LS sont d'environ 10 kΩ. Les résistances de tirage vers le bas doivent être assez petites pour faire descendre la tension en territoire bas. Pour la plupart des lignes de signaux, j'ai utilisé des résistances de 3,3 kΩ. Cependant, il y a deux exceptions : d'abord, le signal « SU » est connecté à 8 portes XOR, ce qui fait que la résistance de tirage vers le haut effective est de 10 kΩ / 8 = 1,25 kΩ. Une résistance de tirage vers le bas devrait être significativement inférieure à 1 kΩ pour tirer ce signal bas. Heureusement, le signal SU (soustraction) ne contrôle aucune interaction avec le bus, on peut donc l'ignorer et ne pas mettre de résistance de tirage vers le bas. Ensuite, le signal CE (counter enable) nécessitait une résistance de tirage vers le bas de 1 kΩ — des valeurs plus grandes causaient un comportement aléatoire du compteur de programme dans certains cas.
J'ai trouvé plus simple d'ajouter les résistances de tirage vers le bas sur la plaque d'essai qui contient toutes les LED bleues, c'est-à-dire entre les anodes des LED (connectées aux sorties de l'EEPROM) et la masse.
Réinitialisation du compteur annulaire
L'autre modification consiste à réinitialiser le compteur annulaire. Techniquement, ce n'est pas strictement nécessaire pour permettre la sauvegarde/le chargement des programmes, mais cela permet un transfert de contrôle fluide de l'Arduino vers la logique de contrôle. L'idée est de maintenir le compteur annulaire à 0 tant que CE est haut (logique de contrôle désactivée). Lorsque l'Arduino remet CE à l'état bas (réactivation de la logique de contrôle), le compteur annulaire sera à zéro et l'ordinateur sur plaque d'essai commencera à exécuter l'instruction suivante.
Pour ma construction, je n'ai rien eu à faire de spécial car j'utilise une sortie de l'EEPROM pour réinitialiser le compteur annulaire. Cela améliore les performances en réinitialisant le compteur dès qu'une instruction est terminée. Cela me donne aussi automatiquement la réinitialisation du compteur annulaire lorsque l'EEPROM de la logique de contrôle est désactivée : la résistance de tirage vers le bas sur la sortie de l'EEPROM tire le signal bas, ce qui réinitialise le compteur.
Si vous utilisez l'implémentation de Ben d'un compteur annulaire fixe à 5 étapes, l'extension suivante de son circuit de réinitialisation devrait réinitialiser le compteur lorsque CE est haut (cliquez sur les flèches gauche/droite ci-dessous pour basculer entre le circuit original de Ben et la version étendue) :
Comme vous pouvez le voir, cela nécessite 3 portes NAND supplémentaires, soit une puce 74LS00 de plus. Notez que je n'ai pas testé cette approche, mais elle devrait fonctionner selon mon analyse.
Cette modification n'est pas absolument nécessaire — vous pouvez l'omettre au début. Le chargement et la sauvegarde ainsi que le moniteur/assembleur/désassembleur fonctionneront très bien. Cependant, toute action nécessitant un transfert de contrôle de l'Arduino vers la logique de contrôle ne fonctionnera pas. Notamment, l'exécution automatique de programmes sauvegardés au démarrage, ainsi que le pas à pas et l'exécution dans le débogueur.
Comment configurer l'Arduino ?
Téléversez le sketch depuis l'archive GIT sur l'Arduino et connectez l'Arduino à l'ordinateur sur plaque d'essai comme suit :
- Broche 5V de l'Arduino (pas Vin !) au rail 5V de la plaque d'essai
- Broche GND de l'Arduino à la broche GND de la plaque d'essai
- Broches numériques 2-9 de l'Arduino au bus 0-7
- Broche numérique 10 de l'Arduino à la sortie de l'EEPROM de la logique de contrôle qui pilote RO
- Broche numérique 11 de l'Arduino à la sortie de l'EEPROM de la logique de contrôle qui pilote RI
- Broche numérique 12 de l'Arduino à la sortie de l'EEPROM de la logique de contrôle qui pilote MI
- Broche analogique 0 de l'Arduino au signal CLOCK
- Broche analogique 3 de l'Arduino à CE (broche 18) de toutes les EEPROM de la logique de contrôle et via une résistance de 10 kΩ au +5V
En plus de cela, vous devrez câbler les broches analogiques 1 et 2 de l'Arduino aux commutateurs DIP et boutons poussoirs comme indiqué dans les schémas (pour plus de détails, voir le fichier Fritzing joint).
Pour une version minimale (mais toujours fonctionnelle), vous pouvez faire ce qui suit :
- Ajouter des résistances de tirage vers le bas de 3,3 kΩ aux broches de sortie de l'EEPROM qui pilotent AO, CO, EO, IO, RO
- Ignorer les instructions « Réinitialisation du compteur annulaire » ci-dessus
- Effectuer le câblage Arduino vers ordinateur sur plaque d'essai comme indiqué ci-dessus (vous pouvez omettre la résistance de 10 kΩ à la dernière étape si vous le souhaitez)
- Connecter à la fois la broche analogique 1 et la broche analogique 2 à la masse
Pour utiliser les boutons de chargement/sauvegarde, il suffit de connecter les boutons, commutateurs DIP et résistances associées aux broches analogiques 1 et 2 selon les schémas.
Pour utiliser la fonctionnalité de démarrage automatique, l'Arduino doit maintenir le compteur de programme et le compteur annulaire à 0 pendant la réinitialisation et pendant le transfert du programme. La résistance de tirage vers le haut entre la broche analogique 3 et +5V maintient la logique de contrôle désactivée (et donc le compteur de programme à 0) pendant que l'Arduino redémarre. Pour le compteur annulaire, suivez les instructions « Réinitialisation du compteur annulaire » ci-dessus.
Comment charger et sauvegarder des programmes ?
La configuration minimale ci-dessus vous permettra de contrôler l'Arduino via l'interface série. Pour communiquer avec l'Arduino, vous aurez besoin d'un programme terminal tel que Putty ou TeraTerm. Le moniteur série du logiciel Arduino fonctionnera aussi, mais la séparation entre la zone d'entrée et de sortie dans le moniteur série le rend un peu maladroit dans ce scénario.
- Allumez l'ordinateur sur plaque d'essai
- Connectez un PC à l'Arduino via le câble USB
- Lancez le programme terminal et configurez-le à 9600 bauds, 8 bits, pas de parité, 1 bit de stop
- Appuyez sur ÉCHAP dans la fenêtre du terminal pour entrer en mode Moniteur
- Vous devriez voir un « . » comme invite de commande
- Tapez « h » et appuyez sur Entrée pour obtenir la liste des commandes supportées
Avec la configuration minimale, vous devriez pouvoir utiliser les commandes « m », « M », « C », « l » et « s ». Elles permettent de voir le contenu de la mémoire, de le modifier, et de charger et sauvegarder des programmes.
Pour sauvegarder ou charger un programme via les boutons :
- Coupez l'horloge de l'ordinateur sur plaque d'essai
- Réglez les commutateurs DIP pour sélectionner le numéro de fichier sous lequel les données doivent être sauvegardées
- Appuyez sur le bouton « save » ou « load ». La LED connectée à CE s'allumera, indiquant que l'Arduino a pris le contrôle
- Remettez en marche l'horloge de l'ordinateur sur plaque d'essai. Vous devriez voir l'Arduino parcourir les adresses (observez les LED du registre d'adresse mémoire)
- Attendez que les LED du bus cessent de clignoter et que les LED du registre d'adresse mémoire affichent 1111
- Coupez l'horloge de l'ordinateur sur plaque d'essai. La LED connectée à CE s'éteindra, indiquant que le contrôle a été restitué à la logique de contrôle
Pour exécuter automatiquement un programme au démarrage (assurez-vous d'avoir tout le circuit nécessaire en place), réglez simplement les commutateurs DIP sur le numéro de fichier sous lequel le programme est sauvegardé et allumez l'ordinateur sur plaque d'essai (ou appuyez sur le bouton de réinitialisation). Il y a deux cas particuliers : si tous les commutateurs DIP sont éteints, l'ordinateur démarre normalement, sans démarrage automatique. Si tous les commutateurs DIP sont allumés, l'Arduino passe directement en mode Moniteur au démarrage.
Comment utiliser l'assembleur et le désassembleur ?
Pour utiliser les fonctionnalités d'assembleur/désassembleur et de débogueur, vous devrez d'abord modifier le programme sur l'Arduino pour qu'il corresponde à votre configuration spécifique. Trouvez la section dans le code source qui définit la structure opcodes_4bit :
struct opcodes_struct opcodes_4bit [] = {
{"NOP ", B00000000, 0, false},
{"LDA ", B00010000, 2, true},
...
{".OR ", B11111110, 0, true}, // définir l'adresse de début
{".BY ", B11111111, 0, true}, // définir un octet de données
{NULL, 0, 0, false}
};
Chaque ligne spécifie un opcode :
- Le premier champ est le mnémonique (« LDA »). Pour l'adressage immédiat, incluez un « # » dans le mnémonique. Ce que Ben appelle « LDI » serait appelé « LDA # » ici. On voit que j'ai grandi en programmant en assembleur 6510 sur un C64 !
- Le second champ est l'opcode lui-même. Les 4 bits de poids faible doivent toujours être 0 (sauf pour les opcodes spéciaux .OR et .BY, voir ci-dessous).
- Le troisième champ est le nombre de cycles que l'opcode prend pour s'exécuter (en plus des cycles de fetch). Par exemple, dans mon implémentation, LDA a l'opcode 0001 et prend un total de 4 cycles pour s'exécuter, dont 2 sont le cycle de fetch. Si vous avez suivi les instructions de Ben (où tous les opcodes utilisent 5 cycles), cela devrait toujours être 3.
- Le dernier champ indique si cet opcode nécessite un argument. Par exemple, LDA nécessite un argument mais OUT non.
Vous devrez ajuster cette liste pour refléter les opcodes que vous avez implémentés pour votre ordinateur sur plaque d'essai. Les deux dernières lignes sont des opcodes spéciaux utilisés par l'assembleur et doivent être laissées telles quelles.
Après avoir entré tous vos opcodes, téléversez le logiciel sur l'Arduino. Connectez votre terminal et passez en mode Moniteur (soit en appuyant sur ÉCHAP dans la fenêtre du terminal, soit en mettant tous les commutateurs DIP à ON). Vous devriez maintenant pouvoir désassembler votre programme. Taper simplement « d » dans le moniteur lancera le désassemblage à l'adresse 0.
L'assembleur est minimal mais fonctionne assez bien. Tapez « a » pour commencer l'assemblage à l'adresse 0. Les conventions sont :
- Si une ligne ne commence pas par un espace, elle doit commencer par une définition d'étiquette. Les étiquettes doivent commencer par un caractère alphabétique suivi de caractères alphanumériques, faire au maximum 3 caractères et sont sensibles à la casse.
- Après le premier espace dans une ligne, l'assembleur attend un mnémonique (LDA, STA, OUT...).
- Le mnémonique spécial « .BY » spécifie directement un octet de données à stocker à l'emplacement courant.
- Le mnémonique spécial « .OR » dit à l'assembleur de continuer l'assemblage à une nouvelle adresse.
- Si un argument commence par un caractère alphabétique, il est considéré comme une étiquette.
- Tout argument numérique est attendu en décimal. Pour spécifier en hexadécimal, précédez l'argument d'un « $ ». Par exemple, pour charger le nombre hexadécimal FF dans le registre A, utilisez « LDA #$FF ».
- Tout ce qui suit un « ; » est considéré comme un commentaire et ignoré.
Par exemple, le code de Fibonacci peut être entré comme suit :
rst LDA #0 ; x = 0
STA x
LDA #1 ; y = 1
STA y
lp ADD x ; z = y + x
STA z
JC rst ; redémarrer si débordement
OUT ; afficher z
LDA y ; x = y
STA x
LDA z ; y = z
STA y
JMP lp ; boucle
x .BY 0
y .BY 0
z .BY 0
Quelles sont les limitations ?
Pour économiser la RAM sur l'Arduino, l'assembleur fonctionne comme un assembleur à 1 passe (sinon l'Arduino devrait mettre en tampon tout le code source). L'assembleur écrit les opcodes dans la mémoire de l'ordinateur sur plaque d'essai au fur et à mesure de leur saisie. Cela signifie que l'assemblage est ralenti par la vitesse d'horloge de l'ordinateur sur plaque d'essai. Si vous copiez-collez du texte dans la fenêtre du terminal, cela peut entraîner une perte de caractères car l'Arduino ne peut pas suivre les caractères arrivant à 9600 bauds (car il passe trop de temps à attendre l'horloge de l'ordinateur sur plaque d'essai). Pour contourner cela, soit réduisez le débit en bauds, soit utilisez TeraTerm qui offre un réglage pour spécifier un délai entre les caractères envoyés. L'autre solution est d'augmenter la vitesse d'horloge sur l'ordinateur sur plaque d'essai. Mon horloge monte jusqu'à 160 kHz et à cette vitesse, je peux copier-coller du code à 9600 bauds sans problème.
Dans sa configuration par défaut, le sketch Arduino peut gérer des fréquences d'horloge sur l'ordinateur sur plaque d'essai jusqu'à environ 1-2 kHz (peut-être un peu plus). Notez que l'horloge de Ben dans sa configuration par défaut ne dépasse pas 500 Hz. Si votre horloge est plus rapide, cherchez l'interrupteur #ifdef FAST_IO dans le code. Activer FAST_IO devrait permettre à l'Arduino de fonctionner avec des vitesses d'horloge jusqu'à 250 kHz. Je l'ai testé jusqu'à 160 kHz. Il serait probablement possible de supporter des vitesses plus élevées en implémentant les boucles critiques en assembleur directement, mais honnêtement, une vitesse d'horloge de 160 kHz semble déjà trop rapide sur l'ordinateur sur plaque d'essai avec ses capacités par ailleurs limitées. Assurez-vous de lire les commentaires correspondants dans le code avant d'activer FAST_IO.
L'Arduino dispose de 1 ko d'EEPROM et peut donc contenir 1024/16 = 64 programmes différents. En réalité, c'est 63 car 16 octets sont réservés pour sauvegarder les données de configuration. Ce n'est pas beaucoup, mais probablement suffisant pour tous les programmes que vous pouvez imaginer. Seuls les programmes numéros 0-15 peuvent être sélectionnés via les commutateurs DIP (1-14 pour le démarrage automatique), mais les commandes « s » et « l » fonctionneront avec la plage complète 0-62.
Ça a l'air un peu en désordre — pouvez-vous ranger ?
Oui ! Dans ma version finale ici, j'ai en fait utilisé uniquement la puce Atmega 328P nue (avec un quartz 16 MHz et des condensateurs) au lieu d'un Arduino UNO. Le régulateur de tension sur l'UNO n'est pas nécessaire ici puisque l'Arduino utilise directement le 5V de notre alimentation. La seule perte est qu'il faut maintenant utiliser un convertisseur USB-série séparé (FTDI ou similaire) pour communiquer avec l'Atmega. L'aspect global s'intègre beaucoup mieux avec le reste de l'ordinateur sur plaque d'essai :
Une autre optimisation consiste à retirer le bootloader de l'Arduino/Atmega. Cela élimine le délai de 2 secondes pendant le démarrage du bootloader. Je posterai des instructions pour le faire si les gens sont intéressés. Dites-le-moi dans les commentaires !
Code
Programmeur pour ordinateur sur plaque d'essai
Le sketch Arduino pour le programmeur d'ordinateur sur plaque d'essai : https://github.com/dhansel/programmer
Schémas
drive_pD8k28E85v.fzz
Processus de fabrication
- L'Évolution de la Souris d'Ordinateur : Design, Fabrication et Tendances Futures
- Créer des effets audio pilotés par capteurs avec le kit Satellite CCRMA
- Construire un Spybot Arduino piloté par Bluetooth
- FlickMote : Télécommande IR à gestes pour maison connectée
- TV-B-Gone DIY Arduino : Télécommande universelle pour toutes les TV
- Construire une horloge maître LED personnalisée avec alarme – Visible à 12 mètres
- Find Me : Localisateur d'objets intelligent avec Arduino et Bluetooth
- Solutions d'alimentation optimisées pour projets Arduino
- Morpion sur matrice LED MAX7219 avec boîtier en carton
