Pantalla OLED SSD1306

Las tiendas online están saturadas con pantallas OLED de 0.96 pulgadas basada en el controlador SSD1306 que resulta ideal para utilizar con nuestros proyectos Arduino.

Pantalla OLED para proyecto Arduino 128x64Algunas propuestas incluyen OLED monocromo, otras con tintes de un color, o incluso como esta pantalla OLED que estamos utilizando aquí, tiene un area superior de 16 pixeles de grosor de la pantalla en OLED amarillo y el resto en OLED celeste.

El costo es razonable, usualmente inferior a los 10 dólares por pantalla OLED.

En cuanto a su calidad de funcionamiento, la visibilidad es excepcional, como corresponde a la tecnología OLED, pero no descarten un poco de luminancia  perdida en las áreas más utilizadas luego de un par de meses de uso.

Características técnicas de la pantalla OLED SSD1306

La pantalla OLED de 0.96 pulgadas está basada en la controladora SSD1306 y puede funcionar con un rango de voltaje de 2.2v a 5.5v.

Esto para mi fue una buena noticia a la hora de usar la pantalla OLED en mi proyecto de Arduino con un ESP8266 que opera a 3.3V.

El interface con Arduino puede darse por 2 o 3 hilos ( i2c ó Spi). Yo prefiero i2c, aunque el SPI es sustancialmente más rápido. En todo caso les recomiendo que verifiquen antes de comprar que conectividad incluye.

En mi pantalla OLED, la dirección para comunicarse es 0x3C. En tu caso, debes verificar con la web del fabricante o vendedor.

Programando la pantalla OLED SSD1306

La controladora SSD1306 para pantallas OLED tiene varias librerías disponibles desde el Manager de librerías del IDE Arduino.  En mi caso, selecciono la Adafruit SSD1306.

También será necesario descargar otra librería que agrega la funcionalidad gráfica que seguramente necesites, llamada Adafruit_GFX.

Pasemos a ver un poquito de código: Voy a unir la pantallita OLED al sensor de temperatura, humedad y presión BME280.

La idea es mostrar en los dos renglones amarillos superiores, los datos numéricos. El resto de la pantalla se dividirá en seis cuadrantes de igual tamaño.

Los tres cuadrantes de arriba graficarán en tiempo real cada parámetro. Los tres de abajo, tendrán la gráfica histórica de las últimas 8 horas de valores.

La controladora que utilizo es una ESP8266 genérica.

/*
Connectar sensor BME280 
Sensor -> Arduino
-----------------------------
Vin (Voltage In) -> 3.3V
Gnd (Ground) -> Gnd
SDA (Serial Data) -> A4 en Uno/Pro-Mini, 
                   20 en Mega2560/Due, 2 Leonardo/Pro-Micro
SCK (Serial Clock) -> A5 en Uno/Pro-Mini, 21 en Mega2560/Due,
                       3 Leonardo/Pro-Micro
Pantalla OLED 128 x 64: primeros 16 pixeles son para
                        los dos renglones de texto. 
Del pixel 17 para abajo, quedan 47 pixeles para graficar
1 pixel lo utilizo para la linea divisoria.
Si quiero dos renglones de gráficas por valor,
cada gráfica puede tener 23 pixeles de alto
y para graficar tres parametros temp, humid y pres, 
para cada uno tengo 42 de ancho 
(y 2 pixeles para lineas divisorias entre las tres gráficas)

 ==== Includes ==== */
#include <BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h> // ESP8266 para definir pines i2c
/* ==== Defines ==== */
#define SERIAL_BAUD 115200
#define ejex 42 //cantidad de valores que se grafican 
                //(largo ejeX) y guardan en array
#define ejey 23 //altura de cada gráfica
#define pausa 1000 //demora en milisegundos entre cada 
                   //captura y almacenamiento de datos
#define pausah 49 //cantidad de ciclos de 42 capturas 
                  //instantáneas que tienen que transcurrir 
                  //para guardar un histórico. 
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

/* ==== Global Variables ==== */
BME280 bme(1,4,3,3,5,2,false,0x76); 
Adafruit_SSD1306 ssd;

float temp(NAN), hume(NAN), pres(NAN);
bool metric = true;
uint8_t pressureUnit(B001); 
 //te hu y pr arrays para temp hum y pres instantáneos ...
int te[ejex]; 
int hu[ejex];
int pr[ejex];
//teh huh y prh arrays para guardar valores de 
// temp hum y pres cada 12 minutos aprox. 
// y asi tener una gráfica de las ult. 8 horas aprox.
int teh[ejex];
int huh[ejex];
int prh[ejex];
// teprom, huprom y prprom son acumuladores para ir 
// calculando promedios de instantáneas y guardarlos 
// en los arrays históricos
long teprom, huprom, prprom;
int promediar;
//Contadores varios para guardar datos en arrays
byte cont,conth,waith;

/* ==== Setup ==== */
void setup() {
 Wire.begin(2,14); //GPIO2 es D4 y GPIO14 es D5 en el ESP8266
 ssd.begin(0x3C); // inicializo OLED con la dir de datos
 
 ssd.clearDisplay();
 ssd.setTextSize(0);
 ssd.setTextColor(WHITE);
 ssd.setCursor(0, 0);
 while(!Serial) {} // Wait
 while(!bme.begin()){
 ssd.println("Sensor BME280 no responde!");
 ssd.display();
 delay(1000);
 }

 cont=0; //reseteo contador de puntos grafica "instantánea"
 waith = 0; //reseteo contador de espera x guardar 
            //valor en histórico 
            //(va sumando uno hasta contar cada N vueltas de 
            //cont y ahi guarda nuevo punto en historico 
            //y suma uno en conth)
 conth = 0; //reseteo el contador de puntos 
            //a plotear grafica "historica"

// Levanto los datos del sensor en la previa para arrancar 
// con valores reales haciendo un prefill de los arrays
bme.ReadData(pres, temp, hume, metric, pressureUnit); 
 for (byte i = 0; i < ejex; i++) {
 te[i] = round(temp);
 teh[i] = te[i];
 hu[i] = round(hume);
 huh[i] = hu[i];
 pr[i] = round(pres * 10);
 prh[i] = pr[i];
 }
}

/* ==== Loop ==== */
void loop() {
 ssd.clearDisplay(); //Limpio pantalla
 //IMPRIMO CABECERA CON TEMP HUMED PRESION
 ssd.setCursor(0,0); ssd.print("Temp");
 ssd.setCursor(42,0); ssd.print("Humed");
 ssd.setCursor(85,0); ssd.print("Presion");

//IMPRIMO VALORES CON UN LUGAR DESPUES DE LA COMA:
 ssd.setCursor(0,9); ssd.print(temp,1); ssd.print("'"); 
 ssd.setCursor(42,9); ssd.print(hume,0); ssd.print("%"); 
 ssd.setCursor(85,9); ssd.print(pres,1);
 
//Armo cuadrícula para las gráficas
 LineaDotH(0, 40, 128); //horiz medio dotteado
 LineaDotV(42, 16, 48); //separador vert 1/3
 LineaDotV(84, 16, 48); //separador vert 2/3

 //GRAFICO DATOS INSTANTANEOS
 grafico(0,40,te,cont); // Temperatura instantanea
 grafico(42,40,hu,cont); // humedad instantanea
 grafico(84, 40, pr, cont);// presion instantanea
 
//GRAFICO DATOS HISTORICOS
 grafico(0, 63, teh, conth); // Temperatura historica
 grafico(42, 63, huh, conth); // humedad historica
 grafico(84, 63, prh, conth);// presion historica

//Almaceno datos en array instantáneo con un multiplicador
// para preservar en la presión y temperatura el primer 
// decimal luego de la coma aún siendo el array un integer. 
 te[cont] = round(temp * 10);
 hu[cont] = round(hume);
 pr[cont] = round(pres * 10);

 //agrego estos valores a los contadores de promedios
 teprom += temp;
 huprom += hume;
 prprom += pres;

 promediar++; //Aumento divisor para promediar
 cont++; //Aumento contador de almacenamiento para datos instantáneos
 
// Si me paso del array, lo vuelvo a cero 
// y sobreescribo info mas vieja.
if (cont == ejex) {
 cont = 0; 

 //Ahora con estos 41 valores, busco promedio 
 //y almaceno resultado en el array de historico
 waith++;
 if (waith == pausah)
 { //Tengo un nuevo punto para almacenar en la gráfica de históricos
 //Almaceno promedios de todo este período y reseteo 
 //contadores de promedio
 teh[conth] =(teprom / promediar) * 10;
 huh[conth] =huprom / promediar;
 prh[conth] =(prprom / promediar) * 10;
 promediar = 0;
 teprom = huprom = prprom = 0;
 conth++;

//Si ploteé el ultimo punto del array, vuelvo a cero 
//y sobreescribo info mas vieja
 if (conth == ejex) conth = 0; 
 waith = 0; //reseteo espera para plotear próximo histórico
 }
 }

//Refresco la pantalla y levanto nuevos valores 
//para repetir el ciclo
 ssd.display();
 bme.ReadData(pres, temp, hume, metric, pressureUnit); 
 delay(pausa);
}

//Funcion que grafica el array de valores, 
//con el offset x e y para arrancar en el tercio correcto 
// de la pantallita OLED.  El valor yaxis marca el eje 
// horizontal donde tengo que generar esa grafiquita
// que puede tener "ejey" pixeles para arriba
void grafico(byte xoffset,byte yaxis, int valores[], byte puntero) 
{
 int maximo, minimo, cero;
 long valor;
 maximo=-30000;
 minimo=30000;
 //Primero hallo máximo y mínimo en este array de 41 valores
 for (byte i=0; i<ejex; i++) {
 valor= valores[i];
 if(maximo < valores[i]){ maximo=valores[i]; } 
else if (minimo > valores[i]){ minimo=valores[i];}
 }

// Bajamos el mínimo a 0 y máximo queda ajustado a escala
maximo=maximo-minimo; 

// si el rango maximo es menor a la cantidad de puntos 
//en el ejey que podemos graficar, lo aumentamos a ese rango
if (maximo < ejey) maximo=ejey;

//Genero el ploteo en la pantalla OLED
//Voy de atrás hacia el inicio, en le array:
for (byte i=ejex; i > 0; i--) {
  if (puntero > 0) {puntero--;}
  else {puntero=ejex-1;}

  valor=valores[puntero]; //cargo valor a graficar

  //Adecuo el valor a graficar para el piso "0" 
  valor = valor - minimo;

  //Hago regla de tres contra la altura del ejey y el maximo
  valor = (valor * ejey) / maximo;

 ssd.drawFastVLine(xoffset + i,yaxis-valor,valor,WHITE);
 }
}

//Funcion de linea horizontal doteada cada 5 pixeles
void LineaDotH(byte x, byte y, byte largo)
{
 largo = x + largo - 1;
 for (byte i = x; i < largo; i = i + 5) { 
 ssd.drawPixel(i, y, WHITE); }
}

//Funcion de linea vertical doteada cada 5 pixeles
void LineaDotV(byte x, byte y, byte largo)
{
 largo = y + largo - 1;
 for (byte i = y; i < largo; i = i + 5) { 
  ssd.drawPixel(x, i, WHITE); }
}

Si este código les es útil, me encantará leer sobre ello en el area de comentarios.

Como siempre cualquier idea, mejora o duda del código o la explicación con respecto a la pantalla OLED SSD1306, será bienvenida también en el área de comentarios.

11 respuestas a «Pantalla OLED SSD1306»

  1. Hola, a que pines del esp8266 conectas el ssd3106 y el bme280? van los dos al bus i2c?
    entiendo que el SDA al D4 (GPIO2) y el SCL al D5 (GPIO14)

    saludos

    1. Hola Diego, si, cuando inicializo el interfaz i2c («Wire») coloco los GPIO a los que quiero conectar el i2c: Wire.begin(2,14); //GPIO2 es D4 y GPIO14 es D5 en el ESP8266

  2. Todavía no se porque no me funciona la librería de adafruit_ssd1306, están los pines bien, de hecho con la librería ssd1306Wire el oled funciona bien
    El reset del display va a GPIO4 (D2) ?
    vos lo conectas?

  3. Hola Diego,
    Disculpa pero cuando pruebo el sketch tal cual, en la linea 40 me da error BMP no es un tipo de variable.
    si le pongo int entonces dice que bmp no esta declarado.
    Estoy perdido.
    Saludos y gracias por la ayuda

  4. Hola, He puesto la libreria generica del sensor BME280
    al compilar, en la linea 40 me da los siguientes errores:
    He buscado en foros y he probado mil cosas pero nada. Soy novato.
    Si me puedes encaminar… Gracias por tu tiempo!

    Arduino:1.8.0 (Windows 10), Tarjeta:»NodeMCU 1.0 (ESP-12E Module), 80 MHz, 115200, 4M (3M SPIFFS)»

    IOTuy2:47: error: ‘BME280’ does not name a type
    BME280 bme(1,4,3,3,5,2,false,0x76);

    C:\Users\Andreu\Documents\Arduino\IOTuy2\IOTuy2.ino: In function ‘void setup()’:
    IOTuy2:81: error: ‘bme’ was not declared in this scope
    while(!bme.begin()){

    Saludos

    1. Andreu, has podido ya resolver esto ? Se me pasó por alto tu comentario. Yo supongo que la librería BME que estás usando define el tipo BME de otra manera (no BME280) por lo que no ocurre la «magia» y el entorno de Arduino para programar que estás utilizando no entiende que estás definiendo.

      Tendrías que buscar ejemplos específicos de esa librería que estás utilizando, para ver como definen el objeto bme (a partir de que nombre / tipo).

      1. Hola de nuevo, He comprendido lo de las librerias, pero ahora en la misma linea 40 me da el siguiente error:

        IOT_UY_pantalla_oled_BME280_nodemcu:40: error: no matching function for call to ‘BME280::BME280(int, int, int, int, int, int, bool, int)’

        BME280 bme(1,4,3,3,5,2,false,0x76);

        Muchas gracias, Un cordial saludo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

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