Bare-metal mbed; UART and syscalls support
(Last modified 13 Jan 2012)


I spent some time bringing up the UARTs on the mbed.  Having four UARTs is a treat, since most MCUs I've used in the past have just one.  Of course, the mbed is somewhat crippled since UART0 is tied into the PC interconnect system and isn't a true UART, and UART1 has a bunch of modem-control I/O hooked to it that I still haven't figured out how to disconnect.  But I still have UART2 and UART3...

There are really two code modules here when talking about the mbed's UARTs.  First is the code needed to exchange chars with the mbed via RS-232 at the UART level.  This is typically done with custom routines named things like OutputCharUART0().  The second module is code that connects the low-level UART code into the higher-level, stream-based I/O available on the system.  Not all implementations need to bother with this second module, but it offers some powerful advantages when it comes to platform independence, so we might as well do it.

The mbed dev system uses newlib, which supports a small set of platform-dependent routines you must provide for hooking into the standard stream I/O.  By this, I mean that newlib provides the standard stream I/O, such as printf() and gets(), which want to talk to stdin, stdout, and stderr.  However, to make this connection actually work, to exchange chars over the serial port, you have to provide some custom software for a carefully defined set of routines.



Part one, the UART code

I created a module named uart.c, which supports all four UARTs.  Here is the full uart.h header file:

/*****************************************************************************
 *   uart.h:  Header file for NXP LPC17xx Family Microprocessors

 *

 *   Copyright(C) 2009, NXP Semiconductor

 *   All rights reserved.

 *

 *   History

 *   2009.05.27  ver 1.00    Prelimnary version, first Release

 *

******************************************************************************/


/*
 *  Original version heavily modified by Karl Lunt, 13 Jan 2012
 *
 *  Added function prototypes for UART support routines.  Refer to the
 *  individual function prototypes below for details.
 */


#ifndef __UART_H
#define __UART_H


/*
 *  Need to allow one program (the owner) to define certain variables; everyone
 *  else sees these variables as externed.
 */
#ifdef   OWNER
#define  EXTERN
#else
#define  EXTERN  extern
#endif


EXTERN  uint32_t                SystemCoreClock;        // provides core clock freq to all modules





#define IER_RBR         0x01
#define IER_THRE        0x02
#define IER_RLS         0x04

#define IIR_PEND        0x01
#define IIR_RLS         0x03
#define IIR_RDA         0x02
#define IIR_CTI         0x06
#define IIR_THRE        0x01

#define LSR_RDR         0x01
#define LSR_OE          0x02
#define LSR_PE          0x04
#define LSR_FE          0x08
#define LSR_BI          0x10
#define LSR_THRE        0x20
#define LSR_TEMT        0x40
#define LSR_RXFE        0x80

#define BUFSIZE         0x40


#define  MAX_LEN_CALLBACK_CHAR_LIST    10



/*
 *  Function declarations
 */

/*
 *  UARTInit      initialize connection to a UART
 *
 *  This routine allows the calling program to initialize a selected
 *  UART.  Upon entry, portNum holds the UART of interest (0-3) and
 *  Baudrate holds the baud rate of interest.
 *
 *  Note that Baudrate holds the actual baud rate, not an enum or
 *  identifier.  For example, to select a baud rate of 9.6 kbaud,
 *  use a value of 9600 for baudrate.
 *
 *  Upon exit, the selected UART will have been configured as:
 *
 *  Baud rate: as selected by argument baudrate
 *  Format: 8N1
 *  Receive: chars recieved via interrupt
 *  Transmit: chars sent via polling
 *
 *  Upon exit, UART interrupts will be enabled but global interrupts
 *  are not altered by this routine.  The calling program is responsible
 *  for enabling global interrupts.
 */
uint32_t         UARTInit(uint32_t  portNum, uint32_t Baudrate);


/*
 *  UARTRegisterCharCallback      register an alert callback function
 *
 *  This routine allows the calling program to set up a callback function
 *  that will be invoked if any of a select group of chars is input on the
 *  selected UART.
 *
 *  This permits a program, which does not have direct access to the received
 *  chars before they are entered into a queue, to be notified if a special
 *  char arrives.  For example, the program could request a callback if the
 *  UART ever sees a control-C.
 *
 *  Argument portNum is the UART number (0-3).
 *  Argument alert is a pointer to a void callback function that takes a single
 *  argument; this argument will be the char that caused the callback to occur.
 *  Argument chrs is a null-terminated list (up to MAX_LEN_CALLBACK_CHAR_LIST)
 *  of chars, any of which can trigger the callback.
 *
 *  This routine returns -1 if an error occurs; this can be an illegal UART
 *  number or a char list greater than MAX_LEN_CALLBACK_CHAR_LIST chars.  Upon
 *  success, this routine returns the number of chars in the list.
 */
int32_t  UARTRegisterCharCallback(uint8_t portNum, void(* alert)(char  flag), char  *chrs);


/*
 *  The following groups of function provide low-level stream I/O support for
 *  each of four UARTs.  These functions will be called by the low-level system
 *  routines, such as _write() and _read().  These routines should NOT be called
 *  directly by a user program!
 *
 *  UARTxOpen      sets up the UART for use as a stream I/O device.
 *  Currently, this function is stubbed out.  Any specific setup the
 *  calling program needs to do is done via UARTInit().
 *
 *  UARTxWrite      writes a set of characters to the UART
 *  This routine writes the chars in the array pointed to by ptr,
 *  assumed to hold the number of chars in argument len.  Argument
 *  fd holds a stream identifier provided by the system caller, but
 *  that identifier is currently ignored.
 *
 *  UARTxAvail      returns flag showing if a char is available in
 *  the UART's receive queue.  Argument fd holds a stream identifier
 *  provided by the system caller, but that identifier is currently ignored.
 *  (Note that UARTxAvail() is not normally calledby a system routine.  It
 *  is provided here for use by custom terminal I/O routines; see xavail()
 *  in term_io.c.)  This routine returns the number of chars available in
 *  the UART's recieve queue.
 *
 *  UARTxRead      returns the requested number of chars, blocks until complete
 *  This routine fills the buffer pointed to by argument ptr with chars from
 *  the UART's receive queue until the number of chars passed in argument len
 *  is reached.  This routine blocks until that event occurs.  If you need
 *  non-blocking char reception, use UARTxAvail to determine how many chars
 *  are in the queue before calling this routine.  Argument fd holds a stream
 *  identifier provided by the system caller, but that identifier is currently
 *  ignored.
 *
 *  UARTxClose      closes connection to a UART.
 *  This routine currently does nothing.  There is no way to disconnect
 *  a UART.  Instead, simply call UARTInit to activate another UART.
 *  Argument fd holds a stream identifier provided by the system caller,
 *  but that identifier is currently ignored.
 */
int              UART0Open(const char  *path, int  flags, int  mode);
long             UART0Write(int fd, const char *ptr, int len);
int              UART0Avail(int  fd);
long             UART0Read(int fd, char *ptr, int len);
int              UART0Close(int  fd);

int              UART1Open(const char  *path, int  flags, int  mode);
long             UART1Write(int fd, const char *ptr, int len);
int              UART1Avail(int  fd);
long             UART1Read(int fd, char *ptr, int len);
int              UART1Close(int  fd);

int              UART2Open(const char  *path, int  flags, int  mode);
long             UART2Write(int fd, const char *ptr, int len);
int              UART2Avail(int  fd);
long             UART2Read(int fd, char *ptr, int len);
int              UART2Close(int  fd);

int              UART3Open(const char  *path, int  flags, int  mode);
long             UART3Write(int fd, const char *ptr, int len);
int              UART3Avail(int  fd);
long             UART3Read(int fd, char *ptr, int len);
int              UART3Close(int  fd);

/*
 *  The following functions are the low-level interrupt service routines
 *  used to process chars received by the UARTs.  These functions are provided
 *  by the code in uart.c and should not be invoked or rewritten by any other
 *  modules.
 */
void             UART0_IRQHandler(void);
void             UART1_IRQHandler(void);
void             UART2_IRQHandler(void);
void             UART3_IRQHandler(void);



#endif                // end __UART_H


Your main program first invokes UARTInit() to configure a UART.  The configuration covers the baud rate and format.  It also hooks into the UART IRQ handlers, so you don't need to worry about that in your main code.  Note that if you don't like some of the default settings, such as parity, you can first initialize the UART, then directly modify the UART's control registers to get the configuration you want.

However, the above routines do not provide the ability to send or receive a character directly.  For example, there is no OutputCharUART0() routine.  For that, I use the newlib routines.


Part two, the syscalls code
newlib lets me hook my UART code into the stream I/O support through a set of predefined routines.  These routines are predefined in the sense that their API is defined for you so you can write suitable code.  You then compile and link your code, the linker connects your API implementation into the rest of the canned stream I/O code already in the newlib libraries, and now your UARTs support stdin, stdout, and stderr.

My code for making this connection is in the file syscalls.c; here is the associated syscalls.h header file:

/*
 *  syscalls.h
 *
 *  This header file provides prototypes for the newlib stub functions
 *  you will need to provide if you want to use the C functions standard
 *  on most "real" operating systems.
 *
 *  This is only needed if you are trying to write C programs that use
 *  system utilities (such as printf) on "bare-metal" systems.
 *
 *  For an excellent explanation on this concept, along with some working
 *  examples, refer to: http://eetimes.com/discussion/guest-editor/4023922/Embedding-GNU-Newlib-Part-2
 *
 */

#ifndef  SYSCALLS_H
#define  SYSCALLS_H


#include "LPC17xx.h"
#include <errno.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include <stdint.h>
#include "uart.h"



/*
 *  Define a structure for the device operations table, used to hold device
 *  descriptors for the different stream devices supported.
 */
typedef struct  devoptab_t
{
   const char                *name;            // string containing the name of the device ("stdin" or "COM1")
   int                       (*open)(const char *path, int flags, int mode);
   int                       (*close)(int fd);
   long                      (*write) (int fd, const char *ptr, int len);
   long                      (*read)(int fd, char *ptr, int len);
   int                       (*avail)(int fd);                // returns number of chars available in stream
}  DEVOPTAB;







int                _write(int fd, char *ptr, int len);        // used by printf and others to write chars to I/O
int                _open(const char  *file, int  flags, int  mode);
int                _close(int fd);
int                _execve(char *name, char **argv, char **env);
int                _fork();
int                _fstat(int fd, struct stat *st);
int                _getpid();
int                _isatty(int fd);
int                _kill(int pid, int sig);
int                _link(char *old, char *new);
int                _lseek(int fd, int ptr, int dir);
caddr_t            _sbrk(int incr);
int                _read(int fd, char *ptr, int len);
int                _stat(const char *filepath, struct stat *st);
clock_t            _times(struct tms *buf);
int                _unlink(char *name);
int                _wait(int *status);


/*
 *  Custom routines added by Karl Lunt.
 */
int                _avail(int  fd);
int                set_stdio(int  fd, int  uartn);



#endif


Most of the functions above (function names beginning with '_') are stubs; they either do nothing at all or return a benign result code.  However, most of the routines specific to stream I/O support (functions that include an 'int fd' argument) contain code that uses UARTs to provide the associated stream I/O capabilities.  For example, the _write() function writes a length-specified string of characters to the UART assigned to the stream identified by argument fd.

Using a UART as a stream device requires a small amount of bookkeeping.  The stream devices (stdin, stdout, and stderr) are identified by file descriptors STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO, respectively.  These are the file descriptors passed in when you call _write(), for instance.  I have added a new routine to this group, set_stdio(), that provides the connection between a stream device (by file descriptor) and a UART (by argument uartn).

The following code snippet, from my uart_test.c program, shows what is involved in connecting UART2 to the standard stream devices:

    UARTInit(2, 38400);                         // this configures the UART baudrate and format

    set_stdio(STDIN_FILENO, 2);                 // connect stdin to a UART
    set_stdio(STDOUT_FILENO, 2);                // connect stdout to a UART
    set_stdio(STDERR_FILENO, 2);                // connect stderr to a UART

    __enable_irq();


Once this code executes, any of the standard stream I/O routines should work as expected.  However, I have not been able to get iprintf() to work properly.  This is an integer-only version of printf(), so it doesn't take up nearly the flash space that the full printf() does.  Adding calls to iprintf() links properly, but executing the call locks up the mbed device.  I suspect it is an issue with malloc() but I haven't taken the time to track it down.

Instead, I followed the lead of Martin Thomas, who has done a lot of bare-metal work on all kinds of ARM devices.  Martin, starting from code developed by ChaN of FatFS fame, recoded several of the stream I/O routines to avoid using malloc(), then made his rewrite available on the web (do a search for Martin Thomas, he's got lots of good stuff out there!).  I consolidated his code and created my own file, called term_io.c.

For example, here is my code for Martin's xputc() function, which writes a character to stdout:

/*
 *  xputc      write a single char to stdout.
 */

void xputc (char c)
{
    if (c == '\n') _write(STDOUT_FILENO, "\r", 1);
    _write(STDOUT_FILENO, &c, 1);
}

You can see how combining this code with the above setup example results in a function that will write to whatever UART happens to be hooked to the stdout stream, as identified by the STDOUT_FILENO descriptor.  In fact, none of my programs should ever invoke _write() directly.  Instead, they should always invoke the corresponding equivalent system stream I/O function and let the code in syscalls.c deal with the UART-to-stream connection.


Part three, polling the receive queue
My code in uart.c uses receive queues to store incoming characters.  But there isn't normally any easy way to determine if a char is already available.  Functions such as xgetc() will lock until a char arrives, which in the embedded world may never happen.

To work around this locking issue, I added a set of routines to the device operations table in syscalls.c.  These routines, one per UART, are named UARTxAvail() and return immediately the number of chars in the receive queue.  I added a complementary function, named xavail(), to my term_io.c file that will return the number of chars available in the stdin receive queue.


Part four, character-specific callbacks
I'm almost done.  The remaining problem is a bit subtle and may come up only in rare instances.  The program that is using the terminal I/O routines no longer has access to the UART's receive queue and cannot tell asynchronously if a given char has arrived.  The only way the calling program can determine if, for example, that the user has entered a Cntrl-C or other break character, is to keep pulling chars from the receive queue until the break char is read.  But if the calling program is stuck in a loop or has no need to check for characters, any break char entered by the user will never be seen.

To get around this problem, I've added a callback registration feature.  The calling program can invoke the function named UARTRegisterCharCallback() to provide a pointer to a callback function and a list of characters of importance.  If the UART's receive interrupt handler sees any character in this list, the handler will immediately invoke the callback function, which in turn will execute code provided in the calling program.  Here, the calling program can set flags or otherwise use the knowledge that an important char was received, even before that char would be pulled from the queue.


Part five, the files
I've bundled up the files for my UART, terminal I/O, and syscalls support into this zip file.  Note that you will not be able to compile these files into a working module; several vital subordinate files are missing.  However, I did include the binary of a simple UART test program that you can copy to your mbed and play with.  The test program requires a connection to UART2 (RXD2 on pin 27, TXD2 on pin 28); the connection is 38.4 kbaud, 8N1.




Home