ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
epaper_spi_jd79660.cpp
Go to the documentation of this file.
2#include "colorconv.h"
3
4#include "esphome/core/log.h"
5
6namespace esphome::epaper_spi {
7static constexpr const char *const TAG = "epaper_spi.jd79660";
8
10enum JD79660Color : uint8_t {
11 BLACK = 0b00,
12 WHITE = 0b01,
13 YELLOW = 0b10,
14 RED = 0b11,
15};
16
18static JD79660Color HOT color_to_hex(Color color) {
19 return color_to_bwyr(color, JD79660Color::BLACK, JD79660Color::WHITE, JD79660Color::YELLOW, JD79660Color::RED);
20}
21
23 // If clipping is active, fall back to base implementation
24 if (this->get_clipping().is_set()) {
25 EPaperBase::fill(color);
26 return;
27 }
28
29 const auto pixel_color = color_to_hex(color);
30
31 // We store 4 pixels per byte
32 this->buffer_.fill(pixel_color | (pixel_color << 2) | (pixel_color << 4) | (pixel_color << 6));
33}
34
35void HOT EPaperJD79660::draw_pixel_at(int x, int y, Color color) {
36 if (!this->rotate_coordinates_(x, y))
37 return;
38 const auto pixel_bits = color_to_hex(color);
39 const uint32_t pixel_position = x + y * this->get_width_internal();
40 // We store 4 pixels per byte at LSB offsets 6, 4, 2, 0
41 const uint32_t byte_position = pixel_position / 4;
42 const uint32_t bit_offset = 6 - ((pixel_position % 4) * 2);
43 const auto original = this->buffer_[byte_position];
44
45 this->buffer_[byte_position] = (original & (~(0b11 << bit_offset))) | // mask old 2bpp
46 (pixel_bits << bit_offset); // add new 2bpp
47}
48
50 // On entry state RESET set step, next state will be RESET_END
51 if (this->state_ == EPaperState::RESET) {
53 }
54
55 switch (this->step_) {
57 // Step #0: Reset H for some settle time.
58
59 ESP_LOGVV(TAG, "reset #0");
60 this->reset_pin_->digital_write(true);
61
64 return false; // another loop: step #1 below
65
67 // Step #1: Reset L pulse for slightly >1.5ms.
68 // This is actual reset trigger.
69
70 ESP_LOGVV(TAG, "reset #1");
71
72 // As commented on SLEEP_MS_RESET1: Reset pulse must happen within time window.
73 // So do not use FSM loop, and avoid other calls/logs during pulse below.
74 this->reset_pin_->digital_write(false);
76 this->reset_pin_->digital_write(true);
77
80 return false; // another loop: step #2 below
81
83 // Step #2: Basically finished. Check sanity, and move FSM to INITIALISE state
84 ESP_LOGVV(TAG, "reset #2");
85
86 if (!this->is_idle_()) {
87 // Expectation: Idle after reset + settle time.
88 // Improperly connected/unexpected hardware?
89 // Error path reproducable e.g. with disconnected VDD/... pins
90 // (optimally while busy_pin configured with local pulldown).
91 // -> Mark failed to avoid followup problems.
92 this->mark_failed(LOG_STR("Busy after reset"));
93 }
94 break; // End state loop below
95
96 default:
97 // Unexpected step = bug?
98 this->mark_failed();
99 }
100
101 this->step_ = FSMState::INIT_STEP0_REGULARINIT; // reset for initialize state
102 return true;
103}
104
105bool EPaperJD79660::initialise(bool partial) {
106 switch (this->step_) {
108 // Step #0: Regular init sequence
109 ESP_LOGVV(TAG, "init #0");
110 if (!EPaperBase::initialise(partial)) { // Call parent impl
111 return false; // If parent should request another loop, do so
112 }
113
114 // Fast init requested + supported?
115 if (partial && (this->fast_update_length_ > 0)) {
117 this->wait_for_idle_(true); // Must wait for idle before fastinit sequence in next loop
118 return false; // another loop: step #1 below
119 }
120
121 break; // End state loop below
122
124 // Step #1: Fast init sequence
125 ESP_LOGVV(TAG, "init #1");
126 this->write_fastinit_();
127 break; // End state loop below
128
129 default:
130 // Unexpected step = bug?
131 this->mark_failed();
132 }
133
134 this->step_ = FSMState::NONE;
135 return true; // Finished: State transition waits for idle
136}
137
139 size_t buf_idx = 0;
140 uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
141 const uint32_t start_time = App.get_loop_component_start_time();
142 const auto buffer_length = this->buffer_length_;
143 while (this->current_data_index_ != buffer_length) {
144 bytes_to_send[buf_idx++] = this->buffer_[this->current_data_index_++];
145
146 if (buf_idx == sizeof bytes_to_send) {
147 this->start_data_();
148 this->write_array(bytes_to_send, buf_idx);
149 this->disable();
150 ESP_LOGVV(TAG, "Wrote %zu bytes at %ums", buf_idx, (unsigned) millis());
151 buf_idx = 0;
152
153 if (millis() - start_time > MAX_TRANSFER_TIME) {
154 // Let the main loop run and come back next loop
155 return false;
156 }
157 }
158 }
159
160 // Finished the entire dataset
161 if (buf_idx != 0) {
162 this->start_data_();
163 this->write_array(bytes_to_send, buf_idx);
164 this->disable();
165 ESP_LOGVV(TAG, "Wrote %zu bytes at %ums", buf_idx, (unsigned) millis());
166 }
167 // Cleanup for next transfer
168 this->current_data_index_ = 0;
169
170 // Finished with all buffer chunks
171 return true;
172}
173
175 // Undocumented register sequence in vendor register range.
176 // Related to Fast Init/Update.
177 // Should likely happen after regular init seq and power on, but before refresh.
178 // Might only work for some models with certain factory MTP.
179 // Please do not change without knowledge to avoid breakage.
180
182}
183
185 // For now always send full frame buffer in chunks.
186 // JD79660 might support partial window transfers. But sample code missing.
187 // And likely minimal impact, solely on SPI transfer time into RAM.
188
189 if (this->current_data_index_ == 0) {
190 this->command(CMD_TRANSFER);
191 }
192
193 return this->transfer_buffer_chunks_();
194}
195
196void EPaperJD79660::refresh_screen([[maybe_unused]] bool partial) {
197 ESP_LOGV(TAG, "Refresh");
198 this->cmd_data(CMD_REFRESH, {(uint8_t) 0x00});
199}
200
202 ESP_LOGV(TAG, "Power off");
203 this->cmd_data(CMD_POWEROFF, {(uint8_t) 0x00});
204}
205
207 ESP_LOGV(TAG, "Deep sleep");
208 // "Deepsleep between update": Ensure EPD sleep to avoid early hardware wearout!
209 this->cmd_data(CMD_DEEPSLEEP, {(uint8_t) 0xA5});
210
211 // Notes:
212 // - VDD: Some boards (Waveshare) with "clever reset logic" would allow switching off
213 // EPD VDD by pulling reset pin low for longer time.
214 // However, a) not all boards have this, b) reliable sequence timing is difficult,
215 // c) saving is not worth it after deepsleep command above.
216 // If needed: Better option is to drive VDD via MOSFET with separate enable pin.
217 //
218 // - Possible safe shutdown:
219 // EPaperBase::on_safe_shutdown() may also trigger deep_sleep() again.
220 // Regularly, in IDLE state, this does not make sense for this "deepsleep between update" model,
221 // but SPI sequence should simply be ignored by sleeping receiver.
222 // But if triggering during lengthy update, this quick SPI sleep sequence may have benefit.
223 // Optimally, EPDs should even be set all white for longer storage.
224 // But full sequence (>15s) not possible w/o app logic.
225}
226
227} // namespace esphome::epaper_spi
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
virtual void mark_failed()
Mark this component as failed.
virtual void digital_write(bool value)=0
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:764
void command(uint8_t value)
void fill(Color color) override
Definition epaper_spi.h:84
bool rotate_coordinates_(int &x, int &y)
Check and rotate coordinates based on the transform flags.
virtual bool initialise(bool partial)
void send_init_sequence_(const uint8_t *sequence, size_t length)
split_buffer::SplitBuffer buffer_
Definition epaper_spi.h:166
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length)
void wait_for_idle_(bool should_wait)
static constexpr uint16_t SLEEP_MS_RESET0
Wait time (millisec) for first reset phase: High.
void deep_sleep() override
Deepsleep: Must be used to avoid hardware wearout!
bool initialise(bool partial) override
Initialise (multistep sequence)
static constexpr uint8_t CMD_REFRESH
void write_fastinit_()
Internal: Send fast init sequence via undocumented vendor registers.
@ NONE
Initial/default value: Unused.
bool transfer_data() override
Buffer transfer.
static constexpr uint8_t CMD_POWEROFF
void draw_pixel_at(int x, int y, Color color) override
Draw colored pixel into frame buffer.
static constexpr uint16_t SLEEP_MS_RESET2
Wait time (millisec) for third reset phase: High.
bool transfer_buffer_chunks_()
Internal: Send raw buffer in chunks.
static constexpr uint8_t CMD_DEEPSLEEP
static constexpr uint16_t SLEEP_MS_RESET1
Wait time (millisec) for second reset phase: Low.
void refresh_screen(bool partial) override
Refresh screen.
static constexpr uint8_t CMD_TRANSFER
FSMState step_
Counter for tracking substeps within FSM state.
bool reset() override
Reset (multistep sequence)
void fill(uint8_t value) const
Fill the entire buffer with a single byte value.
constexpr NATIVE_COLOR color_to_bwyr(Color color, NATIVE_COLOR hw_black, NATIVE_COLOR hw_white, NATIVE_COLOR hw_yellow, NATIVE_COLOR hw_red)
Map RGB color to discrete BWYR hex 4 color key.
Definition colorconv.h:31
JD79660Color
Pixel color as 2bpp.
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6