quinta-feira, fevereiro 02, 2012

Display Gráfico Nokia 5110 - Parte 7: PIC

O microcontrolador PIC tradicional (família 16Fxxx) apresenta várias dificuldades para o uso em conjunto com um display gráfico. Não estou me referindo ao hardware, mas à programação. Em primeiro lugar, estes PICs possuem capacidade baixa de memória, impedindo manter nela uma imagem da tela (como fizemos no Arduino na parte anterior).

Mais preocupante é a questão de armazenar na Flash a matriz de caracteres e outras imagens. A matriz que usamos no Arduino tem 96x5 bytes o que é uma quantidade respeitável para os PICs mais simples. Além disso, os modelos mais tradicionais assumem que a Flash conterá apenas código, não tendo instruções específicas para acessar dados armazenados nela. Vamos usar aqui um PIC mais moderno, o 16F882, e deixar por conta do compilador CSC C o trabalho sujo de armazenar e extrair os dados da Flash.

Hardware

O hardware é bastante simples, principalmente se alimentarmos o PIC com 3V:
Montagem com alimentação de 3V
Circuito para alimentação de 3V
 Se alimentarmos o PIC com 5V, são necessárias conversões de nível para conectar o display.:
Montagem com alimentação de 5V
Circuito para alimentação de 5V
 Nos dois casos estamos usando o oscilador interno do 16F882, o que dispensa o uso de um cristal ou ressonador.

Software

O software a seguir é uma adaptação direta do primeiro exemplo que vimos com o Arduino. As principais mudanças se devem às diferenças entre os compiladores e as bibliotecas. Como não temos a função shiftOut, os sinais de dados e clock são pulsados manualmente.
#include <16f882.h>

#device adc=8
#use delay(clock=8000000)
#fuses NOWDT, NOCPD, NOPROTECT
#fuses MCLR, INTRC_IO

#use fast_io(A)

// Conexões do display ao PIC
#define PIN_SCE   PIN_A0
#define PIN_RESET PIN_A1
#define PIN_DC    PIN_A2
#define PIN_SDIN  PIN_A3
#define PIN_SCLK  PIN_A4

// Um LED para debug
#define PIN_LED   PIN_A5

// Seleção de dado ou comando
#define LCD_CMD   0
#define LCD_DAT   1

// Tamanho da tela
#define LCD_DX    84
#define LCD_DY    48

static const byte ASCII[][5] =
{
 {0x00, 0x00, 0x00, 0x00, 0x00} // 20  
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
// etc
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ?
,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f ?
};

// Rotinas locais
void InitHw (void);
void LcdWrite (byte dc, byte data);
void LcdInitialise (void);
void LcdClear (void);
void LcdPos (byte lin, byte col);
void LcdWriteChar (char character);
void LcdWriteString(char *characters, byte lin, byte col);

// Ponto de entrada e loop principal
void main (void)
{
 InitHw ();
 LcdInitialise ();
 LcdClear ();
 LcdPos (2, 3);
 LcdWriteChar ('D');
 LcdWriteChar ('Q');
 LcdWriteChar ('S');
 LcdWriteChar ('o');
 LcdWriteChar ('f');
 LcdWriteChar ('t');
 output_high (PIN_LED);
 for (;;)
  ;
}

// Iniciação do hardware
void InitHw (void)
{
 // Bota para correr
 setup_oscillator (OSC_8MHZ);

 // Dispositivos não usados
    setup_comparator(NC_NC);
    setup_vref(FALSE);
    setup_adc(ADC_OFF);

 // E/S
 set_tris_a (0xC0); // RA0-RA5 output
}

// Envia um byte para o controlador do display
// dc:   LCD_CMD ou LCD_DAT
// data: byte a enviar
void LcdWrite(byte dc, byte data)
{
  byte i;

  if (dc == LCD_CMD)
 output_low (PIN_DC);
  else
 output_high (PIN_DC);
  output_low (PIN_SCE);
  delay_us(10);
  for (i = 0; i < 8; i++)
  {
 if (data & 0x80)
   output_high (PIN_SDIN);
    else
   output_low (PIN_SDIN);
    output_high (PIN_SCLK);
 delay_us(10);
    output_low (PIN_SCLK);
 delay_us(10);
 data = data << 1;
  }
  delay_us(10);
  output_high (PIN_SCE);
}

// Iniciação do display
void LcdInitialise(void)
{
   output_low  (PIN_SCLK);
   output_high (PIN_SCE);

  // executa um reset do controlador
  output_low (PIN_RESET);
  delay_us (10);
  output_high (PIN_RESET);
  
  // envia os comandos de iniciação
  LcdWrite( LCD_CMD, 0x21 ); // LCD Extended Commands.
  LcdWrite( LCD_CMD, 0xaf ); // Set LCD Vop (Contraste)
  LcdWrite( LCD_CMD, 0x04 ); // Set Temp coefficent
  LcdWrite( LCD_CMD, 0x14 ); // LCD bias mode 1:48
  LcdWrite( LCD_CMD, 0x20 ); // LCD Basic Commands.
  LcdWrite( LCD_CMD, 0x0c ); // LCD no modo normal  
}

// Limpa a tela
void LcdClear(void)
{
  int16 i;

  // posiciona ponteiro no inicio da memória
  LcdWrite( LCD_CMD, 0x40);
  LcdWrite( LCD_CMD, 0x80);
  
  // preenche a memória com zeros
  for (i = 0; i < ((LCD_DX * LCD_DY) / 8); i++)
  {
    LcdWrite(LCD_DAT, 0x00);
  }
}

// Posiciona em uma determina linha e coluna alfanuméricas
void LcdPos(byte lin, byte col)
{
  LcdWrite( LCD_CMD, 0x40 + lin);  
  LcdWrite( LCD_CMD, 0x80 + col*7);  
}

// Escreve um caracter na posição atual
void LcdWriteChar(char character)
{
  byte i;

  LcdWrite(LCD_DAT, 0x00);
  for (i = 0; i < 5; i++)
  {
    LcdWrite(LCD_DAT, ASCII[character - 0x20][i]);
  }
  LcdWrite(LCD_DAT, 0x00);
}

// Escreve um string a partir de uma certa
// linha e coluna
void LcdWriteString(char *characters, byte lin, byte col)
{
  LcdPos (lin, col);  
  while (*characters)
    LcdWriteChar(*characters++);
}

Como de costume, o projeto completo está nos arquivos do blog, no arquivo Nokia5110_PIC.zip.

4 comentários:

Nivaldo Junior disse...

Esses mesmos arquivos e bibliotecas funcionam com o 18F4550?

Daniel Quadros disse...

Nivaldo,

Não tenho experiência com o PIC18, mas acho que serão precisos ajustes, principalmente no InitHw(). Além disso, costuma ter uma diferença grande entre os compiladores C para PIC.

Renato Costa disse...

boa noite, no pic 16f877a é a mesma configuração?

Daniel Quadros disse...

Renato,

Em uma olhada rápida no datasheet do 16F877 reparei que ele não tem oscilador interno, portanto será necessário acrescentar componentes para gera o clock (por exemplo um cristal de 8MHz). Acho que o resto (inclusive o acesso ao gerador de caracteres na Flash) devem funcionar.