Add full SSD1327 display support (#1406)

This commit is contained in:
Keith Burzinski 2020-12-30 03:48:23 -06:00 committed by GitHub
parent ac15ce576b
commit 9aa14a2e83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 500 additions and 0 deletions

View file

@ -65,6 +65,9 @@ esphome/components/sim800l/* @glmnet
esphome/components/spi/* @esphome/core
esphome/components/ssd1325_base/* @kbx81
esphome/components/ssd1325_spi/* @kbx81
esphome/components/ssd1327_base/* @kbx81
esphome/components/ssd1327_i2c/* @kbx81
esphome/components/ssd1327_spi/* @kbx81
esphome/components/ssd1331_base/* @kbx81
esphome/components/ssd1331_spi/* @kbx81
esphome/components/ssd1351_base/* @kbx81

View file

@ -0,0 +1,41 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import display
from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@kbx81']
ssd1327_base_ns = cg.esphome_ns.namespace('ssd1327_base')
SSD1327 = ssd1327_base_ns.class_('SSD1327', cg.PollingComponent, display.DisplayBuffer)
SSD1327Model = ssd1327_base_ns.enum('SSD1327Model')
MODELS = {
'SSD1327_128X128': SSD1327Model.SSD1327_MODEL_128_128,
}
SSD1327_MODEL = cv.enum(MODELS, upper=True, space="_")
SSD1327_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({
cv.Required(CONF_MODEL): SSD1327_MODEL,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
}).extend(cv.polling_component_schema('1s'))
@coroutine
def setup_ssd1327(var, config):
yield cg.register_component(var, config)
yield display.register_display(var, config)
cg.add(var.set_model(config[CONF_MODEL]))
if CONF_RESET_PIN in config:
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset))
if CONF_BRIGHTNESS in config:
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
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_))

View file

@ -0,0 +1,178 @@
#include "ssd1327_base.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ssd1327_base {
static const char *TAG = "ssd1327";
static const uint8_t SSD1327_MAX_CONTRAST = 127;
static const uint8_t SSD1327_COLORMASK = 0x0f;
static const uint8_t SSD1327_COLORSHIFT = 4;
static const uint8_t SSD1327_PIXELSPERBYTE = 2;
static const uint8_t SSD1327_SETCOLUMNADDRESS = 0x15;
static const uint8_t SSD1327_SETROWADDRESS = 0x75;
static const uint8_t SSD1327_SETCONTRAST = 0x81;
static const uint8_t SSD1327_SETREMAP = 0xA0;
static const uint8_t SSD1327_SETSTARTLINE = 0xA1;
static const uint8_t SSD1327_SETOFFSET = 0xA2;
static const uint8_t SSD1327_NORMALDISPLAY = 0xA4;
static const uint8_t SSD1327_DISPLAYALLON = 0xA5;
static const uint8_t SSD1327_DISPLAYALLOFF = 0xA6;
static const uint8_t SSD1327_INVERTDISPLAY = 0xA7;
static const uint8_t SSD1327_SETMULTIPLEX = 0xA8;
static const uint8_t SSD1327_FUNCTIONSELECTIONA = 0xAB;
static const uint8_t SSD1327_DISPLAYOFF = 0xAE;
static const uint8_t SSD1327_DISPLAYON = 0xAF;
static const uint8_t SSD1327_SETPHASELENGTH = 0xB1;
static const uint8_t SSD1327_SETFRONTCLOCKDIVIDER = 0xB3;
static const uint8_t SSD1327_SETGPIO = 0xB5;
static const uint8_t SSD1327_SETSECONDPRECHARGEPERIOD = 0xB6;
static const uint8_t SSD1327_SETGRAYSCALETABLE = 0xB8;
static const uint8_t SSD1327_SELECTDEFAULTLINEARGRAYSCALETABLE = 0xB9;
static const uint8_t SSD1327_SETPRECHARGEVOLTAGE = 0xBC;
static const uint8_t SSD1327_SETVCOMHVOLTAGE = 0xBE;
static const uint8_t SSD1327_FUNCTIONSELECTIONB = 0xD5;
static const uint8_t SSD1327_SETCOMMANDLOCK = 0xFD;
static const uint8_t SSD1327_HORIZONTALSCROLLRIGHTSETUP = 0x26;
static const uint8_t SSD1327_HORIZONTALSCROLLLEFTSETUP = 0x27;
static const uint8_t SSD1327_DEACTIVATESCROLL = 0x2E;
static const uint8_t SSD1327_ACTIVATESCROLL = 0x2F;
void SSD1327::setup() {
this->init_internal_(this->get_buffer_length_());
this->turn_off(); // display OFF
this->command(SSD1327_SETFRONTCLOCKDIVIDER); // set osc division
this->command(0xF1); // 145
this->command(SSD1327_SETMULTIPLEX); // multiplex ratio
this->command(0x7f); // duty = height - 1
this->command(SSD1327_SETOFFSET); // set display offset
this->command(0x00); // 0
this->command(SSD1327_SETSTARTLINE); // set start line
this->command(0x00); // ...
this->command(SSD1327_SETREMAP); // set segment remapping
this->command(0x53); // COM bottom-up, split odd/even, enable column and nibble remapping
this->command(SSD1327_SETGRAYSCALETABLE);
// gamma ~2.2
this->command(0);
this->command(1);
this->command(2);
this->command(3);
this->command(6);
this->command(8);
this->command(12);
this->command(16);
this->command(20);
this->command(26);
this->command(32);
this->command(39);
this->command(46);
this->command(54);
this->command(63);
this->command(SSD1327_SETPHASELENGTH);
this->command(0x55);
this->command(SSD1327_SETVCOMHVOLTAGE); // Set High Voltage Level of COM Pin
this->command(0x1C);
this->command(SSD1327_NORMALDISPLAY); // set display mode
set_brightness(this->brightness_);
this->fill(COLOR_BLACK); // clear display - ensures we do not see garbage at power-on
this->display(); // ...write buffer, which actually clears the display's memory
this->turn_on(); // display ON
}
void SSD1327::display() {
this->command(SSD1327_SETCOLUMNADDRESS); // set column address
this->command(0x00); // set column start address
this->command(0x3F); // set column end address
this->command(SSD1327_SETROWADDRESS); // set row address
this->command(0x00); // set row start address
this->command(127); // set last row
this->write_display_data();
}
void SSD1327::update() {
if (!this->is_failed()) {
this->do_update_();
this->display();
}
}
void SSD1327::set_brightness(float brightness) {
// validation
this->brightness_ = clamp(brightness, 0, 1);
// now write the new brightness level to the display
this->command(SSD1327_SETCONTRAST);
this->command(int(SSD1327_MAX_CONTRAST * (this->brightness_)));
}
bool SSD1327::is_on() { return this->is_on_; }
void SSD1327::turn_on() {
this->command(SSD1327_DISPLAYON);
this->is_on_ = true;
}
void SSD1327::turn_off() {
this->command(SSD1327_DISPLAYOFF);
this->is_on_ = false;
}
int SSD1327::get_height_internal() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
return 128;
default:
return 0;
}
}
int SSD1327::get_width_internal() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
return 128;
default:
return 0;
}
}
size_t SSD1327::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / SSD1327_PIXELSPERBYTE;
}
void HOT SSD1327::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;
uint32_t color4 = color.to_grayscale4();
// where should the bits go in the big buffer array? math...
uint16_t pos = (x / SSD1327_PIXELSPERBYTE) + (y * this->get_width_internal() / SSD1327_PIXELSPERBYTE);
uint8_t shift = (x % SSD1327_PIXELSPERBYTE) * SSD1327_COLORSHIFT;
// ensure 'color4' is valid (only 4 bits aka 1 nibble) and shift the bits left when necessary
color4 = (color4 & SSD1327_COLORMASK) << shift;
// first mask off the nibble we must change...
this->buffer_[pos] &= (~SSD1327_COLORMASK >> shift);
// ...then lay the new nibble back on top. done!
this->buffer_[pos] |= color4;
}
void SSD1327::fill(Color color) {
const uint32_t color4 = color.to_grayscale4();
uint8_t fill = (color4 & SSD1327_COLORMASK) | ((color4 & SSD1327_COLORMASK) << SSD1327_COLORSHIFT);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1327::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);
}
}
const char *SSD1327::model_str_() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
return "SSD1327 128x128";
default:
return "Unknown";
}
}
} // namespace ssd1327_base
} // namespace esphome

View file

@ -0,0 +1,52 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace ssd1327_base {
enum SSD1327Model {
SSD1327_MODEL_128_128 = 0,
};
class SSD1327 : public PollingComponent, public display::DisplayBuffer {
public:
void setup() override;
void display();
void update() override;
void set_model(SSD1327Model model) { this->model_ = model; }
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void init_brightness(float brightness) { this->brightness_ = brightness; }
void set_brightness(float brightness);
bool is_on();
void turn_on();
void turn_off();
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
void fill(Color color) override;
protected:
virtual void command(uint8_t value) = 0;
virtual void write_display_data() = 0;
void init_reset_();
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_();
const char *model_str_();
SSD1327Model model_{SSD1327_MODEL_128_128};
GPIOPin *reset_pin_{nullptr};
bool is_on_{false};
float brightness_{1.0};
};
} // namespace ssd1327_base
} // namespace esphome

View file

@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ssd1327_base, i2c
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1327_base']
DEPENDENCIES = ['i2c']
ssd1327_i2c = cg.esphome_ns.namespace('ssd1327_i2c')
I2CSSD1327 = ssd1327_i2c.class_('I2CSSD1327', ssd1327_base.SSD1327, i2c.I2CDevice)
CONFIG_SCHEMA = cv.All(ssd1327_base.SSD1327_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(I2CSSD1327),
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x3D)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield ssd1327_base.setup_ssd1327(var, config)
yield i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,44 @@
#include "ssd1327_i2c.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ssd1327_i2c {
static const char *TAG = "ssd1327_i2c";
void I2CSSD1327::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C SSD1327...");
this->init_reset_();
this->parent_->raw_begin_transmission(this->address_);
if (!this->parent_->raw_end_transmission(this->address_)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
SSD1327::setup();
}
void I2CSSD1327::dump_config() {
LOG_DISPLAY("", "I2C SSD1327", this);
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_UPDATE_INTERVAL(this);
if (this->error_code_ == COMMUNICATION_FAILED) {
ESP_LOGE(TAG, "Communication with SSD1327 failed!");
}
}
void I2CSSD1327::command(uint8_t value) { this->write_byte(0x00, value); }
void HOT I2CSSD1327::write_display_data() {
for (uint32_t i = 0; i < this->get_buffer_length_();) {
uint8_t data[16];
for (uint8_t &j : data)
j = this->buffer_[i++];
this->write_bytes(0x40, data, sizeof(data));
}
}
} // namespace ssd1327_i2c
} // namespace esphome

View file

@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ssd1327_base/ssd1327_base.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ssd1327_i2c {
class I2CSSD1327 : public ssd1327_base::SSD1327, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE};
};
} // namespace ssd1327_i2c
} // namespace esphome

View file

@ -0,0 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, ssd1327_base
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
CODEOWNERS = ['@kbx81']
AUTO_LOAD = ['ssd1327_base']
DEPENDENCIES = ['spi']
ssd1327_spi = cg.esphome_ns.namespace('ssd1327_spi')
SPISSD1327 = ssd1327_spi.class_('SPISSD1327', ssd1327_base.SSD1327, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(ssd1327_base.SSD1327_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SPISSD1327),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=False)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield ssd1327_base.setup_ssd1327(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))

View file

@ -0,0 +1,59 @@
#include "ssd1327_spi.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace ssd1327_spi {
static const char *TAG = "ssd1327_spi";
void SPISSD1327::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI SSD1327...");
this->spi_setup();
this->dc_pin_->setup(); // OUTPUT
if (this->cs_)
this->cs_->setup(); // OUTPUT
this->init_reset_();
delay(500); // NOLINT
SSD1327::setup();
}
void SPISSD1327::dump_config() {
LOG_DISPLAY("", "SPI SSD1327", this);
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
if (this->cs_)
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_);
LOG_UPDATE_INTERVAL(this);
}
void SPISSD1327::command(uint8_t value) {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(false);
delay(1);
this->enable();
if (this->cs_)
this->cs_->digital_write(false);
this->write_byte(value);
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
void HOT SPISSD1327::write_display_data() {
if (this->cs_)
this->cs_->digital_write(true);
this->dc_pin_->digital_write(true);
if (this->cs_)
this->cs_->digital_write(false);
delay(1);
this->enable();
this->write_array(this->buffer_, this->get_buffer_length_());
if (this->cs_)
this->cs_->digital_write(true);
this->disable();
}
} // namespace ssd1327_spi
} // namespace esphome

View file

@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ssd1327_base/ssd1327_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace ssd1327_spi {
class SPISSD1327 : public ssd1327_base::SSD1327,
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) { dc_pin_ = dc_pin; }
void setup() override;
void dump_config() override;
protected:
void command(uint8_t value) override;
void write_display_data() override;
GPIOPin *dc_pin_;
};
} // namespace ssd1327_spi
} // namespace esphome

View file

@ -1722,6 +1722,26 @@ display:
reset_pin: GPIO23
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: ssd1327_i2c
model: 'SSD1327 128X128'
reset_pin: GPIO23
address: 0x3D
id: display1327
brightness: 60%
pages:
- id: page13271
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- id: page13272
lambda: |-
// Nothing
- platform: ssd1327_spi
model: 'SSD1327 128x128'
cs_pin: GPIO23
dc_pin: GPIO23
reset_pin: GPIO23
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: ssd1331_spi
cs_pin: GPIO23
dc_pin: GPIO23