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:
static const byte gc[10][5] =Timing
{
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
};
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:
/*
Spoke-o-dometer
Teste3 - Teste de Display
Daniel Quadros - abril, 2009
*/
#include <msp430.h>
// Valor para contar 500us c/ clock de 12KHz
#define TEMPO_500uS 6 // 0,5ms * 12KHz
// Limites para detectar passagem pelo imã
#define VREPOUSO 88 // 01011000
#define VPOS_1 120 // 01111000
#define VPOS_2 100 // 01111000
#define VNEG_1 56 // 00111000
#define VNEG_2 76 // 00111000
#define FALSE 0
#define TRUE 1
typedef unsigned char byte;
// Gerador de Caracter (versão numérica)
// Cada caracter corresponde a 5 bytes
// Cada byte corresponde a uma coluna
static const byte gc[10][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
};
static byte bDetectou = FALSE; // TRUE qdo detecta o imã
static unsigned long tickVolta;
static unsigned long tickFrame = 0;
static unsigned long contFrame;
static byte col;
char *pMsg;
#define NFRAMES 360
#define POS 45
#define SPACE 3
// 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;
// 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_3 + SD16SSEL_1; // 1.2V ref, SMCLK / 8
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 0,5ms
TACCR0 = TEMPO_500uS;
TACTL = TASSEL_1 + MC_1 + TAIE; // ACLK, up mode, interrupt
// O nosso programa principal vai ficar dormindo,
// todo o tratamento será feito na interrupção
_BIS_SR(LPM3_bits + GIE); // 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++;
if (tickFrame != 0)
{
if (contFrame == 0)
{
if (col > 0)
{
col--;
valor = gc [*pMsg - '0'][col];
P1OUT = (P1OUT & 0xC1) | (valor & 0x3E);
P2OUT = (P2OUT & 0x3F) | (valor & 0xC0);
contFrame = tickFrame;
}
else
{
P1OUT = P1OUT & 0xC1;
P2OUT = P2OUT & 0x3F;
col = 5;
pMsg++;
if (*pMsg == 0)
tickFrame = 0;
else
contFrame = tickFrame * SPACE;
}
}
else
contFrame--;
}
SD16CTL |= SD16REFON; // Liga a referência do SD16
SD16CCTL0 |= SD16SC; // Inicia uma conversão
_BIC_SR_IRQ (SCG1+SCG0); // Manter osciladores ligados
break;
case 2: // CCR1, não utilizado
break;
}
}
// Tratamento da interrupção do SD16
#pragma vector = SD16_VECTOR
__interrupt void SD16ISR(void)
{
unsigned int result;
SD16CTL &= ~SD16REFON; // Desliga referência do SD16_A
result = SD16MEM0 >> 8; // Pega resultado (limpa IFG)
if (!bDetectou && ((result > VPOS_1) || (result < VNEG_1)))
{
bDetectou = TRUE;
tickFrame = tickVolta / NFRAMES;
pMsg = "0123456789";
contFrame = tickFrame*(NFRAMES - POS - 10*(5+SPACE));
tickVolta = 0;
col = 5;
P1OUT |= 1;
}
else
{
if (bDetectou && (result < VPOS_2) && (result > VNEG_2))
{
bDetectou = FALSE;
P1OUT &= 0xFE;
}
}
_BIS_SR_IRQ (SCG1+SCG0); // voltar para LPM3
}
O Resultado
Ainda está longe do que eu espero chegar, mas o vídeo abaixo é uma bela "prova de conceito".
2 comentários:
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.
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!
[]
Postar um comentário