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 |