Week 09 — Output Devices. This week I redesigned and milled a new PCB to start my final project. The output device is a 0.96″ OLED display, driven over I²C and controlled by four buttons that switch the on‑screen scenes.

Assignments

  • Group: measure the power consumption of an output device.
  • Individual: add an output device to a microcontroller board I designed, and program it to do something.

Group assignment (1–5)

We profiled the current draw of small LEDs and an OLED breakout at different voltages/brightness levels using a bench supply and a DMM inline. Gert also showed what happens when an LED is driven past its safe voltage — a brief flash, then a visible failure. It was a vivid reminder to respect datasheets and series resistors.

Measuring setup with bench instruments
1
Close-up of probes and DUT
2
3
4
Aftermath of an overdriven LED
5 · Gert’s LED-overvoltage demo

Individual assignment (6–15)

I redesigned my ESP32‑S3 board to include a 4‑button input cluster and an I²C header for the SSD1306 OLED (0x3C/0x3D). After milling and soldering, I verified continuity, then brought the display up on I2C1 to avoid conflicts with default pins. Each button selects a different scene label on the screen — a tiny UI prototype for the final project’s environmental “modes.”

Wired prototype on the bench
6
Schematic overview
7 · Schematic
PCB layout render
8 · PCB layout
CAM or drawing for machining
9
Trace inspection on the screen
10
Board during milling
11
Board after milling
12
Assembled board on wooden fish base
13
Holding the OLED module
14
15 · Four-button OLED demo

Firmware — Four‑button OLED UI

I used Adafruit_GFX and Adafruit_SSD1306 on I2C(1). Four buttons (D0–D3) are debounced in software; a falling edge updates the center‑aligned label on the screen. SDA/SCL are on D8/D7. The sketch below is what’s running in the video.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define W 128
#define H 64
const int SDA_PIN = D8;  
const int SCL_PIN = D7;  

TwoWire I2C(1);
Adafruit_SSD1306 display(W, H, &I2C, -1);

const int BTN[4] = { D0, D1, D2, D3 };
const char* LABEL[4] = { "RIVER", "LAKE", "OCEAN", "???" };

void showText(const char* s) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);

  int16_t x1, y1; uint16_t w, h;
  display.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
  int x = (W - (int)w) / 2;
  int y = (H - (int)h) / 2;
  display.setCursor(x, y);
  display.println(s);
  display.display();
}

void setup() {
  Serial.begin(115200);
  delay(200);

  pinMode(SDA_PIN, INPUT_PULLUP);
  pinMode(SCL_PIN, INPUT_PULLUP);
  I2C.begin(SDA_PIN, SCL_PIN, 100000);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
      Serial.println("OLED init failed.");
      while (1) delay(10);
    }
  }

  for (int i = 0; i < 4; i++) pinMode(BTN[i], INPUT_PULLUP);

  showText("READY");
}

void loop() {
  static uint8_t last[4] = {HIGH, HIGH, HIGH, HIGH};
  static unsigned long lastDebounce[4] = {0,0,0,0};
  const unsigned long DEBOUNCE_MS = 30;

  for (int i = 0; i < 4; i++) {
    uint8_t now = digitalRead(BTN[i]);
    if (now != last[i]) {
      lastDebounce[i] = millis();     
      last[i] = now;
    }

    if ((millis() - lastDebounce[i]) > DEBOUNCE_MS) {
      static uint8_t stablePrev[4] = {HIGH, HIGH, HIGH, HIGH};
      if (stablePrev[i] == HIGH && now == LOW) {
        showText(LABEL[i]);
        Serial.print("Button "); Serial.print(i); Serial.println(" pressed.");
      }
      stablePrev[i] = now;
    }
  }
}