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