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