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.