Ofcourse, the first thing I needed to check was if the entire idea with the STM acting as ROM would work. I started with hardcoding a test cartridge into a C array and writing some code to serve that up to the Vectrex. I tried multiple approaches. My first one was to write a simple C routine that polls the NCART line; if it goes low it grabs the address lines, does a lookup in the cartridge array and sends the corresponding data out to the data lines so the vectrex can use them. This approach seemed to work, but somehow wasn't really stable. It also meant the chip was polling the NCART line and couldn't do anything else in the mean time.
That's why I tried using an interrupt next: using that, the Vectrex could happily do whatever it wants until the NCART line goes low. When that happens, an interrupt routine gets executed and I could then read out the address lines and set the data lines there. Unfortunately, it seems interrupt latency it a bit too high for this: the STM spends so much time in jumping to the interrupt routine that it's too late in outputting the data to the Vectrex.
Finally, I ended up writing a polling routine in assembly. Why assembly? Mostly so I could control
what instructions are executed when more precisely than I can in C, and the timings are somewhat
critical here. Especially when I want to add more functionality, the fine-grained control that
assembly gives me is an advantage. With this in place, the test cartridge booted every single time:
While it works, this is just a single cartridge, hardcoded in the flash of the STM. What I wanted is a way to upload and select one from multiple cartridges, with a nice menuing system to choose from them.
To upload data, I decided to use the USB port. As a supporting library for the STM, I already used libopencm3, a firmware library supporting various Arm Cortex-M CPUs. This library has a bunch of USB drivers, amongst which is a mass storage driver. You basically use that, plus a few routines to read and write blocks of data, to make the STM act like an USB stick: plug it into a computer and the computer can directly read from or write to the storage you use. Unfortunately, it has a bug in the writing code, but with that solved, it worked pretty well. My Vectrex cart now had a dual function as a somewhat slow and only 16MB large USB disk, and you can just format it with the FAT filesystem and drag and drop files on it.
Ofcourse, the STM itself still is oblivious to the whole concept of files and directories: for it, the data in flash is just a bunch of sectors. To change that, it'd need to learn how to interpret the FAT filesystem. I decided on using Elm-Chans FatFS to do that. FatFS is a small, platform-independent library that can basically take a block device like my SPI flash and give you a standard file-based API: you can enumerate over the files in a directory, open a file, read from it etc.
Using that, making a multigame cartridge gets a lot easier. Initially, I can just load a cartridge that provides me the menuing system. I then list all the ROMs on the flash memory and modify the menu cartridge on the fly to add that list to it. When the Vectrex then boots, the software in the menu cartridge reads the list and displays it. Once the user has selected a game, the STM will load the corresponding ROM file in cartridge space and the menu software can then boot that, allowing the user to play the game. To play a different game, you can press the reset button: the STM can't directly see that happening, but it watches for reads of the copyright portion of the cartridge header. This usually only happens when the Vectrex ROM displays the intro screen for a game, and if that happens unexpectedly, the STM decides the reset-button is pressed and will re-load the menu ROM.
So now I had a real multigame cartridge: I could load all existing Vectrex games on it, and then some. I was done, right? Well, not really... I had a lot of storage left, and only using the 100MHz STM to watch the Vectrex read bytes is a bit sad. There must be something more we can do with that.