1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-09 21:33:48 +01:00

[epaper_spi] New epaper component (#10462)

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: Tudor Sandu <tm.sandu@gmail.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
Jesse Hills
2025-10-08 21:11:06 +13:00
committed by GitHub
parent a62c7a03dd
commit 2fac813f18
13 changed files with 841 additions and 0 deletions

View File

@@ -139,6 +139,7 @@ esphome/components/ens160_base/* @latonita @vincentscode
esphome/components/ens160_i2c/* @latonita
esphome/components/ens160_spi/* @latonita
esphome/components/ens210/* @itn3rd77
esphome/components/epaper_spi/* @esphome/core
esphome/components/es7210/* @kahrendt
esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81
@@ -429,6 +430,7 @@ esphome/components/speaker/media_player/* @kahrendt @synesthesiam
esphome/components/spi/* @clydebarrow @esphome/core
esphome/components/spi_device/* @clydebarrow
esphome/components/spi_led_strip/* @clydebarrow
esphome/components/split_buffer/* @jesserockz
esphome/components/sprinkler/* @kbx81
esphome/components/sps30/* @martgras
esphome/components/ssd1322_base/* @kbx81

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@esphome/core"]

View File

@@ -0,0 +1,80 @@
from esphome import core, pins
import esphome.codegen as cg
from esphome.components import display, spi
import esphome.config_validation as cv
from esphome.const import (
CONF_BUSY_PIN,
CONF_DC_PIN,
CONF_ID,
CONF_LAMBDA,
CONF_MODEL,
CONF_PAGES,
CONF_RESET_DURATION,
CONF_RESET_PIN,
)
AUTO_LOAD = ["split_buffer"]
DEPENDENCIES = ["spi"]
epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi")
EPaperBase = epaper_spi_ns.class_(
"EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
)
EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase)
EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6)
MODELS = {
"7.3in-spectra-e6": EPaper7p3InSpectraE6,
}
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(EPaperBase),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True, space="-"),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_RESET_DURATION): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(max=core.TimePeriod(milliseconds=500)),
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(spi.spi_device_schema()),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
)
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
"epaper_spi", require_miso=False, require_mosi=True
)
async def to_code(config):
model = MODELS[config[CONF_MODEL]]
rhs = model.new()
var = cg.Pvariable(config[CONF_ID], rhs, model)
await display.register_display(var, config)
await spi.register_spi_device(var, config)
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
if CONF_RESET_PIN in config:
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset))
if CONF_BUSY_PIN in config:
busy = await cg.gpio_pin_expression(config[CONF_BUSY_PIN])
cg.add(var.set_busy_pin(busy))
if CONF_RESET_DURATION in config:
cg.add(var.set_reset_duration(config[CONF_RESET_DURATION]))

View File

@@ -0,0 +1,227 @@
#include "epaper_spi.h"
#include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi";
static const LogString *epaper_state_to_string(EPaperState state) {
switch (state) {
case EPaperState::IDLE:
return LOG_STR("IDLE");
case EPaperState::UPDATE:
return LOG_STR("UPDATE");
case EPaperState::RESET:
return LOG_STR("RESET");
case EPaperState::INITIALISE:
return LOG_STR("INITIALISE");
case EPaperState::TRANSFER_DATA:
return LOG_STR("TRANSFER_DATA");
case EPaperState::POWER_ON:
return LOG_STR("POWER_ON");
case EPaperState::REFRESH_SCREEN:
return LOG_STR("REFRESH_SCREEN");
case EPaperState::POWER_OFF:
return LOG_STR("POWER_OFF");
case EPaperState::DEEP_SLEEP:
return LOG_STR("DEEP_SLEEP");
default:
return LOG_STR("UNKNOWN");
}
}
void EPaperBase::setup() {
if (!this->init_buffer_(this->get_buffer_length())) {
this->mark_failed("Failed to initialise buffer");
return;
}
this->setup_pins_();
this->spi_setup();
}
bool EPaperBase::init_buffer_(size_t buffer_length) {
if (!this->buffer_.init(buffer_length)) {
return false;
}
this->clear();
return true;
}
void EPaperBase::setup_pins_() {
this->dc_pin_->setup(); // OUTPUT
this->dc_pin_->digital_write(false);
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup(); // OUTPUT
this->reset_pin_->digital_write(true);
}
if (this->busy_pin_ != nullptr) {
this->busy_pin_->setup(); // INPUT
}
}
float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
void EPaperBase::command(uint8_t value) {
this->start_command_();
this->write_byte(value);
this->end_command_();
}
void EPaperBase::data(uint8_t value) {
this->start_data_();
this->write_byte(value);
this->end_data_();
}
// write a command followed by zero or more bytes of data.
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(const uint8_t *data) {
const uint8_t command = data[0];
const uint8_t length = data[1];
const uint8_t *ptr = data + 2;
ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
format_hex_pretty(ptr, length, '.', false).c_str());
this->dc_pin_->digital_write(false);
this->enable();
this->write_byte(command);
if (length > 0) {
this->dc_pin_->digital_write(true);
this->write_array(ptr, length);
}
this->disable();
}
bool EPaperBase::is_idle_() {
if (this->busy_pin_ == nullptr) {
return true;
}
return !this->busy_pin_->digital_read();
}
void EPaperBase::reset() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
this->disable_loop();
this->set_timeout(this->reset_duration_, [this] {
this->reset_pin_->digital_write(true);
this->set_timeout(20, [this] { this->enable_loop(); });
});
}
}
void EPaperBase::update() {
if (!this->state_queue_.empty()) {
ESP_LOGE(TAG, "Display update already in progress - %s",
LOG_STR_ARG(epaper_state_to_string(this->state_queue_.front())));
return;
}
this->state_queue_.push(EPaperState::UPDATE);
this->state_queue_.push(EPaperState::RESET);
this->state_queue_.push(EPaperState::INITIALISE);
this->state_queue_.push(EPaperState::TRANSFER_DATA);
this->state_queue_.push(EPaperState::POWER_ON);
this->state_queue_.push(EPaperState::REFRESH_SCREEN);
this->state_queue_.push(EPaperState::POWER_OFF);
this->state_queue_.push(EPaperState::DEEP_SLEEP);
this->state_queue_.push(EPaperState::IDLE);
this->enable_loop();
}
void EPaperBase::loop() {
if (this->waiting_for_idle_) {
if (this->is_idle_()) {
this->waiting_for_idle_ = false;
} else {
if (App.get_loop_component_start_time() - this->waiting_for_idle_last_print_ >= 1000) {
ESP_LOGV(TAG, "Waiting for idle");
this->waiting_for_idle_last_print_ = App.get_loop_component_start_time();
}
return;
}
}
auto state = this->state_queue_.front();
switch (state) {
case EPaperState::IDLE:
this->disable_loop();
break;
case EPaperState::UPDATE:
this->do_update_(); // Calls ESPHome (current page) lambda
break;
case EPaperState::RESET:
this->reset();
break;
case EPaperState::INITIALISE:
this->initialise_();
break;
case EPaperState::TRANSFER_DATA:
if (!this->transfer_data()) {
return; // Not done yet, come back next loop
}
break;
case EPaperState::POWER_ON:
this->power_on();
break;
case EPaperState::REFRESH_SCREEN:
this->refresh_screen();
break;
case EPaperState::POWER_OFF:
this->power_off();
break;
case EPaperState::DEEP_SLEEP:
this->deep_sleep();
break;
}
this->state_queue_.pop();
}
void EPaperBase::start_command_() {
this->dc_pin_->digital_write(false);
this->enable();
}
void EPaperBase::end_command_() { this->disable(); }
void EPaperBase::start_data_() {
this->dc_pin_->digital_write(true);
this->enable();
}
void EPaperBase::end_data_() { this->disable(); }
void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
void EPaperBase::initialise_() {
size_t index = 0;
const auto &sequence = this->init_sequence_;
const size_t sequence_size = this->init_sequence_length_;
while (index != sequence_size) {
if (sequence_size - index < 2) {
this->mark_failed("Malformed init sequence");
return;
}
const auto *ptr = sequence + index;
const uint8_t length = ptr[1];
if (sequence_size - index < length + 2) {
this->mark_failed("Malformed init sequence");
return;
}
this->cmd_data(ptr);
index += length + 2;
}
this->power_on();
}
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,93 @@
#pragma once
#include "esphome/components/display/display_buffer.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/split_buffer/split_buffer.h"
#include "esphome/core/component.h"
#include <queue>
namespace esphome::epaper_spi {
enum class EPaperState : uint8_t {
IDLE,
UPDATE,
RESET,
INITIALISE,
TRANSFER_DATA,
POWER_ON,
REFRESH_SCREEN,
POWER_OFF,
DEEP_SLEEP,
};
static const uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
class EPaperBase : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_2MHZ> {
public:
EPaperBase(const uint8_t *init_sequence, const size_t init_sequence_length)
: init_sequence_length_(init_sequence_length), init_sequence_(init_sequence) {}
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; }
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
void command(uint8_t value);
void data(uint8_t value);
void cmd_data(const uint8_t *data);
void update() override;
void loop() override;
void setup() override;
void on_safe_shutdown() override;
protected:
bool is_idle_();
void setup_pins_();
virtual void reset();
void initialise_();
bool init_buffer_(size_t buffer_length);
virtual int get_width_controller() { return this->get_width_internal(); };
virtual void deep_sleep() = 0;
/**
* Send data to the device via SPI
* @return true if done, false if should be called next loop
*/
virtual bool transfer_data() = 0;
virtual void refresh_screen() = 0;
virtual void power_on() = 0;
virtual void power_off() = 0;
virtual uint32_t get_buffer_length() = 0;
void start_command_();
void end_command_();
void start_data_();
void end_data_();
const size_t init_sequence_length_{0};
size_t current_data_index_{0};
uint32_t reset_duration_{200};
uint32_t waiting_for_idle_last_print_{0};
GPIOPin *dc_pin_;
GPIOPin *busy_pin_{nullptr};
GPIOPin *reset_pin_{nullptr};
const uint8_t *init_sequence_{nullptr};
bool waiting_for_idle_{false};
split_buffer::SplitBuffer buffer_;
std::queue<EPaperState> state_queue_{{EPaperState::IDLE}};
};
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,42 @@
#include "epaper_spi_model_7p3in_spectra_e6.h"
namespace esphome::epaper_spi {
static constexpr const char *const TAG = "epaper_spi.7.3in-spectra-e6";
void EPaper7p3InSpectraE6::power_on() {
ESP_LOGI(TAG, "Power on");
this->command(0x04);
this->waiting_for_idle_ = true;
}
void EPaper7p3InSpectraE6::power_off() {
ESP_LOGI(TAG, "Power off");
this->command(0x02);
this->data(0x00);
this->waiting_for_idle_ = true;
}
void EPaper7p3InSpectraE6::refresh_screen() {
ESP_LOGI(TAG, "Refresh");
this->command(0x12);
this->data(0x00);
this->waiting_for_idle_ = true;
}
void EPaper7p3InSpectraE6::deep_sleep() {
ESP_LOGI(TAG, "Deep sleep");
this->command(0x07);
this->data(0xA5);
}
void EPaper7p3InSpectraE6::dump_config() {
LOG_DISPLAY("", "E-Paper SPI", this);
ESP_LOGCONFIG(TAG, " Model: 7.3in Spectra E6");
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,45 @@
#pragma once
#include "epaper_spi_spectra_e6.h"
namespace esphome::epaper_spi {
class EPaper7p3InSpectraE6 : public EPaperSpectraE6 {
static constexpr const uint16_t WIDTH = 800;
static constexpr const uint16_t HEIGHT = 480;
// clang-format off
// Command, data length, data
static constexpr uint8_t INIT_SEQUENCE[] = {
0xAA, 6, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18,
0x01, 1, 0x3F,
0x00, 2, 0x5F, 0x69,
0x03, 4, 0x00, 0x54, 0x00, 0x44,
0x05, 4, 0x40, 0x1F, 0x1F, 0x2C,
0x06, 4, 0x6F, 0x1F, 0x17, 0x49,
0x08, 4, 0x6F, 0x1F, 0x1F, 0x22,
0x30, 1, 0x03,
0x50, 1, 0x3F,
0x60, 2, 0x02, 0x00,
0x61, 4, WIDTH / 256, WIDTH % 256, HEIGHT / 256, HEIGHT % 256,
0x84, 1, 0x01,
0xE3, 1, 0x2F,
};
// clang-format on
public:
EPaper7p3InSpectraE6() : EPaperSpectraE6(INIT_SEQUENCE, sizeof(INIT_SEQUENCE)) {}
void dump_config() override;
protected:
int get_width_internal() override { return WIDTH; };
int get_height_internal() override { return HEIGHT; };
void refresh_screen() override;
void power_on() override;
void power_off() override;
void deep_sleep() override;
};
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,135 @@
#include "epaper_spi_spectra_e6.h"
#include "esphome/core/log.h"
namespace esphome::epaper_spi {
static constexpr const char *const TAG = "epaper_spi.6c";
static inline uint8_t color_to_hex(Color color) {
if (color.red > 127) {
if (color.green > 170) {
if (color.blue > 127) {
return 0x1; // White
} else {
return 0x2; // Yellow
}
} else {
return 0x3; // Red (or Magenta)
}
} else {
if (color.green > 127) {
if (color.blue > 127) {
return 0x5; // Cyan -> Blue
} else {
return 0x6; // Green
}
} else {
if (color.blue > 127) {
return 0x5; // Blue
} else {
return 0x0; // Black
}
}
}
}
void EPaperSpectraE6::fill(Color color) {
uint8_t pixel_color;
if (color.is_on()) {
pixel_color = color_to_hex(color);
} else {
pixel_color = 0x1;
}
// We store 8 bitset<3> in 3 bytes
// | byte 1 | byte 2 | byte 3 |
// |aaabbbaa|abbbaaab|bbaaabbb|
uint8_t byte_1 = pixel_color << 5 | pixel_color << 2 | pixel_color >> 1;
uint8_t byte_2 = pixel_color << 7 | pixel_color << 4 | pixel_color << 1 | pixel_color >> 2;
uint8_t byte_3 = pixel_color << 6 | pixel_color << 3 | pixel_color << 0;
const size_t buffer_length = this->get_buffer_length();
for (size_t i = 0; i < buffer_length; i += 3) {
this->buffer_[i + 0] = byte_1;
this->buffer_[i + 1] = byte_2;
this->buffer_[i + 2] = byte_3;
}
}
uint32_t EPaperSpectraE6::get_buffer_length() {
// 6 colors buffer, 1 pixel = 3 bits, we will store 8 pixels in 24 bits = 3 bytes
return this->get_width_controller() * this->get_height_internal() / 8u * 3u;
}
void HOT EPaperSpectraE6::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return;
uint8_t pixel_bits = color_to_hex(color);
uint32_t pixel_position = x + y * this->get_width_controller();
uint32_t first_bit_position = pixel_position * 3;
uint32_t byte_position = first_bit_position / 8u;
uint32_t byte_subposition = first_bit_position % 8u;
if (byte_subposition <= 5) {
this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 << (5 - byte_subposition)))) |
(pixel_bits << (5 - byte_subposition));
} else {
this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 >> (byte_subposition - 5)))) |
(pixel_bits >> (byte_subposition - 5));
this->buffer_[byte_position + 1] =
(this->buffer_[byte_position + 1] & (0xFF ^ (0xFF & (0b111 << (13 - byte_subposition))))) |
(pixel_bits << (13 - byte_subposition));
}
}
bool HOT EPaperSpectraE6::transfer_data() {
const uint32_t start_time = App.get_loop_component_start_time();
if (this->current_data_index_ == 0) {
ESP_LOGV(TAG, "Sending data");
this->command(0x10);
}
uint8_t bytes_to_send[4]{0};
const size_t buffer_length = this->get_buffer_length();
for (size_t i = this->current_data_index_; i < buffer_length; i += 3) {
const uint32_t triplet = encode_uint24(this->buffer_[i + 0], this->buffer_[i + 1], this->buffer_[i + 2]);
// 8 pixels are stored in 3 bytes
// |aaabbbaa|abbbaaab|bbaaabbb|
// | byte 1 | byte 2 | byte 3 |
bytes_to_send[0] = ((triplet >> 17) & 0b01110000) | ((triplet >> 18) & 0b00000111);
bytes_to_send[1] = ((triplet >> 11) & 0b01110000) | ((triplet >> 12) & 0b00000111);
bytes_to_send[2] = ((triplet >> 5) & 0b01110000) | ((triplet >> 6) & 0b00000111);
bytes_to_send[3] = ((triplet << 1) & 0b01110000) | ((triplet << 0) & 0b00000111);
this->start_data_();
this->write_array(bytes_to_send, sizeof(bytes_to_send));
this->end_data_();
if (millis() - start_time > MAX_TRANSFER_TIME) {
// Let the main loop run and come back next loop
this->current_data_index_ = i + 3;
return false;
}
}
// Finished the entire dataset
this->current_data_index_ = 0;
return true;
}
void EPaperSpectraE6::reset() {
if (this->reset_pin_ != nullptr) {
this->disable_loop();
this->reset_pin_->digital_write(true);
this->set_timeout(20, [this] {
this->reset_pin_->digital_write(false);
delay(2);
this->reset_pin_->digital_write(true);
this->set_timeout(20, [this] { this->enable_loop(); });
});
}
}
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,23 @@
#pragma once
#include "epaper_spi.h"
namespace esphome::epaper_spi {
class EPaperSpectraE6 : public EPaperBase {
public:
EPaperSpectraE6(const uint8_t *init_sequence, const size_t init_sequence_length)
: EPaperBase(init_sequence, init_sequence_length) {}
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
void fill(Color color) override;
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
uint32_t get_buffer_length() override;
bool transfer_data() override;
void reset() override;
};
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,5 @@
CODEOWNERS = ["@jesserockz"]
# Allows split_buffer to be configured in yaml, to allow use of the C++ api.
CONFIG_SCHEMA = {}

View File

@@ -0,0 +1,133 @@
#include "split_buffer.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::split_buffer {
static constexpr const char *const TAG = "split_buffer";
SplitBuffer::~SplitBuffer() { this->free(); }
bool SplitBuffer::init(size_t total_length) {
this->free(); // Clean up any existing allocation
if (total_length == 0) {
return false;
}
this->total_length_ = total_length;
size_t current_buffer_size = total_length;
RAMAllocator<uint8_t *> ptr_allocator;
RAMAllocator<uint8_t> allocator;
// Try to allocate the entire buffer first
while (current_buffer_size > 0) {
// Calculate how many buffers we need of this size
size_t needed_buffers = (total_length + current_buffer_size - 1) / current_buffer_size;
// Try to allocate array of buffer pointers
uint8_t **temp_buffers = ptr_allocator.allocate(needed_buffers);
if (temp_buffers == nullptr) {
// If we can't even allocate the pointer array, don't need to continue
ESP_LOGE(TAG, "Failed to allocate pointers");
return false;
}
// Initialize all pointers to null
for (size_t i = 0; i < needed_buffers; i++) {
temp_buffers[i] = nullptr;
}
// Try to allocate all the buffers
bool allocation_success = true;
for (size_t i = 0; i < needed_buffers; i++) {
size_t this_buffer_size = current_buffer_size;
// Last buffer might be smaller if total_length is not divisible by current_buffer_size
if (i == needed_buffers - 1 && total_length % current_buffer_size != 0) {
this_buffer_size = total_length % current_buffer_size;
}
temp_buffers[i] = allocator.allocate(this_buffer_size);
if (temp_buffers[i] == nullptr) {
allocation_success = false;
break;
}
// Initialize buffer to zero
memset(temp_buffers[i], 0, this_buffer_size);
}
if (allocation_success) {
// Success! Store the result
this->buffers_ = temp_buffers;
this->buffer_count_ = needed_buffers;
this->buffer_size_ = current_buffer_size;
ESP_LOGD(TAG, "Allocated %zu * %zu bytes - %zu bytes", this->buffer_count_, this->buffer_size_,
this->total_length_);
return true;
}
// Allocation failed, clean up and try smaller buffers
for (size_t i = 0; i < needed_buffers; i++) {
if (temp_buffers[i] != nullptr) {
allocator.deallocate(temp_buffers[i], 0);
}
}
ptr_allocator.deallocate(temp_buffers, 0);
// Halve the buffer size and try again
current_buffer_size = current_buffer_size / 2;
}
ESP_LOGE(TAG, "Failed to allocate %zu bytes", total_length);
return false;
}
void SplitBuffer::free() {
if (this->buffers_ != nullptr) {
RAMAllocator<uint8_t> allocator;
for (size_t i = 0; i < this->buffer_count_; i++) {
if (this->buffers_[i] != nullptr) {
allocator.deallocate(this->buffers_[i], 0);
}
}
RAMAllocator<uint8_t *> ptr_allocator;
ptr_allocator.deallocate(this->buffers_, 0);
this->buffers_ = nullptr;
}
this->buffer_count_ = 0;
this->buffer_size_ = 0;
this->total_length_ = 0;
}
uint8_t &SplitBuffer::operator[](size_t index) {
if (index >= this->total_length_) {
ESP_LOGE(TAG, "Out of bounds - %zu >= %zu", index, this->total_length_);
// Return reference to a static dummy byte to avoid crash
static uint8_t dummy = 0;
return dummy;
}
size_t buffer_index = index / this->buffer_size_;
size_t offset_in_buffer = index - this->buffer_size_ * buffer_index;
return this->buffers_[buffer_index][offset_in_buffer];
}
const uint8_t &SplitBuffer::operator[](size_t index) const {
if (index >= this->total_length_) {
ESP_LOGE(TAG, "Out of bounds - %zu >= %zu", index, this->total_length_);
// Return reference to a static dummy byte to avoid crash
static const uint8_t DUMMY = 0;
return DUMMY;
}
size_t buffer_index = index / this->buffer_size_;
size_t offset_in_buffer = index - this->buffer_size_ * buffer_index;
return this->buffers_[buffer_index][offset_in_buffer];
}
} // namespace esphome::split_buffer

View File

@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <cstdlib>
namespace esphome::split_buffer {
class SplitBuffer {
public:
SplitBuffer() = default;
~SplitBuffer();
// Initialize the buffer with the desired total length
bool init(size_t total_length);
// Free all allocated buffers
void free();
// Access operators
uint8_t &operator[](size_t index);
const uint8_t &operator[](size_t index) const;
// Get the total length
size_t size() const { return this->total_length_; }
// Get buffer information
size_t get_buffer_count() const { return this->buffer_count_; }
size_t get_buffer_size() const { return this->buffer_size_; }
// Check if successfully initialized
bool is_valid() const { return this->buffers_ != nullptr && this->buffer_count_ > 0; }
private:
uint8_t **buffers_{nullptr};
size_t buffer_count_{0};
size_t buffer_size_{0};
size_t total_length_{0};
};
} // namespace esphome::split_buffer

View File

@@ -0,0 +1,15 @@
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
display:
- platform: epaper_spi
model: 7.3in-spectra-e6
cs_pin: GPIO5
dc_pin: GPIO17
reset_pin: GPIO16
busy_pin: GPIO4
rotation: 0
update_interval: 60s
lambda: |-
it.circle(64, 64, 50, Color::BLACK);