Week 9: Input devices

Input devices week. The group work looked at what signals actually do on copper and wire (slow ramps, noisy edges, decoded buses). For my individual assignment I used the JLCPCB panel from Week 8 (Seeed XIAO ESP32-S3 plus a NanoStat footprint) but only read two inputs this week: a light sensor and a DHT11 for humidity / temperature. NanoStat stays unintegrated until a later week. Sensors sit on long rainbow harnesses for final-project placement; readings go to serial and a small display.

Individual assignment: input devices on my PCB

Fab Academy Input Devices asks me to measure something: add a sensor to a microcontroller board I designed and read it. The module FAQ also says the assessed interface should not stay on a breadboard, so this week I used the board provisions from my electronics-design work: MCU pads, sensor signal/power pads, and off-board harness points. My first idea was to mill the board on the Week 8 CNC workflow, but the sensor and XIAO solder pads were small enough that I was worried about lifting copper while hand-soldering. For the tested version I therefore used the JLCPCB-fabricated board documented in Week 8, then used long rainbow harnesses only where the final project needs the transducers away from the logic board.

Board context (Week 8) vs scope (Week 9)

The fabricated board from Week 8 is meant to host two compute / sensor blocks on one panel: a soldered Seeed XIAO ESP32-S3 and a mechanical landing zone for a NanoStat module (ESP32 Pico–class footprint, reserved in revision 2 for final-project plant impedance work). The important Week 9 point is that the board already provides sensor connections: 3.3 V, GND, one DHT11 digital data net, and one light-sensor ADC net routed to the XIAO side.

Week 9 evidence is deliberately narrow: prove the XIAO can read light and humidity / temperature that I soldered on the same PCB. The later final-project upload also adds the Pico/NanoStat plant-impedance node; I include those original source files below so the full input-device data path is visible, while the graded Week 9 sensor read is the XIAO DHT11 + light circuit.

1. Task

For my birthday-spirit final project I will need to know how bright it is and how warm / humid the air is near the plant, but the MCU will not sit in the same enclosure as the sensors. This week’s job was therefore: on the Week 8 carrier, land and read only those two input devices through the XIAO ESP32-S3, prove the signals in firmware (serial first, then a small UI while I tune sensor placement). The NanoStat site on the board stays unused for now.

  • Humidity / temperature (in scope): DHT11 on D2 (GPIO3), single-wire digital bitstream.
  • Light (in scope): photoresistor-style divider on D3 (GPIO4) → ADC → % of full scale.
  • MCU (in scope): XIAO ESP32-S3 soldered on the Week 8 PCB, USB‑C, 3.3 V GPIO.
  • NanoStat (out of scope this week): footprint reserved on the panel; no module mounted, no firmware, no layout changes for Week 9.
  • Mechanical intent: light and DHT11 on rainbow ribbon so they can mount elsewhere later; the PCB stays the hub (XIAO + future NanoStat side-by-side).

Connection diagram

This is the same system layout I later used in Week 11, expanded here to make the input-device connections explicit. For Week 9 the key connections are the two sensors on the XIAO board: DHT11 data on D2 / GPIO3 and the light divider tap on D3 / GPIO4. Power and ground are shared across the boards whenever data lines cross between modules.

flowchart LR
  DHT["DHT11 humidity + temperature sensor
VCC 3.3 V / GND / DATA"] Light["Light sensor divider
3.3 V - LDR - ADC tap - resistor - GND"] Xiao["Week 8 JLCPCB sensor hub
Seeed XIAO ESP32-S3"] Pico["ESP32 Pico + NanoStat
plant impedance input"] Wroom["ESP32-S3 WROOM UI board
ST7789 + FT6336"] Display["Environment / Plant pages
live values on screen"] Uno["Arduino UNO motion node
later final-project output"] DHT -- "DATA -> XIAO D2 / GPIO3
single-wire timed digital signal" --> Xiao Light -- "ADC tap -> XIAO D3 / GPIO4
12-bit analogRead()" --> Xiao Xiao -- "I2C master: SDA D4 / GPIO9
SCL D5 / GPIO8, addr 0x55" --> Wroom Pico -- "UART1 115200 8N1
Pico TX GPIO1 -> XIAO D7 / GPIO44" --> Xiao Wroom --> Display Wroom -- "UART1 9600 8N1
WROOM G45 TX / G35 RX" --> Uno
Figure I-0: Input-device connection diagram. DHT11 and light are the Week 9 measured inputs on the board I made; Pico/NanoStat and WROOM are shown because the original final-upload source files use the same S3 hub to move sensor data through the final system.

2. Learning

I re-read the academy sensing notes and the DHT11 timing diagram: the humidity chip is not “I²C simple.” It needs a strict start pulse and bit sampling on one data pin. The light path is the opposite problem: essentially a DC voltage from a divider, so I care about reference voltage, ADC resolution, and keeping the sense node away from switching noise from the USB front-end. Group work this week ( below) reinforced that slow analog looks boring on a scope until you zoom in, while one-wire sensors fail silently if the wire is loose. Both lessons mattered when I moved from jumper tests to soldered leads.

Working principle of the sensors

The DHT11 is a digital humidity/temperature sensor. Internally it measures relative humidity and temperature, then returns calibrated bytes over a single data line. In my code I do not manually time every pulse; the Arduino DHT library sends the start pulse on GPIO3, samples the returned bitstream, checks whether the values are valid, and gives me readHumidity() and readTemperature(). If either value is NaN, the program treats that sample as a failed DHT read instead of displaying a fake number.

The light sensor is read as an analog voltage divider. The photoresistor's resistance changes with light, so the divider tap voltage moves up or down as the light level changes. The XIAO ESP32-S3 reads that tap on GPIO4 with a 12-bit ADC. The firmware keeps the raw ADC value for debugging and converts it to a simple percentage using 100.0 * adc / 4095.0. This is not a calibrated lux meter; it is a repeatable project-level brightness input for deciding whether the plant area is dark or bright.

3. Plan

  1. Use the Week 8 JLCPCB board as-is (XIAO pad + NanoStat keep-out). Populate only XIAO, light front-end, and DHT11; leave the NanoStat area empty.
  2. Solder sensor bodies, then bring out SIG / power / GND on rainbow wire with strain relief at the board edge.
  3. Map nets to silkscreen pins (D2 DHT11 data, D3 analog light in the hub firmware; see pin table below).
  4. Flash a minimal XIAO read loop (light + DHT only) → confirm serial → mirror values on a display for placement checks.

4. Build and readout

Assembled PCB

Custom PCB with Seeed XIAO ESP32-S3 soldered on and rainbow wires to light and humidity sensors
Figure I-1: Week 8 JLCPCB with XIAO ESP32-S3 and light + DHT11 harnesses; NanoStat landing zone visible but not populated this week.

Soldering the light sensor and bring-out

I hand-soldered the light sensor legs to the copper I routed for the divider, checked continuity with a meter, then added the rainbow bundle so the sensor head can live off-board in the final mechanical layout.

Close-up of light sensor soldered to the PCB pads
Figure I-2: Light sensor soldered to the carrier.
Diagram or photo showing rainbow wires brought out from the light sensor
Figure I-3: Light sensor harness: which pad goes to 3.3 V, GND, and the ADC node.

Soldering the DHT11 and bring-out

The DHT11 got the same treatment: anchor on the PCB, long leads for placement flexibility. I labelled the ribbon ends in my notebook so I would not swap data and power during final assembly.

DHT11 humidity sensor soldered on the custom PCB
Figure I-4: DHT11 soldered on the board.
DHT11 with rainbow wires brought out for remote mounting
Figure I-5: DHT11 bring-out: data wire length matters for timing.

Firmware and pin map

Week 9 evidence starts with serial print on the XIAO hub. I trimmed the sensor loop from my S3上传程序 bench tree so the assignment sketch focuses on input reading first; the full upload then packages the same values and sends them by I²C to the WROOM UI. Source files in the repo:

Signal XIAO silkscreen GPIO Type
DHT11 data D2 GPIO3 Single-wire digital
Light level (divider tap) D3 GPIO4 Analog read → % of full scale (12-bit ADC)

Every ~2.5 s the hub reads dht.readHumidity() / readTemperature(), analogRead() on the light pin, and prints a banner block at 115200 8N1 (USB CDC on the XIAO S3). Example:

======== XIAO S3 本地传感 ========
DHT11  湿度 45.0 %RH  |  温度 23.5 C
光强(ADC 满幅) 12.3 %  |  ADC=504  mV=405

NaN from the DHT usually means a loose data wire or a timing glitch: I re-seat D2 before blaming library code.

How the final-upload program reads and moves the data

In the full S3上传程序, the same read loop runs inside sampleSensorsAndPush(). It samples humidity and temperature, checks !isnan(), reads analogRead(kPinLight), converts the ADC count to a percentage, and then calls pushTelemetry(). That function stores the values in an 8-byte TelemetryPacked frame: magic byte 0xA5, command 0x01, DHT-ok flag, rounded RH percent, temperature multiplied by 10, and light percent multiplied by 10. The S3 then writes the frame to the WROOM over I²C address 0x55.

On the WROOM side, env_i2c_slave.cpp receives bytes in an I²C interrupt, pushes them into a ring buffer, and lets the main loop parse complete frames. It rejects impossible values such as RH above 100% or light above 1000 tenths of a percent, updates an EnvFromS3 snapshot, and increments a data-version counter so the UI redraws only when the sensor data actually changes.

MCU reads sensors successfully

Bench photo showing XIAO on PCB with sensors connected and serial or debug context
Figure I-6: Bring-up moment: XIAO on my PCB reading both sensors after soldering and harness routing.

Displaying the same signals on a UI

Serial is enough for grading logic, but I also wanted a face-level check while moving the sensor heads around. I ran I²C from the XIAO on the PCB to a small OLED module on a breadboard (only the display stayed on headers; sensors and MCU are on copper). The screen shows humidity, temperature, and light percentage together.

Rainbow wires from custom PCB to a breadboard-mounted OLED display
Figure I-7: PCB hub to breadboard display: SDA/SCL and power from the XIAO side of the build.
OLED screen showing humidity temperature and light level readings
Figure I-8: Live UI: humidity, temperature, and light on one screen (same quantities as serial).

Problems I hit

  • DHT11 NaN after lengthening wires: the one-wire protocol is edge-time sensitive. Re-soldering the data pin and keeping that conductor shorter than the power pair fixed most failures.
  • Light reading drifts with supply: dividing against 3.3 V means USB sag shows up as a few percent shift; for this week I log raw ADC and percent. Later I may calibrate against a known lux source.
  • Pin mix-ups: always match PIN_DHT / PIN_LIGHT to the silkscreen rows before blaming library code.

5. Conclusion

I can now measure ambient light and RH / temperature on the Week 8 carrier, with the XIAO and both input interfaces on fabricated copper, not a breadboard shim for those sensors. The panel still has room for NanoStat, but that is intentionally out of Week 9 scope; I did not revise the board or start impedance firmware yet. The rainbow harnesses are a deliberate mechanical choice for the final project layout, not a workaround for skipping PCB work.

Next steps for input sensing: tuck the light and DHT heads into the plant enclosure and keep this read loop. Next steps for the same PCB: mount and wire the NanoStat in a later week when I merge plant impedance with the hub firmware (e.g. week15 … s3-hub).

Design files: KiCad project: Week 6 electronics design; fabricated panels: Week 8 electronics production. Source: week09-s3-serial-env.ino (trimmed assignment sketch); week09-xiao-light-dht.ino (early); original final-upload files: S3 hub, WROOM I²C receiver, WROOM UI, Pico/NanoStat input. Group reflection: group assignment on this page (scope / LA practice).

Group assignment (Chaihuo Makerspace)

The Fab brief asked us to probe input-device analog levels and digital waveforms with a multimeter, oscilloscope, and logic analyzer, and write down what we saw, not to polish a product. Our cohort reused the Seeed Grove ecosystem on the input side and treated every screenshot as lab notebook evidence.

A fuller regional write-up with the same structure lives on the Fab Academy site: Week 9, Group Assignment: Input Devices (Fab26 Chaihuo). What follows here is our mirror: English notes and media we actually captured in the room.

How we thought about input devices

We kept reminding ourselves that sensors translate physics into voltage or timed bits:

  • Analog inputs land near steady DC when things move slowly (wiper, LDR divider), but still carry noise and settling behaviour worth measuring.
  • Digital inputs can be single wires with bounce, quadrature pairs, or framed packets — scope vs. logic analyzer depends on whether we cared about shape or meaning.

Lab gear we leaned on

  • Digital multimeter (DT-660B): rails, continuity, slow-moving sensor DC while someone tweaks the knob.
  • Oscilloscope (OWON EDS102 CV): 100 MHz, 1 GSa/s, two analog channels; we avoided autoset so each knob stayed meaningful.
  • Logic analyzer (Alientek DL16): sixteen channels, buffer captures ≥50 MSa/ch when decoding UART/I²C.

Rule we drilled until it hurt: tie scope/LA ground to circuit ground before touching signal probes. Floating grounds gave nonsense traces once; almost-short anxiety convinced everyone on the second lap.

Devices we characterised together

Device Principle Signal type Instrument
Grove rotary angle sensor Potentiometer Analog DC (slow) DMM + scope
Grove button Mechanical contact Single-ended digital Scope
Grove rotary encoder Quadrature switches Two square waves Scope (CH1+CH2)
Grove RTC (DS1307) I²C clock IC I²C Logic analyzer
Grove I²C OLED (SSD1306) Display updates I²C traffic reference Logic analyzer
Grove GPS (Air530) NMEA UART Serial @9600 8N1 Logic analyzer + host capture

Grove four-pin harness sanity check

Grove cables bundle GND, VCC, SIG1, SIG2. When we improvised jumper spaghetti we caught classmates swapping power onto signal pins — a quick visual check before powering up beat hours of mystery debugging.

Correct Grove jumper routing with labeled pins
Wiring we powered up happily.
Incorrect Grove jumper routing warning layout
Example mistake layout: we did not energise boards wired like this.

Scope baseline workflow we repeated every bench session

  1. Utility → Factory Setting → confirm cleared ghosts from whoever used the bench last.
  2. Single-channel captures hid CH2; encoder demos toggled CH2 back on for quadrature.
  3. CH1 coupling DC, time-base tuned so one–three cycles (or ramps) filled the display, volts/div avoiding clipping.
  4. Trigger parked inside the swing (~2.5 V for a 0–3.3 V logic-ish waveform) stabilised shots that looked “alive” otherwise.
  5. Optional probe compensation against the front-panel square-wave calibrator — dull tops or ringing overshoot meant tweaking trim caps until corners squared up.
  6. Measure soft-keys dumped automated freq/VPP/duty readouts so we weren’t counting divisions by thumb.

Analog: rotary angle sensor

The Grove module is a 10 kΩ pot wired rail-to-rail with the wiper exposed. At 3.3 V we traced ~0.00 V → ~3.28 V end stops with the handheld meter — smooth motion meant no cracked carbon track surprises.

On the scope we slowed time-base (~500 mV/div vertical, ~200 ms/div horizontal) so knob rotations drew ramps rather than pops. Result: essentially DC plus tens-of-millivolt ripple sitting still — negligible next to a 10-bit ADC LSB (~3.2 mV here). Transitions stayed buttery, so firmware filtering stayed optional for this sensor class.

Scope clip: rotary sweep proving slow ramps stayed quiet aside bread noise.

Analog: faster-changing waveform sanity capture

We also chased an analog output whose voltage visibly danced inside one sweep window so we could practise triggering slightly above idle — resting traces jitter otherwise even when hardware behaved.

Second analog exercise — trigger discipline mattered more than probe fancy-ness.

Digital: button on a pulled-up GPIO

We moved the probe to the microcontroller pin (XIAO ESP32-C3, internal pull-up enabled) so we witnessed exactly what firmware sampled — idle sat ~3.3 V, presses slammed low, and mechanical bounce showed as rapid chatter before settling — textbook reason we debounce in software or with RC networks.

The idle-high rail picked up tiny USB ripple yet stayed confidently above CMOS “recognized-high”; documenting ripple beats pretending rails are textbook flat lines.

Oscilloscope capture of GPIO with switch bounce
Single-channel capture: bounce visible on the falling edge.

Digital: rotary encoder quadrature

Two square waves ~90° apart: whichever channel edges first spells direction. Each detent produced one clean A/B cycle — exactly what interrupt-driven decoders expect.

Dual-trace clip: phase relationship flips when we reversed rotation.

Protocol: I²C (RTC + OLED reference)

DS1307 at address 0x68 shared the bus with an SSD1306 OLED we already trusted. The XIAO master polled once per second; logic analyzer inputs were D0→SCL, D1→SDA with decoder set to I²C.

Clean captures showed clocks bursting ~100 kHz, data toggling only while SCL low per spec, decoder listing address byte, R/W, register pointer, seven time-bytes with ACKs. OLED-heavy segments stretched transactions dramatically — instant illustration why bus capacitance and pull-ups matter: long loose wires without pull-ups rounded edges until decoders threw framing errors; restoring 4.7 kΩ-ish pull-ups at 3.3 V put teeth back on edges.

Buffer-mode capture: decoder overlay while OLED + RTC shared SCL/SDA.
Additional timing slice while we dissected RTC read bursts — handy when correlating to firmware logs.

Protocol: UART GPS (Air530 @9600 baud)

TX idles high, start bit dives low, eight data bits LSB-first, stop bit releases high — classic 10-bit ASCII frames. Indoors we still saw text streaming even when fixes were garbage (empty fields / zeros); outdoors $GPRMC / $GPGGA sentences filled with real coordinates once the antenna saw sky.

We measured narrowest pulse width (~104 µs) as baud sanity — inverse equals ~9600 baud — handy cheat when baudrate stickers peel off mystery UART peripherals.

Indoor host-side capture — sentences present even without trustworthy satellites yet.

What stuck with us

  • Match instrument to question: scopes answer morphology; analyzers answer semantics.
  • Ground first: every “broken probe afternoon” traced to grounding mistakes or swapped Grove legs.
  • Trigger smart: jitter blamed on hardware turned into textbook triggering drills once we level-shifted thresholds inside swing.
  • I²C pull-ups stay mandatory classroom lore: rounding rising edges visually predicts protocol flake before firmware guesses wrong.
  • Log volts/div, time/div, trigger voltage beside screenshots — future-us retracing steps thanks past-us for scribbling metadata on stickers beside HDMI grabs.