Optimize ST7796 driver: Use bulk SPI transfers and blocking DMA for higher framerate

This commit is contained in:
Adolfo Reyna
2026-02-11 11:40:26 -05:00
parent 47fc02f05c
commit b59d716965
4 changed files with 69 additions and 2 deletions

View File

@@ -58,6 +58,7 @@
#include "hardware/gpio.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "hardware/pwm.h"
#include "pico/binary_info.h"
#include "pico/stdlib.h"
@@ -123,6 +124,10 @@ static uint8_t current_brightness = 100; // Current brightness level (0-100)
static uint pwm_slice; // PWM slice number for backlight control
static uint pwm_channel; // PWM channel number for backlight control
// DMA Channel for fast transfers
static int dma_tx_channel = -1;
static dma_channel_config dma_tx_config;
/**
* @brief Activate chip select (pull CS LOW)
*
@@ -337,6 +342,12 @@ void st7796_init(const struct st7796_config *c, uint16_t w, uint16_t h) {
// only use a portion of it. Adjust these if your display is misaligned.
x_offset = 0;
y_offset = 0;
// Initialize DMA for SPI
dma_tx_channel = dma_claim_unused_channel(true);
dma_tx_config = dma_channel_get_default_config(dma_tx_channel);
channel_config_set_transfer_data_size(&dma_tx_config, DMA_SIZE_8); // 8-bit transfers
channel_config_set_dreq(&dma_tx_config, spi_get_dreq(config->spi, true)); // Sync with SPI TX
// Initialize SPI at maximum stable speed for ST7796
// Datasheet says max 15.15 MHz, but modern displays work much faster
@@ -551,6 +562,39 @@ void st7796_write(const uint16_t *data, size_t len) {
cs_deselect();
}
/**
* @brief Write raw buffer data directly to display
*/
void st7796_write_raw(const uint8_t *data, size_t len) {
dc_data();
cs_select();
// DMA Implementation (Blocking)
// 1. Configure DMA channel to write from buffer to SPI TX register
dma_channel_configure(
dma_tx_channel,
&dma_tx_config,
&spi_get_hw(config->spi)->dr, // Write to SPI TX register
data, // Read from buffer
len, // Element count (bytes)
true // Start immediately
);
// 2. Wait for DMA to finish
dma_channel_wait_for_finish_blocking(dma_tx_channel);
// 3. Wait for SPI Transfer to complete (DMA only fills the FIFO)
// We need to wait for the FIFO to drain and the bus to be idle before raising CS
while (spi_is_busy(config->spi)) {
// tight_loop_contents() is empty on some platforms,
// using a simple volatile read ensures the compiler doesn't optimize this away
// efficiently.
__asm volatile ("nop");
}
cs_deselect();
}
/**
* @brief Draw single pixel at specific coordinates
*

View File

@@ -233,6 +233,21 @@ void st7796_set_cursor(uint16_t x, uint16_t y);
*/
void st7796_write(const uint16_t *data, size_t len);
/**
* @brief Write raw buffer data directly to display
*
* Writes a buffer of bytes directly to the display without any
* conversion or byte swapping. This function assume the data is
* already in the correct format (Big-Endian RGB565) for the display.
*
* This is significantly faster for large block transfers than
* st7796_write() as it uses a single SPI transaction.
*
* @param data Pointer to raw byte buffer
* @param len Number of bytes to send
*/
void st7796_write_raw(const uint8_t *data, size_t len);
/**
* @brief Draw a single pixel at specified coordinates
*