Developing in C for the ATmega328P: Functions - Serial input/output

6 minute read

Where I describe the functions for serial input/out - puts(), getchar() and printf().


The serial input/output capabilities of C are vastly superior to those in the Arduino framework. In order to communicate via the USB serial cable, you must run a serial monitor program such as the Arduino Serial Monitor, CoolTerm, or moserial. See this page for more details.

Currently available are:

  • getchar(): reads the next character from the standard input stream and returns it as type int
  • putchar(char): writes a character to the standard output stream
  • puts(string): print a null-terminated string to the standard output stream, ending with a newline character
  • printf(format, variable list): writes to the standard output stream, the list of variables using the formatting string specified. There is considerable detail as to how to use printf below.

Baud Setting

While the serial I/O capabilities are polling and not interrupt-based, I’ve found the baud rate can still be set quite high. This is of value when you are attempting to transmit a signficant amount of information such as in examples/pointers. I have successfully set the baud rate to the following speeds in env.make:

  • 9600UL
  • 76800UL
  • 250000UL
  • 1000000UL

These values were selected after viewing the error values on page 199 of the ATmega328P datasheet. After changing the baud rate, its best to do a make LIB_clean && make all_clean && make flash to ensure everything has been updated. And make sure you have changed the baud rate of your serial program as well.

How to Use Serial Input/Output

The concept of serial input/output (I/O) is to provide the capability to communicate to (input) the Uno and have the Uno communicate back (output) to the PC. For our purposes, the standard input and output stream will be the USB communications between the Uno and our computer.

To setup the Uno properly for serial communications, the following elements have to part of your program (see highlighted lines below):

#include <stdio.h>
#include "uart.h"

int main() {
// initialize code goes here 

printing code goes here...

The highlighted lines won’t print anything, however, what they will do is setup the Uno to be able to print. Following these three lines, you may use any one of the functions described in the Introduction to communicate with the Uno.


// Serial I/O test
// Requires serial monitor set to 9600, 8, 1, None with no line ending
// Use Arduino Serial Monitor, CoolTerm, or your favorite serial monitor
#include <stdio.h>
#include "uart.h"

int main(void) {    
    int input;

    puts("Serial I/O Test: Enter a character");
    while(1) {
        input = getchar();
        printf(" You entered the character %c, ", input);
        printf("%i in ASCII decimal, %x in ASCII hex\n", input, input);        
    return 0;
Line(s) Description
4-5, 8 Lines required to setup serial I/O between Uno and PC
11 will output a string of text followed by a newline
13 will get one char at a time from the serial connection
14 will output one char
15 prints the char as a character format
16 prints the char as a decimal integer format and hexadecimal integer format

Sample Output

Serial I/O Test: Enter a character
a You entered the character a, 97 in ASCII decimal, 61 in ASCII hex
b You entered the character b, 98 in ASCII decimal, 62 in ASCII hex
1 You entered the character 1, 49 in ASCII decimal, 31 in ASCII hex

Format specifier (see avr-libc online manual)


Zero or more of the following flags:

  • # The value should be converted to an “alternate form”. For c, d, i, s, and u conversions, this option has no effect. For o conversions, the precision of the number is increased to force the first character of the output string to a zero (except if a zero value is printed with an explicit precision of zero). For x and X conversions, a non-zero result has the string 0x' (or 0X’ for X conversions) prepended to it.
  • 0 (zero) Zero padding. For all conversions, the converted value is padded on the left with zeros rather than blanks. If a precision is given with a numeric conversion (d, i, o, u, i, x, and X), the 0 flag is ignored.
    • A negative field width flag; the converted value is to be left adjusted on the field boundary. The converted value is padded on the right with blanks, rather than on the left with blanks or zeros. A - overrides a 0 if both are given.
  • ’ ’ (space) A blank should be left before a positive number produced by a signed conversion (d, or i).
    • A sign must always be placed before a number produced by a signed conversion. A + overrides a space if both are used.

An optional decimal digit string specifying a minimum field width. If the converted value has fewer characters than the field width, it will be padded with spaces on the left (or right, if the left-adjustment flag has been given) to fill out the field width.

An optional precision, in the form of a period . followed by an optional digit string. If the digit string is omitted, the precision is taken as zero. This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions, or the maximum number of characters to be printed from a string for s conversions.

An optional l or h length modifier, that specifies that the argument for the d, i, o, u, x, or X conversion is a “long int” rather than int. The h is ignored, as “short int” is equivalent to int.

A character that specifies the type of conversion to be applied.

printf(format, variable list)

Some of the details regarding format:

  • diouxX The int (or appropriate variant) argument is converted to signed decimal (d and i), unsigned octal (o), unsigned decimal (u), or unsigned hexadecimal (x and X) notation. The letters “abcdef” are used for x conversions; the letters “ABCDEF” are used for X conversions. The precision, if any, gives the minimum number of digits that must appear; if the converted value requires fewer digits, it is padded on the left with zeros.
  • p The void * argument is taken as an unsigned integer, and converted similarly as a %#x command would do.
  • c The int argument is converted to an “unsigned char”, and the resulting character is written.
  • s The “char *” argument is expected to be a pointer to an array of character type (pointer to a string). Characters from the array are written up to (but not including) a terminating NUL character; if a precision is specified, no more than the number specified are written. If a precision is given, no null character need be present; if the precision is not specified, or is greater than the size of the array, the array must contain a terminating NUL character.
  • eE The double argument is rounded and converted in the format “[-]d.ddde┬▒dd” where there is one digit before the decimal-point character and the number of digits after it is equal to the precision; if the precision is missing, it is taken as 6; if the precision is zero, no decimal-point character appears. An E conversion uses the letter ‘E’ (rather than ’e’) to introduce the exponent. The exponent always contains two digits; if the value is zero, the exponent is 00.
  • fF The double argument is rounded and converted to decimal notation in the format “[-]ddd.ddd”, where the number of digits after the decimal-point character is equal to the precision specification. If the precision is missing, it is taken as 6; if the precision is explicitly zero, no decimal-point character appears. If a decimal point appears, at least one digit appears before it.

For all of the details as to format, avr-libc manual: vprintf, printf uses vprintf to print.

Additional explanation as to formatting can be found here.

Comments powered by Talkyard.