Projet informatique Z80 – Programmation de la mémoire EEPROM

Projet informatique Z80 - Programmation de la mémoire EEPROM

Plus tôt, nous avons appris comment ajouter de la mémoire à notre système Z80 et comment le décodeur de mémoire sépare la ROM de la RAM. Mais maintenant, nous devons apprendre comment installer des programmes sur notre ordinateur en construisant un programmeur EEPROM!

Récapitulatif de la mémoire

Dans l’article précédent du projet, nous avons appris le fonctionnement de la mémoire et son rôle dans notre projet informatique Z80. Avant de nous lancer dans la construction physique de l’ordinateur, ainsi que dans la construction d’un programmeur de mémoire, nous allons d’abord passer rapidement en revue les détails clés des articles précédents pour rafraîchir notre mémoire (jeu de mots).

La mémoire est la zone qui stocke les informations que notre ordinateur peut utiliser en cas de besoin. La mémoire peut être utilisée pour contenir des instructions ainsi que des données comprenant des variables, des images, des identifiants, etc. La mémoire elle-même se présente souvent sous deux formes principales; Mémoire en lecture seule (ROM) et mémoire à accès aléatoire (RAM). La ROM est généralement utilisée pour contenir des programmes (tels que le système d’entrée-sortie de base ou BIOS), tandis que la RAM est utilisée pour contenir des applications, des données et des variables. Dans notre ordinateur Z80, la ROM contiendra le programme, tandis que la RAM contiendra des variables pour le programme telles que les compteurs, les valeurs de minuterie et les résultats des équations.

Dans ce projet, nous devrons écrire des programmes pour le Z80 qui seront stockés dans la ROM, mais nous devons apprendre comment intégrer ces programmes dans la puce ROM pour que l’ordinateur les lise. La puce de mémoire spécifique que nous utiliserons dans ce projet est une mémoire EEPROM (lecture seule programmable effaçable électriquement) AT28C256 qui contient 32 Ko de données. Cette puce, une fois programmée, se souviendra des données même lorsque l’alimentation est coupée, c’est pourquoi nous l’utiliserons.

Cycle d’écriture EEPROM

Puisque nous allons apprendre à mettre des données dans la puce EEPROM à partir d’un ordinateur afin de pouvoir programmer notre ordinateur Z80, nous devons d’abord apprendre comment fonctionne le cycle d’écriture pour un périphérique EEPROM. La puce est contrôlée avec trois broches; WR, OE et CS. WR est la broche d’écriture, et elle est utilisée pour enregistrer les données dans la puce. La broche OE est utilisée pour lire les données de la puce et la broche CS est utilisée pour sélectionner la puce mémoire elle-même. Les cycles de lecture et d’écriture nécessitent l’utilisation du CS, sinon la puce ne saura pas qu’elle est utilisée.

Les cycles d’écriture sont très simples et utilisent la logique suivante:

  1. Assurez-vous que CS, WR et OE sont tous un 1 logique (ce sont des broches basses actives)
  2. Définissez les broches d’adresse sur l’adresse mémoire dans laquelle vous souhaitez enregistrer
  3. Définissez les broches de données sur les données que vous souhaitez stocker
  4. Réglez CS sur 0
  5. Réglez WR sur 0. C’est là que les données sont verrouillées
  6. Réglez WR sur 1
  7. Réglez CS sur 1. Vous avez maintenant terminé!

La puce mémoire prend un certain temps pour enregistrer les données (jusqu’à 10 ms), il est donc important de laisser une pause de quelques ms entre chaque étape.

ordinateur z80 - programmation de la mémoire RAM eeprom

http://ww1.microchip.com/downloads/en/DeviceDoc/doc0006.pdf

Programmeur DIY – Matériel

Donc, maintenant que nous savons comment enregistrer des données dans l’EEPROM, il est temps de construire notre programmeur. Malheureusement, nous devons construire un programmeur séparé du Z80 car le CI de mémoire ne peut pas être programmé dans l’ordinateur. Cela signifie que chaque fois que nous voulons changer de programme, nous devons physiquement éloigner le CI de l’ordinateur et le placer dans le programmateur. Pour garder les choses simples, nous aurons le programmeur sur la même maquette que l’ordinateur.

Le programmeur est basé sur un Arduino Uno car il s’agit d’un appareil très facile à créer des prototypes. Cependant, ceux qui sont intelligents se rendront vite compte que nous n’avons que 12 E / S numériques sur l’Uno (0 et 1 sont utilisées pour TX, RX), et 6 entrées analogiques, mais l’EEPROM a 15 entrées d’adresse, 8 E / S numériques, et 3 broches de commande. Cela signifie que nous sommes 26 – 18 = 8 broches courtes. Maintenant, nous pourrions utiliser moins de broches d’adresse sur la puce de mémoire, mais cela ne donnerait qu’un espace de 256 octets pour y mettre les programmes. Afin d’étendre nos E / S, nous allons à la place incorporer deux verrous 74HC374. Chacun de ces verrous peut contenir 8 bits de données, et si tous sont connectés à un bus commun, nous pouvons avoir jusqu’à 24 bits à partir d’une seule sortie 8 bits et un contrôle 3 bits de l’Uno. La raison pour laquelle nous n’avons pas besoin d’un troisième verrou pour le bus de données est que nous pouvons connecter le bus de données du circuit intégré de mémoire directement au bus commun; la puce n’enregistre les données que lorsque la broche WR devient basse!

ordinateur z80 - programmation eeprom mémoire matériel de programmeur bricolage

Programmeur DIY – Logiciel

Le programmeur EEPROM n’est qu’à 30% matériel car c’est le logiciel qui rend ce projet possible. Notre logiciel doit pouvoir exécuter l’Uno pour enregistrer correctement les octets dans l’EEPROM, tout en lisant correctement les données d’un programme qui s’exécute sur notre ordinateur. Pour garder le projet multiplateforme, nous utiliserons Python comme langage de script pour l’application PC.

Lorsque notre PC demande au programmeur de sauvegarder les données via une connexion série, nous devons nous assurer que les données envoyées ne sont pas corrompues. Au lieu d’envoyer des données sous forme d’octets bruts (ce qui est rapide), nous utiliserons plutôt une méthode presque identique à notre série IoT personnalisée qui utilise la messagerie texte. L’avantage de ceci est que les données sont envoyées dans leur format ASCII et sont donc facilement distinguables dans les messages. Cela nous permet également de réserver des caractères d’octet spéciaux pour des commandes telles que la sauvegarde de données, le chargement de données et la réinitialisation des messages. Toutes les données envoyées à partir du PC seront sous forme hexadécimale composé de deux caractères par octet. Par exemple, si nous voulons envoyer l’octet 0xFF, nous envoyons à la place les caractères ASCII FF. Les autres caractères de l’alphabet seront réservés aux commandes spéciales comme indiqué dans le tableau ci-dessous.

Messages PC vers Uno

Personnage Commander
R Réinitialiser le système
S Enregistrer le message d’octet
P Enregistrer le message de la page
g Obtenir un message d’octet
H Définir un octet d’adresse supérieur
L Définir l’octet inférieur d’adresse

Messages Uno vers PC

Personnage Commander
Oui La commande était bonne (Y)
N Commande non comprise (N)

Logiciel Uno

Le logiciel de l’Uno utilise principalement des bibliothèques Arduino standard à l’exception de la bibliothèque de ports 8 bits. L’un des problèmes rencontrés avec l’Arduino Uno est que la communication avec un bus 8 bits est difficile sans utiliser les registres AVR. Cependant, il existe une bibliothèque, appelée Port IO 8 bits, qui nous permet de combiner 8 broches sur l’Arduino pour créer un port auquel on peut communiquer.

La fonction du logiciel sur l’Uno est très simple; attendez une commande via le port série, décodez le message, exécutez la commande, puis renvoyez la réponse appropriée. Alors que nous aurions pu utiliser l’instruction Serial.readline () pour garder les choses simples, cela a abouti à un logiciel très lent. Ainsi, notre logiciel utilise une fonction de lecture personnalisée qui prend des caractères individuels, les met dans une mémoire tampon, puis vérifie un caractère de fin. À partir de là, le tampon est ensuite transféré dans une chaîne, qui est ensuite vérifiée pour les caractères de commande.

Projet informatique z80 - programmation de la mémoire eeprom logiciel arduino unoProjet informatique z80 - programmation arduino uno mémoire RAM eeprom

Fonction de lecture personnalisée (à gauche) et décodage de commande (à droite)

Script Python

Le script Python est incroyablement basique; il charge un fichier appelé ROM.bin dans le même répertoire que le fichier python, réinitialise l’Uno, puis transmet chaque octet à la puce mémoire. Chaque commande envoyée à Uno se termine par un caractère zéro ainsi qu’une nouvelle ligne ( 0 n), pour garantir que l’Uno termine correctement la commande de lecture personnalisée en boucle while.

projet informatique z80 - programmation script python de mémoire eeprom

Langage d’assemblage Z80

La programmation du Z80 nécessite un programme appelé assembleur; ce programme prend les instructions de la CPU et les transforme en octets que la CPU peut réellement lire. Bien qu’il existe de nombreux assembleurs parmi lesquels choisir, mon expérience personnelle est que tnyASM est de loin le meilleur. cependant, ce programme est une application console, ce qui rend son utilisation un peu délicate. Mais n’ayez crainte, car je suis ici, et inclus dans ces fichiers de projet n’est pas seulement l’assembleur, mais un fichier Compile.bat personnalisé qui lancera le compilateur et passera les paramètres corrects pour compiler votre programme. Le programme compilera le fichier dans le répertoire src appelé main.asm, alors assurez-vous que votre code est dans ce fichier! Une fois compilé, votre fichier ROM.bin sera situé dans le répertoire Rom, et c’est ce que vous placez dans le même répertoire que le script python.

Projet informatique z80 - Programmation de la mémoire eeprom RAM Z80 Assembly Language

Malheureusement, décrire le fonctionnement du langage d’assemblage Z80 est beaucoup trop complexe et constitue une série de projets à part entière. Cependant, vous aurez la chance de savoir qu’il existe une tonne de ressources en ligne dont vous pouvez apprendre, notamment des exemples de projets!

Bootloader.py:

import serial
import time

comPort = serial.Serial("COM21", 9600)

time.sleep(3)

# Send RESET command
comPort.write("Rn".encode("ASCII"))
response = comPort.readline().decode("ASCII")
print(response)

# Check to see if device found
try:
    response.index("Y")
    print("Response OK, now sending data")
except:
    print("Bad response")


# Open ROM file
with open("ROM.bin", "rb") as f:

    counter = 0
    byte = b"n"
    
    while (byte != b""):
        # Do stuff with byte.
        byte = f.read(1)           
        line = (byte.hex().upper()).encode("ASCII")
        comPort.write(b"S" + line + b"n")
        response = comPort.readline().decode("ASCII")
        print(response)
        counter += 1
        print(line)
        if(counter > 255):
            print("block done")
            counter = 0
    # Terminate loop
    comPort.write(b"Rn")
response = comPort.readline().decode("ASCII")
print(response)
print("Done")

Programmer.ino:

#include "IO_Port_8bit.h"

IO_Port_8bit port(2, 3, 4, 5, 6, 7, 8, 9, 'I'); //create port object

#define   MEM_LW  A1
#define   MEM_UP  A2
#define   MEM_CS  A3
#define   MEM_RD  A4
#define   MEM_WR  A5

char inBuffer[32];
char bufferPtr;
char dataIn;

int currentAddress;
int tempAddress;
byte tempByte;

String byteToAscii(byte b);
byte asciiToByte(char upper, char lower);
void writeByte(int address, char data);
void saveByte(int address, byte data);
byte readByte(int address);



void setup() {
  
  pinMode(A1, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(A3, OUTPUT);
  pinMode(A4, OUTPUT);
  pinMode(A5, OUTPUT);

  digitalWrite(MEM_LW, LOW);
  digitalWrite(MEM_UP, LOW);
  digitalWrite(MEM_CS, HIGH);
  digitalWrite(MEM_RD, HIGH);
  digitalWrite(MEM_WR, HIGH);

  Serial.begin(9600);

  // Disable write protect system
  saveByte(0x5555, 0xAA);
  saveByte(0x2AAA, 0x55);  
  saveByte(0x5555, 0x80);  
  saveByte(0x5555, 0xAA);  
  saveByte(0x2AAA, 0x55);  
  saveByte(0x5555, 0x20);  

  bufferPtr = 0;
}


void loop() {

  char dataIn = 0x00;
  bufferPtr = 0;

  // Loop to wait for completed data
  while(dataIn != 'n')
  {
    if(Serial.available() > 0)
    {
      dataIn = Serial.read();
      inBuffer[bufferPtr] = dataIn;
      bufferPtr ++;
    
      if(bufferPtr > sizeof(inBuffer))
        bufferPtr = 0;      
    }
  }

  String command = String(inBuffer);
  
  // Save byte message
  if(command.indexOf("S") != -1)
  {
    saveByte(currentAddress, asciiToByte(command.charAt(1), command.charAt(2)));
    currentAddress ++;  
    Serial.println("Y");
  }  
  
  // Reset command
  else if(command.indexOf("R") != -1)
  {
    currentAddress = 0;
    Serial.println("Y");
  }

  // Higher address set command
  else if(command.indexOf("H") != -1)
  {
    tempAddress = asciiToByte(command.charAt(1), command.charAt(2)); 
    currentAddress = currentAddress & 0x00FF;
    currentAddress = currentAddress | ((tempAddress << 8) & 0xFF00);
    Serial.println("Y");
  }  

  // Lower address set command
  else if(command.indexOf("L") != -1)
  {
    tempAddress = asciiToByte(command.charAt(1), command.charAt(2)); 
    currentAddress = currentAddress & 0xFF00;
    currentAddress = currentAddress | ((tempAddress) & 0x00FF);
    Serial.println("Y");
  }  

  // Get byte message
  else if(command.indexOf("G") != -1)
  {
    Serial.println(byteToAscii(readByte(currentAddress)));
    currentAddress ++;
    Serial.println("Y");
  }  
}


byte readByte(int address)
{
  // Set address
  byte lowerAddress = address & 0x00FF;
  byte upperAddress = ((address >> 8)& 0x00FF);
  byte dataIn = 0;
  port.set_IO_direction('O');

  // Latch the address to the two latches
  port.send_8bit_data(lowerAddress);
  delay(1);
  digitalWrite(MEM_LW, HIGH);
  delay(1);
  digitalWrite(MEM_LW, LOW);
  delay(1);
  port.send_8bit_data(upperAddress);
  delay(1);
  digitalWrite(MEM_UP, HIGH);
  delay(1);
  digitalWrite(MEM_UP, LOW);
  delay(1);

  // Read the data from the memory chip
  port.set_IO_direction('I');
  digitalWrite(MEM_WR, HIGH);
  digitalWrite(MEM_CS, LOW);
  digitalWrite(MEM_RD, LOW);
  delay(1);
  dataIn = port.get_8bit_data();
  delay(1);
  digitalWrite(MEM_RD, HIGH);
  digitalWrite(MEM_CS, HIGH);
  delay(1);

  return dataIn;
}


void saveByte(int address, byte data)
{
  // Set address
  byte lowerAddress = address & 0x00FF;
  byte upperAddress = ((address >> 8)& 0x00FF);
  port.set_IO_direction('O');

  // Latch the address to the two latches
  port.send_8bit_data(lowerAddress);
  digitalWrite(MEM_RD, HIGH);
  digitalWrite(MEM_LW, HIGH);
  digitalWrite(MEM_LW, LOW);
  port.send_8bit_data(upperAddress);
  digitalWrite(MEM_UP, HIGH);
  delay(1);
  digitalWrite(MEM_UP, LOW);

  // Write the data to the memory chip
  port.send_8bit_data(data);
  digitalWrite(MEM_CS, LOW);
  digitalWrite(MEM_WR, LOW);
  digitalWrite(MEM_WR, HIGH);
  delay(1);
  digitalWrite(MEM_CS, HIGH);
  port.set_IO_direction('I');
}


String byteToAscii(byte b)
{
  char upperHex = (b & 0xF0) >> 4;
  char lowerHex = b & 0x0F;
  String r = "";
  
  upperHex += 0x30;
  if(upperHex > 0x39)
    upperHex += 0x07;
 
  lowerHex += 0x30;
  if(lowerHex > 0x39)
    lowerHex += 0x07;

  r.concat(upperHex);
  r.concat(lowerHex);
  
  return r;
}

byte asciiToByte(char upper, char lower)
{
  upper = upper - 0x30;
  if(upper > 0x09)
    upper -= 0x07;

  lower = lower - 0x30;
  if(lower > 0x09)
    lower -= 0x07;

  return ((upper << 4 ) & 0xF0) | (lower & 0x0F);   
}

Compile.bat:

z80asm.exe 
SourceMAIN.ASM 
RomDOS.bin

Pause
  • https://www.youtube.com/watch?v=LpQCEwk2U9w
  • https://www.chibiakumas.com/z80/
  • https://riptutorial.com/assembly/example/16904/zilog-z80-registers

Série de projets Z80 – Programmation de la mémoire EEPROM Réflexions finales

Ce programmeur vous permettra de créer vos propres programmes pour la plate-forme Z80, et sera essentiel dans les projets à suivre car nous commencerons à accéder aux E / S. Bien que vous puissiez acheter un programmeur, en créer un vous apprend non seulement sur plusieurs plates-formes, mais vous permet également d’être indépendant de l’équipement commercial qui n’est pas toujours là.