Implement 4-quadrant dirty rectangle optimization and 30 FPS limiting for ST7796

Add intelligent partial screen update system using bitwise XOR change detection
and 4-quadrant tracking (top-left, top-right, bottom-left, bottom-right). Each
changed pixel is routed to its quadrant, with sophisticated merge logic that
combines adjacent rectangles when beneficial (<40% overhead). This dramatically
reduces SPI bandwidth for UIs with scattered updates (e.g., corners, sidebars).

Key changes:
- 4-quadrant dirty rectangle tracking with automatic merging
- XOR-based change detection for fast byte-level comparison
- Expose st7796_set_window() for partial region updates
- 30 FPS frame rate limiter (33ms per frame) to prevent excessive refreshes
- Smart sleep timing when frame rate limit is active

Performance: Up to 99% reduction in SPI traffic for corner-based UIs
(e.g., 4 small regions vs full 480x320 screen updates).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adolfo Reyna
2026-02-11 12:56:10 -05:00
parent b59d716965
commit eacc03a38c
5 changed files with 370 additions and 48 deletions

View File

@@ -268,7 +268,7 @@ static void write_command_with_data(uint8_t cmd, const uint8_t *data, size_t len
* This compensates for displays where the physical screen doesn't align
* with the controller's framebuffer (common with ST7789/ST7796).
*/
static void set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
void st7796_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
uint8_t data[4];
// Add offsets for display positioning
@@ -475,7 +475,7 @@ void st7796_init(const struct st7796_config *c, uint16_t w, uint16_t h) {
* @param color RGB565 color value (0x0000=black, 0xFFFF=white)
*/
void st7796_fill(uint16_t color) {
set_window(0, 0, width - 1, height - 1);
st7796_set_window(0, 0, width - 1, height - 1);
dc_data();
cs_select();
@@ -535,7 +535,7 @@ void st7796_put(uint16_t color) {
* @param y Starting Y coordinate
*/
void st7796_set_cursor(uint16_t x, uint16_t y) {
set_window(x, y, width - 1, height - 1);
st7796_set_window(x, y, width - 1, height - 1);
}
/**
@@ -614,8 +614,8 @@ void st7796_write_raw(const uint8_t *data, size_t len) {
*/
void st7796_draw_pixel(uint16_t x, uint16_t y, uint16_t color) {
if (x >= width || y >= height) return; // Bounds check
set_window(x, y, x, y); // 1x1 window
st7796_set_window(x, y, x, y); // 1x1 window
uint8_t data[2] = {(color >> 8) & 0xFF, color & 0xFF};
dc_data();
@@ -676,8 +676,8 @@ void st7796_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t c
if (x >= width || y >= height) return;
if (x + w > width) w = width - x;
if (y + h > height) h = height - y;
set_window(x, y, x + w - 1, y + h - 1);
st7796_set_window(x, y, x + w - 1, y + h - 1);
dc_data();
cs_select();

View File

@@ -209,16 +209,38 @@ void st7796_put(uint16_t color);
/**
* @brief Set cursor position for subsequent writes
*
*
* Sets the drawing window starting at (x, y) and extending to the
* bottom-right of the display. Subsequent calls to st7796_put()
* will write pixels starting from this position.
*
*
* @param x X coordinate (0 to width-1)
* @param y Y coordinate (0 to height-1)
*/
void st7796_set_cursor(uint16_t x, uint16_t y);
/**
* @brief Set drawing window to a specific rectangle
*
* Sets the drawing window to a rectangular region defined by (x0, y0)
* as the top-left corner and (x1, y1) as the bottom-right corner.
* Subsequent write operations will only affect this region.
*
* This is useful for partial screen updates (dirty rectangle optimization)
* where only a portion of the screen needs to be redrawn, significantly
* improving performance by reducing SPI data transfer.
*
* @param x0 Top-left X coordinate (0 to width-1)
* @param y0 Top-left Y coordinate (0 to height-1)
* @param x1 Bottom-right X coordinate (x0 to width-1)
* @param y1 Bottom-right Y coordinate (y0 to height-1)
*
* Example: Update only a 100x50 region starting at (50, 50):
* st7796_set_window(50, 50, 149, 99);
* st7796_write_raw(pixel_data, 100 * 50 * 2);
*/
void st7796_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
/**
* @brief Write multiple pixels at current cursor position
*