diff options
| author | Sergey Vlasov <sigprof@gmail.com> | 2020-10-03 21:44:19 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-04 05:44:19 +1100 |
| commit | 459ccb681f991d5b39060d25197233ac9c51d8b9 (patch) | |
| tree | 9f3bccfc5c8880ffc67a3471c05892be69830d70 | |
| parent | 1fe7743af88cf0d65800d3ccfc1c59d1c2d65db2 (diff) | |
| download | qmk_firmware-459ccb681f991d5b39060d25197233ac9c51d8b9.tar.gz qmk_firmware-459ccb681f991d5b39060d25197233ac9c51d8b9.zip | |
OLED driver fixes (#10377)
* Fix dirtying in oled_write_pixel()
Set the dirty bit for the block only if oled_write_pixel() actually
changed the buffer state. Without this check oled_write_pixel() could
not be used inside the oled_task_user() code using the “redraw always”
style, because the blocks touched by oled_write_pixel() would always
appear dirty, and oled_render() would not proceed beyond the first such
dirty block.
* Fix oled_write_pixel() with 90/270 degree rotation
Use oled_rotation_width instead of OLED_DISPLAY_WIDTH, so that a rotated
display would be handled correctly.
* Fix compilation with custom OLED_BLOCK_COUNT and OLED_BLOCK_SIZE
Some OLED sizes (e.g., 64×48) may require a nonstandard value of
OLED_BLOCK_COUNT. The documentation says that this value may be
redefined in config.h, but actually trying to redefine it caused a
compile error, because the macro was redefined in oled_driver.c.
Make the OLED_BLOCK_COUNT definition in oled_driver.c respect any
user override, and do the same for OLED_BLOCK_SIZE just in case.
* Fix handling of out-of-range bits in oled_dirty
If a custom OLED_BLOCK_COUNT value is specified, some bits in oled_dirty
may not correspond to existing blocks; however, if those bits are set
somewhere (e.g., by code with sets oled_dirty to ~0 or even -1),
oled_render() would try to handle them and could access memory beyond
oled_buffer and perform hardware operations with out of range values.
Prevent this by masking off unused bits in oled_render(), and also avoid
setting those bits in other functions.
* Fix potentially wrong dirtying in oled_write_char()
oled_write_char() tried to mark the position just beyond the written
character as dirty; use (OLED_FONT_WIDTH - 1) to dirty the last position
still belonging to the character instead.
* Fix `#define OLED_BLOCK_TYPE uint32_t` on AVR
Using uint32_t as OLED_BLOCK_TYPE did not work properly on AVR, because
some bit shifts were performed using 16-bit int. Add explicit casts to
OLED_BLOCK_TYPE to those shifts.
| -rw-r--r-- | drivers/oled/oled_driver.c | 48 |
1 files changed, 31 insertions, 17 deletions
diff --git a/drivers/oled/oled_driver.c b/drivers/oled/oled_driver.c index 0e15c3c4b..0b24a987d 100644 --- a/drivers/oled/oled_driver.c +++ b/drivers/oled/oled_driver.c | |||
| @@ -75,8 +75,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| 75 | #define CHARGE_PUMP 0x8D | 75 | #define CHARGE_PUMP 0x8D |
| 76 | 76 | ||
| 77 | // Misc defines | 77 | // Misc defines |
| 78 | #define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) | 78 | #ifndef OLED_BLOCK_COUNT |
| 79 | #define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) | 79 | # define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) |
| 80 | #endif | ||
| 81 | #ifndef OLED_BLOCK_SIZE | ||
| 82 | # define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) | ||
| 83 | #endif | ||
| 84 | |||
| 85 | #define OLED_ALL_BLOCKS_MASK (((((OLED_BLOCK_TYPE)1 << (OLED_BLOCK_COUNT - 1)) - 1) << 1) | 1) | ||
| 80 | 86 | ||
| 81 | // i2c defines | 87 | // i2c defines |
| 82 | #define I2C_CMD 0x00 | 88 | #define I2C_CMD 0x00 |
| @@ -212,7 +218,7 @@ __attribute__((weak)) oled_rotation_t oled_init_user(oled_rotation_t rotation) { | |||
| 212 | void oled_clear(void) { | 218 | void oled_clear(void) { |
| 213 | memset(oled_buffer, 0, sizeof(oled_buffer)); | 219 | memset(oled_buffer, 0, sizeof(oled_buffer)); |
| 214 | oled_cursor = &oled_buffer[0]; | 220 | oled_cursor = &oled_buffer[0]; |
| 215 | oled_dirty = -1; // -1 will be max value as long as display_dirty is unsigned type | 221 | oled_dirty = OLED_ALL_BLOCKS_MASK; |
| 216 | } | 222 | } |
| 217 | 223 | ||
| 218 | static void calc_bounds(uint8_t update_start, uint8_t *cmd_array) { | 224 | static void calc_bounds(uint8_t update_start, uint8_t *cmd_array) { |
| @@ -262,13 +268,14 @@ static void rotate_90(const uint8_t *src, uint8_t *dest) { | |||
| 262 | 268 | ||
| 263 | void oled_render(void) { | 269 | void oled_render(void) { |
| 264 | // Do we have work to do? | 270 | // Do we have work to do? |
| 271 | oled_dirty &= OLED_ALL_BLOCKS_MASK; | ||
| 265 | if (!oled_dirty || oled_scrolling) { | 272 | if (!oled_dirty || oled_scrolling) { |
| 266 | return; | 273 | return; |
| 267 | } | 274 | } |
| 268 | 275 | ||
| 269 | // Find first dirty block | 276 | // Find first dirty block |
| 270 | uint8_t update_start = 0; | 277 | uint8_t update_start = 0; |
| 271 | while (!(oled_dirty & (1 << update_start))) { | 278 | while (!(oled_dirty & ((OLED_BLOCK_TYPE)1 << update_start))) { |
| 272 | ++update_start; | 279 | ++update_start; |
| 273 | } | 280 | } |
| 274 | 281 | ||
| @@ -314,7 +321,7 @@ void oled_render(void) { | |||
| 314 | oled_on(); | 321 | oled_on(); |
| 315 | 322 | ||
| 316 | // Clear dirty flag | 323 | // Clear dirty flag |
| 317 | oled_dirty &= ~(1 << update_start); | 324 | oled_dirty &= ~((OLED_BLOCK_TYPE)1 << update_start); |
| 318 | } | 325 | } |
| 319 | 326 | ||
| 320 | void oled_set_cursor(uint8_t col, uint8_t line) { | 327 | void oled_set_cursor(uint8_t col, uint8_t line) { |
| @@ -404,9 +411,9 @@ void oled_write_char(const char data, bool invert) { | |||
| 404 | // Dirty check | 411 | // Dirty check |
| 405 | if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) { | 412 | if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) { |
| 406 | uint16_t index = oled_cursor - &oled_buffer[0]; | 413 | uint16_t index = oled_cursor - &oled_buffer[0]; |
| 407 | oled_dirty |= (1 << (index / OLED_BLOCK_SIZE)); | 414 | oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); |
| 408 | // Edgecase check if the written data spans the 2 chunks | 415 | // Edgecase check if the written data spans the 2 chunks |
| 409 | oled_dirty |= (1 << ((index + OLED_FONT_WIDTH) / OLED_BLOCK_SIZE)); | 416 | oled_dirty |= ((OLED_BLOCK_TYPE)1 << ((index + OLED_FONT_WIDTH - 1) / OLED_BLOCK_SIZE)); |
| 410 | } | 417 | } |
| 411 | 418 | ||
| 412 | // Finally move to the next char | 419 | // Finally move to the next char |
| @@ -441,7 +448,7 @@ void oled_pan(bool left) { | |||
| 441 | } | 448 | } |
| 442 | } | 449 | } |
| 443 | } | 450 | } |
| 444 | oled_dirty = ~((OLED_BLOCK_TYPE)0); | 451 | oled_dirty = OLED_ALL_BLOCKS_MASK; |
| 445 | } | 452 | } |
| 446 | 453 | ||
| 447 | oled_buffer_reader_t oled_read_raw(uint16_t start_index) { | 454 | oled_buffer_reader_t oled_read_raw(uint16_t start_index) { |
| @@ -456,7 +463,7 @@ void oled_write_raw_byte(const char data, uint16_t index) { | |||
| 456 | if (index > OLED_MATRIX_SIZE) index = OLED_MATRIX_SIZE; | 463 | if (index > OLED_MATRIX_SIZE) index = OLED_MATRIX_SIZE; |
| 457 | if (oled_buffer[index] == data) return; | 464 | if (oled_buffer[index] == data) return; |
| 458 | oled_buffer[index] = data; | 465 | oled_buffer[index] = data; |
| 459 | oled_dirty |= (1 << (index / OLED_BLOCK_SIZE)); | 466 | oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); |
| 460 | } | 467 | } |
| 461 | 468 | ||
| 462 | void oled_write_raw(const char *data, uint16_t size) { | 469 | void oled_write_raw(const char *data, uint16_t size) { |
| @@ -464,21 +471,28 @@ void oled_write_raw(const char *data, uint16_t size) { | |||
| 464 | for (uint16_t i = 0; i < size; i++) { | 471 | for (uint16_t i = 0; i < size; i++) { |
| 465 | if (oled_buffer[i] == data[i]) continue; | 472 | if (oled_buffer[i] == data[i]) continue; |
| 466 | oled_buffer[i] = data[i]; | 473 | oled_buffer[i] = data[i]; |
| 467 | oled_dirty |= (1 << (i / OLED_BLOCK_SIZE)); | 474 | oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE)); |
| 468 | } | 475 | } |
| 469 | } | 476 | } |
| 470 | 477 | ||
| 471 | void oled_write_pixel(uint8_t x, uint8_t y, bool on) { | 478 | void oled_write_pixel(uint8_t x, uint8_t y, bool on) { |
| 472 | if (x >= OLED_DISPLAY_WIDTH || y >= OLED_DISPLAY_HEIGHT) { | 479 | if (x >= oled_rotation_width) { |
| 473 | return; | 480 | return; |
| 474 | } | 481 | } |
| 475 | uint16_t index = x + (y / 8) * OLED_DISPLAY_WIDTH; | 482 | uint16_t index = x + (y / 8) * oled_rotation_width; |
| 483 | if (index >= OLED_MATRIX_SIZE) { | ||
| 484 | return; | ||
| 485 | } | ||
| 486 | uint8_t data = oled_buffer[index]; | ||
| 476 | if (on) { | 487 | if (on) { |
| 477 | oled_buffer[index] |= (1 << (y % 8)); | 488 | data |= (1 << (y % 8)); |
| 478 | } else { | 489 | } else { |
| 479 | oled_buffer[index] &= ~(1 << (y % 8)); | 490 | data &= ~(1 << (y % 8)); |
| 491 | } | ||
| 492 | if (oled_buffer[index] != data) { | ||
| 493 | oled_buffer[index] = data; | ||
| 494 | oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); | ||
| 480 | } | 495 | } |
| 481 | oled_dirty |= (1 << (index / OLED_BLOCK_SIZE)); | ||
| 482 | } | 496 | } |
| 483 | 497 | ||
| 484 | #if defined(__AVR__) | 498 | #if defined(__AVR__) |
| @@ -501,7 +515,7 @@ void oled_write_raw_P(const char *data, uint16_t size) { | |||
| 501 | uint8_t c = pgm_read_byte(data++); | 515 | uint8_t c = pgm_read_byte(data++); |
| 502 | if (oled_buffer[i] == c) continue; | 516 | if (oled_buffer[i] == c) continue; |
| 503 | oled_buffer[i] = c; | 517 | oled_buffer[i] = c; |
| 504 | oled_dirty |= (1 << (i / OLED_BLOCK_SIZE)); | 518 | oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE)); |
| 505 | } | 519 | } |
| 506 | } | 520 | } |
| 507 | #endif // defined(__AVR__) | 521 | #endif // defined(__AVR__) |
| @@ -597,7 +611,7 @@ bool oled_scroll_off(void) { | |||
| 597 | return oled_scrolling; | 611 | return oled_scrolling; |
| 598 | } | 612 | } |
| 599 | oled_scrolling = false; | 613 | oled_scrolling = false; |
| 600 | oled_dirty = -1; | 614 | oled_dirty = OLED_ALL_BLOCKS_MASK; |
| 601 | } | 615 | } |
| 602 | return !oled_scrolling; | 616 | return !oled_scrolling; |
| 603 | } | 617 | } |
