ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
epaper_spi_t133a01.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include "esphome/core/log.h"
6
7namespace esphome::epaper_spi {
8
9static constexpr const char *const TAG = "epaper_spi.t133a01";
10
11// Color indices used in the 4bpp buffer (sprite-side)
12// These MUST match the Arduino GFX TFT_eSPI.h color definitions and
13// the remap_color()/COLOR_GET mapping:
14// 0x0F=BLACK, 0x00=WHITE, 0x02=GREEN, 0x06=RED, 0x0B=YELLOW, 0x0D=BLUE
15static constexpr uint8_t T133A01_BLACK = 0x0F;
16static constexpr uint8_t T133A01_WHITE = 0x00;
17static constexpr uint8_t T133A01_GREEN = 0x02;
18static constexpr uint8_t T133A01_RED = 0x06;
19static constexpr uint8_t T133A01_YELLOW = 0x0B;
20static constexpr uint8_t T133A01_BLUE = 0x0D;
21
22// T133A01 register addresses
23static constexpr uint8_t R00_PSR = 0x00;
24static constexpr uint8_t R01_PWR = 0x01;
25static constexpr uint8_t R02_POF = 0x02;
26static constexpr uint8_t R04_PON = 0x04;
27static constexpr uint8_t R05_BTST_N = 0x05;
28static constexpr uint8_t R06_BTST_P = 0x06;
29static constexpr uint8_t R10_DTM = 0x10;
30static constexpr uint8_t R12_DRF = 0x12;
31static constexpr uint8_t R50_CDI = 0x50;
32static constexpr uint8_t R61_TRES = 0x61;
33static constexpr uint8_t RA5_DCDC = 0xA5;
34static constexpr uint8_t RE0_CCSET = 0xE0;
35static constexpr uint8_t RE3_PWS = 0xE3;
36
43uint8_t EPaperT133A01::remap_color(uint8_t index) {
44 switch (index & 0x0F) {
45 case 0x0F:
46 return 0x00; // Black
47 case 0x00:
48 return 0x01; // White
49 case 0x02:
50 return 0x06; // Green
51 case 0x06:
52 return 0x03; // Red
53 case 0x0B:
54 return 0x02; // Yellow
55 case 0x0D:
56 return 0x05; // Blue
57 default:
58 return 0x01; // White fallback
59 }
60}
61
68 unsigned char max_rgb = std::max({color.r, color.g, color.b});
69 unsigned char min_rgb = std::min({color.r, color.g, color.b});
70
71 // Check for grayscale
72 if ((max_rgb - min_rgb) < 50) {
73 if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
74 return T133A01_WHITE;
75 }
76 return T133A01_BLACK;
77 }
78
79 bool r_on = (color.r > 128);
80 bool g_on = (color.g > 128);
81 bool b_on = (color.b > 128);
82
83 if (r_on && g_on && !b_on)
84 return T133A01_YELLOW;
85 if (r_on && !g_on && !b_on)
86 return T133A01_RED;
87 if (!r_on && g_on && !b_on)
88 return T133A01_GREEN;
89 if (!r_on && !g_on && b_on)
90 return T133A01_BLUE;
91 // Handle mixed colors: map to nearest primary
92 if (!r_on && g_on && b_on)
93 return T133A01_GREEN; // Cyan -> Green
94 if (r_on && !g_on)
95 return T133A01_RED; // Magenta -> Red
96 if (r_on)
97 return T133A01_WHITE;
98 return T133A01_BLACK;
99}
100
102 // Base setup initialises the buffer, the standard pins and the SPI bus.
104
105 // Both chip-selects are driven directly by this driver (the dual-CS
106 // protocol needs CS held HIGH while CS1 receives data, which the SPI
107 // bus cannot do). Start both deselected (HIGH).
108 this->cs_pin_->setup();
109 this->cs_pin_->digital_write(true);
110 this->cs1_pin_->setup();
111 this->cs1_pin_->digital_write(true);
112}
113
115 for (auto *enable_pin : this->enable_pins_) {
116 enable_pin->digital_write(true);
117 }
118 if (this->reset_pin_ != nullptr) {
119 if (this->state_ == EPaperState::RESET) {
120 this->reset_pin_->digital_write(false);
121 return false;
122 }
123 this->reset_pin_->digital_write(true);
124 }
125 return true;
126}
127
135bool EPaperT133A01::initialise(bool partial) {
136 // Init sequence mirrors the Arduino GFX library's EPD_INIT() macro
137 // (T133A01_Defines.h). Commands routed to CS only leave CS1 deselected;
138 // commands routed to both controllers assert CS and CS1 together.
139
140 // 0x74 - panel config (CS only)
141 this->write_command_(0x74, {0x00, 0x0C, 0x0C, 0xD9, 0xDD, 0xDD, 0x15, 0x15, 0x55}, true, false);
142 delay(10);
143
144 // 0xF0 - panel config (CS + CS1)
145 this->write_command_(0xF0, {0x49, 0x55, 0x13, 0x5D, 0x05, 0x10}, true, true);
146 delay(10);
147
148 // PSR - Panel Setting Register (CS + CS1)
149 this->write_command_(0x00, {0xDF, 0x69}, true, true);
150 delay(10);
151
152 // DCDC (CS only)
153 this->write_command_(RA5_DCDC, {0x44, 0x54, 0x00}, true, false);
154 delay(10);
155
156 // CDI (CS + CS1)
157 this->write_command_(R50_CDI, {0x37}, true, true);
158 delay(10);
159
160 // 0x60 (CS + CS1)
161 this->write_command_(0x60, {0x03, 0x03}, true, true);
162 delay(10);
163
164 // 0x86 (CS + CS1)
165 this->write_command_(0x86, {0x10}, true, true);
166 delay(10);
167
168 // PWS - Phase Width Setting (CS + CS1)
169 this->write_command_(RE3_PWS, {0x22}, true, true);
170 delay(10);
171
172 // TRES - Resolution Setting (CS + CS1).
173 // With width=1200, height=1600: first word = width = 1200, second word = height/2 = 800.
174 this->write_command_(R61_TRES,
175 {(uint8_t) (this->width_ >> 8), (uint8_t) (this->width_ & 0xFF),
176 (uint8_t) ((this->height_ / 2) >> 8), (uint8_t) ((this->height_ / 2) & 0xFF)},
177 true, true);
178 delay(10);
179
180 // PWR - Power Setting (CS only)
181 this->write_command_(R01_PWR, {0x0F, 0x00, 0x28, 0x2C, 0x28, 0x38}, true, false);
182 delay(10);
183
184 // 0xB6 (CS only)
185 this->write_command_(0xB6, {0x07}, true, false);
186 delay(10);
187
188 // BTST_P (CS only)
189 this->write_command_(R06_BTST_P, {0xE0, 0x20}, true, false);
190 delay(10);
191
192 // 0xB7 (CS only)
193 this->write_command_(0xB7, {0x01}, true, false);
194 delay(10);
195
196 // BTST_N (CS only)
197 this->write_command_(R05_BTST_N, {0xE0, 0x20}, true, false);
198 delay(10);
199
200 // 0xB0 (CS only)
201 this->write_command_(0xB0, {0x01}, true, false);
202 delay(10);
203
204 // 0xB1 (CS only)
205 this->write_command_(0xB1, {0x02}, true, false);
206 delay(10);
207
208 return true;
209}
210
211void EPaperT133A01::write_command_(uint8_t command, const uint8_t *data, size_t length, bool use_cs, bool use_cs1) {
212 ESP_LOGV(TAG, "Command: 0x%02X, Length: %u, CS: %d, CS1: %d", command, (unsigned) length, use_cs, use_cs1);
213 // Chip-selects are active-low: assert the requested controllers.
214 this->cs_pin_->digital_write(!use_cs);
215 this->cs1_pin_->digital_write(!use_cs1);
216 this->dc_pin_->digital_write(false);
217 this->enable();
218 this->write_byte(command);
219 if (length > 0) {
220 this->dc_pin_->digital_write(true);
221 this->write_array(data, length);
222 }
223 this->disable();
224 this->cs_pin_->digital_write(true);
225 this->cs1_pin_->digital_write(true);
226}
227
229 if (this->get_clipping().is_set()) {
230 EPaperBase::fill(color);
231 return;
232 }
233 auto pixel_color = color_to_index(color);
234 this->buffer_.fill(pixel_color + (pixel_color << 4));
235}
236
237void EPaperT133A01::draw_pixel_at(int x, int y, Color color) {
238 if (!this->rotate_coordinates_(x, y))
239 return;
240 auto pixel_bits = color_to_index(color);
241 uint32_t pixel_position = x + y * this->get_width_internal();
242 uint32_t byte_position = pixel_position / 2;
243 auto original = this->buffer_[byte_position];
244 if ((pixel_position & 1) != 0) {
245 this->buffer_[byte_position] = (original & 0xF0) | pixel_bits;
246 } else {
247 this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4);
248 }
249}
250
252 ESP_LOGV(TAG, "Power on");
253 this->write_command_(R04_PON, true, true);
254}
255
257 ESP_LOGV(TAG, "Power off");
258 this->write_command_(R02_POF, {0x00}, true, true);
259}
260
262 ESP_LOGV(TAG, "Refresh screen");
263 // Display Refresh
264 this->write_command_(R12_DRF, {0x01}, true, true);
265}
266
268 ESP_LOGV(TAG, "Deep sleep");
269 this->write_command_(0x07, {0xA5}, true, true);
270}
271
273 const uint32_t start_time = millis();
274 const uint16_t bytes_per_half_row = this->width_ / 4;
275 const uint16_t total_rows = this->height_;
276 const uint16_t bytes_per_row = this->width_ / 2;
277 uint8_t line_data[400] = {};
278
279 size_t half = this->current_data_index_;
280
281 // --- CCSET: select color set before data transfer (CS + CS1) ---
282 if (half == 0) {
283 this->write_command_(RE0_CCSET, {0x01}, true, true);
284 this->wait_for_idle_(true);
285 delay(10);
286 }
287
288 // --- CS phase: left half of each row via CS ---
289 // T133A01 requires CS to stay LOW for the ENTIRE DTM data stream.
290 // Toggling CS between chunks resets the controller's data pointer,
291 // causing only the last chunk to be retained. Keep CS asserted
292 // across timeout boundaries by NOT deselecting on yield.
293 if (half < total_rows) {
294 if (half == 0) {
295 this->cs_pin_->digital_write(false); // select CS
296 this->cs1_pin_->digital_write(true); // deselect CS1
297 this->dc_pin_->digital_write(false);
298 this->enable();
299 this->write_byte(R10_DTM);
300 this->dc_pin_->digital_write(true);
301 }
302
303 while (half < total_rows) {
304 size_t buf_offset = half * bytes_per_row;
305 for (uint16_t col = 0; col < bytes_per_half_row; col++) {
306 uint8_t b = this->buffer_[buf_offset + col];
307 line_data[col] = (remap_color(b >> 4) << 4) | remap_color(b & 0x0F);
308 }
309 this->write_array(line_data, bytes_per_half_row);
310 half++;
311 this->current_data_index_ = half;
312
313 if (millis() - start_time > MAX_TRANSFER_TIME) {
314 return false;
315 }
316 }
317 ESP_LOGD(TAG, "CS phase done");
318 this->disable();
319 this->cs_pin_->digital_write(true); // deselect CS
320 }
321
322 // --- CS1 phase: right half of each row via CS1 ---
323 // Same continuous-transaction requirement as the CS phase.
324 // CS is held HIGH so only CS1 receives the data.
325 if (half >= total_rows && half < total_rows * 2) {
326 size_t cs1_row = half - total_rows;
327
328 if (cs1_row == 0) {
329 this->cs_pin_->digital_write(true); // deselect CS
330 this->cs1_pin_->digital_write(false); // select CS1
331 this->enable();
332 this->dc_pin_->digital_write(false);
333 this->write_byte(R10_DTM);
334 this->dc_pin_->digital_write(true);
335 }
336
337 while (half < total_rows * 2) {
338 size_t row = half - total_rows;
339 size_t buf_offset = row * bytes_per_row + bytes_per_half_row;
340 for (uint16_t col = 0; col < bytes_per_half_row; col++) {
341 uint8_t b = this->buffer_[buf_offset + col];
342 line_data[col] = (remap_color(b >> 4) << 4) | remap_color(b & 0x0F);
343 }
344 this->write_array(line_data, bytes_per_half_row);
345 half++;
346 this->current_data_index_ = half;
347
348 if (millis() - start_time > MAX_TRANSFER_TIME) {
349 return false;
350 }
351 }
352 ESP_LOGD(TAG, "CS1 phase done");
353 this->disable();
354 this->cs1_pin_->digital_write(true); // deselect CS1
355 }
356
357 this->current_data_index_ = 0;
358 return true;
359}
360
363 LOG_PIN(" CS Pin: ", this->cs_pin_);
364 LOG_PIN(" CS1 Pin: ", this->cs1_pin_);
365}
366
367} // namespace esphome::epaper_spi
virtual void setup()=0
virtual void digital_write(bool value)=0
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
void command(uint8_t value)
void fill(Color color) override
Definition epaper_spi.h:92
bool rotate_coordinates_(int &x, int &y)
Check and rotate coordinates based on the transform flags.
split_buffer::SplitBuffer buffer_
Definition epaper_spi.h:177
void wait_for_idle_(bool should_wait)
std::vector< GPIOPin * > enable_pins_
Definition epaper_spi.h:181
static uint8_t remap_color(uint8_t index)
Apply COLOR_GET remap table to translate sprite indices to hardware values.
void refresh_screen(bool partial) override
static uint8_t color_to_index(Color color)
Convert Color to 4-bit T133A01 color index.
bool initialise(bool partial) override
Initialise the T133A01 display.
void draw_pixel_at(int x, int y, Color color) override
void write_command_(uint8_t command, const uint8_t *data, size_t length, bool use_cs, bool use_cs1)
Send a command (and optional data) selecting one or both controllers.
void fill(uint8_t value) const
Fill the entire buffer with a single byte value.
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
static void uint32_t
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6