CONFIGURANDO CORRETAMENTE OS REGISTRADORES DOS MICROCONTROLADORES AVR - parte 2

USO DOS REGISTRADORES TCCR0, TCNT0 e TIMSK – CONTROLE DO TIMER0




ESPERA OCUPADA

O uso correto de um temporizador (timer) em um projeto microcontrolado pode garantir o sucesso deste. Muitas linguagens de programação oferecem funções prontas para temporização, mas estas funcionam ,em sua maioria, através de algoritmos compreendidos como “espera ocupada”.

Mas afinal o que é espera ocupada?”. A espera ocupada é um algoritmo desenvolvido para fazer o programa aguardar um determinado período necessário a algum processo. Este algoritmo é inserido no programa e quando executado passa a “ocupar” o microcontrolador e este só é “liberado” após o término da rotina. Porém o grande problema é que enquanto o microcontrolador se "ocupa" da tal rotina de temporização, nenhuma outra pode ser executada. Em alguns casos isso é muito ruim e pode prejudicar todo o projeto. Veja o código fonte abaixo1.


/*-----------------------------------------------------------
Subrotina para temporizar – espera ocupada
-----------------------------------------------------------*/
void tempo(void){

    unsigned char i, j;

 for (i=0; i<255; i++)
for (j=0; j<255; j++)
; /* nenhum processamento é */
/* executado aqui, apenas */
/* perde-se tempo */
}

/*-----------------------------------------------------------
Subrotina principal
-----------------------------------------------------------*/

void main(void){

    /* Pull-ups para PORTB – desligados */
    PORTB = 0;

    /* Direção dos pinos de I/O para PORTB – PB0 = saída */
    DDRB = (1<<DDB0);

    /* Insere um nop (não operando) para sincronizar*/
    _NOP();

    /* inicia processamento principal */
    while (1){
        PORTB = 1; /*liga pino 0 de PORTB */
        tempo(); /*chama espera ocupada */
        PORTB = 0; /*desliga todos os pinos de PORTB */
        tempo(); /*chama espera ocupada*/
    }

}


O código fonte acima mostra um um programa hipotético que utiliza o recurso da espera ocupada. Você pode perceber que enquanto o microcontrolador executa a subrotina tempo() mais nada pode ser executado, ou seja, o microcontrolador está processando o “tempo” de espera.

O programa demonstrado não esta de todo errado se a intenção for apenas manter o pino PB0 ligando e desligando continuamente. Agora em um programa maior, onde mais processos devem ser cuidados, a idéia não seria assim tão boa e, portanto, nada adequada, pois o grande problema é que o microcontrolador ficaria ocupado enquanto aguarda a passagem do tempo.

Imagine se o tempo tivesse de ser igual a um minuto!!! Agora imagine que uma válvula de segurança de um equipamento qualquer tivesse de ser monitorada ao mesmo tempo pelo programa em questão. Enquanto o programa esta preso na temporização, o pino ligado a entrada de monitoramento da válvula poderia alterar seu estado, informando uma situação de emergência e nenhum algoritmo de correção seria executado até que a temporização anterior tivesse fim e o programa retornasse ao ponto após a chamada da função para espera ocupada. É ou não é algo que pode por em cheque-mate todo um projeto?

Um outro detalhe importante é a precisão deste tipo de temporização. Ela não é nada segura e o desenvolvedor não teria como configurar a rotina para temporizar com precisão. Muito ruim não é mesmo?

Desta forma, o desenvolvedor só deve utilizar algoritmos de espera ocupada em situações onde:

  1. o programa não tem que cuidar de outros processos importantes

  2. e/ou programa não precisa de temporizações precisas

Agora, se qualquer uma destas situações for positiva então você só tem um caminho: fazer uso de um dos temporizadores internos no microcontrolador. Assim fica garantido que o programa não ira se ocupar com qualquer temporização e as mesmas também serão mais precisas (precisão esta depende apenas do tipo do clock utilizado).


O PERIFÉRICO TIMER0 DO AVR

O periférico TIMER0 nos microcontroladores AVR é o mais simples de se usar, entre os demais timers presentes, e consequentemente o mais utilizado pelos desenvolvedores. Neste artigo demonstrarei a sua configuração e uso, deixando os demais para uma outra oportunidade.

O TIMER0 do microcontrolador possui oito bits, com um prescale que permite a divisão do clock por até 1024. Os microcontroladores AVR possuem registradores dedicados para a configuração dos seus timers. Para utilizar o TIMER0 é necessário compreender o uso de três registradores básicos. A descrição destes três registradores pode ser vista a seguir:

- Registrador TCCR0 (Controle do timer e contador)
- Registrador TIMSK (Mascara de interrupção para timer e contador);
- Registrador TCNT0 (Registro para contagem do timer e contador).

Na figura abaixo apresento a estrutura do timer/counter de 8 bits dos microcontroladores AVR. Na figura é possível observar a presença dos registradores TCCRx e TCNTx. O registrador TIMSK não é mostrado, pois o mesmo é compartilhado com outros periféricos.


REGISTRO TCCR0

A seguir, na tabela é dada a descrição do registro TCCR0 – Timer/Counter Control, a posição de cada um dos bits de controle, o valor inicial dos mesmos durante o RESET do microcontrolador e a descrição de suas funções.


Tabela – Registro TCCR0

Bit        

7

6

5

4

3

2

1

0

FOC0

WGM00

COM01

COM00

WGM01

CS02

CS01

CS00

Leitura/

Escrita

W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

Valor

Inicial

0

0

0

0

0

0

0

0


FOC0 – Force Output Compare

Bit de escrita que permite configurar de acordo com seu valor lógico (0 ou 1):

0 – modo PWM não selecionado;
1 – modo PWM selecionado.

Obs.: A seleção acima citada depende da configuração adotada através dos bits WGM00 e WGM01.


WGM00:01 – Wave Forme Generation Mode

Bits de leitura e escrita que permitem configurar a forma de onda gerada no PWM. Esta configuração pode ser vista na tabela abaixo.

                    Tabela – Configuração dos bits WGM00 e WGM01

MODO

WGM01

WGM00

Operação

0

0

0

Normal

1

0

1

PWM com correção de fase

2

1

0

CTC

3

1

1

PWM rápido


COM00:01 – Compare Match Output Mode

Bits de leitura e escrita que permitem configurar o pino OC0 (Output/Compare 0). Esta configuração pode ser vista na tabela a seguir.


                    Tabela – Configuração dos bits COM00 e COM01

COM01

COM00

Descrição

0

0

Normal, OC0 desconectado

0

1

Muda OC0 na comparação

1

0

Limpa OC0 na comparação

1

1

Seta OC0 na comparação



CS02:00 – Clock Select

Bits de leitura e escrita que permitem configurar o clock para o Timer/Counter. Esta configuração pode ser vista na tabela abaixo.


                Tabela – Configuração dos bits CS02, CS01 e CS01

CS02

CS01

CS00

Descrição

0

0

0

Sem clock, pára o Timer

0

0

1

CLKI/O sem prescale

0

1

0

CLKI/O/8

0

1

1

CLKI/O/64

1

0

0

CLKI/O/256

1

0

1

CLKI/O/1024

1

1

0

Clock vindo de I/O – borda de descida

1

1

1

Clock vindo de I/O – borda de subida



REGISTRO TIMSK

A seguir, na tabela, é dada a descrição do registro TIMSK – Timer/Counter Interrupt Mask, a posição de cada um dos bits de controle, o valor inicial dos mesmos durante o RESET do microcontrolador e a descrição de suas funções. Este registro permite controlar as interrupções para todos os timers e comparadores presentes no microcontrolador. Os bits 1 e 0 controlam o TIMER0, e sendo assim, apenas estes bits serão analisados, pois são de total interesse aqui. Os outros bits são irrelevantes neste momento e não serão discutidos.


        Tabela – Registro TIMSK

Bit

7

6

5

4

3

2

1

0

OCIE2

TOIE2

TICIE1

OCIE1A

OCIE1B

TOIE1

OCIE0

TOIE0

Leitura/

Escrita

R/W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

Valor

Inicial

0

0

0

0

0

0

0

0


OCIE0 – Timer/Counter 0 Output Comp Math INT Enable

Bit de escrita e leitura que permite configurar de acordo com seu valor lógico (0 ou 1):

0 – desabilita INT para modo comparador;
1 – habilita INT para modo comparador.


TOIE0 – Timer/Counter 0 Overflow INT Enable

Bit de escrita e leitura que permite configurar de acordo com seu valor lógico (0 ou 1):

    0 – desabilita INT por Overflow do Timer;
    1 – habilita INT por
    Overflow do Timer.



REGISTRO TCNT0

A seguir, na tabela, é dada a descrição do registro TCNT0 – Timer/Counter Register. Este registro não é utilizado para o controle do TIMER0 propriamente dito. Sua função principal é conter o valor a ser “contado”.


Tabela – Registro TCNT0

Bit

7

6

5

4

3

2

1

0

MSB







LSB

Leitura/

Escrita

R/W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

Valor

Inicial

0

0

0

0

0

0

0

0


UM PEQUENO EXEMPLO

A prática permite que a teoria seja aplica e sendo assim, sem teoria não há prática! Portanto, antes de se dedicar ao teste aqui sugerido, é interessante que você tenha compreendido o uso dos registradores de controle para o TIMER0. Caso algo não tenha ficado muito claro, leia novamente tudo o explicado acima até que fique claro.

Meu exemplo prático sugere a aplicação do timer na contagem de tempo (dãrh) que será usado na troca do estado de um pino de I/O do microcontrolador. Para verificar a troca de estado nada melhor que um LED. Ou seja, vamos preparar um pisca LED microcontrolado! (uhuaua).

Para muitos este tipo de aplicação é algo tão simples que não deveria nem mais ser utilizado. Mas é justamente pela simplicidade da aplicação que a mesma foi aqui valorizada. “Porque?”. Simples. Porque com base nesta “simples” aplicação você poderá implementar outras que julgar, digamos, “mais complexas”.

A figura abaixo demonstra o circuito proposto. Vamos utilizar para tal o microcontrolador AVR ATMEGA8535, porém um outro AVR qualquer também poderia ser utilizado. O código fonte fornecido mais adiante foi preparado para o microcontrolador em questão. O compilador utilizado foi o GCC-AVR (Linux), sendo que seu compatível para Windows® é o WinAVR. O uso de um outro compilador qualquer, como ICC AVR ou CodeVision AVR, requer as devidas adaptações e não é meu intuito discutí-las aqui. Se você escolheu usar um outro compilador, altere o código fonte caso constate alguma incompatibilidade.

O circuito proposto é bem simples e minha sugestão é a sua montagem em uma matriz de contatos ou então em uma placa padrão, pois este servirá apenas para alguns testes. Para os mais "moderninhos" o uso de simuladores também é possível.

O código fonte a ser utilizado em conjunto com o circuito apresentado pode ser visto logo abaixo.

Código Fonte para testes

//----------------------------------------------------------------------------
// Arquivos incluídos no módulo
//----------------------------------------------------------------------------
#define __AVR_ATmega8535__
#include </usr/avr/include/stdio.h>
#include </usr/avr/include/avr/io.h>
#include </usr/avr/include/avr/pgmspace.h>
#include </usr/avr/include/avr/interrupt.h>

//----------------------------------------------------------------------------
// Variáveis globais
//----------------------------------------------------------------------------
unsigned char timecount;

//----------------------------------------------------------------------------
// Interrupção TIMER/COUNTER0 OVERFLOW
//
// Entradas - nenhuma
// Saídas - nenhuma
//----------------------------------------------------------------------------
ISR(SIG_OVERFLOW0){

    TCNT0 = 0x4C; //recarrega com 76 decimal 
    ++timecount; //incrementa contador

    if (timecount == 40){ //se já tem um segundo contado (40 x 25ms = 1000ms)
        timecount = 0;     //zera contador auxiliar   
        PORTB ^= 0x01;     //função OR Exclusivo do valor contido na porta
                            //B com 1 decimal - inverte o estado do pino
    }
}


//----------------------------------------------------------------------------
// Função main (principal)
//
// Entradas - nenhuma
// Saídas - nenhuma
//----------------------------------------------------------------------------

main(void){

    cli(); //Desliga interrupção GLOBAL
    PORTB = 0; //Pull-ups para PORTB ? desligados
    DDRB = (1<<DDB0); //Direção dos pinos de I/O para PORTB ? PB0 = saída
    _NOP(); //não operando, não faz nada, apenas perde um ciclo

    //--------------------------------------------------------------
    // cristal externo a 7.3728MHz, então clock interno a 7.3728MHz
    // 7.3728MHz / 1024 (prescale) = 7.2Khz => T=1/7.2kHz = 138,88us
    // 138,88us x 180 = 25ms
    // então TCNT = 256 - 180 = 76 (4CH)
    //--------------------------------------------------------------

    TCCR0 = 0x05; //clock dividido por 1024
    TCNT0 = 0x4C; //começa em 76 e conta até 256 = contar 180 vezes
    TIMSK = 0x01; //habilita int por Overflow

    PORTB = 0; //zera todos os pinos da porta B
    sei(); //liga interrupção GLOBAL

    while(1) //inicia processamento principal
        ;     //o programa fica parado aqui, aguardando uma int do Timer0

}


Se você já leu a primeira parte fica fácil compreender a o "primeiro pedaço" deste programa onde é configurado o pino de I/O a ser utilizado (PB0). Se você ainda não leu, sugero a leitura para uma melhor compreensão do uso dos registradores de controle para os pinos de I/O.

Antes de detalhar como os registradores de configuração do TIMER0 foram utilizados, vou me ater ao ponto principal deste programa que é a temporização. Esta equivale a 1000 ms (1 segundo). O TIMER0 não tem condições de realizar uma temporização deste tamanho, com o clock selecionado. Neste caso o que deve-se fazer é encontrar o melhor tempo (com a máxima precisão) que pode ser obtido, de acordo com o clock utilizado, para que este seja posteriormente “multiplicado” via software para se obter o tempo desejado. Para isso é necessário configurar o TIMER0, através de seus registradores, de acordo com o clock externo do microcontrolador.

Estou considerando um clock externo de 7.3728MHz para o microcontrolador. Se o TIMER0 for configurado para que a divisão (prescale) seja a máxima, ou seja 1024, e o mesmo for carregardo com o valor a ser contado igual a 180 teremos:

Tempo contado = [(1/7.3728 MHz) x 1024] x 180 = 25 ms

Este tempo de 25 ms não é o desejado, mas com um simples tratamento é possível conseguí-lo. Para isso usei uma variável auxiliar com o único intuito de contar 40 vezes a chamada da interrupção do timer. Quando a variável for igual a 40, tem-se exatos um segundo passado (Tempo Total = 25 ms x 40 = 1 segundo). Com base nestes dados é possível carregar os registradores de acordo com o desejado.

O registrador TCCR0 é carregado então, durante a inicialização do microcontrolador, com o valor 5 decimal, que convertido para binário é igual a b00000101. Isso configura os bits CS02, CS01 e CS00 do referido registrador para que o prescale seja igual a 1024.

No registrador TCNT0 é inserido o valor 4CH (hexadecimal) que equivale ao valor 76 decimal. Você deve estar se perguntando: “Porque 76 e não 180 conforme disposto na equação?”. Se queremos contar 180 vezes através do TIMER0, o valor a ser inserido no registrador TCNT0 deve ser igual ao valor que desejamos contar, subtraído do valor máximo admitido no registrador que é 255. Veja:

TCNT0 = 255 – valor desejado = 256 – 180 = 76

No registrador TIMSK é inserido o valor um decimal (b00000001) com o único intuito de habilitar a interrupção por overflow do TIMER0.

Você deve ter notado a presença de duas funções: cli() e sei(). Estas funções tem como objetivo desabilitar e habilitar, respectivamente, o bit de controle para interrupção GLOBAL. Assim, enquanto tudo está sendo configurado não é permitido que nenhuma interrupção ocorra.

A troca de estado do pino PB0 é feita dentro da função de tratamento da interrupção do TIMER0, sempre que a variável timercount for igual a 40. Quando esta situação for verdadeira, o pino PB0 tem seu estado invertido através de uma operação lógica XOR (Ou exclusivo) entre o valor contido na porta e o literário “1” (PORTB ^= 1 que corresponde a PORTB = PORTB ^ 1).

Um outro detalhe importante no programa é a forma como a função principal é mantida “rodando”. A função while(1) seguida do “;” fica tratando “nada” constantemente. Qualquer outro processamento desejado deverá ser inserido no lugar do ponto e virgula (dentro do laço while). Assim, quando necessário o timer solicitará ao microcontrolador que interrompa o processamento normal, desvie para a subrotina de tratamento da interrupção do timer, execute o processamento nela contida e em seguida retorne para o ponto onde o programa foi interrompido. No programa isso é feito a cada 25ms.


CONCLUSÃO

Nesta parte demonstrei o uso do TIMER0 do microcontrolador AVR através dos seus registradores.Você perceberá que a medida avançamos, algumas novidades serão introduzidas. Nesta, além do uso dos registradores de controle você viu também como é possível usar uma interrupção em conjunto com o TIMER0 e novas formas de “uso” na linguagem escolhida (Linguagem C) também foram introduzidas.

Abaixo alguns documentos que eu recomendo a leitura:

ATMEGA8535 - http://www.atmel.com/dyn/resources/prod_documents/doc2502.pdf
ATMEGA16 - http://www.atmel.com/dyn/resources/prod_documents/doc2466.pdf



Este artigo foi publicado, com minha autorização, na revista Eletrônica Total nrº 124 de Julho de 2007.



Copyright deste conteúdo reservado para Márcio José Soares e protegido pela Lei de Direitos Autorais LEI N° 9.610, de 19 de Fevereiro de 1998. É estritamente proibida a reprodução total ou parcial do conteúdo desta página em outros pontos da internet, livros ou outros tipos de publicações comerciais ou não, sem a prévia autorização por escrito do autor.