Files
rotaryThermo/main/drivers/display/st7701_init.h
T
mattandClaude Opus 5 d2e4c974dd Implement rotary thermostat firmware from PRD
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>
2026-08-03 19:47:00 -07:00

149 lines
4.6 KiB
C

/*
* ST7701S initialisation sequence for the CrowPanel 2.1" round panel.
*
* Transcribed verbatim from `st7701_type5_init_operations` in the
* Arduino_GFX bundled with Elecrow's own example for this board
* (example/libraries/GFX_Library_for_Arduino/src/display/Arduino_ST7701_RGBPanel.h),
* which is what example/RotaryScreen_2_1/RotaryScreen_2_1.ino passes to the
* panel constructor.
*
* PRD 8.1: one wrong byte here gives garbage or nothing, with no useful
* diagnostic. Do not "tidy" these values. Do not reorder them. The bank
* selects (0xFF ...) change the meaning of every register that follows, so a
* command moved across one is a different command.
*
* Cross-checked against example/esphome/esphome2.1.yaml, which carries the
* same sequence minus three commands in bank 0x10 (0xC0, 0xC1, 0xC2) — and
* which admits in its own comments that its colours come out wrong. The three
* extra commands are kept.
*
* The leading software reset (0x01) is from the ESPHome version. It is not in
* the Arduino one, but it costs 5ms and makes the starting state deterministic
* rather than "whatever the previous firmware left behind".
*/
#pragma once
#include <stdint.h>
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t data_len;
uint16_t delay_ms;
} st7701_cmd_t;
static const st7701_cmd_t st7701_init_seq[] = {
/* Software reset. */
{0x01, {0}, 0, 5},
/* ---- Bank 0x10 ---- */
{0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
{0xC0, {0x3B, 0x00}, 2, 0},
{0xC1, {0x0B, 0x02}, 2, 0}, /* VBP */
{0xC2, {0x00, 0x02}, 2, 0},
{0xCC, {0x10}, 1, 0},
{0xCD, {0x08}, 1, 0},
/*
* Not part of st7701_type5_init_operations, but Arduino_GFX's
* begin() calls setRotation(0) straight afterwards, and that path sends
* 0xC7 0x00 here (Y direction) before dropping back to bank 0x00 to write
* MADCTL. The vendor example therefore does send it, so it belongs in the
* sequence.
*
* That same setRotation(0) call is where the constructor's `bgr = true`
* argument lands: with bgr set it writes MADCTL 0x00, which is what this
* sequence already writes below. So bgr=true is a no-op at rotation 0 and
* the RGB panel does not need its BGR flag set.
*/
{0xC7, {0x00}, 1, 0},
/* Positive voltage gamma control. */
{0xB0, {0x02, 0x13, 0x1B, 0x0D,
0x10, 0x05, 0x08, 0x07,
0x07, 0x24, 0x04, 0x11,
0x0E, 0x2C, 0x33, 0x1D}, 16, 0},
/* Negative voltage gamma control. */
{0xB1, {0x05, 0x13, 0x1B, 0x0D,
0x11, 0x05, 0x08, 0x07,
0x07, 0x24, 0x04, 0x11,
0x0E, 0x2C, 0x33, 0x1D}, 16, 0},
/* ---- Bank 0x11 ---- */
{0xFF, {0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, {0x5D}, 1, 0},
{0xB1, {0x43}, 1, 0}, /* VCOM amplitude */
{0xB2, {0x81}, 1, 0}, /* VGH, 12V */
{0xB3, {0x80}, 1, 0},
{0xB5, {0x43}, 1, 0}, /* VGL, -8.3V */
{0xB7, {0x85}, 1, 0},
{0xB8, {0x20}, 1, 0},
{0xC1, {0x78}, 1, 0},
{0xC2, {0x78}, 1, 0},
{0xD0, {0x88}, 1, 0},
{0xE0, {0x00, 0x00, 0x02}, 3, 0},
{0xE1, {0x03, 0xA0, 0x00, 0x00,
0x04, 0xA0, 0x00, 0x00,
0x00, 0x20, 0x20}, 11, 0},
{0xE2, {0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00}, 13, 0},
{0xE3, {0x00, 0x00, 0x11, 0x00}, 4, 0},
{0xE4, {0x22, 0x00}, 2, 0},
{0xE5, {0x05, 0xEC, 0xA0, 0xA0,
0x07, 0xEE, 0xA0, 0xA0,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00}, 16, 0},
{0xE6, {0x00, 0x00, 0x11, 0x00}, 4, 0},
{0xE7, {0x22, 0x00}, 2, 0},
{0xE8, {0x06, 0xED, 0xA0, 0xA0,
0x08, 0xEF, 0xA0, 0xA0,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00}, 16, 0},
{0xEB, {0x00, 0x00, 0x40, 0x40,
0x00, 0x00, 0x00}, 7, 0},
{0xED, {0xFF, 0xFF, 0xFF, 0xBA,
0x0A, 0xBF, 0x45, 0xFF,
0xFF, 0x54, 0xFB, 0xA0,
0xAB, 0xFF, 0xFF, 0xFF}, 16, 0},
{0xEF, {0x10, 0x0D, 0x04, 0x08,
0x3F, 0x1F}, 6, 0},
/* ---- Bank 0x13 ---- */
{0xFF, {0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
{0xEF, {0x08}, 1, 0},
/* ---- Back to the command bank ---- */
{0xFF, {0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
{0x36, {0x00}, 1, 0}, /* MADCTL */
{0x3A, {0x60}, 1, 0}, /* 0x70=RGB888, 0x60=RGB666, 0x50=RGB565 */
{0x11, {0}, 0, 120}, /* sleep out */
{0x29, {0}, 0, 50}, /* display on */
};
#define ST7701_INIT_SEQ_LEN \
(sizeof(st7701_init_seq) / sizeof(st7701_init_seq[0]))