quinta-feira, junho 28, 2007

Controlando um LED com um PIC - Parte III

Nesta parte final, vamos ver como testar o nosso projeto na prática.

Montagem do Circuito

Praticamente qualquer tipo de montagem pode ser feito, tomando-se o cuidado de usar um soquete para o PIC, já que ele terá que ser retirado do circuito para ser programado. Para uma montagem de teste, use uma breadboard como a abaixo:



Para uma montagem definitiva, você pode usar uma placa padrão. Existem algumas cujas trilhas seguem o mesmo padrão das breadboards.

Programação do PIC

O modelo de PIC selecionado armazena o programa em memória Flash, o que permite a gravação de forma relativamente simples (inclusive sem retirar o PIC do circuito, o que o nosso projeto não inclui).

De uma forma simplificada, a programação do PIC requer:

  • alimentá-lo através dos pinos VDD e VSS, tipicamente 5 e 0 volts.
  • colocar uma tensão de programação no pino GP3, tipicamente 13 volts.
  • enviar comandos e receber respostas serialmente pelo pino GP0, usando o pino GP1 para sinalizar os bits (clock).

Para isto normalmente é usado um hardware adicional (o programador) que de um lado se conecta ao PIC e de outro a um PC (através de uma porta serial, paralela ou USB). Uma aplicação no PC controla a programação.

Existem vários modelos de programador para o PIC no mercado, com características diversas. Os mais aventureiros podem até projetar o seu próprio gravador a partir das especificações da Microchip.

No meu caso, usei o programador McFlash da Mosaico que permite a programação a partir do próprio MPLAB.

Referências

terça-feira, junho 26, 2007

Controlando um LED com um PIC - Parte II

Nesta parte vamos ver o software do nosso projeto.

Uma vez que o software é bastante simples, vamos desenvolvê-lo em linguagem assembler e usar o ambiente de desenvolvimento MPLab, disponibilizado gratuitamente pela Microchip.

Definido o algorítmo é só pegar o manual de referência do PIC e enfrentar as esquisitices do conjunto de instruções. Alguns pontos a destacar:
  • a execução começa sempre no endereço 0, a interrupção desvia sempre para o endereço 4. No endereço 0 foi colocado um GOTO para desviar para o corpo do programa, que fica após o tratamenteo da interrupção.

  • é preciso tomar cuidado com o fato dos registradores de controle (SFR) estarem armazenados em dois bancos (BANK0 e BANK1). Antes de acessar um registrador de controle é preciso garantir que o banco correto está selecionado.

  • no início da rotina de interrupção é preciso salvar os registradores W e STATUS. Ao final da interrupção estes registradores precisam ser restaurados.

  • o PIC não possui instruções de desvio condicional convencionais. As instruções condicionais são sempre na forma de Skip If, na qual a instrução seguinte é pulada se uma determinada condição for verdadeira.
No inicio do programa deve-se definir a configuração do PIC, que será gravada em uma área especial da Flash:

; Configuração do PIC;
- habilita BrownOut Detect;
- desabilita pino MCLR;
- desabilita Watchdog;
- usa o oscilador interno
__CONFIG _BODEN_ON & _MCLRE_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT

Uma variável (MODO_LED) é usada armazenar o modo atual de atuação do LED (APAGADO, ACESO ou PISCANDO). Quando for detectado o pressionamento do botão o modo será avançado para o modo seguinte (circularmente).

; Estados do LED
#define APAGADO 0
#define ACESO 1
#define PISCANDO 2

; Loop principal
PRINC
BTFSS MODO_BOTAO, BOTAO_APERT
GOTO PRINC ; aguarda apertar o botão
BCF MODO_BOTAO, BOTAO_APERT ; limpa indicação
MOVF MODO_LED,W
XORLW APAGADO
BTFSC STATUS,Z
GOTO ACENDER
MOVF MODO_LED,W
XORLW ACESO
BTFSC STATUS,Z
GOTO PISCAR

; Estava piscando, vamos apagar
MOVLW APAGADO
MOVWF MODO_LED
BCF LED
GOTO PRINC

; Estava apagado, vamos acender
ACENDER
MOVLW ACESO
MOVWF MODO_LED
BSF LED
GOTO PRINC

; Estava aceso, vamos piscar
PISCAR
MOVLW PISCANDO
MOVWF MODO_LED
GOTO PRINC

O tratamento do botão possui uma pequena dificuldade: quando o botão é apertado ou solto, por alguns instantes o contato abre e fecha rapidamente. Isto é chamado de 'bounce' (algo como 'quicar'). Para evitar que cada uma destas aberturas/fechamentos seja tratada como um aperta/solta do botão, é preciso aguardar que o sinal do botão fique estável por um certo tempo.

Para fazer o LED piscar e executar o 'debounce' do botão, vamos configurar o Timer 0 do PIC para gerar interrupções periodicamente. O valor que adotei foi aproximadamente 50 milisegundos, o que permite um piscar nítido e fazer o debounce simplesmente exigindo duas leituras iguais em interrupções consecutivas.

; Inicia o timer
BANK0
BCF INTCON,T0IF ; limpa a interrupção do timer
MOVLW .256-CNT_TIMER
MOVWF TMR0 ; programa o timer
BSF INTCON,T0IE
BSF INTCON,GIE ; permite interrupções

O piscar do LED fica bastante simples: na interrupção testamos se o modo atual é piscar; se sim invertemos o sinal no pino do LED.

MOVF MODO_LED,W
XORLW PISCANDO
BTFSS STATUS,Z
GOTO TRATA_BOTAO
MOVLW 0x01
XORWF GPIO,F ; pisca o LED
TRATA_BOTAO

A lógica do botão é um pouco mais complicada. São usados três valores lógicos, armazenados em três bits de uma variável (MODO_BOTAO):

  • o estado do botão na interrupção anterior, que é atualizado ao final de toda interrupção

  • o estado do botão após o debounce, que é atualizado somente quando o estado atual do botão é igual ao estado anterior

  • uma indicação de que o botão foi apertado, que é ligado na interrupção quando o estado após o debounce passa de solto para apertado. Esta indicação é limpa no laço principal do programa, após tratar o acionamento da tecla.


; Controles do estado do botão
#define BOTAO_ANT 0x01 ; este bit indica o estado anterior
#define BOTAO_DEB 0x02 ; este bit tem o valor c/ "debounce"
#define BOTAO_APERT 0x04 ; este botão indica que foi detectado
; um pressionmento do botão

TRATA_BOTAO
BTFSC BOTAO ; testa o botão
GOTO SOLTO

; botao apertado
BTFSC MODO_BOTAO,BOTAO_ANT ; testa leitura anterior
GOTO APERT_10
BSF MODO_BOTAO,BOTAO_ANT ; mudou
GOTO FIM_INT
APERT_10 ; igual a vez anterior
BTFSC MODO_BOTAO,BOTAO_DEB
GOTO FIM_INT ; ja estava apertado
BSF MODO_BOTAO,BOTAO_APERT ; apertou agora
BSF MODO_BOTAO,BOTAO_DEB
GOTO FIM_INT

SOLTO ; botao solto
BTFSS MODO_BOTAO,BOTAO_ANT
GOTO SOLTO_10
BCF MODO_BOTAO,BOTAO_ANT ; mudou
GOTO FIM_INT
SOLTO_10 ; igual a vez anterior
BCF MODO_BOTAO,BOTAO_DEB

O fonte completo está aqui

Na próxima parte vamos encerrar esta série, vendo como montar o circuito e as referências para maiores detalhes.

segunda-feira, junho 18, 2007

Controlando um LED com um PIC - Parte I

Este é mais um post sugerido pelas buscas que trouxeram alguem a este blog. Nesta série vamos ver como controlar um LED usando um microcontrolador PIC.

O Objetivo

O objetivo desta série é mostrar o projeto do hardware e software de um pequeno dispositivo que ilustra como controlar um LED usando um PIC. O dispositivo possui um LED e um botão que será usado para controlar o estado do LED (apagado, piscando ou aceso).

O Projeto de hardware

Para este projeto selecionei um modelo de PIC bastante simples, o 12F675. O modelo 12F629 pode ser usado sem nenhuma alteração e é simples alterar tanto o hardware como o software para outros modelos.

O PIC 12F675 tem as seguintes vantagens para este projeto:
  • pode operar com alimentação de 2 a 5.5V, o que simplifica a operação com baterias e pilhas
  • disponível em encapsulamento DIP de 8 pinos, o que simplifica a montagem
  • possui um oscilador interno de 4MHz, dispensando a conexão de um cristal ou ressonador
  • memória Flash para o programa, o que simplifica a gravação e regravação
O primeiro passo para o projeto de hardware é examinar o datasheet do microcontrolador, que pode ser baixado do site da Microchip.

No datasheet verificamos que podemos operar de 4 a 10 MHz com uma alimentação de 3 a 5.5 V (a operação com tensões entre 2 e 3 Volts requer clock inferior a 4MHz). Minha opção foi operar com o oscilador interno de 4MHz usando uma bateria de 3V (Duracell DL2032 ou equivalente). O positivo da bateria deve ser conectado ao pino 1 (VDD) do PIC e o negativo ao pino 8 (VSS).

O LED e o botão são conectados a pinos de entrada/saída de uso geral, que no 12F675 são qualquer um dos outros 6 pinos (para ser mais preciso, o botão não pode ser ligado ao pino 4 pois vou usar o pull-up interno que não está disponível no GP3). Escolhi o pino 7 para o LED e o pino 2 para o botão.

O LED, como diz a sigla, é um diodo emissor de luz. Quando submetido a uma tensão direta acima de sua tensão de queda ele emite uma luz com intensidade proporcional à corrente. Existem vários modelos de LEDs, que emitem as mais diversas cores. A tensão de queda é tipicamente de 2V e uma intensidade boa para um LED montado em painel pode ser obtida com uma corrente de 10 mA.

Voltando ao datasheet do PIC, verificamos que um pino de entrada/saída é capaz de gerar ou absorver uma corrente de até 125mA e tem uma tensão de 0,6V (nível zero) ou VDD-0.7V (nível um). A capacidade de corrente do PIC permite ligar um LED diretamente das duas maneiras abaixo:


Na primeira maneira, com o LED ligado entre o pino do PIC e VSS, o valor do resistor em série (conforme a lei de Ohm) deve ser

(VDD - 0.7 - 2,0)/0,01 = 30 ohms

Analogamente, com o LED ligando entre o pino do PIC e VDD, o valor do resistor deve ser

(VDD - 0.6 - 2,0)/0,01 = 40 ohms

No primeiro caso, o LED acende quando o pino do PIS está no nível um, no segundo quando está no nível zero. No meu circuito adotei a primeira maneira com um resistor de 33 ohms.

Para a ligação do botão poderia ser usado uma forma semelhante às vistas para o LED. Olhando mais uma vez o datasheet, o PIC considera nível zero um valor abaixo de 0.15*VDD (0.45V) e nível um um valor acima de 0.25*VDD+0,8 (1,55V). Poderíamos calcular a partir destes dados valores apropriados para o resistor em série com o botão que garantam os níveis apropriados com um valor reduzido de corrente.

Entretanto, a Microchip já fez estes cálculos e disponibiliza internamente ao PIC um resistor de weak pull-up, que faz com que um pino aberto seja lido como em nível um. Desta forma, o botão pode ser ligado diretamente ao VSS e erá lido como nível zero quando fechado e como nível um quando aberto.

A lista de componentes para o circuito fica sendo:
  • 1 PIC 12F675 (ou 12F629)
  • 1 LED
  • 1 Botão de contato momentâneo
  • 1 Resistor de 33 Ohms 1/8 W
  • 1 Bateria de 3V
  • 1 Suporte para a bateria
O circuito completo fica:



No próximo post da série vamos ver o software.

segunda-feira, junho 11, 2007

Construindo um Compilador - Parte 4

Recordando o que vimos na parte anterior, o algorítmo que estamos apresentando realiza uma análise descendente, na qual se procura identificar itens sintáticos cada ver mais simples. Em cada instante temos como objetivo reconhecer um determinado item sintático. A gramática indica quais os itens sintáticos que compõem o nosso objetivo, Alguns destes itens correspondem diretamente a item léxicos (são os chamados símbolos terminais), outros correspondem a itens sintáticos mais complexos (os não-terminais). No caso dos não-terminais, vamos salvar temporariamente o nosso objetivo atual numa pilha enquanto reconhecemos este novo objetivo.

A estrutura principal para realizar a análise sintática representa o grafo sintático e é um vetor de estruturas que armazenam os nós. Exemplificando em C:

typedef struct
{
int simb; // código do símbolo
int fTerm; // TRUE se simbolo terminal, FALSe se não-terminal
int alt; // índice do nó alternativo (-1 se não tiver)
int seg; // índice do nó seguinte (-1 se não tiver)
} NO;

Um segundo vetor armazena o índice do primeiro nó de cada não-terminal:

typedef struct
{
char *nome; // nome do não terminal (para debug)
int prim; // índice do primeiro nó
} NT;

Desta forma, quando queremos reconhecer um não-terminal, partimos do primeiro nó e vemos se ele corresponde ao item léxico atual. Se sim, obtemos o próximo item léxico e passamos ao nó seguinte. Se for diferente, vamos examinar o nó alternativo.

Estas tabelas podem ser constantes dentro do programa ou carregadas dinamicamente a partir de um arquivo. O trecho abaixo corresponde ao nosso exemplo da parte anterior:

// códigos dos terminais
#define T_VAZIO 0
#define T_NUM 1
#define T_MUL 2
#define T_DIV 3
#define T_SOMA 4
#define T_SUB 5
#define T_ABRE 6
#define T_FECHA 7

// códigos dos não-terminais
#define NT_FATOR 0
#define NT_TERMO 1
#define NT_EXPR 2

NT TabNaoTerm[] =
{
{ "Fator", 0 },
{ "Termo", 4 },
{ "Expr", 8 }
}

NO GrafoSint[] =
{
/* simb fTerm alt seg */
/* 0 */ { T_NUM, FALSE, 1, -1 },
/* 1 */ { T_ABRE, TRUE, -1, 2 },
/* 2 */ { NT_EXPR, FALSE, -1, 3 },
/* 3 */ { T_ABRE, TRUE, -1, -1 },

/* 4 */ { NT_FATOR, FALSE, -1, 5 },
/* 5 */ { T_MUL, TRUE, 5, 4 },
/* 6 */ { T_DIV, TRUE, 6, 4 },
/* 7 */ { T_VAZIO, TRUE, -1, -1 },

/* 8 */ { NT_TERMO, FALSE, -1, 9 },
/* 9 */ { T_SOMA, TRUE, 10, 8 },
/* 10 */ { T_SUB, TRUE, 11, 8 },
/* 11 */ { T_VAZIO, TRUE, -1, -1 },

}

O terminal T_VAZIO indica a situação em que o item léxico atual não precisa ser consumido para o reconhecimento.

O algorítmo do analisador sintático fica assim:

#define TAM_PILHA
int pilha[TAM_PILHA];
int topo = 0;

// retorna TRUE se sucesso, FALSE se encontrou erro
int AnalisadorSintatico ()
{
int item; // item léxico atual
int no; // no atual

// Nosso objetivo é uma expressão
no = TabNaoTerm [NT_EXPR].prim;

// le o primeiro item léxico
item = AnalisadorLexico ();

while (TRUE)
{
if (no != -1)
{
if (GrafoSint[no].fTerm)
{
if (GrafoSint[no].simb == T_VAZIO)
no = GrafoSint[no].seg; // reconheceu vazio
else if (GrafoSint[no].simb == item)
{
no = GrafoSint[no].seg; // reconheceu item léxico
item = AnalisadorLexico ();
}
else if (GrafoSint[no].alt != -1)
no = GrafoSint[no].alt; // não reconheceu mas tem alternativa
else
return FALSE; // sem alternativas: erro sintático
}
else
{
// temos um novo objetivo
if (topo >= TAM_PILHA)
return FALSE; // pilha cheia
pilha[topo++] = no;
no = TabNaoTerm [GrafoSint[no].simb].prim;
}
}
else
{
// reconheceu um não terminal
if (topo > 0)
{
// continua no seguinte
no = pilha[--topo];
no = GrafoSint[no].seg;
}
else
return TRUE; // chegamos ao final
}
}
}

Obviamente estamos fazendo grandes simplicações neste código.

Em primeiro lugar, estamos ignorando o tratamento de erros sintáticos. No mínimo o analisador deveria indicar em que ponto ocorreu o erro. Uma outra informação é a indicação do que era esperado. Por último existe a questão de recuperação do erro, permitindo prosseguir a análise. Embora tudo isto possa ser feito automaticamente a partir do grafo sintático, na prática é preferível programar um heurística específica para a linguagem, considerando quais os erros mais comuns e quais as recuperações que introduzem menos erros espúrios.

Neste algorítmo estamos apenas percorrendo o gráfico e verificando se a entrada obedece à gramática. Um compilador ou interpretador precisa executar ações à medida em que os itens são reconhecidos. Isto pode ser feito acrescentando ao nó uma indicação de qual rotina semântica deve ser executada (esta indicação pode ser um índice ou um ponteiro).

Na próxima parte vamos examinar um pouco mais o tratamento semântico.

sexta-feira, junho 08, 2007

De volta com o post #100 e um balanço

Após um mês de muito trabalho, estou retomando os posts no blog com um balanço deste um ano e meio de existência e uma idéia do que pretendo publicar no futuro.

Pois é, este é o centésimo post. Num levantamento rápido, os 99 posts passados envolveram principalmente Programação (27), Livros (22) e Microsoft (21). Como o gráfico abaixo (total de visitas por semana) mostra, tem algumas pessoas passando por aqui, mesmo quando não tem um post novo. As séries sobre gerenciamento de memória e construção de compiladores são as mais populares . Os posts sobre livros também tem os seus hits, principalmente por gente procurando eBooks para baixar.

O número de posts me parece satisfatório, considerando o tipo de posts que eu procuro fazer. De um modo geral, eu evito os posts curtos que tem apenas um link (principalmente para notícias efêmeras). A maioria dos meus posts exige um tempo razoável de pesquisa e escrita e certamente não dá para fazer um por dia.

Segue uma lista parcial de idéias para posts (ou série de posts) futuros; aceito sugestões nos comentários:
  • Concluir a série "Construindo um Compilador" (urgente!)
  • Projeto de Hw com PIC (controlando um LED, construindo um velocímetro para bicicleta)
  • Gravação de CD usando a interface IMAPI do Windows
  • A Reserva de Mercado de Informática
  • Memórias (programando em Assembler 8bits e participando do projeto de um clone do IBM-PC na Scopus, desenvolvendo um software de comunicação de dados, etc)
  • Meus primeiros computadores (TK-82C e o Apple II)