ESPHome 2026.5.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 this->dc_pin_->setup(); // OUTPUT
42 this->dc_pin_->digital_write(false);
43
44 if (this->reset_pin_ != nullptr) {
45 this->reset_pin_->setup(); // OUTPUT
46 this->reset_pin_->digital_write(true);
47 }
48
49 if (this->busy_pin_ != nullptr) {
50 this->busy_pin_->setup(); // INPUT
51 }
52}
53
55
56void EPaperBase::command(uint8_t value) {
57 ESP_LOGV(TAG, "Command: 0x%02X", value);
58 this->dc_pin_->digital_write(false);
59 this->enable();
60 this->write_byte(value);
61 this->disable();
62}
63
64// write a command followed by zero or more bytes of data.
65void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
66#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
67 char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
68 ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
69 format_hex_pretty_to(hex_buf, ptr, length, '.'));
70#endif
71
72 this->dc_pin_->digital_write(false);
73 this->enable();
74 this->write_byte(command);
75 if (length > 0) {
76 this->dc_pin_->digital_write(true);
77 this->write_array(ptr, length);
78 }
79 this->disable();
80}
81
83 if (this->busy_pin_ == nullptr) {
84 return true;
85 }
86 return !this->busy_pin_->digital_read();
87}
88
90 if (this->reset_pin_ != nullptr) {
91 if (this->state_ == EPaperState::RESET) {
92 this->reset_pin_->digital_write(false);
93 return false;
94 }
95 this->reset_pin_->digital_write(true);
96 }
97 return true;
98}
99
101 switch (this->rotation_) {
103 this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_X);
104 break;
106 this->effective_transform_ = this->transform_ ^ (MIRROR_Y | MIRROR_X);
107 break;
109 this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_Y);
110 break;
111 default:
112 this->effective_transform_ = this->transform_;
113 break;
114 }
115}
116
118 if (this->state_ != EPaperState::IDLE) {
119 ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
120 return;
121 }
123 this->enable_loop();
124#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
125 this->update_start_time_ = millis();
126#endif
127}
128
129void EPaperBase::wait_for_idle_(bool should_wait) {
130#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
132#endif
133 this->waiting_for_idle_ = should_wait;
134}
135
143 auto now = millis();
144 // using modulus arithmetic to handle wrap-around
145 int diff = now - this->delay_until_;
146 if (diff < 0)
147 return;
148 if (this->waiting_for_idle_) {
149 if (this->is_idle_()) {
150 this->waiting_for_idle_ = false;
151#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
152 ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
153#endif
154 } else {
155#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
156 if (now - this->waiting_for_idle_last_print_ >= 1000) {
157 ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
159 }
160#endif
161 return;
162 }
163 }
164 this->process_state_();
165}
166
176 ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
177 switch (this->state_) {
178 default:
179 ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
181 break;
183 this->disable_loop();
184 break;
187 if (this->reset()) {
189 } else {
191 }
192 break;
194 this->do_update_(); // Calls ESPHome (current page) lambda
197 return;
198 }
200 break;
202 if (!this->initialise(this->update_count_ != 0)) {
203 return; // Not done yet, come back next loop
204 }
206 break;
208 if (!this->transfer_data()) {
209 return; // Not done yet, come back next loop
210 }
211 this->x_low_ = this->width_;
212 this->x_high_ = 0;
213 this->y_low_ = this->height_;
214 this->y_high_ = 0;
216 break;
218 this->power_on();
220 break;
222 this->refresh_screen(this->update_count_ != 0);
223 this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
225 break;
227 this->power_off();
229 break;
231 this->deep_sleep();
233 ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
234 break;
235 }
236}
237
239 ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
240 this->state_ = state;
242 // allow subclasses to nominate delays
243 if (delay == 0)
244 delay = this->next_delay_;
245 this->next_delay_ = 0;
246 this->delay_until_ = millis() + delay;
247 ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
248 TRUEFALSE(this->waiting_for_idle_));
249 if (state == EPaperState::IDLE) {
250 this->disable_loop();
251 }
252}
253
255 this->dc_pin_->digital_write(true);
256 this->enable();
257}
258
260
261void EPaperBase::send_init_sequence_(const uint8_t *sequence, size_t length) {
262 size_t index = 0;
263
264 while (index != length) {
265 if (length - index < 2) {
266 this->mark_failed(LOG_STR("Malformed init sequence"));
267 return;
268 }
269 const uint8_t cmd = sequence[index++];
270 if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
271 ESP_LOGV(TAG, "Delay %dms", cmd);
272 delay(cmd);
273 } else {
274 const uint8_t num_args = x & 0x7F;
275 if (length - index < num_args) {
276 ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
277 this->mark_failed();
278 return;
279 }
280 this->cmd_data(cmd, sequence + index, num_args);
281 index += num_args;
282 }
283 }
284}
285
286bool EPaperBase::initialise(bool partial) {
288 return true;
289}
290
298 if (!this->get_clipping().inside(x, y))
299 return false;
300 if (this->effective_transform_ & SWAP_XY)
301 std::swap(x, y);
302 if (this->effective_transform_ & MIRROR_X)
303 x = this->width_ - x - 1;
304 if (this->effective_transform_ & MIRROR_Y)
305 y = this->height_ - y - 1;
306 if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
307 return false;
308 this->x_low_ = clamp_at_most(this->x_low_, x);
309 this->x_high_ = clamp_at_least(this->x_high_, x + 1);
310 this->y_low_ = clamp_at_most(this->y_low_, y);
311 this->y_high_ = clamp_at_least(this->y_high_, y + 1);
312 return true;
313}
314
321void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
322 if (!rotate_coordinates_(x, y))
323 return;
324 const size_t byte_position = y * this->row_width_ + x / 8;
325 const uint8_t bit_position = x % 8;
326 const uint8_t pixel_bit = 0x80 >> bit_position;
327 const auto original = this->buffer_[byte_position];
328 if ((color_to_bit(color) == 0)) {
329 this->buffer_[byte_position] = original & ~pixel_bit;
330 } else {
331 this->buffer_[byte_position] = original | pixel_bit;
332 }
333}
334
336 LOG_DISPLAY("", "E-Paper SPI", this);
337 ESP_LOGCONFIG(TAG,
338 " Model: %s\n"
339 " SPI Data Rate: %uMHz\n"
340 " Full update every: %d\n"
341 " Swap X/Y: %s\n"
342 " Mirror X: %s\n"
343 " Mirror Y: %s",
344 this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_,
345 YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X),
346 YESNO(this->transform_ & MIRROR_Y));
347 LOG_PIN(" Reset Pin: ", this->reset_pin_);
348 LOG_PIN(" DC Pin: ", this->dc_pin_);
349 LOG_PIN(" Busy Pin: ", this->busy_pin_);
350 LOG_PIN(" CS Pin: ", this->cs_);
351 LOG_UPDATE_INTERVAL(this);
352}
353
354} // namespace esphome::epaper_spi
void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
Definition component.h:258
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:790
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:176
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:83
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:138
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:137
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:136
constexpr float PROCESSOR
For components that use data from sensors like displays.
Definition component.h:44
T clamp_at_most(T value, U max)
Definition helpers.h:2330
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:409
T clamp_at_least(T value, U min)
Definition helpers.h:2325
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:1368
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6