Skip to content

ATmega328 UART Control Notes

  • by

Notes on UART with the ATMega 48/88/168/328 AVR 8-Bit MCUs. I don’t know how much of this applies to other AVR MCUs.

Quick Notes

  • Send/receive data one byte at a time by reading/writing to the UDRn register (where n is the UART number, in my case, UDR0).
  • When sending/receiving data you need to either poll the ready flags (easier) or use interrupts (more efficient).
  • When you #define BAUD as the desired baud rate, the setbaud.h header file will provide UBRRH_VALUE & UBRRL_VALUE to use as well as define USE_2X if you need to use double speed.
  • There is some receive buffering (2 bytes + the shift register)

Register Reference

Where “n” is the UART number.

  • UCSRnA: Control & Status Reg A
    • RXCn bit: Receive complete flag used for interrupt or polling
    • TXCn bit: Transmit complete flag used for interrupt or polling
    • UDREn bit: Data register is empty (and ready to receive another transmit byte if there is one)
    • U2Xn: Use double speed (Set if USE_2X is defined by setbaud.h)
  • USCRnB: Control & Status Reg B
    • RXCIEn: Receive compete interrupt enable
    • UDRIEn: Data register empty interrupt enable (Condition based interrupt -> Load UDRn register with the next byte to send, if any.)
    • TXCIEn: Transmit complete interrupt enable (Event based interrupt -> Data was just transmitted and there is no more in UDRn.)
    • RXENn: Enable UART receive
    • TXENn: Enable UART send
  • UDRn: Send data by writing to this register and receive by reading from this register.
  • UBRRnH: Baud rate high bit (set to UBRRH_VALUE defined in setbaud.h)
  • UBRRnL: Baud rate low bit (set to UBRRL_VALUE defined in setbaud.h)

Troubleshooting List

If things are not working right then here are some questions to ask yourself.

  • Are transmit and/or receive bits set?
  • Are UBRRnH/L registers both set?
  • Is BAUD defined before setbaud.h is included?
  • Is the U2Xn bit set in UCSRnA if necessary?
  • Are applicable interrupts enabled?
  • Is global interrupt flag enabled?
  • Are the ISR routines setup correctly?
  • Are any variables that can be modified by an ISR marked as volatile?

Note About UDREn and Condition-Based Interrupts

Be sure to disable the UDREn interrupt when there is no data to send. This interrupt is condition based, not event based. So if this interrupt is enabled it will continually trigger the ISR while UDRn transmit register is empty. This can really slow things down since the MCU will only execute one instruction in between jumps to the ISR while the interrupt condition is still met.

Simple Transmit examples

Capturing the message “abc” sent over UART from the ATmega328P

Each of the examples below send the ASCII encoded message “abc” every 10ms using different strategies.

Polling Example

// If not BAUD is not already defined somewhere.
// 9600, 14.4k, 57.6k, 115.2k
#define BAUD 9600

#include <avr/io.h>
#include <util/delay.h>
#include <util/setbaud.h>

char message[] = "abc";

int main() {

    //Set Baud using UBRR0H_VALUE & UBRR0L_VALUE macros
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;

    // setbaud.h will define USE_2X if it is needed to get timing tolerance
    #if USE_2X
    UCSR0A |= (1 << U2X0);
    #else
    UCSR0A &= ~(1 << U2X0);
    #endif

    UCSR0B |= (1 << RXEN0) | (1 << TXEN0);

    while (1) {
        uint8_t i = 0;
        char c = message[i];

        while (c != 0) {
            if (UCSR0A & (1 << UDRE0)) {
                UDR0 = c;
                c = message[i++];
            }
        }

        _delay_ms(10);
    }
}

Interrupt Example

// If not BAUD is not already defined somewhere.
// 9600, 14.4k, 57.6k, 115.2k
#define BAUD 9600

#include <avr/io.h>
#include <util/delay.h>
#include <util/setbaud.h>
#include <avr/interrupt.h>

char message[] = "abc";
const uint8_t messageLength = 3;
volatile uint8_t messageIndex = 0;

int main() {

    //Set Baud using UBRR0H_VALUE & UBRR0L_VALUE macros
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;

    // setbaud.h will define USE_2X if it is needed to get timing tolerance
    #if USE_2X
    UCSR0A |= (1 << U2X0);
    #else
    UCSR0A &= ~(1 << U2X0);
    #endif

    UCSR0B |= (1 << RXEN0) | (1 << TXEN0); // Enable transmit & receive
    UCSR0B |= (1 << UDRIE0); // Enable transmit data register empty interrupt

    sei();

    while (1) {
        if (!(UCSR0B & (1 << UDRIE0))) {
            _delay_ms(10);
            UCSR0B |= (1 << UDRIE0); // Re-enable interrupt after delay
        }
    }
}

ISR(USART_UDRE_vect) {
    // Send next character
    UDR0 = message[messageIndex++];

    // Disable data reg empty interrupt when complete message is sent
    if (messageIndex >= messageLength) {
        messageIndex = 0;
        UCSR0B &= ~(1 << UDRIE0);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *