mirror of
https://github.com/esphome/esphome.git
synced 2026-08-17 19:13:18 +08:00
[epaper_spi] Add Inkplate 6COLOR support (#17717)
CI / Create common environment (push) Canceled after 0s
CI / Check pylint (push) Canceled after 0s
CI / Run script/ci-custom (push) Canceled after 0s
CI / Check import esphome.__main__ time (push) Canceled after 0s
CI / Test downstream esphome/device-builder (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.13) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.14) (push) Canceled after 0s
CI / Determine which jobs to run (push) Canceled after 0s
CI / Run integration tests () (push) Canceled after 0s
CI / Run C++ unit tests (push) Canceled after 0s
CI / Run CodSpeed benchmarks (push) Canceled after 0s
CI / Run script/clang-tidy for LibreTiny (push) Canceled after 0s
CI / Run script/clang-tidy for ESP8266 (push) Canceled after 0s
CI / Run script/clang-tidy for RP2 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 Arduino (push) Canceled after 0s
CI / Run script/clang-tidy for ZEPHYR (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 1/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 2/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 3/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 C6 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 P4 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 S3 (push) Canceled after 0s
CI / Test components batch () (push) Canceled after 0s
CI / Test esp32 components with PlatformIO (push) Canceled after 0s
CI / Seed pre-commit cache (push) Canceled after 0s
CI / pre-commit.ci lite (push) Canceled after 0s
CI / Build target branch for memory impact (push) Canceled after 0s
CI / Build PR branch for memory impact (push) Canceled after 0s
CI / Comment memory impact (push) Canceled after 0s
CI / CI Status (push) Canceled after 0s
CI / Create common environment (push) Canceled after 0s
CI / Check pylint (push) Canceled after 0s
CI / Run script/ci-custom (push) Canceled after 0s
CI / Check import esphome.__main__ time (push) Canceled after 0s
CI / Test downstream esphome/device-builder (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.13) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.14) (push) Canceled after 0s
CI / Determine which jobs to run (push) Canceled after 0s
CI / Run integration tests () (push) Canceled after 0s
CI / Run C++ unit tests (push) Canceled after 0s
CI / Run CodSpeed benchmarks (push) Canceled after 0s
CI / Run script/clang-tidy for LibreTiny (push) Canceled after 0s
CI / Run script/clang-tidy for ESP8266 (push) Canceled after 0s
CI / Run script/clang-tidy for RP2 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 Arduino (push) Canceled after 0s
CI / Run script/clang-tidy for ZEPHYR (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 1/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 2/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 3/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 C6 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 P4 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 S3 (push) Canceled after 0s
CI / Test components batch () (push) Canceled after 0s
CI / Test esp32 components with PlatformIO (push) Canceled after 0s
CI / Seed pre-commit cache (push) Canceled after 0s
CI / pre-commit.ci lite (push) Canceled after 0s
CI / Build target branch for memory impact (push) Canceled after 0s
CI / Build PR branch for memory impact (push) Canceled after 0s
CI / Comment memory impact (push) Canceled after 0s
CI / CI Status (push) Canceled after 0s
This commit is contained in:
@@ -81,4 +81,134 @@ constexpr NATIVE_COLOR color_to_bwr(Color color, NATIVE_COLOR hw_black, NATIVE_C
|
||||
return color_to_bwyr<NATIVE_COLOR>(color, hw_black, hw_white, /*hw_yellow=*/hw_white, hw_red);
|
||||
}
|
||||
|
||||
/** Map RGB color to discrete BWYRGB hex 6 color key
|
||||
*
|
||||
* Divides the RGB cube into 8 corners by which components are "on" (over 128), same as
|
||||
* color_to_bwyr, but also resolves the green and blue corners instead of folding them into
|
||||
* white/black.
|
||||
*
|
||||
* @tparam NATIVE_COLOR Type of native hardware color values
|
||||
* @param color RGB color to convert from
|
||||
* @param hw_black Native value for black
|
||||
* @param hw_white Native value for white
|
||||
* @param hw_yellow Native value for yellow
|
||||
* @param hw_red Native value for red
|
||||
* @param hw_green Native value for green
|
||||
* @param hw_blue Native value for blue
|
||||
* @return Converted native hardware color value
|
||||
* @internal Constexpr. Does not depend on side effects ("pure").
|
||||
*/
|
||||
template<typename NATIVE_COLOR>
|
||||
constexpr NATIVE_COLOR color_to_bwyrgb(Color color, NATIVE_COLOR hw_black, NATIVE_COLOR hw_white,
|
||||
NATIVE_COLOR hw_yellow, NATIVE_COLOR hw_red, NATIVE_COLOR hw_green,
|
||||
NATIVE_COLOR hw_blue) {
|
||||
const auto [min_rgb, max_rgb] = std::minmax({color.r, color.g, color.b});
|
||||
|
||||
if ((max_rgb - min_rgb) < COLORCONV_GRAY_THRESHOLD) {
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
|
||||
return hw_white;
|
||||
}
|
||||
return hw_black;
|
||||
}
|
||||
|
||||
const bool r_on = (color.r > 128);
|
||||
const bool g_on = (color.g > 128);
|
||||
const bool b_on = (color.b > 128);
|
||||
|
||||
if (r_on && g_on && !b_on) {
|
||||
return hw_yellow;
|
||||
}
|
||||
if (r_on && !g_on && !b_on) {
|
||||
return hw_red;
|
||||
}
|
||||
if (!r_on && g_on && !b_on) {
|
||||
return hw_green;
|
||||
}
|
||||
if (!r_on && !g_on && b_on) {
|
||||
return hw_blue;
|
||||
}
|
||||
// Handle "impure" colors (cyan, magenta) by folding into the closest primary.
|
||||
if (!r_on && g_on && b_on) {
|
||||
return hw_green; // cyan
|
||||
}
|
||||
if (r_on && !g_on) {
|
||||
return hw_red; // magenta
|
||||
}
|
||||
if (r_on) {
|
||||
// All high (but not gray) -> white
|
||||
return hw_white;
|
||||
}
|
||||
// !r_on && !g_on && !b_on
|
||||
// All low (but not gray) -> black
|
||||
return hw_black;
|
||||
}
|
||||
|
||||
/** Map RGB color to discrete BWYRGBO hex 7 color key
|
||||
*
|
||||
* Same corner logic as color_to_bwyrgb, except the red/yellow corner is split three ways
|
||||
* instead of two, for panels with a dedicated orange ink.
|
||||
*
|
||||
* @tparam NATIVE_COLOR Type of native hardware color values
|
||||
* @param color RGB color to convert from
|
||||
* @param hw_black Native value for black
|
||||
* @param hw_white Native value for white
|
||||
* @param hw_yellow Native value for yellow
|
||||
* @param hw_red Native value for red
|
||||
* @param hw_green Native value for green
|
||||
* @param hw_blue Native value for blue
|
||||
* @param hw_orange Native value for orange
|
||||
* @return Converted native hardware color value
|
||||
* @internal Constexpr. Does not depend on side effects ("pure").
|
||||
*/
|
||||
template<typename NATIVE_COLOR>
|
||||
constexpr NATIVE_COLOR color_to_bwyrgbo(Color color, NATIVE_COLOR hw_black, NATIVE_COLOR hw_white,
|
||||
NATIVE_COLOR hw_yellow, NATIVE_COLOR hw_red, NATIVE_COLOR hw_green,
|
||||
NATIVE_COLOR hw_blue, NATIVE_COLOR hw_orange) {
|
||||
const auto [min_rgb, max_rgb] = std::minmax({color.r, color.g, color.b});
|
||||
|
||||
if ((max_rgb - min_rgb) < COLORCONV_GRAY_THRESHOLD) {
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
|
||||
return hw_white;
|
||||
}
|
||||
return hw_black;
|
||||
}
|
||||
|
||||
const bool r_on = (color.r > 128);
|
||||
const bool g_on = (color.g > 128);
|
||||
const bool b_on = (color.b > 128);
|
||||
|
||||
if (r_on && !b_on) {
|
||||
// Between red and yellow: split the gradient in three instead of two, since this panel has a
|
||||
// dedicated orange ink. Named orange (e.g. 0xFFA500) has g close to the midpoint, so the
|
||||
// plain g_on (>128) threshold used by color_to_bwyrgb can't tell it apart from yellow.
|
||||
if (color.g > 170) {
|
||||
return hw_yellow;
|
||||
}
|
||||
if (color.g > 85) {
|
||||
return hw_orange;
|
||||
}
|
||||
return hw_red;
|
||||
}
|
||||
if (!r_on && g_on && !b_on) {
|
||||
return hw_green;
|
||||
}
|
||||
if (!r_on && !g_on && b_on) {
|
||||
return hw_blue;
|
||||
}
|
||||
// Handle "impure" colors (cyan, magenta) by folding into the closest primary.
|
||||
if (!r_on && g_on && b_on) {
|
||||
return hw_green; // cyan
|
||||
}
|
||||
if (r_on && !g_on) {
|
||||
return hw_red; // magenta
|
||||
}
|
||||
if (r_on) {
|
||||
// All high (but not gray) -> white
|
||||
return hw_white;
|
||||
}
|
||||
// !r_on && !g_on && !b_on
|
||||
// All low (but not gray) -> black
|
||||
return hw_black;
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "epaper_spi_4bpp.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
static constexpr const char *const TAG = "epaper_spi.4bpp";
|
||||
|
||||
void EPaper4bpp::fill(Color color) {
|
||||
// If clipping is active, fall back to base implementation
|
||||
if (this->get_clipping().is_set()) {
|
||||
EPaperBase::fill(color);
|
||||
return;
|
||||
}
|
||||
|
||||
auto pixel_color = this->color_to_native(color);
|
||||
|
||||
// We store 2 pixels per byte
|
||||
this->buffer_.fill(pixel_color + (pixel_color << 4));
|
||||
|
||||
// Whole buffer just changed; mark the entire canvas dirty.
|
||||
this->x_low_ = 0;
|
||||
this->y_low_ = 0;
|
||||
this->x_high_ = this->width_;
|
||||
this->y_high_ = this->height_;
|
||||
}
|
||||
|
||||
void EPaper4bpp::clear() {
|
||||
// clear buffer to white, just like real paper.
|
||||
this->fill(COLOR_ON);
|
||||
}
|
||||
|
||||
void HOT EPaper4bpp::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->rotate_coordinates_(x, y))
|
||||
return;
|
||||
auto pixel_bits = this->color_to_native(color);
|
||||
uint32_t pixel_position = x + y * this->get_width_internal();
|
||||
uint32_t byte_position = pixel_position / 2;
|
||||
auto original = this->buffer_[byte_position];
|
||||
if ((pixel_position & 1) != 0) {
|
||||
this->buffer_[byte_position] = (original & 0xF0) | pixel_bits;
|
||||
} else {
|
||||
this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4);
|
||||
}
|
||||
}
|
||||
|
||||
bool HOT EPaper4bpp::transfer_data() {
|
||||
const uint32_t start_time = App.get_loop_component_start_time();
|
||||
const size_t buffer_length = this->buffer_length_;
|
||||
if (this->current_data_index_ == 0) {
|
||||
this->command(CMD_TRANSFER_DATA);
|
||||
}
|
||||
|
||||
size_t buf_idx = 0;
|
||||
uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
|
||||
while (this->current_data_index_ != buffer_length) {
|
||||
bytes_to_send[buf_idx++] = this->buffer_[this->current_data_index_++];
|
||||
|
||||
if (buf_idx == sizeof bytes_to_send) {
|
||||
this->start_data_();
|
||||
this->write_array(bytes_to_send, buf_idx);
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis());
|
||||
buf_idx = 0;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
// Let the main loop run and come back next loop
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finished the entire dataset
|
||||
if (buf_idx != 0) {
|
||||
this->start_data_();
|
||||
this->write_array(bytes_to_send, buf_idx);
|
||||
this->disable();
|
||||
}
|
||||
this->current_data_index_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
/**
|
||||
* Intermediate base for panels with a 4-bit-per-pixel native buffer layout (2 pixels per byte).
|
||||
*
|
||||
* Owns buffer sizing, fill()/clear()/draw_pixel_at() and the chunked SPI transfer loop shared by
|
||||
* this family of controllers. Concrete subclasses supply only their RGB -> 4-bit color mapping via
|
||||
* color_to_native() plus their IC-specific power/refresh/sleep command sequences.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
void fill(Color color) override;
|
||||
void clear() override;
|
||||
|
||||
protected:
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
bool transfer_data() override;
|
||||
|
||||
/// Map an RGB color to this panel's native 4-bit color key (low nibble).
|
||||
virtual uint8_t color_to_native(Color color) = 0;
|
||||
|
||||
static constexpr uint8_t CMD_TRANSFER_DATA = 0x10;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,47 @@
|
||||
// Reference: https://github.com/SolderedElectronics/Inkplate-Arduino-library (src/boards/Inkplate6COLOR)
|
||||
|
||||
#include "epaper_spi_inkplate6color.h"
|
||||
#include "colorconv.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
static constexpr const char *const TAG = "epaper_spi.inkplate6color";
|
||||
|
||||
// Native hardware color codes for this panel's 4-bit color values.
|
||||
enum Inkplate6ColorHex : uint8_t {
|
||||
BLACK = 0,
|
||||
WHITE = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3,
|
||||
RED = 4,
|
||||
YELLOW = 5,
|
||||
ORANGE = 6,
|
||||
};
|
||||
|
||||
uint8_t EPaperInkplate6Color::color_to_native(Color color) {
|
||||
return color_to_bwyrgbo<uint8_t>(color, BLACK, WHITE, YELLOW, RED, GREEN, BLUE, ORANGE);
|
||||
}
|
||||
|
||||
void EPaperInkplate6Color::power_on() {
|
||||
ESP_LOGV(TAG, "Power on");
|
||||
this->command(0x04);
|
||||
}
|
||||
|
||||
void EPaperInkplate6Color::power_off() {
|
||||
ESP_LOGV(TAG, "Power off");
|
||||
this->command(0x02);
|
||||
}
|
||||
|
||||
void EPaperInkplate6Color::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh"); // full refresh only; partial is unused
|
||||
this->cmd_data(0x12, {0x00});
|
||||
}
|
||||
|
||||
void EPaperInkplate6Color::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->cmd_data(0x07, {0xA5});
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi_4bpp.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
// Soldered Inkplate 6COLOR: 600x448 7-color (black/white/green/blue/red/yellow/orange) e-paper,
|
||||
// UC8159-family controller.
|
||||
class EPaperInkplate6Color final : public EPaper4bpp {
|
||||
public:
|
||||
using EPaper4bpp::EPaper4bpp;
|
||||
|
||||
protected:
|
||||
uint8_t color_to_native(Color color) override;
|
||||
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override;
|
||||
void power_off() override;
|
||||
void deep_sleep() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -1,76 +1,23 @@
|
||||
#include "epaper_spi_spectra_e6.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "colorconv.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
static constexpr const char *const TAG = "epaper_spi.6c";
|
||||
static constexpr unsigned char GRAY_THRESHOLD = 50;
|
||||
|
||||
enum E6Color {
|
||||
BLACK,
|
||||
WHITE,
|
||||
YELLOW,
|
||||
RED,
|
||||
SKIP_1,
|
||||
BLUE,
|
||||
GREEN,
|
||||
CYAN,
|
||||
SKIP_2,
|
||||
// Native hardware color codes for this panel's 4-bit color values.
|
||||
enum E6Color : uint8_t {
|
||||
BLACK = 0,
|
||||
WHITE = 1,
|
||||
YELLOW = 2,
|
||||
RED = 3,
|
||||
BLUE = 5,
|
||||
GREEN = 6,
|
||||
};
|
||||
|
||||
static uint8_t color_to_hex(Color color) {
|
||||
// --- Step 1: Check for Grayscale (Black or White) ---
|
||||
// We define "grayscale" as a color where the min and max components
|
||||
// are close to each other.
|
||||
unsigned char max_rgb = std::max({color.r, color.g, color.b});
|
||||
unsigned char min_rgb = std::min({color.r, color.g, color.b});
|
||||
|
||||
if ((max_rgb - min_rgb) < GRAY_THRESHOLD) {
|
||||
// It's a shade of gray. Map to BLACK or WHITE.
|
||||
// We split the luminance at the halfway point (382 = (255*3)/2)
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
|
||||
return WHITE;
|
||||
}
|
||||
return BLACK;
|
||||
}
|
||||
// --- Step 2: Check for Primary/Secondary Colors ---
|
||||
// If it's not gray, it's a color. We check which components are
|
||||
// "on" (over 128) vs "off". This divides the RGB cube into 8 corners.
|
||||
bool r_on = (color.r > 128);
|
||||
bool g_on = (color.g > 128);
|
||||
bool b_on = (color.b > 128);
|
||||
|
||||
if (r_on && g_on && !b_on) {
|
||||
return YELLOW;
|
||||
}
|
||||
if (r_on && !g_on && !b_on) {
|
||||
return RED;
|
||||
}
|
||||
if (!r_on && g_on && !b_on) {
|
||||
return GREEN;
|
||||
}
|
||||
if (!r_on && !g_on && b_on) {
|
||||
return BLUE;
|
||||
}
|
||||
// Handle "impure" colors (Cyan, Magenta)
|
||||
if (!r_on && g_on && b_on) {
|
||||
// Cyan (G+B) -> Closest is Green or Blue. Pick Green.
|
||||
return GREEN;
|
||||
}
|
||||
if (r_on && !g_on) {
|
||||
// Magenta (R+B) -> Closest is Red or Blue. Pick Red.
|
||||
return RED;
|
||||
}
|
||||
// Handle the remaining corners (White-ish, Black-ish)
|
||||
if (r_on) {
|
||||
// All high (but not gray) -> White
|
||||
return WHITE;
|
||||
}
|
||||
// !r_on && !g_on && !b_on
|
||||
// All low (but not gray) -> Black
|
||||
return BLACK;
|
||||
uint8_t EPaperSpectraE6::color_to_native(Color color) {
|
||||
return color_to_bwyrgb<uint8_t>(color, BLACK, WHITE, YELLOW, RED, GREEN, BLUE);
|
||||
}
|
||||
|
||||
void EPaperSpectraE6::power_on() {
|
||||
@@ -92,71 +39,4 @@ void EPaperSpectraE6::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->cmd_data(0x07, {0xA5});
|
||||
}
|
||||
|
||||
void EPaperSpectraE6::fill(Color color) {
|
||||
// If clipping is active, fall back to base implementation
|
||||
if (this->get_clipping().is_set()) {
|
||||
EPaperBase::fill(color);
|
||||
return;
|
||||
}
|
||||
|
||||
auto pixel_color = color_to_hex(color);
|
||||
|
||||
// We store 2 pixels per byte
|
||||
this->buffer_.fill(pixel_color + (pixel_color << 4));
|
||||
}
|
||||
|
||||
void EPaperSpectraE6::clear() {
|
||||
// clear buffer to white, just like real paper.
|
||||
this->fill(COLOR_ON);
|
||||
}
|
||||
|
||||
void HOT EPaperSpectraE6::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->rotate_coordinates_(x, y))
|
||||
return;
|
||||
auto pixel_bits = color_to_hex(color);
|
||||
uint32_t pixel_position = x + y * this->get_width_internal();
|
||||
uint32_t byte_position = pixel_position / 2;
|
||||
auto original = this->buffer_[byte_position];
|
||||
if ((pixel_position & 1) != 0) {
|
||||
this->buffer_[byte_position] = (original & 0xF0) | pixel_bits;
|
||||
} else {
|
||||
this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4);
|
||||
}
|
||||
}
|
||||
|
||||
bool HOT EPaperSpectraE6::transfer_data() {
|
||||
const uint32_t start_time = App.get_loop_component_start_time();
|
||||
const size_t buffer_length = this->buffer_length_;
|
||||
if (this->current_data_index_ == 0) {
|
||||
this->command(0x10);
|
||||
}
|
||||
|
||||
size_t buf_idx = 0;
|
||||
uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
|
||||
while (this->current_data_index_ != buffer_length) {
|
||||
bytes_to_send[buf_idx++] = this->buffer_[this->current_data_index_++];
|
||||
|
||||
if (buf_idx == sizeof bytes_to_send) {
|
||||
this->start_data_();
|
||||
this->write_array(bytes_to_send, buf_idx);
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis());
|
||||
buf_idx = 0;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
// Let the main loop run and come back next loop
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finished the entire dataset
|
||||
if (buf_idx != 0) {
|
||||
this->start_data_();
|
||||
this->write_array(bytes_to_send, buf_idx);
|
||||
this->disable();
|
||||
}
|
||||
this->current_data_index_ = 0;
|
||||
return true;
|
||||
}
|
||||
} // namespace esphome::epaper_spi
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi.h"
|
||||
#include "epaper_spi_4bpp.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
class EPaperSpectraE6 final : public EPaperBase {
|
||||
class EPaperSpectraE6 final : public EPaper4bpp {
|
||||
public:
|
||||
EPaperSpectraE6(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
|
||||
}
|
||||
|
||||
void fill(Color color) override;
|
||||
void clear() override;
|
||||
using EPaper4bpp::EPaper4bpp;
|
||||
|
||||
protected:
|
||||
uint8_t color_to_native(Color color) override;
|
||||
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override;
|
||||
void power_off() override;
|
||||
void deep_sleep() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
bool transfer_data() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Reference: https://github.com/SolderedElectronics/Inkplate-Arduino-library (src/boards/Inkplate6COLOR)
|
||||
|
||||
from esphome.components.mipi import delay
|
||||
|
||||
from . import EpaperModel
|
||||
|
||||
|
||||
class Inkplate6ColorModel(EpaperModel):
|
||||
def __init__(self, name, class_name="EPaperInkplate6Color", **kwargs):
|
||||
super().__init__(name, class_name, **kwargs)
|
||||
|
||||
# fmt: off
|
||||
def get_init_sequence(self, config: dict):
|
||||
width, height = self.get_dimensions(config)
|
||||
return (
|
||||
(0x00, 0xEF, 0x08,), # panel setting
|
||||
(0x01, 0x37, 0x00, 0x05, 0x05,), # power setting
|
||||
(0x03, 0x00,), # power off sequence setting
|
||||
(0x06, 0xC7, 0xC7, 0x1D,), # booster soft start
|
||||
(0x41, 0x00,), # temperature sensor enable
|
||||
(0x50, 0x37,), # VCOM and data interval
|
||||
(0x60, 0x20,), # TCON setting
|
||||
(0x61, width // 256, width % 256, height // 256, height % 256,), # resolution set
|
||||
(0xE3, 0xAA,), # power saving
|
||||
delay(100),
|
||||
(0x50, 0x37,), # VCOM and data interval, resent once the power-saving setting settles
|
||||
)
|
||||
|
||||
|
||||
# Native orientation is landscape (600x448).
|
||||
inkplate6color = Inkplate6ColorModel(
|
||||
"inkplate6color",
|
||||
width=600,
|
||||
height=448,
|
||||
# Vendor library drives the panel at 2MHz; the controller doesn't reliably support faster rates.
|
||||
data_rate="2MHz",
|
||||
# Vendor library waits 200ms after releasing reset before talking to the panel.
|
||||
reset_duration="200ms",
|
||||
# A full 7-color refresh takes tens of seconds; disallow faster updates to avoid FSM update loops.
|
||||
minimum_update_interval="30s",
|
||||
# Panel's native buffer orientation is rotated 180 degrees relative to the logical
|
||||
# rotation=0 orientation; confirmed on real hardware.
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
# Default GPIO pins for the on-board Inkplate 6COLOR wiring.
|
||||
reset_pin=19,
|
||||
dc_pin=33,
|
||||
cs_pin=27,
|
||||
busy_pin={
|
||||
"number": 32,
|
||||
"inverted": True, # hardware: LOW=busy, HIGH=idle
|
||||
"mode": {
|
||||
"input": True,
|
||||
"pullup": True,
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -212,6 +212,29 @@ display:
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color::BLACK);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 15, Color(255, 0, 0));
|
||||
|
||||
# Soldered Inkplate 6COLOR 7-color e-paper (600x448, UC8159-family)
|
||||
- platform: epaper_spi
|
||||
spi_id: spi_bus
|
||||
model: inkplate6color
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
dc_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO17
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
inverted: true
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 30, Color::BLACK);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color(255, 0, 0));
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 10, Color(255, 165, 0));
|
||||
|
||||
# Waveshare 7.5" V2 BWR (800x480, UC8179 controller, EDP_7in5b_V2)
|
||||
- platform: epaper_spi
|
||||
spi_id: spi_bus
|
||||
|
||||
Reference in New Issue
Block a user