Vamos a instalar y configurar otro pequeño display OLED a nuestro ESP8266, ideal para proyectos pequeños (y no tan pequeños) del Internet de las cosas (IoT).

Este es de la marca Keyestudio y está a un precio razonable. Su tamaño de 1.3′‘ lo coloca en el sector de los grandes display de su tipo. Está gobernado por un chip SH1106, por otro lado bastante común y para el que existen librerías en el entorno IDE de Arduino.

La comunicación de este tipo de dispositivos suele ser I2C o SPI. En este caso es SPI, lo que lo hace ideal para ser usado en los ESP8266, ya que lo soporta por hardware y no por software. Hablamos ampliamente de estos protocolos y el ESP8266 anteriormente. El único problema de SPI es el consumo de salidas adicionales, dos por cada dispositivo.

Por último, antes de adquirir un módulo de estos, asegurarse bien que los pines NO están en vertical, algo que nos ahorrará quebraderos de cabeza a la hora de conectarlo a nuestros proyectos IoT.

En este pequeño tutorial vamos a conectar 2 pantallas.

Conexiones

En su momento ya hablamos del protocolo SPI. Las conexiones son las típicas de SPI:

  • CLK. El reloj. Conectar en BUS al GPIO14
  • MOSI. Master out, Slave in. Conectar en BUS al GPIO16.
  • MISO. Comunicación inexistente. No conectar.
  • CS. El Chip Select activa por turnos a cada dispositivo del bus. Conectar cada pantalla a un pin distinto.
  • DC. El Data Command no es del protocolo SPI, pero estos displays lo llevan. Conectar cada pantalla a un pin distinto.
  • RST. El reset lo podemos conectar en BUS directamente al reset del ESP8266. Realmente los estamos conectando a HIGH.

Debemos conectar el bus hardware SPI (CLK y MOSI) a ambos LCDs añadiendo el pin RST. Reutilizamos el CS de hardware para el primero y usamos otro CS y otros dos DC para completar  el conexionado. Los módulos funcionan a 5V.

Librería ESP8266 OLED SSD1306/SH1106

Enlace: https://github.com/ThingPulse/esp8266-oled-ssd1306

#include <SPI.h>
#include "SH1106Spi.h"

// Constructor, muy sencillo de usar.
SH1106Spi display(RES, DC, CS);

Es una librería relativamente simple de utilizar pero que no funciona directamente si tienes más de una pantalla instalada en el BUS SPI. Supongo que se trata de un problema con los constructores, que comparten posiblemente el mismo buffer de memoria que luego vuelcan de golpe a pantalla.

Cabría la posibilidad de estructurar la programación para que se escriba en el buffer de manera exclusiva para cada pantalla y que acto seguido se vuelque. La cuestión es que ambas escrituras NO se entremezclen.

Aún así sigue siendo una opción para una sola pantalla.

Librería U8g2

Dos OLED, una con un XBM

Enlace: https://github.com/olikraus/u8g2

Esta librería ya la hemos utilizado otras veces con otros diaplays. Está muy documentada y abarca un amplísimo número de dispositivos, con diferentes chips de control y con distintos protocolos de comunicación. Esto hace que sea terriblemente espesa, con un número exagerado de constructores entre lo cuales elegir el que va a funcionar con nuestro modelo. Vamos a particularizar para este tipo de pantalla.

Los constructores tienen la siguiente forma:

[supsystic-tables id=7 ]

Un ejemplo para nuestro caso (en esta página hay una discusión sobre varias pantallas a la vez):

// Constructores. Al HW le falta el clock (CLK) y el data (MOSI) que supongo son 
// los que le corresponden por HARDWARE.
// Supongo que el RESET se puede dejar sin poner y conectar al pin RST de ESP8266
// U8G2_SH1106_128X64_NONAME_1_4W_SW_SPI(rotation, clock, data, cs, dc [, reset])
// U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI(rotation, cs, dc [, reset])

// ========================== SOFT =============================================
//                                              rotation  CLK  DATA  CS  DC
U8G2_SH1106_128X64_NONAME_1_4W_SW_SPI pantalla1( U8G2_R0,  D5,  D7,  D8, D0);
U8G2_SH1106_128X64_NONAME_1_4W_SW_SPI pantalla2( U8G2_R0,  D5,  D7,  D2, D1);

// ========================== HARD =============================================
//                                              rotation  CS  DC
U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI pantalla1( U8G2_R0, D8, D0);
U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI pantalla2( U8G2_R0, D2, D1);

Para el caso de SPI hay un tramo en el constructor «extra» que según la documentación es:

  • 3W.- 3 Wire SPI: Serial Peripheral Interface with three signals: Clock, Data and Chip-Select.
  • 4W.- 4 Wire SPI: Same as 3SPI, but with one additional line for commands and data (often labeled as D/C, RS or A0). Que es justo el caso del display que manejamos en este artículo.
  • 4W_2ND_HW.- No está documentado pero sospecho que se trata de un segundo bus hardware SPI. El ESP8266 sólo tiene uno utilizable.

Mención aparte es el buffer que crea el constructor. El buffer es una porción de memoria donde se dibuja la pantalla para posteriormente volcarla a memoria. Esta es una descripción de lo descrito en la tabla anterior:

  • U8X8.- Modo texto. No precisa buffer. Sencillo de manejar. No soporta gráficos. Este display no parece soportar ese modo tan básico.
  • U4G2.- Modo gráfico. Precisa buffer. Más complejo de programar. Soporta gráficos. Precisa memoria.
    • Full screen buffer.- Reserva toda la pantalla en memoria. Muy rápido para programas que lo requieran. Precisa mucha memoria. Sencillo de programar.
    • Page buffer 1.- Reserva 128 bytes de memoria. Algo lento. Precisa poca memoria. Algo complejo de programar (paginación).
    • Page buffer 2.- Reserva 256 bytes de memoria. Velocidad intermedia. Memoria intermedia. Algo complejo de programar (paginación).

La programación de full screen buffer es sencilla:

void setup(void) {
  u8g2.begin();
}

void loop(void) {
  // Se escribe en el buffer
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.drawStr(0,20,"Hello World!");

  // Y después se vuelca de golpe
  u8g2.sendBuffer();
}

La programación del page buffer (1 o 2) es más compleja:

void setup(void) {
  u8g2.begin();
}

void loop(void) {
  // REDERIZACIÓN COMPLETA DE LA PANTALLA

  // 1.- Apunta a la posición cero de la pantalla
  u8g2.firstPage();

  // 2.- Bucle de renderización. No interrumpir.
  do {
    // Pinta TODO seguido en este bloque
    u8g2.setFont(u8g2_font_ncenB14_tr);
    u8g2.drawStr(0,24,"Hello World!");

  // 3.- Apunta a la siguiente pos de pantalla (actual + tmño buffer)
  //     Cuando acaba de pintarlo todo devuelve false y termina el bucle
  } while ( u8g2.nextPage() );
}

En el caso de modo texto es casi trivial (aunque esta pantalla parece que no lo soporta):

void setup(void) {
  u8x8.begin();
}

void loop(void) {
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.drawString(0,1,"Hello World!");
}

Y un ejemplo funcional con dos pantallas:

#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>


// Por soft
//                                                rotation  CLK  DATA  CS  DC
//U8G2_SH1106_128X64_NONAME_1_4W_SW_SPI pantalla1( U8G2_R0,  D5,  D7,  D8, D0);
//U8G2_SH1106_128X64_NONAME_1_4W_SW_SPI pantalla2( U8G2_R0,  D5,  D7,  D2, D1);

// Por Hard - resto de pines los establecidos por hard
//                                               rotation CS  DC
U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI pantalla1( U8G2_R0, D8, D0);
U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI pantalla2( U8G2_R0, D2, D1);

void setup(void) {
  pantalla1.begin();
  pantalla2.begin();
}

void loop(void) {
  pintaPantalla(&pantalla1);
  delay(1000);
  pintaPantalla(&pantalla2);
  delay(1000);
}

void pintaPantalla(U8G2_SH1106_128X64_NONAME_1_4W_SW_SPI * pant) {
  pant->setFont(u8g2_font_ncenB14_tr);
  pant->firstPage();
  do {
    pant->setCursor(0, 20);
    pant->print(F("Hello World!"));
  } while ( pant->nextPage() );
}