terça-feira, janeiro 24, 2012

Display Gráfico Nokia 5110 - Parte 5

No post anterior nos usamos o display gráfico para apresentar texto. Vejamos agora como mostrar gráficos, continuando a usar o display conectado a um Arduino.


Dada a forma como os pontos da tela são mapeados na memória e o fato do controlador nos permitir apenas escrever bytes (não é possível ler ou alterar apenas parte de um byte), as coisas serão mais fáceis se alterarmos sempre regiões retangulares com coordenada Y e altura múltiplos de oito. Nestas alterações vamos substituir todos os pontos no retângulo, ignorando o que já estava desenhado nela.

Vejamos, por exemplo, uma rotina que desenha uma barra horizontal:
// Desenha uma barra horizontal de tamanho t a partir da posição x, y
// y precisa ser múltiplo de 8, x + c deve ser menor que 84
// a barra tem altura de 6 pontos e possui margens de 1 ponto em cima e em baixo
// c é a cor da barra
void LcdBarraHoriz (int x, int y, int t, int c)
{
  // posiciona no primeiro byte
  LcdWrite( LCD_CMD, 0x40 + (y >> 3));  
  LcdWrite( LCD_CMD, 0x80 + x);
  
  // escreve os bytes
  for (int i = 0; i < t; i++)
    LcdWrite(LCD_DAT, c ? 0x7E : 0);
}
Um outro exemplo é apresentar uma imagem. Neste caso uma imagem retangular é copiada da flash para o display.
// Desenha uma imagem de tamanho dx, dy
// a partir de x, y. y e dy devem ser múltiplos de 8
void LcdWriteImg (const byte *pImg, int x, int y, int dx, int dy)
{
  // convert de pontos para bytes
  y = y >> 3;
  dy = (dy + 7) >> 3;
  for (int i = 0; i < dy; i++)
  {
    // posiciona no primeiro byte do bloco
    LcdWrite( LCD_CMD, 0x40 + y + i);  
    LcdWrite( LCD_CMD, 0x80 + x);
    
    // escreve os bytes
    for (int j = 0; j < dx; j++)
      LcdWrite(LCD_DAT, *pImg++);
  } 
}
Vejamos um exemplo simples que gera a tela da foto acima. As barras são desenhadas conforme a leitura das entradas analógicas 0 e 1:
static const byte invasor1[] =
{ 
  0x70, 0x18, 0x7D, 0xB6, 0xBC, 0x3C, 
  0xBC, 0xB6, 0x7D, 0x18, 0x70 
};

static const byte invasor2[] =
{ 
  0x98, 0x5C, 0xB6, 0x5F,
  0x5F, 0xB6, 0x5C, 0x98 
};

void setup(void)
{
  LcdInitialise();
  LcdClear();
  LcdWriteString ("DQSoft", 0, 3);
  LcdWriteImg (invasor1, 30, 16, 11, 8);
  LcdWriteImg (invasor2, 51, 16, 8, 8);
  analogReference (DEFAULT);
}

void loop(void)
{
  int v;
  v = analogRead(0);
  LcdBarraHoriz(0, 40, LCD_DX, 0);
  LcdBarraHoriz(0, 40, (v * 21) >> 8, 1);
  v = analogRead(1);
  LcdBarraHoriz(0, 32, LCD_DX, 0);
  LcdBarraHoriz(0, 32, (v * 21) >> 8, 1);
  delay (300);  
}
Se quisermos trabalhar com valores arbitrários de Y e altura, mantendo o que já estiver na tela nas posições fora do retângulo a coisa se complica. O primeiro passo é manter na memória do microcontrolador uma cópia da memória do display. Isto requer 84*48/8 = 504 bytes. Dependendo do microcontrolador isto pode até não caber na Ram! Em seguida, todas as rotinas que escreverem diretamente no display devem atualizar esta cópia. Na maioria dos casos, as rotinas irão primeiro atualizar a cópia (para manter o que for preciso mantido da tela anterior) e depois os bytes afetados serão atualizados no display. Otimizar este processo, de forma a evitar atualizações redundantes, é a chave para obter velocidade e fluidez em animações.

Um outro complicador é que na hora de apresentar uma imagem a origem não estará necessariamente com o alinhamento correto dos bits nos bytes. Por exemplo, uma figura com a altura de 8 pontos pode afetar 1 ou 2 bytes dos display dependendo do Y. Isto requer o uso de deslocamentos antes da escrita e uma lógica adicional para tratar a agora exceção da imagem já estar alinhada.

Estes aperfeiçoamentos ficam por conta do leitor... O meu livro "PC Assembler: Gráficos e Sons" apresentava os algorítimos correspondentes. Na minha opinião, para projetos simples baseados em microcontroladores é preferível definir as telas de forma a ficar sempre nos casos mais simples.

Nenhum comentário: