La idea de este proyecto es hacer un montaje que disponga de una placa ESP32 STEAMakers AI como microcontrolador, una pantalla TFT de 240×240 píxeles SPI ST7789 y tres botones. Las piezas para imprimir en 3D se pueden descargar de Thingiverse.
Con los tres botones, conectados a los pines 11,12 y 13, se puede interactuar en un juego de preguntas y respuestas. Con dos de ellos nos podremos desplazar por la pantalla y con el tercero se validará la opción seleccionada.
La caja también incorpora un interruptor para encender y apagar la placa.
La placa dispone de un zócalo para la conexión de la pantalla TFT. La lista de pines que van conectados a la placa son los siguientes.
GND
VCC
SCL 20
SDA 21
RST 38
DC 40
CS 41
BL 42
Primer programa
Este primer programa realizado con SteamakersBlocks muestra en pantalla la imagen keyestudio1.jpg, haciendo que se vaya desplazando hacia abajo a la derecha
Segundo programa
En este programa, a cada ciclo, se reduce y se gira la imagen hasta un mínimo y se vuelve a aumentar.
Programa con un pulsador
Ahora haremos que la imagen pase de un aspecto a otro apretando el pulsador del pin 12, lo que hará variar el valor de la variable Contador.
Programa con dos pulsadores
En este caso al apretar el pulsador del pin 11 aumentará el valor de la variable Contador y al apretar el del pin 13 disminuirá, variando el aspecto de la imagen.
Vamos a desplazar un rectángulo por la pantalla
Ahora vamos a utilizar los dos pulsadores para variar el valor de la variable Contador, que nos hará desplazar el rectángulo amarillo y la palabra "Hola" verticalmente por la pantalla.
Menú de tres opciones
Ahora desplazamos el rectángulo amarillo entre tres posiciones, remarcando el texto de cada una de las tres opciones.
Un tercer botón para validar la opción
Un tercer botón conectado al pin 12 nos permite validar la opción resaltada, para saber si es verdadera o falsa.
Programa de prueba en el IDE de Arduino
Vamos a programa esta placa desde el IDE de Arduino. Para ello seleccionaremos la placa ESP32S3 Dev Module.
Comandos
drawPixel(x, y, color);
drawLine(x0, y0, x1, y1, color);
drawRect(x, y, w, h, color);
fillRect(x, y, w, h, color);
drawCircle(x, y, r, color);
fillCircle(x, y, r, color);
drawTriangle(x0, y0, x1, y1, x2, y2, color);
fillTriangle(x0, y0, x1, y1, x2, y2, color);
drawRoundRect(x, y, w, h, r, color);
fillRoundRect(x, y, w, h, r, color);
Este es el primer programa que haremos para probar la pantalla TFT.
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
double i;
#define COL_BLACK 0x0000
#define COL_BLUE 0x001F
#define COL_RED 0xF800
#define COL_GREEN 0x07E0
#define COL_CYAN 0x07FF
#define COL_MAGENTA 0xF81F#define COL_YELLOW 0xFFE0#define COL_WHITE 0xFFFF#define COL_DARKGREY 0x39E7
SPIClass spi_TFT(HSPI);Adafruit_ST7789 tft = Adafruit_ST7789(&spi_TFT, 41, 40, 38);
static inline void TFT_Backlight(uint8_t state) {digitalWrite(42, state ? HIGH : LOW); //inverted}
void setup(){pinMode(42, OUTPUT); digitalWrite(42, HIGH);
spi_TFT.begin(20, -1, 21, -1);
pinMode(41, OUTPUT);digitalWrite(41, HIGH);pinMode(40, OUTPUT);
tft.init(240, 240);tft.setRotation(0);tft.fillScreen(COL_BLACK);
TFT_Backlight(1);tft.fillScreen(0xF81F);}
void loop(){
for (i = 1; i <= 3; i=i+1) { //Circulostft.drawCircle(64, 32, (i * 10), COL_BLACK);}tft.drawRect(100, 100, 50, 30, COL_BLACK); //Rectangulotft.setFont(NULL); //Textotft.setTextSize(2);tft.setTextColor(0xFFE0);tft.setCursor(10, 10);tft.print(String("Hola"));}
Esto es lo que se puede ver en la pantalla.
Utilizando los pulsadores
El siguiente programa escribe una palabra cuando presionamos un botón que está conectado al pin 12.
#include <SPI.h>#include <Adafruit_GFX.h>#include <Adafruit_ST7789.h>double i;#define COL_BLACK 0x0000#define COL_BLUE 0x001F#define COL_RED 0xF800#define COL_GREEN 0x07E0#define COL_CYAN 0x07FF#define COL_MAGENTA 0xF81F#define COL_YELLOW 0xFFE0#define COL_WHITE 0xFFFF#define COL_DARKGREY 0x39E7SPIClass spi_TFT(HSPI);Adafruit_ST7789 tft = Adafruit_ST7789(&spi_TFT, 41, 40, 38);static inline void TFT_Backlight(uint8_t state) {digitalWrite(42, state ? HIGH : LOW); //inverted}void setup(){pinMode(42, OUTPUT); digitalWrite(42, HIGH);spi_TFT.begin(20, -1, 21, -1);pinMode(41, OUTPUT);digitalWrite(41, HIGH);pinMode(40, OUTPUT);tft.init(240, 240);tft.setRotation(0);tft.fillScreen(COL_BLACK);TFT_Backlight(1);tft.fillScreen(0xF81F);}void loop(){tft.fillScreen(0xF81F);for (i = 1; i <= 3; i=i+1) {tft.drawCircle(64, 32, (i * 10), COL_BLACK);}tft.drawRect(100, 100, 50, 30, COL_BLACK);tft.setFont(NULL);tft.setTextSize(2);tft.setTextColor(0xFFE0);tft.setCursor(10, 10);tft.print(String("Hola"));if (digitalRead(12) == HIGH) {tft.print(String("Adios"));}}
Moviendo un rectángulo amarillo por la pantalla
Apretando los pulsadores conectados a los pines 13 y 11 cambiamos el valor de la variable Contador y desplazamos el fondo amarillo, arriba y abajo de la pantalla, en tres posiciones posibles.
#include <SPI.h>#include <Adafruit_GFX.h>#include <Adafruit_ST7789.h>double i;int Contador = 1;#define COL_BLACK 0x0000#define COL_BLUE 0x001F#define COL_RED 0xF800#define COL_GREEN 0x07E0#define COL_CYAN 0x07FF#define COL_MAGENTA 0xF81F#define COL_YELLOW 0xFFE0#define COL_WHITE 0xFFFF#define COL_DARKGREY 0x39E7SPIClass spi_TFT(HSPI);Adafruit_ST7789 tft = Adafruit_ST7789(&spi_TFT, 41, 40, 38);static inline void TFT_Backlight(uint8_t state) {digitalWrite(42, state ? HIGH : LOW); //inverted}void setup(){pinMode(42, OUTPUT); digitalWrite(42, HIGH);spi_TFT.begin(20, -1, 21, -1);pinMode(41, OUTPUT);digitalWrite(41, HIGH);pinMode(40, OUTPUT);tft.init(240, 240);tft.setRotation(0);tft.fillScreen(COL_BLACK);TFT_Backlight(1);tft.fillScreen(0xF81F);}void loop(){Pulsadores();Fondodepantalla();delay (100);}void Pulsadores(){if (digitalRead(13) == HIGH) {Contador = Contador + 1;}if (digitalRead(11) == HIGH) {Contador = Contador - 1;}}void Fondodepantalla(){tft.fillRect(0, 0, 250, 60, COL_CYAN);if (Contador == 1) {tft.fillRect(0, 60, 250, 60, COL_YELLOW);}else {tft.fillRect(0, 60, 250, 60, COL_DARKGREY);}if (Contador == 2) {tft.fillRect(0, 120, 250, 60, COL_YELLOW);}else {tft.fillRect(0, 120, 250, 60, COL_DARKGREY);}if (Contador == 3) {tft.fillRect(0, 180, 250, 60, COL_YELLOW);}else {tft.fillRect(0, 180, 250, 60, COL_DARKGREY);}}
Programa para mostrar un archivo de imagen que se encuentra en la tarjeta SD
Este código está sacado de SteamakersBlocks y permite mostrar en la pantalla TFT un archivo de imagen existente en la tarjeta Micro SD.
#include <SPI.h>#include <Adafruit_GFX.h>#include <Adafruit_ST7789.h>#include <SD.h>#include <JPEGDecoder.h>#define COL_BLACK 0x0000#define COL_BLUE 0x001F#define COL_RED 0xF800#define COL_GREEN 0x07E0#define COL_CYAN 0x07FF#define COL_MAGENTA 0xF81F#define COL_YELLOW 0xFFE0#define COL_WHITE 0xFFFF#define COL_DARKGREY 0x39E7static inline uint16_t TFT_COLOR565(uint8_t r, uint8_t g, uint8_t b) {return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);}static inline bool tftColorNear565(uint16_t c, uint16_t key, uint8_t thr) {int r1 = (c >> 11) & 0x1F;int g1 = (c >> 5) & 0x3F;int b1 = c & 0x1F;int r2 = (key >> 11) & 0x1F;int g2 = (key >> 5) & 0x3F;int b2 = key & 0x1F;return (abs(r1 - r2) <= thr) &&(abs(g1 - g2) <= thr) &&(abs(b1 - b2) <= thr);}SPIClass spi_TFT(HSPI);Adafruit_ST7789 tft = Adafruit_ST7789(&spi_TFT, 41, 40, 38);static inline void TFT_Backlight(uint8_t state) {digitalWrite(42, state ? HIGH : LOW); //inverted}SPIClass spi_SD(FSPI);static inline void tftPlotRot(Adafruit_ST7789 &disp,int x0, int y0, int w, int h,int rot, int dx, int dy, uint16_t c) {int px = x0, py = y0;switch (rot & 3) {case 0: // 0°px = x0 + dx;py = y0 + dy;break;case 1: // 90° CWpx = x0 + (h - 1 - dy);py = y0 + dx;break;case 2: // 180°px = x0 + (w - 1 - dx);py = y0 + (h - 1 - dy);break;case 3: // 270° CWpx = x0 + dy;py = y0 + (w - 1 - dx);break;}disp.writePixel(px, py, c);}static uint16_t tftRead16(fs::File &f) {uint16_t result;((uint8_t *)&result)[0] = f.read();((uint8_t *)&result)[1] = f.read();return result;}static uint32_t tftRead32(fs::File &f) {uint32_t result;((uint8_t *)&result)[0] = f.read();((uint8_t *)&result)[1] = f.read();((uint8_t *)&result)[2] = f.read();((uint8_t *)&result)[3] = f.read();return result;}static bool tftReadBytes(fs::File &s, uint8_t *buf, size_t n) {size_t got = 0;while (got < n) {int c = s.read();if (c < 0) return false;buf[got++] = (uint8_t)c;}return true;}static inline int tftStepFromScale(float scale01) {if (scale01 <= 0.0f) scale01 = 0.01f;if (scale01 > 1.0f) scale01 = 1.0f;int step = (int)(1.0f / scale01 + 0.5f);if (step < 1) step = 1;return step;}static bool tftDrawBMPFromSD_Rot_Transp(const String &filename,int x, int y,float scale01,int rot,bool useTransp,uint16_t transp565,uint8_t thr) {fs::File bmpFile = SD.open(filename.c_str(), FILE_READ);if (!bmpFile) return false;if (tftRead16(bmpFile) != 0x4D42) { bmpFile.close(); return false; } // 'BM'(void)tftRead32(bmpFile);(void)tftRead32(bmpFile);uint32_t imageOffset = tftRead32(bmpFile);uint32_t headerSize = tftRead32(bmpFile);if (headerSize < 40) { bmpFile.close(); return false; }int32_t bmpWidth = (int32_t)tftRead32(bmpFile);int32_t bmpHeight = (int32_t)tftRead32(bmpFile);if (tftRead16(bmpFile) != 1) { bmpFile.close(); return false; }uint16_t depth = tftRead16(bmpFile);uint32_t compression = tftRead32(bmpFile);if (depth != 24 || compression != 0) { bmpFile.close(); return false; }if (headerSize > 40) bmpFile.seek(bmpFile.position() + (headerSize - 40));bool flip = true;if (bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; }uint32_t rowSize = (bmpWidth * 3 + 3) & ~3;int step = tftStepFromScale(scale01);int outW = (bmpWidth + step - 1) / step;int outH = (bmpHeight + step - 1) / step;// =========================================================// FAST-SAFE: rot=0 y sin transparencia// =========================================================if (((rot & 3) == 0) && !useTransp) {// Lee solo los píxeles de la fila (sin padding). Padding se ignora.uint8_t *raw = (uint8_t*)malloc((size_t)bmpWidth * 3);if (!raw) { bmpFile.close(); return false; }tft.startWrite();for (int row = 0; row < bmpHeight; row++) {if ((row % step) != 0) continue;int dy = row / step;int srcRow = flip ? (bmpHeight - 1 - row) : row;uint32_t pos = imageOffset + (uint32_t)srcRow * rowSize;if (!bmpFile.seek(pos)) { tft.endWrite(); free(raw); bmpFile.close(); return false; }int need = bmpWidth * 3;int got = bmpFile.read(raw, need);if (got != need) { tft.endWrite(); free(raw); bmpFile.close(); return false; }int dx = 0;for (int col = 0; col < bmpWidth; col += step) {int i3 = col * 3;uint8_t b = raw[i3 + 0];uint8_t g = raw[i3 + 1];uint8_t r = raw[i3 + 2];uint16_t c = TFT_COLOR565(r, g, b);// writePixel evita overhead del drawPixel (y respeta tu init)tft.writePixel(x + dx, y + dy, c);dx++;}}tft.endWrite();free(raw);bmpFile.close();return true;}// =========================================================// Fallback seguro: pixel-a-pixel + rot/transp// =========================================================uint8_t bgr[3];tft.startWrite();for (int row = 0; row < bmpHeight; row++) {int srcRow = flip ? (bmpHeight - 1 - row) : row;uint32_t pos = imageOffset + (uint32_t)srcRow * rowSize;if (!bmpFile.seek(pos)) {tft.endWrite();bmpFile.close();return false;}if ((row % step) != 0) continue;int dy = row / step;for (int col = 0; col < bmpWidth; col++) {if (!tftReadBytes(bmpFile, bgr, 3)) {tft.endWrite();bmpFile.close();return false;}if ((col % step) != 0) continue;uint16_t c = TFT_COLOR565(bgr[2], bgr[1], bgr[0]);if (useTransp && tftColorNear565(c, transp565, thr)) continue;int dx = col / step;tftPlotRot(tft, x, y, outW, outH, rot, dx, dy, c);}}tft.endWrite();bmpFile.close();return true;}static bool tftDrawJPGFromSD_Rot_Transp(const String &filename,int x, int y,float scale01,int rot,bool useTransp,uint16_t transp565,uint8_t thr) {int step = tftStepFromScale(scale01);// decode desde SD (usa c_str por compatibilidad)if (!JpegDec.decodeSdFile(filename.c_str())) return false;const int w = (int)JpegDec.width;const int h = (int)JpegDec.height;if (w <= 0 || h <= 0) { JpegDec.abort(); return false; }const int outW = (w + step - 1) / step;const int outH = (h + step - 1) / step;// =========================================================// FAST-SAFE: rot=0// =========================================================if (((rot & 3) == 0)) {tft.startWrite();while (JpegDec.read()) {int16_t mcuX = JpegDec.MCUx * JpegDec.MCUWidth;int16_t mcuY = JpegDec.MCUy * JpegDec.MCUHeight;int16_t bw = JpegDec.MCUWidth;int16_t bh = JpegDec.MCUHeight;if (mcuX + bw > w) bw = w - mcuX;if (mcuY + bh > h) bh = h - mcuY;uint16_t *src = (uint16_t*)JpegDec.pImage; // normalmente big-endianint stride = JpegDec.MCUWidth;for (int yy = 0; yy < bh; yy++) {int sy = mcuY + yy;if ((sy % step) != 0) continue;int dy = sy / step;for (int xx = 0; xx < bw; xx++) {int sx = mcuX + xx;if ((sx % step) != 0) continue;int dx = sx / step;uint16_t c = src[yy * stride + xx];//if (useTransp && c == transp565) continue;if (useTransp && tftColorNear565(c, transp565, thr)) continue;tft.writePixel(x + dx, y + dy, c);}}}tft.endWrite();JpegDec.abort();return true;}// =========================================================// Fallback: rotación lógica !=0 con tftPlotRot (seguro)// =========================================================tft.startWrite();while (JpegDec.read()) {int16_t mcuX = JpegDec.MCUx * JpegDec.MCUWidth;int16_t mcuY = JpegDec.MCUy * JpegDec.MCUHeight;int16_t bw = JpegDec.MCUWidth;int16_t bh = JpegDec.MCUHeight;if (mcuX + bw > w) bw = w - mcuX;if (mcuY + bh > h) bh = h - mcuY;uint16_t *src = (uint16_t*)JpegDec.pImage;int stride = JpegDec.MCUWidth;for (int yy = 0; yy < bh; yy++) {int sy = mcuY + yy;if ((sy % step) != 0) continue;int dy = sy / step;for (int xx = 0; xx < bw; xx++) {int sx = mcuX + xx;if ((sx % step) != 0) continue;int dx = sx / step;uint16_t c = src[yy * stride + xx];if (useTransp && tftColorNear565(c, transp565, thr)) continue;tftPlotRot(tft, x, y, outW, outH, rot, dx, dy, c);}}}tft.endWrite();JpegDec.abort();return true;}static bool tftImgDrawFromSD_Rot(const String &path,int x, int y, float scale01, int rot,bool useTransp, uint16_t transp565, uint8_t thr) {String p = path;p.toLowerCase();if (p.endsWith(".bmp")) {return tftDrawBMPFromSD_Rot_Transp("/"+path, x, y, scale01, rot, useTransp, transp565,thr);}if (p.endsWith(".jpg") || p.endsWith(".jpeg")) {return tftDrawJPGFromSD_Rot_Transp("/"+path, x, y, scale01, rot, useTransp, transp565,thr);}return false;}void setup(){pinMode(42, OUTPUT); digitalWrite(42, HIGH);spi_TFT.begin(20, -1, 21, -1);pinMode(41, OUTPUT);digitalWrite(41, HIGH);pinMode(40, OUTPUT);tft.init(240, 240);tft.setRotation(0);tft.fillScreen(COL_BLACK);spi_SD.begin(37, 36, 35, 39);if (!SD.begin(39, spi_SD, 20000000)) {SD.begin(39, spi_SD, 10000000);}}void loop(){tftImgDrawFromSD_Rot(String(String("keyestudio1.jpg")), (int)(0), (int)(0), (float)(1.0), (int)(0), true, (uint16_t)(((uint16_t)TFT_COLOR565(255, 255, 255))),(uint16_t)(0));}
Juego de preguntas
Primeramente realiza una presentación del juego. a continuación, mediante los pulsadores conectados a los pines 11 y 13 seleccionamos la opción de respuesta a la pregunta realizada. Utilizando el pulsador conectado al pin 12 realizamos la comprobación de nuestra elección. Si es correcto avanzamos a la siguiente pregunta, si no lo es hemos de seguir probando.
#include <SPI.h>#include <Adafruit_GFX.h>#include <Adafruit_ST7789.h>#include <SD.h>#include <JPEGDecoder.h>#define COL_BLACK 0x0000#define COL_BLUE 0x001F#define COL_RED 0xF800#define COL_GREEN 0x07E0#define COL_CYAN 0x07FF#define COL_MAGENTA 0xF81F#define COL_YELLOW 0xFFE0#define COL_WHITE 0xFFFF#define COL_DARKGREY 0x39E7int Contador = 1;int Contador1 = 1;int Contador2 = 1;static inline uint16_t TFT_COLOR565(uint8_t r, uint8_t g, uint8_t b) {return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);}static inline bool tftColorNear565(uint16_t c, uint16_t key, uint8_t thr) {int r1 = (c >> 11) & 0x1F;int g1 = (c >> 5) & 0x3F;int b1 = c & 0x1F;int r2 = (key >> 11) & 0x1F;int g2 = (key >> 5) & 0x3F;int b2 = key & 0x1F;return (abs(r1 - r2) <= thr) &&(abs(g1 - g2) <= thr) &&(abs(b1 - b2) <= thr);}SPIClass spi_TFT(HSPI);Adafruit_ST7789 tft = Adafruit_ST7789(&spi_TFT, 41, 40, 38);static inline void TFT_Backlight(uint8_t state) {digitalWrite(42, state ? HIGH : LOW); //inverted}SPIClass spi_SD(FSPI);static inline void tftPlotRot(Adafruit_ST7789 &disp,int x0, int y0, int w, int h,int rot, int dx, int dy, uint16_t c) {int px = x0, py = y0;switch (rot & 3) {case 0: // 0°px = x0 + dx;py = y0 + dy;break;case 1: // 90° CWpx = x0 + (h - 1 - dy);py = y0 + dx;break;case 2: // 180°px = x0 + (w - 1 - dx);py = y0 + (h - 1 - dy);break;case 3: // 270° CWpx = x0 + dy;py = y0 + (w - 1 - dx);break;}disp.writePixel(px, py, c);}static uint16_t tftRead16(fs::File &f) {uint16_t result;((uint8_t *)&result)[0] = f.read();((uint8_t *)&result)[1] = f.read();return result;}static uint32_t tftRead32(fs::File &f) {uint32_t result;((uint8_t *)&result)[0] = f.read();((uint8_t *)&result)[1] = f.read();((uint8_t *)&result)[2] = f.read();((uint8_t *)&result)[3] = f.read();return result;}static bool tftReadBytes(fs::File &s, uint8_t *buf, size_t n) {size_t got = 0;while (got < n) {int c = s.read();if (c < 0) return false;buf[got++] = (uint8_t)c;}return true;}static inline int tftStepFromScale(float scale01) {if (scale01 <= 0.0f) scale01 = 0.01f;if (scale01 > 1.0f) scale01 = 1.0f;int step = (int)(1.0f / scale01 + 0.5f);if (step < 1) step = 1;return step;}static bool tftDrawBMPFromSD_Rot_Transp(const String &filename,int x, int y,float scale01,int rot,bool useTransp,uint16_t transp565,uint8_t thr) {fs::File bmpFile = SD.open(filename.c_str(), FILE_READ);if (!bmpFile) return false;if (tftRead16(bmpFile) != 0x4D42) { bmpFile.close(); return false; } // 'BM'(void)tftRead32(bmpFile);(void)tftRead32(bmpFile);uint32_t imageOffset = tftRead32(bmpFile);uint32_t headerSize = tftRead32(bmpFile);if (headerSize < 40) { bmpFile.close(); return false; }int32_t bmpWidth = (int32_t)tftRead32(bmpFile);int32_t bmpHeight = (int32_t)tftRead32(bmpFile);if (tftRead16(bmpFile) != 1) { bmpFile.close(); return false; }uint16_t depth = tftRead16(bmpFile);uint32_t compression = tftRead32(bmpFile);if (depth != 24 || compression != 0) { bmpFile.close(); return false; }if (headerSize > 40) bmpFile.seek(bmpFile.position() + (headerSize - 40));bool flip = true;if (bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; }uint32_t rowSize = (bmpWidth * 3 + 3) & ~3;int step = tftStepFromScale(scale01);int outW = (bmpWidth + step - 1) / step;int outH = (bmpHeight + step - 1) / step;// =========================================================// FAST-SAFE: rot=0 y sin transparencia// =========================================================if (((rot & 3) == 0) && !useTransp) {// Lee solo los píxeles de la fila (sin padding). Padding se ignora.uint8_t *raw = (uint8_t*)malloc((size_t)bmpWidth * 3);if (!raw) { bmpFile.close(); return false; }tft.startWrite();for (int row = 0; row < bmpHeight; row++) {if ((row % step) != 0) continue;int dy = row / step;int srcRow = flip ? (bmpHeight - 1 - row) : row;uint32_t pos = imageOffset + (uint32_t)srcRow * rowSize;if (!bmpFile.seek(pos)) { tft.endWrite(); free(raw); bmpFile.close(); return false; }int need = bmpWidth * 3;int got = bmpFile.read(raw, need);if (got != need) { tft.endWrite(); free(raw); bmpFile.close(); return false; }int dx = 0;for (int col = 0; col < bmpWidth; col += step) {int i3 = col * 3;uint8_t b = raw[i3 + 0];uint8_t g = raw[i3 + 1];uint8_t r = raw[i3 + 2];uint16_t c = TFT_COLOR565(r, g, b);// writePixel evita overhead del drawPixel (y respeta tu init)tft.writePixel(x + dx, y + dy, c);dx++;}}tft.endWrite();free(raw);bmpFile.close();return true;}// =========================================================// Fallback seguro: pixel-a-pixel + rot/transp// =========================================================uint8_t bgr[3];tft.startWrite();for (int row = 0; row < bmpHeight; row++) {int srcRow = flip ? (bmpHeight - 1 - row) : row;uint32_t pos = imageOffset + (uint32_t)srcRow * rowSize;if (!bmpFile.seek(pos)) {tft.endWrite();bmpFile.close();return false;}if ((row % step) != 0) continue;int dy = row / step;for (int col = 0; col < bmpWidth; col++) {if (!tftReadBytes(bmpFile, bgr, 3)) {tft.endWrite();bmpFile.close();return false;}if ((col % step) != 0) continue;uint16_t c = TFT_COLOR565(bgr[2], bgr[1], bgr[0]);if (useTransp && tftColorNear565(c, transp565, thr)) continue;int dx = col / step;tftPlotRot(tft, x, y, outW, outH, rot, dx, dy, c);}}tft.endWrite();bmpFile.close();return true;}static bool tftDrawJPGFromSD_Rot_Transp(const String &filename,int x, int y,float scale01,int rot,bool useTransp,uint16_t transp565,uint8_t thr) {int step = tftStepFromScale(scale01);// decode desde SD (usa c_str por compatibilidad)if (!JpegDec.decodeSdFile(filename.c_str())) return false;const int w = (int)JpegDec.width;const int h = (int)JpegDec.height;if (w <= 0 || h <= 0) { JpegDec.abort(); return false; }const int outW = (w + step - 1) / step;const int outH = (h + step - 1) / step;// =========================================================// FAST-SAFE: rot=0// =========================================================if (((rot & 3) == 0)) {tft.startWrite();while (JpegDec.read()) {int16_t mcuX = JpegDec.MCUx * JpegDec.MCUWidth;int16_t mcuY = JpegDec.MCUy * JpegDec.MCUHeight;int16_t bw = JpegDec.MCUWidth;int16_t bh = JpegDec.MCUHeight;if (mcuX + bw > w) bw = w - mcuX;if (mcuY + bh > h) bh = h - mcuY;uint16_t *src = (uint16_t*)JpegDec.pImage; // normalmente big-endianint stride = JpegDec.MCUWidth;for (int yy = 0; yy < bh; yy++) {int sy = mcuY + yy;if ((sy % step) != 0) continue;int dy = sy / step;for (int xx = 0; xx < bw; xx++) {int sx = mcuX + xx;if ((sx % step) != 0) continue;int dx = sx / step;uint16_t c = src[yy * stride + xx];//if (useTransp && c == transp565) continue;if (useTransp && tftColorNear565(c, transp565, thr)) continue;tft.writePixel(x + dx, y + dy, c);}}}tft.endWrite();JpegDec.abort();return true;}// =========================================================// Fallback: rotación lógica !=0 con tftPlotRot (seguro)// =========================================================tft.startWrite();while (JpegDec.read()) {int16_t mcuX = JpegDec.MCUx * JpegDec.MCUWidth;int16_t mcuY = JpegDec.MCUy * JpegDec.MCUHeight;int16_t bw = JpegDec.MCUWidth;int16_t bh = JpegDec.MCUHeight;if (mcuX + bw > w) bw = w - mcuX;if (mcuY + bh > h) bh = h - mcuY;uint16_t *src = (uint16_t*)JpegDec.pImage;int stride = JpegDec.MCUWidth;for (int yy = 0; yy < bh; yy++) {int sy = mcuY + yy;if ((sy % step) != 0) continue;int dy = sy / step;for (int xx = 0; xx < bw; xx++) {int sx = mcuX + xx;if ((sx % step) != 0) continue;int dx = sx / step;uint16_t c = src[yy * stride + xx];if (useTransp && tftColorNear565(c, transp565, thr)) continue;tftPlotRot(tft, x, y, outW, outH, rot, dx, dy, c);}}}tft.endWrite();JpegDec.abort();return true;}static bool tftImgDrawFromSD_Rot(const String &path,int x, int y, float scale01, int rot,bool useTransp, uint16_t transp565, uint8_t thr) {String p = path;p.toLowerCase();if (p.endsWith(".bmp")) {return tftDrawBMPFromSD_Rot_Transp("/"+path, x, y, scale01, rot, useTransp, transp565,thr);}if (p.endsWith(".jpg") || p.endsWith(".jpeg")) {return tftDrawJPGFromSD_Rot_Transp("/"+path, x, y, scale01, rot, useTransp, transp565,thr);}return false;}void setup(){pinMode(42, OUTPUT); digitalWrite(42, HIGH);spi_TFT.begin(20, -1, 21, -1);pinMode(41, OUTPUT);digitalWrite(41, HIGH);pinMode(40, OUTPUT);tft.init(240, 240);tft.setRotation(0);tft.fillScreen(COL_BLACK);spi_SD.begin(37, 36, 35, 39);if (!SD.begin(39, spi_SD, 20000000)) {SD.begin(39, spi_SD, 10000000);}Presentacion();}void Presentacion(){tft.fillScreen(COL_BLACK);tftImgDrawFromSD_Rot(String(String("keyestudio1.jpg")), (int)(0), (int)(0), (float)(1.0), (int)(0), true, (uint16_t)(((uint16_t)TFT_COLOR565(255, 255, 255))),(uint16_t)(0));delay(3000);tft.fillScreen(COL_WHITE);tft.setFont(NULL); //Textotft.setTextSize(3);tft.setTextColor(COL_MAGENTA);tft.setCursor(30, 30);tft.print(String("EMPIEZA EL"));tft.setCursor(30, 60);tft.print(String("JUEGO"));delay(3000);tftImgDrawFromSD_Rot(String(String("keyestudio2.jpg")), (int)(0), (int)(0), (float)(1.0), (int)(0), true, (uint16_t)(((uint16_t)TFT_COLOR565(255, 255, 255))),(uint16_t)(0));delay(3000);tft.fillScreen(COL_WHITE);tft.setCursor(30, 30);tft.print(String("GRANDES"));tft.setCursor(30, 60);tft.print(String("RECORDS"));delay(3000);}void loop(){Pulsadores();Fondodepantalla();Texto();delay (100);if (digitalRead(12) == HIGH) {Contador2 = 2;Control();}}void Pulsadores(){if (digitalRead(13) == HIGH) {Contador = Contador + 1;}if (digitalRead(11) == HIGH) {Contador = Contador - 1;}if (Contador > 3) {Contador = 3;}if (Contador < 1) {Contador = 1;}}void Fondodepantalla(){tft.fillRect(0, 0, 250, 60, COL_CYAN);if (Contador == 1) {tft.fillRect(0, 60, 250, 60, COL_YELLOW);}else {tft.fillRect(0, 60, 250, 60, COL_DARKGREY);}if (Contador == 2) {tft.fillRect(0, 120, 250, 60, COL_YELLOW);}else {tft.fillRect(0, 120, 250, 60, COL_DARKGREY);}if (Contador == 3) {tft.fillRect(0, 180, 250, 60, COL_YELLOW);}else {tft.fillRect(0, 180, 250, 60, COL_DARKGREY);}}void Texto(){if(Contador1==1){tft.setFont(NULL); //Textotft.setTextSize(2);tft.setTextColor(COL_MAGENTA);tft.setCursor(4, 4);tft.print(String("Cual es el arbol"));tft.setCursor(4, 34);tft.print(String("mas grande?"));tft.setCursor(4, 64);tft.print(String("Abuelo de Chavin"));tft.setCursor(4, 124);tft.print(String("Hyperion"));tft.setCursor(4, 184);tft.print(String("Platano de la Devesa"));}if(Contador1==2){tft.setFont(NULL); //Textotft.setTextSize(2);tft.setTextColor(COL_MAGENTA);tft.setCursor(4, 4);tft.print(String("Cual es la persona"));tft.setCursor(4, 34);tft.print(String("mas alta?"));tft.setCursor(4, 64);tft.print(String("Fermin Arrudi"));tft.setCursor(4, 124);tft.print(String("Gigante de Alzo"));tft.setCursor(4, 184);tft.print(String("Robert Wadlow"));}if(Contador1==3){tft.setFont(NULL); //Textotft.setTextSize(2);tft.setTextColor(COL_MAGENTA);tft.setCursor(4, 4);tft.print(String("Cual es el libro"));tft.setCursor(4, 34);tft.print(String("mas antiguo?"));tft.setCursor(4, 64);tft.print(String("Epopeya de Gilgamesh"));tft.setCursor(4, 124);tft.print(String("Glosas Emilianenses"));tft.setCursor(4, 184);tft.print(String("Les Homilies d'Organya"));}}void Control(){if (Contador2 == 2){if (Contador1 == 1){if (Contador == 2){tft.fillScreen(COL_WHITE);tft.setTextSize(3);tft.setTextColor(COL_CYAN);tft.setCursor(4, 4);tft.print(String("CIERTO"));Contador1 = Contador1 + 1;Contador2 = 1;delay(1000);}else {tft.fillScreen(COL_WHITE);tft.setCursor(4, 4);tft.print(String("VUELVE A PROBAR"));delay(1000);}}}if (Contador2 == 2){if (Contador1 == 2){if (Contador == 3){tft.fillScreen(COL_WHITE);tft.setTextSize(3);tft.setTextColor(COL_CYAN);tft.setCursor(4, 4);tft.print(String("CIERTO"));Contador1 = Contador1 + 1;Contador2 = 1;delay(1000);}else {tft.fillScreen(COL_WHITE);tft.setCursor(4, 4);tft.print(String("VUELVE A PROBAR"));delay(1000);}}}if (Contador2 == 2){if (Contador1 == 3){if (Contador == 1){tft.fillScreen(COL_WHITE);tft.setTextSize(3);tft.setTextColor(COL_CYAN);tft.setCursor(4, 4);tft.print(String("CIERTO"));Contador1 = Contador1 + 1;Contador2 = 1;delay(1000);}else {tft.fillScreen(COL_WHITE);tft.setCursor(4, 4);tft.print(String("VUELVE A PROBAR"));delay(1000);}}}}













No hay comentarios:
Publicar un comentario