Mi. Apr 24th, 2024

Die Programmierung eines Speicherchips war in den 1980er Jahren eines der spannendsten Themen wenn man sich mit dem Bau eines eigenen Computers beschäftigt hat. Heute stehen neben den klassischen RAM Bausteinen auch EEPROMS zur Verfügung, die einfacher zu programmieren sein sollen.

Die Abkürzung EEPROM steht für „Electrically Erasable Programmable Read-Only Memory“. Es handelt sich um einen Halbleiterspeicher, der sich durch Spannungsimpulse beschreiben und löschen lässt. Die auf dem Chip abgelegten Informationen sind nicht-flüchtig und bleiben ohne eine angelegte Versorgungsspannung erhalten.

Man unterscheidet verschiedene Arten von EEPROMS. Die EPROMS (z.B. 28F…, 29C…, 29F…) lassen es zu einzelne Bytes zu lesen und zu schreiben. Gleichzeitig lassen sich diese Bausteine auch komplett oder blockweise elektrisch löschen und einige auch blockweise (wie die AT28C-Serie) programmieren.

Daneben gibt es serielle EEPROMS (z.B. 24C…, 93C…). Seriell bedeutet bei diesen Bausteinen, das die Datenausgabe sowie die Adressangabe Bit für Bit (=seriell) erfolgt. Damit kann zwar nur ein Bit zur Zeit ausgegeben werden und die angesteuerte Adresse muss auch Bit für Bit übertragen werden aber dafür hat es den großen Vorteil das dass serielle EEPROM mit einem kleinen 8 Pin Gehäuse auskommt. Diese Bausteine werden demnach gerne eingesetzt, wenn Platz oder Ansteuerungsleitungen gespart werden sollen und dabei keine großen Datenmengen oder große Geschwindigkeit gefordert sind.

Programmiergerät für parallele EEPROMS

Für das EEPROM Programmiergerät soll ein EEPROM vom Typ 28C64-150 und einer vom Typ 28C256 verwendet werden. Wie man dem Datenblatt entnehmen kann handelt es sich um ein Baustein der 64k bzw. 256kByte Speicherkapazität enthält und eine Zugriffszeit von 150ns erlaubt.
Wenn mann sich die Pin-Belegung eines EEPROMs anschaut erkennt man folgende grundsätzliche Aufteilung die man bei allen Typen dieser Familie findet:

• Spannungsversorgungs Anschlüsse (GND, Vcc) • Adress-Leitungen (A0…Ax)
• Daten-Leitungen (D0-D7)
• Steuer-Leitungen (CE, OE und WE)

Pin Belegung gängiger EEPROM Bausteine
Pin Belegung gängiger EEPROM Bausteine

Die Idee besteht nun darin, einen Arduino Mega mit seinen ausreichenden Schnittstellen zum programmieren von EEPROM Bausteinen zu verwenden. Dabei wurden die Pins so gewählt, dass man über die Port-Manipulations-Mechanismen die Ein- und Ausgabe der Daten steuern kann. Da das größte mir vorliegende EEPROM über 15 Adressleitungen verfügt, habe ich mich entschieden, dafür die Analogen-Pins A0 bis A14 zu verwenden. Hinzu kommt, dass diese Pins über die beiden Ports F und K gesteuert werden können, was die Handhabung sehr erleichtert.

Lesen von EEPROM Daten

Wie das gemacht werden muss ist im Datenblatt mehr oder weniger verständlich beschrieben. Man muss sich die Beschreibung in Ruhe mehrmals durchlesen, dann ergibt sich folgender Ablauf:

  1. Setze ChipEnable CE auf auf LOW, d.h verbinde mit Masse. Da wir ständig mit dem Chip arbeiten kann dies eine statische Verbindung sein.
  2. Setze OutputEnable OE auf LOW, d.h. Daten werden ausgegeben.
  3. Lege die Adresse an, die ausgelesen werden soll
  4. Setze WriteEnable W E auf HIGH , dass heisst Daten können nur gelesen werden.

Damit sind wir in der Lage, Daten aus einem EEPROM auszulesen.

Schreiben von Daten in das EEPROM

Um Daten auszulesen muss CE und OE auf LOW und WE auf High gesetzt werden. Dann muss die entsprechende Adresse des auszulesenden Bereichs angelegt werden und an den 8 Datenbits werden dann die gespeicherten Datenbits ausgegeben.

In den Datenblättern der Chip-Hersteller wird ausführlich auf die einzuhaltenden Zeiten für ein reibungsloses Lesen und Schreiben eingegangen.

Erforderliche Funktionen für das EEPROM Programmiergerät

Bereitstellen einer Adresse an den Arduino-Pins

Man benötigt eine Funktion setAddress deren Aufgabe es ist, eine vorgegebene Speicheradresse als 16bit Adresse über den Arduino MEGA bereitzustellen. Die Herausforderung besteht nun darin, zu einer gegebenen Adresse Adr (die eine 16 Bit Breite aufweist), die einzelnen Bitwerte an die definierten Adressleitungen A_0,.... bis A_{14} überträgt. Dazu muss zuerst eine Aufteilung in die oberen-8 Bit-Werte und die unteren 8-Bit Werte erfolgen:

Mit dem Schiebe-Operator >>8 werden die oberen 8-Bit der Variable  wert um 8 Stellen nach rechts verschoben. Die bereits bestehenden Daten gehen dabei verloren. Somit steht in der wert nun nur noch das obere Byte. Um das untere Byte zu identifizieren, wird eine sogenannte Maskierung durchgeführt. Die UND-Verknüfung mit 0x00 übernimmt diese Aufgabe. Anschliessend hat man die 16-Bit breite Variable wert in zwei 8-Bit breite Daten aufgeteilt.

Diese können nun an die Ports übertragen werden. Dazu geht man wie folgt vor:

  void setAdress(word adresse){
    // Setze 16Bit Adresse über Port-Register F und K
    //        Bit7|Bit6|Bit5|Bit4|Bit3|Bit2|Bit1|Bit0 
    // PortF:  A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 
    // PortK:  A15| A14| A13| A12| A11| A10| A9 | A8
    
    // setze Port-Bits auf Ausgang
       DDRF = B11111111;
       DDRK = B11111111;
    
    // Bestimmung Low- und High-Bytes
       byte high_byte = adresse >>8;  
       byte low_byte  = adresse & 0x00ff;
    
    // Übertragung der beiden Bytes auf die PortRegister
       PORTF = low_byte;
       PORTK = high_byte;
    }// end setAdress

Der zentrale Teil der Übertragung der einzelnen Bits an die Ausgabe-Pins kann durch eine einfache Und-Verknüpfung mit den entsprechenden Port-Registern erfolgen.

EEPROM Daten einlesen

Nach dem Setzen des Output_Enable-Bits auf Low (= Daten Lesen) und dem Setzen des Port-registers auf Eingang können die an den Ausgangs-Pins anstehenden EEPROM-Daten einfach in die Variable data eingelesen werden. Die entsprechende Funktion könnte so umgesetzt werden:

    byte readEEPROM(int address) {
      // setze Output_Enable auf LOW = ermöglicht das Lesen von Daten
      digitalWrite(Output_EN, HIGH);
      delayMicroseconds(1);
      digitalWrite(Output_EN, LOW);
      
      // Setze Portregister C auf Input
      DDRC = B00000000;
      
      setAdress(address);
      // Lese Daten
      byte data = 0;
      data=PINC;
      return data;
    }//end readEEPROM

Formatierte Ausgabe im Monitor-Stil

 Aus Remineszenz an die frühen Tage der Computertechnik wo es nur sehr eingeschränkte Ein- und Ausgabe-Möglichkeiten gab, stellten die damaligen Consolen die Inhalte in Form von Hexadezimalen Datenblöcken dar. Es beginnt immer mit der Startadresse gefolgt von 8 aufeinanderfolgenden Daten-Werten ohne explizite Adress-Angabe. Dann folgt ein Zeilenumbruch und es wird die um 8 Stellen erhöhte Adresse-Angabe wieder gefolgt von 8 einzelnen Datenblöcken, zu denen man sich die zugehörige Speicheradresse leicht durch abzählen ermitteln kann.

Wie gesagt, die Idee stammt aus der Vergangenheit aber ist bis heute immer noch ein gutes Instrument in der maschinennahen Programmierung, daher habe ich so eine Funktion zur Datenansicht geschrieben.

void Monitor (int Startadresse, word Endadresse) {
// Ausgabe der Speicherstellen von Startadresse bis Endadresse
Serial.println();
Serial.println("==============================");
Serial.println("  **  Monitor-Programm  **");
Serial.println("==============================");
for (int adr=Startadresse;adr< Endadresse;adr++){
   randomData = random(255); // Lese Soeicherstelle muss ersetzt werden
   
   if ((adr == 0)||(adr % 8 == 0)) // zeige 8 Stellen in einer Reiche
       Serial.print(Int2Hex(adr,4)+": ");
   Serial.print(Int2Hex(randomData,2)+" ");
   if ((adr+1)%8 == 0) // stelle fest ob Zeilenumbruch erforderlich
      Serial.println();
  }// end for adr
}// end Monitor

String Int2Hex(int wert, int stellen){
  String temp = String(wert,HEX);
  String prae = "";
  int len = temp.length();
  int diff = stellen-len;
  for (int i=0;i< diff;i++)
      prae = prae +"0";
  return prae+temp;
}// end Int2Hex
Beispiel eines sogenannten Hex Dumps

Beispiel EPPROM Programmer Prototyp

Der erste Entwurf für ein komplettes Programm mit dem man EEPROMs lesen und schreiben können kann ist im folgenden dargestellt. Es basiert auf den bereits vorgestellten Teil-Programmen und ist erweitert um die Enable Funktionen für die Output- und die Write-Funktion. Dazu kommt ein Arduino Mega zum Einsatz, da er über genügend IO-Ports verfügt und man so ohne Shift-Register auskommt.

   //============================================
    // EEPROM Programme 
    // Monitor: Anzeige von Speichstellen
    // SetAdress SetData setzen von Adressen und Daten
    // PHOF Nov 2020
    //============================================
    //   ----    Arduino MEGA Portbelegung    ----
    //        Bit7| Bit6| Bit5| Bit4| Bit3| Bit2| Bit1| Bit0 
    //PortA:  P29 | P28 | P27 | P26 | P25 | P24 | P23 | P22 
    //PortL:  P42 | P43 | P44 | P45 | P46 | P47 | P48 | P49
    //==============================================================
    
    #define WRITE_EN 12
    #define Output_EN 10
     
    // 4-bit hex decoder for common cathode 7-segment display
    byte data[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0xff, 0x6f, 0x77, 0x7c, 0x58, 0x5e, 0x79, 0x71 };
    
    void setup() {
      DDRA = B11111111; // alle Ports output
      DDRC = B11111111;
      DDRL = B11111111;
      DDRF = B11111111;
      DDRK = B11111111;
      Serial.begin(9600);
      digitalWrite(WRITE_EN, HIGH); pinMode(WRITE_EN, OUTPUT);
      digitalWrite(Output_EN, HIGH);pinMode(Output_EN, OUTPUT);

     // Test Prozedur 
      Serial.print("Programming EEPROM");
      for (int address = 0; address < sizeof(data); address += 1) {
        writeEEPROM(address, data[address]);
         Serial.println();Serial.print("Schreibe Daten Byte ... Adresse: ");
         Serial.print(address,HEX); Serial.print("  : "); Serial.print(data[address],BIN);Serial.print(" ... ");
         Serial.println(data[address],HEX);
        if (address % 64 == 0) {
             Serial.print(".");
           }
        delay(500);
      }
      Serial.println(" done");  
   }// end setup
     
    
   void Monitor (int Startadresse, word Endadresse) {
    // Ausgabe der Speicherstellen von Startadresse bis Endadresse
      Serial.println();
      Serial.println("==============================");
      Serial.println("  **  Monitor-Programm  **");
      Serial.println("==============================");
    
      for (int adr=Startadresse;adr< Endadresse;adr++){
  //       randomData = random(255); // Lese Speicherstelle muss ersetzt werden
         
         if ((adr == 0)||(adr % 8 == 0)) // zeige 8 Stellen in einer Reiche
             Serial.print(Int2Hex(adr,4)+": ");
      
         Serial.print(Int2Hex(randomData,2)+" ");
      
         if ((adr+1)%8 == 0) // stelle fest ob Zeilenumbruch erforderlich
            Serial.println();
        }// ennd for adr
    
    }// end Monitor
    
  String Int2Hex(int wert, int stellen){
      String temp = String(wert,HEX);
      String prae = "";
      int len = temp.length();
      int diff = stellen-len;
      for (int i=0;i< diff;i++)
        prae = prae +"0";
      return prae+temp;
  }// end Int2Hex
    
 void setAdress(word adresse){
    // Setze 16Bit Adresse über Port-Register F und K
    //        Bit7|Bit6|Bit5|Bit4|Bit3|Bit2|Bit1|Bit0 
    // PortF:  A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 
    // PortK:  A15| A14| A13| A12| A11| A10| A9 | A8
    
    // setze Port-Bits auf Ausgang
       DDRF = B11111111;
       DDRK = B11111111;
    
    // Bestimmung Low- und High-Bytes
       byte high_byte = adresse >>8; // oder (adresse & 0xff00) >> 8
       byte low_byte  = adresse & 0x00ff;
    
    // Übertragung der beiden Bytes auf die PortRegister
       PORTF = low_byte;
       PORTK = high_byte;
    }// end setAdress
   
    
    byte readEEPROM(int address) {
      // setze Output_Enable auf LOW = ermöglicht das Lesen von Daten
      digitalWrite(Output_EN, HIGH);
      delayMicroseconds(1);
      digitalWrite(Output_EN, LOW);
      
      // Setze Portregister C auf Input
      DDRC = B00000000;
      
      setAdress(address);
      // Lese Daten
      byte data = 0;
      data=PINC;
      return data;
    }//end readEEPROM

    void writeEEPROM(int address, byte data) {
      // setze Output_Enable auf HIGH = ermöglicht das Schreiben von Daten
      digitalWrite(Output_EN, LOW);
      delayMicroseconds(1);
      digitalWrite(Output_EN, HIGH);
      setAdress(address);
  
      // PortC:  P30 | P31 | P32 | P33 | P34 | P35 | P36 | P37  
       DDRC = B11111111; // setze Port-Bits C auf Ausgang
       PORTC = data; // damit stehen die Daten an den Pins von Port C
     
      // set write Enable um die Daten ins EEPROM zu schrieben
      digitalWrite(WRITE_EN, LOW);
      delayMicroseconds(1);
      digitalWrite(WRITE_EN, HIGH);
      delay(10);
    }//end writeEEPROM
    
   void loop() { }

Dabei handelt es sich um einen ersten Prototypen, realisiert auf einem Steckbrett. Man kann hier nach belieben Erweiterungen wie beispielsweise LED Anzeigen dazu bauen. Oder andere Arduino-PINs verwenden. Wenn dann alles so funktioniert wie ausgedacht, steht einer Version mit einer speziell angefertigten Platine und einem Schnellpannsockel für das unproblematische Einsetzen und Herausnehmen der EEPROMs nichts mehr im Weg.

EEPROM Programmer Prototyp Aufbau