Introducing the Minstrel Joystick, a Kempston compatible joystick interface.
It could be fitted with an card edge connector to plug directly into the back of a Minstrel 2, Minstrel 3, ZX80 or ZX81, even a ZX Spectrum.
However, it is designed as the first module for the Minstrel Expansion Bus.
You might spot some other things on there, I will get onto those in due course.
"Kempston Compatible"
Kempston Compatible is a term used by lots of very different hardware, so many devices that look something like this. All of which implement pretty much the same interface as far as software is concerned.
Plug in a "standard" 9 way D joystick, a Commodore or Atari (but not a Spectrum +2, Amstrad or Atari 5200).
Or maybe even one of TFW8b's custom build ones.
Read port 31 ($1F in hex) and you will get back 0 if the joystick is not moving.
You will read 1 if the joystick is moving right, 2 for left, 4 for down, 8 for up and 16 if fire is pressed.
If the joystick is held at a diagonal, you will get the sum of both directions (e.g. up and right is 8+1 = 9), or +16 if fire is pressed (e.g. up and fire = 8+16 = 24)
If you are writing code in assembler, it is just a case of
IN A,(31)
and you have your value to parse as appropriate.
A BASIC IN command was added for the ZX Spectrum, but in ZX81 BASIC land, things are a bit more complicated.
You can create small assembler routines to call from BASIC, and return values via the BC register pair (or HL if you are running ZX80 4K BASIC).
XOR A LD B,A IN A,(31) LD C,A RET
Thanks to George Beckett for help with that.
You need somewhere to store these 6 bytes of code, and REM statements are traditionally used. You would normally use a loader where you run a program on the ZX81 and type in the bytes and it populates the REM statement for you. In this case, it's easier to just use POKE statements.
16514 is the address of the "J" of "JSREAD" in RAM, and should always be that if it remains the first line of code.
When you run the program, the characters in the REM statement are replaced with the assembled code.
You can then delete lines 10-60 and replace them with your program code, just call USR 16514 when you need to read the joystick. (the REM statement needs to stay as the first line so this address does not change).
The REM statement was modified when the code ran, and the six bytes after it have been overwritten.
Several of these are symbols, such as "<=" and "TAN" which are stored as a single byte, but displayed as multiple characters. The two "?" represents bytes that do not have a printable equivalent.
The code added in lines 10-30, will display the value read from the port.
The value displayed will be 0 and will change as you move the joystick and press fire.
That single byte input port is all pretty standard, and is implemented by dozens of different devices over the years, but the hardware is often very different. Usually it involves a read buffer IC and some address decoding.
The Read Buffer
There are a few options here. Most often used are things like the 74LS240, an octal inverting buffer, or the 74LS366, a hex inverting buffer.
These are inverting as the joystick is traditionally setup as it is in the Atari 2600 or Commodore 64 etc. with common as 0V.
As you move in a direction, a switch is closed and connects that pin to the common.
If you are lucky, those will be nice microswitches, if not they will be little metal domes that flex down to make contact with the PCB trace below.
The switch inputs are pulled up to 5V normally, so read as a 1, unless the joystick is pushed in that direction and the switch is activated, in which case they read as a 0.
The inverting buffers convert that to the slightly easier to understand 1 when moving, 0 otherwise, which is the standard.
There are some versions of the "Kempston" interface which use non-inverting buffers, 74LS365, 74LS367 or 74LS244 (including one from Kempston themselves, so it must be OK then?)
These do not invert the signal, but still need to support the same protocol, so they feed the common of the joystick with 5V and pull all the signal down to 0V. This will stop any autofire circuits from working, potentially even damage them if they are not expecting a reverse voltage? But will otherwise give the correct 0s and 1s on the interface bus.
All of theses buffer chips have two enable inputs. Some require both inputs to be low for any of the outputs to be enabled (e.g. 74LS365, 74LS366, 74LS540), but most use one pin for 4 of the outputs and the other other for the remaining 2 or 4 outputs (e.g. 74LS367, 74LS240, 74LS244).
Decoding Logic
So you have your buffer, this needs to be enabled in one precise condition - an IO read request at address 31.
IORQ is low, RD is low, and the lower half of the address bus is 0001 1111, so A7, A6 and A5 are low, and A4-A0 are high.
It should also not respond during an interrupt acknowledge cycle. Here IORQ and M1 are low to indicate the external device should place the interrupt handler address on the bus. The joystick interface should stay out of this. That can be detected by making sure that at the same time as the IO request, that the M1 signal is high, or the read RD signal is low (or both).
The logic used varies a lot, usually one chip, occasionally two are used, although sometimes no additional decoding is employed.
A common theme though is not bothering to decode the whole address. Most only look at A5.
A typical interface looks something like this.
Here the decoding is A5, IORQ and RD are low. This should avoid problems with interrupt acknowledge as RD is not low at this point. But the address decoding means it will respond to IO read events on addresses from $00-$1F, $40-$5F, $80-9F and $C0-$DF (that is 128 out of the 256 available addresses).
Not really a problem as most of these interfaces do not have pass through connectors, so will likely be the only thing plugged into the bus. In that case, it doesn't matter if they use up lots of the address space.
Remember this is the ZX81, and it has already used up all even addresses by decoding FE as "A0 is low".
It does mean that if you try to read any even address in the decoded range that the buffer chip on the interface and the buffer chip in the ZX81 (well, part of the ULA), will both be writing at the same time.
The three unused inputs are pulled up to 5V, so when inverted, those bits will always read as 0 as expected.
The most complete decoding I have seen is one from Cheetah which decoded A5, A6 and A7 low, and also IORQ and RD low and M1 high.
That covers most of the bases and only responds to IO read requests from $00-$1F, and not interrupt acknowledges.
It uses a different approach to dealing with the unused bits, D5 is driven by the buffer chip, but D6 and D7 are pulled low when the enable signal goes low. Both valid approaches. These days an octal buffer would be cheaper than a hex buffer and two diodes, but probably not in the 1980s.
I did find one interface from "RAM" with only a single chip.
It looks like it was designed for two chips, but at some point they cheaped out and instead of the second chip, they fitted a wire link.
I think I need to reverse engineer the schematic.
One moment please.
This is what I think it was meant to look like, with a 74LS00 as the missing chip.
The 74LS366 is only a hex buffer, so diodes are used to drive the three unused bits from a single output. They can't do it in the same was as the Cheetah one above as their partially decoded enable line only exists within the buffer chip.
The intended circuit deals with things slightly differently, checking for IORQ low, A5 low and M1 high. This will avoid the interrupt acknowledge, and be active over half the address range as above. It will also be active during write cycles, should you try to write to $00-$1F etc. you will get a bus conflict.
The single chip + wire link version just connected IORQ to the enable line, so will only check for IO and A5 low. That means it will respond to read and write and interrupt acknowledge events. Not ideal, but I guess it is functional and someone saved 10p in the 1980s.
It wasn't the only way they cheaped out. You might notice all of these interfaces use the 2x23 way ZX81 edge connector. Most of them were acutally used with ZX Spectrums, which has a larger 2x28 way edge connector. They are not identical, but the few pins that are used here are common between the two systems, so you can use these on a ZX81 or ZX Spectrum as long as you make sure they key way is aligned.
I wonder if the missing M1 signal you find on many Spectrums is what led to changing the decoding on that interface to ignore M1?
Which way did I do it?
Well, I went a bit far, as usual, and fully decoded the address.
I fully decoded address $1F, and also checked for IORQ and RD low and M1 high for good measure.
This is intended to sit with other modules in a bus system, so I didn't want to tie up extra addresses unnecessarily.
The other difference is I have wired up pins 5 and 9 on the joystick as additional fire buttons to bits 5 and 7, as I have done on other interfaces in the past.
Which games support it?
There are a few ZX81 games which already support a Kempston joystick, such as Paul Farrow's ZX81 Kong (and other games).
http://www.fruitcake.plus.com/Sinclair/ZX80/FlickerFree/ZX80_Kong.htm
And David Stephenson's Minossss Knossosssss.
https://www.zx81keyboardadventure.com/2024/04/zx81-game-minoss-knossoss.html
The Minstrel Joystick is available as assembled - https://www.sellmyretro.com/offer/details/64329
Full kit - https://www.sellmyretro.com/offer/details/64328
Or PCB only - https://www.sellmyretro.com/offer/details/64330
As are some of the other new things, posts on those to follow shortly.
There is an introductory post with all the links you need - http://blog.tynemouthsoftware.co.uk/2024/04/lots-of-new-things-for-minstrel-3.html
Advertisements
The full range of Minstrel and Mini PET kits and accessories are available form my SellMyRetro store.
All the links can be found here:
Patreon
You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. These are often in more detail than I can fit in here, and some of these posts contain bits from several Patreon posts. This also includes access to my Patreon only Discord server for even more regular updates.