Programando AVR na IDE e placa Arduino - Portas

 

A maioria dos Arduinos, principalmente os primeiros lançamentos usam um micro controlador da linha ATMega em suas placas. O que a Arduino fez foi deixar a placa com pinos de fácil acesso, criar um bootloader onde é possível carregar o firmware de forma fácil via USB-Serial e criar uma interface IDE com bibliotecas de fácil entendimento. Mas lá no fundo de todo processo o que está rodando são programações AVR. Na programação AVR fora do Arduino,  o processo de carga de firmware é feito via serial SPI.

Vou mostrar aqui em três, quatro ou até mais postagens do básico de uma programação em AVR, será  aquele norte para qualquer um que queira se aventurar e depois poder aprofundar sozinho, porém usando uma placa e IDE Arduino para ficar um pouco mais fácil.

Mas porque programar em AVR e não em Arduino? A programação em AVR é imensamente mais flexível e abrangente que Arduino, além de outras vantagens como velocidades de trabalho, só deixa a desejar mesmo por não ser muito didática.

Todas as postagens irão usar uma placa Arduino UNO que utiliza micro controlador ATMega 328P e a própria IDE Arduino.

Com a IDE Arduino instalada, primeiramente não usaremos as funções padrão void setup() e void loop().


Segundo detalhe que quando programamos em AVR fora da IDE Arduino, é sempre necessário definir as bibliotecas como essas abaixo que são mais usadas. Porém pela IDE Arduino não faz diferença entre usar ou não já que o processo está embutido na própria IDE, nos exemplos não usarei mas fica o alerta.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

Um guia de pinagem como este abaixo ajuda bastante, já que a pinagem do CI ATMega 328P é diferente do Arduino. Além disso o datasheet do CI é de extrema importancia.




Com tudo em mãos vamos primeiro acender o led embutido no proprio Arduino que trabalha junto ao pino 13 dele, o pino 13 conforme a figura dos pinos acima é o PB5. O AT 328P possui três blocos de portas PBx, PCx e PDx que são controlados por registradores de 8 bits. Na pagina 100 do datasheet temos a disposição dos bits de portas desses registradores conforme abaixo


O que interessa no primeiro momento são apenas os registrador DDRB, PORTB e seus bits que vão de PORTB0 até PORTB7 e DDB0 a DDB7. Detalhe, o compilador AVR entende tanto PORTBx ou de forma abreviada PBx, portanto podemos usar por exemplo PB5 no lugar de PORTB5.

Na IDE do Arduino cole o código abaixo e carregue ele no Arduino (não esqueça de deletar as linhas que estiverem lá):

int main(void) {
  DDRB |= (1 << DDB5);
  PORTB |= (1 << PB5);
}

O led irá acender. Para apagar use o código abaixo:

int main(void) {
  DDRB |= (1 << DDB5);
  PORTB &= ~(1 << PB5);
}
O que mudou foi apenas a terceira linha.

Entendendo o código, na segunda linha temos o registrador DDRB (Data Direction Register), ele controla a direção das portas B como entrada e saída, por padrão o micro controlador liga configurando as portas como entrada, então temos que configurar ou setar o bit correspondente com 1 para a porta passar a ser uma saída (com 0 será entrada).

Após DDRB temos "|= (1 << DDB5);" onde "|=" é um operador OR e "(1 << DDB5)" está fazendo um bit shift com 1. O DDB5 indica o sexto bit do registrador e pode também ser usado apenas o numeral 5, ou em binário 0b00000101 ou em hex 0x05. Assim por exemplo:
  PORTB &= ~(1 << 0b00000101);
Mas porque usar OR e bit shift? Imagina um código onde esse registrador DDRB já tenha algumas portas setadas com 1 (entrada), se usar por exemplo "DDRB = (1 << PB5);", ou seja atribuição no lugar de OR, ele vai setar as outras portas como 0, já que foi definido apenas o sexto bit. Para um melhor entendimento visual do OR imaginando um terceiro bit sendo setado:
0 0 1 1    operando1
0 1 0 0    operando2
-------
0 1 1 1    resultado
Veja que o primeiro, segundo e ultimo bit continuaram com o mesmo valor, apenas o terceiro foi alterado.

Se for usado atribuição "=" ficaria:
0 0 1 1    operando1
0 1 0 0    operando2
-------
0 1 0 0    resultado
Ou seja as duas primeiras portas voltariam a ser 0.

Já o bit shift ajuda na visualização da programação, fica fácil saber que é o DDB5 ou o sexto bit que está sendo setado.

A linha "PORTB |= (1 << PB5);" é a que realmente seta (ou liga) a porta, neste caso recebendo 1 liga e 0 desliga, mesmo entendimento do DDRB sendo setado. Para o segundo código foi usado "PORTB &= ~(0 << PB5);" para desligar a porta, se fosse usado o OR a porta não iria ser setada com 0, já que em OR 1 e 0 é igual a 1, então é usado AND "|=" porém com inversão dos bits usando NOT "~". Invertendo os bits, os outros 7 bits do registrador PORTB não sofre alterações, todos os outros bits ficarão como 1 e apenas o que precisa ser setado ficará como 0 na operação, se já tiver algum outro setado ele permanecerá em 1. Para um melhor entendimento visual do AND imaginando um terceiro bit sendo setado:
0 1 1 1    operando1
1 0 1 1    operando2
-------
0 0 1 1    resultado
Veja que o primeiro, segundo e ultimo bit continuaram com o mesmo valor, apenas o terceiro foi alterado.

Um detalhe, se tentar rodar o código como abaixo após o primeiro código de exemplo acima que liga a porta, colocando 0 no bit shift da terceira linha, o led não irá acender, mas não por causa do código e sim porque o micro controlador sempre liga com os registradores em valor 0. Este exemplo é apenas porque muita gente que está começando sempre erra tentando usar atribuição "=" ou até OR com 0. Isso causa a falsa impressão que o código funcionou.
int main(void) {
  DDRB |= (1 << DDB5);
  PORTB = (0 << PB5);
}
O entendimento dos registradores acima será o mesmo para outros registradores nas próximas postagens, importante entender muito bem esse assunto.

Abaixo outro código acendendo e apagando o led a cada segundo, neste caso foi apenas adicionado um loop infinto while(1) e a função _delay_ms() que para o processamento durante 1seg a cada mudança de estado da porta. Futuramente na postagem sobre Timers veremos como fazer o mesmo sem parar o processamento.
int main(void) {
  DDRB |= (1 << DDB5);
  while (1) {
    PORTB |= (1 << PB5);
    _delay_ms(1000);
    PORTB &= ~(1 << PB5);
    _delay_ms(1000);
  }
}
Por fim vamos ver um exemplo usando a porta Arduino 2 (PD2) como entrada. Para esse teste será preciso um hardware adicional além do Arduino Uno, com um resistor com valor próximo de 1kohm e uma chave push-button conforme abaixo:

Carregue o código e ao pressionar o botão o led irá acender:
int main(void) {
  DDRB |= (1 << DDB5);
  DDRD |= (0 << DDD2);
  PORTB |= (0 << PB5);

  while (1) {
    while (PIND & (1 << PD2)) {
      PORTB |= (1 << PB5);
    }
    PORTB &= ~(1 << PB5);
  }
}
Neste código temos um loop dentro do loop infinito  sendo controlado pelo valor que o registrador PIND e sua porta PD2 irá receber, caso seja 1 (botão pressionado) ele entra no loop e acende o led, caso 0 ele fica fora do loop e o led apaga. De novo o entendimento do registrador PIND é o mesmo do explicado acima e usando AND apenas para "pegar", ler ou testar o valor lido apenas do bit PB5. Neste caso não se usa NOT. Para um melhor entendimento visual deste AND imaginando o terceiro bit sendo testado:
0 1 0 1    operando1
0 1 0 0    operando2
-------
0 1 0 0    resultado
Veja que apesar do primeiro bit também estar em 1, com um código construído da forma acima você consegue testar apenas um bit especifico.

Referência:
Códigos de exemplo do livro AVR Programming:


Comentários