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.
*/
#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
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: