/*
* I2C Communication Demo - ESP32 + SSD1306 OLED
* ================================================
* Demonstrates I2C protocol by communicating with an SSD1306 OLED display.
*
* What students will see:
* - ESP32 (Master) sends commands/data to OLED (Slave) over I2C
* - OLED displays text, proving I2C communication works
* - Serial Monitor shows I2C bus scanning and protocol details
* - Logic Analyzer can capture SDA/SCL waveforms
*
* Wiring:
* ESP32 GPIO21 (SDA) <--> OLED SDA [Data line - bidirectional]
* ESP32 GPIO22 (SCL) <--> OLED SCL [Clock line - Master controlled]
* ESP32 3V3 <--> OLED VCC
* ESP32 GND <--> OLED GND
*
* Protocol: I2C (Inter-Integrated Circuit)
* - Synchronous: Master generates clock on SCL
* - Half-duplex: Data on SDA, one direction at a time
* - Addressed: Each slave has a 7-bit address (OLED = 0x3C)
* - ACK/NACK: Slave acknowledges each byte received
* - Multi-slave: Multiple devices can share same SDA/SCL bus
*
* Libraries needed (add to libraries.txt):
* Wire (built-in)
* Adafruit GFX Library
* Adafruit SSD1306
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR_1 0x3C // 7-bit I2C address of SSD1306
#define OLED_ADDR_2 0x3D
// Create display object connected to I2C (Wire)
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
int counter = 0;
// Scan the I2C bus and report all devices found
void scanI2CBus() {
Serial.println("[I2C SCAN] Scanning bus for devices...");
Serial.println(" Address range: 0x01 to 0x7F (7-bit)");
Serial.println();
int devicesFound = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr); // START + address + Write bit
byte error = Wire.endTransmission(); // Check for ACK
if (error == 0) {
Serial.print(" [FOUND] Device at address: 0x");
if (addr < 16) Serial.print("0");
Serial.print(addr, HEX);
// Identify known devices
if (addr == 0x3C || addr == 0x3D) {
Serial.print(" --> SSD1306 OLED Display");
}
Serial.println();
devicesFound++;
}
}
Serial.print(" Total devices found: ");
Serial.println(devicesFound);
Serial.println();
}
void setup() {
Serial.begin(115200);
Serial.println("==========================================");
Serial.println(" I2C Communication Demo (ESP32 + OLED)");
Serial.println("==========================================");
Serial.println();
Serial.println("I2C Bus Configuration:");
Serial.println(" SDA: GPIO21 (data line)");
Serial.println(" SCL: GPIO22 (clock line)");
Serial.println(" Mode: Standard (100 kHz)");
Serial.println(" Role: ESP32 = Master, OLED = Slave");
Serial.println("----------------------------------------");
// Initialize I2C bus
Wire.begin(21, 22);
// Step 1: Scan for I2C devices
scanI2CBus();
// Step 2: Initialize the OLED display
Serial.println("[INIT] Starting SSD1306 at address 0x3C...");
if (!display1.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR_1)) {
Serial.println("[ERROR] SSD1306 not found! Check wiring.");
while (1);
}
if (!display2.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR_2)) {
Serial.println("[ERROR] SSD1306 not found! Check wiring.");
while (1);
}
Serial.println("[INIT] OLED initialized successfully!");
Serial.println();
// Step 3: Show welcome message on OLED
display1.clearDisplay();
display1.setTextSize(2);
display1.setTextColor(SSD1306_WHITE);
display1.setCursor(10, 5);
display1.println("I2C Demo");
display1.setTextSize(1);
display1.setCursor(10, 30);
display1.println("ESP32 -> SSD1306_1");
display1.setCursor(10, 45);
display1.println("Addr: 0x3C");
display1.display(); // This sends the buffer over I2C!
display2.clearDisplay();
display2.setTextSize(2);
display2.setTextColor(SSD1306_WHITE);
display2.setCursor(10, 5);
display2.println("I2C Demo");
display2.setTextSize(1);
display2.setCursor(10, 30);
display2.println("ESP32 -> SSD1306_2");
display2.setCursor(10, 45);
display2.println("Addr: 0x3D");
display2.display(); // This sends the buffer over I2C!
Serial.println("[I2C TX] Sent display buffer to OLED");
Serial.println(" Transaction breakdown:");
Serial.println(" 1. START condition (SDA goes LOW while SCL HIGH)");
Serial.println(" 2. Address byte: 0x3C + Write bit = 0x78");
Serial.println(" 3. Slave sends ACK");
Serial.println(" 4. Data bytes (display buffer: 1024 bytes)");
Serial.println(" 5. STOP condition");
Serial.println("----------------------------------------");
delay(3000);
}
void loop() {
counter++;
// Update the OLED display with new data
display1.clearDisplay();
display1.setTextSize(2);
display1.setTextColor(SSD1306_WHITE);
display1.setCursor(10, 5);
display1.println("Counter:");
display1.setTextSize(3);
display1.setCursor(30, 30);
display1.println(counter);
display1.display(); // Send updated buffer via I2C
display2.clearDisplay();
display2.setTextSize(2);
display2.setTextColor(SSD1306_WHITE);
display2.setCursor(10, 5);
display2.println("Counter:");
display2.setTextSize(3);
display2.setCursor(30, 30);
display2.println(counter);
display2.display(); // Send updated buffer via I2C
Serial.print("[I2C TX] Updated display, counter = ");
Serial.println(counter);
delay(10);
}