From 94b28102f56d7bee37f87b0282a76dbd8c918b51 Mon Sep 17 00:00:00 2001 From: marsjan155 Date: Thu, 26 Aug 2021 04:33:03 +0200 Subject: [PATCH] Add st7920 display, (#1440) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/st7920/__init__.py | 0 esphome/components/st7920/display.py | 42 ++++++++ esphome/components/st7920/st7920.cpp | 146 ++++++++++++++++++++++++++ esphome/components/st7920/st7920.h | 50 +++++++++ tests/test1.yaml | 8 ++ 6 files changed, 247 insertions(+) create mode 100644 esphome/components/st7920/__init__.py create mode 100644 esphome/components/st7920/display.py create mode 100644 esphome/components/st7920/st7920.cpp create mode 100644 esphome/components/st7920/st7920.h diff --git a/CODEOWNERS b/CODEOWNERS index 1298d4d43d..eebaf02671 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -129,6 +129,7 @@ esphome/components/ssd1351_base/* @kbx81 esphome/components/ssd1351_spi/* @kbx81 esphome/components/st7735/* @SenexCrenshaw esphome/components/st7789v/* @kbx81 +esphome/components/st7920/* @marsjan155 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/switch/* @esphome/core diff --git a/esphome/components/st7920/__init__.py b/esphome/components/st7920/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/st7920/display.py b/esphome/components/st7920/display.py new file mode 100644 index 0000000000..9b544fa644 --- /dev/null +++ b/esphome/components/st7920/display.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import display, spi +from esphome.const import CONF_ID, CONF_LAMBDA, CONF_WIDTH, CONF_HEIGHT + +AUTO_LOAD = ["display"] +CODEOWNERS = ["@marsjan155"] +DEPENDENCIES = ["spi"] + +st7920_ns = cg.esphome_ns.namespace("st7920") +ST7920 = st7920_ns.class_( + "ST7920", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice +) +ST7920Ref = ST7920.operator("ref") + +CONFIG_SCHEMA = ( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ST7920), + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(ST7920Ref, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + + await display.register_display(var, config) diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp new file mode 100644 index 0000000000..d985b0a426 --- /dev/null +++ b/esphome/components/st7920/st7920.cpp @@ -0,0 +1,146 @@ +#include "st7920.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7920 { + +static const char *const TAG = "st7920"; + +// ST7920 COMMANDS +static const uint8_t LCD_DATA = 0xFA; +static const uint8_t LCD_COMMAND = 0xF8; +static const uint8_t LCD_CLS = 0x01; +static const uint8_t LCD_HOME = 0x02; +static const uint8_t LCD_ADDRINC = 0x06; +static const uint8_t LCD_DISPLAYON = 0x0C; +static const uint8_t LCD_DISPLAYOFF = 0x08; +static const uint8_t LCD_CURSORON = 0x0E; +static const uint8_t LCD_CURSORBLINK = 0x0F; +static const uint8_t LCD_BASIC = 0x30; +static const uint8_t LCD_GFXMODE = 0x36; +static const uint8_t LCD_EXTEND = 0x34; +static const uint8_t LCD_TXTMODE = 0x34; +static const uint8_t LCD_STANDBY = 0x01; +static const uint8_t LCD_SCROLL = 0x03; +static const uint8_t LCD_SCROLLADDR = 0x40; +static const uint8_t LCD_ADDR = 0x80; +static const uint8_t LCD_LINE0 = 0x80; +static const uint8_t LCD_LINE1 = 0x90; +static const uint8_t LCD_LINE2 = 0x88; +static const uint8_t LCD_LINE3 = 0x98; + +void ST7920::setup() { + ESP_LOGCONFIG(TAG, "Setting up ST7920..."); + this->dump_config(); + this->spi_setup(); + this->init_internal_(this->get_buffer_length_()); + display_init_(); +} + +void ST7920::command_(uint8_t value) { + this->enable(); + this->send_(LCD_COMMAND, value); + this->disable(); +} + +void ST7920::data_(uint8_t value) { + this->enable(); + this->send_(LCD_DATA, value); + this->disable(); +} + +void ST7920::send_(uint8_t type, uint8_t value) { + this->write_byte(type); + this->write_byte(value & 0xF0); + this->write_byte(value << 4); +} + +void ST7920::goto_xy_(uint16_t x, uint16_t y) { + if (y >= 32 && y < 64) { + y -= 32; + x += 8; + } else if (y >= 64 && y < 64 + 32) { + y -= 32; + x += 0; + } else if (y >= 64 + 32 && y < 64 + 64) { + y -= 64; + x += 8; + } + this->command_(LCD_ADDR | y); // 6-bit (0..63) + this->command_(LCD_ADDR | x); // 4-bit (0..15) +} + +void HOT ST7920::write_display_data() { + uint8_t i, j, b; + for (j = 0; j < this->get_height_internal() / 2; j++) { + this->goto_xy_(0, j); + this->enable(); + for (i = 0; i < 16; i++) { // 16 bytes from line #0+ + b = this->buffer_[i + j * 16]; + this->send_(LCD_DATA, b); + } + for (i = 0; i < 16; i++) { // 16 bytes from line #32+ + b = this->buffer_[i + (j + 32) * 16]; + this->send_(LCD_DATA, b); + } + this->disable(); + App.feed_wdt(); + } +} + +void ST7920::fill(Color color) { memset(this->buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } + +void ST7920::dump_config() { + LOG_DISPLAY("", "ST7920", this); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, " Height: %d", this->height_); + ESP_LOGCONFIG(TAG, " Width: %d", this->width_); +} + +float ST7920::get_setup_priority() const { return setup_priority::PROCESSOR; } + +void ST7920::update() { + this->clear(); + if (this->writer_local_.has_value()) // call lambda function if available + (*this->writer_local_)(*this); + this->write_display_data(); +} + +int ST7920::get_width_internal() { return this->width_; } + +int ST7920::get_height_internal() { return this->height_; } + +size_t ST7920::get_buffer_length_() { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; +} + +void HOT ST7920::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) { + ESP_LOGW(TAG, "Position out of area: %dx%d", x, y); + return; + } + int width = this->get_width_internal() / 8u; + if (color.is_on()) { + this->buffer_[y * width + x / 8] |= (0x80 >> (x & 7)); + } else { + this->buffer_[y * width + x / 8] &= ~(0x80 >> (x & 7)); + } +} + +void ST7920::display_init_() { + ESP_LOGD(TAG, "Initializing display..."); + this->command_(LCD_BASIC); // 8bit mode + this->command_(LCD_BASIC); // 8bit mode + this->command_(LCD_CLS); // clear screen + delay(12); // >10 ms delay + this->command_(LCD_ADDRINC); // cursor increment right no shift + this->command_(LCD_DISPLAYON); // D=1, C=0, B=0 + this->command_(LCD_EXTEND); // LCD_EXTEND); + this->command_(LCD_GFXMODE); // LCD_GFXMODE); + this->write_display_data(); +} + +} // namespace st7920 +} // namespace esphome diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h new file mode 100644 index 0000000000..d0258d922c --- /dev/null +++ b/esphome/components/st7920/st7920.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace st7920 { + +class ST7920; + +using st7920_writer_t = std::function; + +class ST7920 : public PollingComponent, + public display::DisplayBuffer, + public spi::SPIDevice { + public: + void set_writer(st7920_writer_t &&writer) { this->writer_local_ = writer; } + void set_height(uint16_t height) { this->height_ = height; } + void set_width(uint16_t width) { this->width_ = width; } + + // ========== 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 fill(Color color) override; + void write_display_data(); + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + int get_height_internal() override; + int get_width_internal() override; + size_t get_buffer_length_(); + void display_init_(); + void command_(uint8_t value); + void data_(uint8_t value); + void send_(uint8_t type, uint8_t value); + void goto_xy_(uint16_t x, uint16_t y); + void start_transaction_(); + void end_transaction_(); + + int16_t width_ = 128, height_ = 64; + optional writer_local_{}; +}; + +} // namespace st7920 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index bcf5f932a8..a3f7a97281 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2038,6 +2038,14 @@ display: backlight_pin: GPIO4 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7920 + width: 128 + height: 64 + cs_pin: + number: GPIO23 + inverted: true + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 model: 'INITR_BLACKTAB' cs_pin: GPIO5