Reading large parallel mask rom with Arduino MEGA

hey folks.

I'm currently trying to read the contents of an old audio board to my hard drive. It's interleaved across two 64 mbit (4mx16 WORD) chips. I've managed to put together an adapter to read it with the MEGA, and have had some success, but after the first 256k are read, it loops around.

Now I am aware that this is likely to do with using an "int" to count my address space, but when I move to something like an uint32_t, it seems to no longer read the data on the board properly. At least with the first approach I'm getting some readable data, even if it's very minimal.

Can someone point me in the right direction? I am extremely green with this stuff, so I am really hoping I'm just missing something painfully obvious.

// Set pin definitions for address and data
// first half are chip one (d0-d15), second half are chip two (d16-d31)
int dataPinsAll[] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 8, 9, 10, 11, 12, 13, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9};

// address pins are the same on both chips - A0-A21
int addressPinsA0_A21[] = {38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 2, 3, 4, 5, 6, 7}; // combined address pins

// OE (A10) and CE (A11) pins
const int pinOE = A10;
const int pinCE = A11;

bool startReading = false;  // starting flag for serial read

void setup() {

  // Set all address lines as outputs

  for (int i = 0; i < 22; i++) {
    pinMode(addressPinsA0_A21[i], OUTPUT);
  }
  // Set data lines as inputs
  for (int i = 0; i < 32; i++) {
    pinMode(dataPinsAll[i], INPUT_PULLUP);
  }

  // Set OE and CE as outputs
  pinMode(pinOE, OUTPUT);
  pinMode(pinCE, OUTPUT);

  // Set OE and CE to HIGH (inactive state)
  digitalWrite(pinOE, HIGH);
  digitalWrite(pinCE, HIGH);

  delay(100);

  Serial.begin(115200);
}

void SetAddress(int addr) {
  // Set all 22 bits of the address (A0-A21)
  for (int i = 0; i < 22; i++) {
    digitalWrite(addressPinsA0_A21[i], (addr & (1 << i)) ? HIGH : LOW);
  }
}


uint32_t ReadData() {
  uint32_t data = 0;
  // Loop through the 32 data pins and construct the 32-bit word
  for (int i = 0; i < 32; i++) {
    if (digitalRead(dataPinsAll[i]) == HIGH) {
      data |= (1UL << i);  // Set the corresponding bit if the pin is HIGH
    }
  }
  return data;
}


void waitForStartCommand() {
  // Wait until the word "start" is received via serial
  while (!startReading) {
    // Check if the 'start' signal has been received
    if (Serial.available() > 0) {
      String command = Serial.readStringUntil('\n');  // Read until a newline character
      command.trim();  // Remove any leading/trailing whitespace

      if (command == "start") {
        startReading = true;  // Set flag to start reading
        Serial.println("Start signal received, reading memory...");
      }
    }
    delay(10);  // Small delay to avoid flooding serial communication
  }
}

#define MAX_ADDR 16000000L // 16MB address space (for the 8M x 32 SIMM configuration)

#define BUFFER_SIZE 64  // seems to be abotu right

void loop() {
  waitForStartCommand();  // wait for start


  uint32_t combinedWord;
  int addr;
  uint8_t buffer[BUFFER_SIZE];
  int bufferIndex = 0;

  // Read from both U1 and U2, combining into 32-bit words
  for (addr = 0; addr < MAX_ADDR; addr++) {  // Read each address for U1 and U2
    // Set address before enabling the chip
    SetAddress(addr);

    // Wait for the address to settle (1 µs delay is safe)
    delayMicroseconds(1);

    // Enable OE and CE to read data
    digitalWrite(pinOE, LOW);
    digitalWrite(pinCE, LOW);

    // Wait for tOE to ensure the data is ready (50 ns minimum, 1 µs is safe)
    delayMicroseconds(1);

    // Read the 32-bit data word from all data lines
    combinedWord = ReadData();

    // Disable OE and CE after reading
    digitalWrite(pinOE, HIGH);
    digitalWrite(pinCE, HIGH);

    // Correct the byte order for output
    buffer[bufferIndex++] = (combinedWord >> 8) & 0xFF;   // MSB of U1 (Byte 2)
    buffer[bufferIndex++] = combinedWord & 0xFF;          // LSB of U1 (Byte 1)
    buffer[bufferIndex++] = (combinedWord >> 24) & 0xFF;  // MSB of U2 (Byte 4)
    buffer[bufferIndex++] = (combinedWord >> 16) & 0xFF;  // LSB of U2 (Byte 3)

    // If the buffer is full, send the data via Serial
    if (bufferIndex >= BUFFER_SIZE) {
      Serial.write(buffer, BUFFER_SIZE);  // Send buffer data
      bufferIndex = 0;  // Reset buffer index
    }
  }

  // Flush remaining data in the buffer if any
  if (bufferIndex > 0) {
    Serial.write(buffer, bufferIndex);
  }

  // Add a 5-second delay after the entire board has been read
  delay(5000);

  // Send a Serial disconnect signal (optional custom message)
  Serial.println("DISCONNECT");  // Signal to the host that reading is complete

  // Close the Serial connection
  Serial.end();  // This ends the serial communication
}

Thanks for listening!

A couple of quick spots:

The variable addr used in your for loop is defined as an int (see a few lines further up in your code). You should make this an uint32_t to handle values up to MAX_ADDR.

You need to define the addr parameter as a uint32_t as well.

I built your code. It compiles okay, but there's this warning that needs fixing (irrelevant path to ino file deleted)

C:\Users\David\Dropbox\WorkArea\Electronics\Microcontrollers\Sketches\deleteme2\deleteme2.ino: In function 'void loop()':
*.ino:90:23: warning: comparison is always true due to limited range of data type [-Wtype-limits]
   for (addr = 0; addr < MAX_ADDR; addr++) {  // Read each address for U1 and U2

I'll modify your code to sort out the address so it's using 32 bit variables. I'll post the modified code shortly

Hey folks, just something I feel is important to mention - I've tried just replacing the addr int value with uint32_t and suddenly I am no longer getting readable data. I'm not sure exactly why, maybe I'm not updating the int correctly? Also thanks Dave! I look forward to seeing what you've changed.

void SetAddress(int addr) {
  // Set all 22 bits of the address (A0-A21)
  for (int i = 0; i < 22; i++) {
    digitalWrite(addressPinsA0_A21[i], (addr & (1 << i)) ? HIGH : LOW);
  }
}

You may need to specify (1UL << i) in addition to changing the address to uint32_t.

< edit >
16MB is not 16,000,000, it is 16 * 1024 * 1024 = 16777216 bytes. Also should be unsigned long.

#define MAX_ADDR 16000000L // 16MB address space (for the 8M x 32 SIMM configuration)

< additional edit >
Since you have two 4M x 16 memory chips, the address range is only 4MB.

1 Like

I caught that one, but not the "two 4M x 16 memory chips, the address range is only 4MB"

// Set pin definitions for address and data
// first half are chip one (d0-d15), second half are chip two (d16-d31)
int dataPinsAll[] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 8, 9, 10, 11, 12, 13, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9};

// address pins are the same on both chips - A0-A21
int addressPinsA0_A21[] = {38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 2, 3, 4, 5, 6, 7}; // combined address pins

// OE (A10) and CE (A11) pins
const int pinOE = A10;
const int pinCE = A11;

bool startReading = false;  // starting flag for serial read

void setup() {

  // Set all address lines as outputs

  for (int i = 0; i < 22; i++) {
    pinMode(addressPinsA0_A21[i], OUTPUT);
  }
  // Set data lines as inputs
  for (int i = 0; i < 32; i++) {
    pinMode(dataPinsAll[i], INPUT_PULLUP);
  }

  // Set OE and CE as outputs
  pinMode(pinOE, OUTPUT);
  pinMode(pinCE, OUTPUT);

  // Set OE and CE to HIGH (inactive state)
  digitalWrite(pinOE, HIGH);
  digitalWrite(pinCE, HIGH);

  delay(100);

  Serial.begin(115200);
}

void SetAddress(uint32_t addr) { // <--- made it 32 bit
  // Set all 22 bits of the address (A0-A21)
  for (int i = 0; i < 22; i++) {
    // This wasn't working for bits above bit 15 hence "uint32_t(1)"
    digitalWrite(addressPinsA0_A21[i], (addr & (uint32_t(1) << i)) ? HIGH : LOW); // <---
  }
}


uint32_t ReadData() {
  uint32_t data = 0;
  // Loop through the 32 data pins and construct the 32-bit word
  for (int i = 0; i < 32; i++) {
    if (digitalRead(dataPinsAll[i]) == HIGH) {
      data |= (1UL << i);  // Set the corresponding bit if the pin is HIGH
    }
  }
  return data;
}


void waitForStartCommand() {
  // Wait until the word "start" is received via serial
  while (!startReading) {
    // Check if the 'start' signal has been received
    if (Serial.available() > 0) {
      String command = Serial.readStringUntil('\n');  // Read until a newline character
      command.trim();  // Remove any leading/trailing whitespace

      if (command == "start") {
        startReading = true;  // Set flag to start reading
        Serial.println("Start signal received, reading memory...");
      }
    }
    delay(10);  // Small delay to avoid flooding serial communication
  }
}

// #define MAX_ADDR 16000000L // 16MB address space (for the 8M x 32 SIMM configuration)
const uint32_t MAX_ADDR = 0x1000000UL; // 16*1024*1024 =  16,777,216 = 0x100,0000 <---
#define BUFFER_SIZE 64  // seems to be abotu right

void loop() {
  waitForStartCommand();  // wait for start


  uint32_t combinedWord;
  uint32_t addr; // <--- made it 32 bit
  uint8_t buffer[BUFFER_SIZE];
  int bufferIndex = 0;

  // Read from both U1 and U2, combining into 32-bit words
  for (addr = 0; addr < MAX_ADDR; addr++) {  // Read each address for U1 and U2
    // Set address before enabling the chip
    SetAddress(addr);

    // Wait for the address to settle (1 µs delay is safe)
    delayMicroseconds(1);

    // Enable OE and CE to read data
    digitalWrite(pinOE, LOW);
    digitalWrite(pinCE, LOW);

    // Wait for tOE to ensure the data is ready (50 ns minimum, 1 µs is safe)
    delayMicroseconds(1);

    // Read the 32-bit data word from all data lines
    combinedWord = ReadData();

    // Disable OE and CE after reading
    digitalWrite(pinOE, HIGH);
    digitalWrite(pinCE, HIGH);

    // Correct the byte order for output
    buffer[bufferIndex++] = (combinedWord >> 8) & 0xFF;   // MSB of U1 (Byte 2)
    buffer[bufferIndex++] = combinedWord & 0xFF;          // LSB of U1 (Byte 1)
    buffer[bufferIndex++] = (combinedWord >> 24) & 0xFF;  // MSB of U2 (Byte 4)
    buffer[bufferIndex++] = (combinedWord >> 16) & 0xFF;  // LSB of U2 (Byte 3)

    // If the buffer is full, send the data via Serial
    if (bufferIndex >= BUFFER_SIZE) {
      Serial.write(buffer, BUFFER_SIZE);  // Send buffer data
      bufferIndex = 0;  // Reset buffer index
    }
  }

  // Flush remaining data in the buffer if any
  if (bufferIndex > 0) {
    Serial.write(buffer, bufferIndex);
  }

  // Add a 5-second delay after the entire board has been read
  delay(5000);

  // Send a Serial disconnect signal (optional custom message)
  Serial.println("DISCONNECT");  // Signal to the host that reading is complete

  // Close the Serial connection
  Serial.end();  // This ends the serial communication
}

Search for "<---" to see which lines I changed

1 Like

Huge thank you to Dave_Lowther, you've cracked it! I've tried so many subtle variations of this and had no success, so well done for finding the exact solution!

It does still seem to just loop around when it reaches the end of the ROMs, but at least it makes it to the end in this scenario!

Incidentally, I also needed to correct the INPUT_PULLUP's to INPUT's, as the chips were not happy with the pullups. It did work with it but the data was compromised. So if anyone uses this code for a similar project, remember to change that.

Thanks once again.

That is because you are reading 16MB x 32 bits or 64MB of data, the address space is only 4MB x 32 bits.

This took me a distressingly long time to fully wrap my head around. Now I understand, but it took a REALLY long time, haha. Thank you.

While I'm still asking questions - does anyone have any tips on improving the quality of the data being sent back to the serial program? I'm getting quite inconsistent dumps and I'm curious if I'm doing something silly or if there's a way of improving it. I just tried flow control and that seemed to make things worse. Cheers!

Can you expand on this? Are the correct number of bytes being received? Are there corruptions in the data?

It looks like you are writing binary data to the serial port direct from the array called buffer. Is your capture program on your PC intercepting any of those bytes thinking that they may be flow control (XON & XOFF) or similar.

You could modify the output to generate Motorola S-Records or Intel-Hex records that are simply ASCII text.

I wouldn't trust myself, or the serial comms, to send raw data with no checks.
I'd make a packet structure. e.g. first 4 bytes = known pattern, such as 0xFFEEDDCC. Then your 64 bytes payload. I'd put a checksum on the end as the last byte.
Then I'd send dummy data of a kown pattern in the payload as fast as possible and check that the receiver of the data doesn't detect any errors. Until you get such a test working reliably, you can't be certain that the ROM data you are receiving isn't missing bytes.
[Edit, the 'known pattern' for the 64 byte payload could just be a count from 0x00.0x3F for the first packet, 0x40..0x7F for the next, then 0x80..0xBF, then 0xC0..0xFF, then repeat. This would cover @markd833's point about XON/XOFF being used for flow control unintentionally]

I was writing my reply, which appears after your post, when you posted. I hadn't seen it when I posted my reply. I like your suggestion so long as transmission time isn't too important.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.