Module 13: Introduction to WiFi (ESP8266)
Level: 🟡 Intermediate
Board: ESP8266 (NodeMCU / Wemos D1 Mini)
Prerequisites: Modules 5, 7
Estimated time: 80–100 minutes
Goal: Connect your projects to WiFi and understand the ESP8266 platform.
What You'll Learn
This is where your projects leave the bench and join the network. The ESP8266 is a WiFi-enabled microcontroller that you program with the same Arduino IDE you've been using: same language, same libraries, same workflow. But it adds WiFi, which means your projects can serve web pages, make HTTP requests, and talk to the internet.
By the end of this module, you'll set up the ESP8266 in the Arduino IDE, understand its key differences from the Uno, connect to WiFi, serve a web page, and build a WiFi-controlled LED strip.
13.1 ESP8266 vs. Arduino Uno: What's Different
Hardware Comparison
| Spec | Arduino Uno | ESP8266 (NodeMCU) |
|---|---|---|
| Processor | ATmega328P (8-bit, 16 MHz) | Tensilica L106 (32-bit, 80/160 MHz) |
| RAM | 2 KB | 80 KB (usable ~50 KB) |
| Flash | 32 KB | 4 MB (typical) |
| WiFi | None | 802.11 b/g/n (2.4 GHz) |
| Digital GPIO | 14 | 11 (usable, some reserved) |
| Analog input | 6 pins (10-bit) | 1 pin (10-bit, 0–1V range!) |
| Operating voltage | 5V | 3.3V |
| USB | USB-B | Micro-USB |
| Price (clone) | ~$3–5 | ~$2–4 |
What's the Same
- Programmed with the Arduino IDE
- Same C/C++ language
- Same
setup()/loop()structure - Same
digitalRead(),digitalWrite(),analogRead(),Serial.begin() - Most Arduino libraries work (with some exceptions)
- Same upload workflow (click Upload, code runs)
What's Different: The Critical Points
1. Operating voltage: 3.3V, not 5V
This is the most important difference. The ESP8266's GPIO pins are 3.3V. Applying 5V to any GPIO pin will damage the chip. This means:
- You cannot directly connect 5V sensors/modules to ESP GPIO pins
- 5V output devices (like some relays) need level shifting
- The onboard 3.3V regulator powers the chip from the USB 5V input
- Most I2C sensors (BME280, SSD1306) work fine at 3.3V (check the datasheet)
2. Limited GPIO pins
The NodeMCU exposes ~11 usable GPIO pins, but several have boot-mode functions:
| GPIO | NodeMCU Label | Notes |
|---|---|---|
| GPIO0 | D3 | Must be HIGH during boot. Used for flash mode. |
| GPIO1 | TX | Serial TX. Avoid for general use if using Serial |
| GPIO2 | D4 | Must be HIGH during boot. Has onboard LED (inverted). |
| GPIO3 | RX | Serial RX. Avoid for general use if using Serial |
| GPIO4 | D2 | ✅ Safe for any use. Common I2C SDA. |
| GPIO5 | D1 | ✅ Safe for any use. Common I2C SCL. |
| GPIO12 | D6 | ✅ Safe for any use. |
| GPIO13 | D7 | ✅ Safe for any use. |
| GPIO14 | D5 | ✅ Safe for any use. |
| GPIO15 | D8 | Must be LOW during boot. Needs pull-down. |
| GPIO16 | D0 | Can wake from deep sleep. No PWM or I2C support. |
| ADC0 | A0 | Analog input. Range: 0–1V (NodeMCU has a voltage divider for 0–3.3V). |
Pin naming confusion: The ESP8266 has GPIO numbers (GPIO4, GPIO5) and NodeMCU D-labels (D1, D2). They do NOT match: GPIO5 is D1, not D5. Always use the D-labels in code or the GPIO numbers, never assume they correspond.
3. Single analog input
The ESP8266 has only one analog pin (A0) with a 0–1V range. The NodeMCU board has a built-in voltage divider that extends this to 0–3.3V. If you need multiple analog inputs, use an external ADC (like the ADS1115 over I2C).
4. WiFi draws serious current
| Mode | Current Draw |
|---|---|
| WiFi off (modem sleep) | ~15 mA |
| WiFi connected, idle | ~70 mA |
| WiFi transmitting | 170–300 mA (peaks to 430 mA) |
| Deep sleep | ~20 µA |
Plan your power accordingly. USB provides 500 mA, enough for the ESP plus light peripherals. For battery projects, deep sleep is essential.
13.2 Setting Up ESP8266 in the Arduino IDE
Step 1: Add the ESP8266 Board Package
- Open Arduino IDE
- Go to File → Preferences
- In "Additional Board Manager URLs," add:
(If there's already a URL there, add a comma and paste the new one after)http://arduino.esp8266.com/stable/package_esp8266com_index.json - Click OK
Step 2: Install the Board Package
- Go to Tools → Board → Boards Manager
- Search for "esp8266"
- Install "esp8266 by ESP8266 Community" (latest version)
- Wait for the download to complete (~150 MB)
Step 3: Select Your Board
- Go to Tools → Board → ESP8266 Boards
- Select:
- NodeMCU 1.0 (ESP-12E Module) for NodeMCU boards
- LOLIN(WEMOS) D1 Mini for Wemos D1 Mini boards
- Set upload speed: 115200 (or 921600 if your board supports it)
- Select the correct port (same as with Arduino)
Step 4: Test with Blink
The NodeMCU has a built-in LED on GPIO2 (D4), but it's inverted: LOW turns it on, HIGH turns it off.
/*
* ESP8266 Blink Test
* The built-in LED on NodeMCU is on GPIO2 (D4), active LOW.
*/
const int LED_PIN = LED_BUILTIN; // GPIO2 on most ESP8266 boards
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200); // ESP8266 defaults to 115200 baud
Serial.println("ESP8266 Blink Test");
}
void loop() {
digitalWrite(LED_PIN, LOW); // LED ON (active LOW)
delay(500);
digitalWrite(LED_PIN, HIGH); // LED OFF
delay(500);
}
If the LED blinks, your setup is working.
Serial baud rate: The ESP8266 defaults to 115200 baud for its boot messages. Using 9600 is fine for your code, but you'll see garbage characters at startup (the bootloader output at 74880 baud). Use 115200 for cleaner Serial Monitor output.
13.3 GPIO Differences and 3.3V Logic
The 5V Problem, Revisited
From Module 5: 5V logic means HIGH = 5V. The ESP8266 uses 3.3V logic. Connecting a 5V output to an ESP8266 input exceeds the chip's absolute maximum rating and can cause immediate or gradual damage.
What Works Directly at 3.3V
Many modules work fine at 3.3V without modification:
- SSD1306 OLED displays (I2C)
- BME280/BMP280 pressure sensors (I2C)
- DHT22 temperature sensor (works at 3.3V–5.5V)
- Passive buzzer (reduced volume at 3.3V)
- Standard LEDs (with appropriate resistor)
- Most I2C devices
What Needs Attention
- HC-SR04 ultrasonic: Operates at 5V, Echo pin outputs 5V → needs level shifter or use 3.3V-compatible version (HC-SR04P)
- 5V relay modules: Signal pin often needs 5V for reliable triggering → use 3.3V relay modules or add a transistor
- WS2812B (NeoPixel) LED strips: Data line spec says 0.7×VCC for HIGH, which at 5V is 3.5V. At 3.3V the ESP barely meets this. Works for short strips, but a level shifter (74HCT125) is recommended for reliability.
- 5V serial devices: TX line from device may be 5V → voltage divider needed
Resistor Calculation for 3.3V LED
V_supply = 3.3V
V_LED (red) = 2.0V
I_LED = 10 mA (dimmer is fine at 3.3V)
R = (3.3 - 2.0) / 0.010 = 130Ω → use 150Ω
At 3.3V, there's less voltage headroom. LEDs will be dimmer. For full brightness, power the LED from a 5V source and switch it with a transistor driven by a 3.3V GPIO pin.
13.4 Connecting to WiFi
Basic WiFi Connection
/*
* Module 13: Basic WiFi Connection
* Connects to a WiFi network and prints the IP address.
*/
#include <ESP8266WiFi.h>
const char* WIFI_SSID = "YOUR_NETWORK_NAME";
const char* WIFI_PASS = "YOUR_PASSWORD";
void setup() {
Serial.begin(115200);
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Signal strength (RSSI): ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
}
void loop() {
// Your connected code here
}
Handling Connection Failures Gracefully
The basic example hangs forever if it can't connect. A production-quality connection handler:
#include <ESP8266WiFi.h>
const char* WIFI_SSID = "YOUR_NETWORK_NAME";
const char* WIFI_PASS = "YOUR_PASSWORD";
const int MAX_CONNECT_ATTEMPTS = 20;
const unsigned long RECONNECT_INTERVAL = 30000; // Retry every 30 seconds
bool wifiConnected = false;
unsigned long lastReconnectAttempt = 0;
bool connectWiFi() {
Serial.print("Connecting to WiFi");
WiFi.mode(WIFI_STA); // Station mode (client, not access point)
WiFi.begin(WIFI_SSID, WIFI_PASS);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < MAX_CONNECT_ATTEMPTS) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(" Connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
return true;
} else {
Serial.println(" FAILED");
WiFi.disconnect();
return false;
}
}
void setup() {
Serial.begin(115200);
wifiConnected = connectWiFi();
}
void loop() {
// Check connection and reconnect if needed
if (WiFi.status() != WL_CONNECTED) {
wifiConnected = false;
unsigned long now = millis();
if (now - lastReconnectAttempt >= RECONNECT_INTERVAL) {
lastReconnectAttempt = now;
Serial.println("WiFi lost — attempting reconnect...");
wifiConnected = connectWiFi();
}
}
// Your main code — handle both connected and disconnected states
if (wifiConnected) {
// Do WiFi-dependent tasks
} else {
// Do offline tasks (keep sensors running, display local data, etc.)
}
}
WiFi Status Codes
| Status | Meaning |
|---|---|
WL_CONNECTED |
Successfully connected to network |
WL_DISCONNECTED |
Not connected |
WL_CONNECT_FAILED |
Password wrong or network rejected connection |
WL_NO_SSID_AVAIL |
Network not found (wrong name or out of range) |
WL_IDLE_STATUS |
WiFi is in process of changing state |
13.5 Making HTTP Requests (GET / POST)
HTTP GET: Fetching Data
/*
* Module 13: HTTP GET Request
* Fetches data from an API endpoint.
*/
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
const char* WIFI_SSID = "YOUR_NETWORK";
const char* WIFI_PASS = "YOUR_PASSWORD";
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected");
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
WiFiClient client;
HTTPClient http;
// Example: fetch a public API
http.begin(client, "http://worldtimeapi.org/api/timezone/Etc/UTC");
int httpCode = http.GET();
if (httpCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println("Response:");
Serial.println(payload);
}
} else {
Serial.print("HTTP Error: ");
Serial.println(http.errorToString(httpCode));
}
http.end();
}
delay(30000); // Request every 30 seconds — be respectful to APIs
}
HTTP POST: Sending Data
void sendSensorData(float temperature, float humidity) {
if (WiFi.status() != WL_CONNECTED) return;
WiFiClient client;
HTTPClient http;
http.begin(client, "http://your-server.com/api/data");
http.addHeader("Content-Type", "application/json");
// Build JSON payload
String json = "{\"temperature\":" + String(temperature, 1) +
",\"humidity\":" + String(humidity, 1) + "}";
int httpCode = http.POST(json);
if (httpCode > 0) {
Serial.print("POST Response: ");
Serial.println(httpCode);
} else {
Serial.print("POST Error: ");
Serial.println(http.errorToString(httpCode));
}
http.end();
}
API etiquette: Don't hammer public APIs with requests every second. Most free APIs have rate limits (e.g., 60 requests per minute). A 30-second or 60-second interval is usually appropriate for sensor data.
13.6 Hosting a Web Server on the ESP8266
This is where things get exciting. The ESP8266 can serve a complete web page from its own WiFi connection. Any device on the same network can open a browser, type in the ESP's IP address, and interact with your project.
Basic Web Server
/*
* Module 13: Basic Web Server
* Serves a page showing sensor data and a toggle button.
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
const char* WIFI_SSID = "YOUR_NETWORK";
const char* WIFI_PASS = "YOUR_PASSWORD";
ESP8266WebServer server(80); // HTTP port 80
const int LED_PIN = D5; // GPIO14
bool ledState = false;
void handleRoot() {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>";
html += "body{font-family:sans-serif;text-align:center;padding:20px;background:#1a1a2e;color:#eee}";
html += "h1{color:#e94560}";
html += ".btn{display:inline-block;padding:15px 40px;margin:10px;font-size:18px;";
html += "border:none;border-radius:8px;cursor:pointer;color:#fff}";
html += ".on{background:#27ae60}.off{background:#e74c3c}";
html += ".status{font-size:24px;margin:20px;padding:10px;border-radius:8px;";
html += "background:#16213e}";
html += "</style></head><body>";
html += "<h1>ESP8266 Control Panel</h1>";
html += "<div class='status'>LED is: <strong>";
html += ledState ? "ON" : "OFF";
html += "</strong></div>";
html += "<a href='/led/on'><button class='btn on'>Turn ON</button></a>";
html += "<a href='/led/off'><button class='btn off'>Turn OFF</button></a>";
html += "<br><br>";
html += "<p>Uptime: " + String(millis() / 1000) + " seconds</p>";
html += "<p>WiFi Signal: " + String(WiFi.RSSI()) + " dBm</p>";
html += "<p><small>Free heap: " + String(ESP.getFreeHeap()) + " bytes</small></p>";
html += "</body></html>";
server.send(200, "text/html", html);
}
void handleLedOn() {
ledState = true;
digitalWrite(LED_PIN, HIGH);
server.sendHeader("Location", "/");
server.send(303); // Redirect back to root page
}
void handleLedOff() {
ledState = false;
digitalWrite(LED_PIN, LOW);
server.sendHeader("Location", "/");
server.send(303);
}
void handleNotFound() {
server.send(404, "text/plain", "404: Not Found");
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Connect to WiFi
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Server running at: http://");
Serial.println(WiFi.localIP());
// Define routes
server.on("/", handleRoot);
server.on("/led/on", handleLedOn);
server.on("/led/off", handleLedOff);
server.onNotFound(handleNotFound);
// Start server
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient(); // Must be called in every loop iteration
}
How the Web Server Works
- The ESP connects to your WiFi network and gets an IP address (e.g., 192.168.1.42)
server.begin()starts listening for HTTP connections on port 80- When a browser requests
http://192.168.1.42/, thehandleRoot()function runs and sends back HTML - When the browser requests
/led/on,handleLedOn()runs, toggles the LED, and redirects back to/ server.handleClient()inloop()processes incoming requests; if you forget this, the server won't respond
Adding Auto-Refresh for Live Data
Add this to the HTML <head> section:
<meta http-equiv='refresh' content='5'>
This tells the browser to reload the page every 5 seconds, a simple way to show updating sensor data without JavaScript.
For smoother updates, you can use JavaScript with fetch() to update just the data without reloading the entire page (AJAX approach). That's covered in the Advanced modules.
13.7 Power Considerations for WiFi Projects
Current Consumption
WiFi transmission draws 170–300 mA in bursts. This has implications:
USB power: 500 mA from USB is enough for the ESP8266 plus a few sensors and LEDs. Don't add servos or motors to the same USB supply.
Battery power: WiFi-active ESP8266 with sensors draws ~100 mA average. On a 2000 mAh 18650 battery (with boost converter at 85% efficiency): 2000 × 0.85 / 100 ≈ 17 hours. Not great for always-on monitoring.
Deep sleep: For battery projects, use deep sleep between readings. The ESP wakes, reads sensors, connects to WiFi, sends data, and sleeps again. At ~20 µA during sleep, a 2000 mAh battery could last months if waking only every 5 minutes.
// Deep sleep example — wake every 5 minutes
void setup() {
// Read sensors
// Connect WiFi
// Send data
// Go to sleep for 5 minutes (300 seconds = 300,000,000 microseconds)
ESP.deepSleep(300e6); // Wakes by resetting, so setup() runs again
}
void loop() {
// Never reached — deepSleep resets the chip
}
Deep sleep wiring: GPIO16 (D0) must be connected to RST for deep sleep wake to work. This is the wake signal that resets the chip.
Decoupling for WiFi Stability
WiFi transmit bursts cause brief voltage drops. Add a 100µF electrolytic capacitor across the ESP's 3.3V and GND pins (close to the board) to absorb these dips. Without it, you may see random disconnections or resets during transmission.
Module Project: WiFi-Controlled LED Strip
Objective
Build a web-controlled WS2812B (NeoPixel) LED strip powered by an ESP8266. A web interface lets you choose colors, patterns (solid, rainbow, chase, breathe), and brightness from any device on the network.
Components Needed
| Component | Quantity | Notes |
|---|---|---|
| NodeMCU (ESP8266) | 1 | |
| Micro-USB cable | 1 | |
| WS2812B LED strip | 1 | 8–30 LEDs. Cut to desired length. |
| 74HCT125 level shifter | 1 | Recommended for reliable 3.3V→5V data signal |
| 5V/2A power supply | 1 | For LED strip (each LED draws up to 60 mA at full white) |
| 470Ω Resistor | 1 | Series on data line (reduces ringing) |
| 1000µF Electrolytic Capacitor | 1 | Across LED strip 5V/GND |
| 100µF Electrolytic Capacitor | 1 | Near ESP8266 3.3V/GND |
| Breadboard | 1 | |
| Jumper wires | 6+ |
Power Budget
Each WS2812B LED draws up to 60 mA at full white. For a 30-LED strip:
Maximum: 30 × 60 mA = 1800 mA = 1.8 A
Typical (colored, not full white): ~500–800 mA
A 5V/2A supply provides adequate headroom. Never power the LED strip from the ESP's 5V pin; route power directly from the supply to the strip.
Wiring
ESP8266 D5 (GPIO14) → 470Ω resistor → 74HCT125 input → 74HCT125 output → LED strip DIN
External 5V supply + → LED strip 5V → 74HCT125 VCC
External 5V supply − → LED strip GND → ESP GND → 74HCT125 GND
1000µF cap across LED strip 5V/GND
100µF cap across ESP 3.3V/GND
If skipping the level shifter (short strips, < 10 LEDs, may work without):
ESP8266 D5 → 470Ω → LED strip DIN
Library: FastLED
Install FastLED from the Library Manager. It's faster and more feature-rich than the Adafruit NeoPixel library.
The Code
/*
* Module 13 Project: WiFi-Controlled LED Strip
*
* Web interface for controlling WS2812B LED strip:
* - Color picker (solid color)
* - Pattern selection (solid, rainbow, chase, breathe)
* - Brightness slider
*
* Circuit:
* - LED strip data: D5 (GPIO14) → 470Ω → [level shifter] → strip DIN
* - LED strip power: external 5V supply (NOT from ESP)
* - 1000µF cap across strip power
* - 100µF cap near ESP 3.3V/GND
* - All GNDs connected
*
* Libraries: ESP8266WiFi, ESP8266WebServer, FastLED
* Board: NodeMCU 1.0
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FastLED.h>
// --- WiFi credentials ---
const char* WIFI_SSID = "YOUR_NETWORK";
const char* WIFI_PASS = "YOUR_PASSWORD";
// --- LED strip ---
#define LED_PIN 14 // GPIO14 = D5
#define NUM_LEDS 30
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
// --- Web server ---
ESP8266WebServer server(80);
// --- State ---
uint8_t brightness = 128;
uint8_t red = 255, green = 0, blue = 0;
int currentPattern = 0; // 0=solid, 1=rainbow, 2=chase, 3=breathe
const char* patternNames[] = {"Solid", "Rainbow", "Chase", "Breathe"};
const int NUM_PATTERNS = 4;
// --- Animation timing ---
unsigned long lastAnimUpdate = 0;
const unsigned long ANIM_INTERVAL = 30;
uint8_t animStep = 0;
// === WEB PAGE ===
String buildPage() {
String html = R"rawliteral(
<!DOCTYPE html>
<html><head>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>LED Controller</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,sans-serif;background:#0d1117;color:#e6edf3;padding:16px;max-width:480px;margin:0 auto}
h1{text-align:center;margin:16px 0;font-size:22px;color:#58a6ff}
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:16px;margin:12px 0}
.card h2{font-size:14px;color:#8b949e;margin-bottom:10px;text-transform:uppercase;letter-spacing:1px}
.color-row{display:flex;gap:8px;align-items:center;margin:6px 0}
.color-row label{width:20px;font-weight:bold}
.color-row input[type=range]{flex:1;height:8px;-webkit-appearance:none;background:#30363d;border-radius:4px;outline:none}
.color-row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;cursor:pointer}
.r-slider::-webkit-slider-thumb{background:#f85149}
.g-slider::-webkit-slider-thumb{background:#3fb950}
.b-slider::-webkit-slider-thumb{background:#58a6ff}
.br-slider::-webkit-slider-thumb{background:#e6edf3}
.preview{width:100%;height:40px;border-radius:8px;margin:10px 0;border:1px solid #30363d}
.patterns{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.pat-btn{padding:12px;border:2px solid #30363d;border-radius:8px;background:#0d1117;color:#e6edf3;
font-size:14px;cursor:pointer;text-align:center;transition:all 0.2s}
.pat-btn:hover{border-color:#58a6ff}
.pat-btn.active{border-color:#58a6ff;background:#161b22;color:#58a6ff;font-weight:bold}
.val{color:#58a6ff;font-family:monospace;min-width:32px;text-align:right}
</style>
</head><body>
<h1>💡 LED Controller</h1>
<div class='card'>
<h2>Color</h2>
<div class='color-row'><label style='color:#f85149'>R</label>
<input type='range' min='0' max='255' value='__R__' class='r-slider' id='r'
oninput="document.getElementById('rv').textContent=this.value;updatePreview();"><span class='val' id='rv'>__R__</span></div>
<div class='color-row'><label style='color:#3fb950'>G</label>
<input type='range' min='0' max='255' value='__G__' class='g-slider' id='g'
oninput="document.getElementById('gv').textContent=this.value;updatePreview();"><span class='val' id='gv'>__G__</span></div>
<div class='color-row'><label style='color:#58a6ff'>B</label>
<input type='range' min='0' max='255' value='__B__' class='b-slider' id='b'
oninput="document.getElementById('bv').textContent=this.value;updatePreview();"><span class='val' id='bv'>__B__</span></div>
<div class='preview' id='preview'></div>
<button class='pat-btn' style='width:100%' onclick='applyColor()'>Apply Color</button>
</div>
<div class='card'>
<h2>Brightness</h2>
<div class='color-row'><label>☀</label>
<input type='range' min='0' max='255' value='__BR__' class='br-slider' id='br'
oninput="document.getElementById('brv').textContent=this.value;fetch('/brightness?v='+this.value)">
<span class='val' id='brv'>__BR__</span></div>
</div>
<div class='card'>
<h2>Pattern</h2>
<div class='patterns'>
<div class='pat-btn __P0__' onclick="location='/pattern?p=0'">Solid</div>
<div class='pat-btn __P1__' onclick="location='/pattern?p=1'">Rainbow</div>
<div class='pat-btn __P2__' onclick="location='/pattern?p=2'">Chase</div>
<div class='pat-btn __P3__' onclick="location='/pattern?p=3'">Breathe</div>
</div>
</div>
<div class='card'>
<h2>Status</h2>
<p>IP: __IP__ Signal: __RSSI__ dBm Heap: __HEAP__</p>
</div>
<script>
function updatePreview(){
var r=document.getElementById('r').value,g=document.getElementById('g').value,b=document.getElementById('b').value;
document.getElementById('preview').style.background='rgb('+r+','+g+','+b+')';}
function applyColor(){
var r=document.getElementById('r').value,g=document.getElementById('g').value,b=document.getElementById('b').value;
location='/color?r='+r+'&g='+g+'&b='+b;}
updatePreview();
</script>
</body></html>
)rawliteral";
// Replace placeholders
html.replace("__R__", String(red));
html.replace("__G__", String(green));
html.replace("__B__", String(blue));
html.replace("__BR__", String(brightness));
html.replace("__IP__", WiFi.localIP().toString());
html.replace("__RSSI__", String(WiFi.RSSI()));
html.replace("__HEAP__", String(ESP.getFreeHeap()));
for (int i = 0; i < NUM_PATTERNS; i++) {
String placeholder = "__P" + String(i) + "__";
html.replace(placeholder, (i == currentPattern) ? "active" : "");
}
return html;
}
// === ROUTE HANDLERS ===
void handleRoot() {
server.send(200, "text/html", buildPage());
}
void handleColor() {
if (server.hasArg("r")) red = server.arg("r").toInt();
if (server.hasArg("g")) green = server.arg("g").toInt();
if (server.hasArg("b")) blue = server.arg("b").toInt();
currentPattern = 0; // Switch to solid when color changes
server.sendHeader("Location", "/");
server.send(303);
}
void handleBrightness() {
if (server.hasArg("v")) {
brightness = server.arg("v").toInt();
FastLED.setBrightness(brightness);
}
server.send(200, "text/plain", "OK");
}
void handlePattern() {
if (server.hasArg("p")) {
currentPattern = constrain(server.arg("p").toInt(), 0, NUM_PATTERNS - 1);
}
server.sendHeader("Location", "/");
server.send(303);
}
// === LED PATTERNS ===
void patternSolid() {
fill_solid(leds, NUM_LEDS, CRGB(red, green, blue));
}
void patternRainbow() {
fill_rainbow(leds, NUM_LEDS, animStep, 7);
}
void patternChase() {
fadeToBlackBy(leds, NUM_LEDS, 80);
int pos = animStep % NUM_LEDS;
leds[pos] = CRGB(red, green, blue);
}
void patternBreathe() {
uint8_t breathVal = beatsin8(12, 20, 255); // 12 BPM, range 20–255
fill_solid(leds, NUM_LEDS, CRGB(red, green, blue));
FastLED.setBrightness(scale8(brightness, breathVal));
}
void updateLEDs() {
switch (currentPattern) {
case 0: patternSolid(); break;
case 1: patternRainbow(); break;
case 2: patternChase(); break;
case 3: patternBreathe(); break;
}
if (currentPattern != 3) {
FastLED.setBrightness(brightness);
}
FastLED.show();
animStep++;
}
// === SETUP ===
void setup() {
Serial.begin(115200);
Serial.println("\nWiFi LED Controller — Module 13");
// Initialize LEDs
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(brightness);
fill_solid(leds, NUM_LEDS, CRGB::Black);
FastLED.show();
// Connect to WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting");
// Animate LEDs while connecting
int connectLED = 0;
while (WiFi.status() != WL_CONNECTED) {
leds[connectLED % NUM_LEDS] = CRGB(0, 0, 50);
FastLED.show();
delay(200);
leds[connectLED % NUM_LEDS] = CRGB::Black;
connectLED++;
Serial.print(".");
}
// Connection success animation
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB(0, 50, 0);
FastLED.show();
delay(30);
}
delay(500);
fill_solid(leds, NUM_LEDS, CRGB::Black);
FastLED.show();
Serial.println();
Serial.print("Open in browser: http://");
Serial.println(WiFi.localIP());
// Set up routes
server.on("/", handleRoot);
server.on("/color", handleColor);
server.on("/brightness", handleBrightness);
server.on("/pattern", handlePattern);
server.begin();
}
// === LOOP ===
void loop() {
server.handleClient();
unsigned long now = millis();
if (now - lastAnimUpdate >= ANIM_INTERVAL) {
lastAnimUpdate = now;
updateLEDs();
}
}
Circuit Diagram (SVG)
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 450" font-family="monospace" font-size="11">
<!-- ESP8266 NodeMCU -->
<rect x="30" y="100" width="130" height="280" fill="#1a5a6a" stroke="#333" stroke-width="2" rx="8"/>
<text x="95" y="128" text-anchor="middle" fill="white" font-size="13" font-weight="bold">NodeMCU</text>
<text x="95" y="146" text-anchor="middle" fill="white" font-size="11">ESP8266</text>
<text x="95" y="164" text-anchor="middle" fill="#aaddee" font-size="9">3.3V Logic!</text>
<!-- Pins -->
<rect x="160" y="190" width="22" height="14" fill="#2a7a8a" stroke="white" stroke-width="1" rx="2"/>
<text x="171" y="200" text-anchor="middle" fill="white" font-size="7">3V3</text>
<rect x="160" y="210" width="22" height="14" fill="#2a7a8a" stroke="white" stroke-width="1" rx="2"/>
<text x="171" y="220" text-anchor="middle" fill="white" font-size="7">GND</text>
<rect x="160" y="250" width="22" height="14" fill="#2a7a8a" stroke="white" stroke-width="1" rx="2"/>
<text x="171" y="260" text-anchor="middle" fill="white" font-size="7">D5</text>
<!-- External 5V Supply -->
<rect x="600" y="90" width="130" height="55" fill="#cc4444" stroke="#333" stroke-width="2" rx="6"/>
<text x="665" y="113" text-anchor="middle" fill="white" font-size="11" font-weight="bold">External 5V</text>
<text x="665" y="132" text-anchor="middle" fill="white" font-size="10">2A+ supply</text>
<!-- 1000µF cap -->
<rect x="750" y="105" width="40" height="18" fill="none" stroke="#333" stroke-width="1.5" rx="3"/>
<text x="770" y="118" text-anchor="middle" font-size="7">1000µF</text>
<!-- 100µF cap near ESP -->
<rect x="200" y="185" width="35" height="14" fill="none" stroke="#333" stroke-width="1" rx="2"/>
<text x="218" y="195" text-anchor="middle" font-size="7">100µF</text>
<!-- Level Shifter -->
<rect x="330" y="230" width="100" height="50" fill="#775599" stroke="#333" stroke-width="2" rx="6"/>
<text x="380" y="252" text-anchor="middle" fill="white" font-size="10" font-weight="bold">74HCT125</text>
<text x="380" y="268" text-anchor="middle" fill="#ddbbff" font-size="8">Level Shifter</text>
<!-- Signal path: ESP → resistor → level shifter → strip -->
<line x1="182" y1="257" x2="250" y2="257" stroke="orange" stroke-width="2"/>
<text x="216" y="250" font-size="8" fill="orange">D5 (3.3V)</text>
<!-- 470Ω resistor -->
<rect x="250" y="250" width="45" height="14" fill="none" stroke="#333" stroke-width="1.5" rx="2"/>
<text x="273" y="260" text-anchor="middle" font-size="7">470Ω</text>
<line x1="295" y1="257" x2="330" y2="257" stroke="orange" stroke-width="2"/>
<!-- Level shifter to LED strip -->
<line x1="430" y1="257" x2="520" y2="257" stroke="#00cc00" stroke-width="2.5"/>
<text x="475" y="250" font-size="8" fill="#00cc00">5V Data</text>
<!-- WS2812B LED Strip -->
<rect x="520" y="210" width="180" height="80" fill="#222" stroke="#333" stroke-width="2" rx="8"/>
<!-- Individual LEDs -->
<rect x="535" y="235" width="20" height="20" fill="#ff0000" stroke="#555" stroke-width="1" rx="3"/>
<rect x="565" y="235" width="20" height="20" fill="#00ff00" stroke="#555" stroke-width="1" rx="3"/>
<rect x="595" y="235" width="20" height="20" fill="#0000ff" stroke="#555" stroke-width="1" rx="3"/>
<rect x="625" y="235" width="20" height="20" fill="#ff00ff" stroke="#555" stroke-width="1" rx="3"/>
<rect x="655" y="235" width="20" height="20" fill="#ffff00" stroke="#555" stroke-width="1" rx="3"/>
<rect x="685" y="235" width="5" height="20" fill="#555" rx="1"/>
<text x="693" y="249" font-size="8" fill="#aaa">...</text>
<text x="610" y="228" text-anchor="middle" fill="white" font-size="11" font-weight="bold">WS2812B Strip</text>
<text x="610" y="278" text-anchor="middle" fill="#aaa" font-size="9">30 LEDs × 60mA = 1.8A max</text>
<!-- Power from external to strip -->
<line x1="665" y1="145" x2="665" y2="210" stroke="red" stroke-width="2.5"/>
<text x="690" y="180" font-size="9" fill="red" font-weight="bold">5V direct</text>
<!-- GND rail -->
<line x1="182" y1="217" x2="750" y2="217" stroke="blue" stroke-width="2" stroke-dasharray="6,3"/>
<line x1="665" y1="290" x2="665" y2="217" stroke="blue" stroke-width="2"/>
<text x="420" y="212" text-anchor="middle" font-size="9" fill="blue" font-weight="bold">SHARED GND</text>
<!-- Level shifter power -->
<line x1="380" y1="230" x2="380" y2="180" stroke="red" stroke-width="1"/>
<line x1="380" y1="180" x2="665" y2="180" stroke="red" stroke-width="1"/>
<text x="380" y="175" text-anchor="middle" font-size="7" fill="red">VCC = 5V</text>
<!-- Phone/Browser illustration -->
<rect x="50" y="400" width="80" height="40" fill="#f0f0f0" stroke="#999" stroke-width="1.5" rx="8"/>
<text x="90" y="418" text-anchor="middle" font-size="8">📱 Browser</text>
<text x="90" y="432" text-anchor="middle" font-size="7" fill="#666">any device</text>
<line x1="130" y1="420" x2="160" y2="420" stroke="#58a6ff" stroke-width="1.5" stroke-dasharray="3,3"/>
<text x="175" y="418" font-size="7" fill="#58a6ff">WiFi</text>
<line x1="190" y1="420" x2="210" y2="420" stroke="#58a6ff" stroke-width="1.5" stroke-dasharray="3,3"/>
<line x1="210" y1="420" x2="95" y2="380" stroke="#58a6ff" stroke-width="1.5" stroke-dasharray="3,3"/>
<!-- Warning box -->
<rect x="520" y="330" width="280" height="65" fill="#332200" stroke="#cc8800" stroke-width="1.5" rx="6"/>
<text x="660" y="348" text-anchor="middle" font-size="10" fill="#ffcc00" font-weight="bold">⚠ Power Rules</text>
<text x="530" y="364" font-size="8" fill="#ffcc00">• LED strip powered from EXTERNAL 5V only</text>
<text x="530" y="378" font-size="8" fill="#ffcc00">• Never power strip from ESP 5V/3V3 pins</text>
<text x="530" y="392" font-size="8" fill="#ffcc00">• All GNDs must be connected together</text>
</svg>
Circuit Schema (JSON)
{
"module": 13,
"project": "WiFi-Controlled LED Strip",
"board": "NodeMCU (ESP8266)",
"schematic": {
"components": [
{
"id": "U1", "type": "nodemcu_esp8266",
"pins_used": {
"3V3": "net_3v3", "GND": "net_gnd",
"D5": "net_data_3v3"
},
"note": "3.3V logic — do NOT connect 5V signals to GPIO"
},
{
"id": "PS1", "type": "power_supply", "value": "5V", "max_current": "2A",
"pins": { "positive": "net_5v", "negative": "net_gnd" }
},
{
"id": "C1", "type": "capacitor_electrolytic", "value": "1000", "unit": "uF",
"purpose": "LED strip power smoothing",
"pins": { "positive": "net_5v", "negative": "net_gnd" }
},
{
"id": "C2", "type": "capacitor_electrolytic", "value": "100", "unit": "uF",
"purpose": "ESP8266 WiFi decoupling",
"pins": { "positive": "net_3v3", "negative": "net_gnd" }
},
{
"id": "R1", "type": "resistor", "value": "470", "unit": "ohm",
"purpose": "Data line signal integrity",
"pins": { "pin1": "net_data_3v3", "pin2": "net_data_shifted_in" }
},
{
"id": "LS1", "type": "level_shifter", "model": "74HCT125",
"purpose": "3.3V to 5V data signal conversion",
"pins": {
"input": "net_data_shifted_in",
"output": "net_data_5v",
"vcc": "net_5v",
"gnd": "net_gnd"
}
},
{
"id": "STRIP1", "type": "ws2812b",
"num_leds": 30,
"max_current_per_led": "60mA",
"max_total_current": "1800mA",
"pins": {
"din": "net_data_5v",
"vcc": "net_5v",
"gnd": "net_gnd"
}
}
],
"nets": [
{ "name": "net_gnd", "description": "Shared ground (ESP + supply + strip + shifter)",
"nodes": ["U1.GND", "PS1.negative", "C1.negative", "C2.negative", "LS1.gnd", "STRIP1.gnd"] },
{ "name": "net_5v", "description": "External 5V (strip + level shifter only)",
"nodes": ["PS1.positive", "C1.positive", "LS1.vcc", "STRIP1.vcc"] },
{ "name": "net_3v3", "description": "ESP8266 3.3V rail",
"nodes": ["U1.3V3", "C2.positive"] },
{ "name": "net_data_3v3", "description": "LED data at 3.3V from ESP",
"nodes": ["U1.D5", "R1.pin1"] },
{ "name": "net_data_shifted_in", "description": "After resistor, into level shifter",
"nodes": ["R1.pin2", "LS1.input"] },
{ "name": "net_data_5v", "description": "LED data at 5V to strip",
"nodes": ["LS1.output", "STRIP1.din"] }
],
"power": {
"esp_source": "USB (500mA) — powers ESP only",
"strip_source": "External 5V/2A — powers strip and level shifter",
"esp_current_ma": 100,
"strip_max_current_ma": 1800,
"note": "GNDs MUST be connected. Strip power NEVER from ESP."
}
},
"code": { "filename": "module13_wifi_led_strip.ino", "language": "cpp" },
"validation": {
"expected_behavior": "Web UI accessible from any browser on the same WiFi. Color sliders, pattern buttons, and brightness control update the LED strip in real time.",
"testing_steps": [
"Step 1: Upload sketch, open Serial Monitor at 115200 baud",
"Step 2: Wait for WiFi connection — note the IP address printed",
"Step 3: Open that IP in your phone/laptop browser",
"Step 4: Test color sliders — strip should change color",
"Step 5: Test patterns — rainbow, chase, breathe should animate",
"Step 6: Test brightness — should smoothly dim/brighten"
],
"common_mistakes": [
"Blank web page: ESP not connected to WiFi — check SSID/password, check Serial output",
"Page loads but LEDs don't respond: wrong data pin (D5 = GPIO14, not GPIO5!)",
"LEDs flicker or show wrong colors: missing level shifter, or COLOR_ORDER wrong (try RGB vs GRB)",
"ESP resets when LEDs turn on: LED strip powered from ESP — must use external supply",
"Only first LED works: data line issue — check 470Ω resistor, try without level shifter",
"WiFi keeps disconnecting: add 100µF cap near ESP, check distance to router",
"Colors wrong: WS2812B uses GRB order by default, not RGB. Adjust COLOR_ORDER."
]
}
}
Experiments to Try
Experiment 1: Access Point Mode
Instead of connecting to an existing WiFi network, make the ESP be the network. This lets you control the LEDs anywhere, even without a router:
#include <ESP8266WiFi.h>
void setup() {
WiFi.softAP("LED-Controller", "password123");
Serial.print("AP IP: ");
Serial.println(WiFi.softAPIP()); // Usually 192.168.4.1
// ... rest of server setup
}
Connect your phone to the "LED-Controller" network and browse to 192.168.4.1.
Experiment 2: Add Sensor Data to the Web Page
Connect a DHT22 (at 3.3V) and display temperature/humidity on the web page alongside the LED controls. The page becomes a dashboard and controller in one.
Experiment 3: External API Integration
Make the LED strip respond to external data: change color based on weather (blue = cold, red = hot), or flash when you get a notification from a webhook.
Experiment 4: mDNS (Friendly Name Instead of IP)
#include <ESP8266mDNS.h>
void setup() {
// ... after WiFi connects:
if (MDNS.begin("ledstrip")) {
Serial.println("Access at: http://ledstrip.local");
}
}
void loop() {
MDNS.update();
server.handleClient();
}
Now you can browse to http://ledstrip.local instead of remembering the IP address.
Self-Check: Module 13
Before moving to the Advanced level, make sure you can:
- Explain the key differences between ESP8266 and Arduino Uno
- Set up the ESP8266 board package in the Arduino IDE
- Identify which GPIO pins are safe for general use and which have boot restrictions
- Explain why 3.3V logic matters and when level shifting is needed
- Connect the ESP8266 to a WiFi network with error handling and reconnection
- Make HTTP GET and POST requests from the ESP8266
- Host a web server that serves HTML and handles URL routes
- Build the WiFi-controlled LED strip with a responsive web interface
- Calculate power requirements for WiFi + LED projects
Key Terms Glossary
| Term | Definition |
|---|---|
| ESP8266 | WiFi-enabled 32-bit microcontroller, programmed with the Arduino IDE |
| NodeMCU | Popular ESP8266 development board with USB, voltage regulator, and labeled pins |
| Wemos D1 Mini | Compact ESP8266 board, smaller than NodeMCU, same capabilities |
| GPIO | General Purpose Input/Output. The usable pins on the ESP8266 |
| 3.3V logic | HIGH = 3.3V on the ESP8266 (vs. 5V on the Uno) |
| Level shifter | Circuit that converts signals between voltage levels (3.3V ↔ 5V) |
| Station mode (STA) | ESP connects to an existing WiFi network as a client |
| Access Point mode (AP) | ESP creates its own WiFi network that others connect to |
| ESP8266WebServer | Library for hosting an HTTP web server on the ESP |
| HTTP GET | Request to retrieve data from a server |
| HTTP POST | Request to send data to a server |
| RSSI | Received Signal Strength Indicator. WiFi signal quality in dBm |
| Deep sleep | Ultra-low-power mode (~20 µA). ESP wakes by resetting |
| WS2812B | Individually addressable RGB LED. Each LED has a built-in controller |
| FastLED | High-performance library for controlling addressable LED strips |
| mDNS | Multicast DNS. Lets you use a .local name instead of an IP address |
| Route | A URL path (like /led/on) mapped to a handler function on the web server |
| Raw literal | C++ R"rawliteral(...)rawliteral" syntax for embedding multi-line strings (like HTML) without escaping |
Course Milestone: Intermediate Complete 🎉
You've finished all 13 Intermediate modules. You now have working knowledge of:
- Electricity fundamentals, Ohm's Law, power budgets
- Arduino programming (digital/analog I/O, timing, state machines)
- Sensor reading, calibration, and noise filtering
- Communication protocols (UART, I2C, SPI)
- Displays (LCD, OLED) and audio output
- Motor control (servo, DC, stepper)
- Compact builds with the Arduino Nano
- Systematic debugging methodology
- WiFi connectivity with the ESP8266
The Advanced level (Modules 14–22) covers ESP32, IoT/MQTT, device-to-device communication, home automation, robotics, Raspberry Pi integration, networking, permanent builds, and a capstone project.
Previous: ← Module 12: Troubleshooting & Debugging Next: Module 14: ESP32: The Powerhouse → (Advanced Level)