ESPHome 2026.1.5
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
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 if (this->state_ != EPaperState::IDLE) {
102 ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
103 return;
104 }
106 this->enable_loop();
107#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
108 this->update_start_time_ = millis();
109#endif
110}
111
112void EPaperBase::wait_for_idle_(bool should_wait) {
113#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
115#endif
116 this->waiting_for_idle_ = should_wait;
117}
118
126 auto now = millis();
127 // using modulus arithmetic to handle wrap-around
128 int diff = now - this->delay_until_;
129 if (diff < 0)
130 return;
131 if (this->waiting_for_idle_) {
132 if (this->is_idle_()) {
133 this->waiting_for_idle_ = false;
134#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
135 ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
136#endif
137 } else {
138#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
139 if (now - this->waiting_for_idle_last_print_ >= 1000) {
140 ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
142 }
143#endif
144 return;
145 }
146 }
147 this->process_state_();
148}
149
159 ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
160 switch (this->state_) {
161 default:
162 ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
164 break;
166 this->disable_loop();
167 break;
170 if (this->reset()) {
172 } else {
174 }
175 break;
177 this->do_update_(); // Calls ESPHome (current page) lambda
180 return;
181 }
183 break;
185 this->initialise(this->update_count_ != 0);
187 break;
189 if (!this->transfer_data()) {
190 return; // Not done yet, come back next loop
191 }
192 this->x_low_ = this->width_;
193 this->x_high_ = 0;
194 this->y_low_ = this->height_;
195 this->y_high_ = 0;
197 break;
199 this->power_on();
201 break;
203 this->refresh_screen(this->update_count_ != 0);
204 this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
206 break;
208 this->power_off();
210 break;
212 this->deep_sleep();
214 ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
215 break;
216 }
217}
218
220 ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
221 this->state_ = state;
223 // allow subclasses to nominate delays
224 if (delay == 0)
225 delay = this->next_delay_;
226 this->next_delay_ = 0;
227 this->delay_until_ = millis() + delay;
228 ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
229 TRUEFALSE(this->waiting_for_idle_));
230 if (state == EPaperState::IDLE) {
231 this->disable_loop();
232 }
233}
234
236 this->dc_pin_->digital_write(true);
237 this->enable();
238}
239
241
242void EPaperBase::initialise(bool partial) {
243 size_t index = 0;
244
245 auto *sequence = this->init_sequence_;
246 auto length = this->init_sequence_length_;
247 while (index != length) {
248 if (length - index < 2) {
249 this->mark_failed(LOG_STR("Malformed init sequence"));
250 return;
251 }
252 const uint8_t cmd = sequence[index++];
253 if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
254 ESP_LOGV(TAG, "Delay %dms", cmd);
255 delay(cmd);
256 } else {
257 const uint8_t num_args = x & 0x7F;
258 if (length - index < num_args) {
259 ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
260 this->mark_failed();
261 return;
262 }
263 this->cmd_data(cmd, sequence + index, num_args);
264 index += num_args;
265 }
266 }
267}
268
276 if (!this->get_clipping().inside(x, y))
277 return false;
278 if (this->transform_ & SWAP_XY)
279 std::swap(x, y);
280 if (this->transform_ & MIRROR_X)
281 x = this->width_ - x - 1;
282 if (this->transform_ & MIRROR_Y)
283 y = this->height_ - y - 1;
284 if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
285 return false;
286 this->x_low_ = clamp_at_most(this->x_low_, x);
287 this->x_high_ = clamp_at_least(this->x_high_, x + 1);
288 this->y_low_ = clamp_at_most(this->y_low_, y);
289 this->y_high_ = clamp_at_least(this->y_high_, y + 1);
290 return true;
291}
292
299void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
300 if (!rotate_coordinates_(x, y))
301 return;
302 const size_t byte_position = y * this->row_width_ + x / 8;
303 const uint8_t bit_position = x % 8;
304 const uint8_t pixel_bit = 0x80 >> bit_position;
305 const auto original = this->buffer_[byte_position];
306 if ((color_to_bit(color) == 0)) {
307 this->buffer_[byte_position] = original & ~pixel_bit;
308 } else {
309 this->buffer_[byte_position] = original | pixel_bit;
310 }
311}
312
314 LOG_DISPLAY("", "E-Paper SPI", this);
315 ESP_LOGCONFIG(TAG,
316 " Model: %s\n"
317 " SPI Data Rate: %uMHz\n"
318 " Full update every: %d\n"
319 " Swap X/Y: %s\n"
320 " Mirror X: %s\n"
321 " Mirror Y: %s",
322 this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_,
323 YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X),
324 YESNO(this->transform_ & MIRROR_Y));
325 LOG_PIN(" Reset Pin: ", this->reset_pin_);
326 LOG_PIN(" DC Pin: ", this->dc_pin_);
327 LOG_PIN(" Busy Pin: ", this->busy_pin_);
328 LOG_PIN(" CS Pin: ", this->cs_);
329 LOG_UPDATE_INTERVAL(this);
330}
331
332} // namespace esphome::epaper_spi
virtual void mark_failed()
Mark this component as failed.
void enable_loop()
Enable this component's loop.
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:764
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 transfer_data()=0
Methods that must be implemented by concrete classes to control the display.
split_buffer::SplitBuffer buffer_
Definition epaper_spi.h:165
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.
virtual void initialise(bool partial)
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:76
uint32_t data_rate_
Definition spi.h:410
bool init(size_t total_length)
bool state
Definition fan.h:0
const float PROCESSOR
For components that use data from sensors like displays.
Definition component.cpp:82
T clamp_at_most(T value, U max)
Definition helpers.h:1543
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:334
T clamp_at_least(T value, U min)
Definition helpers.h:1538
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:830
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6