Objetivo

     Em diversos projetos se faz necessário o uso de um display demonstrativo de informações, um muito utilizado é o Display OLED em seus variados tamanhos e formas de comunicação. Em razão disso, nesse tutorial serão apresentados os dois tipos de protocolos de comunicação que podem ser utilizados pelo OLED–I2C e SPI, com suas características, diferenças e configurações necessárias para uso, ao final das explicações, desenvolveremos um programa de projeção de logotipo na tela do Display. Acompanhe o menu interativo abaixo para pular para algum tópico específico do tutorial:

Introdução
Lista de Materiais
Protocolos de Comunicação
-Comunicação Assíncrona
-Comunicação Síncrona
-Envio e Recebimento de Dados
-Seleção de Escravo
Comunicação I²C
-Half Duplex
Principais Diferenças entre I²C e SPI
Teste do Oled SPI
Teste do Oled I²C
Conclusão


Introdução

     O OLED (Diodo Orgânico Emissor de Luz) é uma melhoria do LCD (Display de Cristal Liquido) o OLED conta com uma camada orgânica que emite luz quando estimulada por um campo eletromagnético. As principais vantagens do OLED em relação à tecnologia anterior são: o fato do mesmo consumir menos energia, ser mais leve, fino, apresentar maior contraste de cores, melhor brilho e maior ângulo de visão, essas características se dão pelo fato do OLED emitir luz própria, diferentemente do LCD que precisa de uma luz de fundo.

     Existem displays OLEDs com variações de tamanho, cor, controlador, protocolo de comunicação e número de pinos, para o projeto utilizaremos um display OLED 128x64 Px – 0.96”- 7 pin Branco, mas não se aflija porque eu acrescentarei as informações necessárias para desenvolvimento com outros modelos de OLED. Vamos lá?


Lista de Materiais

Para realizar a atividade proposta nesse tutorial, tenha em mãos os seguintes componentes:

01 - Protoboard

01- Arduino UNO

01- Resistor de 10KΩ

01- Display OLED 128x64 Px – 0.96” – 7 Pin

Kit de jumpers Macho – Macho

 

Você pode adquiri-los clicando em cada componente ou em nosso site:

https://www.curtocircuito.com.br/


Protocolos de Comunicação

     Os protocolos de comunicação possibilitam a transmissão de dados entre o microcontrolador e os seus periféricos, existem alguns padrões como: I²C, UART, One Wire, SPI... Nesse tutorial vamos detalhar apenas o I²C e o SPI, pois são os utilizados pelo OLED.

-Comunicação Assíncrona

     Em uma comunicação simples do tipo Assíncrona, não há confirmação quanto ao envio dos dados ou quanto à taxa de transmissão e recepção, em razão disso é necessário sincronizar com antecedência para que ambos os lados estejam na mesma taxa (Ex: 96000 Bps –Bits por segundo). Para que os dados sejam transmitidos em sua totalidade um bit extra é enviado no início e no final de cada byte, ou seja, a cada 8 bits, isso impede que uma mensagem corrompida seja interpretada pelo receptor, como pode ser visto na imagem abaixo:

Lembrando que TX representa o transmissor e RX o receptor.

Imagem retirada do site da Sparkfun

 

A desvantagem desse tipo de comunicação é o excesso de bits extras e a necessidade da pré-configuração de velocidade de transmissão e recepção para o seu funcionamento.


-Comunicação Síncrona

     O Protocolo SPI funciona de forma Síncrona, ou seja, o mesmo conta com uma ligação de clock e uma destinada ao envio dos dados, o clock é responsável por manter o sincronismo, sendo um sinal oscilante que informa ao receptor o momento exato em que ocorre a transmissão, podendo atuar no modo de borda de descida ou borda de subida, no caso abaixo vemos o data representando o dado, e o clock agindo em borda de subida, analise que quando se inicia um pulso de clock, o dado é lido pelo receptor.

Imagem retirada do site da Sparkfun

 


-Envio e Recebimento de Dados

     Até o momento foi explicado como é feita uma comunicação unilateral, vamos entender agora como o receptor pode agir como transmissor também, primeiro é necessário ser esclarecido que o Transmissor é nomeado como Mestre (Master) e o Receptor como Escravo (Slave), em projetos normalmente temos um Mestre que é o microcontrolador, enquanto os periféricos atuam como escravos, podendo ser mais de um.

     Quando o dado é enviado do Mestre para o Escravo, o pino é denominado MOSI (Master Output/ Slave Input – Mestre Saída/ Escravo Entrada), além do clock que normalmente é representado como SCK ou CLK.

Imagem retirada do site da Sparkfun

 

Como a Comunicação SPI é Full Duplex as linhas de recebimento e envio são separadas, ou seja, é possível que a recepção e a transmissão sejam feitas de forma simultânea.


-Seleção de Escravo

     Além dos pinos já mencionados, há o SS (Slave Select) ou CS (Chip Select), responsável por selecionar o Escravo que enviará e receberá os dados enviados. Normalmente é utilizado mais de um SS, sendo um para cada escravo, como pode ser visto na imagem seguinte:

Imagem retirada do site da Sparkfun

 

     Como os escravos são ativados em nível lógico baixo, todos os SS devem ficar em nível alto, com exceção do selecionado, evitando que mais de um dado seja retornado para o mestre.

     Uma outra forma de fazer a ligação é a ilustrada abaixo, o clock ativa um slave por vez, essa ligação normalmente é feita em registradores de deslocamento.

 

Imagem retirada do site da Sparkfun



Comunicação SPI

     O Protocolo SPI é um dos protocolos mais utilizados, sua comunicação é feita de forma síncrona full duplex. Para este protocolo são utilizados quatro pinos (SCK: Clock, MOSI: Master Out/Slave In, MISO: Master In/ Slave Out e CS: Chip Select), além dos pinos de alimentação (VCC e GND).


Full Duplex

     Explicando de forma mais detalhada, a configuração Full Duplex é capaz de transmitir dados e receber de forma simultânea em ambos os sentidos por conta do uso de diferentes linhas, um exemplo claro do uso é em serviços telefônicos.


Comunicação I²C

     O Protocolo I²C é síncrono half duplex e possui a vantagem da simplicidade de ligação, esse tipo de comunicação requer apenas dois pinos de dados (SDA: Dados e SCK: Clock) e os de alimentação (VCC e GND).


Half Duplex

     A comunicação Half Duplex, assim como a comunicação Full Duplex, é feita de forma bidirecional, ou seja, o escravo e o mestre agem como transmissor e como receptor, a diferença está no fato dessa troca não ser possível de forma simultânea, por haver apenas uma linha destinada a transmissão e recepção. Um exemplo do uso é em Walk-Talk.

 


Principais diferenças entre I²C e SPI

     Em suma, o SPI é melhor para aplicações que exigem baixo consumo de energia e baixa velocidade, enquanto o I²C é mais utilizado em projetos que necessitam de um grande número de periféricos e alteração dinâmica na função mestre entre os periféricos no barramento.


Teste do OLED SPI

     Para fazer esse projeto temos que inicialmente conferir a ligação atrás do nosso OLED, o modelo escolhido para o tutorial pode funcionar tanto em SPI quanto em I²C, ele é enviado por nossa loja já pré-configurado para SPI, como pode ser visto na imagem abaixo:

     Analisando a descrição do OLED é possível concluir que quando o circuito está fechado em R3 e R4, a configuração é para comunicação em SPI.

     Identificando a configuração do OLED, iremos para o teste para confirmação de seu funcionamento.

     Primeiramente baixe a biblioteca U8g2, você pode encontrá-la indo em Sketch> Incluir Biblioteca > Gerenciar Bibliotecas...

     Em seguida faça uma busca por U8g2 e vá em “Instalar

     Depois de instalar, vá em Arquivo> Exemplos> U8g2 > page_buffer> GraphicsTest, ao abrir o exemplo você verá que existem muitas linhas comentadas, essas linhas correspondem aos modelos de display, devemos encontrar a linha do código que corresponde ao nosso display OLED.

Display OLED:

  • Controlador: SSD1306;
  • Dimensões: 128x64;
  • Interface: SPI

     Então as linhas comentadas que condizem com o nosso modelo são essas

     Eu vou apagar as barras de comentário (//) de uma delas, eu escolhi a primeira, então ligarei meu Oled conforme os pinos declarados na linha escolhida.

     Como pode ser visto, clock ou SCK deve ser ligado ao pino 13 do Arduino, o data ou SDA ao 11, o CS ao 10, o DC ao 9 e reset ao 8.

     Feita a alteração no código e a ligação, vamos conectar o Arduino ao computador e fazer as configurações para carregar o programa na placa Arduino.

     Vá em ferramentas, selecione a placa Arduino Uno e a COM identificada.

     Em seguida, carregue o programa em

  O programa também pode ser encontrado abaixo, mas lembrando que isso não anula a necessidade de baixar a biblioteca correspondente

 

/* Exemplo GraphicsTest disponível na biblioteca U8G2
 * Desenvolvida pelo Oliver 
 * Modificações da Curto Circuito
*/

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

#ifdef U8X8_HAVE_HW_SPI 
#include <SPI.h>  
#endif 
#ifdef U8X8_HAVE_HW_I2C 
#include <Wire.h>  
#endif    


U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8); //definição dos pinos para Oled


void u8g2_prepare(void) {             
  u8g2.setFont(u8g2_font_6x10_tf);     
  u8g2.setFontRefHeightExtendedText
  u8g2.setDrawColor(1);                
  u8g2.setFontPosTop();               
  u8g2.setFontDirection(0);            
}

void u8g2_box_frame(uint8_t a) {   
  u8g2.drawStr( 0, 0, "drawBox"); 
  u8g2.drawBox(5,10,20,10);
  u8g2.drawBox(10+a,15,30,7);
  u8g2.drawStr( 0, 30, "drawFrame"); 
  u8g2.drawFrame(5,10+30,20,10);
  u8g2.drawFrame(10+a,15+30,30,7);
}

void u8g2_disc_circle(uint8_t a) { 
  u8g2.drawStr( 0, 0, "drawDisc"); 
  u8g2.drawDisc(10,18,9);
  u8g2.drawDisc(24+a,16,7);
  u8g2.drawStr( 0, 30, "drawCircle"); 
  u8g2.drawCircle(10,18+30,9);
  u8g2.drawCircle(24+a,16+30,7);
}

void u8g2_r_frame(uint8_t a) {
  u8g2.drawStr( 0, 0, "drawRFrame/Box"); 
  u8g2.drawRFrame(5, 10,40,30, a+1);
  u8g2.drawRBox(50, 10,25,40, a+1);
}

void u8g2_string(uint8_t a) {
  u8g2.setFontDirection(0);
  u8g2.drawStr(30+a,31, " 0");
  u8g2.setFontDirection(1);
  u8g2.drawStr(30,31+a, " 90");
  u8g2.setFontDirection(2);
  u8g2.drawStr(30-a,31, " 180");
  u8g2.setFontDirection(3);
  u8g2.drawStr(30,31-a, " 270");
}

void u8g2_line(uint8_t a) {
  u8g2.drawStr( 0, 0, "drawLine");
  u8g2.drawLine(7+a, 10, 40, 55);
  u8g2.drawLine(7+a*2, 10, 60, 55);
  u8g2.drawLine(7+a*3, 10, 80, 55);
  u8g2.drawLine(7+a*4, 10, 100, 55);
}

void u8g2_triangle(uint8_t a) {
  uint16_t offset = a;
  u8g2.drawStr( 0, 0, "drawTriangle");
  u8g2.drawTriangle(14,7, 45,30, 10,40);
  u8g2.drawTriangle(14+offset,7-offset, 45+offset,30-offset, 57+offset,10-offset);
  u8g2.drawTriangle(57+offset*2,10, 45+offset*2,30, 86+offset*2,53);
  u8g2.drawTriangle(10+offset,40+offset, 45+offset,30+offset, 86+offset,53+offset);
}

void u8g2_ascii_1() {
  char s[2] = " ";
  uint8_t x, y;
  u8g2.drawStr( 0, 0, "ASCII page 1");
  for( y = 0; y < 6; y++ ) {
    for( x = 0; x < 16; x++ ) {
      s[0] = y*16 + x + 32;
      u8g2.drawStr(x*7, y*10+10, s);
    }
  }
}

void u8g2_ascii_2() {
  char s[2] = " ";
  uint8_t x, y;
  u8g2.drawStr( 0, 0, "ASCII page 2");
  for( y = 0; y < 6; y++ ) {
    for( x = 0; x < 16; x++ ) {
      s[0] = y*16 + x + 160;
      u8g2.drawStr(x*7, y*10+10, s);
    }
  }
}

void u8g2_extra_page(uint8_t a)
{
  u8g2.drawStr( 0, 0, "Unicode");
  u8g2.setFont(u8g2_font_unifont_t_symbols);
  u8g2.setFontPosTop();
  u8g2.drawUTF8(0, 24, "☀ ☁");
  switch(a) {
    case 0:
    case 1:
    case 2:
    case 3:
      u8g2.drawUTF8(a*3, 36, "☂");
      break;
    case 4:
    case 5:
    case 6:
    case 7:
      u8g2.drawUTF8(a*3, 36, "☔");
      break;
  }
}

#define cross_width 24
#define cross_height 24
static const unsigned char cross_bits[] U8X8_PROGMEM  = {
  0x00, 0x18, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x42, 0x00, 
  0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x81, 0x00, 0x00, 0x81, 0x00, 
  0xC0, 0x00, 0x03, 0x38, 0x3C, 0x1C, 0x06, 0x42, 0x60, 0x01, 0x42, 0x80, 
  0x01, 0x42, 0x80, 0x06, 0x42, 0x60, 0x38, 0x3C, 0x1C, 0xC0, 0x00, 0x03, 
  0x00, 0x81, 0x00, 0x00, 0x81, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 
  0x00, 0x42, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x18, 0x00, };

#define cross_fill_width 24
#define cross_fill_height 24
static const unsigned char cross_fill_bits[] U8X8_PROGMEM  = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x64, 0x00, 0x26, 
  0x84, 0x00, 0x21, 0x08, 0x81, 0x10, 0x08, 0x42, 0x10, 0x10, 0x3C, 0x08, 
  0x20, 0x00, 0x04, 0x40, 0x00, 0x02, 0x80, 0x00, 0x01, 0x80, 0x18, 0x01, 
  0x80, 0x18, 0x01, 0x80, 0x00, 0x01, 0x40, 0x00, 0x02, 0x20, 0x00, 0x04, 
  0x10, 0x3C, 0x08, 0x08, 0x42, 0x10, 0x08, 0x81, 0x10, 0x84, 0x00, 0x21, 
  0x64, 0x00, 0x26, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

#define cross_block_width 14
#define cross_block_height 14
static const unsigned char cross_block_bits[] U8X8_PROGMEM  = {
  0xFF, 0x3F, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 
  0xC1, 0x20, 0xC1, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 
  0x01, 0x20, 0xFF, 0x3F, };

void u8g2_bitmap_overlay(uint8_t a) {
  uint8_t frame_size = 28;

  u8g2.drawStr(0, 0, "Bitmap overlay");

  u8g2.drawStr(0, frame_size + 12, "Solid / transparent");
  u8g2.setBitmapMode(false /* solid */);
  u8g2.drawFrame(0, 10, frame_size, frame_size);
  u8g2.drawXBMP(2, 12, cross_width, cross_height, cross_bits);
  if(a & 4)
    u8g2.drawXBMP(7, 17, cross_block_width, cross_block_height, cross_block_bits);

  u8g2.setBitmapMode(true /* transparent*/);
  u8g2.drawFrame(frame_size + 5, 10, frame_size, frame_size);
  u8g2.drawXBMP(frame_size + 7, 12, cross_width, cross_height, cross_bits);
  if(a & 4)
    u8g2.drawXBMP(frame_size + 12, 17, cross_block_width, cross_block_height, cross_block_bits);
}

void u8g2_bitmap_modes(uint8_t transparent) {
  const uint8_t frame_size = 24;

  u8g2.drawBox(0, frame_size * 0.5, frame_size * 5, frame_size);
  u8g2.drawStr(frame_size * 0.5, 50, "Black");
  u8g2.drawStr(frame_size * 2, 50, "White");
  u8g2.drawStr(frame_size * 3.5, 50, "XOR");
  
  if(!transparent) {
    u8g2.setBitmapMode(false /* solid */);
    u8g2.drawStr(0, 0, "Solid bitmap");
  } else {
    u8g2.setBitmapMode(true /* transparent*/);
    u8g2.drawStr(0, 0, "Transparent bitmap");
  }
  u8g2.setDrawColor(0);// Black
  u8g2.drawXBMP(frame_size * 0.5, 24, cross_width, cross_height, cross_bits);
  u8g2.setDrawColor(1); // White
  u8g2.drawXBMP(frame_size * 2, 24, cross_width, cross_height, cross_bits);
  u8g2.setDrawColor(2); // XOR
  u8g2.drawXBMP(frame_size * 3.5, 24, cross_width, cross_height, cross_bits);
}

uint8_t draw_state = 0;

void draw(void) {
  u8g2_prepare();
  switch(draw_state >> 3) {
    case 0: u8g2_box_frame(draw_state&7); break;
    case 1: u8g2_disc_circle(draw_state&7); break;
    case 2: u8g2_r_frame(draw_state&7); break;
    case 3: u8g2_string(draw_state&7); break;
    case 4: u8g2_line(draw_state&7); break;
    case 5: u8g2_triangle(draw_state&7); break;
    case 6: u8g2_ascii_1(); break;
    case 7: u8g2_ascii_2(); break;
    case 8: u8g2_extra_page(draw_state&7); break;
    case 9: u8g2_bitmap_modes(0); break;
    case 10: u8g2_bitmap_modes(1); break;
    case 11: u8g2_bitmap_overlay(draw_state&7); break;
  }
}


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

void loop(void) {
  // picture loop  
  u8g2.clearBuffer();
  draw();
  u8g2.sendBuffer();
  
  // increase the state
  draw_state++;
  if ( draw_state >= 12*8 )
    draw_state = 0;

 //delay de 0,1s
  delay(100);

}

 

     Alguns símbolos e desenhos aleatórios irão aparecer, não se preocupe se eles parecerem desorganizados, é apenas um teste para confirmar se o seu OLED está exibindo imagens.

 


Teste do OLED I²C

     Se o seu display ainda estiver no modo SPI e você quiser fazer a configuração para I²C, prepare o ferro de solda e o estanho e mãos a obra.

     Primeiramente devemos fazer as modificações necessárias na placa, como pode ser visto escrito na mesma, a configuração I2C depende da ligação de R1, R4 e R8, como o R4 é uma ligação semelhante entre os dois modos, manteremos o resistor de 0 Ohms fechando contato nesse ponto, o R3 é apenas para o SPI, então o removeremos, depois disso, faremos um caminho de estanho interligando os terminais de R8 para fechar contato (é exatamente essa a função dos 0 Ohms nos outros casos), e faremos o mesmo para R1, ficando da forma que pode ser visto abaixo.

     Perceba que o contato está fechado apenas em: R1, R4 e R8, assim como escrito na placa.

     Com isso, faremos a ligação e a instalação.

     Se o DC for ligado ao GND, o endereço do I2C será 0X3C, se for ligado ao VCC será 0X3D. Os dispositivos I2C contam com endereços de identificação em hexadecimal, que para os casos podem ser interpretados de forma decimal da seguinte forma: o X representa que está em hexadecimal, o 0 deve vir antes como forma de anotação, a leitura de 0X3C, ou 3C será 60 em decimal, enquanto 0X3D, ou 3D será de 61, que representa o endereço que o OLED está ligado ao Arduino.

     Quanto ao resistor de 10KΩ agindo como pull-up, a sua função é impedir o estado de floating (flutuante), que é quando o nível lógico do pino fica em um estado que não é identificado como alto e nem como baixo, então ele puxa para nível lógico alto, impedindo essa ambiguidade.

     Ao fazer a ligação, vamos para a programação.

     Se ainda lhe resta dúvidas quanto ao endereço, carregue o programa de Scan do I2C, o programa pode ser encontrado na biblioteca wire> i2c_scanner, segue o programa abaixo para o caso de alguma dificuldade em encontrar o exemplo.

 

// --------------------------------------
// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    https://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// Version 6, November 27, 2015.
//    Added waiting for the Leonardo serial communication.
//
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//

#include <Wire.h>

void setup() {
  Wire.begin();

  Serial.begin(9600);
  while (!Serial); // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}

void loop() {
  int nDevices = 0;

  Serial.println("Scanning...");

  for (byte address = 1; address < 127; ++address) {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address, HEX);
      Serial.println("  !");

      ++nDevices;
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  } else {
    Serial.println("done\n");
  }
  delay(5000); // Wait 5 seconds for next scan
}
 

     No monitor serial deve aparecer o endereço do seu OLED, que provavelmente irá se alterar se você trocar o fio DC de GND para VCC, faça esse teste.

     Utilizando a mesma biblioteca anterior –U8glib, repetiremos o processo, Arquivo> Exemplos > U8glib> GraphicsTest.

     Na linha com os comentários respectivos ao modelo do display, deve ser feito o mesmo que foi feito para o caso anterior, mas respeitando o protocolo I2C, então a linha que deve ser utilizada é a de I2C.

     Como pode ser visto, nessa linha é declarado o modelo do display, além do protocolo.
     Para carregar o programa, faça como fizemos no exemplo anterior, selecione a porta e a placa.

     As mesmas imagens que apareceram no exemplo de teste do SPI, devem aparecer para esse caso.

 

/* Exemplo GraphicsTest disponível na biblioteca U8G2
 * Desenvolvida pelo Oliver 
 * Modificações da Curto Circuito
*/


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

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif


U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);



void u8g2_prepare(void) {
  u8g2.setFont(u8g2_font_6x10_tf);
  u8g2.setFontRefHeightExtendedText();
  u8g2.setDrawColor(1);
  u8g2.setFontPosTop();
  u8g2.setFontDirection(0);
}

void u8g2_box_frame(uint8_t a) {
  u8g2.drawStr( 0, 0, "drawBox");
  u8g2.drawBox(5,10,20,10);
  u8g2.drawBox(10+a,15,30,7);
  u8g2.drawStr( 0, 30, "drawFrame");
  u8g2.drawFrame(5,10+30,20,10);
  u8g2.drawFrame(10+a,15+30,30,7);
}

void u8g2_disc_circle(uint8_t a) {
  u8g2.drawStr( 0, 0, "drawDisc");
  u8g2.drawDisc(10,18,9);
  u8g2.drawDisc(24+a,16,7);
  u8g2.drawStr( 0, 30, "drawCircle");
  u8g2.drawCircle(10,18+30,9);
  u8g2.drawCircle(24+a,16+30,7);
}

void u8g2_r_frame(uint8_t a) {
  u8g2.drawStr( 0, 0, "drawRFrame/Box");
  u8g2.drawRFrame(5, 10,40,30, a+1);
  u8g2.drawRBox(50, 10,25,40, a+1);
}

void u8g2_string(uint8_t a) {
  u8g2.setFontDirection(0);
  u8g2.drawStr(30+a,31, " 0");
  u8g2.setFontDirection(1);
  u8g2.drawStr(30,31+a, " 90");
  u8g2.setFontDirection(2);
  u8g2.drawStr(30-a,31, " 180");
  u8g2.setFontDirection(3);
  u8g2.drawStr(30,31-a, " 270");
}

void u8g2_line(uint8_t a) {
  u8g2.drawStr( 0, 0, "drawLine");
  u8g2.drawLine(7+a, 10, 40, 55);
  u8g2.drawLine(7+a*2, 10, 60, 55);
  u8g2.drawLine(7+a*3, 10, 80, 55);
  u8g2.drawLine(7+a*4, 10, 100, 55);
}

void u8g2_triangle(uint8_t a) {
  uint16_t offset = a;
  u8g2.drawStr( 0, 0, "drawTriangle");
  u8g2.drawTriangle(14,7, 45,30, 10,40);
  u8g2.drawTriangle(14+offset,7-offset, 45+offset,30-offset, 57+offset,10-offset);
  u8g2.drawTriangle(57+offset*2,10, 45+offset*2,30, 86+offset*2,53);
  u8g2.drawTriangle(10+offset,40+offset, 45+offset,30+offset, 86+offset,53+offset);
}

void u8g2_ascii_1() {
  char s[2] = " ";
  uint8_t x, y;
  u8g2.drawStr( 0, 0, "ASCII page 1");
  for( y = 0; y < 6; y++ ) {
    for( x = 0; x < 16; x++ ) {
      s[0] = y*16 + x + 32;
      u8g2.drawStr(x*7, y*10+10, s);
    }
  }
}

void u8g2_ascii_2() {
  char s[2] = " ";
  uint8_t x, y;
  u8g2.drawStr( 0, 0, "ASCII page 2");
  for( y = 0; y < 6; y++ ) {
    for( x = 0; x < 16; x++ ) {
      s[0] = y*16 + x + 160;
      u8g2.drawStr(x*7, y*10+10, s);
    }
  }
}

void u8g2_extra_page(uint8_t a)
{
  u8g2.drawStr( 0, 0, "Unicode");
  u8g2.setFont(u8g2_font_unifont_t_symbols);
  u8g2.setFontPosTop();
  u8g2.drawUTF8(0, 24, "☀ ☁");
  switch(a) {
    case 0:
    case 1:
    case 2:
    case 3:
      u8g2.drawUTF8(a*3, 36, "☂");
      break;
    case 4:
    case 5:
    case 6:
    case 7:
      u8g2.drawUTF8(a*3, 36, "☔");
      break;
  }
}

#define cross_width 24
#define cross_height 24
static const unsigned char cross_bits[] U8X8_PROGMEM  = {
  0x00, 0x18, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x42, 0x00, 
  0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x81, 0x00, 0x00, 0x81, 0x00, 
  0xC0, 0x00, 0x03, 0x38, 0x3C, 0x1C, 0x06, 0x42, 0x60, 0x01, 0x42, 0x80, 
  0x01, 0x42, 0x80, 0x06, 0x42, 0x60, 0x38, 0x3C, 0x1C, 0xC0, 0x00, 0x03, 
  0x00, 0x81, 0x00, 0x00, 0x81, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 
  0x00, 0x42, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x18, 0x00, };

#define cross_fill_width 24
#define cross_fill_height 24
static const unsigned char cross_fill_bits[] U8X8_PROGMEM  = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x64, 0x00, 0x26, 
  0x84, 0x00, 0x21, 0x08, 0x81, 0x10, 0x08, 0x42, 0x10, 0x10, 0x3C, 0x08, 
  0x20, 0x00, 0x04, 0x40, 0x00, 0x02, 0x80, 0x00, 0x01, 0x80, 0x18, 0x01, 
  0x80, 0x18, 0x01, 0x80, 0x00, 0x01, 0x40, 0x00, 0x02, 0x20, 0x00, 0x04, 
  0x10, 0x3C, 0x08, 0x08, 0x42, 0x10, 0x08, 0x81, 0x10, 0x84, 0x00, 0x21, 
  0x64, 0x00, 0x26, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

#define cross_block_width 14
#define cross_block_height 14
static const unsigned char cross_block_bits[] U8X8_PROGMEM  = {
  0xFF, 0x3F, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 
  0xC1, 0x20, 0xC1, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 
  0x01, 0x20, 0xFF, 0x3F, };

void u8g2_bitmap_overlay(uint8_t a) {
  uint8_t frame_size = 28;

  u8g2.drawStr(0, 0, "Bitmap overlay");

  u8g2.drawStr(0, frame_size + 12, "Solid / transparent");
  u8g2.setBitmapMode(false /* solid */);
  u8g2.drawFrame(0, 10, frame_size, frame_size);
  u8g2.drawXBMP(2, 12, cross_width, cross_height, cross_bits);
  if(a & 4)
    u8g2.drawXBMP(7, 17, cross_block_width, cross_block_height, cross_block_bits);

  u8g2.setBitmapMode(true /* transparent*/);
  u8g2.drawFrame(frame_size + 5, 10, frame_size, frame_size);
  u8g2.drawXBMP(frame_size + 7, 12, cross_width, cross_height, cross_bits);
  if(a & 4)
    u8g2.drawXBMP(frame_size + 12, 17, cross_block_width, cross_block_height, cross_block_bits);
}

void u8g2_bitmap_modes(uint8_t transparent) {
  const uint8_t frame_size = 24;

  u8g2.drawBox(0, frame_size * 0.5, frame_size * 5, frame_size);
  u8g2.drawStr(frame_size * 0.5, 50, "Black");
  u8g2.drawStr(frame_size * 2, 50, "White");
  u8g2.drawStr(frame_size * 3.5, 50, "XOR");
  
  if(!transparent) {
    u8g2.setBitmapMode(false /* solid */);
    u8g2.drawStr(0, 0, "Solid bitmap");
  } else {
    u8g2.setBitmapMode(true /* transparent*/);
    u8g2.drawStr(0, 0, "Transparent bitmap");
  }
  u8g2.setDrawColor(0);// Black
  u8g2.drawXBMP(frame_size * 0.5, 24, cross_width, cross_height, cross_bits);
  u8g2.setDrawColor(1); // White
  u8g2.drawXBMP(frame_size * 2, 24, cross_width, cross_height, cross_bits);
  u8g2.setDrawColor(2); // XOR
  u8g2.drawXBMP(frame_size * 3.5, 24, cross_width, cross_height, cross_bits);
}

uint8_t draw_state = 0;

void draw(void) {
  u8g2_prepare();
  switch(draw_state >> 3) {
    case 0: u8g2_box_frame(draw_state&7); break;
    case 1: u8g2_disc_circle(draw_state&7); break;
    case 2: u8g2_r_frame(draw_state&7); break;
    case 3: u8g2_string(draw_state&7); break;
    case 4: u8g2_line(draw_state&7); break;
    case 5: u8g2_triangle(draw_state&7); break;
    case 6: u8g2_ascii_1(); break;
    case 7: u8g2_ascii_2(); break;
    case 8: u8g2_extra_page(draw_state&7); break;
    case 9: u8g2_bitmap_modes(0); break;
    case 10: u8g2_bitmap_modes(1); break;
    case 11: u8g2_bitmap_overlay(draw_state&7); break;
  }
}


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

void loop(void) {

  u8g2.clearBuffer();
  draw();
  u8g2.sendBuffer();
  

  draw_state++;
  if ( draw_state >= 12*8 )
    draw_state = 0;

  delay(100);

}

 


Exibição do logotipo no OLED

     Agora que o display está testado nos dois tipos de protocolos, partiremos para o nosso projeto de projeção de logotipo no display.
Primeiramente temos que editar a imagem que usaremos para que se encaixe nos padrões exigidos pelo OLED.

     Você deve editar o logo para que ele fique em preto e branco e com dimensões de 128x64, assim como pode ser visto abaixo:

     Você pode utilizar um programa de edição de imagens como Photoshop, Photoscape, entre outros para que a sua imagem fique de acordo e salvar como bitmap.

     Depois de preparar a imagem, é necessário ter um programa para a transformação da imagem em código, o programa utilizado está disponível no link:

http://en.radzio.dxp.pl/bitmap_converter/

     Depois de baixar, abra o arquivo .exe

     Depois de carregar a imagem é necessário fazer as configurações necessárias quanto à orientação do byte, com relação ao tamanho entre outros.

     Em byte orientation definiremos a orientação de acordo com o modelo do OLED, para o que estamos utilizamos, definiremos como orientação horizontal. O tamanho será respectivo ao tamanho da tela, para o caso é 128x64, selecionaremos Include Size para a matriz ser do tamanho da imagem, definiremos Size Endianness, sendo que representa o sentido de leitura do menos significativo para o mais significativo, a maioria dos microcontroladores utilizam o padrão de Little Endianness, apenas algumas raras exceções utilizam o Big Endianness, então marcaremos como Little. Definiremos também a quantidade de pixels/byte, para esse caso eu utilizo 8, para finalizar, escolha um nome para a constante que será gerada e salve em File> Save Output

 

     Será gerado um arquivo com a imagem em código hexadecimal, para o programa você usará todo o trecho de const unsigned em diante...

     Depois de copiar, você deve colar no programa abaixo, assim como eu fiz.

 

// --------------------------------------
// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    https://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// Version 6, November 27, 2015.
//    Added waiting for the Leonardo serial communication.
//
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//

#include <Wire.h>

void setup() {
  Wire.begin();

  Serial.begin(9600);
  while (!Serial); // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}

void loop() {
  int nDevices = 0;

  Serial.println("Scanning...");

  for (byte address = 1; address < 127; ++address) {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    byte error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address, HEX);
      Serial.println("  !");

      ++nDevices;
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  } else {
    Serial.println("done\n");
  }
  delay(5000); // Wait 5 seconds for next scan
}

 

     Ao carregar, é possível visualizar o logo no OLED, como pode ser visto abaixo.


Conclusão 

Com o acompanhamento do tutorial, é possível ter um breve vislumbre do display OLED e suas finalidades, sendo que o modelo em questão apresenta a vantagem de permitir o uso em dois procolos (SPI ou I²C), no tutorial vimos como utilizá-lo em ambas. Além do mais, aprendemos a criar uma imagem específica, abrindo um leque de possibilidades de projetos. Espero que vocês tenham gostado e não se esqueçam de deixar o feedback nos comentários.

Abraços, Curto Circuito :)