13static const char *
const TAG =
"it8951";
17static constexpr uint32_t MAX_TRANSFER_TIME_MS = 20;
22 if (!this->
queue_.push_back(
Op{type, a, b})) {
23 ESP_LOGE(TAG,
"Op queue overflow (cap=%u); dropping op type=%u",
static_cast<unsigned>(OP_QUEUE_SIZE),
24 static_cast<unsigned>(
type));
29 if (!this->
queue_.push_front(
Op{type, a, b})) {
30 ESP_LOGE(TAG,
"Op queue overflow (cap=%u); dropping op type=%u",
static_cast<unsigned>(OP_QUEUE_SIZE),
31 static_cast<unsigned>(
type));
47 if (this->
queue_.empty()) {
63 if (needs_hardware_ready && this->
is_busy_()) {
67 ESP_LOGV(TAG,
"HW_RDY is LOW (busy) in phase %u, elapsed=%" PRId32
"ms",
static_cast<unsigned>(this->
phase_),
70 ESP_LOGW(TAG,
"Busy timeout (%" PRIu32
"ms) in phase %u, recovering", elapsed,
71 static_cast<unsigned>(this->
phase_));
82 ESP_LOGV(TAG,
"Processing op type=%u a=0x%04X b=0x%04X",
static_cast<unsigned>(op.
type), op.
a, op.
b);
155 ESP_LOGV(TAG,
"Phase %u -> %u",
static_cast<unsigned>(this->
phase_),
static_cast<unsigned>(next));
193 if (this->
dev_info_.
panel_width == 0 || this->dev_info_.panel_width > 2048 || this->dev_info_.panel_height == 0 ||
194 this->dev_info_.panel_height > 2048 || this->dev_info_.panel_width == 0xFFFF ||
195 this->dev_info_.panel_height == 0xFFFF) {
197 ESP_LOGW(TAG,
"DevInfo attempt %u returned invalid data (W=%u H=%u), retrying...", this->
dev_info_attempts_,
204 ESP_LOGE(TAG,
"DevInfo invalid after %u attempts (W=%u H=%u)", this->
dev_info_attempts_,
206 this->
mark_failed(LOG_STR(
"Failed to read IT8951 device info"));
211 if (this->
dev_info_.
panel_width != this->width_ || this->dev_info_.panel_height != this->height_) {
212 ESP_LOGE(TAG,
"Panel dimension mismatch: configured=%ux%u, DevInfo=%ux%u. Check model/dimensions settings.",
214 this->
mark_failed(LOG_STR(
"IT8951 panel dimensions do not match DevInfo"));
252 ESP_LOGCONFIG(TAG,
"IT8951 setup complete");
261 ESP_LOGD(TAG,
"Nothing to update");
302 ESP_LOGCONFIG(TAG,
"Setting up IT8951...");
310 pin->digital_write(
true);
333 if (this->
buffer_ ==
nullptr) {
334 this->
mark_failed(LOG_STR(
"Failed to allocate IT8951 framebuffer"));
429 ESP_LOGV(TAG,
"Enqueueing refresh ops: grayscale=%u", this->
grayscale_);
471static constexpr uint32_t INTRA_CS_READY_TIMEOUT_US = 50;
473static inline void wait_for_hardware_ready(
GPIOPin *busy_pin) {
474 if (busy_pin ==
nullptr)
478 if (waited >= INTRA_CS_READY_TIMEOUT_US)
488 wait_for_hardware_ready(this->
busy_pin_);
496 wait_for_hardware_ready(this->
busy_pin_);
506 wait_for_hardware_ready(this->
busy_pin_);
516 wait_for_hardware_ready(this->
busy_pin_);
517 for (uint16_t i = 0; i < count; i++)
527 wait_for_hardware_ready(this->
busy_pin_);
529 wait_for_hardware_ready(this->
busy_pin_);
544 wait_for_hardware_ready(this->
busy_pin_);
546 wait_for_hardware_ready(this->
busy_pin_);
547 auto *words =
reinterpret_cast<uint16_t *
>(&this->
dev_info_);
549 for (
uint32_t i = 0; i < word_count; i++) {
574 args[0] =
static_cast<uint16_t
>((LDIMG_B_ENDIAN << 8) | (PIXEL_4BPP << 4));
582 args[0] =
static_cast<uint16_t
>((LDIMG_L_ENDIAN << 8) | (PIXEL_8BPP << 4));
583 args[1] =
static_cast<uint16_t
>(this->
area_x_ / 8);
585 args[3] =
static_cast<uint16_t
>(this->
area_w_ / 8);
595 const uint16_t area_y = this->
area_y_;
596 const uint16_t area_h = this->
area_h_;
603 const uint16_t bytes_per_row =
605 const uint16_t row_x_bytes =
611 wait_for_hardware_ready(this->
busy_pin_);
620 if (
millis() - start_time >= MAX_TRANSFER_TIME_MS)
633 const uint16_t
args[5] = {
639 const uint16_t
args[7] = {
652 ESP_LOGV(TAG,
"Checking LUT idle, read_result_=0x%04X", this->
read_result_);
668 const uint16_t modified =
static_cast<uint16_t
>(this->
read_result_ | (1U << 2));
696 uint16_t temp_max = this->
x_high_ > 0 ?
static_cast<uint16_t
>(this->
x_high_ - 1) : 0;
697 temp_max =
static_cast<uint16_t
>(temp_max | 0x001F);
698 if (temp_max >= this->
width_)
699 temp_max =
static_cast<uint16_t
>(this->
width_ - 1);
700 this->
x_high_ =
static_cast<uint16_t
>(temp_max + 1);
708 const uint16_t
x = this->
x_low_;
709 const uint16_t
y = this->
y_low_;
710 const uint16_t width =
static_cast<uint16_t
>(this->
x_high_ - this->
x_low_);
711 const uint16_t height =
static_cast<uint16_t
>(this->
y_high_ - this->
y_low_);
714 ESP_LOGE(TAG,
"Dirty region (%u,%u %ux%u) out of bounds",
x,
y, width, height);
742 ESP_LOGV(TAG,
"Update: %ux%u@%u,%u mode=%u (%s)", width, height,
x,
y,
static_cast<unsigned>(
mode),
783 ESP_LOGW(TAG,
"Unknown update mode");
793 ESP_LOGE(TAG,
"Recovery failed after %u attempts; giving up. Check BUSY pin wiring and power.",
795 this->
mark_failed(LOG_STR(
"IT8951 recovery exhausted"));
801 ESP_LOGW(TAG,
"Recovering (attempt %u): hardware-resetting controller (was in phase %u)", this->
recovery_attempts_,
802 static_cast<unsigned>(this->
phase_));
872static uint8_t quantize_8bit_to_nibble(uint8_t value) {
873 uint8_t nibble =
static_cast<uint8_t
>((
static_cast<uint16_t
>(value) + 8) >> 4);
874 return nibble > 0x0F ? 0x0F : nibble;
877static uint8_t color_to_nibble(
const Color &color) {
880 if (color.
w == 0xFF && color.
r == color.
g && color.
g == color.
b)
881 return quantize_8bit_to_nibble(color.
r);
885 if (color.
raw_32 == 0xFFFFFFFF)
892 auto luma =
static_cast<uint8_t
>((77u * color.
r + 150u * color.
g + 29u * color.
b + 128u) >> 8);
893 return quantize_8bit_to_nibble(luma);
901static uint16_t dither_threshold(uint16_t
x, uint16_t
y) {
902 static const uint8_t BAYER4[16] = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
903 return static_cast<uint16_t
>(BAYER4[((
y & 3) << 2) | (
x & 3)] * 4096u + 2048u);
913 uint8_t packed = color_to_nibble(color);
915 packed = 0x0F - packed;
918 fill_byte =
static_cast<uint8_t
>((packed << 4) | packed);
920 fill_byte = (packed <= 0x07) ? 0xFF : 0x00;
940 uint8_t nibble = color_to_nibble(color);
942 nibble =
static_cast<uint8_t
>(0x0F - nibble);
947 auto lum =
static_cast<uint16_t
>(77u * color.
r + 151u * color.
g + 29u * color.
b);
949 lum =
static_cast<uint16_t
>(65535u - lum);
953 const uint16_t threshold = this->
dithering_ ? dither_threshold(
x,
y) : 32768;
959 ColorBitness bitness,
bool big_endian,
int x_offset,
int y_offset,
int x_pad) {
967 Display::draw_pixels_at(x_start, y_start, w,
h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
971 const size_t line_stride =
static_cast<size_t>(x_offset) + w + x_pad;
972 for (
int y = 0;
y <
h;
y++) {
974 size_t source_idx = (
static_cast<size_t>(y_offset) +
y) * line_stride + x_offset;
975 for (
int x = 0;
x < w;
x++, source_idx++) {
979 const size_t i = source_idx * 2;
980 color_value = big_endian ? (
static_cast<uint32_t>(ptr[i]) << 8) | ptr[i + 1]
981 : ptr[i] | (
static_cast<uint32_t>(ptr[i + 1]) << 8);
985 const size_t i = source_idx * 3;
988 ? (
static_cast<uint32_t>(ptr[i]) << 16) | (
static_cast<uint32_t>(ptr[i + 1]) << 8) | ptr[i + 2]
989 : ptr[i] | (
static_cast<uint32_t>(ptr[i + 1]) << 8) | (
static_cast<uint32_t>(ptr[i + 2]) << 16);
993 color_value = ptr[source_idx];
996 int nx = x_start +
x;
997 int ny = y_start +
y;
999 if (nx < 0 || ny < 0 || nx >= this->
width_ || ny >= this->
height_)
1009 int x0 = x_start, y0 = y_start;
1010 int x1 = x_start + w - 1, y1 = y_start +
h - 1;
1013 const int nx_lo = std::max(0, std::min(x0, x1));
1014 const int ny_lo = std::max(0, std::min(y0, y1));
1015 const int nx_hi = std::min(this->
width_ - 1, std::max(x0, x1));
1016 const int ny_hi = std::min(this->
height_ - 1, std::max(y0, y1));
1017 if (nx_hi >= nx_lo && ny_hi >= ny_lo) {
1032 const uint16_t group =
static_cast<uint16_t
>(
x >> 4);
1033 const uint8_t sub =
static_cast<uint8_t
>(
x & 0x0F);
1034 const uint16_t byte_index =
static_cast<uint16_t
>(group * 2u + (sub < 8u ? 1u : 0u));
1035 const uint8_t mask =
static_cast<uint8_t
>(1u << (sub & 0x07));
1040 this->
buffer_[index] &=
static_cast<uint8_t
>(~mask);
1046 uint8_t buf = this->
buffer_[index];
1048 buf = (buf & 0xF0) | nibble;
1050 buf = (buf & 0x0F) |
static_cast<uint8_t
>(nibble << 4);
1058 LOG_DISPLAY(
"",
"IT8951 E-Paper",
this);
1059 char force_temperature[24];
1061 snprintf(force_temperature,
sizeof(force_temperature),
"%d °C", this->
force_temperature_);
1063 strncpy(force_temperature,
"(controller default)",
sizeof(force_temperature));
1064 force_temperature[
sizeof(force_temperature) - 1] =
'\0';
1068 "\n Dimensions: %dx%d"
1069 "\n Buffer: %u bytes"
1070 "\n Image buffer addr: 0x%04X%04X"
1071 "\n VCOM: %.02fV (set selector 0x%04X)"
1072 "\n Force temperature: %s"
1073 "\n Display command: %s"
1074 "\n Sleep when done: %s"
1075 "\n Full update every: %u"
1076 "\n Inverted colors: %s"
1077 "\n Pixel format: %s"
1078 "\n Reset duration: %" PRIu32
"ms",
1082 force_temperature, this->
use_legacy_dpy_area_ ?
"DPY_AREA (0x0034, legacy)" :
"DPY_BUF_AREA (0x0037)",
1086 LOG_PIN(
" Busy Pin: ", this->
busy_pin_);
1087 LOG_PIN(
" CS Pin: ", this->
cs_);
1088 LOG_UPDATE_INTERVAL(
this);
BedjetMode mode
BedJet operating mode.
void feed_wdt()
Feed the task watchdog.
void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
void stop()
Stop running the loop continuously.
void start()
Start running the loop continuously.
An STL allocator that uses SPI or internal RAM.
static Color to_color(uint32_t colorcode, ColorOrder color_order, ColorBitness color_bitness=ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned=true)
virtual void fill(Color color)
Fill the entire screen with the given color.
Rect get_clipping() const
Get the current the clipping rectangle.
DisplayRotation rotation_
virtual 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)
Given an array of pixels encoded in the nominated format, draw these into the display's buffer.
uint8_t full_update_every_
void update_mode(UpdateMode mode)
bool use_legacy_dpy_area_
void start_update_(UpdateMode mode)
void spi_write_args_(const uint16_t *args, uint16_t count)
void enqueue_init_temp_()
void enqueue_update_refresh_()
HighFrequencyLoopRequester high_freq_
void reset_dirty_region_()
uint32_t partial_update_count_
int16_t force_temperature_
void set_phase_(Phase next)
void spi_cmd_(uint16_t cmd)
UpdateMode default_update_mode_
bool rotate_coordinates_(int &x, int &y)
void dump_config() override
void enqueue_(OpType type, uint16_t a=0, uint16_t b=0)
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
uint16_t compute_row_width_() const
void spi_write_reg_(uint16_t addr, uint16_t value)
bool force_temperature_set_
void prepend_(OpType type, uint16_t a=0, uint16_t b=0)
std::vector< GPIOPin * > enable_pins_
int get_height_internal() override
void enqueue_init_dev_info_()
StaticOpQueue< Op, OP_QUEUE_SIZE > queue_
uint32_t phase_started_at_
void set_mono_pixel_(uint16_t x, uint16_t y, bool value) const
void update_effective_transform_()
void enqueue_update_transfer_()
void spi_read_dev_info_()
uint8_t dev_info_attempts_
void enqueue_init_vcom_()
uint32_t configured_data_rate_
void enqueue_update_sleep_()
void write_pixel_native_(uint16_t x, uint16_t y, const Color &color) const
uint32_t update_started_at_
UpdateMode pending_update_mode_
void apply_transform_(int &x, int &y) const
uint8_t effective_transform_
void enqueue_init_reset_()
void process_op_(const Op &op)
void fill(Color color) override
uint16_t spi_read_word_()
void spi_write_word_(uint16_t value)
void on_safe_shutdown() override
static constexpr uint32_t BUSY_TIMEOUT_MS
bool prepare_update_region_(UpdateMode &mode)
void set_gray_pixel_(uint16_t x, uint16_t y, uint8_t nibble) const
uint8_t recovery_attempts_
void op_xfer_area_args_()
void op_check_lut_idle_()
int get_width_internal() override
void draw_pixel_at(int x, int y, Color color) override
void set_data_rate(uint32_t data_rate)
void write_byte16(uint16_t data)
void spi_setup() override
uint8_t transfer_byte(uint8_t data)
void write_array(const uint8_t *data, size_t length)
void spi_teardown() override
@ DISPLAY_ROTATION_270_DEGREES
@ DISPLAY_ROTATION_180_DEGREES
@ DISPLAY_ROTATION_90_DEGREES
T clamp_at_most(T value, U max)
const char int const __FlashStringHelper va_list args
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
T clamp_at_least(T value, U min)
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.