ZYNQ’s UART controllers are full-duplex asynchronous receivers and transmitters with separate transmit and receive data paths. The UARTs are memory-mapped peripherals controlled and accessed by the following registers (see the Blackboard Programmers Reference and the ZYNQ TRM page 1175 for more info).
Both the transmit and receive data paths include a 64-byte managed FIFO, and both FIFOs are accessed by the same 8-bit data port (the FIFO register at 0x00000030). A FIFO is a First-In, First-Out data structure that is commonly used to decouple data producers and data consumers, so that data producers and consumers do not need to be tightly synchronized. For example, a data producer might quickly produce a collection of data points all at the same time, and deposit them in a FIFO where a consumer can access them one at a time, at a slower rate. As the FIFO nears empty, the producer can quickly load the FIFO with another batch of data, and again wait until the consumer finishes processing that batch. Likewise, a data producer might produce data points at a slower rate, and store them in a FIFO. Then when the FIFO is full (or nearly full), the consumer can retrieve the points all at once, and then wait again for the producer to refill the FIFO.
In a managed hardware FIFO structure, a single data port exists for loading data into the FIFO and for reading data out of the FIFO. The FIFO memory structure used on ZYNQ contains 64 bytes, and it seamlessly rolls over so that there is no logical first or last address – address 0 follows address 63, making a continuous modulo-64 memory structure. As data is loaded in, pointers maintain the address of the most recently added byte, and the oldest unread byte. If the pointers ever match after a FIFO load, the FIFO is full. As data is removed, the oldest unread byte is removed first, and the oldest data pointer is incremented. If the pointers ever match after a read, the FIFO is empty.
ZYNQ’s UART Transmit module
In the UART controller on ZYNQ, transmit data placed in the FIFO will automatically be serialized and sent out the TXD pin. The transmit module removes parallel data from the FIFO and loads it into the transmitter shift register so that it can be serialized and sent out the TXD pin. A start bit (always logic 0, since the TXD signal idles at logic 1) is shifted out first, then then 8 data bits, then the parity bit (if parity was enabled), and then one or two stop bits. These 9 to 12 bit packets will be sent continuously as long as data remains in the transmit FIFO. Depending on the defined bit-clock, each packet could take 40us to several milliseconds to send.
Data is shifted out on the falling edge of the bit-period clock (the bit-period clock is called the “baud_tx_rate” in the ZYNQ TRM). If the processor writes data into the FIFO faster than it can be sent out the TXD pin, the FIFO will fill and new data will not be added to the FIFO. It is up to the user program to ensure the FIFO doesn’t fill, either by checking the transmit FIFO status bits, or by setting interrupts (including possibly setting a “trigger point” to indicate when FIFO is nearing full status).
ZYNQ’s UART Receive module
The receive module constantly oversamples the RXD data pin using the baud_sample clock (the baud_sample clock runs at a higher frequency than the bit-period clock - see below). When a 1 – 0 transition is first detected, a start bit may be present. The receiver waits for ½ of a bit time (based on the baud rate), and then samples the RXD signal three more times using the baud_sample clock. If all three samples are ‘0’, a start bit is assumed. The receiver then waits one bit-clock period, then samples RXD again in the theoretical center of the LSB, and then waits another bit-clock period and samples bit1, and so on for all 8 bits.
The sampled data from the RXD pin is loaded into the receive shift register. The receive module checks the parity bit (if enabled), and checks for the selected number of stop bits (parity or framing errors are gneerated if these bits are not correct).
Received data must be read by the processor at least as fast as it arrives, or the FIFO will fill and new data will be lost. When parallelized data enters the receive FIFO, the RxFIFO empty flag is set to ‘0’, and it remains low until all data has been read from the FIFO (reading from an empty FIFO returns all zeros). If the read FIFO is filled, new data cannot be written into the FIFO. A threshold trigger level can be set to issue an interrupt when the receive FIFO reaches a certain fill level. Readable status bits are available to indicate when the receive FIFO has at least one data point, and when it’s full, empty, or nearly full. Interrupts can also be set to indicate these conditions, or when the FIFO data has reached an user-settable trigger point.
The table below provides a brief overview of the available UART FIFO status bits. Refer to the ZYNQ TRM or BPR for more information.
Several interrupts can be enabled to help manage both the transmit and receive FIFOs. The table below provides a brief overview of the available interruts; refer to the ZYNQ TRM or BPR for more information.
Baud rate generation
The UART controllers produce two internal clocks from the master clock input. One clock, here called the “bit period_clock” is used by the transmitter to shift data out on the TXD pin, and a second higher frequency clock called the “baud_sample_clock” is used by the receiver to sample incoming data on the RXD pin. The RXD sample clock uses a higher frequency so it can sample the RXD input signal several samples during each bit period (the bit period is defined by the TXD clock, and the TXD clock defines the “baud rate”). By taking several samples, the receiver can do a better job of sampling the RXD pin in the middle of the bit period, and it can do a better job of filtering and rejecting noise.
ZYNQ’s UART controllers include two programmable clock dividers to produce the receiver’s baud_sample_clock and the transmitter’s bit_period_clock. On the Blackboard, the UART controllers receive a 100MHz master clock input. The 100MHz input clock is divided by the 16-bit value in the BAUDGEN register to produce the baud_sample_clock, and the baud_sample_clock is further divided by the 8-bit value stored in the Baud_Rate_Divider regsiter to produce the transmitter TXD clock (Note: prior to doing the divide, a ‘1’ is added to the value stored in the Baud_Rate_Divider register).
The RXD and TXD pins from UART1 are routed to two of ZYNQ’s MIO pins, and those two pins are routed to the FTDI FT2232H USB interface chip. The FTDI chip and FTDI windows driver work together to “pass through” the UART signals from the Blackboard to the PC, allowing you to configure and use the UART on ZYNQ without needing to know anything about the physical transport layer. Likewise on the PC side, the FTDI COM driver allows any terminal program to seamlessly connect to the UART, again without any concern for the physical transport layer.
Configuring ZYNQ’s UART Controller
A typical sequence for configuring and initializing ZYNQ’s UART1 might include the following steps. You are encouraged to read the Blackboard Programmers Reference and/or the ZYNQ TRM to learn more as you consider these steps.
- Reset the UART Controller by setting the appropriate bits in the Control Register
- Set the BAUDGEN clock divider (a baud rate of 115200 works well, and a 0x7C is a workable divider value)
- Set the Buad_Rate_Divider value to define the bit-clock (again for 115200, 0x6 works)
- Set bits in the Control Register to enable software resets for the RX and TX data paths, enable the transmitter and receiver, and enable Stop Transmitter Break
- Set bits in the UART mode register to set the channel mode to normal, stop bits to 1, parity to “no parity”, character length to 8 bits, and clock source to UART reference clock
- Set bits in the RXTOUT register to set the timeout value to 1
- Disable all interrupts by setting bits in the IDR register
After completing these steps, the UART1 will be properly configured. Any data you write to the FIFO data port register will be automatically sent out UART1’s TXD pin (and it will route through the FTDI part and USB cable), and if you are running a terminal program, the data will be displayed. Any incoming data on RXD (again, from the terminal program, through the FTDI part and to UART1’s RXD pin) will be available for reading at the FIFO data port register.