# Hardware

## Parts

- An ESP32-C6 dev board (e.g. ESP32-C6-DevKitC-1). Needs 8MB flash --
  see [`firmware/sdkconfig.defaults`](../firmware/sdkconfig.defaults) and
  [`firmware/partitions.csv`](../firmware/partitions.csv) if yours differs.
- [Waveshare 7.3" E Ink Spectra 6 (E6) panel](https://www.waveshare.com/7.3inch-e-paper-hat-e.htm) --
  800x480, 6-color, SPI.
- A USB cable for flashing/power.

## Wiring

The panel connects over SPI plus three control lines (data/command, reset,
busy). Defaults below match the reference build and are set in
[`firmware/components/epd7in3e/Kconfig`](../firmware/components/epd7in3e/Kconfig) --
override via `idf.py menuconfig` under **E-Paper Display (epd7in3e)
Configuration** if your wiring differs.

| Panel pin | ESP32-C6 GPIO | Kconfig option    |
| --------- | ------------- | ----------------- |
| CLK       | 20            | `EPD_PIN_CLK`      |
| DIN       | 19            | `EPD_PIN_MOSI`     |
| CS        | 18            | `EPD_PIN_CS`       |
| DC        | 9             | `EPD_PIN_DC`       |
| RST       | 10            | `EPD_PIN_RST`      |
| BUSY      | 11            | `EPD_PIN_BUSY`     |
| VCC       | 3.3V          | --                 |
| GND       | GND           | --                 |

Optionally, three buttons, all wired the same way -- momentary push button
between the GPIO and GND, no external resistor needed (the firmware
enables each pin's internal pull-up, so it idles high and reads low when
pressed):

- **Next photo (GPIO2)**: a normal press skips immediately to the next
  photo; see
  [`firmware/README.md`](../firmware/README.md#skipping-to-the-next-photo).
- **Back photo (GPIO0)**: a normal press returns to the previously-shown
  photo; see
  [`firmware/README.md`](../firmware/README.md#going-back-to-the-previous-photo).
- **Menu / reset (GPIO1)**: one button, three actions by hold duration --
  a quick press overlays a "scan to manage" QR code on the current photo
  for 30 seconds; holding ~3s then releasing soft-resets the device
  (config kept); holding ~15s factory-resets it (clears WiFi/server
  config, reprovisions); see
  [`firmware/README.md`](../firmware/README.md#managing-the-queue-soft-resetting-and-factory-resetting).

All three pins were picked because they're within GPIO 0-7 -- the only
pins the ESP32-C6 can wake from deep sleep on -- aren't strapping pins,
and aren't already used by the panel wiring above. Also, not
incidentally, GPIO 0-7 is *all* the deep-sleep-wakeup-capable pins this
chip has (`SOC_RTCIO_PIN_COUNT` is 8) -- worth knowing if you're
targeting a compact board like the Seeed XIAO ESP32-C6, which only
breaks out 3 of them (GPIO0/1/2). These three buttons were deliberately
designed to fit exactly that budget: two dedicated pins for the
actions where instant, unambiguous response matters most (next, back),
and everything else folded onto the third pin via hold duration instead
of needing its own pin.

### Wiring on the Seeed XIAO ESP32-C6

The XIAO only breaks out 11 GPIOs (0, 1, 2, 16, 17, 18, 19, 20, 21, 22,
23), so the dev-board defaults above don't fit -- DC/RST/BUSY (GPIO 9,
10, 11) aren't exposed on this board at all. Built with
[`build_for_board.sh xiao`](../firmware/README.md#building-for-the-seeed-xiao-esp32-c6-production-board),
which layers [`firmware/sdkconfig.xiao`](../firmware/sdkconfig.xiao) on
top of the dev-board defaults, remapping DC/RST/BUSY onto three of the
remaining free pins:

| Panel pin | XIAO GPIO | XIAO silkscreen label | Kconfig option |
| --------- | --------- | ---------------------- | --------------- |
| CLK       | 20        | D9                      | `EPD_PIN_CLK` (unchanged) |
| DIN       | 19        | D8                      | `EPD_PIN_MOSI` (unchanged) |
| CS        | 18        | D10                     | `EPD_PIN_CS` (unchanged) |
| DC        | 16        | D6                      | `EPD_PIN_DC` |
| RST       | 17        | D7                      | `EPD_PIN_RST` |
| BUSY      | 21        | D3                      | `EPD_PIN_BUSY` |
| VCC       | 3.3V      | 3V3                     | -- |
| GND       | GND       | GND                     | -- |

The XIAO's silkscreen labels its pins D0-D10, not raw GPIO numbers --
the table above gives both.

Buttons (unchanged from the table above -- the XIAO's only three
ADC/deep-sleep-capable pins, GPIO 0/1/2, are exactly the ones already
used): next=GPIO2/**D2**, back=GPIO0/**D0**, menu/reset=GPIO1/**D1**.
That leaves GPIO22/**D4** and GPIO23/**D5** free, e.g. for the optional
VBUS mains-sense divider described under
[Battery](#battery-optional-xiao-esp32-c6) below.

`sdkconfig.xiao` also sets `FRAME_XIAO_ANTENNA_INIT=y`, which powers
the XIAO's onboard RF switch and selects its ceramic antenna at boot
(GPIO3/14, internal to the board -- not part of the wiring above).
Without it, WiFi doesn't reliably work on this board at all: the radio
comes up and logs look normal (e.g. the softAP starts and prints its
SSID/password) but the switch's control pins are left floating, so
nothing actually reaches the antenna -- the AP never becomes visible to
a scan, and a station connection would fail to associate the same way.
Seeed's own Arduino board package does this automatically; plain
ESP-IDF (what this firmware uses) doesn't, hence the explicit init.

A couple of things worth knowing if you pick different pins:

- **Avoid the ESP32-C6's strapping pins** (GPIO 4, 5, 8, 9, 15) and the
  USB-JTAG pins (12, 13) where possible -- strapping pins are sampled at
  reset to select boot mode. The reference wiring above already uses
  GPIO9 for DC, which *is* a strapping pin; it's only sampled during
  power-on/reset, so it's safe once the app is running, but if
  flashing/boot ever misbehaves on your board, check whether the panel is
  pulling that line low during reset.
- The panel's SPI interface is rated well above the firmware's default
  4MHz clock (`EPD_SPI_CLOCK_HZ`), but breadboard/dupont-wire connections
  are often unreliable much past a few MHz. Raise it once your physical
  wiring is confirmed solid.

## Battery (optional, XIAO ESP32-C6)

For a battery-powered build on the Seeed XIAO ESP32-C6 (which has
BAT+/BAT- charge pads on its underside and charges over USB-C):

- A 1S 3.7V LiPo **with an integrated protection circuit** (the board
  does no low-voltage cutoff of its own), soldered or JST-PH-pigtailed
  to the BAT pads. JST-PH polarity is not standardized -- verify with a
  multimeter before connecting.
- Battery level sense: two equal-value resistors in series from BAT+ to
  GND (200k is the reference value, but any matched pair works -- it's
  a ratio divider, so what matters is the two resistors matching each
  other, not the absolute value; higher values just draw less constant
  current from the battery, e.g. ~10uA for 200k+200k vs ~2uA for
  1M+1M, at the cost of a slightly noisier ADC reading from the higher
  source impedance -- not noticeable in practice up to around 1M given
  how coarse the percent curve already is). Midpoint to GPIO0/A0
  (shared with the back button -- deliberate, see
  [`firmware/README.md`](../firmware/README.md#battery-xiao-esp32-c6)).
- Optional mains detection: 2x100k divider from the 5V pin to GND,
  midpoint to a spare GPIO (e.g. 22).

Both features are off by default in firmware
(`FRAME_BATTERY_ADC_GPIO`/`FRAME_VBUS_SENSE_GPIO` = -1) -- except
`FRAME_BATTERY_ADC_GPIO`, which [`firmware/sdkconfig.xiao`](../firmware/sdkconfig.xiao)
turns on (GPIO0) by default for XIAO builds, since that's the one
board this feature was designed for. Mains detection stays off by
default even there; set `FRAME_VBUS_SENSE_GPIO` yourself if you wire
that divider too.

## Power

The device spends nearly all its time in deep sleep, waking briefly once
an hour (configurable, see the server's web UI) to fetch and display a
photo. A full-color refresh on this panel takes 15-30+ seconds and draws
more current than deep sleep by a wide margin -- expect battery life (if
not running from USB power) to be dominated by refresh frequency, not
sleep current.
