Hardware Guide - Kush-Kelaiya22/imPress GitHub Wiki

Hardware Guide

What to buy, how to wire it, and what each board needs. Reference documents: docs/hardware/ and docs/reference/hardware.md.

Verification status: everything on this page comes from the build configuration and from builds measured on 2026-10-08. None of it has yet been checked on a running board.

Contents


What a classroom needs

Item Quantity Board
Gateway 1 per room ESP32-C6 with ≥ 8 MB flash
Hub 1 per room ESP32-S3 with 32 MB octal flash (the shipped image). PSRAM is optional
Student module 1 per student (≤ 300 per room) classic ESP32 with ≥ 4 MB flash, plus 5 push buttons; optionally a 128×64 SSD1306 OLED and an LED
Jumper wires 6 signals + ground between hub and gateway short (< 10 cm), each paired with a ground
Power a stable 5 V supply for the hub and gateway; batteries or USB for the modules
Network 2.4 GHz Wi-Fi with WPA2 or better, reaching the backend

Choosing boards

Role Chip Shipped firmware needs Notes
Gateway ESP32-C6 (RISC-V, 160 MHz, Wi-Fi 6 on 2.4 GHz) 8 MB flash, DIO mode for example an ESP32-C6-DevKitC-1 with 8 MB
Hub ESP32-S3 (2 × Xtensa LX7, 160 MHz) 32 MB octal flash (header and mode are set for it) the ESP32-S3-WROOM-2 family has octal flash. A quad-flash module needs the reduced-flash profile
Student module ESP32 (classic, 2 × LX6, 160 MHz) 4 MB flash, DIO mode the pin map assumes a classic ESP32 (GPIO 22 and 23 exist)

Why these chips:

  • Gateway: the C6 has Wi-Fi.
  • Hub: the S3 has the radio capacity to run the mesh, plus enough RAM for 300 students and the SPI buffers.
  • Student module: the classic ESP32 is cheap and has ESP-NOW.

Flash, RAM and partitions

Measured with idf.py size on ESP-IDF v6.1:

Gateway class_c6 Hub class_s3 Student student
App image 1,142,021 B 980,692 B 808,948 B
OTA slot 3,968 KB 4,096 KB 1,792 KB
Share of slot 28 % 23 % 44 %
Static RAM 41.5 % of DIRAM 59.9 % of DIRAM 21.9 % of DRAM

Partition tables (firmware/<project>/partitions.csv):

Project Layout Minimum flash Flash header / mode
class_c6 nvs 24 KB @0x9000 · otadata @0xF000 · phy_init @0x11000 · ota_0 3.875 MB @0x20000 · ota_1 3.875 MB @0x400000 8 MB 8 MB / DIO
class_s3 ota_0 4 MB @0x20000 · ota_1 4 MB @0x420000 16 MB (ends at 0x820000) 32 MB / octal
student ota_0 1.75 MB @0x20000 · ota_1 1.75 MB @0x1E0000 4 MB 4 MB / DIO

A repository test (firmware/tests/test_hardware_requirements.py) recomputes these minimums from the partition tables and sdkconfig, and fails if the documentation drifts.

PSRAM. The hub used to refuse to boot without PSRAM, although no code needs it: its routing table is a static array. Since v2.1 PSRAM is optional (CONFIG_SPIRAM_IGNORE_NOTFOUND=y). When PSRAM is present, large allocations may use it.

Wiring the hub to the gateway

flowchart LR
    subgraph S3["ESP32-S3 hub (SPI master)"]
        s12["GPIO12 SCLK"]
        s11["GPIO11 MOSI"]
        s13["GPIO13 MISO"]
        s10["GPIO10 CS"]
        s16["GPIO16 READY S3→C6 (out)"]
        s15["GPIO15 READY C6→S3 (in, pull-down)"]
    end
    subgraph C6["ESP32-C6 gateway (SPI slave)"]
        c2["GPIO2 SCLK"]
        c7["GPIO7 MOSI"]
        c6["GPIO6 MISO"]
        c10["GPIO10 CS"]
        c12["GPIO12 READY S3→C6 (in)"]
        c13["GPIO13 READY C6→S3 (out)"]
    end
    s12 --> c2
    s11 --> c7
    c6 --> s13
    s10 --> c10
    s16 --> c12
    c13 --> s15
Loading
Signal Hub (S3) GPIO Gateway (C6) GPIO Direction
SCLK 12 2 hub → gateway
MOSI 11 7 hub → gateway
MISO 13 6 gateway → hub
CS 10 10 hub → gateway
READY S3→C6 16 (output) 12 (input) hub → gateway
READY C6→S3 15 (input, pull-down) 13 (output) gateway → hub
GND GND GND a common ground is required

Wiring rules:

  1. Mode and clock: standard full-duplex SPI (mode 0) at 10 MHz by default (CONFIG_SPI_CLOCK_MHZ on the hub). That rate was validated on hand-wired boards.
  2. Keep it short: under 10 cm, with ground next to the signals.
  3. CRC errors: if the logs show Dropped C6 slot (error -2 …) or Dropped S3 slot (error -2 …), the wiring is too noisy: lower the clock. Raise it only on a PCB.
  4. The gateway loses its native USB port: GPIO12 and 13 on the C6 are its USB-Serial-JTAG pins, so using them as ready lines disables native USB. Flash and monitor the gateway through its UART bridge (console on GPIO16/17). Or move the ready lines, in both firmware/class_c6/main/config.h and firmware/class_s3/main/config.h.
  5. Pin authority: the pin definitions in those two config.h files are authoritative. Older wiring for quad mode also connected S3 GPIO14/9 to C6 GPIO3/4. Standard SPI doesn't use them, so those pins are free.

Building a student module

Function GPIO Notes
Button A 4 active-low; internal pull-up; falling-edge interrupt; 50 ms debounce
Button B 16
Button C 18
Button D 22
CONFIRM 23
Status LED 2 only if STUDENT_HAS_LED 1 in firmware/student/main/config.h
OLED SDA / SCL 8 / 9 SSD1306 128×64 at I²C address 0x3C, 400 kHz; only if STUDENT_HAS_DISPLAY 1
  • Wiring the buttons: connect each one between its GPIO and ground. No external resistors are needed.
  • Unavailable pins: a pin the flash bus owns is detected at boot and skipped (Button N (GPIOx) unavailable — skipping).
  • Other chips: the pin map targets the classic ESP32. GPIO 22–25 don't exist on the ESP32-S3, so building modules on another chip means editing config.h and CONFIG_IDF_TARGET.

Giving a module its student's identity (provisioning)

A new module has no identity: it shows NO ID and never joins the mesh. There are two ways to give it one.

With the buttons

Button Action
A next character
B previous character
C backspace
D clear
CONFIRM commit the character; the 10th commit saves the identity

Over USB serial: while the module is unprovisioned, type the 10-character enrollment number into the serial console.

Rules:

  • Format: exactly 10 letters or digits; the same rule as the backend.
  • Set once: a module refuses a different identity while it has one.
  • To re-assign a module: hold CONFIRM + C for 2 seconds at boot. That clears the identity and starts provisioning again.
  • Storage: the identity lives in NVS (enroll), so it survives reboots and firmware updates.

Power

  • Hub and gateway are mains-powered: 5 V over USB or a regulated supply. Wi-Fi transmit bursts draw about 300 mA peaks. A weak cable causes brown-out resets (Brownout detector was triggered), and the gateway then reports brownout as its reset reason (see Device Management).
  • Student modules: the battery percentage they report is currently fixed at 100 %; reading the ADC is not implemented. The gateway has an optional supply ADC (BATTERY_ADC_EN).

The reduced-flash hub profile

The shipped hub image needs 32 MB of octal flash. To run the hub on the far more common 8 MB quad-flash ESP32-S3, use this profile. It needs a rebuild and one serial flash: the bootloader, flash mode and partition table can't change over the air.

  1. Create firmware/class_s3/partitions_8mb.csv:
    # Name,   Type, SubType, Offset,   Size,     Flags
    nvs,      data, nvs,     0x9000,   0x6000,
    otadata,  data, ota,     0xf000,   0x2000,
    phy_init, data, phy,     0x11000,  0x1000,
    ota_0,    app,  ota_0,   0x20000,  0x3E0000,
    ota_1,    app,  ota_1,   0x400000, 0x3E0000,
  2. Create firmware/class_s3/sdkconfig.defaults.quad8:
    # CONFIG_ESPTOOLPY_OCT_FLASH is not set
    CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
    CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
    CONFIG_PARTITION_TABLE_CUSTOM=y
    CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_8mb.csv"
    CONFIG_SPIRAM_MODE_QUAD=y
  3. Delete sdkconfig, then build:
    idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.quad8" build
  4. Flash everything over serial, bootloader included:
    idf.py -p <port> flash

Status: it builds (image 1,022,400 B; headers declare 8 MB and quad-capable mode). It has not been booted on a board.

Two side effects:

  • Slot size: each slot is 3,968 KB, but the backend still allows 4,096 KB for hub images. That only matters for an image over 3.9 MB; today's is about 1 MB.
  • Profile matching: every later image for such a hub must be built with the same profile.

Capacity

Cost per student Value
Hub routing-table entry 32 B
Gateway online-set entry 11 B
300 students hub 9.6 KB, gateway 3.3 KB

Static RAM is not what limits a room. Radio airtime and the hub's 64-message forwarding queue are more likely to, when every student presses at once. Measure a full room's burst before relying on 300.

Bench checks still to do

These figures haven't been measured on boards: heap in use, button-to-dashboard latency, burst handling, mesh range and hop counts, power draw, OTA duration. The full checklist is on Testing and CI → Hardware validation.

⚠️ **GitHub.com Fallback** ⚠️