/* Copyright (c) 2005,2006 Joerg Wunsch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
/* $Id*/
/** \defgroup stdiodemo Using the standard IO facilities
\ingroup demos
This project illustrates how to use the standard IO facilities (stdio)
provided by this library. It assumes a basic knowledge of how the
stdio subsystem is used in standard C applications, and concentrates
on the differences in this library's implementation that mainly result
from the differences of the microcontroller environment, compared to a
hosted environment of a standard computer.
This demo is meant to supplement the \ref avr_stdio "documentation",
not to replace it.
\section stdiodemo_hw Hardware setup
The demo is set up in a way so it can be run on the ATmega16 that
ships with the STK500 development kit. The UART port needs to be
connected to the RS-232 "spare" port by a jumper cable
that connects PD0 to RxD and PD1 to TxD. The RS-232 channel is set up
as standard input (\c stdin) and standard output (\c stdout),
respectively.
In order to have a different device available for a standard error
channel (\c stderr), an industry-standard LCD display with an
HD44780-compatible LCD controller has been chosen. This display needs
to be connected to port A of the STK500 in the following way:
Port | Header | Function |
A0 | 1 | LCD D4 |
A1 | 2 | LCD D5 |
A2 | 3 | LCD D6 |
A3 | 4 | LCD D7 |
A4 | 5 | LCD R/~W |
A5 | 6 | LCD E |
A6 | 7 | LCD RS |
A7 | 8 | unused |
GND | 9 | GND |
VCC | 10 | Vcc |
\image html stdiodemo-setup.jpg "Wiring of the STK500"
\image latex stdiodemo-setup.eps "Wiring of the STK500" width=12cm
The LCD controller is used in 4-bit mode, including polling the
"busy" flag so the R/~W line from the LCD controller needs
to be connected. Note that the LCD controller has yet another supply
pin that is used to adjust the LCD's contrast (V5). Typically, that
pin connects to a potentiometer between Vcc and GND. Often, it might
work to just connect that pin to GND, while leaving it unconnected
usually yields an unreadable display.
Port A has been chosen as 7 pins on a single port are needed to
connect the LCD, yet all other ports are already partially in use:
port B has the pins for in-system programming (ISP), port C has the
ports for JTAG (can be used for debugging), and port D is used for the
UART connection.
\section stdiodemo_overview Functional overview
The project consists of the following files:
- \c stdiodemo.c This is the main example file.
- \c defines.h Contains some global defines, like the LCD wiring
- \c hd44780.c Implementation of an HD44780 LCD display driver
- \c hd44780.h Interface declarations for the HD44780 driver
- \c lcd.c Implementation of LCD character IO on top of the HD44780 driver
- \c lcd.h Interface declarations for the LCD driver
- \c uart.c Implementation of a character IO driver for the internal UART
- \c uart.h Interface declarations for the UART driver
\section stdiodemo_code A code walkthrough
\subsection stdiodemo_main stdiodemo.c
As usual, include files go first. While conventionally, system header
files (those in angular brackets \c < ... \c >) go before
application-specific header files (in double quotes), \c defines.h
comes as the first header file here. The main reason is that this
file defines the value of \c F_CPU which needs to be known before
including <utils/delay.h>.
The function \c ioinit() summarizes all hardware initialization tasks.
As this function is declared to be module-internal only (\c static),
the compiler will notice its simplicity, and with a reasonable
optimization level in effect, it will inline that function. That
needs to be kept in mind when debugging, because the inlining might
cause the debugger to "jump around wildly" at a first
glance when single-stepping.
The definitions of \c uart_str and \c lcd_str set up two stdio
streams. The initialization is done using the \c FDEV_SETUP_STREAM()
initializer template macro, so a static object can be constructed that
can be used for IO purposes. This initializer macro takes three
arguments, two function macros to connect the corresponding output and
input functions, respectively, the third one describes the intent of
the stream (read, write, or both). Those functions that are not
required by the specified intent (like the input function for
\c lcd_str which is specified to only perform output operations) can
be given as \c NULL.
The stream \c uart_str corresponds to input and output operations
performed over the RS-232 connection to a terminal (e.g. from/to a PC
running a terminal program), while the \c lcd_str stream provides a
method to display character data on the LCD text display.
The function \c delay_1s() suspends program execution for
approximately one second. This is done using the \c _delay_ms()
function from <util/delay.h> which in turn needs the \c
F_CPU macro in order to adjust the cycle counts. As the \c _delay_ms()
function has a limited range of allowable argument values (depending on
\c F_CPU), a value of 10 ms has been chosen as the base delay which
would be safe for CPU frequencies of up to about 26 MHz. This function
is then called 100 times to accomodate for the actual one-second delay.
In a practical application, long delays like this one were better be
handled by a hardware timer, so the main CPU would be free for other
tasks while waiting, or could be put on sleep.
At the beginning of \c main(), after initializing the peripheral devices,
the default stdio streams \c stdin, \c stdout, and \c stderr are set
up by using the existing static \c FILE stream objects. While this is
not mandatory, the availability of \c stdin and \c stdout allows to
use the shorthand functions (e.g. \c printf() instead of \c fprintf()),
and \c stderr can mnemonically be referred to when sending out
diagnostic messages.
Just for demonstration purposes, \c stdin and \c stdout are connected
to a stream that will perform UART IO, while \c stderr is arranged to
output its data to the LCD text display.
Finally, a main loop follows that accepts simple
"commands" entered via the RS-232 connection, and performs
a few simple actions based on the commands.
First, a prompt is sent out using \c printf_P() (which takes a
\ref avr_pgmspace "program space string"). The string is read into
an internal buffer as one line of input, using \c fgets(). While it
would be also possible to use \c gets() (which implicitly reads from
\c stdin), \c gets() has no control that the user's input does not
overflow the input buffer provided so it should never be used at all.
If \c fgets() fails to read anything, the main loop is left. Of
course, normally the main loop of a microcontroller application is
supposed to never finish, but again, for demonstrational purposes,
this explains the error handling of stdio. \c fgets() will return
NULL in case of an input error or end-of-file condition on input.
Both these conditions are in the domain of the function that is used
to establish the stream, \c uart_putchar() in this case. In short,
this function returns EOF in case of a serial line "break"
condition (extended start condition) has been recognized on the serial
line. Common PC terminal programs allow to assert this condition as
some kind of out-of-band signalling on an RS-232 connection.
When leaving the main loop, a goodbye message is sent to standard
error output (i.e. to the LCD), followed by three dots in one-second
spacing, followed by a sequence that will clear the LCD. Finally,
\c main() will be terminated, and the library will add an infinite
loop, so only a CPU reset will be able to restart the application.
There are three "commands" recognized, each determined
by the first letter of the line entered (converted to lower case):
- The 'q' (quit) command has the same effect of
leaving the main loop.
- The 'l' (LCD) command takes its second argument,
and sends it to the LCD.
- The 'u' (UART) command takes its second argument,
and sends it back to the UART connection.
Command recognition is done using \c sscanf() where the first format
in the format string just skips over the command itself (as the
assignment suppression modifier \c * is given).
\subsection stdiodemo_defines defines.h
This file just contains a few peripheral definitions.
The \c F_CPU macro defines the CPU clock frequency, to be used in
delay loops, as well as in the UART baud rate calculation.
The macro \c UART_BAUD defines the RS-232 baud rate. Depending on the
actual CPU frequency, only a limited range of baud rates can be
supported.
The remaining macros customize the IO port and pins used for the
HD44780 LCD driver.
\subsection stdiodemo_hd44780_h hd44780.h
This file describes the public interface of the low-level LCD driver
that interfaces to the HD44780 LCD controller. Public functions are
available to initialize the controller into 4-bit mode, to wait for
the controller's busy bit to be clear, and to read or write one byte
from or to the controller.
As there are two different forms of controller IO, one to send a
command or receive the controller status (RS signal clear), and one to
send or receive data to/from the controller's SRAM (RS asserted),
macros are provided that build on the mentioned function primitives.
Finally, macros are provided for all the controller commands to allow
them to be used symbolically. The HD44780 datasheet explains these
basic functions of the controller in more detail.
\subsection stdiodemo_hd44780_c hd44780.c
This is the implementation of the low-level HD44780 LCD controller
driver.
On top, a few preprocessor glueing tricks are used to establish
symbolic access to the hardware port pins the LCD controller is
attached to, based on the application's definitions made in
\ref stdiodemo_defines.
The \c hd44780_pulse_e() function asserts a short pulse to the
controller's E (enable) pin.
Since reading back the data asserted by the LCD controller needs
to be performed while E is active, this function reads and returns
the input data if the parameter \c readback is true.
When called with a compile-time constant parameter that is false,
the compiler will completely eliminate the unused readback operation,
as well as the return value as part of its optimizations.
As the controller is used in 4-bit interface mode, all byte IO to/from
the controller needs to be handled as two nibble IOs. The functions
\c hd44780_outnibble() and \c hd44780_innibble() implement this. They
do not belong to the public interface, so they are declared static.
Building upon these, the public functions \c hd44780_outbyte() and
\c hd44780_inbyte() transfer one byte to/from the controller.
The function \c hd44780_wait_ready() waits for the controller to
become ready, by continuously polling the controller's status (which
is read by performing a byte read with the RS signal cleard), and
examining the BUSY flag within the status byte. This function needs
to be called before performing any controller IO.
Finally, \c hd44780_init() initializes the LCD controller into 4-bit
mode, based on the initialization sequence mandated by the datasheet.
As the BUSY flag cannot be examined yet at this point, this is the
only part of this code where timed delays are used.
While the controller can perform a power-on reset when certain
constraints on the power supply rise time are met, always calling the
software initialization routine at startup ensures the controller will
be in a known state. This function also puts the interface into 4-bit
mode (which would not be done automatically after a power-on reset).
\subsection stdiodemo_lcd_h lcd.h
This function declares the public interface of the higher-level
(character IO) LCD driver.
\subsection stdiodemo_lcd_c lcd.c
The implementation of the higher-level LCD driver. This driver builds
on top of the HD44780 low-level LCD controller driver, and offers a
character IO interface suitable for direct use by the standard IO
facilities. Where the low-level HD44780 driver deals with setting up
controller SRAM addresses, writing data to the controller's SRAM, and
controlling display functions like clearing the display, or moving the
cursor, this high-level driver allows to just write a character to the
LCD, in the assumption this will somehow show up on the display.
Control characters can be handled at this level, and used to perform
specific actions on the LCD. Currently, there is only one control
character that is being dealt with: a newline character (\c \\n) is
taken as an indication to clear the display and set the cursor into
its initial position upon reception of the next character, so a
"new line" of text can be displayed. Therefore, a
received newline character is remembered until more characters have
been sent by the application, and will only then cause the display to
be cleared before continuing. This provides a convenient abstraction
where full lines of text can be sent to the driver, and will remain
visible at the LCD until the next line is to be displayed.
Further control characters could be implemented, e. g. using a set of
escape sequences. That way, it would be possible to implement
self-scrolling display lines etc.
The public function \c lcd_init() first calls the initialization entry
point of the lower-level HD44780 driver, and then sets up the LCD in a
way we'd like to (display cleared, non-blinking cursor enabled, SRAM
addresses are increasing so characters will be written left to right).
The public function \c lcd_putchar() takes arguments that make it
suitable for being passed as a \c put() function pointer to the stdio
stream initialization functions and macros (\c fdevopen(),
\c FDEV_SETUP_STREAM() etc.). Thus, it takes two arguments, the
character to display itself, and a reference to the underlying stream
object, and it is expected to return 0 upon success.
This function remembers the last unprocessed newline character seen in
the function-local static variable \c nl_seen. If a newline character
is encountered, it will simply set this variable to a true value, and
return to the caller. As soon as the first non-newline character is
to be displayed with \c nl_seen still true, the LCD controller is told
to clear the display, put the cursor home, and restart at SRAM address
0. All other characters are sent to the display.
The single static function-internal variable \c nl_seen works for this
purpose. If multiple LCDs should be controlled using the same set of
driver functions, that would not work anymore, as a way is needed to
distinguish between the various displays. This is where the second
parameter can be used, the reference to the stream itself: instead of
keeping the state inside a private variable of the function, it can be
kept inside a private object that is attached to the stream itself. A
reference to that private object can be attached to the stream
(e.g. inside the function \c lcd_init() that then also needs to be
passed a reference to the stream) using \c fdev_set_udata(), and can
be accessed inside \c lcd_putchar() using fdev_get_udata().
\subsection stdiodemo_uart_h uart.h
Public interface definition for the RS-232 UART driver, much like in
\ref stdiodemo_lcd_h "lcd.h" except there is now also a character
input function available.
As the RS-232 input is line-buffered in this example, the macro
\c RX_BUFSIZE determines the size of that buffer.
\subsection stdiodemo_uart_c uart.c
This implements an stdio-compatible RS-232 driver using an AVR's
standard UART (or USART in asynchronous operation mode). Both,
character output as well as character input operations are
implemented. Character output takes care of converting the internal
newline \c \\n into its external representation carriage return/line
feed (\\r\\n).
Character input is organized as a line-buffered operation that allows
to minimally edit the current line until it is "sent" to the
application when either a carriage return (\c \\r) or newline (\c \\n)
character is received from the terminal. The line editing functions
implemented are:
- \c \\b (back space) or \c \\177 (delete) deletes the previous
character
- ^u (control-U, ASCII NAK) deletes the entire input buffer
- ^w (control-W, ASCII ETB) deletes the previous input word,
delimited by white space
- ^r (control-R, ASCII DC2) sends a \c \\r, then reprints the
buffer (refresh)
- \c \\t (tabulator) will be replaced by a single space
The function \c uart_init() takes care of all hardware initialization
that is required to put the UART into a mode with 8 data bits, no
parity, one stop bit (commonly referred to as 8N1) at the baud rate
configured in \ref stdiodemo_defines "defines.h". At low CPU clock
frequencies, the \c U2X bit in the UART is set, reducing the
oversampling from 16x to 8x, which allows for a 9600 Bd rate to be
achieved with tolerable error using the default 1 MHz RC oscillator.
The public function \c uart_putchar() again has suitable arguments for
direct use by the stdio stream interface. It performs the \c \\n into
\c \\r\\n translation by recursively calling itself when it sees a \c \\n
character. Just for demonstration purposes, the \c \\a (audible bell,
ASCII BEL) character is implemented by sending a string to \c stderr,
so it will be displayed on the LCD.
The public function \c uart_getchar() implements the line editor. If
there are characters available in the line buffer (variable \c rxp is
not \c NULL), the next character will be returned from the buffer
without any UART interaction.
If there are no characters inside the line buffer, the input loop will
be entered. Characters will be read from the UART, and processed
accordingly. If the UART signalled a framing error (\c FE bit set),
typically caused by the terminal sending a line break
condition (start condition held much longer than one character period),
the function will return an end-of-file condition using \c _FDEV_EOF.
If there was a data overrun condition on input (\c DOR bit set),
an error condition will be returned as \c _FDEV_ERR.
Line editing characters are handled inside the loop, potentially
modifying the buffer status. If characters are attempted to be
entered beyond the size of the line buffer, their reception is
refused, and a \c \\a character is sent to the terminal. If a \c \\r
or \c \\n character is seen, the variable \c rxp (receive pointer) is
set to the beginning of the buffer, the loop is left, and the first
character of the buffer will be returned to the application. (If no
other characters have been entered, this will just be the newline
character, and the buffer is marked as being exhausted immediately
again.)
\section stdiodemo_src The source code
\htmlonly
- stdiodemo.c
- defines.h
- hd44780.h
- hd44780.c
- lcd.h
- lcd.c
- uart.h
- uart.c
\endhtmlonly
\latexonly
The source code is installed under
\texttt{\$prefix/share/doc/avr-libc/examples/stdiodemo/},
where \texttt{\$prefix} is a configuration option. For Unix
systems, it is usually set to either \texttt{/usr} or
\texttt{/usr/local}.
\endlatexonly
*/