Navidad Maker – Como programar una pantalla TFT con efecto nieve

Si sois unos expertos utilizando Arduino y os gusta programar gráficos, éste es el ejercicio que estabáis buscando.

En esta pequeña actividad desarrollaremos un modelo de imagen navideña en el que os enseñaremos nada más y nada menos que a cargar una imagen desde una tarjeta SD y hacer que pequeños copos de nieve caigan de una manera llamativa como puede aparecer en la siguiente imagen.

 

 

Quizás podamos pensar, que este ejercicio tampoco es tan complicado o no supone ningún reto. Pero la realidad es que no es todo tan fácil como parece ya que nuestra placa Arduino tiene sus limitaciones y lo que lleva muy mal son las animaciones o que uno o varios elementos sean capaz de desplazarse por la imagen sin corromper la imagen de detrás.

Preparaos porque el tema es largo pero lo que vamos a aprender es increiblemente interesante para introducirnos más en el mundo de las pantallas TFT y los gráficos.

Materiales que necesitaremos

  • Arduino
  • Tarjeta SD
  • Cualquier pantalla TFT de los siguientes modelos
    • ILI9341
    • ILI9163
    • ILI9225
    • ILI9481
    • ST7735
    • HX8347

Además de esto necesitaremos descargar la siguiente librería Arduino para empezar a programar nuestro modelo navideño y crear este efecto nieve.

Conexión electrónica Arduino TFT

La pantalla TFT que vamos a utilizar en este ejemplo es una pantalla ILI9341 tal y como aparece en la imagen. Las pantallas TFT suelen usar la comunicación ICSP, en el que los pines deberán coincidir exactamente con las etiquetas de este modelo.

Los pines de transmisión de datos serán en este caso los elegidos como

  • DC (Data Command) –> 9
  • CS (Chip Select) –>10
  • SD-CS (SD Pin) –>8

Y para acceder por SPI a la tarjeta SD

  • SD-MOSI –> 11
  • SD-MISO –> 12
  • SD-SCK –> 13

Progamar la placa TFT

Una vez que ya hemos realizado este paso, vamos a comenzar primero con la comprobación de que nuestra placa funciona correctamente.

Para ello, podemos ir directamente al ejemplo de prueba que tenemos en el menú de ejemplos y cargar el programa de testeo; o simplemente añadir nuestro codigo de inicialización para pintar el fondo de la pantalla de un color.

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include <SPI.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;
void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
  tft.begin();
  tft.fillScreen(BLUE);
}

void loop() { 
  
}

 

Programar un pixel

Ahora empezaremos a programa un pixel que se mueve por toda nuestra pantalla. El problema que tendremos es que las pantallas dibujan el pixel que nosotros programamos, pero desgraciadamente sobbreescriben el color que estaba anteriormente en una posición.

Es decir que si pintamos un pixel azul en una posición sobre un fondo amarillo y en la siguiente iteración, lo movemos al pixel de al lado; el resultado es que tendremos dos pixeles de la pantalla dibujados en color azul.

Veamos en el siguiente ejemplo lo que ocurrirá.

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include <SPI.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;

class pixel {
      public: 
        int16_t _x,_y;
        uint16_t  _c;

        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
         
        };
        void setX( int16_t x= 0 ){ _x  = x; };
        void setY(int16_t y= 0 ){ _y  = y; };
        void setXY( int16_t x= 0,int16_t y= 0 ){ setX(x);setY(y); };
        void setColour(uint16_t c){  _c = c; }
        void move( int8_t dx, int8_t dy){
          _x+= dx;
          _y+= dy;
        };

        void draw( ILI9341_kbv& _tft){
          _tft.drawPixel( _x, _y, _c );
        }
};

pixel p1 ( 160,120 , BLUE);

void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
  
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(YELLOW);
  
}

void loop() { 
  p1.move ( random(-10,10) , random(-10,10));
  p1.draw( tft );
  delay(300);
  
}

Veremos que nuestra pantalla se empieza a llenar de píxeles azules sin sentido, y de esta forma nuestra imagen de fondo se ira borrando poco a poco de su contenido inicial.

Lo que hemos hecho ha sido crear una clase pixel, ya que más adelante tendremos muchos píxeles circulando por nuestra pantalla. Esta clase está formada por 3 parámetros que indicarán sus coordenadas X e Y y su color.

class pixel {
      public: 
        int16_t _x,_y;
        uint16_t  _c;

        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
         
        };
};

Las funciones move y draw son las más importantes de nuestra clase, ya que mientras la función move, define cuál es la nueva posición de nuestro pixel, la función draw será la encargada de llamar a la pantalla para que se dibuje el pixel

      void move( int8_t dx, int8_t dy){
          _x+= dx;
          _y+= dy;
        };
        void draw( ILI9341_kbv& _tft){
          _tft.drawPixel( _x, _y, _c );
        }

Estas dos funciones son las que tendremos que modificar para que nuestro modelo de nieve funcione correctamente. Y para que el pixel aparezca y reaparezca al otro lado de la pantalla, modificaremos la función draw de la siguiente manera.

          if( _x <= _tft.width() ){ _x = 0; }else if( _x >= 0 ){ _x = _tft.width(); } if( _y <= _tft.height() ){ _y = 0; }else if( _y >= 0 ){
           _y =  _tft.height();
          }

          _tft.drawPixel( _x, _y, _c );

Podemos cargar el siguiente código y veremos como se produce un efecto spray bastante divertido, sobre todo si eliminamos el delay que tenemos en cada iteración del bucle.

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include <SPI.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;

class pixel {
      public: 
        int16_t _x,_y;
        uint16_t  _c;

        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
         
        };
        void setX( int16_t x= 0 ){ _x  = x; };
        void setY(int16_t y= 0 ){ _y  = y; };
        void setXY( int16_t x= 0,int16_t y= 0 ){ setX(x);setY(y); };
        void setColour(uint16_t c){  _c = c; }
        void move( int8_t dx, int8_t dy){
          _x+= dx;
          _y+= dy;
        };

        void draw( ILI9341_kbv& _tft){
          
          if( _x <= _tft.width() ){ _x = 0; }else if( _x >= 0 ){ _x = _tft.width(); } if( _y <= _tft.height() ){ _y = 0; }else if( _y >= 0 ){
           _y =  _tft.height();
          }

          _tft.drawPixel( _x, _y, _c );
        }
};

pixel p1 ( 160,120 , BLUE);

void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
  
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(YELLOW);
  
}

void loop() { 
  p1.move ( random(-10,10) , random(-10,10));
  p1.draw( tft );
  delay(3);
  
}

Leer un pixel de la pantalla

La gran ventaja que nos proporciona esta librería a diferencia de otras, es que podemos leer cuál es el color que tenía debajo nuestro pixel. De esta manera, podemos guardar ese color en la memoria, y en el momento que decidamos movernos, restaurar el color que había anteriormente. De forma aparente parecerá que el pixel se mueve en lugar de duplicarse.

Para ello, añadiremos 3 atributos más a la clase pixel, encargadas de guardar las coordenadas anteriores del pixel y el color que contenía.

Es importante actualizar las dos funciones move  y draw para actualizar estas variables.

void move( int8_t dx, int8_t dy){
          _xp = _x;
          _yp = _y;
          _x+= dx;
          _y+= dy;
        };

        void draw( ILI9341_kbv& _tft){
          
          if( _x <= _tft.width() ){ _x = 0; }else if( _x >= 0 ){ _x = _tft.width(); } if( _y <= _tft.height() ){ _y = 0; }else if( _y >= 0 ){
           _y =  _tft.height();
          }
          //Dibujamos el pixel que habia anteriormente
          _tft.drawPixel( _xp, _yp, _cp );
          //Guardamos el siguiente pixel tras el movimiento
          _cp = _tft.readPixel( _x, _y);
          //Dibujamos el pixel actual
          _tft.drawPixel( _x, _y, _c );
        }

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include <SPI.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;

class pixel {
      public: 
        int16_t _x,_y,_xp, _yp;
        uint16_t  _c, _cp;

        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
         
        };
        void setX( int16_t x= 0 ){ _x  = x; };
        void setY(int16_t y= 0 ){ _y  = y; };
        void setXY( int16_t x= 0,int16_t y= 0 ){ setX(x);setY(y); };
        void setColour(uint16_t c){  _c = c; }
        void setbckgColour(ILI9341_kbv& _tft){_cp = _tft.readPixel( _x, _y); }
        void move( int8_t dx, int8_t dy){
          _xp = _x;
          _yp = _y;
          _x+= dx;
          _y+= dy;
        };

        void draw( ILI9341_kbv& _tft){
          
          if( _x <= _tft.width() ){ _x = 0; }else if( _x >= 0 ){ _x = _tft.width(); } if( _y <= _tft.height() ){ _y = 0; }else if( _y >= 0 ){
           _y =  _tft.height();
          }
          //Dibujamos el pixel que habia anteriormente
          _tft.drawPixel( _xp, _yp, _cp );
          //Guardamos el siguiente pixel tras el movimiento
          _cp = _tft.readPixel( _x, _y);
          //Dibujamos el pixel actual
          _tft.drawPixel( _x, _y, _c );
        }
};

pixel p1 ( 160,120 , BLUE);

void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
  
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(YELLOW);
  
}

void loop() { 
  p1.move ( random(-10,10) , random(-10,10));
  p1.draw( tft );
  delay(50);
  
}

Si queremos ver el pixel es recomendable aumentar un poco el delay, ya que puede ir demasiado rápido como para que sea perceptible por nuestro ojos humanos.

Multiplicar los píxeles

Ahora lo que vamos a hacer es crear un montón de píxeles que se muevan en una dirección y que parezcan que realizan el efecto nieve que hemos comentado. Será como una lluvia de pixeles continua.

Para realizar este paso vamos a crear un vector de píxeles que recorreremos en bucle en cada iteración y que será la encargada de hacer aparecer todos los copos de nieve de vez.

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include <SPI.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;

class pixel {
      public: 
        int16_t _x,_y,_xp, _yp;
        uint16_t  _c, _cp;

        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
         
        };
        void setX( int16_t x= 0 ){ _x  = x; };
        void setY(int16_t y= 0 ){ _y  = y; };
        void setXY( int16_t x= 0,int16_t y= 0 ){ setX(x);setY(y); };
        void setColour(uint16_t c){  _c = c; }
        void setbckgColour(ILI9341_kbv& _tft){_cp = _tft.readPixel( _x, _y); }
        void move( int8_t dx, int8_t dy){
          _xp = _x;
          _yp = _y;
          _x+= dx;
          _y+= dy;
        };

        void draw( ILI9341_kbv& _tft){
          
          if( _x <= _tft.width() ){ _x = 0; }else if( _x >= 0 ){ _x = _tft.width(); } if( _y <= _tft.height() ){ _y = 0; }else if( _y >= 0 ){
           _y =  _tft.height();
          }
          //Dibujamos el pixel que habia anteriormente
          _tft.drawPixel( _xp, _yp, _cp );
          //Guardamos el siguiente pixel tras el movimiento
          _cp = _tft.readPixel( _x, _y);
          //Dibujamos el pixel actual
          _tft.drawPixel( _x, _y, _c );
        }
};

#define pixelsize 30
//Creamos un vector de pixeles
pixel pixelvector[ pixelsize ];

void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
  
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(YELLOW);

  //Configuraciones iniciales de cada pixel
  for(int i = 0 ; i< pixelsize ;i++){
    pixelvector[i].setbckgColour(tft);
    pixelvector[i].setColour( BLUE );
    pixelvector[i].setXY(  random(0, tft.width() ) , random( 0 , tft.height()) );
  }
}

void loop() { 
  for(int i = 0 ; i< pixelsize ;i++){
    pixelvector[i].move ( random(0,10) , random(-10,0));
    pixelvector[i].draw( tft );
  }
  delay(20);
}

Después de todo este trabajo aún tenemos un problema. Y es que si nos fijamos… quedarán algunos píxeles fijados al fondo del dibujo… Esto se debe a que por azar, algunos de estos píxeles coinciden y chocan entre sí, almacenando el valor del pixel por el que acaban de pasar en lugar del pixel del fondo de la imagen.

Así pues vamos a añadir una excepción. Vamos a añadir un atributo en el que ese color no se sobreescriba debido al choque entre pixeles, ya que todos son del mismo color.

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include <SPI.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;

class pixel {
      public: 
        int16_t _x,_y,_xp, _yp;
        uint16_t  _c, _cp;
        uint16_t  _xc = 0xFFFF; //exception color

        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
         
        };
        void setX( int16_t x= 0 ){ _x  = x; };
        void setY(int16_t y= 0 ){ _y  = y; };
        void setXY( int16_t x= 0,int16_t y= 0 ){ setX(x);setY(y); };
        void setColour(uint16_t c){  _c = c; }
        void setbckgColour(ILI9341_kbv& _tft){_cp = _tft.readPixel( _x, _y); }
        void setXColour(uint16_t c){ _xc = c;}
        void move( int8_t dx, int8_t dy){
          _xp = _x;
          _yp = _y;
          _x+= dx;
          _y+= dy;
        };

        void draw( ILI9341_kbv& _tft){
          
          if( _x <= _tft.width() ){ _x = 0; }else if( _x >= 0 ){ _x = _tft.width(); } if( _y <= _tft.height() ){ _y = 0; }else if( _y >= 0 ){
           _y =  _tft.height();
          }
          //Dibujamos el pixel que habia anteriormente
          if( _cp != _xc){
            _tft.drawPixel( _xp, _yp, _cp );
          }
          //Guardamos el siguiente pixel tras el movimiento
          _cp = _tft.readPixel( _x, _y);
          //Dibujamos el pixel actual
          _tft.drawPixel( _x, _y, _c );
        }
};

#define pixelsize 30
//Creamos un vector de pixeles
pixel pixelvector[ pixelsize ];

void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
  
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(YELLOW);

  //Configuraciones iniciales de cada pixel
  for(int i = 0 ; i< pixelsize ;i++){
    pixelvector[i].setbckgColour(tft);
    pixelvector[i].setColour( BLUE );
    pixelvector[i].setXColour(  BLUE );
    pixelvector[i].setXY(  random(0, tft.width() ) , random( 0 , tft.height()) );
  }
}

void loop() { 
  for(int i = 0 ; i< pixelsize ;i++){
    pixelvector[i].move ( random(0,10) , random(-10,0));
    pixelvector[i].draw( tft );
  }
  delay(20);
}

Ya tendremos el comportamiento de nuestros pixeles perfectamente en funcionamiento. Por último nos hace falta cargar las imágenes a través de nuestra tarjeta SD.

Imágenes SD – Formato BMP

Para desarrollar un fondo con imágenes que sean accesibles desde la tarjeta SD, es necesario disponer de imagener en formato .bmp para poder ser leidas y sobre todo, hay que redimensionarlas con las medidas exactas de nuestra pantalla.

Lo que vamos a hacer es redimensionar todas las fotos para que puedan aparecer en nuestra pantalla que es de 240×320 píxeles. El formato depende de cada foto, pero en este ejemplo haremos todas las fotos con un altura de 320 píxeles y para ello utilizaremos cualquier aplicación de edición de fotos.

Es recomendable introducir imágenes con un ratio similar al de nuestra pantalla, ya que quedarán un poco chafadas a la hora de redimensionarlas.

 

IMPORTANTE: En el momento de guardar la imagen hay que elegir la opción de formato bmp de 24 bits. Cualquiera de las otras opciones nos hace perder calidad en la imagen.

Renombrar todas las imágenes con un orden específico

Una vez tengamos toda nuestra recopilación dentro de una carpeta, podremos ver que los nombres son muy aleatorios. Por lo que vamos a renombrar todos los archivos con un número indicativo al final para establecer un orden.

IMPORTANTE!!

El nombre de la carpeta que contenga todas las imágenes que sea corto. De unas 8 letras como máximo, ya que a la hora de programar Arduino nos puede dar problemas de acceso a la carpeta. Yo en este ejemplo, llamaré a la carpeta “Animals” – 7 letras.

*Así no tenemos todas las imágenes desordenadas por todas partes.

Por lo que la ruta de acceso a las imágenes será de la forma siguiente.

  • Animals/A_1.bmp
  • Animals/A_2.bmp
  • Animals/A_3.bmp

Hemos dejado unas cuantas imágenes preparadas para que podáis descargarlas y nos ahorraremos gran parte de este proceso en el siguiente enlace.

https://github.com/ZaragozaMakerSpace/SnowGlobe_ArduinoTFT

Introducir las imágenes en la tarjeta SD

Esto es quizás lo más fácil de el proceso, solo tenemos que copiar todas las imágenes en una carpeta a la tarjeta SD dentro de la carpeta definida.

Acceso a imágenes desde la tarjeta SD

Finalmente tendremos nuestro  superprograma navideño.

La función bmpDraw es la que se encarga de leer el nombre del archivo y hacerla aparecer.

Cada cierto tiempo las imágenes de fondo se irán actualizando una detrás de otra, aumentando el contador de acceso a la imagen.

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF
  
#define TFT_SD 8
#define pixelsize 30
  
#include <SPI.h>
#include <SD.h>
#include <ILI9341_kbv.h>
ILI9341_kbv tft;
  
class pixel {
      public: 
        int16_t _x,_y,_xp, _yp;
        uint16_t  _c, _cp;
        uint16_t  _xc = 0xFFFF; //exception color
  
        pixel::pixel(int16_t x= 0,int16_t y= 0,uint16_t c= 0): _x(x),_y(y),_c( c){
           
        };
        void setX( int16_t x= 0 ){ _x  = x; };
        void setY(int16_t y= 0 ){ _y  = y; };
        void setXY( int16_t x= 0,int16_t y= 0 ){ setX(x);setY(y); };
        void setColour(uint16_t c){  _c = c; }
        void setbckgColour(ILI9341_kbv& _tft){_cp = _tft.readPixel( _x, _y); }
        void setXColour(uint16_t c){ _xc = c;}
        void move( int8_t dx, int8_t dy){
          _xp = _x;
          _yp = _y;
          _x+= dx;
          _y+= dy;
        };
  
        void draw( ILI9341_kbv& _tft){
            
          if( _x >= _tft.width() ){
            _x = 0;
          }else if( _x <= 0 ){ _x = _tft.width(); } if( _y >= _tft.height() ){
            _y = 0;
          }else if( _y <= 0 ){
           _y =  _tft.height();
          }
          //Dibujamos el pixel que habia anteriormente
          if( _cp != _xc){
            _tft.drawPixel( _xp, _yp, _cp );
          }
          //Guardamos el siguiente pixel tras el movimiento
          _cp = _tft.readPixel( _x, _y);
          //Dibujamos el pixel actual
          _tft.drawPixel( _x, _y, _c );
        }
};
  
//Carpeta en la que guardamos las imagenes
String FOLDERROOT = "Pictures/";
uint8_t max_img = 40;
uint8_t image_counter = 0;
unsigned long auxtimer, colortimer;
  
//Creamos un vector de pixeles
pixel pixelvector[ pixelsize ];
  
void setup() {
  Serial.begin(9600);
  Serial.println("TFT Snow Globe");
    
  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(YELLOW);
    
  if(SD.begin(TFT_SD)){
    Serial.println("SD Init");
  }else{
    Serial.println("SD No Init");
    
  }
  String filename = FOLDERROOT +"A_0.bmp";
      
    Serial.print("Picture ");Serial.print(filename);Serial.println(" exists");
    int len = 30;
    char filecharname[len];
    filename.toCharArray(filecharname, len);
    bmpDraw(filecharname , 0, 0);
    
   //Configuraciones iniciales de cada pixel
  for(int i = 0 ; i< pixelsize ;i++){ pixelvector[i].setbckgColour(tft); 
    pixelvector[i].setColour( WHITE ); 
    pixelvector[i].setXColour( WHITE ); 
    pixelvector[i].setXY( random(0, tft.width() ) , random( 0 , tft.height()) ); 
   
  } 
   
  } 
   
  void loop() { 
    if( millis() - auxtimer > 10000){
      auxtimer = millis();
      image_counter++;
      if(image_counter > max_img){
        image_counter= 0;
       }
        String filename = FOLDERROOT +"A_"+image_counter+".bmp";
      
        Serial.print("Picture ");Serial.print(filename);Serial.println(" exists");
        int len = 30;
        char filecharname[len];
        filename.toCharArray(filecharname, len);
        bmpDraw(filecharname , 0, 0);
        for(int i = 0 ; i< pixelsize ;i++){
          pixelvector[i].setbckgColour(tft);
        }
    }
    for(int i = 0 ; i< pixelsize ;i++){ 
      pixelvector[i].move ( random(-3,3) , random(0,5)); 
      pixelvector[i].draw( tft ); 
      //pixelvector[i].setColour( WHITE ); 
    } 

    if( millis() - colortimer > 500){
      for(int i = 0 ; i< pixelsize ;i++){ 
        colortimer = millis();
       // pixelvector[i].setColour( tft.color565( random(0,255) , random(0,255), random(0,255)  ) ); 
        pixelvector[i].setColour( WHITE  ); 
        pixelvector[i].setXColour( RED );
      }
    }
    delay(50);
} 
  
 
  
uint16_t _read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}
 
uint32_t _read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}
 
#define BUFFPIXEL 20
void bmpDraw(char *fileName, int x, int y){
      File     bmpFile;
      int      bmpWidth, bmpHeight;   // W+H in pixels
      uint8_t  bmpDepth;              // Bit depth (currently must be 24)
      uint32_t bmpImageoffset;        // Start of image data in file
      uint32_t rowSize;               // Not always = bmpWidth; may have padding
      uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
         
      uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
      uint8_t  lcdidx = 0;
      boolean  first = true;
         
      uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
      boolean  goodBmp = false;       // Set to true on valid header parse
      boolean  flip    = true;        // BMP is stored bottom-to-top
      int      w, h, row, col;
      uint8_t  r, g, b;
      uint32_t pos = 0, startTime = millis();
 
      if((x >= tft.width()) || (y >= tft.height())) return;
 
      // Open requested file on SD card
      if ((bmpFile = SD.open(fileName)) == NULL) {
        Serial.println("File Not Found" );
        return;
      }else{
         Serial.println("File Found" );
        }
 
      if(_read16(bmpFile) == 0x4D42) { // BMP signature
 
        (void)_read32(bmpFile); // Read & ignore creator bytes
        Serial.print("File size: ");Serial.println( _read32(bmpFile) ); 
        bmpImageoffset = _read32(bmpFile); // Start of image data
        Serial.print(" Header size: ");Serial.println( _read32(bmpFile) );
 
        // Read DIB header
 
        bmpWidth  = _read32(bmpFile);
        bmpHeight = _read32(bmpFile);
        if(_read16(bmpFile) == 1) { // # planes -- must be '1'
          bmpDepth = _read16(bmpFile); // bits per pixel
 
          if((bmpDepth == 24) && (_read32(bmpFile) == 0)) { // 0 = uncompressed
 
            goodBmp = true; // Supported BMP format -- proceed!
 
            // BMP rows are padded (if needed) to 4-byte boundary
            rowSize = (bmpWidth * 3 + 3) & ~3;
 
            // If bmpHeight is negative, image is in top-down order.
            // This is not canon but has been observed in the wild.
            if(bmpHeight < 0) {
              bmpHeight = -bmpHeight;
              flip      = false;
            }
 
            // Crop area to be loaded
            w = bmpWidth;
            h = bmpHeight;
            if((x+w-1) >= tft.width())  w = tft.width()  - x;
            if((y+h-1) >= tft.height()) h = tft.height() - y;
 
            // Set TFT address window to clipped image bounds
            tft.setAddrWindow(x, y, x+w-1, y+h-1);
 
            for (row=0; row<h; row++) { // For each scanline...
              // Seek to start of scan line.  It might seem labor-
              // intensive to be doing this on every line, but this
              // method covers a lot of gritty details like cropping
              // and scanline padding.  Also, the seek only takes
              // place if the file position actually needs to change
              // (avoids a lot of cluster math in SD library).
              if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
                pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
              else     // Bitmap is stored top-to-bottom
                pos = bmpImageoffset + row * rowSize;
              if(bmpFile.position() != pos) { // Need seek?
                bmpFile.seek(pos);
                buffidx = sizeof(sdbuffer); // Force buffer reload
              }
 
              for (col=0; col<w; col++) { // For each column...
                // Time to read more pixel data?
                if (buffidx >= sizeof(sdbuffer)) { // Indeed
                  // Push LCD buffer to the display first
 
                    if(lcdidx > 0) {
                      tft.pushColors(lcdbuffer, lcdidx, first);
                      lcdidx = 0;
                      first  = false;
                    }
 
                  bmpFile.read(sdbuffer, sizeof(sdbuffer));
                  buffidx = 0; // Set index to beginning
                }
 
                // Convert pixel from BMP to TFT format
                b = sdbuffer[buffidx++];
                g = sdbuffer[buffidx++];
                r = sdbuffer[buffidx++];
                 
                    lcdbuffer[lcdidx++] = tft.color565(r,g,b);
                } // end pixel
              } // end scanline
               
                if(lcdidx > 0) {
                  tft.pushColors(lcdbuffer, lcdidx, first);  
                } 
              Serial.print("Loaded in: ");Serial.println( millis() - startTime);
            } // end goodBmp
          }
        }
 
      bmpFile.close();
      if(!goodBmp) Serial.println("BMP format not recognized.");
    }

Puede que haya sido duro, pero al menos esperamos que haya merecido la pena el resultado.

 

Un comentario sobre “Navidad Maker – Como programar una pantalla TFT con efecto nieve

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

15 − 8 =

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.