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.
Process
- Connected the XIAO to the breadboard and completed power, ground, I²C, and DHT data runs.
- Ran a small I²C scanner sketch to confirm the OLED responded at 0x3C before writing graphics code.
- Wrote a minimal U8g2 test that centered the string Fab Academy — proves the bus, controller choice (SSD1306), and font pipeline.
- Installed DHT sensor library and U8g2 in the Arduino IDE Library Manager; resolved a first build error when U8g2 was not yet present.
- 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.
0x3C.
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), callsu8g2.begin(), sets I²C bus to 400 kHz, enables UTF‑8 for Chinese error strings, anddht.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 forTEMPandHUMI.
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:
- Group assignment: measure the power consumption of an output device.
- 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
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.
Experiment 2 — Open-circuit baseline
We disconnected/opened the loop to verify near-zero baseline current and confirm wiring behavior.
Experiment 3 — Dynamic / repeated operation
We repeated operation and observed current/power changes across multiple states to verify consistency.
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.