sexta-feira, junho 17, 2011

Projeto 'Epoch' - Parte4 - Software (cont)

Na parte anterior vimos a interface com o módulo de relógio DS1302. Veremos hoje o resto do software.

Display

O tratamento do display não é muito diferente daquilo que já fiz em outros projetos. Um vetor de 16 bytes armazena o valor a apresentar no display, na forma de um dígito por byte. A rotina de tratamento da interrupção de timer faz dois tratamentos:
  • A cada segundo soma um ao valor a apresentar
  • A cada três interrupções seleciona um par de dígitos (sinais S2 a S0 do display) e coloca os valores correspondentes nos sinais Ax e Bx. Reparar que a ordem de seleção dos dígitos é diferente nas duas metades.
Eis o código correspondente:
  1. #define  DISP_S0 PIN_C0  
  2. #define  DISP_S1 PIN_C1  
  3. #define  DISP_S2 PIN_C2  
  4.   
  5. #define TAXA_DISP 3     // taxa de varredura do display  
  6. #define INTS_SEG  3906  // interrupções por segundo  
  7.   
  8. // valor a apresentar no display  
  9. int8 valor[16];  
  10.   
  11. //////////////////////////////////////////////  
  12. // Interrupcao de tempo real  
  13. // Ocorre a cada 512 uSeg  
  14. #int_RTCC  
  15. void RTCC_isr()  
  16. {  
  17.  static unsigned int16 cont_seg = INTS_SEG;  
  18.    static unsigned int8 cont_disp = TAXA_DISP;  
  19.    static unsigned int8 disp = 0;  
  20.   
  21.    // Atualização da contagem de tempo  
  22.  if (--cont_seg == 0)  
  23.  {  
  24.   int8 i=15;  
  25.   while ((i > 0) && (++valor[i] == 10))  
  26.    valor[i--] = 0;  
  27.   cont_seg = INTS_SEG;  
  28.  }  
  29.   
  30.    // Atualização do Display  
  31.    if (--cont_disp == 0)  
  32.    {  
  33.      // seleciona o digito atual  
  34.   if (disp & 1)  
  35.    output_high (DISP_S0);  
  36.   else  
  37.    output_low (DISP_S0);  
  38.   if (disp & 2)  
  39.    output_high (DISP_S1);  
  40.   else  
  41.    output_low (DISP_S1);  
  42.   if (disp & 4)  
  43.    output_high (DISP_S2);  
  44.   else  
  45.    output_low (DISP_S2);  
  46.    
  47.      // coloca os valores  
  48.   output_a ((valor[15-disp] << 4) | valor[disp]);  
  49.         
  50.      // passa para o dígito seguinte  
  51.      disp = (disp + 1) & 7;  
  52.       cont_disp = TAXA_DISP;  
  53.    }  
  54. }  

Comunicação Serial

A comunicação serial é feita usando as rotinas do compilador C da CSC. A operação de acerto do relógio é bem simples: envia-se um CR (enter) e o PIC responde com um prompt '>'. Em seguida deve ser enviada a data e hora no formato ddmmaahhmmss, finalizado por outro CR. Antes do CR final o valor pode ser editado através do backspace. O valor digitado é enviado ao relógio (sem consistências...).
  1. // Caracteres ASCII  
  2. #define BS 8  
  3. #define LF 10  
  4. #define CR 13  
  5. #define ESC 27  
  6.   
  7. // valor a programar no relogio (ddmmaahhmmss)  
  8. int8 horario[12];  
  9.   
  10. // data/hora para o relogio  
  11. byte relogio[8];  
  12.   
  13. // Trata comunicação com o PC  
  14. void TrataUart (void)  
  15. {  
  16.  char c;  
  17.  int i;  
  18.   
  19.  if (kbhit())  
  20.  {  
  21.   c = getc();  
  22.   if (c != CR)  
  23.    return;  
  24.   
  25.   output_high (LED);  
  26.   putchar (CR);  
  27.   putchar (LF);  
  28.   putchar ('>');  
  29.   i = c = 0;  
  30.   //ddmmaahhmmss  
  31.   while ((c != CR) || (i != 12))  
  32.   {  
  33.    c = getc();  
  34.    if ((c == ESC) || (c == 0))  
  35.     break;  
  36.    if ((c == BS) && (i > 0))  
  37.    {  
  38.     putchar(BS);  
  39.     putchar(' ');  
  40.     putchar(BS);  
  41.     i--;  
  42.    }  
  43.    else if ((i < 12) && (c >= '0') && (c <= '9'))  
  44.    {  
  45.     putchar(c);  
  46.     horario[i++] = c-'0';  
  47.    }  
  48.   }  
  49.   putchar (CR);  
  50.   putchar (LF);  
  51.   if (c != ESC)  
  52.   {  
  53.    relogio[0] = (horario[10] << 4) | horario[11];  
  54.    relogio[1] = (horario[8] << 4) | horario[9];  
  55.    relogio[2] = (horario[6] << 4) | horario[7];  
  56.    relogio[3] = (horario[0] << 4) | horario[1];  
  57.    relogio[4] = (horario[2] << 4) | horario[3];  
  58.    relogio[5] = 1;  
  59.    relogio[6] = (horario[4] << 4) | horario[5];  
  60.    relogio[7] = 0x80;  
  61.   
  62.    for (i = 0; i < 8; i++)  
  63.    {  
  64.     putchar (hexa[relogio[i] >> 4]);  
  65.     putchar (hexa[relogio[i] & 0x0F]);  
  66.     putchar (' ');  
  67.    }  
  68.    putchar (CR);  
  69.    putchar (LF);  
  70.   
  71.    DS1302_WriteClock (relogio);  
  72.    LoadClk ();  
  73.   }  
  74.   output_low (LED);  
  75.  }  
  76. }  


Iniciação do Relógio

Após um reset ou acerto do relógio, o valor a ser apresentado no display é preenchido com o valor correspondente à data e hora no relógio. Isto requer converter uma data e hora em uma contagem de segundos. Na biblioteca padrão do C existe a rotina mktime para isto, no meu caso precisei fazer "na unha":
  1. // Dias por mês      31 28 31  30  31  30  31  31  30  31  30  
  2. const int dias[] =  { 0,31,59,90,120,151,181,212,243,273,304,334 };  
  3.   
  4. // EPOCH0 - fundação do GHC em segundos após 1/1/2000 00:00:00  
  5. #define EPOCH0 351549263L  
  6.   
  7. // Coloca no display o valor do relógio não volátil  
  8. void LoadClk (void)  
  9. {  
  10.  byte i, aux;  
  11.  unsigned int32 time;  
  12.   
  13.  DS1302_ReadClock (relogio);  
  14.  if ((relogio[3] & 0x3F) == 0)  
  15.  {  
  16.   // não programado  
  17.      for (i = 0; i < 16; i++)  
  18.      {  
  19.          valor[i] = 0;  
  20.      }  
  21.   return;  
  22.  }  
  23.   
  24.    // Zera os bits não implementados no timer  
  25.    relogio[0] &= 0x7F;  
  26.    relogio[1] &= 0x7F;  
  27.    relogio[2] &= 0x3F;  
  28.    relogio[3] &= 0x3F;  
  29.    relogio[4] &= 0x1F;  
  30.   
  31.    // Converte valores BCD para binário  
  32.    for (i = 0; i < 7; i++)  
  33.    {  
  34.        relogio[i] = (relogio[i] >> 4) * 10 + (relogio[i] & 0x0F);  
  35.    }  
  36.   
  37.    // Determina o número de dias nos anos inteiros  
  38.    // Na faixa 2000 a 2099 os anos divisíveis por 4 são bissextos  
  39.    // Cada grupo de 4 anos tem 3*365+366 dias  
  40.    time = ((unsigned int32) (relogio[6] >> 2)) * (3L*365L+366L);  
  41.    aux = (relogio[6] & 3);  
  42.    if (aux > 0)  
  43.    {  
  44.        time += 366;  
  45.        if (aux > 1)  
  46.            time += 365;  
  47.        if (aux > 2)  
  48.            time += 365;  
  49.    }  
  50.   
  51.    // Soma os dias até a data atual  
  52.    time += dias[relogio[4]-1];  
  53.    if ((aux == 0) && (relogio[4] > 2))  
  54.        time++;      // ano bissexto  
  55.    time += relogio[3]-1;  
  56.   
  57.    // Soma as horas  
  58.    time = time*24UL + (unsigned int32) relogio[2];  
  59.   
  60.    // Soma os minutos  
  61.    time = time*60UL + (unsigned int32) relogio[1];  
  62.   
  63.    // Soma os segundos  
  64.    time = time*60UL + (unsigned int32) relogio[0];  
  65.   
  66.  // Contar a partir do EPOCH0  
  67.  time -= EPOCH0;  
  68.   
  69.  // coloca no valor do display  
  70.  disable_interrupts (INT_RTCC);  
  71.  for (i = 0; i < 16; i++)  
  72.  {  
  73.   valor[15-i] = time % 10L;  
  74.   time = time / 10L;  
  75.  }  
  76.  enable_interrupts (INT_RTCC);  
  77. }  
Iniciação e Programa Principal

Para completar o programa fica faltando somente o Programa Principal e a iniciação do Hardware:
  1. // Ponto de entrada e loop principal  
  2. void main (void)  
  3. {  
  4.  InitHw ();  
  5.  LoadClk ();  
  6.  enable_interrupts(GLOBAL);  
  7.  for (;;)  
  8.  {  
  9.   TrataUart();  
  10.  }  
  11. }  
  12.   
  13. // Iniciação do hardware  
  14. void InitHw (void)  
  15. {  
  16.  // Bota para correr  
  17.  setup_oscillator (OSC_8MHZ);  
  18.   
  19.  // Dispositivos não usados  
  20.    setup_comparator(NC_NC);  
  21.    setup_vref(FALSE);  
  22.    setup_adc(ADC_OFF);  
  23.   
  24.  // E/S  
  25.  set_tris_a (0x00); // RA0-RA7 output  
  26.  set_tris_b (0xFA); // RB0 e RB2 output  
  27.  set_tris_c (0xF0); // RC0-RC3 output  
  28.  output_low (LED);  
  29.   
  30.  // Timer  
  31.    setup_timer_0(RTCC_INTERNAL|RTCC_DIV_2);  
  32.    setup_timer_1(T1_DISABLED);  
  33.  enable_interrupts (INT_RTCC);  
  34.   
  35.  // Uart  
  36.  while (kbhit())  
  37.   getc();  
  38. }  

O conjunto completo de arquivos para gerar o software, usando o ambiente MPLAB e o compilador PCM da CCS estão em Epoch.zip no SkyDrive que você acessa pelo ícone na lateral do blog.

Nenhum comentário: