ZYNQZYNQ7000 UART controller and driver application example

Introduction to UART

When we use PS, we usually add a UART controller for printing information and debugging code. In addition, PS in and out
When communicating with external devices, the serial port is often used for communication.

UART controller

The UART controller is a full-duplex asynchronous transceiver controller. ZYNQ contains two UART controllers, UART0 and UART1. Each UART controller supports programmable baud rate generator, 64-byte receive FIFO and transmit FIFO, interrupt generation, loopback mode setting for RXD and TXD signals, and configurable data bit length, stop bits and parity way etc.
The block diagram of the UART controller system is shown in the figure:

As can be seen from the figure above, the UART controller and IO ports are driven by the reference clock (UART REF_CLK), and the controller also needs to be connected to the APB bus clock (CPU_1x clock). Both UART REF_CLK and CPU_1x clock come from the PS clock subsystem. The configuration of the UART controller and the acquisition of the status are completed by the control (Control) and status registers (Status Registers).

In addition, the UART controller can not only be connected to MIO, but also be mapped to EMIO, so as to use the port of PL to realize the function of serial communication. When the UART controller is connected to MIO, there are only two pins, Tx (transmit) and Rx (receive); and when connected to EMIO, in addition to Tx and Rx pins, there are optional pins such as CTSN, DSDN, DSRN, etc. These pins are used for the flow control of the serial port, that is, in the data communication of the modem.

The UART controller uses independent receive and transmit data paths, each path contains a 64-byte FIFO, and the controller performs serial-to-parallel conversion on the data in the transmit and receive FIFOs. The interrupt flag of the FIFO supports polling processing or interrupt-driven processing. Additionally, there is a mode switch in the controller that supports various loopback configurations for the RXD and TXD signals. The internal block diagram of the UART controller is shown in the figure below:

The registers of the UART controller are interconnected with the PS AXI bus through the APB slave interface, and the registers of the controller are used to configure and obtain the status of the UART controller. The baud rate generator (Baud Rate Generator) provides the bit period clock for the receiving end and the transmitting end of the UART controller; the interrupt controller (GIC) provides the interrupt service function for the serial port transceiver.

The APB bus interface loads data into the TxFIFO memory by writing a value to the TxFIFO register. When the data is loaded into the TxFIFO, the empty flag of the TxFIFO becomes invalid until the last data is moved out of the TxFIFO and loaded into the transmission shift register, and the TxFIFO restores the empty flag. At the same time, TxFIFO uses TFULL (full interrupt status) to indicate that the current TxFIFO is full, and it will prevent data from continuing to be written. If the write operation continues at this point, an overflow will be triggered and the data will not be loaded into the TxFIFO. The RxFIFO memory receives the data from the receiving shift register. When the data is received, the RxFIFO empty flag signal also becomes invalid until all the data is sent out through the APB bus. The full flag status of the RxFIFO is used to indicate that the RxFIFO is full and will prevent more data from being written.

Mode Switch

The mode switch (Mode Switch) controller controls the signal connection mode of RxD and TxD, which is divided into four modes in total, namely: Normal Mode, Automatic Echo Mode, Local Loopback Mode (Local Loopback Mode) Mode) and Remote Loopback Mode. The function diagram of mode switching is shown as follows:

From the figure above, we can clearly see the functions realized by UART in different modes. In practical applications, the most commonly used is the normal mode of UART.

  • Normal mode is the standard UART operating mode;
  • In auto-echo mode, RxD is connected to TxD, the controller can receive data, but cannot send data;
  • The local loopback mode does not connect the RxD and TxD pins, which is used for the loopback test of the local program;
  • In remote loopback mode, RxD is connected to TxD, but not connected to the controller, so the controller cannot send and receive data in this mode.

UART driver application example

Test platform: Heijin AX7Z035

Chip model: XC7Z035-2FFG676

Driver example

  • uart.c
/**
 * Copyright (c) 2022-2023, HelloAlpha
 *
 * Change Logs:
 * Date Author Notes
 */
#include "uart.h"

/**
 * @brief serial port initialization
 *
 * @param UartInstancePtr serial port instance
 * @param UartFormat serial communication format
 * @param UartDeviceId serial port ID number
 * @return int
 */
int UartInit(XUartPs* UartInstancePtr, XUartPsFormat* UartFormat, uint16_t UartDeviceId)
{<!-- -->
    int Status;
    XUartPs_Config *UartConfigPtr;

    UartConfigPtr = XUartPs_LookupConfig(UartDeviceId);
    if (NULL == UartConfigPtr) {<!-- -->
        return XST_FAILURE;
    }

    Status = XUartPs_CfgInitialize(UartInstancePtr, UartConfigPtr,
                        UartConfigPtr->BaseAddress);
    if (Status != XST_SUCCESS) {<!-- -->
        return XST_FAILURE;
    }

    Status = XUartPs_SelfTest(UartInstancePtr);
    if (Status != XST_SUCCESS) {<!-- -->
        return XST_FAILURE;
    }

    /* Set UART mode Baud Rate 115200, 8bits, no parity, 1 stop bit */
    XUartPs_SetDataFormat(UartInstancePtr, UartFormat);
    /* Set the UART in Normal Mode */
    XUartPs_SetOperMode(UartInstancePtr, XUARTPS_OPER_MODE_NORMAL);

    return Status;
}

/**
 * @brief serial interrupt initialization
 *
 * @param IntcInstancePtr interrupt instance
 * @param UartInstancePtr Serial interrupt instance
 * @param UartIntrId Serial port interrupt ID number
 * @param CallBack interrupt service function
 * @return int
 */
int UartIntrInit(XScuGic *IntcInstancePtr, XUartPs *UartInstancePtr,
        uint32_t UartIntrId, void(* CallBack)(void *))
{<!-- -->
    int Status;

    XScuGic_Config *IntcConfig;

    IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
    if (NULL == IntcConfig) {<!-- -->
        return XST_FAILURE;
    }

    Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
            IntcConfig->CpuBaseAddress);
    if (Status != XST_SUCCESS) {<!-- -->
        return XST_FAILURE;
    }

    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler) XScuGic_InterruptHandler,
            IntcInstancePtr);
    Xil_ExceptionEnable();

    Status = XScuGic_Connect(IntcInstancePtr, UartIntrId,
            (Xil_ExceptionHandler) CallBack,
            (void *) UartInstancePtr);
    if (Status != XST_SUCCESS) {<!-- -->
        return XST_FAILURE;
    }

    /* Set the receiver timeout. */
    XUartPs_SetRecvTimeout(UartInstancePtr, 8);

    /*Set receiver FIFO interrupt trigger level, here set to 1*/
    XUartPs_SetFifoThreshold(UartInstancePtr, 1);
    /* Set interrupt trigger type */
    XUartPs_SetInterruptMask(UartInstancePtr,
                        XUARTPS_IXR_RXOVR | XUARTPS_IXR_RXEMPTY | XUARTPS_IXR_TOUT);
    XScuGic_Enable(IntcInstancePtr, UartIntrId);

    return Status;
}

/**
 * @brief Serial data sending function
 *
 * @param InstancePtr serial port instance
 * @param BufferPtr send buffer pointer
 * @param NumBytes the number of bytes to send
 * @return int the number of bytes successfully sent
 */
int UartPsSend(XUartPs *InstancePtr, uint8_t *BufferPtr, uint32_t NumBytes)
{<!-- -->
    uint32_t SentCount = 0U;

    /* Setup the buffer parameters */
    InstancePtr->SendBuffer.RequestedBytes = NumBytes;
    InstancePtr->SendBuffer.RemainingBytes = NumBytes;
    InstancePtr->SendBuffer.NextBytePtr = BufferPtr;


    while (InstancePtr->SendBuffer. RemainingBytes > SentCount)
    {<!-- -->
        /* Fill the FIFO from the buffer */
        if (!XUartPs_IsTransmitFull(InstancePtr->Config.BaseAddress))
        {<!-- -->
            XUartPs_WriteReg(InstancePtr->Config.BaseAddress,
                    XUARTPS_FIFO_OFFSET,
                    ((uint32_t)InstancePtr->SendBuffer.
                            NextBytePtr[SentCount]));

            /* Increment the send count. */
            SentCount++;
        }
    }

    /* Update the buffer to reflect the bytes that were sent from it */
    InstancePtr->SendBuffer.NextBytePtr + = SentCount;
    InstancePtr->SendBuffer.RemainingBytes -= SentCount;

    return SentCount;
}

/**
 * @brief Serial data receiving function
 *
 * @param InstancePtr serial port instance
 * @param BufferPtr receive buffer pointer
 * @param NumBytes the number of bytes to read
 * @return int the number of bytes successfully read
 */
int UartPsRev(XUartPs *InstancePtr, uint8_t *BufferPtr, uint32_t NumBytes)
{<!-- -->
    uint32_t ReceivedCount = 0;
    uint32_t CsrRegister;

    /* Setup the buffer parameters */
    InstancePtr->ReceiveBuffer.RequestedBytes = NumBytes;
    InstancePtr->ReceiveBuffer.RemainingBytes = NumBytes;
    InstancePtr->ReceiveBuffer.NextBytePtr = BufferPtr;

    /*
     * Read the Channel Status Register to determine if there is any data in
     * the RX FIFO
     */
    CsrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
            XUARTPS_SR_OFFSET);

    /*
     * Loop until there is no more data in RX FIFO or the specified
     * number of bytes has been received
     */
    while((ReceivedCount < InstancePtr->ReceiveBuffer. RemainingBytes) & amp; & amp;
            (((CsrRegister & XUARTPS_SR_RXEMPTY) == (u32)0)))
    {<!-- -->
        InstancePtr->ReceiveBuffer.NextBytePtr[ReceivedCount] =
                XUartPs_ReadReg(InstancePtr->Config.BaseAddress, XUARTPS_FIFO_OFFSET);

        ReceivedCount++;

        CsrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
                XUARTPS_SR_OFFSET);
    }
    InstancePtr->is_rxbs_error = 0;
    /*
     * Update the receive buffer to reflect the number of bytes just
     * received
     */
    if(NULL != InstancePtr->ReceiveBuffer.NextBytePtr){<!-- -->
        InstancePtr->ReceiveBuffer.NextBytePtr + = ReceivedCount;
    }
    InstancePtr->ReceiveBuffer. RemainingBytes -= ReceivedCount;

    return ReceivedCount;
}

/**
 * @brief serial port baud rate change
 *
 * @param UartDeviceId serial port ID number
 * @param Baudrate the baud rate to be set
 * @return int
 */
int UartSetBaudRate(uint16_t UartDeviceId, uint32_t Baudrate)
{<!-- -->
    static XUartPs Uart;
    XUartPs_Config *UartConfigPtr;
    UartConfigPtr = XUartPs_LookupConfig(UartDeviceId);
    if (NULL == UartConfigPtr) {<!-- -->
        return XST_FAILURE;
    }
    XUartPs_CfgInitialize( & Uart, UartConfigPtr, UartConfigPtr->BaseAddress);
    XUartPs_SetBaudRate( & amp; Uart, Baudrate);
    return XST_FAILURE;
}

  • uart.h
/**
 * Copyright (c) 2022-2023, HelloAlpha
 *
 * Change Logs:
 * Date Author Notes
 */
#ifndef __UART_H__
#define __UART_H__

#include "xuartps.h"
#include "xscugic.h"

#ifndef INTC_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#endif

#define UART0_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID
#define UART1_DEVICE_ID XPAR_XUARTPS_1_DEVICE_ID
#define UART0_IRPT_INTR XPAR_XUARTPS_0_INTR
#define UART1_IRPT_INTR XPAR_XUARTPS_1_INTR

/*
 * Function declaration
 */
int UartInit(XUartPs* UartInstancePtr, XUartPsFormat* UartFormat, uint16_t UartDeviceId);
int UartIntrInit(XScuGic *IntcInstancePtr, XUartPs *UartInstancePtr,
        uint32_t UartIntrId, void(* CallBack)(void *));
int UartPsSend(XUartPs *InstancePtr, uint8_t *BufferPtr, uint32_t NumBytes) ;
int UartPsRev (XUartPs *InstancePtr, uint8_t *BufferPtr, uint32_t NumBytes) ;
int UartSetBaudRate(uint16_t UartDeviceId , uint32_t Baudrate);

#endif

Application example

  • Initialize serial port one

  • app_uart.c

/**
 * Copyright (c) 2022-2023, HelloAlpha
 *
 * Change Logs:
 * Date Author Notes
 */
#include "app_uart.h"

#define UART0_BAUDRATE 115200U

extern XScuGic IntcInstPtr;

static XUartPsFormat _Uart0Format = {<!-- -->UART0_BAUDRATE, 8, 0, 1};

/**
 * @brief Interrupt handling functions
 *
 * @param CallBackRef is a pointer to an upper-level callback reference
 */
static void Uart0Handler(void *CallBackRef)
{<!-- -->
    XUartPs *UartInstancePtr = (XUartPs *) CallBackRef;
    struct uart_msg *_uart0_msg = &g_uart0_msg;
    static uint32_t ReceivedCount = 0;
    static uint32_t UartSrValue = 0;

    _uart0_msg->ReceivedFlag = 0;

    /* Read interrupt status and enable bits */
    UartSrValue = XUartPs_ReadReg(UartInstancePtr->Config.BaseAddress, XUARTPS_IMR_OFFSET);
    UartSrValue & amp;= XUartPs_ReadReg(UartInstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET);

    /* check if receiver FIFO trigger */
    if (UartSrValue & XUARTPS_IXR_RXOVR)
    {<!-- -->
        ReceivedCount = UartPsRev(UartInstancePtr, _uart0_msg->ReceivedBufferPtr, UART_MSG_MAX_LEN);
        _uart0_msg->ReceivedByteNum + = ReceivedCount;
        _uart0_msg->ReceivedBufferPtr + = ReceivedCount;
    }
    /* check if receiver FIFO empty */
    if (UartSrValue & XUARTPS_IXR_RXEMPTY)
    {<!-- -->
        /* do nothing */
    }
    /* check if it is a timeout interrupt */
    if (UartSrValue & XUARTPS_IXR_TOUT)
    {<!-- -->
        _uart0_msg->ReceivedFlag = 1;
    }

    /* clear trigger interrupt */
    XUartPs_WriteReg(UartInstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET, UartSrValue);
}

int app_uart_init(void)
{<!-- -->
    int Status;
    XUartPs *_Uart0_Ps = &Uart0_Ps;

    /* init UART */
    Status = UartInit( &Uart0_Ps, &_Uart0Format, UART0_DEVICE_ID);
    if (Status != XST_SUCCESS) {<!-- -->
        return XST_FAILURE;
    }

    /* init UART interrupt */
    Status = UartIntrInit( &IntcInstPtr, &Uart0_Ps, UART0_IRPT_INTR, Uart0Handler);
    if (Status != XST_SUCCESS) {<!-- -->
        return XST_FAILURE;
    }

    /* clear trigger interrupt */
    XUartPs_WriteReg(_Uart0_Ps->Config.BaseAddress, XUARTPS_ISR_OFFSET, XUARTPS_IXR_MASK);

    return XST_SUCCESS;
}
  • app_uart.h
/**
 * Copyright (c) 2022-2023, HelloAlpha
 *
 * Change Logs:
 * Date Author Notes
 */
#ifndef __APP_UART_H__
#define __APP_UART_H__

#include "uart.h"

/* maximum receiver length */
#define UART_MSG_MAX_LEN 100

struct uart_msg
{<!-- -->
    uint8_t ReceivedBuffer[UART_MSG_MAX_LEN];
    uint8_t *ReceivedBufferPtr;
    uint32_t ReceivedByteNum;
    char ReceivedFlag;
    uint8_t SendBuffer[UART_MSG_MAX_LEN];
    uint8_t *SendBufferPtr;
    uint32_t SendByteNum;
};

typedef struct uart_msg uart_msg_t;

XUartPs Uart0_Ps;

uart_msg_t g_uart0_msg;

int app_uart_init(void);
int uart_msg_print(void);
int uart_lookback_test(void);
int uart_report(uint8_t dev, uint8_t err);

#endif

Reference source: UG585