Oli's old stuff

Tinkering with retro and electronics

Feb 6, 2021 - 7 minute read - sinclair electronics retro hardware zx spectrum zxsprite

ZX-Sprite Hardware Prototype - Part 2

ZX-Sprite

This is a short series about exploring ZX-Sprite, a fantasy hardware add-on for the ZX Spectrum that aims to bring Spectrum Next style sprites to the 48K and 128K systems.

The series

Going Baremetal

Last time we went over how to use a GAL22v10 to decode the 3 ports we need to control the sprite system.

If you recall, I found 4 solutions to the “display a ULA screen on a modern TV” problem:

ZX-HD and TK-Pie use a Raspberry Pi Zero to drive a HDMI display, whereas the two VGA solutions use a microcontroller to output VGA. I’m unsure as to the final form of this project, if there even is one, but at this point I decided that picking the Raspberry Pi option was more convenient for me.

There’s a couple of barriers to consider with the Pi; the obvious one is that running an OS like Raspian would add a lot to tbe startup time and slow down the system. We also don’t need a full OS here, we really want the simplicity of a microcontroller with the convenience of an on board graphics chip, display connector and in a nice form factor. Luckily, you can chuck out the OS of a Raspberry Pi and write your own kernel. This is called baremetal programming.

The blinkenlights Kernel

I’m not going to go into the ins and outs of setting up a baremetal pi environment; but I will list some links I found helpful:

After setting up the toolchain (I used GCC on Windows), I set about breaking down the main steps I needed to take to get my Pi Zero blinking its onboard LED in response to the Spectrum.

The first thing to figure out is how the GPIO works on the baremetal system. On a Pi Zero the ACT LED is on GPIO port 47, so it’s largely a case of setting that pin to output and then setting or clearing it on a delay.

GPIO on the Raspberry Pi is very nice compared to the STM32 and ATMega microcontrollers I’ve coded for. Similar to those controllers, they’re accessed via memory-mapped IO, meaning you can read/write to a memory address to control them.

The first steps are to set up the ‘select’ settings. The GPIO base is at address 0x20200000, which is also the start of the GPFSEL registers. These are organised in banks of 10 usable registers in each 32-bit word. This broadly means:

Address Registers
0x20200000 0-9
0x20200004 10-19
0x20200008 20-29
0x2020000C 30-39
0x20200010 40-49

Each register entry here is 3 bits long, so to enable GPIO as output we need to write the value 001 to bit position 21 (aka 7 * 3) at 0x20200010.

The nice part about the Pi’s GPIO is that SET and CLEAR are separate, meaning you don’t have to worry about load/modify/store when setting or clearing a GPIO pin. These operations are also packed into 32-bit words, meaning you can read or clear the state of 32 GPIO pins in a single operation. This is far nicer than dealing with the ‘ports’ that you’d find on ATMega or STM32 microcontrollers.

So to set the state of GPIO 47 to ON, we write bit to position 15 (eg: 47 - 32) of address GPSET1, or 0x2020001C. To clear it, we write a bit to the same position at address GPCLR1, or 0x20200028.

Put that in a delay loop (several tutorials show you just burning cycles) and you can blink the ACT LED on and off.

int main()
{
    unsigned int* GPFSEL4 = 0x20200010;
    unsigned int* GPSET1 = 0x2020001C;
    unsigned int* GPFCLR1 = 0x20200028;

    *GPFSEL4 = 1 << 21;

    while(1)
    {
        *GPSET1 = (1 << 15);
        delay_nop(1000);
        *GPCLR1 = (1 << 15);
        delay_nop(1000);
    }
}

With that program compiled into a kernel.img file you can boot your pi and hopefully see it blinking its LED at a steady rate.

NOTE

I have deliberately skipped past the set up of the assembly code needed to properly boot the kernel. This information is found on the pages I linked earlier.

Speccy meet Pi, Pi meet Speccy

That’s great but we’re just doing some blinky lights stuff on the PI. Where does the Spectrum come into this? The next thing to learn is to hook up the GPIO interrupts and control the blinking from the Spectrum.

This is done in two stages; the assembly level vectors initialisation/setup and the configuration of the GPIO ports in C. The first part was a little beyond me and I had to crib from the various resources above. You basically tell the ARM processor to enable interrupts (IRQ) and fast interrupts (FIQ) and initialize the vector table with the call location when they happen. This is set up to thunk into C code, which is much more convenient for me (who doesn’t know ARM assembler).

The dwelch67 blinker tutorials have some fine examples of setting up the assembly part.

With all that done, we have two functions exposed to C (enable_irq() and enable_fiq()), and are expected to implement two ourselves to actually handle any interrupts (c_irq_handler() and c_fiq_handler()).

Before enabling the interrupts we need to set up the triggers. For this example I will use the SELECT port from my GAL22v10; as this is active low I want to set an IRQ trigger on the falling edge. I decided to use GPIO pin 23 for this.

To do this we need to:

  • ensure the pin is flagged as INPUT
  • ensure the GPIO pull up/down clock is set to PULLUP
  • tie our GPIO pin to the pull up/down clock
  • enable falling edge event detection for this pin

As I used the FIQ interrupts, I also had to say which GPIO banks to monitor for FIQ (in my case, the first one).

In code this looked like:

    // enable pullups
    *((unsigned int*)GPIO_PUD) = GPIO_PUD_FLAG_PULLUP;

    // Mark the control as being part of pud clock
    GPIO_PUD_CLOCK[0] = ZXSPRITE_CTRL_0;
    GPIO_FALLINGEDGE[0], ZXSPRITE_CTRL_0;
    // Clear the status
    GPIO_EVENT_STATUS[0] |= ZXSPRITE_CTRL_0;
    *((unsigned int*)IRQ_FIQ_CTRL) = (1 << 7) | 49; //FIQ to gpio_int[0]

    enable_fiq();

Whenever the IRQ (or FIQ, depending which one you set up) is triggered you’ll get a call to your handler function. In here you need to check the GPIOEDS registers for your pin to see if it’s been triggered. You can also check the levels of any GPIO pins via the GPIOLEV registers.

This looks like:

void c_fiq_handler()
{
    if ((GPEDS[0] & ZXSPRITE_CTRL_PORT) == ZXSPRITE_CTRL_PORT)
	{
		GPEDS[0] |= ZXSPRITE_CTRL_PORT;
		// do the thing here, eg: toggle the LED status
	}
}

Speccy Pi Blinken

Once I set all this up I needed to test it but we hit a snag. The Spectrum uses TTL at 5V, whereas the Raspberry Pi GPIO uses LVTTL at 3.3V.

IMPORTANT

Do not connect your Raspberry Pi’s GPIO directly into your Spectrum’s signal lines, or any other 5V electronics connected in this circuit. Doing so will damage your Raspberry Pi.

Another level

To connect our Pi into the same circuit as the Spectrum we need to use some bidirectional level shifters. These take two power supplies (eg: +3.3v and +5.0v) and will convert the signals up or down depending on their direction.

I rushed out to Amazon and board some 8-way shifters based on the TXS0108E chip. I didn’t realise at the time, but these are open-drain outputs, which ended up causing me a wealth of issues when I got further into working on ZX-Sprite. However at this stage of development, they seemed to work fine.

Speccy Pi Blinken Shifted

And with that I was able to hook up the Speccy to blink the Pi’s onboard ACT led.

That’s all well and good - but it’s not really a sprite system is it? What we need to do next is to set up the Raspberry Pi’s framebuffer and start drawing something. I’ll cover this in the next post.

Until next time.