terça-feira, novembro 20, 2012

Display de LEDs JY-LKM1638 - Parte 2

Na primeira parte vimos os recursos do CI TM1638. Vamos agora ver como ele é utilizado no display LKM1638 e como conectá-lo a um Arduino.


O display possui 8 dígitos de 7 segmentos (mais o ponto), 8 LEDs bi-colores e 8 botões. O que precisamos descobrir é como estes componentes estão ligados ao TM1638.



O display possui 2 conectores de 10 pinos, na própria placa estão indicados os sinais presentes. Além da alimentação, CLK e DIO, o conector da esquerda (para quem vê a placa de frente) possui 6 sinais de strobe (STB0 a STB5). O sinal STB0 é o sinal de strobe do TM1638 que está na placa. O conector da direita possui somente 5 sinais de strobe (STB1 a STB5) que são os sinais da entrada deslocados de uma posição do conector. Isto permite ligar em cadeia até 6 placas usando cabos do tipo 1:1 (isto é, que ligam o pino 1 ao pino 1, o 2 ao 2, etc).

Nos meus testes, eu liguei a alimentação do display aos pinos Gnd e +5 do Arduino e os sinais STB0, DIO e CLK aos sinais digitais 7, 8 e 9 (respectivamente). Você pode, é claro, ligar os sinais a outros sinais digitais, usei estes apenas porque são os usados nos exemplos da biblioteca TM-1638. Aliás, é desta biblioteca (de autoria do português Ricardo Batista), que extraí as rotinas básicas de comunicação com o TM-1638. Para simplificar o meu teste, converti a classe em rotinas e variáveis; aproveitei para colocar alguns comentários adicionais:
// Pinos usados na conexão do display
byte TM16XX_dataPin;      // DIO
byte TM16XX_clockPin;     // CLK
byte TM16XX_strobePin;    // STB

// Códigos dos comandos do TM1638
#define TM1638_CMD_WRDISPA 0x40  // Escrita no display, auto incremento
#define TM1638_CMD_WRDISPF 0x44  // Escrita no display, endereço fixo
#define TM1638_CMD_RDKEYS  0x42  // Leitura das teclas
#define TM1638_CMD_SETADDR 0xC0  // Programa endereço
#define TM1638_CMD_DISPON  0x88  // Liga display e ajusta intensidade

// Iniciação
void TM16XX_setup(byte dataPin, byte clockPin, byte strobePin)
{
  // salva a configuração
  TM16XX_dataPin = dataPin;
  TM16XX_clockPin = clockPin;
  TM16XX_strobePin = strobePin;
  
  // Configura os pinos
  pinMode(TM16XX_dataPin, OUTPUT);
  pinMode(TM16XX_clockPin, OUTPUT);
  pinMode(TM16XX_strobePin, OUTPUT);

  digitalWrite(TM16XX_strobePin, HIGH);
  digitalWrite(TM16XX_clockPin, HIGH);

  // Faz a configuração inicial
  TM16XX_sendCommand(TM1638_CMD_WRDISPA);
  TM16XX_sendCommand(TM1638_CMD_DISPON | 7);

  // Limpa a memória
  // Posiciona no endereço 0 e usa auto-incremento
  digitalWrite(TM16XX_strobePin, LOW);
  TM16XX_send(TM1638_CMD_SETADDR | 0);
  for (int i = 0; i < 16; i++) 
    TM16XX_send(0x00);
  digitalWrite(TM16XX_strobePin, HIGH);
}

// Lê as teclas
void TM16XX_readButtons(byte *keys)
{
  digitalWrite(TM16XX_strobePin, LOW);
  TM16XX_send(TM1638_CMD_RDKEYS);
  for (int i = 0; i < 4; i++) {
    *keys++ = TM16XX_receive();
  }
  digitalWrite(TM16XX_strobePin, HIGH);
}

// Escreve um dado em um endereço da memória
void TM16XX_sendData(byte address, byte data)
{
  TM16XX_sendCommand(TM1638_CMD_WRDISPF);
  digitalWrite(TM16XX_strobePin, LOW);
  TM16XX_send(TM1638_CMD_SETADDR | address);
  TM16XX_send(data);
  digitalWrite(TM16XX_strobePin, HIGH);
}

// Envia um comando
void TM16XX_sendCommand(byte cmd)
{
  digitalWrite(TM16XX_strobePin, LOW);
  TM16XX_send(cmd);
  digitalWrite(TM16XX_strobePin, HIGH);
}

// Envia um byte
void TM16XX_send(byte data)
{
  for (int i = 0; i < 8; i++) {
    digitalWrite(TM16XX_clockPin, LOW);
    digitalWrite(TM16XX_dataPin, data & 1 ? HIGH : LOW);
    data >>= 1;
    digitalWrite(TM16XX_clockPin, HIGH);
  }
}

// Recebe um byte
byte TM16XX_receive()
{
  byte temp = 0;

  // Vira DIO para dentro e liga o pull-up
  pinMode(TM16XX_dataPin, INPUT);
  digitalWrite(TM16XX_dataPin, HIGH);

  // Le os bits
  for (int i = 0; i < 8; i++) 
  {
    temp >>= 1;
    digitalWrite(TM16XX_clockPin, LOW);
    if (digitalRead(TM16XX_dataPin))
      temp |= 0x80;
    digitalWrite(TM16XX_clockPin, HIGH);
  }

  // Volta DIO para saida
  pinMode(TM16XX_dataPin, OUTPUT);
  digitalWrite(TM16XX_dataPin, LOW);

  return temp;
}
Estas rotinas implementam a comunicação serial com o TM1638. Embora alguns se refiram a esta comunicação como sendo SPI, ela não segue este padrão (que utiliza sinais separados para entrada e saída de dados, fazendo a comunicação simultânea nos dois sentidos). É uma comunicação a três fios, semelhante à usada no relógio/calendário DS1302. O mestre (o Arduino) fornece sempre o sinal CLK, que comanda a transferência de um bit. O sinal strobe sinaliza o início e fim das transferências de dados e comandos.

O programa principal recebe pela serial comandos para escrever na memória, alterar a intensidade e ler as chaves:
// Variáveis para tratamento dos comandos na serial
byte cmd[10];
int iCmd = 0;

// Tabela para mostrar valores em hexadecimal
char hexa[] = "0123456789ABCDEF";

// Iniciacao do Arduino
void setup ()
{
  Serial.begin (9600);
  TM16XX_setup (8, 9, 7);
}

// Execução
void loop ()
{
  if (Serial.available())
  {
    // Monta o comando
    int car = Serial.read();
    if (car == ';')
    {
      // Completou um comando
      cmd [iCmd] = 0;
      trataCmd();
      iCmd = 0;
    }
    else if (iCmd < (sizeof(cmd)-2))
    {
      if ((car >= 'a') && (car <= 'z'))
        car -= 0x20;  // converte para maiúscula
      cmd [iCmd++] = car;
    }
  }
}

// Trata comando
// R -> lê chaves
// Wadd -> escreve dd no endereço a (dado e endereços em hexa)
// In -> intensidade (n de 0 a 7)
void trataCmd (void)
{
  if (cmd[0] == 'R')
  {
    byte chaves[4];
    TM16XX_readButtons (chaves);
    Serial.print ("Chaves = 0x");
    for (int i = 0; i < 4; i++)
    {
      Serial.print(hexa[chaves[i] >> 4]);
      Serial.print(hexa[chaves[i] & 0xF]);
    }
    Serial.println();
  }
  else if ((cmd[0] == 'I') && (iCmd == 2))
  {
    TM16XX_sendCommand(TM1638_CMD_DISPON + (cmd[1] & 0x7));
  }
  else if ((cmd[0] == 'W') && (iCmd == 4))
  {
    byte ender, dado;
    ender = htoi (cmd[1]);
    dado =  (htoi (cmd[2]) << 4) + htoi (cmd[3]);
    TM16XX_sendData (ender, dado);
    Serial.print ("Mem[");
    Serial.print (ender, HEX);
    Serial.print ("] = 0x");
    Serial.print (dado, HEX);
    Serial.println ();
  }
  else
    Serial.println ("ERRO");
}

// Decodifica um dígito hexadecimal
int htoi (char car)
{
  if ((car >= '0') && (car <= '9'))
    return car - '0';
  if ((car >= 'A') && (car <= 'F'))
    return car - 'A' + 10;
  return 0;
}
Usando este programa (que está nos arquivos do blog em Explora_LKM1638.zip) fica fácil descobrir o mapeamento dos LEDs e chaves nos displays:
  • Os segmentos dos dígitos são controlados pelas posições pares das memória. Partindo da esquerda para a direita do display, as posições correspondentes são 0, 2, 4, ... 14.
  • Em cada posição par, o bit mais significativo (7) corresponde ao ponto decimal. Os bits 0 a 6 correspondem aos segmentos 'a' a 'g' (ver figura abaixo).
  • As posições ímpares da memória controlam os LEDs bicolores. Partindo da esquerda para a direita, os LEDs são controlados pelas posições 1, 3, 5, ... 15.
  • Lembrando, no TM1638 apenas os dois bits menos significativos estão disponíveis nas posições ímpares. O bit 0 acende o LED na cor vermelha vermelho e o bit 1 na cor verde. Se você ativar simultaneamente os dois bits o LED acenderá nas duas cores resultando em um laranja (muito parecido com o vermelho para o meu gosto).
  • As primeiras quatro chaves correspondem ao bit menos significativos (bit 0) dos quatro bytes de leitura das chaves. As quatro últimas chaves correspondem ao bit 3 dos mesmos byte.
A figura abaixo resume o mapeamento:


2 comentários:

Anônimo disse...

Seria possivel implementar um relógio com um display desses?
Sou novo na area do arduino e gostaria de fazer um relógio com meu display. Voce não teria o codigo para essa finalidade?
Obrigado

Daniel Quadros disse...

Não é difícil implementar um relógio, mas eu prefiro usar o JY-MEGA3208 que já tem embutido o microcontrolador. Uma limitação dos dois é que não tem um relógio de tempo real propriamente dito, o ideal seria ter um com uma bateria para manter a data e hora, como fiz em outro projeto.