AVR serial communication

In this article an at90s2313 is used to send the string "hello, world!" to a PC via serial cable, and echo strings that are sent to it. The first part basically repeats the process of the previous article, pic_serial_communication with a different MCU. The second part, tests the receive operation of the MCU, and is the first step to creating peripheral devices.

Author's note: Unfortunately, the AVR UART library (see html and html) did not seem to work for this chip. If you are using a different MCU, it's ALWAYS better to use the library.

Hardware

The hardware used was:

  • an at902313
  • an Olimex AVR-P20 dev board
  • a serial-to-usb cable
  • a power supply (see power_supply_breakout)

On this particular dev board, the RS232 port and MCU socket were not connected by default. Female header strip was soldered to the board and the RS232 port was wired to the MCU. Initially the board was wired such that RX was connected to PD0 and TX was connected to PD1.

Registers

To enable serial communication, the pins on the MCU must be set, as well as configuring the correct registers.

The UART module has a data register (UDR) a control register (UCR) and status register (USR). Both the UCR and USR registers need to be set for serial communication. The UCR is described below.

bit label description
7 RXCIE rx interrupt enable (see USR.RXC=1) (1 = on, 0 = off)
6 TXCIE tx interrupt enable (see USR.TXC=1) (1 = on, 0 = off)
5 UDRIE data reg interrupt enable (1 = on, 0 = off)
4 RXEN enables rx (1 = on, 0 = off)
3 TXEN enables tx (1 = on, 0 = off)
2 CHR9 chars are 9-bit plus start/stop bit (1 = on, 0 = off)
1 RXB8 9th rx data bit
0 TXB8 9th tx data bit

The USR is described below.

bit label description
7 RXC rx complete flag, calls interrupt (see UCR.RXCIE=1)
6 TXC tx complete flag, calls interrupt (see UCR.TXCIE=1)
5 UDRE UDR empty flag
4 FE framing error
3 OR overrun
2..0 - not implemented

Code

The datasheet for both the at90s2313 (see pdf) and AVR-P20 (see pdf) were referenced heavily during the writing of the UART library.

#define __AVR_AT90S2313__       1

#define F_CPU 10000000L
#define BAUD 9600

#define valbit(reg, bit) ((reg) &   (1 << (bit)))
#define setbit(reg, bit) ((reg) |=  (1 << (bit)))
#define clrbit(reg, bit) ((reg) &= ~(1 << (bit)))

#define STATUS_LED_ON  clrbit(PORTB, PB7)
#define STATUS_LED_OFF setbit(PORTB, PB7)

#define SUCCESS 0
#define FAILURE 1

#include "avr/io.h"
#include "util/delay.h"
#include "avr/interrupt.h"

#include "serial_communication.h"

ISR(UART_RX_vect)
{
    /*
     * UART Rx process
     * 1. char arrives in receive shift register
     * 2. char transferred to UDR (RXC is set)
     * 3. if RXCIE, interrupt called
     * 4. RXC cleared by reading UDR
     * 
     * Therefore, this interrupt is called every time a char arrives
     */

     if (valbit(USR,FE)) {} // check framing error before reading UDR
     char c;
     uart_getc(&c); // receive string
     uart_putc(c);  // echo string 
     if (valbit(USR,DOR)) {} // check overrun error after reading UDR
}

void
set_baudrate(void)
{
    // set baudrate (p.47)
    UBRR = F_CPU/(16L*BAUD) - 1;
}

void
uart_getc(char *c)
{
    while(!valbit(USR,RXC)); // wait until receive register is empty
    *c = UDR; // get character
}

void
uart_putc(const char c)
{
    // replace newline with carriage return
    if (c == '\n') {
        uart_putc('\r');
    }
    while (!valbit(USR,UDRE)); // wait until data register is empty
    UDR = c; // load data register
}

void
uart_puts(const char *s)
{
    while (*s) { // while there is data in the string
        uart_putc(*s++); // put char in UDR
    }
}

int
setup(void)
{
    DDRB  = 0b10000000; // set PORTB pin 7 as output
    STATUS_LED_OFF;

    // initialize UART
    set_baudrate();

    // status register
    SREG = 0b10000000; // enable global interrupts

    // control register
    UCR  = 0b10011000; // enable rx interrupt, tx, rx (p.46)

    // status register
    USR  = 0b01000000; // clear interrupt flags

    // data registers
    DDRD  = 0b00000001; // set Rx (PD0) as input, Tx (PD1) as output
    PORTD = 0b00000000; // clear values

    return SUCCESS;
}

int
main(void)
{
    setup();
    char tx_data[] = "hello, world!";

    while (1) {
        uart_puts(tx_data);
        _delay_ms(1000);
    }

    return SUCCESS;
}
Listing 1: UART code (hover to view)

Debugging

Initially, this code didn't work. To troubleshoot, PD0 and PD1 were set as GPIO pins (i.e. RXEN = 0, TXEN = 0) and switched on and off with a 1000ms delay. They were measured with a DMM to ensure they were working. A different power supply and new MCU were also tried to no avail.

After inspecting the ST232C manual (see pdf) it became clear that the problem was with the wiring. In hindsight it's obvious, but for the benefit of others, always connect the TX pin of one device to the RX pin of the other. The board was rewired such that RX was connected to PD1 and TX was connected to PD0.

Result

This code both transmits data to the PC, and echos data it receives from the PC using interrupts. The ouput is shown below:

\

Figure 2: "Hello, world!" by serial cable

Note the string "do you read me?" was entered in via the terminal and echoed by the MCU.

Conclusion

Serial data was transmitted to and from an at90s2313 dev board via a serial cable.