Publicidade:

quinta-feira, 28 de janeiro de 2016

Arduino - Dicas de Programação - Bit Fields

Quando se trata de programação de microcontroladores, precisamos estar sempre atento ao tamanho do data type a ser usado ao declarar uma variável, pois em geral contamos com poucos bytes a disposição para nossos programas.

Eu já fiz um vídeo (veja aqui) explicando como utilizar operações de bitwise, que se bem utilizadas nos garante uma boa economia de bytes. Mas nesse artigo quero falar sobre outro recurso que a liguagem C nos oferece: Os Bit Fields.

Bit fields são utilizados em conjunto com structs, que pra quem não sabe, são estruturas de dados que podem ser definidas pelo programador, para representar dados compostos, como por exemplo, dados sobre uma pessoa, que pode ter o nome, idade, etc.

Exemplo de struct:

struct Pessoa {
   char nome[10];
   int idade;
};

Além disso bit fields também podem ser utilizados em unions e classes.

Vamos a um exmeplo: É comum precisarmos utilizar flags em nossos programas, que são variáveis que irão receber possivelmente apenas dois valores, como 0 e 1, ou true e false, etc. Nesses casos é comum utilizarmos variáveis do tipo boolean, byte ou pior ainda int. As variáveis do tipo boolean e do tipo byte, possuem tamanho de 1 byte, já um int precisa de 2 bytes. Se a nossa intenção é armazenar valores que podem assumir no máximo dois valores, em teoria não precisaríamos de 1 byte completo (lembrando que um byte tem 8 bits), já que um único bit nesse caso seria suficiente para representar o nosso flag.

Infelizmente não temos como declarar variáveis do tipo bit, mas temos o recurso de Bit Fields que são muito fáceis de serem usados. Vamos a um exemplo:

struct Flags {
   byte flag_1 : 1;
};

Veja que adicionamos um sinal de ":" e após isso o número 1. Isso significa que estamos limitando o tamanho da variável flag_1 para que tenha apenas um bit, logo ela somente irá representar valores que podem ser 1 ou 0. Porém na prática, ainda não temos nenhuma economia de memória, pois apesar da variável flag_1 ter sido limitada no que pode representar, a memória alocada fisicamente ainda será de um byte por variável do tipo Flags.

Mas isso acontece, pois tivemos a necessidade de apenas um flag, se nós tivéssemos que utilizar dois, três ou mais, aí sim começaríamos a nos beneficiar dos bit fields. Então vamos incluir mais alguns flags nessa estrutura:

struct Flags {
   byte flag_1 : 1;
   byte flag_2 : 1;
   byte flag_3 : 1;
   byte flag_4 : 1;
   byte flag_5 : 1;
   byte flag_6 : 1;
   byte flag_7 : 1;
   byte flag_8 : 1;
};

Agora temos 8 flags e ainda assim o tamanho que uma variável do tipo Flags ocupará é de apenas 1 byte. Isso por que cada um dos bits fará parte de um único byte. Caso adicionássemos um "flag_9 : 1" aí um novo byte teria que ser alocado.

Apesar desse exemplo utilizar apenas 1 bit pra cada variável da struct, poderíamos ter outras quantidades de bits. Por exemplo, uma variável que tenha valores possíveis entre 0 e 7, poderia ser declarada da seguinte maneira:

struct MeuBitField {
   byte valor_1 : 3;  /*0..7 ou 000 ... 111*/
   byte valor_2 : 3;
   byte flag_1 : 1;
   byte flag_2 : 1;
};

Para provar que esse recurso realmente funciona, veja as imagens abaixo, onde na primeira compilação, sem o uso de bit fields tivemos o uso de 17 bytes. Enquanto que na segunda vez, já com o uso dos bit fields, tivemos o uso de apenas 10 bytes. E para provarmos que uma variável do tipo Flags irá consumir apenas 1 byte, compilamos também o código vazio, apenas com o loop e setup sem nenhum código. O que consumiu 9 bytes.







Orientação a Objetos

Uma desculpa que sempre vejo que alguns programadores dão para não usarem orientação a objetos em programas para microcontroladores é que objetos ocupam muita memória. Mas geralmente essas afirmações ficam apenas nisso, ninguém se dá ao trabalho de realmente tirar a prova ou demonstrar que objetos consomem mais memória que programação estrutural.

Um bom exemplo de que dá pra programar Orientado a objetos com código enxuto, é que classes também podem ter bit fields.

Veja:

class Teste {
  private:
    byte _x : 1;
    byte _y : 1;
  public:
    void setX(boolean x){ _x = x; }
    void setY(boolean y){ _y = y; }
    
    boolean getX(){ return _x; }
    boolean getY(){ return _y; }
};

Teste t;

void setup() {
  t.setX(true);
  t.setX(false);
}

void loop() { }

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.