Un Framebuffer es un arreglo bidimensional en RAM que representa la pantalla entera. Si nuestra pantalla es de 240x240 píxeles, y cada píxel usa 2 bytes (RGB565), necesitamos 240 * 240 * 2 = 115,200 Bytes (115.2 KB). El problema es que nuestro microcontrolador STM32F401RE tiene un máximo absoluto de 96 KB de SRAM. ¡La pantalla no cabe en el cerebro del microcontrolador!
No necesitamos redibujar toda la pantalla para actualizar el precio del gas. Solo necesitamos redibujar el rectángulo donde están los números. El controlador ST7789 tiene una función mágica llamada Windowing (Enventanado) a través de los comandos CASET (Column Address Set) y RASET (Row Address Set).
El flujo de trabajo es el siguiente:
Ejercicio 1: Vamos a agregar a nuestro st7789_driver.c la función crítica que configura las ventanas (CASET y RASET). Luego crearemos una función que tome un arreglo en RAM y lo inyecte masivamente a la pantalla, lidiando con la trampa mortal del Endianness: El procesador ARM guarda los datos en memoria en formato Little-Endian (Byte Menos Significativo primero), pero la pantalla ST7789 exige recibir los colores en formato Big-Endian (Byte Más Significativo primero).
/**
* @file st7789_driver.c
* @brief Implementación de Ventanas y Framebuffers para ST7789
*/
#include "stm32f401xe.h"
#include <stdint.h>
// Dependencias previas (Día 171)
extern void ST7789_SendCmd(uint8_t cmd);
extern void ST7789_SendData(uint8_t data);
extern void SPI_WriteByte(uint8_t dato);
// =========================================================================
// DEFINICIÓN DE VENTANA Y TRANSFERENCIA MASIVA
// =========================================================================
/**
* @brief Define el área activa de dibujo en la GRAM de la pantalla.
* Cualquier dato enviado posteriormente se dibujará confinado aquí.
*/
void ST7789_SetWindow(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end) {
// 1. Comando CASET: Configurar rango de Columnas (X)
ST7789_SendCmd(0x2A);
ST7789_SendData(x_start >> 8); // MSB Inicio
ST7789_SendData(x_start & 0xFF); // LSB Inicio
ST7789_SendData(x_end >> 8); // MSB Fin
ST7789_SendData(x_end & 0xFF); // LSB Fin
// 2. Comando RASET: Configurar rango de Filas (Y)
ST7789_SendCmd(0x2B);
ST7789_SendData(y_start >> 8);
ST7789_SendData(y_start & 0xFF);
ST7789_SendData(y_end >> 8);
ST7789_SendData(y_end & 0xFF);
// 3. Comando RAMWR: Memory Write. Prepara a la pantalla para recibir píxeles.
ST7789_SendCmd(0x2C);
}
/**
* @brief Dibuja un buffer de memoria completo en un área de la pantalla.
*
* @param x Coordenada X inicial
* @param y Coordenada Y inicial
* @param w Ancho de la imagen (Width)
* @param h Alto de la imagen (Height)
* @param buffer Puntero al arreglo en la SRAM del STM32 (FrameBuffer parcial)
*/
void ST7789_DrawBuffer(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *buffer) {
// Configurar la caja receptora
ST7789_SetWindow(x, y, x + w - 1, y + h - 1);
// Habilitar modo datos y bajar Chip Select
GPIOA->BSRR = (1 << 3); // DC = 1 (Data)
GPIOA->BSRR = (1 << 20); // CS = 0 (Activo)
uint32_t total_pixels = w * h;
// Transferencia Masiva Píxel por Píxel (Endianness Fix)
for (uint32_t i = 0; i < total_pixels; i++) {
// El formato es RGB565 (16 bits)
uint16_t color = buffer[i];
// TRAMPA DE ENDIANNESS:
// ARM guarda 0xF800 (Rojo) en RAM como [0x00][0xF8].
// Si enviamos la memoria cruda, la ST7789 recibe 0x00F8 (Azul oscuro).
// Solución Bare-Metal: Extraer y enviar manualmente MSB y luego LSB.
while (!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = (color >> 8); // Enviar Byte Alto (MSB)
while (!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = (color & 0xFF); // Enviar Byte Bajo (LSB)
}
// Esperar a que el último byte salga físicamente de la línea
while (SPI1->SR & SPI_SR_BSY);
// Soltar pantalla
GPIOA->BSRR = (1 << 4); // CS = 1 (Inactivo)
}
// =========================================================================
// IMPLEMENTACIÓN EN LA APLICACIÓN (Dibujado Offline)
// =========================================================================
// Reservamos 5 KB de RAM para una "ventana" de números (50 de ancho x 50 de alto)
uint16_t ventana_precio[50 * 50];
#define COLOR_ROJO 0xF800
#define COLOR_NEGRO 0x0000
int main(void) {
// inicializar_st7789();
while(1) {
// 1. DIBUJADO OFFLINE (En la RAM del procesador)
// Esto es instantáneo. La pantalla física no se entera de lo que hacemos.
for (int i = 0; i < 2500; i++) {
// Lógica ficticia: Limpiar el fondo
ventana_precio[i] = COLOR_NEGRO;
// Escribir fuente tipográfica usando arreglos (se verá más adelante)
// if (fuente[i]) ventana_precio[i] = COLOR_ROJO;
}
// 2. DISPARO BALÍSTICO (Burst Update)
// Una vez que la imagen está perfecta en la RAM, la disparamos a la
// pantalla en las coordenadas X=100, Y=100.
// La actualización es tan rápida que el ojo humano no percibe parpadeo.
ST7789_DrawBuffer(100, 100, 50, 50, ventana_precio);
// ... (resto del Súper Bucle)
}
return 0;
}
Objetivo del día: Experiencia de Usuario (UX) Ininterrumpida y Fluida.
Cuando un taxista carga gas en tu Despachador, sus ojos están clavados en el indicador de "Importe Total" girando a toda velocidad. Si utilizas las librerías genéricas de Arduino (como Adafruit_GFX) para dibujar este número, la pantalla primero borrará el número viejo dibujando un cuadrado negro, y luego escribirá el número nuevo en verde. Al repetirse 10 veces por segundo, el número parpadeará violentamente causándole dolor de cabeza al usuario.
Al implementar **Framebuffers Parciales y Enventanado**, has separado el cálculo del renderizado físico. Tu STM32 procesa la venta, "pinta" el número nuevo sobre el fondo negro dentro de la memoria RAM interna en unos pocos microsegundos, y luego dispara un bloque contiguo hacia el bus SPI. El viejo número es aplastado por el nuevo número en una sola transacción sólida. El resultado visual es indistinguible de la pantalla OLED de un smartphone moderno: liso, limpio e instantáneo.
CASET y RASET del controlador de video para delimitar una región específica de la memoria GRAM, acelerando masivamente la actualización al no tener que apuntar las coordenadas (X,Y) por cada píxel.