diff --git a/docs/quantum_painter.md b/docs/quantum_painter.md index ac37053c79..acb9d1d384 100644 --- a/docs/quantum_painter.md +++ b/docs/quantum_painter.md @@ -32,16 +32,18 @@ Supported devices: ## Quantum Painter Configuration :id=quantum-painter-config -| Option | Default | Purpose | -|------------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------| -| `QUANTUM_PAINTER_NUM_IMAGES` | `8` | The maximum number of images/animations that can be loaded at any one time. | -| `QUANTUM_PAINTER_NUM_FONTS` | `4` | The maximum number of fonts that can be loaded at any one time. | -| `QUANTUM_PAINTER_CONCURRENT_ANIMATIONS` | `4` | The maximum number of animations that can be executed at the same time. | -| `QUANTUM_PAINTER_LOAD_FONTS_TO_RAM` | `FALSE` | Whether or not fonts should be loaded to RAM. Relevant for fonts stored in off-chip persistent storage, such as external flash. | -| `QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE` | `32` | The limit of the amount of pixel data that can be transmitted in one transaction to the display. Higher values require more RAM on the MCU. | -| `QUANTUM_PAINTER_SUPPORTS_256_PALETTE` | `FALSE` | If 256-color palettes are supported. Requires significantly more RAM on the MCU. | -| `QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS` | `FALSE` | If native color range is supported. Requires significantly more RAM on the MCU. | -| `QUANTUM_PAINTER_DEBUG` | _unset_ | Prints out significant amounts of debugging information to CONSOLE output. Significant performance degradation, use only for debugging. | +| Option | Default | Purpose | +|------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `QUANTUM_PAINTER_DISPLAY_TIMEOUT` | `30000` | This controls the amount of time (in milliseconds) that all displays will remain on after the last user input. If set to `0`, the display will remain on indefinitely. | +| `QUANTUM_PAINTER_TASK_THROTTLE` | `1` | This controls the amount of time (in milliseconds) that the Quantum Painter internal task will wait between each execution. Affects animations, display timeout, and LVGL timing if enabled. | +| `QUANTUM_PAINTER_NUM_IMAGES` | `8` | The maximum number of images/animations that can be loaded at any one time. | +| `QUANTUM_PAINTER_NUM_FONTS` | `4` | The maximum number of fonts that can be loaded at any one time. | +| `QUANTUM_PAINTER_CONCURRENT_ANIMATIONS` | `4` | The maximum number of animations that can be executed at the same time. | +| `QUANTUM_PAINTER_LOAD_FONTS_TO_RAM` | `FALSE` | Whether or not fonts should be loaded to RAM. Relevant for fonts stored in off-chip persistent storage, such as external flash. | +| `QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE` | `32` | The limit of the amount of pixel data that can be transmitted in one transaction to the display. Higher values require more RAM on the MCU. | +| `QUANTUM_PAINTER_SUPPORTS_256_PALETTE` | `FALSE` | If 256-color palettes are supported. Requires significantly more RAM on the MCU. | +| `QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS` | `FALSE` | If native color range is supported. Requires significantly more RAM on the MCU. | +| `QUANTUM_PAINTER_DEBUG` | _unset_ | Prints out significant amounts of debugging information to CONSOLE output. Significant performance degradation, use only for debugging. | Drivers have their own set of configurable options, and are described in their respective sections. 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; } } diff --git a/quantum/painter/qp.h b/quantum/painter/qp.h index 00f5d7931a..7222d3b413 100644 --- a/quantum/painter/qp.h +++ b/quantum/painter/qp.h @@ -1,4 +1,4 @@ -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -11,6 +11,22 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Quantum Painter global configurables (add to your keyboard's config.h) +#ifndef QUANTUM_PAINTER_DISPLAY_TIMEOUT +/** + * @def This controls the amount of time (in milliseconds) that all displays will remain on after the last user input. + * If set to 0, the display will remain on indefinitely. + */ +# define QUANTUM_PAINTER_DISPLAY_TIMEOUT 30000 +#endif // QUANTUM_PAINTER_DISPLAY_TIMEOUT + +#ifndef QUANTUM_PAINTER_TASK_THROTTLE +/** + * @def This controls the amount of time (in milliseconds) that the Quantum Painter internal task will wait between + * each execution. + */ +# define QUANTUM_PAINTER_TASK_THROTTLE 1 +#endif // QUANTUM_PAINTER_TASK_THROTTLE + #ifndef QUANTUM_PAINTER_NUM_IMAGES /** * @def This controls the maximum number of images that Quantum Painter can load at any one time. Images can be loaded @@ -53,7 +69,7 @@ * @def This controls the maximum size of the pixel data buffer used for single blocks of transmission. Larger buffers * means more data is processed at one time, with less frequent transmissions, at the cost of RAM. */ -# define QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE 32 +# define QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE 1024 #endif #ifndef QUANTUM_PAINTER_SUPPORTS_256_PALETTE @@ -442,34 +458,50 @@ int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, pai #ifdef QUANTUM_PAINTER_RGB565_SURFACE_ENABLE # include "qp_rgb565_surface.h" +#else // QUANTUM_PAINTER_RGB565_SURFACE_ENABLE +# define RGB565_SURFACE_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_RGB565_SURFACE_ENABLE #ifdef QUANTUM_PAINTER_ILI9163_ENABLE # include "qp_ili9163.h" +#else // QUANTUM_PAINTER_ILI9163_ENABLE +# define ILI9163_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_ILI9163_ENABLE #ifdef QUANTUM_PAINTER_ILI9341_ENABLE # include "qp_ili9341.h" +#else // QUANTUM_PAINTER_ILI9341_ENABLE +# define ILI9341_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_ILI9341_ENABLE #ifdef QUANTUM_PAINTER_ILI9488_ENABLE # include "qp_ili9488.h" +#else // QUANTUM_PAINTER_ILI9488_ENABLE +# define ILI9488_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_ILI9488_ENABLE #ifdef QUANTUM_PAINTER_ST7789_ENABLE # include "qp_st7789.h" +#else // QUANTUM_PAINTER_ST7789_ENABLE +# define ST7789_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_ST7789_ENABLE #ifdef QUANTUM_PAINTER_ST7735_ENABLE # include "qp_st7735.h" +#else // QUANTUM_PAINTER_ST7735_ENABLE +# define ST7735_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_ST7735_ENABLE #ifdef QUANTUM_PAINTER_GC9A01_ENABLE # include "qp_gc9a01.h" +#else // QUANTUM_PAINTER_GC9A01_ENABLE +# define GC9A01_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_GC9A01_ENABLE #ifdef QUANTUM_PAINTER_SSD1351_ENABLE # include "qp_ssd1351.h" +#else // QUANTUM_PAINTER_SSD1351_ENABLE +# define SSD1351_NUM_DEVICES 0 #endif // QUANTUM_PAINTER_SSD1351_ENABLE //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/quantum/painter/qp_draw_image.c b/quantum/painter/qp_draw_image.c index fa80617242..e722f3cf02 100644 --- a/quantum/painter/qp_draw_image.c +++ b/quantum/painter/qp_draw_image.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" @@ -414,15 +414,3 @@ void qp_internal_animation_tick(void) { static uint32_t last_anim_exec = 0; deferred_exec_advanced_task(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, &last_anim_exec); } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Quantum Painter Core API: qp_internal_task - -void qp_internal_task(void) { - qp_internal_animation_tick(); -#ifdef QUANTUM_PAINTER_LVGL_INTEGRATION_ENABLE - // Run LVGL ticks - void qp_lvgl_internal_tick(void); - qp_lvgl_internal_tick(); -#endif -} diff --git a/quantum/painter/qp_internal.c b/quantum/painter/qp_internal.c new file mode 100644 index 0000000000..ea23aef7c3 --- /dev/null +++ b/quantum/painter/qp_internal.c @@ -0,0 +1,96 @@ +// Copyright 2023 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "qp_internal.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter Core API: device registration + +enum { + // Work out how many devices we're actually going to be instantiating + // NOTE: We intentionally do not include surfaces here, despite them conforming to the same API. + QP_NUM_DEVICES = (ILI9163_NUM_DEVICES) // ILI9163 + + (ILI9341_NUM_DEVICES) // ILI9341 + + (ILI9488_NUM_DEVICES) // ILI9488 + + (ST7789_NUM_DEVICES) // ST7789 + + (ST7735_NUM_DEVICES) // ST7735 + + (GC9A01_NUM_DEVICES) // GC9A01 + + (SSD1351_NUM_DEVICES) // SSD1351 +}; + +static painter_device_t qp_devices[QP_NUM_DEVICES] = {NULL}; + +bool qp_internal_register_device(painter_device_t driver) { + for (uint8_t i = 0; i < QP_NUM_DEVICES; i++) { + if (qp_devices[i] == NULL) { + qp_devices[i] = driver; + return true; + } + } + + // We should never get here -- someone has screwed up their device counts during config + qp_dprintf("qp_internal_register_device: no more space for devices!\n"); + return false; +} + +#if (QUANTUM_PAINTER_DISPLAY_TIMEOUT) > 0 +static void qp_internal_display_timeout_task(void) { + // Handle power on/off state + static bool display_on = true; + bool should_change_display_state = false; + bool target_display_state = false; + if (last_input_activity_elapsed() < (QUANTUM_PAINTER_DISPLAY_TIMEOUT)) { + should_change_display_state = display_on == false; + target_display_state = true; + } else { + should_change_display_state = display_on == true; + target_display_state = false; + } + + if (should_change_display_state) { + for (uint8_t i = 0; i < QP_NUM_DEVICES; i++) { + if (qp_devices[i] != NULL) { + qp_power(qp_devices[i], target_display_state); + } + } + + display_on = target_display_state; + } +} +#endif // (QUANTUM_PAINTER_DISPLAY_TIMEOUT) > 0 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Quantum Painter Core API: qp_internal_task + +_Static_assert((QUANTUM_PAINTER_TASK_THROTTLE) > 0 && (QUANTUM_PAINTER_TASK_THROTTLE) < 1000, "QUANTUM_PAINTER_TASK_THROTTLE must be between 1 and 999"); + +void qp_internal_task(void) { + // Perform throttling of the internal processing of Quantum Painter + static uint32_t last_tick = 0; + uint32_t now = timer_read32(); + if (TIMER_DIFF_32(now, last_tick) < (QUANTUM_PAINTER_TASK_THROTTLE)) { + return; + } + last_tick = now; + +#if (QUANTUM_PAINTER_DISPLAY_TIMEOUT) > 0 + qp_internal_display_timeout_task(); +#endif // (QUANTUM_PAINTER_DISPLAY_TIMEOUT) > 0 + + // Handle animations + void qp_internal_animation_tick(void); + qp_internal_animation_tick(); + +#ifdef QUANTUM_PAINTER_LVGL_INTEGRATION_ENABLE + // Run LVGL ticks + void qp_lvgl_internal_tick(void); + qp_lvgl_internal_tick(); +#endif + + // Flush (render) dirty regions to corresponding displays + for (uint8_t i = 0; i < QP_NUM_DEVICES; i++) { + if (qp_devices[i] != NULL) { + qp_flush(qp_devices[i]); + } + } +} diff --git a/quantum/painter/qp_internal_driver.h b/quantum/painter/qp_internal_driver.h index 82a0178a73..c976ff9db7 100644 --- a/quantum/painter/qp_internal_driver.h +++ b/quantum/painter/qp_internal_driver.h @@ -1,4 +1,4 @@ -// Copyright 2021 Nick Brassel (@tzarc) +// Copyright 2021-2023 Nick Brassel (@tzarc) // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -82,3 +82,8 @@ struct painter_driver_t { // Comms config pointer -- needs to point to an appropriate comms config if the comms driver requires it. void *comms_config; }; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Device internals + +bool qp_internal_register_device(painter_device_t driver); diff --git a/quantum/painter/rules.mk b/quantum/painter/rules.mk index 199e406dd6..7752936cbd 100644 --- a/quantum/painter/rules.mk +++ b/quantum/painter/rules.mk @@ -24,6 +24,7 @@ SRC += \ $(QUANTUM_DIR)/unicode/utf8.c \ $(QUANTUM_DIR)/color.c \ $(QUANTUM_DIR)/painter/qp.c \ + $(QUANTUM_DIR)/painter/qp_internal.c \ $(QUANTUM_DIR)/painter/qp_stream.c \ $(QUANTUM_DIR)/painter/qgf.c \ $(QUANTUM_DIR)/painter/qff.c \