Making devices talk: Architecture, Nodes, I2C, and Wireless Protocols.
00. GROUP ASSIGNMENT
What I learned from the group assignment: We probed the analog levels and digital signals of an output device using an oscilloscope and a multimeter. My main takeaway was visualizing the difference between a constant voltage reading on a multimeter versus seeing the actual PWM square waves and voltage spikes on the oscilloscope when a motor is running. This taught me why calculating power limits is so critical before connecting outputs.
This week's objective is to establish communication between at least two processors or devices. I tackled both wired and wireless protocols using the powerful ESP32-C6 microcontroller. The mission involves mapping the network architecture, wiring an I2C bus to extract data from a sensor and display it on an OLED, and setting up a wireless server.
01. NETWORK ARCHITECTURE & THE "SECOND NODE"
To understand how the communication works, we must define the Network Topology. A valid critique during my evaluation was: "It seems you only have one node PCB, and your computer... add at least another node."
To solve this, I expanded my network to include distinct microcontrollers and devices talking to each other.
Node 1 (The Master MCU): My custom ESP32-C6 board. It acts as the brain, polling data and hosting the wireless server.
Node 2 (Wired Slave MCU): The built-in controller inside the SH1106 OLED Display. It receives rendering commands via I2C.
Node 3 (Wired Sensor Node): The SHT31 IC. It calculates temperature and humidity and sends the raw bytes via I2C.
Node 4 (Wireless Client): A Smartphone connecting to the ESP32-C6's local IP address to read the live data stream.
Architecture Diagram: The flow of data across multiple nodes in the local and wireless network.
02. PROTOCOL ANALYSIS: WHY MQTT?
During the development of the wireless communication, I evaluated different protocols. While I ultimately used an HTTP Web Server for quick visualization in this specific documentation, MQTT (Message Queuing Telemetry Transport) is the superior protocol for my Final Project's sensor network. Here is the technical justification:
1. Packets & Overhead
HTTP is a "heavy" protocol. Every time my ESP32 requests or sends data via HTTP, it must send massive textual headers (Cookies, User-Agent, Accept, etc.), often exceeding 500 bytes just to send a 2-byte sensor reading. MQTT, on the other hand, has a fixed, ultra-lightweight header of only 2 Bytes.
2. Speed & Latency
Because MQTT packets are tiny, the transmission speed is significantly faster, and it requires far less bandwidth. For a project like a "Smart Goal" where I need real-time target detection, the latency of opening and closing TCP connections for every HTTP request is unacceptable. MQTT keeps a persistent connection open to a Broker.
3. Publish / Subscribe Architecture
HTTP uses a strict Request/Response model (the client must ask for data). MQTT uses a Pub/Sub model. My ESP32 simply "Publishes" data to a topic (e.g., goal/sensor1), and any node "Subscribed" to that topic receives the data instantly without having to constantly ask for it.
03. WIRING DIAGRAMS
Before jumping into the code, here is the exact wiring diagram mapping the physical connections between the nodes. Both the OLED and the SHT31 sensor share the exact same I2C bus (SDA and SCL pins) without interfering with each other because they have different hardware addresses.
Wiring Diagram: Routing the 3.3V, GND, SDA, and SCL lines across the nodes.
04. LOCAL NETWORK: I2C BUS (WIRED)
The I2C (Inter-Integrated Circuit) protocol is perfect for connecting multiple devices using just a few pins. The ESP32 acts as the "Master" and calls out to different "Slave" nodes using unique hexadecimal addresses.
I routed 4 wires in parallel (VCC, GND, SDA, SCL) to hook up the SH1106G OLED (Address: 0x3C) and the SHT31 Sensor (Address: 0x44). The ESP32 asks the SHT31 for data, translates the bytes, and routes them to the OLED.
I2C IN ACTION: ESP32 fetching data from Node 3 (SHT31) and routing it to Node 2 (OLED).
05. WIRELESS NETWORK: HTTP SERVER
Moving from wires to invisible waves, I used the ESP32-C6's built-in Wi-Fi to create a local HTTP server on port 80. This allowed me to add my Smartphone and Laptop as new wireless nodes.
The ESP32 connects to my router and gets a local IP. I copied that IP into my browser. Using a C++ rawliteral string, I injected custom HTML and CSS directly from the microcontroller, including a <meta http-equiv="refresh" content="2"> tag so the client node auto-refreshes every 2 seconds to fetch live data!
WIRELESS ACCESS: Entering the local IP to load the live-refreshing Web Server.
SERVER INTERFACE
The HTML interface rendered directly from the ESP32 memory.
06. THE CODE VAULT (DEEP DIVE EXPLANATION)
To ensure my work is fully replicable, here is a detailed breakdown of how the code operates.
PART 1: THE I2C MULTI-DEVICE LOGIC
How the I2C Code Works:
Line 7-10: Defines the I2C Addresses. 0x3C is standard for SH1106 displays. 0x44 is the factory address for the SHT31 sensor.
setup(): Calls Wire.begin() to initialize the hardware I2C pins. It then pings the OLED to verify it's connected.
Wire.beginTransmission(0x44): The ESP32 opens a connection to the sensor node.
Wire.write(0x24) & Wire.write(0x00): These are specific HEX commands from the SHT31 datasheet that tell the sensor to take a "High Repeatability Measurement" without clock stretching.
Wire.requestFrom(0x44, 6): The ESP32 demands 6 bytes back (2 for Temp, 1 CRC, 2 for Hum, 1 CRC).
Bit Shifting:(data[0] << 8) | data[1] merges two 8-bit bytes into a single 16-bit integer so we can do the math to convert it to Celsius based on the datasheet's formula.
WiFi.begin(ssid, password): Connects the ESP32 to the local Wi-Fi router.
server.on("/", handleRoot): Defines the Routing. When a node navigates to the root IP address, the ESP32 triggers the handleRoot function.
handleRoot(): First, it calls readSHT3X() to ping the I2C sensor for fresh data. Then, it creates a massive String containing HTML code.
R"rawliteral(...)rawliteral": A C++ feature that allows multi-line strings without needing to escape quotation marks, perfect for writing raw HTML/CSS inside an Arduino sketch.
server.send(200, "text/html", html): The ESP32 acts as a true server, responding to the client's HTTP request with a 200 OK status code and pushing the constructed HTML string to their browser.
#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <WebServer.h>
#define SHT31_ADDR 0x44
const char* ssid = "RoyZ";
const char* password = "Zarate28";
WebServer server(80); // Port 80 is standard for HTTP
float temperature = 0.0;
float humidity = 0.0;
// Function to read sensor via I2C
void readSHT3X() {
Wire.beginTransmission(SHT31_ADDR);
Wire.write(0x24);
Wire.write(0x00);
if (Wire.endTransmission() != 0) return;
delay(20);
Wire.requestFrom(SHT31_ADDR, 6);
if (Wire.available() == 6) {
uint8_t data[6];
for (int i = 0; i < 6; i++) { data[i] = Wire.read(); }
uint16_t rawTemp = (data[0] << 8) | data[1];
uint16_t rawHum = (data[3] << 8) | data[4];
temperature = -45.0 + (175.0 * rawTemp / 65535.0);
humidity = 100.0 * rawHum / 65535.0;
}
}
// Function to serve the Web Page
void handleRoot() {
readSHT3X(); // Get fresh data
// Construct HTML with CSS and Auto-Refresh tag
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="2">
<title>SHT3X Monitor</title>
<style>
body { font-family: Arial; text-align: center; background-color: #111; color: white; margin-top: 50px;}
.card { background: #222; display: inline-block; padding: 30px; border-radius: 20px; box-shadow: 0 0 20px rgba(255,255,255,0.1); }
h1 { font-size: 32px; }
p { font-size: 28px; margin: 20px 0; }
</style>
</head>
<body>
<div class="card">
<h1>SHT3X Sensor</h1>
<p>🌡 Temperature: )rawliteral";
html += String(temperature, 1);
html += R"rawliteral( °C</p>
<p>💧 Humidity: )rawliteral";
html += String(humidity, 1);
html += R"rawliteral( %</p>
</div>
</body>
</html>
)rawliteral";
server.send(200, "text/html", html); // Push to client
}
void setup() {
Serial.begin(115200);
delay(1000);
Wire.begin();
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP: ");
Serial.println(WiFi.localIP()); // Print IP so user can connect
// Start Server Routing
server.on("/", handleRoot);
server.begin();
Serial.println("HTTP Server started");
}
void loop() {
// Constantly listen for incoming clients (Laptops, Phones)
server.handleClient();
}
07. CLASSIFIED FILES & BOARDS
As required, here are the links to the PCB design files and schematics of the microcontroller board used as the primary Node in this network.