Close

Flash Programmer - Finishing Up

A project log for Z80 Computer

An attempt to build a Z80 computer capable of running CP/M

techavtechav 05/07/2015 at 23:502 Comments

I've finally completed the code for programming my flash ROMs, and successfully tested all functions for the first time. The code is an absolute mess, but I'm posting it here in case anyone else might find it useful. I've broken out the functions specific to working with the flash into a library. This is my first experiment with classes in C++, so forgive me if its implementation is non-standard.

The Arduino sketch posted below will present a prompt and menu for working with the chip. The Read function will read the specified address range and output Intel Hex format. The Write function parses Hex format and programs the chip with it. Extended Linear Address entries are used to select the sectors on the chip (0-7, for 8 64k sectors). Verify checks the lock bits for the specified sector, and scans through the sector looking for any bytes that are programmed (not reading $FF).

I did run into a few problems while testing the programmer. The first was I noticed that between reads of the same area on the flash chip I was testing with, the value returned was inconsistent. Sometimes it would be just a single bit different on one read out of three ($FF $7F $FF), but sometimes it would be three very different reads ($7F $1F $60). My first thought was perhaps I needed decoupling capacitors, so I broke out the soldering iron. No change. Maybe I'm not accounting for setup time, so I added a delay, and read the byte three times in a row before bringing OE back up. No change. Finally, I threw together a quick sketch that would scan through the entire address range of the ROM and read each byte sixteen times, and report any that were inconsistent.

Clearly, this chip has a bad sector. I pulled these chips out of a wall-mount touch panel that is at least 15 years old, so a bad sector is not surprising. Of the four, two have bad sectors.

The other big problem I ran into while testing was none of the flash chips wanted to respond to any of the write commands. I pored over the datasheet for hours, checking, rechecking the commands, the timing diagrams, everything I could think of. Nothing seemed out of place, and I know I had it working before I moved from the breadboard. ... Which of course means that must be where it went bad. Sure enough, another quick sketch to step through the address pins one-by-one, and I had swapped A14 and A15 when building the board. Trying to desolder from the cheap import board resulted in completely removing the copper pad as well, but I got them connected the right way in the end. It was pretty exciting to see the byte write command complete successfully, and be able to read back in the data I had written.

Programmer library and Arduino sketch below:

AM29_Flash.h

/* AM29F040 Flash memory library
 * This library was designed for the 512KB AMD AM29F040 chip
 * to be programmed with an Arduino Mega.
 *
 * Modification will be required to work with anything else
 * I make no guarantees this code will work for you, and provide
 * it here as an example of what worked for me.
 * 
 * To use, first edit this header to fill in the proper ports
 * for the high and low address bytes, and the DQ byte.
 * I used Port A for address low, Port C for address high,
 * and Port K for DQ. The constructor for AM29 will set which
 * Arduino pins are used for A16-A18, and for the control signals.
 * Calling the setup() method will set all pins as input/output
 * as necessary, disable pullups, and set initial values.
 */
#include <stdint.h> 

#ifndef __JA__AM29__FLASH__INCLUDED__
#define __JA__AM29__FLASH__INCLUDED__

#define AM29ADDRLOPORT     PORTA
#define AM29ADDRLODDR    DDRA
#define AM29ADDRHIPORT     PORTC
#define AM29ADDRHIDDR    DDRC
#define AM29IOPORT    PORTK
#define AM29IODDR    DDRK
#define AM29IOPIN    PINK

#define NOP() do { __asm__ __volatile__ ("nop"); } while(0);

#define cSECTORLIMIT 7

class AM29{
private:
    int A16;
    int A17;
    int A18;
    int CE;
    int OE;
    int WE;
    
public:
    AM29(int,int,int,int,int,int);
    
    void setup();
    
    void setAddr(uint32_t addr);
    void setAddr(uint8_t sector, uint16_t addr);
    
    uint8_t readByte(uint32_t addr);
    uint8_t readByte(uint8_t sector, uint16_t addr);
    
    int verifySector(uint8_t sector);
    
    int progressCheck(uint8_t data, uint32_t addr);
    
    void byteWrite(uint8_t data, uint32_t addr);
    
    int programByte(uint8_t data, uint8_t sector, uint16_t addr);
    int programByte(uint8_t data, uint32_t addr);
    
    void autoSelect();
    void readReset();
    
    int eraseSector(uint8_t sector);
    
    int eraseChip();
};

#endif

AM29_Flash.cpp

#include "pins_arduino.h"
#include "wiring_private.h"
#include <avr/pgmspace.h>
#include <stdint.h>
#include "AM29_Flash.h"

AM29::AM29 (int inA16, int inA17, int inA18, int inCE, int inOE, int inWE){
    A16 = inA16;
    A17 = inA17;
    A18 = inA18;
    CE = inCE;
    OE = inOE;
    WE = inWE;
}

void AM29::setup(){
    //call to set up proper port directions and initialize
    AM29ADDRLODDR = 0xFF;    //lo addr output
    AM29ADDRLOPORT = 0;
    AM29ADDRHIDDR = 0xFF;    //hi addr output
    AM29ADDRHIPORT = 0;
    
    //Sector addr outputs
    pinMode(A16, OUTPUT);
    digitalWrite(A16, LOW);
    pinMode(A17, OUTPUT);
    digitalWrite(A17, LOW);
    pinMode(A18, OUTPUT);
    digitalWrite(A18, LOW);
    
    AM29IODDR = 0;        //io input
    AM29IOPORT = 0;        //io disable pullup
    
    //Control signal outputs (Active low)
    pinMode(CE, OUTPUT);
    digitalWrite(CE,HIGH);
    pinMode(OE, OUTPUT);
    digitalWrite(OE,HIGH);
    pinMode(WE, OUTPUT);
    digitalWrite(WE,HIGH);
}

//set the address pins to the given address
void AM29::setAddr(uint32_t addr){
    setAddr( (uint8_t)(addr>>16), (uint16_t)addr);
}
void AM29::setAddr(uint8_t sector, uint16_t addr){
    AM29ADDRLOPORT = (uint8_t)addr;
    AM29ADDRHIPORT = (uint8_t)(addr>>8);
    digitalWrite(A16, (sector & (1<<0)));
    digitalWrite(A17, (sector & (1<<1)));
    digitalWrite(A18, (sector & (1<<2)));
}


//read in uint8_t at given address
uint8_t AM29::readByte(uint32_t addr){
    return(readByte( (uint8_t)(addr>>16), (uint16_t)addr));
}
uint8_t AM29::readByte(uint8_t sector, uint16_t addr){
    boolean errorFound = false;
    digitalWrite(CE, LOW);
    setAddr(sector, addr);
    digitalWrite(OE, LOW);
    NOP();
    NOP();
    uint8_t b = AM29IOPIN;
    digitalWrite(OE, HIGH);
    digitalWrite(CE, HIGH);
    setAddr(0);
    
    return(b);
}

int AM29::verifySector(uint8_t sector){
    //return 0 for sector erased
    //return -1 for error
    //return 1 for sector programmed
    //return 2 for sector erased but locked
    //return 3 for sector programmed and locked
    int retval = 0;
    bool lock = false;
    if(sector > cSECTORLIMIT) return(-1);
    
    uint32_t startA = sector;
    startA = startA << 16;
    uint32_t endA = startA;
    endA |= 0xFFFF;
    
    //check sector lock
    autoSelect();
    uint8_t b = readByte(startA + 2);
    readReset();
    if(b&1) lock = true;
    
    //check sector data
    do{
        if(readByte(endA) != 0xFF){
            retval = 1;
            break;
        }
        endA--;
    }while(endA > startA);
    
    if(lock) return(retval+2);
    else return(retval);
}

int AM29::progressCheck(uint8_t data, uint32_t addr){
    //returns -1 for error
    //returns 0 for operation in progress
    //returns 1 for valid
    //this function based on flow chart in datasheet
    
    uint8_t b, c;
    b = readByte(addr);
    c = readByte(addr);
    
    if((b&(1<<6)) != (c&(1<<6))){
        //toggle bit has toggled
        if((c&(1<<5)) == (1<<5)){
            //possible error
            b=readByte(addr);
            if((b&(1<<6)) == (c&(1<<6))){
                //toggling has stopped
                if(b == data) return(1);
                else return(-1);
          }else{
            return(-1);
          }
        }else{
            return(0);
        }
    }else{
        if(b == data) return(1);
        else return(-1);
    }
}

//output given byte at given address
void AM29::byteWrite(uint8_t data, uint32_t addr){
    digitalWrite(CE, LOW);
    digitalWrite(OE, HIGH);
    setAddr(addr);
    AM29IODDR = 0xFF;
    AM29IOPORT = data;
    digitalWrite(WE, LOW);
    delayMicroseconds(60);
    digitalWrite(WE, HIGH);
    delayMicroseconds(60);
    digitalWrite(CE, HIGH);
    AM29IOPORT = 0;
    AM29IODDR = 0;
}

//program uint8_t at given address, including setup commands
//returns -2 if programmed, -1 if verification error, 1 if ok
int AM29::programByte(uint8_t data, uint8_t sector, uint16_t addr){
    uint32_t fullAddr = (sector << 16);
    fullAddr += addr;
    return(programByte(data, fullAddr));
}
int AM29::programByte(uint8_t data, uint32_t addr){
    if(readByte(addr) != 0xFF) return(-2);
    
    autoSelect();
    byteWrite(0xAA, 0x05555);
    byteWrite(0x55, 0x02AAA);
    byteWrite(0xA0, 0x05555);
    byteWrite(data, addr);
    readReset();
    
    //verify
    int check=0;
    do{
        check = progressCheck(data, addr);
        Serial.print(".");
    }while(check==0);
    return(check);
}

//Send the autoSelect command
void AM29::autoSelect(){
    byteWrite(0xAA, 0x05555);
    byteWrite(0x55, 0x02AAA);
    byteWrite(0x90, 0x05555);
    uint8_t b = readByte(0);
}

//Send the readReset command
void AM29::readReset(){
    byteWrite(0xF0, 0);
}

//Erase the given sector
int AM29::eraseSector(uint8_t sector){
    uint32_t addr = sector;
    addr = addr << 16;
    Serial.println(String(addr,HEX));
    byteWrite(0xAA, 0x05555);
    byteWrite(0x55, 0x02AAA);
    byteWrite(0x80, 0x05555);
    byteWrite(0xAA, 0x05555);
    byteWrite(0x55, 0x02AAA);
    byteWrite(0x30, addr);
    delayMicroseconds(80);
    int check = 0;
    do{
        check = progressCheck(0xFF, addr);
        Serial.print(".");
    }while(check==0);
    return(check);
}

//Erase entire chip
int AM29::eraseChip(){
    byteWrite(0xAA, 0x05555);
    byteWrite(0x55, 0x02AAA);
    byteWrite(0x80, 0x05555);
    byteWrite(0xAA, 0x05555);
    byteWrite(0x55, 0x02AAA);
    byteWrite(0x10, 0x05555);
    
    int check = 0;
    do{
        check = progressCheck(0xFF, 0);
        Serial.print(".");
    }while(check==0);
    return(check);
}

And the Arduino sketch with a serial menu using the above functions:

#include <AM29_Flash.h>

#define A16 38
#define A17 39
#define A18 40
#define CE 56
#define OE 57
#define WE 58

AM29 flash = AM29(A16, A17, A18, CE, OE, WE);

void setup() {
  //setup code
  flash.setup();
  
  Serial.begin(115200);
  Serial.println(F("\nFlash Memory Manager"));
  Serial.println(F("(C)2015 techav"));
  Serial.println();
}

void loop() {
  mainMenu();
}

void printHex8(uint8_t in){
  Serial.print(String((in>>4) & 0x0F,HEX));
  Serial.print(String((in & 0x0F),HEX));
}
void printHex16(uint16_t in){
  Serial.print(String((in>>12) & 0x000F, HEX));
  Serial.print(String((in>>8) & 0x000F, HEX));
  Serial.print(String((in>>4) & 0x000F, HEX));
  Serial.print(String((in & 0x000F), HEX));
}

void writeFlashMenu() {
  boolean exit = false;
  uint8_t sector = 0;
  uint16_t addr = 0;
  uint8_t writeCount;
  
  do{
    String input;
    Serial.print(F("?"));
    input = readLineConsole();
    
    uint8_t inBuf[255];
    int e = input.length()-1;
    int bufLoc = 0;
    uint8_t checksum = 0;
    uint8_t readSum = 0;
    for(int i=1; i<e; i+=2){
      uint8_t b = strtoul(input.substring(i,i+2).c_str(),NULL,16);
      inBuf[bufLoc++] = b;
    }
    bufLoc--;
    for(int i=0; i<bufLoc; i++){
      checksum += (uint8_t)inBuf[i];
    }
    checksum = (~checksum)+1;
    bufLoc++;
    
    readSum = (uint8_t)strtoul(input.substring(e-1).c_str(),NULL,16);
    if(checksum != readSum){
      Serial.println(F("Invalid checksum!"));
      return;
    }
    switch(inBuf[3]){
      case(0):
        //data
        writeCount = 0;
        addr = inBuf[1];
        addr = addr << 8;
        addr += inBuf[2];
        Serial.print(F("Starting at address "));
        printHex16(addr);
        Serial.println();
        do{
          int r = flash.programByte(inBuf[writeCount+4],sector,addr);
          switch(r){
            case(1):
              Serial.println();
              break;
            case(-2):
              Serial.println(F("Sector is programmed. Erase and try again. Exiting."));
              return;
              break;
            case(-1):
            default:
              Serial.print(F("Unkown error programming uint8_t "));
              printHex8(inBuf[writeCount+4]);
              Serial.print(F(" to address "));
              printHex16(addr);
              Serial.println();
              break;
          }
          addr++;
          writeCount++;
        }while(writeCount<inBuf[0]);
        break;
      case(1):
        //end of file
        Serial.println(F("End of file reached. Exiting."));
        exit=true;
        break;
      case(4):
        //sector
        sector = inBuf[5];
        Serial.print(F("Jumping to sector "));
        printHex8(sector);
        Serial.println();
        break;
      default:
        Serial.print(F("Record type "));
        printHex8(inBuf[3]);
        Serial.println(F(" not supported at this time."));
    }
  }while(!exit);
}

void verifyFlashMenu() {
  String input;
  uint8_t sector = 0;
  bool valid = false;
  do{
    Serial.print(F("Select Sector to verify (1-8): "));
    input = readLineConsole();
    sector = (uint8_t)input.toInt();
    if(sector > 0 && sector < 9){
      valid = true;
      sector--;
    }
  }while(!valid);
  int r = flash.verifySector(sector);
  switch(r){
    case(3):
      Serial.println(F("Sector is Locked"));
    case(1):
      Serial.println(F("Sector has data"));
      break;
    case(2):
      Serial.println(F("Sector is Locked"));
    case(0):
      Serial.println(F("Sector is erased"));
      break;
    default:
      Serial.println(F("An unknown error occurred"));
  }
}

void eraseFlashMenu() {
  String input;
  uint8_t sector=0;
  bool valid = false;
  Serial.print(F("Erase CHIP or SECTOR? "));
  input = readLineConsole();
  if(input == F("CHIP")){
      //Chip erase
      Serial.print(F("Type YES to confirm chip erase: "));
      input = readLineConsole();
      if(input == F("YES")){
        int r = flash.eraseChip();
        if(r==1){
          Serial.println(F("Chip erase successful"));
        }else{
          Serial.println(F("An error occurred. Please verify Flash contents"));
        }
      }else{
        Serial.println(F("Operation cancelled."));
      }
  }else if(input == F("SECTOR")){
    //Sector erase
    do{
      Serial.print(F("Select Sector to erase (1-8): "));
      input = readLineConsole();
      sector = (uint8_t)input.toInt();
      if(sector > 0 && sector < 9){
        valid = true;
        sector--;
      }
    }while(!valid);
    Serial.print(F("Type YES to confirm erase sector "));
    Serial.print(sector + 1);
    Serial.print(F(": "));
    input = readLineConsole();
    if(input == F("YES")){
      int r = flash.eraseSector(sector);
      if(r==1){
        Serial.println(F("Chip erase successful"));
      }else{
        Serial.println(F("An error occurred. Please verify Flash contents"));
      }
    }
  }else{
    Serial.println(F("Invalid selection. Aborting"));
  }
}

void readFlashMenu() {
  String input;
  uint8_t sector;
  uint32_t addr, count;
  bool valid = false;
  do{
    Serial.print(F("Select Sector (1-8): "));
    input = readLineConsole();
    sector = (uint8_t)input.toInt();
    if(sector > 0 && sector < 9){
      valid = true;
      sector--;
    }
  }while(!valid);
  //Serial.println(sector);
  valid = false;
  do{
    Serial.print(F("Start address for read ($0000-$FFFF): $"));
    input = readLineConsole();
    
    addr = strtol(input.c_str(),NULL,16);
    if(addr >= 0 && addr < 0xFFFF) valid = true;
  }while(!valid);
  //Serial.println(String(addr,HEX));
  valid = false;
  do{
    Serial.print(F("End address for read ($0000-$FFFF): $"));
    input = readLineConsole();
    count = strtol(input.c_str(),NULL,16);
    if(count > addr && count <= 0xFFFF) valid = true;
  }while(!valid);
  //Serial.println(String(count,HEX));
  
  //start with printing Extended Address record
  Serial.print(F(":0200000400"));
  printHex8(sector);
  printHex8((~(6+sector))+1);
  Serial.println();
  //read and output Flash data
  do {
    int a;
    uint8_t checksum = 0;
    if((count-addr)>=16){
      a = 16;
    }else{
      a = count-addr+1;
    }
    
    Serial.print(":");
    printHex8(a);      //uint8_t count
    printHex16(addr);  //start address
    printHex8(0);      //field type
    
    checksum += a;
    checksum += (uint8_t)(addr>>8);
    checksum += (uint8_t)(addr);
    
    for(a; a>0; a--){
      uint8_t b = flash.readByte(sector, addr);
      checksum += (uint8_t)b;
      printHex8(b);
      addr++;
    }
    checksum = (~checksum)+1;
    printHex8(checksum);
    Serial.println();
  }while(addr < count);
  Serial.println(F(":00000001FF"));
}

String readLineConsole() {
  String rx;
  bool lineTerm = false;
  
  do{
    if(Serial.available()){
      char c = Serial.read();
      if(c >= 'a' && c <= 'z'){
        c -= 0x20;
      }
      if(c == 0x0A){
        lineTerm = true;
      }else if(c != 0x0D){
        rx += c;
      }
    }
  }while(!lineTerm);
  //flush out anything else that may be in the buffer
  //after the line termination
  while(Serial.available()){
    char c = Serial.read();
  }
  Serial.println(rx);
  return(rx);
}

void mainMenu() {
  Serial.print(F("> "));
  String rx = readLineConsole();
  if(rx == F("READ")){
    readFlashMenu();
  } else if(rx == F("WRITE")){
    //
    writeFlashMenu();
  } else if(rx == F("VERIFY")){
    verifyFlashMenu();
  } else if(rx == F("ERASE")){
    eraseFlashMenu();
  } else {
    //show help
    Serial.println(F("Command List:"));
    Serial.println(F("Help - (This List)"));
    Serial.println(F("Read - Read data from Flash"));
    Serial.println(F("Write - Write data to Flash"));
    Serial.println(F("Erase - Erase data from Flash"));
    Serial.println(F("Verify - Check Flash sector status"));
  }
}

In hindsight, I suppose I could have made this a project all its own, but here it is, as part of my Z80 project.

Discussions

ignacio wrote 06/02/2023 at 16:36 point

Hello. your project is very interesting. I am just trying to replicate the flash recorder part and there is something I don't understand. How comes that in your arduino sketch you define pins 56,57 and 58 for CE, OE and WE when mega doesn't have those pins ( it goes till 53. ) . When I checked your photo of the protoboard it seems you used ports 3,4 and 5 PWM , The point is that I believe the code as written won't work Iike this. Thank you

  Are you sure? yes | no

Hariagung.prabowo wrote 05/10/2019 at 20:09 point

Dear friend,

What software is used to connect to a PC?

  Are you sure? yes | no