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.

/*
Spoke-o-dometer
Teste 5 - Versão Completa

Daniel & Flávio Quadros - abril/junho, 2009
*/

#include <msp430.h>

// Constantes lógicas
#define FALSE 0
#define TRUE 1

// Tipo byte
typedef unsigned char byte;

// Circunferência aprox. da roda em milimetros
#define CIRCUNF 2090L

// Valor para contar 100us c/ clock de 1MHz
#define TEMPO_100uS 99 // (100us * 1MHz) - 1

// Gerador de Caracteres
// Cada caracter corresponde a 5 bytes
// Cada byte corresponde a uma coluna
static const byte gc[16][5] =
{
0x7C, 0x82, 0x82, 0x82, 0x7C, // 0
0x02, 0x42, 0xFE, 0x02, 0x02, // 1
0x46, 0x8A, 0x92, 0xA2, 0x42, // 2
0x44, 0x82, 0x92, 0x92, 0x6C, // 3
0xF0, 0x10, 0x10, 0x10, 0xFE, // 4
0xF2, 0x92, 0x92, 0x92, 0x8C, // 5
0x6C, 0x92, 0x92, 0x92, 0x0C, // 6
0x80, 0x86, 0x98, 0xA0, 0xC0, // 7
0x6C, 0x92, 0x92, 0x92, 0x6C, // 8
0x60, 0x92, 0x92, 0x92, 0x6C, // 9
0x00, 0x00, 0x00, 0x00, 0x00, // SPACE (10)
0x00, 0x04, 0x06, 0x00, 0x00, // , (11)
0x7E, 0x08, 0x14, 0x22, 0x00, // k (12)
0x1E, 0x10, 0x0C, 0x10, 0x1E, // m (13)
0x03, 0x0C, 0x18, 0x30, 0x60, // / (14)
0x7E, 0x08, 0x08, 0x06, 0x00 // h (15)
};

#define CHAR_SPACE 10;
#define CHAR_COMMA 11;
#define CHAR_K 12;
#define CHAR_M 13;
#define CHAR_SLASH 14;
#define CHAR_H 15;


// Limites para detectar passagem pelo ímã
#define VREPOUSO 88 // 01011000
#define VPOS_1 120 // 01111000
#define VPOS_2 100 // 01111000
#define VNEG_1 56 // 00111000
#define VNEG_2 76 // 00111000

static byte bAmostrando = FALSE; // TRUE enquanto aguarda ADC
static byte bDetectou = FALSE; // TRUE qdo detecta o imã


// Contagem do tempo de uma volta e
// controle da escrita da mensagem

#define NFRAMES 360 // frames por volta
#define POS_MSG 45 // inicio da mensagem
#define SPACE 3 // frames de espaço entre letras

static unsigned long tickVolta; // número de ticks em uma volta
static unsigned long tickVoltaAnt; // número de ticks da volta anterior
static unsigned long tickFrame = 0; // número de ticks por frame
static unsigned long contFrame; // ticks até o próximo frame
static byte col; // coluna sendo apresentada
static char *pMsg; // caracter sendo apresentado
static char msg[16]; // mensagem a apresentar

// Rotinas locais
int escreve_kmh (unsigned int vel, char *pv);
int char_index (char c);

// Programa Principal
void main (void)
{
unsigned int cont = 0;

// Desliga Watchdog
WDTCTL = WDTPW + WDTSSEL + WDTHOLD;

// Altera a configuração de clock para ativar o VLOCLK
BCSCTL3 |= LFXT1S1;

// SMCLK = MCLK / 8
BCSCTL2 = DIVS_3;

// Programa entradas e saídas
P1SEL = 0xC0; // P1.0 a P1.5 -> LEDs
// P1.6 e P1.7 são A3+ e A3-
P1DIR = 0x3F;
P1OUT = 0; // Todos as saídas em zero

P2SEL = 0; // Todos os pinos como I/O
P2DIR = 0xFF; // Todos os pinos como saída
P2OUT = 0; // Todos as saídas em zero

// Programa o ADC
// MSP430F2013 -> SD16
SD16CTL = SD16VMIDON + SD16REFON + SD16DIV_0 + SD16SSEL_1; // 1.2V ref, SMCLK
SD16INCTL0 = SD16INCH_3; // PGA = 1x, Diff inputs A3- & A3+
SD16CCTL0 = SD16SNGL + SD16UNI + SD16IE; // Single conversion, Unipolar, 256 SR, Int enable
SD16CTL &= ~SD16VMIDON; // VMID off: used to settle ref cap
SD16AE = SD16AE6 + SD16AE7; // P1.6 & P1.7: A3+/- SD16_A inputs

// Dá um tempinho para estabilizar a alimentação
while (cont < 0xFF)
cont++;
cont = 0;

// Alimentação já deve estar estável, vamos ligar o DCO
BCSCTL1 = CALBC1_8MHZ;
DCOCTL = CALDCO_8MHZ;

// Programar a interrupção de tempo real p/ cada 100us
TACCR0 = TEMPO_100uS;
TACTL = TASSEL_2 + MC_1 + TAIE; // SCLK, up mode, interrupt

// O nosso programa principal vai ficar dormindo,
// todo o tratamento será feito na interrupção
_BIS_SR(GIE);
LPM0; // Dorme tratando interrupção

// Nuca vai chegar aqui
while (1)
;
}

// Tratamento da interrupção do Timer A
#pragma vector=TIMERA1_VECTOR
__interrupt void Timer_A_TO(void)
{
byte valor;

switch (TAIV)
{
case 10: // overflow
tickVolta++; // mais um tick na volta atual
if (tickFrame != 0)
{
if (contFrame == 0) // fim do frame atual
{
if (col > 0)
{
// vamos para a coluna anterior
col--;
valor = gc [char_index(*pMsg)][col];
P1OUT &= 0xC1;
P1OUT |= (valor & 0x3E);
P2OUT &= 0x3F;
P2OUT |= (valor & 0xC0);
contFrame = tickFrame;
}
else
{
// passar ao caracter seguinte
P1OUT = P1OUT & 0xC1;
P2OUT = P2OUT & 0x3F;
col = 5;
pMsg++;
if (*pMsg == 0)
tickFrame = 0; // fim da mensagem
else
contFrame = tickFrame * SPACE;
}
}
else
contFrame--;
}
if (!bAmostrando)
{
SD16CTL |= SD16REFON; // Liga a referência do SD16
SD16CCTL0 |= SD16SC; // Inicia uma conversão
bAmostrando = TRUE;
}
break;

case 2: // CCR1, não utilizado
break;
}
}

// Tratamento da interrupção do SD16
#pragma vector = SD16_VECTOR
__interrupt void SD16ISR(void)
{
unsigned int medida;
unsigned int veloc;
unsigned long delta;
int tmsg;

SD16CTL &= ~SD16REFON; // Desliga referência do SD16_A
medida = SD16MEM0 >> 8; // Pega medida (limpa IFG)
bAmostrando = FALSE; // Fim da amostragem

if (!bDetectou && ((medida > VPOS_1) || (medida < VNEG_1)))
{
// fim de uma volta
bDetectou = TRUE;

// Calcula variação em relação à volta anterior
if (tickVolta > tickVoltaAnt)
delta = tickVolta - tickVoltaAnt;
else
delta = tickVoltaAnt - tickVolta;

// Calcula a velocidade em 0,1 Km/h
// veloc = distância / tempo
// CIRCUNF em mm, ticVolta em 100 us
// veloc = 3600 * (CIRCUNF / 100000) / (ticVolta / 10000)
veloc = (unsigned int) ((CIRCUNF*360L)/tickVolta);

// Só mostra velocidade se maior que 7.0 Km/h
// e não variou mais que 25%
if ((veloc > 70) && (delta < (tickVolta >> 2)))
tmsg = escreve_kmh(veloc, msg);
else
{
msg[0] = ' ';
msg[1] = 0;
tmsg = 1;
}
pMsg = &msg[0];

// Calcula o tempo por frame e a contagem para a posição inicial
tickFrame = tickVolta / NFRAMES;
contFrame = (tickVolta *(NFRAMES - POS_MSG - tmsg*(5+SPACE))) / NFRAMES;
tickVoltaAnt = tickVolta;
tickVolta = 0;
col = 5;
P1OUT |= 1;
}
else
{
if (bDetectou && (medida < VPOS_2) && (medida > VNEG_2))
{
// pode procurar imã de novo
bDetectou = FALSE;
P1OUT &= 0xFE;
}
}
}

// Formata a velocidade para apresentação
// Mensagem é montada em ordem reversa
// "h/mk#,###"
int escreve_kmh (unsigned int vel, char *pv)
{
int tam = 7;

*pv++ = 'h';
*pv++ = '/';
*pv++ = 'm';
*pv++ = 'k';
*pv++ = '0' + (vel % 10); vel /= 10;
*pv++ = ',';
*pv++ = '0' + (vel % 10); vel /= 10;
if (vel != 0)
{
*pv++ = '0' + (vel % 10); vel /= 10;
tam++;
if (vel != 0)
{
*pv++ = '0' + (vel % 10);
tam++;
}
}
*pv = 0;
return tam;
}

// c_index: retorna o índice do caractere na matriz gc
int char_index(char c)
{
if ((c - '0') >= 0 && (c - '0') < 10)
return (c - '0');
if (c == ' ') return CHAR_SPACE;
if (c == ',') return CHAR_COMMA;
if (c == 'k') return CHAR_K;
if (c == 'm') return CHAR_M;
if (c == '/') return CHAR_SLASH;
if (c == 'h') return CHAR_H;

//else (caractere estranho)
return CHAR_SLASH;
}
O Resultado

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

video

Nenhum comentário: