Provided by: avr-libc_2.0.0+Atmel3.7.0-1_all bug

NAME

       stdiodemo - Using the standard IO facilities

SYNOPSIS

Detailed Description

       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

       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.

Version 2.0.0                               Fri Nov 24 2023 23:59:10                             stdiodemo(3avr)