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:
* 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.
* Need to allow one program (the owner) to define certain variables; everyone
* else sees these variables as externed.
#define EXTERN extern
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
* 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
#endif // end __UART_H
However, the above routines do not provide the ability
to send or receive a character directly. For example, there is no
routine. For that, I use the newlib routines.
Part two, the syscalls
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: