ESPHome 2026.10.2
Loading...
Searching...
No Matches
epaper_spi.cpp
Go to the documentation of this file.
1#include "epaper_spi.h"
2#include <cinttypes>
5#include "esphome/core/log.h"
6
7namespace esphome::epaper_spi {
8
9static const char *const TAG = "epaper_spi";
10static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
11
12static constexpr const char *const EPAPER_STATE_STRINGS[] = {
13 "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
14 "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
15};
16
18 if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
19 return EPAPER_STATE_STRINGS[idx];
20 return "Unknown";
21}
22
24 if (!this->init_buffer_(this->buffer_length_)) {
25 this->mark_failed(LOG_STR("Failed to initialise buffer"));
26 return;
27 }
28 this->setup_pins_();
29 this->spi_setup();
30}
31
32bool EPaperBase::init_buffer_(size_t buffer_length) {
33 if (!this->buffer_.init(buffer_length)) {
34 return false;
35 }
36 this->clear();
37 return true;
38}
39
41 for (auto *pin : this->enable_pins_) {
42 pin->setup();
43 pin->digital_write(true);
44 }
45 this->dc_pin_->setup(); // OUTPUT
46 this->dc_pin_->digital_write(false);
47
48 if (this->reset_pin_ != nullptr) {
49 this->reset_pin_->setup(); // OUTPUT
50 this->reset_pin_->digital_write(true);
51 }
52
53 if (this->busy_pin_ != nullptr) {
54 this->busy_pin_->setup(); // INPUT
55 }
56}
57
59
60void EPaperBase::command(uint8_t value) {
61 ESP_LOGV(TAG, "Command: 0x%02X", value);
62 this->dc_pin_->digital_write(false);
63 this->enable();
64 this->write_byte(value);
65 this->disable();
66}
67
68// write a command followed by zero or more bytes of data.
69void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
70#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
71 char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
72 ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
73 format_hex_pretty_to(hex_buf, ptr, length, '.'));
74#endif
75
76 this->dc_pin_->digital_write(false);
77 this->enable();
78 this->write_byte(command);
79 if (length > 0) {
80 this->dc_pin_->digital_write(true);
81 this->write_array(ptr, length);
82 }
83 this->disable();
84}
85
87 if (this->busy_pin_ == nullptr) {
88 return true;
89 }
90 return !this->busy_pin_->digital_read();
91}
92
94 if (this->reset_pin_ != nullptr) {
95 if (this->state_ == EPaperState::RESET) {
96 this->reset_pin_->digital_write(false);
97 return false;
98 }
99 this->reset_pin_->digital_write(true);
100 }
101 return true;
102}
103
105 switch (this->rotation_) {
107 this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_X);
108 break;
110 this->effective_transform_ = this->transform_ ^ (MIRROR_Y | MIRROR_X);
111 break;
113 this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_Y);
114 break;
115 default:
116 this->effective_transform_ = this->transform_;
117 break;
118 }
119}
120
122 if (this->state_ != EPaperState::IDLE) {
123 ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
124 return;
125 }
127 this->enable_loop();
128#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
129 this->update_start_time_ = millis();
130#endif
131}
132
133void EPaperBase::wait_for_idle_(bool should_wait) {
134#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
136#endif
137 this->waiting_for_idle_ = should_wait;
138}
139
147 auto now = millis();
148 // using modulus arithmetic to handle wrap-around
149 int diff = now - this->delay_until_;
150 if (diff < 0)
151 return;
152 if (this->waiting_for_idle_) {
153 if (this->is_idle_()) {
154 this->waiting_for_idle_ = false;
155#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
156 ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
157#endif
158 } else {
159#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
160 if (now - this->waiting_for_idle_last_print_ >= 1000) {
161 ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
163 }
164#endif
165 return;
166 }
167 }
168 this->process_state_();
169}
170
180 ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
181 switch (this->state_) {
182 default:
183 ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
185 break;
187 this->disable_loop();
188 break;
191 if (this->reset()) {
193 } else {
195 }
196 break;
198 this->do_update_(); // Calls ESPHome (current page) lambda
199 if (this->full_update_requested_) {
200 // Refresh the whole panel even if nothing was drawn
201 this->full_update_requested_ = false;
202 this->update_count_ = 0;
203 this->x_low_ = 0;
204 this->y_low_ = 0;
205 this->x_high_ = this->width_;
206 this->y_high_ = this->height_;
207 }
210 return;
211 }
213 break;
215 if (!this->initialise(this->update_count_ != 0)) {
216 return; // Not done yet, come back next loop
217 }
219 break;
221 if (!this->transfer_data()) {
222 return; // Not done yet, come back next loop
223 }
224 this->x_low_ = this->width_;
225 this->x_high_ = 0;
226 this->y_low_ = this->height_;
227 this->y_high_ = 0;
229 break;
231 this->power_on();
233 break;
235 this->refresh_screen(this->update_count_ != 0);
236 this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
238 break;
240 this->power_off();
242 break;
244 this->deep_sleep();
246 ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
247 break;
248 }
249}
250
252 ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
253 this->state_ = state;
255 // allow subclasses to nominate delays
256 if (delay == 0)
257 delay = this->next_delay_;
258 this->next_delay_ = 0;
259 this->delay_until_ = millis() + delay;
260 ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
261 TRUEFALSE(this->waiting_for_idle_));
262 if (state == EPaperState::IDLE) {
263 this->disable_loop();
264 }
265}
266
268 this->dc_pin_->digital_write(true);
269 this->enable();
270}
271
273
274void EPaperBase::send_init_sequence_(const uint8_t *sequence, size_t length) {
275 size_t index = 0;
276
277 while (index != length) {
278 if (length - index < 2) {
279 this->mark_failed(LOG_STR("Malformed init sequence"));
280 return;
281 }
282 const uint8_t cmd = sequence[index++];
283 if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
284 ESP_LOGV(TAG, "Delay %dms", cmd);
285 delay(cmd);
286 } else {
287 const uint8_t num_args = x & 0x7F;
288 if (length - index < num_args) {
289 ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
290 this->mark_failed();
291 return;
292 }
293 this->cmd_data(cmd, sequence + index, num_args);
294 index += num_args;
295 }
296 }
297}
298
299bool EPaperBase::initialise(bool partial) {
301 return true;
302}
303
311 if (this->is_point_clipped(x, y))
312 return false;
313 if (this->effective_transform_ & SWAP_XY)
314 std::swap(x, y);
315 if (this->effective_transform_ & MIRROR_X)
316 x = this->width_ - x - 1;
317 if (this->effective_transform_ & MIRROR_Y)
318 y = this->height_ - y - 1;
319 if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
320 return false;
321 this->x_low_ = clamp_at_most(this->x_low_, x);
322 this->x_high_ = clamp_at_least(this->x_high_, x + 1);
323 this->y_low_ = clamp_at_most(this->y_low_, y);
324 this->y_high_ = clamp_at_least(this->y_high_, y + 1);
325 return true;
326}
327
334void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
335 if (!rotate_coordinates_(x, y))
336 return;
337 const size_t byte_position = y * this->row_width_ + x / 8;
338 const uint8_t bit_position = x % 8;
339 const uint8_t pixel_bit = 0x80u >> bit_position;
340 const auto original = this->buffer_[byte_position];
341 if (color_to_mono(color) == 0) {
342 this->buffer_[byte_position] = original & ~pixel_bit;
343 } else {
344 this->buffer_[byte_position] = original | pixel_bit;
345 }
346}
347
349 LOG_DISPLAY("", "E-Paper SPI", this);
350 ESP_LOGCONFIG(TAG,
351 " Model: %s\n"
352 " SPI Data Rate: %uMHz\n"
353 " Full update every: %d\n"
354 " Swap X/Y: %s\n"
355 " Mirror X: %s\n"
356 " Mirror Y: %s",
357 this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_,
358 YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X),
359 YESNO(this->transform_ & MIRROR_Y));
360 LOG_PIN(" Reset Pin: ", this->reset_pin_);
361 LOG_PIN(" DC Pin: ", this->dc_pin_);
362 LOG_PIN(" Busy Pin: ", this->busy_pin_);
363 LOG_PIN(" CS Pin: ", this->cs_);
364 LOG_UPDATE_INTERVAL(this);
365}
366
367} // namespace esphome::epaper_spi
void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
Definition component.h:246
void disable_loop()
Disable this component's loop.
virtual void setup()=0
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
bool ESPHOME_ALWAYS_INLINE is_point_clipped(int x, int y) const
Whether (x, y) falls outside the active clipping rectangle.
Definition display.h:766
DisplayRotation rotation_
Definition display.h:809
virtual void power_off()=0
Power the display off.
void command(uint8_t value)
void process_state_()
Process the state machine.
virtual void deep_sleep()=0
Place the display into deep sleep.
void draw_pixel_at(int x, int y, Color color) override
Default implementation for monochrome displays where 8 pixels are packed to a byte.
bool rotate_coordinates_(int &x, int &y)
Check and rotate coordinates based on the transform flags.
void loop() override
Called during the loop task.
virtual void refresh_screen(bool partial)=0
Refresh the screen after data transfer.
virtual bool initialise(bool partial)
void send_init_sequence_(const uint8_t *sequence, size_t length)
virtual bool transfer_data()=0
Methods that must be implemented by concrete classes to control the display.
split_buffer::SplitBuffer buffer_
Definition epaper_spi.h:174
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length)
void set_state_(EPaperState state, uint16_t delay=0)
const char * epaper_state_to_string_()
virtual void power_on()=0
Power the display on.
void wait_for_idle_(bool should_wait)
float get_setup_priority() const override
bool init_buffer_(size_t buffer_length)
std::vector< GPIOPin * > enable_pins_
Definition epaper_spi.h:178
uint32_t data_rate_
Definition spi.h:430
bool init(size_t total_length, size_t max_buffer_size=SIZE_MAX)
bool state
Definition fan.h:2
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:137
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:136
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:135
constexpr uint8_t color_to_mono(Color color)
Map RGB color to a single monochrome bit.
Definition colorconv.h:29
constexpr float PROCESSOR
For components that use data from sensors like displays.
Definition component.h:47
T clamp_at_most(T value, U max)
Definition helpers.h:2402
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:433
T clamp_at_least(T value, U min)
Definition helpers.h:2397
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1532
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6