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
- Two wires: TX (transmit) and RX (receive)
- Asynchronous: No shared clock. Both sides must agree on the speed (baud rate) in advance.
- Point-to-point: One sender, one receiver per connection
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
- GPS modules (NMEA sentences over UART)
- Bluetooth modules (HC-05, HC-06)
- Communication between two Arduinos
- ESP8266/ESP32 in AT command mode
- Any module whose datasheet says "serial interface" or "TTL serial"
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
- Two wires: SDA (data) and SCL (clock)
- Synchronous: The master generates a clock signal on SCL
- Bus architecture: Up to 127 devices on the same two wires
- Each device has a unique address (usually 7-bit, set by the manufacturer)
- Master-slave: The Arduino (master) initiates all communication. Devices (slaves) respond when addressed.
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
- Sensors (temperature, pressure, accelerometer, gyroscope)
- Displays (OLED, some LCD modules)
- Real-time clocks (DS3231, DS1307)
- EEPROMs
- I/O expanders
- Any time you need multiple devices with minimal wiring
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
- Four wires:
- MOSI (Master Out Slave In): data from Arduino to device
- MISO (Master In Slave Out): data from device to Arduino
- SCK (Serial Clock): clock from Arduino
- CS/SS (Chip Select): one per device, pulled LOW to select it
- Synchronous: Clock provided by the master
- Full duplex: Data flows both directions simultaneously
- No addressing: Devices are selected by pulling their CS pin LOW
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
- SD card readers
- TFT / color LCD displays
- High-speed ADCs and DACs
- Flash memory modules
- Radio modules (nRF24L01, LoRa)
- Any device requiring high data throughput
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
- Baud rate mismatch: Serial monitor shows garbage. Check both sides use the same baud rate.
- TX↔RX not crossed: Nothing received. TX must connect to RX and vice versa.
- Missing ground: Random data errors. Ground must be shared.
I2C Mistakes
- Missing pull-ups: Scanner finds nothing. Add 4.7kΩ pull-ups to SDA and SCL.
- Too many pull-ups: Communication errors. Multiple breakout boards each adding their own pull-ups can bring total resistance too low. Desolder extras.
- Wrong address: Library initializes but reads zeros. Use the I2C scanner to find the actual address.
- Voltage mismatch: 5V Arduino + 3.3V sensor without level shifting can damage the sensor.
- Long wires: I2C is designed for short distances. Over 50cm, consider lower pull-up values or switching to UART/SPI.
SPI Mistakes
- CS pin not set to OUTPUT: Device never selected.
- Wrong SPI mode: Check the device's clock polarity and phase in its datasheet.
- 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.
- 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
- Adafruit BMP280 (+ Adafruit Unified Sensor dependency)
- Adafruit SSD1306 (+ Adafruit GFX dependency)
- 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:
- Explain the difference between UART, I2C, and SPI
- Wire a UART connection between two devices (TX→RX crossover)
- Wire multiple I2C devices on a shared bus
- Use the I2C scanner to find device addresses
- Explain why I2C pull-up resistors are needed
- Explain SPI's CS pin approach for selecting devices
- Choose the right protocol for a given scenario
- Diagnose common I2C, SPI, and UART problems
- Build the multi-sensor I2C hub with display and RTC
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 →