ESPHome 2026.8.0-dev
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
201 return;
202 }
204 break;
206 if (!this->initialise(this->update_count_ != 0)) {
207 return; // Not done yet, come back next loop
208 }
210 break;
212 if (!this->transfer_data()) {
213 return; // Not done yet, come back next loop
214 }
215 this->x_low_ = this->width_;
216 this->x_high_ = 0;
217 this->y_low_ = this->height_;
218 this->y_high_ = 0;
220 break;
222 this->power_on();
224 break;
226 this->refresh_screen(this->update_count_ != 0);
227 this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
229 break;
231 this->power_off();
233 break;
235 this->deep_sleep();
237 ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
238 break;
239 }
240}
241
243 ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
244 this->state_ = state;
246 // allow subclasses to nominate delays
247 if (delay == 0)
248 delay = this->next_delay_;
249 this->next_delay_ = 0;
250 this->delay_until_ = millis() + delay;
251 ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
252 TRUEFALSE(this->waiting_for_idle_));
253 if (state == EPaperState::IDLE) {
254 this->disable_loop();
255 }
256}
257
259 this->dc_pin_->digital_write(true);
260 this->enable();
261}
262
264
265void EPaperBase::send_init_sequence_(const uint8_t *sequence, size_t length) {
266 size_t index = 0;
267
268 while (index != length) {
269 if (length - index < 2) {
270 this->mark_failed(LOG_STR("Malformed init sequence"));
271 return;
272 }
273 const uint8_t cmd = sequence[index++];
274 if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
275 ESP_LOGV(TAG, "Delay %dms", cmd);
276 delay(cmd);
277 } else {
278 const uint8_t num_args = x & 0x7F;
279 if (length - index < num_args) {
280 ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
281 this->mark_failed();
282 return;
283 }
284 this->cmd_data(cmd, sequence + index, num_args);
285 index += num_args;
286 }
287 }
288}
289
290bool EPaperBase::initialise(bool partial) {
292 return true;
293}
294
302 if (!this->get_clipping().inside(x, y))
303 return false;
304 if (this->effective_transform_ & SWAP_XY)
305 std::swap(x, y);
306 if (this->effective_transform_ & MIRROR_X)
307 x = this->width_ - x - 1;
308 if (this->effective_transform_ & MIRROR_Y)
309 y = this->height_ - y - 1;
310 if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
311 return false;
312 this->x_low_ = clamp_at_most(this->x_low_, x);
313 this->x_high_ = clamp_at_least(this->x_high_, x + 1);
314 this->y_low_ = clamp_at_most(this->y_low_, y);
315 this->y_high_ = clamp_at_least(this->y_high_, y + 1);
316 return true;
317}
318
325void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
326 if (!rotate_coordinates_(x, y))
327 return;
328 const size_t byte_position = y * this->row_width_ + x / 8;
329 const uint8_t bit_position = x % 8;
330 const uint8_t pixel_bit = 0x80 >> bit_position;
331 const auto original = this->buffer_[byte_position];
332 if ((color_to_bit(color) == 0)) {
333 this->buffer_[byte_position] = original & ~pixel_bit;
334 } else {
335 this->buffer_[byte_position] = original | pixel_bit;
336 }
337}
338
340 LOG_DISPLAY("", "E-Paper SPI", this);
341 ESP_LOGCONFIG(TAG,
342 " Model: %s\n"
343 " SPI Data Rate: %uMHz\n"
344 " Full update every: %d\n"
345 " Swap X/Y: %s\n"
346 " Mirror X: %s\n"
347 " Mirror Y: %s",
348 this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_,
349 YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X),
350 YESNO(this->transform_ & MIRROR_Y));
351 LOG_PIN(" Reset Pin: ", this->reset_pin_);
352 LOG_PIN(" DC Pin: ", this->dc_pin_);
353 LOG_PIN(" Busy Pin: ", this->busy_pin_);
354 LOG_PIN(" CS Pin: ", this->cs_);
355 LOG_UPDATE_INTERVAL(this);
356}
357
358} // 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
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
DisplayRotation rotation_
Definition display.h:789
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:177
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)
static uint8_t color_to_bit(Color color)
Definition epaper_spi.h:84
std::vector< GPIOPin * > enable_pins_
Definition epaper_spi.h:181
uint32_t data_rate_
Definition spi.h:412
bool init(size_t total_length)
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 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:2211
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:340
T clamp_at_least(T value, U min)
Definition helpers.h:2206
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:1400
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6