Serial Communication

Economic communication between different devices is achieved by reducing the number of wires connecting the devices. A small number of wires means that the communication must be done serially (i.e., a bit at a time). A bit signals either a 1 or a 0.

Using a single wire how are bits transmitted between the devices that the wire connects.

Two wire Serial Communication

If two wire connect the devices, then one wire can be used to send the data value (0 or 1) and the other wire can be used to indicate when the data wire should be examined.

clock01010101
data00111100

The transition from 0 to 1 on the clock line indicates when the bit is transmitted. The clock signal controls the timing of the data communication. This type of communication is called synchronous.

One wire Serial Communication

The asynchronous technique uses one wire to send the timing and data values. Consider the following transmission of the letter 'R':

data10010010101

Asynchronous communication is limited by the accuracy of the sending and receiving clocks.

Asynchronous Communication

Many of the AVR devices provide a device that communicates using the asynchronous technique. The following properties must be defined when using asynchronous communication.

baud rate
The time period of the timer interval. A common period is 1/9600 seconds.
number of data bits
The number of data bits transmitted after the start bit.
number of stop bits
There is usually one or two periods used to transmit the stop bit.
parity
An extra bit containing the parity can be sent to ensure error free communication. The parity bit can be even or odd. The is not always tranmitted.

USART Device for AVR

"The Universal Synchronous and Asynchronous serial Receiver and Transmitter (USART) is a highly flexible serial communication device. The main features are:

* ATmega644/V manual

USART Block diagram

Control and status register A for USART0.

UCSR0A7

RXC0

r
6

TXC0

rw
5

UDRE0

r
4

FE0

r
3

DOR0

r
2

UPE0

r
1

U2X0

rw
0

MPCM0

rw
RXC0 (bit7) USART Receive Complete
RXC0 is set when data is available and the data register has not be read yet. When set the Receive Complete interrupt is generated.
TXC0 (bit6) USART Transmit Complete
TXC0 is set when the data has been shifted out. When set the Transmit Complete interrupt is generated.
UDRE0 (bit5) USART Data Register Empty
UDRE0 is set when the UDR0 register is empty (i.e., a new byte to transmit can be loaded).
FE0 (bit4) Frame Error
Set when the next byte in the UDR0 register has a framing error.
DOR0 (bit3) Data OverRun
DOR0 is set when the UDR0 was not read before the next frame arrived.
UPE0 (bit2) USART Parity Error
UPE0 is set when the next frame in the UDR0 has a parity error.
U2X0 (bit1) Double the USART Transmission Speed
Setting U2X0 to 1 decreases the bit time by half (double the speed).
MPCM0 (bit0) Multi-processor Communication Mode
The incomming frame is ignored if there no addressing information is provided when MPCM0 is set.

Control and status register B for USART0.

UCSR0B7

RXCIE0

rw
6

TXCIE0

rw
5

UDRIE0

rw
4

RXEN0

rw
3

TXEN0

rw
2

UCSZ02

rw
1

RXB80

r
0

TXB80

rw
RXCIE0 (bit7) RX Complete Interrupt Enable 0
Setting RXCIE0 allows receiver complete interrupts.
TXCIE0 (bit6) TX Complete Interrupt Enable 0
Setting TXCIE0 allows transmitter complete interrupts.
UDRIE0 (bit5) USART Data Register Empty Interrupt Enable 0
Setting UDRIE0 allows data empty interrupts.
RXEN0 (bit4) Receiver Enable 0
The USART receiver is enabled when RXEN0 is set.
TXEN0 (bit3) Transmitter Enable 0
The USART transmitter is enabled when RXEN0 is set.
UCSZ02 (bit2) Character Size 0
UCSZ02 is combined with UCSZ01 and UCSZ00 to control the number of data bits per frame.
RXB80 (bit1) Receive Data Bit 8 0
RXB80 is the 8th received bit.
TXB80 (bit0) Transmit Data Bit 8 0
TXB80 sets the value of the 8th data bit.

Control and status register C for USART0.

UCSR0C7

UMSEL01

rw
6

UMSEL00

rw
5

UPM01

rw
4

UPM00

rw
3

USBS0

rw
2

UCSZ01

rw
1

UCSZ00

rw
0

UCPOL0

rw
UMSEL01 (bit7) USART Mode Select 1
UMSEL01 and UMSEL00 select the operating mode. The modes are Asynchronous USART (00), Synchronous USART (01), and Master SPI(11).
UMSEL00 (bit6) USART Mode Select 0
See UMSEL01.
UPM01 (bit5) USART Parity Mode 1
UPM01 and UPM00 select the parity operating modes. The modes are: Disabled (00), Even Parity (10), and Odd Parity (11).
UPM00 (bit4) USART Parity Mode 0
See UPM01.
USBS0 (bit3) USART Stop Bit Select
USBS0 sets to 1 selects a 1-bit stop bit. A zero setting selects a 2-bit stop bit.
UCSZ01 (bit2) USART Character Size 1
The number of bits in a character frame are selected by UCSZ02 (from UCSR0B), UCSZ01, and UCSZ00. The sizes are: 5-bit (000), 6-bit (001), 7-bit (010), 8-bit (011), and 9-bit (111).
UCSZ00 (bit1) USART Character Size 0
See UCSZ01.
UCPOL0 (bit0) USART Clock Polarity
The clock polarity for synchronous mode is set with UCPOL0. A value 0 selects transmit on rising edge, and sample on following edge. A value of 1 sets the converse.

Initializing USART

#define HI(x) (x>>8)
#define LO(x) (x&0xff)

void
usart0_init( uint16_t baud )
{
    DDRD |= _BV(1) ; // enable tx output
    /* UBRR0H = HI(baud); UBRR0L = LO(baud) */
    UBRR0 = baud;
    /* Asynchronous mode, no parity, 1-stop bit, 8-bit data */
    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00 );

    /* no 2x mode, no multi-processor mode */
    UCSR0A = 0x00;

    /* interrupts disabled, rx and tx enabled, 8-bit data */
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);
}

USART I/O Data Register 0 is used to read received frames and to send new frames. A received frame is retrieved by reading UDR0. A frame is transmitted by writting to UDR0.

UDR07

rw
6

rw
5

rw
4

rw
3

rw
2

rw
1

rw
0

rw

A byte, b is transmitted with:

UDR0 = b; // write 

A byte is retrieved with:

b = UDR0; // read

Transmitting a byte

void
usart0_put( uint8_t b )
{
    // wait for data register to be ready
    while ( (UCSR0A & _BV(UDRE0)) == 0 )
        ;
    // load b for transmission
    UDR0 = b;
}

The byte can also be transmitted with:

UDR0 = b;
// wait for transmit to complete
while ( (UCSR0A & _BV(TXC0)) == 0 )
    ;

Which is better?

Receiving a byte

uint8_t
usart0_get( void )
{
    // poll for data available
    while ( (UCSR0A & _BV(RXC0)) == 0 )
        ;
    return UDR0;
}

Receiving a byte with a time out

int16_t
usart0_get_delay( uint16_t max_delay )
{
    // poll for data available, with timeout
    while ( (UCSR0A & _BV(RXC0)) == 0  && max_delay != 0) {
        max_delay--;
    }
    if ( (UCSR0A & _BV(RXC0)) == 0  ) {
        return -1;
    }
    return UDR0;
}

USART Module Interface

usart0.h
#ifndef USART0_H
#define USART0_H

#include <stdint.h>

void usart0_init( uint16_t baud );

void usart0_put( uint8_t b );

uint8_t usart0_get( void );

int16_t usart0_get_delay( uint16_t max_delay );

#define BAUD_RATE(clockFreq, baud) ((uint16_t)(clockFreq/(16L*(baud))-1))

#endif

USART Module Implementation

usart0.c
#include <avr/io.h>
#include "usart0.h"

#define HI(x) (x>>8)
#define LO(x) (x&0xff)

void
usart0_init( uint16_t baud )
{
    DDRD |= _BV(1) ; // enable tx output
    /* UBRR0H = HI(baud); UBRR0L = LO(baud) */
    UBRR0 = baud;
    /* Asynchronous mode, no parity, 1-stop bit, 8-bit data */
    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00 );

    /* no 2x mode, no multi-processor mode */
    UCSR0A = 0x00;

    /* interrupts disabled, rx and tx enabled, 8-bit data */
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);
}

void
usart0_put( uint8_t b )
{
    // wait for data register to be ready
    while ( (UCSR0A & _BV(UDRE0)) == 0 )
        ;
    // load b for transmission
    UDR0 = b;
}

uint8_t
usart0_get( void )
{
    // poll for data available
    while ( (UCSR0A & _BV(RXC0)) == 0 )
        ;
    return UDR0;
}

int16_t
usart0_get_delay( uint16_t max_delay )
{
    // poll for data available, with timeout
    while ( (UCSR0A & _BV(RXC0)) == 0  && max_delay != 0) {
        max_delay--;
    }
    if ( (UCSR0A & _BV(RXC0)) == 0  ) {
        return -1;
    }
    return UDR0;
}

USART Module Testing application

The USART module can be tested with a simple echo application that echos any received characters. Any upper case characters are converted to lower case.

echo.c
#include "avr/io.h"
#include "usart0.h"

int
main( void )
{
    CLKPR = _BV(CLKPCE); // enable clock scalar update
    CLKPR = 0x00; // set to 8Mhz
    PORTC = 0xff; // all off
    DDRC = 0xff; // show byte in leds
    usart0_init( 51 ); // 9600 baud at 8MHz

    while ( 1 ) {
        uint8_t b = usart0_get();
        if ( b >= 'A' && b <= 'Z' ) {
            b ^= 0x20; // convert to lower case
        }
        PORTC = ~b;
        usart0_put( b );
    }
    return 0;
}

USART Module Testing application makefile

Makefile
MCU= atmega644
BMCU = m644
CC= avr-gcc
LD= avr-gcc
CFLAGS= -Os
OBJCOPY = avr-objcopy
SIZE = avr-size
TTY = /dev/ttyS0

echo.srec: echo.elf
	$(OBJCOPY) -O srec echo.elf echo.srec

echo.elf: echo.o usart0.o
	$(LD) -mmcu=$(MCU) echo.o usart0.o -o echo.elf

burn: echo.srec
	avrdude -P $(TTY) -p $(BMCU) -c stk500v2 -U flash:w:echo.srec

usart0.o: usart0.c usart0.h
	$(CC) $(CFLAGS) -mmcu=$(MCU) -c usart0.c

echo.o: echo.c usart0.h
	$(CC) $(CFLAGS) -mmcu=$(MCU) -c echo.c

clean:
	rm -f echo.o usart0.o echo.elf echo.srec

PC-side testing with Python

Test output for the echo application is created with the following Python program:

echo_test.py
import serial
import sys

if len(sys.argv) != 4 :
    print 'usage: python echo_test.py count char1 char2'
    sys.exit(0)

count = int(sys.argv[1])
char1 = ord(sys.argv[2][0])
char2 = ord(sys.argv[3][0])
ser = serial.Serial('/dev/ttyS0', 9600)

while count > 0 :
    ch = char1
    while ch <= char2 and count > 0 :
        ser.write( chr( ch ) )
        print ser.read(1),
        ch += 1
        count -= 1
    print

ser.close()

Echo with receiver interrupts

echo_intr.c
#include "avr/io.h"
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <stdint.h>

ISR(USART0_RX_vect)
{
    uint8_t b = UDR0;
    if ( b >= 'A' && b <= 'Z' ) {
        b ^= 0x20; // convert to lower case
    }
    PORTC = ~b;
    // wait for data register to be ready
    while ( (UCSR0A & _BV(UDRE0)) == 0 )
        ;
    // load b for transmission
    UDR0 = b;
}

int
main( void )
{
    clock_prescale_set( clock_div_1 );
    PORTC = 0xff; // all off
    DDRC = 0xff; // show byte in leds

    DDRD |= _BV(1) ; // enable tx output
    /* UBRR0H = HI(baud); UBRR0L = LO(baud) */
    UBRR0 = 51; // /9600 at 8Mhz
    /* Asynchronous mode, no parity, 1-stop bit, 8-bit data */
    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00 );
    /* no 2x mode, no multi-processor mode */
    UCSR0A = 0x00;
    /* rx interrupts enabled, rx and tx enabled, 8-bit data */
    UCSR0B = _BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0);

    sei();
    while ( 1 ) {
        sleep_mode();
    }
    return 0;
}

Echo with receiver interrupts Makefile

Makefile.intr
MCU= atmega644
BMCU = m644
CC= avr-gcc
LD= avr-gcc
CFLAGS= -Os
OBJCOPY = avr-objcopy
SIZE = avr-size
TTY = /dev/ttyS0

echo_intr.srec: echo_intr.elf
	$(OBJCOPY) -O srec echo_intr.elf echo_intr.srec

echo_intr.elf: echo_intr.o
	$(LD) -mmcu=$(MCU) echo_intr.o -o $@

burn: echo_intr.srec
	avrdude -P $(TTY) -p $(BMCU) -c stk500v2 -U flash:w:echo_intr.srec

echo_intr.o: echo_intr.c
	$(CC) $(CFLAGS) -mmcu=$(MCU) -c echo_intr.c

clean:
	rm -f echo_intr.o echo_intr.elf echo_intr.srec