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
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
- 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.
- Solder sensor bodies, then bring out SIG / power / GND on rainbow wire with strain relief at the board edge.
- Map nets to silkscreen pins (D2 DHT11 data, D3 analog light in the hub firmware; see pin table below).
- 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
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.
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.
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:
-
Canonical (matches hub):
code/week09-s3-serial-env.ino, same print format as the integrated firmware’ssampleSensorsAndPush()serial block. -
Early bring-up:
code/week09-xiao-light-dht.ino, one-line English log; pin names follow ArduinoD2/D3aliases from an earlier harness test. -
Original full S3 hub source:
final-project-upload/s3-upload/src/main.cppandi2c_env_link.h— reads DHT11/light, packsTelemetryPacked, and writes it to WROOM address0x55. -
Original WROOM receiver source:
env_i2c_slave.cppandwroom-upload/src/main.cpp— receives the environment packet and draws humidity, temperature, and light on the screen. -
Original Pico/NanoStat input source:
pico-upload/src/main.cpp— reads the LMP91000/NanoStat plant-impedance circuit and printsSTAT,...lines for the S3 bridge.
| 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
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.
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_LIGHTto 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.
Scope baseline workflow we repeated every bench session
- Utility → Factory Setting → confirm cleared ghosts from whoever used the bench last.
- Single-channel captures hid CH2; encoder demos toggled CH2 back on for quadrature.
- CH1 coupling DC, time-base tuned so one–three cycles (or ramps) filled the display, volts/div avoiding clipping.
- Trigger parked inside the swing (~2.5 V for a 0–3.3 V logic-ish waveform) stabilised shots that looked “alive” otherwise.
- Optional probe compensation against the front-panel square-wave calibrator — dull tops or ringing overshoot meant tweaking trim caps until corners squared up.
- 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.
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.
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.
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.
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.
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.
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.