Skip to content
Home » Embedded Systems » Communication Interfaces: UART, SPI, and I2C Explained

Communication Interfaces: UART, SPI, and I2C Explained

UART SPI and I2C featured image with dark green background, HARDWARE badge, TX RX icon in green circle, and Communication Interfaces Explained subtitle by nerdyelectronics.com
Embedded Systems Learning Path
Part 65 of 129View Full Path →

KEY TAKEAWAYS

  • UART is asynchronous (no clock line), simple, and used for point-to-point communication between two devices
  • SPI uses 4 wires (MOSI, MISO, SCK, CS) and supports high-speed full-duplex communication with multiple slaves
  • I2C uses only 2 wires (SDA, SCL) with addressing to support multiple devices on one bus
  • Choose UART for simplicity, SPI for speed, and I2C for multiple devices with fewer pins

Why Communication Interfaces Matter

Microcontrollers rarely work alone. They need to communicate with sensors, displays, memory chips, other microcontrollers, and computers. This communication happens through standardized communication interfaces or protocols.The three most common serial communication interfaces in embedded systems are:
  • UART – Universal Asynchronous Receiver/Transmitter
  • SPI – Serial Peripheral Interface
  • I2C – Inter-Integrated Circuit
Each has its own strengths, trade-offs, and ideal use cases. By the end of this article, you will know how each one works and when to use which.

UART (Universal Asynchronous Receiver/Transmitter)

How UART Works

UART is the simplest serial communication interface. It uses just two wires:
  • TX (Transmit) – sends data
  • RX (Receive) – receives data
The TX pin of one device connects to the RX pin of the other, and vice versa. This is called a point-to-point connection – UART connects exactly two devices.
  Device A              Device B
  +------+              +------+
  |  TX  |--------------|  RX  |
  |  RX  |--------------|  TX  |
  |  GND |--------------|  GND |
  +------+              +------+
UART is asynchronous, meaning there is no shared clock signal. Both devices must agree on the same baud rate (speed) beforehand. Common baud rates are 9600, 19200, 38400, 57600, and 115200 bits per second.

UART Data Frame

Each byte is sent as a frame:
  Idle  |Start|  D0  |  D1  |  D2  |  D3  |  D4  |  D5  |  D6  |  D7  |Parity| Stop |  Idle
  ______       _____ _____ _____ _____ _____ _____ _____ _____                    ______
        |_____|     |     |     |     |     |     |     |     |______|_____|_____|

  - Start bit: Always 0 (pulls the line low)
  - Data bits: 5 to 9 bits (usually 8)
  - Parity bit: Optional error checking (even or odd)
  - Stop bit: 1 or 2 bits, always high
The most common configuration is 8N1: 8 data bits, No parity, 1 stop bit.

UART Characteristics

FeatureDetail
Wires2 (TX, RX) + GND
SpeedTypically up to 115200 bps, some up to several Mbps
DirectionFull-duplex (send and receive simultaneously)
DevicesPoint-to-point (2 devices only)
ClockNo clock signal (asynchronous)
DistanceShort (PCB level), longer with RS-232/RS-485 transceivers

Common Uses

  • Debugging output (serial monitor)
  • GPS modules
  • Bluetooth modules (HC-05, HM-10)
  • GSM/LTE modems
  • Communication between two microcontrollers

SPI (Serial Peripheral Interface)

How SPI Works

SPI is a synchronous protocol that uses a shared clock signal. It follows a master-slave architecture where one master device controls communication with one or more slave devices.SPI uses four wires:
  • MOSI (Master Out, Slave In) – data from master to slave
  • MISO (Master In, Slave Out) – data from slave to master
  • SCLK (Serial Clock) – clock signal generated by the master
  • SS/CS (Slave Select / Chip Select) – selects which slave to talk to (one per slave)
  Master                 Slave 1             Slave 2
  +------+              +------+            +------+
  | MOSI |---+----------|MOSI  |---+--------|MOSI  |
  | MISO |---+----------|MISO  |---+--------|MISO  |
  | SCLK |---+----------|SCLK  |---+--------|SCLK  |
  | SS1  |--------------|SS    |   |         |      |
  | SS2  |----------------------------+------|SS    |
  +------+              +------+            +------+
To communicate with a specific slave, the master pulls that slave’s SS/CS pin LOW. Only the selected slave responds.

SPI Data Transfer

SPI is full-duplex: the master and slave exchange data simultaneously. For every byte the master sends, it receives a byte back.
SCLK:  ___|‾‾‾|___|‾‾‾|___|‾‾‾|___|‾‾‾|___|‾‾‾|___|‾‾‾|___|‾‾‾|___|‾‾‾|
MOSI:  ---| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |---  (Master sends)
MISO:  ---| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |---  (Slave sends)
SS:    ‾‾‾|_______________________________________________|‾‾‾

SPI Clock Modes

SPI has 4 clock modes based on two settings:
  • CPOL (Clock Polarity) – idle state of the clock (0 = low, 1 = high)
  • CPHA (Clock Phase) – when data is sampled (0 = leading edge, 1 = trailing edge)
ModeCPOLCPHADescription
Mode 000Clock idle low, sample on rising edge (most common)
Mode 101Clock idle low, sample on falling edge
Mode 210Clock idle high, sample on falling edge
Mode 311Clock idle high, sample on rising edge
Always check the slave device’s datasheet to use the correct mode.

SPI Characteristics

FeatureDetail
Wires4 (MOSI, MISO, SCLK, SS) + GND
SpeedUp to tens of MHz (very fast)
DirectionFull-duplex
Devices1 master, multiple slaves (1 SS pin per slave)
ClockSynchronous (master provides clock)
OverheadNo addressing, no acknowledgment (minimal overhead)

Common Uses

  • SD cards
  • TFT/OLED displays
  • Flash memory chips
  • ADC and DAC chips
  • High-speed sensor modules

I2C (Inter-Integrated Circuit)

How I2C Works

I2C (pronounced “I-squared-C” or “I-two-C”) uses just two wires to connect multiple devices on a shared bus:
  • SDA (Serial Data) – bidirectional data line
  • SCL (Serial Clock) – clock signal from the master
Both lines need pull-up resistors (typically 4.7K ohm) because I2C uses open-drain outputs.
        VCC          VCC
         |            |
        [4.7K]       [4.7K]
         |            |
  SDA ---+-----+------+-----+---
         |     |      |     |
  SCL ---+-----+------+-----+---
         |     |      |     |
      Master  Slave  Slave  Slave
              0x48   0x68   0x3C
Each slave device has a unique 7-bit address (some use 10-bit). The master sends the address to select which slave to communicate with. No extra wires are needed per device.

I2C Data Transfer

An I2C transaction follows this sequence:
1. Master sends START condition (SDA goes low while SCL is high)
2. Master sends 7-bit slave address + 1-bit R/W direction
3. Slave sends ACK (acknowledgment)
4. Data bytes are transferred (8 bits + ACK each)
5. Master sends STOP condition (SDA goes high while SCL is high)

  START   Address (7 bits)  R/W  ACK   Data (8 bits)   ACK   STOP
  |_____|__________________|____|_____|_______________|_____|_____/‾‾‾

I2C Speeds

ModeSpeed
Standard Mode100 kHz
Fast Mode400 kHz
Fast Mode Plus1 MHz
High Speed Mode3.4 MHz

I2C Characteristics

FeatureDetail
Wires2 (SDA, SCL) + GND
Speed100 kHz to 3.4 MHz
DirectionHalf-duplex (one direction at a time)
DevicesUp to 127 on one bus (7-bit addressing)
ClockSynchronous (master provides clock)
AcknowledgmentBuilt-in ACK/NACK after each byte

Common Uses

  • Temperature sensors (LM75, BME280)
  • EEPROM memory chips
  • Real-time clocks (DS1307, DS3231)
  • Small OLED displays (SSD1306)
  • Accelerometers and gyroscopes (MPU6050)

UART vs SPI vs I2C: Comparison

FeatureUARTSPII2C
Wires24 + 1 per slave2
SpeedUp to ~1 MbpsUp to ~50 MHzUp to 3.4 MHz
DuplexFullFullHalf
Max Devices2Limited by SS pins127
ClockNone (async)Yes (master)Yes (master)
ComplexitySimpleMediumMedium
Error DetectionOptional parityNone built-inACK/NACK
Best ForDebug, GPS, BTFast data, displaysMany slow sensors

How to Choose

Choose UART when:
  • You need to connect just two devices
  • You need debug/serial output
  • The peripheral uses UART (GPS, Bluetooth modules)
Choose SPI when:
  • You need high data transfer speed
  • You are interfacing with displays, SD cards, or flash memory
  • You have enough free GPIO pins for chip select lines
Choose I2C when:
  • You need to connect many devices with minimal wiring
  • Speed is not critical (sensors, EEPROMs, RTCs)
  • You are short on GPIO pins

Summary

UART, SPI, and I2C are the backbone of embedded communication. Most real-world projects use a combination of all three. A typical embedded system might use UART for debugging, SPI for a display and SD card, and I2C for temperature and motion sensors, all running simultaneously. Understanding when and how to use each one is a fundamental skill for every embedded developer.

📘 Working with UART, SPI or I²C? Solid C fundamentals make every protocol easier to debug. My complete Master C & Embedded C course takes you from zero to hardware-ready code — free on YouTube (53 videos), or guided on Udemy with quizzes, certificate and my Q&A support.

Code: Initializing Each Protocol on ATmega328P

Theory is only half the picture. Here is how you actually configure each protocol in code on an ATmega328P. These are complete, compilable init routines you can drop into your project.

UART Initialization (9600 baud, 16 MHz clock)

#include <avr/io.h>

void uart_init(void)
{
    /* Baud rate = 9600, F_CPU = 16 MHz
       UBRR = F_CPU / (16 * baud) - 1 = 103 */
    UBRR0H = 0;
    UBRR0L = 103;

    UCSR0B = (1 << TXEN0) | (1 << RXEN0);   /* Enable TX and RX */
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); /* 8-bit data, 1 stop, no parity */
}

void uart_send(uint8_t data)
{
    while (!(UCSR0A & (1 << UDRE0)));  /* Wait until buffer is empty */
    UDR0 = data;
}

uint8_t uart_recv(void)
{
    while (!(UCSR0A & (1 << RXC0)));   /* Wait until data is received */
    return UDR0;
}

SPI Master Initialization

#include <avr/io.h>

void spi_master_init(void)
{
    /* MOSI (PB3), SCK (PB5), SS (PB2) = output; MISO (PB4) = input */
    DDRB |= (1 << PB3) | (1 << PB5) | (1 << PB2);
    DDRB &= ~(1 << PB4);

    /* Enable SPI, Master mode, clock = F_CPU/16 */
    SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
}

uint8_t spi_transfer(uint8_t data)
{
    SPDR = data;                       /* Start transmission */
    while (!(SPSR & (1 << SPIF)));     /* Wait for transfer complete */
    return SPDR;                       /* Return received byte */
}

/* Usage: pull SS low, transfer, pull SS high */
void spi_read_sensor(uint8_t reg, uint8_t *value)
{
    PORTB &= ~(1 << PB2);             /* SS LOW — select slave */
    spi_transfer(reg | 0x80);         /* Send register address (read bit) */
    *value = spi_transfer(0xFF);      /* Clock out data */
    PORTB |= (1 << PB2);              /* SS HIGH — deselect */
}

I2C (TWI) Master Initialization

#include <avr/io.h>

#define F_SCL 100000UL  /* 100 kHz standard mode */

void i2c_init(void)
{
    /* TWBR = (F_CPU / F_SCL - 16) / 2  (with prescaler = 1) */
    TWSR = 0x00;                        /* Prescaler = 1 */
    TWBR = ((F_CPU / F_SCL) - 16) / 2; /* Set bit rate */
}

void i2c_start(void)
{
    TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
    while (!(TWCR & (1 << TWINT)));  /* Wait for START to complete */
}

void i2c_stop(void)
{
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
}

void i2c_write(uint8_t data)
{
    TWDR = data;
    TWCR = (1 << TWINT) | (1 << TWEN);
    while (!(TWCR & (1 << TWINT)));
}

uint8_t i2c_read_ack(void)
{
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
    while (!(TWCR & (1 << TWINT)));
    return TWDR;
}

/* Example: read 1 byte from device 0x68, register 0x75 */
uint8_t read_register(uint8_t dev_addr, uint8_t reg)
{
    uint8_t data;
    i2c_start();
    i2c_write(dev_addr << 1);        /* Address + Write bit */
    i2c_write(reg);                   /* Register address */
    i2c_start();                      /* Repeated START */
    i2c_write((dev_addr << 1) | 1);  /* Address + Read bit */
    data = i2c_read_ack();
    i2c_stop();
    return data;
}

Quick Decision Guide: Which Protocol Should You Use?

Use UART when you need simple point-to-point communication between two devices — a microcontroller talking to a GPS module, a Bluetooth module, or a PC over a serial-to-USB adapter. UART is easy to set up (just TX, RX, and GND), needs no clock line, and works well for distances up to a few metres. The trade-off is speed (typically 115200 baud max) and the two-device limitation.

Use SPI when you need speed. SPI can run at several MHz — 10× faster than I2C — making it the right choice for SD cards, TFT displays, high-speed ADCs, and flash memory. The cost is pin count: you need MOSI, MISO, SCK, plus one chip-select (SS) line per slave device. For two or three devices on one bus, that is manageable. For a dozen, the wiring becomes impractical.

Use I2C when you have many low-speed devices on one bus. I2C uses only two wires (SDA, SCL) regardless of how many devices are connected — each device has a unique 7-bit address. It is ideal for sensors (temperature, humidity, accelerometer), EEPROMs, and RTCs. The trade-off is speed (100–400 kHz standard) and the need for pull-up resistors on both lines.

Articles in This Communication Protocols Series

Explore each protocol in depth with these dedicated guides:

Leave a Reply

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