Add support for ST7789V display module (as on TTGO T-Display) (#1050)

* TFT-LCD ST7789V of ESP32 TTGO.

This patch allows you to use TFT-LCD ST7789V of ESP32 TTGO

* Lots of polish and a few tweaks

* Add test

* Add color to core, take 1

* Where did those tabs come from?

* Fix lines too long

* Added color component

* Linted

* Rebase, SPI fix, test

* Shuffle bits

* One more thing...oops

* Image type fix...oops

* Make display_buffer use Color

* Fix BGR/RGB, remove predefined colors

* Fix all the things

* renamed colors to color

* migrate max7219

Co-authored-by: musk95 <musk95@naver.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
Keith Burzinski 2020-06-28 16:37:36 -05:00 committed by GitHub
parent bfb9cb6732
commit 491f7e96f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 843 additions and 85 deletions

View file

@ -0,0 +1,23 @@
from esphome import config_validation as cv
from esphome import codegen as cg
from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
ColorStruct = cg.esphome_ns.struct('Color')
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
cv.Optional(CONF_RED, default=0.0): cv.percentage,
cv.Optional(CONF_GREEN, default=0.0): cv.percentage,
cv.Optional(CONF_BLUE, default=0.0): cv.percentage,
cv.Optional(CONF_WHITE, default=0.0): cv.percentage,
}).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
cg.variable(config[CONF_ID], cg.StructInitializer(
ColorStruct,
('r', config[CONF_RED]),
('g', config[CONF_GREEN]),
('b', config[CONF_BLUE]),
('w', config[CONF_WHITE])))

View file

@ -7,8 +7,8 @@ namespace display {
static const char *TAG = "display"; static const char *TAG = "display";
const uint8_t COLOR_OFF = 0; const Color COLOR_OFF = 0;
const uint8_t COLOR_ON = 1; const Color COLOR_ON = 1;
void DisplayBuffer::init_internal_(uint32_t buffer_length) { void DisplayBuffer::init_internal_(uint32_t buffer_length) {
this->buffer_ = new uint8_t[buffer_length]; this->buffer_ = new uint8_t[buffer_length];
@ -18,7 +18,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
} }
this->clear(); this->clear();
} }
void DisplayBuffer::fill(int color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void DisplayBuffer::clear() { this->fill(COLOR_OFF); } void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
int DisplayBuffer::get_width() { int DisplayBuffer::get_width() {
switch (this->rotation_) { switch (this->rotation_) {
@ -43,7 +43,7 @@ int DisplayBuffer::get_height() {
} }
} }
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) { void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
switch (this->rotation_) { switch (this->rotation_) {
case DISPLAY_ROTATION_0_DEGREES: case DISPLAY_ROTATION_0_DEGREES:
break; break;
@ -63,7 +63,7 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
this->draw_absolute_pixel_internal(x, y, color); this->draw_absolute_pixel_internal(x, y, color);
App.feed_wdt(); App.feed_wdt();
} }
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) { void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1; const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int32_t err = dx + dy; int32_t err = dx + dy;
@ -83,29 +83,29 @@ void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
} }
} }
} }
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, int color) { void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations. // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = x; i < x + width; i++) for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color); this->draw_pixel_at(i, y, color);
} }
void HOT DisplayBuffer::vertical_line(int x, int y, int height, int color) { void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations. // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++) for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color); this->draw_pixel_at(x, i, color);
} }
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, int color) { void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color); this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color); this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color); this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color); this->vertical_line(x1 + width - 1, y1, height, color);
} }
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, int color) { void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses. // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) { for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color); this->horizontal_line(x1, i, width, color);
} }
} }
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int color) { void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius; int dx = -radius;
int dy = 0; int dy = 0;
int err = 2 - 2 * radius; int err = 2 - 2 * radius;
@ -128,7 +128,7 @@ void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int colo
} }
} while (dx <= 0); } while (dx <= 0);
} }
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int color) { void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius); int dx = -int32_t(radius);
int dy = 0; int dy = 0;
int err = 2 - 2 * radius; int err = 2 - 2 * radius;
@ -155,7 +155,7 @@ void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int co
} while (dx <= 0); } while (dx <= 0);
} }
void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) {
int x_start, y_start; int x_start, y_start;
int width, height; int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height); this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
@ -197,16 +197,34 @@ void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align,
i += match_length; i += match_length;
} }
} }
void DisplayBuffer::vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg) { void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) {
char buffer[256]; char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg); int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0) if (ret > 0)
this->print(x, y, font, color, align, buffer); this->print(x, y, font, color, align, buffer);
} }
void DisplayBuffer::image(int x, int y, Image *image) { void DisplayBuffer::image(int x, int y, Image *image) { this->image(x, y, COLOR_ON, image); }
for (int img_x = 0; img_x < image->get_width(); img_x++) { void DisplayBuffer::image(int x, int y, Color color, Image *image, bool invert) {
for (int img_y = 0; img_y < image->get_height(); img_y++) { if (image->get_type() == BINARY) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_ON : COLOR_OFF); for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
if (invert)
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_OFF : color);
else
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color : COLOR_OFF);
}
}
} else if (image->get_type() == GRAYSCALE4) {
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale4_pixel(img_x, img_y));
}
}
} else if (image->get_type() == RGB565) {
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
}
} }
} }
} }
@ -248,7 +266,7 @@ void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font,
break; break;
} }
} }
void DisplayBuffer::print(int x, int y, Font *font, int color, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text); this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
} }
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
@ -257,13 +275,13 @@ void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char
void DisplayBuffer::print(int x, int y, Font *font, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
} }
void DisplayBuffer::printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) { void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
this->vprintf_(x, y, font, color, align, format, arg); this->vprintf_(x, y, font, color, align, format, arg);
va_end(arg); va_end(arg);
} }
void DisplayBuffer::printf(int x, int y, Font *font, int color, const char *format, ...) { void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg); this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
@ -306,14 +324,14 @@ void DisplayBuffer::do_update_() {
} }
} }
#ifdef USE_TIME #ifdef USE_TIME
void DisplayBuffer::strftime(int x, int y, Font *font, int color, TextAlign align, const char *format, void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
time::ESPTime time) { time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
this->print(x, y, font, color, align, buffer); this->print(x, y, font, color, align, buffer);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
@ -431,10 +449,30 @@ bool Image::get_pixel(int x, int y) const {
const uint32_t pos = x + y * width_8; const uint32_t pos = x + y * width_8;
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
} }
int Image::get_color_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return 0;
const uint32_t pos = (x + y * this->width_) * 2;
int color = (pgm_read_byte(this->data_start_ + pos) << 8) + (pgm_read_byte(this->data_start_ + pos + 1));
return color;
}
int Image::get_grayscale4_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return 0;
const uint32_t pos = (x + y * this->width_) / 2;
// 2 = number of pixels per byte, 4 = pixel shift
uint8_t shift = (x % 2) * 4;
int color = (pgm_read_byte(this->data_start_ + pos) >> shift) & 0x0f;
return color;
}
int Image::get_width() const { return this->width_; } int Image::get_width() const { return this->width_; }
int Image::get_height() const { return this->height_; } int Image::get_height() const { return this->height_; }
ImageType Image::get_type() const { return this->type_; }
Image::Image(const uint8_t *data_start, int width, int height) Image::Image(const uint8_t *data_start, int width, int height)
: width_(width), height_(height), data_start_(data_start) {} : width_(width), height_(height), data_start_(data_start) {}
Image::Image(const uint8_t *data_start, int width, int height, int type)
: width_(width), height_(height), type_((ImageType) type), data_start_(data_start) {}
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {} DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
void DisplayPage::show() { this->parent_->show_page(this); } void DisplayPage::show() { this->parent_->show_page(this); }

View file

@ -3,6 +3,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/color.h"
#ifdef USE_TIME #ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
@ -63,9 +64,11 @@ enum class TextAlign {
}; };
/// Turn the pixel OFF. /// Turn the pixel OFF.
extern const uint8_t COLOR_OFF; extern const Color COLOR_OFF;
/// Turn the pixel ON. /// Turn the pixel ON.
extern const uint8_t COLOR_ON; extern const Color COLOR_ON;
enum ImageType { BINARY = 0, GRAYSCALE4 = 1, RGB565 = 2 };
enum DisplayRotation { enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0, DISPLAY_ROTATION_0_DEGREES = 0,
@ -91,7 +94,7 @@ using display_writer_t = std::function<void(DisplayBuffer &)>;
class DisplayBuffer { class DisplayBuffer {
public: public:
/// Fill the entire screen with the given color. /// Fill the entire screen with the given color.
virtual void fill(int color); virtual void fill(Color color);
/// Clear the entire screen by filling it with OFF pixels. /// Clear the entire screen by filling it with OFF pixels.
void clear(); void clear();
@ -100,29 +103,29 @@ class DisplayBuffer {
/// Get the height of the image in pixels with rotation applied. /// Get the height of the image in pixels with rotation applied.
int get_height(); int get_height();
/// Set a single pixel at the specified coordinates to the given color. /// Set a single pixel at the specified coordinates to the given color.
void draw_pixel_at(int x, int y, int color = COLOR_ON); void draw_pixel_at(int x, int y, Color color = COLOR_ON);
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void line(int x1, int y1, int x2, int y2, int color = COLOR_ON); void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color. /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
void horizontal_line(int x, int y, int width, int color = COLOR_ON); void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color. /// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void vertical_line(int x, int y, int height, int color = COLOR_ON); void vertical_line(int x, int y, int height, Color color = COLOR_ON);
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at /// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
/// [x1+width,y1+height]. /// [x1+width,y1+height].
void rectangle(int x1, int y1, int width, int height, int color = COLOR_ON); void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height]. /// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
void filled_rectangle(int x1, int y1, int width, int height, int color = COLOR_ON); void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color. /// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
void circle(int center_x, int center_xy, int radius, int color = COLOR_ON); void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color. /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, int color = COLOR_ON); void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`. /** Print `text` with the anchor point at [x,y] with `font`.
* *
@ -133,7 +136,7 @@ class DisplayBuffer {
* @param align The alignment of the text. * @param align The alignment of the text.
* @param text The text to draw. * @param text The text to draw.
*/ */
void print(int x, int y, Font *font, int color, TextAlign align, const char *text); void print(int x, int y, Font *font, Color color, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`. /** Print `text` with the top left at [x,y] with `font`.
* *
@ -143,7 +146,7 @@ class DisplayBuffer {
* @param color The color to draw the text with. * @param color The color to draw the text with.
* @param text The text to draw. * @param text The text to draw.
*/ */
void print(int x, int y, Font *font, int color, const char *text); void print(int x, int y, Font *font, Color color, const char *text);
/** Print `text` with the anchor point at [x,y] with `font`. /** Print `text` with the anchor point at [x,y] with `font`.
* *
@ -174,7 +177,7 @@ class DisplayBuffer {
* @param format The format to use. * @param format The format to use.
* @param ... The arguments to use for the text formatting. * @param ... The arguments to use for the text formatting.
*/ */
void printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...)
__attribute__((format(printf, 7, 8))); __attribute__((format(printf, 7, 8)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
@ -186,7 +189,7 @@ class DisplayBuffer {
* @param format The format to use. * @param format The format to use.
* @param ... The arguments to use for the text formatting. * @param ... The arguments to use for the text formatting.
*/ */
void printf(int x, int y, Font *font, int color, const char *format, ...) __attribute__((format(printf, 6, 7))); void printf(int x, int y, Font *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
* *
@ -220,7 +223,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, int color, TextAlign align, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time)
__attribute__((format(strftime, 7, 0))); __attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
@ -232,7 +235,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time)
__attribute__((format(strftime, 6, 0))); __attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
@ -261,6 +264,7 @@ class DisplayBuffer {
/// Draw the `image` with the top-left corner at [x,y] to the screen. /// Draw the `image` with the top-left corner at [x,y] to the screen.
void image(int x, int y, Image *image); void image(int x, int y, Image *image);
void image(int x, int y, Color color, Image *image, bool invert = false);
/** Get the text bounds of the given string. /** Get the text bounds of the given string.
* *
@ -290,9 +294,9 @@ class DisplayBuffer {
void set_rotation(DisplayRotation rotation); void set_rotation(DisplayRotation rotation);
protected: protected:
void vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg); void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
virtual void draw_absolute_pixel_internal(int x, int y, int color) = 0; virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
virtual int get_height_internal() = 0; virtual int get_height_internal() = 0;
@ -378,13 +382,18 @@ class Font {
class Image { class Image {
public: public:
Image(const uint8_t *data_start, int width, int height); Image(const uint8_t *data_start, int width, int height);
Image(const uint8_t *data_start, int width, int height, int type);
bool get_pixel(int x, int y) const; bool get_pixel(int x, int y) const;
int get_color_pixel(int x, int y) const;
int get_grayscale4_pixel(int x, int y) const;
int get_width() const; int get_width() const;
int get_height() const; int get_height() const;
ImageType get_type() const;
protected: protected:
int width_; int width_;
int height_; int height_;
ImageType type_{BINARY};
const uint8_t *data_start_; const uint8_t *data_start_;
}; };

View file

@ -4,13 +4,15 @@ from esphome import core
from esphome.components import display, font from esphome.components import display, font
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE, CONF_TYPE
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['display'] DEPENDENCIES = ['display']
MULTI_CONF = True MULTI_CONF = True
ImageType = {'binary': 0, 'grayscale4': 1, 'rgb565': 2}
Image_ = display.display_ns.class_('Image') Image_ = display.display_ns.class_('Image')
CONF_RAW_DATA_ID = 'raw_data_id' CONF_RAW_DATA_ID = 'raw_data_id'
@ -19,6 +21,7 @@ IMAGE_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.declare_id(Image_), cv.Required(CONF_ID): cv.declare_id(Image_),
cv.Required(CONF_FILE): cv.file_, cv.Required(CONF_FILE): cv.file_,
cv.Optional(CONF_RESIZE): cv.dimensions, cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_TYPE): cv.string,
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
}) })
@ -37,20 +40,54 @@ def to_code(config):
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) image.thumbnail(config[CONF_RESIZE])
image = image.convert('1', dither=Image.NONE) if CONF_TYPE in config:
width, height = image.size if config[CONF_TYPE].startswith('GRAYSCALE4'):
if width > 500 or height > 500: width, height = image.size
_LOGGER.warning("The image you requested is very big. Please consider using the resize " image = image.convert('L', dither=Image.NONE)
"parameter") pixels = list(image.getdata())
width8 = ((width + 7) // 8) * 8 data = [0 for _ in range(height * width // 2)]
data = [0 for _ in range(height * width8 // 8)] pos = 0
for y in range(height): for pixnum, pix in enumerate(pixels):
for x in range(width): pixshift = (pixnum % 2) * 4
if image.getpixel((x, y)): data[pos] |= (pix >> 4) << pixshift
continue if pixshift != 0:
pos = x + y * width8 pos += 1
data[pos // 8] |= 0x80 >> (pos % 8) rhs = [HexInt(x) for x in data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['grayscale4'])
elif config[CONF_TYPE].startswith('RGB565'):
width, height = image.size
image = image.convert('RGB')
pixels = list(image.getdata())
data = [0 for _ in range(height * width * 2)]
pos = 0
for pix in pixels:
r = (pix[0] >> 3) & 0x1F
g = (pix[1] >> 2) & 0x3F
b = (pix[2] >> 3) & 0x1F
p = (r << 11) + (g << 5) + b
data[pos] = (p >> 8) & 0xFF
pos += 1
data[pos] = p & 0xFF
pos += 1
rhs = [HexInt(x) for x in data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['rgb565'])
else:
image = image.convert('1', dither=Image.NONE)
width, height = image.size
if width > 500 or height > 500:
_LOGGER.warning("The image you requested is very big. Please consider using"
" the resize parameter.")
width8 = ((width + 7) // 8) * 8
data = [0 for _ in range(height * width8 // 8)]
for y in range(height):
for x in range(width):
if image.getpixel((x, y)):
continue
pos = x + y * width8
data[pos // 8] |= 0x80 >> (pos % 8)
rhs = [HexInt(x) for x in data] rhs = [HexInt(x) for x in data]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height) cg.new_Pvariable(config[CONF_ID], prog_arr, width, height)

View file

@ -123,7 +123,7 @@ int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; }
size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; } size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, int color) { void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_); this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
} }
@ -134,7 +134,7 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, int color)
uint16_t pos = x; // X is starting at 0 top left uint16_t pos = x; // X is starting at 0 top left
uint8_t subpos = y; // Y is starting at 0 top left uint8_t subpos = y; // Y is starting at 0 top left
if (color == 1) { if (color.is_on()) {
this->max_displaybuffer_[pos] |= (1 << subpos); this->max_displaybuffer_[pos] |= (1 << subpos);
} else { } else {
this->max_displaybuffer_[pos] &= ~(1 << subpos); this->max_displaybuffer_[pos] &= ~(1 << subpos);

View file

@ -40,7 +40,7 @@ class MAX7219Component : public PollingComponent,
void turn_on_off(bool on_off); void turn_on_off(bool on_off);
void draw_absolute_pixel_internal(int x, int y, int color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override; int get_height_internal() override;
int get_width_internal() override; int get_width_internal() override;

View file

@ -85,14 +85,14 @@ void HOT PCD8544::display() {
this->command(this->PCD8544_SETYADDR); this->command(this->PCD8544_SETYADDR);
} }
void HOT PCD8544::draw_absolute_pixel_internal(int x, int y, int color) { void HOT PCD8544::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) { if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) {
return; return;
} }
uint16_t pos = x + (y / 8) * this->get_width_internal(); uint16_t pos = x + (y / 8) * this->get_width_internal();
uint8_t subpos = y % 8; uint8_t subpos = y % 8;
if (color) { if (color.is_on()) {
this->buffer_[pos] |= (1 << subpos); this->buffer_[pos] |= (1 << subpos);
} else { } else {
this->buffer_[pos] &= ~(1 << subpos); this->buffer_[pos] &= ~(1 << subpos);
@ -117,8 +117,8 @@ void PCD8544::update() {
this->display(); this->display();
} }
void PCD8544::fill(int color) { void PCD8544::fill(Color color) {
uint8_t fill = color ? 0xFF : 0x00; uint8_t fill = color.is_on() ? 0xFF : 0x00;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill; this->buffer_[i] = fill;
} }

View file

@ -43,7 +43,7 @@ class PCD8544 : public PollingComponent,
void update() override; void update() override;
void fill(int color) override; void fill(Color color) override;
void setup() override { void setup() override {
this->setup_pins_(); this->setup_pins_();
@ -51,7 +51,7 @@ class PCD8544 : public PollingComponent,
} }
protected: protected:
void draw_absolute_pixel_internal(int x, int y, int color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
void setup_pins_(); void setup_pins_();

View file

@ -179,20 +179,20 @@ size_t SSD1306::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
} }
void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, int color) { void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
return; return;
uint16_t pos = x + (y / 8) * this->get_width_internal(); uint16_t pos = x + (y / 8) * this->get_width_internal();
uint8_t subpos = y & 0x07; uint8_t subpos = y & 0x07;
if (color) { if (color.is_on()) {
this->buffer_[pos] |= (1 << subpos); this->buffer_[pos] |= (1 << subpos);
} else { } else {
this->buffer_[pos] &= ~(1 << subpos); this->buffer_[pos] &= ~(1 << subpos);
} }
} }
void SSD1306::fill(int color) { void SSD1306::fill(Color color) {
uint8_t fill = color ? 0xFF : 0x00; uint8_t fill = color.is_on() ? 0xFF : 0x00;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill; this->buffer_[i] = fill;
} }

View file

@ -32,7 +32,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
void set_brightness(float brightness) { this->brightness_ = brightness; } void set_brightness(float brightness) { this->brightness_ = brightness; }
float get_setup_priority() const override { return setup_priority::PROCESSOR; } float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(int color) override; void fill(Color color) override;
protected: protected:
virtual void command(uint8_t value) = 0; virtual void command(uint8_t value) = 0;
@ -41,7 +41,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
bool is_sh1106_() const; bool is_sh1106_() const;
void draw_absolute_pixel_internal(int x, int y, int color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override; int get_height_internal() override;
int get_width_internal() override; int get_width_internal() override;

View file

@ -144,20 +144,20 @@ size_t SSD1325::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
} }
void HOT SSD1325::draw_absolute_pixel_internal(int x, int y, int color) { void HOT SSD1325::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
return; return;
uint16_t pos = x + (y / 8) * this->get_width_internal(); uint16_t pos = x + (y / 8) * this->get_width_internal();
uint8_t subpos = y % 8; uint8_t subpos = y % 8;
if (color) { if (color.is_on()) {
this->buffer_[pos] |= (1 << subpos); this->buffer_[pos] |= (1 << subpos);
} else { } else {
this->buffer_[pos] &= ~(1 << subpos); this->buffer_[pos] &= ~(1 << subpos);
} }
} }
void SSD1325::fill(int color) { void SSD1325::fill(Color color) {
uint8_t fill = color ? 0xFF : 0x00; uint8_t fill = color.is_on() ? 0xFF : 0x00;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill; this->buffer_[i] = fill;
} }

View file

@ -28,14 +28,14 @@ class SSD1325 : public PollingComponent, public display::DisplayBuffer {
void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; } void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; }
float get_setup_priority() const override { return setup_priority::PROCESSOR; } float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(int color) override; void fill(Color color) override;
protected: protected:
virtual void command(uint8_t value) = 0; virtual void command(uint8_t value) = 0;
virtual void write_display_data() = 0; virtual void write_display_data() = 0;
void init_reset_(); void init_reset_();
void draw_absolute_pixel_internal(int x, int y, int color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
int get_height_internal() override; int get_height_internal() override;
int get_width_internal() override; int get_width_internal() override;

View file

@ -0,0 +1,3 @@
import esphome.codegen as cg
st7789v_ns = cg.esphome_ns.namespace('st7789v')

View file

@ -0,0 +1,44 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display, spi
from esphome.const import CONF_BACKLIGHT_PIN, CONF_BRIGHTNESS, CONF_CS_PIN, CONF_DC_PIN, CONF_ID, \
CONF_LAMBDA, CONF_RESET_PIN
from . import st7789v_ns
DEPENDENCIES = ['spi']
ST7789V = st7789v_ns.class_('ST7789V', cg.PollingComponent, spi.SPIDevice,
display.DisplayBuffer)
ST7789VRef = ST7789V.operator('ref')
CONFIG_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(ST7789V),
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema())
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield spi.register_spi_device(var, config)
dc = yield cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc))
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset))
bl = yield cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN])
cg.add(var.set_backlight_pin(bl))
if CONF_LAMBDA in config:
lambda_ = yield cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void)
cg.add(var.set_writer(lambda_))
yield display.register_display(var, config)

View file

@ -0,0 +1,274 @@
#include "st7789v.h"
#include "esphome/core/log.h"
namespace esphome {
namespace st7789v {
static const char *TAG = "st7789v";
void ST7789V::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI ST7789V...");
this->spi_setup();
this->dc_pin_->setup(); // OUTPUT
this->init_reset_();
this->write_command_(ST7789_SLPOUT); // Sleep out
delay(120); // NOLINT
this->write_command_(ST7789_NORON); // Normal display mode on
// *** display and color format setting ***
this->write_command_(ST7789_MADCTL);
this->write_data_(ST7789_MADCTL_COLOR_ORDER);
// JLX240 display datasheet
this->write_command_(0xB6);
this->write_data_(0x0A);
this->write_data_(0x82);
this->write_command_(ST7789_COLMOD);
this->write_data_(0x55);
delay(10);
// *** ST7789V Frame rate setting ***
this->write_command_(ST7789_PORCTRL);
this->write_data_(0x0c);
this->write_data_(0x0c);
this->write_data_(0x00);
this->write_data_(0x33);
this->write_data_(0x33);
this->write_command_(ST7789_GCTRL); // Voltages: VGH / VGL
this->write_data_(0x35);
// *** ST7789V Power setting ***
this->write_command_(ST7789_VCOMS);
this->write_data_(0x28); // JLX240 display datasheet
this->write_command_(ST7789_LCMCTRL);
this->write_data_(0x0C);
this->write_command_(ST7789_VDVVRHEN);
this->write_data_(0x01);
this->write_data_(0xFF);
this->write_command_(ST7789_VRHS); // voltage VRHS
this->write_data_(0x10);
this->write_command_(ST7789_VDVS);
this->write_data_(0x20);
this->write_command_(ST7789_FRCTRL2);
this->write_data_(0x0f);
this->write_command_(ST7789_PWCTRL1);
this->write_data_(0xa4);
this->write_data_(0xa1);
// *** ST7789V gamma setting ***
this->write_command_(ST7789_PVGAMCTRL);
this->write_data_(0xd0);
this->write_data_(0x00);
this->write_data_(0x02);
this->write_data_(0x07);
this->write_data_(0x0a);
this->write_data_(0x28);
this->write_data_(0x32);
this->write_data_(0x44);
this->write_data_(0x42);
this->write_data_(0x06);
this->write_data_(0x0e);
this->write_data_(0x12);
this->write_data_(0x14);
this->write_data_(0x17);
this->write_command_(ST7789_NVGAMCTRL);
this->write_data_(0xd0);
this->write_data_(0x00);
this->write_data_(0x02);
this->write_data_(0x07);
this->write_data_(0x0a);
this->write_data_(0x28);
this->write_data_(0x31);
this->write_data_(0x54);
this->write_data_(0x47);
this->write_data_(0x0e);
this->write_data_(0x1c);
this->write_data_(0x17);
this->write_data_(0x1b);
this->write_data_(0x1e);
this->write_command_(ST7789_INVON);
// Clear display - ensures we do not see garbage at power-on
this->draw_filled_rect_(0, 0, 239, 319, 0x0000);
delay(120); // NOLINT
this->write_command_(ST7789_DISPON); // Display on
delay(120); // NOLINT
backlight_(true);
this->init_internal_(this->get_buffer_length_());
memset(this->buffer_, 0x00, this->get_buffer_length_());
}
void ST7789V::dump_config() {
LOG_DISPLAY("", "SPI ST7789V", this);
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" B/L Pin: ", this->backlight_pin_);
LOG_UPDATE_INTERVAL(this);
}
float ST7789V::get_setup_priority() const { return setup_priority::PROCESSOR; }
void ST7789V::update() {
this->do_update_();
this->write_display_data();
}
void ST7789V::loop() {}
void ST7789V::write_display_data() {
uint16_t x1 = 52; // _offsetx
uint16_t x2 = 186; // _offsetx
uint16_t y1 = 40; // _offsety
uint16_t y2 = 279; // _offsety
this->enable();
// set column(x) address
this->dc_pin_->digital_write(false);
this->write_byte(ST7789_CASET);
this->dc_pin_->digital_write(true);
this->write_addr_(x1, x2);
// set page(y) address
this->dc_pin_->digital_write(false);
this->write_byte(ST7789_RASET);
this->dc_pin_->digital_write(true);
this->write_addr_(y1, y2);
// write display memory
this->dc_pin_->digital_write(false);
this->write_byte(ST7789_RAMWR);
this->dc_pin_->digital_write(true);
this->write_array(this->buffer_, this->get_buffer_length_());
this->disable();
}
void ST7789V::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void ST7789V::backlight_(bool onoff) {
if (this->backlight_pin_ != nullptr) {
this->backlight_pin_->setup();
this->backlight_pin_->digital_write(onoff);
}
}
void ST7789V::write_command_(uint8_t value) {
this->enable();
this->dc_pin_->digital_write(false);
this->write_byte(value);
this->dc_pin_->digital_write(true);
this->disable();
}
void ST7789V::write_data_(uint8_t value) {
this->dc_pin_->digital_write(true);
this->enable();
this->write_byte(value);
this->disable();
}
void ST7789V::write_addr_(uint16_t addr1, uint16_t addr2) {
static uint8_t BYTE[4];
BYTE[0] = (addr1 >> 8) & 0xFF;
BYTE[1] = addr1 & 0xFF;
BYTE[2] = (addr2 >> 8) & 0xFF;
BYTE[3] = addr2 & 0xFF;
this->dc_pin_->digital_write(true);
this->write_array(BYTE, 4);
}
void ST7789V::write_color_(uint16_t color, uint16_t size) {
static uint8_t BYTE[1024];
int index = 0;
for (int i = 0; i < size; i++) {
BYTE[index++] = (color >> 8) & 0xFF;
BYTE[index++] = color & 0xFF;
}
this->dc_pin_->digital_write(true);
return write_array(BYTE, size * 2);
}
int ST7789V::get_height_internal() {
return 240; // 320;
}
int ST7789V::get_width_internal() {
return 135; // 240;
}
size_t ST7789V::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) * 2;
}
// Draw a filled rectangle
// x1: Start X coordinate
// y1: Start Y coordinate
// x2: End X coordinate
// y2: End Y coordinate
// color: color
void ST7789V::draw_filled_rect_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
// ESP_LOGD(TAG,"offset(x)=%d offset(y)=%d",dev->_offsetx,dev->_offsety);
this->enable();
this->dc_pin_->digital_write(false);
this->write_byte(ST7789_CASET); // set column(x) address
this->dc_pin_->digital_write(true);
this->write_addr_(x1, x2);
this->dc_pin_->digital_write(false);
this->write_byte(ST7789_RASET); // set Page(y) address
this->dc_pin_->digital_write(true);
this->write_addr_(y1, y2);
this->dc_pin_->digital_write(false);
this->write_byte(ST7789_RAMWR); // begin a write to memory
this->dc_pin_->digital_write(true);
for (int i = x1; i <= x2; i++) {
uint16_t size = y2 - y1 + 1;
this->write_color_(color, size);
}
this->disable();
}
void HOT ST7789V::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
return;
auto color565 = color.to_rgb_565();
uint16_t pos = (x + y * this->get_width_internal()) * 2;
this->buffer_[pos++] = (color565 >> 8) & 0xff;
this->buffer_[pos] = color565 & 0xff;
}
} // namespace st7789v
} // namespace esphome

View file

@ -0,0 +1,151 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace st7789v {
static const uint8_t BLACK = 0;
static const uint8_t WHITE = 1;
static const uint8_t ST7789_NOP = 0x00; // No Operation
static const uint8_t ST7789_SWRESET = 0x01; // Software Reset
static const uint8_t ST7789_RDDID = 0x04; // Read Display ID
static const uint8_t ST7789_RDDST = 0x09; // Read Display Status
static const uint8_t ST7789_RDDPM = 0x0A; // Read Display Power Mode
static const uint8_t ST7789_RDDMADCTL = 0x0B; // Read Display MADCTL
static const uint8_t ST7789_RDDCOLMOD = 0x0C; // Read Display Pixel Format
static const uint8_t ST7789_RDDIM = 0x0D; // Read Display Image Mode
static const uint8_t ST7789_RDDSM = 0x0E; // Read Display Signal Mod
static const uint8_t ST7789_RDDSDR = 0x0F; // Read Display Self-Diagnostic Resul
static const uint8_t ST7789_SLPIN = 0x10; // Sleep in
static const uint8_t ST7789_SLPOUT = 0x11; // Sleep Out
static const uint8_t ST7789_PTLON = 0x12; // Partial Display Mode O
static const uint8_t ST7789_NORON = 0x13; // Normal Display Mode O
static const uint8_t ST7789_INVOFF = 0x20; // Display Inversion Off
static const uint8_t ST7789_INVON = 0x21; // Display Inversion O
static const uint8_t ST7789_GAMSET = 0x26; // Gamma Set
static const uint8_t ST7789_DISPOFF = 0x28; // Display Off
static const uint8_t ST7789_DISPON = 0x29; // Display On
static const uint8_t ST7789_CASET = 0x2A; // Column Address Set
static const uint8_t ST7789_RASET = 0x2B; // Row Address Set
static const uint8_t ST7789_RAMWR = 0x2C; // Memory Write
static const uint8_t ST7789_RAMRD = 0x2E; // Memory Read
static const uint8_t ST7789_PTLAR = 0x30; // Partial Area
static const uint8_t ST7789_VSCRDEF = 0x33; // Vertical Scrolling Definitio
static const uint8_t ST7789_TEOFF = 0x34; // Tearing Effect Line OFF
static const uint8_t ST7789_TEON = 0x35; // Tearing Effect Line On
static const uint8_t ST7789_MADCTL = 0x36; // Memory Data Access Control
static const uint8_t ST7789_VSCSAD = 0x37; // Vertical Scroll Start Address of RAM
static const uint8_t ST7789_IDMOFF = 0x38; // Idle Mode Off
static const uint8_t ST7789_IDMON = 0x39; // Idle mode on
static const uint8_t ST7789_COLMOD = 0x3A; // Interface Pixel Format
static const uint8_t ST7789_WRMEMC = 0x3C; // Write Memory Continue
static const uint8_t ST7789_RDMEMC = 0x3E; // Read Memory Continue
static const uint8_t ST7789_STE = 0x44; // Set Tear Scanline
static const uint8_t ST7789_GSCAN = 0x45; // Get Scanlin
static const uint8_t ST7789_WRDISBV = 0x51; // Write Display Brightness
static const uint8_t ST7789_RDDISBV = 0x52; // Read Display Brightness Value
static const uint8_t ST7789_WRCTRLD = 0x53; // Write CTRL Display
static const uint8_t ST7789_RDCTRLD = 0x54; // Read CTRL Value Display
static const uint8_t ST7789_WRCACE = 0x55; // Write Content Adaptive Brightness Control and Color Enhancement
static const uint8_t ST7789_RDCABC = 0x56; // Read Content Adaptive Brightness Control
static const uint8_t ST7789_WRCABCMB = 0x5E; // Write CABC Minimum Brightnes
static const uint8_t ST7789_RDCABCMB = 0x5F; // Read CABC Minimum Brightnes
static const uint8_t ST7789_RDABCSDR = 0x68; // Read Automatic Brightness Control Self-Diagnostic Result
static const uint8_t ST7789_RDID1 = 0xDA; // Read ID1
static const uint8_t ST7789_RDID2 = 0xDB; // Read ID2
static const uint8_t ST7789_RDID3 = 0xDC; // Read ID3
static const uint8_t ST7789_RAMCTRL = 0xB0; // RAM Control
static const uint8_t ST7789_RGBCTRL = 0xB1; // RGB Interface Contro
static const uint8_t ST7789_PORCTRL = 0xB2; // Porch Setting
static const uint8_t ST7789_FRCTRL1 = 0xB3; // Frame Rate Control 1 (In partial mode/ idle colors)
static const uint8_t ST7789_PARCTRL = 0xB5; // Partial mode Contro
static const uint8_t ST7789_GCTRL = 0xB7; // Gate Contro
static const uint8_t ST7789_GTADJ = 0xB8; // Gate On Timing Adjustmen
static const uint8_t ST7789_DGMEN = 0xBA; // Digital Gamma Enable
static const uint8_t ST7789_VCOMS = 0xBB; // VCOMS Setting
static const uint8_t ST7789_LCMCTRL = 0xC0; // LCM Control
static const uint8_t ST7789_IDSET = 0xC1; // ID Code Settin
static const uint8_t ST7789_VDVVRHEN = 0xC2; // VDV and VRH Command Enabl
static const uint8_t ST7789_VRHS = 0xC3; // VRH Set
static const uint8_t ST7789_VDVS = 0xC4; // VDV Set
static const uint8_t ST7789_VCMOFSET = 0xC5; // VCOMS Offset Set
static const uint8_t ST7789_FRCTRL2 = 0xC6; // Frame Rate Control in Normal Mode
static const uint8_t ST7789_CABCCTRL = 0xC7; // CABC Control
static const uint8_t ST7789_REGSEL1 = 0xC8; // Register Value Selection 1
static const uint8_t ST7789_REGSEL2 = 0xCA; // Register Value Selection
static const uint8_t ST7789_PWMFRSEL = 0xCC; // PWM Frequency Selection
static const uint8_t ST7789_PWCTRL1 = 0xD0; // Power Control 1
static const uint8_t ST7789_VAPVANEN = 0xD2; // Enable VAP/VAN signal output
static const uint8_t ST7789_CMD2EN = 0xDF; // Command 2 Enable
static const uint8_t ST7789_PVGAMCTRL = 0xE0; // Positive Voltage Gamma Control
static const uint8_t ST7789_NVGAMCTRL = 0xE1; // Negative Voltage Gamma Control
static const uint8_t ST7789_DGMLUTR = 0xE2; // Digital Gamma Look-up Table for Red
static const uint8_t ST7789_DGMLUTB = 0xE3; // Digital Gamma Look-up Table for Blue
static const uint8_t ST7789_GATECTRL = 0xE4; // Gate Control
static const uint8_t ST7789_SPI2EN = 0xE7; // SPI2 Enable
static const uint8_t ST7789_PWCTRL2 = 0xE8; // Power Control 2
static const uint8_t ST7789_EQCTRL = 0xE9; // Equalize time control
static const uint8_t ST7789_PROMCTRL = 0xEC; // Program Mode Contro
static const uint8_t ST7789_PROMEN = 0xFA; // Program Mode Enabl
static const uint8_t ST7789_NVMSET = 0xFC; // NVM Setting
static const uint8_t ST7789_PROMACT = 0xFE; // Program action
// Flags for ST7789_MADCTL
static const uint8_t ST7789_MADCTL_MY = 0x80;
static const uint8_t ST7789_MADCTL_MX = 0x40;
static const uint8_t ST7789_MADCTL_MV = 0x20;
static const uint8_t ST7789_MADCTL_ML = 0x10;
static const uint8_t ST7789_MADCTL_RGB = 0x00;
static const uint8_t ST7789_MADCTL_BGR = 0x08;
static const uint8_t ST7789_MADCTL_MH = 0x04;
static const uint8_t ST7789_MADCTL_SS = 0x02;
static const uint8_t ST7789_MADCTL_GS = 0x01;
static const uint8_t ST7789_MADCTL_COLOR_ORDER = ST7789_MADCTL_BGR;
class ST7789V : public PollingComponent,
public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
spi::DATA_RATE_8MHZ> {
public:
void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void set_backlight_pin(GPIOPin *backlight_pin) { this->backlight_pin_ = backlight_pin; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void loop() override;
void write_display_data();
protected:
GPIOPin *dc_pin_;
GPIOPin *reset_pin_{nullptr};
GPIOPin *backlight_pin_{nullptr};
void init_reset_();
void backlight_(bool onoff);
void write_command_(uint8_t value);
void write_data_(uint8_t value);
void write_addr_(uint16_t addr1, uint16_t addr2);
void write_color_(uint16_t color, uint16_t size);
int get_height_internal() override;
int get_width_internal() override;
size_t get_buffer_length_();
void draw_filled_rect_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void draw_absolute_pixel_internal(int x, int y, Color color) override;
};
} // namespace st7789v
} // namespace esphome

View file

@ -115,20 +115,20 @@ void WaveshareEPaper::update() {
this->do_update_(); this->do_update_();
this->display(); this->display();
} }
void WaveshareEPaper::fill(int color) { void WaveshareEPaper::fill(Color color) {
// flip logic // flip logic
const uint8_t fill = color ? 0x00 : 0xFF; const uint8_t fill = color.is_on() ? 0x00 : 0xFF;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill; this->buffer_[i] = fill;
} }
void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, int color) { void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return; return;
const uint32_t pos = (x + y * this->get_width_internal()) / 8u; const uint32_t pos = (x + y * this->get_width_internal()) / 8u;
const uint8_t subpos = x & 0x07; const uint8_t subpos = x & 0x07;
// flip logic // flip logic
if (!color) if (!color.is_on())
this->buffer_[pos] |= 0x80 >> subpos; this->buffer_[pos] |= 0x80 >> subpos;
else else
this->buffer_[pos] &= ~(0x80 >> subpos); this->buffer_[pos] &= ~(0x80 >> subpos);

View file

@ -26,7 +26,7 @@ class WaveshareEPaper : public PollingComponent,
void update() override; void update() override;
void fill(int color) override; void fill(Color color) override;
void setup() override { void setup() override {
this->setup_pins_(); this->setup_pins_();
@ -36,7 +36,7 @@ class WaveshareEPaper : public PollingComponent,
void on_safe_shutdown() override; void on_safe_shutdown() override;
protected: protected:
void draw_absolute_pixel_internal(int x, int y, int color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
bool wait_until_idle_(); bool wait_until_idle_();

View file

@ -49,6 +49,7 @@ CONF_AUTOMATION_ID = 'automation_id'
CONF_AVAILABILITY = 'availability' CONF_AVAILABILITY = 'availability'
CONF_AWAY = 'away' CONF_AWAY = 'away'
CONF_AWAY_CONFIG = 'away_config' CONF_AWAY_CONFIG = 'away_config'
CONF_BACKLIGHT_PIN = 'backlight_pin'
CONF_BATTERY_LEVEL = 'battery_level' CONF_BATTERY_LEVEL = 'battery_level'
CONF_BATTERY_VOLTAGE = 'battery_voltage' CONF_BATTERY_VOLTAGE = 'battery_voltage'
CONF_BAUD_RATE = 'baud_rate' CONF_BAUD_RATE = 'baud_rate'

161
esphome/core/color.h Normal file
View file

@ -0,0 +1,161 @@
#pragma once
#include "component.h"
#include "helpers.h"
namespace esphome {
inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; }
struct Color {
union {
struct {
union {
uint8_t r;
uint8_t red;
};
union {
uint8_t g;
uint8_t green;
};
union {
uint8_t b;
uint8_t blue;
};
union {
uint8_t w;
uint8_t white;
};
};
uint8_t raw[4];
uint32_t raw_32;
};
inline Color() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT
inline Color(float red, float green, float blue) ALWAYS_INLINE : r(uint8_t(red * 255)),
g(uint8_t(green * 255)),
b(uint8_t(blue * 255)),
w(0) {}
inline Color(float red, float green, float blue, float white) ALWAYS_INLINE : r(uint8_t(red * 255)),
g(uint8_t(green * 255)),
b(uint8_t(blue * 255)),
w(uint8_t(white * 255)) {}
inline Color(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF),
g((colorcode >> 8) & 0xFF),
b((colorcode >> 0) & 0xFF),
w((colorcode >> 24) & 0xFF) {}
inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; }
inline Color &operator=(const Color &rhs) ALWAYS_INLINE {
this->r = rhs.r;
this->g = rhs.g;
this->b = rhs.b;
this->w = rhs.w;
return *this;
}
inline Color &operator=(uint32_t colorcode) ALWAYS_INLINE {
this->w = (colorcode >> 24) & 0xFF;
this->r = (colorcode >> 16) & 0xFF;
this->g = (colorcode >> 8) & 0xFF;
this->b = (colorcode >> 0) & 0xFF;
return *this;
}
inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; }
inline Color operator*(uint8_t scale) const ALWAYS_INLINE {
return Color(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale),
esp_scale8(this->white, scale));
}
inline Color &operator*=(uint8_t scale) ALWAYS_INLINE {
this->red = esp_scale8(this->red, scale);
this->green = esp_scale8(this->green, scale);
this->blue = esp_scale8(this->blue, scale);
this->white = esp_scale8(this->white, scale);
return *this;
}
inline Color operator*(const Color &scale) const ALWAYS_INLINE {
return Color(esp_scale8(this->red, scale.red), esp_scale8(this->green, scale.green),
esp_scale8(this->blue, scale.blue), esp_scale8(this->white, scale.white));
}
inline Color &operator*=(const Color &scale) ALWAYS_INLINE {
this->red = esp_scale8(this->red, scale.red);
this->green = esp_scale8(this->green, scale.green);
this->blue = esp_scale8(this->blue, scale.blue);
this->white = esp_scale8(this->white, scale.white);
return *this;
}
inline Color operator+(const Color &add) const ALWAYS_INLINE {
Color ret;
if (uint8_t(add.r + this->r) < this->r)
ret.r = 255;
else
ret.r = this->r + add.r;
if (uint8_t(add.g + this->g) < this->g)
ret.g = 255;
else
ret.g = this->g + add.g;
if (uint8_t(add.b + this->b) < this->b)
ret.b = 255;
else
ret.b = this->b + add.b;
if (uint8_t(add.w + this->w) < this->w)
ret.w = 255;
else
ret.w = this->w + add.w;
return ret;
}
inline Color &operator+=(const Color &add) ALWAYS_INLINE { return *this = (*this) + add; }
inline Color operator+(uint8_t add) const ALWAYS_INLINE { return (*this) + Color(add, add, add, add); }
inline Color &operator+=(uint8_t add) ALWAYS_INLINE { return *this = (*this) + add; }
inline Color operator-(const Color &subtract) const ALWAYS_INLINE {
Color ret;
if (subtract.r > this->r)
ret.r = 0;
else
ret.r = this->r - subtract.r;
if (subtract.g > this->g)
ret.g = 0;
else
ret.g = this->g - subtract.g;
if (subtract.b > this->b)
ret.b = 0;
else
ret.b = this->b - subtract.b;
if (subtract.w > this->w)
ret.w = 0;
else
ret.w = this->w - subtract.w;
return ret;
}
inline Color &operator-=(const Color &subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
inline Color operator-(uint8_t subtract) const ALWAYS_INLINE {
return (*this) - Color(subtract, subtract, subtract, subtract);
}
inline Color &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
static Color random_color() {
float r = float(random_uint32()) / float(UINT32_MAX);
float g = float(random_uint32()) / float(UINT32_MAX);
float b = float(random_uint32()) / float(UINT32_MAX);
float w = float(random_uint32()) / float(UINT32_MAX);
return Color(r, g, b, w);
}
Color fade_to_white(uint8_t amnt) { return Color(1, 1, 1, 1) - (*this * amnt); }
Color fade_to_black(uint8_t amnt) { return *this * amnt; }
Color lighten(uint8_t delta) { return *this + delta; }
Color darken(uint8_t delta) { return *this - delta; }
uint32_t to_rgb_565() const {
uint32_t color565 =
(esp_scale8(this->red, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->blue, 31) << 0);
return color565;
}
uint32_t to_bgr_565() const {
uint32_t color565 =
(esp_scale8(this->blue, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->red, 31) << 0);
return color565;
}
uint32_t to_grayscale4() const {
uint32_t gs4 = esp_scale8(this->white, 15);
return gs4;
}
};
static const Color COLOR_BLACK(0, 0, 0);
static const Color COLOR_WHITE(1, 1, 1);
}; // namespace esphome

View file

@ -1507,6 +1507,16 @@ interval:
id(btn_left)->set_threshold(btn_left_state * 0.9); id(btn_left)->set_threshold(btn_left_state * 0.9);
color:
- id: kbx_red
red: 100%
green: 1%
blue: 2%
- id: kbx_blue
red: 0%
green: 1%
blue: 100%
display: display:
- platform: lcd_gpio - platform: lcd_gpio
dimensions: 18x4 dimensions: 18x4
@ -1591,6 +1601,13 @@ display:
full_update_every: 30 full_update_every: 30
lambda: |- lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height()); it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: st7789v
cs_pin: GPIO5
dc_pin: GPIO16
reset_pin: GPIO23
backlight_pin: GPIO4
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
tm1651: tm1651:
id: tm1651_battery id: tm1651_battery