Module 7: Communication Protocols

Level: 🟡 Intermediate
Board: Arduino Uno
Prerequisites: Module 6
Estimated time: 70–90 minutes
Goal: Understand I2C, SPI, and UART so you can connect any module.


What You'll Learn

Until now, every component you've used communicates in a simple way: a voltage on a pin. But complex components (displays, real-time clocks, pressure sensors, SD card readers) need to send structured data. They need protocols.

In this module, you'll learn the three protocols that cover 95% of all Arduino peripherals: UART (Serial), I2C, and SPI. You'll learn when to use each, how to debug them, and you'll wire three I2C devices on a single two-wire bus.


7.1 Why Protocols Exist

A digital pin can only say HIGH or LOW. That's one bit of information. But a temperature sensor needs to send a number like 23.7, and that's multiple bytes. A display needs to receive hundreds of bytes per frame.

Protocols solve this by defining rules for how bits are sent over wires: what order, at what speed, who talks when, and how the receiver knows a message is complete.

Think of it like language. Two people can hear each other (wires are connected), but they still need to speak the same language at the same pace, take turns, and agree on sentence structure.


7.2 UART / Serial: The Protocol You Already Know

You've been using UART since Module 3; that's what Serial.begin(9600) sets up. UART (Universal Asynchronous Receiver-Transmitter) is the simplest protocol for device-to-device communication.

How UART Works

Key Parameters

Parameter Common Value Notes
Baud rate 9600, 115200 Both devices must match exactly
Data bits 8 Almost always 8
Parity None Error checking (rarely used in Arduino)
Stop bits 1 Marks the end of a byte

Wiring Between Two Devices

Device A TX → Device B RX
Device A RX → Device B TX
Device A GND → Device B GND

Critical cross-connection: TX goes to RX, not TX to TX. You're connecting output to input. This is the #1 UART wiring mistake.

Voltage levels: If Device A is 5V and Device B is 3.3V, you need a level shifter on the TX→RX line from the 5V device. The 3.3V→5V direction usually works without one (5V Arduinos read 3.3V as HIGH).

Arduino UART Pins

The Uno has one hardware UART on pins 0 (RX) and 1 (TX). This is also used for USB communication. If you use pins 0/1 for a device, you can't upload code or use the Serial Monitor at the same time.

Solution: Use SoftwareSerial for additional UART ports:

#include <SoftwareSerial.h>

SoftwareSerial mySerial(4, 5);  // RX=4, TX=5

void setup() {
  Serial.begin(9600);      // Hardware serial (USB)
  mySerial.begin(9600);    // Software serial (device)
}

void loop() {
  // Forward data from device to Serial Monitor
  if (mySerial.available()) {
    Serial.write(mySerial.read());
  }
}

SoftwareSerial limitations: Can't reliably exceed 57600 baud, only one instance can actively listen at a time, and it uses interrupts that can affect timing-sensitive code. For serious UART work, consider a board with multiple hardware UARTs (Mega, ESP32).

When to Use UART


7.3 I2C: The Two-Wire Bus

I2C (Inter-Integrated Circuit, pronounced "eye-squared-see" or "eye-two-see") is the most common protocol for sensors and displays in Arduino projects. Two wires, multiple devices, simple wiring.

How I2C Works

Wiring

Arduino SDA (A4) → Device 1 SDA → Device 2 SDA → Device 3 SDA
Arduino SCL (A5) → Device 1 SCL → Device 2 SCL → Device 3 SCL

All SDA lines connect together. All SCL lines connect together. Add pull-up resistors (typically 4.7kΩ) from SDA to VCC and SCL to VCC. Many breakout boards include these, so check before adding your own (too many pull-ups in parallel lower the total resistance and can cause communication failures).

I2C Addresses

Every I2C device has a fixed address (or a small set of configurable addresses). Common addresses:

Device Typical Address
SSD1306 OLED (128×64) 0x3C or 0x3D
BMP280 pressure sensor 0x76 or 0x77
DS3231 RTC 0x68
AT24C32 EEPROM 0x57
PCF8574 I/O expander 0x20–0x27
MPU6050 accelerometer 0x68 or 0x69

Address conflict: The DS3231 RTC and MPU6050 accelerometer both default to 0x68. You can't use both on the same I2C bus unless one has a configurable address pin. Always check for conflicts before wiring.

I2C Scanner: Your Debugging Best Friend

When I2C isn't working, the first step is: are the devices actually visible on the bus?

/*
 * I2C Scanner — finds all devices on the I2C bus
 * Upload this whenever you're debugging I2C connections.
 */

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("I2C Scanner — scanning...");
}

void loop() {
  int deviceCount = 0;

  for (byte address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("Device found at 0x");
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.println();
      deviceCount++;
    }
  }

  if (deviceCount == 0) {
    Serial.println("No I2C devices found!");
    Serial.println("Check: SDA/SCL wiring, pull-ups, power, voltage levels");
  } else {
    Serial.print("Found ");
    Serial.print(deviceCount);
    Serial.println(" device(s)");
  }

  Serial.println("---");
  delay(5000);
}

Common I2C Problems

Symptom Likely Cause
Scanner finds no devices SDA/SCL swapped, missing pull-ups, device not powered, wrong voltage
Scanner finds device but library fails Wrong address in code, library doesn't support your variant
Communication works then stops Pull-up resistance too high/low, long wires (keep I2C under 1m)
Random garbage data Missing ground connection, noise on long wires

When to Use I2C


7.4 SPI: Speed When You Need It

SPI (Serial Peripheral Interface) is faster than I2C but uses more wires. It's the go-to for high-throughput peripherals.

How SPI Works

Arduino Uno SPI Pins

Function Pin Notes
MOSI 11 Fixed on Uno
MISO 12 Fixed on Uno
SCK 13 Fixed on Uno (conflicts with built-in LED)
CS/SS 10 (default) Can use any digital pin

Wiring Multiple SPI Devices

Arduino MOSI → Device 1 MOSI → Device 2 MOSI  (shared)
Arduino MISO → Device 1 MISO → Device 2 MISO  (shared)
Arduino SCK  → Device 1 SCK  → Device 2 SCK   (shared)
Arduino pin 9 → Device 1 CS                     (unique)
Arduino pin 8 → Device 2 CS                     (unique)

MOSI, MISO, and SCK are shared. Each device gets its own CS pin. Only one CS is pulled LOW at a time; that device responds, all others ignore the bus.

SPI vs. I2C Comparison

Feature I2C SPI
Wires 2 (SDA, SCL) 4 + 1 per device (MOSI, MISO, SCK, CS)
Speed Up to 400 kHz (standard) Up to 10+ MHz
Devices Up to 127 on same bus Limited by CS pins
Addressing Built-in (7-bit address) Via CS pin (no address conflicts)
Complexity Simpler wiring More wires but simpler protocol
Best for Sensors, displays, RTCs SD cards, TFT displays, fast ADCs

Basic SPI Communication

#include <SPI.h>

const int CS_PIN = 10;

void setup() {
  SPI.begin();
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);  // Deselect device
}

byte spiTransfer(byte data) {
  digitalWrite(CS_PIN, LOW);   // Select device
  byte result = SPI.transfer(data);  // Send and receive simultaneously
  digitalWrite(CS_PIN, HIGH);  // Deselect device
  return result;
}

When to Use SPI


7.5 Choosing a Protocol: Decision Framework

When you buy a new module or sensor, its datasheet will tell you which protocol(s) it supports. Some support multiple. Here's how to choose:

Do you need to connect more than one device?
├── Yes → Is speed critical (display, SD card)?
│         ├── Yes → SPI
│         └── No  → I2C (fewer wires, simpler)
└── No  → Is it a simple serial device (GPS, Bluetooth)?
          ├── Yes → UART
          └── No  → I2C or SPI (check what the device supports)

Real-World Decision Examples

Scenario Best Protocol Why
3 sensors + 1 OLED display I2C All can share two wires
SD card for data logging SPI SD cards require SPI speed
GPS module UART GPS outputs NMEA sentences via serial
Color TFT display + SD card SPI Both need high speed, share bus with separate CS
Single temperature sensor I2C Simplest wiring for a single device
Bluetooth module for phone UART Serial interface is standard for BT modules

7.6 Common Protocol Mistakes

UART Mistakes

  1. Baud rate mismatch: Serial monitor shows garbage. Check both sides use the same baud rate.
  2. TX↔RX not crossed: Nothing received. TX must connect to RX and vice versa.
  3. Missing ground: Random data errors. Ground must be shared.

I2C Mistakes

  1. Missing pull-ups: Scanner finds nothing. Add 4.7kΩ pull-ups to SDA and SCL.
  2. Too many pull-ups: Communication errors. Multiple breakout boards each adding their own pull-ups can bring total resistance too low. Desolder extras.
  3. Wrong address: Library initializes but reads zeros. Use the I2C scanner to find the actual address.
  4. Voltage mismatch: 5V Arduino + 3.3V sensor without level shifting can damage the sensor.
  5. Long wires: I2C is designed for short distances. Over 50cm, consider lower pull-up values or switching to UART/SPI.

SPI Mistakes

  1. CS pin not set to OUTPUT: Device never selected.
  2. Wrong SPI mode: Check the device's clock polarity and phase in its datasheet.
  3. Pin 13 conflict: SCK shares pin 13 with the built-in LED, which can interfere. Avoid using pin 13 for other purposes in SPI projects.
  4. Missing level shifting: Same as I2C, 5V to 3.3V needs attention.

Module Project: Multi-Sensor I2C Hub

Objective

Connect three I2C devices to the same two-wire bus: a BMP280 barometric pressure/temperature sensor, an SSD1306 OLED display, and a DS3231 real-time clock. Display timestamped pressure and temperature readings on the OLED.

Components Needed

Component Quantity Notes
Arduino Uno 1
USB-B cable 1
Breadboard 1
BMP280 sensor module 1 I2C address 0x76 or 0x77
SSD1306 OLED (128×64, I2C) 1 I2C address 0x3C
DS3231 RTC module 1 I2C address 0x68
4.7kΩ Resistors 2 I2C pull-ups (if not on breakout boards)
CR2032 coin cell 1 For RTC backup battery (usually included on module)
Jumper wires 8+

Wiring

All three devices share the I2C bus:

Connection Wire
All device VCC pins → 5V (or 3.3V if module requires) Red
All device GND pins → GND Black
All device SDA pins → Arduino A4 (SDA) Yellow
All device SCL pins → Arduino A5 (SCL) White
4.7kΩ from SDA to VCC (if needed) Pull-up
4.7kΩ from SCL to VCC (if needed) Pull-up

Before wiring: Run the I2C scanner to verify each device works individually. Then add them one at a time to the bus, scanning after each addition. This isolates problems.

Libraries to Install

  1. Adafruit BMP280 (+ Adafruit Unified Sensor dependency)
  2. Adafruit SSD1306 (+ Adafruit GFX dependency)
  3. RTClib by Adafruit

The Code

/*
 * Module 7 Project: Multi-Sensor I2C Hub
 *
 * Three I2C devices on one bus:
 * - BMP280: temperature and barometric pressure
 * - SSD1306: 128×64 OLED display
 * - DS3231: real-time clock
 *
 * Circuit:
 * All devices share SDA (A4) and SCL (A5).
 * 4.7kΩ pull-ups on SDA and SCL (if not on breakout boards).
 *
 * Libraries: Adafruit BMP280, Adafruit SSD1306, RTClib
 * Board: Arduino Uno
 */

#include <Wire.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>

// --- Display setup ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// --- Sensor setup ---
Adafruit_BMP280 bmp;
RTC_DS3231 rtc;

// --- Timing ---
unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL = 1000;

// --- Track min/max ---
float minTemp = 999.0;
float maxTemp = -999.0;
float minPressure = 9999.0;
float maxPressure = 0.0;

void setup() {
  Serial.begin(9600);
  Serial.println("I2C Hub starting...");

  // --- Initialize I2C bus ---
  Wire.begin();

  // --- Scan for devices ---
  Serial.println("Scanning I2C bus...");
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print("  Found: 0x");
      if (addr < 16) Serial.print("0");
      Serial.println(addr, HEX);
    }
  }

  // --- Initialize OLED ---
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("ERROR: SSD1306 not found at 0x3C");
    Serial.println("Check: wiring, address (try 0x3D), power");
    while (1);  // Halt — can't display without screen
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("I2C Hub");
  display.println("Initializing...");
  display.display();

  // --- Initialize BMP280 ---
  if (!bmp.begin(0x76)) {
    Serial.println("WARNING: BMP280 not found at 0x76, trying 0x77...");
    if (!bmp.begin(0x77)) {
      Serial.println("ERROR: BMP280 not found");
      display.println("BMP280 MISSING");
      display.display();
    }
  }

  // Configure BMP280 for weather monitoring
  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
                   Adafruit_BMP280::SAMPLING_X2,   // temperature
                   Adafruit_BMP280::SAMPLING_X16,  // pressure
                   Adafruit_BMP280::FILTER_X16,
                   Adafruit_BMP280::STANDBY_MS_500);

  // --- Initialize RTC ---
  if (!rtc.begin()) {
    Serial.println("ERROR: DS3231 RTC not found");
    display.println("RTC MISSING");
    display.display();
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power — setting time to compile time");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  Serial.println("All devices initialized");
  delay(1000);
}

void loop() {
  unsigned long now = millis();

  if (now - lastUpdate < UPDATE_INTERVAL) return;
  lastUpdate = now;

  // --- Read sensors ---
  DateTime dateTime = rtc.now();
  float temperature = bmp.readTemperature();
  float pressure = bmp.readPressure() / 100.0;  // Pa → hPa (mbar)
  float altitude = bmp.readAltitude(1013.25);    // Assumes sea-level pressure

  // --- Update min/max ---
  if (temperature < minTemp) minTemp = temperature;
  if (temperature > maxTemp) maxTemp = temperature;
  if (pressure < minPressure) minPressure = pressure;
  if (pressure > maxPressure) maxPressure = pressure;

  // --- Update display ---
  display.clearDisplay();

  // Row 1: Time and date
  display.setTextSize(1);
  display.setCursor(0, 0);
  char timeStr[20];
  sprintf(timeStr, "%02d:%02d:%02d  %02d/%02d",
          dateTime.hour(), dateTime.minute(), dateTime.second(),
          dateTime.day(), dateTime.month());
  display.println(timeStr);

  // Divider line
  display.drawLine(0, 10, 127, 10, SSD1306_WHITE);

  // Row 2: Temperature (large)
  display.setTextSize(2);
  display.setCursor(0, 14);
  display.print(temperature, 1);
  display.setTextSize(1);
  display.print(" C");

  // Row 3: Temp min/max
  display.setCursor(0, 34);
  display.print("Lo:");
  display.print(minTemp, 1);
  display.print("  Hi:");
  display.println(maxTemp, 1);

  // Row 4: Pressure
  display.setCursor(0, 44);
  display.print("P: ");
  display.print(pressure, 1);
  display.println(" hPa");

  // Row 5: Altitude estimate
  display.setCursor(0, 54);
  display.print("Alt: ~");
  display.print(altitude, 0);
  display.println(" m");

  display.display();

  // --- Serial output (CSV-compatible) ---
  Serial.print(timeStr);
  Serial.print(",");
  Serial.print(temperature, 2);
  Serial.print(",");
  Serial.print(pressure, 2);
  Serial.print(",");
  Serial.println(altitude, 1);
}

Circuit Diagram (SVG)

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 850 420" font-family="monospace" font-size="11">
  <!-- Arduino -->
  <rect x="30" y="100" width="140" height="250" fill="#1a7a8a" stroke="#333" stroke-width="2" rx="8"/>
  <text x="100" y="130" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Arduino Uno</text>

  <!-- Pins -->
  <rect x="170" y="155" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="181" y="165" text-anchor="middle" fill="white" font-size="8">5V</text>
  <rect x="170" y="175" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="181" y="185" text-anchor="middle" fill="white" font-size="8">GND</text>
  <rect x="170" y="215" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="181" y="225" text-anchor="middle" fill="white" font-size="8">A4</text>
  <rect x="170" y="240" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
  <text x="181" y="250" text-anchor="middle" fill="white" font-size="8">A5</text>

  <!-- I2C Bus lines -->
  <line x1="192" y1="222" x2="750" y2="222" stroke="#cc8800" stroke-width="3"/>
  <text x="470" y="216" text-anchor="middle" font-size="11" fill="#cc8800" font-weight="bold">SDA (A4)</text>
  <line x1="192" y1="247" x2="750" y2="247" stroke="#008800" stroke-width="3"/>
  <text x="470" y="265" text-anchor="middle" font-size="11" fill="#008800" font-weight="bold">SCL (A5)</text>

  <!-- VCC rail -->
  <line x1="192" y1="162" x2="750" y2="162" stroke="red" stroke-width="2"/>
  <text x="470" y="156" text-anchor="middle" font-size="9" fill="red">VCC (5V)</text>

  <!-- GND rail -->
  <line x1="192" y1="182" x2="750" y2="182" stroke="blue" stroke-width="2"/>
  <text x="470" y="195" text-anchor="middle" font-size="9" fill="blue">GND</text>

  <!-- Pull-up resistors -->
  <rect x="240" y="140" width="35" height="14" fill="none" stroke="#333" stroke-width="1.5" rx="2"/>
  <text x="258" y="150" text-anchor="middle" font-size="7">4.7kΩ</text>
  <line x1="258" y1="154" x2="258" y2="162" stroke="red" stroke-width="1"/>
  <line x1="258" y1="154" x2="258" y2="222" stroke="#cc8800" stroke-width="1" stroke-dasharray="3,3"/>

  <rect x="290" y="140" width="35" height="14" fill="none" stroke="#333" stroke-width="1.5" rx="2"/>
  <text x="308" y="150" text-anchor="middle" font-size="7">4.7kΩ</text>
  <line x1="308" y1="154" x2="308" y2="162" stroke="red" stroke-width="1"/>
  <line x1="308" y1="154" x2="308" y2="247" stroke="#008800" stroke-width="1" stroke-dasharray="3,3"/>

  <!-- BMP280 -->
  <rect x="380" y="290" width="110" height="55" fill="#4488aa" stroke="#333" stroke-width="2" rx="6"/>
  <text x="435" y="312" text-anchor="middle" fill="white" font-size="11" font-weight="bold">BMP280</text>
  <text x="435" y="328" text-anchor="middle" fill="white" font-size="9">Temp + Pressure</text>
  <text x="435" y="340" text-anchor="middle" fill="#aaddee" font-size="8">0x76</text>

  <line x1="435" y1="290" x2="435" y2="247" stroke="#008800" stroke-width="1.5"/>
  <line x1="415" y1="290" x2="415" y2="222" stroke="#cc8800" stroke-width="1.5"/>
  <line x1="455" y1="290" x2="455" y2="182" stroke="blue" stroke-width="1.5"/>
  <line x1="395" y1="290" x2="395" y2="162" stroke="red" stroke-width="1.5"/>

  <!-- OLED Display -->
  <rect x="530" y="290" width="110" height="55" fill="#333" stroke="#333" stroke-width="2" rx="6"/>
  <text x="585" y="312" text-anchor="middle" fill="white" font-size="11" font-weight="bold">SSD1306</text>
  <text x="585" y="328" text-anchor="middle" fill="white" font-size="9">OLED 128×64</text>
  <text x="585" y="340" text-anchor="middle" fill="#aaa" font-size="8">0x3C</text>

  <line x1="585" y1="290" x2="585" y2="247" stroke="#008800" stroke-width="1.5"/>
  <line x1="565" y1="290" x2="565" y2="222" stroke="#cc8800" stroke-width="1.5"/>
  <line x1="605" y1="290" x2="605" y2="182" stroke="blue" stroke-width="1.5"/>
  <line x1="545" y1="290" x2="545" y2="162" stroke="red" stroke-width="1.5"/>

  <!-- DS3231 RTC -->
  <rect x="680" y="290" width="110" height="55" fill="#669944" stroke="#333" stroke-width="2" rx="6"/>
  <text x="735" y="312" text-anchor="middle" fill="white" font-size="11" font-weight="bold">DS3231</text>
  <text x="735" y="328" text-anchor="middle" fill="white" font-size="9">RTC</text>
  <text x="735" y="340" text-anchor="middle" fill="#cce6aa" font-size="8">0x68</text>

  <line x1="735" y1="290" x2="735" y2="247" stroke="#008800" stroke-width="1.5"/>
  <line x1="715" y1="290" x2="715" y2="222" stroke="#cc8800" stroke-width="1.5"/>
  <line x1="755" y1="290" x2="755" y2="182" stroke="blue" stroke-width="1.5"/>
  <line x1="695" y1="290" x2="695" y2="162" stroke="red" stroke-width="1.5"/>

  <!-- Labels -->
  <rect x="380" y="370" width="410" height="35" fill="#f0f0f0" stroke="#ccc" stroke-width="1" rx="4"/>
  <text x="585" y="388" text-anchor="middle" font-size="10">All 3 devices share the same SDA + SCL lines</text>
  <text x="585" y="400" text-anchor="middle" font-size="9" fill="#666">Each identified by unique I2C address</text>
</svg>

Circuit Schema (JSON)

{
  "module": 7,
  "project": "Multi-Sensor I2C Hub",
  "board": "Arduino Uno",
  "schematic": {
    "components": [
      {
        "id": "U1",
        "type": "arduino_uno",
        "pins_used": {
          "5V": "net_vcc", "GND": "net_gnd",
          "A4": "net_sda", "A5": "net_scl"
        }
      },
      {
        "id": "BMP1",
        "type": "bmp280",
        "protocol": "I2C",
        "address": "0x76",
        "operating_voltage": "3.3V-5.5V",
        "current_draw": "2.7mA",
        "pins": { "vcc": "net_vcc", "gnd": "net_gnd", "sda": "net_sda", "scl": "net_scl" }
      },
      {
        "id": "OLED1",
        "type": "ssd1306",
        "protocol": "I2C",
        "address": "0x3C",
        "resolution": "128x64",
        "operating_voltage": "3.3V-5V",
        "current_draw": "20mA",
        "pins": { "vcc": "net_vcc", "gnd": "net_gnd", "sda": "net_sda", "scl": "net_scl" }
      },
      {
        "id": "RTC1",
        "type": "ds3231",
        "protocol": "I2C",
        "address": "0x68",
        "operating_voltage": "2.3V-5.5V",
        "current_draw": "0.2mA",
        "backup_battery": "CR2032",
        "pins": { "vcc": "net_vcc", "gnd": "net_gnd", "sda": "net_sda", "scl": "net_scl" }
      },
      {
        "id": "R_SDA",
        "type": "resistor", "value": "4700", "unit": "ohm",
        "purpose": "I2C SDA pull-up (omit if breakout boards have pull-ups)",
        "pins": { "pin1": "net_vcc", "pin2": "net_sda" }
      },
      {
        "id": "R_SCL",
        "type": "resistor", "value": "4700", "unit": "ohm",
        "purpose": "I2C SCL pull-up (omit if breakout boards have pull-ups)",
        "pins": { "pin1": "net_vcc", "pin2": "net_scl" }
      }
    ],
    "nets": [
      { "name": "net_vcc", "nodes": ["U1.5V", "BMP1.vcc", "OLED1.vcc", "RTC1.vcc", "R_SDA.pin1", "R_SCL.pin1"] },
      { "name": "net_gnd", "nodes": ["U1.GND", "BMP1.gnd", "OLED1.gnd", "RTC1.gnd"] },
      { "name": "net_sda", "nodes": ["U1.A4", "BMP1.sda", "OLED1.sda", "RTC1.sda", "R_SDA.pin2"] },
      { "name": "net_scl", "nodes": ["U1.A5", "BMP1.scl", "OLED1.scl", "RTC1.scl", "R_SCL.pin2"] }
    ],
    "power": {
      "source": "USB",
      "total_current_ma": 73,
      "note": "BMP280(2.7) + OLED(20) + RTC(0.2) + Arduino(50) = ~73mA"
    }
  },
  "code": { "filename": "module07_i2c_hub.ino", "language": "cpp" },
  "validation": {
    "expected_behavior": "OLED shows time, temperature, pressure, altitude. Updates every second. Serial outputs CSV.",
    "debug_steps": [
      "Step 1: Run I2C scanner — verify all 3 addresses appear",
      "Step 2: Test each device individually with its library example sketch",
      "Step 3: Add devices to the combined sketch one at a time",
      "Step 4: If display is blank, check address (0x3C vs 0x3D)"
    ],
    "common_mistakes": [
      "No devices found: SDA and SCL pins swapped, or missing pull-ups",
      "One device disappears when another is added: address conflict or too many pull-ups",
      "RTC shows wrong time: needs rtc.adjust() on first run",
      "BMP280 reads 0: wrong I2C address — try both 0x76 and 0x77",
      "OLED flickers or blanks: update rate too fast, or insufficient power"
    ]
  }
}

Experiments to Try

Experiment 1: I2C Address Conflict Resolution

If you have two devices with the same address, explore solutions: some devices have address select pins (e.g., connecting SDO on BMP280 to GND changes address from 0x77 to 0x76). Document the options from each device's datasheet.

Experiment 2: UART GPS Module

Connect a GPS module (NEO-6M) via SoftwareSerial. Parse the NMEA sentences to extract latitude and longitude. Display on the OLED alongside time from the RTC.

Experiment 3: SPI + I2C Together

Add an SD card module (SPI) to log the I2C sensor data. This combines both protocols in one project. Write timestamped CSV data to the SD card every 10 seconds.


Self-Check: Module 7

Before moving to Module 8, make sure you can:


Key Terms Glossary

Term Definition
UART Universal Asynchronous Receiver-Transmitter. Serial protocol using TX/RX at an agreed baud rate.
I2C Inter-Integrated Circuit. Two-wire bus (SDA/SCL) supporting up to 127 addressed devices.
SPI Serial Peripheral Interface. Four-wire protocol (MOSI/MISO/SCK/CS) for high-speed communication.
Baud rate Data speed in bits per second. Both UART sides must match.
SDA Serial Data, the I2C data line.
SCL Serial Clock, the I2C clock line (generated by master).
MOSI Master Out Slave In, SPI data from Arduino to device.
MISO Master In Slave Out, SPI data from device to Arduino.
SCK Serial Clock, SPI clock (generated by master).
CS/SS Chip Select / Slave Select. Pulled LOW to activate an SPI device.
Pull-up resistor Keeps I2C bus lines at HIGH when no device is actively pulling them LOW
I2C address 7-bit identifier unique to each device on the bus
Master/Slave The master (Arduino) initiates communication; slaves respond when addressed
SoftwareSerial Arduino library that emulates UART on any digital pins
Full duplex Both directions simultaneously (SPI). Half duplex = one direction at a time (I2C)

Previous: ← Module 6: Sensors Deep Dive Next: Module 8: Displays & Visual Feedback →