Add SSD1305 support to SSD1306 integration along with few new options (#1902)

* Add serveral options for SSD1306 integration
* Add SSD1305 support
  (SSD1305 is similar to SSD1306, it seems SSD1305 has
  brightness and color register but does not have charge pump)
* Add some description when manipulating registers
* Add flip, offset and invert option to get more compatibility
  with various display modules
* Fix typo `setup_ssd1036' -> `setup_ssd1306'

* Add SSD1306 brightness validation tip

* Add more description, limit offset range

* Changes according to linter

* Fix test

* Raise error instead of using warning

* Fix wrong logic

* Remove logger

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>

* Remove logging import

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
ZJY 2021-09-22 19:47:41 +08:00 committed by GitHub
parent 8bebf138ee
commit 66761ff340
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 159 additions and 27 deletions

View file

@ -8,12 +8,19 @@ from esphome.const import (
CONF_MODEL,
CONF_RESET_PIN,
CONF_BRIGHTNESS,
CONF_CONTRAST,
CONF_INVERT,
)
ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base")
SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer)
SSD1306Model = ssd1306_base_ns.enum("SSD1306Model")
CONF_FLIP_X = "flip_x"
CONF_FLIP_Y = "flip_y"
CONF_OFFSET_X = "offset_x"
CONF_OFFSET_Y = "offset_y"
MODELS = {
"SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32,
"SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64,
@ -23,21 +30,44 @@ MODELS = {
"SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64,
"SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16,
"SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48,
"SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32,
"SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64,
}
SSD1306_MODEL = cv.enum(MODELS, upper=True, space="_")
def _validate(value):
model = value[CONF_MODEL]
if model not in ("SSD1305_128X32", "SSD1305_128X64"):
# Contrast is default value (1.0) while brightness is not
# Indicates user is using old `brightness` option
if value[CONF_BRIGHTNESS] != 1.0 and value[CONF_CONTRAST] == 1.0:
raise cv.Invalid(
"SSD1306/SH1106 no longer accepts brightness option, "
'please use "contrast" instead.'
)
return value
SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
{
cv.Required(CONF_MODEL): SSD1306_MODEL,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
cv.Optional(CONF_CONTRAST, default=1.0): cv.percentage,
cv.Optional(CONF_EXTERNAL_VCC): cv.boolean,
cv.Optional(CONF_FLIP_X, default=True): cv.boolean,
cv.Optional(CONF_FLIP_Y, default=True): cv.boolean,
cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=15),
cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=15),
cv.Optional(CONF_INVERT, default=False): cv.boolean,
}
).extend(cv.polling_component_schema("1s"))
async def setup_ssd1036(var, config):
async def setup_ssd1306(var, config):
await cg.register_component(var, config)
await display.register_display(var, config)
@ -47,8 +77,20 @@ async def setup_ssd1036(var, config):
cg.add(var.set_reset_pin(reset))
if CONF_BRIGHTNESS in config:
cg.add(var.init_brightness(config[CONF_BRIGHTNESS]))
if CONF_CONTRAST in config:
cg.add(var.init_contrast(config[CONF_CONTRAST]))
if CONF_EXTERNAL_VCC in config:
cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC]))
if CONF_FLIP_X in config:
cg.add(var.init_flip_x(config[CONF_FLIP_X]))
if CONF_FLIP_Y in config:
cg.add(var.init_flip_y(config[CONF_FLIP_X]))
if CONF_OFFSET_X in config:
cg.add(var.init_offset_x(config[CONF_OFFSET_X]))
if CONF_OFFSET_Y in config:
cg.add(var.init_offset_y(config[CONF_OFFSET_Y]))
if CONF_INVERT in config:
cg.add(var.init_invert(config[CONF_INVERT]))
if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void

View file

@ -8,12 +8,13 @@ namespace ssd1306_base {
static const char *const TAG = "ssd1306";
static const uint8_t SSD1306_MAX_CONTRAST = 255;
static const uint8_t SSD1305_MAX_BRIGHTNESS = 255;
static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE;
static const uint8_t SSD1306_COMMAND_DISPLAY_ON = 0xAF;
static const uint8_t SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV = 0xD5;
static const uint8_t SSD1306_COMMAND_SET_MULTIPLEX = 0xA8;
static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET = 0xD3;
static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y = 0xD3;
static const uint8_t SSD1306_COMMAND_SET_START_LINE = 0x40;
static const uint8_t SSD1306_COMMAND_CHARGE_PUMP = 0x8D;
static const uint8_t SSD1306_COMMAND_MEMORY_MODE = 0x20;
@ -28,33 +29,60 @@ static const uint8_t SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME = 0xA4;
static const uint8_t SSD1306_COMMAND_DEACTIVATE_SCROLL = 0x2E;
static const uint8_t SSD1306_COMMAND_COLUMN_ADDRESS = 0x21;
static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22;
static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6;
static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7;
static const uint8_t SSD1306_NORMAL_DISPLAY = 0xA6;
static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82;
static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8;
void SSD1306::setup() {
this->init_internal_(this->get_buffer_length_());
// Turn off display during initialization (0xAE)
this->command(SSD1306_COMMAND_DISPLAY_OFF);
this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV);
this->command(0x80); // suggested ratio
// Set oscillator frequency to 4'b1000 with no clock division (0xD5)
this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV);
// Oscillator frequency <= 4'b1000, no clock division
this->command(0x80);
// Enable low power display mode for SSD1305 (0xD8)
if (this->is_ssd1305_()) {
this->command(SSD1305_COMMAND_SET_AREA_COLOR);
this->command(0x05);
}
// Set mux ratio to [Y pixels - 1] (0xA8)
this->command(SSD1306_COMMAND_SET_MULTIPLEX);
this->command(this->get_height_internal() - 1);
this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET);
this->command(0x00); // no offset
this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); // start at line 0
this->command(SSD1306_COMMAND_CHARGE_PUMP);
if (this->external_vcc_)
this->command(0x10);
else
this->command(0x14);
// Set Y offset (0xD3)
this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y);
this->command(0x00 + this->offset_y_);
// Set start line at line 0 (0x40)
this->command(SSD1306_COMMAND_SET_START_LINE | 0x00);
// SSD1305 does not have charge pump
if (!this->is_ssd1305_()) {
// Enable charge pump (0x8D)
this->command(SSD1306_COMMAND_CHARGE_PUMP);
if (this->external_vcc_)
this->command(0x10);
else
this->command(0x14);
}
// Set addressing mode to horizontal (0x20)
this->command(SSD1306_COMMAND_MEMORY_MODE);
this->command(0x00);
this->command(SSD1306_COMMAND_SEGRE_MAP | 0x01);
this->command(SSD1306_COMMAND_COM_SCAN_DEC);
// X flip mode (0xA0, 0xA1)
this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_);
// Y flip mode (0xC0, 0xC8)
this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3));
// Set pin configuration (0xDA)
this->command(SSD1306_COMMAND_SET_COM_PINS);
switch (this->model_) {
case SSD1306_MODEL_128_32:
@ -67,25 +95,37 @@ void SSD1306::setup() {
case SH1106_MODEL_128_64:
case SSD1306_MODEL_64_48:
case SH1106_MODEL_64_48:
case SSD1305_MODEL_128_32:
case SSD1305_MODEL_128_64:
this->command(0x12);
break;
}
// Pre-charge period (0xD9)
this->command(SSD1306_COMMAND_SET_PRE_CHARGE);
if (this->external_vcc_)
this->command(0x22);
else
this->command(0xF1);
// Set V_COM (0xDB)
this->command(SSD1306_COMMAND_SET_VCOM_DETECT);
this->command(0x00);
// Display output follow RAM (0xA4)
this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME);
this->command(SSD1306_NORMAL_DISPLAY);
// Inverse display mode (0xA6, 0xA7)
this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_);
// Disable scrolling mode (0x2E)
this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL);
set_brightness(this->brightness_);
// Contrast and brighrness
// SSD1306 does not have brightness setting
set_contrast(this->contrast_);
if (this->is_ssd1305_())
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
@ -101,12 +141,12 @@ void SSD1306::display() {
this->command(SSD1306_COMMAND_COLUMN_ADDRESS);
switch (this->model_) {
case SSD1306_MODEL_64_48:
this->command(0x20);
this->command(0x20 + this->get_width_internal() - 1);
this->command(0x20 + this->offset_x_);
this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1);
break;
default:
this->command(0); // Page start address, 0
this->command(this->get_width_internal() - 1);
this->command(0 + this->offset_x_); // Page start address, 0
this->command(this->get_width_internal() + this->offset_x_ - 1);
break;
}
@ -122,16 +162,28 @@ bool SSD1306::is_sh1106_() const {
return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 ||
this->model_ == SH1106_MODEL_128_64;
}
bool SSD1306::is_ssd1305_() const {
return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64;
}
void SSD1306::update() {
this->do_update_();
this->display();
}
void SSD1306::set_contrast(float contrast) {
// validation
this->contrast_ = clamp(contrast, 0.0F, 1.0F);
// now write the new contrast level to the display (0x81)
this->command(SSD1306_COMMAND_SET_CONTRAST);
this->command(int(SSD1306_MAX_CONTRAST * (this->contrast_)));
}
void SSD1306::set_brightness(float brightness) {
// validation
if (!this->is_ssd1305_())
return;
this->brightness_ = clamp(brightness, 0.0F, 1.0F);
// now write the new brightness level to the display
this->command(SSD1306_COMMAND_SET_CONTRAST);
this->command(int(SSD1306_MAX_CONTRAST * (this->brightness_)));
// now write the new brightness level to the display (0x82)
this->command(SSD1305_COMMAND_SET_BRIGHTNESS);
this->command(int(SSD1305_MAX_BRIGHTNESS * (this->brightness_)));
}
bool SSD1306::is_on() { return this->is_on_; }
void SSD1306::turn_on() {
@ -146,9 +198,11 @@ int SSD1306::get_height_internal() {
switch (this->model_) {
case SSD1306_MODEL_128_32:
case SH1106_MODEL_128_32:
case SSD1305_MODEL_128_32:
return 32;
case SSD1306_MODEL_128_64:
case SH1106_MODEL_128_64:
case SSD1305_MODEL_128_64:
return 64;
case SSD1306_MODEL_96_16:
case SH1106_MODEL_96_16:
@ -166,6 +220,8 @@ int SSD1306::get_width_internal() {
case SH1106_MODEL_128_32:
case SSD1306_MODEL_128_64:
case SH1106_MODEL_128_64:
case SSD1305_MODEL_128_32:
case SSD1305_MODEL_128_64:
return 128;
case SSD1306_MODEL_96_16:
case SH1106_MODEL_96_16:
@ -227,6 +283,10 @@ const char *SSD1306::model_str_() {
return "SH1106 96x16";
case SH1106_MODEL_64_48:
return "SH1106 64x48";
case SSD1305_MODEL_128_32:
return "SSD1305 128x32";
case SSD1305_MODEL_128_64:
return "SSD1305 128x32";
default:
return "Unknown";
}

View file

@ -16,6 +16,8 @@ enum SSD1306Model {
SH1106_MODEL_128_64,
SH1106_MODEL_96_16,
SH1106_MODEL_64_48,
SSD1305_MODEL_128_32,
SSD1305_MODEL_128_64,
};
class SSD1306 : public PollingComponent, public display::DisplayBuffer {
@ -29,8 +31,15 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
void set_model(SSD1306Model model) { this->model_ = model; }
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void set_external_vcc(bool external_vcc) { this->external_vcc_ = external_vcc; }
void init_contrast(float contrast) { this->contrast_ = contrast; }
void set_contrast(float contrast);
void init_brightness(float brightness) { this->brightness_ = brightness; }
void set_brightness(float brightness);
void init_flip_x(boolean flip_x) { this->flip_x_ = flip_x; }
void init_flip_y(boolean flip_y) { this->flip_y_ = flip_y; }
void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; }
void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; }
void init_invert(boolean invert) { this->invert_ = invert; }
bool is_on();
void turn_on();
void turn_off();
@ -43,6 +52,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
void init_reset_();
bool is_sh1106_() const;
bool is_ssd1305_() const;
void draw_absolute_pixel_internal(int x, int y, Color color) override;
@ -55,7 +65,13 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
GPIOPin *reset_pin_{nullptr};
bool external_vcc_{false};
bool is_on_{false};
float contrast_{1.0};
float brightness_{1.0};
bool flip_x_{true};
bool flip_y_{true};
uint8_t offset_x_{0};
uint8_t offset_y_{0};
bool invert_{false};
};
} // namespace ssd1306_base

View file

@ -1,6 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ssd1306_base, i2c
from esphome.components.ssd1306_base import _validate
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES
AUTO_LOAD = ["ssd1306_base"]
@ -18,10 +19,11 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x3C)),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
_validate,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await ssd1306_base.setup_ssd1036(var, config)
await ssd1306_base.setup_ssd1306(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -25,6 +25,11 @@ void I2CSSD1306::dump_config() {
ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_());
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_));
ESP_LOGCONFIG(TAG, " Flip X: %s", YESNO(this->flip_x_));
ESP_LOGCONFIG(TAG, " Flip Y: %s", YESNO(this->flip_y_));
ESP_LOGCONFIG(TAG, " Offset X: %d", this->offset_x_);
ESP_LOGCONFIG(TAG, " Offset Y: %d", this->offset_y_);
ESP_LOGCONFIG(TAG, " Inverted Color: %s", YESNO(this->invert_));
LOG_UPDATE_INTERVAL(this);
if (this->error_code_ == COMMUNICATION_FAILED) {

View file

@ -2,6 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, ssd1306_base
from esphome.components.ssd1306_base import _validate
from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES
AUTO_LOAD = ["ssd1306_base"]
@ -20,12 +21,13 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema()),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
_validate,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await ssd1306_base.setup_ssd1036(var, config)
await ssd1306_base.setup_ssd1306(var, config)
await spi.register_spi_device(var, config)
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])

View file

@ -22,6 +22,11 @@ void SPISSD1306::dump_config() {
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_));
ESP_LOGCONFIG(TAG, " Flip X: %s", YESNO(this->flip_x_));
ESP_LOGCONFIG(TAG, " Flip Y: %s", YESNO(this->flip_y_));
ESP_LOGCONFIG(TAG, " Offset X: %d", this->offset_x_);
ESP_LOGCONFIG(TAG, " Offset Y: %d", this->offset_y_);
ESP_LOGCONFIG(TAG, " Inverted Color: %s", YESNO(this->invert_));
LOG_UPDATE_INTERVAL(this);
}
void SPISSD1306::command(uint8_t value) {

View file

@ -2072,7 +2072,7 @@ display:
reset_pin: GPIO23
address: 0x3C
id: display1
brightness: 60%
contrast: 60%
pages:
- id: page1
lambda: |-