Parent directory

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}