qmk_firmware

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

qp_sh1106.c (9494B)


      1 // Copyright 2023 Nick Brassel (@tzarc)
      2 // SPDX-License-Identifier: GPL-2.0-or-later
      3 
      4 #include "qp_internal.h"
      5 #include "qp_comms.h"
      6 #include "qp_oled_panel.h"
      7 #include "qp_sh1106.h"
      8 #include "qp_sh1106_opcodes.h"
      9 #include "qp_surface.h"
     10 #include "qp_surface_internal.h"
     11 
     12 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     13 // Driver storage
     14 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     15 
     16 typedef struct sh1106_device_t {
     17     oled_panel_painter_device_t oled;
     18 
     19     uint8_t framebuffer[SURFACE_REQUIRED_BUFFER_BYTE_SIZE(128, 64, 1)];
     20 } sh1106_device_t;
     21 
     22 static sh1106_device_t sh1106_drivers[SH1106_NUM_DEVICES] = {0};
     23 
     24 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     25 // Quantum Painter API implementations
     26 
     27 // Initialisation
     28 __attribute__((weak)) bool qp_sh1106_init(painter_device_t device, painter_rotation_t rotation) {
     29     sh1106_device_t *driver = (sh1106_device_t *)device;
     30 
     31     // Change the surface geometry based on the panel rotation
     32     if (rotation == QP_ROTATION_90 || rotation == QP_ROTATION_270) {
     33         driver->oled.surface.base.panel_width  = driver->oled.base.panel_height;
     34         driver->oled.surface.base.panel_height = driver->oled.base.panel_width;
     35     } else {
     36         driver->oled.surface.base.panel_width  = driver->oled.base.panel_width;
     37         driver->oled.surface.base.panel_height = driver->oled.base.panel_height;
     38     }
     39 
     40     // Init the internal surface
     41     if (!qp_init(&driver->oled.surface.base, QP_ROTATION_0)) {
     42         qp_dprintf("Failed to init internal surface in qp_sh1106_init\n");
     43         return false;
     44     }
     45 
     46     // clang-format off
     47     uint8_t sh1106_init_sequence[] = {
     48         // Command,                 Delay,  N, Data[N]
     49         SH1106_SET_MUX_RATIO,           0,  1, 0x3F,
     50         SH1106_DISPLAY_OFFSET,          0,  1, 0x00,
     51         SH1106_DISPLAY_START_LINE,      0,  0,
     52         SH1106_SET_SEGMENT_REMAP_INV,   0,  0,
     53         SH1106_COM_SCAN_DIR_DEC,        0,  0,
     54         SH1106_COM_PADS_HW_CFG,         0,  1, 0x12,
     55         SH1106_SET_CONTRAST,            0,  1, 0x7F,
     56         SH1106_ALL_ON_RESUME,           0,  0,
     57         SH1106_NON_INVERTING_DISPLAY,   0,  0,
     58         SH1106_SET_OSC_DIVFREQ,         0,  1, 0x80,
     59         SH1106_SET_CHARGE_PUMP,         0,  1, 0x14,
     60         SH1106_DISPLAY_ON,              0,  0,
     61     };
     62     // clang-format on
     63 
     64     // If the display height is anything other than the default 64 pixels, change SH1106_SET_MUX_RATIO data byte to the correct value
     65     if (driver->oled.base.panel_height != 64) {
     66         sh1106_init_sequence[3] = driver->oled.base.panel_height - 1;
     67     }
     68 
     69     // For 128x32 or 96x16 displays, change SH1106_COM_PADS_HW_CFG data byte from alternative (0x12) to sequential (0x02) configuration
     70     if (driver->oled.base.panel_height <= 32) {
     71         sh1106_init_sequence[20] = 0x02;
     72     }
     73 
     74     return qp_comms_bulk_command_sequence(device, sh1106_init_sequence, sizeof(sh1106_init_sequence));
     75 }
     76 
     77 // Screen flush
     78 bool qp_sh1106_flush(painter_device_t device) {
     79     sh1106_device_t *driver = (sh1106_device_t *)device;
     80 
     81     if (!driver->oled.surface.dirty.is_dirty) {
     82         return true;
     83     }
     84 
     85     switch (driver->oled.base.rotation) {
     86         default:
     87         case QP_ROTATION_0:
     88             qp_oled_panel_page_column_flush_rot0(device, &driver->oled.surface.dirty, driver->framebuffer);
     89             break;
     90         case QP_ROTATION_90:
     91             qp_oled_panel_page_column_flush_rot90(device, &driver->oled.surface.dirty, driver->framebuffer);
     92             break;
     93         case QP_ROTATION_180:
     94             qp_oled_panel_page_column_flush_rot180(device, &driver->oled.surface.dirty, driver->framebuffer);
     95             break;
     96         case QP_ROTATION_270:
     97             qp_oled_panel_page_column_flush_rot270(device, &driver->oled.surface.dirty, driver->framebuffer);
     98             break;
     99     }
    100 
    101     // Clear the dirty area
    102     qp_flush(&driver->oled.surface);
    103 
    104     return true;
    105 }
    106 
    107 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    108 // Driver vtable
    109 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    110 
    111 const oled_panel_painter_driver_vtable_t sh1106_driver_vtable = {
    112     .base =
    113         {
    114             .init            = qp_sh1106_init,
    115             .power           = qp_oled_panel_power,
    116             .clear           = qp_oled_panel_clear,
    117             .flush           = qp_sh1106_flush,
    118             .pixdata         = qp_oled_panel_passthru_pixdata,
    119             .viewport        = qp_oled_panel_passthru_viewport,
    120             .palette_convert = qp_oled_panel_passthru_palette_convert,
    121             .append_pixels   = qp_oled_panel_passthru_append_pixels,
    122             .append_pixdata  = qp_oled_panel_passthru_append_pixdata,
    123         },
    124     .opcodes =
    125         {
    126             .display_on     = SH1106_DISPLAY_ON,
    127             .display_off    = SH1106_DISPLAY_OFF,
    128             .set_page       = SH1106_PAGE_ADDR,
    129             .set_column_lsb = SH1106_SETCOLUMN_LSB,
    130             .set_column_msb = SH1106_SETCOLUMN_MSB,
    131         },
    132 };
    133 
    134 #ifdef QUANTUM_PAINTER_SH1106_SPI_ENABLE
    135 // Factory function for creating a handle to the SH1106 device
    136 painter_device_t qp_sh1106_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) {
    137     for (uint32_t i = 0; i < SH1106_NUM_DEVICES; ++i) {
    138         sh1106_device_t *driver = &sh1106_drivers[i];
    139         if (!driver->oled.base.driver_vtable) {
    140             painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->oled.surface, 1, panel_width, panel_height, driver->framebuffer);
    141             if (!surface) {
    142                 return NULL;
    143             }
    144 
    145             // Setup the OLED device
    146             driver->oled.base.driver_vtable         = (const painter_driver_vtable_t *)&sh1106_driver_vtable;
    147             driver->oled.base.comms_vtable          = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
    148             driver->oled.base.native_bits_per_pixel = 1; // 1bpp mono
    149             driver->oled.base.panel_width           = panel_width;
    150             driver->oled.base.panel_height          = panel_height;
    151             driver->oled.base.rotation              = QP_ROTATION_0;
    152             driver->oled.base.offset_x              = 0;
    153             driver->oled.base.offset_y              = 0;
    154 
    155             // SPI and other pin configuration
    156             driver->oled.base.comms_config                                   = &driver->oled.spi_dc_reset_config;
    157             driver->oled.spi_dc_reset_config.spi_config.chip_select_pin      = chip_select_pin;
    158             driver->oled.spi_dc_reset_config.spi_config.divisor              = spi_divisor;
    159             driver->oled.spi_dc_reset_config.spi_config.lsb_first            = false;
    160             driver->oled.spi_dc_reset_config.spi_config.mode                 = spi_mode;
    161             driver->oled.spi_dc_reset_config.dc_pin                          = dc_pin;
    162             driver->oled.spi_dc_reset_config.reset_pin                       = reset_pin;
    163             driver->oled.spi_dc_reset_config.command_params_uses_command_pin = true;
    164 
    165             if (!qp_internal_register_device((painter_device_t)driver)) {
    166                 memset(driver, 0, sizeof(sh1106_device_t));
    167                 return NULL;
    168             }
    169 
    170             return (painter_device_t)driver;
    171         }
    172     }
    173     return NULL;
    174 }
    175 
    176 #endif // QUANTUM_PAINTER_SH1106_SPI_ENABLE
    177 
    178 #ifdef QUANTUM_PAINTER_SH1106_I2C_ENABLE
    179 // Factory function for creating a handle to the SH1106 device
    180 painter_device_t qp_sh1106_make_i2c_device(uint16_t panel_width, uint16_t panel_height, uint8_t i2c_address) {
    181     for (uint32_t i = 0; i < SH1106_NUM_DEVICES; ++i) {
    182         sh1106_device_t *driver = &sh1106_drivers[i];
    183         if (!driver->oled.base.driver_vtable) {
    184             // Instantiate the surface, intentional swap of width/high due to transpose
    185             painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->oled.surface, 1, panel_width, panel_height, driver->framebuffer);
    186             if (!surface) {
    187                 return NULL;
    188             }
    189 
    190             // Setup the OLED device
    191             driver->oled.base.driver_vtable         = (const painter_driver_vtable_t *)&sh1106_driver_vtable;
    192             driver->oled.base.comms_vtable          = (const painter_comms_vtable_t *)&i2c_comms_cmddata_vtable;
    193             driver->oled.base.native_bits_per_pixel = 1; // 1bpp mono
    194             driver->oled.base.panel_width           = panel_width;
    195             driver->oled.base.panel_height          = panel_height;
    196             driver->oled.base.rotation              = QP_ROTATION_0;
    197             driver->oled.base.offset_x              = 0;
    198             driver->oled.base.offset_y              = 0;
    199 
    200             // I2C configuration
    201             driver->oled.base.comms_config       = &driver->oled.i2c_config;
    202             driver->oled.i2c_config.chip_address = i2c_address;
    203 
    204             if (!qp_internal_register_device((painter_device_t)driver)) {
    205                 memset(driver, 0, sizeof(sh1106_device_t));
    206                 return NULL;
    207             }
    208 
    209             return (painter_device_t)driver;
    210         }
    211     }
    212     return NULL;
    213 }
    214 
    215 #endif // QUANTUM_PAINTER_SH1106_I2C_ENABLE