oled_driver.c
31583 bytes
1/*
2Copyright 2019 Ryan Caltabiano <https://github.com/XScorpion2>
3
4This program is free software: you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation, either version 2 of the License, or
7(at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#if defined(OLED_TRANSPORT_SPI)
19# include "spi_master.h"
20#elif defined(OLED_TRANSPORT_I2C)
21# include "i2c_master.h"
22# if defined(USE_I2C) && defined(SPLIT_KEYBOARD)
23# include "keyboard.h"
24# endif
25#endif
26#include "oled_driver.h"
27#include OLED_FONT_H
28#include "timer.h"
29#include "print.h"
30#include <string.h>
31#include "progmem.h"
32#include "wait.h"
33
34// Used commands from spec sheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
35// for SH1106: https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf
36// for SH1107: https://www.displayfuture.com/Display/datasheet/controller/SH1107.pdf
37
38// Fundamental Commands
39#define CONTRAST 0x81
40#define DISPLAY_ALL_ON 0xA5
41#define DISPLAY_ALL_ON_RESUME 0xA4
42#define NORMAL_DISPLAY 0xA6
43#define INVERT_DISPLAY 0xA7
44#define DISPLAY_ON 0xAF
45#define DISPLAY_OFF 0xAE
46#define NOP 0xE3
47
48// Scrolling Commands
49#define ACTIVATE_SCROLL 0x2F
50#define DEACTIVATE_SCROLL 0x2E
51#define SCROLL_RIGHT 0x26
52#define SCROLL_LEFT 0x27
53#define SCROLL_RIGHT_UP 0x29
54#define SCROLL_LEFT_UP 0x2A
55
56// Addressing Setting Commands
57#define MEMORY_MODE 0x20
58#define COLUMN_ADDR 0x21
59#define PAGE_ADDR 0x22
60#define PAM_SETCOLUMN_LSB 0x00
61#define PAM_SETCOLUMN_MSB 0x10
62#define PAM_PAGE_ADDR 0xB0 // 0xb0 -- 0xb7
63
64// Hardware Configuration Commands
65#define DISPLAY_START_LINE 0x40
66#define SEGMENT_REMAP 0xA0
67#define SEGMENT_REMAP_INV 0xA1
68#define MULTIPLEX_RATIO 0xA8
69#define COM_SCAN_INC 0xC0
70#define COM_SCAN_DEC 0xC8
71#define DISPLAY_OFFSET 0xD3
72#define COM_PINS 0xDA
73#define COM_PINS_SEQ 0x02
74#define COM_PINS_ALT 0x12
75#define COM_PINS_SEQ_LR 0x22
76#define COM_PINS_ALT_LR 0x32
77
78// Timing & Driving Commands
79#define DISPLAY_CLOCK 0xD5
80#define PRE_CHARGE_PERIOD 0xD9
81#define VCOM_DETECT 0xDB
82
83// Advance Graphic Commands
84#define FADE_BLINK 0x23
85#define ENABLE_FADE 0x20
86#define ENABLE_BLINK 0x30
87
88// Charge Pump Commands
89#define CHARGE_PUMP 0x8D
90
91// Commands specific to the SH1107 chip
92#define SH1107_DISPLAY_START_LINE 0xDC
93#define SH1107_MEMORY_MODE_PAGE 0x20
94#define SH1107_MEMORY_MODE_VERTICAL 0x21
95
96// Misc defines
97#ifndef OLED_BLOCK_COUNT
98# define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8)
99#endif
100#ifndef OLED_BLOCK_SIZE
101# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT)
102#endif
103// Default display clock
104#if !defined(OLED_DISPLAY_CLOCK)
105# define OLED_DISPLAY_CLOCK 0x80
106#endif
107// Default VCOMH deselect value
108#if !defined(OLED_VCOM_DETECT)
109# define OLED_VCOM_DETECT 0x20
110#endif
111#if !defined(OLED_PRE_CHARGE_PERIOD)
112# define OLED_PRE_CHARGE_PERIOD 0xF1
113#endif
114
115#define OLED_ALL_BLOCKS_MASK (((((OLED_BLOCK_TYPE)1 << (OLED_BLOCK_COUNT - 1)) - 1) << 1) | 1)
116
117#define OLED_IC_HAS_HORIZONTAL_MODE (OLED_IC == OLED_IC_SSD1306)
118#define OLED_IC_COM_PINS_ARE_COLUMNS (OLED_IC == OLED_IC_SH1107)
119
120#ifndef OLED_COM_PIN_COUNT
121# if OLED_IC == OLED_IC_SSD1306
122# define OLED_COM_PIN_COUNT 64
123# elif OLED_IC == OLED_IC_SH1106
124# define OLED_COM_PIN_COUNT 64
125# elif OLED_IC == OLED_IC_SH1107
126# define OLED_COM_PIN_COUNT 128
127# else
128# error Invalid OLED_IC value
129# endif
130#endif
131
132#ifndef OLED_COM_PIN_OFFSET
133# define OLED_COM_PIN_OFFSET 0
134#endif
135
136// i2c defines
137#define I2C_CMD 0x00
138#define I2C_DATA 0x40
139
140#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)
141
142// Display buffer's is the same as the OLED memory layout
143// this is so we don't end up with rounding errors with
144// parts of the display unusable or don't get cleared correctly
145// and also allows for drawing & inverting
146uint8_t oled_buffer[OLED_MATRIX_SIZE];
147uint8_t * oled_cursor;
148OLED_BLOCK_TYPE oled_dirty = 0;
149bool oled_initialized = false;
150bool oled_active = false;
151bool oled_scrolling = false;
152bool oled_inverted = false;
153uint8_t oled_brightness = OLED_BRIGHTNESS;
154oled_rotation_t oled_rotation = 0;
155uint8_t oled_rotation_width = 0;
156uint8_t oled_scroll_speed = 0; // this holds the speed after being remapped to ssd1306 internal values
157uint8_t oled_scroll_start = 0;
158uint8_t oled_scroll_end = 7;
159#if OLED_TIMEOUT > 0
160uint32_t oled_timeout;
161#endif
162#if OLED_SCROLL_TIMEOUT > 0
163uint32_t oled_scroll_timeout;
164#endif
165#if OLED_UPDATE_INTERVAL > 0
166uint16_t oled_update_timeout;
167#endif
168
169#if defined(OLED_TRANSPORT_SPI)
170# ifndef OLED_DC_PIN
171# error "The OLED driver in SPI needs a D/C pin defined"
172# endif
173# ifndef OLED_CS_PIN
174# error "The OLED driver in SPI needs a CS pin defined"
175# endif
176# ifndef OLED_SPI_MODE
177# define OLED_SPI_MODE 3
178# endif
179# ifndef OLED_SPI_DIVISOR
180# define OLED_SPI_DIVISOR 2
181# endif
182#elif defined(OLED_TRANSPORT_I2C)
183# if !defined(OLED_DISPLAY_ADDRESS)
184# define OLED_DISPLAY_ADDRESS 0x3C
185# endif
186#endif
187
188// Transmit/Write Funcs.
189__attribute__((weak)) bool oled_send_cmd(const uint8_t *data, uint16_t size) {
190#if defined(OLED_TRANSPORT_SPI)
191 if (!spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR)) {
192 return false;
193 }
194 // Command Mode
195 gpio_write_pin_low(OLED_DC_PIN);
196 // Send the commands
197 if (spi_transmit(&data[1], size - 1) != SPI_STATUS_SUCCESS) {
198 spi_stop();
199 return false;
200 }
201 spi_stop();
202 return true;
203#elif defined(OLED_TRANSPORT_I2C)
204 i2c_status_t status = i2c_transmit((OLED_DISPLAY_ADDRESS << 1), data, size, OLED_I2C_TIMEOUT);
205
206 return (status == I2C_STATUS_SUCCESS);
207#endif
208}
209
210__attribute__((weak)) bool oled_send_cmd_P(const uint8_t *data, uint16_t size) {
211#if defined(__AVR__)
212# if defined(OLED_TRANSPORT_SPI)
213 if (!spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR)) {
214 return false;
215 }
216 spi_status_t status = SPI_STATUS_SUCCESS;
217 // Command Mode
218 gpio_write_pin_low(OLED_DC_PIN);
219 // Send the commands
220 for (uint16_t i = 1; i < size && status >= 0; i++) {
221 status = spi_write(pgm_read_byte((const char *)&data[i]));
222 }
223 spi_stop();
224 return (status >= 0);
225# elif defined(OLED_TRANSPORT_I2C)
226
227 i2c_status_t status = i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), data, size, OLED_I2C_TIMEOUT);
228
229 return (status == I2C_STATUS_SUCCESS);
230# endif
231#else
232 return oled_send_cmd(data, size);
233#endif
234}
235
236__attribute__((weak)) bool oled_send_data(const uint8_t *data, uint16_t size) {
237#if defined(OLED_TRANSPORT_SPI)
238 if (!spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR)) {
239 return false;
240 }
241 // Data Mode
242 gpio_write_pin_high(OLED_DC_PIN);
243 // Send the commands
244 if (spi_transmit(data, size) != SPI_STATUS_SUCCESS) {
245 spi_stop();
246 return false;
247 }
248 spi_stop();
249 return true;
250#elif defined(OLED_TRANSPORT_I2C)
251 i2c_status_t status = i2c_write_register((OLED_DISPLAY_ADDRESS << 1), I2C_DATA, data, size, OLED_I2C_TIMEOUT);
252 return (status == I2C_STATUS_SUCCESS);
253#endif
254}
255
256__attribute__((weak)) void oled_driver_init(void) {
257#if defined(OLED_TRANSPORT_SPI)
258 spi_init();
259 gpio_set_pin_output(OLED_CS_PIN);
260 gpio_write_pin_high(OLED_CS_PIN);
261
262 gpio_set_pin_output(OLED_DC_PIN);
263 gpio_write_pin_low(OLED_DC_PIN);
264# ifdef OLED_RST_PIN
265 /* Reset device */
266 gpio_set_pin_output(OLED_RST_PIN);
267 gpio_write_pin_low(OLED_RST_PIN);
268 wait_ms(20);
269 gpio_write_pin_high(OLED_RST_PIN);
270 wait_ms(20);
271# endif
272#elif defined(OLED_TRANSPORT_I2C)
273 i2c_init();
274#endif
275}
276
277// Flips the rendering bits for a character at the current cursor position
278static void InvertCharacter(uint8_t *cursor) {
279 const uint8_t *end = cursor + OLED_FONT_WIDTH;
280 while (cursor < end) {
281 *cursor = ~(*cursor);
282 cursor++;
283 }
284}
285
286bool oled_init(oled_rotation_t rotation) {
287#if defined(USE_I2C) && defined(SPLIT_KEYBOARD) && defined(OLED_TRANSPORT_I2C)
288 if (!is_keyboard_master()) {
289 return true;
290 }
291#endif
292
293 oled_rotation = oled_init_user(oled_init_kb(rotation));
294 if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
295 oled_rotation_width = OLED_DISPLAY_WIDTH;
296 } else {
297 oled_rotation_width = OLED_DISPLAY_HEIGHT;
298 }
299 oled_driver_init();
300
301 static const uint8_t PROGMEM display_setup1[] = {
302 I2C_CMD,
303 DISPLAY_OFF,
304 DISPLAY_CLOCK,
305 OLED_DISPLAY_CLOCK,
306 MULTIPLEX_RATIO,
307#if OLED_IC_COM_PINS_ARE_COLUMNS
308 OLED_DISPLAY_WIDTH - 1,
309#else
310 OLED_DISPLAY_HEIGHT - 1,
311#endif
312#if OLED_IC == OLED_IC_SH1107
313 SH1107_DISPLAY_START_LINE,
314 0x00,
315#else
316 DISPLAY_START_LINE | 0x00,
317#endif
318 CHARGE_PUMP,
319 0x14,
320#if OLED_IC_HAS_HORIZONTAL_MODE
321 // MEMORY_MODE is unsupported on SH1106 (Page Addressing only)
322 MEMORY_MODE,
323 0x00, // Horizontal addressing mode
324#elif OLED_IC == OLED_IC_SH1107
325 // Page addressing mode
326 SH1107_MEMORY_MODE_PAGE,
327#endif
328 };
329 if (!oled_send_cmd_P(display_setup1, ARRAY_SIZE(display_setup1))) {
330 print("oled_init cmd set 1 failed\n");
331 return false;
332 }
333
334 if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_180)) {
335 static const uint8_t PROGMEM display_normal[] = {
336 I2C_CMD, SEGMENT_REMAP_INV, COM_SCAN_DEC, DISPLAY_OFFSET, OLED_COM_PIN_OFFSET,
337 };
338 if (!oled_send_cmd_P(display_normal, ARRAY_SIZE(display_normal))) {
339 print("oled_init cmd normal rotation failed\n");
340 return false;
341 }
342 } else {
343 static const uint8_t PROGMEM display_flipped[] = {
344 I2C_CMD, SEGMENT_REMAP, COM_SCAN_INC, DISPLAY_OFFSET, (OLED_COM_PIN_COUNT - OLED_COM_PIN_OFFSET) % OLED_COM_PIN_COUNT,
345 };
346 if (!oled_send_cmd_P(display_flipped, ARRAY_SIZE(display_flipped))) {
347 print("display_flipped failed\n");
348 return false;
349 }
350 }
351
352 static const uint8_t PROGMEM display_setup2[] = {I2C_CMD, COM_PINS, OLED_COM_PINS, CONTRAST, OLED_BRIGHTNESS, PRE_CHARGE_PERIOD, OLED_PRE_CHARGE_PERIOD, VCOM_DETECT, OLED_VCOM_DETECT, DISPLAY_ALL_ON_RESUME, NORMAL_DISPLAY, DEACTIVATE_SCROLL, DISPLAY_ON};
353 if (!oled_send_cmd_P(display_setup2, ARRAY_SIZE(display_setup2))) {
354 print("display_setup2 failed\n");
355 return false;
356 }
357
358#if OLED_TIMEOUT > 0
359 oled_timeout = timer_read32() + OLED_TIMEOUT;
360#endif
361#if OLED_SCROLL_TIMEOUT > 0
362 oled_scroll_timeout = timer_read32() + OLED_SCROLL_TIMEOUT;
363#endif
364
365 oled_clear();
366 oled_initialized = true;
367 oled_active = true;
368 oled_scrolling = false;
369 return true;
370}
371
372__attribute__((weak)) oled_rotation_t oled_init_kb(oled_rotation_t rotation) {
373 return rotation;
374}
375__attribute__((weak)) oled_rotation_t oled_init_user(oled_rotation_t rotation) {
376 return rotation;
377}
378
379void oled_clear(void) {
380 memset(oled_buffer, 0, sizeof(oled_buffer));
381 oled_cursor = &oled_buffer[0];
382 oled_dirty = OLED_ALL_BLOCKS_MASK;
383}
384
385static void calc_bounds(uint8_t update_start, uint8_t *cmd_array) {
386 // Calculate commands to set memory addressing bounds.
387 uint8_t start_page = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_WIDTH;
388 uint8_t start_column = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_WIDTH;
389#if !OLED_IC_HAS_HORIZONTAL_MODE
390 // Commands for Page Addressing Mode. Sets starting page and column; has no end bound.
391 // Column value must be split into high and low nybble and sent as two commands.
392 cmd_array[0] = PAM_PAGE_ADDR | start_page;
393 cmd_array[1] = PAM_SETCOLUMN_LSB | ((OLED_COLUMN_OFFSET + start_column) & 0x0f);
394 cmd_array[2] = PAM_SETCOLUMN_MSB | ((OLED_COLUMN_OFFSET + start_column) >> 4 & 0x0f);
395#else
396 // Commands for use in Horizontal Addressing mode.
397 cmd_array[1] = start_column + OLED_COLUMN_OFFSET;
398 cmd_array[4] = start_page;
399 cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) % OLED_DISPLAY_WIDTH + cmd_array[1];
400 cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) / OLED_DISPLAY_WIDTH - 1 + cmd_array[4];
401#endif
402}
403
404static void calc_bounds_90(uint8_t update_start, uint8_t *cmd_array) {
405 // Block numbering starts from the bottom left corner, going up and then to
406 // the right. The controller needs the page and column numbers for the top
407 // left and bottom right corners of that block.
408
409 // Total number of pages across the screen height.
410 const uint8_t height_in_pages = OLED_DISPLAY_HEIGHT / 8;
411
412 // Difference of starting page numbers for adjacent blocks; may be 0 if
413 // blocks are large enough to occupy one or more whole 8px columns.
414 const uint8_t page_inc_per_block = OLED_BLOCK_SIZE % OLED_DISPLAY_HEIGHT / 8;
415
416 // Top page number for a block which is at the bottom edge of the screen.
417 const uint8_t bottom_block_top_page = (height_in_pages - page_inc_per_block) % height_in_pages;
418
419#if !OLED_IC_HAS_HORIZONTAL_MODE
420 // Only the Page Addressing Mode is supported
421 uint8_t start_page = bottom_block_top_page - (OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT / 8);
422 uint8_t start_column = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8;
423 cmd_array[0] = PAM_PAGE_ADDR | start_page;
424 cmd_array[1] = PAM_SETCOLUMN_LSB | ((OLED_COLUMN_OFFSET + start_column) & 0x0f);
425 cmd_array[2] = PAM_SETCOLUMN_MSB | ((OLED_COLUMN_OFFSET + start_column) >> 4 & 0x0f);
426#else
427 cmd_array[1] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8 + OLED_COLUMN_OFFSET;
428 cmd_array[4] = bottom_block_top_page - (OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT / 8);
429 cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8 - 1 + cmd_array[1];
430 cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) % OLED_DISPLAY_HEIGHT / 8 + cmd_array[4];
431#endif
432}
433
434uint8_t crot(uint8_t a, int8_t n) {
435 const uint8_t mask = 0x7;
436 n &= mask;
437 return a << n | a >> (-n & mask);
438}
439
440static void rotate_90(const uint8_t *src, uint8_t *dest) {
441 for (uint8_t i = 0, shift = 7; i < 8; ++i, --shift) {
442 uint8_t selector = (1 << i);
443 for (uint8_t j = 0; j < 8; ++j) {
444 dest[i] |= crot(src[j] & selector, shift - (int8_t)j);
445 }
446 }
447}
448
449void oled_render_dirty(bool all) {
450 // Do we have work to do?
451 oled_dirty &= OLED_ALL_BLOCKS_MASK;
452 if (!oled_dirty || !oled_initialized || oled_scrolling) {
453 return;
454 }
455
456 // Turn on display if it is off
457 oled_on();
458
459 uint8_t update_start = 0;
460 uint8_t num_processed = 0;
461 while (oled_dirty && (num_processed++ < OLED_UPDATE_PROCESS_LIMIT || all)) { // render all dirty blocks (up to the configured limit)
462 // Find next dirty block
463 while (!(oled_dirty & ((OLED_BLOCK_TYPE)1 << update_start))) {
464 ++update_start;
465 }
466
467 // Set column & page position
468#if OLED_IC_HAS_HORIZONTAL_MODE
469 static uint8_t display_start[] = {I2C_CMD, COLUMN_ADDR, 0, OLED_DISPLAY_WIDTH - 1, PAGE_ADDR, 0, OLED_DISPLAY_HEIGHT / 8 - 1};
470#else
471 static uint8_t display_start[] = {I2C_CMD, PAM_PAGE_ADDR, PAM_SETCOLUMN_LSB, PAM_SETCOLUMN_MSB};
472#endif
473 if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
474 calc_bounds(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
475 } else {
476 calc_bounds_90(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start
477 }
478
479 // Send column & page position
480 if (!oled_send_cmd(display_start, ARRAY_SIZE(display_start))) {
481 print("oled_render offset command failed\n");
482 return;
483 }
484
485 if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
486 // Send render data chunk as is
487 if (!oled_send_data(&oled_buffer[OLED_BLOCK_SIZE * update_start], OLED_BLOCK_SIZE)) {
488 print("oled_render data failed\n");
489 return;
490 }
491 } else {
492 // Rotate the render chunks
493 const static uint8_t source_map[] = OLED_SOURCE_MAP;
494 const static uint8_t target_map[] = OLED_TARGET_MAP;
495
496 static uint8_t temp_buffer[OLED_BLOCK_SIZE];
497 memset(temp_buffer, 0, sizeof(temp_buffer));
498 for (uint8_t i = 0; i < sizeof(source_map); ++i) {
499 rotate_90(&oled_buffer[OLED_BLOCK_SIZE * update_start + source_map[i]], &temp_buffer[target_map[i]]);
500 }
501
502#if OLED_IC_HAS_HORIZONTAL_MODE
503 // Send render data chunk after rotating
504 if (!oled_send_data(&temp_buffer[0], OLED_BLOCK_SIZE)) {
505 print("oled_render90 data failed\n");
506 return;
507 }
508#else
509 // For SH1106 or SH1107 the data chunk must be split into separate pieces for each page
510 const uint8_t columns_in_block = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8;
511 const uint8_t num_pages = OLED_BLOCK_SIZE / columns_in_block;
512 for (uint8_t i = 0; i < num_pages; ++i) {
513 // Send column & page position for all pages except the first one
514 if (i > 0) {
515 display_start[1]++;
516 if (!oled_send_cmd(display_start, ARRAY_SIZE(display_start))) {
517 print("oled_render offset command failed\n");
518 return;
519 }
520 }
521 // Send data for the page
522 if (!oled_send_data(&temp_buffer[columns_in_block * i], columns_in_block)) {
523 print("oled_render90 data failed\n");
524 return;
525 }
526 }
527#endif
528 }
529
530 // Clear dirty flag of just rendered block
531 oled_dirty &= ~((OLED_BLOCK_TYPE)1 << update_start);
532 }
533}
534
535void oled_set_cursor(uint8_t col, uint8_t line) {
536 uint16_t index = line * oled_rotation_width + col * OLED_FONT_WIDTH;
537
538 // Out of bounds?
539 if (index >= OLED_MATRIX_SIZE) {
540 index = 0;
541 }
542
543 oled_cursor = &oled_buffer[index];
544}
545
546void oled_advance_page(bool clearPageRemainder) {
547 uint16_t index = oled_cursor - &oled_buffer[0];
548 uint8_t remaining = oled_rotation_width - (index % oled_rotation_width);
549
550 if (clearPageRemainder) {
551 // Remaining Char count
552 remaining = remaining / OLED_FONT_WIDTH;
553
554 // Write empty character until next line
555 while (remaining--)
556 oled_write_char(' ', false);
557 } else {
558 // Next page index out of bounds?
559 if (index + remaining >= OLED_MATRIX_SIZE) {
560 index = 0;
561 remaining = 0;
562 }
563
564 oled_cursor = &oled_buffer[index + remaining];
565 }
566}
567
568void oled_advance_char(void) {
569 uint16_t nextIndex = oled_cursor - &oled_buffer[0] + OLED_FONT_WIDTH;
570 uint8_t remainingSpace = oled_rotation_width - (nextIndex % oled_rotation_width);
571
572 // Do we have enough space on the current line for the next character
573 if (remainingSpace < OLED_FONT_WIDTH) {
574 nextIndex += remainingSpace;
575 }
576
577 // Did we go out of bounds
578 if (nextIndex >= OLED_MATRIX_SIZE) {
579 nextIndex = 0;
580 }
581
582 // Update cursor position
583 oled_cursor = &oled_buffer[nextIndex];
584}
585
586// Main handler that writes character data to the display buffer
587void oled_write_char(const char data, bool invert) {
588 // Advance to the next line if newline
589 if (data == '\n') {
590 // Old source wrote ' ' until end of line...
591 oled_advance_page(true);
592 return;
593 }
594
595 if (data == '\r') {
596 oled_advance_page(false);
597 return;
598 }
599
600 // copy the current render buffer to check for dirty after
601 static uint8_t oled_temp_buffer[OLED_FONT_WIDTH];
602 memcpy(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH);
603
604 _Static_assert(sizeof(font) >= ((OLED_FONT_END + 1 - OLED_FONT_START) * OLED_FONT_WIDTH), "OLED_FONT_END references outside array");
605
606 // set the reder buffer data
607 uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index
608 if (cast_data < OLED_FONT_START || cast_data > OLED_FONT_END) {
609 memset(oled_cursor, 0x00, OLED_FONT_WIDTH);
610 } else {
611 const uint8_t *glyph = &font[(cast_data - OLED_FONT_START) * OLED_FONT_WIDTH];
612 memcpy_P(oled_cursor, glyph, OLED_FONT_WIDTH);
613 }
614
615 // Invert if needed
616 if (invert) {
617 InvertCharacter(oled_cursor);
618 }
619
620 // Dirty check
621 if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) {
622 uint16_t index = oled_cursor - &oled_buffer[0];
623 oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE));
624 // Edgecase check if the written data spans the 2 chunks
625 oled_dirty |= ((OLED_BLOCK_TYPE)1 << ((index + OLED_FONT_WIDTH - 1) / OLED_BLOCK_SIZE));
626 }
627
628 // Finally move to the next char
629 oled_advance_char();
630}
631
632void oled_write(const char *data, bool invert) {
633 const char *end = data + strlen(data);
634 while (data < end) {
635 oled_write_char(*data, invert);
636 data++;
637 }
638}
639
640void oled_write_ln(const char *data, bool invert) {
641 oled_write(data, invert);
642 oled_advance_page(true);
643}
644
645void oled_pan(bool left) {
646 uint16_t i = 0;
647 for (uint16_t y = 0; y < OLED_DISPLAY_HEIGHT / 8; y++) {
648 if (left) {
649 for (uint16_t x = 0; x < OLED_DISPLAY_WIDTH - 1; x++) {
650 i = y * OLED_DISPLAY_WIDTH + x;
651 oled_buffer[i] = oled_buffer[i + 1];
652 }
653 } else {
654 for (uint16_t x = OLED_DISPLAY_WIDTH - 1; x > 0; x--) {
655 i = y * OLED_DISPLAY_WIDTH + x;
656 oled_buffer[i] = oled_buffer[i - 1];
657 }
658 }
659 }
660 oled_dirty = OLED_ALL_BLOCKS_MASK;
661}
662
663oled_buffer_reader_t oled_read_raw(uint16_t start_index) {
664 if (start_index > OLED_MATRIX_SIZE) start_index = OLED_MATRIX_SIZE;
665 oled_buffer_reader_t ret_reader;
666 ret_reader.current_element = &oled_buffer[start_index];
667 ret_reader.remaining_element_count = OLED_MATRIX_SIZE - start_index;
668 return ret_reader;
669}
670
671void oled_write_raw_byte(const char data, uint16_t index) {
672 if (index > OLED_MATRIX_SIZE) index = OLED_MATRIX_SIZE;
673 if (oled_buffer[index] == data) return;
674 oled_buffer[index] = data;
675 oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE));
676}
677
678void oled_write_raw(const char *data, uint16_t size) {
679 uint16_t cursor_start_index = oled_cursor - &oled_buffer[0];
680 if ((size + cursor_start_index) > OLED_MATRIX_SIZE) size = OLED_MATRIX_SIZE - cursor_start_index;
681 for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
682 uint8_t c = *data++;
683 if (oled_buffer[i] == c) continue;
684 oled_buffer[i] = c;
685 oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE));
686 }
687}
688
689void oled_write_pixel(uint8_t x, uint8_t y, bool on) {
690 if (x >= oled_rotation_width) {
691 return;
692 }
693 uint16_t index = x + (y / 8) * oled_rotation_width;
694 if (index >= OLED_MATRIX_SIZE) {
695 return;
696 }
697 uint8_t data = oled_buffer[index];
698 if (on) {
699 data |= (1 << (y % 8));
700 } else {
701 data &= ~(1 << (y % 8));
702 }
703 if (oled_buffer[index] != data) {
704 oled_buffer[index] = data;
705 oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE));
706 }
707}
708
709#if defined(__AVR__)
710void oled_write_P(const char *data, bool invert) {
711 uint8_t c = pgm_read_byte(data);
712 while (c != 0) {
713 oled_write_char(c, invert);
714 c = pgm_read_byte(++data);
715 }
716}
717
718void oled_write_ln_P(const char *data, bool invert) {
719 oled_write_P(data, invert);
720 oled_advance_page(true);
721}
722
723void oled_write_raw_P(const char *data, uint16_t size) {
724 uint16_t cursor_start_index = oled_cursor - &oled_buffer[0];
725 if ((size + cursor_start_index) > OLED_MATRIX_SIZE) size = OLED_MATRIX_SIZE - cursor_start_index;
726 for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
727 uint8_t c = pgm_read_byte(data++);
728 if (oled_buffer[i] == c) continue;
729 oled_buffer[i] = c;
730 oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE));
731 }
732}
733#endif // defined(__AVR__)
734
735bool oled_on(void) {
736 if (!oled_initialized) {
737 return oled_active;
738 }
739
740#if OLED_TIMEOUT > 0
741 oled_timeout = timer_read32() + OLED_TIMEOUT;
742#endif
743
744 static const uint8_t PROGMEM display_on[] =
745#ifdef OLED_FADE_OUT
746 {I2C_CMD, FADE_BLINK, 0x00};
747#else
748 {I2C_CMD, DISPLAY_ON};
749#endif
750
751 if (!oled_active) {
752 if (!oled_send_cmd_P(display_on, ARRAY_SIZE(display_on))) {
753 print("oled_on cmd failed\n");
754 return oled_active;
755 }
756 oled_active = true;
757 }
758 return oled_active;
759}
760
761bool oled_off(void) {
762 if (!oled_initialized) {
763 return !oled_active;
764 }
765
766 static const uint8_t PROGMEM display_off[] =
767#ifdef OLED_FADE_OUT
768 {I2C_CMD, FADE_BLINK, ENABLE_FADE | OLED_FADE_OUT_INTERVAL};
769#else
770 {I2C_CMD, DISPLAY_OFF};
771#endif
772
773 if (oled_active) {
774 if (!oled_send_cmd_P(display_off, ARRAY_SIZE(display_off))) {
775 print("oled_off cmd failed\n");
776 return oled_active;
777 }
778 oled_active = false;
779 }
780 return !oled_active;
781}
782
783bool is_oled_on(void) {
784 return oled_active;
785}
786
787uint8_t oled_set_brightness(uint8_t level) {
788 if (!oled_initialized) {
789 return oled_brightness;
790 }
791
792 uint8_t set_contrast[] = {I2C_CMD, CONTRAST, level};
793 if (oled_brightness != level) {
794 if (!oled_send_cmd(set_contrast, ARRAY_SIZE(set_contrast))) {
795 print("set_brightness cmd failed\n");
796 return oled_brightness;
797 }
798 oled_brightness = level;
799 }
800 return oled_brightness;
801}
802
803uint8_t oled_get_brightness(void) {
804 return oled_brightness;
805}
806
807// Set the specific 8 lines rows of the screen to scroll.
808// 0 is the default for start, and 7 for end, which is the entire
809// height of the screen. For 128x32 screens, rows 4-7 are not used.
810void oled_scroll_set_area(uint8_t start_line, uint8_t end_line) {
811 oled_scroll_start = start_line;
812 oled_scroll_end = end_line;
813}
814
815void oled_scroll_set_speed(uint8_t speed) {
816 // Sets the speed for scrolling... does not take effect
817 // until scrolling is either started or restarted
818 // the ssd1306 supports 8 speeds
819 // FrameRate2 speed = 7
820 // FrameRate3 speed = 4
821 // FrameRate4 speed = 5
822 // FrameRate5 speed = 0
823 // FrameRate25 speed = 6
824 // FrameRate64 speed = 1
825 // FrameRate128 speed = 2
826 // FrameRate256 speed = 3
827 // for ease of use these are remaped here to be in order
828 static const uint8_t scroll_remap[8] = {7, 4, 5, 0, 6, 1, 2, 3};
829 oled_scroll_speed = scroll_remap[speed];
830}
831
832bool oled_scroll_right(void) {
833 if (!oled_initialized) {
834 return oled_scrolling;
835 }
836
837 // Dont enable scrolling if we need to update the display
838 // This prevents scrolling of bad data from starting the scroll too early after init
839 if (!oled_dirty && !oled_scrolling) {
840 uint8_t display_scroll_right[] = {I2C_CMD, SCROLL_RIGHT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL};
841 if (!oled_send_cmd(display_scroll_right, ARRAY_SIZE(display_scroll_right))) {
842 print("oled_scroll_right cmd failed\n");
843 return oled_scrolling;
844 }
845 oled_scrolling = true;
846 }
847 return oled_scrolling;
848}
849
850bool oled_scroll_left(void) {
851 if (!oled_initialized) {
852 return oled_scrolling;
853 }
854
855 // Dont enable scrolling if we need to update the display
856 // This prevents scrolling of bad data from starting the scroll too early after init
857 if (!oled_dirty && !oled_scrolling) {
858 uint8_t display_scroll_left[] = {I2C_CMD, SCROLL_LEFT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL};
859 if (!oled_send_cmd(display_scroll_left, ARRAY_SIZE(display_scroll_left))) {
860 print("oled_scroll_left cmd failed\n");
861 return oled_scrolling;
862 }
863 oled_scrolling = true;
864 }
865 return oled_scrolling;
866}
867
868bool oled_scroll_off(void) {
869 if (!oled_initialized) {
870 return !oled_scrolling;
871 }
872
873 if (oled_scrolling) {
874 static const uint8_t PROGMEM display_scroll_off[] = {I2C_CMD, DEACTIVATE_SCROLL};
875 if (!oled_send_cmd_P(display_scroll_off, ARRAY_SIZE(display_scroll_off))) {
876 print("oled_scroll_off cmd failed\n");
877 return oled_scrolling;
878 }
879 oled_scrolling = false;
880 oled_dirty = OLED_ALL_BLOCKS_MASK;
881 }
882 return !oled_scrolling;
883}
884
885bool is_oled_scrolling(void) {
886 return oled_scrolling;
887}
888
889bool oled_invert(bool invert) {
890 if (!oled_initialized) {
891 return oled_inverted;
892 }
893
894 if (invert && !oled_inverted) {
895 static const uint8_t PROGMEM display_inverted[] = {I2C_CMD, INVERT_DISPLAY};
896 if (!oled_send_cmd_P(display_inverted, ARRAY_SIZE(display_inverted))) {
897 print("oled_invert cmd failed\n");
898 return oled_inverted;
899 }
900 oled_inverted = true;
901 } else if (!invert && oled_inverted) {
902 static const uint8_t PROGMEM display_normal[] = {I2C_CMD, NORMAL_DISPLAY};
903 if (!oled_send_cmd_P(display_normal, ARRAY_SIZE(display_normal))) {
904 print("oled_invert cmd failed\n");
905 return oled_inverted;
906 }
907 oled_inverted = false;
908 }
909
910 return oled_inverted;
911}
912
913uint8_t oled_max_chars(void) {
914 if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
915 return OLED_DISPLAY_WIDTH / OLED_FONT_WIDTH;
916 }
917 return OLED_DISPLAY_HEIGHT / OLED_FONT_WIDTH;
918}
919
920uint8_t oled_max_lines(void) {
921 if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
922 return OLED_DISPLAY_HEIGHT / OLED_FONT_HEIGHT;
923 }
924 return OLED_DISPLAY_WIDTH / OLED_FONT_HEIGHT;
925}
926
927void oled_task(void) {
928 if (!oled_initialized) {
929 return;
930 }
931
932#if OLED_UPDATE_INTERVAL > 0
933 if (timer_elapsed(oled_update_timeout) >= OLED_UPDATE_INTERVAL) {
934 oled_update_timeout = timer_read();
935 oled_set_cursor(0, 0);
936 oled_task_kb();
937 }
938#else
939 oled_set_cursor(0, 0);
940 oled_task_kb();
941#endif
942
943#if OLED_SCROLL_TIMEOUT > 0
944 if (oled_dirty && oled_scrolling) {
945 oled_scroll_timeout = timer_read32() + OLED_SCROLL_TIMEOUT;
946 oled_scroll_off();
947 }
948#endif
949
950 // Smart render system, no need to check for dirty
951 oled_render();
952
953 // Display timeout check
954#if OLED_TIMEOUT > 0
955 if (oled_active && timer_expired32(timer_read32(), oled_timeout)) {
956 oled_off();
957 }
958#endif
959
960#if OLED_SCROLL_TIMEOUT > 0
961 if (!oled_scrolling && timer_expired32(timer_read32(), oled_scroll_timeout)) {
962# ifdef OLED_SCROLL_TIMEOUT_RIGHT
963 oled_scroll_right();
964# else
965 oled_scroll_left();
966# endif
967 }
968#endif
969}
970
971__attribute__((weak)) bool oled_task_kb(void) {
972 return oled_task_user();
973}
974__attribute__((weak)) bool oled_task_user(void) {
975 return true;
976}