quarta-feira, junho 17, 2009

Sopke-o-dometer - Parte 11

Neste final da série vamos ver a lógica da aplicação em si. Temos três grandes partes: medir o tempo de uma volta da roda, calcular a velocidade correspondente e apresentar nos LEDs este resultado.

Medindo o Tempo de Uma Volta

A medição em si é simples: um contador (tickVolta) é zerado a cada começo de volta e incrementado pela interrupção periódica do timer. A parte mais complicada está em detectar o começo da volta, o que é feito pelo sensor de efeito Hall conectado ao ADC.

Como detalhei anteriormente, o ADC nos fornecerá um valor proporcional à proximidade do imã fixo ao quadro da bicicleta. Nos meus testes verifiquei um valor de 88 quando o sensor está longe do imã. Quando o sensor passa na frente do imã este valor aumenta ou diminui conforme o polo do imã que fica próximo do sensor.

Uma vez que o valor varia ligeiramente durante a passada, uso uma histerese (como detalhei aqui). O código abaixo faz a detecção:
  1.     if (!bDetectou && ((medida > VPOS_1) || (medida < VNEG_1)))  
  2. {  
  3.     // fim de uma volta  
  4.     bDetectou = TRUE;  
  5.   
  6.     // pulando um trecho que explico depois...  
  7.   
  8.     tickVolta = 0;  
  9.     P1OUT |= 1;  
  10. }  
  11. else  
  12. {  
  13.     if (bDetectou && (medida < VPOS_2) && (medida > VNEG_2))  
  14.     {  
  15.         // pode procurar imã de novo  
  16.         bDetectou = FALSE;  
  17.         P1OUT &= 0xFE;  
  18.     }  
  19. }  
O teste (!bDetectou && ((medida > VPOS_1) || (medida < VNEG_1))) verifica se nos afastamos da referência num momento em que ainda não detectamos o imã, indicando o início da passagem.

Após a detecção do imã é preciso esperar o valor retornar para perto da referência antes de detectar uma outra passagem do imã: (bDetectou && (medida < VPOS_2) && (medida > VNEG_2)).

Cálculo da Velocidade

Esta parte é trivial, desde que se preste atenção às unidades de medida. A velociadade é distância dividida por tempo e queremos exibir o resultado final em Km/h, com uma casa decimal.

A constante CIRCUNF é a circunferência da roda (quanto ela anda em uma volta), medida em milímetros. A variável tickVolta indica quantos ticks de 100 microsegundos demorou a volta.

A velocidade em Km/s corresponde a (CIRCUNF/1000000)/(tickVolta/10000). Para obtermos em Km/h precisamos multiplicar por 3600 (número de segundos por hora). Por último vamos multiplicar por 10 para termos um casa decimal (mais precisamente, estamos medindo a velocidade em unidades de 0,1Km/h). Simplificando as potências de dez, ficamos com:
  1.   // Calcula a velocidade em 0,1 Km/h  
  2. // veloc = distância / tempo  
  3. // CIRCUNF em mm, ticVolta em 100 us  
  4. // veloc = 3600 * (CIRCUNF / 100000) / (ticVolta / 10000)  
  5. veloc = (unsigned int) ((CIRCUNF*360L)/tickVolta);  
Apresentação do Resultado

Para a apresentação do resultado, dividimos o tempo de uma volta em frames. Cada coluna de cada caracter do resultado será apresentada por um frame. Entre um caracter e outro teremos SPACE frames. A variável tickFrame contem quantos ticks dura cada frame; contFrame conta os ticks até o final do frame atual. Inicialmente contFrame contém a quantidade de ticks que precisam ser aguardados até a posição inicial da mensagem (escolhida para garantir a legibilidade da mensagem).

Uma dificuldade é que a apresentação é feita na volta seguinte a cada medição de velocidade. Se a bicicleta estiver acelerando ou freando a apresentação fica muito ruim. Após algumas tentativas mal sucedidas de compensar isto, acabei optando por não apresentar a mensagem quando ocorre uma mudança grande de velocidade entre duas voltas.

Dada a posição onde eu montei o Spoke-o-dometer e o sentido de rotação da roda, é necessário apresentar a mensagem do fim para o começo. Ou seja da última coluna do último caracter para a primeira coluna do primeiro caracter. Por este motivo a mensagem é montada em ordem contrária e a variável col é iniciada com 5.

Segue o resto do código que é executado quando a roda completa uma volta:
  1.  // Calcula variação em relação à volta anterior  
  2. if (tickVolta > tickVoltaAnt)  
  3.     delta = tickVolta - tickVoltaAnt;  
  4. else  
  5.     delta = tickVoltaAnt - tickVolta;  
  6.   
  7. // Só mostra velocidade se maior que 7.0 Km/h  
  8. // e não variou mais que 25%  
  9. if ((veloc > 70) && (delta < (tickVolta >> 2)))  
  10.     tmsg = escreve_kmh(veloc, msg);  
  11. else  
  12. {  
  13.     msg[0] = ' ';  
  14.     msg[1] = 0;  
  15.     tmsg = 1;  
  16. }  
  17. pMsg = &msg[0];  
  18.   
  19. // Calcula o tempo por frame e a contagem para a posição inicial  
  20. tickFrame = tickVolta / NFRAMES;  
  21. contFrame = (tickVolta *(NFRAMES - POS_MSG - tmsg*(5+SPACE))) / NFRAMES;  
  22. tickVoltaAnt = tickVolta;  
  23. col = 5;  
A formatação da mensagem é feita por uma conversão "caseira" do número binário em caracteres:
  1. // Formata a velocidade para apresentação  
  2. // Mensagem é montada em ordem reversa  
  3. // "h/mk#,###"  
  4. int escreve_kmh (unsigned int vel, char *pv)  
  5. {  
  6.    int tam = 7;  
  7.     
  8.    *pv++ = 'h';  
  9.    *pv++ = '/';  
  10.    *pv++ = 'm';  
  11.    *pv++ = 'k';  
  12.    *pv++ = '0' + (vel % 10);  vel /= 10;  
  13.    *pv++ = ',';  
  14.    *pv++ = '0' + (vel % 10);  vel /= 10;  
  15.    if (vel != 0)  
  16.    {  
  17.        *pv++ = '0' + (vel % 10);  vel /= 10;  
  18.        tam++;  
  19.        if (vel != 0)  
  20.        {  
  21.            *pv++ = '0' + (vel % 10);  
  22.            tam++;  
  23.        }  
  24.    }  
  25.    *pv = 0;  
  26.    return tam;  
  27. }  
O gerador de caracteres, que indica qual LED deve estar aceso em cada coluna de cada caracter está em uma matriz de bytes:
  1. // Gerador de Caracteres  
  2. // Cada caracter corresponde a 5 bytes  
  3. // Cada byte corresponde a uma coluna  
  4. static const byte gc[16][5] =  
  5. {  
  6.    0x7C, 0x82, 0x82, 0x82, 0x7C,       // 0  
  7.    0x02, 0x42, 0xFE, 0x02, 0x02,       // 1  
  8.    0x46, 0x8A, 0x92, 0xA2, 0x42,       // 2  
  9.    0x44, 0x82, 0x92, 0x92, 0x6C,       // 3  
  10.    0xF0, 0x10, 0x10, 0x10, 0xFE,       // 4  
  11.    0xF2, 0x92, 0x92, 0x92, 0x8C,       // 5  
  12.    0x6C, 0x92, 0x92, 0x92, 0x0C,       // 6  
  13.    0x80, 0x86, 0x98, 0xA0, 0xC0,       // 7  
  14.    0x6C, 0x92, 0x92, 0x92, 0x6C,       // 8  
  15.    0x60, 0x92, 0x92, 0x92, 0x6C,       // 9  
  16.    0x00, 0x00, 0x00, 0x00, 0x00,       // SPACE    (10)  
  17.    0x00, 0x04, 0x06, 0x00, 0x00,       // ,        (11)  
  18.    0x7E, 0x08, 0x14, 0x22, 0x00,       // k        (12)  
  19.    0x1E, 0x10, 0x0C, 0x10, 0x1E,       // m        (13)  
  20.    0x03, 0x0C, 0x18, 0x30, 0x60,       // /        (14)  
  21.    0x7E, 0x08, 0x08, 0x06, 0x00        // h        (15)  
  22. };  
Sobra apenas o trabalho de mudar os LEDs ao final de cada frame, na interrupção do timer:
  1.  if (contFrame == 0)         // fim do frame atual  
  2. {  
  3.     if (col > 0)  
  4.     {  
  5.         // vamos para a coluna anterior  
  6.         col--;  
  7.         valor = gc [char_index(*pMsg)][col];  
  8.         P1OUT &=  0xC1;  
  9.         P1OUT |= (valor & 0x3E);  
  10.         P2OUT &= 0x3F;  
  11.         P2OUT |= (valor & 0xC0);  
  12.         contFrame = tickFrame;  
  13.     }  
  14.     else  
  15.     {  
  16.         //  passar ao caracter seguinte  
  17.         P1OUT = P1OUT & 0xC1;  
  18.         P2OUT = P2OUT & 0x3F;  
  19.         col = 5;  
  20.         pMsg++;  
  21.         if (*pMsg == 0)  
  22.             tickFrame = 0;      // fim da mensagem  
  23.         else  
  24.             contFrame = tickFrame * SPACE;  
  25.     }  
  26. }  
  27. else  
  28.     contFrame--;  
A rotina char_index devolve o indice no gerador de caracteres para cada caracter:
  1. / c_index: retorna o índice do caractere na matriz gc  
  2. int char_index(char c)  
  3. {  
  4.    if ((c - '0') >= 0 && (c - '0') < 10)  
  5.        return (c - '0');  
  6.    if (c == ' 'return CHAR_SPACE;  
  7.    if (c == ','return CHAR_COMMA;  
  8.    if (c == 'k'return CHAR_K;  
  9.    if (c == 'm'return CHAR_M;  
  10.    if (c == '/'return CHAR_SLASH;  
  11.    if (c == 'h'return CHAR_H;  
  12.   
  13.    //else (caractere estranho)  
  14.    return CHAR_SLASH;  
  15. }  

Com isto acredito ter coberto todo o código. Em caso de dúvida, o formulário de comentários está logo abaixo.

Nenhum comentário: