ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
it8951.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <utility>
5#include <vector>
6
12
13#include "it8951_defs.h"
14
15namespace esphome::it8951 {
16
17using namespace display;
18
19// --- Bounded op queue --------------------------------------------------------
20// Fixed-capacity ring buffer used by the loop scheduler. Replaces std::deque
21// to comply with ESPHome's STL container guidelines (std::deque allocates in
22// 512-byte blocks regardless of element size). Size analysis: the deepest
23// observed scenario is UPDATE_REFRESH (10 enqueued ops) + CHECK_LUT_IDLE's
24// 5 push_front rescheduling = 14 simultaneous entries. We use 32 for a
25// comfortable margin while keeping RAM cost low (~192 bytes per instance vs
26// 512+ bytes for std::deque).
27template<typename T, size_t N> class StaticOpQueue {
28 public:
29 bool empty() const { return this->count_ == 0; }
30 size_t size() const { return this->count_; }
31 static constexpr size_t capacity() { return N; }
32
33 bool push_back(const T &value) {
34 if (this->count_ >= N)
35 return false;
36 this->data_[(this->head_ + this->count_) % N] = value;
37 ++this->count_;
38 return true;
39 }
40
41 bool push_front(const T &value) {
42 if (this->count_ >= N)
43 return false;
44 this->head_ = (this->head_ + N - 1) % N;
45 this->data_[this->head_] = value;
46 ++this->count_;
47 return true;
48 }
49
50 void pop_front() {
51 if (this->count_ == 0)
52 return;
53 this->head_ = (this->head_ + 1) % N;
54 --this->count_;
55 }
56
57 const T &front() const { return this->data_[this->head_]; }
58 T &front() { return this->data_[this->head_]; }
59
60 void clear() {
61 this->head_ = 0;
62 this->count_ = 0;
63 }
64
65 private:
66 T data_[N]{};
67 size_t head_{0};
68 size_t count_{0};
69};
70
71// Op queue capacity. See StaticOpQueue comment for sizing analysis.
72static constexpr size_t OP_QUEUE_SIZE = 32;
73
74// --- Op queue ---------------------------------------------------------------
75// Each Op is a single CS-asserted SPI transaction (or a tiny bookkeeping
76// step). The loop processes one Op per iteration after gating on HW_RDY, so
77// the natural ESPHome loop cadence (~8-16 ms) provides inter-op pacing
78// without any blocking waits.
79//
80// Compound Ops (READ_DEV_INFO, XFER_*, DPY_BUF_AREA, ENABLE_1BPP, ...) are
81// short self-contained methods that do all their SPI work inside a single
82// CS cycle (or a small handful of cycles) and complete well under 2ms, so
83// they don't break the no-blocking budget.
84//
85// Each write-type op is a SINGLE CS-asserted transaction. The loop-level
86// HW_RDY gate ensures the controller is ready before dispatching any op, so
87// no blocking waits are needed within write ops.
88//
89// Read ops are decomposed: the command/address that triggers data preparation
90// is sent as write ops (CMD, WRITE_W), then a separate read op runs only
91// after the loop confirms HW_RDY is back HIGH (data ready). No blocking.
92enum class OpType : uint8_t {
93 CMD, // single CS: CMD preamble + command word (a)
94 WRITE_W, // single CS: WRITE preamble + data word (a)
95 WRITE_REG, // single CS: WRITE preamble + addr(a) + value(b)
96 // (caller must enqueue CMD(TCON_REG_WR) before this)
97 READ_DEV_INFO, // single CS: READ preamble + dummy + read DevInfo struct
98 // (caller enqueues CMD(GET_DEV_INFO) first; loop HW_RDY gate
99 // ensures data is ready before this op runs)
100 READ_WORD, // single CS: READ preamble + dummy + read one 16-bit word
101 // into read_result_. Loop HW_RDY gate ensures data ready.
102 CHECK_LUT_IDLE, // checks read_result_; if non-zero, re-enqueues read sequence
103 SET_1BPP, // uses read_result_ to set UP1SR bit 2, enqueues writes
104 XFER_LISAR, // set image-buffer target address (2× reg write: 4 CS transactions)
105 XFER_AREA_CMD, // single CS: CMD preamble + TCON_LD_IMG_AREA
106 XFER_AREA_ARGS, // single CS: WRITE preamble + 5 area-parameter words
107 XFER_ROWS, // single CS: WRITE preamble + row pixel data (time-sliced)
108 XFER_AREA_END, // single CS: CMD preamble + TCON_LD_IMG_END
109 DPY_BUF_CMD, // single CS: CMD preamble + I80_CMD_DPY_BUF_AREA
110 DPY_BUF_ARGS, // single CS: WRITE preamble + 7 display-area words
111 GPIO_RESET_LOW, // drive RESET pin low
112 GPIO_RESET_HIGH, // drive RESET pin high
113 DELAY_MS, // park `delay_until_` for a few ms (no SPI)
114};
115
116struct Op {
118 uint16_t a{0};
119 uint16_t b{0};
120};
121
122// High-level controller phases. Each phase enqueues a sequence of Ops; when
123// the queue drains, advance_phase_() runs the next phase.
124// This separation keeps per-Op work tiny and predictable.
125enum class Phase : uint8_t {
126 IDLE,
127 // Initialisation
128 INIT_RESET, // reset pulse + wake controller + packed-write enable
129 INIT_DEV_INFO, // GET_DEV_INFO and validate
130 INIT_VCOM, // write configured VCOM
131 INIT_TEMP, // force temperature for waveform LUT selection
132 INIT_DONE, // allocate framebuffer; transition to IDLE
133 // Update flow
134 UPDATE_PREPARE, // do_update_, compute dirty region, decide 4bpp/1bpp
135 UPDATE_TRANSFER, // one LD_IMG_AREA, time-sliced row streaming, one LD_IMG_END
136 UPDATE_REFRESH, // wait LUT idle, optionally enable 1bpp, send DPY_BUF_AREA
137 UPDATE_SLEEP, // optional deep sleep
138};
139
140class IT8951Display : public Display,
141 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
142 spi::DATA_RATE_2MHZ> {
143 public:
144 IT8951Display(const char *name, uint16_t width, uint16_t height) : name_(name), width_(width), height_(height) {
145 this->row_width_ = this->compute_row_width_();
146 this->buffer_length_ = static_cast<size_t>(this->row_width_) * static_cast<size_t>(height);
147 }
148
149 // --- Component lifecycle ---
150 void setup() override;
151 void loop() override;
152 void dump_config() override;
153 void on_safe_shutdown() override;
154 float get_setup_priority() const override { return setup_priority::PROCESSOR; }
155
156 // --- Config setters (called from generated code) ---
157 void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
158 void set_busy_pin(GPIOPin *pin) { this->busy_pin_ = pin; }
159 void set_enable_pins(std::vector<GPIOPin *> pins) { this->enable_pins_ = std::move(pins); }
161 void set_full_update_every(uint8_t n) {
162 this->full_update_every_ = n;
163 // Seed the counter so the very first update trips the full-update branch in
164 // prepare_update_region_, giving a freshly-booted panel a clean GC16 refresh
165 // before any partial (fast-waveform) updates begin.
166 this->partial_update_count_ = n;
167 }
168 void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; }
169 void set_sleep_when_done(bool s) { this->sleep_when_done_ = s; }
170 void set_vcom(uint16_t vcom_mv) { this->vcom_ = vcom_mv; }
171 void set_vcom_register(uint16_t selector) { this->vcom_register_ = selector; }
172 void set_force_temperature(int16_t celsius) {
173 this->force_temperature_ = celsius;
174 this->force_temperature_set_ = true;
175 }
176 void set_use_legacy_dpy_area(bool use) { this->use_legacy_dpy_area_ = use; }
177 // Pixel format: true = 4bpp grayscale framebuffer, false = packed 1bpp
178 // monochrome framebuffer. Chosen at config time; the framebuffer is stored
179 // in this native format and every update uses the matching transfer path.
180 void set_grayscale(bool g) { this->grayscale_ = g; }
181 // Monochrome only: ordered-dither pale colours (true) vs a hard 50% threshold.
182 void set_dithering(bool d) { this->dithering_ = d; }
183 void set_update_mode(uint16_t m) { this->default_update_mode_ = static_cast<UpdateMode>(m); }
184 void set_transform(uint8_t t) {
185 this->transform_ = t;
187 }
188 void set_rotation(DisplayRotation rotation) override {
189 Display::set_rotation(rotation);
191 }
192
193 // --- Display API ---
194 void update() override;
197 void fill(Color color) override;
198 void clear() override { this->fill(Color::WHITE); }
199 void draw_pixel_at(int x, int y, Color color) override;
200 // Bulk pixel blit (used by LVGL and image rendering). Overridden to write
201 // straight into the framebuffer, avoiding the base class's per-pixel
202 // draw_pixel_at overhead (watchdog feed, clipping test, dirty-box clamps).
203 void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
204 ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
205 int get_width() override { return (this->effective_transform_ & TRANSFORM_SWAP_XY) ? this->height_ : this->width_; }
206 int get_height() override { return (this->effective_transform_ & TRANSFORM_SWAP_XY) ? this->width_ : this->height_; }
207
208 protected:
209 int get_height_internal() override { return this->height_; }
210 int get_width_internal() override { return this->width_; }
211
212 // --- Coord transform / dirty region ---
214 // Map display (logical) coordinates to native framebuffer coordinates by
215 // applying effective_transform_ (swap/mirror). Shared by rotate_coordinates_
216 // and the bulk draw_pixels_at path.
217 void apply_transform_(int &x, int &y) const;
218 bool rotate_coordinates_(int &x, int &y);
219 void reset_dirty_region_();
220
221 // --- Framebuffer geometry / monochrome packing ---
222 // Bytes per row for the configured pixel format: 4bpp grayscale packs two
223 // pixels per byte; monochrome packs eight bits per byte, rounded up to a
224 // whole 16-pixel group (matching the controller's 8bpp-load / 1bpp trick).
225 uint16_t compute_row_width_() const {
226 return this->grayscale_ ? static_cast<uint16_t>((static_cast<uint32_t>(this->width_) + 1) / 2)
227 : static_cast<uint16_t>(((static_cast<uint32_t>(this->width_) + 15) / 16) * 2);
228 }
229 void set_mono_pixel_(uint16_t x, uint16_t y, bool value) const;
230 // Write a 4bpp grayscale nibble into the framebuffer (two pixels per byte).
231 void set_gray_pixel_(uint16_t x, uint16_t y, uint8_t nibble) const;
232 // Convert a color and write it at native framebuffer coordinates: a 4bpp
233 // nibble in grayscale mode, or an ordered-dithered bit in monochrome mode.
234 void write_pixel_native_(uint16_t x, uint16_t y, const Color &color) const;
235
236 // --- Op queue / loop machinery ---
237 void enqueue_(OpType type, uint16_t a = 0, uint16_t b = 0);
238 void prepend_(OpType type, uint16_t a = 0, uint16_t b = 0);
239 bool is_busy_() const;
240 void process_op_(const Op &op);
241 void advance_phase_();
242 void set_phase_(Phase next);
244
245 // --- SPI primitives (each is one CS-asserted burst, fully non-blocking) ---
246 void spi_cmd_(uint16_t cmd);
247 void spi_write_word_(uint16_t value);
248 void spi_write_reg_(uint16_t addr, uint16_t value);
249 void spi_write_args_(const uint16_t *args, uint16_t count);
250 uint16_t spi_read_word_(); // non-blocking: HW_RDY confirmed by loop gate
251 void spi_read_dev_info_(); // non-blocking: HW_RDY confirmed by loop gate
252
253 // --- Compound Ops (small bounded helpers) ---
254 void op_xfer_lisar_();
255 void op_xfer_area_args_();
256 void op_xfer_area_end_();
257 bool op_xfer_rows_(); // returns true when current update area fully sent
258 void op_dpy_buf_args_();
259 void op_check_lut_idle_();
260 void op_set_1bpp_();
261
262 // --- Phase enqueuers ---
263 void enqueue_init_reset_();
265 void enqueue_init_vcom_();
266 void enqueue_init_temp_();
270
272
273 // --- Recovery ---
274 void recover_();
275
276 // --- State ---
277 static constexpr uint32_t BUSY_TIMEOUT_MS = 5000;
278
283 // Requests a continuous (non-throttled) main loop while streaming image data
284 // so 20ms transfer slices aren't separated by the ~16ms default loop interval.
286
287 // Pending update bookkeeping
288 bool update_pending_{false};
291 uint16_t area_x_{0}, area_y_{0}, area_w_{0}, area_h_{0};
292 uint16_t transfer_row_{0};
293 bool initialised_{false};
294 // True once TCON_SLEEP has been sent and the controller has not been woken
295 // since. The next update must issue TCON_SYS_RUN before any SPI op.
296 bool asleep_{false};
299
300 // Read result storage for decomposed read-modify-write op sequences
301 uint16_t read_result_{0};
302
303 // Device info
305 uint16_t img_buf_addr_l_{0};
306 uint16_t img_buf_addr_h_{0};
307
308 // Configured properties
309 const char *name_;
310 uint16_t width_;
311 uint16_t height_;
312 uint16_t row_width_;
314 uint8_t *buffer_{};
315 uint8_t transform_{0};
319 uint16_t vcom_{2300};
320 uint16_t vcom_register_{I80_CMD_VCOM_WRITE};
321 int16_t force_temperature_{DEFAULT_FORCE_TEMP_C};
324 bool invert_colors_{false};
325 bool sleep_when_done_{false};
326 // Pixel format selector (see set_grayscale): true = 4bpp grayscale,
327 // false = packed 1bpp monochrome.
328 bool grayscale_{true};
329 // Monochrome dithering (see set_dithering): true = ordered dither.
330 bool dithering_{true};
334 // GPIOs driven high during setup to power on the panel (empty if unused).
335 std::vector<GPIOPin *> enable_pins_;
336
337 // Dirty region (pixel coordinates of bounding box of changes since last update)
338 uint16_t x_low_{0}, y_low_{0}, x_high_{0}, y_high_{0};
339
340 // Saved data rate so we can probe slow then run fast
342
343 // Consecutive recovery attempts; used to give up rather than infinite-loop
344 // when the controller is unresponsive (e.g. wiring issue).
346
347 // DevInfo read retry counter (controller often returns garbage on the first
348 // read after reset; the original driver retried up to 3 times with 100ms
349 // between attempts).
351};
352
353// --- Automation action ---
354template<typename... Ts> class IT8951UpdateAction : public Action<Ts...> {
355 public:
356 explicit IT8951UpdateAction(IT8951Display *display) : display_(display) {}
357 TEMPLATABLE_VALUE(UpdateMode, mode)
358
359 protected:
360 void play(const Ts &...x) override {
361 if (!this->display_->is_ready())
362 return;
363 if (this->mode_.has_value()) {
364 this->display_->update_mode(this->mode_.value(x...));
365 } else {
366 this->display_->update();
367 }
368 }
369
370 IT8951Display *display_;
371};
372
373} // namespace esphome::it8951
BedjetMode mode
BedJet operating mode.
uint8_t m
Definition bl0906.h:1
uint8_t h
Definition bl0906.h:2
virtual void play(const Ts &...x)=0
Helper class to request loop() to be called as fast as possible.
Definition helpers.h:2007
virtual void set_rotation(DisplayRotation rotation)
Internal method to set the display rotation with.
Definition display.cpp:16
int get_height() override
Definition it8951.h:206
void update_mode(UpdateMode mode)
Definition it8951.cpp:779
float get_setup_priority() const override
Definition it8951.h:154
void start_update_(UpdateMode mode)
Definition it8951.cpp:754
void spi_write_args_(const uint16_t *args, uint16_t count)
Definition it8951.cpp:512
HighFrequencyLoopRequester high_freq_
Definition it8951.h:285
void set_invert_colors(bool invert_colors)
Definition it8951.h:168
void set_transform(uint8_t t)
Definition it8951.h:184
void set_phase_(Phase next)
Definition it8951.cpp:154
void spi_cmd_(uint16_t cmd)
Definition it8951.cpp:485
bool rotate_coordinates_(int &x, int &y)
Definition it8951.cpp:857
void enqueue_(OpType type, uint16_t a=0, uint16_t b=0)
Definition it8951.cpp:21
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override
Definition it8951.cpp:958
void set_reset_pin(GPIOPin *pin)
Definition it8951.h:157
IT8951Display(const char *name, uint16_t width, uint16_t height)
Definition it8951.h:144
uint16_t compute_row_width_() const
Definition it8951.h:225
void spi_write_reg_(uint16_t addr, uint16_t value)
Definition it8951.cpp:501
void set_force_temperature(int16_t celsius)
Definition it8951.h:172
void prepend_(OpType type, uint16_t a=0, uint16_t b=0)
Definition it8951.cpp:28
void set_update_mode(uint16_t m)
Definition it8951.h:183
std::vector< GPIOPin * > enable_pins_
Definition it8951.h:335
int get_height_internal() override
Definition it8951.h:209
StaticOpQueue< Op, OP_QUEUE_SIZE > queue_
Definition it8951.h:279
void set_mono_pixel_(uint16_t x, uint16_t y, bool value) const
Definition it8951.cpp:1025
void set_busy_pin(GPIOPin *pin)
Definition it8951.h:158
void set_enable_pins(std::vector< GPIOPin * > pins)
Definition it8951.h:159
void set_reset_duration(uint32_t ms)
Definition it8951.h:160
void write_pixel_native_(uint16_t x, uint16_t y, const Color &color) const
Definition it8951.cpp:938
void set_rotation(DisplayRotation rotation) override
Definition it8951.h:188
void apply_transform_(int &x, int &y) const
Definition it8951.cpp:848
void process_op_(const Op &op)
Definition it8951.cpp:81
void fill(Color color) override
Definition it8951.cpp:906
void spi_write_word_(uint16_t value)
Definition it8951.cpp:493
void on_safe_shutdown() override
Definition it8951.cpp:349
static constexpr uint32_t BUSY_TIMEOUT_MS
Definition it8951.h:277
void set_vcom(uint16_t vcom_mv)
Definition it8951.h:170
void set_full_update_every(uint8_t n)
Definition it8951.h:161
void set_sleep_when_done(bool s)
Definition it8951.h:169
void set_use_legacy_dpy_area(bool use)
Definition it8951.h:176
bool prepare_update_region_(UpdateMode &mode)
Definition it8951.cpp:677
DisplayType get_display_type() override
Definition it8951.h:196
void set_gray_pixel_(uint16_t x, uint16_t y, uint8_t nibble) const
Definition it8951.cpp:1044
void set_vcom_register(uint16_t selector)
Definition it8951.h:171
int get_width_internal() override
Definition it8951.h:210
void draw_pixel_at(int x, int y, Color color) override
Definition it8951.cpp:929
IT8951UpdateAction(IT8951Display *display)
Definition it8951.h:356
const T & front() const
Definition it8951.h:57
bool push_front(const T &value)
Definition it8951.h:41
bool push_back(const T &value)
Definition it8951.h:33
static constexpr size_t capacity()
Definition it8951.h:31
The SPIDevice is what components using the SPI will create.
Definition spi.h:429
uint16_t type
constexpr float PROCESSOR
For components that use data from sensors like displays.
Definition component.h:47
const char int const __FlashStringHelper va_list args
Definition log.h:74
static void uint32_t
static const Color WHITE
Definition color.h:185
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6