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