Lecture 06 — ATmega32

Serial Communication Interfaces in AVR Microcontrollers

USART · SPI · I2C — New Mansoura University, Embedded Systems Course

3
Protocols covered
2
Wires for UART
115200
Max standard baud

What is a communication protocol?

A defined system that allows two or more entities to exchange data. Protocols specify rules for syntax, semantics, error handling, flow control, addressing, and routing.

Data format Address mapping Flow control Sequence control Error detection

The three AVR protocols

USART

2 data wires

Async or sync

PC ↔ MCU, GPS, Bluetooth modules

SPI

4 wires

Synchronous only

Displays, Flash memory, SD cards

I2C

2 wires

Multi-device bus

Sensors, EEPROM, RTC modules

Choosing the right protocol

FactorUSARTSPII2C
Wire count242
SpeedMediumHighLow–Medium
Devices on bus1 each1 per CS pinUp to 127
ClockNone (async)Shared (SCLK)Shared (SCL)
DuplexFullFullHalf

UART / USART

Definitions

UART (Universal Asynchronous Receiver/Transmitter) — a hardware circuit inside the MCU that converts parallel data (from CPU) into serial bits for transmission, and vice versa on reception.

USART extends UART by also supporting synchronous mode, where both devices share a clock signal (XCK pin), allowing higher reliability and speed over short distances.

How two devices connect

MCU (ATmega32)
Tx    Rx    GND
Tx ————→ Rx
Rx ←———— Tx
GND — GND
(wires cross!)
PC / Device
Rx    Tx    GND

Shift registers on both sides handle the serial ↔ parallel conversion. No external clock needed in async mode.

Duplex modes

Simplex

Data flows in one direction only. E.g. sensor → MCU only.

Half-duplex

Both directions, but not at the same time.

Full-duplex

Both directions simultaneously — standard UART mode.

Asynchronous

No shared clock. Both sides must agree on the same baud rate.

Baud rate

Speed is measured in bits per second (bps). Both devices must use the same baud rate or data will be garbled. Standard rates:

480096001920057600115200
UBRR = F_CPU / (16 × Baud) − 1
Example: 9600 baud at 16 MHz

UBRR = 16,000,000 / (16 × 9600) − 1 = 104.17 − 1 ≈ 103

UART frame structure

Every byte is wrapped in a frame. The receiver detects the start bit, samples each data bit at the correct time, checks parity, then waits for the stop bit.

Visual frame layout

idle
HIGH
start
0
data bits (5–9, usually 8) — LSB first
b0  b1  b2  b3  b4  b5  b6  b7
parity
optional
stop
1
idle
HIGH

Bit-by-bit reference

FieldValuePurpose
Start bitalways 0Signals frame start; transitions from idle HIGH to LOW, waking the receiver
Data bitsLSB firstPayload — usually 8 bits (1 byte). Can be 5, 6, 7, 8, or 9 bits
Parity bit0 or 1Optional error check: even parity, odd parity, or disabled
Stop bit(s)always 11 or 2 bits. Marks end of frame; returns line to idle HIGH

Parity modes

Even parity

Parity bit is set so total number of 1-bits (including parity) is even.

Odd parity

Parity bit is set so total number of 1-bits is odd.

No parity

Parity bit omitted. Most common in practice (shorter frames, faster).

Error detection limit

Parity only detects single-bit errors. For robust error handling use CRC.

Example: sending 'A' (0x41)

0x41 = 01000001 binary. Sent LSB first = 1, 0, 0, 0, 0, 0, 1, 0

Idle: ‾‾‾‾‾‾‾‾‾‾‾ Start: _ Data: 1 0 0 0 0 0 1 0 (LSB first) Stop: ‾‾

USART registers (ATmega32)

Five register groups control the entire USART peripheral. You configure them once in init, then use status flags during operation.

UBRR0H / UBRR0L Baud rate

16-bit register split into high byte (UBRR0H, bits 11–8) and low byte (UBRR0L, bits 7–0). Stores the prescaler value.

// 9600 baud at 16 MHz → prescaler = 103 #define BAUD_PRESCALER ((F_CPU / (BAUDRATE * 16UL)) - 1) UBRR0H = (uint8_t)(BAUD_PRESCALER >> 8); UBRR0L = (uint8_t) BAUD_PRESCALER;
UCSR0A Status flags (read-only mostly)
RXC
TXC
UDRE
FE
DOR
PE
U2X
MPCM
BitNameMeaning
7RXC0Receive Complete — set when unread data is in UDR0. Poll before reading.
6TXC0Transmit Complete — set when transmit buffer is empty and shift register done.
5UDRE0Data Register Empty — set when UDR0 is ready for new data. Poll before writing.
4FE0Frame Error — stop bit was not 1. Indicates sync problem.
3DOR0Data OverRun — receive buffer full, incoming byte lost.
2PE0Parity Error — received parity bit doesn't match data.
UCSR0B Control — enable/disable + interrupts
RXCIE
TXCIE
UDRIE
RXEN
TXEN
UCSZ2
RXB8
TXB8
BitNameMeaning
7RXCIE0RX Complete Interrupt Enable — fires ISR when RXC0 is set
6TXCIE0TX Complete Interrupt Enable — fires ISR when TXC0 is set
5UDRIE0Data Register Empty Interrupt Enable
4RXEN0Receiver Enable — must be 1 to receive data
3TXEN0Transmitter Enable — must be 1 to transmit data
2UCSZ20Character size MSB (combined with UCSZ1:0 in UCSR0C)
UCSR0C Frame format configuration
URSEL
UMSEL
UPM1
UPM0
USBS
UCSZ1
UCSZ0
UCPOL
Character size (UCSZ2:1:0)

0 1 1 → 8 bits (most common)
0 1 0 → 7 bits
1 1 1 → 9 bits

Parity (UPM1:0)

0 0 → disabled
1 0 → even parity
1 1 → odd parity

Stop bits (USBS)

0 → 1 stop bit (default)
1 → 2 stop bits

Mode (UMSEL)

0 → Asynchronous (UART)
1 → Synchronous (USART)

UDR0 Data I/O register

The single data register that serves as both transmit buffer (write to it) and receive buffer (read from it). The hardware handles all serial bit-shifting automatically.

To transmit

Wait for UDRE0 = 1, then write: UDR0 = myByte;

To receive

Wait for RXC0 = 1, then read: data = UDR0;

Bit-shift trick explained

How does (1 << RXEN0) work? RXEN0 is just a number (4). The operation shifts binary 1 left by 4 positions:

// 1 in binary = 00000001 // RXEN0 = 4 // 1 << 4 = 00010000 (bit 4 is now set) UCSR0B = (1 << RXEN0) | (1 << TXEN0); // = 00010000 | 00001000 // = 00011000 (both bits set)

Complete code example

Sending and receiving a byte over USART — 8-bit data, no parity, 1 stop bit, 9600 baud at 16 MHz.

1

Define constants and include header

#include <avr/io.h> #define F_CPU 16000000UL // CPU clock: 16 MHz #define BAUDRATE 9600 // desired baud rate #define BAUD_PRESCALER ((F_CPU / (BAUDRATE * 16UL)) - 1)
2

Initialize USART

void USART_Init() { // Step 1: set baud rate UBRR0H = (uint8_t)(BAUD_PRESCALER >> 8); UBRR0L = (uint8_t) BAUD_PRESCALER; // Step 2: enable receiver and transmitter UCSR0B = (1 << RXEN0) | (1 << TXEN0); // Step 3: set frame format — 8 data, no parity, 1 stop UCSR0C = (3 << UCSZ00); }
3

Transmit a byte (polling)

void USART_Transmit(uint8_t data) { // Wait until transmit buffer is empty while (!(UCSR0A & (1 << UDRE0))); // Write data to register — hardware does the rest UDR0 = data; }
4

Receive a byte (polling)

uint8_t USART_Receive() { // Wait until data has been received while (!(UCSR0A & (1 << RXC0))); // Return received byte from data register return UDR0; }
5

Main program

int main() { USART_Init(); // configure USART USART_Transmit('A'); // send character 'A' uint8_t rx = USART_Receive(); // wait & receive return 0; }

Polling vs interrupts

Polling (used above)

CPU loops waiting for the flag. Simple but blocks execution. Fine for simple programs.

Interrupt-driven

Set RXCIE0 / TXCIE0 in UCSR0B. ISR fires automatically. CPU is free for other work.

MCQ Quiz — Lecture 06

12 questions covering all topics. Select an answer — instant feedback after each one.