Feed on
Posts
Comments

Recently I’ve been collecting retro gaming consoles such as the Gameboy and SNES and whilst playing a game on the SNES I actually lost all my saves when I turned it off (turns out the battery got disconnected from the cartridge). The thought came to me, how can I backup the save game from these devices? There are cart readers around but they seem to be harder to find in this day and age so I thought why not make one myself using the Arduino as it’s so versatile!

Before I begin looking into extracting the save games I’ll look into dumping the ROM which was a way for me to learn how to communicate with the Gameboy cartridge. I’ll now guide you in how to communicate with the Gameboy cartridge to read the ROM so without further delay, let’s begin!

Gameboy Cartridge


Let’s firstly take a look inside a Gameboy cartridge. A typical Gameboy cartridge contains:

  • ROM is where the game’s data is stored
  • SRAM is where your save games/high scores are keep. Some cartridges (like the one of the left) don’t have this chip because they don’t store any data or it’s built into the MBC
  • MBC is the memory bank controller, it allows us switch ROM banks to read the game’s data from the ROM.
  • MM1134 IC to control when SRAM should be run from battery or not
  • 3v coin cell

Gameboy Cartridge Pins

You can see it has 32 pins and those pin functions are:

  • VCC – Power (5 volts)
  • CLK – Clock signal (not used)
  • ~WR – if low(grounded) and if RD is low, we can write to the SRAM and load a ROM or SRAM bank
  • ~RD – if low (grounded) and if WR is high, we can read the ROM and SRAM
  • CS_SRAM – if enabled, selects the SRAM
  • A0 – A15 – the 16 addresses lines that we tell the ROM which particular byte of data we want to read
  • D0 – D7 – the 8 data lines that we read the byte of data selected by the 16 address lines. These data lines can also be used to control which ROM bank to load (important for later).
  • Reset – needs to be connected to VCC
  • Audio in – (not used)
  • GND – Ground

For reading the ROM we are only interested in the WR, RD, A0-A15 and D0-D7 lines.

Gameboy Address

The Gameboy can only address 16bits (16 address lines: A0-A15) which means that the number of combinations of addresses is 2 ^ 16 = 65,536 with 1 byte for each address means that 65,536 bytes can be read/written to. These 16 address lines also control the Gameboy’s internal RAM, etc and so they decided to assign the addresses 0000h to 7FFFh for the Gameboy’s ROM Cartridge which gives us 32,767 bytes to work with. You can view the full list of addresses and what they correspond to here. Note: you will see that 0000h to 00FFh is not really part of the ROM.

In“0000h” and “7FFFh” the h stands for hexadecimal (hex) which is a way that’s used to express the addresses instead of just writing the decimal number; you can convert to and from hex by using an online calculator or if in Windows by opening the calculator and choosing scientific mode.

32K bytes for a Gameboy game sometimes isn’t enough so what they were able to do is have multiple 16K ROM banks and have a chip (MBC) to change the ROM bank if we requested it. We would then just re-read the same 16K address range and be presented with new 16K worth of data.

For example, we could have 8 banks x 16K bytes which gives us 128K bytes to work with. You could have the main code in the first 16K bytes then ask the MBC to switch to the next 16K bytes because you might have an image on the 2nd bank and then switch to the 4th bank of 16K as you might have text there. So you can see how by having a MBC they overcame the issue of only being able to address 32K bytes.

Visualising the use of hex as an address

I’ve mentioned that we use hex addresses instead of just writing the decimal number and we know that there are 16 address lines (A0-A15) but let’s see how we visualise the conversion of hex to binary so that the appropriate address lines are set to 1 (high).

Let’s take for example the hex address 0148h which is 328 in decimal but more importantly is 101001000 in binary. When we want to tell the ROM that we want to read from 0148h, we turn the address lines on or leave them off.

Address lines     15, 14, 13, 12, 11, 10, 9,  8,  7,  6,  5,  4,  3,  2,  1,  0
On (1), Off (0)                                             1,  0,  1,  0,  0,   1  0,  0,  0

As you can see the address lines are going from A0 right to A15 left and then we take our binary number and align it to the right. We would be turning on address lines 3, 6 and 8 which the ROM would read as 0148h; this is the basics of how address lines are used to tell the ROM the address we want to access.

Gameboy Cart Physical Header


The only downside to this project was I had to rip the cartridge header from a Gameboy as I wasn’t able to find any online shops still selling them! I just built a small PCB that I mounted the header to and then just female headers on the other side, it worked well. You can build your own Gameboy Cart Adapter here.

Setting up the Arduino

We need to connect the Arduino up to the Gameboy cartridge but we’ll need 26 lines and the Arduino only has 13 digital pins. What we can do is use our old friend the Shift Register (more about Shift Registers here) to shift out the address A0-A15 lines for us. Now all we need is 26 – 16 (address lines) + 3 (shift register lines) = 13 lines which is achievable as we can set some analog pins on the Arduino to act as digital pins.

The data that we read from the ROM has to go somewhere right? We’ll be using the serial to transfer the data back to the computer. Let me say that when setting up the Arduino as we are using serial I’ve found that using digital pins 0 and 1 cause issues so we will avoid them, plus it says this in the Arduino Reference which I found out later.

  • VCC we can connect to Arduino’s 5V pin.
  • WR and RD which will be on digital pin 13 and analog pin 5 respectively. We will pull these to ground with a 10K resistor and we’ll connect a 470Ohm resistor to protect them when we do set them to high.
  • A0 – A15 are connected through our shift registers. A0-A7 is handled by shift register 1 and A8-15 is handled by shift register 2. The outputs from the shift registers have 470 Ohm resistors (these resistors are optional but good if you are wiring this up for the first time. It appears the cartridge has some built in resistors somewhere.) We connect Clock to pin 12, Data to digital pin 11 and Latch to 10.
  • D0-D7 will be on digital pins 2 to 9 respectively (D0 = digital pin 2, D1 is pin 3, etc). 470 Ohm resistors can also be placed on these lines for safety but once again they are optional.
  • Reset is pulled up with a 10K resistor.
  • GND is connected to Arduino’s ground.

Setting up the computer side

The reason for setting up the computer side is so that we don’t have to use the Arduino serial monitor to receive the data, I mean who would want to copy and paste the ROM to a file themselves? We can utilise Python and create a script that will open the serial port and place everything read into a file. Python for me was a tricky language to deal, it’s not quite like C or Java.

Firstly download and install Python 3.2 and pySerial, then download my iG_GBCartRead_ROM_Only_v1.0 script. To explain my script in short, it opens the serial port, waits for the START command, converts the serial data received and writes it into a file called myrom.rom then stops once the END command is received.

Cartridge Header

Before we dive into reading the ROM we need to know firstly which MBC the cartridge uses and how many ROM banks of 16K bytes there are, this is done by reading the cartridge header. There are many other bits of useful information in the header such as the game title, ram size, etc. A more extensive list can be found on page 56 of TheNintendoGameboy PDF, this PDF document is also worth keeping handy.

0147h – Cartridge Type (MBC type)
This address defines which MBC the cartridge uses.
0 – no MBC
1 – MBC1
2 – MBC1+RAM
etc

0148h – ROM size
At this address, the number of ROM banks is specified and is done by multiplying 32K by 2 times the number we read. There are exceptions to this rule.
0 – 32K byte
1 – 64K byte (i.e. 32K x 2 = 64K, 16K banks so 4 ROM banks)
2 – 128K byte (i.e. 32K x 4 = 64K, 16K banks so 8 ROM banks)
etc

Read the first 16,383 bytes of the ROM – Bank 0 (0000h – 3FFFh)

The first ROM bank 0 ranges from 0000h to 3FFFh and no switching of banks is required. Bank 0 will always be at this address and is like this way so that we can always read the Cartridge header no matter which MBC the Gameboy cartridge uses.

Alright we’ve learnt a fair bit so let’s jump into some code to read the first bank of ROM, here’s the download you will need to upload to the Arduino: iG_GBCartRead_ROM_Bank_0

int latchPin = 10;
int dataPin = 11;
int clockPin = 12;
int rdPin = A5;
int wrPin = 13;

Just our standard assignment of pins.

void setup() {
 Serial.begin(57600);
 pinMode(latchPin, OUTPUT);
 pinMode(clockPin, OUTPUT);
 pinMode(dataPin, OUTPUT);
 pinMode(rdPin, OUTPUT);
 pinMode(wrPin, OUTPUT);

 // Reads D0 - D7, set as inputs
 pinMode(2, INPUT);
 pinMode(3, INPUT);
 pinMode(4, INPUT);
 pinMode(5, INPUT);
 pinMode(6, INPUT);
 pinMode(7, INPUT);
 pinMode(8, INPUT);
 pinMode(9, INPUT);
}

We now set begin the serial connection, set the pins used to talk to the shift registers and RD/WR pins as outputs. We set the digital pins 2 to 9 as inputs as they need to read the data that comes from the cartridge connectors D0 – D7.

void loop() {
  unsigned int addr = 0; // Variable ranges between 0 to 65,536

Now for the start of the main loop, we set up our address variable as an unsigned integer and it needs to be unsigned because we have a 16 bit address that is 2 ^ 16 = 65,536, so we range between 0 and 65,536. If we didn’t put it as unsigned the range would be -32,768 to 32,768 which would really mean 0 to 32,768 for us as we only deal with positive addresses (this would be fine because we never go over 32,768 but we’ll do things right). Thus once it would reach 32,769 it would “overflow” and actually become -32,768!

digitalWrite(rdPin, LOW); // RD 0
 digitalWrite(wrPin, HIGH); // WR 1

 Serial.println("START"); // Send the start command

We set RD to low and WR as high in order to read the ROM, you can check what to set RD/WR to here. After that we send the text “START” by serial over to our computer.

// Read addresses 0000h to 3FFF, i.e. ROM bank 0
 for (addr = 0; addr <= 0x3FFF; addr++) {
 digitalWrite(latchPin, LOW);
 shiftOut(dataPin, clockPin, MSBFIRST, (addr >> 8)); // Most left 8 bits transferred
 shiftOut(dataPin, clockPin, MSBFIRST, (addr & 0xFF)); // Most right 8 bits transferred
 digitalWrite(latchPin, HIGH);
 delayMicroseconds(50); // So ROM can process our request

We begin our for loop used to read every address between 0000h and 3FFF which is ROM bank 0, so we set up x as 0000h and the end as 3FFF. Next is the standard Shift Register code, pull the latch to low, shift out the address and pull the latch to high. We delay 50 microseconds so the ROM has had enough time to register our request.

For example, lets say the address is 3FFF (0011 1111 1111 1111). What you’ll notice is that we do “x>>8″, this means only give us the “most left” 8 bits and does this by moving the bits to the right by 8; we have a 16 bit address so moving all the bits to the right 8 times which means we are left with the “most left” 8 bits only. The other “1111 1111″ bits have moved to the right that they have “disappeared”.

The next part “x & 0xFF” we do an AND operator on the “most right” 8 bits. 1 AND 1 = 1, 0 AND 1 = 0. As we didn’t specify a 1 on the 8 “most left” bits this basically gets rid of them and we are left with only the 8 “most right” bits.

So we’ve now split up the 16 bit address into 2 x 8 bit parts which we send to each shift register and then they send it to the A0-A15 addresses.

byte bval = 0;
for (int z = 9; z <= 2; z--) {
  if (digitalRead(z) == HIGH) {
    bitWrite(bval, (z-2), HIGH);
  }
}
Serial.println(bval, DEC);

We now read the 8 bits coming from the D0 – D7 address using the for loop. If we find any bit that is on we use bitWrite to set that bit to high on our bval variable, basically we are converting the hardware bits we are receiving from D0 – D7 into software bits in the variable bval. We then print out bval as a decimal number.

}
Serial.println("END");

while(1);
}

After the code has read all the addresses, we print out END and then just stop.

So now it’s time to test it out, go ahead and upload the downloaded “iG_GBCartRead_ROM_Bank_0.pde” file to your Arduino.

Now open up the script by right clicking it and selecting “Edit with IDLE”.

You will need to make a change in the script so the serial connections to the correct COM port on your computer, for me it’s COM2. You can find this out from the Arduino software by going to Tools -> Serial port when the Arduino is plugged in. Once you have made the change, hit F5 to run the script.

You should see that some hashes should start printing every few seconds, once it’s done it will stop.

Now let’s read the file called myrom.rom with a hex editor, the one I use is called HxD Hex Editor. If you scroll down just a tiny bit the data we’ll see is the cartridge header, can you see the game title? It’s located at 0143gh. The cartridge type (MBC) is on 0147h and the ROM size (which is important to us in order to read the whole ROM chip) is at 0148h also the RAM size is at 0149h.

Read the next ROM bank (4000 – 7FFFh)

So we have read the first 16,383 bytes of the ROM and now we need to switch ROM banks so we can read the next 16,383 bytes (4000 to 7FFF) and so on. Here’s the download you will need to upload to the Arduino: iG_GBCartRead_ROM_Banks

We complete the following steps for MBC2; the only difference between the MBCs for ROM reading is what address you need to write to in order to switch banks, it’s a good idea to keep this link handy.

  1. Put the WD pin to high
  2. Select the specific address that allows us to change ROM banks, in our case 0×2100 (10 0001 0000 0000 which means A8 and A13 are enabled)
  3. Use the D0-D7 lines to write the ROM bank number we wish to use, e.g. 0000 0001 would select bank 1, 0000 0010 for bank 2, etc.
  4. Put the WD pin to low so that we can begin to read the ROM bank
  5. Read the address 4000 to 7FFF which will contain the ROM bank we requested

We repeat steps 1 to 5 and specify the next ROM bank number in step 2 until all ROM banks have been read. If you recall the ROM size of my cartridge of F1RACE is 128Kbytes which means 16K x 8 banks, so we ourselves have to stop once we reach the 8th ROM bank otherwise we will receive duplicate data.

Did you notice something weird with Step 3? Remember that we actually used D0-D7 before to read the data from the ROM but now we write data to it. What happens is that the MBC knows that we can’t write to the ROM so it intercepts our request and knows that we want to change ROM banks.

Now for the code, we modify the main loop a bit and add a new function to switch ROM banks.

void loop() {

 unsigned int addr = 0; // Variable ranges between 0 to 65,536
 Serial.println("START"); // Send the start command

 int romBanks = 8; // Change to the number of ROM banks

We’ve now added a variable called romBanks which you need to change to the ROM banks to the number your Gameboy cartridge has (remember do this by reading Cartridge header – ROM bank 0).

 // Read x number of banks
 for (int y = 1; y < romBanks; y++) {
 selectROMbank(y);
 if (y > 1) {
 addr = 0x4000;
 }

 // Reads addresses 0000h to 7FFF in the first run, then
 // only 4000h to 7FFF in the second or more runs
 for (; addr <= 0x7FFF; addr++) {
   digitalWrite(latchPin, LOW);
   shiftOut(dataPin, clockPin, MSBFIRST, (addr >> 8));
   shiftOut(dataPin, clockPin, MSBFIRST, (addr & 0xFF));
   digitalWrite(latchPin, HIGH);
   delayMicroseconds(50);

   byte bval = 0;
   for (int z = 9; z >= 2; z--) {
     if (digitalRead(z) == HIGH) {
       bitWrite(bval, (z-2), HIGH);
     }
   }
   Serial.println(bval, DEC);
 }
 Serial.println("END");

 while(1);
}

Now we have a for loop that loops 8 times because my ROM has 8 banks.

What it does is in the first run when y =1, the selectROMbank method selects ROM bank 1 because we will actually be reading two ROM banks (0000h to 7FFF) in one go in the first run. Remember that Bank 0 is always at 0000h to 3FFF and Bank 1+ is 4000h to 7FFF. After the second or more runs, y is 2 (or more) which activates the if statement to change the address to 4000h because this is the address we use to read the other ROM banks.

Next is the for loop we had before except we don’t assign the addr variable anything because we set that before in the if (y > 1) part, so all we do is set the end of the loop at 7FFF.

// Select the ROM bank by writing the bank number on the D0-D7 pins
void selectROMbank(int bank) {
 digitalWrite(rdPin, HIGH); // RD 1
 digitalWrite(wrPin, LOW); // WR 0
 delay(5);

Now we reach the selectROMbank function, it takes the bank number as the input. First thing it does is switch RD to 1 and WR to 0 as per the RD/WR page and then we just wait a little while.

// Set D0-D7 as outputs
 pinMode(2, OUTPUT);
 pinMode(3, OUTPUT);
 pinMode(4, OUTPUT);
 pinMode(5, OUTPUT);
 pinMode(6, OUTPUT);
 pinMode(7, OUTPUT);
 pinMode(8, OUTPUT);
 pinMode(9, OUTPUT);

We set the D0-D7 pins as outputs as we will need to write to these pins soon.

 unsigned int x = 0x2100; // Address where we write to
 digitalWrite(latchPin, LOW);
 shiftOut(dataPin, clockPin, MSBFIRST, (x >> 8));
 shiftOut(dataPin, clockPin, MSBFIRST, (x & 0xFF));
 digitalWrite(latchPin, HIGH);
 delay(5);

We set the address to 2100h as per the switch banks URL and do the usual shifting out to the shift registers.

 // Select the bank
 if (bank & 1) {
   digitalWrite(2, HIGH);
 }
 if (bank & 2) {
   digitalWrite(3, HIGH);
 }
 if (bank & 4) {
   digitalWrite(4, HIGH);
 }
 if (bank & 8 ) {
   digitalWrite(5, HIGH);
 }
 if (bank & 16) {
   digitalWrite(6, HIGH);
 }
 if (bank & 32) {
   digitalWrite(7, HIGH);
 }
 if (bank & 64) {
   digitalWrite(8, HIGH);
 }
 if (bank & 128) {
   digitalWrite(9, HIGH);
 }
 delay(5);

Now we select the bank we want to use. Using the AND operator with the bank input number and a multiple of 2 we are actually able to check which individual bits are enabled in the bank variable. Remember how we did hardware bits converted to software bits? We’re now doing the reverse of that.

For example, if bank was equal to 3 the first (bank & 1) would pass as 11 AND 1 gives 1 and if statement executes. Next  (bank & 2) would go through as 11 AND 10 gives back 1. For (bank & 4), this wouldn’t execute as 11 AND 100 gives 0.

 // Set back to reading ROM
 digitalWrite(rdPin, LOW); // RD 0
 digitalWrite(wrPin, HIGH); // WR 1

Next we set RD and WR back as they were so we can now read the loaded ROM bank.

 // Reset outputs to LOW
 for (int l = 2; l <= 9; l++) {
 digitalWrite(l, LOW);
 }

 pinMode(2, INPUT);
 pinMode(3, INPUT);
 pinMode(4, INPUT);
 pinMode(5, INPUT);
 pinMode(6, INPUT);
 pinMode(7, INPUT);
 pinMode(8, INPUT);
 pinMode(9, INPUT);
 delay(5);
}

We now set the D0-D7 pins back to LOW and set them back as inputs so we are now ready to read the ROM bank.

Before you upload this to the Arduino, you need to change the romBanks variable to the amount of ROM banks you have which as you should know is indicated by reading address 0148h and referencing that with page 56 of TheNintendoGameboy.pdf, for me it’s 8 banks.

After uploading, go back to my script and run it. Just so you know I am in the process of making improvements to my script so it can adjust the ROM banks automatically, read our the game title, etc but that’s for another post. You should now see more hashes appear than last time and it will take a bit longer to complete too.

And that’s it we’re done. I hope you’ve learned how we used the Arduino and some documentation to read the Gameboy Cartridge’s ROM and how the ROM banks work. In my next part, I’ll show you how we can read the RAM so we can read those game saves and hopefully even load our game saves to the cartridge if it ever gets erased. The GBCartRead project page can be found here which will contain the latest version of my Python script and Arduino code.

Edit (21/03/11): GBCartRead has been updated to v1.1 which can automatically detect the number of ROM banks so you don’t need to change anything on the Arduino side. It will also print the Game Title, MBC type, ROM size and RAM size in the Python script.

GBCartRead Part 1: Read the ROM
GBCartRead Part 2: Read the RAM
GBCartRead Part 3: Write to RAM
GBCartRead: Completed
Gameboy Cart Shield released

Buy the Gameboy Cart Shield
Emulating the Nintendo Logo on the Gameboy

83 Responses to “GBCartRead: Arduino based Gameboy Cart Reader – Part 1: Read the ROM”

  1. [...] While cart readers exist, he says that they are hard to find nowadays, so he decided to construct his own using an Arduino. SNES cartridges are relatively complex, so he opted to focus on Gameboy cartridges for the time being. Before attempting to back up save games, he first chose to learn how to communicate with the cartridges in general, by reading the ROM. [...]

  2. XNOR16 says:

    I have to say very nice write up for people looking to learn from you. Over all I think you should have just used a MCU that has enough pins on it already. I did this with a ATMEGA16 and it came out nice and small. I threw up some pictures. I used Java for dumping to the PC though. For the life of me I can not get Python and PySerial to play nice on a 64-bit machine.

    http://www.rival-corp.com/2011/03/20/gameboy-rom-dumper/

  3. [...] While cart readers exist, he says that they are hard to find nowadays, so he decided to construct his own using an Arduino. SNES cartridges are relatively complex, so he opted to focus on Gameboy cartridges for the time being. Before attempting to back up save games, he first chose to learn how to communicate with the cartridges in general, by reading the ROM. [...]

  4. [...] While cart readers exist, he says that they are hard to find nowadays, so he decided to construct his own using an Arduino. SNES cartridges are relatively complex, so he opted to focus on Gameboy cartridges for the time being. Before attempting to back up save games, he first chose to learn how to communicate with the cartridges in general, by reading the ROM. [...]

  5. Eric Fairbanks says:

    I finished up my Arduino cartridge reader a few days ago. I was drawn here by the link on HackADay today (well, yesterday now) and I was pleasantly surprised to see that someone had built the same thing using the Arduino at approximately the same time.

    For me, the reader portion is just a step on the way to building a flash cartridge writer so that I can load my own code onto my Gameboys. I wonder, are you planning on using yours as a cartridge writer as well? I plan on documenting my exploits, and if you are interested, I would really like to work together, or at least bounce some ideas back and forth.

    Some visual proof: http://www.cs.uml.edu/~efairban/CartReaderPhotos/

    P.S. Cart adapter PCB is awesome. I’m definitely going to have to use that in later builds.

    • Alex says:

      Yeah it’s a coincidence! I looked around to see if anyone had done one using the Arduino and was surprised that it wasn’t done before, so thought I’d make a large post about mine.

      I was just planning to read/write to the SRAM in the end of the project but I’m interesting in hearing your ideas, I think you should start a blog straight away :). Is this the sort of thing you mean by flash cartridge writer: http://www.reinerziegler.de/readplus.htm#Home%20made%20cartridges

      You should get some bonus points for soldering those wires to the GB connector!

  6. Eric Fairbanks says:

    Yeah. I’m happy to see that other people are working on similar projects. It shows that I’m not wasting my time documenting my project, and it means that there will be more resources available that myself and others can reference.

    I do have a programming blog over at drunkencoders. I think I’m going to work updating it regularly and making it less ugly. I’m currently getting back in the swing of things after an awesome spring break, so there will probably be a few days of delay before I manage to get anything new up there, but I will spread the word when I have something noteworthy.

    Is there any way that I can/should contact you other than commenting on your blog?

    Also, stripping and soldering those 32 wires to the GB cart connector was a grueling experience. I do not recommend it. I look forward to examining your code when I have some free time. I had a surprising amount of trouble writing a serial protocol for communication between Processing and the Arduino, and I am interested to see if you had an easier time using Python.

    • Alex says:

      I like to document everything especially when learning new things, it helps to re-enforce what you’ve learnt (or well for me it does).

      You can contact me on alex@insidegadgets.com or by skype: insidegadgets

      I’ve heard about that Processing software, never really tried it out myself. Checked out your blog, hehe the Processing file called “crappyreader”. If you use the Pyserial then it makes it very easy to Python with serial, I didn’t need to use any checksums.

      The difficulty for me with Python was figuring out how to do things with the serial data because you have to encode it some special way otherwise it prints out all weirdly or just won’t work. The other thing is that you don’t use square brackets or semi-columns with it and it’s all about how you format your code, once you can adapt to that you should be write.

      I would rather prefer to use C though, I did find some simple code that you can kind of “mount” the serial port as a file, I’ll need to test it out one of these days:
      system( “MODE COM2: BAUD=57600 PARITY=n DATA=8 STOP=1″ ) ;
      FILE *port = fopen( “COM2:”, “wb” ) ;
      fprintf( port, “HEADER” ) ;
      fclose( port )

  7. [...] was browsing HackADay and noticed that a mysterious man named Alex had recently completed a Gameboy cartridge reader. “Well,” I said “I am working on a project quite similar to this. Dovoto [...]

  8. Vaati says:

    Awesome gb cart reader! Can I add a link to this from my Gameboy programming blog?

  9. [...] While cart readers exist, he says that they are hard to find nowadays, so he decided to construct his own using an Arduino. SNES cartridges are relatively complex, so he opted to focus on Gameboy cartridges for the time being. Before attempting to back up save games, he first chose to learn how to communicate with the cartridges in general, by reading the ROM. [...]

  10. [...] While cart readers exist, he says that they are hard to find nowadays, so he decided to construct his own using an Arduino. SNES cartridges are relatively complex, so he opted to focus on Gameboy cartridges for the time being. Before attempting to back up save games, he first chose to learn how to communicate with the cartridges in general, by reading the ROM. [...]

  11. [...] Comments « GBCartRead: Arduino based Gameboy Cart Reader – Part 1: Read the ROM [...]

  12. Vaati says:

    Hmmm… How long does this thing take to read one bank?
    I’d like to get an idea as to how fast mine will be in comparison, since all it does currently (since I haven’t made a breakout for the connector yet) is read a 512Kbit (64Kbyte) uv eprom, which takes 2 minutes and 14 seconds, approximately. I think that’s fairly fast, but I have no idea. ;-)

    • Alex says:

      Hi Vaati,

      With the serial communication set to 57,600 and delaying for 50 microseconds, reading the first bank 16Kbytes takes about 16 seconds. I’ve tried removing the 50 microseconds delay but that doesn’t make any changes.

      Now if I disable the serial printing of every byte read, set to 5 microseconds delay and just set it to print to serial once every 1,024bytes (to check it works), it can read the 16Kbytes in 6 seconds.

  13. dext3r says:

    thanks for this post, great read. i just tried to do something similar today with my arduino and an SNES cart. (just reading from the rom itself, no memory mapper stuff)

    im afraid i’ve fried the cart somehow though. i didn’t have any shift registers on hand so i was only going to use A0-A4, just to get a decent amount of bytes to compare to an already dumped rom.

    anyways, thanks again, i’m sure i’ll be reviewing this site when i get some shift regs

  14. Pete says:

    Terrific post! Just letting you know haha. Keep up the awesome stuff.

  15. asetto says:

    greets an salutations! I confess I have no clue what you are talking about once you start in with the hex code the binary or the script.. I do however have an extremely self-absorbing hobby of editing roms. Only problem is I’ve never had a high enough intelligence score to figure out how to connect the cart to my computer, let alone program a user-friendly (aka idiot-proof) method of altering which game a cart plays. (I’ve had this evil desire to swap out an old mario cart or zelda cart and drop a pokemon red/blue rom onto it, or upload my remake of the infamous “pokemon ghost black” onto about five dozen old carts and begin distributing that).. The only thing I run into for an issue with THAT is the amount of work necessary to rewire the carts I would be distributing (free, really) and where pokemon was such a large game with so much save data available to add I would assume it is the best platform to use as a cart one would not mind being rid of. The last problem I run into is that I haven’t the slightest of how to create an IPS patch.. thus no simple patching of files already on carts.
    Any suggestions on a good place to start? Heck I almost think at this point I need to hire myself a computer programmer.
    email is
    lapine.samurai @ gmail.com
    names Jeff
    ‘preciate it!

    • Alex says:

      Hi Jeff,

      The best place to start would be at http://www.reinerziegler.de/readplus.htm. About half way down the page there is a schematic on how to build a GB Cart Flasher with the flashing software and it shows you how to wire up a flash chip to replace the cartridge’s ROM chip.

      Sounds like it might be too much work, so your best bet would be to try and find some home made cartridges to buy which I’m guessing are pretty rare. This website might have what you are after for sale – http://www.smartboy.ugu.pl/index.html

      Good luck!

  16. Rob says:

    Hi,

    I am in the progress of building a gb cartridge reader, but I am not building it with shift registers instead I’m using a IOexpander, got any tips?
    you send your data out via a shift register command, but i’ll be using 16 different/extra pins :)
    any idea’s? Let me say it this way, how would you’ve written the code if your arduino had enough pins?

    Greetings,
    Rob

    • Alex says:

      Hi Rob,

      If I wasn’t using shift registers then I would turn on and off each of the pins by using digitalWrite. The Arduino software lets you read each bit of a variable by using bitRead. I would do a loop for 16 times, read each bit of the address and then do a digitalWrite HIGH or LOW depending if the bit was 1 or 0.

      Below is an example of the “shiftoutAddress” function re-written if our extra IO pins were numbered 20 to 36 and we assume that 20 is bit 0 (A0 in the schematic).

      void shiftoutAddress(unsigned int shiftAddress) {
        for (int z = 0; z <= 16; z++) {
          // We need to read bit 0 of the shiftAddress variable, then bit 1, etc.
          if (bitRead(shiftAddress, z) == HIGH) { 
            digitalWrite(z+20, HIGH); // Now we add 20 to because we start with IO pin 20 
          }
          else {
            digitalWrite(z+20, LOW);
          }
        }
        delayMicroseconds(50);
      }

      Hope that helps,
      Alex.

  17. Ryan Gibson says:

    Hey,

    Great site. Absolutely fantastic detail. Do you have an email I can grab you on? Have a few questions i’d be happy to ask you.

    Thanks

    Ryan

  18. Danish says:

    Extremely impressive. This kind of useful tech is rare, flash carts are not easy to make or easily findable. Great job on this.

  19. [...] started with the project or just review the excellent instructions, visit Alex’s website here.  Share this:TwitterFacebookLike this:LikeBe the first to like this. « Previous [...]

  20. Jeff says:

    Can you clarify what the ‘protection’ resistors are for and why they are optional but recommended?

    • Alex says:

      Hi Jeff,

      They are there to prevent any potential damage to the gameboy cartridge and to the Arduino itself. If something is fails or is faulty then the maximum current is limited by the resistors, say if you connect the wires up incorrect, then without the resistors you could damage it.

      But once you have it hooked up correctly and tested it all works (and won’t make any changes to the wiring), then you should be able to remove the resistors as I believe the gameboy cartridge already has resistors internally.

      • Jeff says:

        Right. I think I get it. Like if you accidentally shorted an output to ground, for instance, then that pin would source (sink?) a bunch of current when it goes high and potentially fry things? I want to use the same method to read an old ROM chip, the 2764.

        One more question: I noticed in your schematic that you’re using TTL shift registers (LC rather than CMOS HC or AHC). Is this a deliberate choice because the gameboy cartridge is TTL? My ROM is TTL, so I’m a little worried about getting the line levels compatible between it and the Arduino. My understanding is that CMOS to TTL should be OK with the same supply voltage, but that TTL to CMOS (ROM data out) may need pull up resistors.

        Thanks for answering!

  21. Josh says:

    Hey,

    Fantastic writeup and build! Thanks very much for the details!
    I have just finished up a ROM/RAM dumper following your instructions and it works great.
    I have a gameboy camera which I am wanting to get some pictures off, though it looks like the camera uses some non-standard MBC and RAM types?
    I have added the ‘POCKET CAMERA’ to the MBC detection with value 0x001Fh at address 0×0147, however it never seems to return this value. The RAM appears to return value
    4 – 1MBit = 128kB = 16 banks on a semi consistent basis.

    The weird thing is that every time I run through the script, I get different values being returned. Not even the title returns properly. It would suggest a problem with my hardware, but any other cartridge at all works fine.
    Below is the output I get from your python script…

    GBCartRead v1.3 by InsideGadgets
    ################################
    Game title… \xf3\xf3MEBOYCAMERA
    MBC type… Not found
    ROM size… 1MByte (64 banks)
    RAM size… Not Found

    [/code/

    Do you happen to have any experience or further suggestions for gameboy camera support?

    Additionally, a minor [aesthetic] adjustment for your script for linux users. After you perform a sys.stdout.write(‘#’), place a sys.stdout.flush() in the next line, as the buffer is not automatically flushed unless an input is requested I believe.

    • Josh says:

      Making some changes to the arduino sketch, I have added a type 4 ramsize, which has 16 rambanks of 8K each. I couldn’t find any information on RAM bank switching, so I am unsure if your code will support 16 ram banks.
      I normally wouldn’t have any issues with just trying it out, however I do not want to lose the data on my GB camera, so I need to know that it won’t erase the data before I try it.

      • Josh says:

        Sorry for the triple post but I thought I would share some things I have discovered experimenting with the Camera.

        It appears that the camera’s hardware is performing some funny stuff. The first read from the Cart does produce the correct values, but subsequent reads appear to have some offsets applied to memory addresses?
        If I run the script a second time, I get incorrect header information. The only way to get correct header info again, is to plug the camera back into an actual gameboy and do a power cycle.
        As the script you have performs a header read at launch, then another header read when you issue a rom/ram dump, the dump was failing each time.
        One of two things had to be done to get it to work, either modifying your script to not perform the ‘HEADER’ request at the start, OR by starting the script to check the header, then when at the main menu pull the cartridge from the reader and power cycle it in a gameboy, then replug it back in. The ram dump now works correctly.
        Additionally, I modified the sketch as stated above, simply by adding

        if (ramSize == 4) { ramBanks = 16; }
        

        at the end of the header read. As well as modifying the line

            if (ramSize == 2 || ramSize == 3 { endaddr = 0xBFFF; } // 8K RAM
        

        TO

            if (ramSize == 2 || ramSize == 3 || ramSize == 4) { endaddr = 0xBFFF; } // 8K RAM
        

        Further still, the .sav file produced will not work with any emulator I have tried [again, due to some hardware controls in the camera's ROM], they just appear as garbled black images.
        There is a tool available however which extracts the bitmap’s from the savefile, however. You can get it at http://drx.a-blast.org/~drx/projects/gameboy/howto/gb_cam_dump.zip

        It is a windows application but it does work under WINE for any other linux users out there.

        • Alex says:

          Hi Josh, Thanks for all the information, I’m sure this will be helpful for others. With the .sav and the gb_cam_dump program, did it actually work – were you able to view your photos?

          • Josh says:

            Hi Alex, no worries at all!
            With all of the hard work you have shared, I am more than happy to share some extra findings!

            And yes, using the gb_cam_dump program, my photo’s were successfully extracted from the .sav file from the camera cart [in all of their 1996 glory! :p ]

            Also, to make sure I was reading the cartidge correctly, the following additions were added to the python script, simply adding the Camera MBC and 128K RAM types.

            Add the following to the end of the MBC detection before “else”

            elif (cartridgeType == 252):
                print ('POCKET CAMERA')
            

            And the following to the end of the RAM type detection

            elif (ramSize == 4):
                print ('128 KBytes (16 banks of 8Kbytes)\n')
            
  22. Squiddles says:

    I found this when I got interested in dumping my old GBC carts, but I’ve got a question. Since I don’t have a spare Gameboy, and it would probably be too expensive to buy another, would I be able to use this as a header?: https://dx.com/p/repair-parts-replacement-gba-game-cart-slot-for-nds-lite-37787

    Or would it be recommended to find a GBC header?

    • Alex says:

      I’m glad you posted that because I think that will work! You will need to remove the 2 plastic ends of the header in order to fit in GB/GBC carts as they differ a little from GBA carts. I have a NDS and to fit my GB cart (which they don’t offer backwards compatibility for) I had to remove the plastic ends of the GB cartridge itself (because I didn’t want to modify the NDS header) as it wouldn’t physically fit in the NDS slot 2. http://www.insidegadgets.com/wp-content/uploads/2012/09/IMG_2511.jpg

      I think I’ll buy some headers too to try out for myself and if it works it will save me having to destroy gameboys :)

  23. Ness says:

    Hello!
    This is all extremely impressive. This kind of thing always starts to make my head spin, so what you’ve done seems like magic to me. I have sort-of a weird question – I ended up with a bootleg copy of a GBA game, with a translation so bad it’s hilarious. It also doesn’t allow me to save. I’d like to make a ROM of this bootleg cart so that I can both save the game and progress, share it with my friends, and take nice screen shots of the funniest bits. Would this work for it?

    • Alex says:

      Hi Ness,

      That sounds like a strange game you’ve got, unfortunately this product only works for Gameboy / Gameboy Colour games although the concept would be the same for GBA games.

  24. bernd says:

    Hey, very cool project. How about the logic voltage of the gameboy? Can I drive the adress bus with 3,3 volt? What will be the data voltage be like?

  25. billy says:

    Are you anywhere near new York? I want to preserve my pokemon save file

  26. Benny says:

    Hey,
    Can I also use 74HC595N shift registers instead of 75LS595N? And if I can use 74HC595N shift registers which resistors do I have to use instead of 470 Ohm?

    • Alex says:

      Hi Benny,

      Yes you can use just about any 595 shift register. For the resistors, they are mostly for protection, in the case that something may short out that it won’t deliver too much current. Anything from 220 Ohm to 10K should be fine.

  27. Benny says:

    Hey,
    I’ve built the reader and it’s working. I get displayed the correct name of the game, rom size, rom ram, cartridge type and the data bytes on the serial monitor of the arduinoIDE.
    But the .exe file and the python script don’t work. If I start the exe. file, I get an error: “msys-1.0.dll not found” and if I start the python script on ubuntu, I get also an error: “name ‘ascii’ is not defined”?
    Do you have any idea?
    tia, Benny

    • Alex says:

      Hi Benny,

      Could you install MinGW to run the exe file: http://sourceforge.net/projects/mingw/

      Are you using Python 3.1?

      • Benny says:

        Hey,
        Now I’ve used Python 3.3 and installed the serial module for Python 3.x but if I start the script, it prints:
        GBCartRead v1.3 by InsideGadgets
        ################################
        and then nothing happen…I’ve not tested the Windows version with MinGW yet.

        • Benny says:

          Hey,
          I’ve installed MinGW but I get the same error as before…

          • Alex says:

            I seem to have also had this issue now on a different computer. I installed MinGW and also msys_base when it came up with the package list. It looks like the msys-1.0.dll is in C:\MinGW\msys\1.0\bin but it’s not picking it up. Please copy C:\MinGW\msys\1.0\bin\msys-1.0.dll to the C_version directory.

            Also note that if you are changing between the C and Python version that you need to upload the Arduino sketch inside the C_verison or Python_version folders.

            • Benny says:

              Hey,
              thanks! Now I can dump GB Games but no GBC Games? The c-programm sometimes get information about the the size, ram and cartrigde type but if I start the dump nothing happen (there are no #’s) and the game title is strange…

              • Alex says:

                Which Gameboy cartridges are giving you those results? I recall that sometimes the C program would do that when I was writing it but I thought I’d fixed it up.

                Have you tried the Python version?

                • Benny says:

                  The python script doesn’t work so I will keep working with the c program ^^ I can’t dump Pokemon Yellow, Silver, Gold and Cristal.

                • Benny says:

                  I’ve got python to work on windows and I can dump GB games but not GBC games…so the same problem as with the c programm…

                  • Alex says:

                    Very odd, I have Pokemon Red and can dump it without problems. Could you check in the Arduino serial if you write HEADER what it brings up?
                    Also try the READROM command too?

                    • Benny says:

                      I’ve tried both commands on my windows pc…e.g. HEADER return incomplete data but on my linux pc I got complete data…this problem only occurs with gbc games…

                    • Benny says:

                      Hey,
                      I’ve written a java program that can dump gb and gbc games. I’ve also changed a few things in the sketch. Instead of the task “HEADER” I’ve just used 1 as byte.

                    • Alex says:

                      Hi Benny,

                      So it sounds like the Python program or C program just didn’t work correctly or would you say it was the Arduino sketch?

  28. Benny says:

    Hey,
    the sketch works without problems. But I’ve changed a few things (the tasks: ‘A’ instead of ‘HEADER’) and that only because of my java programm. The c program and the python script work fine. I think my windows pc has a bad usb port and on my linux pc the python script can’t send data through the serial port.
    Now I’m making a gui for my program. When I’m done I’ll send you my program with the sketch ^^

  29. Frode says:

    Nice project!

    I was thinking on something, and that was to maybe use a pair of 74HC590 (rigged as 14-bit counters) instead of the shift registers. Then use a pair of “analog in” outputs to controll the two high address lines. This would potentially speed up sequencial reads/writes signifficantly.

    And on the note of speed; Most Mask ROM typically have response times of less than 400ns, so a wait of 50ms is quite a bit of overkill.

    • nadohha says:

      I am curious about the 50ms as well. From looking at the game-boy address pins with a digital analysiser they seem to change a lot faster than 50ms.

      Correct me if I am wrong but I am guessing the 50ms is actually there to allow adequate time for the serial write function “Serial.write(bval);” to dump the data to PC before latching in a new address and reading the ROM data lines again.

      • Alex says:

        Hi Nadohha,

        I don’t seem to see the 50ms delay, but you might be referring to the delayMicroseconds(50). I agree it’s not needed when running on the Arduino software, because I later found out when you do “digitalWrite” it takes a few microseconds to do it (this probably applies to other functions) where as direct AVR code would do it in 1-1.5x the clock cycle.

        If you were to run this code on an AVR directly without Arduino code, then as Frode says you might need to delay 400ns.

    • Alex says:

      Hi Frode,

      That sounds like a good idea, it should save you some overhead from not having to write to the shift registers. I’m interested to see how much faster it goes.

  30. nadohha says:

    Great project Alex! I recently sat down to attempt this myself, although I used an Arduino mega 2560 since I had one handy and it has 53 digital pins, removing the requirement for the shift registers. I actual stumbled across your guide after finishing my cart dumper, when researching for some information about building a flash cart, it seem liked the logical next step.

    However, I ran into complications and end up back on the net hoping to find somebody else who attempted the same. I wasn’t at all surprised when I found your project “Emulating the Nintendo logo on a Gameboy”, given I had previously found your “ROM dumper”. I was happy to see you ran into most of the same problems that I am having, (issues with noise and speed). After reading about your attempts I switched up to a 40Mhz microprocessor and I am now clearly seeing a pattern in the GB address bus (A0-A7) during boot up. In fact, the address patterns matches the address of the Nintendo logos location in ROM.

    However, in-between these addresses I am seeing random addresses being put on the bus. This has me stumped!! I tried adding some 47Khz pull down resistors on the address pins in case they were floating and I also added in a filter capacitor across the Vdd and GND pins. This helped smooth the CLK output of the GB but the address pins still seem to be random.

    Did you get any further with this project? And do you have any thoughts on why this “noise” is on the address pins. I have ordered a digital analysiser so that I can take a closer look at the timing logic. I am now assuming the GB writes the address pins and reads the data pins at a known time and that the key to intercepting these is knowing this timing but that doesn’t seem like a very reliable system.

    Any thoughts would be great!

    • Alex says:

      Hi Nadohha,

      However, in-between these addresses I am seeing random addresses being put on the bus.

      I didn’t do anymore analysis into the random addresses, have you seen if they are truly random at every time you power the gameboy on?

      Did you get any further with this project? And do you have any thoughts on why this “noise” is on the address pins.

      I haven’t worked any more on this project, can you try to check the reset, rd and wr pins if there is any change when the randomness appears? It could be that the Gameboy is using the address lines for communication with other devices on board.

      I have ordered a digital analysiser so that I can take a closer look at the timing logic.

      Hoping it’s at least 20MHz or more, the more the better, because I didn’t know at the time but for example to capture a 1MHz signal you need at least 4x the bandwidth, so a analyser that can do 4 Megasamples per second.

      I am now assuming the GB writes the address pins and reads the data pins at a known time and that the key to intercepting these is knowing this timing but that doesn’t seem like a very reliable system.

      It also can write to the data pins too so be aware of that. I would say check the RD, WR and Reset pins to see if there is a better way to check when it’s going to ask something of the gameboy cart.

    • Frode says:

      Are you sure those addresses are random?

      When the Gameboy or GBC boots, it excecutes a small boot program stored inside the CPU itself, between address 0000h and 0100h (and 0200h-08FFh in addition for the GBC). This program sets up everything, copies the logo and displays it, and it also checks the checksum of the logo. The final thing this code does is to disable itself, and it can not be re-enabled without a power-cycle. It took a good decade before someone figured how to dump this bit of ROM, and I guess the external bus is tristated when it’s accessed due to sniffing protection.

      The GB and GBC runs a variation of the Z80 CPU, so I expect the timing to be much similar to those. Try the datasheet or one of the many manuals written for it if you’re interested in timing diagrams.

      • Nadohha says:

        Hi Frode,
        Thanks for your input. Let me correct myself, random was a terrible choice of word. What I really meant was that from the perspective of the address pins I was expected to see the GB requesting the memory address between (0000h and 0100h). A sequential, ordered read. However, instead I was seeing some form of ordered memory read hidden among what appeared to be other “random” memory address. This is similar to what Alex saw when he attempted to emulate the Nintendo logo “http://www.insidegadgets.com/2011/04/23/emulating-the-nintendo-logo-on-the-gameboy/”. In fact I suspect that these “random” address as I called them as in fact deterministic. Alex suggested that the GB probably uses the address pins for more than just talking to the cartridge.

        I eventually figured out how to read the address lines correctly. It looks like the GB sets the CS pin low (active low) to lock in the correct address. There is a window of 700ns here to read the address lines, a challenge but achievable with a fast enough microcontroller. It seems you then have a further 6us to set the data pins with the appropriate data.

  31. Nadohha says:

    Alex,

    Thanks for the ideas. I finally managed to get my hands on a digital logic analyser. I probed the WR, RD and reset pins. Nothing unexpected there, the WR pin goes high followed by the RD pin going low, as you would expect when the GB wants to read from the ROM chip. The reset pin just goes high and then stays that way. I also checked the CLK pin, thinking that it might be useful in determining when the GB reads the address pins. Perhaps its not useful but I did notice the address pin seem to change slightly after the rising edge of the clock. However, I couldn’t determine a decent time to read the address pins based on the clock alone.

    I then checked the CS pin which looked a lot more promising. The CS drops low for a small amount of time roughly 700ns before going high again for a further 6us. This pattern seems to repeat itself. This was interesting as my initial assumption was there must be some trigger that the GB uses to lock in the address pins and to read the corresponding data back.

    I realised that if you read the address pins on the falling edge of the CS pin then they make sense. The GB seems to be requesting sequential bytes of memory. The relatively long 6us delay between these low periods might be the time allotted for the cartridge to set its data pins before the GB attempts to read back the data. I have more testing to do to confirm this.

    Next I am going to try and write some code using an interrupt routine that reacts to the falling edge of the CS pin. Hopefully I can read the 16 address pins fast enough in the 700ns window. Then I will have 6us to look up this address in flash memory and serve the bits back to the GB on its data pins.

    I will let you know how I get on, thanks again for the help.

  32. Ben Depew says:

    Can you help me with this? When i run the python it gives me

    Traceback (most recent call last):
    File “C:\Users\ben\Desktop\iG_GBCartRead_ROM_Only_v1.0.py”, line 14, in
    lineascii = ascii(line)
    NameError: name ‘ascii’ is not defined

  33. Ben Depew says:

    O, I had python 2.7. Ok thanks.

  34. Ben Depew says:

    now i have another problem. When i run python i get

    GBCartRead v1.3 by InsideGadgets
    ################################
    Game title… b’\xff\xff\xff\xff\xff\xff\xff\xff\xff’
    MBC type… Traceback (most recent call last):
    File “C:\Users\ben\Desktop\GBCartRead_v1.3.py”, line 19, in
    cartridgeType = int(cartridgeType[2:(len(cartridgeType)-5)])
    ValueError: invalid literal for int() with base 10: ”

Leave a Reply