qp_ld7032.c (18309B)
1 // Copyright 2023 Nick Brassel (@tzarc) 2 // Copyright 2023 Dasky (@daskygit) 3 // SPDX-License-Identifier: GPL-2.0-or-later 4 5 #include "qp_internal.h" 6 #include "qp_comms.h" 7 #include "qp_oled_panel.h" 8 #include "qp_ld7032.h" 9 #include "qp_ld7032_opcodes.h" 10 #include "qp_surface.h" 11 #include "qp_surface_internal.h" 12 13 typedef bool (*ld7032_driver_comms_send_command_and_data_func)(painter_device_t device, uint8_t cmd, uint8_t data); 14 typedef uint32_t (*ld7032_driver_comms_send_command_and_databuf_func)(painter_device_t device, uint8_t cmd, const void *data, uint32_t byte_count); 15 16 typedef struct ld7032_comms_with_command_vtable_t { 17 painter_comms_vtable_t base; // must be first, so this object can be cast from the painter_comms_vtable_t* type 18 painter_driver_comms_send_command_func send_command; 19 painter_driver_comms_bulk_command_sequence bulk_command_sequence; 20 ld7032_driver_comms_send_command_and_data_func send_command_data; 21 ld7032_driver_comms_send_command_and_databuf_func send_command_databuf; 22 } ld7032_comms_with_command_vtable_t; 23 24 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 // LD7032 Internal API 26 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 28 #ifdef QUANTUM_PAINTER_LD7032_I2C_ENABLE 29 bool ld7032_comms_i2c_send_command_and_data(painter_device_t device, uint8_t cmd, uint8_t data) { 30 uint8_t buf[2] = {cmd, data}; 31 return qp_comms_i2c_send_data(device, buf, 2); 32 } 33 34 bool ld7032_comms_i2c_bulk_command_sequence(painter_device_t device, const uint8_t *sequence, size_t sequence_len) { 35 uint8_t buf[32]; 36 for (size_t i = 0; i < sequence_len;) { 37 uint8_t command = sequence[i]; 38 uint8_t delay = sequence[i + 1]; 39 uint8_t num_bytes = sequence[i + 2]; 40 buf[0] = command; 41 memcpy(&buf[1], &sequence[i + 3], num_bytes); 42 if (!qp_comms_i2c_send_data(device, buf, num_bytes + 1)) { 43 return false; 44 } 45 if (delay > 0) { 46 wait_ms(delay); 47 } 48 i += (3 + num_bytes); 49 } 50 51 return true; 52 } 53 54 uint32_t ld7032_comms_i2c_send_command_and_databuf(painter_device_t device, uint8_t cmd, const void *data, uint32_t byte_count) { 55 uint8_t buf[byte_count + 1]; 56 memset(buf, 0, sizeof(buf)); 57 buf[0] = cmd; 58 memcpy(&buf[1], data, byte_count); 59 return qp_comms_send(device, buf, byte_count + 1); 60 } 61 #endif // QUANTUM_PAINTER_LD7032_I2C_ENABLE 62 63 // Power control 64 bool qp_ld7032_power(painter_device_t device, bool power_on) { 65 painter_driver_t *driver = (painter_driver_t *)device; 66 ld7032_comms_with_command_vtable_t *comms_vtable = (ld7032_comms_with_command_vtable_t *)driver->comms_vtable; 67 68 comms_vtable->send_command_data(device, LD7032_DISP_ON_OFF, power_on ? 0x01 : 0x00); 69 70 return true; 71 } 72 73 // Screen clear 74 bool qp_ld7032_clear(painter_device_t device) { 75 qp_rect(device, 0, 0, 127, 127, 0, 0, 0, true); // clear memory 76 painter_driver_t *driver = (painter_driver_t *)device; 77 driver->driver_vtable->init(device, driver->rotation); // Re-init the display 78 return true; 79 } 80 81 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 82 // Flush helpers 83 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 84 85 void ld7032_flush_0(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer, bool inverted) { 86 painter_driver_t *driver = (painter_driver_t *)device; 87 ld7032_comms_with_command_vtable_t *comms_vtable = (ld7032_comms_with_command_vtable_t *)driver->comms_vtable; 88 89 int x_start = dirty->l >> 3; 90 int x_end = dirty->r >> 3; 91 int y_start = dirty->t; 92 int y_end = dirty->b; 93 int x_length = (x_end - x_start) + 1; 94 uint8_t x_view_offset = driver->offset_x >> 3; 95 uint8_t y_view_offset = driver->offset_y; 96 97 for (int y_pos = y_start; y_pos <= y_end; y_pos++) { 98 int y_new_pos = y_pos; 99 if (inverted) { 100 y_new_pos = y_end - y_pos; 101 } 102 uint8_t packet[x_length]; 103 memcpy(packet, &framebuffer[(y_pos * (driver->panel_width >> 3)) + x_start], x_length); 104 uint8_t x_write_start = MIN(x_start + x_view_offset, (128 >> 3)); 105 uint8_t x_write_end = MIN(x_end + x_view_offset, (128 >> 3)); 106 uint8_t y_write_start = MIN(y_new_pos + y_view_offset, 39); 107 uint8_t y_write_end = MIN(y_new_pos + y_view_offset, 39); 108 109 comms_vtable->send_command_data(device, LD7032_X_BOX_ADR_START, x_write_start); 110 comms_vtable->send_command_data(device, LD7032_X_BOX_ADR_END, x_write_end); 111 comms_vtable->send_command_data(device, LD7032_Y_BOX_ADR_START, y_write_start); 112 comms_vtable->send_command_data(device, LD7032_Y_BOX_ADR_END, y_write_end); 113 comms_vtable->send_command_databuf(device, LD7032_DATA_RW, packet, x_length); 114 } 115 } 116 117 void ld7032_flush_90(painter_device_t device, surface_dirty_data_t *dirty, const uint8_t *framebuffer, bool inverted) { 118 painter_driver_t *driver = (painter_driver_t *)device; 119 ld7032_comms_with_command_vtable_t *comms_vtable = (ld7032_comms_with_command_vtable_t *)driver->comms_vtable; 120 121 int x_start = dirty->t >> 3; 122 int x_end = dirty->b >> 3; 123 int y_start = dirty->l; 124 int y_end = dirty->r; 125 int x_length = (x_end - x_start) + 1; 126 uint8_t x_view_offset = driver->offset_x >> 3; 127 uint8_t y_view_offset = driver->offset_y; 128 129 for (int y_pos = y_start; y_pos <= y_end; y_pos++) { 130 int y_new_pos = y_pos; 131 if (inverted) { 132 y_new_pos = y_end - y_pos; 133 } 134 uint8_t packet[x_length]; 135 memset(packet, 0, sizeof(packet)); 136 int count = 0; 137 for (int x_pos = x_start; x_pos <= x_end; x_pos++) { 138 for (int x = 0; x < 8; ++x) { 139 uint32_t pixel_num = (((x_pos << 3) + x) * driver->panel_height) + y_pos; 140 uint32_t byte_offset = pixel_num / 8; 141 uint8_t bit_offset = pixel_num % 8; 142 packet[count] |= ((framebuffer[byte_offset] & (1 << bit_offset)) >> bit_offset) << x; 143 } 144 count++; 145 } 146 uint8_t x_width = (driver->panel_width >> 3) - 1; 147 uint8_t x_write_start = MAX((int)x_width - x_end - x_view_offset, 0); 148 uint8_t x_write_end = MAX((int)x_width - x_start - x_view_offset, 0); 149 uint8_t y_write_start = MIN(y_new_pos + y_view_offset, 39); 150 uint8_t y_write_end = MIN(y_new_pos + y_view_offset, 39); 151 152 comms_vtable->send_command_data(device, LD7032_X_BOX_ADR_START, x_write_start); 153 comms_vtable->send_command_data(device, LD7032_X_BOX_ADR_END, x_write_end); 154 comms_vtable->send_command_data(device, LD7032_Y_BOX_ADR_START, y_write_start); 155 comms_vtable->send_command_data(device, LD7032_Y_BOX_ADR_END, y_write_end); 156 comms_vtable->send_command_databuf(device, LD7032_DATA_RW, packet, x_length); 157 } 158 } 159 160 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 161 // Driver storage 162 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 163 164 typedef struct ld7032_device_t { 165 oled_panel_painter_device_t oled; 166 167 uint8_t framebuffer[SURFACE_REQUIRED_BUFFER_BYTE_SIZE(128, 40, 1)]; 168 } ld7032_device_t; 169 170 static ld7032_device_t ld7032_drivers[LD7032_NUM_DEVICES] = {0}; 171 172 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 173 // Quantum Painter API implementations 174 175 // Initialisation 176 __attribute__((weak)) bool qp_ld7032_init(painter_device_t device, painter_rotation_t rotation) { 177 ld7032_device_t *driver = (ld7032_device_t *)device; 178 179 // Change the surface geometry based on the panel rotation 180 if (rotation == QP_ROTATION_90 || rotation == QP_ROTATION_270) { 181 driver->oled.surface.base.panel_width = driver->oled.base.panel_height; 182 driver->oled.surface.base.panel_height = driver->oled.base.panel_width; 183 } else { 184 driver->oled.surface.base.panel_width = driver->oled.base.panel_width; 185 driver->oled.surface.base.panel_height = driver->oled.base.panel_height; 186 } 187 188 // Init the internal surface 189 if (!qp_init(&driver->oled.surface.base, QP_ROTATION_0)) { 190 qp_dprintf("Failed to init internal surface in qp_ld7032_init\n"); 191 return false; 192 } 193 194 // clang-format off 195 const uint8_t ld7032_init_sequence[] = { 196 // Command, Delay, N, Data[N] 197 LD7032_DISP_STBY_ON_OFF, 0, 1, 0x00, 198 LD7032_DISP_ON_OFF, 0, 1, 0x00, 199 LD7032_DFRAME, 0, 1, 0x05, 200 //LD7032_WRITE_DIRECTION, 0, 1, 0b00001000, // 0 Right, 1 Up, 2 Vertical, 3 Bit Order, 4-7 Unused 201 LD7032_DISP_DIRECTION, 0, 1, 0x00, 202 LD7032_PEAK_WIDTH, 0, 1, 0x1F, 203 LD7032_PEAK_DELAY, 0, 1, 0x05, 204 LD7032_SCAN_MODE, 0, 1, 0x01, 205 LD7032_DOT_CURRENT, 0, 1, 0x1f, 206 LD7032_VDD_SEL, 0, 1, 0x01, 207 }; 208 // clang-format on 209 210 if (!qp_comms_bulk_command_sequence(device, ld7032_init_sequence, sizeof(ld7032_init_sequence))) { 211 return false; 212 } 213 214 uint8_t display_y_start = 40 - driver->oled.base.panel_height; 215 uint8_t display_x_start = (128 - driver->oled.base.panel_width) / 2; 216 217 // clang-format off 218 uint8_t ld7032_memory_setup[] = { 219 // Command, Delay, N, Data[N] 220 LD7032_DISP_SIZE_X, 0, 2, 0x00, 0x7F, 221 LD7032_DISP_SIZE_Y, 0, 2, 0x00, 0x27, 222 LD7032_X_DISP_START, 0, 1, 0x0, 223 LD7032_Y_DISP_START, 0, 1, 0x0, 224 }; 225 // clang-format on 226 227 ld7032_memory_setup[3] = display_x_start; 228 ld7032_memory_setup[4] = display_x_start + driver->oled.base.panel_width - 1; 229 ld7032_memory_setup[8] = display_y_start; 230 ld7032_memory_setup[9] = display_y_start + driver->oled.base.panel_height - 1; 231 ld7032_memory_setup[13] = ld7032_memory_setup[4] + 1; 232 ld7032_memory_setup[17] = driver->oled.base.panel_height; 233 234 if (!qp_comms_bulk_command_sequence(device, ld7032_memory_setup, sizeof(ld7032_memory_setup))) { 235 return false; 236 } 237 238 uint8_t write_direction = 0; 239 switch (rotation) { 240 default: 241 case QP_ROTATION_0: 242 write_direction = 0b00001000; 243 break; 244 case QP_ROTATION_90: 245 write_direction = 0b00000001; 246 break; 247 case QP_ROTATION_180: 248 write_direction = 0b00000001; 249 break; 250 case QP_ROTATION_270: 251 write_direction = 0b00001000; 252 break; 253 } 254 255 painter_driver_t *pdriver = (painter_driver_t *)device; 256 ld7032_comms_with_command_vtable_t *comms_vtable = (ld7032_comms_with_command_vtable_t *)pdriver->comms_vtable; 257 258 if (!comms_vtable->send_command_data(device, LD7032_WRITE_DIRECTION, write_direction)) { 259 return false; 260 } 261 262 qp_ld7032_power(device, true); 263 264 return true; 265 } 266 267 // Screen flush 268 bool qp_ld7032_flush(painter_device_t device) { 269 ld7032_device_t *driver = (ld7032_device_t *)device; 270 271 if (!driver->oled.surface.dirty.is_dirty) { 272 return true; 273 } 274 275 switch (driver->oled.base.rotation) { 276 default: 277 case QP_ROTATION_0: 278 ld7032_flush_0(device, &driver->oled.surface.dirty, driver->framebuffer, false); 279 break; 280 case QP_ROTATION_180: 281 ld7032_flush_0(device, &driver->oled.surface.dirty, driver->framebuffer, true); 282 break; 283 case QP_ROTATION_90: 284 ld7032_flush_90(device, &driver->oled.surface.dirty, driver->framebuffer, false); 285 break; 286 case QP_ROTATION_270: 287 ld7032_flush_90(device, &driver->oled.surface.dirty, driver->framebuffer, true); 288 break; 289 } 290 291 // Clear the dirty area 292 qp_flush(&driver->oled.surface); 293 294 return true; 295 } 296 297 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 298 // Driver vtable 299 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 300 301 const painter_driver_vtable_t ld7032_driver_vtable = { 302 .init = qp_ld7032_init, 303 .power = qp_ld7032_power, 304 .clear = qp_ld7032_clear, 305 .flush = qp_ld7032_flush, 306 .pixdata = qp_oled_panel_passthru_pixdata, 307 .viewport = qp_oled_panel_passthru_viewport, 308 .palette_convert = qp_oled_panel_passthru_palette_convert, 309 .append_pixels = qp_oled_panel_passthru_append_pixels, 310 .append_pixdata = qp_oled_panel_passthru_append_pixdata, 311 }; 312 313 #ifdef QUANTUM_PAINTER_LD7032_SPI_ENABLE 314 315 const ld7032_comms_with_command_vtable_t ld7032_spi_comms_vtable = { 316 .base = 317 { 318 .comms_init = qp_comms_spi_dc_reset_init, 319 .comms_start = qp_comms_spi_start, 320 .comms_send = qp_comms_spi_dc_reset_send_data, 321 .comms_stop = qp_comms_spi_stop, 322 }, 323 .send_command = qp_comms_spi_dc_reset_send_command, 324 .send_command_data = qp_comms_command_databyte, 325 .send_command_databuf = qp_comms_command_databuf, 326 .bulk_command_sequence = qp_comms_spi_dc_reset_bulk_command_sequence, 327 }; 328 329 // Factory function for creating a handle to the LD7032 device 330 painter_device_t qp_ld7032_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) { 331 for (uint32_t i = 0; i < LD7032_NUM_DEVICES; ++i) { 332 ld7032_device_t *driver = &ld7032_drivers[i]; 333 if (!driver->oled.base.driver_vtable) { 334 painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->oled.surface, 1, panel_width, panel_height, driver->framebuffer); 335 if (!surface) { 336 return NULL; 337 } 338 339 // Setup the OLED device 340 driver->oled.base.driver_vtable = (const painter_driver_vtable_t *)&ld7032_driver_vtable; 341 driver->oled.base.comms_vtable = (const painter_comms_vtable_t *)&ld7032_spi_comms_vtable; 342 driver->oled.base.native_bits_per_pixel = 1; // 1bpp mono 343 driver->oled.base.panel_width = panel_width; 344 driver->oled.base.panel_height = panel_height; 345 driver->oled.base.rotation = QP_ROTATION_0; 346 driver->oled.base.offset_x = 0; 347 driver->oled.base.offset_y = 0; 348 349 // SPI and other pin configuration 350 driver->oled.base.comms_config = &driver->oled.spi_dc_reset_config; 351 driver->oled.spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin; 352 driver->oled.spi_dc_reset_config.spi_config.divisor = spi_divisor; 353 driver->oled.spi_dc_reset_config.spi_config.lsb_first = false; 354 driver->oled.spi_dc_reset_config.spi_config.mode = spi_mode; 355 driver->oled.spi_dc_reset_config.dc_pin = dc_pin; 356 driver->oled.spi_dc_reset_config.reset_pin = reset_pin; 357 driver->oled.spi_dc_reset_config.command_params_uses_command_pin = true; 358 359 if (!qp_internal_register_device((painter_device_t)driver)) { 360 memset(driver, 0, sizeof(ld7032_device_t)); 361 return NULL; 362 } 363 364 return (painter_device_t)driver; 365 } 366 } 367 return NULL; 368 } 369 370 #endif // QUANTUM_PAINTER_LD7032_SPI_ENABLE 371 372 #ifdef QUANTUM_PAINTER_LD7032_I2C_ENABLE 373 374 const ld7032_comms_with_command_vtable_t ld7032_i2c_comms_vtable = { 375 .base = 376 { 377 .comms_init = qp_comms_i2c_init, 378 .comms_start = qp_comms_i2c_start, 379 .comms_send = qp_comms_i2c_send_data, 380 .comms_stop = qp_comms_i2c_stop, 381 }, 382 .send_command = NULL, 383 .send_command_data = ld7032_comms_i2c_send_command_and_data, 384 .send_command_databuf = ld7032_comms_i2c_send_command_and_databuf, 385 .bulk_command_sequence = ld7032_comms_i2c_bulk_command_sequence, 386 }; 387 388 // Factory function for creating a handle to the LD7032 device 389 painter_device_t qp_ld7032_make_i2c_device(uint16_t panel_width, uint16_t panel_height, uint8_t i2c_address) { 390 for (uint32_t i = 0; i < LD7032_NUM_DEVICES; ++i) { 391 ld7032_device_t *driver = &ld7032_drivers[i]; 392 if (!driver->oled.base.driver_vtable) { 393 painter_device_t surface = qp_make_mono1bpp_surface_advanced(&driver->oled.surface, 1, panel_width, panel_height, driver->framebuffer); 394 if (!surface) { 395 return NULL; 396 } 397 398 // Setup the OLED device 399 driver->oled.base.driver_vtable = (const painter_driver_vtable_t *)&ld7032_driver_vtable; 400 driver->oled.base.comms_vtable = (const painter_comms_vtable_t *)&ld7032_i2c_comms_vtable; 401 driver->oled.base.native_bits_per_pixel = 1; // 1bpp mono 402 driver->oled.base.panel_width = panel_width; 403 driver->oled.base.panel_height = panel_height; 404 driver->oled.base.rotation = QP_ROTATION_0; 405 driver->oled.base.offset_x = 0; 406 driver->oled.base.offset_y = 0; 407 408 // I2C configuration 409 driver->oled.base.comms_config = &driver->oled.i2c_config; 410 driver->oled.i2c_config.chip_address = i2c_address; 411 412 if (!qp_internal_register_device((painter_device_t)driver)) { 413 memset(driver, 0, sizeof(ld7032_device_t)); 414 return NULL; 415 } 416 417 return (painter_device_t)driver; 418 } 419 } 420 return NULL; 421 } 422 423 #endif // QUANTUM_PAINTER_LD7032_SPI_ENABLE