System/1's serial interface board provides a single RS232 port (with headers to use either 9- or 25-pin D connectors), running at a selection of popular baud rates from 38,400bps down to 75bps, with 16-byte transmit and receive buffers and full hardware handshaking signals. Only 8N1 framing is supported and baud rates must be symmetrical, so it probably doesn't count as a UART — but it does the job for talking to the odd terminal or peripheral!
The board connects to the rest of the system via the 8-bit peripheral interface board, and uses four registers within the 4-bit address space this provides. A future refinement would be to implement an additional address decoder in front of the existing design, so that up to four serial boards can share one peripheral slot. Before I do that, though, it would make sense to turn the hand-wired prototype into a PCB design so I don't have to spend a couple of days wiring each board up...
The baud-rate generator is implemented with a Pierce oscillator (as used in the CPU's clock board, except this time using a baud-rate-friendly 2.4576MHz crystal) feeding a 74HC4020 ripple counter; a 74HC151 1-of-8 data selector then picks off the right clock rate to feed the receiver. This rate ends up being four times the actual baud rate we're using, as the receiver is sampling four sub-bits during each bit time and then using a basic majority detector to settle on the final value for each bit; this might be overkill, but I had visions of noise showing up at exactly the wrong time and flipping bits on me! The same clock is also fed through a pair of flip-flops to divide it down to the real baud rate, and the result is used to clock bits out of the transmitter. I toyed briefly with the idea of accommodating split baud rates, especially since I have a cute little Minitel terminal that speaks 1200/75bps natively, but even that can be configured for more modern symmetrical speeds so I'd rather save the added complexity for the time being.
The most important part of the receiver is simply a 74HC164 SIPO shift register, clocked from a counter formed from a pair of flip-flops; these are held in reset until the start bit is detected, and then produce a pulse during the fourth quarter of each bit time. This pulse clocks the output of the majority detector into the shift register, and also advances a 74HC161 counter; when this reaches 9 (start bit plus eight data bits), the shift register's outputs are strobed into the receive FIFO (formed from a pair of 74HC40105s, for a total of 16x8 bits) and the quarter-bit counter circuit is returned to the reset state and held there awaiting the next start bit.
On the transmitter side, another 74HC161 is used to form a state machine which is held in state 0 (IDLE) until a byte is available from the transmit FIFO — another pair of 74HC40105s — and the handshaking inputs indicate that the device at the other end of the serial connection is ready to receive. When both conditions are met the counter is allowed to advance; state 1 is the START state, during which the start bit is sent and the next byte from the FIFO is loaded into a 74HC166 PISO shift register. During states 2-9 the eight data bits are clocked out of the shift register, after which it returns to state 0. Since the availability of another byte is only checked in state 0, this means that a single stop bit will be sent between bytes even if the FIFO can provide data immediately.
Alongside these main functional blocks are various bits of glue and miscellany. A 74HC125 quad buffer is used to provide four bits split across a pair of status registers (one for transmitter status, one for receiver status); these mainly indicate whether there is available data (or room for data) in the corresponding FIFOs. A configuration register is also implemented using a 74HC273 octal D flip-flop, with a 74HC244 octal buffer allowing for read-back; the eight configuration bits control the following:
- Baud rate (three bits, covering 38,400bps down to 75bps)
- The state of the RS232 DTR line, allowing us to indicate to the other end that we know we're going to be too busy to service the serial port for a while; the RTS signal is automatically generated based on whether there's room in the receive FIFO for another byte.
- Whether the transmitter obeys or ignores incoming handshaking signals
- Whether the transmitter status register includes the state of the relevant handshaking signals — masking them off allows a running program to poll the transmit FIFO status more rapidly if it only cares about keeping the buffer full
- Whether an interrupt should be generated when a byte is available in the receive FIFO
- Whether an interrupt should be generated when there is room for a new byte in the transmit FIFO
Finally, a MAX238 converts between the 5V CMOS levels used by the main logic and the higher bipolar voltages used by RS232, and a 74HC139 is used to map the FIFOs, status registers, and config register to four addresses within the register address space provided to each device attached to the 8-bit peripheral interface board. The serial board doesn't fully decode the address lines, so each register appears four times within the 4-bit address space; if I decide I need to add additional serial ports later, I will probably try to squeeze another decoder onto a PCB version of the design in order to allow several ports to share one base address.
Related Build Log Entries
- 20th August, 2017 (backdated) - Learning to talk to the outside world
- 28th November, 2017 (backdated) - Driving the serial interface prototype from software
- 23rd December, 2017 (backdated) - On the dangers of foolish shortcuts
- 8th March, 2018 (backdated) - Shifty-looking characters hanging around (serial) ports