terça-feira, outubro 20, 2015

Display OLED com interface I2C - Parte 1

Mais um display com interface I2C, mas vesta vez gráfico (128x64) e OLED ao invés de LCD.


A tecnologia OLED permite uma maior legibilidade, principalmente em ambientes mal iluminados, por emitirem diretamente luz. Este display, comprado na DX, é bem pequeno e apresenta uma faixa superior na cor amarela (16 pontos de altura) e o restante na cor azul. Existe uma separação entre as duas faixas, com altura de um ponto.

Para quem quiser sair usando direto com o Arduino, tem uma biblioteca da Adafruit no github:

https://github.com/adafruit/Adafruit_SSD1306

Os exemplos desta biblioteca usam uma outra biblioteca da Adafruit que implementa várias funções gráficas:

https://github.com/adafruit/Adafruit-GFX-Library

O controlador do display é o SSD1306, cujo datasheet pode ser visto aqui. O endereço I2C pode ser 0x3C ou 0x3D (o módulo veio configurado para 0x3C, tem uns pontos de solda atrás que devem permitir alterar o endereço).

Cada transferência de iniciar com um byte de controle. Neste byte apenas os dois bits mais significativos são usados: Co (bit 7) e D/C (bit 6). O bit D/C indica se o byte seguinte é um comando (0) ou dado a ser escrito na memória de vídeo(1). Se Co for 1, o byte de controle se aplica apenas ao byte seguinte e um novo byte de controle deve ser fornecido após o dado ou comando. Se Co for 0, os bytes seguintes serão todos tratados como dado ou comando. O datasheet detalha os comandos disponíveis.

Confuso? Vejamos alguns exemplos:

0x00 0x20 -> 0x20 é tratado como comando
0x00 0x22 0x01 0x07 -> 0x22, 0x01 e 0x07 são tratados como comandos
0x40 0x01 0x02 -> 0x01 e 0x02 são escritos na memória de vídeo
0x80 0x20 0x40 0x01 0x02 -> 0x20 é tratado como comando e 0x01 e 0x02 são escritos na memória de vídeo

A memória do display possui 1Kbyte, dividido em oito páginas. Cada byte corresponde a 8 pontos em uma coluna, com o bit menos significativo correspondendo à linha superior:


No meu primeiro teste, liguei o display a um Arduino. A ligação é trivial: Vcc em +5V, GND ao GND, SDA a A4 e SCL a A5 (estes dois últimos pinos são para Uno e Nano, no Mega os pinos são outros).



A iniciação do display é feita enviando vários comandos, que eu peguei do código da Adafruit. Para simplificar, enviei todos os comandos de iniciação em uma única transação I2C.

A escrita na memória também é um pouco confusa. O comando Set Column Address define uma coluna inicial e uma coluna final. O comando Set Page Address define uma página inicial e uma página final. O comando Set Memory Address Mode define como é o feito o incremento da posição atual após uma escrita. Usei o modo "horizontal" no qual a coluna começa na inicial e vai sendo incrementada até atingir a final, neste ponto volta à coluna inicial e a página é incrementada.

A minha rotina de limpeza de tela mostra isto em funcionamento, usando todas as colunas e páginas da memória.

Para completar este teste, uma rotina de escrita de caracteres. Para simplificar, usei uma célula de 8 x 8 pontos, o que gerou 8 linhas de 16 caracteres bem pequenos.

  1. //  
  2. // Teste de comunicação com Display OLED  
  3. // baseado no controlador SSD1306  
  4. //  
  5.   
  6. #include <Wire.h>  
  7.   
  8. // Endereço I2C do display  
  9. #define DISP_ADDR  0x3C  
  10.   
  11. // Comandos  
  12. #define SSD1306_SETCONTRAST 0x81  
  13. #define SSD1306_DISPLAYALLON_RESUME 0xA4  
  14. #define SSD1306_DISPLAYALLON 0xA5  
  15. #define SSD1306_NORMALDISPLAY 0xA6  
  16. #define SSD1306_INVERTDISPLAY 0xA7  
  17. #define SSD1306_DISPLAYOFF 0xAE  
  18. #define SSD1306_DISPLAYON 0xAF  
  19. #define SSD1306_SETDISPLAYOFFSET 0xD3  
  20. #define SSD1306_SETCOMPINS 0xDA  
  21. #define SSD1306_SETVCOMDETECT 0xDB  
  22. #define SSD1306_SETDISPLAYCLOCKDIV 0xD5  
  23. #define SSD1306_SETPRECHARGE 0xD9  
  24. #define SSD1306_SETMULTIPLEX 0xA8  
  25. #define SSD1306_SETLOWCOLUMN 0x00  
  26. #define SSD1306_SETHIGHCOLUMN 0x10  
  27. #define SSD1306_SETSTARTLINE 0x40  
  28. #define SSD1306_MEMORYMODE 0x20  
  29. #define SSD1306_COLUMNADDR 0x21  
  30. #define SSD1306_PAGEADDR   0x22  
  31. #define SSD1306_COMSCANINC 0xC0  
  32. #define SSD1306_COMSCANDEC 0xC8  
  33. #define SSD1306_SEGREMAP 0xA0  
  34. #define SSD1306_CHARGEPUMP 0x8D  
  35. #define SSD1306_EXTERNALVCC 0x1  
  36. #define SSD1306_SWITCHCAPVCC 0x2  
  37.   
  38. // Tamanho da tela  
  39. #define SSD1306_LCDWIDTH                  128  
  40. #define SSD1306_LCDHEIGHT                 64  
  41.   
  42. // Iniciação  
  43. void setup()  
  44. {  
  45.   Wire.begin();  
  46.   Display_init();  
  47. }  
  48.   
  49. // Laço principal  
  50. void loop()  
  51. {  
  52.   int d = 0;  
  53.   for (int l = 0; l < 8; l++)  
  54.   {  
  55.     for (int c = 0; c < 16; c++)  
  56.     {  
  57.       Display_write (l, c, d);  
  58.       if (++d == 10)  
  59.         d = 0;  
  60.       delay (500);  
  61.     }  
  62.   }  
  63.   delay (10000);  
  64.   Display_clear();  
  65. }  
  66.   
  67.   
  68. // Sequência de iniciação do display  
  69. byte cmdInit[] =  
  70. {  
  71.     SSD1306_DISPLAYOFF,  
  72.     SSD1306_SETDISPLAYCLOCKDIV, 0x80,  
  73.     SSD1306_SETMULTIPLEX, 0x3F,  
  74.     SSD1306_SETDISPLAYOFFSET, 0x00,  
  75.     SSD1306_SETSTARTLINE | 0x0,  
  76.     SSD1306_CHARGEPUMP, 0x14,  
  77.     SSD1306_MEMORYMODE, 0x00,  
  78.     SSD1306_SEGREMAP | 0x1,  
  79.     SSD1306_COMSCANDEC,  
  80.     SSD1306_SETCOMPINS, 0x12,  
  81.     SSD1306_SETCONTRAST, 0xCF,  
  82.     SSD1306_SETPRECHARGE, 0xF1,  
  83.     SSD1306_SETVCOMDETECT, 0x40,  
  84.     SSD1306_DISPLAYALLON_RESUME,  
  85.     SSD1306_NORMALDISPLAY,  
  86.     SSD1306_DISPLAYON  
  87. };  
  88.   
  89. // Iniciação do display  
  90. void Display_init()  
  91. {  
  92.   Display_sendcmd (cmdInit, sizeof(cmdInit));  
  93.   Display_clear();  
  94. }  
  95.   
  96. // Limpa o display  
  97. void Display_clear()  
  98. {  
  99.   // Define endereços iniciais e finais de colunas e páginas  
  100.   Display_sendcmd (SSD1306_COLUMNADDR);  
  101.   Display_sendcmd (0);  
  102.   Display_sendcmd (SSD1306_LCDWIDTH-1);  
  103.   Display_sendcmd (SSD1306_PAGEADDR);  
  104.   Display_sendcmd (0);  
  105.   Display_sendcmd (7);  
  106.   
  107.   // Preenche a memória com zeros  
  108.   for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++)   
  109.   {  
  110.       Wire.beginTransmission(DISP_ADDR);  
  111.       Wire.write(0x40);  // Co=0, DC = 1  
  112.       for (uint8_t x=0; x<16; x++)   
  113.       {  
  114.  Wire.write(0);  
  115.       }  
  116.       Wire.endTransmission();  
  117.   }  
  118. }  
  119.   
  120. // Desenho dos números de 0 a 9  
  121. byte gc[][7] =  
  122. {  
  123.  {0x3E, 0x61, 0x51, 0x49, 0x45, 0x43, 0x3E} // 0  
  124. ,{0x40, 0x40, 0x42, 0x7F, 0x40, 0x40, 0x40} // 1  
  125. ,{0x42, 0x61, 0x51, 0x49, 0x45, 0x42, 0x40} // 2  
  126. ,{0x22, 0x41, 0x49, 0x49, 0x49, 0x49, 0x36} // 3  
  127. ,{0x10, 0x18, 0x14, 0x12, 0x11, 0x7F, 0x50} // 4  
  128. ,{0x27, 0x45, 0x45, 0x45, 0x45, 0x49, 0x31} // 5  
  129. ,{0x3C, 0x4A, 0x49, 0x49, 0x49, 0x49, 0x32} // 6  
  130. ,{0x41, 0x21, 0x11, 0x09, 0x05, 0x03, 0x01} // 7  
  131. ,{0x36, 0x49, 0x49, 0x49, 0x49, 0x49, 0x36} // 8  
  132. ,{0x26, 0x49, 0x49, 0x49, 0x49, 0x29, 0x1E} // 9  
  133. };  
  134.   
  135. // Escreve um caracter na linha l(0 a 7), coluna c(0 a 16)  
  136. void Display_write (byte l, byte c, byte car)  
  137. {  
  138.   byte *pc = gc[car];  
  139.   
  140.   // Endereça o caracter  
  141.   Display_sendcmd (SSD1306_COLUMNADDR);  
  142.   Display_sendcmd (c*8);  
  143.   Display_sendcmd (c*8 + 7);  
  144.   Display_sendcmd (SSD1306_PAGEADDR);  
  145.   Display_sendcmd (l);  
  146.   Display_sendcmd (l);  
  147.     
  148.   // Escreve  
  149.   Wire.beginTransmission(DISP_ADDR);  
  150.   Wire.write(0x40);  // Co=0, DC = 1  
  151.   for (uint8_t x = 0;  x < 7; x++)   
  152.   {  
  153.     Wire.write(*pc++);  
  154.   }  
  155.   Wire.endTransmission();  
  156. }  
  157.   
  158. // Envia sequência de comandos ao display  
  159. void Display_sendcmd (byte *cmd, int nCmds)  
  160. {  
  161.   Wire.beginTransmission(DISP_ADDR);  
  162.   Wire.write (0);  // Co= 0. DC = 0  
  163.   for (int i = 0; i < nCmds; i++)  
  164.   {  
  165.     Wire.write(cmd[i]);  
  166.   }  
  167.   Wire.endTransmission();  
  168. }  
  169.   
  170. // Envia um byte de comando ao display  
  171. void Display_sendcmd (byte cmd)  
  172. {  
  173.   Wire.beginTransmission(DISP_ADDR);  
  174.   Wire.write (0);  // Co= 0. DC = 0  
  175.   Wire.write(cmd);  
  176.   Wire.endTransmission();  
  177. }  

Nenhum comentário: