Skip to content

Communication protocols

Modern embedded projects rarely use a microcontroller in isolation: it almost always has to talk to sensors, displays, other boards, or a PC.
Communication protocols define how bits move between devices and how both sides agree on speed, timing, and meaning of the data.

On Arduino‑class boards, three protocols are especially common:

  • Serial (UART): point‑to‑point communication with a computer or another microcontroller.
  • I²C (Two‑Wire Interface): short‑range bus with one master and (usually) several slave devices on just two wires.
  • SPI (Serial Peripheral Interface): very fast, full‑duplex bus for displays, SD cards, and high‑speed sensors.

In this page we’ll briefly introduce each protocol and show a minimal Arduino example.


Serial (UART)

Serial (often called UART or “Serial over USB”) is the simplest protocol to start with:

  • Wires: TX, RX, and GND.
  • Typical use: debugging via the Serial Monitor, sending simple commands, logging sensor data.
  • Data rate: configured as a baud rate (e.g. 9600, 115200).

Most Arduino boards expose a Serial object that is already connected to the USB port.

Example: send sensor data over Seria

This example (for an ESP32-S3 DevKit‑style board) reads a value from GPIO 34 and prints it to the Serial Monitor:

// Simple Serial example for ESP32: read from GPIO34 and print to Serial Monitor

const int SENSOR_PIN = 34;  // ADC pin on many ESP32-S3 DevKit boards

void setup() {
  // Start serial communication at 115200 baud
  Serial.begin(115200);

  Serial.println("Serial ready!");
}

void loop() {
  int sensorValue = analogRead(SENSOR_PIN);

  Serial.print("Sensor value: ");
  Serial.println(sensorValue);

  delay(500);
}

Open the Serial Monitor in the Arduino IDE and make sure the baud rate matches the value in Serial.begin(...).


I²C (Two‑Wire Interface)

I²C is a bus protocol that lets one master (your Arduino or ESP32) talk to multiple slave devices on the same two wires:

  • Wires: SDA (data), SCL (clock), plus GND and usually 3.3 V or 5 V power.
  • Addressing: each device has a 7‑bit address (e.g. 0x27).
  • Typical use: temperature/pressure sensors, real‑time clocks, small LCDs, I/O expanders.

On Arduino you use the Wire library to work with I²C.

Example: I²C master sending a byte

This minimal sketch shows an Arduino acting as I²C master and sending a single byte to a device at address 0x27:

#include <Wire.h>

// I2C address of the peripheral device
const uint8_t DEVICE_ADDRESS = 0x27;

void setup() {
  // Initialize I2C as master
  Wire.begin(); // on most Arduino boards: SDA/SCL pins; on ESP32: GPIO 21/22 by default

  // Optional: also initialize Serial for debugging
  Serial.begin(115200);
}

void loop() {
  uint8_t valueToSend = 42;

  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(valueToSend);      // send a single byte
  uint8_t error = Wire.endTransmission(); // actually transmit

  if (error == 0) {
    Serial.println("I2C write OK");
  } else {
    Serial.print("I2C error: ");
    Serial.println(error);
  }

  delay(1000);
}

In a real project you would check the datasheet for your specific device to know which bytes/commands to send and in what order.


SPI (Serial Peripheral Interface)

SPI is a high‑speed, full‑duplex protocol that uses separate lines for data and clock:

  • Wires: MOSI, MISO, SCK, one chip select (CS) per device, plus GND and power.
  • Typical use: color displays (TFT/OLED), SD cards, high‑speed sensors, external flash memory.
  • Speed: much faster than I²C; megabits per second are common.

On Arduino you use the SPI library.

Example: send a command over SPI

This example shows how to send a single byte command to an SPI device:

#include <SPI.h>

// Chip Select pin for the SPI device (GPIO 5 is free on many ESP32 DevKit boards)
const int CS_PIN = 5;

void setup() {
  // Configure CS pin
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH); // deselect device

  // Initialize SPI for ESP32 VSPI: SCK=18, MISO=19, MOSI=23, CS=CS_PIN
  SPI.begin(18, 19, 23, CS_PIN);

  // Optional: Serial for debug
  Serial.begin(115200);
}

void loop() {
  uint8_t command = 0xA5; // example command byte

  // Select the device by pulling CS low
  digitalWrite(CS_PIN, LOW);

  // Transfer one byte; this both sends and receives a byte
  uint8_t response = SPI.transfer(command);

  // Deselect the device again
  digitalWrite(CS_PIN, HIGH);

  Serial.print("Sent 0x");
  Serial.print(command, HEX);
  Serial.print(", received 0x");
  Serial.println(response, HEX);

  delay(1000);
}

For real peripherals (e.g. displays or SD cards) you usually:

  • Configure SPI mode (0–3), clock speed, and bit order to match the datasheet.
  • Use a dedicated Arduino library that wraps the low‑level SPI commands.

When to choose which protocol?

  • Serial (UART)
    Use for debugging, logging to a PC, or simple point‑to‑point links between two boards.

  • I²C
    Use when you want to connect several low‑speed peripherals (sensors, small displays) using just two wires.

  • SPI
    Use for fast data transfer (displays, SD cards, high‑speed sensors) where you can afford a few extra wires.

Most real projects mix these protocols: for example Serial for debugging, I²C for sensors, and SPI for a display or SD card, all on the same Arduino‑compatible board.