sábado, junho 13, 2009

Spoke-o-dometer - Parte 9

Este post e os dois seguintes concluem esta série sobre um velocímetro que vai montado diretamente no aro da bicicleta e apresenta mensagens se valendo da persistência da visão. Neste post vou mostrar o hardware e o código final; no post seguinte vou explicar a programação dos periféricos do microcontrolador e no último explicar a lógica usada para calcular e apresentar a velocidade.

O Hardware

O projeto de hardware resistiu inalterado desde o começo. Repetindo o circuito (clique para ampliar):

A foto abaixo mostra a montagem:


O Software

Segue abaixo a listagem completa do software, que vou explicar nos post seguintes.
  1. /* 
  2.    Spoke-o-dometer 
  3.    Teste 5 - Versão Completa 
  4.  
  5.    Daniel & Flávio Quadros - abril/junho, 2009 
  6. */  
  7.   
  8. #include <msp430.h>  
  9.   
  10. // Constantes lógicas  
  11. #define FALSE       0  
  12. #define TRUE        1  
  13.   
  14. // Tipo byte  
  15. typedef unsigned char  byte;  
  16.   
  17. // Circunferência aprox. da roda em milimetros  
  18. #define CIRCUNF 2090L  
  19.   
  20. // Valor para contar 100us c/ clock de 1MHz  
  21. #define TEMPO_100uS 99   // (100us * 1MHz) - 1  
  22.   
  23. // Gerador de Caracteres  
  24. // Cada caracter corresponde a 5 bytes  
  25. // Cada byte corresponde a uma coluna  
  26. static const byte gc[16][5] =  
  27. {  
  28.    0x7C, 0x82, 0x82, 0x82, 0x7C,       // 0  
  29.    0x02, 0x42, 0xFE, 0x02, 0x02,       // 1  
  30.    0x46, 0x8A, 0x92, 0xA2, 0x42,       // 2  
  31.    0x44, 0x82, 0x92, 0x92, 0x6C,       // 3  
  32.    0xF0, 0x10, 0x10, 0x10, 0xFE,       // 4  
  33.    0xF2, 0x92, 0x92, 0x92, 0x8C,       // 5  
  34.    0x6C, 0x92, 0x92, 0x92, 0x0C,       // 6  
  35.    0x80, 0x86, 0x98, 0xA0, 0xC0,       // 7  
  36.    0x6C, 0x92, 0x92, 0x92, 0x6C,       // 8  
  37.    0x60, 0x92, 0x92, 0x92, 0x6C,       // 9  
  38.    0x00, 0x00, 0x00, 0x00, 0x00,       // SPACE    (10)  
  39.    0x00, 0x04, 0x06, 0x00, 0x00,       // ,        (11)  
  40.    0x7E, 0x08, 0x14, 0x22, 0x00,       // k        (12)  
  41.    0x1E, 0x10, 0x0C, 0x10, 0x1E,       // m        (13)  
  42.    0x03, 0x0C, 0x18, 0x30, 0x60,       // /        (14)  
  43.    0x7E, 0x08, 0x08, 0x06, 0x00        // h        (15)  
  44. };  
  45.   
  46. #define CHAR_SPACE 10;  
  47. #define CHAR_COMMA 11;  
  48. #define CHAR_K     12;  
  49. #define CHAR_M     13;  
  50. #define CHAR_SLASH 14;  
  51. #define CHAR_H     15;  
  52.   
  53.   
  54. // Limites para detectar passagem pelo ímã  
  55. #define VREPOUSO    88      // 01011000  
  56. #define VPOS_1      120     // 01111000  
  57. #define VPOS_2      100     // 01111000  
  58. #define VNEG_1      56      // 00111000  
  59. #define VNEG_2      76      // 00111000  
  60.   
  61. static byte bAmostrando = FALSE;        // TRUE enquanto aguarda ADC  
  62. static byte bDetectou = FALSE;          // TRUE qdo detecta o imã  
  63.   
  64.   
  65. // Contagem do tempo de uma volta e  
  66. // controle da escrita da mensagem  
  67.   
  68. #define NFRAMES 360                     // frames por volta  
  69. #define POS_MSG 45                      // inicio da mensagem  
  70. #define SPACE   3                       // frames de espaço entre letras  
  71.   
  72. static unsigned long tickVolta;         // número de ticks em uma volta  
  73. static unsigned long tickVoltaAnt;      // número de ticks da volta anterior  
  74. static unsigned long tickFrame = 0;     // número de ticks por frame  
  75. static unsigned long contFrame;         // ticks até o próximo frame  
  76. static byte col;                        // coluna sendo apresentada  
  77. static char *pMsg;                      // caracter sendo apresentado  
  78. static char msg[16];                    // mensagem a apresentar  
  79.   
  80. // Rotinas locais  
  81. int escreve_kmh    (unsigned int vel, char *pv);  
  82. int char_index     (char c);  
  83.   
  84. // Programa Principal  
  85. void main (void)  
  86. {  
  87.    unsigned int cont = 0;  
  88.     
  89.    // Desliga Watchdog  
  90.    WDTCTL = WDTPW + WDTSSEL + WDTHOLD;  
  91.   
  92.    // Altera a configuração de clock para ativar o VLOCLK  
  93.    BCSCTL3 |= LFXT1S1;  
  94.     
  95.    // SMCLK = MCLK / 8  
  96.    BCSCTL2 = DIVS_3;  
  97.     
  98.    // Programa entradas e saídas  
  99.    P1SEL = 0xC0;               // P1.0 a P1.5 -> LEDs  
  100.                                // P1.6 e P1.7 são A3+ e A3-  
  101.  P1DIR = 0x3F;  
  102.  P1OUT = 0;                  // Todos as saídas em zero  
  103.       
  104.    P2SEL = 0;                  // Todos os pinos como I/O  
  105.  P2DIR = 0xFF;               // Todos os pinos como saída  
  106.  P2OUT = 0;                  // Todos as saídas em zero  
  107.     
  108.    // Programa o ADC  
  109.    // MSP430F2013 -> SD16  
  110.    SD16CTL = SD16VMIDON + SD16REFON + SD16DIV_0 + SD16SSEL_1;  // 1.2V ref, SMCLK  
  111.    SD16INCTL0 = SD16INCH_3;                        // PGA = 1x, Diff inputs A3- & A3+  
  112.    SD16CCTL0 =  SD16SNGL + SD16UNI + SD16IE;       // Single conversion, Unipolar, 256 SR, Int enable  
  113.    SD16CTL &= ~SD16VMIDON;                     // VMID off: used to settle ref cap  
  114.    SD16AE = SD16AE6 + SD16AE7;                     // P1.6 & P1.7: A3+/- SD16_A inputs  
  115.     
  116.    // Dá um tempinho para estabilizar a alimentação  
  117.    while (cont < 0xFF)  
  118.      cont++;  
  119.    cont = 0;  
  120.     
  121.    // Alimentação já deve estar estável, vamos ligar o DCO  
  122.  BCSCTL1 = CALBC1_8MHZ;  
  123.  DCOCTL  = CALDCO_8MHZ;  
  124.     
  125.    // Programar a interrupção de tempo real p/ cada 100us  
  126.    TACCR0 = TEMPO_100uS;  
  127.  TACTL = TASSEL_2 + MC_1 + TAIE; // SCLK, up mode, interrupt  
  128.   
  129.    // O nosso programa principal vai ficar dormindo,  
  130.    // todo o tratamento será feito na interrupção  
  131.    _BIS_SR(GIE);  
  132.    LPM0;             // Dorme tratando interrupção  
  133.     
  134.    // Nuca vai chegar aqui  
  135.    while (1)  
  136.      ;  
  137. }  
  138.   
  139. // Tratamento da interrupção do Timer A  
  140. #pragma vector=TIMERA1_VECTOR  
  141. __interrupt void Timer_A_TO(void)  
  142. {  
  143.    byte valor;  
  144.     
  145.    switch (TAIV)  
  146.    {  
  147.        case 10:        // overflow  
  148.            tickVolta++;                    // mais um tick na volta atual  
  149.            if (tickFrame != 0)  
  150.            {  
  151.                if (contFrame == 0)         // fim do frame atual  
  152.                {  
  153.                    if (col > 0)  
  154.                    {  
  155.                        // vamos para a coluna anterior  
  156.                        col--;  
  157.                        valor = gc [char_index(*pMsg)][col];  
  158.                        P1OUT &=  0xC1;  
  159.                        P1OUT |= (valor & 0x3E);  
  160.                        P2OUT &= 0x3F;  
  161.                        P2OUT |= (valor & 0xC0);  
  162.                        contFrame = tickFrame;  
  163.                    }  
  164.                    else  
  165.                    {  
  166.                        //  passar ao caracter seguinte  
  167.                        P1OUT = P1OUT & 0xC1;  
  168.                        P2OUT = P2OUT & 0x3F;  
  169.                        col = 5;  
  170.                        pMsg++;  
  171.                        if (*pMsg == 0)  
  172.                            tickFrame = 0;      // fim da mensagem  
  173.                        else  
  174.                            contFrame = tickFrame * SPACE;  
  175.                    }  
  176.                }  
  177.                else  
  178.                    contFrame--;  
  179.            }  
  180.            if (!bAmostrando)  
  181.            {  
  182.                SD16CTL |= SD16REFON;       // Liga a referência do SD16  
  183.                SD16CCTL0 |= SD16SC;        // Inicia uma conversão  
  184.                bAmostrando = TRUE;  
  185.            }  
  186.            break;  
  187.         
  188.        case 2:         // CCR1, não utilizado  
  189.            break;  
  190.    }  
  191. }  
  192.   
  193. // Tratamento da interrupção do SD16  
  194. #pragma vector = SD16_VECTOR  
  195. __interrupt void SD16ISR(void)  
  196. {  
  197.    unsigned int medida;  
  198.    unsigned int veloc;  
  199.    unsigned long delta;  
  200.    int tmsg;  
  201.     
  202.    SD16CTL &= ~SD16REFON;        // Desliga referência do SD16_A  
  203.    medida = SD16MEM0 >> 8;       // Pega medida (limpa IFG)  
  204.    bAmostrando = FALSE;          // Fim da amostragem  
  205.     
  206.    if (!bDetectou && ((medida > VPOS_1) || (medida < VNEG_1)))  
  207.    {  
  208.        // fim de uma volta  
  209.        bDetectou = TRUE;  
  210.         
  211.        // Calcula variação em relação à volta anterior  
  212.        if (tickVolta > tickVoltaAnt)  
  213.            delta = tickVolta - tickVoltaAnt;  
  214.        else  
  215.            delta = tickVoltaAnt - tickVolta;  
  216.         
  217.        // Calcula a velocidade em 0,1 Km/h  
  218.        // veloc = distância / tempo  
  219.        // CIRCUNF em mm, ticVolta em 100 us  
  220.        // veloc = 3600 * (CIRCUNF / 100000) / (ticVolta / 10000)  
  221.        veloc = (unsigned int) ((CIRCUNF*360L)/tickVolta);  
  222.         
  223.        // Só mostra velocidade se maior que 7.0 Km/h  
  224.        // e não variou mais que 25%  
  225.        if ((veloc > 70) && (delta < (tickVolta >> 2)))  
  226.            tmsg = escreve_kmh(veloc, msg);  
  227.        else  
  228.        {  
  229.            msg[0] = ' ';  
  230.            msg[1] = 0;  
  231.            tmsg = 1;  
  232.        }  
  233.        pMsg = &msg[0];  
  234.         
  235.        // Calcula o tempo por frame e a contagem para a posição inicial  
  236.        tickFrame = tickVolta / NFRAMES;  
  237.        contFrame = (tickVolta *(NFRAMES - POS_MSG - tmsg*(5+SPACE))) / NFRAMES;  
  238.        tickVoltaAnt = tickVolta;  
  239.        tickVolta = 0;  
  240.        col = 5;  
  241.        P1OUT |= 1;  
  242.    }  
  243.    else  
  244.    {  
  245.        if (bDetectou && (medida < VPOS_2) && (medida > VNEG_2))  
  246.        {  
  247.            // pode procurar imã de novo  
  248.            bDetectou = FALSE;  
  249.            P1OUT &= 0xFE;  
  250.        }  
  251.    }  
  252. }  
  253.   
  254. // Formata a velocidade para apresentação  
  255. // Mensagem é montada em ordem reversa  
  256. // "h/mk#,###"  
  257. int escreve_kmh (unsigned int vel, char *pv)  
  258. {  
  259.    int tam = 7;  
  260.     
  261.    *pv++ = 'h';  
  262.    *pv++ = '/';  
  263.    *pv++ = 'm';  
  264.    *pv++ = 'k';  
  265.    *pv++ = '0' + (vel % 10);  vel /= 10;  
  266.    *pv++ = ',';  
  267.    *pv++ = '0' + (vel % 10);  vel /= 10;  
  268.    if (vel != 0)  
  269.    {  
  270.        *pv++ = '0' + (vel % 10);  vel /= 10;  
  271.        tam++;  
  272.        if (vel != 0)  
  273.        {  
  274.            *pv++ = '0' + (vel % 10);  
  275.            tam++;  
  276.        }  
  277.    }  
  278.    *pv = 0;  
  279.    return tam;  
  280. }  
  281.   
  282. // c_index: retorna o índice do caractere na matriz gc  
  283. int char_index(char c)  
  284. {  
  285.    if ((c - '0') >= 0 && (c - '0') < 10)  
  286.        return (c - '0');  
  287.    if (c == ' 'return CHAR_SPACE;  
  288.    if (c == ','return CHAR_COMMA;  
  289.    if (c == 'k'return CHAR_K;  
  290.    if (c == 'm'return CHAR_M;  
  291.    if (c == '/'return CHAR_SLASH;  
  292.    if (c == 'h'return CHAR_H;  
  293.   
  294.    //else (caractere estranho)  
  295.    return CHAR_SLASH;  
  296. }  
O Resultado

O vídeo abaixo mostra o Spoke-o-dometer em funcionamento:

Nenhum comentário: