From f1894e4bac7d61d3074484d87f872edc81c6d475 Mon Sep 17 00:00:00 2001 From: Huckies <46043032+Huckies@users.noreply.github.com> Date: Wed, 1 Mar 2023 09:44:40 +0800 Subject: Improve robustness of AW20216 driver (#19849) * added soft reset and auto lowpower for AW20216 --- drivers/led/aw20216.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'drivers') diff --git a/drivers/led/aw20216.c b/drivers/led/aw20216.c index cbb0b60774..7895f1497b 100644 --- a/drivers/led/aw20216.c +++ b/drivers/led/aw20216.c @@ -1,4 +1,5 @@ /* Copyright 2021 Jasper Chan + * 2023 Huckies * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,6 +16,7 @@ */ #include "aw20216.h" +#include "wait.h" #include "spi_master.h" /* The AW20216 appears to be somewhat similar to the IS31FL743, although quite @@ -34,6 +36,8 @@ #define AW_REG_CONFIGURATION 0x00 // PG0 #define AW_REG_GLOBALCURRENT 0x01 // PG0 +#define AW_REG_RESET 0x2F // PG0 +#define AW_REG_MIXFUNCTION 0x46 // PG0 // Default value of AW_REG_CONFIGURATION // D7:D4 = 1011, SWSEL (SW1~SW12 active) @@ -41,7 +45,10 @@ // D2:D1 = 00, OSDE (open/short detection enable) // D0 = 0, CHIPEN (write 1 to enable LEDs when hardware enable pulled high) #define AW_CONFIG_DEFAULT 0b10110000 +#define AW_MIXCR_DEFAULT 0b00000000 +#define AW_RESET_CMD 0xAE #define AW_CHIPEN 1 +#define AW_LPEN (0x01 << 1) #define AW_PWM_REGISTER_COUNT 216 @@ -94,6 +101,10 @@ static inline bool AW20216_write_register(pin_t cs_pin, uint8_t page, uint8_t re return AW20216_write(cs_pin, page, reg, &value, 1); } +void AW20216_soft_reset(pin_t cs_pin) { + AW20216_write_register(cs_pin, AW_PAGE_FUNCTION, AW_REG_RESET, AW_RESET_CMD); +} + static void AW20216_init_scaling(pin_t cs_pin) { // Set constant current to the max, control brightness with PWM for (uint8_t i = 0; i < AW_PWM_REGISTER_COUNT; i++) { @@ -111,15 +122,23 @@ static inline void AW20216_soft_enable(pin_t cs_pin) { AW20216_write_register(cs_pin, AW_PAGE_FUNCTION, AW_REG_CONFIGURATION, AW_CONFIG_DEFAULT | AW_CHIPEN); } +static inline void AW20216_auto_lowpower(pin_t cs_pin) { + AW20216_write_register(cs_pin, AW_PAGE_FUNCTION, AW_REG_MIXFUNCTION, AW_MIXCR_DEFAULT | AW_LPEN); +} + void AW20216_init(pin_t cs_pin, pin_t en_pin) { setPinOutput(en_pin); writePinHigh(en_pin); + AW20216_soft_reset(cs_pin); + wait_ms(2); + // Drivers should start with all scaling and PWM registers as off AW20216_init_current_limit(cs_pin); AW20216_init_scaling(cs_pin); AW20216_soft_enable(cs_pin); + AW20216_auto_lowpower(cs_pin); } void AW20216_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { -- cgit v1.2.3 From 65a80f411f03be466d0b74c5b1d44f161e19ac7e Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 20 Mar 2023 08:12:19 +1100 Subject: Clean up APA102 config and add DD mapping (#20159) --- drivers/led/apa102.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/led/apa102.c b/drivers/led/apa102.c index f291948975..40fc68e4f1 100644 --- a/drivers/led/apa102.c +++ b/drivers/led/apa102.c @@ -27,7 +27,7 @@ # if defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(GD32VF103) # define APA102_NOPS (100 / (1000000000L / (CPU_CLOCK / 4))) // This calculates how many loops of 4 nops to run to delay 100 ns # else -# error("APA102_NOPS configuration required") +# error APA102_NOPS configuration required # define APA102_NOPS 0 // this just pleases the compile so the above error is easier to spot # endif # endif @@ -43,14 +43,14 @@ } \ } while (0) -#define APA102_SEND_BIT(byte, bit) \ - do { \ - writePin(RGB_DI_PIN, (byte >> bit) & 1); \ - io_wait; \ - writePinHigh(RGB_CI_PIN); \ - io_wait; \ - writePinLow(RGB_CI_PIN); \ - io_wait; \ +#define APA102_SEND_BIT(byte, bit) \ + do { \ + writePin(APA102_DI_PIN, (byte >> bit) & 1); \ + io_wait; \ + writePinHigh(APA102_CI_PIN); \ + io_wait; \ + writePinLow(APA102_CI_PIN); \ + io_wait; \ } while (0) uint8_t apa102_led_brightness = APA102_DEFAULT_BRIGHTNESS; @@ -77,11 +77,11 @@ void rgblight_call_driver(LED_TYPE *start_led, uint8_t num_leds) { } void static apa102_init(void) { - setPinOutput(RGB_DI_PIN); - setPinOutput(RGB_CI_PIN); + setPinOutput(APA102_DI_PIN); + setPinOutput(APA102_CI_PIN); - writePinLow(RGB_DI_PIN); - writePinLow(RGB_CI_PIN); + writePinLow(APA102_DI_PIN); + writePinLow(APA102_CI_PIN); } void apa102_set_brightness(uint8_t brightness) { -- cgit v1.2.3 From cd94ba031c832266cca609951abc97eb52c90567 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 20 Mar 2023 14:13:53 +1100 Subject: Quantum Painter QoL enhancements -- auto-poweroff, auto-flush, buffer sizing (#20013) --- drivers/painter/gc9a01/qp_gc9a01.c | 7 +++++++ drivers/painter/ili9xxx/qp_ili9163.c | 8 +++++++- drivers/painter/ili9xxx/qp_ili9341.c | 8 +++++++- drivers/painter/ili9xxx/qp_ili9488.c | 8 +++++++- drivers/painter/ssd1351/qp_ssd1351.c | 8 +++++++- drivers/painter/st77xx/qp_st7735.c | 8 +++++++- drivers/painter/st77xx/qp_st7789.c | 8 +++++++- 7 files changed, 49 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/painter/gc9a01/qp_gc9a01.c b/drivers/painter/gc9a01/qp_gc9a01.c index 5d079435c6..56140fa50e 100644 --- a/drivers/painter/gc9a01/qp_gc9a01.c +++ b/drivers/painter/gc9a01/qp_gc9a01.c @@ -1,4 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) +// Copyright 2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -141,6 +142,12 @@ painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_ driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } diff --git a/drivers/painter/ili9xxx/qp_ili9163.c b/drivers/painter/ili9xxx/qp_ili9163.c index af37686631..917650953d 100644 --- a/drivers/painter/ili9xxx/qp_ili9163.c +++ b/drivers/painter/ili9xxx/qp_ili9163.c @@ -1,4 +1,4 @@ -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #include "qp_internal.h" @@ -110,6 +110,12 @@ painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } diff --git a/drivers/painter/ili9xxx/qp_ili9341.c b/drivers/painter/ili9xxx/qp_ili9341.c index aca3809912..ed6766666c 100644 --- a/drivers/painter/ili9xxx/qp_ili9341.c +++ b/drivers/painter/ili9xxx/qp_ili9341.c @@ -1,4 +1,4 @@ -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #include "qp_internal.h" @@ -117,6 +117,12 @@ painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } diff --git a/drivers/painter/ili9xxx/qp_ili9488.c b/drivers/painter/ili9xxx/qp_ili9488.c index e51f0e1d51..3740666583 100644 --- a/drivers/painter/ili9xxx/qp_ili9488.c +++ b/drivers/painter/ili9xxx/qp_ili9488.c @@ -1,4 +1,4 @@ -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #include "qp_internal.h" @@ -110,6 +110,12 @@ painter_device_t qp_ili9488_make_spi_device(uint16_t panel_width, uint16_t panel driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } diff --git a/drivers/painter/ssd1351/qp_ssd1351.c b/drivers/painter/ssd1351/qp_ssd1351.c index 548785a1bd..7ee249752b 100644 --- a/drivers/painter/ssd1351/qp_ssd1351.c +++ b/drivers/painter/ssd1351/qp_ssd1351.c @@ -1,4 +1,4 @@ -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #include "qp_internal.h" @@ -114,6 +114,12 @@ painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } diff --git a/drivers/painter/st77xx/qp_st7735.c b/drivers/painter/st77xx/qp_st7735.c index 7ee5a6b562..1fd8f42186 100644 --- a/drivers/painter/st77xx/qp_st7735.c +++ b/drivers/painter/st77xx/qp_st7735.c @@ -1,5 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // Copyright 2022 David Hoelscher (@customMK) // SPDX-License-Identifier: GPL-2.0-or-later @@ -134,6 +134,12 @@ painter_device_t qp_st7735_make_spi_device(uint16_t panel_width, uint16_t panel_ driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } diff --git a/drivers/painter/st77xx/qp_st7789.c b/drivers/painter/st77xx/qp_st7789.c index 9f474369d6..df43437733 100644 --- a/drivers/painter/st77xx/qp_st7789.c +++ b/drivers/painter/st77xx/qp_st7789.c @@ -1,5 +1,5 @@ // Copyright 2021 Paul Cotter (@gr1mr3aver) -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #include "qp_internal.h" @@ -133,6 +133,12 @@ painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_ driver->spi_dc_reset_config.spi_config.mode = spi_mode; driver->spi_dc_reset_config.dc_pin = dc_pin; driver->spi_dc_reset_config.reset_pin = reset_pin; + + if (!qp_internal_register_device((painter_device_t)driver)) { + memset(driver, 0, sizeof(tft_panel_dc_reset_painter_device_t)); + return NULL; + } + return (painter_device_t)driver; } } -- cgit v1.2.3 From 9c3c159286d20b05c3526ddb96661b10f706f222 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 22 Mar 2023 14:32:49 +1100 Subject: Update Doxygen comments for some headers (#20194) --- drivers/lcd/hd44780.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/lcd/hd44780.h b/drivers/lcd/hd44780.h index 9e43339344..402217a547 100644 --- a/drivers/lcd/hd44780.h +++ b/drivers/lcd/hd44780.h @@ -21,9 +21,9 @@ along with this program. If not, see . #include /** - * \defgroup hd44780 + * \file * - * HD44780 Character LCD Driver + * \defgroup hd44780 HD44780 Character LCD Driver * \{ */ -- cgit v1.2.3 From cd542a0f6767231d73386c0913a9d2ee062ccf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez?= <58857054+elpekenin@users.noreply.github.com> Date: Sat, 25 Mar 2023 18:56:04 +0100 Subject: [Cleanup] Quantum Painter (#19825) Co-authored-by: Nick Brassel --- drivers/painter/comms/qp_comms_spi.c | 32 +++++++++++++++-------------- drivers/painter/comms/qp_comms_spi.h | 18 ++++++++-------- drivers/painter/gc9a01/qp_gc9a01.c | 6 +++--- drivers/painter/generic/qp_rgb565_surface.c | 18 ++++++++-------- drivers/painter/ili9xxx/qp_ili9163.c | 6 +++--- drivers/painter/ili9xxx/qp_ili9341.c | 6 +++--- drivers/painter/ili9xxx/qp_ili9488.c | 6 +++--- drivers/painter/ssd1351/qp_ssd1351.c | 6 +++--- drivers/painter/st77xx/qp_st7735.c | 8 ++++---- drivers/painter/st77xx/qp_st7789.c | 8 ++++---- drivers/painter/tft_panel/qp_tft_panel.c | 12 +++++------ drivers/painter/tft_panel/qp_tft_panel.h | 10 ++++----- 12 files changed, 69 insertions(+), 67 deletions(-) (limited to 'drivers') diff --git a/drivers/painter/comms/qp_comms_spi.c b/drivers/painter/comms/qp_comms_spi.c index e644ba9f84..7534e844d8 100644 --- a/drivers/painter/comms/qp_comms_spi.c +++ b/drivers/painter/comms/qp_comms_spi.c @@ -10,8 +10,8 @@ // Base SPI support bool qp_comms_spi_init(painter_device_t device) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config; + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_config_t *comms_config = (qp_comms_spi_config_t *)driver->comms_config; // Initialize the SPI peripheral spi_init(); @@ -24,8 +24,8 @@ bool qp_comms_spi_init(painter_device_t device) { } bool qp_comms_spi_start(painter_device_t device) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config; + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_config_t *comms_config = (qp_comms_spi_config_t *)driver->comms_config; return spi_start(comms_config->chip_select_pin, comms_config->lsb_first, comms_config->mode, comms_config->divisor); } @@ -33,8 +33,10 @@ bool qp_comms_spi_start(painter_device_t device) { uint32_t qp_comms_spi_send_data(painter_device_t device, const void *data, uint32_t byte_count) { uint32_t bytes_remaining = byte_count; const uint8_t *p = (const uint8_t *)data; + const uint32_t max_msg_length = 1024; + while (bytes_remaining > 0) { - uint32_t bytes_this_loop = bytes_remaining < 1024 ? bytes_remaining : 1024; + uint32_t bytes_this_loop = QP_MIN(bytes_remaining, max_msg_length); spi_transmit(p, bytes_this_loop); p += bytes_this_loop; bytes_remaining -= bytes_this_loop; @@ -44,13 +46,13 @@ uint32_t qp_comms_spi_send_data(painter_device_t device, const void *data, uint3 } void qp_comms_spi_stop(painter_device_t device) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config; + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_config_t *comms_config = (qp_comms_spi_config_t *)driver->comms_config; spi_stop(); writePinHigh(comms_config->chip_select_pin); } -const struct painter_comms_vtable_t spi_comms_vtable = { +const painter_comms_vtable_t spi_comms_vtable = { .comms_init = qp_comms_spi_init, .comms_start = qp_comms_spi_start, .comms_send = qp_comms_spi_send_data, @@ -67,8 +69,8 @@ bool qp_comms_spi_dc_reset_init(painter_device_t device) { return false; } - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config; + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config; // Set up D/C as output low, if specified if (comms_config->dc_pin != NO_PIN) { @@ -89,15 +91,15 @@ bool qp_comms_spi_dc_reset_init(painter_device_t device) { } uint32_t qp_comms_spi_dc_reset_send_data(painter_device_t device, const void *data, uint32_t byte_count) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config; + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config; writePinHigh(comms_config->dc_pin); return qp_comms_spi_send_data(device, data, byte_count); } void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config; + painter_driver_t * driver = (painter_driver_t *)device; + qp_comms_spi_dc_reset_config_t *comms_config = (qp_comms_spi_dc_reset_config_t *)driver->comms_config; writePinLow(comms_config->dc_pin); spi_write(cmd); } @@ -118,7 +120,7 @@ void qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const } } -const struct painter_comms_with_command_vtable_t spi_comms_with_dc_vtable = { +const painter_comms_with_command_vtable_t spi_comms_with_dc_vtable = { .base = { .comms_init = qp_comms_spi_dc_reset_init, diff --git a/drivers/painter/comms/qp_comms_spi.h b/drivers/painter/comms/qp_comms_spi.h index 9989987327..b3da86d573 100644 --- a/drivers/painter/comms/qp_comms_spi.h +++ b/drivers/painter/comms/qp_comms_spi.h @@ -13,36 +13,36 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Base SPI support -struct qp_comms_spi_config_t { +typedef struct qp_comms_spi_config_t { pin_t chip_select_pin; uint16_t divisor; bool lsb_first; int8_t mode; -}; +} qp_comms_spi_config_t; bool qp_comms_spi_init(painter_device_t device); bool qp_comms_spi_start(painter_device_t device); uint32_t qp_comms_spi_send_data(painter_device_t device, const void* data, uint32_t byte_count); void qp_comms_spi_stop(painter_device_t device); -extern const struct painter_comms_vtable_t spi_comms_vtable; +extern const painter_comms_vtable_t spi_comms_vtable; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SPI with D/C and RST pins # ifdef QUANTUM_PAINTER_SPI_DC_RESET_ENABLE -struct qp_comms_spi_dc_reset_config_t { - struct qp_comms_spi_config_t spi_config; - pin_t dc_pin; - pin_t reset_pin; -}; +typedef struct qp_comms_spi_dc_reset_config_t { + qp_comms_spi_config_t spi_config; + pin_t dc_pin; + pin_t reset_pin; +} qp_comms_spi_dc_reset_config_t; void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd); uint32_t qp_comms_spi_dc_reset_send_data(painter_device_t device, const void* data, uint32_t byte_count); void qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const uint8_t* sequence, size_t sequence_len); -extern const struct painter_comms_with_command_vtable_t spi_comms_with_dc_vtable; +extern const painter_comms_with_command_vtable_t spi_comms_with_dc_vtable; # endif // QUANTUM_PAINTER_SPI_DC_RESET_ENABLE diff --git a/drivers/painter/gc9a01/qp_gc9a01.c b/drivers/painter/gc9a01/qp_gc9a01.c index 56140fa50e..a2eb2cf57c 100644 --- a/drivers/painter/gc9a01/qp_gc9a01.c +++ b/drivers/painter/gc9a01/qp_gc9a01.c @@ -94,7 +94,7 @@ __attribute__((weak)) bool qp_gc9a01_init(painter_device_t device, painter_rotat // Driver vtable //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -const struct tft_panel_dc_reset_painter_driver_vtable_t gc9a01_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t gc9a01_driver_vtable = { .base = { .init = qp_gc9a01_init, @@ -125,8 +125,8 @@ painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_ for (uint32_t i = 0; i < GC9A01_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &gc9a01_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&gc9a01_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&gc9a01_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.native_bits_per_pixel = 16; // RGB565 driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; diff --git a/drivers/painter/generic/qp_rgb565_surface.c b/drivers/painter/generic/qp_rgb565_surface.c index 474c86feec..9c283e0687 100644 --- a/drivers/painter/generic/qp_rgb565_surface.c +++ b/drivers/painter/generic/qp_rgb565_surface.c @@ -9,7 +9,7 @@ // Device definition typedef struct rgb565_surface_painter_device_t { - struct painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type + painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type // The target buffer uint16_t *buffer; @@ -95,7 +95,7 @@ static inline void stream_pixdata(rgb565_surface_painter_device_t *surface, cons // Driver vtable static bool qp_rgb565_surface_init(painter_device_t device, painter_rotation_t rotation) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; + painter_driver_t * driver = (painter_driver_t *)device; rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; memset(surface->buffer, 0, driver->panel_width * driver->panel_height * driver->native_bits_per_pixel / 8); return true; @@ -107,13 +107,13 @@ static bool qp_rgb565_surface_power(painter_device_t device, bool power_on) { } static bool qp_rgb565_surface_clear(painter_device_t device) { - struct painter_driver_t *driver = (struct painter_driver_t *)device; + painter_driver_t *driver = (painter_driver_t *)device; driver->driver_vtable->init(device, driver->rotation); // Re-init the surface return true; } static bool qp_rgb565_surface_flush(painter_device_t device) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; + painter_driver_t * driver = (painter_driver_t *)device; rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; surface->dirty_l = surface->dirty_t = UINT16_MAX; surface->dirty_r = surface->dirty_b = 0; @@ -122,7 +122,7 @@ static bool qp_rgb565_surface_flush(painter_device_t device) { } static bool qp_rgb565_surface_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; + painter_driver_t * driver = (painter_driver_t *)device; rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; // Set the viewport locations @@ -139,7 +139,7 @@ static bool qp_rgb565_surface_viewport(painter_device_t device, uint16_t left, u // Stream pixel data to the current write position in GRAM static bool qp_rgb565_surface_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; + painter_driver_t * driver = (painter_driver_t *)device; rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver; stream_pixdata(surface, (const uint16_t *)pixel_data, native_pixel_count); return true; @@ -170,7 +170,7 @@ static bool qp_rgb565_surface_append_pixdata(painter_device_t device, uint8_t *t return true; } -const struct painter_driver_vtable_t rgb565_surface_driver_vtable = { +const painter_driver_vtable_t rgb565_surface_driver_vtable = { .init = qp_rgb565_surface_init, .power = qp_rgb565_surface_power, .clear = qp_rgb565_surface_clear, @@ -201,7 +201,7 @@ uint32_t qp_rgb565_surface_comms_send(painter_device_t device, const void *data, return byte_count; } -struct painter_comms_vtable_t rgb565_surface_driver_comms_vtable = { +painter_comms_vtable_t rgb565_surface_driver_comms_vtable = { // These are all effective no-op's because they're not actually needed. .comms_init = qp_rgb565_surface_comms_init, .comms_start = qp_rgb565_surface_comms_start, @@ -234,7 +234,7 @@ painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_hei // Drawing routine to copy out the dirty region and send it to another device bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y) { - struct painter_driver_t * surface_driver = (struct painter_driver_t *)surface; + painter_driver_t * surface_driver = (painter_driver_t *)surface; rgb565_surface_painter_device_t *surface_handle = (rgb565_surface_painter_device_t *)surface_driver; // If we're not dirty... we're done. diff --git a/drivers/painter/ili9xxx/qp_ili9163.c b/drivers/painter/ili9xxx/qp_ili9163.c index 917650953d..a75be57904 100644 --- a/drivers/painter/ili9xxx/qp_ili9163.c +++ b/drivers/painter/ili9xxx/qp_ili9163.c @@ -58,7 +58,7 @@ __attribute__((weak)) bool qp_ili9163_init(painter_device_t device, painter_rota //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Driver vtable -const struct tft_panel_dc_reset_painter_driver_vtable_t ili9163_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t ili9163_driver_vtable = { .base = { .init = qp_ili9163_init, @@ -93,8 +93,8 @@ painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel for (uint32_t i = 0; i < ILI9163_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &ili9163_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ili9163_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&ili9163_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; driver->base.rotation = QP_ROTATION_0; diff --git a/drivers/painter/ili9xxx/qp_ili9341.c b/drivers/painter/ili9xxx/qp_ili9341.c index ed6766666c..4130271f71 100644 --- a/drivers/painter/ili9xxx/qp_ili9341.c +++ b/drivers/painter/ili9xxx/qp_ili9341.c @@ -65,7 +65,7 @@ __attribute__((weak)) bool qp_ili9341_init(painter_device_t device, painter_rota //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Driver vtable -const struct tft_panel_dc_reset_painter_driver_vtable_t ili9341_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t ili9341_driver_vtable = { .base = { .init = qp_ili9341_init, @@ -100,8 +100,8 @@ painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel for (uint32_t i = 0; i < ILI9341_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &ili9341_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ili9341_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&ili9341_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.native_bits_per_pixel = 16; // RGB565 driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; diff --git a/drivers/painter/ili9xxx/qp_ili9488.c b/drivers/painter/ili9xxx/qp_ili9488.c index 3740666583..a8da52132e 100644 --- a/drivers/painter/ili9xxx/qp_ili9488.c +++ b/drivers/painter/ili9xxx/qp_ili9488.c @@ -58,7 +58,7 @@ __attribute__((weak)) bool qp_ili9488_init(painter_device_t device, painter_rota //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Driver vtable -const struct tft_panel_dc_reset_painter_driver_vtable_t ili9488_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t ili9488_driver_vtable = { .base = { .init = qp_ili9488_init, @@ -93,8 +93,8 @@ painter_device_t qp_ili9488_make_spi_device(uint16_t panel_width, uint16_t panel for (uint32_t i = 0; i < ILI9488_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &ili9488_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ili9488_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&ili9488_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.native_bits_per_pixel = 24; // RGB888 driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; diff --git a/drivers/painter/ssd1351/qp_ssd1351.c b/drivers/painter/ssd1351/qp_ssd1351.c index 7ee249752b..434b7f0327 100644 --- a/drivers/painter/ssd1351/qp_ssd1351.c +++ b/drivers/painter/ssd1351/qp_ssd1351.c @@ -62,7 +62,7 @@ __attribute__((weak)) bool qp_ssd1351_init(painter_device_t device, painter_rota //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Driver vtable -const struct tft_panel_dc_reset_painter_driver_vtable_t ssd1351_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t ssd1351_driver_vtable = { .base = { .init = qp_ssd1351_init, @@ -97,8 +97,8 @@ painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel for (uint32_t i = 0; i < SSD1351_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &ssd1351_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ssd1351_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&ssd1351_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; driver->base.rotation = QP_ROTATION_0; diff --git a/drivers/painter/st77xx/qp_st7735.c b/drivers/painter/st77xx/qp_st7735.c index 1fd8f42186..98baf400ab 100644 --- a/drivers/painter/st77xx/qp_st7735.c +++ b/drivers/painter/st77xx/qp_st7735.c @@ -25,7 +25,7 @@ tft_panel_dc_reset_painter_device_t st7735_drivers[ST7735_NUM_DEVICES] = {0}; #ifndef ST7735_NO_AUTOMATIC_OFFSETS static inline void st7735_automatic_viewport_offsets(painter_device_t device, painter_rotation_t rotation) { - struct painter_driver_t *driver = (struct painter_driver_t *)device; + painter_driver_t *driver = (painter_driver_t *)device; // clang-format off const struct { @@ -82,7 +82,7 @@ __attribute__((weak)) bool qp_st7735_init(painter_device_t device, painter_rotat //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Driver vtable -const struct tft_panel_dc_reset_painter_driver_vtable_t st7735_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t st7735_driver_vtable = { .base = { .init = qp_st7735_init, @@ -117,8 +117,8 @@ painter_device_t qp_st7735_make_spi_device(uint16_t panel_width, uint16_t panel_ for (uint32_t i = 0; i < ST7735_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &st7735_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&st7735_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&st7735_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; driver->base.rotation = QP_ROTATION_0; diff --git a/drivers/painter/st77xx/qp_st7789.c b/drivers/painter/st77xx/qp_st7789.c index df43437733..f9065f5178 100644 --- a/drivers/painter/st77xx/qp_st7789.c +++ b/drivers/painter/st77xx/qp_st7789.c @@ -24,7 +24,7 @@ tft_panel_dc_reset_painter_device_t st7789_drivers[ST7789_NUM_DEVICES] = {0}; #ifndef ST7789_NO_AUTOMATIC_OFFSETS static inline void st7789_automatic_viewport_offsets(painter_device_t device, painter_rotation_t rotation) { - struct painter_driver_t *driver = (struct painter_driver_t *)device; + painter_driver_t *driver = (painter_driver_t *)device; // clang-format off const struct { @@ -81,7 +81,7 @@ __attribute__((weak)) bool qp_st7789_init(painter_device_t device, painter_rotat //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Driver vtable -const struct tft_panel_dc_reset_painter_driver_vtable_t st7789_driver_vtable = { +const tft_panel_dc_reset_painter_driver_vtable_t st7789_driver_vtable = { .base = { .init = qp_st7789_init, @@ -116,8 +116,8 @@ painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_ for (uint32_t i = 0; i < ST7789_NUM_DEVICES; ++i) { tft_panel_dc_reset_painter_device_t *driver = &st7789_drivers[i]; if (!driver->base.driver_vtable) { - driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&st7789_driver_vtable; - driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable; + driver->base.driver_vtable = (const painter_driver_vtable_t *)&st7789_driver_vtable; + driver->base.comms_vtable = (const painter_comms_vtable_t *)&spi_comms_with_dc_vtable; driver->base.panel_width = panel_width; driver->base.panel_height = panel_height; driver->base.rotation = QP_ROTATION_0; diff --git a/drivers/painter/tft_panel/qp_tft_panel.c b/drivers/painter/tft_panel/qp_tft_panel.c index 4a24cf9953..16dba9d6a6 100644 --- a/drivers/painter/tft_panel/qp_tft_panel.c +++ b/drivers/painter/tft_panel/qp_tft_panel.c @@ -12,15 +12,15 @@ // Power control bool qp_tft_panel_power(painter_device_t device, bool power_on) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable; + painter_driver_t * driver = (painter_driver_t *)device; + tft_panel_dc_reset_painter_driver_vtable_t *vtable = (tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable; qp_comms_command(device, power_on ? vtable->opcodes.display_on : vtable->opcodes.display_off); return true; } // Screen clear bool qp_tft_panel_clear(painter_device_t device) { - struct painter_driver_t *driver = (struct painter_driver_t *)device; + painter_driver_t *driver = (painter_driver_t *)device; driver->driver_vtable->init(device, driver->rotation); // Re-init the LCD return true; } @@ -33,8 +33,8 @@ bool qp_tft_panel_flush(painter_device_t device) { // Viewport to draw to bool qp_tft_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { - struct painter_driver_t * driver = (struct painter_driver_t *)device; - struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable; + painter_driver_t * driver = (painter_driver_t *)device; + tft_panel_dc_reset_painter_driver_vtable_t *vtable = (tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable; // Fix up the drawing location if required left += driver->offset_x; @@ -80,7 +80,7 @@ bool qp_tft_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, // Stream pixel data to the current write position in GRAM bool qp_tft_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) { - struct painter_driver_t *driver = (struct painter_driver_t *)device; + painter_driver_t *driver = (painter_driver_t *)device; qp_comms_send(device, pixel_data, native_pixel_count * driver->native_bits_per_pixel / 8); return true; } diff --git a/drivers/painter/tft_panel/qp_tft_panel.h b/drivers/painter/tft_panel/qp_tft_panel.h index 83b8dd5406..67168645b7 100644 --- a/drivers/painter/tft_panel/qp_tft_panel.h +++ b/drivers/painter/tft_panel/qp_tft_panel.h @@ -12,8 +12,8 @@ // Common TFT panel implementation using D/C, and RST pins. // Driver vtable with extras -struct tft_panel_dc_reset_painter_driver_vtable_t { - struct painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type +typedef struct tft_panel_dc_reset_painter_driver_vtable_t { + painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type // Number of bytes for transmitting x/y coordinates uint8_t num_window_bytes; @@ -29,16 +29,16 @@ struct tft_panel_dc_reset_painter_driver_vtable_t { uint8_t set_row_address; uint8_t enable_writes; } opcodes; -}; +} tft_panel_dc_reset_painter_driver_vtable_t; // Device definition typedef struct tft_panel_dc_reset_painter_device_t { - struct painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type + painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type union { #ifdef QUANTUM_PAINTER_SPI_ENABLE // SPI-based configurables - struct qp_comms_spi_dc_reset_config_t spi_dc_reset_config; + qp_comms_spi_dc_reset_config_t spi_dc_reset_config; #endif // QUANTUM_PAINTER_SPI_ENABLE // TODO: I2C/parallel etc. -- cgit v1.2.3 From 9a68472da87bea6eb9e209b8cf90de09211f23c8 Mon Sep 17 00:00:00 2001 From: Vladislav Marchenko Date: Mon, 3 Apr 2023 08:57:19 +0600 Subject: Added PMW3320 driver (#19543) --- drivers/sensors/pmw3320.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/sensors/pmw3320.h | 119 ++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 drivers/sensors/pmw3320.c create mode 100644 drivers/sensors/pmw3320.h (limited to 'drivers') diff --git a/drivers/sensors/pmw3320.c b/drivers/sensors/pmw3320.c new file mode 100644 index 0000000000..a4648ef425 --- /dev/null +++ b/drivers/sensors/pmw3320.c @@ -0,0 +1,192 @@ +/* Copyright 2021 Colin Lam (Ploopy Corporation) + * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) + * Copyright 2019 Sunjun Kim + * Copyright 2019 Hiroyuki Okada + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "pmw3320.h" +#include "wait.h" +#include "debug.h" +#include "gpio.h" + +void pmw3320_init(void) { + // Initialize sensor serial pins. + setPinOutput(PMW3320_SCLK_PIN); + setPinOutput(PMW3320_SDIO_PIN); + setPinOutput(PMW3320_CS_PIN); + + // reboot the sensor. + pmw3320_write_reg(REG_Power_Up_Reset, 0x5a); + + // wait maximum time before sensor is ready. + // this ensures that the sensor is actually ready after reset. + wait_ms(55); + + // read a burst from the sensor and then discard it. + // gets the sensor ready for write commands + // (for example, setting the dpi). + pmw3320_read_burst(); + + // Pretty sure that this shouldn't be in the driver. + // Probably device specific? + // Set rest mode to default + pmw3320_write_reg(REG_Rest_Mode_Status, 0x00); + // Set LED to be always on + pmw3320_write_reg(REG_Led_Control, 0x4); + // Disable rest mode + pmw3320_write_reg(REG_Performance, 0x80); +} + +// Perform a synchronization with sensor. +// Just as with the serial protocol, this is used by the slave to send a +// synchronization signal to the master. +void pmw3320_sync(void) { + writePinLow(PMW3320_CS_PIN); + wait_us(1); + writePinHigh(PMW3320_CS_PIN); +} + +void pmw3320_cs_select(void) { + writePinLow(PMW3320_CS_PIN); +} + +void pmw3320_cs_deselect(void) { + writePinHigh(PMW3320_CS_PIN); +} + +uint8_t pmw3320_serial_read(void) { + setPinInput(PMW3320_SDIO_PIN); + uint8_t byte = 0; + + for (uint8_t i = 0; i < 8; ++i) { + writePinLow(PMW3320_SCLK_PIN); + wait_us(1); + + byte = (byte << 1) | readPin(PMW3320_SDIO_PIN); + + writePinHigh(PMW3320_SCLK_PIN); + wait_us(1); + } + + return byte; +} + +void pmw3320_serial_write(uint8_t data) { + setPinOutput(PMW3320_SDIO_PIN); + + for (int8_t b = 7; b >= 0; b--) { + writePinLow(PMW3320_SCLK_PIN); + + if (data & (1 << b)) + writePinHigh(PMW3320_SDIO_PIN); + else + writePinLow(PMW3320_SDIO_PIN); + + wait_us(2); + + writePinHigh(PMW3320_SCLK_PIN); + } + + // This was taken from ADNS5050 driver. + // There's no any info in PMW3320 datasheet about this... + // tSWR. See page 15 of the ADNS5050 spec sheet. + // Technically, this is only necessary if the next operation is an SDIO + // read. This is not guaranteed to be the case, but we're being lazy. + wait_us(4); + + // Note that tSWW is never necessary. All write operations require at + // least 32us, which exceeds tSWW, so there's never a need to wait for it. +} + +// Read a byte of data from a register on the sensor. +uint8_t pmw3320_read_reg(uint8_t reg_addr) { + pmw3320_cs_select(); + + pmw3320_serial_write(reg_addr); + + uint8_t byte = pmw3320_serial_read(); + + // This was taken directly from ADNS5050 driver... + // tSRW & tSRR. See page 15 of the ADNS5050 spec sheet. + // Technically, this is only necessary if the next operation is an SDIO + // read or write. This is not guaranteed to be the case. + // Honestly, this wait could probably be removed. + wait_us(1); + + pmw3320_cs_deselect(); + + return byte; +} + +void pmw3320_write_reg(uint8_t reg_addr, uint8_t data) { + pmw3320_cs_select(); + pmw3320_serial_write(0b10000000 | reg_addr); + pmw3320_serial_write(data); + pmw3320_cs_deselect(); +} + +report_pmw3320_t pmw3320_read_burst(void) { + pmw3320_cs_select(); + + report_pmw3320_t data; + data.dx = 0; + data.dy = 0; + + pmw3320_serial_write(REG_Motion_Burst); + + uint8_t x = pmw3320_serial_read(); + uint8_t y = pmw3320_serial_read(); + + // Probably burst mode may include contents of delta_xy register, + // which contain HI parts of x/y deltas, but I had no luck finding it. + // Probably it's required to activate 12-bit mode to access this data. + // So we end burst mode early to not read unneeded information. + pmw3320_cs_deselect(); + + data.dx = convert_twoscomp(x); + data.dy = convert_twoscomp(y); + + return data; +} + +// Convert a two's complement byte from an unsigned data type into a signed +// data type. +int8_t convert_twoscomp(uint8_t data) { + if ((data & 0x80) == 0x80) + return -128 + (data & 0x7F); + else + return data; +} + +uint16_t pmw3320_get_cpi(void) { + uint8_t cpival = pmw3320_read_reg(REG_Resolution); + // 0x1F is an inversion of 0x20 which is 0b100000 + return (uint16_t)((cpival & 0x1F) * PMW3320_CPI_STEP); +} + +void pmw3320_set_cpi(uint16_t cpi) { + uint8_t cpival = constrain((cpi / PMW3320_CPI_STEP) - 1U, 0, (PMW3320_CPI_MAX / PMW3320_CPI_STEP) - 1U); + // Fifth bit is probably a control bit. + // PMW3320 datasheet don't have any info on this, so this is a pure guess. + pmw3320_write_reg(REG_Resolution, 0x20 | cpival); +} + +bool pmw3320_check_signature(void) { + uint8_t pid = pmw3320_read_reg(REG_Product_ID); + uint8_t pid2 = pmw3320_read_reg(REG_Inverse_Product_ID); + + return (pid == 0x3b && pid2 == 0xc4); +} diff --git a/drivers/sensors/pmw3320.h b/drivers/sensors/pmw3320.h new file mode 100644 index 0000000000..a1fd546919 --- /dev/null +++ b/drivers/sensors/pmw3320.h @@ -0,0 +1,119 @@ +/* Copyright 2021 Colin Lam (Ploopy Corporation) + * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) + * Copyright 2019 Sunjun Kim + * Copyright 2019 Hiroyuki Okada + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt))) + +// Definitions for the PMW3320 serial line. +#ifndef PMW3320_SCLK_PIN +# ifdef POINTING_DEVICE_SCLK_PIN +# define PMW3320_SCLK_PIN POINTING_DEVICE_SCLK_PIN +# else +# error "No clock pin defined -- missing POINTING_DEVICE_SCLK_PIN or PMW3320_SCLK_PIN" +# endif +#endif + +#ifndef PMW3320_SDIO_PIN +# ifdef POINTING_DEVICE_SDIO_PIN +# define PMW3320_SDIO_PIN POINTING_DEVICE_SDIO_PIN +# else +# error "No data pin defined -- missing POINTING_DEVICE_SDIO_PIN or PMW3320_SDIO_PIN" +# endif +#endif + +#ifndef PMW3320_CS_PIN +# ifdef POINTING_DEVICE_CS_PIN +# define PMW3320_CS_PIN POINTING_DEVICE_CS_PIN +# else +# error "No chip select pin defined -- missing POINTING_DEVICE_CS_PIN or PMW3320_CS_PIN define" +# endif +#endif + +typedef struct { + int8_t dx; + int8_t dy; +} report_pmw3320_t; + +// A bunch of functions to implement the PMW3320-specific serial protocol. +// Mostly taken from ADNS5050 driver. +// Note that the "serial.h" driver is insufficient, because it does not +// manually manipulate a serial clock signal. +void pmw3320_init(void); +void pmw3320_sync(void); +uint8_t pmw3320_serial_read(void); +void pmw3320_serial_write(uint8_t data); +uint8_t pmw3320_read_reg(uint8_t reg_addr); +void pmw3320_write_reg(uint8_t reg_addr, uint8_t data); +report_pmw3320_t pmw3320_read_burst(void); +void pmw3320_set_cpi(uint16_t cpi); +uint16_t pmw3320_get_cpi(void); +int8_t convert_twoscomp(uint8_t data); +bool pmw3320_check_signature(void); + +#if !defined(PMW3320_CPI) +# define PMW3320_CPI 1000 +#endif + +#define PMW3320_CPI_STEP 250 +#define PMW3320_CPI_MIN 250 +#define PMW3320_CPI_MAX 3500 + +// PMW3320 register addresses +// clang-format off +#define REG_Product_ID 0x00 +#define REG_Revision_ID 0x01 +#define REG_Motion 0x02 +#define REG_Delta_X 0x03 +#define REG_Delta_Y 0x04 +#define REG_SQUAL 0x05 +#define REG_Shutter_Upper 0x06 +#define REG_Shutter_Lower 0x07 +#define REG_Maximum_Pixel 0x08 +#define REG_Pixel_Accum 0x09 +#define REG_Minimum_Pixel 0x0a +#define REG_Pixel_Grab 0x0b +#define REG_Delta_XY 0x0c +#define REG_Resolution 0x0d +#define REG_Run_Downshift 0x0e +#define REG_Rest1_Period 0x0f +#define REG_Rest1_Downshift 0x10 +#define REG_Rest2_Preiod 0x11 +#define REG_Rest2_Downshift 0x12 +#define REG_Rest3_Period 0x13 +#define REG_Min_SQ_Run 0x17 +#define REG_Axis_Control 0x1a +#define REG_Performance 0x22 +#define REG_Low_Motion_Jitter 0x23 +#define REG_Shutter_Max_HI 0x36 +#define REG_Shutter_Max_LO 0x37 +#define REG_Frame_Rate 0x39 +#define REG_Power_Up_Reset 0x3a +#define REG_Shutdown 0x3b +#define REG_Inverse_Revision_ID 0x3f +#define REG_Led_Control 0x40 +#define REG_Motion_Control 0x41 +#define REG_Burst_Read_First 0x42 +#define REG_Rest_Mode_Status 0x45 +#define REG_Inverse_Product_ID 0x4f +#define REG_Motion_Burst 0x63 +// clang-format on -- cgit v1.2.3 From cf5626e0246b66879b03a18115a19a566e839f48 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Wed, 12 Apr 2023 21:43:27 -0700 Subject: [Bug] Fix compilation issues with PS/2 driver on F4x1 controllers (#20433) --- drivers/ps2/ps2_interrupt.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/ps2/ps2_interrupt.c b/drivers/ps2/ps2_interrupt.c index 2810a0f126..f7400564ef 100644 --- a/drivers/ps2/ps2_interrupt.c +++ b/drivers/ps2/ps2_interrupt.c @@ -47,6 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. // chibiOS headers # include "ch.h" # include "hal.h" +# include "gpio.h" #endif #include "ps2.h" -- cgit v1.2.3 From 6a619e64037d54e04148e1ee7e1c170533d49236 Mon Sep 17 00:00:00 2001 From: Xelus22 <17491233+Xelus22@users.noreply.github.com> Date: Sun, 30 Apr 2023 12:35:27 +1000 Subject: [Core] Clean up ISSI drivers, Add IS31FL3736 support (#20572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pablo Martínez <58857054+elpekenin@users.noreply.github.com> --- drivers/led/issi/is31fl3218.c | 4 +--- drivers/led/issi/is31fl3731-simple.c | 4 +--- drivers/led/issi/is31fl3731.c | 4 +--- drivers/led/issi/is31fl3733-simple.c | 4 +--- drivers/led/issi/is31fl3736.c | 17 +++++++---------- drivers/led/issi/is31fl3736.h | 4 ++-- drivers/led/issi/is31fl3737.c | 4 +--- drivers/led/issi/is31fl3737.h | 4 ++-- drivers/led/issi/is31fl3741.c | 8 +++++--- 9 files changed, 21 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/led/issi/is31fl3218.c b/drivers/led/issi/is31fl3218.c index d43863ac4b..c2300ebe89 100644 --- a/drivers/led/issi/is31fl3218.c +++ b/drivers/led/issi/is31fl3218.c @@ -45,9 +45,7 @@ void IS31FL3218_write_register(uint8_t reg, uint8_t data) { void IS31FL3218_write_pwm_buffer(uint8_t *pwm_buffer) { g_twi_transfer_buffer[0] = ISSI_REG_PWM; - for (int i = 0; i < 18; i++) { - g_twi_transfer_buffer[1 + i] = pwm_buffer[i]; - } + memcpy(g_twi_transfer_buffer + 1, pwm_buffer, 18); i2c_transmit(ISSI_ADDRESS, g_twi_transfer_buffer, 19, ISSI_TIMEOUT); } diff --git a/drivers/led/issi/is31fl3731-simple.c b/drivers/led/issi/is31fl3731-simple.c index 84060f9426..a62b21cc6b 100644 --- a/drivers/led/issi/is31fl3731-simple.c +++ b/drivers/led/issi/is31fl3731-simple.c @@ -123,9 +123,7 @@ void IS31FL3731_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { // copy the data from i to i+15 // device will auto-increment register for data after the first byte // thus this sets registers 0x24-0x33, 0x34-0x43, etc. in one transfer - for (int j = 0; j < 16; j++) { - g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j]; - } + memcpy(g_twi_transfer_buffer + 1, pwm_buffer + i, 16); #if ISSI_PERSISTENCE > 0 for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { diff --git a/drivers/led/issi/is31fl3731.c b/drivers/led/issi/is31fl3731.c index fed5354145..80344ca721 100644 --- a/drivers/led/issi/is31fl3731.c +++ b/drivers/led/issi/is31fl3731.c @@ -111,9 +111,7 @@ void IS31FL3731_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { // copy the data from i to i+15 // device will auto-increment register for data after the first byte // thus this sets registers 0x24-0x33, 0x34-0x43, etc. in one transfer - for (int j = 0; j < 16; j++) { - g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j]; - } + memcpy(g_twi_transfer_buffer + 1, pwm_buffer + i, 16); #if ISSI_PERSISTENCE > 0 for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { diff --git a/drivers/led/issi/is31fl3733-simple.c b/drivers/led/issi/is31fl3733-simple.c index 1e0994d780..21138c6e05 100644 --- a/drivers/led/issi/is31fl3733-simple.c +++ b/drivers/led/issi/is31fl3733-simple.c @@ -129,9 +129,7 @@ bool IS31FL3733_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { // Copy the data from i to i+15. // Device will auto-increment register for data after the first byte // Thus this sets registers 0x00-0x0F, 0x10-0x1F, etc. in one transfer. - for (int j = 0; j < 16; j++) { - g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j]; - } + memcpy(g_twi_transfer_buffer + 1, pwm_buffer + i, 16); #if ISSI_PERSISTENCE > 0 for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { diff --git a/drivers/led/issi/is31fl3736.c b/drivers/led/issi/is31fl3736.c index 82e7ee3d18..74e3c8f0a9 100644 --- a/drivers/led/issi/is31fl3736.c +++ b/drivers/led/issi/is31fl3736.c @@ -107,9 +107,7 @@ void IS31FL3736_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { // copy the data from i to i+15 // device will auto-increment register for data after the first byte // thus this sets registers 0x00-0x0F, 0x10-0x1F, etc. in one transfer - for (int j = 0; j < 16; j++) { - g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j]; - } + memcpy(g_twi_transfer_buffer + 1, pwm_buffer + i, 16); #if ISSI_PERSISTENCE > 0 for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { @@ -262,16 +260,15 @@ void IS31FL3736_mono_set_led_control_register(uint8_t index, bool enabled) { g_led_control_registers_update_required = true; } -void IS31FL3736_update_pwm_buffers(uint8_t addr1, uint8_t addr2) { - if (g_pwm_buffer_update_required) { +void IS31FL3736_update_pwm_buffers(uint8_t addr, uint8_t index) { + if (g_pwm_buffer_update_required[index]) { // Firstly we need to unlock the command register and select PG1 - IS31FL3736_write_register(addr1, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); - IS31FL3736_write_register(addr1, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM); + IS31FL3736_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); + IS31FL3736_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM); - IS31FL3736_write_pwm_buffer(addr1, g_pwm_buffer[0]); - // IS31FL3736_write_pwm_buffer(addr2, g_pwm_buffer[1]); + IS31FL3736_write_pwm_buffer(addr, g_pwm_buffer[index]); } - g_pwm_buffer_update_required = false; + g_pwm_buffer_update_required[index] = false; } void IS31FL3736_update_led_control_registers(uint8_t addr1, uint8_t addr2) { diff --git a/drivers/led/issi/is31fl3736.h b/drivers/led/issi/is31fl3736.h index ccb19afbcc..d7307b7414 100644 --- a/drivers/led/issi/is31fl3736.h +++ b/drivers/led/issi/is31fl3736.h @@ -58,8 +58,8 @@ void IS31FL3736_mono_set_led_control_register(uint8_t index, bool enabled); // (eg. from a timer interrupt). // Call this while idle (in between matrix scans). // If the buffer is dirty, it will update the driver with the buffer. -void IS31FL3736_update_pwm_buffers(uint8_t addr1, uint8_t addr2); -void IS31FL3736_update_led_control_registers(uint8_t addr1, uint8_t addr2); +void IS31FL3736_update_pwm_buffers(uint8_t addr, uint8_t index); +void IS31FL3736_update_led_control_registers(uint8_t addr, uint8_t index); #define PUR_0R 0x00 // No PUR resistor #define PUR_05KR 0x01 // 0.5k Ohm resistor diff --git a/drivers/led/issi/is31fl3737.c b/drivers/led/issi/is31fl3737.c index 45a20018c5..b6ed6b2629 100644 --- a/drivers/led/issi/is31fl3737.c +++ b/drivers/led/issi/is31fl3737.c @@ -114,9 +114,7 @@ void IS31FL3737_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { // copy the data from i to i+15 // device will auto-increment register for data after the first byte // thus this sets registers 0x00-0x0F, 0x10-0x1F, etc. in one transfer - for (int j = 0; j < 16; j++) { - g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j]; - } + memcpy(g_twi_transfer_buffer + 1, pwm_buffer + i, 16); #if ISSI_PERSISTENCE > 0 for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { diff --git a/drivers/led/issi/is31fl3737.h b/drivers/led/issi/is31fl3737.h index fb0c33420c..372e9e4e9b 100644 --- a/drivers/led/issi/is31fl3737.h +++ b/drivers/led/issi/is31fl3737.h @@ -45,8 +45,8 @@ void IS31FL3737_set_led_control_register(uint8_t index, bool red, bool green, bo // (eg. from a timer interrupt). // Call this while idle (in between matrix scans). // If the buffer is dirty, it will update the driver with the buffer. -void IS31FL3737_update_pwm_buffers(uint8_t addr1, uint8_t addr2); -void IS31FL3737_update_led_control_registers(uint8_t addr1, uint8_t addr2); +void IS31FL3737_update_pwm_buffers(uint8_t addr, uint8_t index); +void IS31FL3737_update_led_control_registers(uint8_t addr, uint8_t index); #define PUR_0R 0x00 // No PUR resistor #define PUR_05KR 0x01 // 0.5k Ohm resistor in t_NOL diff --git a/drivers/led/issi/is31fl3741.c b/drivers/led/issi/is31fl3741.c index c2cdd4c46f..d5dfaf1de6 100644 --- a/drivers/led/issi/is31fl3741.c +++ b/drivers/led/issi/is31fl3741.c @@ -104,9 +104,7 @@ void IS31FL3741_write_register(uint8_t addr, uint8_t reg, uint8_t data) { } bool IS31FL3741_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { - // unlock the command register and select PG2 - IS31FL3741_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); - IS31FL3741_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM0); + // Assume PG1 is already selected for (int i = 0; i < 342; i += 18) { if (i == 180) { @@ -222,6 +220,10 @@ void IS31FL3741_set_led_control_register(uint8_t index, bool red, bool green, bo void IS31FL3741_update_pwm_buffers(uint8_t addr, uint8_t index) { if (g_pwm_buffer_update_required[index]) { + // unlock the command register and select PG2 + IS31FL3741_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); + IS31FL3741_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM0); + IS31FL3741_write_pwm_buffer(addr, g_pwm_buffer[index]); } -- cgit v1.2.3 From 75e1ef7734413521387b110275e472a0dadae1df Mon Sep 17 00:00:00 2001 From: Albert Y <76888457+filterpaper@users.noreply.github.com> Date: Sun, 30 Apr 2023 16:39:28 +0800 Subject: Add required string header file (#20638) --- drivers/led/issi/is31fl3218.h | 1 + drivers/led/issi/is31fl3731-simple.h | 1 + drivers/led/issi/is31fl3731.h | 1 + drivers/led/issi/is31fl3733-simple.h | 1 + drivers/led/issi/is31fl3736.h | 1 + drivers/led/issi/is31fl3737.h | 1 + 6 files changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/led/issi/is31fl3218.h b/drivers/led/issi/is31fl3218.h index fa760da191..26bb01a014 100644 --- a/drivers/led/issi/is31fl3218.h +++ b/drivers/led/issi/is31fl3218.h @@ -18,6 +18,7 @@ #include #include +#include void IS31FL3218_init(void); void IS31FL3218_set_color(int index, uint8_t red, uint8_t green, uint8_t blue); diff --git a/drivers/led/issi/is31fl3731-simple.h b/drivers/led/issi/is31fl3731-simple.h index 1ddadd5209..7834766b94 100644 --- a/drivers/led/issi/is31fl3731-simple.h +++ b/drivers/led/issi/is31fl3731-simple.h @@ -20,6 +20,7 @@ #include #include +#include #include "progmem.h" typedef struct is31_led { diff --git a/drivers/led/issi/is31fl3731.h b/drivers/led/issi/is31fl3731.h index 6791289c9e..4c79cb8146 100644 --- a/drivers/led/issi/is31fl3731.h +++ b/drivers/led/issi/is31fl3731.h @@ -19,6 +19,7 @@ #include #include +#include #include "progmem.h" typedef struct is31_led { diff --git a/drivers/led/issi/is31fl3733-simple.h b/drivers/led/issi/is31fl3733-simple.h index f0ea3adca0..1571fdd3d5 100644 --- a/drivers/led/issi/is31fl3733-simple.h +++ b/drivers/led/issi/is31fl3733-simple.h @@ -22,6 +22,7 @@ #include #include +#include #include "progmem.h" typedef struct is31_led { diff --git a/drivers/led/issi/is31fl3736.h b/drivers/led/issi/is31fl3736.h index d7307b7414..332b2035f3 100644 --- a/drivers/led/issi/is31fl3736.h +++ b/drivers/led/issi/is31fl3736.h @@ -19,6 +19,7 @@ #include #include +#include #include "progmem.h" // Simple interface option. diff --git a/drivers/led/issi/is31fl3737.h b/drivers/led/issi/is31fl3737.h index 372e9e4e9b..ca9a917ee2 100644 --- a/drivers/led/issi/is31fl3737.h +++ b/drivers/led/issi/is31fl3737.h @@ -21,6 +21,7 @@ #include #include +#include #include "progmem.h" typedef struct is31_led { -- cgit v1.2.3 From 4b8796168913e40de57352c7daa8eb726398aec8 Mon Sep 17 00:00:00 2001 From: Xelus22 <17491233+Xelus22@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:39:52 +1000 Subject: [Bug] Add Develop is31fl3736 multi drivers (#20642) --- drivers/led/issi/is31fl3736.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/led/issi/is31fl3736.c b/drivers/led/issi/is31fl3736.c index 74e3c8f0a9..d6b0881139 100644 --- a/drivers/led/issi/is31fl3736.c +++ b/drivers/led/issi/is31fl3736.c @@ -77,7 +77,7 @@ uint8_t g_twi_transfer_buffer[20]; // buffers and the transfers in IS31FL3736_write_pwm_buffer() but it's // probably not worth the extra complexity. uint8_t g_pwm_buffer[DRIVER_COUNT][192]; -bool g_pwm_buffer_update_required = false; +bool g_pwm_buffer_update_required[DRIVER_COUNT] = {false}; uint8_t g_led_control_registers[DRIVER_COUNT][24] = {{0}, {0}}; bool g_led_control_registers_update_required = false; @@ -169,10 +169,10 @@ void IS31FL3736_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { if (index >= 0 && index < RGB_MATRIX_LED_COUNT) { memcpy_P(&led, (&g_is31_leds[index]), sizeof(led)); - g_pwm_buffer[led.driver][led.r] = red; - g_pwm_buffer[led.driver][led.g] = green; - g_pwm_buffer[led.driver][led.b] = blue; - g_pwm_buffer_update_required = true; + g_pwm_buffer[led.driver][led.r] = red; + g_pwm_buffer[led.driver][led.g] = green; + g_pwm_buffer[led.driver][led.b] = blue; + g_pwm_buffer_update_required[led.driver] = true; } } @@ -230,9 +230,9 @@ void IS31FL3736_mono_set_brightness(int index, uint8_t value) { if (index >= 0 && index < 96) { // Index in range 0..95 -> A1..A8, B1..B8, etc. // Map index 0..95 to registers 0x00..0xBE (interleaved) - uint8_t pwm_register = index * 2; - g_pwm_buffer[0][pwm_register] = value; - g_pwm_buffer_update_required = true; + uint8_t pwm_register = index * 2; + g_pwm_buffer[0][pwm_register] = value; + g_pwm_buffer_update_required[0] = true; } } -- cgit v1.2.3 From 2ddad246ce85066d5aee798e6ea516ea8e49eea9 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Wed, 10 May 2023 14:04:53 -0700 Subject: OLED Driver improvements (#20331) Co-authored-by: Sergey Vlasov --- drivers/oled/oled_driver.c | 979 ++++++++++++++++++++++++++++++++++++++++++ drivers/oled/oled_driver.h | 153 +++++++ drivers/oled/ssd1306_sh1106.c | 795 ---------------------------------- 3 files changed, 1132 insertions(+), 795 deletions(-) create mode 100644 drivers/oled/oled_driver.c delete mode 100644 drivers/oled/ssd1306_sh1106.c (limited to 'drivers') diff --git a/drivers/oled/oled_driver.c b/drivers/oled/oled_driver.c new file mode 100644 index 0000000000..e50a881120 --- /dev/null +++ b/drivers/oled/oled_driver.c @@ -0,0 +1,979 @@ +/* +Copyright 2019 Ryan Caltabiano + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#if defined(OLED_TRANSPORT_SPI) +# include "spi_master.h" +#elif defined(OLED_TRANSPORT_I2C) +# include "i2c_master.h" +#endif +#include "oled_driver.h" +#include OLED_FONT_H +#include "timer.h" +#include "print.h" +#include +#include "progmem.h" +#include "wait.h" + +// Used commands from spec sheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf +// for SH1106: https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf +// for SH1107: https://www.displayfuture.com/Display/datasheet/controller/SH1107.pdf + +// Fundamental Commands +#define CONTRAST 0x81 +#define DISPLAY_ALL_ON 0xA5 +#define DISPLAY_ALL_ON_RESUME 0xA4 +#define NORMAL_DISPLAY 0xA6 +#define INVERT_DISPLAY 0xA7 +#define DISPLAY_ON 0xAF +#define DISPLAY_OFF 0xAE +#define NOP 0xE3 + +// Scrolling Commands +#define ACTIVATE_SCROLL 0x2F +#define DEACTIVATE_SCROLL 0x2E +#define SCROLL_RIGHT 0x26 +#define SCROLL_LEFT 0x27 +#define SCROLL_RIGHT_UP 0x29 +#define SCROLL_LEFT_UP 0x2A + +// Addressing Setting Commands +#define MEMORY_MODE 0x20 +#define COLUMN_ADDR 0x21 +#define PAGE_ADDR 0x22 +#define PAM_SETCOLUMN_LSB 0x00 +#define PAM_SETCOLUMN_MSB 0x10 +#define PAM_PAGE_ADDR 0xB0 // 0xb0 -- 0xb7 + +// Hardware Configuration Commands +#define DISPLAY_START_LINE 0x40 +#define SEGMENT_REMAP 0xA0 +#define SEGMENT_REMAP_INV 0xA1 +#define MULTIPLEX_RATIO 0xA8 +#define COM_SCAN_INC 0xC0 +#define COM_SCAN_DEC 0xC8 +#define DISPLAY_OFFSET 0xD3 +#define COM_PINS 0xDA +#define COM_PINS_SEQ 0x02 +#define COM_PINS_ALT 0x12 +#define COM_PINS_SEQ_LR 0x22 +#define COM_PINS_ALT_LR 0x32 + +// Timing & Driving Commands +#define DISPLAY_CLOCK 0xD5 +#define PRE_CHARGE_PERIOD 0xD9 +#define VCOM_DETECT 0xDB + +// Advance Graphic Commands +#define FADE_BLINK 0x23 +#define ENABLE_FADE 0x20 +#define ENABLE_BLINK 0x30 + +// Charge Pump Commands +#define CHARGE_PUMP 0x8D + +// Commands specific to the SH1107 chip +#define SH1107_DISPLAY_START_LINE 0xDC +#define SH1107_MEMORY_MODE_PAGE 0x20 +#define SH1107_MEMORY_MODE_VERTICAL 0x21 + +// Misc defines +#ifndef OLED_BLOCK_COUNT +# define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) +#endif +#ifndef OLED_BLOCK_SIZE +# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) +#endif +// Default display clock +#if !defined(OLED_DISPLAY_CLOCK) +# define OLED_DISPLAY_CLOCK 0x80 +#endif +// Default VCOMH deselect value +#if !defined(OLED_VCOM_DETECT) +# define OLED_VCOM_DETECT 0x20 +#endif +#if !defined(OLED_PRE_CHARGE_PERIOD) +# define OLED_PRE_CHARGE_PERIOD 0xF1 +#endif + + +#define OLED_ALL_BLOCKS_MASK (((((OLED_BLOCK_TYPE)1 << (OLED_BLOCK_COUNT - 1)) - 1) << 1) | 1) + +#define OLED_IC_HAS_HORIZONTAL_MODE (OLED_IC == OLED_IC_SSD1306) +#define OLED_IC_COM_PINS_ARE_COLUMNS (OLED_IC == OLED_IC_SH1107) + +#ifndef OLED_COM_PIN_COUNT +# if OLED_IC == OLED_IC_SSD1306 +# define OLED_COM_PIN_COUNT 64 +# elif OLED_IC == OLED_IC_SH1106 +# define OLED_COM_PIN_COUNT 64 +# elif OLED_IC == OLED_IC_SH1107 +# define OLED_COM_PIN_COUNT 128 +# else +# error Invalid OLED_IC value +# endif +#endif + +#ifndef OLED_COM_PIN_OFFSET +# define OLED_COM_PIN_OFFSET 0 +#endif + +// i2c defines +#define I2C_CMD 0x00 +#define I2C_DATA 0x40 + +#define HAS_FLAGS(bits, flags) ((bits & flags) == flags) + +// Display buffer's is the same as the OLED memory layout +// this is so we don't end up with rounding errors with +// parts of the display unusable or don't get cleared correctly +// and also allows for drawing & inverting +uint8_t oled_buffer[OLED_MATRIX_SIZE]; +uint8_t *oled_cursor; +OLED_BLOCK_TYPE oled_dirty = 0; +bool oled_initialized = false; +bool oled_active = false; +bool oled_scrolling = false; +bool oled_inverted = false; +uint8_t oled_brightness = OLED_BRIGHTNESS; +oled_rotation_t oled_rotation = 0; +uint8_t oled_rotation_width = 0; +uint8_t oled_scroll_speed = 0; // this holds the speed after being remapped to ssd1306 internal values +uint8_t oled_scroll_start = 0; +uint8_t oled_scroll_end = 7; +#if OLED_TIMEOUT > 0 +uint32_t oled_timeout; +#endif +#if OLED_SCROLL_TIMEOUT > 0 +uint32_t oled_scroll_timeout; +#endif +#if OLED_UPDATE_INTERVAL > 0 +uint16_t oled_update_timeout; +#endif + +#if defined(OLED_TRANSPORT_SPI) +# ifndef OLED_DC_PIN +# error "The OLED driver in SPI needs a D/C pin defined" +# endif +# ifndef OLED_CS_PIN +# error "The OLED driver in SPI needs a CS pin defined" +# endif +# ifndef OLED_SPI_MODE +# define OLED_SPI_MODE 3 +# endif +# ifndef OLED_SPI_DIVISOR +# define OLED_SPI_DIVISOR 2 +# endif +#elif defined(OLED_TRANSPORT_I2C) +# if !defined(OLED_DISPLAY_ADDRESS) +# define OLED_DISPLAY_ADDRESS 0x3C +# endif +#endif + +// Transmit/Write Funcs. +__attribute__((weak)) bool oled_send_cmd(const uint8_t *data, uint16_t size) { +#if defined(OLED_TRANSPORT_SPI) + if (!spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR)) { + return false; + } + // Command Mode + writePinLow(OLED_DC_PIN); + // Send the commands + if (spi_transmit(&data[1], size - 1) != SPI_STATUS_SUCCESS) { + spi_stop(); + return false; + } + spi_stop(); + return true; +#elif defined(OLED_TRANSPORT_I2C) + i2c_status_t status = i2c_transmit((OLED_DISPLAY_ADDRESS << 1), data, size, OLED_I2C_TIMEOUT); + + return (status == I2C_STATUS_SUCCESS); +#endif +} + +__attribute__((weak)) bool oled_send_cmd_P(const uint8_t *data, uint16_t size) { +#if defined(__AVR__) +# if defined(OLED_TRANSPORT_SPI) + if (!spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR)) { + return false; + } + spi_status_t status = SPI_STATUS_SUCCESS; + // Command Mode + writePinLow(OLED_DC_PIN); + // Send the commands + for (uint16_t i = 1; i < size && status >= 0; i++) { + status = spi_write(pgm_read_byte((const char *)&data[i])); + } + spi_stop(); + return (status >= 0); +# elif defined(OLED_TRANSPORT_I2C) + i2c_status_t status = i2c_start((OLED_DISPLAY_ADDRESS << 1) | I2C_WRITE, OLED_I2C_TIMEOUT); + + for (uint16_t i = 0; i < size && status >= 0; i++) { + status = i2c_write(pgm_read_byte((const char *)data++), OLED_I2C_TIMEOUT); + } + + i2c_stop(); + + return (status == I2C_STATUS_SUCCESS); +# endif +#else + return oled_send_cmd(data, size); +#endif +} + +__attribute__((weak)) bool oled_send_data(const uint8_t *data, uint16_t size) { +#if defined(OLED_TRANSPORT_SPI) + if (!spi_start(OLED_CS_PIN, false, OLED_SPI_MODE, OLED_SPI_DIVISOR)) { + return false; + } + // Data Mode + writePinHigh(OLED_DC_PIN); + // Send the commands + if (spi_transmit(data, size) != SPI_STATUS_SUCCESS) { + spi_stop(); + return false; + } + spi_stop(); + return true; +#elif defined(OLED_TRANSPORT_I2C) + i2c_status_t status = i2c_writeReg((OLED_DISPLAY_ADDRESS << 1), I2C_DATA, data, size, OLED_I2C_TIMEOUT); + return (status == I2C_STATUS_SUCCESS); +#endif +} + +__attribute__((weak)) void oled_driver_init(void) { +#if defined(OLED_TRANSPORT_SPI) + spi_init(); + setPinOutput(OLED_CS_PIN); + writePinHigh(OLED_CS_PIN); + + setPinOutput(OLED_DC_PIN); + writePinLow(OLED_DC_PIN); +# ifdef OLED_RST_PIN + /* Reset device */ + setPinOutput(OLED_RST_PIN); + writePinLow(OLED_RST_PIN); + wait_ms(20); + writePinHigh(OLED_RST_PIN); + wait_ms(20); +# endif +#elif defined(OLED_TRANSPORT_I2C) + i2c_init(); +#endif +} + +// Flips the rendering bits for a character at the current cursor position +static void InvertCharacter(uint8_t *cursor) { + const uint8_t *end = cursor + OLED_FONT_WIDTH; + while (cursor < end) { + *cursor = ~(*cursor); + cursor++; + } +} + +bool oled_init(oled_rotation_t rotation) { +#if defined(USE_I2C) && defined(SPLIT_KEYBOARD) && defined(OLED_TRANSPORT_I2C) + if (!is_keyboard_master()) { + return true; + } +#endif + + oled_rotation = oled_init_user(oled_init_kb(rotation)); + if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { + oled_rotation_width = OLED_DISPLAY_WIDTH; + } else { + oled_rotation_width = OLED_DISPLAY_HEIGHT; + } + oled_driver_init(); + + static const uint8_t PROGMEM display_setup1[] = { + I2C_CMD, + DISPLAY_OFF, + DISPLAY_CLOCK, + OLED_DISPLAY_CLOCK, + MULTIPLEX_RATIO, +#if OLED_IC_COM_PINS_ARE_COLUMNS + OLED_DISPLAY_WIDTH - 1, +#else + OLED_DISPLAY_HEIGHT - 1, +#endif +#if OLED_IC == OLED_IC_SH1107 + SH1107_DISPLAY_START_LINE, + 0x00, +#else + DISPLAY_START_LINE | 0x00, +#endif + CHARGE_PUMP, + 0x14, +#if OLED_IC_HAS_HORIZONTAL_MODE + // MEMORY_MODE is unsupported on SH1106 (Page Addressing only) + MEMORY_MODE, + 0x00, // Horizontal addressing mode +#elif OLED_IC == OLED_IC_SH1107 + // Page addressing mode + SH1107_MEMORY_MODE_PAGE, +#endif + }; + if (!oled_send_cmd_P(display_setup1, ARRAY_SIZE(display_setup1))) { + print("oled_init cmd set 1 failed\n"); + return false; + } + + if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_180)) { + static const uint8_t PROGMEM display_normal[] = { + I2C_CMD, SEGMENT_REMAP_INV, COM_SCAN_DEC, DISPLAY_OFFSET, OLED_COM_PIN_OFFSET, + }; + if (!oled_send_cmd_P(display_normal, ARRAY_SIZE(display_normal))) { + print("oled_init cmd normal rotation failed\n"); + return false; + } + } else { + static const uint8_t PROGMEM display_flipped[] = { + I2C_CMD, SEGMENT_REMAP, COM_SCAN_INC, DISPLAY_OFFSET, (OLED_COM_PIN_COUNT - OLED_COM_PIN_OFFSET) % OLED_COM_PIN_COUNT, + }; + if (!oled_send_cmd_P(display_flipped, ARRAY_SIZE(display_flipped))) { + print("display_flipped failed\n"); + return false; + } + } + + 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}; + if (!oled_send_cmd_P(display_setup2, ARRAY_SIZE(display_setup2))) { + print("display_setup2 failed\n"); + return false; + } + +#if OLED_TIMEOUT > 0 + oled_timeout = timer_read32() + OLED_TIMEOUT; +#endif +#if OLED_SCROLL_TIMEOUT > 0 + oled_scroll_timeout = timer_read32() + OLED_SCROLL_TIMEOUT; +#endif + + oled_clear(); + oled_initialized = true; + oled_active = true; + oled_scrolling = false; + return true; +} + +__attribute__((weak)) oled_rotation_t oled_init_kb(oled_rotation_t rotation) { + return rotation; +} +__attribute__((weak)) oled_rotation_t oled_init_user(oled_rotation_t rotation) { + return rotation; +} + +void oled_clear(void) { + memset(oled_buffer, 0, sizeof(oled_buffer)); + oled_cursor = &oled_buffer[0]; + oled_dirty = OLED_ALL_BLOCKS_MASK; +} + +static void calc_bounds(uint8_t update_start, uint8_t *cmd_array) { + // Calculate commands to set memory addressing bounds. + uint8_t start_page = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_WIDTH; + uint8_t start_column = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_WIDTH; +#if !OLED_IC_HAS_HORIZONTAL_MODE + // Commands for Page Addressing Mode. Sets starting page and column; has no end bound. + // Column value must be split into high and low nybble and sent as two commands. + cmd_array[0] = PAM_PAGE_ADDR | start_page; + cmd_array[1] = PAM_SETCOLUMN_LSB | ((OLED_COLUMN_OFFSET + start_column) & 0x0f); + cmd_array[2] = PAM_SETCOLUMN_MSB | ((OLED_COLUMN_OFFSET + start_column) >> 4 & 0x0f); +#else + // Commands for use in Horizontal Addressing mode. + cmd_array[1] = start_column + OLED_COLUMN_OFFSET; + cmd_array[4] = start_page; + cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) % OLED_DISPLAY_WIDTH + cmd_array[1]; + cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) / OLED_DISPLAY_WIDTH - 1 + cmd_array[4]; +#endif +} + +static void calc_bounds_90(uint8_t update_start, uint8_t *cmd_array) { + // Block numbering starts from the bottom left corner, going up and then to + // the right. The controller needs the page and column numbers for the top + // left and bottom right corners of that block. + + // Total number of pages across the screen height. + const uint8_t height_in_pages = OLED_DISPLAY_HEIGHT / 8; + + // Difference of starting page numbers for adjacent blocks; may be 0 if + // blocks are large enough to occupy one or more whole 8px columns. + const uint8_t page_inc_per_block = OLED_BLOCK_SIZE % OLED_DISPLAY_HEIGHT / 8; + + // Top page number for a block which is at the bottom edge of the screen. + const uint8_t bottom_block_top_page = (height_in_pages - page_inc_per_block) % height_in_pages; + +#if !OLED_IC_HAS_HORIZONTAL_MODE + // Only the Page Addressing Mode is supported + uint8_t start_page = bottom_block_top_page - (OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT / 8); + uint8_t start_column = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8; + cmd_array[0] = PAM_PAGE_ADDR | start_page; + cmd_array[1] = PAM_SETCOLUMN_LSB | ((OLED_COLUMN_OFFSET + start_column) & 0x0f); + cmd_array[2] = PAM_SETCOLUMN_MSB | ((OLED_COLUMN_OFFSET + start_column) >> 4 & 0x0f); +#else + cmd_array[1] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8 + OLED_COLUMN_OFFSET; + cmd_array[4] = bottom_block_top_page - (OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT / 8); + cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8 - 1 + cmd_array[1]; + cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) % OLED_DISPLAY_HEIGHT / 8 + cmd_array[4]; +#endif +} + +uint8_t crot(uint8_t a, int8_t n) { + const uint8_t mask = 0x7; + n &= mask; + return a << n | a >> (-n & mask); +} + +static void rotate_90(const uint8_t *src, uint8_t *dest) { + for (uint8_t i = 0, shift = 7; i < 8; ++i, --shift) { + uint8_t selector = (1 << i); + for (uint8_t j = 0; j < 8; ++j) { + dest[i] |= crot(src[j] & selector, shift - (int8_t)j); + } + } +} + +void oled_render(void) { + // Do we have work to do? + oled_dirty &= OLED_ALL_BLOCKS_MASK; + if (!oled_dirty || !oled_initialized || oled_scrolling) { + return; + } + + // Turn on display if it is off + oled_on(); + + uint8_t update_start = 0; + uint8_t num_processed = 0; + while (oled_dirty && num_processed++ < OLED_UPDATE_PROCESS_LIMIT) { // render all dirty blocks (up to the configured limit) + // Find next dirty block + while (!(oled_dirty & ((OLED_BLOCK_TYPE)1 << update_start))) { + ++update_start; + } + + // Set column & page position +#if OLED_IC_HAS_HORIZONTAL_MODE + static uint8_t display_start[] = {I2C_CMD, COLUMN_ADDR, 0, OLED_DISPLAY_WIDTH - 1, PAGE_ADDR, 0, OLED_DISPLAY_HEIGHT / 8 - 1}; +#else + static uint8_t display_start[] = {I2C_CMD, PAM_PAGE_ADDR, PAM_SETCOLUMN_LSB, PAM_SETCOLUMN_MSB}; +#endif + if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { + calc_bounds(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start + } else { + calc_bounds_90(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start + } + + // Send column & page position + if (!oled_send_cmd(display_start, ARRAY_SIZE(display_start))) { + print("oled_render offset command failed\n"); + return; + } + + if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { + // Send render data chunk as is + if (!oled_send_data(&oled_buffer[OLED_BLOCK_SIZE * update_start], OLED_BLOCK_SIZE)) { + print("oled_render data failed\n"); + return; + } + } else { + // Rotate the render chunks + const static uint8_t source_map[] = OLED_SOURCE_MAP; + const static uint8_t target_map[] = OLED_TARGET_MAP; + + static uint8_t temp_buffer[OLED_BLOCK_SIZE]; + memset(temp_buffer, 0, sizeof(temp_buffer)); + for (uint8_t i = 0; i < sizeof(source_map); ++i) { + rotate_90(&oled_buffer[OLED_BLOCK_SIZE * update_start + source_map[i]], &temp_buffer[target_map[i]]); + } + +#if OLED_IC_HAS_HORIZONTAL_MODE + // Send render data chunk after rotating + if (!oled_send_data(&temp_buffer[0], OLED_BLOCK_SIZE)) { + print("oled_render90 data failed\n"); + return; + } +#else + // For SH1106 or SH1107 the data chunk must be split into separate pieces for each page + const uint8_t columns_in_block = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8; + const uint8_t num_pages = OLED_BLOCK_SIZE / columns_in_block; + for (uint8_t i = 0; i < num_pages; ++i) { + // Send column & page position for all pages except the first one + if (i > 0) { + display_start[1]++; + if (!oled_send_cmd(display_start, ARRAY_SIZE(display_start))) { + print("oled_render offset command failed\n"); + return; + } + } + // Send data for the page + if (!oled_send_data(&temp_buffer[columns_in_block * i], columns_in_block)) { + print("oled_render90 data failed\n"); + return; + } + } +#endif + } + + // Clear dirty flag of just rendered block + oled_dirty &= ~((OLED_BLOCK_TYPE)1 << update_start); + } +} + +void oled_set_cursor(uint8_t col, uint8_t line) { + uint16_t index = line * oled_rotation_width + col * OLED_FONT_WIDTH; + + // Out of bounds? + if (index >= OLED_MATRIX_SIZE) { + index = 0; + } + + oled_cursor = &oled_buffer[index]; +} + +void oled_advance_page(bool clearPageRemainder) { + uint16_t index = oled_cursor - &oled_buffer[0]; + uint8_t remaining = oled_rotation_width - (index % oled_rotation_width); + + if (clearPageRemainder) { + // Remaining Char count + remaining = remaining / OLED_FONT_WIDTH; + + // Write empty character until next line + while (remaining--) + oled_write_char(' ', false); + } else { + // Next page index out of bounds? + if (index + remaining >= OLED_MATRIX_SIZE) { + index = 0; + remaining = 0; + } + + oled_cursor = &oled_buffer[index + remaining]; + } +} + +void oled_advance_char(void) { + uint16_t nextIndex = oled_cursor - &oled_buffer[0] + OLED_FONT_WIDTH; + uint8_t remainingSpace = oled_rotation_width - (nextIndex % oled_rotation_width); + + // Do we have enough space on the current line for the next character + if (remainingSpace < OLED_FONT_WIDTH) { + nextIndex += remainingSpace; + } + + // Did we go out of bounds + if (nextIndex >= OLED_MATRIX_SIZE) { + nextIndex = 0; + } + + // Update cursor position + oled_cursor = &oled_buffer[nextIndex]; +} + +// Main handler that writes character data to the display buffer +void oled_write_char(const char data, bool invert) { + // Advance to the next line if newline + if (data == '\n') { + // Old source wrote ' ' until end of line... + oled_advance_page(true); + return; + } + + if (data == '\r') { + oled_advance_page(false); + return; + } + + // copy the current render buffer to check for dirty after + static uint8_t oled_temp_buffer[OLED_FONT_WIDTH]; + memcpy(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH); + + _Static_assert(sizeof(font) >= ((OLED_FONT_END + 1 - OLED_FONT_START) * OLED_FONT_WIDTH), "OLED_FONT_END references outside array"); + + // set the reder buffer data + uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index + if (cast_data < OLED_FONT_START || cast_data > OLED_FONT_END) { + memset(oled_cursor, 0x00, OLED_FONT_WIDTH); + } else { + const uint8_t *glyph = &font[(cast_data - OLED_FONT_START) * OLED_FONT_WIDTH]; + memcpy_P(oled_cursor, glyph, OLED_FONT_WIDTH); + } + + // Invert if needed + if (invert) { + InvertCharacter(oled_cursor); + } + + // Dirty check + if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) { + uint16_t index = oled_cursor - &oled_buffer[0]; + oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); + // Edgecase check if the written data spans the 2 chunks + oled_dirty |= ((OLED_BLOCK_TYPE)1 << ((index + OLED_FONT_WIDTH - 1) / OLED_BLOCK_SIZE)); + } + + // Finally move to the next char + oled_advance_char(); +} + +void oled_write(const char *data, bool invert) { + const char *end = data + strlen(data); + while (data < end) { + oled_write_char(*data, invert); + data++; + } +} + +void oled_write_ln(const char *data, bool invert) { + oled_write(data, invert); + oled_advance_page(true); +} + +void oled_pan(bool left) { + uint16_t i = 0; + for (uint16_t y = 0; y < OLED_DISPLAY_HEIGHT / 8; y++) { + if (left) { + for (uint16_t x = 0; x < OLED_DISPLAY_WIDTH - 1; x++) { + i = y * OLED_DISPLAY_WIDTH + x; + oled_buffer[i] = oled_buffer[i + 1]; + } + } else { + for (uint16_t x = OLED_DISPLAY_WIDTH - 1; x > 0; x--) { + i = y * OLED_DISPLAY_WIDTH + x; + oled_buffer[i] = oled_buffer[i - 1]; + } + } + } + oled_dirty = OLED_ALL_BLOCKS_MASK; +} + +oled_buffer_reader_t oled_read_raw(uint16_t start_index) { + if (start_index > OLED_MATRIX_SIZE) start_index = OLED_MATRIX_SIZE; + oled_buffer_reader_t ret_reader; + ret_reader.current_element = &oled_buffer[start_index]; + ret_reader.remaining_element_count = OLED_MATRIX_SIZE - start_index; + return ret_reader; +} + +void oled_write_raw_byte(const char data, uint16_t index) { + if (index > OLED_MATRIX_SIZE) index = OLED_MATRIX_SIZE; + if (oled_buffer[index] == data) return; + oled_buffer[index] = data; + oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); +} + +void oled_write_raw(const char *data, uint16_t size) { + uint16_t cursor_start_index = oled_cursor - &oled_buffer[0]; + if ((size + cursor_start_index) > OLED_MATRIX_SIZE) size = OLED_MATRIX_SIZE - cursor_start_index; + for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) { + uint8_t c = *data++; + if (oled_buffer[i] == c) continue; + oled_buffer[i] = c; + oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE)); + } +} + +void oled_write_pixel(uint8_t x, uint8_t y, bool on) { + if (x >= oled_rotation_width) { + return; + } + uint16_t index = x + (y / 8) * oled_rotation_width; + if (index >= OLED_MATRIX_SIZE) { + return; + } + uint8_t data = oled_buffer[index]; + if (on) { + data |= (1 << (y % 8)); + } else { + data &= ~(1 << (y % 8)); + } + if (oled_buffer[index] != data) { + oled_buffer[index] = data; + oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); + } +} + +#if defined(__AVR__) +void oled_write_P(const char *data, bool invert) { + uint8_t c = pgm_read_byte(data); + while (c != 0) { + oled_write_char(c, invert); + c = pgm_read_byte(++data); + } +} + +void oled_write_ln_P(const char *data, bool invert) { + oled_write_P(data, invert); + oled_advance_page(true); +} + +void oled_write_raw_P(const char *data, uint16_t size) { + uint16_t cursor_start_index = oled_cursor - &oled_buffer[0]; + if ((size + cursor_start_index) > OLED_MATRIX_SIZE) size = OLED_MATRIX_SIZE - cursor_start_index; + for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) { + uint8_t c = pgm_read_byte(data++); + if (oled_buffer[i] == c) continue; + oled_buffer[i] = c; + oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE)); + } +} +#endif // defined(__AVR__) + +bool oled_on(void) { + if (!oled_initialized) { + return oled_active; + } + +#if OLED_TIMEOUT > 0 + oled_timeout = timer_read32() + OLED_TIMEOUT; +#endif + + static const uint8_t PROGMEM display_on[] = +#ifdef OLED_FADE_OUT + {I2C_CMD, FADE_BLINK, 0x00}; +#else + {I2C_CMD, DISPLAY_ON}; +#endif + + if (!oled_active) { + if (!oled_send_cmd_P(display_on, ARRAY_SIZE(display_on))) { + print("oled_on cmd failed\n"); + return oled_active; + } + oled_active = true; + } + return oled_active; +} + +bool oled_off(void) { + if (!oled_initialized) { + return !oled_active; + } + + static const uint8_t PROGMEM display_off[] = +#ifdef OLED_FADE_OUT + {I2C_CMD, FADE_BLINK, ENABLE_FADE | OLED_FADE_OUT_INTERVAL}; +#else + {I2C_CMD, DISPLAY_OFF}; +#endif + + if (oled_active) { + if (!oled_send_cmd_P(display_off, ARRAY_SIZE(display_off))) { + print("oled_off cmd failed\n"); + return oled_active; + } + oled_active = false; + } + return !oled_active; +} + +bool is_oled_on(void) { + return oled_active; +} + +uint8_t oled_set_brightness(uint8_t level) { + if (!oled_initialized) { + return oled_brightness; + } + + uint8_t set_contrast[] = {I2C_CMD, CONTRAST, level}; + if (oled_brightness != level) { + if (!oled_send_cmd(set_contrast, ARRAY_SIZE(set_contrast))) { + print("set_brightness cmd failed\n"); + return oled_brightness; + } + oled_brightness = level; + } + return oled_brightness; +} + +uint8_t oled_get_brightness(void) { + return oled_brightness; +} + +// Set the specific 8 lines rows of the screen to scroll. +// 0 is the default for start, and 7 for end, which is the entire +// height of the screen. For 128x32 screens, rows 4-7 are not used. +void oled_scroll_set_area(uint8_t start_line, uint8_t end_line) { + oled_scroll_start = start_line; + oled_scroll_end = end_line; +} + +void oled_scroll_set_speed(uint8_t speed) { + // Sets the speed for scrolling... does not take effect + // until scrolling is either started or restarted + // the ssd1306 supports 8 speeds + // FrameRate2 speed = 7 + // FrameRate3 speed = 4 + // FrameRate4 speed = 5 + // FrameRate5 speed = 0 + // FrameRate25 speed = 6 + // FrameRate64 speed = 1 + // FrameRate128 speed = 2 + // FrameRate256 speed = 3 + // for ease of use these are remaped here to be in order + static const uint8_t scroll_remap[8] = {7, 4, 5, 0, 6, 1, 2, 3}; + oled_scroll_speed = scroll_remap[speed]; +} + +bool oled_scroll_right(void) { + if (!oled_initialized) { + return oled_scrolling; + } + + // Dont enable scrolling if we need to update the display + // This prevents scrolling of bad data from starting the scroll too early after init + if (!oled_dirty && !oled_scrolling) { + uint8_t display_scroll_right[] = {I2C_CMD, SCROLL_RIGHT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL}; + if (!oled_send_cmd(display_scroll_right, ARRAY_SIZE(display_scroll_right))) { + print("oled_scroll_right cmd failed\n"); + return oled_scrolling; + } + oled_scrolling = true; + } + return oled_scrolling; +} + +bool oled_scroll_left(void) { + if (!oled_initialized) { + return oled_scrolling; + } + + // Dont enable scrolling if we need to update the display + // This prevents scrolling of bad data from starting the scroll too early after init + if (!oled_dirty && !oled_scrolling) { + uint8_t display_scroll_left[] = {I2C_CMD, SCROLL_LEFT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL}; + if (!oled_send_cmd(display_scroll_left, ARRAY_SIZE(display_scroll_left))) { + print("oled_scroll_left cmd failed\n"); + return oled_scrolling; + } + oled_scrolling = true; + } + return oled_scrolling; +} + +bool oled_scroll_off(void) { + if (!oled_initialized) { + return !oled_scrolling; + } + + if (oled_scrolling) { + static const uint8_t PROGMEM display_scroll_off[] = {I2C_CMD, DEACTIVATE_SCROLL}; + if (!oled_send_cmd_P(display_scroll_off, ARRAY_SIZE(display_scroll_off))) { + print("oled_scroll_off cmd failed\n"); + return oled_scrolling; + } + oled_scrolling = false; + oled_dirty = OLED_ALL_BLOCKS_MASK; + } + return !oled_scrolling; +} + +bool is_oled_scrolling(void) { + return oled_scrolling; +} + +bool oled_invert(bool invert) { + if (!oled_initialized) { + return oled_inverted; + } + + if (invert && !oled_inverted) { + static const uint8_t PROGMEM display_inverted[] = {I2C_CMD, INVERT_DISPLAY}; + if (!oled_send_cmd_P(display_inverted, ARRAY_SIZE(display_inverted))) { + print("oled_invert cmd failed\n"); + return oled_inverted; + } + oled_inverted = true; + } else if (!invert && oled_inverted) { + static const uint8_t PROGMEM display_normal[] = {I2C_CMD, NORMAL_DISPLAY}; + if (!oled_send_cmd_P(display_normal, ARRAY_SIZE(display_normal))) { + print("oled_invert cmd failed\n"); + return oled_inverted; + } + oled_inverted = false; + } + + return oled_inverted; +} + +uint8_t oled_max_chars(void) { + if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { + return OLED_DISPLAY_WIDTH / OLED_FONT_WIDTH; + } + return OLED_DISPLAY_HEIGHT / OLED_FONT_WIDTH; +} + +uint8_t oled_max_lines(void) { + if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { + return OLED_DISPLAY_HEIGHT / OLED_FONT_HEIGHT; + } + return OLED_DISPLAY_WIDTH / OLED_FONT_HEIGHT; +} + +void oled_task(void) { + if (!oled_initialized) { + return; + } + +#if OLED_UPDATE_INTERVAL > 0 + if (timer_elapsed(oled_update_timeout) >= OLED_UPDATE_INTERVAL) { + oled_update_timeout = timer_read(); + oled_set_cursor(0, 0); + oled_task_kb(); + } +#else + oled_set_cursor(0, 0); + oled_task_kb(); +#endif + +#if OLED_SCROLL_TIMEOUT > 0 + if (oled_dirty && oled_scrolling) { + oled_scroll_timeout = timer_read32() + OLED_SCROLL_TIMEOUT; + oled_scroll_off(); + } +#endif + + // Smart render system, no need to check for dirty + oled_render(); + + // Display timeout check +#if OLED_TIMEOUT > 0 + if (oled_active && timer_expired32(timer_read32(), oled_timeout)) { + oled_off(); + } +#endif + +#if OLED_SCROLL_TIMEOUT > 0 + if (!oled_scrolling && timer_expired32(timer_read32(), oled_scroll_timeout)) { +# ifdef OLED_SCROLL_TIMEOUT_RIGHT + oled_scroll_right(); +# else + oled_scroll_left(); +# endif + } +#endif +} + +__attribute__((weak)) bool oled_task_kb(void) { + return oled_task_user(); +} +__attribute__((weak)) bool oled_task_user(void) { + return true; +} diff --git a/drivers/oled/oled_driver.h b/drivers/oled/oled_driver.h index 291049e36b..627a3da0ba 100644 --- a/drivers/oled/oled_driver.h +++ b/drivers/oled/oled_driver.h @@ -22,6 +22,7 @@ along with this program. If not, see . // an enumeration of the chips this driver supports #define OLED_IC_SSD1306 0 #define OLED_IC_SH1106 1 +#define OLED_IC_SH1107 2 #if defined(OLED_DISPLAY_CUSTOM) // Expected user to implement the necessary defines @@ -68,6 +69,152 @@ along with this program. If not, see . // If OLED_BLOCK_TYPE is uint8_t, these tables would look like: // #define OLED_SOURCE_MAP { 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120 } // #define OLED_TARGET_MAP { 56, 120, 48, 112, 40, 104, 32, 96, 24, 88, 16, 80, 8, 72, 0, 64 } + +#elif defined(OLED_DISPLAY_64X32) +# ifndef OLED_DISPLAY_WIDTH +# define OLED_DISPLAY_WIDTH 64 +# endif +# ifndef OLED_DISPLAY_HEIGHT +# define OLED_DISPLAY_HEIGHT 32 +# endif +# ifndef OLED_COLUMN_OFFSET +# define OLED_COLUMN_OFFSET 32 +# endif +# ifndef OLED_MATRIX_SIZE +# define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) +# endif +# ifndef OLED_BLOCK_TYPE +# define OLED_BLOCK_TYPE uint8_t +# endif +# ifndef OLED_BLOCK_COUNT +# define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) // 8 (compile time mathed) +# endif +# ifndef OLED_BLOCK_SIZE +# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) // 32 (compile time mathed) +# endif +# ifndef OLED_COM_PINS +# define OLED_COM_PINS COM_PINS_ALT +# endif + +# ifndef OLED_SOURCE_MAP +# define OLED_SOURCE_MAP \ + { 0, 8, 16, 24 } +# endif +# ifndef OLED_TARGET_MAP +# define OLED_TARGET_MAP \ + { 24, 16, 8, 0 } +# endif + +#elif defined(OLED_DISPLAY_64X48) +# ifndef OLED_DISPLAY_WIDTH +# define OLED_DISPLAY_WIDTH 64 +# endif +# ifndef OLED_DISPLAY_HEIGHT +# define OLED_DISPLAY_HEIGHT 48 +# endif +# ifndef OLED_COLUMN_OFFSET +# define OLED_COLUMN_OFFSET 32 +# endif +# ifndef OLED_MATRIX_SIZE +# define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) +# endif +# ifndef OLED_BLOCK_TYPE +# define OLED_BLOCK_TYPE uint32_t +# endif +# ifndef OLED_BLOCK_COUNT +# define OLED_BLOCK_COUNT 24 +# endif +# ifndef OLED_BLOCK_SIZE +# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) +# endif +# ifndef OLED_COM_PINS +# define OLED_COM_PINS COM_PINS_ALT +# endif + +# ifndef OLED_SOURCE_MAP +# define OLED_SOURCE_MAP \ + { 0, 8 } +# endif +# ifndef OLED_TARGET_MAP +# define OLED_TARGET_MAP \ + { 8, 0 } +# endif + +#elif defined(OLED_DISPLAY_64X128) +# ifndef OLED_DISPLAY_WIDTH +# define OLED_DISPLAY_WIDTH 64 +# endif +# ifndef OLED_DISPLAY_HEIGHT +# define OLED_DISPLAY_HEIGHT 128 +# endif +# ifndef OLED_IC +# define OLED_IC OLED_IC_SH1107 +# endif +# ifndef OLED_COM_PIN_OFFSET +# define OLED_COM_PIN_OFFSET 32 +# endif +# ifndef OLED_MATRIX_SIZE +# define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) +# endif +# ifndef OLED_BLOCK_TYPE +# define OLED_BLOCK_TYPE uint16_t +# endif +# ifndef OLED_BLOCK_COUNT +# define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) +# endif +# ifndef OLED_BLOCK_SIZE +# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) +# endif +# ifndef OLED_COM_PINS +# define OLED_COM_PINS COM_PINS_ALT +# endif + +# ifndef OLED_SOURCE_MAP +# define OLED_SOURCE_MAP \ + { 0, 8, 16, 24, 32, 40, 48, 56 } +# endif +# ifndef OLED_TARGET_MAP +# define OLED_TARGET_MAP \ + { 56, 48, 40, 32, 24, 16, 8, 0 } +# endif + +#elif defined(OLED_DISPLAY_128X128) +// Quad height 128x128 +# ifndef OLED_DISPLAY_WIDTH +# define OLED_DISPLAY_WIDTH 128 +# endif +# ifndef OLED_DISPLAY_HEIGHT +# define OLED_DISPLAY_HEIGHT 128 +# endif +# ifndef OLED_IC +# define OLED_IC OLED_IC_SH1107 +# endif +# ifndef OLED_MATRIX_SIZE +# define OLED_MATRIX_SIZE (OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH) // 2048 (compile time mathed) +# endif +# ifndef OLED_BLOCK_TYPE +# define OLED_BLOCK_TYPE uint32_t +# endif +# ifndef OLED_BLOCK_COUNT +# define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) // 32 (compile time mathed) +# endif +# ifndef OLED_BLOCK_SIZE +# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) // 64 (compile time mathed) +# endif +# ifndef OLED_COM_PINS +# define OLED_COM_PINS COM_PINS_ALT +# endif + +// For 90 degree rotation, we map our internal matrix to oled matrix using fixed arrays +// The OLED writes to it's memory horizontally, starting top left, but our memory starts bottom left in this mode +# ifndef OLED_SOURCE_MAP +# define OLED_SOURCE_MAP \ + { 0, 8, 16, 24, 32, 40, 48, 56 } +# endif +# ifndef OLED_TARGET_MAP +# define OLED_TARGET_MAP \ + { 56, 48, 40, 32, 24, 16, 8, 0 } +# endif #else // defined(OLED_DISPLAY_128X64) // Default 128x32 # ifndef OLED_DISPLAY_WIDTH @@ -191,6 +338,12 @@ typedef enum { // Returns true if the OLED was initialized successfully bool oled_init(oled_rotation_t rotation); +// Send commands and data to screen +bool oled_send_cmd(const uint8_t *data, uint16_t size); +bool oled_send_cmd_P(const uint8_t *data, uint16_t size); +bool oled_send_data(const uint8_t *data, uint16_t size); +void oled_driver_init(void); + // Called at the start of oled_init, weak function overridable by the user // rotation - the value passed into oled_init // Return new oled_rotation_t if you want to override default rotation diff --git a/drivers/oled/ssd1306_sh1106.c b/drivers/oled/ssd1306_sh1106.c deleted file mode 100644 index 342920572e..0000000000 --- a/drivers/oled/ssd1306_sh1106.c +++ /dev/null @@ -1,795 +0,0 @@ -/* -Copyright 2019 Ryan Caltabiano - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -#include "i2c_master.h" -#include "oled_driver.h" -#include OLED_FONT_H -#include "timer.h" -#include "print.h" - -#include - -#include "progmem.h" - -#include "keyboard.h" - -// Used commands from spec sheet: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf -// for SH1106: https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf - -// Fundamental Commands -#define CONTRAST 0x81 -#define DISPLAY_ALL_ON 0xA5 -#define DISPLAY_ALL_ON_RESUME 0xA4 -#define NORMAL_DISPLAY 0xA6 -#define INVERT_DISPLAY 0xA7 -#define DISPLAY_ON 0xAF -#define DISPLAY_OFF 0xAE -#define NOP 0xE3 - -// Scrolling Commands -#define ACTIVATE_SCROLL 0x2F -#define DEACTIVATE_SCROLL 0x2E -#define SCROLL_RIGHT 0x26 -#define SCROLL_LEFT 0x27 -#define SCROLL_RIGHT_UP 0x29 -#define SCROLL_LEFT_UP 0x2A - -// Addressing Setting Commands -#define MEMORY_MODE 0x20 -#define COLUMN_ADDR 0x21 -#define PAGE_ADDR 0x22 -#define PAM_SETCOLUMN_LSB 0x00 -#define PAM_SETCOLUMN_MSB 0x10 -#define PAM_PAGE_ADDR 0xB0 // 0xb0 -- 0xb7 - -// Hardware Configuration Commands -#define DISPLAY_START_LINE 0x40 -#define SEGMENT_REMAP 0xA0 -#define SEGMENT_REMAP_INV 0xA1 -#define MULTIPLEX_RATIO 0xA8 -#define COM_SCAN_INC 0xC0 -#define COM_SCAN_DEC 0xC8 -#define DISPLAY_OFFSET 0xD3 -#define COM_PINS 0xDA -#define COM_PINS_SEQ 0x02 -#define COM_PINS_ALT 0x12 -#define COM_PINS_SEQ_LR 0x22 -#define COM_PINS_ALT_LR 0x32 - -// Timing & Driving Commands -#define DISPLAY_CLOCK 0xD5 -#define PRE_CHARGE_PERIOD 0xD9 -#define VCOM_DETECT 0xDB - -// Advance Graphic Commands -#define FADE_BLINK 0x23 -#define ENABLE_FADE 0x20 -#define ENABLE_BLINK 0x30 - -// Charge Pump Commands -#define CHARGE_PUMP 0x8D - -// Misc defines -#ifndef OLED_BLOCK_COUNT -# define OLED_BLOCK_COUNT (sizeof(OLED_BLOCK_TYPE) * 8) -#endif -#ifndef OLED_BLOCK_SIZE -# define OLED_BLOCK_SIZE (OLED_MATRIX_SIZE / OLED_BLOCK_COUNT) -#endif - -#define OLED_ALL_BLOCKS_MASK (((((OLED_BLOCK_TYPE)1 << (OLED_BLOCK_COUNT - 1)) - 1) << 1) | 1) - -// i2c defines -#define I2C_CMD 0x00 -#define I2C_DATA 0x40 -#if defined(__AVR__) -# define I2C_TRANSMIT_P(data) i2c_transmit_P((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), OLED_I2C_TIMEOUT) -#else // defined(__AVR__) -# define I2C_TRANSMIT_P(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), OLED_I2C_TIMEOUT) -#endif // defined(__AVR__) -#define I2C_TRANSMIT(data) i2c_transmit((OLED_DISPLAY_ADDRESS << 1), &data[0], sizeof(data), OLED_I2C_TIMEOUT) -#define I2C_WRITE_REG(mode, data, size) i2c_writeReg((OLED_DISPLAY_ADDRESS << 1), mode, data, size, OLED_I2C_TIMEOUT) - -#define HAS_FLAGS(bits, flags) ((bits & flags) == flags) - -// Display buffer's is the same as the OLED memory layout -// this is so we don't end up with rounding errors with -// parts of the display unusable or don't get cleared correctly -// and also allows for drawing & inverting -uint8_t oled_buffer[OLED_MATRIX_SIZE]; -uint8_t * oled_cursor; -OLED_BLOCK_TYPE oled_dirty = 0; -bool oled_initialized = false; -bool oled_active = false; -bool oled_scrolling = false; -bool oled_inverted = false; -uint8_t oled_brightness = OLED_BRIGHTNESS; -oled_rotation_t oled_rotation = 0; -uint8_t oled_rotation_width = 0; -uint8_t oled_scroll_speed = 0; // this holds the speed after being remapped to ssd1306 internal values -uint8_t oled_scroll_start = 0; -uint8_t oled_scroll_end = 7; -#if OLED_TIMEOUT > 0 -uint32_t oled_timeout; -#endif -#if OLED_SCROLL_TIMEOUT > 0 -uint32_t oled_scroll_timeout; -#endif -#if OLED_UPDATE_INTERVAL > 0 -uint16_t oled_update_timeout; -#endif - -// Internal variables to reduce math instructions - -#if defined(__AVR__) -// identical to i2c_transmit, but for PROGMEM since all initialization is in PROGMEM arrays currently -// probably should move this into i2c_master... -static i2c_status_t i2c_transmit_P(uint8_t address, const uint8_t *data, uint16_t length, uint16_t timeout) { - i2c_status_t status = i2c_start(address | I2C_WRITE, timeout); - - for (uint16_t i = 0; i < length && status >= 0; i++) { - status = i2c_write(pgm_read_byte((const char *)data++), timeout); - if (status) break; - } - - i2c_stop(); - - return status; -} -#endif - -// Flips the rendering bits for a character at the current cursor position -static void InvertCharacter(uint8_t *cursor) { - const uint8_t *end = cursor + OLED_FONT_WIDTH; - while (cursor < end) { - *cursor = ~(*cursor); - cursor++; - } -} - -bool oled_init(oled_rotation_t rotation) { -#if defined(USE_I2C) && defined(SPLIT_KEYBOARD) - if (!is_keyboard_master()) { - return true; - } -#endif - - oled_rotation = oled_init_user(oled_init_kb(rotation)); - if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { - oled_rotation_width = OLED_DISPLAY_WIDTH; - } else { - oled_rotation_width = OLED_DISPLAY_HEIGHT; - } - i2c_init(); - - static const uint8_t PROGMEM display_setup1[] = { - I2C_CMD, - DISPLAY_OFF, - DISPLAY_CLOCK, - 0x80, - MULTIPLEX_RATIO, - OLED_DISPLAY_HEIGHT - 1, - DISPLAY_OFFSET, - 0x00, - DISPLAY_START_LINE | 0x00, - CHARGE_PUMP, - 0x14, -#if (OLED_IC != OLED_IC_SH1106) - // MEMORY_MODE is unsupported on SH1106 (Page Addressing only) - MEMORY_MODE, - 0x00, // Horizontal addressing mode -#endif - }; - if (I2C_TRANSMIT_P(display_setup1) != I2C_STATUS_SUCCESS) { - print("oled_init cmd set 1 failed\n"); - return false; - } - - if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_180)) { - static const uint8_t PROGMEM display_normal[] = {I2C_CMD, SEGMENT_REMAP_INV, COM_SCAN_DEC}; - if (I2C_TRANSMIT_P(display_normal) != I2C_STATUS_SUCCESS) { - print("oled_init cmd normal rotation failed\n"); - return false; - } - } else { - static const uint8_t PROGMEM display_flipped[] = {I2C_CMD, SEGMENT_REMAP, COM_SCAN_INC}; - if (I2C_TRANSMIT_P(display_flipped) != I2C_STATUS_SUCCESS) { - print("display_flipped failed\n"); - return false; - } - } - - static const uint8_t PROGMEM display_setup2[] = {I2C_CMD, COM_PINS, OLED_COM_PINS, CONTRAST, OLED_BRIGHTNESS, PRE_CHARGE_PERIOD, 0xF1, VCOM_DETECT, 0x20, DISPLAY_ALL_ON_RESUME, NORMAL_DISPLAY, DEACTIVATE_SCROLL, DISPLAY_ON}; - if (I2C_TRANSMIT_P(display_setup2) != I2C_STATUS_SUCCESS) { - print("display_setup2 failed\n"); - return false; - } - -#if OLED_TIMEOUT > 0 - oled_timeout = timer_read32() + OLED_TIMEOUT; -#endif -#if OLED_SCROLL_TIMEOUT > 0 - oled_scroll_timeout = timer_read32() + OLED_SCROLL_TIMEOUT; -#endif - - oled_clear(); - oled_initialized = true; - oled_active = true; - oled_scrolling = false; - return true; -} - -__attribute__((weak)) oled_rotation_t oled_init_kb(oled_rotation_t rotation) { - return rotation; -} -__attribute__((weak)) oled_rotation_t oled_init_user(oled_rotation_t rotation) { - return rotation; -} - -void oled_clear(void) { - memset(oled_buffer, 0, sizeof(oled_buffer)); - oled_cursor = &oled_buffer[0]; - oled_dirty = OLED_ALL_BLOCKS_MASK; -} - -static void calc_bounds(uint8_t update_start, uint8_t *cmd_array) { - // Calculate commands to set memory addressing bounds. - uint8_t start_page = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_WIDTH; - uint8_t start_column = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_WIDTH; -#if (OLED_IC == OLED_IC_SH1106) - // Commands for Page Addressing Mode. Sets starting page and column; has no end bound. - // Column value must be split into high and low nybble and sent as two commands. - cmd_array[0] = PAM_PAGE_ADDR | start_page; - cmd_array[1] = PAM_SETCOLUMN_LSB | ((OLED_COLUMN_OFFSET + start_column) & 0x0f); - cmd_array[2] = PAM_SETCOLUMN_MSB | ((OLED_COLUMN_OFFSET + start_column) >> 4 & 0x0f); - cmd_array[3] = NOP; - cmd_array[4] = NOP; - cmd_array[5] = NOP; -#else - // Commands for use in Horizontal Addressing mode. - cmd_array[1] = start_column; - cmd_array[4] = start_page; - cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) % OLED_DISPLAY_WIDTH + cmd_array[1]; - cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_WIDTH - 1) / OLED_DISPLAY_WIDTH - 1; -#endif -} - -static void calc_bounds_90(uint8_t update_start, uint8_t *cmd_array) { - cmd_array[1] = OLED_BLOCK_SIZE * update_start / OLED_DISPLAY_HEIGHT * 8; - cmd_array[4] = OLED_BLOCK_SIZE * update_start % OLED_DISPLAY_HEIGHT; - cmd_array[2] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) / OLED_DISPLAY_HEIGHT * 8 - 1 + cmd_array[1]; - ; - cmd_array[5] = (OLED_BLOCK_SIZE + OLED_DISPLAY_HEIGHT - 1) % OLED_DISPLAY_HEIGHT / 8; -} - -uint8_t crot(uint8_t a, int8_t n) { - const uint8_t mask = 0x7; - n &= mask; - return a << n | a >> (-n & mask); -} - -static void rotate_90(const uint8_t *src, uint8_t *dest) { - for (uint8_t i = 0, shift = 7; i < 8; ++i, --shift) { - uint8_t selector = (1 << i); - for (uint8_t j = 0; j < 8; ++j) { - dest[i] |= crot(src[j] & selector, shift - (int8_t)j); - } - } -} - -void oled_render(void) { - // Do we have work to do? - oled_dirty &= OLED_ALL_BLOCKS_MASK; - if (!oled_dirty || !oled_initialized || oled_scrolling) { - return; - } - - // Turn on display if it is off - oled_on(); - - uint8_t update_start = 0; - uint8_t num_processed = 0; - while (oled_dirty && num_processed++ < OLED_UPDATE_PROCESS_LIMIT) { // render all dirty blocks (up to the configured limit) - // Find next dirty block - while (!(oled_dirty & ((OLED_BLOCK_TYPE)1 << update_start))) { - ++update_start; - } - - // Set column & page position - static uint8_t display_start[] = {I2C_CMD, COLUMN_ADDR, 0, OLED_DISPLAY_WIDTH - 1, PAGE_ADDR, 0, OLED_DISPLAY_HEIGHT / 8 - 1}; - if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { - calc_bounds(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start - } else { - calc_bounds_90(update_start, &display_start[1]); // Offset from I2C_CMD byte at the start - } - - // Send column & page position - if (I2C_TRANSMIT(display_start) != I2C_STATUS_SUCCESS) { - print("oled_render offset command failed\n"); - return; - } - - if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { - // Send render data chunk as is - if (I2C_WRITE_REG(I2C_DATA, &oled_buffer[OLED_BLOCK_SIZE * update_start], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) { - print("oled_render data failed\n"); - return; - } - } else { - // Rotate the render chunks - const static uint8_t source_map[] = OLED_SOURCE_MAP; - const static uint8_t target_map[] = OLED_TARGET_MAP; - - static uint8_t temp_buffer[OLED_BLOCK_SIZE]; - memset(temp_buffer, 0, sizeof(temp_buffer)); - for (uint8_t i = 0; i < sizeof(source_map); ++i) { - rotate_90(&oled_buffer[OLED_BLOCK_SIZE * update_start + source_map[i]], &temp_buffer[target_map[i]]); - } - - // Send render data chunk after rotating - if (I2C_WRITE_REG(I2C_DATA, &temp_buffer[0], OLED_BLOCK_SIZE) != I2C_STATUS_SUCCESS) { - print("oled_render90 data failed\n"); - return; - } - } - - // Clear dirty flag of just rendered block - oled_dirty &= ~((OLED_BLOCK_TYPE)1 << update_start); - } -} - -void oled_set_cursor(uint8_t col, uint8_t line) { - uint16_t index = line * oled_rotation_width + col * OLED_FONT_WIDTH; - - // Out of bounds? - if (index >= OLED_MATRIX_SIZE) { - index = 0; - } - - oled_cursor = &oled_buffer[index]; -} - -void oled_advance_page(bool clearPageRemainder) { - uint16_t index = oled_cursor - &oled_buffer[0]; - uint8_t remaining = oled_rotation_width - (index % oled_rotation_width); - - if (clearPageRemainder) { - // Remaining Char count - remaining = remaining / OLED_FONT_WIDTH; - - // Write empty character until next line - while (remaining--) - oled_write_char(' ', false); - } else { - // Next page index out of bounds? - if (index + remaining >= OLED_MATRIX_SIZE) { - index = 0; - remaining = 0; - } - - oled_cursor = &oled_buffer[index + remaining]; - } -} - -void oled_advance_char(void) { - uint16_t nextIndex = oled_cursor - &oled_buffer[0] + OLED_FONT_WIDTH; - uint8_t remainingSpace = oled_rotation_width - (nextIndex % oled_rotation_width); - - // Do we have enough space on the current line for the next character - if (remainingSpace < OLED_FONT_WIDTH) { - nextIndex += remainingSpace; - } - - // Did we go out of bounds - if (nextIndex >= OLED_MATRIX_SIZE) { - nextIndex = 0; - } - - // Update cursor position - oled_cursor = &oled_buffer[nextIndex]; -} - -// Main handler that writes character data to the display buffer -void oled_write_char(const char data, bool invert) { - // Advance to the next line if newline - if (data == '\n') { - // Old source wrote ' ' until end of line... - oled_advance_page(true); - return; - } - - if (data == '\r') { - oled_advance_page(false); - return; - } - - // copy the current render buffer to check for dirty after - static uint8_t oled_temp_buffer[OLED_FONT_WIDTH]; - memcpy(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH); - - _Static_assert(sizeof(font) >= ((OLED_FONT_END + 1 - OLED_FONT_START) * OLED_FONT_WIDTH), "OLED_FONT_END references outside array"); - - // set the reder buffer data - uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index - if (cast_data < OLED_FONT_START || cast_data > OLED_FONT_END) { - memset(oled_cursor, 0x00, OLED_FONT_WIDTH); - } else { - const uint8_t *glyph = &font[(cast_data - OLED_FONT_START) * OLED_FONT_WIDTH]; - memcpy_P(oled_cursor, glyph, OLED_FONT_WIDTH); - } - - // Invert if needed - if (invert) { - InvertCharacter(oled_cursor); - } - - // Dirty check - if (memcmp(&oled_temp_buffer, oled_cursor, OLED_FONT_WIDTH)) { - uint16_t index = oled_cursor - &oled_buffer[0]; - oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); - // Edgecase check if the written data spans the 2 chunks - oled_dirty |= ((OLED_BLOCK_TYPE)1 << ((index + OLED_FONT_WIDTH - 1) / OLED_BLOCK_SIZE)); - } - - // Finally move to the next char - oled_advance_char(); -} - -void oled_write(const char *data, bool invert) { - const char *end = data + strlen(data); - while (data < end) { - oled_write_char(*data, invert); - data++; - } -} - -void oled_write_ln(const char *data, bool invert) { - oled_write(data, invert); - oled_advance_page(true); -} - -void oled_pan(bool left) { - uint16_t i = 0; - for (uint16_t y = 0; y < OLED_DISPLAY_HEIGHT / 8; y++) { - if (left) { - for (uint16_t x = 0; x < OLED_DISPLAY_WIDTH - 1; x++) { - i = y * OLED_DISPLAY_WIDTH + x; - oled_buffer[i] = oled_buffer[i + 1]; - } - } else { - for (uint16_t x = OLED_DISPLAY_WIDTH - 1; x > 0; x--) { - i = y * OLED_DISPLAY_WIDTH + x; - oled_buffer[i] = oled_buffer[i - 1]; - } - } - } - oled_dirty = OLED_ALL_BLOCKS_MASK; -} - -oled_buffer_reader_t oled_read_raw(uint16_t start_index) { - if (start_index > OLED_MATRIX_SIZE) start_index = OLED_MATRIX_SIZE; - oled_buffer_reader_t ret_reader; - ret_reader.current_element = &oled_buffer[start_index]; - ret_reader.remaining_element_count = OLED_MATRIX_SIZE - start_index; - return ret_reader; -} - -void oled_write_raw_byte(const char data, uint16_t index) { - if (index > OLED_MATRIX_SIZE) index = OLED_MATRIX_SIZE; - if (oled_buffer[index] == data) return; - oled_buffer[index] = data; - oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); -} - -void oled_write_raw(const char *data, uint16_t size) { - uint16_t cursor_start_index = oled_cursor - &oled_buffer[0]; - if ((size + cursor_start_index) > OLED_MATRIX_SIZE) size = OLED_MATRIX_SIZE - cursor_start_index; - for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) { - uint8_t c = *data++; - if (oled_buffer[i] == c) continue; - oled_buffer[i] = c; - oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE)); - } -} - -void oled_write_pixel(uint8_t x, uint8_t y, bool on) { - if (x >= oled_rotation_width) { - return; - } - uint16_t index = x + (y / 8) * oled_rotation_width; - if (index >= OLED_MATRIX_SIZE) { - return; - } - uint8_t data = oled_buffer[index]; - if (on) { - data |= (1 << (y % 8)); - } else { - data &= ~(1 << (y % 8)); - } - if (oled_buffer[index] != data) { - oled_buffer[index] = data; - oled_dirty |= ((OLED_BLOCK_TYPE)1 << (index / OLED_BLOCK_SIZE)); - } -} - -#if defined(__AVR__) -void oled_write_P(const char *data, bool invert) { - uint8_t c = pgm_read_byte(data); - while (c != 0) { - oled_write_char(c, invert); - c = pgm_read_byte(++data); - } -} - -void oled_write_ln_P(const char *data, bool invert) { - oled_write_P(data, invert); - oled_advance_page(true); -} - -void oled_write_raw_P(const char *data, uint16_t size) { - uint16_t cursor_start_index = oled_cursor - &oled_buffer[0]; - if ((size + cursor_start_index) > OLED_MATRIX_SIZE) size = OLED_MATRIX_SIZE - cursor_start_index; - for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) { - uint8_t c = pgm_read_byte(data++); - if (oled_buffer[i] == c) continue; - oled_buffer[i] = c; - oled_dirty |= ((OLED_BLOCK_TYPE)1 << (i / OLED_BLOCK_SIZE)); - } -} -#endif // defined(__AVR__) - -bool oled_on(void) { - if (!oled_initialized) { - return oled_active; - } - -#if OLED_TIMEOUT > 0 - oled_timeout = timer_read32() + OLED_TIMEOUT; -#endif - - static const uint8_t PROGMEM display_on[] = -#ifdef OLED_FADE_OUT - {I2C_CMD, FADE_BLINK, 0x00}; -#else - {I2C_CMD, DISPLAY_ON}; -#endif - - if (!oled_active) { - if (I2C_TRANSMIT_P(display_on) != I2C_STATUS_SUCCESS) { - print("oled_on cmd failed\n"); - return oled_active; - } - oled_active = true; - } - return oled_active; -} - -bool oled_off(void) { - if (!oled_initialized) { - return !oled_active; - } - - static const uint8_t PROGMEM display_off[] = -#ifdef OLED_FADE_OUT - {I2C_CMD, FADE_BLINK, ENABLE_FADE | OLED_FADE_OUT_INTERVAL}; -#else - {I2C_CMD, DISPLAY_OFF}; -#endif - - if (oled_active) { - if (I2C_TRANSMIT_P(display_off) != I2C_STATUS_SUCCESS) { - print("oled_off cmd failed\n"); - return oled_active; - } - oled_active = false; - } - return !oled_active; -} - -bool is_oled_on(void) { - return oled_active; -} - -uint8_t oled_set_brightness(uint8_t level) { - if (!oled_initialized) { - return oled_brightness; - } - - uint8_t set_contrast[] = {I2C_CMD, CONTRAST, level}; - if (oled_brightness != level) { - if (I2C_TRANSMIT(set_contrast) != I2C_STATUS_SUCCESS) { - print("set_brightness cmd failed\n"); - return oled_brightness; - } - oled_brightness = level; - } - return oled_brightness; -} - -uint8_t oled_get_brightness(void) { - return oled_brightness; -} - -// Set the specific 8 lines rows of the screen to scroll. -// 0 is the default for start, and 7 for end, which is the entire -// height of the screen. For 128x32 screens, rows 4-7 are not used. -void oled_scroll_set_area(uint8_t start_line, uint8_t end_line) { - oled_scroll_start = start_line; - oled_scroll_end = end_line; -} - -void oled_scroll_set_speed(uint8_t speed) { - // Sets the speed for scrolling... does not take effect - // until scrolling is either started or restarted - // the ssd1306 supports 8 speeds - // FrameRate2 speed = 7 - // FrameRate3 speed = 4 - // FrameRate4 speed = 5 - // FrameRate5 speed = 0 - // FrameRate25 speed = 6 - // FrameRate64 speed = 1 - // FrameRate128 speed = 2 - // FrameRate256 speed = 3 - // for ease of use these are remaped here to be in order - static const uint8_t scroll_remap[8] = {7, 4, 5, 0, 6, 1, 2, 3}; - oled_scroll_speed = scroll_remap[speed]; -} - -bool oled_scroll_right(void) { - if (!oled_initialized) { - return oled_scrolling; - } - - // Dont enable scrolling if we need to update the display - // This prevents scrolling of bad data from starting the scroll too early after init - if (!oled_dirty && !oled_scrolling) { - uint8_t display_scroll_right[] = {I2C_CMD, SCROLL_RIGHT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL}; - if (I2C_TRANSMIT(display_scroll_right) != I2C_STATUS_SUCCESS) { - print("oled_scroll_right cmd failed\n"); - return oled_scrolling; - } - oled_scrolling = true; - } - return oled_scrolling; -} - -bool oled_scroll_left(void) { - if (!oled_initialized) { - return oled_scrolling; - } - - // Dont enable scrolling if we need to update the display - // This prevents scrolling of bad data from starting the scroll too early after init - if (!oled_dirty && !oled_scrolling) { - uint8_t display_scroll_left[] = {I2C_CMD, SCROLL_LEFT, 0x00, oled_scroll_start, oled_scroll_speed, oled_scroll_end, 0x00, 0xFF, ACTIVATE_SCROLL}; - if (I2C_TRANSMIT(display_scroll_left) != I2C_STATUS_SUCCESS) { - print("oled_scroll_left cmd failed\n"); - return oled_scrolling; - } - oled_scrolling = true; - } - return oled_scrolling; -} - -bool oled_scroll_off(void) { - if (!oled_initialized) { - return !oled_scrolling; - } - - if (oled_scrolling) { - static const uint8_t PROGMEM display_scroll_off[] = {I2C_CMD, DEACTIVATE_SCROLL}; - if (I2C_TRANSMIT_P(display_scroll_off) != I2C_STATUS_SUCCESS) { - print("oled_scroll_off cmd failed\n"); - return oled_scrolling; - } - oled_scrolling = false; - oled_dirty = OLED_ALL_BLOCKS_MASK; - } - return !oled_scrolling; -} - -bool is_oled_scrolling(void) { - return oled_scrolling; -} - -bool oled_invert(bool invert) { - if (!oled_initialized) { - return oled_inverted; - } - - if (invert && !oled_inverted) { - static const uint8_t PROGMEM display_inverted[] = {I2C_CMD, INVERT_DISPLAY}; - if (I2C_TRANSMIT_P(display_inverted) != I2C_STATUS_SUCCESS) { - print("oled_invert cmd failed\n"); - return oled_inverted; - } - oled_inverted = true; - } else if (!invert && oled_inverted) { - static const uint8_t PROGMEM display_normal[] = {I2C_CMD, NORMAL_DISPLAY}; - if (I2C_TRANSMIT_P(display_normal) != I2C_STATUS_SUCCESS) { - print("oled_invert cmd failed\n"); - return oled_inverted; - } - oled_inverted = false; - } - - return oled_inverted; -} - -uint8_t oled_max_chars(void) { - if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { - return OLED_DISPLAY_WIDTH / OLED_FONT_WIDTH; - } - return OLED_DISPLAY_HEIGHT / OLED_FONT_WIDTH; -} - -uint8_t oled_max_lines(void) { - if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) { - return OLED_DISPLAY_HEIGHT / OLED_FONT_HEIGHT; - } - return OLED_DISPLAY_WIDTH / OLED_FONT_HEIGHT; -} - -void oled_task(void) { - if (!oled_initialized) { - return; - } - -#if OLED_UPDATE_INTERVAL > 0 - if (timer_elapsed(oled_update_timeout) >= OLED_UPDATE_INTERVAL) { - oled_update_timeout = timer_read(); - oled_set_cursor(0, 0); - oled_task_kb(); - } -#else - oled_set_cursor(0, 0); - oled_task_kb(); -#endif - -#if OLED_SCROLL_TIMEOUT > 0 - if (oled_dirty && oled_scrolling) { - oled_scroll_timeout = timer_read32() + OLED_SCROLL_TIMEOUT; - oled_scroll_off(); - } -#endif - - // Smart render system, no need to check for dirty - oled_render(); - - // Display timeout check -#if OLED_TIMEOUT > 0 - if (oled_active && timer_expired32(timer_read32(), oled_timeout)) { - oled_off(); - } -#endif - -#if OLED_SCROLL_TIMEOUT > 0 - if (!oled_scrolling && timer_expired32(timer_read32(), oled_scroll_timeout)) { -# ifdef OLED_SCROLL_TIMEOUT_RIGHT - oled_scroll_right(); -# else - oled_scroll_left(); -# endif - } -#endif -} - -__attribute__((weak)) bool oled_task_kb(void) { - return oled_task_user(); -} -__attribute__((weak)) bool oled_task_user(void) { - return true; -} -- cgit v1.2.3 From 853c8d4617efcf779ddde92ee81dadc892b88142 Mon Sep 17 00:00:00 2001 From: QMK Bot Date: Wed, 10 May 2023 17:36:15 -0400 Subject: Format code according to conventions (#20846) --- drivers/oled/oled_driver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/oled/oled_driver.c b/drivers/oled/oled_driver.c index e50a881120..8ff6e0426c 100644 --- a/drivers/oled/oled_driver.c +++ b/drivers/oled/oled_driver.c @@ -109,7 +109,6 @@ along with this program. If not, see . # define OLED_PRE_CHARGE_PERIOD 0xF1 #endif - #define OLED_ALL_BLOCKS_MASK (((((OLED_BLOCK_TYPE)1 << (OLED_BLOCK_COUNT - 1)) - 1) << 1) | 1) #define OLED_IC_HAS_HORIZONTAL_MODE (OLED_IC == OLED_IC_SSD1306) @@ -142,7 +141,7 @@ along with this program. If not, see . // parts of the display unusable or don't get cleared correctly // and also allows for drawing & inverting uint8_t oled_buffer[OLED_MATRIX_SIZE]; -uint8_t *oled_cursor; +uint8_t * oled_cursor; OLED_BLOCK_TYPE oled_dirty = 0; bool oled_initialized = false; bool oled_active = false; -- cgit v1.2.3 From e278715f7f48e6a2e46cb3f4702b2c6eb27eb1d3 Mon Sep 17 00:00:00 2001 From: Daniel Kao Date: Sat, 20 May 2023 05:07:50 -0700 Subject: Support PS/2 mouse 9-bit output with MOUSE_EXTENDED_REPORT (#20734) --- drivers/ps2/ps2_mouse.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/ps2/ps2_mouse.c b/drivers/ps2/ps2_mouse.c index b32ad1e222..d6911d66f2 100644 --- a/drivers/ps2/ps2_mouse.c +++ b/drivers/ps2/ps2_mouse.c @@ -81,10 +81,10 @@ void ps2_mouse_task(void) { rcv = ps2_host_send(PS2_MOUSE_READ_DATA); if (rcv == PS2_ACK) { mouse_report.buttons = ps2_host_recv_response(); - mouse_report.x = ps2_host_recv_response() * PS2_MOUSE_X_MULTIPLIER; - mouse_report.y = ps2_host_recv_response() * PS2_MOUSE_Y_MULTIPLIER; + mouse_report.x = ps2_host_recv_response(); + mouse_report.y = ps2_host_recv_response(); # ifdef PS2_MOUSE_ENABLE_SCROLLING - mouse_report.v = -(ps2_host_recv_response() & PS2_MOUSE_SCROLL_MASK) * PS2_MOUSE_V_MULTIPLIER; + mouse_report.v = -(ps2_host_recv_response() & PS2_MOUSE_SCROLL_MASK); # endif } else { if (debug_mouse) print("ps2_mouse: fail to get mouse packet\n"); @@ -92,10 +92,10 @@ void ps2_mouse_task(void) { #else if (pbuf_has_data()) { mouse_report.buttons = ps2_host_recv_response(); - mouse_report.x = ps2_host_recv_response() * PS2_MOUSE_X_MULTIPLIER; - mouse_report.y = ps2_host_recv_response() * PS2_MOUSE_Y_MULTIPLIER; + mouse_report.x = ps2_host_recv_response(); + mouse_report.y = ps2_host_recv_response(); # ifdef PS2_MOUSE_ENABLE_SCROLLING - mouse_report.v = -(ps2_host_recv_response() & PS2_MOUSE_SCROLL_MASK) * PS2_MOUSE_V_MULTIPLIER; + mouse_report.v = -(ps2_host_recv_response() & PS2_MOUSE_SCROLL_MASK); # endif } else { if (debug_mouse) print("ps2_mouse: fail to get mouse packet\n"); @@ -168,6 +168,7 @@ void ps2_mouse_set_sample_rate(ps2_mouse_sample_rate_t sample_rate) { #define X_IS_OVF (mouse_report->buttons & (1 << PS2_MOUSE_X_OVFLW)) #define Y_IS_OVF (mouse_report->buttons & (1 << PS2_MOUSE_Y_OVFLW)) static inline void ps2_mouse_convert_report_to_hid(report_mouse_t *mouse_report) { +#ifndef MOUSE_EXTENDED_REPORT // PS/2 mouse data is '9-bit integer'(-256 to 255) which is comprised of sign-bit and 8-bit value. // bit: 8 7 ... 0 // sign \8-bit/ @@ -175,8 +176,18 @@ static inline void ps2_mouse_convert_report_to_hid(report_mouse_t *mouse_report) // Meanwhile USB HID mouse indicates 8bit data(-127 to 127), note that -128 is not used. // // This converts PS/2 data into HID value. Use only -127-127 out of PS/2 9-bit. + mouse_report->x *= PS2_MOUSE_X_MULTIPLIER; + mouse_report->y *= PS2_MOUSE_Y_MULTIPLIER; mouse_report->x = X_IS_NEG ? ((!X_IS_OVF && -127 <= mouse_report->x && mouse_report->x <= -1) ? mouse_report->x : -127) : ((!X_IS_OVF && 0 <= mouse_report->x && mouse_report->x <= 127) ? mouse_report->x : 127); mouse_report->y = Y_IS_NEG ? ((!Y_IS_OVF && -127 <= mouse_report->y && mouse_report->y <= -1) ? mouse_report->y : -127) : ((!Y_IS_OVF && 0 <= mouse_report->y && mouse_report->y <= 127) ? mouse_report->y : 127); +#else + // Sign extend if negative, otherwise leave positive 8-bits as-is + mouse_report->x = X_IS_NEG ? (mouse_report->x | ~0xFF) : mouse_report->x; + mouse_report->y = Y_IS_NEG ? (mouse_report->y | ~0xFF) : mouse_report->y; + mouse_report->x *= PS2_MOUSE_X_MULTIPLIER; + mouse_report->y *= PS2_MOUSE_Y_MULTIPLIER; +#endif + mouse_report->v *= PS2_MOUSE_V_MULTIPLIER; #ifdef PS2_MOUSE_INVERT_BUTTONS // swap left & right buttons @@ -197,8 +208,8 @@ static inline void ps2_mouse_convert_report_to_hid(report_mouse_t *mouse_report) #endif #ifdef PS2_MOUSE_ROTATE - int8_t x = mouse_report->x; - int8_t y = mouse_report->y; + mouse_xy_report_t x = mouse_report->x; + mouse_xy_report_t y = mouse_report->y; # if PS2_MOUSE_ROTATE == 90 mouse_report->x = y; mouse_report->y = -x; -- cgit v1.2.3 From 249fb3c2c2cb1a62f3ea99a67963f5127e280b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Greiner-Petter?= <5708782+AndreG-P@users.noreply.github.com> Date: Sat, 27 May 2023 12:26:14 +0900 Subject: [BUG] Allow multiple IS31FL3741 drivers per board in rgb_matrix (#20988) --- drivers/led/issi/is31fl3741.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/led/issi/is31fl3741.c b/drivers/led/issi/is31fl3741.c index d5dfaf1de6..2f43473fc1 100644 --- a/drivers/led/issi/is31fl3741.c +++ b/drivers/led/issi/is31fl3741.c @@ -104,11 +104,11 @@ void IS31FL3741_write_register(uint8_t addr, uint8_t reg, uint8_t data) { } bool IS31FL3741_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { - // Assume PG1 is already selected + // Assume PG0 is already selected for (int i = 0; i < 342; i += 18) { if (i == 180) { - // unlock the command register and select PG2 + // unlock the command register and select PG1 IS31FL3741_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); IS31FL3741_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM1); } @@ -246,7 +246,7 @@ void IS31FL3741_update_led_control_registers(uint8_t addr, uint8_t index) { // CS1_SW1 to CS30_SW6 are on PG2 for (int i = CS1_SW1; i <= CS30_SW6; ++i) { - IS31FL3741_write_register(addr, i, g_scaling_registers[0][i]); + IS31FL3741_write_register(addr, i, g_scaling_registers[index][i]); } // unlock the command register and select PG3 @@ -255,7 +255,7 @@ void IS31FL3741_update_led_control_registers(uint8_t addr, uint8_t index) { // CS1_SW7 to CS39_SW9 are on PG3 for (int i = CS1_SW7; i <= CS39_SW9; ++i) { - IS31FL3741_write_register(addr, i - CS1_SW7, g_scaling_registers[0][i]); + IS31FL3741_write_register(addr, i - CS1_SW7, g_scaling_registers[index][i]); } g_scaling_registers_update_required[index] = false; -- cgit v1.2.3