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:
  - •
 
  - stdiodemo.c This is the main example file.
 
  - •
 
  - defines.h Contains some global defines, like the LCD
    wiring
 
  - •
 
  - hd44780.c Implementation of an HD44780 LCD display driver
 
  - •
 
  - hd44780.h Interface declarations for the HD44780 driver
 
  - •
 
  - lcd.c Implementation of LCD character IO on top of the HD44780 driver
 
  - •
 
  - lcd.h Interface declarations for the LCD driver
 
  - •
 
  - uart.c Implementation of a character IO driver for the internal UART
 
  - •
 
  - 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):
  - •
 
  - 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 
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:
  - •
 
  - \b (back space) or \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 \r, then reprints the buffer
    (refresh)
 
  - •
 
  - \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.