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