quarta-feira, maio 06, 2009

Spoke-o-dometer - Parte 7

Continuando a nossa série, vamos ver se conseguimos mostrar alguns caracteres com o circuito. Neste ponto vou tentar mostrar apenas números.

O Gerador de Caracteres

Lembrando, o nosso circuito possui 7 LEDs que correspondem a uma coluna no nosso gerador de caracteres. Optei pelo formato 5x7 (5 colunas x 7 linhas) para a matriz de caracteres, que era bastante tradicional nos primeiros terminais de vídeos e assemelhados.

A figura abaixo mostra os desenhos que fiz para os dígitos:

No meu circuito, os LEDs correspondem (de cima para baixo) a P2.7, P2.6, P1.5, P1.4, P1.3, P1.2 e P1.1. Isto facilita guardar cada coluna em um byte, seguindo a mesma ordem de bits:
  1. static const byte gc[10][5] =  
  2. {  
  3. 0x7C, 0x82, 0x82, 0x82, 0x7C,       // 0  
  4. 0x02, 0x42, 0xFE, 0x02, 0x02,       // 1  
  5. 0x46, 0x8A, 0x92, 0xA2, 0x42,       // 2  
  6. 0x44, 0x82, 0x92, 0x92, 0x6C,       // 3  
  7. 0xF0, 0x10, 0x10, 0x10, 0xFE,       // 4  
  8. 0xF2, 0x92, 0x92, 0x92, 0x8C,       // 5  
  9. 0x6C, 0x92, 0x92, 0x92, 0x0C,       // 6  
  10. 0x80, 0x86, 0x98, 0xA0, 0xC0,       // 7  
  11. 0x6C, 0x92, 0x92, 0x92, 0x6C,       // 8  
  12. 0x60, 0x92, 0x92, 0x92, 0x6C        // 9  
  13. };  
Timing

Neste teste, resolvi aumentar o clock via DCO para 8MHz e usar uma interrupção periódica a cada 0,5 ms (o meu tick). A rotina de interrupção conta os ticks entre as detecções do imã (ou seja, mede o tempo de uma volta). Esta contagem é dividida em 360 frames (360 para ficar fácil pensar em graus). Cada coluna de caracter é apresentada por um frame; após cada caracter os LEDs ficam apagados por 3 frames, para dar uma separação entre os caracteres.

Os LEDs ficam também apagados nos 45 frames após o imã (para colocar a imagem numa posição fora do quadro) e ao final da mensagem.

Uma última complicação é que na posição em que eu montei os caracteres precisam ser escritos de "trás para frente" (da última coluna do último caracter para a primeira coluna do primeiro caracter). Por simplificação a "mensagem" é fixa e deverá já estar com os caracteres em ordem invertida (mais exatamente, a mensagem é "9876543210" o que aparece no programa como "0123456789".

O Código

O código nesta etapa ficou assim:
  1. /* 
  2.  Spoke-o-dometer 
  3.  Teste3 - Teste de Display 
  4.  
  5.  Daniel Quadros - abril, 2009 
  6. */  
  7.   
  8. #include <msp430.h>  
  9.   
  10. // Valor para contar 500us c/ clock de 12KHz  
  11. #define TEMPO_500uS 6   // 0,5ms * 12KHz  
  12.   
  13. // Limites para detectar passagem pelo imã  
  14. #define VREPOUSO    88      // 01011000  
  15. #define VPOS_1      120     // 01111000  
  16. #define VPOS_2      100     // 01111000  
  17. #define VNEG_1      56      // 00111000  
  18. #define VNEG_2      76      // 00111000  
  19.   
  20. #define FALSE       0  
  21. #define TRUE        1  
  22.   
  23. typedef unsigned char  byte;  
  24.   
  25. // Gerador de Caracter (versão numérica)  
  26. // Cada caracter corresponde a 5 bytes  
  27. // Cada byte corresponde a uma coluna  
  28. static const byte gc[10][5] =  
  29. {  
  30.  0x7C, 0x82, 0x82, 0x82, 0x7C,       // 0  
  31.  0x02, 0x42, 0xFE, 0x02, 0x02,       // 1  
  32.  0x46, 0x8A, 0x92, 0xA2, 0x42,       // 2  
  33.  0x44, 0x82, 0x92, 0x92, 0x6C,       // 3  
  34.  0xF0, 0x10, 0x10, 0x10, 0xFE,       // 4  
  35.  0xF2, 0x92, 0x92, 0x92, 0x8C,       // 5  
  36.  0x6C, 0x92, 0x92, 0x92, 0x0C,       // 6  
  37.  0x80, 0x86, 0x98, 0xA0, 0xC0,       // 7  
  38.  0x6C, 0x92, 0x92, 0x92, 0x6C,       // 8  
  39.  0x60, 0x92, 0x92, 0x92, 0x6C        // 9  
  40. };  
  41.   
  42. static byte bDetectou = FALSE;  // TRUE qdo detecta o imã  
  43.   
  44. static unsigned long tickVolta;  
  45. static unsigned long tickFrame = 0;  
  46. static unsigned long contFrame;  
  47. static byte col;  
  48. char *pMsg;  
  49.   
  50. #define NFRAMES 360  
  51. #define POS     45  
  52. #define SPACE   3  
  53.   
  54.   
  55.   
  56. // Programa Principal  
  57. void main (void)  
  58. {  
  59.  unsigned int cont = 0;  
  60.   
  61.  // Desliga Watchdog  
  62.  WDTCTL = WDTPW + WDTSSEL + WDTHOLD;  
  63.   
  64.  // Altera a configuração de clock para ativar o VLOCLK  
  65.  BCSCTL3 |= LFXT1S1;  
  66.   
  67.  // Programa entradas e saídas  
  68.  P1SEL = 0xC0;               // P1.0 a P1.5 -> LEDs  
  69.                              // P1.6 e P1.7 são A3+ e A3-  
  70.  P1DIR = 0x3F;  
  71.  P1OUT = 0;                  // Todos as saídas em zero  
  72.   
  73.  P2SEL = 0;                  // Todos os pinos como I/O  
  74.  P2DIR = 0xFF;               // Todos os pinos como saída  
  75.  P2OUT = 0;                  // Todos as saídas em zero  
  76.   
  77.  // Programa o ADC  
  78.  // MSP430F2013 -> SD16  
  79.  SD16CTL = SD16VMIDON + SD16REFON + SD16DIV_3 + SD16SSEL_1;  // 1.2V ref, SMCLK / 8  
  80.  SD16INCTL0 = SD16INCH_3;                        // PGA = 1x, Diff inputs A3- & A3+  
  81.  SD16CCTL0 =  SD16SNGL + SD16UNI + SD16IE;       // Single conversion, Unipolar, 256 SR, Int enable  
  82.  SD16CTL &= ~SD16VMIDON;                         // VMID off: used to settle ref cap  
  83.  SD16AE = SD16AE6 + SD16AE7;                     // P1.6 & P1.7: A3+/- SD16_A inputs  
  84.   
  85.  // Dá um tempinho para estabilizar a alimentação  
  86.  while (cont < 0xFF)  
  87.    cont++;  
  88.  cont = 0;  
  89.   
  90.  // Alimentação já deve estar estável, vamos ligar o DCO  
  91.  BCSCTL1 = CALBC1_8MHZ;  
  92.  DCOCTL  = CALDCO_8MHZ;  
  93.   
  94.  // Programar a interrupção de tempo real p/ cada 0,5ms  
  95.  TACCR0 = TEMPO_500uS;  
  96.  TACTL = TASSEL_1 + MC_1 + TAIE; // ACLK, up mode, interrupt  
  97.   
  98.  // O nosso programa principal vai ficar dormindo,  
  99.  // todo o tratamento será feito na interrupção  
  100.  _BIS_SR(LPM3_bits + GIE);     // Dorme tratando interrupção  
  101.   
  102.  // Nuca vai chegar aqui  
  103.  while (1)  
  104.    ;  
  105. }  
  106.   
  107. // Tratamento da interrupção do Timer A  
  108. #pragma vector=TIMERA1_VECTOR  
  109. __interrupt void Timer_A_TO(void)  
  110. {  
  111.  byte valor;  
  112.   
  113.  switch (TAIV)  
  114.  {  
  115.      case 10:        // overflow  
  116.          tickVolta++;  
  117.          if (tickFrame != 0)  
  118.          {  
  119.              if (contFrame == 0)  
  120.              {  
  121.                  if (col > 0)  
  122.                  {  
  123.                      col--;  
  124.                      valor = gc [*pMsg - '0'][col];  
  125.                      P1OUT = (P1OUT & 0xC1) | (valor & 0x3E);  
  126.                      P2OUT = (P2OUT & 0x3F) | (valor & 0xC0);  
  127.                      contFrame = tickFrame;  
  128.                  }  
  129.                  else  
  130.                  {  
  131.                      P1OUT = P1OUT & 0xC1;  
  132.                      P2OUT = P2OUT & 0x3F;  
  133.                      col = 5;  
  134.                      pMsg++;  
  135.                      if (*pMsg == 0)  
  136.                          tickFrame = 0;  
  137.                      else  
  138.                          contFrame = tickFrame * SPACE;  
  139.                  }  
  140.              }  
  141.              else  
  142.                  contFrame--;  
  143.          }  
  144.          SD16CTL |= SD16REFON;       // Liga a referência do SD16  
  145.          SD16CCTL0 |= SD16SC;        // Inicia uma conversão  
  146.          _BIC_SR_IRQ (SCG1+SCG0);    // Manter osciladores ligados  
  147.          break;  
  148.     
  149.      case 2:         // CCR1, não utilizado  
  150.          break;  
  151.  }  
  152. }  
  153.   
  154. // Tratamento da interrupção do SD16  
  155. #pragma vector = SD16_VECTOR  
  156. __interrupt void SD16ISR(void)  
  157. {  
  158.  unsigned int result;  
  159.   
  160.  SD16CTL &= ~SD16REFON;        // Desliga referência do SD16_A  
  161.  result = SD16MEM0 >> 8;       // Pega resultado (limpa IFG)  
  162.   
  163.  if (!bDetectou && ((result > VPOS_1) || (result < VNEG_1)))  
  164.  {  
  165.      bDetectou = TRUE;  
  166.      tickFrame = tickVolta / NFRAMES;  
  167.      pMsg = "0123456789";  
  168.      contFrame = tickFrame*(NFRAMES - POS - 10*(5+SPACE));  
  169.      tickVolta = 0;  
  170.      col = 5;  
  171.      P1OUT |= 1;  
  172.  }  
  173.  else  
  174.  {  
  175.      if (bDetectou && (result < VPOS_2) && (result > VNEG_2))  
  176.      {  
  177.          bDetectou = FALSE;  
  178.          P1OUT &= 0xFE;  
  179.      }  
  180.  }  
  181.   
  182.  _BIS_SR_IRQ (SCG1+SCG0);      // voltar para LPM3  
  183. }  


O Resultado

Ainda está longe do que eu espero chegar, mas o vídeo abaixo é uma bela "prova de conceito".

2 comentários:

Raphz disse...

Sou um voyeur do seu blog, Daniel. Mas esse post ficou demais. São posts que me deixam com raiva, pois não entendi lhufas da lógica do código :(

Formação em analise de sistemas é uma droga ahsehasehas.

Grande abraço e parabéns.

Daniel Quadros disse...

Rapho,

Acho que a culpa é minha, que não coloquei uma boa explicação do código. Aliás, percebi isto também quando o meu filho quis dar uma mexidas nele. Fica a promessa de colocar uma explicação decente em um post futuro!

[]