CONFIGURANDO CORRETAMENTE OS REGISTRADORES DOS MICROCONTROLADORES AVR – PARTE 1

USO DOS REGISTROS PORTX, DDRX E PINX 



CONTROLE DE I/O NOS AVR

Os microcontroladores AVR possuem registradores dedicados para a configuração dos seus pinos de I/O. A descrição destes três registradores pode ser vista a seguir:

- Registrador PORTxn (dados – leitura/escrita)
- Registrador DDRxn (controle de direção dos pinos de I/O – escrita/leitura);
- Registrador PINxn (leitura dos pinos de I/O – apenas leitura).


Na figura abaixo mostro uma estrutura genérica de um pino de I/O dos microcontroladores AVR.

Observando atentamente a figura acima você notará a presença dos três registradores descritos, representados na figura com apenas 1 bit (o controle dos pinos de I/O no AVR é feito sempre de maneira independente). WDx e RDx representam um bit do registrador DDRxn, enquanto WPx e RRx um bit do registrador PORTxn e RPx um bit do registrador PINxn. É preciso compreender que WDx, RDx, WPx, RRx, e RPx são comuns a uma mesma porta, enquanto clkI/O, SLEEP e PUD são comuns a todas as portas.

Na momenclatura usada, o “x” presente na descrição dos registradores indicará a porta que se deseja escrever, ler ou configurar e este pode ser: “A”, “B”, “C”, “D”, etc, dependendo do número de portas presentes na versão do microcontrolador utilizado. Já o “n” representa o número do bit da porta e este pode estar dentro da faixa de “0” a “7”.

Se, por exemplo, você estiver utilizando o microcontrolador AVR AT90S8535, ou seu sucessor ATMEGA8535, terá ao seu dispor quatro portas de oito bits cada (“A”, “B”, “C” e “D”) num total de 32 pinos de I/O. Em uma versão com menos pinos de I/O como o ATtiny2313 temos disponível apenas as portas “A”, “B” e “D”, sendo que a porta “A” possui apenas três bits (PA0 a PA2), a porta “B” tem oito bits (PB0 a PB7) e a porta “D” com sete bits (PD0 a PD6), perfazendo um total de 18 pinos de I/O. Em um caso mais extremo, como quando se usa o ATMEGA128 dispomos de 53 pinos de I/O divididos entre as portas “A”, “B”, “C”, “D”, “E”, “F” e “G” (lembrando que nem todas as portas podem se apresentar de maneira completa, ou seja, com oito bits).

De qualquer maneira, seja qual for o microcontrolador AVR selecionado (com um número pequeno ou grande de I/Os) a maneira como a porta será utilizado é sempre a mesma, tanto para o ATtiny2313 quanto para o ATMEGA128. 

A seguir falarei de cada um destes registradores e também passarei alguns exemplos de uso, usando para tal a Linguagem de Programação C.

Obs.: A escolha da Linguagem C deve-se a alguns pontos positivos como: o fato de podermos contar com compiladores gratuitos, tanto para Linux (GCC-AVR) quanto para Windows (WinAVR); devido a muitas empresas buscarem por profissionais com conhecimentos nesta Linguagem; e o mais relevante de todos que é esta a minha Linguagem de Programação preferida! ;-) .


REGISTRADOR PORTX

O registrador PORTx permite o controle dos dados que são enviados ou recebidos (escrita e leitura) através de uma determinada porta de I/O do microcontrolador AVR. É através deste registrador que o microcontrolador “acessa o mundo exterior”. A tabela abaixo mostra o registrador PORTB usado no controle de dados para a porta “B”.

Tabela – Registro PORTB

BIT

Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

PORTB7

PORTB7

PORTB7

PORTB7

PORTB7

PORTB7

PORTB7

PORTB7

Leitura/

Escrita

W/R

W/R

W/R

W/R

W/R

W/R

W/R

W/R

Valor

Inicial

0

0

0

0

0

0

0

0

W – Write (escrita)
R – Read (leitura)

Obs.: Este registrador pode ser lido ou escrito

Se um pino de I/O for configurado como “saída”, podemos alterar o estado de tal pino (nível lógico “1” ou “0”) controlando cargas externas conectadas ao mesmo. A figura abaixo demonstra dois pequenos exemplos para controle de cargas com consumo de corrente de até 400 mA, feito através de transistores.



Mas se um pino de I/O for configurado como “entrada” será possível ler o estado deste pino. É desta forma que poderemos “ler”, por exemplo, o estado de uma chave (ligada ou desligada) quando a mesma estiver conectada a um pino de I/O do microcontrolador. Isso está demonstrado na figura abaixo.



REGISTRADOR DDRX

O registrador DDRx permite configurar a direção (entrada ou saída) dos I/Os de uma porta. A tabela abaixo apresenta o registro DDRB usado para configurar a direção dos pinos de I/O da porta B.

Tabela – Registro DDRB

BIT

Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

DDRB7

DDRB7

DDRB7

DDRB7

DDRB7

DDRB7

DDRB7

DDRB7

Leitura/

Escrita

W/R

W/R

W/R

W/R

W/R

W/R

W/R

W/R

Valor

Inicial

0

0

0

0

0

0

0

0

W – Write (escrita)
R – Read (leitura)

Obs.: Este registrador pode ser lido ou escrito

Ao tornar um bit igual a “1” (nível lógico HI) no registrador DDRxn, configuramos o respectivo pino de I/O como saída. Se fazemos um bit igual a “0” (nível lógico LOW) configuramos o pino de I/O como entrada.

Um detalhe muito interessante sobre os pinos de I/O dos microcontroladores AVR é a presença de resistores de pull-up internos para cada um dos pinos. Estes resistores podem ser conectados e desconectados de maneira independente, diferente de muitos outros microcontroladores que só permitem a conexão de todos os resistores presentes em uma mesma porta.


Nota: Para saber mais a respeito dos resistores de pull-up dos microcontroladores AVR, consulte o datasheet do microcontrolador a ser utilizado na seção Eletrical Characteristics.

Para conectar um resistor de pull-up interno é necessário que o pino de I/O seja configurado como “entrada” através do registrador DDRxn e o valor lógico presente no pino no momento desta configuração, inserido através do registrador PORTxn, seja igual a “1”. Ou seja, usando os registradores PORTxn e DDRxn em conjunto é possível conectar ou desconectar os resistores de pull-up internos do microcontrolador. Veja a tabela abaixo.

Tabela – Seleção dos resistores de pull-up

DDRxn

PORTxn

Configuração

Pull-up

Comentário

0

0

Entrada

Desligado

Alta impedância (tri-state)

0

1

Entrada

Ligado

Fornecimento de corrente por PAxn quando o pino é levado ao estado lógico “0”

1

0

Saída

Desligado

Saída Push-pull “0”

1

1

Saída

Desligado

Saída Push-pull “1”

Usando os resistores de pull-up internos é possível reduzir os custos de um projeto, não somente pela eliminação direta de resistores externos mas também pela provável redução no tamanho da placa de circuito impresso e conseqüentemente de seus custos de confecção.


REGISTRADOR PINX

O registrador PINx é um registrador que permite acessar de forma direta o valor inserido na porta (pinos de I/O), independentemente da configuração selecionada para esta (entrada ou saída). A tabela abaixo mostra o registrador PINB, como exemplo.

Tabela – Registro PINB

BIT

Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

PINB7

PINB7

PINB7

PINB7

PINB7

PINB7

PINB7

PINB7

Leitura/

Escrita

R

R

R

R

R

R

R

R

Valor

Inicial

0

0

0

0

0

0

0

0

R – Read (leitura)

Obs.: Este registrador pode apenas ser lido



EXEMPLOS DE USO

Agora que você já foi devidamente apresentado aos registradores de controle para os pinos de I/O de um AVR, nada melhor que alguns pequenos exemplos de configuração e uso. Vamos começar com a configuração. Veja o segmento de um código fonte hipotético à seguir:


...

unsigned char i;

...

/* Define os resistores de pull-ups e configura os pinos PB0, PB1, PB6 e PB7 com nível lógico 1 */
PORTB = (1<<PB7)|(1<<PB6);

/* Define as direções para os mesmos pinos de I/O */
DDRB = (1<<DDB5)|(1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0);

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

/*Lê a porta – todos os pinos*/
i = PINB;

...


Na primeira linha deste segmento de código foi criado a variável i” do tipo char”. Esta variável tem o tamanho de um byte (oito bits). Sempre que você lidar com portas de I/O é interessante usar variáveis deste tipo. Se precisar de maiores informações sobre o tamanho, em bytes, dos tipos de variáveis você poderá pesquisar sobre o assunto no manual do compilador.

Em seguida, os bits 6 e 7 do registrador PORTB são levados ao nível lógico “1”, mantendo os demais (0, 1, 2, 3, 4 e 5) em zero. Para quem tem pouco conhecimento na Linguagem C a lógica (y << x) pode parecer a primeira vista estranha. Trata-se de um deslocamento à esquerda (shift) de “x” bits do valor “y”.

Se você pesquisar nos arquivos de diretrizes de pré-compilação no seu compilador (arquivos de inclusão) irá descobrir que PB0 está definido como “0”, PB1 como “1” e assim por diante até PB7 com o valor “7”.

Então, se estamos fazendo um deslocamento de “7” e “6” bits em dois valores distintos e iguais a “1” temos:


Valor atual = 00000001 (binário)

Valor após deslocamento de seis bits = 01000000 (binário)

Valor após deslocamento de sete bits = 10000000 (binário)


Ao “traduzirmos” os deslocamentos teremos a seguinte situação na linha descrita:


Em notação binária:

PORTB = b10000000 | b01000000;

Então será inserido no registrador PORTB o resultado da operação lógica “OR” (OU) entre os dois valores obtidos nos deslocamentos e este valor será igual a b11000000.

Claro que você pode inserir diretamente o valor desejado no registrador, tanto em binário quanto em hexadecimal ou mesmo decimal. A notação não importa, desde que o valor inserido reflita a configuração desejada. O uso de deslocamentos e operações lógicas deixa mais elegante e inteligível o programa.

Na linha seguinte do segmento de código, o registrador DDRB é preenchido de maneira a configurar os pinos de I/O 0, 1, 2, 3, 4 e 5 como saídas, deixando o restante (6 e 7) como entrada. O valor inserido no registrador DDRB é b00111111.

Se você voltar a teoria, perceberá que os pinos de I/O 6 e 7 (PB6 e PB7) terão seus resistores de pull-up conectados, mantendo os resistores dos outros pinos da porta desconectados.

Uma instrução NOP (não operando) foi inserida para permitir a sincronização do estado atual da porta com o clock interno do microcontrolador. Esta instrução não faz nada, apenas perde um ciclo de máquina no AVR. Na última linha do segmento de código uma representação dos valores presentes nos pinos de I/O da porta “B” é lida através do registrador PINB para a variável “i”. Se você leu atentamente tudo o que foi explicado até aqui, conseguirá chegar ao valor b11000000 para a variável.

Os bits seis e sete da porta, apesar de configurados como entrada, possuem resistores de pull-up internos conectados aos mesmos garantindo nível lógico alto nestes pinos. Já o restante dos bits foram configurados como saída e receberam nível lógico “0” (valor inicial dos bits), e assim, durante a leitura apresentarão este mesmo nível lógico.

No segundo exemplo demonstrarei como alterar o estado de um pino usando o registrador PORTxn. Vamos supor que você deseje “ligar” (nível lógico “1”) o pino PB0 da porta “B”, que foi configurado como saída. Uma sugestão de como fazê-lo pode ser vista no segmento de código a seguir.


...

i = b00000001; //dado assume valor 1

PORTB = PORTB | i; //lógica ou entre PORTB e a variável

...

A variável “i” tem seu valor alterado e é levada até a porta “B” através do registrador PORTB com o auxílio da lógica “OR” (OU) entre o registrador e a variável. É preciso compreender que durante uma operação lógica deste tipo todos os bits do registrador são afetados. Tanto o registrador quanto a variável possuem oito bits de tamanho (um byte).

Agora, se você deseja “desligar” o pino PB0, basta então fazer com que o pino receba o nível lógico “0” através do registrador PORTB. Veja uma sugestão no segmento de código a seguir.


...

i = b11111110; //dado assume valor 1

PORTB = PORTB & i; //lógica ou entre PORTB e dado

...

Este segmento de código usa a lógica “AND” (E) para obter o resultado desejado.

Observe que em ambos os casos nenhum pino, a não ser o desejado, será alterado. Você deve lembrar-se do valor obtido através do primeiro segmento de código demonstrado. Vamos supor que os dois segmentos de código apresentados a seguir fazem parte do mesmo programa. Durante a leitura da porta através do registrador PINB, obtivemos o valor b11000000. Se realizarmos uma lógica “OU” entre o valor b11000000 e b00000001 (segundo segmento de código) o resultado obtido será b11000001. Agora o bit “0” do registrador PORTB possui nível lógico “1”. Este nível lógico será refletido no pino de I/O PB0, levando o mesmo também ao nível lógico “1”.

Em seguida é feita uma lógica “E” entre o valor atual do registrador PORTB, b11000001 (obtido com o segundo segmento de código) e o novo valor da variável “i”, b11111110. Como resultado obtemos b11000000.

Como você pode perceber apenas o bit “0” do registrador PORTB e, conseqüentemente, o pino PB0 foram alterados.

Agora vamos supor que você deseje verificar se uma determinada chave, ligada ao pino PB7, foi acionada. E vamos supor ainda que este pino está com seu resistor de pull-up interno ligado. O segmento de código a seguir demonstra como esta leitura pode ser feita:


...

if (!(PINB & b10000000)){ //lógica “E” 

    //tecla pressionada, trata

}

else{

    //tecla não pressionada

}
...


Este pequeno exemplo demonstra a lógica AND entre o valor presente no registrador PINB (que reflete o valor presente na porta “B”) e o valor b10000000. Se a chave não estiver pressionada o pino PB7 receberá VCC através do resistor de pull-up interno e, portanto, o bit 7 do registrador PINB conterá o nível lógico “1”. Ao fazer a operação AND o resultado será b1000000. O teste é então negado através do operador “!”. Desta forma, o resultado para a instrução “if” será falsa, indicando que a tecla não foi pressionada. Porém se a mesma for pressionada o registrador apresentará o valor b0xxxxxxx (o x indica “não importa”). Realizando novamente a lógica “AND” entre o registrador e o valor b10000000 o resultado será zero. Como o teste é negativo, o resultado será igual a “1” e o resultado para a instrução “if” será verdadeiro indicando que a tecla está pressionada.

Muitas outras lógicas poderiam ser utilizadas para se obter os resultados aqui demonstrados. Tudo depende do estilo do programador. Tenho certeza que você saberá encontrar o seu (se é que já não o fez). Você também não deve se preocupar se as instruções, operadores ou outra coisa qualquer nos segmentos de códigos apresentados não lhe parece familiar. Procure apenas se ater a lógica apresentada e aos resultados obtidos. O resto virá com o tempo.


CONCLUSÃO

Lidar corretamente com microcontroladores não é uma tarefa difícil como muitos pensam. Após um certo tempo você se acostumará com os mesmos e passará a utilizá-los em seus projetos sem maiores problemas. O importante é dedicar-se ao estudo dos mesmos, buscando conhecer não apenas uma Linguagem de Programação com seus "comandinhos mágicos", mas principalmente a estrutura do microcontrolador em questão!



Este artigo foi publicado, com minha autorização, na revista Eletrônica Total nrº 122 de Março/Abril 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.