UART Transmitter
MIT Fall 2024
Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.
UART Protocol
Last week, we saw the SPI protocol in use, and now it's time to use another serial data protocol: Universal Asynchronous Receiver/Transmitter, or UART.
This page will have you build a transmitter for UART, which only needs one wire to transmit data as opposed to the three wires of SPI (data, clock, and chip select)! By moving to fewer wires (a benefit), however, there are sacrifices that need to be made in order for it to work reliably.
Here's a handful of highlights of the protocol that'll be important for implementing it:
- Asynchronous
The UART protocol has only two wires, one for transmitting data and one for receiving data. What that means is there's no clock to rely on. Instead, both sides of the bus agree on a BAUD rate, which defines the number of bits that are transmitted on a wire per second. BAUD rates need to be substantially smaller than the operating clock of the devices on either side of the bus, so that each device can reliably keep time as they send and receive data.
- Start and Stop Bits
In order to distinguish when there's silence as opposed to a byte being sent on a serial wire, UART includes a "start" bit (0) and a "stop" bit (1) before and after each byte. The wire is held high (logical 1
) when not in use to send data, and then the "start" bit (0) indicates that data is about to be sent. The "stop" bit (1) should be sent after the byte is transmitted.
- Little Endian
When the bits within a byte are being sent, UART decided (somewhat arbitrarily, but largely now for backwards compatibility reasons/convention) that the first bit sent should be the least significant bit of the byte (i.e. byte[0]
, i.e. the "1's" place of the byte), and then the following bits will each be from a more significant place. This is different from what we did in SPI1.
Check Yourself: UART
When you write modules for UART, it may be helpful to define a local parameter clk_i
to represent the input clock frequency, and baud
to represent the BAUD rate.
localparam BAUD_BIT_PERIOD
to be equal to this expression.
Building Your Own Transmitter
Try your hand at making a UART transmitter module based on what we discussed above, that can take in data bytes and send them serially over a UART wire.
Parameters
parameter INPUT_CLOCK_FREQ
: frequency, in Hz, ofclk_in
parameter BAUD_RATE
: bitrate for UART transmission, in bits/second
Inputs and Outputs
input wire clk_in
: operational clockinput wire rst_in
: signal to reset the module (active high)input wire [7:0] data_byte_in
: carries the byte that should be transmitted over UART; it should only be read when a transmission is started!input wire trigger_in
: indicates that a byte transmission should be started, and thatdata_byte_in
contains valid data for the next transmission. Should be ignored if your module is still busy sending a message!! Don't let a new trigger interrupt an ongoing message.output logic busy_out
: should be held high whenever the module is still transmitting a byte and can't accept new data input. We didn't do this in our SPI module, but this is a means of allowing "back-pressure", which is a way of telling the system driving the module that new transmits will not be accepted right now (and to, if needed, hold up its operation). This should be busy even when sending start and stop bits!output logic tx_wire_out
: serial output signal, as described by UART
Testing
We're into our third week together. It is time that you start to generate your own test code from scratch. Feel free to bring over your SPI tester code from last week for at least a boilerplate starter (but of course make sure to update/modify all appropriate fields, variables, and function names. use the cocotb documentation, and the exercise from week 01, as reference when writing your tests! A few things to keep in mind while testing:
- When a device is not transmitting, the data line must be kept high. This includes when being reset. Be very careful to avoid false 0's. These can trigger the listener to think a message has started transmitting and cause bad packets to be received. This can impact not only the next byte sent, but many bytes into the future depending on the contents of the data.
- The stop and start bits are extremely important. They must be full-length (as long as any data bit). Do not neglect them.
- Things are transmitted lsb-first. lsb-first.
- lsb-first.