ESPHome 2025.12.3
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";
10
11static constexpr const char *const EPAPER_STATE_STRINGS[] = {
12 "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
13 "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
14};
15
17 if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
18 return EPAPER_STATE_STRINGS[idx];
19 return "Unknown";
20}
21
23 if (!this->init_buffer_(this->buffer_length_)) {
24 this->mark_failed(LOG_STR("Failed to initialise buffer"));
25 return;
26 }
27 this->setup_pins_();
28 this->spi_setup();
29}
30
31bool EPaperBase::init_buffer_(size_t buffer_length) {
32 if (!this->buffer_.init(buffer_length)) {
33 return false;
34 }
35 this->clear();
36 return true;
37}
38
40 this->dc_pin_->setup(); // OUTPUT
41 this->dc_pin_->digital_write(false);
42
43 if (this->reset_pin_ != nullptr) {
44 this->reset_pin_->setup(); // OUTPUT
45 this->reset_pin_->digital_write(true);
46 }
47
48 if (this->busy_pin_ != nullptr) {
49 this->busy_pin_->setup(); // INPUT
50 }
51}
52
54
55void EPaperBase::command(uint8_t value) {
56 this->start_command_();
57 this->write_byte(value);
58 this->end_command_();
59}
60
61void EPaperBase::data(uint8_t value) {
62 this->start_data_();
63 this->write_byte(value);
64 this->end_data_();
65}
66
67// write a command followed by zero or more bytes of data.
68// The command is the first byte, length is the length of data only in the second byte, followed by the data.
69// [COMMAND, LENGTH, DATA...]
70void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
71 ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
72 format_hex_pretty(ptr, length, '.', false).c_str());
73
74 this->dc_pin_->digital_write(false);
75 this->enable();
76 this->write_byte(command);
77 if (length > 0) {
78 this->dc_pin_->digital_write(true);
79 this->write_array(ptr, length);
80 }
81 this->disable();
82}
83
85 if (this->busy_pin_ == nullptr) {
86 return true;
87 }
88 return !this->busy_pin_->digital_read();
89}
90
92 if (this->reset_pin_ != nullptr) {
93 if (this->state_ == EPaperState::RESET) {
94 this->reset_pin_->digital_write(false);
95 return false;
96 }
97 this->reset_pin_->digital_write(true);
98 }
99 return true;
100}
101
103 if (this->state_ != EPaperState::IDLE) {
104 ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
105 return;
106 }
108 this->enable_loop();
109#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
110 this->update_start_time_ = millis();
111#endif
112}
113
114void EPaperBase::wait_for_idle_(bool should_wait) {
115#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
117#endif
118 this->waiting_for_idle_ = should_wait;
119}
120
128 auto now = millis();
129 if (this->delay_until_ != 0) {
130 // using modulus arithmetic to handle wrap-around
131 int diff = now - this->delay_until_;
132 if (diff < 0) {
133 return;
134 }
135 this->delay_until_ = 0;
136 }
137 if (this->waiting_for_idle_) {
138 if (this->is_idle_()) {
139 this->waiting_for_idle_ = false;
140#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
141 ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
142#endif
143 } else {
144#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
145 if (now - this->waiting_for_idle_last_print_ >= 1000) {
146 ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
148 }
149#endif
150 return;
151 }
152 }
153 this->process_state_();
154}
155
165 ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
166 switch (this->state_) {
167 default:
168 ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
170 break;
172 this->disable_loop();
173 break;
176 if (this->reset()) {
178 } else {
180 }
181 break;
183 this->do_update_(); // Calls ESPHome (current page) lambda
186 return;
187 }
189 break;
191 this->initialise_();
193 break;
195 if (!this->transfer_data()) {
196 return; // Not done yet, come back next loop
197 }
198 this->x_low_ = this->width_;
199 this->x_high_ = 0;
200 this->y_low_ = this->height_;
201 this->y_high_ = 0;
203 break;
205 this->power_on();
207 break;
209 this->refresh_screen(this->update_count_ != 0);
210 this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
212 break;
214 this->power_off();
216 break;
218 this->deep_sleep();
220 ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
221 break;
222 }
223}
224
226 ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
227 this->state_ = state;
229 if (delay != 0) {
230 this->delay_until_ = millis() + delay;
231 } else {
232 this->delay_until_ = 0;
233 }
234 ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
235 TRUEFALSE(this->waiting_for_idle_));
236 if (state == EPaperState::IDLE) {
237 this->disable_loop();
238 }
239}
240
242 this->dc_pin_->digital_write(false);
243 this->enable();
244}
245
247
249 this->dc_pin_->digital_write(true);
250 this->enable();
251}
253
255
257 size_t index = 0;
258
259 auto *sequence = this->init_sequence_;
260 auto length = this->init_sequence_length_;
261 while (index != length) {
262 if (length - index < 2) {
263 this->mark_failed(LOG_STR("Malformed init sequence"));
264 return;
265 }
266 const uint8_t cmd = sequence[index++];
267 if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
268 ESP_LOGV(TAG, "Delay %dms", cmd);
269 delay(cmd);
270 } else {
271 const uint8_t num_args = x & 0x7F;
272 if (length - index < num_args) {
273 ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
274 this->mark_failed();
275 return;
276 }
277 this->cmd_data(cmd, sequence + index, num_args);
278 index += num_args;
279 }
280 }
281}
282
290 if (!this->get_clipping().inside(x, y))
291 return false;
292 if (this->transform_ & SWAP_XY)
293 std::swap(x, y);
294 if (this->transform_ & MIRROR_X)
295 x = this->width_ - x - 1;
296 if (this->transform_ & MIRROR_Y)
297 y = this->height_ - y - 1;
298 if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
299 return false;
300 this->x_low_ = clamp_at_most(this->x_low_, x);
301 this->x_high_ = clamp_at_least(this->x_high_, x + 1);
302 this->y_low_ = clamp_at_most(this->y_low_, y);
303 this->y_high_ = clamp_at_least(this->y_high_, y + 1);
304 return true;
305}
306
313void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
314 if (!rotate_coordinates_(x, y))
315 return;
316 const size_t pixel_position = y * this->width_ + x;
317 const size_t byte_position = pixel_position / 8;
318 const uint8_t bit_position = pixel_position % 8;
319 const uint8_t pixel_bit = 0x80 >> bit_position;
320 const auto original = this->buffer_[byte_position];
321 if ((color_to_bit(color) == 0)) {
322 this->buffer_[byte_position] = original & ~pixel_bit;
323 } else {
324 this->buffer_[byte_position] = original | pixel_bit;
325 }
326}
327
329 LOG_DISPLAY("", "E-Paper SPI", this);
330 ESP_LOGCONFIG(TAG, " Model: %s", this->name_);
331 LOG_PIN(" Reset Pin: ", this->reset_pin_);
332 LOG_PIN(" DC Pin: ", this->dc_pin_);
333 LOG_PIN(" Busy Pin: ", this->busy_pin_);
334 LOG_PIN(" CS Pin: ", this->cs_);
335 LOG_UPDATE_INTERVAL(this);
336 ESP_LOGCONFIG(TAG,
337 " SPI Data Rate: %uMHz\n"
338 " Full update every: %d\n"
339 " Swap X/Y: %s\n"
340 " Mirror X: %s\n"
341 " Mirror Y: %s",
342 (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, YESNO(this->transform_ & SWAP_XY),
343 YESNO(this->transform_ & MIRROR_X), YESNO(this->transform_ & MIRROR_Y));
344}
345
346} // 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:155
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:70
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:1231
T clamp_at_least(T value, U min)
Definition helpers.h:1226
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:321
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:31
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6