Programador EEPROM amb Arduino
Contingut
Introducció
Per fer el projecte Home-Built_Z-80_Computer tinc una EEPROM de 16K que he de programar amb el programa que hi vull ficar a dins. Per tant, necessito un programador de EEPROMs. N'hi ha de comercials, però també es pot fer amb Arduino. Es tracta de fer un programaor paral.lel, doncs aquestes memòries exposen tots els pins per direccionar les adreces (11 pins en el cas de 16Kb=2KBx8, 13 pins en el cas de 64Kb=8KBx8), i els 8 bits de les dades.
Intentaré reproduir aquest projecte:
- http://z80dave.blogspot.com.es/2011/09/20-building-eeprom-programmer.html
- http://z80dave.blogspot.com.es/2011/09/21-building-eeprom-programmer-part-2.html
- http://z80dave.blogspot.com.es/2011/09/22-building-eeprom-programmer-part-3.html
Altres enllaços interessants:
- http://danceswithferrets.org/geekblog/?p=496
- http://forum.arduino.cc/index.php?topic=129567.0
- https://www.nycresistor.com/2012/07/07/stick-a-straw-in-its-brain-and-suck-how-to-read-a-rom/
Desenvolupament
NOTA: per fer l'esquema m'he instal.lat Fritzing, tot i que hi ha coses que no he vist com funcionen.
L'article de referència grava una EEPROM 28C64, i jo en el projecte del Z80 he de gravar una 28C16. Resulta que són quasi compatibles, però no del tot. Puc fer un gravador que sigui compatible per tots dos, o anar directe al gra i que només sigui compatible pel 28C16, que és el que tinc (de fet, els 28C64 són més fàcils de trobar i més baratos a eBay).
Aniré al gra. Per tant, la imatge del projecte original no és correcta, i l'he de refer per tal que reflexi el petit canvi que hi ha (a part de tenir dues línies menys d'adreces).
Petita aclaració: La EEPROM 28C16 són 16Kb (kilo bits) de memòria, que són 2KB (kilo bytes). Com que el bus de dades és de 8 bits, queda clar que hi ha 2K posicions de memòria. Això és direcciona amb 11 pins: A0...A10.
ERROR. Compte perquè l'esquemàtic original està malament. Sorry Dave, your schematic seems wrong. Please check pinout of LS04 chip... Yes you are correct, green wires should be pins 1, 3 and 5 and purple wires 2, 4 and 6. Well done for finding that but I'm sorry but there is no prize for "Spot the deliberate mistake." :D. Això ja ho he corregit en l'esquemàtic que ja he fet per al 28C16.
NOTA. He adaptat el codi original per al meu 28C16. El que veia era que començava a gravar no en la direcció 0, sinó en la direcció 1. No se si amb el 28C64 el codi original funciona, però amb el 28C16 no funciona. Cal llegir atentament el datasheet del 28C16, com és l'operació d'escriptura i de lectura.
READ: The AT28C16 is accessed like a Static RAM. When CE(-) and OE(-) are low and WE(-) is high, the data stored at the memory location determined by the address pins is asserted on the outputs. The outputs are put in a high impedance state whenever CE(-) or OE(-) is high. This dual line control gives designers increased flexibility in preventing bus contention. BYTE WRITE: Writing data into the AT28C16 is similar to writing into a Static RAM. A low pulse on the WE(-) or CE(-) input with OE(-) high and CE(-) or WE(-) low (respectively) initiates a byte write. The address location is latched on the last falling edge of WE(-) (or CE(-)); the new data is latched on the first rising edge. Internally, the device performs a self-clear before write. Once a byte write has been started, it will automatically time itself to completion. Once a programming operation has been initiated and for the duration of tWC, a read operation will effectively be a polling operation.
Per aclarir, també s'adjunta un codi mínim per escriure i llegir directament en una posició concreta de la memòria, concretament la posició 0 (o la que es vulgui)
EEPROM writer
Com es comenta en l'article, hi ha un problema amb el buffer a l'hora d'enviar les dades. Parteixo d'un fitxer rom/bin a l'ordinador que he generat amb l'ensamblador, i que representa el programa que vull gravar a la EEPROM i que són les instruccions que executarà el Z80 (en el futur).
Aquests bytes els he d'enviar al Arduino Mega, i el programa que hi ha a l'Arduino Mega ha de ser capaç de llegir els bytes, i gravar-los a la EEPROM. El que s'explica ara és que el procés d'enviar els bytes a l'arduino s'ha de fer en blocs de 64 bytes, i fent petites esperes per assegurar-nos de què no hi ha problemes. Això ho fem amb aquest bash script.
script eeprom_send.sh:
#!/bin/bash split -C 64 ${1} ${1}.part. for part in `ls ${1}.part.*` do cat ${part} >> ${2} sleep 1 done rm -f ${1}.part.*
To use it type:
$ ./eeprom_send.sh zx81.rom /dev/ttyACM0
But first you just need to program the Arduino and then open the serial monitor window (dont ask me why).
Here is the Arduino program: (l'adapto per la 28LC16):
script EEPROM_28C16_writer.ino:
// http://z80dave.blogspot.com.es/2011/09/21-building-eeprom-programmer-part-2.html /* EEPROM Programmer */ #define memsize 2048 int STS = 13; // Status Indicator int AP[11] = {22,24,26,28,30,32,34,36,38,40,42}; int AD[11] = {0,0,0,0,0,0,0,0,0,0,0}; int DP[8] = {23,25,27,29,31,33,35,37}; int DD[8] = {0,0,0,0,0,0,0,0}; int CE = 4; int OE = 3; int WE = 2; int i; int A; int D; int wait; void setup() { Serial.begin(115200); Serial.flush(); pinMode(STS, OUTPUT); // Setup Address Pins for (i=0;i<11;i++) { pinMode(AP[i],OUTPUT); } // Setup Data Pins for (i=0;i<8;i++) { pinMode(DP[i],OUTPUT); } // Setup Control Pins pinMode(CE, OUTPUT); pinMode(WE, OUTPUT); pinMode(OE, OUTPUT); // Setup Chip digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, HIGH); //digitalWrite(OE, LOW); Serial.println("Waiting for Data..."); while(Serial.available()==0) { digitalWrite(STS,LOW); delay(100); digitalWrite(STS,HIGH); delay(100); } for (A=0;A<memsize;A++) { D=Serial.read(); Serial.println(D); digitalWrite(STS,HIGH); //Signal that we're writing. // Setup Address Pins for (i=0;i<11;i++) { if((A&bit(i))>0) { AD[i]=HIGH; } else { AD[i]=LOW; } digitalWrite(AP[i],AD[i]); } delay(1); // Setup Chip digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, HIGH); // Setup Data Pins for (i=0;i<8;i++) { if((D&bit(i))>0) { DD[i]=HIGH; } else { DD[i]=LOW; } digitalWrite(DP[i],DD[i]); } delay(1); digitalWrite(OE, LOW); delay(1); digitalWrite(WE,HIGH); delay(1); digitalWrite(CE,HIGH); delay(1); digitalWrite(STS,LOW); // Signal that we're waiting wait=0; while(Serial.available()==0) { delay(1); if (wait>2000) { A=memsize; break; } wait++; } } } void loop() { digitalWrite(STS, HIGH); // set the LED on delay(1000); // wait for a second digitalWrite(STS, LOW); // set the LED off delay(1000); // wait for a second }
EEPROM reader
script EEPROM_28C16_reader.ino:
//http://z80dave.blogspot.com.es/2011/09/20-building-eeprom-programmer.html /* EEPROM Chip Reader */ #define memsize 2048 int STS = 13; // Status Indicator int AP[11] = {22,24,26,28,30,32,34,36,38,40,42}; int AD[11] = {0,0,0,0,0,0,0,0,0,0,0}; int DP[8] = {23,25,27,29,31,33,35,37}; int DD[8] = {0,0,0,0,0,0,0,0}; int CE = 4; int OE = 3; int WE = 2; int i; int j; int D; int A; void setup() { // Setup Control Pins pinMode(CE, OUTPUT); pinMode(WE, OUTPUT); pinMode(OE, OUTPUT); // Disable Chip, and disable read and write. digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, LOW); Serial.begin(115200); Serial.println("Reading EEPROM..."); pinMode(STS, OUTPUT); digitalWrite(STS,HIGH); // Setup Address Pins for (i=0;i<11;i++) { pinMode(AP[i],OUTPUT); } // Setup Data Pins for (i=0;i<8;i++) { pinMode(DP[i],INPUT); } delay(1000); for (A=0;A<memsize;) { if (A<4096) Serial.print("0"); if (A<256) Serial.print("0"); if (A<16) Serial.print("0"); Serial.print(A,HEX); Serial.print(" "); for (j=0;j<11;j++) { // Setup Address Pins for (i=0;i<11;i++) { if((A&bit(i))>0) { AD[i]=HIGH; } else { AD[i]=LOW; } digitalWrite(AP[i],AD[i]); } digitalWrite(CE,HIGH); // Chip Enabled digitalWrite(OE,HIGH); // Read Enabled // Read Data Pins D=0; for (i=0;i<8;i++) { DD[i]=digitalRead(DP[i]); D=D+bit(i)*DD[i]; } digitalWrite(OE,LOW); // Read Disabled digitalWrite(CE,LOW); // Chip Disabled if (D<16) Serial.print("0"); Serial.print(D,HEX); Serial.print(" "); //Serial.println(D); A++; } Serial.println(); } } void loop() { digitalWrite(STS, HIGH); // set the LED on delay(500); // wait for a second digitalWrite(STS, LOW); // set the LED off delay(500); // wait for a second }
EEPROM clear
Es tracta de posar a 0 tots els bytes.
script EEPROM_28C16_clear.ino:
// http://z80dave.blogspot.com.es/2011/09/21-building-eeprom-programmer-part-2.html /* EEPROM Programmer */ #define memsize 2048 int STS = 13; // Status Indicator int AP[11] = {22,24,26,28,30,32,34,36,38,40,42}; int AD[11] = {0,0,0,0,0,0,0,0,0,0,0}; int DP[8] = {23,25,27,29,31,33,35,37}; int DD[8] = {0,0,0,0,0,0,0,0}; int CE = 4; int OE = 3; int WE = 2; int i; int A; int D; int wait; void setup() { Serial.begin(115200); Serial.flush(); pinMode(STS, OUTPUT); // Setup Address Pins for (i=0;i<11;i++) { pinMode(AP[i],OUTPUT); } // Setup Data Pins for (i=0;i<8;i++) { pinMode(DP[i],OUTPUT); } // Setup Control Pins pinMode(CE, OUTPUT); pinMode(WE, OUTPUT); pinMode(OE, OUTPUT); // Setup Chip digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, HIGH); for (A=0;A<memsize;A++) { D=0; //clear memor Serial.println(D); digitalWrite(STS,HIGH); //Signal that we're writing. // Setup Address Pins for (i=0;i<11;i++) { if((A&bit(i))>0) { AD[i]=HIGH; } else { AD[i]=LOW; } digitalWrite(AP[i],AD[i]); } delay(1); // Setup Chip digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, HIGH); // Setup Data Pins for (i=0;i<8;i++) { if((D&bit(i))>0) { DD[i]=HIGH; } else { DD[i]=LOW; } digitalWrite(DP[i],DD[i]); } delay(1); digitalWrite(OE, LOW); delay(1); digitalWrite(WE,HIGH); delay(1); digitalWrite(CE,HIGH); delay(1); } Serial.println("clear memory finished"); } void loop() { digitalWrite(STS, HIGH); // set the LED on delay(1000); // wait for a second digitalWrite(STS, LOW); // set the LED off delay(1000); // wait for a second }
Reading EEPROM... 0000 00 00 00 00 00 00 00 00 00 00 00 000B 00 00 00 00 00 00 00 00 00 00 00 ... 07F3 00 00 00 00 00 00 00 00 00 00 00 07FE 00 00 00 00 00 00 00 00 00 00 00
Efectivament la última posició de memòria és la 07FF (i s'acaba la línia). Són 0800 posicions de memòria = 8*16^2 = 2048.
Checksum. Comprovació
Per veure el contingut d'un fitxer d'una forma amigable: (el * vol dir que es repeteix la línia)
$ od -Ax -t x1 la_vaca_cega.txt
000000 4c 61 20 56 61 63 61 20 43 65 67 61 0a 2d 2d 2d 000010 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d * 000030 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 0a 54 000040 6f 70 61 6e 74 20 64 65 20 63 61 70 20 65 6e 20 000050 75 6e 61 20 69 20 61 6c 74 72 61 20 73 6f 63 61 000060 2c 0a 61 76 61 6e c3 a7 61 6e 74 20 64 27 65 73 ... 0003a0 6e 74 20 6c 6c c3 a0 6e 67 75 69 64 61 6d 65 6e 0003b0 74 20 6c 61 20 6c 6c 61 72 67 61 20 63 75 61 2e 0003c0 0a 0003c1
Gravo el fitxer la_vaca_cega.txt a la ROM, i comprovaré que està bé, fent un checksum bàsic, que consisteix en sumar el valor de tots els bytes.
#include <stdio.h> main() { int c,sum; sum=0; while ((c=getchar()) != EOF) { sum=sum+c; } printf("Checksum: %d\n",sum); }
$ gcc -o checksum checksum.c ./checksum < la_vaca_cega.txt Checksum: 84646
Ara faig la lectura de la rom, i modifico el codi per tal que en el monitor sèrie sortin el contingut de les adreces en decimal, una sota de l'altra. Comento unes quantes línies
... for (A=0;A<memsize;) { //if (A<4096) Serial.print("0"); // //if (A<256) Serial.print("0"); // //if (A<16) Serial.print("0"); // //Serial.print(A,HEX); // //Serial.print(" "); // for (j=0;j<11;j++) { // Setup Address Pins for (i=0;i<11;i++) { if((A&bit(i))>0) { AD[i]=HIGH; } else { AD[i]=LOW; } digitalWrite(AP[i],AD[i]); } digitalWrite(CE,HIGH); // Chip Enabled digitalWrite(OE,HIGH); // Read Enabled // Read Data Pins D=0; for (i=0;i<8;i++) { DD[i]=digitalRead(DP[i]); D=D+bit(i)*DD[i]; } digitalWrite(OE,LOW); // Read Disabled digitalWrite(CE,LOW); // Chip Disabled //if (D<16) Serial.print("0"); // //Serial.print(D,HEX); // //Serial.print(" "); // Serial.println(D); A++; } //Serial.println(); // ...
Copio tot el contingut, el porto a l'Excel, i faig la suma. Obtinc el mateix valor que abans: 84646
... 32 99 117 97 46 10 0 84646
Si els últims bytes que surten a la llista són diferents de 0 no preocupar-se, doncs la memòria s'acaba a 07FF, i els últims bytes queden fora d'aquest valor, ja no són de la memòria.
EEPROM write first address direction
script EEPROM_28C16_writer_firstbyte
// http://z80dave.blogspot.com.es/2011/09/21-building-eeprom-programmer-part-2.html /* EEPROM Programmer. Prova */ int AP[11] = {22,24,26,28,30,32,34,36,38,40,42}; int AD[11] = {0,0,0,0,0,0,0,0,0,0,0}; int DP[8] = {23,25,27,29,31,33,35,37}; int DD[8] = {0,0,0,0,0,0,0,0}; int CE = 4; int OE = 3; int WE = 2; int i; int A; int D; int wait; void setup() { Serial.begin(115200); Serial.flush(); // Setup Address Pins for (i=0;i<11;i++) { pinMode(AP[i],OUTPUT); } // Setup Data Pins for (i=0;i<8;i++) { pinMode(DP[i],OUTPUT); } // Setup Control Pins pinMode(CE, OUTPUT); pinMode(WE, OUTPUT); pinMode(OE, OUTPUT); // Setup Chip digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, LOW); D=103; Serial.println(D); AD[0]=LOW; AD[1]=LOW; AD[2]=LOW; AD[3]=LOW; AD[4]=LOW; AD[5]=LOW; AD[6]=LOW; AD[7]=LOW; AD[8]=LOW; AD[9]=LOW; AD[10]=LOW; digitalWrite(AP[0],AD[0]); digitalWrite(AP[1],AD[1]); digitalWrite(AP[2],AD[2]); digitalWrite(AP[3],AD[3]); digitalWrite(AP[4],AD[4]); digitalWrite(AP[5],AD[5]); digitalWrite(AP[6],AD[6]); digitalWrite(AP[7],AD[7]); digitalWrite(AP[8],AD[8]); digitalWrite(AP[9],AD[9]); digitalWrite(AP[10],AD[10]); delay(1); //digitalWrite(WE,LOW); // Write Enabled //delay(1); //digitalWrite(CE,LOW); // Chip Enable //delay(1); // Setup Data Pins for (i=0;i<8;i++) { if((D&bit(i))>0) { DD[i]=HIGH; } else { DD[i]=LOW; } digitalWrite(DP[i],DD[i]); Serial.print(DD[i]); } digitalWrite(CE,HIGH); delay(1); digitalWrite(WE,HIGH); delay(1); } void loop() { }
EEPROM read first address direction
script EEPROM_28C16_reader_firstbyte
//http://z80dave.blogspot.com.es/2011/09/20-building-eeprom-programmer.html /* EEPROM Chip Reader. Prova, volem llegir el primer byte */ int AP[11] = {22,24,26,28,30,32,34,36,38,40,42}; int AD[11] = {0,0,0,0,0,0,0,0,0,0,0}; int DP[8] = {23,25,27,29,31,33,35,37}; int DD[8] = {0,0,0,0,0,0,0,0}; int CE = 4; int OE = 3; int WE = 2; int i; int j; int D; int A; void setup() { // Setup Control Pins pinMode(CE, OUTPUT); pinMode(WE, OUTPUT); pinMode(OE, OUTPUT); // Disable Chip, and disable read and write. digitalWrite(CE, LOW); digitalWrite(WE, LOW); digitalWrite(OE, LOW); Serial.begin(115200); Serial.println("Reading EEPROM..."); // Setup Address Pins for (i=0;i<11;i++) { pinMode(AP[i],OUTPUT); } // Setup Data Pins for (i=0;i<8;i++) { pinMode(DP[i],INPUT); } delay(1000); A=0; //primera direccio de la memoria if (A<4096) Serial.print("0"); if (A<256) Serial.print("0"); if (A<16) Serial.print("0"); Serial.print(A,HEX); Serial.print(" "); AD[0]=HIGH; AD[1]=LOW; AD[2]=LOW; AD[3]=LOW; AD[4]=LOW; AD[5]=LOW; AD[6]=LOW; AD[7]=LOW; AD[8]=LOW; AD[9]=LOW; AD[10]=LOW; digitalWrite(AP[0],AD[0]); digitalWrite(AP[1],AD[1]); digitalWrite(AP[2],AD[2]); digitalWrite(AP[3],AD[3]); digitalWrite(AP[4],AD[4]); digitalWrite(AP[5],AD[5]); digitalWrite(AP[6],AD[6]); digitalWrite(AP[7],AD[7]); digitalWrite(AP[8],AD[8]); digitalWrite(AP[9],AD[9]); digitalWrite(AP[10],AD[10]); delay(1); digitalWrite(CE,HIGH); // Chip Enabled delay(1); digitalWrite(OE,HIGH); // Read Enabled delay(1); // Read Data Pins D=0; for (i=0;i<8;i++) { DD[i]=digitalRead(DP[i]); D=D+bit(i)*DD[i]; } digitalWrite(OE,LOW); // Read Disabled digitalWrite(CE,LOW); // Chip Disabled if (D<16) Serial.print("0"); Serial.print(D,HEX); Serial.print(" "); //Serial.println(D); A++; Serial.println(); } void loop() { }
Programar l'exemple per al Z80 mínim
Primer de tot generem el codi màquina a partir del llenguatge ensamblador:
$ cd ~/projectes/Z80/z80asm-1.8 $ ./z80asm -I /headers/ -o examples/exemple2.bin -i examples/exemple2.asm $ od -Ax -t x1 examples/exemple2.bin 000000 3e 01 d3 ff 07 fe 80 28 02 18 f7 d3 ff 0f fe 01 000010 28 f0 18 f7 000014
Si no ho hem fet, seria interessant primer esborrar la EEPROM (EEPROM_28C16_clear.ino).
I ara gravem aquests bytes a la EEPROM. Amb el programador EEPRIM + Arduino Mega, carreguem el EEPROM_28C16_writer.ino. Aquests script escolta els bytes que rep per USB, i amb el script eeprom_send.sh enviem els bytes:
$ cd ~/projectes/Z80 $ ./eeprom_send.sh z80asm-1.8/examples/exemple2.bin /dev/ttyUSB1
Podem comprovar amb el EEPROM_28C16_reader.ino que els bytes s'han gravat de forma correcta:
0000 3E 01 D3 FF 07 FE 80 28 02 18 F7 000B D3 FF 0F FE 01 28 F0 18 F7 00 00
creat per Joan Quintana Compte, juny 2017