ZYNQ's UART Controllers

Working with the UART controllers on ZYNQ

1209

ZYNQ’s UART

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).

Table 1. Important UART registers
Table 1. Important UART registers

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 one-half 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 is 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.

Figure 1. UART FIFOs
Figure 1. UART FIFOs

The table below provides a brief overview of the available UART FIFO status bits. Refer to the ZYNQ TRM or BPR for more information.

Table 2. FIFO Status Bits
Table 2. FIFO Status Bits

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.

Table 3. UART intrrupts
Table 3. UART intrrupts

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 times 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).

Figure 2. UART Baud Rate Control
Figure 2. UART Baud Rate Control

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

Several steps are required to configure ZYNQ’s UART controllers. The registers mentioned are detailed in the Zynq’s reference manual, along with more in-depth explanations of the UART module’s operation. Below is a brief overview of how to configure the UART module.

First, reset the UART module by setting the TX and RX reset bits in the control register. When these bits clear the reset is complete. After the reset, configure the appropriate registers in the module. The steps listed below can occur in any order

  • Configure the mode register for your desired mode.
  • Enable the transmitter and receiver by setting the enable bits in the control register.
  • Configure the baudrate by writing to to the baudgen and bauddiv registers

After these registers are configured you can use the transmit(TX) and recieve(RX) FIFO’s to communicate over UART. When the transmitter is enabled, any data in the TX FIFO is transmitted automatically in the order it was placed in the FIFO. When the receive is enabled, any data received over the channel will be placed in the FIFO and can be read in the order it was received.

A More Detailed look

The UART module has a control register which handles the resets, enabling and disabling of the rx and tx channels. It also has a Mode register which defines how data is sent through the channel. The settings configured in the mode register must be the same on the connected device for UART communication to be correct. A common configuration of uart is using 8 bits of data, 1 stop bit, and no parity bits.

The following code resets and enables the transmitter and receiver of UART1. It also configures the mode register so the UART system uses 8 data bits, 1 stop bit, and no parity. Make sure you read the Zynq’s TRM so you understand why the specific values are being written to the control and mode registers.

// control register
#define UART1_CR	*((uint32_t *) 0xE0001000)

//mode register
#define UART1_MR	*((uint32_t *) 0xE0001004)


//resets the UART module
void reset_uart1()
{
	UART1_CR = 3;	//assert the tx and rx resets

	while(UART1_CR&0x3);	//wait for reset to occur
}


void conf_uart1()
{
	uint32_t temp;

	reset_uart1(); //start reset of uart1

	//configure the mode register
	//leave in normal mdoe, bits [9:8]=00
	//one stop bit, bits [7:6]=00
	//8 data bits, bits [2:1] = 0x
	//use reference clock bit [0]= 0
	temp =0;

	//set to no parity; bits [5:3]=1xx
	temp |= 0b100000;


	UART1_MR = temp; //write mode configuration

	//set only bits 2 and 4 to enable tx and rx
	temp = 4 | 16; //enable transmitter and receiver

	UART1_CR = temp; //write to control reg

}

After configuring the above registers, you only need to configure the baudrate through the Baudgen and Bauddiv registers before you can use the UART. After you configure the control,mode, and baud rate registers you can use the UART’s FIFOs to send and receive data to/from your terminal.

The addresses for the baudgen and bauddiv registers are given below.

//baudgen register
#define UART1_BDGEN	*((uint32_t *) 0xE0001018)

//baud divider register
#define UART1_BDDIV	*((uint32_t *) 0xE0001034)

If you want a baudrate of 115200, you can put 0x7C (decimal 124) in the baudgen register and 6 in the bauddiv reg.