mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
[epaper_spi] Add T133A01 6-color e-paper driver for reTerminal E1004 (#16706)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
@@ -112,6 +112,7 @@ def model_schema(config):
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||
),
|
||||
**model.get_config_options(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -198,6 +199,7 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
await display.register_display(var, config)
|
||||
config = await model.to_code(var, config)
|
||||
await spi.register_spi_device(var, config, write_only=True)
|
||||
|
||||
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
#include "epaper_spi_t133a01.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
static constexpr const char *const TAG = "epaper_spi.t133a01";
|
||||
|
||||
// Color indices used in the 4bpp buffer (sprite-side)
|
||||
// These MUST match the Arduino GFX TFT_eSPI.h color definitions and
|
||||
// the remap_color()/COLOR_GET mapping:
|
||||
// 0x0F=BLACK, 0x00=WHITE, 0x02=GREEN, 0x06=RED, 0x0B=YELLOW, 0x0D=BLUE
|
||||
static constexpr uint8_t T133A01_BLACK = 0x0F;
|
||||
static constexpr uint8_t T133A01_WHITE = 0x00;
|
||||
static constexpr uint8_t T133A01_GREEN = 0x02;
|
||||
static constexpr uint8_t T133A01_RED = 0x06;
|
||||
static constexpr uint8_t T133A01_YELLOW = 0x0B;
|
||||
static constexpr uint8_t T133A01_BLUE = 0x0D;
|
||||
|
||||
// T133A01 register addresses
|
||||
static constexpr uint8_t R00_PSR = 0x00;
|
||||
static constexpr uint8_t R01_PWR = 0x01;
|
||||
static constexpr uint8_t R02_POF = 0x02;
|
||||
static constexpr uint8_t R04_PON = 0x04;
|
||||
static constexpr uint8_t R05_BTST_N = 0x05;
|
||||
static constexpr uint8_t R06_BTST_P = 0x06;
|
||||
static constexpr uint8_t R10_DTM = 0x10;
|
||||
static constexpr uint8_t R12_DRF = 0x12;
|
||||
static constexpr uint8_t R50_CDI = 0x50;
|
||||
static constexpr uint8_t R61_TRES = 0x61;
|
||||
static constexpr uint8_t RA5_DCDC = 0xA5;
|
||||
static constexpr uint8_t RE0_CCSET = 0xE0;
|
||||
static constexpr uint8_t RE3_PWS = 0xE3;
|
||||
|
||||
/**
|
||||
* COLOR_GET remap table from T133A01_Defines.h.
|
||||
* Translates 4bpp sprite color index to the hardware pixel encoding.
|
||||
* Sprite: 0x0F=BLACK 0x00=WHITE 0x02=GREEN 0x06=RED 0x0B=YELLOW 0x0D=BLUE
|
||||
* HW: 0x00=BLACK 0x01=WHITE 0x06=GREEN 0x03=RED 0x02=YELLOW 0x05=BLUE
|
||||
*/
|
||||
uint8_t EPaperT133A01::remap_color(uint8_t index) {
|
||||
switch (index & 0x0F) {
|
||||
case 0x0F:
|
||||
return 0x00; // Black
|
||||
case 0x00:
|
||||
return 0x01; // White
|
||||
case 0x02:
|
||||
return 0x06; // Green
|
||||
case 0x06:
|
||||
return 0x03; // Red
|
||||
case 0x0B:
|
||||
return 0x02; // Yellow
|
||||
case 0x0D:
|
||||
return 0x05; // Blue
|
||||
default:
|
||||
return 0x01; // White fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an ESPHome Color to a 4-bit sprite color index.
|
||||
* Index values match the Arduino GFX TFT_eSPI color definitions:
|
||||
* 0x00=WHITE, 0x02=GREEN, 0x06=RED, 0x0B=YELLOW, 0x0D=BLUE, 0x0F=BLACK
|
||||
*/
|
||||
uint8_t EPaperT133A01::color_to_index(Color color) {
|
||||
unsigned char max_rgb = std::max({color.r, color.g, color.b});
|
||||
unsigned char min_rgb = std::min({color.r, color.g, color.b});
|
||||
|
||||
// Check for grayscale
|
||||
if ((max_rgb - min_rgb) < 50) {
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
|
||||
return T133A01_WHITE;
|
||||
}
|
||||
return T133A01_BLACK;
|
||||
}
|
||||
|
||||
bool r_on = (color.r > 128);
|
||||
bool g_on = (color.g > 128);
|
||||
bool b_on = (color.b > 128);
|
||||
|
||||
if (r_on && g_on && !b_on)
|
||||
return T133A01_YELLOW;
|
||||
if (r_on && !g_on && !b_on)
|
||||
return T133A01_RED;
|
||||
if (!r_on && g_on && !b_on)
|
||||
return T133A01_GREEN;
|
||||
if (!r_on && !g_on && b_on)
|
||||
return T133A01_BLUE;
|
||||
// Handle mixed colors: map to nearest primary
|
||||
if (!r_on && g_on && b_on)
|
||||
return T133A01_GREEN; // Cyan -> Green
|
||||
if (r_on && !g_on)
|
||||
return T133A01_RED; // Magenta -> Red
|
||||
if (r_on)
|
||||
return T133A01_WHITE;
|
||||
return T133A01_BLACK;
|
||||
}
|
||||
|
||||
void EPaperT133A01::setup() {
|
||||
// Base setup initialises the buffer, the standard pins and the SPI bus.
|
||||
EPaperBase::setup();
|
||||
|
||||
// Both chip-selects are driven directly by this driver (the dual-CS
|
||||
// protocol needs CS held HIGH while CS1 receives data, which the SPI
|
||||
// bus cannot do). Start both deselected (HIGH).
|
||||
this->cs_pin_->setup();
|
||||
this->cs_pin_->digital_write(true);
|
||||
this->cs1_pin_->setup();
|
||||
this->cs1_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
bool EPaperT133A01::reset() {
|
||||
for (auto *enable_pin : this->enable_pins_) {
|
||||
enable_pin->digital_write(true);
|
||||
}
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
if (this->state_ == EPaperState::RESET) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
return false;
|
||||
}
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the T133A01 display.
|
||||
*
|
||||
* The init sequence uses a mix of CS and CS1 commands as per the Arduino driver.
|
||||
* The base class init_sequence is NOT used for T133A01 because the dual-CS
|
||||
* protocol requires per-command routing.
|
||||
*/
|
||||
bool EPaperT133A01::initialise(bool partial) {
|
||||
// Init sequence mirrors the Arduino GFX library's EPD_INIT() macro
|
||||
// (T133A01_Defines.h). Commands routed to CS only leave CS1 deselected;
|
||||
// commands routed to both controllers assert CS and CS1 together.
|
||||
|
||||
// 0x74 - panel config (CS only)
|
||||
this->write_command_(0x74, {0x00, 0x0C, 0x0C, 0xD9, 0xDD, 0xDD, 0x15, 0x15, 0x55}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xF0 - panel config (CS + CS1)
|
||||
this->write_command_(0xF0, {0x49, 0x55, 0x13, 0x5D, 0x05, 0x10}, true, true);
|
||||
delay(10);
|
||||
|
||||
// PSR - Panel Setting Register (CS + CS1)
|
||||
this->write_command_(0x00, {0xDF, 0x69}, true, true);
|
||||
delay(10);
|
||||
|
||||
// DCDC (CS only)
|
||||
this->write_command_(RA5_DCDC, {0x44, 0x54, 0x00}, true, false);
|
||||
delay(10);
|
||||
|
||||
// CDI (CS + CS1)
|
||||
this->write_command_(R50_CDI, {0x37}, true, true);
|
||||
delay(10);
|
||||
|
||||
// 0x60 (CS + CS1)
|
||||
this->write_command_(0x60, {0x03, 0x03}, true, true);
|
||||
delay(10);
|
||||
|
||||
// 0x86 (CS + CS1)
|
||||
this->write_command_(0x86, {0x10}, true, true);
|
||||
delay(10);
|
||||
|
||||
// PWS - Phase Width Setting (CS + CS1)
|
||||
this->write_command_(RE3_PWS, {0x22}, true, true);
|
||||
delay(10);
|
||||
|
||||
// TRES - Resolution Setting (CS + CS1).
|
||||
// With width=1200, height=1600: first word = width = 1200, second word = height/2 = 800.
|
||||
this->write_command_(R61_TRES,
|
||||
{(uint8_t) (this->width_ >> 8), (uint8_t) (this->width_ & 0xFF),
|
||||
(uint8_t) ((this->height_ / 2) >> 8), (uint8_t) ((this->height_ / 2) & 0xFF)},
|
||||
true, true);
|
||||
delay(10);
|
||||
|
||||
// PWR - Power Setting (CS only)
|
||||
this->write_command_(R01_PWR, {0x0F, 0x00, 0x28, 0x2C, 0x28, 0x38}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB6 (CS only)
|
||||
this->write_command_(0xB6, {0x07}, true, false);
|
||||
delay(10);
|
||||
|
||||
// BTST_P (CS only)
|
||||
this->write_command_(R06_BTST_P, {0xE0, 0x20}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB7 (CS only)
|
||||
this->write_command_(0xB7, {0x01}, true, false);
|
||||
delay(10);
|
||||
|
||||
// BTST_N (CS only)
|
||||
this->write_command_(R05_BTST_N, {0xE0, 0x20}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB0 (CS only)
|
||||
this->write_command_(0xB0, {0x01}, true, false);
|
||||
delay(10);
|
||||
|
||||
// 0xB1 (CS only)
|
||||
this->write_command_(0xB1, {0x02}, true, false);
|
||||
delay(10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EPaperT133A01::write_command_(uint8_t command, const uint8_t *data, size_t length, bool use_cs, bool use_cs1) {
|
||||
ESP_LOGV(TAG, "Command: 0x%02X, Length: %u, CS: %d, CS1: %d", command, (unsigned) length, use_cs, use_cs1);
|
||||
// Chip-selects are active-low: assert the requested controllers.
|
||||
this->cs_pin_->digital_write(!use_cs);
|
||||
this->cs1_pin_->digital_write(!use_cs1);
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(command);
|
||||
if (length > 0) {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->write_array(data, length);
|
||||
}
|
||||
this->disable();
|
||||
this->cs_pin_->digital_write(true);
|
||||
this->cs1_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::fill(Color color) {
|
||||
if (this->get_clipping().is_set()) {
|
||||
EPaperBase::fill(color);
|
||||
return;
|
||||
}
|
||||
auto pixel_color = color_to_index(color);
|
||||
this->buffer_.fill(pixel_color + (pixel_color << 4));
|
||||
}
|
||||
|
||||
void EPaperT133A01::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->rotate_coordinates_(x, y))
|
||||
return;
|
||||
auto pixel_bits = color_to_index(color);
|
||||
uint32_t pixel_position = x + y * this->get_width_internal();
|
||||
uint32_t byte_position = pixel_position / 2;
|
||||
auto original = this->buffer_[byte_position];
|
||||
if ((pixel_position & 1) != 0) {
|
||||
this->buffer_[byte_position] = (original & 0xF0) | pixel_bits;
|
||||
} else {
|
||||
this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4);
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperT133A01::power_on() {
|
||||
ESP_LOGV(TAG, "Power on");
|
||||
this->write_command_(R04_PON, true, true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::power_off() {
|
||||
ESP_LOGV(TAG, "Power off");
|
||||
this->write_command_(R02_POF, {0x00}, true, true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh screen");
|
||||
// Display Refresh
|
||||
this->write_command_(R12_DRF, {0x01}, true, true);
|
||||
}
|
||||
|
||||
void EPaperT133A01::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->write_command_(0x07, {0xA5}, true, true);
|
||||
}
|
||||
|
||||
bool HOT EPaperT133A01::transfer_data() {
|
||||
const uint32_t start_time = millis();
|
||||
const uint16_t bytes_per_half_row = this->width_ / 4;
|
||||
const uint16_t total_rows = this->height_;
|
||||
const uint16_t bytes_per_row = this->width_ / 2;
|
||||
uint8_t line_data[400] = {};
|
||||
|
||||
size_t half = this->current_data_index_;
|
||||
|
||||
// --- CCSET: select color set before data transfer (CS + CS1) ---
|
||||
if (half == 0) {
|
||||
this->write_command_(RE0_CCSET, {0x01}, true, true);
|
||||
this->wait_for_idle_(true);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// --- CS phase: left half of each row via CS ---
|
||||
// T133A01 requires CS to stay LOW for the ENTIRE DTM data stream.
|
||||
// Toggling CS between chunks resets the controller's data pointer,
|
||||
// causing only the last chunk to be retained. Keep CS asserted
|
||||
// across timeout boundaries by NOT deselecting on yield.
|
||||
if (half < total_rows) {
|
||||
if (half == 0) {
|
||||
this->cs_pin_->digital_write(false); // select CS
|
||||
this->cs1_pin_->digital_write(true); // deselect CS1
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(R10_DTM);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
while (half < total_rows) {
|
||||
size_t buf_offset = half * bytes_per_row;
|
||||
for (uint16_t col = 0; col < bytes_per_half_row; col++) {
|
||||
uint8_t b = this->buffer_[buf_offset + col];
|
||||
line_data[col] = (remap_color(b >> 4) << 4) | remap_color(b & 0x0F);
|
||||
}
|
||||
this->write_array(line_data, bytes_per_half_row);
|
||||
half++;
|
||||
this->current_data_index_ = half;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "CS phase done");
|
||||
this->disable();
|
||||
this->cs_pin_->digital_write(true); // deselect CS
|
||||
}
|
||||
|
||||
// --- CS1 phase: right half of each row via CS1 ---
|
||||
// Same continuous-transaction requirement as the CS phase.
|
||||
// CS is held HIGH so only CS1 receives the data.
|
||||
if (half >= total_rows && half < total_rows * 2) {
|
||||
size_t cs1_row = half - total_rows;
|
||||
|
||||
if (cs1_row == 0) {
|
||||
this->cs_pin_->digital_write(true); // deselect CS
|
||||
this->cs1_pin_->digital_write(false); // select CS1
|
||||
this->enable();
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->write_byte(R10_DTM);
|
||||
this->dc_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
while (half < total_rows * 2) {
|
||||
size_t row = half - total_rows;
|
||||
size_t buf_offset = row * bytes_per_row + bytes_per_half_row;
|
||||
for (uint16_t col = 0; col < bytes_per_half_row; col++) {
|
||||
uint8_t b = this->buffer_[buf_offset + col];
|
||||
line_data[col] = (remap_color(b >> 4) << 4) | remap_color(b & 0x0F);
|
||||
}
|
||||
this->write_array(line_data, bytes_per_half_row);
|
||||
half++;
|
||||
this->current_data_index_ = half;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "CS1 phase done");
|
||||
this->disable();
|
||||
this->cs1_pin_->digital_write(true); // deselect CS1
|
||||
}
|
||||
|
||||
this->current_data_index_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EPaperT133A01::dump_config() {
|
||||
EPaperBase::dump_config();
|
||||
LOG_PIN(" CS Pin: ", this->cs_pin_);
|
||||
LOG_PIN(" CS1 Pin: ", this->cs1_pin_);
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
/**
|
||||
* T133A01-based 6-color e-paper display driver.
|
||||
*
|
||||
* The T133A01 controller uses a dual-CS SPI architecture:
|
||||
* - CS (primary): Controls the first half of pixel data transfer
|
||||
* - CS1 (secondary): Controls panel commands (init, power, refresh) and
|
||||
* the second half of pixel data transfer
|
||||
*
|
||||
* Color depth: 4 bits per pixel, supporting 6 colors:
|
||||
* White, Green, Red, Yellow, Blue, Black
|
||||
*
|
||||
* Buffer layout: 2 pixels per byte (4bpp packed), total buffer size
|
||||
* is width * height / 2 bytes.
|
||||
*/
|
||||
class EPaperT133A01 : public EPaperBase {
|
||||
public:
|
||||
EPaperT133A01(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) {
|
||||
this->buffer_length_ = (size_t) width * height / 2; // 2 pixels per byte at 4bpp
|
||||
}
|
||||
|
||||
void set_cs_pins(GPIOPin *cs, GPIOPin *cs1) {
|
||||
this->cs_pin_ = cs;
|
||||
this->cs1_pin_ = cs1;
|
||||
}
|
||||
|
||||
void fill(Color color) override;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
protected:
|
||||
bool reset() override;
|
||||
bool initialise(bool partial) override;
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override;
|
||||
void power_off() override;
|
||||
void deep_sleep() override;
|
||||
|
||||
bool transfer_data() override;
|
||||
|
||||
/**
|
||||
* Send a command (and optional data) selecting one or both controllers.
|
||||
* Both chip-selects are active-low and managed directly by this driver.
|
||||
* @param command The command byte to send
|
||||
* @param data Optional pointer to data bytes to send after the command
|
||||
* @param length Number of data bytes to send after the command
|
||||
* @param use_cs assert CS (left controller) for this transaction
|
||||
* @param use_cs1 assert CS1 (right controller) for this transaction
|
||||
*/
|
||||
void write_command_(uint8_t command, const uint8_t *data, size_t length, bool use_cs, bool use_cs1);
|
||||
void write_command_(uint8_t command, std::initializer_list<uint8_t> data, bool use_cs, bool use_cs1) {
|
||||
this->write_command_(command, data.begin(), data.size(), use_cs, use_cs1);
|
||||
}
|
||||
void write_command_(uint8_t command, bool use_cs, bool use_cs1) {
|
||||
this->write_command_(command, nullptr, 0, use_cs, use_cs1);
|
||||
}
|
||||
|
||||
/// Convert Color to 4-bit T133A01 color index
|
||||
static uint8_t color_to_index(Color color);
|
||||
|
||||
/// Apply COLOR_GET remap table to translate sprite indices to hardware values
|
||||
static uint8_t remap_color(uint8_t index);
|
||||
|
||||
GPIOPin *cs_pin_{nullptr};
|
||||
GPIOPin *cs1_pin_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -2,11 +2,15 @@ from typing import Any, Self
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_WIDTH
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
|
||||
class EpaperModel:
|
||||
models: dict[str, Self] = {}
|
||||
|
||||
# Whether the driver manages chip-select itself instead of via the SPI bus.
|
||||
manages_cs: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
@@ -35,6 +39,25 @@ class EpaperModel:
|
||||
def get_constructor_args(self, config) -> tuple:
|
||||
return ()
|
||||
|
||||
def get_config_options(self) -> dict:
|
||||
"""
|
||||
Return model-specific configuration schema options.
|
||||
The base implementation adds nothing; specific models override this to
|
||||
declare extra options without cluttering the shared schema.
|
||||
:return: A mapping suitable for cv.Schema.extend()
|
||||
"""
|
||||
return {}
|
||||
|
||||
async def to_code(self, var: MockObj, config: dict) -> dict:
|
||||
"""
|
||||
Generate model-specific code for the options added by add_options().
|
||||
The base implementation does nothing; specific models override this.
|
||||
The config can be updated in place to add or remove options.
|
||||
:param var: The component variable
|
||||
:param config: The validated configuration
|
||||
"""
|
||||
return config
|
||||
|
||||
def get_dimensions(self, config) -> tuple[int, int]:
|
||||
if CONF_DIMENSIONS in config:
|
||||
# Explicit dimensions, just use as is
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""T133A01-based e-paper displays.
|
||||
|
||||
The T133A01 is a 6-color e-paper controller IC that drives large panels
|
||||
(1200x1600 portrait). It uses a dual-CS SPI architecture where CS
|
||||
controls one half of the pixel data and CS1 controls the other half,
|
||||
as well as panel-level commands (power on, refresh, power off).
|
||||
|
||||
Supported models:
|
||||
- Seeed-reTerminal-E1004: 1200x1600 pixels, 6-color (T133A01 panel)
|
||||
"""
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_CS_PIN
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from . import EpaperModel
|
||||
|
||||
CONF_CS1_PIN = "cs1_pin"
|
||||
|
||||
|
||||
class T133A01Model(EpaperModel):
|
||||
"""EpaperModel subclass for T133A01-based 6-color e-paper displays."""
|
||||
|
||||
# The driver drives CS and CS1 directly for the dual-CS protocol.
|
||||
manages_cs = True
|
||||
|
||||
def __init__(self, name, class_name="EPaperT133A01", **defaults):
|
||||
super().__init__(name, class_name, **defaults)
|
||||
|
||||
def get_config_options(self) -> dict:
|
||||
# CS1 is the second chip-select required by the dual-CS architecture.
|
||||
# fallback=None makes it required unless the model provides a default.
|
||||
return {
|
||||
self.option(CONF_CS1_PIN, fallback=None): pins.gpio_output_pin_schema,
|
||||
}
|
||||
|
||||
async def to_code(self, var: MockObj, config: dict) -> dict:
|
||||
cs = await cg.gpio_pin_expression(config[CONF_CS_PIN])
|
||||
cs1 = await cg.gpio_pin_expression(config[CONF_CS1_PIN])
|
||||
cg.add(var.set_cs_pins(cs, cs1))
|
||||
# Remove CS and CS1 from the config so that the base class doesn't try to handle them.
|
||||
return {k: v for k, v in config.items() if k not in (CONF_CS_PIN, CONF_CS1_PIN)}
|
||||
|
||||
|
||||
t133a01_base = T133A01Model(
|
||||
"t133a01",
|
||||
minimum_update_interval="30s",
|
||||
data_rate="10MHz",
|
||||
)
|
||||
|
||||
# Seeed reTerminal E1004 - 13.3" 6-color e-paper (1200x1600, T133A01)
|
||||
# Portrait orientation (1200 wide × 1600 tall), matching the Arduino
|
||||
# Setup523 defines TFT_WIDTH=1200, TFT_HEIGHT=1600.
|
||||
# CS and CS1 each receive half of each row's pixel data
|
||||
# (300 bytes = 600 pixels per controller, for all 1600 rows).
|
||||
Seeed_reTerminal_E1004 = t133a01_base.extend(
|
||||
"Seeed-reTerminal-E1004",
|
||||
width=1200,
|
||||
height=1600,
|
||||
cs_pin=10,
|
||||
cs1_pin=2,
|
||||
dc_pin=11,
|
||||
reset_pin=38,
|
||||
busy_pin={
|
||||
"number": 13,
|
||||
"inverted": True,
|
||||
"mode": {"input": True},
|
||||
},
|
||||
enable_pin=12,
|
||||
)
|
||||
@@ -154,6 +154,10 @@ def test_all_predefined_models(
|
||||
if not model.get_default(CONF_CS_PIN):
|
||||
config[CONF_CS_PIN] = 5
|
||||
|
||||
# Dual-CS models (e.g. T133A01) require a second chip-select pin
|
||||
if model.manages_cs and not model.get_default("cs1_pin"):
|
||||
config["cs1_pin"] = 4
|
||||
|
||||
# Select an ESP32 variant on which all of this model's pins are valid
|
||||
# (some models default to high-numbered pins only present on the S3).
|
||||
choose_variant_with_pins(_pins_for(model, config))
|
||||
@@ -204,6 +208,10 @@ def test_individual_models(
|
||||
if not model.get_default(CONF_CS_PIN):
|
||||
config[CONF_CS_PIN] = 5
|
||||
|
||||
# Dual-CS models (e.g. T133A01) require a second chip-select pin
|
||||
if model.manages_cs and not model.get_default("cs1_pin"):
|
||||
config["cs1_pin"] = 4
|
||||
|
||||
# Select an ESP32 variant on which all of this model's pins are valid
|
||||
# (some models default to high-numbered pins only present on the S3).
|
||||
choose_variant_with_pins(_pins_for(model, config))
|
||||
|
||||
@@ -76,6 +76,14 @@ display:
|
||||
|
||||
- platform: epaper_spi
|
||||
model: seeed-reterminal-e1002
|
||||
- platform: epaper_spi
|
||||
model: seeed-reterminal-e1004
|
||||
cs_pin: 33
|
||||
cs1_pin: 34
|
||||
dc_pin: 35
|
||||
reset_pin: 36
|
||||
busy_pin: 37
|
||||
enable_pin: 39
|
||||
- platform: epaper_spi
|
||||
model: seeed-ee04-mono-4.26
|
||||
full_update_every: 10
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
esphome:
|
||||
name: e1004-test
|
||||
friendly_name: E1004 Test
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
variant: esp32s3
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
spi:
|
||||
- id: epaper_spi_bus
|
||||
clk_pin: GPIO7
|
||||
mosi_pin: GPIO9
|
||||
|
||||
display:
|
||||
- platform: epaper_spi
|
||||
spi_id: epaper_spi_bus
|
||||
model: seeed-reterminal-e1004
|
||||
update_interval: never
|
||||
lambda: |-
|
||||
it.fill(Color::WHITE);
|
||||
it.rectangle(10, 10, it.get_width() - 20, it.get_height() - 20, Color::BLACK);
|
||||
it.print(it.get_width() / 2, it.get_height() / 2, id(my_font), Color::BLACK, TextAlign::CENTER, "E1004 Test");
|
||||
it.circle(100, 100, 30, Color(255, 0, 0));
|
||||
it.circle(200, 100, 30, Color(0, 255, 0));
|
||||
it.circle(300, 100, 30, Color(0, 0, 255));
|
||||
it.circle(400, 100, 30, Color(255, 255, 0));
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: my_font
|
||||
size: 20
|
||||
|
||||
logger:
|
||||
Reference in New Issue
Block a user