Learning Goals 5 min
- Integrate the Weather Station v1 (L02-20) with the LCD (Cluster E) and the SD logger (L02-42) into one ship-ready device — temperatures, humidity, light, all on screen AND on disk.
- Practise the multi-cluster wiring layout — pins, power rails, breadboard real estate — for a build that uses three modules at once.
- Add a single mode button: short press cycles LCD screens; long press toggles logging on/off (with a visible LCD indicator).
Warm-Up 10 min
Cluster H is integration. Each of its three builds (Weather v2, Combination Lock, Reaction Arcade) takes ideas from 4–5 previous clusters and wires them together. No new techniques; just the meaningful, scary art of combining things without breaking any of them.
What v1 lacked
- No screen — needed a laptop.
- No persistence — closing the Serial Monitor lost everything.
- No way to look back over time.
What v2 adds
- LCD showing current readings + headline (Cluster E).
- SD logger writing every 5 s to a CSV (Cluster G).
- Single button UI: short = cycle screens; long = log on/off.
- Unattended operation for hours, returning a chartable CSV.
New Concept · Putting big projects together 25 min
Pin budget — count carefully before wiring
The UNO has 14 digital + 6 analog pins. Used pins:
| Module | Pins |
|---|---|
| LCD (bare, 6 pins) | D12, D11, D5, D4, D3, D2 |
| SD card module | D10 (CS), D11 (MOSI), D12 (MISO), D13 (SCK) |
| DHT11 | D7 |
| Button | D8 |
| LDR | A0 |
The LCD and SD card both want D11 and D12. Conflict. Resolution: switch the LCD to an I²C backpack (free up D11, D12, D4, D5) OR switch the SD to software SPI on different pins (slower).
For this lesson we'll assume the I²C LCD. If you only have the bare LCD, you can:
- Skip the SD card and just do the LCD-only version (this lesson minus logging).
- Use a UNO with extra pins (Mega 2560) or a board with hardware-SPI on a separate header.
- Use the bare LCD and pick non-conflicting LCD pins (D4–D7 for data, D8 + D9 for RS/EN), and put the button somewhere else.
The lesson's code below assumes I²C LCD. Adapt as needed.
Three LCD screens to design
- Now — top row:
27.3°C 67%; bottom row:L:42% Stormy. - Stats — Lo/Hi/Avg for each sensor accumulated since boot.
- Log — filename, sample count, "LOG ON" or "LOG OFF" status.
The state for logging on/off
A simple bool loggingOn. Long-press toggles it. When off, the file isn't opened and no rows are written; the LCD shows "LOG OFF" on the Log screen.
Worked Example · The v2 sketch 25 min
Step 1 — wiring (I²C LCD version)
| Component | Pin |
|---|---|
| I²C LCD SDA | A4 |
| I²C LCD SCL | A5 |
| SD module MOSI | D11 |
| SD module MISO | D12 |
| SD module SCK | D13 |
| SD module CS | D10 |
| DHT11 data | D7 |
| Mode button | D2 (INPUT_PULLUP) |
| LDR divider | A0 |
Step 2 — the sketch (abridged — full version provided)
// L02-43: Weather Station v2 — LCD + SD + multi-sensor
// Long press = toggle logging. Short press = cycle screens.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <SD.h>
#include <EEPROM.h>
#include <DHT.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
DHT dht(7, DHT11);
File logFile;
const int CS = 10, LDR = A0, BTN = 2;
const int LOG_NUM_ADDR = 0;
const unsigned long SAMPLE_MS = 5000;
enum Screen { NOW, STATS, LOG_SCREEN };
Screen screen = NOW;
bool loggingOn = true;
char filename[16];
float curT = 0, curH = 0;
int curLight = 0;
float tMin = 999, tMax = -999, hMin = 999, hMax = -999;
int lMin = 999, lMax = -999;
double tSum = 0, hSum = 0, lSum = 0;
unsigned long samples = 0;
unsigned long lastSample = 0;
bool needRedraw = true;
// Debounced button with long-press from L02-37 — abridged
struct Button {
int pin;
unsigned long lastChange, pressedAt;
int rawState, stableState, prevStable;
bool justPressed, longPressFired;
void begin(int p) {
pin = p; pinMode(p, INPUT_PULLUP);
rawState = stableState = prevStable = digitalRead(p);
lastChange = millis();
}
void tick() {
int n = digitalRead(pin);
if (n != rawState) { rawState = n; lastChange = millis(); }
justPressed = false;
if (millis() - lastChange >= 50 && rawState != stableState) {
prevStable = stableState; stableState = rawState;
if (stableState == LOW && prevStable == HIGH) {
justPressed = true; pressedAt = millis(); longPressFired = false;
}
}
if (stableState == LOW && !longPressFired && millis() - pressedAt >= 2000) {
longPressFired = true;
}
if (stableState == HIGH) longPressFired = false;
}
};
Button btn;
void newLogFile() {
unsigned int n;
EEPROM.get(LOG_NUM_ADDR, n);
if (n == 0xFFFF) n = 0;
n++;
EEPROM.put(LOG_NUM_ADDR, n);
sprintf(filename, "log%03u.csv", n);
logFile = SD.open(filename, FILE_WRITE);
if (logFile) {
logFile.println("seconds,T,H,L");
logFile.close();
}
}
void recordStats() {
if (curT < tMin) tMin = curT; if (curT > tMax) tMax = curT;
if (curH < hMin) hMin = curH; if (curH > hMax) hMax = curH;
if (curLight < lMin) lMin = curLight;
if (curLight > lMax) lMax = curLight;
tSum += curT; hSum += curH; lSum += curLight;
samples++;
}
void logRow() {
if (!loggingOn) return;
logFile = SD.open(filename, FILE_WRITE);
if (logFile) {
char tb[8], hb[8], buf[40];
dtostrf(curT, 4, 1, tb);
dtostrf(curH, 4, 0, hb);
sprintf(buf, "%lu,%s,%s,%d", millis()/1000, tb, hb, curLight);
logFile.println(buf);
logFile.close();
}
}
const char* headline() {
bool hot = curT > 30, humid = curH > 75, bright = curLight > 70;
if (hot && humid) return "Stormy";
if (hot && bright) return "Hot&bright";
if (hot) return "Hot&dim ";
if (humid) return "Warm&humid";
if (bright) return "Cool&brigh";
return "Cool&dim ";
}
void drawNow() {
lcd.clear();
char buf[17], tb[8], hb[8];
dtostrf(curT, 4, 1, tb); dtostrf(curH, 3, 0, hb);
sprintf(buf, "%sC %s%%", tb, hb);
lcd.setCursor(0, 0); lcd.print(buf);
sprintf(buf, "L:%2d%% %s", curLight, headline());
lcd.setCursor(0, 1); lcd.print(buf);
}
void drawStats() {
lcd.clear();
if (samples == 0) { lcd.print("No data yet"); return; }
char buf[17];
sprintf(buf, "T %2d-%2d H %2d-%2d",
(int)tMin, (int)tMax, (int)hMin, (int)hMax);
lcd.setCursor(0, 0); lcd.print(buf);
sprintf(buf, "L %2d-%2d n=%lu",
lMin, lMax, samples);
lcd.setCursor(0, 1); lcd.print(buf);
}
void drawLog() {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(filename);
lcd.setCursor(0, 1);
if (loggingOn) {
char buf[17];
sprintf(buf, "LOG ON n=%lu", samples);
lcd.print(buf);
} else {
lcd.print("LOG OFF");
}
}
void redraw() {
if (screen == NOW) drawNow();
else if (screen == STATS) drawStats();
else drawLog();
}
void setup() {
Serial.begin(9600);
Wire.begin();
lcd.init(); lcd.backlight();
btn.begin(BTN);
dht.begin();
if (!SD.begin(CS)) {
lcd.print("SD INIT FAIL");
loggingOn = false;
} else {
newLogFile();
}
}
void loop() {
btn.tick();
if (btn.justPressed) {
screen = (Screen)((screen + 1) % 3);
needRedraw = true;
}
if (btn.longPressFired) {
loggingOn = !loggingOn;
if (loggingOn) newLogFile(); // start a new file each time we re-enable
needRedraw = true;
btn.longPressFired = false;
}
if (millis() - lastSample >= SAMPLE_MS) {
lastSample = millis();
curT = dht.readTemperature();
curH = dht.readHumidity();
curLight = map(constrain(analogRead(LDR), 0, 1023), 0, 1023, 0, 100);
if (!isnan(curT) && !isnan(curH)) {
recordStats();
logRow();
needRedraw = true;
}
}
if (needRedraw) { redraw(); needRedraw = false; }
}Step 3 — power on the integrated system
LCD should boot to the Now screen. Within 5 seconds the first reading appears. SD module gets a fresh LOG002.CSV (or whichever number EEPROM had next).
Step 4 — cycle screens
Short press → STATS. Short press → LOG (shows filename + sample count). Short press → back to NOW. Screen-to-screen is instant; data carries over.
Step 5 — toggle logging
Hold the button for 2 seconds. The LCD updates the Log screen to show LOG OFF; SD writes stop. Hold again → LOG ON, a fresh file opens, writes resume.
Step 6 — run for 30 minutes
Walk away. Come back. The LCD has been updating, the file has been growing, the Stats screen has accumulated min/max/n. Pop the SD card, chart in Excel. Both real-time display and historical data — two product capabilities from one device.
Try It Yourself 15 min
Goal: Add a custom-character degree symbol from L02-30 and use it on the Now screen.
Hint
Same byte array + createChar approach. The I²C LCD library's createChar works identically to the bare LCD's.
Goal: Add a 4th screen showing average values (separate from Stats which shows min/max). So the cycle is Now → Stats → Avg → Log → Now.
Hint
Add a screen to the enum, a draw function, and update the modulus in the button handler. The averages are easy: tSum / samples etc. Same multi-screen architecture as L02-32.
Goal: Save the daily Lo/Hi/Avg values to EEPROM at midnight (or every hour) so they survive a power cycle and you can see "yesterday's peak temperature". Requires a way to know what time it is — use millis() / 60000 as an approximate clock.
Hint
Track millisAtLastSnapshot. Every 3 600 000 ms (1 hour), write the current Lo/Hi/Avg to a known EEPROM address, then reset the running counters. Read them back on boot to display "previous hour" values.
Mini-Challenge · Build it on a piece of cardboard 15 min
This is a build-to-look-good challenge. Mount the Weather Station v2 on a piece of cardboard or a small project box:
- LCD visible at the top.
- Button accessible (label it "mode").
- Sensors mounted near edges with airflow.
- SD slot facing outward so you can swap cards without disassembling.
- Tag the front: project name, your name, version, date.
It's done when:
- The whole thing fits in your hand or sits steady on a shelf.
- You can press the button without the wiring shaking.
- You can swap the SD card without unplugging USB.
- It runs unattended overnight without crashing.
- A photo of the front looks like a real product you'd see in a science kit.
Recap 5 min
Weather Station v2 is the first of Cluster H's integration builds. The new lesson isn't a technique — every technique was already in your toolbox — it's the discipline of combining three modules (LCD, SD, sensors) into one stable product without each module breaking the others. Pin budgeting, I²C vs SPI choice, RAM management, and the test-each-module-first approach are all skills you'll repeatedly use in L3 robotics builds. Tomorrow we tackle the Digital Combination Lock, which adds a servo and three-button input into the mix.
- Integration build
- A project that combines multiple subsystems (sensors, display, storage, actuator) into one device. The Cluster H category.
- Pin budget
- The accounting exercise of listing every pin a project needs and verifying it fits on your board. Often the limiting factor on UNO; resolved by I²C / SPI sharing or moving to a Mega.
- Subsystem isolation
- Testing each module on its own (with its example sketch) before combining. Catches wiring errors and library conflicts in the easy phase.
- Two-channel UI
- Showing data on both an LCD (real-time) and an SD log (historical). One device, two products.
- Long-press = secondary action
- The single-button UX trick: short press cycles primary modes; long press toggles a secondary setting. Lets one button do two jobs.
- I²C vs SPI
- Two competing "chip-talking" protocols on Arduino. I²C uses 2 pins shared by all devices; SPI uses 3 shared + 1 per device. We pick I²C for the LCD here to free SPI pins for the SD card.
- Pin conflict
- Two modules trying to use the same Arduino pin (here: LCD parallel data on D11 / D12 vs SD's hardware SPI on the same pins). Resolved by switching one module to a different protocol.
- RAM ceiling
- The 2 KB of SRAM on a UNO. Once libraries + buffers cross ~1.5 KB, weird symptoms appear: garbled screen, random resets. Sign you need a Mega or a leaner sketch.
Homework 5 min
Deploy your station for 8+ hours. Put the assembled Weather Station v2 somewhere meaningful (your bedroom, your desk, a balcony) and let it run overnight or all day. Then:
- Power down via long-press → mode-cycle → unplug.
- Pop the SD card, transfer the CSV to your laptop.
- Chart all three values (T, H, L) over time in a spreadsheet.
- Compare with the Stats screen's Lo/Hi values — do they match the chart's extremes?
- Find at least three interesting moments in the data (sunrise, AC kicking on, you opened a window, the cat sat on the sensor, etc.).
Bring back next class:
- Your CSV file.
- A single annotated chart showing the three values + your three highlighted moments.
- A photo of the deployed device.
- Tomorrow we build the Digital Combination Lock — keypad of buttons + servo latch.