Publicidade:

terça-feira, 26 de janeiro de 2016

Arduino - Display LCD Nokia 5110


Barato e de baixo consumo, o display LCD 5110 da Nokia é uma ótima opção para diversos projetos que precisam saídas gráficas, textos, etc. É monocromático e possui uma resolução de 84 x 48 pixels  (o que dá 4032 pixels).



Esse Display foi utilizado no famoso Nokia 5110, lançado em 1998 (Há 18 anos), o qual tinha como grande vantagem o baixo consumo de energia, chegando a bateria a durar vários dias.

Observação: Talvez você não queira se preocupar com os detalhes de funcionamento do display (e do CI PCD8544) e apenas queira uma biblioteca que faça o "serviço sujo". Mas o objetivo desse artigo não é abordar o uso de nenhuma biblioteca, e sim explicar como o display funciona mostrando algumas formas de fazer isso sem o uso de bibliotecas de terceiros.

Para entendermos exatamente como o display funciona, nada melhor que conhecermos o seu CI, o PCD8544 da Philips, portanto é uma boa ideia darmos uma estudada no datasheet para entendermos melhor como fazer a comunicação entre Arduino e o display:

https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf

Na memória do PCD8544 esses dados são divididos em 6 banks onde cada bank armazena 84 bytes, ficando um byte por coluna.


Do ponto de vista do display, o primeiro bank representa as 8 primeiras linhas do display, o segundo bank representa as próximas 8 linhas (da 9ª a 16ª linha) e assim por diante. Já os primeiros bytes de todos os banks representam a primeira coluna do display, os segundos bytes representam a segunda coluna e assim por diante, até a 83ª coluna.

Agora que sabemos como os dados são organizados, precisaremos entender como enviar as informações para o mesmo. Para isso é importante conhecermos alguns dos pinos desses CI, pois é através deles que o Arduino irá se comunicar com o display.




SDIN - Utilizado para enviar os dados seriais.
SCLK - Clock externo
D/C - Seleciona se enviaremos dados ou comandos.
SCE - Indica se o clock está habilitado, ou seja, se está ativado. Ativado em Low.
RES - Utilizado para Resetar o display. Ativado em Low.


Escrita de dados e comandos




Para enviar os dados para o display, precisamos passar  D/C para 1 e enviar o byte contendo os dados que irão setar os pixels de determinada área do display. Nesse ponto precisamos estar atendo ao modo que o display está configurado para a escrita. Se é Vertical (V=1) ou Horizontal (V=0). Para definir o modo de entrada (vertical ou horizontal) devemos utilizar o comando "Function set", mostrado na tabela acima, através do bit DB1.


Se o modo definido for o Vertical, o primeiro byte recebido pelo display será endereçado para o primeiro byte do primeiro bank (Y-address = 0). O segundo byte recebido pelo display será endereçado para o primeiro byte do segundo bank e assim por diantes, ou seja, o endereçamento é feito verticalmente.

Já se o modo definido for o Horizontal, o primeiro byte recebido pelo display será endereçado para o primeiro byte do primeiro bank (Y-address = 0). O segundo byte recebido pelo display será endereçado para o segundo byte do primeiro bank e assim por diante, ou seja, o endereçamento é feito horizontalmente.

Nem sempre vamos querer atualizar o display a partir da primeira linha e primeira coluna, as vezes podemos querer atualizar apenas uma determinada área do display. Isso economiza processamento e tempo. Nesses casos devemos antes de enviar os dados para o display, posicionar a partir de que ponto queremos atualizar os dados do display, informando linha (y-address) e coluna (x-address). Para isso utilizamos os comandos "Set Y address of RAM" e "Set X address of RAM" mostrados na tabela de comandos na imagem mais acima.

Agora que entendemos um pouco melhor o funcionamento do display e de como se comunicar com ele, vamos por a mão na massa.

Vamos fazer a montagem do circuito, seguindo as orientações a baixo. Como são vários exemplos, esse mesmo diagrama será aplicada a todos os exemplos desse artigo.

Atenção. Existem dois modelos desses displays, um de 5V (geralmente da cor azul) e outro de 3.3V (Geralmente da cor vermelha). O display que eu utilizei era de 3.3V. Infelizmente segui algumas orientações que diziam que o display apesar de ser 3.3V poderia ter seus pinos de comunicação ligados direto aos pinos do Arduino, bastando ligar apenas o Vcc ao 3.3V do Arduino. De fato funcionou por alguns dias, porém depois parou de funcionar por uns dias, mas acabou funcionando novamente, mas depois disso o display tem mostrado um comportamento estranho, falhando, as vezes ficando mais fraco. Não sei se a causa foi exatamente essa, mas por via das dúvidas acho que o mais indicado é utilizar um divisor de tensão na hora de ligar os pinos do Arduino nos pinos do display.

Apesar da imagem abaixo está mostrando a ligação direta entre o Arduino e o display sugiro utilizar resistores entre os pinos 1, 2, 3, 4 e 5. Para mais detalhes, veja o artigo do fabricante: https://learn.sparkfun.com/tutorials/graphic-lcd-hookup-guide

Montagem:


Arduino | LCD NOKIA 5110
12 | 1 RST           
11 | 2 CE              
10 | 3 DC             
9 | 4 DIN         
8 | 5 CLK        
3.3V | 6 VCC              
                             3 | 7 LIGHT   (resistor 200 Ohm)
GND | 8 GND              

Arduino | Potenciômetro      
5v | 1                  
A0 | 2                   
GND | 3                       




Com o circuito montado vamos então ao nosso primeiro exemplo, que é um código bem simples apenas para demonstrar o que foi exemplificado inicialmente. No caso iremos mostrar como enviar os bytes pelo endereçamento horizontal e vertical.

Aviso: os códigos de exemplo utilizados aqui fazem uso frequente de operações de bitwise. Caso ainda não domine esses conceitos, neste vídeo explico: https://www.youtube.com/watch?v=XuLR1Sh3HhQ

Código :

/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD  LOW

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

void lcd_write(byte mode, byte w){
  digitalWrite(pin_dc, mode);
  digitalWrite(pin_cs, LOW);
  shiftOut(pin_din, pin_sclk, MSBFIRST, w);
  digitalWrite(pin_cs, HIGH);
}

void setup() {
  pinMode(pin_din, OUTPUT);
  pinMode(pin_sclk, OUTPUT);
  pinMode(pin_dc, OUTPUT);
  pinMode(pin_rst, OUTPUT);
  pinMode(pin_cs, OUTPUT);
  
  digitalWrite(pin_rst, LOW); 
  digitalWrite(pin_rst, HIGH);
  
  lcd_write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001
  lcd_write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
  lcd_write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
  lcd_write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
  
  lcd_write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
  lcd_write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
  
  lcd_write(LCD_NOKIA_CMD, B01000000);   // X-ADDRESS = 0
  lcd_write(LCD_NOKIA_CMD, B10000000);   // Y-ADDRESS = 0
  
  for (int i=0; i<504; i++){
    lcd_write(LCD_NOKIA_DATA, B00000000);
  }
}

void loop() {
  static boolean horizontal = true;
  for (int i=0; i<504; i++){
    lcd_write(LCD_NOKIA_DATA, B11111111);
    delay(20);
  }
  
  for (int i=0; i<504; i++){
    lcd_write(LCD_NOKIA_DATA, B00000000);
    delay(20);
  }
  
  horizontal = !horizontal;
  
  if (horizontal) {
    lcd_write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
  } else {
    lcd_write(LCD_NOKIA_CMD, B00100010 );  // LCD Basic Commands.     00100000
  }
}


Vídeo:



Veja que os pixels são atualizados de oito em oito, isso porque os pixels são enviados agrupados em um mesmo byte, como pode ser observado na primeira imagem desse artigo. Isso significa que não é possível enviar informação de um único pixel (bit) para o display isoladamente. Mas caso queiramos alterar um único pixel do display, será necessário reenviar todo o byte ao qual o pixel pertence, alterando apenas o bit referente ao pixel ao qual se deseja modificar. Porém isso faz com que antes de enviarmos o byte todo, tenhamos que saber de antemão qual é o estados dos demais pixels. Infelizmente não temos como "perguntar" ao display qual é o estado atual de um determinado byte antes de fazermos a alteração, isso nos exige manter na memória do Arduino o estado atual do(s) byte(s) a ser(em) alterado(s).

No próximo exemplo vamos ver como alterar pixel por pixel de maneira sequencial. Nesse caso será preciso também implementar as funções "Set Y address of RAM" e "Set X address of RAM", pois cada vez que alteramos um dos pixels, o display faz o incremento do endereçamento de forma automática para o próximo byte a ser recebido. Como estamos alterando sequencialmente, temos que a cada atualização reposicionar o x-address e o y-address a ser atualizado.

/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD  LOW

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

void lcd_write(byte mode, byte w){
  digitalWrite(pin_dc, mode);
  digitalWrite(pin_cs, LOW);
  shiftOut(pin_din, pin_sclk, MSBFIRST, w);
  digitalWrite(pin_cs, HIGH);
}

void lcd_position(int l, int c) {  
  lcd_write( LCD_NOKIA_CMD, B01000000 + l);    
  lcd_write( LCD_NOKIA_CMD, B10000000 + c);    
}  

void lcd_clear(){
  for (int i=0; i<504; i++){
    lcd_write(LCD_NOKIA_DATA, B00000000);
  }
}

void setup() {
  pinMode(pin_din, OUTPUT);
  pinMode(pin_sclk, OUTPUT);
  pinMode(pin_dc, OUTPUT);
  pinMode(pin_rst, OUTPUT);
  pinMode(pin_cs, OUTPUT);
  
  digitalWrite(pin_rst, LOW); 
  digitalWrite(pin_rst, HIGH);
  
  lcd_write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001 + ENDERECAMENTO VERTICAL
  lcd_write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
  lcd_write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
  lcd_write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
  
  lcd_write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
  lcd_write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
  
  lcd_position(0,0);
  
  lcd_clear();
}

void loop() {
  //percorre horizontalmente
  for(int i=0; i<6; i++){
    for(int j=0; j<84; j++){
      byte b = 0;
      for (int k=0;k<8;k++){
        lcd_position(i,j);
        b = b << 1 | B00000001;  // 1 11 111 1111 11111 111111 1111111 11111111
        lcd_write(LCD_NOKIA_DATA, b );
        if (j>10 || i>0 ){ delay(5); } else { delay(200); }
      }
    }
  }
  
  //percorre verticalmente
  for(int j=0; j<84; j++){
    for(int i=0; i<6; i++){
      byte b = B11111111;
      for (int k=0;k<8;k++){
        lcd_position(i,j);
        b = b << 1;  
        lcd_write(LCD_NOKIA_DATA, b );
        if (j>10 || i>0 ){ delay(5); } else { delay(200); }
      }
    }
  }

}


Nesse exemplo foi implementada uma função chamada lcd_position(int l, int c). Os parâmetros l e c referem-se a posição dos bytes na memória do display, e não à posição linha/coluna dos pixels. Linha vai de 0 a 5 e coluna de 0 a 83.

Vídeo do exemplo anterior



Nesse próximo exemplo vamos implementar uma função que recebe a posição linha e coluna e o valor (HIGH/LOW) de um determinado pixel, mas agora considerando as 48 linhas e 84 colunas, ou seja, o posicionamento será referente aos pixels e não aos bytes na memória.

Como nossa intenção é alterar um pixel qualquer do display, precisamos saber de antemão qual o estado dos demais pixels que estão representados no mesmo byte. Pra isso será necessário criarmos um buffer que armazene informações de todos os dados enviados para o display.

Para criar um buffer que armazene o estado de todos os pixels desse display, é necessário alocar na memória pelo menos 84x6 bytes, o que dá 504 bytes. Pode não parecer muito, mas declarar um array para armazenar essa quantidade de bytes, significa utilizar 25% da memória de um Arduino Uno, por exemplo.

Código-fonte:

/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD  LOW

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

void setup() {
  pinMode(pin_din, OUTPUT);
  pinMode(pin_sclk, OUTPUT);
  pinMode(pin_dc, OUTPUT);
  pinMode(pin_rst, OUTPUT);
  pinMode(pin_cs, OUTPUT);
  
  digitalWrite(pin_rst, LOW); 
  digitalWrite(pin_rst, HIGH);
  
  lcd_write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001
  lcd_write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
  lcd_write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
  lcd_write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
  
  lcd_write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
  lcd_write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
  
  lcd_position(0,0);
  
  lcd_clear();
}


byte lcd_buffer[504];

void lcd_write(byte mode, byte w){
  digitalWrite(pin_dc, mode);
  digitalWrite(pin_cs, LOW);
  shiftOut(pin_din, pin_sclk, MSBFIRST, w);
  digitalWrite(pin_cs, HIGH);
}

void lcd_position(int l, int c) {  
  lcd_write( LCD_NOKIA_CMD, B01000000 + l);    
  lcd_write( LCD_NOKIA_CMD, B10000000 + c);    
}  

void lcd_clear(){
  for (int i=0; i<504; i++){
    lcd_buffer[i] = B00000000;
    lcd_write(LCD_NOKIA_DATA, lcd_buffer[i]);
  }
}

void lcd_set_pixel(int l, int c, boolean val){
  int bit = l%8;
  l = l/8;
  lcd_position(l, c);
  
  if (val) { 
    lcd_buffer[l*84+c] |= (1 << bit); 
  } else { 
    lcd_buffer[l*84+c] &= ~(1 << bit);  
  }
  
  lcd_write(LCD_NOKIA_DATA, lcd_buffer[l*84+c]);
}

void loop() {
  //horizontal
  for (int i=0; i<4032; i++){
    int linha  = i/84;
    int coluna = i%84;
    lcd_set_pixel( linha, coluna,  true);
    if (i<200 ){ delay(20); } else {delay(5);}
  }
  
  //vertical
  for (int i=0; i<4032; i++){
    int linha  = i%48;
    int coluna = i/48;
    lcd_set_pixel( linha, coluna,  false);
    if (i<200 ){ delay(20); } else {delay(5);}
  }
}

O Exemplo acima, serve apenas como demonstração de como setar pixels individualmente, mas como a gente pode ver, enviar pixel a pixel nem sempre é uma boa ideia, já que é preciso fazer sempre o reenvio de outras informações que já estão no display, o que deixa a atualização um pouco lenta. Nesse caso seria interessante implementar uma função separada, responsável apenas por enviar os dados, assim seria possível fazer todas as alterações necessárias no buffer e posteriormente, de uma vez só, enviar os dados do buffer para o display. Como nem sempre todo o display é modificado entre uma mudança de tela e outra, é interessante também implementar algumas funcionalidades que atualize apenas parte do display que foi alterada.

Nesse próximo exemplo vamos criar uma classe que gerencia as alterações e envio de dados para o display, além disso terá rotinas para gerar pixel, linhas, retângulos (preenchidos ou não) e clear, para limpar toda a tela. Outras rotinas podem ser implementadas, como gerar círculos, triângulos e outras formas geométricas. Mas o ideal é implementar essas rotinas apenas se realmente forem necessárias, caso o projeto assim exija, evitando assim a implementação de muitas rotinas desnecessárias que só deixariam o código "inchado".


Código-fonte

/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/

#define LCD_NOKIA_DATA     HIGH
#define LCD_NOKIA_CMD      LOW
#define LCD_PIXEL_ROWS     48
#define LCD_PIXEL_COLS     84
#define LCD_BUF_SIZE       LCD_PIXEL_ROWS * LCD_PIXEL_COLS / 8  // 504
#define LCD_BUF_STATE_SIZE LCD_BUF_SIZE / 8                     // 63
#define sgn(x) ((x<0)?-1:((x>0)?1:0))

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

class Display{
  private:
    byte _min_byte_state;
    byte _max_byte_state;
    byte _pos_linha;
    byte _pos_coluna;
    byte _lcd_buffer[LCD_BUF_SIZE];
    byte _lcd_buffer_state[LCD_BUF_STATE_SIZE];   //504 / 8 = 63 --> cada bit representa o estado de um dos bytes do buffer
    
    void _write(byte mode, byte w){
      digitalWrite(pin_dc, mode);
      digitalWrite(pin_cs, LOW);
      shiftOut(pin_din, pin_sclk, MSBFIRST, w);
      digitalWrite(pin_cs, HIGH);
      
      if (mode == LCD_NOKIA_DATA) {
        if (++_pos_coluna >= LCD_PIXEL_COLS) { 
          _pos_coluna = 0;  
          if ( ++_pos_linha>= 6) {  _pos_linha  = 0;  }
        }
      }
    }
    void _position(int l, int c) {  
      if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
      if (_pos_linha  != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha  = l;}
    }  
    
  public:
    Display() {
      pinMode(pin_din, OUTPUT);
      pinMode(pin_sclk, OUTPUT);
      pinMode(pin_dc, OUTPUT);
      pinMode(pin_rst, OUTPUT);
      pinMode(pin_cs, OUTPUT);
      
      digitalWrite(pin_rst, LOW); 
      digitalWrite(pin_rst, HIGH);
      
      _write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001 + ENDERECAMENTO HORIZONTAL
      _write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
      _write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
      _write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
      _write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
      _write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
      _write(LCD_NOKIA_CMD, B01000000);   // POSICAO-X 0 
      _write(LCD_NOKIA_CMD, B10000000);   // POSICAO-Y 0

      _min_byte_state = LCD_BUF_STATE_SIZE-1;
      _max_byte_state = 0;

      clear(true);
    }
    
    /*48 x 84*/
    void pixel(int l, int c, boolean val){  
      if (l>=LCD_PIXEL_ROWS || c>= LCD_PIXEL_COLS || l <0 || c<0){ return; }
      
      int bit = l%8;
      l = l/8;            
      
      byte temp = _lcd_buffer[l*LCD_PIXEL_COLS+c]; //faz uma copia do estado atual do byte a ser alterado
      
      if (val) { 
        _lcd_buffer[l*LCD_PIXEL_COLS+c] |= (1 << bit); 
      } else { 
        _lcd_buffer[l*LCD_PIXEL_COLS+c] &= ~(1 << bit);  
      }
      
      //se houver alteração no byte, altera o status para alterado.
      if (temp != _lcd_buffer[l*LCD_PIXEL_COLS+c] ){
        byte idx = (l*LCD_PIXEL_COLS+c)/8;  //indice do byte de status
        _lcd_buffer_state[  idx ] |= (1 << ((l*LCD_PIXEL_COLS+c)%8 ));  //inverte o status do byte alterado
        
        if (_min_byte_state > idx) { _min_byte_state = idx; }
        if (_max_byte_state < idx) { _max_byte_state = idx; }
      }
    }
    
    
    void line(int x1, int y1, int x2, int y2){
      /* http://www.brackeen.com/vga/source/djgpp20/lines.c.html */
      
      int i,dx,dy,sdx,sdy,dxabs,dyabs,x,y,px,py;

      dx    = x2-x1;      /* the horizontal distance of the line */
      dy    = y2-y1;      /* the vertical distance of the line */
      dxabs = abs(dx);
      dyabs = abs(dy);
      sdx   = sgn(dx);
      sdy   = sgn(dy);
      x     = dyabs>>1;
      y     = dxabs>>1;
      px    = x1;
      py    = y1;
    
      pixel(x1,y1,true);
    
      if (dxabs>=dyabs) { /* the line is more horizontal than vertical */
        for(i=0;i<dxabs;i++) {
          y+=dyabs;
          if (y>=dxabs) { y-=dxabs; py+=sdy; }
          px+=sdx;
          pixel(px,py,true);
        }
      } else { /* the line is more vertical than horizontal */
        for(i=0;i<dyabs;i++) {
          x+=dxabs;
          if (x>=dyabs) { x-=dyabs; px+=sdx; }
          py+=sdy;
          pixel(px,py,true);
        }
      }
    }
    
    void rectangle(int left,int top, int right, int bottom, boolean fill=false) {
      if (fill){
        if (left>right) { //swap
          left  ^= right;
          right ^= left;
          left  ^= right;
        }
        if (top>bottom) { //swap
          top    ^= bottom;
          bottom ^= top;
          top    ^= bottom;
        }
        for (int i=0; i<bottom-top; i++) {
          line(left,top+i,right,top+i);
        }
      } else {
        line(left,top,right,top);
        line(left,top,left,bottom);
        line(right,top,right,bottom);
        line(left,bottom,right,bottom);
      }
    }
            
    void clear(boolean _send=false){
      for (int i=0; i<LCD_BUF_SIZE; i++)       { _lcd_buffer[i] = B00000000; }
      for (int i=0; i<LCD_BUF_STATE_SIZE; i++) { _lcd_buffer_state[i] = B11111111; }
      
      _min_byte_state = 0;
      _max_byte_state = LCD_BUF_STATE_SIZE-1;
      
      if (_send){ send(); }
    }
    
    void send(){
      //faz uma busca pra saber quais dos bytes foram alterados
      for (int i=_min_byte_state;i<=_max_byte_state;i++){
        if (_lcd_buffer_state[i] == 0) { continue; } 
        for (int j=0;j<8;j++){
          if bitRead( _lcd_buffer_state[i], j ) {
            _position( (i*8+j)/LCD_PIXEL_COLS, (i*8+j)%LCD_PIXEL_COLS );
            _write(LCD_NOKIA_DATA, _lcd_buffer[i*8+j]);
          }
        }
        _lcd_buffer_state[i] = B00000000;
      }
      _min_byte_state = LCD_BUF_STATE_SIZE-1;
      _max_byte_state = 0;
    }
};


Display d;  //declaração do objeto display

void setup() {
  randomSeed(analogRead(A0));
}

void loop() {
  for (int i=0; i<25; i++){
    d.line(random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS),random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS));
    d.send();
    delay(500);
  }
  d.clear();
  for (int i=0; i<25; i++){
    d.rectangle(random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS),random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS));
    d.send();
    delay(500);
  }
  d.clear();
  for (int i=0; i<25; i++){
    d.rectangle(random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS),random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS), true);
    d.send();
    delay(500);
  }
  d.clear();
}

Vídeo


Snake Game

Nesse próximo exemplo vamos implementar o jogo Snake, o famoso jogo da cobrinha. Pra isso vou me basear em um exemplo já mostrado aqui no blog, utilizando matriz de led controlados pelo max7219. Veja o artigo nesse link: http://fabianoallex.blogspot.com.br/2015/10/arduino-snake-game-jogo-da-cobrinha.html

Com poucas modificações foi possível adaptar o exemplo de lá para rodar no nosso display. A principal alteração foi diminuirmos a resolução do jogo. Se mantivéssemos como no original, a cobrinha teria a largura de 1 pixel apenas, o que deixaria o jogo muito pequeno. Pra resolver isso incluímos uma constante que multiplica o tamanho do pixel do jogo, no exemplo abaixo, 1 pixel do jogo é representado por 16 (4x4) pixels do display. O que torna o jogo mais agradável de ser visualizado.

Como a implementação do jogo é servir apenas de exemplo, não foi implementados com botões para controlar a cobrinha, e sim utilizei a saída Serial, como já feito no exemplo da matriz de leds.

Arquivo .bat

MODE COM6 BAUD=9600 PARITY=n DATA=8

:LOOP
  
  CHOICE /C:1235 /M "1: LEFT; 2: BOTTOM; 3: RIGHT; 4: TOP" 
  IF errorlevel 4 GOTO TOP
  IF errorlevel 3 GOTO RIGHT
  IF errorlevel 2 GOTO BOTTOM
  IF errorlevel 1 GOTO LEFT

  :RIGHT  
  ECHO b > COM6 
  GOTO END

  :LEFT
  ECHO a > COM6  
  GOTO END

  :TOP
  ECHO c > COM6  
  GOTO END

  :BOTTOM
  ECHO d > COM6  
  
  :END
  CLS
GOTO LOOP 

:SAIR




Código-fonte:


/*************************************************************************************************************
*******************************CLASSE DISPLAY NOKIA 5110******************************************************
**************************************************************************************************************/
/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/

#define LCD_NOKIA_DATA     HIGH
#define LCD_NOKIA_CMD      LOW
#define LCD_PIXEL_ROWS     48
#define LCD_PIXEL_COLS     84
#define LCD_BUF_SIZE       LCD_PIXEL_ROWS * LCD_PIXEL_COLS / 8  // 504
#define LCD_BUF_STATE_SIZE LCD_BUF_SIZE / 8                     // 63
#define sgn(x) ((x<0)?-1:((x>0)?1:0))

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

class Display{
  private:
    byte _min_byte_state;
    byte _max_byte_state;
    byte _pos_linha;
    byte _pos_coluna;
    byte _lcd_buffer[LCD_BUF_SIZE];
    byte _lcd_buffer_state[LCD_BUF_STATE_SIZE];   //504 / 8 = 63 --> cada bit representa o estado de um dos bytes do buffer
    
    void _write(byte mode, byte w){
      digitalWrite(pin_dc, mode);
      digitalWrite(pin_cs, LOW);
      shiftOut(pin_din, pin_sclk, MSBFIRST, w);
      digitalWrite(pin_cs, HIGH);
      
      if (mode == LCD_NOKIA_DATA) {
        if (++_pos_coluna >= LCD_PIXEL_COLS) { 
          _pos_coluna = 0;  
          if ( ++_pos_linha>= 6) {  _pos_linha  = 0;  }
        }
      }
    }
    void _position(int l, int c) {  
      if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
      if (_pos_linha  != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha  = l;}
    }  
    
  public:
    Display() {
      pinMode(pin_din, OUTPUT);
      pinMode(pin_sclk, OUTPUT);
      pinMode(pin_dc, OUTPUT);
      pinMode(pin_rst, OUTPUT);
      pinMode(pin_cs, OUTPUT);
      
      digitalWrite(pin_rst, LOW); 
      digitalWrite(pin_rst, HIGH);
      
      _write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001 + ENDERECAMENTO HORIZONTAL
      _write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
      _write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
      _write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
      _write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
      _write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
      _write(LCD_NOKIA_CMD, B01000000);   // POSICAO-X 0 
      _write(LCD_NOKIA_CMD, B10000000);   // POSICAO-Y 0

      _min_byte_state = LCD_BUF_STATE_SIZE-1;
      _max_byte_state = 0;

      clear(true);
    }
    
    int rows()    { return LCD_PIXEL_ROWS; }
    int columns() { return LCD_PIXEL_COLS; }
    
    /*48 x 84*/
    void pixel(int l, int c, boolean val){  
      if (l>=LCD_PIXEL_ROWS || c>= LCD_PIXEL_COLS || l <0 || c<0){ return; }
      
      int bit = l%8;
      l = l/8;            
      
      byte temp = _lcd_buffer[l*LCD_PIXEL_COLS+c]; //faz uma copia do estado atual do byte a ser alterado
      
      if (val) { 
        _lcd_buffer[l*LCD_PIXEL_COLS+c] |= (1 << bit); 
      } else { 
        _lcd_buffer[l*LCD_PIXEL_COLS+c] &= ~(1 << bit);  
      }
      
      //se houver alteração no byte, altera o status para alterado.
      if (temp != _lcd_buffer[l*LCD_PIXEL_COLS+c] ){
        byte idx = (l*LCD_PIXEL_COLS+c)/8;  //indice do byte de status
        _lcd_buffer_state[  idx ] |= (1 << ((l*LCD_PIXEL_COLS+c)%8 ));  //inverte o status do byte alterado
        
        if (_min_byte_state > idx) { _min_byte_state = idx; }
        if (_max_byte_state < idx) { _max_byte_state = idx; }
      }
    }

    //OS METODOS LINE E RECTANGLE FORAM REMOVIDOS, POIS NESSE EXEMPLO NÃO FORAM USADOS.
    //MAS CASO DESEJE UTILIZA-LOS, BASTA COPIAR DOS DEMAIS EXEMPLOS DESSE ARTIGO.
            
    void clear(boolean _send=false){
      for (int i=0; i<LCD_BUF_SIZE; i++)       { _lcd_buffer[i] = B00000000; }
      for (int i=0; i<LCD_BUF_STATE_SIZE; i++) { _lcd_buffer_state[i] = B11111111; }
      
      _min_byte_state = 0;
      _max_byte_state = LCD_BUF_STATE_SIZE-1;
      
      if (_send){ send(); }
    }
    
    void send(){
      //faz uma busca pra saber quais dos bytes foram alterados
      for (int i=_min_byte_state;i<=_max_byte_state;i++){
        if (_lcd_buffer_state[i] == 0) { continue; } 
        for (int j=0;j<8;j++){
          if bitRead( _lcd_buffer_state[i], j ) {
            _position( (i*8+j)/LCD_PIXEL_COLS, (i*8+j)%LCD_PIXEL_COLS );
            _write(LCD_NOKIA_DATA, _lcd_buffer[i*8+j]);
          }
        }
        _lcd_buffer_state[i] = B00000000;
      }
      _min_byte_state = LCD_BUF_STATE_SIZE-1;
      _max_byte_state = 0;
    }
};

/*************************************************************************************************************
*******************************FIM CLASSE DISPLAY NOKIA 5110**************************************************
**************************************************************************************************************/

/*************************************************************************************************************
*******************************CLASSE SNAKE GAME**************************************************************
**************************************************************************************************************/

#define SNAKE_PIXEL_LEN 4             /*tamanho do pixel da cobra em relação do display*/

struct Position { byte lin; byte col; };
 
const int SNAKE_MAX_LEN   = 50;  //tamanho maximo da cobra
const int SNAKE_TIME_INIT = 500; //tempo entre deslocamento da cobra (velocidade)
const int SNAKE_TIME_INC  = 15;  //incremento da velocidade
 
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum SnakeStatus { SNAKE_GAME_ON, SNAKE_GAME_OVER };
 
class SnakeGame{
  private:
    Display * _display;
    Position _snake_positions[SNAKE_MAX_LEN];
    Position _apple;
    int _length;
    Direction _direction;
    unsigned long _last_millis;
    int _time;
    int _score;
    SnakeStatus _snakeStatus;
     
    void _generateApple() {
      int lin, col;
      boolean random_ok = false;
       
      while (!random_ok) {
        random_ok = true;
        lin = random(0, _display->rows()/SNAKE_PIXEL_LEN-1);
        col = random(0, _display->columns()/SNAKE_PIXEL_LEN-1);
         
        for (int p=0; p<_length; p++){
          if (_snake_positions[p].col==col && _snake_positions[p].lin==lin){ //verifica se gerou em um local que não seja a cobra
            random_ok = false;
            break;
          }
        }
      }
      _apple.lin = lin;
      _apple.col = col;
    }
     
    void _gameOver(){ 
      _snakeStatus = SNAKE_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
     
    void _inc_length(){
      _length++; _score++;
      _time -= SNAKE_TIME_INC;
    }
     
    void _runGameOver(){
      int r = random(_display->columns() * _display->rows());
      int lin = (r / _display->columns()) / SNAKE_PIXEL_LEN;
      int col = (r % _display->columns()) / SNAKE_PIXEL_LEN;
      
      for (int i=0;i<SNAKE_PIXEL_LEN;i++){
        for (int j=0;j<SNAKE_PIXEL_LEN;j++){
          _display->pixel( lin*SNAKE_PIXEL_LEN+i, col*SNAKE_PIXEL_LEN+j,  true );
        }
      }
      
      if ( _direction != DIR_STOP ) { start(); }
    }
     
    void _run(){
      for (int i=_length-1; i>0; i--){
        _snake_positions[i].lin = _snake_positions[i-1].lin;
        _snake_positions[i].col = _snake_positions[i-1].col;
      }
       
      if (_direction == DIR_TOP )    { _snake_positions[0].lin--;  }
      if (_direction == DIR_BOTTOM ) { _snake_positions[0].lin++;  }
      if (_direction == DIR_LEFT )   { _snake_positions[0].col--;  }
      if (_direction == DIR_RIGHT )  { _snake_positions[0].col++;  }
       
      //verifica se ultrapassou o limite do display
      if (_snake_positions[0].lin < 0)                                     { _gameOver(); }
      if (_snake_positions[0].lin >= _display->rows()/SNAKE_PIXEL_LEN )    { _gameOver(); }
      if (_snake_positions[0].col < 0)                                     { _gameOver(); }
      if (_snake_positions[0].col >= _display->columns()/SNAKE_PIXEL_LEN ) { _gameOver(); }
       
      //verifica se colidiu na cobra
      for (int i=_length-1; i>0; i--){
        if (_snake_positions[i].lin == _snake_positions[0].lin && _snake_positions[i].col == _snake_positions[0].col) {
          _gameOver();
        }  
      }
       
      //verifica se comeu a maça
      if (_snake_positions[0].col == _apple.col && _snake_positions[0].lin == _apple.lin){
        _inc_length();
         
        if (_length > SNAKE_MAX_LEN) { _length = SNAKE_MAX_LEN; } else {
          _snake_positions[_length-1].lin = _snake_positions[_length-2].lin;
          _snake_positions[_length-1].col = _snake_positions[_length-2].col;
        }
        _generateApple();
      }
       
      //update display
      for (int lin=0; lin<_display->rows()/SNAKE_PIXEL_LEN; lin++) {
        for (int col=0; col<_display->columns()/SNAKE_PIXEL_LEN; col++) {
          for (int p=0; p<_length; p++){
            boolean val = _snake_positions[p].col==col && _snake_positions[p].lin==lin;
            for (int i=0;i<SNAKE_PIXEL_LEN;i++){
              for (int j=0;j<SNAKE_PIXEL_LEN;j++){
                _display->pixel( lin*SNAKE_PIXEL_LEN+i, col*SNAKE_PIXEL_LEN+j,  val );
              }
            }
            if (val) {break;}
          }
        }
      }
      for (int i=0;i<SNAKE_PIXEL_LEN;i++){
        for (int j=0;j<SNAKE_PIXEL_LEN;j++){
          _display->pixel( _apple.lin*SNAKE_PIXEL_LEN+i, _apple.col*SNAKE_PIXEL_LEN+j,  true );
        }
      }
    }
     
  public:
    SnakeGame(Display * d){ 
      _display = d;
      start();
    }
     
    void start(){
      _length = 1;
      _score  = 0;
      _time = SNAKE_TIME_INIT;
      _last_millis = 0;
      _snake_positions[0].lin = _display->rows() / (2 * SNAKE_PIXEL_LEN);
      _snake_positions[0].col = _display->columns() / (2 * SNAKE_PIXEL_LEN);
      _direction = DIR_STOP;
       
      _snakeStatus = SNAKE_GAME_ON;
       
      _generateApple();
    }
     
    void left()   { if (_direction == DIR_RIGHT)  return; _direction = DIR_LEFT;   }
    void right()  { if (_direction == DIR_LEFT)   return; _direction = DIR_RIGHT;  }
    void top()    { if (_direction == DIR_BOTTOM) return; _direction = DIR_TOP;    }
    void bottom() { if (_direction == DIR_TOP)    return; _direction = DIR_BOTTOM; }
     
    int getScore(){ return _score; }
     
    int update(){
      int r = false;
       
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();
       
        if (_snakeStatus == SNAKE_GAME_ON)   { _run();         }
        if (_snakeStatus == SNAKE_GAME_OVER) { _runGameOver(); }
      }
       
      return r; //r-->indica se houve mudança no display
    }
};
 
/*************************************************************************************************************
*******************************FIM CLASSE SNAKE GAME**********************************************************
**************************************************************************************************************/

Display d;  //declaração do objeto display
SnakeGame snake(&d);

void setup() {
  randomSeed(analogRead(A0));
  Serial.begin(9600);
}

void loop() {
  int c = Serial.read();
    
  if (c == 97)  { snake.left();   } //a -> 
  if (c == 98)  { snake.right();  } //b -> 
  if (c == 99)  { snake.top();    } //c -> 
  if (c == 100) { snake.bottom(); } //d -> 
   
  if ( snake.update() ) { d.send(); }
}


Vídeo


Textos e Números

Até agora nossos exemplos não tiveram nada de texto nem de números, apenas manipulações gráficas. Isso foi proposital, para que possamos entender melhor o processo de envio de dados para o display. Uma das vantagens de utilizarmos bibliotecas prontas é que em poucas linhas teríamos qualquer texto na tela do display muito facilmente, até porque alguém já fez todo o trabalho de mapear cada um dos caracteres e símbolos que poderíamos querer usar, além de fazer isso em vários formatos e tamanhos diferentes, ou seja, bibliotecas prontas são bem mais completas e podem oferecer vários recursos... Apesar da intenção aqui não ser reinventar a roda e sim aprendermos mais do que simplesmente usar o que está pronto, devemos ter em mente que alguns projetos não exigem mais do que algumas poucas letras ou palavras, então nesses casos vale a pena sim termos soluções mais simples e eficientes, evitando utilizar recursos demais que poderiam ser economizados. Obs. Mais abaixo, abordo ainda como criar soluções sem uso de buffers.

Código-fonte:

/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/

static const PROGMEM byte ASCII[][5] = {
  { 0x00, 0x00, 0x00, 0x00, 0x00 } // 20   (space)
 ,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 !
 ,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 "
 ,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 #
 ,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $
 ,{ 0x23, 0x13, 0x08, 0x64, 0x62 } // 25 %
 ,{ 0x36, 0x49, 0x55, 0x22, 0x50 } // 26 &
 ,{ 0x00, 0x05, 0x03, 0x00, 0x00 } // 27 '
 ,{ 0x00, 0x1c, 0x22, 0x41, 0x00 } // 28 (
 ,{ 0x00, 0x41, 0x22, 0x1c, 0x00 } // 29 )
 ,{ 0x14, 0x08, 0x3e, 0x08, 0x14 } // 2a *
 ,{ 0x08, 0x08, 0x3e, 0x08, 0x08 } // 2b +
 ,{ 0x00, 0x50, 0x30, 0x00, 0x00 } // 2c ,
 ,{ 0x08, 0x08, 0x08, 0x08, 0x08 } // 2d -
 ,{ 0x00, 0x60, 0x60, 0x00, 0x00 } // 2e .
 ,{ 0x20, 0x10, 0x08, 0x04, 0x02 } // 2f /
 ,{ 0x3e, 0x51, 0x49, 0x45, 0x3e } // 30 0
 ,{ 0x00, 0x42, 0x7f, 0x40, 0x00 } // 31 1
 ,{ 0x42, 0x61, 0x51, 0x49, 0x46 } // 32 2
 ,{ 0x21, 0x41, 0x45, 0x4b, 0x31 } // 33 3
 ,{ 0x18, 0x14, 0x12, 0x7f, 0x10 } // 34 4
 ,{ 0x27, 0x45, 0x45, 0x45, 0x39 } // 35 5
 ,{ 0x3c, 0x4a, 0x49, 0x49, 0x30 } // 36 6
 ,{ 0x01, 0x71, 0x09, 0x05, 0x03 } // 37 7
 ,{ 0x36, 0x49, 0x49, 0x49, 0x36 } // 38 8
 ,{ 0x06, 0x49, 0x49, 0x29, 0x1e } // 39 9
 ,{ 0x00, 0x36, 0x36, 0x00, 0x00 } // 3a :
 ,{ 0x00, 0x56, 0x36, 0x00, 0x00 } // 3b ;
 ,{ 0x08, 0x14, 0x22, 0x41, 0x00 } // 3c <
 ,{ 0x14, 0x14, 0x14, 0x14, 0x14 } // 3d =
 ,{ 0x00, 0x41, 0x22, 0x14, 0x08 } // 3e >
 ,{ 0x02, 0x01, 0x51, 0x09, 0x06 } // 3f ?
 ,{ 0x32, 0x49, 0x79, 0x41, 0x3e } // 40 @
 ,{ 0x7e, 0x11, 0x11, 0x11, 0x7e } // 41 A
 ,{ 0x7f, 0x49, 0x49, 0x49, 0x36 } // 42 B
 ,{ 0x3e, 0x41, 0x41, 0x41, 0x22 } // 43 C
 ,{ 0x7f, 0x41, 0x41, 0x22, 0x1c } // 44 D
 ,{ 0x7f, 0x49, 0x49, 0x49, 0x41 } // 45 E
 ,{ 0x7f, 0x09, 0x09, 0x09, 0x01 } // 46 F
 ,{ 0x3e, 0x41, 0x49, 0x49, 0x7a } // 47 G
 ,{ 0x7f, 0x08, 0x08, 0x08, 0x7f } // 48 H
 ,{ 0x00, 0x41, 0x7f, 0x41, 0x00 } // 49 I
 ,{ 0x20, 0x40, 0x41, 0x3f, 0x01 } // 4a J
 ,{ 0x7f, 0x08, 0x14, 0x22, 0x41 } // 4b K
 ,{ 0x7f, 0x40, 0x40, 0x40, 0x40 } // 4c L
 ,{ 0x7f, 0x02, 0x0c, 0x02, 0x7f } // 4d M
 ,{ 0x7f, 0x04, 0x08, 0x10, 0x7f } // 4e N
 ,{ 0x3e, 0x41, 0x41, 0x41, 0x3e } // 4f O
 ,{ 0x7f, 0x09, 0x09, 0x09, 0x06 } // 50 P
 ,{ 0x3e, 0x41, 0x51, 0x21, 0x5e } // 51 Q
 ,{ 0x7f, 0x09, 0x19, 0x29, 0x46 } // 52 R
 ,{ 0x46, 0x49, 0x49, 0x49, 0x31 } // 53 S
 ,{ 0x01, 0x01, 0x7f, 0x01, 0x01 } // 54 T
 ,{ 0x3f, 0x40, 0x40, 0x40, 0x3f } // 55 U
 ,{ 0x1f, 0x20, 0x40, 0x20, 0x1f } // 56 V
 ,{ 0x3f, 0x40, 0x38, 0x40, 0x3f } // 57 W
 ,{ 0x63, 0x14, 0x08, 0x14, 0x63 } // 58 X
 ,{ 0x07, 0x08, 0x70, 0x08, 0x07 } // 59 Y
 ,{ 0x61, 0x51, 0x49, 0x45, 0x43 } // 5a Z
 ,{ 0x00, 0x7f, 0x41, 0x41, 0x00 } // 5b [
 ,{ 0x02, 0x04, 0x08, 0x10, 0x20 } // 5c backslash
 ,{ 0x00, 0x41, 0x41, 0x7f, 0x00 } // 5d ]
 ,{ 0x04, 0x02, 0x01, 0x02, 0x04 } // 5e ^
 ,{ 0x40, 0x40, 0x40, 0x40, 0x40 } // 5f _
 ,{ 0x00, 0x01, 0x02, 0x04, 0x00 } // 60 `
 ,{ 0x20, 0x54, 0x54, 0x54, 0x78 } // 61 a
 ,{ 0x7f, 0x48, 0x44, 0x44, 0x38 } // 62 b
 ,{ 0x38, 0x44, 0x44, 0x44, 0x20 } // 63 c
 ,{ 0x38, 0x44, 0x44, 0x48, 0x7f } // 64 d
 ,{ 0x38, 0x54, 0x54, 0x54, 0x18 } // 65 e
 ,{ 0x08, 0x7e, 0x09, 0x01, 0x02 } // 66 f
 ,{ 0x0c, 0x52, 0x52, 0x52, 0x3e } // 67 g
 ,{ 0x7f, 0x08, 0x04, 0x04, 0x78 } // 68 h
 ,{ 0x00, 0x44, 0x7d, 0x40, 0x00 } // 69 i
 ,{ 0x20, 0x40, 0x44, 0x3d, 0x00 } // 6a j 
 ,{ 0x7f, 0x10, 0x28, 0x44, 0x00 } // 6b k
 ,{ 0x00, 0x41, 0x7f, 0x40, 0x00 } // 6c l
 ,{ 0x7c, 0x04, 0x18, 0x04, 0x78 } // 6d m
 ,{ 0x7c, 0x08, 0x04, 0x04, 0x78 } // 6e n
 ,{ 0x38, 0x44, 0x44, 0x44, 0x38 } // 6f o
 ,{ 0x7c, 0x14, 0x14, 0x14, 0x08 } // 70 p
 ,{ 0x08, 0x14, 0x14, 0x18, 0x7c } // 71 q
 ,{ 0x7c, 0x08, 0x04, 0x04, 0x08 } // 72 r
 ,{ 0x48, 0x54, 0x54, 0x54, 0x20 } // 73 s
 ,{ 0x04, 0x3f, 0x44, 0x40, 0x20 } // 74 t
 ,{ 0x3c, 0x40, 0x40, 0x20, 0x7c } // 75 u
 ,{ 0x1c, 0x20, 0x40, 0x20, 0x1c } // 76 v
 ,{ 0x3c, 0x40, 0x30, 0x40, 0x3c } // 77 w
 ,{ 0x44, 0x28, 0x10, 0x28, 0x44 } // 78 x
 ,{ 0x0c, 0x50, 0x50, 0x50, 0x3c } // 79 y
 ,{ 0x44, 0x64, 0x54, 0x4c, 0x44 } // 7a z
 ,{ 0x00, 0x08, 0x36, 0x41, 0x00 } // 7b {
 ,{ 0x00, 0x00, 0x7f, 0x00, 0x00 } // 7c |
 ,{ 0x00, 0x41, 0x36, 0x08, 0x00 } // 7d }
 ,{ 0x10, 0x08, 0x08, 0x10, 0x08 } // 7e ~
 ,{ 0x78, 0x46, 0x41, 0x46, 0x78 } // 7f DEL
};

#define LCD_NOKIA_DATA     HIGH
#define LCD_NOKIA_CMD      LOW
#define LCD_PIXEL_ROWS     48
#define LCD_PIXEL_COLS     84
#define LCD_BUF_SIZE       LCD_PIXEL_ROWS * LCD_PIXEL_COLS / 8  // 504
#define LCD_BUF_STATE_SIZE LCD_BUF_SIZE / 8                     // 63
#define LCD_NUN_BANKS      LCD_PIXEL_ROWS / 8 
#define sgn(x) ((x<0)?-1:((x>0)?1:0))

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

class Display{
  private:
    byte _write_pos_bank;  //posicoes utilizadas para escrever texto
    byte _write_pos_col;   //posicoes utilizadas para escrever texto
    
    byte _min_byte_state;
    byte _max_byte_state;
    byte _pos_linha;
    byte _pos_coluna;
    byte _lcd_buffer[LCD_BUF_SIZE];
    byte _lcd_buffer_state[LCD_BUF_STATE_SIZE];   //504 / 8 = 63 --> cada bit representa o estado de um dos bytes do buffer
    
    void _write(byte mode, byte w){
      digitalWrite(pin_dc, mode);
      digitalWrite(pin_cs, LOW);
      shiftOut(pin_din, pin_sclk, MSBFIRST, w);
      digitalWrite(pin_cs, HIGH);
      
      if (mode == LCD_NOKIA_DATA) {
        if (++_pos_coluna >= LCD_PIXEL_COLS) { 
          _pos_coluna = 0;  
          if ( ++_pos_linha>= 6) {  _pos_linha  = 0;  }
        }
      }
    }
    void _position(int l, int c) {  
      if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
      if (_pos_linha  != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha  = l;}
    }  
    
    //se houver alteração no byte, altera o status para alterado.
    void _write_buffer(int index, byte b) {
      if (b != _lcd_buffer[index] ){
        _lcd_buffer[index] = b;
        byte idx = (index)/8;  //indice do byte de status
        _lcd_buffer_state[  idx ] |= 1 << (index)%8;  //inverte o status do byte alterado
        
        if (_min_byte_state > idx) { _min_byte_state = idx; }
        if (_max_byte_state < idx) { _max_byte_state = idx; }
      }
    }
    
  public:
    Display() {
      pinMode(pin_din, OUTPUT);
      pinMode(pin_sclk, OUTPUT);
      pinMode(pin_dc, OUTPUT);
      pinMode(pin_rst, OUTPUT);
      pinMode(pin_cs, OUTPUT);
      
      digitalWrite(pin_rst, LOW); 
      digitalWrite(pin_rst, HIGH);
      
      _write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001 + ENDERECAMENTO HORIZONTAL
      _write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
      _write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
      _write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
      _write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
      _write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
      _write(LCD_NOKIA_CMD, B01000000);   // POSICAO-X 0 
      _write(LCD_NOKIA_CMD, B10000000);   // POSICAO-Y 0

      _min_byte_state = LCD_BUF_STATE_SIZE-1;
      _max_byte_state = 0;

      clear(true);
    }
    
    void write_pos(byte bank, byte col){
      if (col + 5 >= LCD_PIXEL_COLS) { 
        col -= (LCD_PIXEL_COLS - 6); 
        bank++ ;
        if (bank >= LCD_NUN_BANKS ){ bank=0; }
      }
      
      _write_pos_bank = bank;  //posicoes utilizadas para escrever texto
      _write_pos_col  = col;   //posicoes utilizadas para escrever texto
    }
    
    void write(byte data) {
     if (data < 0x20 || data > 0x7F) return;                                  // Non-ASCII characters are not supported.
        //if (col + 5 >= LCD_BUF_SIZE) { col -= (LCD_BUF_SIZE - 6); }
        byte buf[6];
 memcpy_P(buf, ASCII[data - 0x20], 5);
 buf[5] = 0x00;
        
        for (int i=0;i<6;i++){ 
          _write_buffer(_write_pos_bank*LCD_PIXEL_COLS+_write_pos_col+i, buf[i]); 
          
        }
        write_pos(_write_pos_bank, _write_pos_col+6);
    }
    
    void write(char * c){
      int i = 0;
      while ( c[i] != '\0') { write(c[i++]); }
    }
    
    /*48 x 84*/
    void pixel(int l, int c, boolean val){  
      if (l>=LCD_PIXEL_ROWS || c>= LCD_PIXEL_COLS || l <0 || c<0){ return; }
      int bit = l%8;
      l = l/8;      
      byte b = _lcd_buffer[l*LCD_PIXEL_COLS+c]; //faz uma copia do estado atual do byte a ser alterado
      if (val) { 
        b |= (1 << bit); 
      } else { 
        b &= ~(1 << bit);  
      }
      _write_buffer(l*LCD_PIXEL_COLS+c, b);
    }
    
    
    void line(int x1, int y1, int x2, int y2){
      /* http://www.brackeen.com/vga/source/djgpp20/lines.c.html */
      
      int i,dx,dy,sdx,sdy,dxabs,dyabs,x,y,px,py;

      dx    = x2-x1;      /* the horizontal distance of the line */
      dy    = y2-y1;      /* the vertical distance of the line */
      dxabs = abs(dx);
      dyabs = abs(dy);
      sdx   = sgn(dx);
      sdy   = sgn(dy);
      x     = dyabs>>1;
      y     = dxabs>>1;
      px    = x1;
      py    = y1;
    
      pixel(x1,y1,true);
    
      if (dxabs>=dyabs) { /* the line is more horizontal than vertical */
        for(i=0;i<dxabs;i++) {
          y+=dyabs;
          if (y>=dxabs) { y-=dxabs; py+=sdy; }
          px+=sdx;
          pixel(px,py,true);
        }
      } else { /* the line is more vertical than horizontal */
        for(i=0;i<dyabs;i++) {
          x+=dxabs;
          if (x>=dyabs) { x-=dyabs; px+=sdx; }
          py+=sdy;
          pixel(px,py,true);
        }
      }
    }
    
    void rectangle(int left,int top, int right, int bottom, boolean fill=false) {
      if (fill){
        if (left>right) { //swap
          left  ^= right;
          right ^= left;
          left  ^= right;
        }
        if (top>bottom) { //swap
          top    ^= bottom;
          bottom ^= top;
          top    ^= bottom;
        }
        for (int i=0; i<bottom-top; i++) {
          line(left,top+i,right,top+i);
        }
      } else {
        line(left,top,right,top);
        line(left,top,left,bottom);
        line(right,top,right,bottom);
        line(left,bottom,right,bottom);
      }
    }
            
    void clear(boolean _send=false){
      for (int i=0; i<LCD_BUF_SIZE; i++)       { _write_buffer(i, B00000000); }
      //for (int i=0; i<LCD_BUF_STATE_SIZE; i++) { _lcd_buffer_state[i] = B11111111; }
      
      if (_send){ send(); }
    }
    
    void send(){
      //faz uma busca pra saber quais dos bytes foram alterados
      for (int i=_min_byte_state;i<=_max_byte_state;i++){
        if (_lcd_buffer_state[i] == 0) { continue; } 
        for (int j=0;j<8;j++){
          if bitRead( _lcd_buffer_state[i], j ) {
            _position( (i*8+j)/LCD_PIXEL_COLS, (i*8+j)%LCD_PIXEL_COLS );
            _write(LCD_NOKIA_DATA, _lcd_buffer[i*8+j]);
          }
        }
        _lcd_buffer_state[i] = B00000000;
      }
      _min_byte_state = LCD_BUF_STATE_SIZE-1;
      _max_byte_state = 0;
    }
};


Display d;  //declaração do objeto display

void setup() {
  randomSeed(analogRead(A0));
  Serial.begin(9600);
}

void loop() {
  for (int i=0; i<LCD_PIXEL_COLS-6; i+=5){
    d.clear();
    d.write_pos(random(LCD_NUN_BANKS),i);
    d.write("Arduino");
    d.send();  
    delay(600);  
  }
}


Vídeo



Soluções que não utilizam Buffer

Nossos exemplos até agora se basearam em soluções que fazem uso de buffer, ou seja, mantém na memória do Arduino um cópia do estado do display, assim conseguimos fazer alterações em áreas específicas do display e enviar apenas o que de fato foi alterado. Isso dá uma liberdade maior na hora de criarmos telas mais elaboradas, porém isso nem sempre é conveniente, pois como já falado anteriormente, em um Arduino Uno, um buffer teria que ter 504 bytes (84 x 6). Isso sozinho representa 25% da memória total. Já no caso de um Attiny85, seria praticamente 100%, então, sem chances!

No caso de uma solução sem buffer, temos a vantagem do uso não exagerado da memória, mas isso tem um preço, por exemplo, ao darmos um "clear" no display gerenciado por um buffer, bastaria apenas verificar quais partes do display não estão limpas, e alterar somente essas partes, já numa solução sem buffer, vamos ter que fazer uma varredura do início ao fim, alterando toda a área do display, mesmo já estando limpa. Em termos de programação acaba sendo mais simples, mas por outro lado, exige mais processamento. Se estamos programando computadores, essas questões muitas vezes são irrelevantes, pois em geral computadores possuem uma boa capacidade de memória e processamento, mas quando começamos a pensar em programação de microcontroladores precisamos estar atento.

Para desenvolvermos soluções que não vão utilizar buffers, precisamos ter em mente exatamente o que o projeto vai exigir. Coisas como desenhar linhas, retângulos, círculos, etc. provavelmente terão que ser deixados de lado, já que elas exigem alterações de pixels individuais do display. Ou então utilizar soluções bem engenhosas, mas que tenham limites bem definidos.

No nosso exemplo, vamos criar uma aplicação que apenas envia texto para o display. Nada além de texto. Necessidade essa que com certeza vai atender muitos projetos.

Veja que após compilado o exemplo abaixo, usamos 1% apenas da memória disponível e tivemos o mesmo resultado do exemplo anterior com um código bem mais enxuto.



Código-fonte:

/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
|            |     |                  COMMAND BYTE                 |                             |
|            |     +-----------------------------------------------+                             |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |         DESCRIPTION         |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1)                                                                                    |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP         |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |No Operation                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set   |  0  |  0  |  0  |  1  |  0  |  0  |  PD |  V  |  H  |power down control;          |
|            |     |     |     |     |     |     |     |     |     |entry mode;                  |
|            |     |     |     |     |     |     |     |     |     |extended instruction set     | 
|            |     |     |     |     |     |     |     |     |     |control (H)|                 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data  |  1  |  D7 |  D6 |  D5 |  D4 |  D3 |  D2 |  D1 |  D0 |Write Data to display        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set                                                               |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control|  0  |  0  |  0  |  0  |  0  |  1  |  D  |  0  |  E  |Sets Display Configuration   |
|            |     |     |     |     |     |     |     |     |     |DE                           |
|            |     |     |     |     |     |     |     |     |     |00 - display blank           | 
|            |     |     |     |     |     |     |     |     |     |01 - all display segment on  |
|            |     |     |     |     |     |     |     |     |     |10 - normal mode             | 
|            |     |     |     |     |     |     |     |     |     |11 - inverse video mode      |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM|  0  |  0  |  1  |  0  |  0  |  0  |  y2 |  y1 |  y0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ Y ≤ 5                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM|  0  |  1  |  x6 |  x5 |  x4 |  x3 |  x2 |  x1 |  x0 |sets Y-address of RAM;       |
|            |     |     |     |     |     |     |     |     |     |0 ≤ x ≤ 83                  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1)  use extended instruction set                                                           |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  0  |  0  |  1  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control|  0  |  0  |  0  |  0  |  0  |  0  |  1  | Tc1 | Tc0 |set Temperature Coefficient  |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  0  |  0  |  0  |  0  |  1  |  X  |  X  |  X  |Do not use                   |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System |  0  |  0  |  0  |  0  |  1  |  0  | BS2 | BS1 | BS0 |set Bias System (BSx)        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd    |  0  |  1  |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register        |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/

//This table contains the hex values that represent pixels
//for a font that is 5 pixels wide and 8 pixels high
static const PROGMEM byte ASCII[][5] = {
  { 0x00, 0x00, 0x00, 0x00, 0x00 } // 20   (space)
 ,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 !
 ,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 "
 ,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 #
 ,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $
 ,{ 0x23, 0x13, 0x08, 0x64, 0x62 } // 25 %
 ,{ 0x36, 0x49, 0x55, 0x22, 0x50 } // 26 &
 ,{ 0x00, 0x05, 0x03, 0x00, 0x00 } // 27 '
 ,{ 0x00, 0x1c, 0x22, 0x41, 0x00 } // 28 (
 ,{ 0x00, 0x41, 0x22, 0x1c, 0x00 } // 29 )
 ,{ 0x14, 0x08, 0x3e, 0x08, 0x14 } // 2a *
 ,{ 0x08, 0x08, 0x3e, 0x08, 0x08 } // 2b +
 ,{ 0x00, 0x50, 0x30, 0x00, 0x00 } // 2c ,
 ,{ 0x08, 0x08, 0x08, 0x08, 0x08 } // 2d -
 ,{ 0x00, 0x60, 0x60, 0x00, 0x00 } // 2e .
 ,{ 0x20, 0x10, 0x08, 0x04, 0x02 } // 2f /
 ,{ 0x3e, 0x51, 0x49, 0x45, 0x3e } // 30 0
 ,{ 0x00, 0x42, 0x7f, 0x40, 0x00 } // 31 1
 ,{ 0x42, 0x61, 0x51, 0x49, 0x46 } // 32 2
 ,{ 0x21, 0x41, 0x45, 0x4b, 0x31 } // 33 3
 ,{ 0x18, 0x14, 0x12, 0x7f, 0x10 } // 34 4
 ,{ 0x27, 0x45, 0x45, 0x45, 0x39 } // 35 5
 ,{ 0x3c, 0x4a, 0x49, 0x49, 0x30 } // 36 6
 ,{ 0x01, 0x71, 0x09, 0x05, 0x03 } // 37 7
 ,{ 0x36, 0x49, 0x49, 0x49, 0x36 } // 38 8
 ,{ 0x06, 0x49, 0x49, 0x29, 0x1e } // 39 9
 ,{ 0x00, 0x36, 0x36, 0x00, 0x00 } // 3a :
 ,{ 0x00, 0x56, 0x36, 0x00, 0x00 } // 3b ;
 ,{ 0x08, 0x14, 0x22, 0x41, 0x00 } // 3c <
 ,{ 0x14, 0x14, 0x14, 0x14, 0x14 } // 3d =
 ,{ 0x00, 0x41, 0x22, 0x14, 0x08 } // 3e >
 ,{ 0x02, 0x01, 0x51, 0x09, 0x06 } // 3f ?
 ,{ 0x32, 0x49, 0x79, 0x41, 0x3e } // 40 @
 ,{ 0x7e, 0x11, 0x11, 0x11, 0x7e } // 41 A
 ,{ 0x7f, 0x49, 0x49, 0x49, 0x36 } // 42 B
 ,{ 0x3e, 0x41, 0x41, 0x41, 0x22 } // 43 C
 ,{ 0x7f, 0x41, 0x41, 0x22, 0x1c } // 44 D
 ,{ 0x7f, 0x49, 0x49, 0x49, 0x41 } // 45 E
 ,{ 0x7f, 0x09, 0x09, 0x09, 0x01 } // 46 F
 ,{ 0x3e, 0x41, 0x49, 0x49, 0x7a } // 47 G
 ,{ 0x7f, 0x08, 0x08, 0x08, 0x7f } // 48 H
 ,{ 0x00, 0x41, 0x7f, 0x41, 0x00 } // 49 I
 ,{ 0x20, 0x40, 0x41, 0x3f, 0x01 } // 4a J
 ,{ 0x7f, 0x08, 0x14, 0x22, 0x41 } // 4b K
 ,{ 0x7f, 0x40, 0x40, 0x40, 0x40 } // 4c L
 ,{ 0x7f, 0x02, 0x0c, 0x02, 0x7f } // 4d M
 ,{ 0x7f, 0x04, 0x08, 0x10, 0x7f } // 4e N
 ,{ 0x3e, 0x41, 0x41, 0x41, 0x3e } // 4f O
 ,{ 0x7f, 0x09, 0x09, 0x09, 0x06 } // 50 P
 ,{ 0x3e, 0x41, 0x51, 0x21, 0x5e } // 51 Q
 ,{ 0x7f, 0x09, 0x19, 0x29, 0x46 } // 52 R
 ,{ 0x46, 0x49, 0x49, 0x49, 0x31 } // 53 S
 ,{ 0x01, 0x01, 0x7f, 0x01, 0x01 } // 54 T
 ,{ 0x3f, 0x40, 0x40, 0x40, 0x3f } // 55 U
 ,{ 0x1f, 0x20, 0x40, 0x20, 0x1f } // 56 V
 ,{ 0x3f, 0x40, 0x38, 0x40, 0x3f } // 57 W
 ,{ 0x63, 0x14, 0x08, 0x14, 0x63 } // 58 X
 ,{ 0x07, 0x08, 0x70, 0x08, 0x07 } // 59 Y
 ,{ 0x61, 0x51, 0x49, 0x45, 0x43 } // 5a Z
 ,{ 0x00, 0x7f, 0x41, 0x41, 0x00 } // 5b [
 ,{ 0x02, 0x04, 0x08, 0x10, 0x20 } // 5c backslash
 ,{ 0x00, 0x41, 0x41, 0x7f, 0x00 } // 5d ]
 ,{ 0x04, 0x02, 0x01, 0x02, 0x04 } // 5e ^
 ,{ 0x40, 0x40, 0x40, 0x40, 0x40 } // 5f _
 ,{ 0x00, 0x01, 0x02, 0x04, 0x00 } // 60 `
 ,{ 0x20, 0x54, 0x54, 0x54, 0x78 } // 61 a
 ,{ 0x7f, 0x48, 0x44, 0x44, 0x38 } // 62 b
 ,{ 0x38, 0x44, 0x44, 0x44, 0x20 } // 63 c
 ,{ 0x38, 0x44, 0x44, 0x48, 0x7f } // 64 d
 ,{ 0x38, 0x54, 0x54, 0x54, 0x18 } // 65 e
 ,{ 0x08, 0x7e, 0x09, 0x01, 0x02 } // 66 f
 ,{ 0x0c, 0x52, 0x52, 0x52, 0x3e } // 67 g
 ,{ 0x7f, 0x08, 0x04, 0x04, 0x78 } // 68 h
 ,{ 0x00, 0x44, 0x7d, 0x40, 0x00 } // 69 i
 ,{ 0x20, 0x40, 0x44, 0x3d, 0x00 } // 6a j 
 ,{ 0x7f, 0x10, 0x28, 0x44, 0x00 } // 6b k
 ,{ 0x00, 0x41, 0x7f, 0x40, 0x00 } // 6c l
 ,{ 0x7c, 0x04, 0x18, 0x04, 0x78 } // 6d m
 ,{ 0x7c, 0x08, 0x04, 0x04, 0x78 } // 6e n
 ,{ 0x38, 0x44, 0x44, 0x44, 0x38 } // 6f o
 ,{ 0x7c, 0x14, 0x14, 0x14, 0x08 } // 70 p
 ,{ 0x08, 0x14, 0x14, 0x18, 0x7c } // 71 q
 ,{ 0x7c, 0x08, 0x04, 0x04, 0x08 } // 72 r
 ,{ 0x48, 0x54, 0x54, 0x54, 0x20 } // 73 s
 ,{ 0x04, 0x3f, 0x44, 0x40, 0x20 } // 74 t
 ,{ 0x3c, 0x40, 0x40, 0x20, 0x7c } // 75 u
 ,{ 0x1c, 0x20, 0x40, 0x20, 0x1c } // 76 v
 ,{ 0x3c, 0x40, 0x30, 0x40, 0x3c } // 77 w
 ,{ 0x44, 0x28, 0x10, 0x28, 0x44 } // 78 x
 ,{ 0x0c, 0x50, 0x50, 0x50, 0x3c } // 79 y
 ,{ 0x44, 0x64, 0x54, 0x4c, 0x44 } // 7a z
 ,{ 0x00, 0x08, 0x36, 0x41, 0x00 } // 7b {
 ,{ 0x00, 0x00, 0x7f, 0x00, 0x00 } // 7c |
 ,{ 0x00, 0x41, 0x36, 0x08, 0x00 } // 7d }
 ,{ 0x10, 0x08, 0x08, 0x10, 0x08 } // 7e ~
 ,{ 0x78, 0x46, 0x41, 0x46, 0x78 } // 7f DEL
};

#define LCD_NOKIA_DATA     HIGH
#define LCD_NOKIA_CMD      LOW
#define LCD_NOKIA_COLS     84
#define LCD_NOKIA_BANKS    6 

const int pin_sclk = 8;  // pin 8 - Serial clock out (SCLK)
const int pin_din  = 9;  // pin 9 - Serial data out (DIN)
const int pin_dc   = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs   = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst  = 12; // pin 12 - LCD reset (RST)

class Display {
  private:
    byte _pos_linha;       //bank
    byte _pos_coluna;
    
    void _write(byte mode, byte w){
      digitalWrite(pin_dc, mode);
      digitalWrite(pin_cs, LOW);
      shiftOut(pin_din, pin_sclk, MSBFIRST, w);
      digitalWrite(pin_cs, HIGH);
      
      if (mode == LCD_NOKIA_DATA) {
        if (++_pos_coluna >= LCD_NOKIA_COLS) { 
          _pos_coluna = 0;  
          if ( ++_pos_linha>= 6) {  _pos_linha  = 0;  }
        }
      }
    }
    void _position(int l, int c) {  
      if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
      if (_pos_linha  != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha  = l;}
    }  
  public:
    Display() {
      pinMode(pin_din, OUTPUT);
      pinMode(pin_sclk, OUTPUT);
      pinMode(pin_dc, OUTPUT);
      pinMode(pin_rst, OUTPUT);
      pinMode(pin_cs, OUTPUT);
      
      digitalWrite(pin_rst, LOW); 
      digitalWrite(pin_rst, HIGH);
      
      _write(LCD_NOKIA_CMD, B00100001 );  // LCD Extended Commands.  00100001 + ENDERECAMENTO HORIZONTAL
      _write(LCD_NOKIA_CMD, B10101111 );  // Set LCD Vop (Contraste) 10101111 
      _write(LCD_NOKIA_CMD, B00000100 );  // Set Temp coefficent     00000100
      _write(LCD_NOKIA_CMD, B00010100 );  // LCD bias mode 1:48      00010100
      _write(LCD_NOKIA_CMD, B00100000 );  // LCD Basic Commands.     00100000
      _write(LCD_NOKIA_CMD, B00001100 );  // LCD no modo normal      00001100
      _write(LCD_NOKIA_CMD, B01000000);   // POSICAO-X 0 
      _write(LCD_NOKIA_CMD, B10000000);   // POSICAO-Y 0

      clear();
    }
    
    void write_pos(byte bank, byte col){
      if (col + 5 >= LCD_NOKIA_COLS) { 
        col -= (LCD_NOKIA_COLS - 6); 
        bank++ ;
        if (bank >= LCD_NOKIA_BANKS ){ bank=0; }
      }
      _position(bank, col);
    }
    
    void write(byte data) {   // Non-ASCII characters are not supported.
     if (data < 0x20 || data > 0x7F) return;
        byte b[5];
 memcpy_P(b, ASCII[data - 0x20], 5);
        for (int i=0;i<5;i++){  _write(LCD_NOKIA_DATA, b[i]);  }
        _write(LCD_NOKIA_DATA, B00000000); 
        
        write_pos(_pos_linha, _pos_coluna);  //posiciona no lugar correto para o próximo caracter
    }
    
    void write(char * c){
      int i = 0;
      while ( c[i] != '\0') { write(c[i++]); }
    }

    void clear(){
      _position( 0, 0 );
      for (int i=0; i<LCD_NOKIA_COLS * LCD_NOKIA_BANKS; i++) {  _write(LCD_NOKIA_DATA, B00000000); }
    }
};


Display d;  //declaração do objeto display

void setup() {
  randomSeed(analogRead(A0));
}

void loop() {
  for (int i=0; i<LCD_NOKIA_COLS-6; i+=5){
    d.clear();
    d.write_pos(random(LCD_NOKIA_BANKS),i);
    d.write("Arduino");
    delay(600);  
  }
}

Continua....


É bem provável que esse artigo venha a ter algumas atualizações, já que tenho mais ideias em mente sobre esse display, então recomendo voltar aqui em breve.

quarta-feira, 30 de dezembro de 2015

Arduino - Como gravar o bootloader (e sketch) em um Atmega328P-PU Standalone

Nesse artigo vou tentar descrever os passos que segui até conseguir carregar o bootloader em um Atmega328p-PU. 

Já fazia um tempo que eu havia comprado alguns kits para Arduino Standalone, como esse mostrado na imagem abaixo, mas que por preguiça acabaram ficando guardados. Hoje então resolvi fazer alguns testes com um e gravar o bootloader nele. Ele possui alguns componentes mínimos que geralmente são usados em placas Standalones, como o regulador de tensão, capacitores, o cristal oscilador, o Atmega328P e um slote onde CI será encaixado na placa.



O Procedimento não é algo tão complicado, mas podemos dizer que é um pouco chato de se fazer, pelo menos foi a impressão que tive ao tentar e vendo vários comentários na internet de pessoas que tiveram as mesmas dificuldades. Por algum motivo não consegui logo de começo, mesmo seguindo os procedimentos descritos em alguns artigos por ai. Mas depois de insistir um pouco consegui sem maiores problemas, por isso resolvi escrever mostrando os passos que segui.

Quando trabalhamos com o Arduino, os pinos do Atmega não ficam explicitos, então a imagem abaixo é bom guia pra nos orientarmos qual pino do Atmega é o seu correspondente do Arduino.


Mas antes de iniciarmos a ligação entre o Arduino e o Atmega, vamos deixar o Arduino Uno configurado para ser um programador ISP. Para isso, abra o arquivo que já está na própria IDE do Arduino, através do menu Arquivo --> exemplos --> ArduinoISP.




Com o arquivo Aberto, basta fazer o upload para o Arquivo Uno.

Com o Arduino Uno Configurado, iremos agora fazer a ligação entre os componentes, conforme a imagem abaixo. Nesse esquema que está abaixo, inicialmente eu não tinha utilizado aquele resistor de pullup de 10K para o pino de reset do Atmega. Mas depois de tentar algumas vezes e não conseguir, encontrei um artigo no qual o mesmo foi utilizado, então resolvi mante-lo, apesar de posteriormente eu ter testado sem o mesmo, e o upload foi feito do mesmo jeito. Mas aconselho manter o resistor. No Arduino Uno, também podemos notar um capacitor eletrolítico de 10uF. Também não tenho certeza se ele é exatamente necessário, mas por desencargo, mantive o mesmo conectado



Pinagem:


  ATmega328 | Arduino Uno 
19 | 13
18 | 12
17 | 11
1 (reset) | 10         
7, 20 |  5V  
  8, 22 |  Gnd  

Aqui podemos ver a montagem que eu fiz (ainda sem o resistor de 10K)



Com todos os componentes conectados, agora é hora de configurarmos a IDE do Arduino para fazermos o upload do bootloader. Pra isso iremos configurar a IDE como se estivéssemos programando um "Arduino Duemilanove or Diecimila", indo em Ferramentas --> Placa --> Arduino Duemilanove or Diecimila, como mostrado na imagem abaixo.




O Próximo passo é informar que iremos utilizar o ATmega328. Pra isso vamos em ferramentas --> Processador --> ATmega328.



O próximo passo agora é configurar o Programador para "Arduino as ISP".



Agora, com todos os passos anteriores configurados, é só gravar o booteloder. Pra isso vá em Ferramentas --> Gravar Bootloader. Como mostrado na imagem abaixo. Aguarde terminar o procedimento. Se tudo ocorreu certo, o pino 13 irá piscar, caso tenha um led conectado a ele. Apesar que nas imagens não aparece o led ligado, é interessante para fins de testes ligar um resistor e um led em série no pino 19 do Atmega (Conhecido como pino 13 no Arduino).



Com o bootloader gravado, é hora de testar o upload de um sketch qualquer. Pra isso abra o sketch que você deseja gravar no Atmega. faça as ligações dos componentes que serão necessários e então faça o upload da sketch, através da Opção "Carregar usando o programador" como mostrado na imagem abaixo.

Pra fugir um pouco do blink padrão do Arduino, fiz um blink "mais legal", que não utiliza o delay. e sim uma classe temporizadora desenvolvida por mim, que ainda não falei sobre ela, mas em breve farei um artigo explicando como ela funciona. O Código está mais abaixo.





/*********************************************************************************************************
************************************CLASSE MYTIMER********************************************************
**********************************************************************************************************/
struct MyTimerSequence{
  unsigned long * times;
  unsigned int size;
  unsigned int repeat;
};
class MyTimer{
  private:
    boolean          _enable;
    unsigned long    _mref;
    unsigned long    _mref_disable;
    unsigned long    _time_disable;
    MyTimerSequence *_sequences;
    long             _lag;
    int              _quantidade;
    int              _index_time;
    int              _last_index_time;
    int              _index_sequence;
    int              _last_index_sequence;
    void             (*_onChanging)( int index_sequence, int index_time ); //ponteiro para funcao do evento onChanging
    void             (*_onEnable)();
    void             (*_onDisable)();
  public:
    MyTimer(MyTimerSequence * sequences, int quantidade, long lag, unsigned long mref){
      _lag = lag;
      _mref = mref;
      _sequences = sequences;
      _quantidade = quantidade;
      _time_disable = 0;
      _enable = false;
      _undetermine();
    }
     
    void _undetermine(){
      _index_time          = -1;
      _last_index_time     = -1;
      _index_sequence      = -1;
      _last_index_sequence = -1;
      update();
    }
    void setSequences(MyTimerSequence * sequences, int quantidade){
      _sequences = sequences;
      _quantidade = quantidade;
      _undetermine();
      update();
    };
    void setMillisRef(unsigned long mref)                  { _mref    = mref;        _undetermine();    }
    void setLag(long lag)                                  { _lag     = lag;         _undetermine();    }
    boolean isChanging()                                   { return (( _index_time != _last_index_time)||( _index_sequence != _last_index_sequence))&&(_enable); }
    boolean isIndexTime(int index_sequence, int index_time){ return ( _index_time == index_time)&&(_index_sequence == index_sequence)&&(_enable);   }
    void setOnChanging( void (*onChanging)(int, int) )     { _onChanging = onChanging;                  }
    void setOnEnable  ( void (*onEnable)() )               { _onEnable   = onEnable;                    }
    void setOnDisable ( void (*onDisable)() )              { _onDisable  = onDisable;                   }
    void update();
     
    void enable()  { _enable = true;  if ( _onEnable )  { (*_onEnable )(  ); }  } 
    void disable() { _enable = false; if ( _onDisable ) { (*_onDisable)(  ); }  } 
     
    void setTimeDisable(unsigned long time){ _time_disable = time; _mref_disable = millis(); } 
};
void MyTimer::update(){
  if ((millis() - _time_disable > _mref_disable) && (_time_disable > 0)) { disable(); }  //verifica se está configurado pra desabilitar por time
  if (!_enable) { return; }
   
  unsigned long s = 0;
  for (int i=0; i<_quantidade;i++){ 
    for (int j=0; j<_sequences[i].repeat; j++){
      for (int k=0; k<_sequences[i].size; k++){
        s += _sequences[i].times[k]; 
      }
    }
  }
  long adjustment      = _mref % s;
  long rest            = (millis() + s - adjustment - _lag) % s;
  _last_index_time     = _index_time;
  _last_index_sequence = _index_sequence;
   
   
  boolean ind_break = false;
  s = 0;
  for (int i=0; i<_quantidade;i++){ 
    for (int j=0; j<_sequences[i].repeat; j++){
      for (int k=0; k<_sequences[i].size; k++) {
        s += _sequences[i].times[k]; 
        if (rest < s) {  
          _index_time = k; 
          _index_sequence = i;
          ind_break = true;
          break; 
        } 
      }
      if (ind_break) { break; }
    }
    if (ind_break) { break; }
  }
  if ( isChanging() && _onChanging ) { (*_onChanging)(  _index_sequence, _index_time  ); }
}
/*********************************************************************************************************
************************************FIM CLASSE MYTIMER****************************************************
**********************************************************************************************************/
 
unsigned long seq01[] = {750};
unsigned long seq02[] = {60, 60};
unsigned long seq03[] = {200};
 
MyTimerSequence sequences[] = { 
                                 { seq01, sizeof(seq01)/sizeof(unsigned long), 1},
                                 { seq02, sizeof(seq02)/sizeof(unsigned long), 10},
                                 { seq03, sizeof(seq03)/sizeof(unsigned long), 1},
                                 { seq02, sizeof(seq02)/sizeof(unsigned long), 6},
                                 { seq03, sizeof(seq03)/sizeof(unsigned long), 1},
                                 { seq02, sizeof(seq02)/sizeof(unsigned long), 3}
                              } ;
 
  
MyTimer t1(sequences, sizeof(sequences)/sizeof(MyTimerSequence), 0, 0);  //tem um atraso de 100 milissegundos em relação a referencia 0
    
void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
   
  t1.setOnChanging( onChanging_t1 );
  t1.enable();
}
  
void loop() {  
  t1.update();
}
  
void onChanging_t1(int sequence, int index ) {
  digitalWrite(13, LOW); 
  if (index == 0 && (sequence == 1 || sequence == 3 || sequence == 5)){ digitalWrite(13, HIGH);  }
   
}


E como fazer o upload através do Arduino Mega (ou outras placas Arduino)?

O Mesmo procedimento mostrado anteriormente também pode ser feito através do Arduino Mega, ou ainda com outros Arduinos. O Procedimento é fazer o upload da sketch ArduinoISP (mostrado lá no início) para o Arduino Mega e seguir os demais passos mostrados anteriormente, apenas tomando o cuidado na hora de ligar os pinos, que são diferentes do uno:


    ATmega328 | Arduino Mega
            19 | 52 (SCK)
              18 | 50 (MISO)
              17 | 51 (MOSI)
  1 (reset) | 53 (RST)
7, 20 | 5V   
  8, 22 | Gnd   

Para outras Placas, certifique-se de utilizar os pinos corretos, pra isso terá que descobrir quais são os pinos ISP: SCK, MISO, MOSI e RST