element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Benefits of Membership
    • Achievement Levels
    • Members Area
    • Personal Blogs
    • Feedback and Support
    • What's New on element14
  • Learn
    Learn
    • Learning Center
    • eBooks
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • Experts & Guidance
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Dev Tools
    • Manufacturers
    • Raspberry Pi
    • RoadTests & Reviews
    • Avnet Boards Community
    • Product Groups
  • Store
    Store
    • Visit Your Store
    • 'Choose another store...'
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
Personal Blogs
  • Members
  • More
Personal Blogs
Legacy Personal Blogs Writing a buffered UART driver for the NXP LPC17xx.
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: tdrazich
  • Date Created: 29 Dec 2015 12:24 AM Date Created
  • Views 864 views
  • Likes 0 likes
  • Comments 2 comments
  • dzx
  • uart
  • buffer
  • lpc17xx
  • nxp
Related
Recommended

Writing a buffered UART driver for the NXP LPC17xx.

tdrazich
tdrazich
29 Dec 2015

Although UARTs have been around for a long time, there are still many uses for them on embedded devices due to their minimal hardware requirements. Whether the UART is used for inter-device communication or for a simple terminal interface, it can be a real challenge to create a driver that not only provides reliable high speed operation, but maintains a simple API. This post explains the steps and design considerations that were made while creating the UART driver for the DZX Embedded SDK on the NXP LPC17xx platforms.

 

Architecture

The driver was required to follow a common API to maintain support across multiple hardware platforms (different MCUs). Each hardware platform driver defines a UART structure that contains the variables necessary to implement the driver for each UART port. The UART data structure for the LPC17xx is shown below.

 

struct UART
{
    NODE            node;                           /**< A node to place the UART into a list of open UARTs */
    UINT32          number;                         /**< The port number of the UART */
    REG_UART*       REG;                            /**< A pointer to the peripheral registers for the UART */
    BUFFER          rxb;                            /**< A circular buffer for storing received data */
    BUFFER          txb;                            /**< A circular buffer for storing data to be transmitted */
    BYTE            rxmem[CFG_UARTRXBUFFERSIZE];    /**< Memory allocation for the receive buffer */
    BYTE            txmem[CFG_UARTTXBUFFERSIZE];    /**< Memory allocation for the transmit buffer */
};
          

 

Using a structure for the variables per port defers all memory allocation until the UART datatype is instantiated, which means that memory resources are only used when the port is used. The driver provides the following functions for opening and transferring data through a UART.

 

STATUS UART_Open(UART* uart, UINT32 number, UINT32 baud, UINT32 nbits, STOPBITS stopbits, PARITY parity, HANDSHAKE hs);
STATUS UART_Read(UART* uart, void* buf, UINT32 nbytes, UINT32* actual, UINT32 timeout);
STATUS UART_Write(UART* uart, const void* data, UINT32 nbytes, UINT32* actual, UINT32 timeout);
        

 

 

Buffering Data

With reliability being a major requirement, it was chosen to use a buffer for holding received data to ensure that no data is dropped, even if it happens to be received without the application making a call to UART_Read(). The driver would make use of a circular buffer. If you are unfamiliar with a circular buffer, you can read about it here: https://en.wikipedia.org/wiki/Circular_buffer.

 

It turns out that the real-time kernel provided within the DZX SDK includes a special implementation of a circular buffer that also supports blocking calling threads until a specified amount of data or free space is available. These circular buffers are exactly what is needed when creating a UART driver. You can view the circular buffer API provided by the kernel here: https://dzxdesigns.com/dev/guide/group__kernel-buffer.aspx .

 

When the UART is opened by the application, by calling UART_Open(), the driver initializes the underlying circular buffer and initializes the UART peripheral. After the peripheral has been initialized, the receive interrupt is enabled. When the receive interrupt occurs, the driver acquires the received data from the peripheral and passes it to the underlying receive buffer. The receive interrupt handler is shown below. Take note that the handler empties the peripheral FIFO in a tight loop and then passes all of that data to the circular buffer. This method was chosen because the buffer component within the kernel is more efficient with fewer writes of larger sets of data rather than a bunch of writes of a single data byte.

 

BYTE buf[UARTFIFOSIZE];                                            /* FIFO is 16-bytes on LPC17xx */
UINT32 num;

switch (uart->REG->IIR.BITS.IID) {                                 /* Determine the source of the interrupt */
    case 0x2:                                                      /* Receive Data Available Interrupt (RDA) */
    case 0x6:                                                      /* Character Time-out Indicator Interrupt (CTI) */

        num = 0;
        while (uart->REG->LSR.BITS.DR == 1) {                      /* Empty the receive FIFO */
            buf[num++] = uart->REG->RBR;
            if (num >= UARTFIFOSIZE) {
                break;
            }
        }

        BUFFER_Write(&uart->rxb, buf, num, NULL, 0);                /* Buffer the received data */
        break;
}
          

 

Now that all of the received data makes its way to the underlying buffer, the driver simply calls the kernel's BUFFER_Read() function when the application wants to read the UART. 

 

Transmitting Data

For the sake of efficiency, the driver uses another circular buffer for transmitting data. A transmit buffer allows the application to make transmit calls without having to block and wait for the transfer to complete. The UART_Write() function will only attempt to block the caller if the transmit buffer happens to be full, but the function allows for a timeout to be specified.

 

To avoid potential race conditions, the driver passes all data to be transmitted to the underlying transmit buffer by first calling BUFFER_Write(). The buffer read/write API is thread safe itself, so threads can simply compete to get their data into the buffer. After the data is buffered, the driver enters a critical section and if the transmit interrupt is disabled and there is data in the buffer, the driver removes some data from the buffer and primes the peripheral for the next transfer and enables the transmit complete interrupt. The transmit interrupt handler then queries whether there is data remaining in the transmit buffer; if so, it removes some data from the buffer and primes the peripheral for another transfer. If the buffer happened to be empty, the interrupt handler simply disables the interrupt to signal the next UART_Write() operation to prime the peripheral for the next transfer.

 

Using the driver

If an application thread wants to read and consume all of the received data from a UART, the code below could be used. The snippet below simply loops and continuously reads the UART. If the specified amount of data is received (sizeof(buf)), the call would return immediately, otherwise the call would take up to 10 kernel ticks to return. The data that is received is then sent back out the UART.

 

void APP_Thread(void* arg)
{
    static UART uart;
    STATUS status;
    BYTE buf[32];
    UINT32 num;


    status = UART_Open(&uart,                               /* Open the uart */
                       0,                                   /* Port number, UART 0 */
                       115200,                              /* Baud rate, in bps */
                       8,                                   /* Number of data bits */
                       STOPBITS_ONE,                        /* One stopbit */
                       PARITY_NONE,                         /* No parity */
                       HANDSHAKE_NONE);                     /* No hardware flow control */

    if (status == SUCCESS) {                                /* Port opened successfully? */

    }


    for (;;) {                                              /* Loop and receive forever */

          UART_Read(&uart,                                  /* A pointer to the uart to read from */
                    buf,                                    /* A pointer to a buffer to receive the data */
                    sizeof(buf),                            /* The maximum amount of data, in bytes, to be returned */
                    &num,                                   /* A pointer to a variable to retrieve the actual amount of data that is returned */
                    10);                                    /* Maximum amount of time, in kernel ticks, to block and wait */

          if (num > 0) {                                    /* Has any data been returned? */

              /* 'num' bytes have been received and are
                  located within the local variable 'buf' */

              UART_Write(&uart,                            /* Echo the received data */                                                 
                          buf,                             /* A pointer to the data to be transmitted */
                          num,                             /* The amount of data, in bytes, to be transmitted */
                          NULL,                            /* Pointer to variable to receive actual amount transferred (not used here) */
                          INFINITE);                       /* Wait indefinitely for space in the transmit buffer */
          }
    }
}
         

 

 

Conclusion

The completed UART driver provides a minimal API for any application to read and write data over a UART. This driver has been tested and shown to be reliable even with baud rates up to 1Mbit/s. The described driver is part of the entire MCU driver that is provided as part of the DZX SDK for each supported microcontroller platform. You can download and try the driver for yourself as part of the SDK evaluations provided by DZX Designs here: https://dzxdesigns.com/sdk/downloads.aspx.

 

 

  • Sign in to reply
  • Former Member
    Former Member over 7 years ago

    Great walk through Tyler.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB over 7 years ago

    Brings back memories of my first use of the Intel 8051 chip, which really simplified serial device use verses the old software driven drivers at the bit level.

     

    DAB

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2023 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube