Gráficos y OLED 0.96” 128×64

Objetivos

 

  • Presentar los gráficos en el display OLED de 0.96”.
  • Presentar las primitivas básicas.
  • Generar un diagrama de barras leyendo los valores de las puertas analógicas.
  • Generar un gráfico de líneas que se van desplazando continuamente hacia la izquierda.
  •  

    Material requerido.

     

    Imagen de Arduino UNO   Arduino UNO.
    Protoboard Una Protoboard .
    conexiones Algunos cables de protoboard.
    Display de 128x64 pixels  Un display OLED de 0.98” SPI.

     

     

    Los gráficos y el OLED de 128×64

     

    Como os contaba en la sesión anterior, he disfrutado mucho de este pequeño display y como habíamos dejado pendiente el tema de los gráficos, no he podido resistirme a darle otra vuelta al asunto y ver como lo resolvíamos.

    No sería difícil de hacer primitivas gráficas sencillas para complementar este display, pero me pareció absurdo que no pudiéramos usar las magníficas librerías graficas GFX de Adafruit que ya probamos en algún capitulo anterior y que para algo se ha currado el tema esta gente (A la que nuevamente renuevo mis agradecimientos).

    Así que me dispuse a darle otro repaso al tema y buscar el problema porque en el primer intento me ganó por goleada y desistí. Me puse un cafecito cargado y empecé las pruebas para ver qué pasaba y…. funcionó todo a la primera. Se ve que el día anterior estaba más tonto de lo habitual.

    Bastó con definir correctamente los pines a la entrada de los programas de ejemplos y arrancó inmediatamente con todas las funciones gráficas activadas.

    Naturalmente, conecté el display OLED directamente a mi Arduino, por pura vagancia y cuando me canse de ver el ejemplo gráfico me puse a ver cómo definir un par de programas más y el resultado, lo tenéis delante. Confio en que os pueda servir de algo.

     

    Conexión de un displays OLED de 128×64

     

    Lo primero es que conectéis vuestro display como en la sesión anterior. Insertando el primer pin GND del display OLED en el pin GND de Arduino que tenemos al lado del digital pin 13, con lo que el resto de los pines encajaran en su sitio sin más.

    Ahora la única cuestión es descargar e instalar las librerías correspondientes de Adafruit. La primera es Adafruit_SSD1306 que corresponde al chip de nuestro display, y que no habíamos usado hasta ahora.

    La segunda librería es la gráfica de Libreria Adafruit GFX, que ya habíamos utilizado anteriormente en las sesión Display TFT SPI 1.8″ y que incluye muchas primitivas básicas de dibujo. Instaladlas siguiendo el procedimiento habitual.

    Una vez listo buscad en el menú \\Archivo\ejemplos\\ Adafruit_SSD1306 el ejemplo SSD1306_128x64_SPI, que es el modelo del que dispongo pero fijaros que también hay ejemplos para display  I2C y para 32 pixels de altura en lugar de 64.

    Ahora tenemos que redefinir los pines de conexión para que correspondan a la conexión que hemos hecho. Reemplaza el código siguiente:

    // If using software SPI (the default case):
    #define OLED_MOSI  11   // 9
    #define OLED_CLK   12   //10
    #define OLED_DC     9   //11
    #define OLED_CS    12
    #define OLED_RESET 10   //13
    
    Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

    De este modo mapeareis correctamente los pines. Además tenemos que alimentar el OLED dando tensión al pin 13. Para ello tienes que incluir estas líneas en el setup del ejemplo:

    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);

    Y eso es todo. Volcad el programa y disfrutad el espectáculo.

  • El programa de ejemplo es grande y lleva la memoria del Arduino UNO muy cerca de su límite, por lo que veréis mensajes de que puede haber problemas. No le deis más importancia recordad que podéis usar cualquier modelo de Arduino.
  •  

    Aquí os dejo el ejemplo ya modificado para que lo probéis: Prog_43_1, y mini video con el tema:

     

    Un diagrama de barras o Bar Chart

     

    ¿Qué hace un ingeniero cuando resuelve algo demasiado pronto? Pues venirse arriba y complicarse la vida buscando algo más difícil, naturalmente.

    La imagen promocional de este display OLED es un diagrama de barras que baila en la pantalla, como si fuera un pequeño ecualizador gráfico, al ritmo de la música mp3, por ejemplo.

    Así que se me ha ocurrido montar un programa que nos permitiese mostrar los valores que leemos en las puertas analógicas en forma de barras de altura proporcional a la señal leída.

    Veamos cómo empezar el tema, y de paso aprovechamos para hacer un pequeño tutorial de esta librería GFX de Adafruit.

    Necesitamos las librerías de SPI y WIRE de Arduino, más las librerías de Adafruit graficas GFX y la que corresponde al chip que controla nuestro display OLED, la SSD_1306:

    #include <SPI.h>
    #include <Wire.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>

    Después tenemos que definir los pines de control de nuestro display, que son los mismo que comentamos en la sección previa:

    #define OLED_MOSI  11   // 9
    #define OLED_CLK   12   //10
    #define OLED_DC     9   //11
    #define OLED_CS    12
    #define OLED_RESET 10   //13

    La siguiente línea crea una instancia del display SSD_1306:

    Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

    Para simplificar un poco los problemas, vamos a usar barras de 16 pixels de ancho porque 128/16 son 8 barras, y para ordenar el problema definiremos un array de 8 elementos, data [8] que contendrá las alturas de las 8 posibles barras.

    const int ancho = 16 ;
    byte data[8] ;

    En el setup, iniciamos el display y lo borramos:

    void setup()  
       {              
          display.begin(SSD1306_SWITCHCAPVCC);    // Iniciar el display
          display.clearDisplay();                 // Borrar todo
       }

    Como la máxima altura de la barra es de 64 pixels, la del display OLED, calcularemos la altura como 64 * Lectura / 1024. Haremos esto para cada puerta analógica y lo guardaremos en data[]:

    void loop()
      {
         for (int i=0 ; i<6 ; i++)
              data[i] = 64 * analogRead( i )/1024 ;
         barChart( );
         delay(150);
      }

    Usamos un bucle de 0 a 5 para leer las puertas analógicas de A0 a A5, porque estoy  usando un UNO. SI usáis un MEGA o DUE, podéis subir este límite hasta 8.

  • Porque no caben más de 8 con el ancho de 16 pixels, pero podrías tocar la const ancho y bajar la anchura a 12 pixels y podrías usar 10 barras simultaneas. 
  •  

    Usaremos la función display.fillRect () de las librerías GFX, que nos rellena un rectángulo de color. Los parámetros a pasarle son, el punto de inicio  X0, Y0, la anchura y la altura del rectángulo y luego un parámetro que es 0 para rellenar con negro y 1 para rellenar con blanco.

  • Nótese que el punto (0,0) está en la esquina superior izquierda del display y no en la esquina inferior izquierda como suele ser habitual en las representaciones cartesianas. 
  •  

    Veamos la función:

    void barChart()
       {
          display.clearDisplay();
          for (int j=0 ; j<6 ; j++)
            {  int X0 = ancho * j ;
               int X1 = ancho - 2 ;
               byte H = data[j];
               display.fillRect( X0,64-H ,X1 , 64, 1 );
            }
          display.drawLine(0, 63, 127, 63, 1);
          display.display();
       }

    Hacemos un for de 0 a 5 para calcular las posiciones de las 6 barras. Y calculamos X0 en función del número de barra que toca dibujar, el ancho de la barra es ancho – 2 para tener una zona de separación.

    La altura H, no tiene perdida, la leemos del array data [], pero… (Siempre hay un pero) si llamásemos a Fillrect así:

    display.fillRect( X0, H , X1 , 0, 1 );

    Las barras colgarían del techo, como los murciélagos y las estalactitas, y la costumbre es que la altura de las barras se mida desde el suelo, por lo que tenemos que invertirlas  haciendo:

    display.fillRect( X0,64-H ,X1 , 64, 1 );

    Piénsalo, verás como es así. Tenéis que saber que si dibujas algo, para que estas librerías muestren los cambios de lo que has dibujado en pantalla tienes que invocar a display.display () .Por eso cuando entramos a la función borramos la pantalla y al salir le pedimos que refresque la pantalla.

    Aquí os dejo el programa listo para ejecutar

    Prog_43_2

    ¡Accede al contenido!

    y un pequeño video que muestra el resultado en forma de barras bailando y siguiendo a un par de potenciómetros.

     

    Dibujando líneas que siguen la lectura

     

    Como dibujar las barras han sido demasiado fácil, necesitamos buscarnos problemas mayores. Así que vamos a probar a ver qué pasa con un gráfico tipo las de esas máquinas que muestran el ritmo cardiaco del paciente, en las que un punto va barriendo la pantalla y dejando una estela que nos muestra los valores anteriores.

    Vamos a empezar con la parte más sencilla, que es dibujar un punto en cada abscisa (O sea la X) y cuando alcanza el final por la derecha simplemente borra y vuelve a empezar. Aquí teneis

    Prog_43_3.

    Empezamos con incluir  algunas librerías y definir los pines correspondientes:

    #include <SPI.h>
    #include <Wire.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    
    #define OLED_MOSI  11   // 9
    #define OLED_CLK   12    //10
    #define OLED_DC     9      //11
    #define OLED_CS    12
    #define OLED_RESET 10   //13
    
    Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

    En esto nada nuevo, y tampoco en el setup.

    void setup()
       {
          display.begin(SSD1306_SWITCHCAPVCC);
          display.clearDisplay();
       }

    Vamos ahora con la parte difícil:

    void loop()
       {
          display.clearDisplay();
          for ( byte x = 0 ;  x<128 ; x++)
            {    int d = 64 * analogRead( 0 )/1024 ;
                 display.drawPixel(x, d, 1);
                 display.display(); 
            }
          delay(500);
       }

    ¡Accede al contenido!

    ¿En serio os habias creido que era difícil? La parte difícil, no acaba de aparecer, por lo menos hasta ahora. Asi que me dispuse a grabar la imagen aleatoria que generaba el display al leer la puerta A0 y tuve una sorpresa. Echar una ojeada al video y me diréis:

     

    Actualizando el display de modo continúo

     

    No está mal, pero seguro que podemos mejorarlo. Como no estamos dibujando más que puntos sueltos cuando el cambio es rápido los puntos aparecen inconexos, y además como nos sobra tiempo, queremos que el display se vaya desplazando hacia la izquierda de modo continuo para hacer sitio a las nuevas lecturas.

    Lo primero que se me ocurre es usar un array de 128, para guardar las lecturas a medida que se produzcan e ir moviendo a la izquierda la pantalla que ya está dibujada.

  • Probablemente haya alguna función que me mueve una serie de pixels a la izquierda, pero como lo que quiero es ilustrar la idea de un buffer circular, no voy a buscarla.
  •  

    En al pantalla solo tengo que dibujar los últimos 128 pixels, pero el número de lecturas puede ser infinito, ¿Cómo puedo resolver esto?

    La solución es un buffer circular. La idea es que reservo un array de 128 bytes y un índice que llamaremos index que crece a con cada nueva lectura.

    Buffer circular

    Al empezar index vale 0 pero va creciendo., y lo que queremos es que apunte al 0 cuando sobrepasa el final del array a la lectura 128. ¿Y cómo hacer esto?  Así de fácil:

    index = index % 128 ;

    A medida que el index crece como tomamos su resto con 128, al alcanzar ese valor se pone a 0 él solito. Si grabamos la lectura actual en la posición que indica index++,  su valor recorre correrá desde 0 127 y volverá a empezar de modo que nunca rebasaremos su capacidad y conservamos las 128 últimas lecturas para poderlas dibujar. ¿Qué os parece?

    A este truco se le llama buffer circular y es muy útil en multitud de ocasiones. Para almacenar las lecturas de A0 tenemos que ajustarlas a escala:

    index = index % 128 ;
    data[index] = 64 * analogRead(A0)/1024 ;
    index++ ;

    Ya tenemos resuelto conservar las 128 últimas lecturas, ¿Pero cómo dibujarlas? Probemos con esto:

    byte i = index ;
    for (byte x = 0 ;  x <128 ; x++)
       {  i = i % 128 ;
          display.drawPixel( x, data[i++], 1);
       }
    display.display();

    Index apunta a la posición del valor más antiguo que tenemos (Lo que significa que será el próximo a borrar) y por tanto lo tengo que dibujar en x=0, el mas a la izquierda, pero no puedo tocar index porque lo necesitamos para saber dónde escribir el próximo valor, así que hago

    byte i = index ;

    Y así ya puedo jugar con i. Ahora uso un for con valores de x de 0 a 127. Solo me queda asignar la altura leyendo el array con i de índice, pero antes la aplicamos el truco de tomar su resto, para que al llegar a 127 se ponga a 0 y no tenga que hacer comprobaciones de si es mayor de 128 y cosas parecidas (Elegancia, mucha elegancia siempre).

    Así que x va de 0 a la izquierda hasta 127, el último punto a la derecha, y la altura son los valores guardados en el array que lo recorremos de modo circular con el truco del resto.

  • Podéis crear un buffer circular de cualquier tamaño con este sencillo sistema y recorrerlo con seguridad, del mismo modo.
  • Fijaros que hacemos data[i++], esto significa que primero usamos el valor de i y luego lo incrementamos. Si fuera ++i seria incrementa primero y úsalo después.
  •  

    El único problema con este planteamiento es que al dibujar puntos, si la variación es importante con relación al valor anterior, los puntos quedaran muy separados, lo que no resulta muy elegante, así que lo suyo seria unir estos puntos con una línea recta, mediante la instrucción:

    display.drawLine(x0, y0, x1, y1, 1) ;

    Y como disponemos de la historia de los puntos en el array data, podemos hacer :

    display.drawLine(x, data[i], x-1, data[i-1], 1);
    i++ ;

    Aquí os dejo el programa completo Prog_43_4

    ¡Accede al contenido!

    Y por último un mini video con el resultado:

     

    Resumen de la sesión

     

  • Presentar los gráficos en el display OLED de 0.96”.
  • Presentar las primitivas básicas.
  • Generar un diagrama de barras leyendo los valores de las puertas analógicas.
  • Generar un gráfico de líneas que se van desplazando continuamente hacia la izquierda.
  •  

    Deja una respuesta