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:
  1. // Pinos usados na conexão do display  
  2. byte TM16XX_dataPin;      // DIO  
  3. byte TM16XX_clockPin;     // CLK  
  4. byte TM16XX_strobePin;    // STB  
  5.   
  6. // Códigos dos comandos do TM1638  
  7. #define TM1638_CMD_WRDISPA 0x40  // Escrita no display, auto incremento  
  8. #define TM1638_CMD_WRDISPF 0x44  // Escrita no display, endereço fixo  
  9. #define TM1638_CMD_RDKEYS  0x42  // Leitura das teclas  
  10. #define TM1638_CMD_SETADDR 0xC0  // Programa endereço  
  11. #define TM1638_CMD_DISPON  0x88  // Liga display e ajusta intensidade  
  12.   
  13. // Iniciação  
  14. void TM16XX_setup(byte dataPin, byte clockPin, byte strobePin)  
  15. {  
  16.   // salva a configuração  
  17.   TM16XX_dataPin = dataPin;  
  18.   TM16XX_clockPin = clockPin;  
  19.   TM16XX_strobePin = strobePin;  
  20.     
  21.   // Configura os pinos  
  22.   pinMode(TM16XX_dataPin, OUTPUT);  
  23.   pinMode(TM16XX_clockPin, OUTPUT);  
  24.   pinMode(TM16XX_strobePin, OUTPUT);  
  25.   
  26.   digitalWrite(TM16XX_strobePin, HIGH);  
  27.   digitalWrite(TM16XX_clockPin, HIGH);  
  28.   
  29.   // Faz a configuração inicial  
  30.   TM16XX_sendCommand(TM1638_CMD_WRDISPA);  
  31.   TM16XX_sendCommand(TM1638_CMD_DISPON | 7);  
  32.   
  33.   // Limpa a memória  
  34.   // Posiciona no endereço 0 e usa auto-incremento  
  35.   digitalWrite(TM16XX_strobePin, LOW);  
  36.   TM16XX_send(TM1638_CMD_SETADDR | 0);  
  37.   for (int i = 0; i < 16; i++)   
  38.     TM16XX_send(0x00);  
  39.   digitalWrite(TM16XX_strobePin, HIGH);  
  40. }  
  41.   
  42. // Lê as teclas  
  43. void TM16XX_readButtons(byte *keys)  
  44. {  
  45.   digitalWrite(TM16XX_strobePin, LOW);  
  46.   TM16XX_send(TM1638_CMD_RDKEYS);  
  47.   for (int i = 0; i < 4; i++) {  
  48.     *keys++ = TM16XX_receive();  
  49.   }  
  50.   digitalWrite(TM16XX_strobePin, HIGH);  
  51. }  
  52.   
  53. // Escreve um dado em um endereço da memória  
  54. void TM16XX_sendData(byte address, byte data)  
  55. {  
  56.   TM16XX_sendCommand(TM1638_CMD_WRDISPF);  
  57.   digitalWrite(TM16XX_strobePin, LOW);  
  58.   TM16XX_send(TM1638_CMD_SETADDR | address);  
  59.   TM16XX_send(data);  
  60.   digitalWrite(TM16XX_strobePin, HIGH);  
  61. }  
  62.   
  63. // Envia um comando  
  64. void TM16XX_sendCommand(byte cmd)  
  65. {  
  66.   digitalWrite(TM16XX_strobePin, LOW);  
  67.   TM16XX_send(cmd);  
  68.   digitalWrite(TM16XX_strobePin, HIGH);  
  69. }  
  70.   
  71. // Envia um byte  
  72. void TM16XX_send(byte data)  
  73. {  
  74.   for (int i = 0; i < 8; i++) {  
  75.     digitalWrite(TM16XX_clockPin, LOW);  
  76.     digitalWrite(TM16XX_dataPin, data & 1 ? HIGH : LOW);  
  77.     data >>= 1;  
  78.     digitalWrite(TM16XX_clockPin, HIGH);  
  79.   }  
  80. }  
  81.   
  82. // Recebe um byte  
  83. byte TM16XX_receive()  
  84. {  
  85.   byte temp = 0;  
  86.   
  87.   // Vira DIO para dentro e liga o pull-up  
  88.   pinMode(TM16XX_dataPin, INPUT);  
  89.   digitalWrite(TM16XX_dataPin, HIGH);  
  90.   
  91.   // Le os bits  
  92.   for (int i = 0; i < 8; i++)   
  93.   {  
  94.     temp >>= 1;  
  95.     digitalWrite(TM16XX_clockPin, LOW);  
  96.     if (digitalRead(TM16XX_dataPin))  
  97.       temp |= 0x80;  
  98.     digitalWrite(TM16XX_clockPin, HIGH);  
  99.   }  
  100.   
  101.   // Volta DIO para saida  
  102.   pinMode(TM16XX_dataPin, OUTPUT);  
  103.   digitalWrite(TM16XX_dataPin, LOW);  
  104.   
  105.   return temp;  
  106. }  
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:
  1. // Variáveis para tratamento dos comandos na serial  
  2. byte cmd[10];  
  3. int iCmd = 0;  
  4.   
  5. // Tabela para mostrar valores em hexadecimal  
  6. char hexa[] = "0123456789ABCDEF";  
  7.   
  8. // Iniciacao do Arduino  
  9. void setup ()  
  10. {  
  11.   Serial.begin (9600);  
  12.   TM16XX_setup (8, 9, 7);  
  13. }  
  14.   
  15. // Execução  
  16. void loop ()  
  17. {  
  18.   if (Serial.available())  
  19.   {  
  20.     // Monta o comando  
  21.     int car = Serial.read();  
  22.     if (car == ';')  
  23.     {  
  24.       // Completou um comando  
  25.       cmd [iCmd] = 0;  
  26.       trataCmd();  
  27.       iCmd = 0;  
  28.     }  
  29.     else if (iCmd < (sizeof(cmd)-2))  
  30.     {  
  31.       if ((car >= 'a') && (car <= 'z'))  
  32.         car -= 0x20;  // converte para maiúscula  
  33.       cmd [iCmd++] = car;  
  34.     }  
  35.   }  
  36. }  
  37.   
  38. // Trata comando  
  39. // R -> lê chaves  
  40. // Wadd -> escreve dd no endereço a (dado e endereços em hexa)  
  41. // In -> intensidade (n de 0 a 7)  
  42. void trataCmd (void)  
  43. {  
  44.   if (cmd[0] == 'R')  
  45.   {  
  46.     byte chaves[4];  
  47.     TM16XX_readButtons (chaves);  
  48.     Serial.print ("Chaves = 0x");  
  49.     for (int i = 0; i < 4; i++)  
  50.     {  
  51.       Serial.print(hexa[chaves[i] >> 4]);  
  52.       Serial.print(hexa[chaves[i] & 0xF]);  
  53.     }  
  54.     Serial.println();  
  55.   }  
  56.   else if ((cmd[0] == 'I') && (iCmd == 2))  
  57.   {  
  58.     TM16XX_sendCommand(TM1638_CMD_DISPON + (cmd[1] & 0x7));  
  59.   }  
  60.   else if ((cmd[0] == 'W') && (iCmd == 4))  
  61.   {  
  62.     byte ender, dado;  
  63.     ender = htoi (cmd[1]);  
  64.     dado =  (htoi (cmd[2]) << 4) + htoi (cmd[3]);  
  65.     TM16XX_sendData (ender, dado);  
  66.     Serial.print ("Mem[");  
  67.     Serial.print (ender, HEX);  
  68.     Serial.print ("] = 0x");  
  69.     Serial.print (dado, HEX);  
  70.     Serial.println ();  
  71.   }  
  72.   else  
  73.     Serial.println ("ERRO");  
  74. }  
  75.   
  76. // Decodifica um dígito hexadecimal  
  77. int htoi (char car)  
  78. {  
  79.   if ((car >= '0') && (car <= '9'))  
  80.     return car - '0';  
  81.   if ((car >= 'A') && (car <= 'F'))  
  82.     return car - 'A' + 10;  
  83.   return 0;  
  84. }  
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.