Jump to content
IGNORED

Colecovision Cartridge reader (read > 32k?)


OriginalJohn

Recommended Posts

Hi Team,

 

I was hoping someone could help me with this, as I've hit a wall.

 

I recently built this project and it works perfectly with standard Colecovision cartridges: https://www.instructables.com/Arduino-ColecoVision-Cartridge-Reader/

 

I wanted to expand the capabilities beyond the 32k realm (for the halfmega and other cartridges) and for the last couple of months I've been researching/changing values/researching/changing code etc, but have been largely unsuccessful.  What is happening at the moment is that I've changed the cartridge size to 128k and trying to do a 'straight' read just using a loop that runs through addresses, I've tried breaking the 128k up into 32k 'chunks' and switching the chipselect, but every time I get the same result - 32k blocks which repeat over and over up to the 128k loop ending.

 

I thought that in the vs code that the actual results that are sent to the windows program was just looping the buffer but after analysis, don't see how the code might do this.

 

I would be grateful if anyone could take a look at the code (in the arduino .ino file and vscode) and make any recommendations on how I might get this to happen, or offer any suggestions, it would be greatly appreciated.

 

Thanks!

Link to comment
Share on other sites

the bigger cartridge uses bank switching.

the arduino code that read the cartridge only read.

But i think on these cartridges , you have to write to the cartridge to switch the bank.

the colecovision always see only 32k on the cart  even if it is a 128k one... but something in the cart , when you write to a given address switch the bank and will replace one part of the 32k by another one.

Once it is switched, you can read again the 32k (or just the part that has been switched) and you will get new data.

How to perform the switch depends on the type of super cart.  

 

I can not help much here, but i guess some others will be able to help you with more precise explanation.

 

 

  • Like 1
Link to comment
Share on other sites

On 9/22/2021 at 8:50 AM, NIAD said:

You would have to search for the CV MegaCart tech files that were made available years ago to add proper support much the same way AtariMax and some emulator authors had to add support to there products for games that use the MegaCart PCB. 

I did try to search for the files, but it appears that they are obscure.   There is, however the thread I linked below.   I'm not sure if this is the doc you are referencing?  If not, does anyone have the original files being referenced?

 

Thanks!

 

Link to comment
Share on other sites

On 9/21/2021 at 2:01 PM, OriginalJohn said:

Hi Team,

 

I was hoping someone could help me with this, as I've hit a wall.

 

I recently built this project and it works perfectly with standard Colecovision cartridges: https://www.instructables.com/Arduino-ColecoVision-Cartridge-Reader/

 

I wanted to expand the capabilities beyond the 32k realm (for the halfmega and other cartridges) and for the last couple of months I've been researching/changing values/researching/changing code etc, but have been largely unsuccessful.  What is happening at the moment is that I've changed the cartridge size to 128k and trying to do a 'straight' read just using a loop that runs through addresses, I've tried breaking the 128k up into 32k 'chunks' and switching the chipselect, but every time I get the same result - 32k blocks which repeat over and over up to the 128k loop ending.

 

I thought that in the vs code that the actual results that are sent to the windows program was just looping the buffer but after analysis, don't see how the code might do this.

 

I would be grateful if anyone could take a look at the code (in the arduino .ino file and vscode) and make any recommendations on how I might get this to happen, or offer any suggestions, it would be greatly appreciated.

 

Thanks!

128k and up bank are 16k   .. you need to manage a14 a15 a16 a17 a18 to read them...  you cant only put en_80,A0,C0,E0 low and read...

Link to comment
Share on other sites

Megacart splits the cartridge space into two 16k segments - the first 16k (at 0x8000) is fixed and comes from the end of the EPROM. The second 16k (at 0xC000) is banked, and is selected by accessing memory locations 0xFFC0 through 0xFFFF (just a read is fine, the address sets the page). That gives you 64 possible banks for a total maximum size of 1MB. It's "reversed" in that 0xffff will map the first 16k of the EPROM, 0xfffe maps the second, etc. Since it includes the fixed bank, you can read the whole thing by banking from 0xFFFF through 0xFFC0 and saving the 16k at 0xC000 each time. There's no easy way to tell the size of the ROM, you can check for duplication after saving. 

 

Make sure you don't read those addresses while saving, just write zeros to the file. If you read them, the bank will change.

 

The info in that thread above came from my work. Can't speak to the original files, but I wrote a whole bank switching tutorial which is here (you can just read the PDF included): 

https://harmlesslion.com/cgi-bin/onesoft.cgi?3

 

SGM carts use a different scheme. The cartridge space is split into 4 8k segments (at 0x8000, 0xA000, 0xC000 and 0xE000) and potentially includes an EEPROM. There are three or four separate registers at the top of the memory space (0xFFFC and up) which are written to to select the page you want in each segment. There's only scattered public information about this scheme.

 

Not aware of any other common schemes.

Edited by Tursi
Link to comment
Share on other sites

  • 5 weeks later...

So I took a stab at it and wanted to check to see if my logic was sound, but ultimately, the behavior was the same (bank not being switched).

 

 

  unsigned int bankAddress = 0xFFFF; //Set Starting Bank switch address.
  int counter = 1;  //Set Counter for loop

  //Loop 8 Times for 128k 
  for (counter; counter < 9; counter++)
  {
    SetAddressMegaCart(bankAddress); //set address to read in order to change the bank
    ReadDataLines(false); //Read address but do not output data to the console

    for (unsigned int readRange = 0xC000; readRange < 0x10000; readRange++) //Is the range correct? 16k from 0xC000?
    {
        SetAddressMegaCart(readRange);
        ReadDataLines(true); //write out to the console
    }

    bankAddress--;  //MegaCart Appears to bank switch in reverse so reduce to new Address.
  }

 

Note For some reason on the for loop, arduino errors when I change to counter == 8

 

void SetAddressMegaCart(unsigned int address)
{
    SelectChip(-1);
  
    digitalWrite(gcStorageRegisterClock, LOW);
    
    shiftOut16(gcSerialAddress, gcShiftRegisterClock, MSBFIRST, address);  

    digitalWrite(gcStorageRegisterClock, HIGH);
    
    SelectChip(0);
}

 

void shiftOut16(int dataPin, int clockPin, int bitOrder, int value)
{
 
  shiftOut(dataPin, clockPin, bitOrder, (bitOrder == MSBFIRST ? (value >> 8) : value));  

  shiftOut(dataPin, clockPin, bitOrder, (bitOrder == MSBFIRST ? value : (value >> 8)));
}

Link to comment
Share on other sites

readRange is a 16-bit integer but you're comparing it to a 17-bit value.  Without the 17th bit, it's truncated to 0, meaning that your loop runs "while readRange < 0" which is never.

 

If you're trying to read C000-FFFF, you could start at C000 and keep going until readRange wraps around to 0.  In other words, for(readRange=0xc000; readRange!=0; readRange++)

 

Also, why write a new function to set the address?  The original one does what the ColecoVision does.  This one forgets to set a proper select line.

Really, you should just use the existing program all as is, except for modifications to ReadCartridge.  Here's a rough cut:

void ReadCartridge()
{
  const unsigned int baseAddress = 0x8000;
  unsigned int bankAddress = 0xffff;
  const unsigned int bankStart = 0xc000;
  const unsigned int bankSize = 0x4000;
  const int numBanks = (<size of cartridge> - 0x8000 + bankSize - 1) / bankSize;
  
  Serial.println("START:");
  
  // Read Cartridge (cartridge is 32K, each chip is 8k)
  SetAddress(bankAddress--);
  for (unsigned int currentAddress = 0; currentAddress < 0x8000; currentAddress++) 
  {
    SetAddress(baseAddress + currentAddress);
    ReadDataLines();  
  }
  
  // Read additional banks
  for (int bank = 0; bank < numBanks; bank++)
  {
    SetAddress(bankAddress--);
    for (unsigned int currentAddress = bankStart; currentAddress < bankSize; currentAddress++) 
    {
      SetAddress(baseAddress + currentAddress);
      ReadDataLines();  
    }
  }
  
  Serial.println(":END");
}

 

Edited by ChildOfCv
  • Like 1
Link to comment
Share on other sites

11 hours ago, ChildOfCv said:

readRange is a 16-bit integer but you're comparing it to a 17-bit value.  Without the 17th bit, it's truncated to 0, meaning that your loop runs "while readRange < 0" which is never.

 

If you're trying to read C000-FFFF, you could start at C000 and keep going until readRange wraps around to 0.  In other words, for(readRange=0xc000; readRange!=0; readRange++)

 

Also, why write a new function to set the address?  The original one does what the ColecoVision does.  This one forgets to set a proper select line.

Really, you should just use the existing program all as is, except for modifications to ReadCartridge.  Here's a rough cut:


void ReadCartridge()
{
  const unsigned int baseAddress = 0x8000;
  unsigned int bankAddress = 0xffff;
  const unsigned int bankStart = 0xc000;
  const unsigned int bankSize = 0x4000;
  const int numBanks = (<size of cartridge> - 0x8000 + bankSize - 1) / bankSize;
  
  Serial.println("START:");
  
  // Read Cartridge (cartridge is 32K, each chip is 8k)
  SetAddress(bankAddress--);
  for (unsigned int currentAddress = 0; currentAddress < 0x8000; currentAddress++) 
  {
    SetAddress(baseAddress + currentAddress);
    ReadDataLines();  
  }
  
  // Read additional banks
  for (int bank = 0; bank < numBanks; bank++)
  {
    SetAddress(bankAddress--);
    for (unsigned int currentAddress = bankStart; currentAddress < bankSize; currentAddress++) 
    {
      SetAddress(baseAddress + currentAddress);
      ReadDataLines();  
    }
  }
  
  Serial.println(":END");
}

 

The one change you may want to make is to change bankSize to 0x3FC0, and add 64 bytes of padding after the additional bank read. Technically it won't hurt much, but the last 64 bytes of each bank will be semi-random garbage as the bank changes every time you access one of those bytes.

Should still end up with a working cart image, mind, so I guess technically that's only a concern for cleanliness. ;)

 

I took a look at my own code, and I see I dump banks 0xFFC0 through 0xFFFF instead of the reverse order that I recommended, so that may have been incorrect advice. Take that into consideration once you get the banks actually paging...

 

Link to comment
Share on other sites

On 10/28/2021 at 1:40 PM, ChildOfCv said:

readRange is a 16-bit integer but you're comparing it to a 17-bit value.  Without the 17th bit, it's truncated to 0, meaning that your loop runs "while readRange < 0" which is never.

 

If you're trying to read C000-FFFF, you could start at C000 and keep going until readRange wraps around to 0.  In other words, for(readRange=0xc000; readRange!=0; readRange++)

 

Also, why write a new function to set the address?  The original one does what the ColecoVision does.  This one forgets to set a proper select line.

Really, you should just use the existing program all as is, except for modifications to ReadCartridge.  Here's a rough cut:


void ReadCartridge()
{
  const unsigned int baseAddress = 0x8000;
  unsigned int bankAddress = 0xffff;
  const unsigned int bankStart = 0xc000;
  const unsigned int bankSize = 0x4000;
  const int numBanks = (<size of cartridge> - 0x8000 + bankSize - 1) / bankSize;
  
  Serial.println("START:");
  
  // Read Cartridge (cartridge is 32K, each chip is 8k)
  SetAddress(bankAddress--);
  for (unsigned int currentAddress = 0; currentAddress < 0x8000; currentAddress++) 
  {
    SetAddress(baseAddress + currentAddress);
    ReadDataLines();  
  }
  
  // Read additional banks
  for (int bank = 0; bank < numBanks; bank++)
  {
    SetAddress(bankAddress--);
    for (unsigned int currentAddress = bankStart; currentAddress < bankSize; currentAddress++) 
    {
      SetAddress(baseAddress + currentAddress);
      ReadDataLines();  
    }
  }
  
  Serial.println(":END");
}

 

 

I don't know if it's a quirk of arduino or what, but trying this code results in a 32k read and then stops... no matter what I hardcode the number of banks to, use a while statement, etc.  Going a little further, removing the first for loop results in no data being read.    For some reason the last for loop to read additional banks isn't executing...

Link to comment
Share on other sites

44 minutes ago, OriginalJohn said:

 

I don't know if it's a quirk of arduino or what, but trying this code results in a 32k read and then stops... no matter what I hardcode the number of banks to, use a while statement, etc.  Going a little further, removing the first for loop results in no data being read.    For some reason the last for loop to read additional banks isn't executing...

You could add a print statement to print numBanks.  Maybe the compiler overflowed the math.  That would be stupid, but it's possible.

 

Anyway, if you know the number of banks, you can just set numBanks to that number and skip the fancy math.

Link to comment
Share on other sites

9 hours ago, OriginalJohn said:

 

I don't know if it's a quirk of arduino or what, but trying this code results in a 32k read and then stops... no matter what I hardcode the number of banks to, use a while statement, etc.  Going a little further, removing the first for loop results in no data being read.    For some reason the last for loop to read additional banks isn't executing...

Not sure why your loop fails, but the first 32k read will get random data from >C000 to >FFFF, as the code never sets a bank before reading it. Only the data from >8000 to >BFFF is fixed, and there's no defined reset state (so the active bank at powerup needs to be considered random).

 

Agreed that it's time to break out the printf debugging ;)

 

Link to comment
Share on other sites

@Tursi:  It sets the bank address before the first loop by addressing bankAddress which is 0xffff at the time.  But I can still weave that into the second part anyway.

 

Oh for crying out loud.  There's the problem.  Okay, let's correct this @#$% and add the slight optimization mentioned above.

void ReadCartridge()
{
  const unsigned int baseAddress = 0x8000;
  unsigned int bankAddress = 0xffff;
  const unsigned int bankStart = 0xc000;
  const unsigned int bankSize = 0x4000;
  const int numBanks = (<size of cartridge> - 0x4000l + bankSize - 1) / bankSize;

  Serial.println("START:");

  // Read Cartridge (cartridge is 32K, but 16K is banked.)
  for (unsigned int currentAddress = 0; currentAddress < 0x4000; currentAddress++) 
  {
    SetAddress(baseAddress + currentAddress);
    ReadDataLines();  
  }

  // Read additional banks
  for (int bank = 0; bank < numBanks; bank++)
  {
    SetAddress(bankAddress--);
    for (unsigned int currentAddress = 0; currentAddress < bankSize; currentAddress++) 
    {
      SetAddress(bankStart + currentAddress);
      ReadDataLines();  
    }
  }
  
  Serial.println(":END");
}

 

Edited by ChildOfCv
  • Like 1
Link to comment
Share on other sites

Thanks for the code correction - I appreciate it.     I wish that whoever has the halfmega (or MEGA) specification would post it because I feel It's into the realm of guessing at this point.   On the upside, the hex is no longer repeating, but without a 'known good' dump to compare it to I'm not going to know if the bankswitch dumps are correct.   I tested with the code posted and then changing the bankAddress = 0xffc0 and changing the bankAddress to ++, and while I get what seems like a full dump, the saved file does not work in colem...  

 

You guys are the best - thanks for the assist with this!  I'm determined!

Link to comment
Share on other sites

10 hours ago, OriginalJohn said:

Thanks for the code correction - I appreciate it.     I wish that whoever has the halfmega (or MEGA) specification would post it because I feel It's into the realm of guessing at this point.   On the upside, the hex is no longer repeating, but without a 'known good' dump to compare it to I'm not going to know if the bankswitch dumps are correct.   I tested with the code posted and then changing the bankAddress = 0xffc0 and changing the bankAddress to ++, and while I get what seems like a full dump, the saved file does not work in colem...  

 

You guys are the best - thanks for the assist with this!  I'm determined!

Well if you get different data for each page, that's at least a good sign that things may be working.  But it's also possible that emulators require some special format to work correctly, similar to the iNES format for Nintendo ROMs.

 

Edit:  Just looked at ColEm.  It seems to expect the banked segments first, and the last 16K is the unbanked part with the 55 AA signature.  So maybe putting the "additional banks" loop first will give you a working ROM.

Edited by ChildOfCv
Link to comment
Share on other sites

On 11/8/2021 at 4:39 PM, OriginalJohn said:

Thanks for the code correction - I appreciate it.     I wish that whoever has the halfmega (or MEGA) specification would post it because I feel It's into the realm of guessing at this point.   On the upside, the hex is no longer repeating, but without a 'known good' dump to compare it to I'm not going to know if the bankswitch dumps are correct.   I tested with the code posted and then changing the bankAddress = 0xffc0 and changing the bankAddress to ++, and while I get what seems like a full dump, the saved file does not work in colem...  

 

You guys are the best - thanks for the assist with this!  I'm determined!

Page order is probably the wrong way around in the resulting file. Try flipping the order of the 16k pages.

 

You might also be able to tell in a hex editor. In a Megacart dump, the AA55 (or 55AA) header bytes should be exactly 16kb before the end of the file.

 

We aren't really guessing. Lots of us have created Megacart carts. I also specified the Megacart support for the Phoenix and implemented the loader.

 

Link to comment
Share on other sites

19 hours ago, ChildOfCv said:

Edit:  Just looked at ColEm.  It seems to expect the banked segments first, and the last 16K is the unbanked part with the 55 AA signature.  So maybe putting the "additional banks" loop first will give you a working ROM.

This is correct. This is the order the data is stored in the EPROM.

 

Link to comment
Share on other sites

Thanks everyone for your input.   When hex editing 'normal' coleco titles, the AA55 (U*) or conversely 55AA's always show up at the beginning of the file.  If this signature is buried somewhere at the end of the file, wouldn't that cause the cartridge not to load?  I did try this suggestion (move the first loop to the end), however, it didn't work.

 

So to confirm the current code by pseudocode, the first 8k from the starting (unbanked) bank should be read to establish the header.   A bank switch is performed at 0xffc0 and the next 8k is read.  Next the bank is incremented.  8k is read and the bank is incremented. The process of incrementing the bank and reading 8k is performed until the end of the cartridge is reached (131072 bytes). 

const int numBanks = (<size of cartridge> - 0x4000l + bankSize - 1) / bankSize;

 

Does this sound correct?

 

 

Link to comment
Share on other sites

A few things...

 

Banks are 16k, not 8k. Don't get confused by the chip select lines, they are not used individually in the megacart.

 

Start reading from the bank switched in at 0xffff, and count down. The entire order needs to be reversed, not just the moving the read of the fixed bank.

 

The fixed bank needs to appear at the end of the image because that is how it is read. The last 16k of the EPROM will appear at 0x8000 in ColecoVision ROM space. The first two bytes at 0x8000 must be the 55AA/AA55 signature for the cartridge to be detected.

 

The fixed bank is /also/ a bank. It can be mapped at 0xC000, though this is unlikely to be useful.

 

Don't fuss yourself with the size to start. Dump the entire space and get it working. A too-big dump will still boot. Worry about size when you understand the concept better. One thing with the size is you will probably need to change the starting page to accommodate it. The addresses /should/ wrap correctly but worry about one thing at a time.

 

So your psuedo code should be:

- loop bank address from 0xffc0 to 0xfffe inclusive

-- map in the bank

-- read 16k minus 64 bytes from 0xc000 to 0xffbf for the image

-- pad the image by 64 bytes (to avoid reading the bank switch registers)

-- loop

- end of loop

- read 16k from 0x8000 to 0xbfff for the final fixed block

 

That will give you a 1MB bootable image that probably repeats a few times, since it's overdumped. Once that works, then you can worry about the size calculation and which page that starts you at.

 

 

Link to comment
Share on other sites

For what it's worth, though, a 128k cart will have 8 pages, so 0xffff, 0xfffe, 0xfffd, 0xfffc, 0xfffb, 0xfffa, 0xfff9, and 0xfff8. You'd read 0xfff8 through 0xfffe as banks at 0xc000, and the last 16k from 0x8000.

 

Number of pages is just cartSize/16384, or if you want it to be a define like above, cartsize/pagesize. You are reading from hardware, so you are never going to get a megacart that is not a multiple of 16384 bytes.

 

 

Link to comment
Share on other sites

Yeah.  In fact, just dump 8000-c000 first into a 16K cartridge to make sure ColEm will at least load it.  If the cartridge is recognized at all, it will of course crash (probably immediately), but at least it won't complain about the cartridge.  Now you can build on that.  Try dumping one bank, followed by the 8000-c000 and see if ColEm still likes it.

 

But as @Tursi said, ColEm definitely expects the last 16K of a megacart to be the part with the signature (the always-swapped-in bank).

 

However, ColEm does count upwards in banking from FFC0.  Here's the relevant code:
 

    if(MegaCart)
    {
      if(A>=0xFFC0)
      {
        /* Set new MegaCart ROM page at C000h */
        MegaPage   = (A-0xFFC0)&(MegaSize-1);
        ROMPage[6] = ROM_CARTRIDGE + (MegaPage<<14);
        ROMPage[7] = ROMPage[6]+0x2000;
      }
    }

 

Edited by ChildOfCv
Link to comment
Share on other sites

14 hours ago, OriginalJohn said:

Thanks everyone for your input.   When hex editing 'normal' coleco titles, the AA55 (U*) or conversely 55AA's always show up at the beginning of the file.  If this signature is buried somewhere at the end of the file, wouldn't that cause the cartridge not to load?

Here's the code that looks for the signature at the end of the file instead.
 

  /* MegaCarts have magic number in the last 16kB page */
  if(!P&&(Size>0x8000))
  {
    if(fseek(F,(Size&~0x3FFF)-0x4000,SEEK_SET)>=0)
      if(fread(Buf,1,2,F)==2)
        if(((Buf[0]==0x55)&&(Buf[1]==0xAA))||((Buf[0]==0xAA)&&(Buf[1]==0x55)))
        {
          I = 1;
          P = ROM_CARTRIDGE;
        }

    rewind(F);
  }

The variable "I" is 1 if it detected a megacart.  It starts at 0.

Edited by ChildOfCv
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...