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. Measuring setup with bench power supply, multimeter, and the DUT.
Close-up of probes and DUT
2. Close-up of probes on the output device while adjusting voltage and current.
3. Watching how current changes as we sweep the supply for a small LED.
4. Gert’s demo: pushing an LED beyond its safe voltage until it fails.
Aftermath of an overdriven LED
5. Aftermath of the overvoltage test — a visibly damaged LED.

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. First wired prototype on the bench with the ESP32-S3 board and OLED.
Schematic overview
7. Schematic showing XIAO ESP32-S3, four buttons, and the SSD1306 OLED on I²C.
PCB layout render
8. PCB layout with button cluster, I²C header, and clear routing around the module.
CAM or drawing for machining
9. CAM / drawing view used to generate milling toolpaths.
Trace inspection on the screen
10. Inspecting isolation traces on screen before sending to the mill.
Board during milling
11. Board during milling with fine traces being cut.
Board after milling
12. Freshly milled board, ready for deburring and soldering.
Assembled board on wooden fish base
13. Assembled board mounted on a wooden “fish” base as a first mockup.
Holding the OLED module
14. OLED display wired and ready to test the four-button UI.
15. Four-button OLED demo — each press switches to a different “environment” label.

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;
    }
  }
}