ESP-IDF v5.3.2 firmware for the CrowPanel 2.1" rotary display. Builds clean;
nothing has been run on hardware yet.
Hardware verified against Elecrow's own source for this board and
cross-checked against their ESPHome config, per PRD 8.1. Three corrections to
the PRD's pinout, all documented in docs/HARDWARE.md:
- touch is CST826 at 0x15, not CST816 (chip ID reads 0x11, not 0xB4)
- the ST7701S needs a 3-wire SPI bus on GPIO 16/2/1 that the PRD omitted;
without it the init sequence cannot be sent and the panel stays black
- the backlight is GPIO6
Two further departures from the PRD, both forced by facts on the ground:
- Transport is MQTT, not HTTP. zigbee2mqtt has no REST endpoint for live
sensor state. Subscribing to the broker also gets retained topics, so
every room's last reading arrives on connect rather than after a poll.
- The furnace is 2-wire R/W with no C, not 3-wire. The board needs its own
5V supply. The thermostat's 5 CPH setting is enforced as a minimum
12-minute period between heat-call starts, which a minimum off-time alone
does not achieve.
All of PRD section 5 is implemented in the relay layer itself: fail-open,
watchdog-gated output, max on-time, min off-time, cycle-rate limit, startup
lockout, and the stale-data fallback ladder down to open-loop freeze
protection. The one gap that cannot be closed in software — the PCF8574 not
resetting when the ESP32 does — is documented rather than papered over.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
222 lines
8.6 KiB
Markdown
222 lines
8.6 KiB
Markdown
# Hardware reference — CrowPanel 2.1" HMI Rotary Display
|
||
|
||
Everything below was taken from Elecrow's own source for **this** board and
|
||
cross-checked against a second independent source, as PRD section 8.1 requires.
|
||
Nothing here is from wiki prose or a search summary.
|
||
|
||
**Sources**
|
||
|
||
1. `Elecrow-RD/CrowPanel-2.1inch-HMI-ESP32-Rotary-Display-480-480-IPS-Round-Touch-Knob-Screen`
|
||
— `example/RotaryScreen_2_1/RotaryScreen_2_1.ino`, the vendor's shipping
|
||
Arduino example, plus the bundled `GFX_Library_for_Arduino`.
|
||
2. The same repo's `example/esphome/esphome2.1.yaml` — an ESP-IDF-framework
|
||
config for the same board, written independently of the Arduino example.
|
||
|
||
The two agree on every pin. Where they disagree on anything else, it is called
|
||
out below.
|
||
|
||
**Not yet verified on hardware.** The board was not connected when this was
|
||
written. Section "First bring-up" at the end is the order to check things in.
|
||
|
||
---
|
||
|
||
## Corrections to the PRD
|
||
|
||
The PRD's section 2.2 was close but incomplete in three ways that matter:
|
||
|
||
| PRD said | Actually | Why it matters |
|
||
|---|---|---|
|
||
| Touch is **CST816** | Touch is **CST826**, I2C `0x15` | Different chip ID register value (`0xAA` reads `0x11`, not `0xB4`). A CST816 driver's probe fails and the PRD's own phase-1 sanity check would look like a hardware fault. |
|
||
| `CS 16` listed alone | The panel needs a **3-wire SPI** bus: **CS 16, SCK 2, MOSI/SDA 1** | The ~30-command ST7701S init sequence goes over this bus. Without SCK and MOSI there is no way to send it, and the panel stays black — exactly the failure mode section 8.1 warns about. |
|
||
| No backlight mentioned | **Backlight is GPIO6**, PWM | Panel can be fully initialised and still appear dead with the backlight off. |
|
||
|
||
Two smaller additions: there is a **breathing LED on GPIO43**, and expander
|
||
**P3 ("LCD power") is driven HIGH during init and LOW at the end** of the
|
||
vendor's setup — see the note under PCF8574 below.
|
||
|
||
The PRD's claim that expander **P1, P6 and P7 are free is correct** — the
|
||
vendor example configures P0, P2, P3, P4, P5 and never touches the other
|
||
three. P1 is what this project uses for the relay.
|
||
|
||
---
|
||
|
||
## Pinout
|
||
|
||
### RGB panel (matches the PRD exactly)
|
||
|
||
| Signal | GPIO |
|
||
|---|---|
|
||
| DE | 40 |
|
||
| HSYNC | 15 |
|
||
| VSYNC | 7 |
|
||
| PCLK | 41 |
|
||
| R0–R4 | 46, 3, 8, 18, 17 |
|
||
| G0–G5 | 14, 13, 12, 11, 10, 9 |
|
||
| B0–B4 | 5, 45, 48, 47, 21 |
|
||
|
||
### Panel control SPI (missing from the PRD)
|
||
|
||
| Signal | GPIO |
|
||
|---|---|
|
||
| CS | 16 |
|
||
| SCK | 2 |
|
||
| SDA (MOSI) | 1 |
|
||
|
||
9-bit writes, MSB first, **SPI mode 0**. Arduino_GFX's `Arduino_SWSPI::begin()`
|
||
drives SCK low at init and shifts on the rising edge, which is mode 0.
|
||
Elecrow's ESPHome config says `spi_mode: MODE3` — that config also carries the
|
||
comment that its colours are wrong, so mode 0 is the one to trust. If the panel
|
||
is black, `ST7701_SPI_MODE` in `drivers/display/display.c` is a one-line change.
|
||
|
||
### Other
|
||
|
||
| Signal | GPIO |
|
||
|---|---|
|
||
| I2C SDA / SCL | 38 / 39 |
|
||
| Backlight (LEDC PWM) | 6 |
|
||
| Encoder A / B | 42 / 4 |
|
||
| Breathing LED | 43 |
|
||
|
||
PCLK **18 MHz**, and `pclk_inverted` (negative edge) per the ESPHome config.
|
||
|
||
### Panel timings
|
||
|
||
The two sources disagree, which is normal — these have slack.
|
||
|
||
| | hsync fp / pw / bp | vsync fp / pw / bp |
|
||
|---|---|---|
|
||
| Arduino example (**used here**) | 10 / 4 / 20 | 10 / 4 / 20 |
|
||
| ESPHome example | 20 / 10 / 10 | 8 / 10 / 10 |
|
||
|
||
The Arduino numbers are from the vendor's shipping firmware, so those are the
|
||
defaults in `display.c`. Either set should work; swap if the image rolls or
|
||
shears.
|
||
|
||
---
|
||
|
||
## I2C bus (SDA 38 / SCL 39, 400 kHz)
|
||
|
||
| Address | Device |
|
||
|---|---|
|
||
| `0x21` | PCF8574 I/O expander |
|
||
| `0x15` | CST826 capacitive touch |
|
||
|
||
Finding both in the boot scan proves the board is alive and the I2C pins are
|
||
right, independently of the display. That is the cheapest first check.
|
||
|
||
### PCF8574 allocation
|
||
|
||
| Pin | Use | Direction |
|
||
|---|---|---|
|
||
| P0 | Touch reset | out |
|
||
| P1 | **Relay — heat call** (this project) | out, active low |
|
||
| P2 | Touch interrupt | out (driven high by vendor init) |
|
||
| P3 | LCD power | out |
|
||
| P4 | LCD reset | out |
|
||
| P5 | Encoder button | in, pull-up |
|
||
| P6 | free | — |
|
||
| P7 | free | — |
|
||
|
||
Two things about this part that are easy to get wrong:
|
||
|
||
**Power-on state is all-high.** A PCF8574's quasi-bidirectional pins come up
|
||
weakly pulled high. With an active-low relay module that means the relay is
|
||
**off** at power-on, which is what we want. The driver keeps a shadow byte
|
||
initialised to `0xFF` so a read-modify-write on any other pin can never
|
||
accidentally pull P1 low.
|
||
|
||
**The expander does not reset when the ESP32 does.** A software reset, panic
|
||
reboot or `esp_restart()` leaves the PCF8574's output latch exactly as it was.
|
||
If P1 was low (heat on) when the firmware crashed, the relay stays closed until
|
||
firmware comes back and clears it. `app_main()` therefore de-asserts P1 before
|
||
doing anything else — before WiFi, before the display — which bounds that
|
||
window to roughly a second. This is documented in `hvac/relay.h` too. A true
|
||
hardware fail-safe would need an external watchdog or an RC on the relay coil;
|
||
this firmware does not have one, and the honest statement is that a crash gives
|
||
you about one second of unwanted heat call, not zero.
|
||
|
||
**P3 is driven HIGH before the LCD reset pulse and LOW at the end of setup**
|
||
in the vendor example, after the backlight PWM is running. Despite the "LCD
|
||
power" label the sequence only makes sense as a blanking control — it hides
|
||
init garbage. `display.c` reproduces the vendor's ordering exactly rather than
|
||
reasoning about what the net is really called.
|
||
|
||
---
|
||
|
||
## ST7701S initialisation
|
||
|
||
`drivers/display/st7701_init.h` carries the sequence verbatim from
|
||
`st7701_type5_init_operations` in the bundled `Arduino_GFX`
|
||
(`display/Arduino_ST7701_RGBPanel.h`), which is what the vendor example passes
|
||
to the panel constructor.
|
||
|
||
The ESPHome config's `init_sequence` is the same sequence with three commands
|
||
missing from bank `0x10` — `0xC0 3B 00`, `0xC1 0B 02` (VBP) and `0xC2 00 02` —
|
||
and an extra leading `0x01` software reset. Since that same file admits its
|
||
colours are wrong, the Arduino/type5 version is the one reproduced here, with
|
||
the software reset added at the front because it is harmless and makes the
|
||
state deterministic.
|
||
|
||
Pixel format is set to **RGB666** (`0x3A 0x60`) by the sequence, with `0x36 0x00`.
|
||
The Arduino example additionally passes `bgr = true` to the panel constructor;
|
||
that is reflected here by setting the RGB panel's BGR flag. If red and blue are
|
||
swapped on first light, that flag is the thing to toggle.
|
||
|
||
---
|
||
|
||
## Furnace side
|
||
|
||
**Confirmed by the user, and it differs from the PRD:** the system is
|
||
**2-wire, R and W only**. Existing thermostat is set to Gas/Oil Warm Air,
|
||
**5 CPH**.
|
||
|
||
Consequences:
|
||
|
||
- **There is no C wire and no third wire to repurpose.** The PRD's section 2.4
|
||
discussion of moving `G` to `C` does not apply. The board must run from its
|
||
own **5V USB supply**. Do not attempt power stealing — this board needs ~5W
|
||
and power stealing supplies roughly a tenth of that.
|
||
- **5 CPH is a real constraint, not a display preference.** Five cycles per
|
||
hour means at most one heat-call *start* every 12 minutes. A minimum
|
||
off-time alone does not enforce that, because a short call followed by a
|
||
5-minute gap would give 8 CPH. `hvac/relay.c` enforces a minimum period
|
||
between successive heat-call starts (720 s) in addition to the minimum
|
||
off-time.
|
||
|
||
Wiring:
|
||
|
||
```
|
||
PCF8574 P1 ──► relay module IN (active low)
|
||
|
||
R (24VAC) ──► relay COM
|
||
W (heat) ◄── relay NO closes to call for heat
|
||
```
|
||
|
||
Opto-isolated relay module with a separate JD-VCC jumper, normally open.
|
||
The PCF8574 sinks ~25 mA but barely sources, which is why the module must be
|
||
active-low triggered.
|
||
|
||
**Never connect R to C** — dead short across the transformer, blows the fuse on
|
||
the furnace control board. On this 2-wire system there is no C at the
|
||
thermostat anyway.
|
||
|
||
---
|
||
|
||
## First bring-up
|
||
|
||
In this order, because each step's failure mode is unambiguous only if the
|
||
previous one passed:
|
||
|
||
1. **I2C scan.** Expect `0x21` and `0x15`. If the bus looks empty, stop and
|
||
re-check SDA 38 / SCL 39 before touching anything else.
|
||
2. **Backlight.** GPIO6 PWM up. Screen should glow even with no image.
|
||
3. **Panel.** Colour bars. If black: check SPI mode, then the init sequence,
|
||
then re-verify the pinout — in that order, and re-verify the pinout before
|
||
writing any new software hypothesis.
|
||
4. **Touch.** Chip ID register `0xAA` should read `0x11`.
|
||
5. **Encoder.** Detents count in both directions; button on P5 reads low when
|
||
pressed.
|
||
6. **Relay — on the bench, with an LED or a spare relay module, not the
|
||
furnace.** Exercise every interlock in PRD section 5 before the unit goes
|
||
anywhere near 24VAC.
|