Week 16 — System integration (Forest Spirit v1)
This week aligns with Fab Academy guidance on
system integration (see the 2026
Assignments and Assessment hub and the 2025 module text for
System integration → learning outcomes).
My individual assignment documents the first integrated version of
森之精灵 / Forest Spirit—the plant companion stack I can demo on a breadboard without pretending every
future sensor island is wired yet:
ASRPRO recognizes fixed Mandarin phrases from an offline table,
the Seeed XIAO ESP32‑S3 hub reads those events and (when WiFi is up) calls the
DeepSeek API, and the ESP32‑WROOM drives the
ILI9341-class TFT so assistant text and UI pages show up on screen.
Week 15 proved the display and HTML-to-TFT path; this week is where voice, cloud dialogue, and pixels
share one I²C bus discipline and a reproducible firmware tree in
code/week15-individual/final/.
Individual assignment
1) Task and motivation
Integration weeks are where separate subsystems either cooperate or fight each other. The v1 acceptance test I cared about was narrow but real:
- Say a phrase from the offline ASRPRO table (wake 灵葭 or a command slot)—not open microphone dictation.
- Let the S3 hub turn dialogue intents into an HTTPS call when the network is up.
-
See the result as UTF‑8 text on the ILI9341 through the WROOM five-page UI, driven over the
0x55 I²C TLV link documented in
DATA_FLOW.md.
That is intentionally smaller than the full paper sketch (Pico impedance, UNO motion, Tavily lookup)—those stay on the roadmap. v1 is voice menu → API → screen, with UART mirrors on each board so I can debug without guessing.
I also ran a controlled ESP‑to‑ESP I²C loop (§6) and spent time on display + bus hygiene when vertical stripes showed up after rewiring (§5)—both lessons feed the same three-board bench, not a separate science project.
2) What I refreshed — v1 data path
The signal chain below is what I actually flashed for the v1 demo. It extends the Week 15 TFT work but names each MCU’s job explicitly.
┌──────────────┐ 3-byte mailbox ┌──────────────────┐ HTTPS (online) ┌─────────────┐
│ ASRPRO V2.0 │ ─── I²C 0x56 ─────▶│ XIAO ESP32‑S3 │ ─────────────────▶│ DeepSeek │
│ offline ASR │ + UART 115200 │ integration hub │ │ API │
│ fixed phrases│ │ DHT11 + light │◀──────────────────│ assistant │
└──────────────┘ └────────┬─────────┘ │ text │
│ mic / speaker │ I²C TLV 0x55 │
│ vendor TTS ▼ │
│ ┌──────────────────┐ │
│ │ ESP32‑WROOM │◀───────────────────────────────┘
│ │ ILI9341 + FT6336 │
└───────────────────────────│ 320×240 UI pages │
└──────────────────┘
snid), not streaming ASR.
I²C ties the boards together: open-drain SDA/SCL, ~4.7 kΩ pull-ups to a
stable 3.3 V reference, common GND. On the XIAO,
D4/D5 map to the documented SDA/SCL pair (not “D4 = GPIO4” by habit—see
§5). The WROOM listens at 0x55; the voice mailbox uses
0x56 with three bytes per hit (flag + snid)—details in
DATA_FLOW.md.
Arduino-ESP32 “GPIO numbers” ≠ silk D# labels still bit me during bring-up; I now photograph silk plus the datasheet row whenever I breadboard.
3) Plan — v1 map vs. what I built this week
Before touching the bench I sketched a first-version system map for Forest Spirit. v1 in production is the centre column of that sketch; the Pico, UNO, and Tavily boxes stay future islands.
Three boards — who owns what in v1
-
ASRPRO — offline voice UI: Microphone in, speaker out, wake/command decoding inside the
Tianwen runtime. Each hit publishes
snidover the mailbox and prints the mapped Chinese phrase on UART—not raw audio to the host. -
XIAO ESP32‑S3 — hub: I²C master on D4/D5; polls
0x56, samples DHT11 + light sensor, calls DeepSeek for dialogue intents
(
snid ≥ 7when WiFi is up), pushes TLV packets to 0x55. - ESP32‑WROOM — display front-end: Week 15’s SPI → ILI9341 + FT6336 touch stack; renders five swipe pages and paints chat text from the hub.
Bench checklist (v1)
- Flash
wroom-display/, thens3-hub/(with localsecrets.h), then ASRPRO in Tianwen. - Wire shared I²C: XIAO D4/D5, WROOM GPIO19/21, ASRPRO PA2/PA3; pull-ups + GND.
- Say 灵葭 and a dialogue command; confirm Serial on each board and text on the TFT chat panel.
- If the panel stripes or touch glitches, fix power domains before blaming the LLM (§5).
4) ASRPRO (天问) — offline phrases and mailbox
The voice path runs on ASRPRO with the Tianwen (天问) toolchain—offline wake +
command slots, vendor audio resources, and hooks that only make sense once you accept their
asr.h / setup.h contract.
Why a mailbox: I needed one deterministic bridge from “chip heard phrase n” to “host MCU
can act on it,” without stuffing a full voice stack into the same firmware that already juggles display traffic. A
tiny I²C mailbox (flag + 16‑bit snid) keeps that boundary explicit.
What went wrong first: inside Tianwen’s block flow, hitting 生成模型 sat spinning
for a long time. I exported the vendor C++ example, merged phrase map and mailbox code in
Cursor, and compiled locally once the source matched vendor entry points
(ASR_CODE(), hardware_init()).
Bus-role note: I initially wanted ASRPRO as an I²C peripheral the S3 could poll. Tianwen did not
give me a workable slave binding, so the production v1 tree in
final/asrpro/ uses vendor
asr_i2c_slave.* with the S3 as master on the shared bench bus—the same three-byte layout either way.
The earlier master/slave pivot narrative is archived in
asrpro_forest_spirit_voice_test.cpp
for documentation.
5) ILI9341 bring-up — stripes, burn-in, and I²C power domains
Once v1 firmware was flashed, the panel sometimes showed vertical tree-like stripes across the whole image. I treated that as an integration failure, not a font bug: if the display bus is marginal, both touch and “API reply on screen” look broken even when DeepSeek returns 200 OK.
I reflashed the Week 15 screen-test firmware (patterns + touch readbacks only). Stripes still appeared, so I suspected panel persistence / burn-in from long bright demos. Resting the backlight cleared most ghosting; I still avoid harsh static white fields between sessions.
The durable mistake: I had tied 3.3 V to the display module supply and reused that same rail as the I²C pull-up reference. Under load the display’s current spikes droop the pull-up reference, which distorts I²C highs and confuses FT6336 reads and hub traffic together.
6) v1 demo — voice → API → ILI9341
The hero clip below is the v1 milestone: I say 灵葭, the module wakes and answers with vendor TTS; for dialogue commands the S3 calls DeepSeek when online and the WROOM paints the reply on the TFT. That matches the flow diagram in §2.
Firmware tree (reproduce v1):
-
code/week15-individual/final/README.md— three-board layout, wiring summary, upload order. -
wroom-display/— ILI9341 + FT6336, slave 0x55. -
s3-hub/— hub +llm_client(DeepSeek); copysecrets.h.example→secrets.h(gitignored). -
final/asrpro/— Tianwen C++ + mailbox. -
Wiring notes (Chinese):
i2c_wiring_notes_zh.txt.
7) Appendix — controlled ESP‑to‑ESP I²C sanity check
Before trusting the three-board stack, I ran a minimal master/slave loop (XIAO → WROOM) with chunked
UTF‑8 and a ring-buffer slave—same pull-up and GPIO lessons, zero ASRPRO in the loop. That work lives in
code/week16-individual/ and explains the Serial captures below.
Wire.endTransmission() looked like a software bug.
| Signal | XIAO ESP32‑S3 (master) | ESP32‑WROOM (slave) |
|---|---|---|
| SDA | GPIO 5 (from D4 silk on my board) | GPIO 19 |
| SCL | GPIO 6 (from D5 silk) | GPIO 21 |
| Parameters | Slave 0x55 in the sanity sketch; ~100 kHz; common GND; 3.3 V pull-ups only. | |
- i2c_master_esp32s3.ino
- i2c_slave_wroom.ino
- asrpro_forest_spirit_voice_test.cpp — earlier ASRPRO mailbox experiment.
8) Conclusion
Week 16, for me, is the first integrated Forest Spirit system: fixed offline phrases on
ASRPRO, cloud dialogue on the S3 when WiFi allows, and readable answers on the ILI9341. Packaging
is still open—Fab Academy allows logging integration before enclosure glamour—but the bench story is auditable:
photos, stripe video, FINAL firmware tree, and DATA_FLOW.md.
The surprises were mostly analog-adjacent: panel persistence once, but the recurring comb pattern traced to sharing display VCC with I²C pull-up reference. The controlled two-ESP I²C exercise paid off separately—it taught pull-ups and XIAO pinmux before I debugged voice and API together.
Next (wildcard / v2): open-ended speech for the three Forest Spirits is documented on
Week 17 (in progress)—that path retires the fixed snid table while the v1
bench wiring lessons stay valid here.