Week 10 — Output devices & power measurement

Fab Academy Output Devices week: group power measurement, and an individual output device on the microcontroller board you designed. My group page documents the bench workflow below; here I drive a 128×64 I²C OLED (SSD1306) from a Seeed XIAO ESP32‑S3, showing humidity, temperature, and heat index (“体感”, computeHeatIndex) using the same DHT11 from Week 9. PCB routing was not ready yet, so this is the same interim breadboard bring-up as in Week 9 — the academy FAQ states the assessed build should not stay on breadboard; I will move these nets to the fabricated board when the cut is fixed.

Individual assignment

I extended the Week 9 breadboard setup with an SSD1306 OLED so the same humidity and temperature numbers also render graphically. Practically that meant proving I²C first (scanner sketch), then layering U8g2 on top of the DHT code. The first build failed once because the graphics library was not installed yet—small, but worth logging because it is the kind of friction that wastes an evening if you assume only your own typo is wrong. The assessed board is still pending; when the PCB is stuffed I will re-shoot the wiring on copper.

Per the Output Devices checklist, this section documents how I controlled a graphical output from firmware: an OLED is more than a single LED, satisfies the “go further than one LED” guidance, and needs I²C initialization, fonts, and a render loop distinct from Week 9’s serial-only prints.

Hardware (interim breadboard)

  • MCU: Seeed Studio XIAO ESP32‑S3 (3.3 V logic, USB‑C).
  • Output: 128×64 monochrome OLED, SSD1306 over I²C (address 0x3C on this module).
  • Sensor (input, Week 9): DHT11 module — single-wire data, provides RH and °C for heat-index calculation.
Breadboard with XIAO ESP32-S3, DHT11 module, and 12864 OLED wired for I2C and data
Figure I-1: Breadboard wiring — XIAO, DHT11, and 128×64 OLED (interim until the custom PCB is usable).
Seeed XIAO ESP32-S3 pin reference diagram
Figure I-2: XIAO ESP32‑S3 pin reference (checking D4/D5/D8 silkscreen vs GPIO numbers in code).

Process

  1. Connected the XIAO to the breadboard and completed power, ground, I²C, and DHT data runs.
  2. Ran a small I²C scanner sketch to confirm the OLED responded at 0x3C before writing graphics code.
  3. Wrote a minimal U8g2 test that centered the string Fab Academy — proves the bus, controller choice (SSD1306), and font pipeline.
  4. Installed DHT sensor library and U8g2 in the Arduino IDE Library Manager; resolved a first build error when U8g2 was not yet present.
  5. Developed the full layout in Cursor (heat index large, temperature and humidity lines below), then compiled and uploaded with the Arduino IDE targeting the XIAO ESP32‑S3.
Serial monitor or tool output showing I2C device at address 0x3C
Figure I-3: I²C scan — OLED at 0x3C.
OLED display showing Fab Academy text test pattern
Figure I-4: First graphics proof — “Fab Academy” on the 128×64 panel.
Arduino IDE Library Manager installing DHT sensor library
Figure I-5: Installing the DHT library.
Arduino IDE Library Manager installing U8g2 OLED library
Figure I-6: Installing U8g2.
Compile error in Arduino IDE due to missing U8g2 include
Figure I-7: First compile failure — missing U8g2 (fixed after install).
Arduino IDE showing successful compile and upload to XIAO ESP32-S3
Figure I-8: Successful compile and upload.
OLED showing HEAT INDEX in large digits plus TEMP and HUMI lines
Figure I-9: Running firmware — heat index (large), dry-bulb temperature and RH (small lines), with a drawn degree ring + “C” glyph where needed.

Pin map (this sketch)

  • DHT11 data: XIAO D8 (ESP32‑S3 GPIO7).
  • OLED I²C: SDA → D4 (GPIO5), SCL → D5 (GPIO6).
  • If your module uses SH1106 instead of SSD1306, swap the U8g2 constructor accordingly — layout differs slightly (column offset).

How the code works

  • setup() starts serial (115200 baud), calls u8g2.begin(), sets I²C bus to 400 kHz, enables UTF‑8 for Chinese error strings, and dht.begin().
  • loop() waits 2 s (DHT11 timing), reads RH and °C; on NaN it draws a short Chinese fault screen and logs a wiring hint.
  • computeHeatIndex(t, h, false) returns heat index in °C (same helper as Week 9 serial demo).
  • Drawing uses U8g2’s display buffer: title “HEAT INDEX”, large numeral from logisoso22, custom hollow circle + “C” for degrees, then two centered 6×10 lines for TEMP and HUMI.

Source sketch

Original file in repo: code/week10-xiao-dht11-oled.ino (Arduino IDE / Arduino‑ESP32 core).

/**
 * XIAO ESP32S3:
 * - D8:DHT11(GPIO7)
 * - D4/D5:12864 OLED I2C(SDA/SCL → GPIO5/GPIO6)
 *
 * 屏驱:U8g2 SSD1306。
 * 摄氏度:空心圆环 + “C” 表示 °C。
 * 若为 SH1106 屏,需改用 U8g2 的 SH1106 构造器。
 */

#include <Arduino.h>
#include <U8g2lib.h>
#include <DHT.h>

#ifndef D8
#define D8 7
#endif
#ifndef D4
#define D4 5
#endif
#ifndef D5
#define D5 6
#endif

#define SENSOR_DATA_PIN D8
#define DHTTYPE DHT11

#define OLED_SDA D4
#define OLED_SCL D5
#define SCREEN_W 128
#define SCREEN_H 64

DHT dht(SENSOR_DATA_PIN, DHTTYPE);

/* rotation, reset, SCL, SDA */
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, OLED_SCL,
                                         OLED_SDA);

/** 大号数字 + 空心圆(度)+ “C”,水平整体居中 */
static void drawBigValueDegreeC(const char *numStr, u8g2_uint_t baselineY) {
  const uint8_t *numFont = u8g2_font_logisoso22_tf;
  const uint8_t *cFont = u8g2_font_helvB12_tf;
  const u8g2_uint_t ringR = 2;

  u8g2.setFont(numFont);
  u8g2_uint_t nw = u8g2.getStrWidth(numStr);
  u8g2.setFont(cFont);
  u8g2_uint_t cw = u8g2.getStrWidth("C");

  const u8g2_uint_t gapNumDot = 3;
  const u8g2_uint_t gapDotC = 2;
  u8g2_uint_t total =
      nw + gapNumDot + (2 * ringR + 1) + gapDotC + cw;
  u8g2_uint_t left = (SCREEN_W - total) / 2;

  u8g2.setFont(numFont);
  u8g2.drawStr(left, baselineY, numStr);
  u8g2_uint_t degCx = left + nw + gapNumDot + ringR;
  u8g2_uint_t degCy = baselineY - 15;
  u8g2.drawCircle(degCx, degCy, ringR);
  u8g2.setFont(cFont);
  u8g2.drawStr(left + nw + gapNumDot + 2 * ringR + gapDotC + 1, baselineY,
               "C");
}

/** 更小字号:TEMP 行,空心圆 + C,整体居中 */
static void drawSmallTempLine(const char *asciiPrefixValue, u8g2_uint_t baselineY) {
  const uint8_t *font = u8g2_font_6x10_tf;
  const u8g2_uint_t ringR = 2;

  u8g2.setFont(font);
  u8g2_uint_t w1 = u8g2.getStrWidth(asciiPrefixValue);
  u8g2_uint_t cw = u8g2.getStrWidth("C");
  u8g2_uint_t total = w1 + 3 + (2 * ringR) + cw;

  u8g2_uint_t x0 = (SCREEN_W - total) / 2;
  u8g2.drawStr(x0, baselineY, asciiPrefixValue);
  u8g2_uint_t cx = x0 + w1 + 2 + ringR;
  u8g2_uint_t cy = baselineY - 7;
  u8g2.drawCircle(cx, cy, ringR);
  u8g2.drawStr(x0 + w1 + 2 + 2 * ringR + 2, baselineY, "C");
}

/** 更小字号:HUMI 行,整体居中 */
static void drawHumiLine(const char *asciiLine, u8g2_uint_t baselineY) {
  u8g2.setFont(u8g2_font_6x10_tf);
  u8g2_uint_t w = u8g2.getStrWidth(asciiLine);
  u8g2.drawStr((SCREEN_W - w) / 2, baselineY, asciiLine);
}

static void showSensorError(void) {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy12_t_gb2312);
  u8g2.drawUTF8(24, 30, "DHT 读取失败");
  u8g2.drawUTF8(32, 48, "检查 D8");
  u8g2.sendBuffer();
}

static void showReadings(float tC, float rh, float heatC) {
  char numbuf[16];
  char humibuf[24];
  char tempbuf[24];

  u8g2.clearBuffer();

  u8g2.setFont(u8g2_font_helvB10_tf);
  const char *title = "HEAT INDEX";
  u8g2_uint_t tw = u8g2.getStrWidth(title);
  u8g2.drawStr((SCREEN_W - tw) / 2, 12, title);

  snprintf(numbuf, sizeof(numbuf), "%.1f", heatC);
  drawBigValueDegreeC(numbuf, 38);

  /* 比原 wqy12 更小一号;两行行距在原 10px 基础上 +1 */
  snprintf(tempbuf, sizeof(tempbuf), "TEMP: %.1f", tC);
  drawSmallTempLine(tempbuf, 51);

  snprintf(humibuf, sizeof(humibuf), "HUMI: %.1f %%", rh);
  drawHumiLine(humibuf, 62);

  u8g2.sendBuffer();
}

void setup() {
  Serial.begin(115200);
  unsigned long t0 = millis();
  while (!Serial && millis() - t0 < 4000) {
    delay(10);
  }

  u8g2.begin();
  u8g2.setBusClock(400000);
  u8g2.enableUTF8Print();

  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy12_t_gb2312);
  u8g2.drawUTF8(40, 36, "启动中...");
  u8g2.sendBuffer();

  dht.begin();
  Serial.println();
  Serial.println(F("XIAO ESP32S3 — DHT11 D8, U8g2 OLED D4/D5"));
  Serial.printf("DHT type: %s\n\n", DHTTYPE == DHT11 ? "DHT11" : "DHT22");
}

void loop() {
  delay(2000);

  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(h) || isnan(t)) {
    Serial.println(
        F("[err] DHT read fail — wiring / DHT type / pull-up"));
    showSensorError();
    return;
  }

  float hi = dht.computeHeatIndex(t, h, false);
  Serial.printf("RH: %.1f %%  T: %.1f C  HI: %.1f C\n", h, t, hi);
  showReadings(t, h, hi);
}

Problems and fixes

  • Missing U8g2: first build failed until the library was installed via Library Manager (Figure I-7).
  • Wrong display constructor: if the panel is SH1106 but code uses SSD1306, the image is shifted or garbage — confirm part and constructor.
  • DHT read errors: NaN usually means data pin mismatch, weak pull-up, or insufficient delay; the sketch shows “检查 D8” on screen to match this wiring.

Next step for academy compliance

When the routed PCB is assembled, I will move the OLED and DHT nets off the breadboard, repeat the I²C scan and display test on the fabricated board, and swap the hero photo for a soldered hero shot as required by the assessment notes.

Group assignment

Guangzhou (Chaihuo) — group documentation: measuring the power consumption of output devices.

Week 10 assignment:

  1. Group assignment: measure the power consumption of an output device.
  2. Individual assignment: add an output device to your own board and program it.

Objective

We measured the power behavior of a simple output load with a bench supply and multimeter, and documented a repeatable method that can be reused in individual projects.

Instruments and setup

  • Bench DC supply (CV mode)
  • Digital multimeter
  • Output load fixture
  • Alligator clips and test leads
Week 10 bench setup overview
Figure 1. Full bench setup with supply, meter, and test fixture.
Week 10 wiring close-up
Figure 2. Close-up of the wiring and fixture connection.

Calculation method

Power is calculated with:

P (W) = V (V) × I (A)

Current can be cross-checked with Ohm's law when resistor voltage is measured:

I = V / R

Experiment 1 — Device ON state

We energized the load and recorded voltage/current from the bench supply and multimeter after readings stabilized.

Week 10 team operation
Figure 3. Team operation during ON-state measurement.
Meter reading example 1
Figure 4. Meter/supply readings under load.
Meter reading example 2
Figure 5. Additional ON-state reading snapshot.

Experiment 2 — Open-circuit baseline

We disconnected/opened the loop to verify near-zero baseline current and confirm wiring behavior.

Zero current baseline example
Figure 6. Open-circuit baseline (current near zero).

Experiment 3 — Dynamic / repeated operation

We repeated operation and observed current/power changes across multiple states to verify consistency.

Team operation close view
Figure 7. Repeated operation test and observation.
Meter reading example 3
Figure 8. Dynamic-state reading snapshot.
Load working state test
Figure 9. Output working-state verification under power.

Summary table

Condition Observation Conclusion
Load ON Current and power rise from baseline Output device consumes measurable power under operation
Open circuit Current near zero Baseline behavior is validated
Repeated/dynamic test Readings change with operating state Method is stable and reusable for future tests

Result summary

  • The workflow successfully captured state-dependent current and power changes.
  • Open-circuit baseline and loaded conditions were both observed.
  • The documented setup is suitable as a reusable template for Week 10 individual work.