Man Linux: Main Page and Category List

NAME

       Using the standard IO facilities - 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 documentation, not to replace it.

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 (stdin) and
       standard output (stdout), respectively.

       In order to have a different device available for a standard error
       channel (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:

       PortHeaderFunction 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 GND9 GND VCC10Vcc

       Wiring of the STK500Wiring of the STK500

       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 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.

Functional overview

       The project consists of the following files:

       o stdiodemo.c This is the main example file.

       o defines.h Contains some global defines, like the LCD wiring

       o hd44780.c Implementation of an HD44780 LCD display driver

       o hd44780.h Interface declarations for the HD44780 driver

       o lcd.c Implementation of LCD character IO on top of the HD44780 driver

       o lcd.h Interface declarations for the LCD driver

       o uart.c Implementation of a character IO driver for the internal UART

       o uart.h Interface declarations for the UART driver

A code walkthrough

   stdiodemo.c
       As usual, include files go first. While conventionally, system header
       files (those in angular brackets < ... >) go before application-
       specific header files (in double quotes), defines.h comes as the first
       header file here. The main reason is that this file defines the value
       of F_CPU which needs to be known before including <utils/delay.h>.

       The function ioinit() summarizes all hardware initialization tasks. As
       this function is declared to be module-internal only (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 uart_str and lcd_str set up two stdio streams. The
       initialization is done using the 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 lcd_str which is
       specified to only perform output operations) can be given as NULL.

       The stream 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 lcd_str stream provides a method
       to display character data on the LCD text display.

       The function delay_1s() suspends program execution for approximately
       one second. This is done using the _delay_ms() function from
       <util/delay.h> which in turn needs the F_CPU macro in order to adjust
       the cycle counts. As the _delay_ms() function has a limited range of
       allowable argument values (depending on 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 main(), after initializing the peripheral devices,
       the default stdio streams stdin, stdout, and stderr are set up by using
       the existing static FILE stream objects. While this is not mandatory,
       the availability of stdin and stdout allows to use the shorthand
       functions (e.g. printf() instead of fprintf()), and stderr can
       mnemonically be referred to when sending out diagnostic messages.

       Just for demonstration purposes, stdin and stdout are connected to a
       stream that will perform UART IO, while 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 printf_P() (which takes a program
       space string). The string is read into an internal buffer as one line
       of input, using fgets(). While it would be also possible to use gets()
       (which implicitly reads from stdin), 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 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. 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,
       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, 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):

       o The 'q' (quit) command has the same effect of leaving the main loop.

       o The 'l' (LCD) command takes its second argument, and sends it to the
         LCD.

       o The 'u' (UART) command takes its second argument, and sends it back
         to the UART connection.

       Command recognition is done using sscanf() where the first format in
       the format string just skips over the command itself (as the assignment
       suppression modifier * is given).

   defines.h
       This file just contains a few peripheral definitions.

       The 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 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. Each definition consists of a letter naming the
       port this pin is attached to, and a respective bit number. For
       accessing the data lines, only the first data line gets its own macro
       (line D4 on the HD44780, lines D0 through D3 are not used in 4-bit
       mode), all other data lines are expected to be in ascending order next
       to D4.

   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.

   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 defines.h.

       The 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 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
       hd44780_outnibble() and hd44780_innibble() implement this. They do not
       belong to the public interface, so they are declared static.

       Building upon these, the public functions hd44780_outbyte() and
       hd44780_inbyte() transfer one byte to/from the controller.

       The function 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, 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).

   lcd.h
       This function declares the public interface of the higher-level
       (character IO) LCD driver.

   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 (\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 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 lcd_putchar() takes arguments that make it suitable
       for being passed as a put() function pointer to the stdio stream
       initialization functions and macros (fdevopen(), 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 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 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 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 lcd_init() that then also needs to be passed a
       reference to the stream) using fdev_set_udata(), and can be accessed
       inside lcd_putchar() using fdev_get_udata().

   uart.h
       Public interface definition for the RS-232 UART driver, much like in
       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
       RX_BUFSIZE determines the size of that buffer.

   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 \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 (\r) or newline (\n)
       character is received from the terminal. The line editing functions
       implemented are:

       o \b (back space) or \177 (delete) deletes the previous character

       o ^u (control-U, ASCII NAK) deletes the entire input buffer

       o ^w (control-W, ASCII ETB) deletes the previous input word, delimited
         by white space

       o ^r (control-R, ASCII DC2) sends a \r, then reprints the buffer
         (refresh)

       o \t (tabulator) will be replaced by a single space

       The function 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 defines.h. At low CPU clock frequencies, the 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 uart_putchar() again has suitable arguments for
       direct use by the stdio stream interface. It performs the \n into \r\n
       translation by recursively calling itself when it sees a \n character.
       Just for demonstration purposes, the \a (audible bell, ASCII BEL)
       character is implemented by sending a string to stderr, so it will be
       displayed on the LCD.

       The public function uart_getchar() implements the line editor. If there
       are characters available in the line buffer (variable rxp is not 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 (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 _FDEV_EOF. If there was a
       data overrun condition on input (DOR bit set), an error condition will
       be returned as _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
       \a character is sent to the terminal. If a \r or \n character is seen,
       the variable 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.)

The source code

Author

       Generated automatically by Doxygen for avr-libc from the source code.

Version 1.6.8                   Thu Aug 12 Using the standard IO facilities(3)