final circuit: a ready-made UART transceiver assembled from a 7400 series IC
First of all, let's figure out what a UART is... It is a universal asynchronous transceiver - a simple protocol that allows you to send and receive 8-bit data asynchronously so that a processor or computer can communicate with the outside world. This is useful in itself - my 8-bit computer can communicate with a laptop and use a serial port monitoring program (like putty ) as an interface for text input and output. Even more interesting, I can program the OS bootloader for my 8-bit computer, and then program it over the UART connection from the laptop! Since the HC-05 type Bluetooth modules essentially communicate with the CPU via the UART, I can even use the Bluetooth module to program my 8-bit computer from a distance! It would be amazing.
Some purists would consider programming an 8-bit computer with a much more powerful computer a fraudulent approach - but this is my project and it lives by my rules! Please program the handcrafted machine with DIP switches if you enjoy data entry rather than programming and want an authentic hard work experience.
Whatever it was with programming, I, at least, decided to limit myself when developing a computer to simple TTL chips - no Arduino, Raspberry Pi, ESP8266 and other Turing-complete modules (otherwise what would be of interest?).
UART protocol and design constraints
Before you is the structure of the UART signal . It has a start bit, denoted by a high to low transition of the signal, followed by a data byte (LSB first), and then a stop bit, which drives the signal high. Sometimes there is also a parity bit, but it is not required, so I omitted it for reasons of simplicity. The transmission time for each bit is determined by the baud rate (in this case, bits per second). For example, a 9600 baud rate means that a bit is transmitted in
1/9600 = 104 μs. The waveform is quite simple, so we can implement it entirely in hardware on logic chips.
I had to choose a crystal oscillator that would give me access to standard baud rates, preferably divisible by powers of two, so that it would be convenient to operate with a binary counter. After some thought, I decided to use a 2.4576 MHz oscillator, since it allowed transferring at 38400 bps (divided by 64), or 9600 bps (divided by 256).
UART transmitter
List of components:
- 2.4576 MHz crystal oscillator
- 3 x 74LS161 4-bit counters
- 74LS674 16-bit shift register
- 74LS06 AND
- 74LS74 D-trigger
- 74LS04 NOT
- Diode 1N4001
- 470 uF (!) Capacitor (power smoothing)
Scheme
The UART transmitter is the easiest to understand. Basically, it is a parallel load shift register with serial output. It loads the data byte, keeps track of the start and end bits, and synchronizes it to the desired baud rate. The diagram below shows this process. In part (1), the 2.4576 MHz crystal oscillator is slowed down to 38 400 Hz using two 4-bit 74LS161 counters. In part (2), the 74LS674 16-bit shift register is used to synchronize data for the UART. I use this register because I already had it at hand. I understand that this IC is expensive and may be difficult to find, but it definitely simplified my whole scheme.
With just three of these ICs (two 4-bit counters and a shift register), you can send a continuous stream of characters to the UART transmitter at 38,400 bps (no parity)! Yes, it is a continuous stream - I did not take into account that the shift register updates the load buffer in a circle - oops. I didn't need this behavior - I wanted the processor to send one byte at a time. Things are complicated by the fact that the clock pulses of the processor and UART are not synchronized, and I did not want to make assumptions about whose timer is faster, which signal will be relevant at what moment, etc. Since I needed to handle asynchrony reliably, I decided to use the following scheme that works well:
- (3) The processor sends a byte transfer signal out of sync with the processor and UART clock.
- «». ( AND 74LS06 D- 74LS74).
- UART «» 4- 74LS161. UART.
- (4) 16 , .
Note that I am shifting 16 bits instead of 10 bits of the UART transmitter signal - mainly due to the convenience of using the carry bit to disable the transmit circuitry. I could use a decimal counter (for example, the 74LS162), but I didn't have one at hand when I assembled the circuit on a breadboard. Perhaps in the final scheme I will switch to it.
UART receiver
List of components:
- 2.4576 MHz crystal oscillator (you can use the same oscillator as the receiver)
- 3 x 74LS161 4-bit counters (can use one of the IC from the receiver)
- 74LS74 D-trigger
- 74LS04 NOT (can use receiver IC)
- Diode 1N4001
- 470 uF (!) Capacitor (power smoothing)
- 220 ohm resistors and LEDs for beauty.
It seems to me that if the UART transmitter described above is easy to understand, then the receiver will be somewhat more complicated. However, what's good with digital logic is that it can be split into separate modules, and then everything doesn't seem so complicated anymore!
The waveforms in the lower left corner of the diagram below show what to consider when receiving a single digital transmitter bit. How do we know if a byte is being sent to us? Easy - the start bit is indicated by a high to low transition, so we can invert that and use the low to high transition to set the D-flip-flop (74LS74) (2).
Now we need to start writing the signal by shifting it into shift registers and sampling at the center of the data bit sequence. What is important to understand: since we do not know when we will start receiving data from the UART, this process will not be synchronous with our clock pulses. Therefore, the faster our impulses are, the closer we come to the true origin of the transmitter signal. For convenience, my clock speed is 16 times the baud rate (1). This means that each transmitted bit goes through 16 pulses of this generator. Therefore, in order to take a sample approximately in the middle of the transmitted data, we must do it at the count of 8 - for this we generate the SAMPLING_CLK (3) signal.
Then, on the rising edge of this new clock signal, we can synchronize the transmitted signal with two associated 8-bit Serial Parallel Output Shift Registers (SIPO) in the middle of each data bit. At the 16th count, we end up with a digital bit, so we increment another counter that keeps track of the total number of bits synchronized in (5). When this counter reaches 16 (it could have been a decimal counter), the receiving circuitry is disabled by clearing the D flip-flop. Phew! I give the diagram below, and I hope that you will be able to trace the logic of its operation using my description.
Unfortunately, I do not have an oscilloscope, and initially my circuit gave some mysterious results, accepting one byte, and then accepting another in a different way. I changed the 2.4576 MHz oscillator for a 1-second 555 oscillator to check the counting logic, and I found a problem with a floating input on the pin of one of the counters (I was debugging using LEDs). I tied both counter reset pins to the RX_active signal, causing the counters to switch between on and reset, which clears their output at the end of each data acquisition cycle. The counters are now working as expected, and when I put the oscillator back on 2.4576 MHz everything started to work correctly and reliably.
The final computer circuit on the breadboard will have an output register to control the data output to the bus. Finally, I used an extra D-flip-flop on the 74LS74 to implement the RX_READY signal, which the processor can read to check if the byte is ready to read (it is true only when the byte is fully received).
Below is a photo of the assembled and working computer. The UART-USB interface is the dongle at the top right. The middle board contains a crystal oscillator and 4-bit counters that generate various clock pulses. At the top, next to the USB power, is a 16-bit shift register. The left board contains the logic for the controlled sending of one byte (UART TX). You can see the button with which I simulated the processor control signal and the 555 timer, which acts as a processor clock pulse. The UART RX module lives on the right board. Green LEDs indicate the receipt of a byte at the input, yellow LEDs indicate data reception (UART RX busy signal), and red LEDs turn on when the byte is ready to be read by the processor.
Looking for prettier breadboards and wiring skills
Addition
I optimized the circuit a little (along the way, having learned a lesson about the difference between processing asynchronous and synchronous events in discrete IC logic). I wanted to reduce the number of chips by using a decimal counter that would count the incoming bits, and count 10 bits instead of 16. Then I could remove the shift register.
I first tried the 74LS162 counter. For one byte, everything worked, but I quickly discovered that it had a synchronous reset mechanism - that is, it takes one clock cycle to reset the signal. Since the clock stopped after the last bit was received, the counter was not cleared. The 74LS161 4-bit counter that I removed had an asynchronous reset, so everything worked before. It's good that we found a decimal counter with asynchronous reset - 74LS160. Everything works fine with him - see the updated diagram.
Checking for errors in the received byte
For simplicity, I have not added any error checking in the resulting byte. You can imagine that we added a parity bit and toggle the flip-flop every time a "1" is received. Then we would know whether we received an even or odd number of bits, and could compare it with the parity bit by setting the flag when it does not match. In addition, this can include a verification check that the stop bit was equal to "1". To save space, I did not add this functionality, but I want to add it in the future. The modularity of the project allows you to do this as needed.
Notes
I love 8-bit computers on breadboards and enjoyed doing this mini-project. I have been designing this circuit for quite some time, and I was still shocked when I put it together and everything worked. This is some kind of magic! Almost.