qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

oled_driver.c (31565B)


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