Home
General Guide
Register File
Boot Sequence
BLDC Motor FOC
Serial Comm Driver
Flash Driver
NVRAM Driver
RVDT Driver
Renesas
Microchip
Texas Instruments
Analog Devices
Mobile Devices
.NET Tips
Miscellaneous
Contact Us
Forum

Serial Communication Driver

Recommended Standard (RS232, RS422, RS485) serial communication could be simple if you get everything correct, or could be very frustrating sometimes. One of the challenges is the protocol between two communication equipment. The protocol could be either standard such as Modbus protocol, or proprietary. A good driver shall have two layers. The low layer driver shall provide port open, port read, port write, and port close functions. The upper layer driver shall parse the received packet and take actions according to the protocol of your application, as well as send a response. Before taking action, validation of the packet shall be conducted, such as SOP (Start of Packet) checking, EOP (End of Packet) checking, Checksum checking. If the validation fails, the receiver shall request a re-sending of the packet, or discard the packet, per the protocol requirements. Another challenge is to deal with the line break interrupt issue. In certain cases, proper handling must be taken to ensure that the communication can be resumed successfully after a line break occurs. Using an oscilloscope or serial communication monitor helps debugging communication problems. Here is an example of lower driver for a PC16650 compatible UART module implemented in a FPGA. It is worth mentioning specifically how the line break error is handled successfully here.

 

When a line break occurs, the current packet may be corrupted. To clear the current packet, read the bytes out of the FIFO one by one continuously till the end of the packet, but don't use the flush command,

pUart->FCR->BIT.RCVR_FIFOR = 1;

which can be used in the reset/init period.

 
1. UART_Open

 

This function initializes an UART port pointer so that we can use it to initialize Line Control Register (LSR) to configure data length, parity, stop bits and bard rate, FIFO Control Register (FCR) to enable FIFO and DMA feature, and Interrupt Enable Register (IER) to enable interrupts. In this application, we did not allow the interrupts, but use the interrupt flags in pulling mode.

#include "ES_UART.h"

/*********************************************************************************************
* Function: UART_Open
*
* Desc: This function opens the UART port to use in FIFO Polling mode
*
* Params: int ch - channel number (1 or 2)
* ConfigStruct *pConfig - pointer to the port Configuration
* FpgaUartCtrlStruct *pUart - pointer to the UART control block
*
* Returns: bool - true if the port opens successfully, false otherwise
*
* Notes:
*
*********************************************************************************************/
bool UART_Open(int ch, FpgaUartCtrlStruct *pUart, UartConfigStruct *pConfig)
{
    if(ch == 0) //FPGA UART 1
    {
        pUart->RBR = pFPGA1_UART_RBR;
        pUart->THR = pFPGA1_UART_THR;
        pUart->DLL = pFPGA1_UART_DLL;
        pUart->DLM = pFPGA1_UART_DLM;
        pUart->IER = pFPGA1_UART_IER;
        pUart->IIR = pFPGA1_UART_IIR;
        pUart->FCR = pFPGA1_UART_FCR;
        pUart->LCR = pFPGA1_UART_LCR;
        pUart->MCR = pFPGA1_UART_MCR;
        pUart->LSR = pFPGA1_UART_LSR;
        pUart->MSR = pFPGA1_UART_MSR;
        pUart->SCR = pFPGA1_UART_SR;
    }
    else if(ch == 1) //FPGA UART 2
    {
        pUart->RBR = pFPGA2_UART_RBR;
        pUart->THR = pFPGA2_UART_THR;
        pUart->DLL = pFPGA2_UART_DLL;
        pUart->DLM = pFPGA2_UART_DLM;
        pUart->IER = pFPGA2_UART_IER;
        pUart->IIR = pFPGA2_UART_IIR;
        pUart->FCR = pFPGA2_UART_FCR;
        pUart->LCR = pFPGA2_UART_LCR;
        pUart->MCR = pFPGA2_UART_MCR;
        pUart->LSR = pFPGA2_UART_LSR;
        pUart->MSR = pFPGA2_UART_MSR;
        pUart->SCR = pFPGA2_UART_SR;
    }
    else
    {
        return false;
    }

    //set up configuration
    pUart->IER->ALL = 0; //disable interrupts

    //sticky
    if (pConfig->Sticky)
    {
        pUart->LCR->BIT.SP = 1;
        pUart->LCR->BIT.PEN = 1;
    }
    else
    {
        //parity
        if (pConfig->Parity == ODD)
        {
            pUart->LCR->BIT.PEN = 1;
            pUart->LCR->BIT.EPS = 0;
        }
        else if(pConfig->Parity == EVEN)
        {
            pUart->LCR->BIT.PEN = 1;
            pUart->LCR->BIT.EPS = 1;
        }
        else
        {
            pUart->LCR->BIT.PEN = 0;
        }
    }

    //data bits
    pUart->LCR->BIT.WLS = pConfig->DataBits;


    //stop bits
    pUart->LCR->BIT.STB = pConfig->StopBits;
    //break bit
    pUart->LCR->BIT.SB = 0;

    //baud rate
    //divisor = Fin / (Baudrate * 16)
    pUart->LCR->BIT.DLAB = 1; //enable baud rate setting
    *pUart->DLL = FPGA_UART_BAUD_RATE_DIVISOR(pConfig->BaudRate) & 0xFF;
    *pUart->DLM = (FPGA_UART_BAUD_RATE_DIVISOR(pConfig->BaudRate) & 0xFF00) >> 8;
    pUart->LCR->BIT.DLAB = 0; //disable baud rate setting

    //modem control register
    pUart->MCR->ALL = 0;

    //FIFO control register
    pUart->FCR->BIT.FIFOE = 1; //enable FIFO
    pUart->FCR->BIT.RCVR_FIFOR = 1; //reset receiver fifo
    pUart->FCR->BIT.XMIT_FIFOR = 1; //reset transmitter fifo
    pUart->FCR->BIT.DMA_MS = 1; //enable DMA
    pUart->FCR->BIT.RCVRT = pConfig->RxFifoLevel; //receiver FIFO trigger level

    //enable Rx and Line Status interrupts
    pUart->IER->BIT.ERBFI = 1;
    pUart->IER->BIT.ELSI = 1;

    return true;
}

 

2. UART_Read

 

This function reads bytes if available from the Rx FIFO buffer to a data buffer. If the mark parameter is not null, we search for the mark as the Start of Packet (SOP). If it is null, the mark search is skipped. Check the Interrupt Identification Register (IIR) to see what interrupt has triggered. When any error occurs, we need to discard the corrupted packet. In that case, read all the bytes from the FIFO one by one till the end of the packet. This solution works reliably in all error cases. Trying to flush the FIFO buffer does not work well.


/*********************************************************************************************
* Function: UART_Read
*
* Desc: This function reads data from the FPGA UART up to the number of bytes required or till
* the end of the bytes available.
*
* Params: FpgaUartCtrlStruct *pUart
* int *pBuffer - the location to store data
* int nBytes - number of byte to be read
* int mark - mark of a packet
*
* Returns: int - the number of bytes read from the port. -1 means error had ocurred
*
* Notes:
*
*********************************************************************************************/

int UART_Read(FpgaUartCtrlStruct *pUart, int *pBuffer, int nBytes, int mark)
{
    int temp;
    int nByteRead = 0;
    int *pDest;
    Uart16550LSRUnion lsr;
    bool markSearch; //flag to indicate the period of mark searching

    //get a complete packet when FIFO receives all bytes of triggered level
    if (pUart->IIR->BIT.IP == 0)
    {
        switch (pUart->IIR->BIT.IIB2)
        {
            case IIR_RX_LINE_ERR: //error occurred
                //flush the fifo if error occurred,
                //but don't use the flush command, instead, read out the bytes one by one until the end
                while (true)
                {
                    lsr.ALL = pUart->LSR->ALL; //reading the LSR resets the IIR
                    if(lsr.BIT.DR == 0)
                    {
                        break;
                    }
                    else
                    {
                        temp = *pUart->RBR;
                    }
                }
                nByteRead = -1; //discard any data
                break;

           case IIR_RX_DATA_READY: //data ready
               do
               {
                   //LSR updates with each word, it shall be read for each word
                   lsr.ALL = pUart->LSR->ALL;
                   if (lsr.BIT.DR == 0)
                   {
                       break; //exit if there is no more word in the FIFO
                   }
                   else
                   {
                       //get valid data
                       if (mark != 0)
                       {
                           //mark is valid, so we need to look for the mark
                           if (nByteRead == 0)
                           {
                               //look for the mark byte
                               temp = *pUart->RBR & 0xFF;
                               if (temp == mark)
                               {
                                   //we found the mark
                                   pDest = pBuffer;
                                   *pDest++ = temp;
                                   nByteRead++;
                                   markSearch = false;
                               }
                               else
                               {
                                   markSearch = true;
                               }
                           }
                           else
                           {
                               //mark has been found, so save the next word
                               *pDest++ = (*pUart->RBR & 0xFF);
                               nByteRead++;
                           }
                       }
                       else
                       {
                           //mark is null, so don't look for the mark
                           markSearch = false;
                           *pDest++ = (*pUart->RBR & 0xFF);
                           nByteRead++;
                       }
                   }
              }while (markSearch || (--nBytes > 0));
               //during the period of mark search, don't decrement the byte count, but use the markSearch flag

              break;

         default:
              break;
         }
    }

    return nByteRead;
}

 

3. UART_Write

 

This function writes data to Tx FIFO one by one. However, a little time delay is needed between writes for the FPGA UART module to work properly.


/*********************************************************************************************
* Function: UART_Write
*
* Desc: This function writes a series of bytes to UART
*
* Params: FpgaUartCtrlStruct *pUart
* int *pBuffer - the location of data source
* int nBytes - number of byte to be written
*
* Returns: int - the number of bytes written successfully
*
* Notes:
*
*********************************************************************************************/
int UART_Write(FpgaUartCtrlStruct *pUart, int *pBuffer, int nBytes)
{
    int nByteWritten = 0;
    int *pDest = pBuffer;
    int i;

    //wait for the currect transmission to complete
    while (pUart->LSR->BIT.THRE == 0);

    //send the packet one byte by one byte
    for(i = 0; i < nBytes; i++)
    {
        *pUart->THR = *pDest++;
        nByteWritten++;
        DSP_Delay(1); //there must be some time delay between writes for FPGA UART to work properly
    }

    return nByteWritten;
}

 

4. UART_Close

 

As a good practice, a driver shall provide a close function in corresponding with the open function.


/*********************************************************************************************
* Function: UART_Close
*
* Desc: This function disables the UART port
*
* Params: FpgaUartCtrlStruct *pUart - the port to be closed
*
* Returns: bool - true if it closes successfully
*
* Notes:
*
*********************************************************************************************/

bool FPGA_UART_Close(FpgaUartCtrlStruct *pUart)
{
    //disable FIFO
    pUart->FCR->BIT.FIFOE = 0;

    //disable Rx and Line Status interrupts
    pUart->IER->BIT.ERBFI = 0;
    pUart->IER->BIT.ELSI = 0;

    return true;
}

5. Upper Level Driver

 

To use the lower layer driver, in the upper layer, i.e., protocol layer, you shall open the port first, then read the incoming packet, and send response.

 

#include "ES_UART.h"

/*********************************************************************************************
 static variables
*********************************************************************************************/
FpgaUartCtrlStruct rs485Uart; //UART used for communication between devices

RS485CommandUnion rs485Command; //Command packet received
RS485StatusRespUnion rs485StatusResp; //Response packet to send

 

//open the port

bool RS485_Init(void)
{
    bool bResult = true;

    UartConfigStruct rs485UartConfig;

    rs485UartConfig.BaudRate = RS485_BAUDRATE;
    rs485UartConfig.DataBits = WL_8;
    rs485UartConfig.Parity = NONE;
    rs485UartConfig.Sticky = true;
    rs485UartConfig.StopBits = SB_1;
    rs485UartConfig.RxFifoLevel = TRIGGER_LEVEL_14; //14 bytes trigger level

    FPGA_UART_Open(RS485_CHANNEL, &rs485Uart, &rs485UartConfig);

    return bResult;
}

 

//read incoming packet
bool RS485_Command(void)
{
    static INT16 RxIndex = 0; //receiver byte index

    bool bResult;
    int nByteRead;

    //get command
    if (RxIndex == 0)
    {
        //at the beginning of a packet, we need to check the mark byte
        nByteRead = FPGA_UART_Read(&rs485Uart, rs485Command.Packet, RS485_COMMAND_LENGTH, RS485_COMMAND_MARK);
    }
    else
    {
        //during the rest of a packet, mark shall not be checked, so pass in null
        nByteRead = FPGA_UART_Read(&rs485Uart, (rs485Command.Packet + RxIndex), (RS485_COMMAND_LENGTH - RxIndex), 0);
    }

    if (nByteRead == -1)
    {
        //reset the index if we had encountered a read error
        RxIndex = 0;
    }
    else
    {
        //update the index for successful read
        RxIndex += nByteRead;
    }

    //process the command if we have received a complete packet
    if(RxIndex >= RS485_COMMAND_LENGTH)
    {
        //verify mark byte
        if ((rs485Command.Command.Mark & 0xff) != RS485_COMMAND_MARK)
        {
            //it Mark is wrong, we must have had disconnection or received corrupted packet, we ignore the packet
            bResult = false;
        }
        //verify checksum
        else if (RS485_Checksum(rs485Command.Packet, RS485_COMMAND_LENGTH - 1) != (rs485Command.Command.Checksum & 0xff))
        {
            //if the checksum does not match, we also ignore the packet
            bResult = false;
        }
        else
        {
            //everything is ok we'll process the command
            bResult = true;
        }

        //reset Rx buffer index for the next packet
        RxIndex = 0;
    }
    else
    {
        //we have not received a complete packet yet
        bResult = false;
    }

    return bResult; 
}

 

//response function

bool RS485_StatusResponse(void)
{

    //compose your response packet according to your application protocol
}


The above functions are called in the application code. Upon receiving the correct packet, the application shall send a response and take actions according to the application protocol.

 

6. Source Code

 

Source code is provided for download.

 

ES_UART.c

ES_UART.h