[epaper_spi] Pass bits per pixel and plane count to the base constructor (#20683)

Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
Bonne Eggleston
2026-10-15 17:41:37 +11:00
committed by GitHub
co-authored by clydebarrow
parent 8d963bde5b
commit ecd5980c5d
14 changed files with 79 additions and 49 deletions
+1 -1
View File
@@ -468,7 +468,7 @@ void HOT EPaperBase::draw_pixels_at(int x_start, int y_start, int w, int h, cons
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
size_t contiguous_length;
uint8_t *contiguous = this->buffer_.get_span(0, contiguous_length);
if (!this->buffer_is_mono() || this->get_clipping().is_set() || bitness != COLOR_BITNESS_565 ||
if (!this->buffer_is_mono_() || this->get_clipping().is_set() || bitness != COLOR_BITNESS_565 ||
contiguous_length != this->buffer_length_) {
Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
return;
+13 -8
View File
@@ -38,15 +38,18 @@ class EPaperBase : public Display,
spi::DATA_RATE_2MHZ> {
public:
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence = nullptr,
size_t init_sequence_length = 0, DisplayType display_type = DISPLAY_TYPE_BINARY)
size_t init_sequence_length = 0, DisplayType display_type = DISPLAY_TYPE_BINARY,
uint8_t bits_per_pixel = 1, uint8_t planes = 1)
: name_(name),
width_(width),
row_width_((width * bits_per_pixel + 7) / 8),
height_(height),
bits_per_pixel_(bits_per_pixel),
planes_(planes),
init_sequence_(init_sequence),
init_sequence_length_(init_sequence_length),
display_type_(display_type) {
this->row_width_ = (this->width_ + 7) / 8; // width of a row in bytes
}
display_type_(display_type),
buffer_length_((size_t) this->row_width_ * height * planes) {}
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override;
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
@@ -134,8 +137,8 @@ class EPaperBase : public Display,
bool rotate_coordinates_(int &x, int &y);
void draw_rgb565_block_(uint8_t *buffer, int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
bool big_endian, int x_offset, int y_offset, int x_pad);
// Whether the buffer holds 1 bit per pixel as draw_pixel_at() writes it, so draw_pixels_at() can write it directly
virtual bool buffer_is_mono() const { return false; }
// Whether the buffer is a single plane of 1 bit per pixel, as draw_pixel_at() writes it
bool buffer_is_mono_() const { return this->bits_per_pixel_ == 1 && this->planes_ == 1; }
/**
* Methods that must be implemented by concrete classes to control the display
@@ -173,13 +176,15 @@ class EPaperBase : public Display,
// properties initialised in the constructor
const char *name_;
uint16_t width_;
uint16_t row_width_; // width of a row in bytes
uint16_t row_width_; // width of a row of one plane in bytes
uint16_t height_;
uint8_t bits_per_pixel_;
uint8_t planes_; // the buffer holds this many planes of row_width_ * height_ bytes, one after another
const uint8_t *init_sequence_;
size_t init_sequence_length_;
DisplayType display_type_;
size_t buffer_length_{};
size_t buffer_length_;
size_t current_data_index_{}; // used by data transfer to track progress
split_buffer::SplitBuffer buffer_{};
GPIOPin *dc_pin_{};
@@ -15,9 +15,7 @@ class EPaper4bpp : public EPaperBase {
public:
EPaper4bpp(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) {
this->buffer_length_ = width * height / 2; // 2 pixels per byte
}
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR, 4) {}
void fill(Color color) override;
void clear() override;
@@ -9,10 +9,7 @@ class EPaperInkplate2 final : public EPaperBase {
public:
EPaperInkplate2(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) {
// Dual-plane buffer: black/white plane followed by red plane, 1 bit per pixel each.
this->buffer_length_ = this->row_width_ * this->height_ * 2;
}
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR, 1, 2) {}
void fill(Color color) override;
void clear() override;
@@ -27,12 +27,9 @@ class EPaperJD79660 final : public EPaperBase {
public:
EPaperJD79660(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, const uint8_t *fast_update, uint16_t fast_update_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR),
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR, 2),
fast_update_(fast_update),
fast_update_length_(fast_update_length) {
this->row_width_ = (width + 3) / 4; // Fix base class calc (2bpp instead of 1bpp)
this->buffer_length_ = this->row_width_ * height;
}
fast_update_length_(fast_update_length) {}
void fill(Color color) override;
@@ -9,13 +9,10 @@ namespace esphome::epaper_spi {
class EPaperMono : public EPaperBase {
public:
EPaperMono(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, display_type) {
this->buffer_length_ = (width + 7) / 8 * height; // 8 pixels per byte, rounded up
}
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY, uint8_t bits_per_pixel = 1)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, display_type, bits_per_pixel) {}
protected:
bool buffer_is_mono() const override { return true; }
void refresh_screen(bool partial) override;
void power_on() override {}
void power_off() override{};
@@ -58,9 +58,9 @@ bool HOT EPaperSSD1677::transfer_data() {
// A full update ignores 0x26, and the copy may not hold a real frame yet (first update after
// boot): send the new frame to both planes.
const bool send_copy = this->plane_ == 0 && this->update_count_ != 0;
// A mono buffer already holds the frame as a RAM plane does, so it can be sent as it is; a deeper
// buffer (the four-level model) returns false and packs the plane in plane_row().
const bool direct = send_copy || this->buffer_is_mono();
// A 1 bit per pixel buffer already holds the frame as a RAM plane does, so it can be sent as it is; a
// deeper buffer (the four-level model, 2 bits per pixel) packs the plane in plane_row() instead.
const bool direct = send_copy || this->buffer_is_mono_();
const auto &source = send_copy ? this->sent_ : this->buffer_;
const size_t row_length = this->plane_row_length_();
const size_t plane_length = row_length * this->height_;
@@ -23,8 +23,8 @@ namespace esphome::epaper_spi {
class EPaperSSD1677 : public EPaperMono {
public:
EPaperSSD1677(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY)
: EPaperMono(name, width, height, init_sequence, init_sequence_length, display_type) {}
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY, uint8_t bits_per_pixel = 1)
: EPaperMono(name, width, height, init_sequence, init_sequence_length, display_type, bits_per_pixel) {}
void setup() override;
@@ -29,16 +29,12 @@ class EPaperSSD1677Gray4 : public EPaperSSD1677 {
public:
EPaperSSD1677Gray4(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperSSD1677(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_GRAYSCALE) {
this->row_width_ = (width + 3) / 4; // 4 pixels per byte
this->buffer_length_ = (size_t) this->row_width_ * height;
}
: EPaperSSD1677(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_GRAYSCALE, 2) {}
void fill(Color color) override;
void draw_pixel_at(int x, int y, Color color) override;
protected:
bool buffer_is_mono() const override { return false; }
// A partial update if partial updates are enabled and this is not a full one.
bool is_partial_push_() const { return this->update_count_ != 0 && this->sent_.is_valid(); }
void plane_row(size_t y, uint8_t *out) override;
@@ -22,9 +22,7 @@ class EPaperT133A01 : public EPaperBase {
public:
EPaperT133A01(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) {
this->buffer_length_ = (size_t) width * height / 2; // 2 pixels per byte at 4bpp
}
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR, 4) {}
void set_cs_pins(GPIOPin *cs, GPIOPin *cs1) {
this->cs_pin_ = cs;
@@ -32,12 +32,9 @@ class EPaperUC8179 final : public EPaperBase {
public:
EPaperUC8179(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
this->buffer_length_ = this->row_width_ * height;
}
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {}
protected:
bool buffer_is_mono() const override { return true; }
bool initialise(bool partial) override;
bool transfer_data() override;
void refresh_screen(bool partial) override;
@@ -22,9 +22,7 @@ class EPaperWaveshareBWR : public EPaperBase {
public:
EPaperWaveshareBWR(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
this->buffer_length_ = this->row_width_ * height * 2;
}
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY, 1, 2) {}
void fill(Color color) override;
@@ -18,9 +18,7 @@ class EPaperWeAct3C : public EPaperBase {
public:
EPaperWeAct3C(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
this->buffer_length_ = this->row_width_ * height * 2;
}
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY, 1, 2) {}
void fill(Color color) override;
void clear() override;
@@ -0,0 +1,49 @@
#include <gtest/gtest.h>
#include "esphome/components/epaper_spi/epaper_spi_ssd1677_gray4.h"
#include "esphome/components/epaper_spi/epaper_spi_ssd1683.h"
#include "esphome/components/epaper_spi/epaper_spi_t133a01.h"
#include "esphome/components/epaper_spi/epaper_waveshare_bwr.h"
#include "esphome/components/epaper_spi/epaper_weact_3c.h"
namespace esphome::epaper_spi::testing {
namespace {
template<typename Driver> class Layout : public Driver {
public:
Layout(uint16_t width, uint16_t height) : Driver("test", width, height, nullptr, 0) {}
uint16_t row_width() const { return this->row_width_; }
size_t buffer_length() const { return this->buffer_length_; }
bool is_mono() const { return this->buffer_is_mono_(); }
};
/// Each driver's bits per pixel and plane count give the row width and buffer size its drawing code expects, and only
/// a single 1 bit per pixel plane takes the direct drawing paths.
TEST(EPaperBufferLayout, SizesFollowBitsPerPixelAndPlanes) {
Layout<EPaperSSD1683> mono(21, 3);
EXPECT_EQ(mono.row_width(), 3);
EXPECT_EQ(mono.buffer_length(), 9u);
EXPECT_TRUE(mono.is_mono());
Layout<EPaperSSD1677Gray4> gray(21, 3);
EXPECT_EQ(gray.row_width(), 6);
EXPECT_EQ(gray.buffer_length(), 18u);
EXPECT_FALSE(gray.is_mono());
Layout<EPaperT133A01> t133(20, 3);
EXPECT_EQ(t133.row_width(), 10);
EXPECT_EQ(t133.buffer_length(), 30u);
EXPECT_FALSE(t133.is_mono());
Layout<EPaperWeAct3C> weact(21, 3);
EXPECT_EQ(weact.row_width(), 3);
EXPECT_EQ(weact.buffer_length(), 18u);
EXPECT_FALSE(weact.is_mono());
Layout<EPaperWaveshareBWR> bwr(21, 3);
EXPECT_EQ(bwr.buffer_length(), 18u);
EXPECT_FALSE(bwr.is_mono());
}
} // namespace
} // namespace esphome::epaper_spi::testing