Module 8: Displays & Visual Feedback
Level: 🟡 Intermediate
Board: Arduino Uno / Arduino Nano
Prerequisites: Module 7
Estimated time: 60–80 minutes
Goal: Output information visually using different display types.
What You'll Learn
The Serial Monitor is great for debugging, but your projects won't always be tethered to a computer. In this module, you'll learn to display information on the project itself, from a simple LED array with shift registers, to a classic LCD, to a sharp OLED screen. You'll also learn to design UIs for tiny screens that are actually readable.
8.1 LED Arrays and Shift Registers (74HC595)
The Pin Problem
The Uno has 14 digital pins. What if you want to control 16 LEDs? 32? A shift register lets you control 8 outputs using only 3 Arduino pins. Chain two together for 16 outputs, three for 24, and so on.
How the 74HC595 Works
The 74HC595 is a serial-in, parallel-out shift register. You send it data one bit at a time (serial), and it holds all 8 bits on its output pins (parallel) simultaneously.
Three control lines:
- Data (SER / DS): The bit value (0 or 1) you're shifting in
- Clock (SRCLK / SH_CP): Each pulse shifts one bit into the register
- Latch (RCLK / ST_CP): Pulse this to update all outputs at once
Pin Configuration (from datasheet)
| Pin | Name | Function |
|---|---|---|
| 1–7, 15 | Q0–Q7 | Parallel outputs (connect to LEDs) |
| 8 | GND | Ground |
| 9 | Q7' | Serial out (chain to next 595's SER pin) |
| 10 | MR (SRCLR) | Master Reset, tie to VCC (active LOW) |
| 11 | SH_CP (SRCLK) | Shift clock input |
| 12 | ST_CP (RCLK) | Storage/latch clock input |
| 13 | OE | Output Enable, tie to GND (active LOW) |
| 14 | DS (SER) | Serial data input |
| 16 | VCC | Power (5V) |
Wiring
Arduino pin 2 → 74HC595 pin 14 (Data/SER)
Arduino pin 3 → 74HC595 pin 11 (Clock/SH_CP)
Arduino pin 4 → 74HC595 pin 12 (Latch/ST_CP)
74HC595 pin 10 (MR) → 5V (disable reset)
74HC595 pin 13 (OE) → GND (enable outputs)
74HC595 pin 16 (VCC) → 5V
74HC595 pin 8 (GND) → GND
74HC595 Q0–Q7 → 220Ω resistor → LED → GND (each)
Code: Controlling 8 LEDs
/*
* Module 8: 74HC595 Shift Register — 8 LED Control
*
* Circuit:
* - Data (SER): pin 2 → 74HC595 pin 14
* - Clock (SH_CP): pin 3 → 74HC595 pin 11
* - Latch (ST_CP): pin 4 → 74HC595 pin 12
* - Q0–Q7 → 220Ω → LEDs → GND
*
* Board: Arduino Uno
*/
const int DATA_PIN = 2; // SER (pin 14)
const int CLOCK_PIN = 3; // SH_CP (pin 11)
const int LATCH_PIN = 4; // ST_CP (pin 12)
void setup() {
pinMode(DATA_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(LATCH_PIN, OUTPUT);
}
void updateShiftRegister(byte data) {
// Pull latch LOW to prepare for data
digitalWrite(LATCH_PIN, LOW);
// Shift out 8 bits, most significant bit first
shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, data);
// Pull latch HIGH to update outputs
digitalWrite(LATCH_PIN, HIGH);
}
void loop() {
// Light each LED one at a time (chase effect)
for (int i = 0; i < 8; i++) {
byte pattern = 1 << i; // Shift a single 1-bit across
updateShiftRegister(pattern);
delay(100);
}
// All LEDs on
updateShiftRegister(0xFF);
delay(500);
// All LEDs off
updateShiftRegister(0x00);
delay(500);
// Binary count 0–255
for (int i = 0; i < 256; i++) {
updateShiftRegister(i);
delay(50);
}
}
Chaining Two Shift Registers
To control 16 LEDs, connect the first 74HC595's Q7' (pin 9) to the second's SER (pin 14). Share the clock and latch lines. Then shift out 16 bits:
void update16LEDs(uint16_t data) {
digitalWrite(LATCH_PIN, LOW);
shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, highByte(data)); // Second register
shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, lowByte(data)); // First register
digitalWrite(LATCH_PIN, HIGH);
}
Why use shift registers? Beyond saving pins, they offer consistent current drive (each output handles 20 mA), and the technique scales: four registers give you 32 outputs using the same 3 Arduino pins.
8.2 LCD Displays (16×2) with I2C Backpack
The Classic 16×2 LCD
The 16×2 LCD displays 2 rows of 16 characters each. It's the most common display in Arduino projects because it's cheap, readable in direct sunlight, and easy to program.
Without an I2C backpack, this display needs 6+ data pins. With an I2C backpack (PCF8574), it needs just 2 (SDA, SCL), which you already know from Module 7.
Wiring (with I2C backpack)
| LCD Module Pin | Connection |
|---|---|
| VCC | 5V |
| GND | GND |
| SDA | Arduino A4 |
| SCL | Arduino A5 |
That's it. The I2C backpack handles all the parallel data pins internally.
Library and Code
Install LiquidCrystal_I2C from the Library Manager.
/*
* Module 8: 16×2 LCD with I2C
*
* Circuit: LCD I2C module → 5V, GND, SDA (A4), SCL (A5)
* Common address: 0x27 (some modules use 0x3F)
*
* Library: LiquidCrystal_I2C
* Board: Arduino Uno
*/
#include <LiquidCrystal_I2C.h>
// Address 0x27, 16 columns, 2 rows
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0); // Column 0, Row 0
lcd.print("Hello, World!");
lcd.setCursor(0, 1); // Column 0, Row 1
lcd.print("Module 8");
}
void loop() {
// Show uptime on second row
lcd.setCursor(0, 1);
lcd.print("Up: ");
lcd.print(millis() / 1000);
lcd.print("s "); // Extra spaces to clear old characters
delay(1000);
}
Custom Characters
The LCD controller (HD44780) lets you define up to 8 custom 5×8 pixel characters:
// Define a thermometer icon
byte thermometer[8] = {
0b00100,
0b01010,
0b01010,
0b01010,
0b01010,
0b10001,
0b10001,
0b01110
};
void setup() {
lcd.init();
lcd.backlight();
lcd.createChar(0, thermometer); // Store in slot 0
lcd.setCursor(0, 0);
lcd.write(byte(0)); // Display custom character
lcd.print(" 23.5C");
}
When to Use LCD vs. OLED
| Feature | 16×2 LCD | SSD1306 OLED |
|---|---|---|
| Content | Text only (+ 8 custom chars) | Text, graphics, icons |
| Sunlight | Excellent (backlit) | Poor (emissive) |
| Size | Larger physical footprint | Compact (0.96") |
| Power | ~25 mA with backlight | ~10–20 mA |
| Price | ~$2–4 | ~$3–5 |
| Best for | Simple readings, stationary | Compact builds, graphical UIs |
8.3 OLED Displays (SSD1306): Graphics on a Tiny Screen
You used the SSD1306 in Module 7. Here we go deeper: custom layouts, graphics, and flicker-free updates.
Library Setup
The Adafruit SSD1306 library (with Adafruit GFX) provides both text and graphics capabilities. You installed these in Module 7.
Text Basics
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
// Text sizes: 1 = 6×8px, 2 = 12×16px, 3 = 18×24px
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Size 1: 21 chars/row");
display.setTextSize(2);
display.setCursor(0, 16);
display.println("Size 2");
display.display(); // Push buffer to screen
}
Character Grid
| Text Size | Chars per Row | Rows (64px) | Pixel Size |
|---|---|---|---|
| 1 | 21 | 8 | 6×8 |
| 2 | 10 | 4 | 12×16 |
| 3 | 7 | 2 | 18×24 |
Drawing Graphics
// Lines
display.drawLine(0, 0, 127, 63, SSD1306_WHITE);
// Rectangles
display.drawRect(10, 10, 50, 30, SSD1306_WHITE); // Outline
display.fillRect(70, 10, 50, 30, SSD1306_WHITE); // Filled
// Circles
display.drawCircle(64, 32, 20, SSD1306_WHITE); // Outline
display.fillCircle(64, 32, 10, SSD1306_WHITE); // Filled
// Triangles
display.drawTriangle(10, 60, 30, 40, 50, 60, SSD1306_WHITE);
// Rounded rectangles
display.drawRoundRect(5, 5, 60, 30, 8, SSD1306_WHITE);
// Don't forget to push to screen!
display.display();
Bitmaps: Custom Icons
Convert any small image to a byte array using online tools like "image2cpp" and display it:
// 16×16 sun icon (example bitmap)
const unsigned char PROGMEM sun_icon[] = {
0x01, 0x80, 0x01, 0x80, 0x24, 0x24, 0x12, 0x48,
0x09, 0x90, 0x07, 0xe0, 0x1f, 0xf8, 0x1f, 0xf8,
0x1f, 0xf8, 0x07, 0xe0, 0x09, 0x90, 0x12, 0x48,
0x24, 0x24, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00
};
display.drawBitmap(56, 0, sun_icon, 16, 16, SSD1306_WHITE);
PROGMEM note:
PROGMEMstores the bitmap in flash memory instead of RAM. On the Uno with only 2 KB of RAM, this is essential for any data larger than a few dozen bytes.
Progress Bar Helper Function
void drawProgressBar(int x, int y, int width, int height, int percent) {
percent = constrain(percent, 0, 100);
display.drawRect(x, y, width, height, SSD1306_WHITE);
int fillWidth = (width - 2) * percent / 100;
display.fillRect(x + 1, y + 1, fillWidth, height - 2, SSD1306_WHITE);
}
8.4 Designing UIs for Small Screens
A 128×64 OLED has 8,192 pixels. A modern phone has millions. Designing for tiny screens requires discipline.
Principles
1. Hierarchy through size The most important value gets the largest text size. Supporting info stays small.
┌──────────────────────────┐
│ 23.5°C ← Size 2 │ ← Primary value (temperature)
│ Humidity: 65% ← Size 1 │ ← Secondary value
│ 1013.2 hPa ← Size 1 │ ← Tertiary value
│ ▓▓▓▓▓▓▓░░░ 65% ← Size 1 │ ← Visual bar
└──────────────────────────┘
2. Avoid clearing the entire screen every frame
display.clearDisplay() followed by redrawing everything causes visible flicker. Instead:
- Only redraw the areas that changed
- Or use a double-buffer approach (the Adafruit library already buffers; just call
display.display()after all drawing)
3. Use icons instead of labels where possible A thermometer icon takes less space than "Temp:" and is recognizable at a glance.
4. Align numbers to the right Right-aligned numbers prevent jumping when digits change (e.g., 9 → 10):
void printRightAligned(int x, int y, float value, int decimals, int width) {
char buf[16];
dtostrf(value, width, decimals, buf);
display.setCursor(x, y);
display.print(buf);
}
5. Update rate matters Updating the display faster than the data changes wastes CPU time. Temperature changes slowly; update every 2 seconds. Distance changes fast; update every 200 ms. Match your display refresh to your data rate.
Layout Template: Dashboard
Row 0 (y=0): Title / mode indicator
Row 1 (y=10): ─────────── divider line
Row 2 (y=14): [Icon] Primary value (Size 2)
Row 3 (y=34): Secondary reading
Row 4 (y=44): Tertiary reading / bar graph
Row 5 (y=54): Status line / timestamp
8.5 Flicker-Free Updates
The SSD1306 library uses an in-memory buffer. You draw to the buffer, then push it to the screen with display.display(). This means:
- Call
display.clearDisplay()once at the start of each frame - Draw everything you want to show
- Call
display.display()once at the end
The screen updates all at once, not pixel by pixel. If you still see flicker, you're likely calling display.display() multiple times per frame, or updating too frequently.
Partial Updates for Better Performance
For data that changes rarely (labels, icons), draw them once and only redraw the values:
bool firstDraw = true;
void updateDisplay(float temp, float humidity) {
if (firstDraw) {
display.clearDisplay();
// Static labels — drawn once
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Weather Station");
display.drawLine(0, 10, 127, 10, SSD1306_WHITE);
display.setCursor(0, 36);
display.print("Humidity:");
firstDraw = false;
}
// Dynamic values — overwrite with black then redraw
display.fillRect(0, 14, 128, 20, SSD1306_BLACK); // Clear temp area
display.setTextSize(2);
display.setCursor(0, 14);
display.print(temp, 1);
display.print(" C");
display.fillRect(72, 36, 56, 8, SSD1306_BLACK); // Clear humidity area
display.setTextSize(1);
display.setCursor(72, 36);
display.print(humidity, 1);
display.print("%");
display.display();
}
Module Project: Personal Weather Station Display
Objective
Take sensor data from Module 6 (DHT22 temperature/humidity) and display it on an OLED screen with a clean UI including icons, a progress bar for humidity, and min/max tracking. The display updates without flicker.
Components Needed
| Component | Quantity | Notes |
|---|---|---|
| Arduino Uno | 1 | |
| USB-B cable | 1 | |
| Breadboard | 1 | |
| SSD1306 OLED (128×64, I2C) | 1 | |
| DHT22 sensor | 1 | |
| 10kΩ Resistor | 1 | DHT22 pull-up |
| Jumper wires | 6+ |
The Code
/*
* Module 8 Project: Personal Weather Station Display
*
* DHT22 data displayed on SSD1306 OLED with:
* - Large temperature readout
* - Humidity with progress bar
* - Min/max tracking
* - Comfort zone indicator
* - Flicker-free partial updates
*
* Circuit:
* - OLED: SDA → A4, SCL → A5, VCC → 5V, GND → GND
* - DHT22: DATA → D2 (10kΩ pull-up to 5V), VCC → 5V, GND → GND
*
* Libraries: Adafruit SSD1306, Adafruit GFX, Adafruit DHT
* Board: Arduino Uno
*/
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
const int DHT_PIN = 2;
DHT dht(DHT_PIN, DHT22);
// --- Tracking ---
float minTemp = 999.0;
float maxTemp = -999.0;
float currentTemp = 0;
float currentHumidity = 0;
bool hasData = false;
// --- Timing ---
unsigned long lastSensorRead = 0;
const unsigned long SENSOR_INTERVAL = 2000;
unsigned long lastDisplayUpdate = 0;
const unsigned long DISPLAY_INTERVAL = 500;
// --- Bitmaps (stored in flash) ---
// 8×8 thermometer icon
const unsigned char PROGMEM icon_temp[] = {
0x10, 0x28, 0x28, 0x28, 0x28, 0x44, 0x44, 0x38
};
// 8×8 droplet icon
const unsigned char PROGMEM icon_drop[] = {
0x10, 0x10, 0x28, 0x28, 0x44, 0x44, 0x44, 0x38
};
void drawProgressBar(int x, int y, int width, int height, int percent) {
percent = constrain(percent, 0, 100);
display.drawRect(x, y, width, height, SSD1306_WHITE);
int fillWidth = (width - 2) * percent / 100;
display.fillRect(x + 1, y + 1, fillWidth, height - 2, SSD1306_WHITE);
}
String getComfortLevel(float temp, float humidity) {
if (temp >= 20 && temp <= 26 && humidity >= 30 && humidity <= 60) {
return "Comfortable";
} else if (temp > 30) {
return "Too hot";
} else if (temp < 18) {
return "Too cold";
} else if (humidity > 70) {
return "Too humid";
} else if (humidity < 25) {
return "Too dry";
}
return "Moderate";
}
void updateDisplay() {
display.clearDisplay();
// --- Title bar ---
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Weather Station");
// Uptime in top-right
char uptimeStr[10];
unsigned long secs = millis() / 1000;
sprintf(uptimeStr, "%02lu:%02lu", secs / 60, secs % 60);
display.setCursor(90, 0);
display.print(uptimeStr);
display.drawLine(0, 9, 127, 9, SSD1306_WHITE);
if (!hasData) {
display.setTextSize(1);
display.setCursor(20, 30);
display.print("Reading sensors...");
display.display();
return;
}
// --- Temperature (large) ---
display.drawBitmap(0, 13, icon_temp, 8, 8, SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(12, 12);
char tempStr[8];
dtostrf(currentTemp, 5, 1, tempStr);
display.print(tempStr);
display.setTextSize(1);
display.setCursor(72, 12);
display.print("o");
display.setTextSize(2);
display.setCursor(78, 12);
display.print("C");
// --- Min/Max ---
display.setTextSize(1);
display.setCursor(0, 30);
display.print("Lo:");
display.print(minTemp, 1);
display.setCursor(64, 30);
display.print("Hi:");
display.print(maxTemp, 1);
// --- Humidity with bar ---
display.drawBitmap(0, 41, icon_drop, 8, 8, SSD1306_WHITE);
display.setCursor(12, 42);
display.print(currentHumidity, 1);
display.print("%");
drawProgressBar(70, 41, 56, 8, (int)currentHumidity);
// --- Comfort indicator ---
display.setCursor(0, 55);
String comfort = getComfortLevel(currentTemp, currentHumidity);
display.print(comfort);
display.display();
}
void setup() {
Serial.begin(9600);
dht.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("ERROR: SSD1306 not found");
while (1);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 25);
display.print("Initializing...");
display.display();
delay(1000);
}
void loop() {
unsigned long now = millis();
// --- Read sensor ---
if (now - lastSensorRead >= SENSOR_INTERVAL) {
lastSensorRead = now;
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
currentTemp = t;
currentHumidity = h;
hasData = true;
if (t < minTemp) minTemp = t;
if (t > maxTemp) maxTemp = t;
Serial.print(t, 1);
Serial.print(",");
Serial.println(h, 1);
}
}
// --- Update display ---
if (now - lastDisplayUpdate >= DISPLAY_INTERVAL) {
lastDisplayUpdate = now;
updateDisplay();
}
}
Circuit Diagram (SVG)
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 380" font-family="monospace" font-size="11">
<!-- Arduino -->
<rect x="30" y="80" width="140" height="240" fill="#1a7a8a" stroke="#333" stroke-width="2" rx="8"/>
<text x="100" y="110" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Arduino Uno</text>
<!-- Pins -->
<rect x="170" y="140" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="150" text-anchor="middle" fill="white" font-size="8">5V</text>
<rect x="170" y="160" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="170" text-anchor="middle" fill="white" font-size="8">GND</text>
<rect x="170" y="195" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="205" text-anchor="middle" fill="white" font-size="8">D2</text>
<rect x="170" y="245" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="255" text-anchor="middle" fill="white" font-size="8">A4</text>
<rect x="170" y="270" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="280" text-anchor="middle" fill="white" font-size="8">A5</text>
<!-- === DHT22 === -->
<rect x="350" y="100" width="110" height="60" fill="#4488aa" stroke="#333" stroke-width="2" rx="6"/>
<text x="405" y="125" text-anchor="middle" fill="white" font-size="12" font-weight="bold">DHT22</text>
<text x="405" y="145" text-anchor="middle" fill="white" font-size="9">Temp + Humidity</text>
<line x1="192" y1="147" x2="350" y2="115" stroke="red" stroke-width="1.5"/>
<line x1="192" y1="202" x2="350" y2="130" stroke="green" stroke-width="1.5"/>
<text x="275" y="155" font-size="8" fill="green">DATA (D2)</text>
<line x1="192" y1="167" x2="350" y2="148" stroke="blue" stroke-width="1.5"/>
<!-- Pull-up -->
<rect x="280" y="105" width="35" height="12" fill="none" stroke="#333" stroke-width="1" rx="2"/>
<text x="298" y="114" text-anchor="middle" font-size="7">10kΩ↑</text>
<!-- === OLED === -->
<rect x="350" y="220" width="140" height="90" fill="#222" stroke="#333" stroke-width="2" rx="6"/>
<rect x="365" y="232" width="110" height="60" fill="#000" stroke="#555" stroke-width="1" rx="3"/>
<text x="420" y="258" text-anchor="middle" fill="#00ccff" font-size="11" font-weight="bold">23.5°C</text>
<text x="420" y="275" text-anchor="middle" fill="#00ccff" font-size="9">Humidity: 52%</text>
<text x="420" y="285" text-anchor="middle" fill="#00ccff" font-size="7">▓▓▓▓▓░░░░░</text>
<text x="420" y="305" text-anchor="middle" fill="white" font-size="10">SSD1306 OLED</text>
<!-- OLED I2C connections -->
<line x1="192" y1="252" x2="350" y2="252" stroke="#cc8800" stroke-width="2"/>
<text x="270" y="248" font-size="8" fill="#cc8800">SDA (A4)</text>
<line x1="192" y1="277" x2="350" y2="270" stroke="#008800" stroke-width="2"/>
<text x="270" y="280" font-size="8" fill="#008800">SCL (A5)</text>
<line x1="290" y1="147" x2="290" y2="237" stroke="red" stroke-width="1"/>
<line x1="290" y1="237" x2="350" y2="237" stroke="red" stroke-width="1.5"/>
<line x1="300" y1="167" x2="300" y2="295" stroke="blue" stroke-width="1"/>
<line x1="300" y1="295" x2="350" y2="295" stroke="blue" stroke-width="1.5"/>
<!-- Display mockup label -->
<rect x="540" y="120" width="180" height="100" fill="#f8f8f8" stroke="#ccc" stroke-width="1" rx="4"/>
<text x="630" y="140" text-anchor="middle" font-size="10" font-weight="bold">OLED Layout</text>
<text x="550" y="158" font-size="8">Row 0: Title + uptime</text>
<text x="550" y="170" font-size="8">Row 1: ───── divider</text>
<text x="550" y="182" font-size="8">Row 2: 🌡 23.5°C (large)</text>
<text x="550" y="194" font-size="8">Row 3: Lo/Hi tracking</text>
<text x="550" y="206" font-size="8">Row 4: 💧 52% [███░░░]</text>
<text x="550" y="218" font-size="8">Row 5: Comfort level</text>
</svg>
Circuit Schema (JSON)
{
"module": 8,
"project": "Personal Weather Station Display",
"board": "Arduino Uno",
"schematic": {
"components": [
{
"id": "U1",
"type": "arduino_uno",
"pins_used": {
"5V": "net_vcc", "GND": "net_gnd",
"D2": "net_dht_data", "A4": "net_sda", "A5": "net_scl"
}
},
{
"id": "OLED1",
"type": "ssd1306",
"protocol": "I2C",
"address": "0x3C",
"resolution": "128x64",
"current_draw": "20mA",
"pins": { "vcc": "net_vcc", "gnd": "net_gnd", "sda": "net_sda", "scl": "net_scl" }
},
{
"id": "DHT1",
"type": "dht22",
"current_draw": "2.5mA",
"pins": { "vcc": "net_vcc", "data": "net_dht_data", "gnd": "net_gnd" }
},
{
"id": "R_PULLUP",
"type": "resistor", "value": "10000", "unit": "ohm",
"purpose": "DHT22 data pull-up",
"pins": { "pin1": "net_vcc", "pin2": "net_dht_data" }
}
],
"nets": [
{ "name": "net_vcc", "nodes": ["U1.5V", "OLED1.vcc", "DHT1.vcc", "R_PULLUP.pin1"] },
{ "name": "net_gnd", "nodes": ["U1.GND", "OLED1.gnd", "DHT1.gnd"] },
{ "name": "net_sda", "nodes": ["U1.A4", "OLED1.sda"] },
{ "name": "net_scl", "nodes": ["U1.A5", "OLED1.scl"] },
{ "name": "net_dht_data", "nodes": ["U1.D2", "DHT1.data", "R_PULLUP.pin2"] }
],
"power": {
"source": "USB",
"total_current_ma": 72.5,
"breakdown": "Arduino(50) + OLED(20) + DHT22(2.5)"
}
},
"code": { "filename": "module08_weather_display.ino", "language": "cpp" },
"validation": {
"expected_behavior": "OLED shows temperature (large), humidity bar, min/max, comfort level. Updates without flicker.",
"common_mistakes": [
"Blank OLED: wrong I2C address — try 0x3D instead of 0x3C",
"Flickering display: calling display.display() multiple times per frame",
"Numbers jumping: not right-aligning or clearing the area before redraw",
"Out of RAM: too many bitmaps in RAM — use PROGMEM",
"DHT reads NaN: poll interval too fast (<2s for DHT22)"
]
}
}
Experiments to Try
Experiment 1: Scrolling Text Banner
Implement horizontal scrolling for long messages on the OLED using display.startscrollleft() or manual pixel-shifting.
Experiment 2: Multi-Page Display
Create multiple "pages" of information. Button press cycles between temperature view, humidity view, and a graph view. Reuse the state machine pattern from Module 4.
Experiment 3: Bar Graph History
Store the last 64 temperature readings and draw them as a mini line graph on the OLED. Each pixel column represents one reading. This gives you a visual history of temperature trends.
Self-Check: Module 8
Before moving to Module 9, make sure you can:
- Use a 74HC595 shift register to control 8+ LEDs from 3 pins
- Wire and program a 16×2 LCD with I2C backpack
- Create custom characters for an LCD
- Draw text, lines, rectangles, circles, and bitmaps on an SSD1306 OLED
- Design a readable UI layout for a 128×64 screen
- Update the display without visible flicker
- Choose the right display type for a given project
- Build the weather station display with clean formatting
Key Terms Glossary
| Term | Definition |
|---|---|
| Shift register | IC that converts serial data to parallel outputs (74HC595) |
| Multiplexing | Sharing resources (pins, wires) by switching between devices rapidly |
| shiftOut() | Arduino function that sends a byte one bit at a time to a shift register |
| LCD | Liquid Crystal Display. Text-based, backlit, excellent in sunlight. |
| I2C backpack | Module that adds I2C interface to a parallel-interface LCD |
| OLED | Organic LED display. Each pixel emits its own light, supports graphics. |
| SSD1306 | Common OLED display controller (128×64 or 128×32 pixels) |
| Frame buffer | Memory holding the complete screen image before sending to display |
| PROGMEM | Stores data in flash memory instead of RAM. Essential for bitmaps. |
| Custom character | User-defined 5×8 pixel pattern stored in LCD controller memory |
| Flicker | Visible blink caused by clearing and redrawing the screen too slowly |
| dtostrf() | Converts a float to a string with specified width and decimal places |
Previous: ← Module 7: Communication Protocols Next: Module 9: Sound & Audio →