ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
epaper_spi.cpp
Go to the documentation of this file.
1#include "epaper_spi.h"
2#include <algorithm>
3#include <cinttypes>
6#include "esphome/core/log.h"
7
8namespace esphome::epaper_spi {
9
10ESPHOME_LOG_TAG(TAG, "epaper_spi");
11static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
12
13static constexpr const char *const EPAPER_STATE_STRINGS[] = {
14 "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
15 "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
16};
17
19 if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
20 return EPAPER_STATE_STRINGS[idx];
21 return "Unknown";
22}
23
25 if (!this->init_buffer_(this->buffer_length_)) {
26 this->mark_failed(LOG_STR("Failed to initialise buffer"));
27 return;
28 }
29 this->setup_pins_();
30 this->spi_setup();
31}
32
33bool EPaperBase::init_buffer_(size_t buffer_length) {
34 if (!this->buffer_.init(buffer_length)) {
35 return false;
36 }
37 this->clear();
38 return true;
39}
40
42 if (!this->is_using_partial_update_())
43 return false;
44 if (this->sent_.init(length))
45 return true;
46 ESP_LOGW(TAG, "No memory for the previous frame; partial updates will send the whole drawn area");
47 return false;
48}
49
52 size_t index = 0;
53 while (index != this->buffer_.size()) {
54 size_t length;
55 const uint8_t *data = this->buffer_.get_span(index, length);
56 for (size_t i = 0; i != length; i++)
57 hash = fnv1a_hash_extend(hash, data[i]);
58 index += length;
59 }
60 return hash;
61}
62
64 this->x_low_ = 0;
65 this->y_low_ = 0;
66 this->x_high_ = this->width_;
67 this->y_high_ = this->height_;
68}
69
71 this->x_low_ = this->width_;
72 this->x_high_ = 0;
73 this->y_low_ = this->height_;
74 this->y_high_ = 0;
75}
76
78 for (auto *pin : this->enable_pins_) {
79 pin->setup();
80 pin->digital_write(true);
81 }
82 this->dc_pin_->setup(); // OUTPUT
83 this->dc_pin_->digital_write(false);
84
85 if (this->reset_pin_ != nullptr) {
86 this->reset_pin_->setup(); // OUTPUT
87 this->reset_pin_->digital_write(true);
88 }
89
90 if (this->busy_pin_ != nullptr) {
91 this->busy_pin_->setup(); // INPUT
92 }
93}
94
96
97void EPaperBase::command(uint8_t value) {
98 ESP_LOGV(TAG, "Command: 0x%02X", value);
99 this->dc_pin_->digital_write(false);
100 this->enable();
101 this->write_byte(value);
102 this->disable();
103}
104
105// write a command followed by zero or more bytes of data.
106void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
107#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
108 char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
109 ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
110 format_hex_pretty_to(hex_buf, ptr, length, '.'));
111#endif
112
113 this->dc_pin_->digital_write(false);
114 this->enable();
115 this->write_byte(command);
116 if (length > 0) {
117 this->dc_pin_->digital_write(true);
118 this->write_array(ptr, length);
119 }
120 this->disable();
121}
122
124 if (this->busy_pin_ == nullptr) {
125 return true;
126 }
127 return !this->busy_pin_->digital_read();
128}
129
130bool EPaperBase::reset() { return this->hardware_reset_(); }
131
133 if (this->reset_pin_ != nullptr) {
134 if (this->state_ == EPaperState::RESET) {
135 this->reset_pin_->digital_write(false);
136 return false;
137 }
138 this->reset_pin_->digital_write(true);
139 }
140 return true;
141}
142
144 switch (this->rotation_) {
146 this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_X);
147 break;
149 this->effective_transform_ = this->transform_ ^ (MIRROR_Y | MIRROR_X);
150 break;
152 this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_Y);
153 break;
154 default:
155 this->effective_transform_ = this->transform_;
156 break;
157 }
158}
159
161 if (this->state_ != EPaperState::IDLE) {
162 ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
163 return;
164 }
166 this->enable_loop();
167#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
168 this->update_start_time_ = millis();
169#endif
170}
171
172void EPaperBase::wait_for_idle_(bool should_wait) {
173 // The wait starts when the step's delay ends, or now for a wait in the middle of a step
174 const uint32_t now = millis();
175 this->waiting_for_idle_start_ = static_cast<int32_t>(this->delay_until_ - now) > 0 ? this->delay_until_ : now;
176 this->waiting_for_idle_ = should_wait;
177}
178
186 auto now = millis();
187 // using modulus arithmetic to handle wrap-around
188 int diff = now - this->delay_until_;
189 if (diff < 0) {
190 // Running the loop continuously cannot shorten a delay longer than one loop interval
191 if (-diff > static_cast<int>(App.get_loop_interval())) {
193 } else {
195 }
196 return;
197 }
198 if (this->waiting_for_idle_) {
199 if (this->is_idle_()) {
200 this->waiting_for_idle_ = false;
202#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
203 ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
204#endif
205 } else {
206 const uint32_t busy_for = now - this->waiting_for_idle_start_;
207 if (busy_for > this->busy_timeout_seconds_ * 1000u) {
208 this->abandon_update_();
209 return;
210 }
211 if (busy_for > FAST_BUSY_POLL_MS)
213#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
214 if (now - this->waiting_for_idle_last_print_ >= 1000) {
215 ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
217 }
218#endif
219 return;
220 }
221 }
222 this->process_state_();
223 if (this->is_failed())
225}
226
227// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
229 ESP_LOGE(TAG, "Display stayed busy for %u s in state %s; check the busy pin", (unsigned) this->busy_timeout_seconds_,
231 this->status_set_warning(LOG_STR("Busy pin timeout"));
233 this->abort_transfer();
234 // Leave the panel powered down rather than with its high-voltage drivers enabled
235 this->power_off();
236 this->full_update_requested_ = true;
237 this->current_data_index_ = 0;
239}
240
250 ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
251 switch (this->state_) {
252 default:
253 ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
255 break;
257 this->disable_loop();
258 break;
261 if (this->reset()) {
263 } else {
265 }
266 break;
268 this->do_update_(); // Calls ESPHome (current page) lambda
269 if (this->full_update_requested_) {
270 // Refresh the whole panel even if nothing was drawn
271 this->full_update_requested_ = false;
272 this->update_count_ = 0;
273 this->set_full_bounds_();
274 }
277 return;
278 }
279 if (this->is_using_partial_update_()) {
280 const uint32_t frame_hash = this->buffer_hash_();
281 if (this->update_count_ != 0 && this->shown_hash_valid_ && frame_hash == this->shown_hash_) {
282 ESP_LOGD(TAG, "Frame unchanged, refresh skipped");
283 this->reset_bounds_();
285 return;
286 }
287 this->shown_hash_ = frame_hash;
288 }
289 this->drawn_since_hash_ = false;
290 // Valid again only once this frame is on the panel, so an abandoned update leaves nothing to match
291 this->shown_hash_valid_ = false;
292 // A full update resets update_count_ to zero, so a non-zero count marks a partial one
293 this->partial_ = this->update_count_ != 0;
294 this->sent_count_ = 0;
296 break;
298 if (!this->initialise(this->partial_)) {
299 return; // Not done yet, come back next loop
300 }
302 break;
304 if (!this->transfer_data()) {
305 return; // Not done yet, come back next loop
306 }
307 if (this->sent_.is_valid() && this->sent_count_ == this->sent_.size())
308 this->sent_valid_ = true;
309 // A draw during the chunked push may not have reached the panel, so the next update sends the whole frame
311 if (this->drawn_since_hash_) {
312 this->set_full_bounds_();
313 } else {
314 this->reset_bounds_();
315 }
317 break;
319 this->power_on();
321 break;
323 this->refresh_screen(this->partial_);
324 this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
326 break;
328 if (!this->after_refresh()) {
329 return; // Not done yet, come back next loop
330 }
331 this->power_off();
333 break;
335 this->deep_sleep();
337 this->status_clear_warning();
339 ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
340 break;
341 }
342}
343
345 ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
346 this->state_ = state;
347 // allow subclasses to nominate delays
348 if (delay == 0)
349 delay = this->next_delay_;
350 this->next_delay_ = 0;
351 this->delay_until_ = millis() + delay;
353 ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
354 TRUEFALSE(this->waiting_for_idle_));
355 // Each step of an update otherwise waits for the next loop iteration; loop() slows down again for long waits
356 if (state == EPaperState::IDLE) {
358 this->disable_loop();
359 } else {
361 }
362}
363
365 this->dc_pin_->digital_write(true);
366 this->enable();
367}
368
370
371void EPaperBase::send_init_sequence_(const uint8_t *sequence, size_t length) {
372 size_t index = 0;
373
374 while (index != length) {
375 if (length - index < 2) {
376 this->mark_failed(LOG_STR("Malformed init sequence"));
377 return;
378 }
379 const uint8_t cmd = sequence[index++];
380 if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
381 ESP_LOGV(TAG, "Delay %dms", cmd);
382 delay(cmd);
383 } else {
384 const uint8_t num_args = x & 0x7F;
385 if (length - index < num_args) {
386 ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
387 this->mark_failed();
388 return;
389 }
390 this->cmd_data(cmd, sequence + index, num_args);
391 index += num_args;
392 }
393 }
394}
395
396bool EPaperBase::initialise(bool partial) {
398 return true;
399}
400
408 if (this->is_point_clipped(x, y))
409 return false;
410 if (this->effective_transform_ & SWAP_XY)
411 std::swap(x, y);
412 if (this->effective_transform_ & MIRROR_X)
413 x = this->width_ - x - 1;
414 if (this->effective_transform_ & MIRROR_Y)
415 y = this->height_ - y - 1;
416 if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
417 return false;
418 this->x_low_ = clamp_at_most(this->x_low_, x);
419 this->x_high_ = clamp_at_least(this->x_high_, x + 1);
420 this->y_low_ = clamp_at_most(this->y_low_, y);
421 this->y_high_ = clamp_at_least(this->y_high_, y + 1);
422 this->drawn_since_hash_ = true;
423 return true;
424}
425
432void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
433 if (!rotate_coordinates_(x, y))
434 return;
435 const size_t byte_position = y * this->row_width_ + x / 8;
436 const uint8_t bit_position = x % 8;
437 const uint8_t pixel_bit = 0x80u >> bit_position;
438 const auto original = this->buffer_[byte_position];
439 if (color_to_mono(color) == 0) {
440 this->buffer_[byte_position] = original & ~pixel_bit;
441 } else {
442 this->buffer_[byte_position] = original | pixel_bit;
443 }
444}
445
446namespace {
447// One source row of an RGB565 block, written into a contiguous 1 bit per pixel buffer. Without SwapAxes the row lies
448// along a buffer row and the bit moves by step; with it the row lies along a bit column and the byte moves by step
449// rows.
450template<bool SwapAxes, bool BigEndianSource>
451void HOT write_rgb565_row(uint8_t *byte, uint8_t mask, int step, size_t row_width, const uint8_t *source, int count,
452 const Rgb565ToMono &to_mono) {
453 const ptrdiff_t byte_step = SwapAxes ? step * static_cast<ptrdiff_t>(row_width) : 0;
454 for (; count != 0; count--, source += 2) {
455 const uint16_t value = BigEndianSource ? encode_uint16(source[0], source[1]) : encode_value_le<uint16_t>(source);
456 if (to_mono(value)) {
457 *byte |= mask;
458 } else {
459 *byte &= ~mask;
460 }
461 if constexpr (SwapAxes) {
462 byte += byte_step;
463 } else if (step > 0) {
464 mask >>= 1;
465 if (mask == 0) {
466 mask = 0x80;
467 byte++;
468 }
469 } else {
470 mask <<= 1;
471 if (mask == 0) {
472 mask = 0x01;
473 byte--;
474 }
475 }
476 }
477}
478} // namespace
479
480// Each source column maps to one buffer coordinate and each source row to the other, each moving by 1 per pixel, so
481// the visible part of a block and its dirty area are worked out once for the whole block.
482void HOT EPaperBase::draw_rgb565_block_(uint8_t *buffer, int x_start, int y_start, int w, int h, const uint8_t *ptr,
483 bool big_endian, int x_offset, int y_offset, int x_pad) {
484 const bool swap = this->effective_transform_ & SWAP_XY;
485 const bool mirror_x = this->effective_transform_ & MIRROR_X;
486 const bool mirror_y = this->effective_transform_ & MIRROR_Y;
487 // The buffer axis a source column moves along, and the one a source row moves along
488 const int column_limit = swap ? this->height_ : this->width_;
489 const int row_limit = swap ? this->width_ : this->height_;
490 const bool column_mirrored = swap ? mirror_y : mirror_x;
491 const bool row_mirrored = swap ? mirror_x : mirror_y;
492 const int first_column = std::max(0, -x_start);
493 const int end_column = std::min(w, column_limit - x_start);
494 const int first_row = std::max(0, -y_start);
495 const int end_row = std::min(h, row_limit - y_start);
496 if (first_column >= end_column || first_row >= end_row)
497 return;
498 auto column_to_buffer = [&](int column) {
499 const int u = x_start + column;
500 return column_mirrored ? column_limit - 1 - u : u;
501 };
502 auto row_to_buffer = [&](int row) {
503 const int v = y_start + row;
504 return row_mirrored ? row_limit - 1 - v : v;
505 };
506
507 const int column_low = std::min(column_to_buffer(first_column), column_to_buffer(end_column - 1));
508 const int row_low = std::min(row_to_buffer(first_row), row_to_buffer(end_row - 1));
509 const int columns = end_column - first_column;
510 const int rows = end_row - first_row;
511 const int x_low = swap ? row_low : column_low;
512 const int y_low = swap ? column_low : row_low;
513 const int x_high = x_low + (swap ? rows : columns);
514 const int y_high = y_low + (swap ? columns : rows);
515
516 const Rgb565ToMono to_mono;
517 const size_t line_stride = x_offset + w + x_pad;
518 const size_t row_width = this->row_width_;
519 const int step = column_mirrored ? -1 : 1;
520 for (int row = first_row; row != end_row; row++) {
521 const uint8_t *source = ptr + ((y_offset + row) * line_stride + x_offset + first_column) * 2;
522 const int along = column_to_buffer(first_column);
523 const int across = row_to_buffer(row);
524 const int x = swap ? across : along;
525 const int y = swap ? along : across;
526 uint8_t *byte = buffer + y * row_width + x / 8;
527 const uint8_t mask = 0x80u >> (x % 8);
528 if (swap) {
529 if (big_endian) {
530 write_rgb565_row<true, true>(byte, mask, step, row_width, source, columns, to_mono);
531 } else {
532 write_rgb565_row<true, false>(byte, mask, step, row_width, source, columns, to_mono);
533 }
534 } else if (big_endian) {
535 write_rgb565_row<false, true>(byte, mask, step, row_width, source, columns, to_mono);
536 } else {
537 write_rgb565_row<false, false>(byte, mask, step, row_width, source, columns, to_mono);
538 }
539 }
540 this->x_low_ = clamp_at_most(this->x_low_, x_low);
541 this->x_high_ = clamp_at_least(this->x_high_, x_high);
542 this->y_low_ = clamp_at_most(this->y_low_, y_low);
543 this->y_high_ = clamp_at_least(this->y_high_, y_high);
544 this->drawn_since_hash_ = true;
545}
546
547// Writes an RGB565 block of pixels, as LVGL sends them, straight into a 1 bit per pixel buffer that is one block (the
548// usual case): the same result as draw_pixel_at() for each pixel. Anything else goes through draw_pixel_at().
549void HOT EPaperBase::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr,
551 int x_pad) {
552 size_t contiguous_length;
553 uint8_t *contiguous = this->buffer_.get_span(0, contiguous_length);
554 if (!this->buffer_is_mono_() || this->get_clipping().is_set() || bitness != COLOR_BITNESS_565 ||
555 contiguous_length != this->buffer_length_) {
557 return;
558 }
560}
561
563 LOG_DISPLAY("", "E-Paper SPI", this);
564 ESP_LOGCONFIG(TAG,
565 " Model: %s\n"
566 " SPI Data Rate: %uMHz\n"
567 " Full update every: %d\n"
568 " Swap X/Y: %s\n"
569 " Mirror X: %s\n"
570 " Mirror Y: %s",
571 this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_,
572 YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X),
573 YESNO(this->transform_ & MIRROR_Y));
574 LOG_PIN(" Reset Pin: ", this->reset_pin_);
575 LOG_PIN(" DC Pin: ", this->dc_pin_);
576 LOG_PIN(" Busy Pin: ", this->busy_pin_);
577 LOG_PIN(" CS Pin: ", this->cs_);
578 LOG_UPDATE_INTERVAL(this);
579}
580
581} // namespace esphome::epaper_spi
uint32_t get_loop_interval() const
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:296
virtual void setup()=0
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
void stop()
Stop running the loop continuously.
Definition helpers.cpp:841
void start()
Start running the loop continuously.
Definition helpers.cpp:835
int int int const uint8_t ColorOrder ColorBitness bitness
Definition display.h:392
int int int const uint8_t ColorOrder ColorBitness bool int int y_offset
Definition display.h:392
int int int const uint8_t ColorOrder ColorBitness bool int int int x_pad
Definition display.h:392
int int int const uint8_t * ptr
Definition display.h:391
bool ESPHOME_ALWAYS_INLINE is_point_clipped(int x, int y) const
Whether (x, y) falls outside the active clipping rectangle.
Definition display.h:810
int int int const uint8_t ColorOrder ColorBitness bool big_endian
Definition display.h:392
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:826
int int int const uint8_t ColorOrder ColorBitness bool int x_offset
Definition display.h:392
DisplayRotation rotation_
Definition display.h:863
virtual void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad)
Draws a block for draw_pixels_at(); the parameters are the same.
Definition display.cpp:119
color_to_mono() of an RGB565 value as ColorUtil::to_color() reads it, from table lookups instead of a...
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 init_sent_frame_(size_t length)
virtual bool after_refresh()
Called after the refresh, before power off, repeatedly until it returns true.
Definition epaper_spi.h:194
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:218
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length)
void draw_rgb565_block_(uint8_t *buffer, int x_start, int y_start, int w, int h, const uint8_t *ptr, bool big_endian, int x_offset, int y_offset, int x_pad)
split_buffer::SplitBuffer sent_
Definition epaper_spi.h:219
void set_state_(EPaperState state, uint16_t delay=0)
const char * epaper_state_to_string_()
virtual void power_on()=0
Power the display on.
HighFrequencyLoopRequester high_frequency_loop_
Definition epaper_spi.h:231
void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override
void wait_for_idle_(bool should_wait)
bool hardware_reset_()
The reset pin part of reset(), for a driver that must skip what its parent adds to it.
float get_setup_priority() const override
bool init_buffer_(size_t buffer_length)
std::vector< GPIOPin * > enable_pins_
Definition epaper_spi.h:229
uint32_t data_rate_
Definition spi.h:436
bool init(size_t total_length, size_t max_buffer_size=SIZE_MAX)
const uint8_t * get_span(size_t index, size_t &length) const
bool state
Definition fan.h:2
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:139
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:138
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:137
static void int int int int const uint8_t ColorOrder ColorBitness bool big_endian
Definition display.cpp:101
static void int int int int const uint8_t ColorOrder ColorBitness bitness
Definition display.cpp:100
static void int x_start
Definition display.cpp:62
static void int int int int const uint8_t int x_offset
Definition display.cpp:63
static void int int y_start
Definition display.cpp:62
static void int int int int const uint8_t int int y_offset
Definition display.cpp:63
static void int int int w
Definition display.cpp:62
ESPHOME_LOG_TAG(TAG, "display")
constexpr uint8_t color_to_mono(Color color)
Map an RGB color to a single monochrome bit: 1 = white, 0 = black.
static void int int int int h
Definition display.cpp:62
static void int int int int const uint8_t * ptr
Definition display.cpp:63
static void int int int int const uint8_t int int int x_pad
Definition display.cpp:63
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:2478
constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str)
Extend a FNV-1a hash with additional string data.
Definition helpers.h:950
constexpr uint32_t FNV1_OFFSET_BASIS
FNV-1 32-bit offset basis.
Definition helpers.h:923
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:498
constexpr T encode_value_le(const uint8_t *bytes)
Encode a value from its constituent bytes (from least to most significant) in an array with length si...
Definition helpers.h:1039
T clamp_at_least(T value, U min)
Definition helpers.h:2473
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:1609
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:998
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6