UPDATE: There is a ‘part 2‘ to this article. In that I use interrupts, instead of the polling technique shown below
So, in emulating a hardware ROM in real time with an STM32F4 I hooked up a cheap US$10 STM32F4 board to my Acorn Electron to act as both a sideways ROM board and a sort of emulation of some IO that connected to an SD card. I thought I would have a go at something similar on my Amstrad CPC 464.
Initially, I just had the STM32F4 emulating some ROMs out of its own Flash, but I started investigating what I could do with the SD card integration in the STM32F4. Eventually I worked out a way to load floppy DSK images from an SD card, and emulate (enough of) the upd765 Floppy Controller chip to be able to load and run disk images. So this is really for CPC 464 users at the moment. ie. You don’t need a DDI-1. This pretends to be a DDI-1 with an SD card.
I will also point out here that there is already the M4 Board that happens to have an STM32F4 on it and a micro SD card slot and does pretty much what you’d like it to do and more (it looks so good, I ordered one myself and it just turned up in the post as I was finishing this page). But for this project, I initially started out with the simple goal of loading some ROMs into my CPC 464.
So, the basic deal with an Amstrad CPC accessing some external ROMs is that there is a sort of sideways ROM register at $DF00 (in Amstrad IO space, A13 has to be low). You effectively write a 1 to it to access external ROM 1, write a 2 for external ROM 2 and so on. ROM 0 is the internal BASIC I think. ROM 1 to 6 are external. ROM 7 is usually AMSDOS or PARADOS on a CPC 6128, then I think you can have loads and loads of ROMS from 8 upwards. There is a great description of how ROMS (and RAM) work in the Amstrad CPC at this cpc-live link.
All ROMs map into $C000 to $FFFF. When the Amstrad wants to access a ROM in that address range the _ROMEN signal goes low. If a valid expansion ROM has been selected via the $DF00 register, then the external ROM board must send ROMDIS high. This disables any internal ROM from appearing at that address I think.
So far I am using my Black VET6 board. The initial code was very similar to the Electron code. However, this time we are polling for two signals; _ROMEN and _IORQ since the Z80 has an IO address space too.
- If _IORQ goes low and its a write to $DF00 then we capture the databus in an ARM register to save the ‘selected ROM’, then wait for _IORQ to go high again.
- If _ROMEN goes low and the value in the ‘selected ROM’ register is between 1 and 6 then we send ROMDIS high, find a copy of the ROM in the internal flash and present its data on the databus. When _ROMEN goes high we immediately tristate the databus, and go back to polling again.
The memory and IO requests are a bit more lenient than the 2MHz 6502A in the Electron (ie. there is more than 250ns. It’s more like 350ns for a ROM access), so the code has spare time. I still couldn’t get it working using an interrupt though. Polling works fine.
Wiring it up is pretty simple. Just like with the Electron I have a 50 pin edge connector with some header pins attached to it, so I can run some jumper cables between the Amstrad and the STM32F407 board. The breadboard is not required. It’s just convenient to have it so I can hook my logic analyser in.
So this all worked. But then I got curious about using the SD card on my STM32F4 board (Must admit getting SD stuff to work was a painful experience, but once it works it works). There are examples of using the Chan FatFS with the STM32F4. I eventually got one to work. That let me mount a FAT32 filesystem on boot. Initially I just copied ROM images from the SD card into the STM32F4’s RAM and then they were presented to the Amstrad. All good.
But curiously, I wondered ‘how hard could it be’ to emulate the upd765 floppy controller that is missing in the CPC 464. If I could emulate it, and have it accessing disk images on the SD card, that would be quite useful. Again, this would have to be in real time without any wait states.
So, that’s exactly what the code does. It is in the realm of ‘proof of concept’, so it’s not very user friendly. The deal is;
- Plug STM32F407 board into Amstrad CPC 464 (it won’t work in a 6128 as is because it emulates the very upd765 chip that is already in a 6128)
- Compile the code to have it emulate two ROMS, instead of the ‘up to 6’ noted earlier. You can alter the code to do more, but I just emulate Maxam (because it’s handy) and AMSDOS or ParaDOS. So Maxam is ROM 6. AMSDOS/ParaDOS is ROM 7
- You copy Amstrad DSK files to the root of a FAT32 card, but you have to rename them (this is the ‘not user friendly’ part). Rename them to CPC000.DSK, CPC001.DSK, CPC002.DSK and so on. You can have up to 250 or so.
- You power on. You should see banners for Maxam and AMSDOS/ParaDOS (actually AMSDOS is a bit quiet sometimes)
- Type the following
- You should get a listing of your CPC000.DSK file if it exists.
- If you want to switch to a different disk, use the BASIC OUT command to effectively ‘poke’ the number into a magic register. eg. to select CPC002.DSK
- Now do the
You should see a new directory listing from your CPC002.DSK
- You can now just load and run something like you would normally do with an Amstrad with a disk drive
- The disk image number you select is stored in the ‘Backup SRAM’ of the STM32F407, so if your board has a battery (like the Black VET6 board), your image selection will be restored after a power off/on. If you board doesn’t have a battery, its probably not that hard to add a big capacitor wired in to VBAT to do much the same thing.
So what’s missing?
- There is no write support, so you cannot write back to a DSK image
- Only the bare minimum of upd765 Floppy controller functions are implemented. Essentially its most of the stuff you need for loading games that don’t have lots of copy protection. For the techies, I implement ‘Read Data’ and a bunch of related functions, but I don’t do ‘Read Track’.
- There is no cool menu to choose and load stuff. At the moment you need to use the OUT instruction to ‘poke’ in the new DSK image.
- Technically it can’t load biggish DSK files. As it loads the entire DSK image into the STM32F4’s RAM at boot time, there needs to be enough RAM for a whole disk image. The STM32F4’s RAM is kind of split between a contiguous 128K chunk and a 64K chunk. At the moment I am limiting it to 96KB out of the 128KB chunk. It’s not all bad. A lot of Amstrad DSK images are quite small and fit into 96KB. But you might notice there are heaps of 192KB DSK files. These are full 40 cylinder images, and if you hex edit them you’ll find they are mostly empty. The code will actually load the first 96KB of a file. So you can put 192KB DSK files on the SD card. It just won’t load the whole file when you use one. I find a lot of these still work since the end of file is effectively empty. I would probably avoid compilation disk images.