terça-feira, junho 12, 2012

Escrevendo um Firmware Alternativo para o Display JY-MCU 3208 - Parte 1

Agora que já conhecemos o avr-gcc, podemos nos aventurar a escrever alguns softwares para o display JY-MCU 3208. Vamos começar acessando o controlador do display para investigar o mapeamento dos LEDs.


O primeiro passo é examinar o circuito e determinar onde estão conectados o controlador e as chaves. Para facilitar, gerei um arquivo .h com esta informação:
  1. #ifndef _JYMCU3208_H  
  2. #define _JYMCU3208_H  
  3.   
  4. // Clock da CPU (clock internio de 8MHz dividido por 8)  
  5. #define CLK_CPU 1000000 // 1.0 MHz  
  6.   
  7. // Conexões do controlador HT1632C  
  8. // Três sinais: CS, RD, WR e DT(DATA)  
  9. #define HT_CS_DDR   DDRB  
  10. #define HT_WR_DDR   DDRB  
  11. #define HT_DT_DDR   DDRB  
  12.   
  13. #define HT_CS_PORT  PORTB  
  14. #define HT_WR_PORT  PORTB  
  15. #define HT_DT_PORT  PORTB  
  16.   
  17. #define HT_CS_BIT   _BV(PB3)  
  18. #define HT_WR_BIT   _BV(PB4)  
  19. #define HT_DT_BIT   _BV(PB5)  
  20.   
  21. // Conexão das Teclas  
  22. #define TEC_DDR     DDRD  
  23. #define TEC_PIN     PIND  
  24. #define TEC_KEY1    _BV(PD7)  
  25. #define TEC_KEY2    _BV(PD6)  
  26. #define TEC_KEY3    _BV(PD5)  
  27. #endif  
A documentação do controlador do display, informa como enviar comandos e escrever na memória. Basicamente:
  • Em repouso os sinais CS e WR devem estar em nível 1
  • O sinal CS é colocado em zero para sinalizar o início da comunicação
  • Para cada bit enviado do microcontrolador para o controlador
    • O sinal WR é colocado em zero
    • O valor do bit é colocado no sinal DATA
    • O sinal WR é retornado ao nível 1
  • Ao final dos bits o sinal CS é retornado ao nível 1
No início de cada sequência de bits (logo após a descida de CS) devem ser enviados três bits que identificam a operação sendo feita: comando, escrito na memória ou leitura na memória (no caso do display JY-MCU 3208 o sinal RD está desconectado, o que impede a leitura).

Cada comando é composto por 9 bits: os primeiros 8 determinam o comando e o final é irrelevante. Logo após ligar o display é necessário enviar alguns comandos de configuração.

Na escrita na memória deve ser enviado o endereço inicial (7 bits) e os valores a escrever (cada posição da memória armazena 4 bits de dados).

O módulo abaixo implementa as operações básicas. Uma vez que não é possível ler a memória do controlador, ela é duplicada na memória do microcontrolador. Neste primeiro teste a escrita será feita sobre esta memória "shadow" que depois é copiada no controlador.
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <inttypes.h>  
  4. #include <avr/io.h>  
  5.   
  6. #include "jymcu3208.h"  
  7. #include "ht1632c.h"  
  8.   
  9. // Comandos do controlador HT1632C  
  10. // Os comandos possuem 8 bits e devem ser precedidos pelos 3 bits de ID e seguidos de   
  11. // um bit adicional (0 ou 1, tanto faz)  
  12. #define HT_ID_CMD     0b100  
  13. #define HT_STARTSYS   0b00000001   // start system oscillator  
  14. #define HT_STOPSYS    0b00000000   // stop sytem oscillator and LED duty  
  15. #define HT_SETCLOCK   0b00011000   // set clock to master with internal RC  
  16. #define HT_SETLAYOUT  0b00100000   // N-MOS open-drain output and 32 ROW * 8 COM  
  17. #define HT_LEDON      0b00000011   // start LEDs  
  18. #define HT_LEDOFF     0b00000010   // stop LEDs  
  19. #define HT_SETBRIGHT  0b10100000   // set brightness 0b1010xxxx  xxxx = brightness  
  20. #define HT_BLINKON    0b00001001   // blinking on  
  21. #define HT_BLINKOFF   0b00001000   // blinking off  
  22.   
  23. // Escrita de dado no controlador HT1632C  
  24. // Enviar 3 bits de ID, 7 bits do endereço inicial e os 4 bits de dados   
  25. // 101-aaaaaaa-dddd-dddd-dddd-dddd-dddd-...  
  26. #define HT_ID_WRITE   0b101  
  27.   
  28. // Copia da Ram do controlador  
  29. // Usa apenas os 4 bits menos significativos de cada byte  
  30. //   
  31. volatile uint8_t ht1632c_shadowram [(HT_COLS*HT_ROWS)/4];  
  32.   
  33. // Comandos de iniciacao do controlador  
  34. static uint8_t cmd_init[] =  
  35. {  
  36.     HT_STOPSYS, HT_SETLAYOUT, HT_SETCLOCK,  
  37.     HT_STARTSYS, HT_LEDON, HT_SETBRIGHT | 3,  
  38.     HT_BLINKOFF  
  39. };  
  40.   
  41. // Rotinas internas  
  42. static void ht1632c_send_commands (uint8_t *pCmds, int8_t nCmds);  
  43. static void ht1632c_send_command (uint8_t cmd);  
  44. static void ht1632c_send (uint8_t valor, int8_t nBits);  
  45.   
  46. // Inicia o controlador  
  47. void ht1632c_init ()  
  48. {  
  49.     // Inicia as direçoes dos pinos de conexão  
  50.     HT_CS_DDR |= HT_CS_BIT;  
  51.     HT_WR_DDR |= HT_WR_BIT;  
  52.     HT_DT_DDR |= HT_DT_BIT;  
  53.       
  54.     // Default para o CS e WR é alto (inativo)  
  55.     HT_CS_PORT |= HT_CS_BIT;  
  56.     HT_WR_PORT |= HT_WR_BIT;  
  57.     HT_DT_PORT |= HT_DT_BIT;  
  58.       
  59.     // Efetua a configuração  
  60.     ht1632c_send_commands (cmd_init, sizeof(cmd_init));  
  61. }  
  62.   
  63. // Atualiza a memoria do controlador com o conteudo  
  64. // da shadow mempory  
  65. void ht1632c_send_screen ()  
  66. {  
  67.     uint8_t addr;  
  68.       
  69.     HT_CS_PORT &= ~HT_CS_BIT;       // seleciona o controlador  
  70.     ht1632c_send (HT_ID_WRITE, 3);  
  71.     ht1632c_send (0, 7);            // endereço inicial  
  72.     for(addr = 0; addr < (HT_COLS*HT_ROWS)/4; addr++)  
  73.     {  
  74.         ht1632c_send (ht1632c_shadowram[addr], 4);  
  75.     }  
  76.     HT_CS_PORT |= HT_CS_BIT;        // libera o controlador  
  77. }  
  78.   
  79. // Envia uma série de comandos ao controlador  
  80. static void ht1632c_send_commands (uint8_t *pCmds, int8_t nCmds)  
  81. {  
  82.     int8_t i;  
  83.       
  84.     HT_CS_PORT &= ~HT_CS_BIT;       // seleciona o controlador  
  85.     ht1632c_send (HT_ID_CMD, 3);    // envia ID de comando  
  86.     for (i = 0; i < nCmds; i++)  
  87.     {  
  88.         ht1632c_send (pCmds[i], 8);  
  89.         ht1632c_send (0, 1);  
  90.     }  
  91.     HT_CS_PORT |= HT_CS_BIT;        // libera o controlador  
  92. }  
  93.   
  94. // Envia um comando ao controlador  
  95. static void ht1632c_send_command (uint8_t cmd)  
  96. {  
  97.     HT_CS_PORT &= ~HT_CS_BIT;       // seleciona o controlador  
  98.     ht1632c_send (HT_ID_CMD, 3);    // envia ID de comando  
  99.     ht1632c_send (cmd, 8);  
  100.     ht1632c_send (0, 1);  
  101.     HT_CS_PORT |= HT_CS_BIT;        // libera o controlador  
  102. }  
  103.   
  104. // Envia uma sequencia de bits ao controlador  
  105. static void ht1632c_send (uint8_t valor, int8_t nBits)  
  106. {  
  107.     int8_t i;  
  108.     uint8_t mask = 1 << (nBits-1);    // enviar mais significativo primeiro  
  109.       
  110.     for (i = nBits; i > 0; i--)  
  111.     {  
  112.         HT_WR_PORT &= ~HT_WR_BIT;  
  113.         if (valor & mask)  
  114.             HT_DT_PORT |= HT_DT_BIT;  
  115.         else  
  116.             HT_DT_PORT &= ~HT_DT_BIT;  
  117.         HT_WR_PORT |= HT_WR_BIT;  
  118.         mask = mask >> 1;  
  119.     }  
  120. }  
Para finalizar, vamos fazer um programa simples de teste, que vai acendendo os LEDs um a um, seguindo a ordem dos bits na memória. Aproveitamos que o display possui um cristal de 32KHz para implementar uma rotina de delay,
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <inttypes.h>  
  4. #include <avr/io.h>  
  5. #include <avr/interrupt.h>  
  6.   
  7. #include "jymcu3208.h"  
  8. #include "ht1632c.h"  
  9.   
  10. // Variaveis  
  11. static volatile uint8_t cnt_delay;  
  12.   
  13. // Rotinas  
  14. static void delay_seg (uint8_t seg);  
  15. static void tempo_init (void);  
  16.   
  17. // Programa principal  
  18. int main(void)  
  19. {  
  20.     uint8_t addr, bit;  
  21.       
  22.     ht1632c_init();             // inicia o controlador do display  
  23.     ht1632c_send_screen ();     // limpa o display  
  24.     tempo_init();               // inicia a contagem de tempo  
  25.       
  26.     // Acende um LED a cada segundo  
  27.     for (addr = 0; addr < (HT_COLS*HT_ROWS)/4; addr++)  
  28.     {  
  29.         for (bit = 0x01; bit < 0x10; bit = bit << 1)  
  30.         {  
  31.             ht1632c_shadowram [addr] |= bit;  
  32.             ht1632c_send_screen ();  
  33.             delay_seg (1);  
  34.         }  
  35.     }  
  36.       
  37.     // nada mais a fazer  
  38.     for (;;)  
  39.         ;  
  40. }  
  41.   
  42. // Aguarda um certo número de segundos (1 a 64)  
  43. static void delay_seg (uint8_t seg)  
  44. {  
  45.     cnt_delay = seg << 2;  
  46.     while (cnt_delay)  
  47.         ;  
  48. }  
  49.   
  50. // Inicia a contagem de tempo  
  51. static void tempo_init (void)  
  52. {  
  53.   ASSR |= (1<<AS2);     // timer2 async from external quartz  
  54.   TCCR2 = 0b00000011;   // normal,off,/32; 32768Hz/256/32 = 4 Hz  
  55.   TIMSK |= (1<<TOIE2);  // enable timer2 overflow int  
  56.   sei();                // enable interrupts  
  57. }  
  58.   
  59. // Interrupção do Timer2  
  60. ISR(TIMER2_OVF_vect)   
  61. {  
  62.   if (cnt_delay)  
  63.       cnt_delay--;  
  64. }  
No próximo post veremos como compilar este código e gravar na Flash do ATmega8. Veremos também o resultado, o que nos revela o mapeamento dos LEDs.

3 comentários:

bilson disse...
Este comentário foi removido pelo autor.
bilson disse...

Hola

Encontré este código que parece ser el original del JY-MCU 3608

https://code.google.com/p/buxiaoyang-project/source/browse/trunk/AVR/?r=23#AVR%2FLED3208V1

Logré cargarlo pero veo sólo caracteres chinos :-(

¿Me darían una mano tratando de adaptarlo para usar caracteres occidentales?

Saludos !

Daniel Quadros disse...

Não parece ser o software original, pois pega a data e hora de um DS1302 que não vem montado no display. Pretendo fazer um firmware de relógio para o display, mas ainda deve demorar algumas semanas.