Add proper support for SH1107 to SSD1306 component (#5166)

This commit is contained in:
Nikita Kuklev 2023-11-16 02:06:03 -06:00 committed by GitHub
parent 754bd5b7be
commit fefdb80fdc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 39 deletions

View file

@ -33,6 +33,7 @@ MODELS = {
"SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16,
"SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48, "SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48,
"SH1107_128X64": SSD1306Model.SH1107_MODEL_128_64, "SH1107_128X64": SSD1306Model.SH1107_MODEL_128_64,
"SH1107_128X128": SSD1306Model.SH1107_MODEL_128_128,
"SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32, "SSD1305_128X32": SSD1306Model.SSD1305_MODEL_128_32,
"SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64, "SSD1305_128X64": SSD1306Model.SSD1305_MODEL_128_64,
} }
@ -63,8 +64,10 @@ SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, cv.Optional(CONF_EXTERNAL_VCC): cv.boolean,
cv.Optional(CONF_FLIP_X, default=True): cv.boolean, cv.Optional(CONF_FLIP_X, default=True): cv.boolean,
cv.Optional(CONF_FLIP_Y, default=True): cv.boolean, cv.Optional(CONF_FLIP_Y, default=True): cv.boolean,
cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=-32, max=32), # Offsets determine shifts of memory location to LCD rows/columns,
cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=-32, max=32), # and this family of controllers supports up to 128x128 screens
cv.Optional(CONF_OFFSET_X, default=0): cv.int_range(min=0, max=128),
cv.Optional(CONF_OFFSET_Y, default=0): cv.int_range(min=0, max=128),
cv.Optional(CONF_INVERT, default=False): cv.boolean, cv.Optional(CONF_INVERT, default=False): cv.boolean,
} }
).extend(cv.polling_component_schema("1s")) ).extend(cv.polling_component_schema("1s"))

View file

@ -35,16 +35,31 @@ static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7;
static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82; static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82;
static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8; static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8;
static const uint8_t SH1107_COMMAND_SET_START_LINE = 0xDC;
static const uint8_t SH1107_COMMAND_CHARGE_PUMP = 0xAD;
void SSD1306::setup() { void SSD1306::setup() {
this->init_internal_(this->get_buffer_length_()); this->init_internal_(this->get_buffer_length_());
// SH1107 resources
//
// Datasheet v2.3:
// www.displayfuture.com/Display/datasheet/controller/SH1107.pdf
// Adafruit C++ driver:
// github.com/adafruit/Adafruit_SH110x
// Adafruit CircuitPython driver:
// github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1107
// Turn off display during initialization (0xAE) // Turn off display during initialization (0xAE)
this->command(SSD1306_COMMAND_DISPLAY_OFF); this->command(SSD1306_COMMAND_DISPLAY_OFF);
// Set oscillator frequency to 4'b1000 with no clock division (0xD5) // If SH1107, use POR defaults (0x50) = divider 1, frequency +0%
this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV); if (!this->is_sh1107_()) {
// Oscillator frequency <= 4'b1000, no clock division // Set oscillator frequency to 4'b1000 with no clock division (0xD5)
this->command(0x80); 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) // Enable low power display mode for SSD1305 (0xD8)
if (this->is_ssd1305_()) { if (this->is_ssd1305_()) {
@ -60,11 +75,26 @@ void SSD1306::setup() {
this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y); this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y);
this->command(0x00 + this->offset_y_); this->command(0x00 + this->offset_y_);
// Set start line at line 0 (0x40) if (this->is_sh1107_()) {
this->command(SSD1306_COMMAND_SET_START_LINE | 0x00); // Set start line at line 0 (0xDC)
this->command(SH1107_COMMAND_SET_START_LINE);
this->command(0x00);
} else {
// 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_()) {
if (!this->is_ssd1305_()) { // SSD1305 does not have charge pump
} else if (this->is_sh1107_()) {
// Enable charge pump (0xAD)
this->command(SH1107_COMMAND_CHARGE_PUMP);
if (this->external_vcc_) {
this->command(0x8A);
} else {
this->command(0x8B);
}
} else {
// Enable charge pump (0x8D) // Enable charge pump (0x8D)
this->command(SSD1306_COMMAND_CHARGE_PUMP); this->command(SSD1306_COMMAND_CHARGE_PUMP);
if (this->external_vcc_) { if (this->external_vcc_) {
@ -76,34 +106,41 @@ void SSD1306::setup() {
// Set addressing mode to horizontal (0x20) // Set addressing mode to horizontal (0x20)
this->command(SSD1306_COMMAND_MEMORY_MODE); this->command(SSD1306_COMMAND_MEMORY_MODE);
this->command(0x00); if (!this->is_sh1107_()) {
// SH1107 memory mode is a 1 byte command
this->command(0x00);
}
// X flip mode (0xA0, 0xA1) // X flip mode (0xA0, 0xA1)
this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_); this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_);
// Y flip mode (0xC0, 0xC8) // Y flip mode (0xC0, 0xC8)
this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3)); this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3));
// Set pin configuration (0xDA) if (!this->is_sh1107_()) {
this->command(SSD1306_COMMAND_SET_COM_PINS); // Set pin configuration (0xDA)
switch (this->model_) { this->command(SSD1306_COMMAND_SET_COM_PINS);
case SSD1306_MODEL_128_32: switch (this->model_) {
case SH1106_MODEL_128_32: case SSD1306_MODEL_128_32:
case SSD1306_MODEL_96_16: case SH1106_MODEL_128_32:
case SH1106_MODEL_96_16: case SSD1306_MODEL_96_16:
this->command(0x02); case SH1106_MODEL_96_16:
break; this->command(0x02);
case SSD1306_MODEL_128_64: break;
case SH1106_MODEL_128_64: case SSD1306_MODEL_128_64:
case SSD1306_MODEL_64_48: case SH1106_MODEL_128_64:
case SSD1306_MODEL_64_32: case SSD1306_MODEL_64_48:
case SH1106_MODEL_64_48: case SSD1306_MODEL_64_32:
case SH1107_MODEL_128_64: case SH1106_MODEL_64_48:
case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_32:
case SSD1305_MODEL_128_64: case SSD1305_MODEL_128_64:
case SSD1306_MODEL_72_40: case SSD1306_MODEL_72_40:
this->command(0x12); this->command(0x12);
break; break;
case SH1107_MODEL_128_64:
case SH1107_MODEL_128_128:
// Not used, but prevents build warning
break;
}
} }
// Pre-charge period (0xD9) // Pre-charge period (0xD9)
@ -118,6 +155,7 @@ void SSD1306::setup() {
this->command(SSD1306_COMMAND_SET_VCOM_DETECT); this->command(SSD1306_COMMAND_SET_VCOM_DETECT);
switch (this->model_) { switch (this->model_) {
case SH1107_MODEL_128_64: case SH1107_MODEL_128_64:
case SH1107_MODEL_128_128:
this->command(0x35); this->command(0x35);
break; break;
case SSD1306_MODEL_72_40: case SSD1306_MODEL_72_40:
@ -149,7 +187,7 @@ void SSD1306::setup() {
this->turn_on(); this->turn_on();
} }
void SSD1306::display() { void SSD1306::display() {
if (this->is_sh1106_()) { if (this->is_sh1106_() || this->is_sh1107_()) {
this->write_display_data(); this->write_display_data();
return; return;
} }
@ -183,6 +221,7 @@ bool SSD1306::is_sh1106_() const {
return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 || return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 ||
this->model_ == SH1106_MODEL_128_64; this->model_ == SH1106_MODEL_128_64;
} }
bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 || this->model_ == SH1107_MODEL_128_128; }
bool SSD1306::is_ssd1305_() const { bool SSD1306::is_ssd1305_() const {
return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64; return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_64;
} }
@ -224,6 +263,7 @@ void SSD1306::turn_off() {
int SSD1306::get_height_internal() { int SSD1306::get_height_internal() {
switch (this->model_) { switch (this->model_) {
case SH1107_MODEL_128_64: case SH1107_MODEL_128_64:
case SH1107_MODEL_128_128:
return 128; return 128;
case SSD1306_MODEL_128_32: case SSD1306_MODEL_128_32:
case SSD1306_MODEL_64_32: case SSD1306_MODEL_64_32:
@ -254,6 +294,7 @@ int SSD1306::get_width_internal() {
case SH1106_MODEL_128_64: case SH1106_MODEL_128_64:
case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_32:
case SSD1305_MODEL_128_64: case SSD1305_MODEL_128_64:
case SH1107_MODEL_128_128:
return 128; return 128;
case SSD1306_MODEL_96_16: case SSD1306_MODEL_96_16:
case SH1106_MODEL_96_16: case SH1106_MODEL_96_16:

View file

@ -19,6 +19,7 @@ enum SSD1306Model {
SH1106_MODEL_96_16, SH1106_MODEL_96_16,
SH1106_MODEL_64_48, SH1106_MODEL_64_48,
SH1107_MODEL_128_64, SH1107_MODEL_128_64,
SH1107_MODEL_128_128,
SSD1305_MODEL_128_32, SSD1305_MODEL_128_32,
SSD1305_MODEL_128_64, SSD1305_MODEL_128_64,
}; };
@ -58,6 +59,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer {
void init_reset_(); void init_reset_();
bool is_sh1106_() const; bool is_sh1106_() const;
bool is_sh1107_() const;
bool is_ssd1305_() const; bool is_ssd1305_() const;
void draw_absolute_pixel_internal(int x, int y, Color color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;

View file

@ -38,13 +38,19 @@ void I2CSSD1306::dump_config() {
} }
void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); } void I2CSSD1306::command(uint8_t value) { this->write_byte(0x00, value); }
void HOT I2CSSD1306::write_display_data() { void HOT I2CSSD1306::write_display_data() {
if (this->is_sh1106_()) { if (this->is_sh1106_() || this->is_sh1107_()) {
uint32_t i = 0; uint32_t i = 0;
for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) { for (uint8_t page = 0; page < (uint8_t) this->get_height_internal() / 8; page++) {
this->command(0xB0 + page); // row this->command(0xB0 + page); // row
this->command(0x02); // lower column if (this->is_sh1106_()) {
this->command(0x10); // higher column this->command(0x02); // lower column - 0x02 is historical SH1106 value
} else {
// Other SH1107 drivers use 0x00
// Column values dont change and it seems they can be set only once,
// but we follow SH1106 implementation and resend them
this->command(0x00);
}
this->command(0x10); // higher column
for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) { for (uint8_t x = 0; x < (uint8_t) this->get_width_internal() / 16; x++) {
uint8_t data[16]; uint8_t data[16];
for (uint8_t &j : data) for (uint8_t &j : data)

View file

@ -36,10 +36,14 @@ void SPISSD1306::command(uint8_t value) {
this->disable(); this->disable();
} }
void HOT SPISSD1306::write_display_data() { void HOT SPISSD1306::write_display_data() {
if (this->is_sh1106_()) { if (this->is_sh1106_() || this->is_sh1107_()) {
for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) { for (uint8_t y = 0; y < (uint8_t) this->get_height_internal() / 8; y++) {
this->command(0xB0 + y); this->command(0xB0 + y);
this->command(0x02); if (this->is_sh1106_()) {
this->command(0x02);
} else {
this->command(0x00);
}
this->command(0x10); this->command(0x10);
this->dc_pin_->digital_write(true); this->dc_pin_->digital_write(true);
for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) { for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x++) {