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:
#ifndef _JYMCU3208_H
#define _JYMCU3208_H

// Clock da CPU (clock internio de 8MHz dividido por 8)
#define CLK_CPU 1000000 // 1.0 MHz

// Conexões do controlador HT1632C
// Três sinais: CS, RD, WR e DT(DATA)
#define HT_CS_DDR   DDRB
#define HT_WR_DDR   DDRB
#define HT_DT_DDR   DDRB

#define HT_CS_PORT  PORTB
#define HT_WR_PORT  PORTB
#define HT_DT_PORT  PORTB

#define HT_CS_BIT   _BV(PB3)
#define HT_WR_BIT   _BV(PB4)
#define HT_DT_BIT   _BV(PB5)

// Conexão das Teclas
#define TEC_DDR     DDRD
#define TEC_PIN     PIND
#define TEC_KEY1    _BV(PD7)
#define TEC_KEY2    _BV(PD6)
#define TEC_KEY3    _BV(PD5)
#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.
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>

#include "jymcu3208.h"
#include "ht1632c.h"

// Comandos do controlador HT1632C
// Os comandos possuem 8 bits e devem ser precedidos pelos 3 bits de ID e seguidos de 
// um bit adicional (0 ou 1, tanto faz)
#define HT_ID_CMD     0b100
#define HT_STARTSYS   0b00000001   // start system oscillator
#define HT_STOPSYS    0b00000000   // stop sytem oscillator and LED duty
#define HT_SETCLOCK   0b00011000   // set clock to master with internal RC
#define HT_SETLAYOUT  0b00100000   // N-MOS open-drain output and 32 ROW * 8 COM
#define HT_LEDON      0b00000011   // start LEDs
#define HT_LEDOFF     0b00000010   // stop LEDs
#define HT_SETBRIGHT  0b10100000   // set brightness 0b1010xxxx  xxxx = brightness
#define HT_BLINKON    0b00001001   // blinking on
#define HT_BLINKOFF   0b00001000   // blinking off

// Escrita de dado no controlador HT1632C
// Enviar 3 bits de ID, 7 bits do endereço inicial e os 4 bits de dados 
// 101-aaaaaaa-dddd-dddd-dddd-dddd-dddd-...
#define HT_ID_WRITE   0b101

// Copia da Ram do controlador
// Usa apenas os 4 bits menos significativos de cada byte
// 
volatile uint8_t ht1632c_shadowram [(HT_COLS*HT_ROWS)/4];

// Comandos de iniciacao do controlador
static uint8_t cmd_init[] =
{
    HT_STOPSYS, HT_SETLAYOUT, HT_SETCLOCK,
    HT_STARTSYS, HT_LEDON, HT_SETBRIGHT | 3,
    HT_BLINKOFF
};

// Rotinas internas
static void ht1632c_send_commands (uint8_t *pCmds, int8_t nCmds);
static void ht1632c_send_command (uint8_t cmd);
static void ht1632c_send (uint8_t valor, int8_t nBits);

// Inicia o controlador
void ht1632c_init ()
{
    // Inicia as direçoes dos pinos de conexão
    HT_CS_DDR |= HT_CS_BIT;
    HT_WR_DDR |= HT_WR_BIT;
    HT_DT_DDR |= HT_DT_BIT;
    
    // Default para o CS e WR é alto (inativo)
    HT_CS_PORT |= HT_CS_BIT;
    HT_WR_PORT |= HT_WR_BIT;
    HT_DT_PORT |= HT_DT_BIT;
    
    // Efetua a configuração
    ht1632c_send_commands (cmd_init, sizeof(cmd_init));
}

// Atualiza a memoria do controlador com o conteudo
// da shadow mempory
void ht1632c_send_screen ()
{
    uint8_t addr;
    
    HT_CS_PORT &= ~HT_CS_BIT;       // seleciona o controlador
    ht1632c_send (HT_ID_WRITE, 3);
    ht1632c_send (0, 7);            // endereço inicial
    for(addr = 0; addr < (HT_COLS*HT_ROWS)/4; addr++)
    {
        ht1632c_send (ht1632c_shadowram[addr], 4);
    }
    HT_CS_PORT |= HT_CS_BIT;        // libera o controlador
}

// Envia uma série de comandos ao controlador
static void ht1632c_send_commands (uint8_t *pCmds, int8_t nCmds)
{
    int8_t i;
    
    HT_CS_PORT &= ~HT_CS_BIT;       // seleciona o controlador
    ht1632c_send (HT_ID_CMD, 3);    // envia ID de comando
    for (i = 0; i < nCmds; i++)
    {
        ht1632c_send (pCmds[i], 8);
        ht1632c_send (0, 1);
    }
    HT_CS_PORT |= HT_CS_BIT;        // libera o controlador
}

// Envia um comando ao controlador
static void ht1632c_send_command (uint8_t cmd)
{
    HT_CS_PORT &= ~HT_CS_BIT;       // seleciona o controlador
    ht1632c_send (HT_ID_CMD, 3);    // envia ID de comando
    ht1632c_send (cmd, 8);
    ht1632c_send (0, 1);
    HT_CS_PORT |= HT_CS_BIT;        // libera o controlador
}

// Envia uma sequencia de bits ao controlador
static void ht1632c_send (uint8_t valor, int8_t nBits)
{
    int8_t i;
    uint8_t mask = 1 << (nBits-1);    // enviar mais significativo primeiro
    
    for (i = nBits; i > 0; i--)
    {
        HT_WR_PORT &= ~HT_WR_BIT;
        if (valor & mask)
            HT_DT_PORT |= HT_DT_BIT;
        else
            HT_DT_PORT &= ~HT_DT_BIT;
        HT_WR_PORT |= HT_WR_BIT;
        mask = mask >> 1;
    }
}
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,
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#include "jymcu3208.h"
#include "ht1632c.h"

// Variaveis
static volatile uint8_t cnt_delay;

// Rotinas
static void delay_seg (uint8_t seg);
static void tempo_init (void);

// Programa principal
int main(void)
{
    uint8_t addr, bit;
    
    ht1632c_init();             // inicia o controlador do display
    ht1632c_send_screen ();     // limpa o display
    tempo_init();               // inicia a contagem de tempo
    
    // Acende um LED a cada segundo
    for (addr = 0; addr < (HT_COLS*HT_ROWS)/4; addr++)
    {
        for (bit = 0x01; bit < 0x10; bit = bit << 1)
        {
            ht1632c_shadowram [addr] |= bit;
            ht1632c_send_screen ();
            delay_seg (1);
        }
    }
    
    // nada mais a fazer
    for (;;)
        ;
}

// Aguarda um certo número de segundos (1 a 64)
static void delay_seg (uint8_t seg)
{
    cnt_delay = seg << 2;
    while (cnt_delay)
        ;
}

// Inicia a contagem de tempo
static void tempo_init (void)
{
  ASSR |= (1<<AS2);     // timer2 async from external quartz
  TCCR2 = 0b00000011;   // normal,off,/32; 32768Hz/256/32 = 4 Hz
  TIMSK |= (1<<TOIE2);  // enable timer2 overflow int
  sei();                // enable interrupts
}

// Interrupção do Timer2
ISR(TIMER2_OVF_vect) 
{
  if (cnt_delay)
      cnt_delay--;
}
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.