ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
ssd1306_base.cpp
Go to the documentation of this file.
1#include "ssd1306_base.h"
3#include "esphome/core/log.h"
5
7
8static const char *const TAG = "ssd1306";
9
10static const uint8_t SSD1306_MAX_CONTRAST = 255;
11static const uint8_t SSD1305_MAX_BRIGHTNESS = 255;
12
13static const uint8_t SSD1306_COMMAND_DISPLAY_OFF = 0xAE;
14static const uint8_t SSD1306_COMMAND_DISPLAY_ON = 0xAF;
15static const uint8_t SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV = 0xD5;
16static const uint8_t SSD1306_COMMAND_SET_MULTIPLEX = 0xA8;
17static const uint8_t SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y = 0xD3;
18static const uint8_t SSD1306_COMMAND_SET_START_LINE = 0x40;
19static const uint8_t SSD1306_COMMAND_CHARGE_PUMP = 0x8D;
20static const uint8_t SSD1306_COMMAND_MEMORY_MODE = 0x20;
21static const uint8_t SSD1306_COMMAND_SEGRE_MAP = 0xA0;
22static const uint8_t SSD1306_COMMAND_COM_SCAN_INC = 0xC0;
23static const uint8_t SSD1306_COMMAND_COM_SCAN_DEC = 0xC8;
24static const uint8_t SSD1306_COMMAND_SET_COM_PINS = 0xDA;
25static const uint8_t SSD1306_COMMAND_SET_CONTRAST = 0x81;
26static const uint8_t SSD1306_COMMAND_SET_PRE_CHARGE = 0xD9;
27static const uint8_t SSD1306_COMMAND_SET_VCOM_DETECT = 0xDB;
28static const uint8_t SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME = 0xA4;
29static const uint8_t SSD1306_COMMAND_DEACTIVATE_SCROLL = 0x2E;
30static const uint8_t SSD1306_COMMAND_COLUMN_ADDRESS = 0x21;
31static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22;
32static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6;
33static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7;
34
35static const uint8_t SSD1306B_COMMAND_SELECT_IREF = 0xAD;
36
37static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82;
38static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8;
39
40static const uint8_t SH1107_COMMAND_SET_START_LINE = 0xDC;
41static const uint8_t SH1107_COMMAND_CHARGE_PUMP = 0xAD;
42
43// Verify first enum value and table sizes match SSD1306_MODEL_COUNT
44static_assert(SSD1306_MODEL_128_32 == 0, "SSD1306Model enum must start at 0");
45
46// PROGMEM lookup table indexed by SSD1306Model enum (width, height per model)
47struct ModelDimensions {
48 uint8_t width;
49 uint8_t height;
50};
51static constexpr ModelDimensions MODEL_DIMS[] PROGMEM = {
52 {128, 32}, // SSD1306_MODEL_128_32
53 {128, 64}, // SSD1306_MODEL_128_64
54 {96, 16}, // SSD1306_MODEL_96_16
55 {64, 48}, // SSD1306_MODEL_64_48
56 {64, 32}, // SSD1306_MODEL_64_32
57 {72, 40}, // SSD1306_MODEL_72_40
58 {128, 32}, // SH1106_MODEL_128_32
59 {128, 64}, // SH1106_MODEL_128_64
60 {96, 16}, // SH1106_MODEL_96_16
61 {64, 48}, // SH1106_MODEL_64_48
62 {64, 128}, // SH1107_MODEL_128_64 (note: width is 64, height is 128)
63 {128, 128}, // SH1107_MODEL_128_128
64 {128, 32}, // SSD1305_MODEL_128_32
65 {128, 64}, // SSD1305_MODEL_128_64
66};
67
68// clang-format off
70 "SSD1306 128x32", // SSD1306_MODEL_128_32
71 "SSD1306 128x64", // SSD1306_MODEL_128_64
72 "SSD1306 96x16", // SSD1306_MODEL_96_16
73 "SSD1306 64x48", // SSD1306_MODEL_64_48
74 "SSD1306 64x32", // SSD1306_MODEL_64_32
75 "SSD1306 72x40", // SSD1306_MODEL_72_40
76 "SH1106 128x32", // SH1106_MODEL_128_32
77 "SH1106 128x64", // SH1106_MODEL_128_64
78 "SH1106 96x16", // SH1106_MODEL_96_16
79 "SH1106 64x48", // SH1106_MODEL_64_48
80 "SH1107 128x64", // SH1107_MODEL_128_64
81 "SH1107 128x128", // SH1107_MODEL_128_128
82 "SSD1305 128x32", // SSD1305_MODEL_128_32
83 "SSD1305 128x64", // SSD1305_MODEL_128_64
84 "Unknown" // fallback
85);
86// clang-format on
87static_assert(sizeof(MODEL_DIMS) / sizeof(MODEL_DIMS[0]) == SSD1306_MODEL_COUNT,
88 "MODEL_DIMS must have one entry per SSD1306Model");
89static_assert(ModelStrings::COUNT == SSD1306_MODEL_COUNT + 1,
90 "ModelStrings must have one entry per SSD1306Model plus fallback");
91
94
95 // SH1107 resources
96 //
97 // Datasheet v2.3:
98 // www.displayfuture.com/Display/datasheet/controller/SH1107.pdf
99 // Adafruit C++ driver:
100 // github.com/adafruit/Adafruit_SH110x
101 // Adafruit CircuitPython driver:
102 // github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1107
103
104 // Turn off display during initialization (0xAE)
105 this->command(SSD1306_COMMAND_DISPLAY_OFF);
106
107 // If SH1107, use POR defaults (0x50) = divider 1, frequency +0%
108 if (!this->is_sh1107_()) {
109 // Set oscillator frequency to 4'b1000 with no clock division (0xD5)
110 this->command(SSD1306_COMMAND_SET_DISPLAY_CLOCK_DIV);
111 // Oscillator frequency <= 4'b1000, no clock division
112 this->command(0x80);
113 }
114
115 // Enable low power display mode for SSD1305 (0xD8)
116 if (this->is_ssd1305_()) {
117 this->command(SSD1305_COMMAND_SET_AREA_COLOR);
118 this->command(0x05);
119 }
120
121 // Set mux ratio to [Y pixels - 1] (0xA8)
122 this->command(SSD1306_COMMAND_SET_MULTIPLEX);
123 this->command(this->get_height_internal() - 1);
124
125 // Set Y offset (0xD3)
126 this->command(SSD1306_COMMAND_SET_DISPLAY_OFFSET_Y);
127 this->command(0x00 + this->offset_y_);
128
129 if (this->is_sh1107_()) {
130 // Set start line at line 0 (0xDC)
131 this->command(SH1107_COMMAND_SET_START_LINE);
132 this->command(0x00);
133 } else {
134 // Set start line at line 0 (0x40)
135 this->command(SSD1306_COMMAND_SET_START_LINE | 0x00);
136 }
137
138 if (this->is_ssd1305_()) {
139 // SSD1305 does not have charge pump
140 } else if (this->is_sh1107_()) {
141 // Enable charge pump (0xAD)
142 this->command(SH1107_COMMAND_CHARGE_PUMP);
143 if (this->external_vcc_) {
144 this->command(0x8A);
145 } else {
146 this->command(0x8B);
147 }
148 } else {
149 if (this->is_ssd1306b_()) {
150 // Select external or internal Iref (0xAD)
151 this->command(SSD1306B_COMMAND_SELECT_IREF);
152 // Enable internal Iref and change from 19ua (POR) to 30uA
153 this->command(0x20 | 0x10);
154 }
155 // Enable charge pump (0x8D)
156 this->command(SSD1306_COMMAND_CHARGE_PUMP);
157 if (this->external_vcc_) {
158 this->command(0x10);
159 } else {
160 this->command(0x14);
161 }
162 }
163
164 // Set addressing mode to horizontal (0x20)
165 this->command(SSD1306_COMMAND_MEMORY_MODE);
166 if (!this->is_sh1107_()) {
167 // SH1107 memory mode is a 1 byte command
168 this->command(0x00);
169 }
170 // X flip mode (0xA0, 0xA1)
171 this->command(SSD1306_COMMAND_SEGRE_MAP | this->flip_x_);
172
173 // Y flip mode (0xC0, 0xC8)
174 this->command(SSD1306_COMMAND_COM_SCAN_INC | (this->flip_y_ << 3));
175
176 if (!this->is_sh1107_()) {
177 // Set pin configuration (0xDA)
178 this->command(SSD1306_COMMAND_SET_COM_PINS);
179 switch (this->model_) {
184 this->command(0x02);
185 break;
194 this->command(0x12);
195 break;
199 // Not used, but prevents build warning
200 break;
201 }
202 }
203
204 // Pre-charge period (0xD9)
205 this->command(SSD1306_COMMAND_SET_PRE_CHARGE);
206 if (this->external_vcc_) {
207 this->command(0x22);
208 } else {
209 this->command(0xF1);
210 }
211
212 // Set V_COM (0xDB)
213 this->command(SSD1306_COMMAND_SET_VCOM_DETECT);
214 switch (this->model_) {
218 this->command(0x35);
219 break;
221 this->command(0x20);
222 break;
223 default:
224 this->command(0x00);
225 break;
226 }
227
228 // Display output follow RAM (0xA4)
229 this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME);
230
231 // Inverse display mode (0xA6, 0xA7)
232 this->set_invert(this->invert_);
233
234 // Disable scrolling mode (0x2E)
235 this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL);
236
237 // Contrast and brightness
238 // SSD1306 does not have brightness setting
239 set_contrast(this->contrast_);
240 if (this->is_ssd1305_())
242
243 this->fill(Color::BLACK); // clear display - ensures we do not see garbage at power-on
244 this->display(); // ...write buffer, which actually clears the display's memory
245
246 this->turn_on();
247}
249 if (this->is_sh1106_() || this->is_sh1107_()) {
250 this->write_display_data();
251 return;
252 }
253
254 this->command(SSD1306_COMMAND_COLUMN_ADDRESS);
255 switch (this->model_) {
258 this->command(0x20 + this->offset_x_);
259 this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1);
260 break;
262 this->command(0x1C + this->offset_x_);
263 this->command(0x1C + this->offset_x_ + this->get_width_internal() - 1);
264 break;
265 default:
266 this->command(0 + this->offset_x_); // Page start address, 0
267 this->command(this->get_width_internal() + this->offset_x_ - 1);
268 break;
269 }
270
271 this->command(SSD1306_COMMAND_PAGE_ADDRESS);
272 // Page start address, 0
273 this->command(0);
274 // Page end address:
275 this->command((this->get_height_internal() / 8) - 1);
276
277 this->write_display_data();
278}
280 return this->model_ == SH1106_MODEL_96_16 || this->model_ == SH1106_MODEL_128_32 ||
282}
283bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 || this->model_ == SH1107_MODEL_128_128; }
285 return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_32;
286}
287bool SSD1306::is_ssd1306b_() const { return this->model_ == SSD1306_MODEL_72_40; }
288
290 this->do_update_();
291 this->display();
292}
293
294void SSD1306::set_invert(bool invert) {
295 this->invert_ = invert;
296 // Inverse display mode (0xA6, 0xA7)
297 this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_);
298}
299float SSD1306::get_contrast() { return this->contrast_; };
300void SSD1306::set_contrast(float contrast) {
301 // validation
302 this->contrast_ = clamp(contrast, 0.0F, 1.0F);
303 // now write the new contrast level to the display (0x81)
304 this->command(SSD1306_COMMAND_SET_CONTRAST);
305 this->command(int(SSD1306_MAX_CONTRAST * (this->contrast_)));
306}
307float SSD1306::get_brightness() { return this->brightness_; };
308void SSD1306::set_brightness(float brightness) {
309 // validation
310 if (!this->is_ssd1305_())
311 return;
312 this->brightness_ = clamp(brightness, 0.0F, 1.0F);
313 // now write the new brightness level to the display (0x82)
314 this->command(SSD1305_COMMAND_SET_BRIGHTNESS);
315 this->command(int(SSD1305_MAX_BRIGHTNESS * (this->brightness_)));
316}
317bool SSD1306::is_on() { return this->is_on_; }
319 this->command(SSD1306_COMMAND_DISPLAY_ON);
320 this->is_on_ = true;
321}
323 this->command(SSD1306_COMMAND_DISPLAY_OFF);
324 this->is_on_ = false;
325}
327 if (this->model_ >= SSD1306_MODEL_COUNT)
328 return 0;
329 return progmem_read_byte(&MODEL_DIMS[this->model_].height);
330}
332 if (this->model_ >= SSD1306_MODEL_COUNT)
333 return 0;
334 return progmem_read_byte(&MODEL_DIMS[this->model_].width);
335}
337 return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
338}
340 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
341 return;
342
343 uint16_t pos = x + (y / 8) * this->get_width_internal();
344 uint8_t subpos = y & 0x07;
345 if (color.is_on()) {
346 this->buffer_[pos] |= (1 << subpos);
347 } else {
348 this->buffer_[pos] &= ~(1 << subpos);
349 }
350}
351void SSD1306::fill(Color color) {
352 // If clipping is active, fall back to base implementation
353 if (this->get_clipping().is_set()) {
354 Display::fill(color);
355 return;
356 }
357
358 uint8_t fill = color.is_on() ? 0xFF : 0x00;
359 for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
360 this->buffer_[i] = fill;
361}
363 if (this->reset_pin_ != nullptr) {
364 this->reset_pin_->setup();
365 this->reset_pin_->digital_write(true);
366 delay(1);
367 // Trigger Reset
368 this->reset_pin_->digital_write(false);
369 delay(10);
370 // Wake up
371 this->reset_pin_->digital_write(true);
372 }
373}
374const LogString *SSD1306::model_str_() {
375 return ModelStrings::get_log_str(static_cast<uint8_t>(this->model_), ModelStrings::LAST_INDEX);
376}
377
378} // namespace esphome::ssd1306_base
virtual void setup()=0
virtual void digital_write(bool value)=0
void init_internal_(uint32_t buffer_length)
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
void fill(Color color) override
virtual void write_display_data()=0
virtual void command(uint8_t value)=0
void draw_absolute_pixel_internal(int x, int y, Color color) override
void set_brightness(float brightness)
void set_contrast(float contrast)
PROGMEM_STRING_TABLE(ModelStrings, "SSD1306 128x32", "SSD1306 128x64", "SSD1306 96x16", "SSD1306 64x48", "SSD1306 64x32", "SSD1306 72x40", "SH1106 128x32", "SH1106 128x64", "SH1106 96x16", "SH1106 64x48", "SH1107 128x64", "SH1107 128x128", "SSD1305 128x32", "SSD1305 128x64", "Unknown")
size_t size_t pos
Definition helpers.h:1038
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:43
static void uint32_t
bool is_on() ESPHOME_ALWAYS_INLINE
Definition color.h:70
static const Color BLACK
Definition color.h:184
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6