Continuing with some extracts from of the Patreon exclusive posts during the development of the Minstrel 4D.
We join the proceedings as I have been modifying the first prototype board. I have wired in a microcontroller development board as I had moved around several of the pins on the microcontroller, and it also made programming and debugging easier.
There also various modifications on the right of that photo to the "auto typing" section, which will be covered in the next instalment.
There were quite a few of the design goals of the Minstrel 5 that I carried over to the Minstrel 4th.
- If I can keep to the unmodified ROM, I will.
- If I can avoid conflicting with other devices, I will.
- If I can keep the hardware minimal, I will.
One of the challenges this creates is dealing with communication between the Z80 and the microcontroller which is handling the SD card and other things.
First off, lets just agree we need a microcontroller. It would be possible to bit bang the SD card interface, but the processing and memory overheads would be a lot for the Z80 and there would be extensive ROM modifications required. It's not impossible, the divMMC does effectively that. But I'd rather not go down that route.
Lets hope I am not back here in 6 months time when we still can't get any microcontrollers and I give up and go down that route (luckily I wasn't. We had to order two slightly different types, from different places, to get enough to make the full batch of kits, but we got there in the end)
This is the normal way for something like the Z80 to interface with a peripheral. Simple IN and OUT instructions read and write to one of the 256 IO addresses on the Z80. 128 of these are wiped out on the Jupiter Ace as it's minimal decoding only uses A0 being low, so ties up all even addresses from 00 to FE, even though only FE is ever used. The Minstrel 4th and 4D fully decode this as FE to free up the other 127 addresses. The Boldfield joystick I looked at previously uses decoding of A0 being high, so ties up all the odd addresses, another 128 gone. Again, the 4D minimises this to the only address used (01), so that the entire address range is not take up by two 6 bit ports.
The Z80 read and write cycles are very fast. There's you data. There's your select line. There's your write pulse. And then it's gone. The problem here is the microcontroller isn't fast enough to deal with that in software.
Even more so with a read, where the Z80 expects the data to be available shortly after it sets the address and read signals. Here the microcontroller is unlikely to be able to get data onto the bus in time (and just as important, to remove it from the bus before the next address is selected). And that leaves no time to work out what data it should be putting on the bus in the first place.
Maybe when you are working with 20 to 50 times faster clock cycles then you have a chance, but it's still quite tight in software to be able to deal with a write from the Z80, without hardware assistance. But with a 16MHz microcontroller and a 6.5MHz Z80, you have no chance.
Some Microchip microcontrollers have a parallel slave port. This is I/O that is designed for this scenario, and will latch data on a write, and set prepared data on a read. The ZXpand used this technique. It has a few downsides, there is no corresponding address latch, so if you have more than one address, you need to latch that separately to know what the data refers to.
This single read /write address, leads to the slightly awkward double cycle reads. For example, the ZXpand joystick. First you write telling it you want to read the joystick. The microcontroller see this, reads the joystick, puts the value into the parallel slave port buffer ready, and then the after a suitable delay, the Z80 reads the port and gets the value.
This is only available on a few Microchip microcontrollers, but not any recent ones, or any of the Atmel range that I am more familiar with.
Parallel with a VIA or PIA
One option I considered for this was to use something like an 8255 or a 6520/1/2 PIA. This provides a Z80 friendly interface to several ports, which can be set up to provide similar functionality to the parallel slave port, but with several real ports that can be read or written by the Z80.
That is a bit limited, and also adds a 40 pin PIA chip and associated parts. Also needs more microcontroller pins to interface to the PIA ports.
This is sort of how the Tube works on a BBC micro, so peripherals such as the Torch Z80 board used things like a 6522 and 8255 to do the Tube interface instead of the custom Acorn part.
One idea that I initially looked at for the Minstrel 4th SD card project back in 2020 was using serial. I had a Z80-SIO dual UART, with one port providing an external RS232 interface, and the other wired to a microcontroller serial port.
This simplified a lot of the interfacing as both ends could reliably talk serial either polled or interrupt driven, and all would be neatly handled by the serial interface, the Z80 and microcontroller could just throw bytes back and forth.
If I had not had to shut everything down in 2020, I would probably have completed that along those lines.
The downside of this is the 40 pin Z80 SIO and support components.
A Sort of a rubbish SPI
The solution I have come up with is a sort of rubbish implementation of SPI, the Serial Peripheral Interface.
SPI has four parts.
- Clock - Generated by the master, new data is transferred on each clock pulse.
- MOSI - Master out, slave in. Data sent from the master to be clocked into the slave.
- MISO - Master in, slave out. Data send from the slave to be clocked into the master.
- Chip Select - Indicates which salve device should respond, also used to indicate start of a byte. You can sort of dispense with that if you are only talking 1:1, although you will have to deal with synchronisation.
When I was designing the Minstrel 5, I worked out I had all the bits I needed to do that. Here on the Minstrel 4D, the same is true.
Clock - The Z80 is in charge. I am using the decoded IO_FE_Write line as the clock. This is activated whenever port FE is written to. This is used for two purposes on the 4D, one is for save and the other as part of the speaker. It is a very short pulse, but should be long enough to trigger an interrupt.
MOSI - As part of save, the IO_FE_WR line is used to latch D3 to provide the save signal. Think of it as a single bit IO port.
(note, some of the schematics show this as D0, but it has been confirmed in both ROM code and PCB tracks on the Jupiter Ace, all use D3)
MISO - The ear bit on the keyboard input can be read by the Z80. There are two spare bits, but that would involve changing the 365 hex buffer for a 245 octal buffer or something like that.
Chip Select - I haven't used this. It could have been done with an extra flip flop. Instead I have handled synchronisation in the protocol.
Now, I said this is rubbish, this is because the timing is wrong to be proper SPI, so I cannot use a hardware SPI port on the microcontroller to talk to it. It also means that the logic analyser will have a good go at understanding the SPI, but will make mistakes.
Clock, this is not going to be consistent, there will be gaps between bytes that vary depending on what is happening, even gaps between bits may be consistent. Since this is a purely synchronous protocol, that doesn't matter other than messing up the display in the logic analyser.
MOSI, when writing, the clock pulse comes before the 74 latch updates. This is not a problem in practice as the microcontroller will first detect the pulse (as an interrupt), and by the time it gets around to reading, the bit will have been set. It is just about good enough to be understood by the logic analyser.
MISO, the timing is very wrong here. The way I have done it is the IO_FE_WR is followed by an IO_FE_RD to read the bit from the ear port. For the same reasons as the parallel port, it's going to be tricky to get the microcontroller to write the next bit on the clock change. So what it does is wait for the clock pulse, which will read the previous bit. It can then change the bit ready to be read on the next clock pulse.
It appears the microcontroller is doing the Two Ronnies "Answering the Question before Last" sketch.
Here you can see the ear signal changes after the write/read signals have completed, but is ready in time for the next pair. The logic analyser is one bit out, so reads 26 instead of 4D.
This thoroughly confuses the logic analyser (and also the developer). The first time I tried this, I over compensated in the wrong direction, and so this slightly odd pattern is because each bytes starts two bits too late, so it appears to step up in fours.
The protocol used to talk between them includes synchronisation, so I just wait until they are in sync and then the data continues correctly.
During testing, I did noticed the odd character was wrong. I was fairly confident the overhead of the C interrupt routine on the microcontroller would mean it was not writing until after the write / read pulses, but also that the Z80 side took long enough that the data was ready before the next cycle.
Here what should be a space (0x20) is showing as £ sign (0x60). Other erroneous characters on other screen also had bits set when they shouldn't have or vice versa.
It turned out the problem was caused by the interrupt on the Z80, which is the 50Hz screen interrupt used to read the keyboard etc. If this happened between bytes, it was fine.
The problem only occurred occasionally, when the interrupt was between the write and the read and the next bit was different to the previous one. The microcontroller would set the next bit ready, not knowing that the Z80 had yet to read the previous one, hence 0110 instead of 0010.
You can see all the other bits the value of Tape Out (MISO) changes after the read and write. This one happened on the first bit of the byte. The write goes out and the value is changed, but the read does not happen until after the interrupt, so it reads a 1 instead of a zero.
This turned a plain r into an inverted r.
Once I had confirmed that was the cause, it was simply a case of disabling the interrupts on the Z80 when receiving data. The protocol is set up as challenge-response, with the Z80 in charge, so it always knows when this is happening.
Disabling the z80 screen interrupts when receiving data has fixed that, no more interruptions to the flow.
Forth vs Z80 Assembler
As an aside, whilst developing that, I wrote the initial test code at the Minstrel 4D end in Forth. My first test took about 10mS to send a byte of data.
I tried rewriting it in Z80 to see how much faster that would be. I was rather disappointed to find to too k 60uS (yes, the units are correct, 60uS vs 10,000uS).
(n.b. I wrote both bits of code and I am not the best Forth programmer, or the best Z80 assembler, so neither may be the most optimised, but that should still be a reasonable comparison)
Have to say, I was expecting better. I was not expecting it to be many hundred times slower for a bit of simple maths and IO.
(on reflection, I was using a multiply by 2 which might have slowed things down, I am used to compilers converting that to a shift, since that is not available in the native language. I should have used an add.
Hang on, let me try that. Back in a minute.....
You still there?
OK, I tried replacing 2 * with DUP +, and it now takes 8mS, so a bit better, but not much)
The Z80 assembler version all seems to be working well, so that is another thing crossed off the list. The Minstrel 4D is coming on well.
The Minstrel 4D kits are shipping now, you can order one from The Future Was 8 bit - SPECIAL OFFER - £40 off the Minstrel 4D and free shipping to the USA
More info in a previous post:
You can support me via Patreon, and get access to advance previews and behind the scenes updates. These are often in more detail than I can fit in here, this post contains bits from several Patreon posts. This also includes access to my Patreon only Discord server for even more regular updates.