/* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * ST7796 TFT LCD Display Driver * * ============================================================================== * ABOUT THE ST7796 DISPLAY CONTROLLER * ============================================================================== * * The ST7796 is a single-chip controller/driver for 262K-color, graphic type * TFT-LCD displays. It consists of 960 source line and 480 gate line driving * circuits. This chip is capable of connecting directly to an external * microprocessor via an 8-bit/9-bit/16-bit/18-bit parallel interface or * Serial Peripheral Interface (SPI). * * Key Features: * - Resolution: Up to 480RGB x 320 dots * - Display Colors: 262K colors (RGB565 format - 16 bits per pixel) * - Interface: 4-wire SPI (used in this driver) * - Framebuffer: Internal RAM that holds the pixel data (480x320x16bit = 300KB) * - Power: 2.5V~3.3V I/O voltage, with internal regulators for LCD drivers * * ============================================================================== * SPI COMMUNICATION PROTOCOL * ============================================================================== * * The ST7796 uses a 4-wire SPI interface: * 1. SCK (Serial Clock) - Clock signal from MCU * 2. MOSI/SDA (Master Out Slave In) - Data signal from MCU to display * 3. CS (Chip Select) - Active LOW, selects the display * 4. DC (Data/Command) - HIGH for data, LOW for commands * * Additional pins: * 5. RST (Reset) - Hardware reset, active LOW * 6. BL (Backlight) - LED backlight control, HIGH for ON * * Communication sequence: * 1. Pull CS LOW to select the display * 2. Set DC LOW for command, HIGH for data * 3. Clock out 8 bits on MOSI while toggling SCK * 4. Pull CS HIGH to deselect * * ============================================================================== * RGB565 COLOR FORMAT * ============================================================================== * * RGB565 is a 16-bit color format: * - Red: 5 bits (bits 15-11) - 32 levels * - Green: 6 bits (bits 10-5) - 64 levels (human eye more sensitive to green) * - Blue: 5 bits (bits 4-0) - 32 levels * * Color encoding: RRRR RGGG GGGB BBBB * Example: 0xF800 = Red, 0x07E0 = Green, 0x001F = Blue, 0xFFFF = White * * ============================================================================== * DISPLAY ORIENTATION (MADCTL) * ============================================================================== * * MADCTL (0x36) controls memory access and display rotation: * - MY (bit 7): Row Address Order (flip vertical) * - MX (bit 6): Column Address Order (flip horizontal) * - MV (bit 5): Row/Column Exchange (swap X/Y for rotation) * - ML (bit 4): Vertical Refresh Order * - BGR (bit 3): RGB or BGR color order * * Current configuration: 0xE0 (MY=1, MX=1, MV=1) * This provides landscape mode (480x320) with correct orientation. * * ============================================================================== * MISSING FEATURES & POTENTIAL IMPROVEMENTS * ============================================================================== * * Currently NOT implemented (but supported by hardware): * * 1. HARDWARE SCROLLING: * - Vertical Scrolling Definition (VSCRDEF) and Vertical Scroll Start Address * - Could enable smooth scrolling without redrawing entire screen * - Useful for: text scrolling, game backgrounds, UI animations * * 2. PARTIAL DISPLAY MODE: * - Show only a portion of the display (save power) * - Commands: PTLON (0x12), PTLAR (0x30) * * 3. IDLE MODE: * - Reduced color depth (8 colors) for power saving * - Commands: IDMON (0x38), IDMOFF (0x39) * * 4. DISPLAY INVERSION: * - Currently set to INVOFF, but INVON available * - Some displays look better with inversion ON * * 5. TEAR EFFECT CONTROL: * - TE (Tearing Effect) pin synchronization * - Prevents tearing during screen updates * - Commands: TEON (0x35), TEOFF (0x34) * * 6. GAMMA CORRECTION: * - Fine-tune color accuracy and contrast * - Commands: PGC (0xE0), NGC (0xE1) * * 7. BRIGHTNESS/CONTRAST CONTROL: * - Software brightness via WRCABCMB command * - Currently only hardware backlight control * * 8. POWER CONTROL: * - Deep sleep modes * - Display ON/OFF without re-initialization * - Commands: SLPIN (0x10), SLPOUT (0x11) * * 9. DMA SUPPORT: * - Use DMA for SPI transfers instead of blocking writes * - Would significantly improve performance for large updates * * 10. DOUBLE BUFFERING: * - Currently single-buffered (direct to framebuffer) * - Could implement software double buffer for tear-free updates * * 11. IMAGE/BITMAP LOADING: * - Load images from SD card or flash * - BMP, PNG decoders * * 12. TEXT RENDERING: * - Font library for text display * - Variable font sizes * * 13. HARDWARE ACCELERATED FEATURES: * - Some displays have built-in shape drawing (not ST7796) * - Window clipping for faster partial updates * * Performance Optimization Ideas: * - Increase SPI speed beyond 80MHz (try 100-125MHz) * - Use DMA for background transfers * - Implement dirty rectangle tracking (only update changed areas) * - Cache frequently drawn graphics * * ============================================================================== */ #ifndef _PICO_ST7796_H_ #define _PICO_ST7796_H_ #include "hardware/spi.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Configuration structure for ST7796 display * * This structure holds all the pin mappings and SPI interface configuration * needed to communicate with the display. All GPIO pins should be valid * Raspberry Pi Pico pins, except gpio_cs which can be -1 to disable CS control. */ struct st7796_config { spi_inst_t* spi; // SPI instance (spi0 or spi1) uint gpio_din; // MOSI/SDA pin (data out from MCU) uint gpio_clk; // SCK pin (clock) int gpio_cs; // CS pin (chip select, -1 to disable) uint gpio_dc; // DC pin (data/command select) uint gpio_rst; // RST pin (hardware reset) uint gpio_bl; // Backlight pin (LED control) }; /** * @brief Initialize the ST7796 display * * This function performs the complete initialization sequence: * 1. Configures all GPIO pins (SPI, CS, DC, RST, BL) * 2. Initializes SPI at 80-100 MHz * 3. Performs hardware reset * 4. Sends initialization commands to the display * 5. Sets up RGB565 color mode and landscape orientation * 6. Turns on backlight and display * * @param config Pointer to st7796_config structure with pin mappings * @param width Display width in pixels (typically 480 for landscape) * @param height Display height in pixels (typically 320 for landscape) * * Note: After this call, display is ready but blank. Call st7796_fill() * to clear with a color, or start drawing primitives. */ void st7796_init(const struct st7796_config *config, uint16_t width, uint16_t height); /** * @brief Fill entire display with a single color * * This is the fastest way to clear the screen or set a background. * Uses optimized 512-byte buffering for maximum SPI throughput. * * @param color RGB565 color value (e.g., 0x0000=black, 0xFFFF=white) * * Performance: ~15ms at 80MHz SPI for full 480x320 screen */ void st7796_fill(uint16_t color); /** * @brief Write a single pixel at current cursor position * * Writes one pixel without changing the drawing window. Use after * st7796_set_cursor() to position the write location. Rarely used * directly - most applications use st7796_draw_pixel() instead. * * @param color RGB565 color value */ 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 Write multiple pixels at current cursor position * * Writes an array of RGB565 color values starting at the current * cursor position. Advances the cursor automatically. * * @param data Pointer to array of RGB565 color values * @param len Number of pixels to write * * Note: Less efficient than fill_rect for solid colors due to * per-pixel conversion overhead. */ 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 * * Draws one pixel at (x, y) with the specified color. This function * sets up a 1x1 drawing window, so it's slower than batch operations. * Use fill_rect() or other primitives when possible. * * @param x X coordinate (0 to width-1) * @param y Y coordinate (0 to height-1) * @param color RGB565 color value * * Performance: ~100-200 pixels/second due to window setup overhead */ void st7796_draw_pixel(uint16_t x, uint16_t y, uint16_t color); /** * @brief Draw a rectangle outline * * Draws a hollow rectangle with 1-pixel thick borders. Implemented * as 4 calls to fill_rect() for efficiency. * * @param x Top-left X coordinate * @param y Top-left Y coordinate * @param w Width in pixels * @param h Height in pixels * @param color RGB565 color value */ void st7796_draw_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); /** * @brief Draw a filled rectangle * * Draws a solid rectangle. This is one of the fastest drawing operations * due to optimized 512-byte buffering. * * @param x Top-left X coordinate * @param y Top-left Y coordinate * @param w Width in pixels * @param h Height in pixels * @param color RGB565 color value * * Performance: Can draw full screen in ~15ms at 80MHz SPI */ void st7796_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); /** * @brief Draw a circle outline * * Draws a hollow circle using the midpoint circle algorithm (Bresenham). * Draws 8 symmetric points per iteration for efficiency. * * @param x0 Center X coordinate * @param y0 Center Y coordinate * @param r Radius in pixels * @param color RGB565 color value * * Note: Implemented as individual pixel draws, so performance is * O(r) with ~100-200 pixels/second throughput. */ void st7796_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color); /** * @brief Draw a filled circle * * Draws a solid circle using midpoint circle algorithm with horizontal * line fills. Much faster than drawing individual pixels. * * @param x0 Center X coordinate * @param y0 Center Y coordinate * @param r Radius in pixels * @param color RGB565 color value * * Performance: Better than draw_circle due to fill_rect optimization */ void st7796_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color); /** * @brief Draw a line between two points * * Draws a line using Bresenham's line algorithm. Works for any angle. * * @param x0 Start point X coordinate * @param y0 Start point Y coordinate * @param x1 End point X coordinate * @param y1 End point Y coordinate * @param color RGB565 color value * * Performance: O(max(dx, dy)) with ~100-200 pixels/second throughput */ void st7796_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color); /** * @brief Draw a triangle outline * * Draws a hollow triangle by connecting three points with lines. * * @param x0 First vertex X coordinate * @param y0 First vertex Y coordinate * @param x1 Second vertex X coordinate * @param y1 Second vertex Y coordinate * @param x2 Third vertex X coordinate * @param y2 Third vertex Y coordinate * @param color RGB565 color value */ void st7796_draw_triangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color); /** * @brief Draw a filled triangle * * Draws a solid triangle using scanline fill algorithm. Sorts vertices * by Y coordinate and fills horizontal spans. * * @param x0 First vertex X coordinate * @param y0 First vertex Y coordinate * @param x1 Second vertex X coordinate * @param y1 Second vertex Y coordinate * @param x2 Third vertex X coordinate * @param y2 Third vertex Y coordinate * @param color RGB565 color value * * Performance: Better than draw_triangle due to fill_rect optimization */ void st7796_fill_triangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color); /** * @brief Set display brightness using PWM on backlight pin * * Controls the backlight LED brightness using hardware PWM. The brightness * is specified as a percentage (0-100), where: * - 0 = Backlight completely off (minimum brightness) * - 100 = Backlight fully on (maximum brightness) * - Values in between = Proportional PWM duty cycle * * PWM Configuration: * - Frequency: 1000 Hz (1 kHz) - high enough to avoid flicker * - Resolution: 16-bit (0-65535 range internally) * - Smooth transitions without visible flickering * * Use cases: * - Power saving: Reduce brightness when idle * - Auto-dimming: Lower brightness after timeout * - Manual control: User brightness preferences * - Night mode: Very low brightness for dark environments * * @param brightness Brightness level (0-100 percent) * 0 = off, 100 = full brightness * * Performance: Instant - PWM is handled by hardware * * Note: Must be called after st7796_init() which configures the backlight pin */ void st7796_set_brightness(uint8_t brightness); /** * @brief Get current display brightness level * * Returns the currently configured brightness level as a percentage (0-100). * This reflects the last value set by st7796_set_brightness() or the * default value (100) if brightness was never explicitly set. * * @return Current brightness level (0-100 percent) */ uint8_t st7796_get_brightness(void); /** * @brief Put display into sleep mode (low power) * * Enters sleep mode to save power while keeping the controller initialized. * In sleep mode: * - Display is turned off (blank screen) * - Internal oscillator is stopped * - Framebuffer contents are preserved * - Backlight is turned off (PWM set to 0) * - Power consumption is minimized (~10μA typical) * * Touch controller remains active and functional since it's on a separate * I2C bus. Touch interrupts can wake the system from sleep. * * Use st7796_wake() to exit sleep mode and restore display. * * Commands sent: * - DISPOFF (0x28): Turn off display * - SLPIN (0x10): Enter sleep mode * - Backlight PWM set to 0 * * Performance: ~120ms to enter sleep mode */ void st7796_sleep(void); /** * @brief Wake display from sleep mode * * Exits sleep mode and restores display to normal operation. * After waking: * - Internal oscillator restarts * - Display controller becomes active * - Framebuffer contents are preserved * - Display is turned on * - Backlight is restored to previous brightness * * Commands sent: * - SLPOUT (0x11): Exit sleep mode * - DISPON (0x29): Turn on display * - Backlight PWM restored to saved level * * Performance: ~120ms to fully wake up * * Note: Must call st7796_init() before first use of sleep/wake */ void st7796_wake(void); #ifdef __cplusplus } #endif #endif