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:

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: PROGMEM stores 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:

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:

  1. Call display.clearDisplay() once at the start of each frame
  2. Draw everything you want to show
  3. 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:


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 →