sexta-feira, fevereiro 27, 2015

Display 16x2 com Interface I2C Conectado ao Arduino

Neste post vamos ver como usar com o Arduino o display I2C que examinamos no post anterior.


Montagem

Do ponto de vista elétrico, a montagem é muito simples. Mas, na prática, existem alguns detalhes a considerar.

O primeiro detalhe é que os pinos do display tem um espaçamento de 0.05", o que é a metade do espaçamento padrão para integrados DIP. Portanto não dá para espetar em uma protoboard. Para o meu teste eu soldei alguns fios grosseiramente. Olhando o display de frente, o pino 1 é o primeiro da esquerda

Além dos pinos na parte inferior do display é preciso ligar o backlight, cujos terminais estão na margem direita. Olhando o display de frente, o terminal superior é o catodo (K) e o inferior é o anodo (A).

Operando a 5V, as ligações necessárias são:
  • Os pinos 1 e 4 devem ser ligados ao 5V do Arduino.
  • Os pinos 2 e 3 não são usados.
  • O pino 5 e o terminal K devem ser ligados ao GND do Arduino.
  • O pino 6 deve ser ligado ao pino SDA do Arduino. No Uno é o pino A4, em outros modelos muda (veja aqui).
  • O pino 7 deve ser ligado ao pino SCL do Arquino, que no Uno é o A5.
  • Liguei o pino 8 (Reset) ao pino 12 do Arduino. Se você está confiante que nada vai dar errado durante o uso, pode ligar através de um resistor de 10K ao 5V.
  • O terminal A deve ser ligado ao 5V através de um resistor. Usando os números do datasheet (tensão do LED do backlight de 3,5V e corrente de 30mA), o valor deveria ser 50 ohms. Eu usei 220 ohms e achei que ficou bom.
I2C

A interface I2C (ou TWI para não usar a marca registrada) é suportada pelo hardware do ATmega e pela biblioteca padrão Wire do Arduino (documentação aqui). Resumindo uma longa história, o I2C usa dois sinais, SCL (clock, gerado pelo mestre) e SDA (dados, bidirecional); sinalizações especiais (start e stop) indicam o início e o fim de uma comunicação. É uma interface do tipo barramento ("varal") onde múltiplos dispositivos podem ser ligados a um mestre (no nosso caso o Arduino). Cada dispositivo tem um endereço (no caso deste display, 0x3E). São necessários resistores de pull-up para os dois sinais, mas a biblioteca Wire ativa os resistores internos do ATmega.

O uso da biblioteca, no nosso caso, ser restringe a 4 funções:
  • Wire.begin: configura o hardware do ATmega
  • Wire.beginTransmission: determina o endereço do dispositivo com que vamos falar
  • Wire.write: coloca um byte na fila de transmissão
  • Wire.endTransmission: transmite os bytes enfileirados, enviando um start no início e um stop ao final
Programa de Teste

Este programa de teste se limita a mostrar uma mensagem e um relógio (tosco) no display. A sequência de iniciação foi copiada diretamente do datasheet; por ela dá para perceber que a interface I2C se conecta ao controlador de display no modo 8 bits. Uma vez que não é possível ler o sinal Busy (ocupado) do controlador, coloquei algumas pausas, grosseiramente baseadas nos tempos indicados no datasheet. O datasheet não explicita isto, mas o sinal de Reset é ativo baixo (isto é, precisa ficar em nível alto durante a operação normal).
// Teste de conexão do display WO1602G ao Arduino
// http://dqsoft.blogspot.com

#include <Wire.h>

const int pinRst = 12;
const int addrDisp = 0x3E;
const byte selReg = 0;
const byte selDado = 0x40;

byte seg, minuto, hora;

// Iniciação
void setup()
{
  // Pulsa pino de reset
  pinMode (pinRst, OUTPUT);
  digitalWrite (pinRst, LOW);
  delay (1);
  digitalWrite (pinRst, HIGH);
  delay (500);
  
  // Inicia I2C
  Wire.begin();
  
  // Configura o display
  DisplayInit();
  
  // Mostra mensagens
  DisplayWrite (0, 0, "DQSoft"); 
  DisplayWrite (1, 0, "00:00:00"); 
}

// Rotina chamada periodicamente
// Coloca um "relógio" na segunda linha
void loop ()
{
  if (++seg == 60)
  {
    seg = 0;
    if (++minuto == 60)
    {
      minuto = 0;
      if (++hora == 99)
        hora = 0;
      DisplayWriteDec (1, 0, hora);
    }
    DisplayWriteDec (1, 3, minuto);
  }
  DisplayWriteDec (1, 6, seg);
  delay (1000);   // aguarda um segundo
}

// Faz a iniciação do display
void DisplayInit ()
{
  DisplayOut (selReg, 0x38);
  DisplayOut (selReg, 0x39);
  DisplayOut (selReg, 0x14);
  DisplayOut (selReg, 0x79);
  DisplayOut (selReg, 0x50);
  DisplayOut (selReg, 0x6C);
  DisplayOut (selReg, 0x0C);
  DisplayOut (selReg, 0x01);
  delay (2);
}

// Mostra número decimal de 00 a 99
void DisplayWriteDec (byte l, byte c, byte num)
{
  char aux[3];
  
  aux[0] = 0x30 + num / 10;
  aux[1] = 0x30 + num % 10;
  aux[2] = 0;
  DisplayWrite (l, c, aux);
}

// Posiciona o cursor e mostra mensagem
void DisplayWrite (byte l, byte c, char *msg)
{
  byte addr = c;
  if (l == 1)
    addr += 0x40;
  DisplayOut (selReg, 0x80 + addr);
  while (*msg)
  {
    DisplayOut (selDado, *msg++);
  }
}


// Envia um byte de controle e um byte de dado
void DisplayOut (byte c, byte d)
{
  Wire.beginTransmission (addrDisp);
  Wire.write (c);
  Wire.write (d);
  Wire.endTransmission ();
  delayMicroseconds (30);
}
Concluindo, gostei muito deste display. Existem alguns desafios quanto à montagem mecânica, mas o resultado é muito legível e elegante (a foto no início não faz justiça ao display).

Nenhum comentário: