Sunday, 30 November 2025

A New Serial Card for Minstrel 4th and RC2014

Here it is, a new serial card specifically designed for the Minstrel 4th and the RC2014 bus.

It may look like there is something missing. Let's just check.

  • No UART
  • No ACIA
  • No DART
  • No SIO
  • No microcontroller
  • No Raspberry Pi

So how is it doing it?

Read on.

There is a lot of backstory to get to this point, I covered in a separate post:

The essential part is I wanted a card that used all new parts, but there was no serial chip in production that I thought was suitable (the W65C51N is in production, but some unfortunate flaws make it unsuitable).

I had tried to create a "pseudo 6850" card many times, in many different ways, but none of them were 100%.

It all seemed to come down to communication between the Z80 and the serial chip. These simple chips can only deal with one byte at a time, so the timing of the RTS signal and getting that one byte read is quite important, and I kept getting into a situation where it worked "most" of the time.

(N.B. as I have been working on this, I have concluded that the 68B50 also works only "most" of the time, and suffers from missing bytes when they are sent too fast, but I still wanted to do better, and I think I have)

The 68B50 only has a single byte buffer, so has to receive a character, wait for it to be read by the Z80, receive the next character, wait for that to be read etc.

The way the software driving the 68B50 works is it set RTS briefly to indicate it is ready to receive a byte. It then checks to see if a byte has been received and reads that byte of data if there is one. If there isn't, it pulses RTS again and repeats.

The calling code then checks if a byte was received, and if not, go back and calls it again.

One day I was looking at the traces and I noticed that the RTS pulses were only slightly longer than the actual data bits......

A dangerous thought occurred to me.

Can I bit-bash RS232?

The answer is yes, of course I can, that was actually one of the first projects I did with the early microcontrollers back in the 1990s (see a previous post), although that was a PIC with something like 4MHz clock and the serial was probably 9600 baud or something like that.

This is a Z80 with no single cycle IO instructions (very far from it), and also the serial is 115200 baud.

They both can run at up to 10MHz, but the Z80 instruction takes 12 times as many cycles, and the baud rate is 12 x what it was. I am 144 times the programmer I was 30 years ago?

Looking at the traces, it became apparent, that all the bits of the serial data were 28 clock cycles long, confirmed by checking the whole bytes from start bit to stop bit with 8 data bits in between were all 280 cycles long.

Just out of academic interest, I had a look to see how close 28 cycles per bit at 3.25MHz would be.

116071 baud

OK, that's not bad, fairly close to 115200 isn't it?

Well, it turns out to be 0.76% fast, and the tolerance is something like 5% on some older systems, so maybe it is viable to bit-bash RS232 at 115200 baud (or near offer).

Ah, but do I have enough cycles?

Software Alarmingly, the OUT instruction is 11 or 12 cycles depending on which version I use. Ouch, that's almost half of the time I have.

After a bit of juggling around, I managed to make it work in 28 cycles.

I added that to my rebuilt Minstrel 4th Enhanced ROM (now you see why I needed to be able to rebuild it)

That looks like RS232 to me.

The logic analyser was happy enough with it, as was a serial terminal. Using the TEE command, I was able to VLIST over serial!

Transmitting was possible, but the next bit with going to be more complicated - was I going to be able to get the receive code into just 28 cycles?

Yes I was, in fact, I even had cycles to spare, so used the 8 cycle version of RL A instead of the 4 cycle RLA. Such luxury.

But, as usual, it wasn't 100%.

The problem is with detecting the start bit, to know when the start sampling the incoming data.

Most of the time, it would hit it fine, and sample all the bits in the right place and decode the data.

However, sometimes it would have been busy setting RTS again and by the time it sampled the start bit, it was a little too late and was off by a whole bit in this case.

I tried a few variations, including turning RTS on and off around each sample point, and turning it on at the start and one turning it off then the stop bit was detected, but none of those options were 100%.

Yay, yet another "not quite good enough serial card replacement".

Plan B

(are we really only onto B? maybe it has looped around?)

I had one ace up my sleeve that I was holding back, time to play it.

The plan had been to use the 28 cycle version at 3.25MHz to generate 115200 baud, to match the original used in the Grant Searle system, the RC2014 and Minstrel 4th enhanced ROM.

This is all based on clock cycles, so when that same code runs at 6.5MHz, it would generate 230400 baud, which is also a standard rate, although not as commonly used as 115200.

I knew I had a fallback that I if I doubled it to 56 cycles per bit, that would give me 57600 baud at 3.25MHz, and would now give 115200 baud at 6.5MHz. 57600 is also a standard rate, more so that 115200, but it felt like a little bit of a cheat, I would have liked to do the 28 cycle version if I could.

Yes, I know I said in a previous post that the modern get-out-clause for lazy programming was to specify a faster processor, but in this case I tried really hard to avoid that.

It just means the baud rate of the serial terminal or serial server Python code would need to be selected based on the clock frequency of the Minstrel 4th, not too much of a problem,

The main receive and transmit code just needed padding out to 56 cycles.

I later changed that from 7x NOP (4 cycles) to 4x LDA (7 cycles).

N.B. I later changed it again to load A with $FF as that value is written to the upper address bus during the IN and OUT instructions and that will disable the keyboard matrix

This was all padding, other than around the code which detected the start bit.

With that now in place, I was able to tweak things again.

Now it is checking more regularly, the data samples fit neatly into the middle of the transmitted bits.

Here I have tried to show the range by overlaying the worse case (in pink) where the start bit appears just after it has been sampled, and the best case (in purple), where the start bit appears just before it is sampled.

That seems to be working well, no bad characters.

I still got the occasional double character when RTS was active just a little too long and a second byte was sent immediately after the first.

To get around that, I tried just a single short RTS pulse at the start, and then a run of samples.

That worked nicely.

I couldn't find any definition of how long the RTS line needs to be asserted to be valid. RS232 is one of the "standards" that is anything but standardised, having evolved over the incredible 65 years it has been around.

Most of the time, this will be used with an FTDI type serial module, so I checked the datasheet for that, but again, nothing specific about RTS/CTS timing.

It appears to work fine with the brief pulses, maybe it's edge triggered?

Because this is very much one byte at a time, it needs RTS/CTS to be used, so I decided to remove the connections to the RX and TX pins on RC2014 bus, as receive would not work without hardware flow control.

I am not sure how much those are used, they were originally there to connect the serial from the 68B50 card to the Raspberry Pi video terminal board - is that still a thing?

I later relented and put solder blob jumpers so you can reconnect them if you really want.

Coding Transmit

Transmit works out fairly straightforward. Toggle the TX line up and down to generate the bits and the return.

As with the 68B50 bards, CTS is not checked, so transmission is never blocked. I have made that pin readable with a CTS word, so you could write code to check that before transmit if you had an application the required it, or roll it into a blocking TX function. Generally an FTDI USB cable to a PC would never need to assert CTS as it can read as many bytes as you can throw at it.

Coding Receive

There are three ways you can deal with reading characters over serial.

Polling

This is usually done as part of a loop, or on a timer interrupt, for example on the vertical refresh on the Minstrel 4th.

The implementation here toggles RTS and waits about a character length before giving up if it does not see a start bit.

This is the equivalent of periodically opening the front door shouting "anyone got any parcels for me?", but is slightly better than it sounds because with the RTS control, they won't attempt to deliver parcels when your door is closed.

Blocking

This is where you want input from the user, and don't have anything else to do. You call the function and it will wait until a character has been received before returning.

Here it repeats the sequence of toggle RTS, wait for a start bit, toggle RTS, wait for a start bit etc. until a character has been received (currently this returns after 1000 tries so as not to lock the machine up)

This is like standing at the front door, waiting for the courier to arrive and only going back inside when you have the parcel.

Interrupt

Here you keep running your code until you get an interrupt telling you a character has been received.

This is the equivalent of working away on other things, and when you hear the doorbell, going to the door and receiving the parcel, assuming you get there in time before the courier gives up and goes back in their van.

That mode is not supported on this new card, the main reason is the interrupt pin is used by the video circuitry on the Minstrel boards, so is not easily available for other purposes.

I did consider having a non-maskable interrupt triggered by the start bit, but the time between the interrupt being raised and the interrupt handler getting started is non consistent, and depends on the instruction being executed when the interrupt occurs, and if you need to preserve registers and machine state, you quickly run out of cycles (the driver is back in the van and on their way back to the depot by the time you get to the door).

The Minstrel 4th Enhanced ROM

The main intended use of this card is with the Minstrel 4th Enhanced ROM, this uses serial in a few ways, all of which this card can be used for.

TEE and TTY

The TEE command can be used to send the screen output to the serial port. This involves transmitting characters as they are printed to the screen, that is fine.

Transmit is called whenever a character is to be printed to the screen.

TTY can be used to allow typing in commands over serial, and using TEE to send the responses back via serial.

Receive is called as part of the keyboard scan routine, using the "polling" method above. If there is a character received, it is handled in the same way as a key press.

I spent quite a while tweaking things to avoid missing characters. It turns out when I went back to get some more sample traces for comparison that the 68B50 version wasn't very good at dealing with that either, and would sometimes miss characters itself.

The new one seems pretty solid and certainly with the FTDI (or clone) USB serial adapters I have tested with, it is using RTS to effectively stop more than one character at a time.

Serial Server

There is a Python script which runs on the PC end with a folder of TAP files, the user can use the LS, TAPIN and TAPOUT commands, along with versions of LOAD and SAVE adapted to use serial.

This uses serial TX and blocking serial RX, so works fine with this card taking only a few seconds to load 3D Monster Maze at 57600 BAUD.

I am sure you know my criteria for something working is "can it run (or in this case load) 3D Monster Maze".

Well, yes, it can.

It seems to spend most of the time toggling RTS, waiting for the next bytes, so the dropping to half the baud rate has not made much of a difference.

Typing in code over serial

One of the features I had on the Minstrel 4D was being able to type in some forth source code, I have added that here with a new TYPEIN command, so it will type in the contents of a .fs or .txt file.

Pasting a file into a terminal is also an option, but I need to work on adding some punctuation characters that are missing.

I also noticed it missed the ; at the end of a statement, when pasted as a bunch of lines. It seemed to successfully read the character, but then ignores it.

One neat thing about software serial is you aren't limited to just one RX and TX. For testing purposes, I added an echo of the character received using the RX_LED pin, and that confirmed the ; is received correctly. Needs more investigation.

Implementation

I have tried to keep things neat and simple. Four main ICs, but which one is the UART?

I did start off needing to add only one 74HC74. My initial plan was to integrate this into the Minstrel 4th, and use the $FE I/O ports already present. Only 6 bits are used on the input port, and one on the output port.

This worked, but unfortunately it triggers the speaker which toggles on and off with reads and writes, so it buzzes when sending or receiving data. I could call that a "feature", or add a jumper to disable the speaker, but I'd rather not.

I didn't have space for four chips, so I went back to an external card.

Address decoding

I went through a few variations on the way. I was initially looking at a 688 and a 138, or a pair of 138s.

I decided I would prefer to go for the simplicity of plain logic gates. I went for address $FF, since I could do that with just two ICs, a 20 and a 32. I decided not to give the option of changing the address, since that would need different versions of the ROM. I don't think $FF is used by other devices, so should be available.

I reordered the pins to make the layout easier. It makes me happy to know the layout is completed with no vias, I like the challenge of laying a board out like that. I'm sure no one else cares one jot.

LEDs

One thing I disliked on the 68B50 card I made was the TX and RX LEDs, these were buffered versions of the RX and TX lines (since there were spare gates on the 04 chip used for the clock). The bits were not very long, so unless you were sending lots of data, you wouldn't see them flash very easily.

One of the things that I spent too much time on with the failed AVR microcontroller version was getting the LEDs to flash for a minimum period, so you got a visible blink, even if it was only a single character.

If I could, I wanted to do something in hardware to make the LEDs persist long enough to see.

Input

The obvious choice for something like this would be a 245 or a 365 / 367, since all of those are already on the Minstrel 4th BOM. Those are 8 or 6 way input buffers, and I only really need one input for RX data. I may as well add a second for CTS input, although that will probably not be used most of the time.

In the end I went for a 74HC125, this is four way, but they are individually controlled, and I have another use for the two spare gates.

Output

Again there are various choices, I need two outputs, TX and RTS so an eight way 574 would be overkill. I could use a 74, that is a logical choice, but I have a use for two more outputs.

My thinking was I could control the TX and RX LEDs separately, but as I found with the AVR version, it is easy to find a place to turn the LED on, but it is difficult to arrange for it to be turned off tens of milliseconds after the byte has finished transmission, long enough for the flash to be visible.

Analogue electronics to the rescue!

This is what I came up with. The two spare 125 gates act as buffers for the RC time delay time delay circuit. The RX LED version is identical.

It is setup so when the TX_LED_Pulse signal goes low during transmission, it is only low for 150µs, but that is long enough to discharge the 100nF capacitor C5 via diode D1 so the input to the buffer goes low and the LED turns on.

Approximately 50ms later, C5 has charged up enough via the 1MΩ resistor R1 to count as a logic 1, so the LED goes off again. This is retriggerable as every transmitted byte will discharge C5 again, so the LED will remain on until 50ms after the last byte was transmitted.

The latch has a reset input, so would set all the outputs to 0V at power on. That would keep C5 permanently discharged via D1 and the LED would be on until the port was written to. I would rather not have to clear the LEDs at power on, as that would mean any code running would need to know about the card, and I wanted to keep the option of using unmodified original ROMs.

I did try inverting the logic and wiring it the other way up, with the capacitor to the 5V rail, reset by a positive pulse. But I had to double think everything, and the negative trigger just made more sense to me.

The extended pulse LEDs adds 8 passive components, (ok 6 passives and 2 semi-conductor diodes if you want to be pedantic), but I think it's useful to have the LEDs there to know things are working.

Getting back to which chip to use, I could use two 74 chips, to give me the four outputs I need, and the /Q output which would reset high and so the LEDs would flash once at power on themselves whist the capacitor was charging for the first time, which is a nice effect.

Then I remembered the 175, this is a four way latch with reset and Q and /Q outputs. It's not a widely used chip, I had to hunt around and the only one I found was from 1980!

I probably desoldered that from some scrap board about 30 years ago. I knew it would come in handy one day. My hoarding habit totally vindicated.

Given the way technology moves these days, it blows my mind that this is still in production 45 years later, in an almost identical form. Long may that continue. (yes, I know that is a 74HC on the right, you can still get 74LS versions as well)

I also wired up the transmit and RTS outputs to the inverted versions, so they will reset to high (inactive), which is ideal, the card needs no software initialisation after a reset.

I have added copious arrows to the schematic to keep reminding me of the signal direction and avoid getting TX and RX swapped.

I added 2K2 resistors in all the signals to the FTDI connector for protection in case of such a swap. The Grant Searle Z80 SBC design did that, and it seems to make sense, so I have been using it every where I add an FTDI port. I also have a jumper to power the RC2014 bus from the FTDI cable 5V if you wanted to.

I was changing things a lot, so did all the initial tested on breadboard until the PCBs arrived.

Once everything was finalised, it was time to move to a PCB. Here is the first off the line.

I have been moving towards labelling things as "Tynemouth" recently, rather than "Tynemouth Software", so this is the "Tynemouth" "Software Serial Card", rather than the "Tynemouth Software" "Serial Card". And never the "Tynemouth Software" "Software Serial Card".

What it can do

  • Bit bash serial transmit at the following rates
    • 57600 BAUD (3.25MHz clock)
    • 115200 BAUD (6.5MHz clock)
    • 115200 BAUD (7.3738MHz clock with extra padding for 64 cycles per bit)
    • Other BAUD rates with more padding code
  • Bit bash protocols other than RS232 8N1 if you want to write the code
  • Blocking and polled serial receive
  • Load TAP files over serial
  • Save TAP files over serial
  • Type FS files over serial
  • Echo screen to serial
  • Remote typing via serial
  • Report CTS status

What it can't do

  • Change BAUD rates on the fly
  • Interrupt driven RX or TX
    • This means currently the MS BASIC and CP/M ROMs for RC2014 will not work
  • Operate with only TX and RX, it needs RTS and CTS for hardware flow control
  • Make tea (working on it, for a future revision)

What next?

I have listed these on Tindie, as usual in kit or assembled form, with optional IC sockets if you want.

I am still working on tidying up the changes to the enhanced ROM for the Minstrel 4th, so will offer that as a download or an optional programmed ROM.

I would like to get this working with Grant Searle's modified Microsoft BASIC ROM, so it could be used on standard RC2014 systems. That uses the interrupts, so need more than a trivial change to make it work. It should be OK as it polls the status register to check for a byte, I should be able to change that to do the quick "toggle RTS and wait briefly to see if there is a start bit received".

I am hoping this will be seen as a neat idea and a good companion board to the Minstrel 4th, and not "some idiot thinks he can bit-bash high baud rate RS232 on a Z80......"


Adverts

The new Software Serial card is available from Tindie,as a kit or assembled, and with an optional replacement ROM if you don't want to burn your own.

The Minstrel 4th is also available form my Tindie store, optionally with a Software Serial card included:

Shipping

I can still ship worldwide.

Currently shipping Royal Mail to the US is working. You pay the 10% tariff upfront (as part of the postage), so that should be plain sailing and no problems with customs. We're all pawns in a petulant child's political games, but we've got to just keep going and try to make this stuff work.

I have also built some more Mini PET 40/80 Internal boards, since the Mini PET II is still in development but people keep asking, so here they are.



Patreon

You can support me via Patreon, and get access to advance previews of posts like this and development logs on new projects like the Mini PET II and Mini VIC and other behind the scenes updates. This also includes access to my Patreon only Discord server for even more regular updates.