4242#define CHAR_WIDTH 8
4343
4444#define I2C_ADDRESS 0x3C
45+ #define I2C_CHUNK_SIZE 64
4546
4647#define CTRL_BYTE_CMD_SINGLE 0x80
4748#define CTRL_BYTE_CMD_STREAM 0x00
5354#define CMD_SET_COM_SCAN_MODE 0xC8
5455#define CMD_SET_CHARGE_PUMP 0x8D
5556
57+ typedef enum
58+ {
59+ DISPLAY_SSD1306 ,
60+ DISPLAY_SSD1315 ,
61+ DISPLAY_SH1106 ,
62+ } display_type_t ;
63+
5664// TODO: let's change name, since also non SPI display are supported now
5765struct SPI
5866{
5967 term i2c_host ;
60- bool is_sh1106 ;
68+ display_type_t type ;
6169 Context * ctx ;
6270};
6371
@@ -116,8 +124,9 @@ static void do_update(Context *ctx, term display_list)
116124
117125 i2c_master_write_byte (cmd , CTRL_BYTE_CMD_SINGLE , true);
118126 i2c_master_write_byte (cmd , 0xB0 | ypos / 8 , true);
119- if (spi -> is_sh1106 ) {
120- // set the column, otherwise the starting column will be somewhere in the middle
127+
128+ if (spi -> type == DISPLAY_SH1106 || spi -> type == DISPLAY_SSD1315 ) {
129+ // SSD1315 and SH1106 require explicit column address reset
121130 i2c_master_write_byte (cmd , CTRL_BYTE_CMD_SINGLE , true);
122131 i2c_master_write_byte (cmd , 0x00 , true);
123132 i2c_master_write_byte (cmd , CTRL_BYTE_CMD_SINGLE , true);
@@ -126,19 +135,26 @@ static void do_update(Context *ctx, term display_list)
126135 i2c_master_write_byte (cmd , CTRL_BYTE_DATA_STREAM , true);
127136
128137
129- if (spi -> is_sh1106 ) {
138+ if (spi -> type == DISPLAY_SH1106 ) {
130139 // add 2 empty pages on sh1106 since it can have up to 132 pixels
131140 // and 128 pixel screen starts at (2, 0)
132141 i2c_master_write_byte (cmd , 0 , true);
133142 i2c_master_write_byte (cmd , 0 , true);
134143 }
135144
136- for (uint8_t j = 0 ; j < DISPLAY_WIDTH ; j ++ ) {
137- i2c_master_write_byte (cmd , out_buf [j ], true);
145+ // send pages in chunks to avoid timeout error (0x107)
146+ int offset = 0 ;
147+ while (offset < DISPLAY_WIDTH ) {
148+ int chunk_len = DISPLAY_WIDTH - offset ;
149+ if (chunk_len > I2C_CHUNK_SIZE ) {
150+ chunk_len = I2C_CHUNK_SIZE ;
151+ }
152+ i2c_master_write (cmd , out_buf + offset , chunk_len , true);
153+ offset += chunk_len ;
138154 }
139155
140156 // no need to send the last 2 page, the position will be set on next line again
141- // if (spi->is_sh1106 ) {
157+ // if (spi->type == DISPLAY_SH1106 ) {
142158 // i2c_master_write_byte(cmd, 0, true);
143159 // i2c_master_write_byte(cmd, 0, true);
144160 // }
@@ -176,12 +192,17 @@ static void display_init(Context *ctx, term opts)
176192 ctx -> platform_data = spi ;
177193
178194 spi -> ctx = ctx ;
195+ spi -> type = DISPLAY_SSD1306 ; // Default to SSD1306
179196
180197 term compat_value_term = interop_kv_get_value_default (opts , ATOM_STR ("\xA" , "compatible" ), term_nil (), ctx -> global );
181198 int str_ok ;
182199 char * compat_string = interop_term_to_string (compat_value_term , & str_ok );
183200 if (str_ok && compat_string ) {
184- spi -> is_sh1106 = !strcmp (compat_string , "sino-wealth,sh1106" );
201+ if (!strcmp (compat_string , "sino-wealth,sh1106" )) {
202+ spi -> type = DISPLAY_SH1106 ;
203+ } else if (!strcmp (compat_string , "solomon-systech,ssd1315" )) {
204+ spi -> type = DISPLAY_SSD1315 ;
205+ }
185206 free (compat_string );
186207 } else {
187208 return ;
@@ -209,11 +230,60 @@ static void display_init(Context *ctx, term opts)
209230 i2c_master_write_byte (cmd , (I2C_ADDRESS << 1 ) | I2C_MASTER_WRITE , true);
210231 i2c_master_write_byte (cmd , CTRL_BYTE_CMD_STREAM , true);
211232
212- i2c_master_write_byte (cmd , CMD_SET_CHARGE_PUMP , true);
213- i2c_master_write_byte (cmd , 0x14 , true);
233+ if (spi -> type == DISPLAY_SSD1315 ) {
234+ /*
235+ * Init sequence derived from u8g2 project (BSD-2-Clause).
236+ * Source: https://github.com/olikraus/u8g2
237+ *
238+ * These values are standard hardware initialization commands
239+ * defined by the Solomon Systech SSD1315 datasheet.
240+ */
241+
242+ i2c_master_write_byte (cmd , 0xAE , true); // Display OFF
243+
244+ i2c_master_write_byte (cmd , 0xD5 , true); // Set Display Clock Divide Ratio / Oscillator Frequency
245+ i2c_master_write_byte (cmd , 0x80 , true); // 0x80 is standard/stable
246+
247+ i2c_master_write_byte (cmd , 0xA8 , true); // Set Multiplex Ratio
248+ i2c_master_write_byte (cmd , 0x3F , true); // 64 MUX
249+
250+ i2c_master_write_byte (cmd , 0xD3 , true); // Set Display Offset
251+ i2c_master_write_byte (cmd , 0x00 , true); // No offset
252+
253+ i2c_master_write_byte (cmd , 0x40 , true); // Set Display Start Line to 0
254+
255+ i2c_master_write_byte (cmd , 0x8D , true); // Set Charge Pump
256+ i2c_master_write_byte (cmd , 0x14 , true); // Enable Charge Pump
257+
258+ i2c_master_write_byte (cmd , 0xA1 , true); // Set Segment Remap
259+ i2c_master_write_byte (cmd , 0xC8 , true); // Set COM Scan Mode
260+
261+ i2c_master_write_byte (cmd , 0xDA , true); // Set COM Pins Hardware Configuration
262+ i2c_master_write_byte (cmd , 0x12 , true); // Alternative COM pin config
263+
264+ i2c_master_write_byte (cmd , 0x81 , true); // Set Contrast Control
265+ i2c_master_write_byte (cmd , 0xCF , true); // Use High Contrast (0xCF) as per u8x8
266+
267+ i2c_master_write_byte (cmd , 0xD9 , true); // Set Pre-charge Period
268+ i2c_master_write_byte (cmd , 0xF1 , true); // 0xF1 is required for stable 400kHz operation
269+
270+ i2c_master_write_byte (cmd , 0xDB , true); // Set VCOMH Deselect Level
271+ i2c_master_write_byte (cmd , 0x40 , true); // 0x40 (approx 0.77x VCC)
272+
273+ i2c_master_write_byte (cmd , 0xA4 , true); // Resume to RAM content display
274+ i2c_master_write_byte (cmd , 0xA6 , true); // Normal Display (not inverted)
275+
276+ i2c_master_write_byte (cmd , 0xAD , true); // Internal IREF Setting
277+ i2c_master_write_byte (cmd , 0x10 , true); // Internal Iref
278+
279+ i2c_master_write_byte (cmd , 0xAF , true); // Display ON
280+ } else {
281+ i2c_master_write_byte (cmd , CMD_SET_CHARGE_PUMP , true);
282+ i2c_master_write_byte (cmd , 0x14 , true);
214283
215- i2c_master_write_byte (cmd , CMD_SET_SEGMENT_REMAP , true);
216- i2c_master_write_byte (cmd , CMD_SET_COM_SCAN_MODE , true);
284+ i2c_master_write_byte (cmd , CMD_SET_SEGMENT_REMAP , true);
285+ i2c_master_write_byte (cmd , CMD_SET_COM_SCAN_MODE , true);
286+ }
217287
218288 if (invert ) {
219289 i2c_master_write_byte (cmd , CMD_DISPLAY_INVERTED , true);
@@ -224,7 +294,7 @@ static void display_init(Context *ctx, term opts)
224294
225295 esp_err_t res = i2c_master_cmd_begin (i2c_num , cmd , 50 / portTICK_PERIOD_MS );
226296 if (res != ESP_OK ) {
227- ESP_LOGE (TAG , "ssd1306 OLED configuration failed. error: 0x%.2X" , res );
297+ ESP_LOGE (TAG , "ssd1306/ssd1315 OLED configuration failed. error: 0x%.2X" , res );
228298 } else {
229299 xTaskCreate (process_messages , "display" , 10000 , spi , 1 , NULL );
230300 }
0 commit comments