Hardware Guide - Kush-Kelaiya22/imPress GitHub Wiki
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.
- What a classroom needs
- Choosing boards
- Flash, RAM and partitions
- Wiring the hub to the gateway
- Building a student module
- Power
- The reduced-flash hub profile
- Capacity
- Bench checks still to do
| 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 |
| 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.
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.
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
| 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:
-
Mode and clock: standard full-duplex SPI (mode 0) at 10 MHz by default (
CONFIG_SPI_CLOCK_MHZon the hub). That rate was validated on hand-wired boards. - Keep it short: under 10 cm, with ground next to the signals.
-
CRC errors: if the logs show
Dropped C6 slot (error -2 …)orDropped S3 slot (error -2 …), the wiring is too noisy: lower the clock. Raise it only on a PCB. -
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.handfirmware/class_s3/main/config.h. -
Pin authority: the pin definitions in those two
config.hfiles 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.
| 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.handCONFIG_IDF_TARGET.
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.
-
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 reportsbrownoutas 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 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.
- 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,
- 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
- Delete
sdkconfig, then build:idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.quad8" build - 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.
| 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.
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.