Você está na página 1de 92

Arduino e ATmega328P

Visão Geral

O Arduino é uma versátil plataforma de prototipagem eletrônica, de hardware


e software aberto, de baixo custo e muito fácil de usar. A placa Arduino é muito
parecida com um computador de pequeno porte, sendo composta pelo
microcontrolador, memória RAM, memória secundária (memória flash), clock e
comunicação USB entre outras funcionalidades. Na Figura 1 temos o modelo mais
popular dos Arduinos que é o Uno R3, porém, para os projetos deste livro qualquer
outro modelo de Arduino, que utilize o microcontrolador ATmega328P, pode ser usado
sem restrições.

Figura 1: Arduino UNO R3

O Arduino Uno R3 apresenta 14 pinos que podem ser utilizados como entradas
ou saídas digitais (pinos 0 a 13), sendo que os pinos 3, 5, 6, 9, 10 e 11 também podem
utilizar Pulse Width Modulation (PWM) para gerar um conjunto de valores inteiros
entre 0 e 255. Os pinos A0 a A5 correspondem às entradas analógicas, que recebem
uma tensão entre 0 e 5V e o produzem em uma escala de 0 a 1023. Também temos os
pinos 3,3V, 5V e GND (Terra) permitem alimentar os componentes dos circuitos
conectados ao Arduino. Possui um microprocessador ATmega328P, com uma memória
RAM de 2KB, memória Flash de 32KB e clock de 16MHz.

O Arduino é construído tendo como elemento fundamental o microcontrolador


ATmega328P, desta forma, iremos inicialmente abordar a estrutura e o conjunto de
instruções deste microcontrolador. O ATmega328P, cujo diagrama de blocos de sua
arquitetura é mostrado na Figura 2, é um microcontrolador AVR RISC de 8 bits
apresentando 32 kB de memória flash, EEPROM de 1 kB, SRAM de 2 kB, 23 linhas de
entrada e saída, 32 registradores de uso geral, programação serial através de USART,
interface serial 2-wire, porta serial SPI, conversor analógico de 10 bits e seis ou oito
canais (dependendo do encapsulamento utilizado). O microcontrolador ATmega328P
pode operar com tensões entre 1,8 e 5,5 volts.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 1


Figura 2: Diagrama de Blocos da Arquitetura AVR (Retirada do datasheet do fabricante)

Os 32 registradores de uso geral são identificados como R0, R1, R2 até R31,
conforme ilustra a Figura 3.

Figura 3: Registradores de Uso Geral (Retirada do datasheet do fabricante)


Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 2
Na Figura 4 apresentamos o diagrama de blocos do ATmega328P destacando as
portas de Entrada e Saída, Temporizadores/Contadores, Conversor Analógico Digital,
além dos protocolos suportados.

Figura 4: Diagrama de Blocos do ATmega328P (Retirada do datasheet do fabricante)

Na mesma Figura 4 podemos observar que o ATmega328P apresenta três


conjuntos de portas, designadas como PORT D, PORT B e PORT C respectivamente.
Cada porta é controlada por três registradores de entrada/saída, sendo que o
registrador DDR determina o modo de operação de cada um dos pinos (entrada ou
saída). Os registradores DDR e PORT podem ser utilizados para leitura e escrita,
enquanto os registradores PIN correspondem o estado dos pinos de entrada e, desta
forma, apenas podem ser lidos. Desta forma, temos que:

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 3


• PORT D (PD0 até PD7) estão ligados aos pinos digitais 0 a 7 do Arduino e os
seguintes registradores estão associados: DDRD (Port D Data Direction
Register), PORTD (Port D Data Register) e PIND (Port D Input Pins Register).

PORT D - Registradores: DDRD, PORTD e PIND


PD7 PD6 PD5 PD4 PD3 PD2 PD1 PD0
Pinos Digitais do Arduino:
7 6 5 4 3 2 1 0

• PORT B (PB0 até PB5) correspondem aos pinos digitais de 8 a 13 no Arduino. Os


pinos PB6 e PB7 estão ligados aos pinos do cristal e não estão disponíveis. Os
registradores associados são: DDRB (Port B Data Direction Register), PORTB
(Port B Data Register) e PINB (Port B Input Pins Register).

PORT B - Registradores: DDRB, PORTB e PINB


PB5 PB4 PB3 PB2 PB1 PB0
Pinos Digitais do Arduino:
13 12 11 10 9 8

• PORT C (PC0 até PC5) correspondem aos pinos analógicos A0 a A5 do Arduino,


enquanto o pino PC6 está disponível apenas no Arduino Mini. Os registradores
DDRC (Port C Data Direction Register), PORTC (Port C Data Register) e PINC
(Port C Input Pins Register) estão associados a esta porta.

PORT C - Registradores: DDRC, PORTC e PINC


PC5 PC4 PC3 PC2 PC1 PC0
Pinos Digitais do Arduino:
19 18 17 16 15 14
Pinos Analógicos do Arduino:
A5 A4 A3 A2 A1 A0

Cada bit desses registradores corresponde a um único pino, por exemplo, o bit
mais baixo dos registradores DDRB, PORTB e PINB se refere exclusivamente ao pino
PB0, isto é, ao pino digital 8 do Arduino.

Além dos registradores de uso geral e de entrada/saída temos também o


registrador de estado (SREG).

I T H S V N Z C SREG
7 6 5 4 3 2 1 0

Onde cada bit representa um Flag:

C - Carry Z - Zero N - Negative V - Overflow


S - Sign H - Half Carry T - Bit Copy I - Interrupt

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 4


Na Figura 5 temos o diagrama do Arduino Uno R3, onde podemos ver a
correlação entre os pinos do Arduino em relação aos pinos do ATmega328P. Como
exemplo, podemos notar que o Pino 13 do Arduino é o Pino PB5 do ATmega328P.

Figura 5: Placa de Prototipagem Arduino Uno R3


(Fonte: www.circuito.io/blog/arduino-uno-pinout/)

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 5


Circuito Básico

O microcontrolador ATmega328P necessita de alguns poucos componentes


eletrônicos externos para funcionar, conforme podemos observar na Figura 6.

Figura 6: Circuito Básico para Funcionamento do ATmega328P

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 6


Linguagens e Ferramentas para Programação do ATmega328P

Tradicionalmente usamos a Arduino IDE (Figura 7), disponível gratuitamente


em www.arduino.cc, para realizar o desenvolvido de programas para o Arduino. Este
ambiente de desenvolvimento usa o framework Wiring para facilitar o acesso às portas
e demais recursos do ATmega328P, porém, pode ser utilizado também para
desenvolver programas diretamente em Linguagem C/C++ e Assembly.

Figura 7: Arduino IDE

A programação também pode ser realizada diretamente através de um


compilador da Linguagem C para a família AVR de microcontroladores. Na plataforma
Windows temos o WinAVR (http://winavr.sourceforge.net/) que consiste em um
conjunto de ferramentas gratuitas e de código-fonte aberta que permite o
desenvolvimento para a família de microcontroladores AVR. O WinAVR é uma
portabilidade do avr-gcc disponível para a maioria das distribuições Linux. O próprio
ambiente de desenvolvimento do Arduino usa o avr-gcc para compilar os programas
desenvolvidos.

Além destas opções, também é possível realizar a programação do


ATmega328P usando linguagem de baixo nível (Assembly) através de um montador
(Assembler). Dentre algumas opções de montadores, podemos citar o AVRASM2
(Microchip Studio), o AVRA e o próprio AVR-AS que está disponível no WinAvr.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 7


Visão Geral da Linguagem C
A linguagem de programação C está presente em praticamente todas as
arquiteturas de microprocessadores e microcontroladores, foi criada em 1972 por
Dennis Ritchie nos laboratórios AT&T Bell Labs, visando o desenvolvimento do sistema
operacional Unix.

É uma linguagem compilada, estruturada e apresenta tipos de dados estáticos,


ou seja, qualquer variável precisa ser declarada antes da sua utilização. Suas principais
características estão relacionadas ao desempenho das aplicações criadas e ao acesso
em baixo nível ao hardware sendo, desta forma, ideal na programação de sistemas
operacionais, drivers e sistemas embarcados.

Os programas apresentados neste tópico podem ser desenvolvidos em


compiladores online como o Programiz, disponível em https://www.programiz.com/c-
programming/online-compiler/ ou o replit que pode ser acessado pelo endereço
https://replit.com/languages/c.

Conforme mostra o exemplo a seguir, a estrutura de um programa apresenta


blocos de instruções e funções delimitados por { chaves }. As instruções devem ser
finalizadas com ponto-e-vírgula e todo programa inicia a partir da função main, sendo
que a linguagem diferencia letras maiúsculas e minúsculas.

#include <stdio.h>

int main (void) {


printf("Ola\n");
}

Além disso, a diretiva #include permite incluir as inúmeras bibliotecas de


funções que estão disponíveis para a Linguagem C. No exemplo, observe que usamos a
biblioteca padrão stdio.h, pois, ela contém a função printf que é responsável pela
exibição de dados na tela do computador.

Variáveis e Operadores

Uma variável, atributo ou até mesmo o valor de retorno de uma função, deve
ser previamente declarado devendo apresentar um tipo de dado. A seguir são
apresentados os principais tipos de dado existentes na Linguagem C:

Tipo de dado Conteúdo Tamanho Faixa de valores


char Caractere 1 byte -128 a 127
unsigned char Caractere sem sinal 1 byte 0 a 255
short int Inteiro 2 bytes -32.768 a 32.767
unsigned short int Inteiro 2 bytes 0 a 65.535

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 8


Tipo de dado Conteúdo Tamanho Faixa de valores
-2.147.483.648 a
int Inteiro 4 bytes
2.147.483.647
unsigned int Inteiro sem sinal 4 bytes 0 a 4.294.967.295
float Número real 4 bytes 3.4x10-38 a 3.4x1038
double Número real duplo 8 1.7x10-308 a 1.7x10308

A declaração de uma variável deve apresentar o seu tipo de dado, o nome que
a variável terá e, opcionalmente, o seu valor inicial. Por exemplo, no seguinte
programa declaramos duas variáveis inteiras.

#include <stdio.h>

int main (void) {


int x = 10;
int y = x + 2;
printf("Valor de y: %d\n", y);
}

Na tabela a seguir temos a simbologia para os principais operadores de


atribuição, aritméticos, relacionais e lógicos adotados pela Linguagem C.

Representação Representação
Operador Operador
Simbólica Simbólica
Atribuição = Maior >
Adição + Maior ou igual >=
Subtração - Menor <
Multiplicação * Menor ou igual <=
Divisão / Igual a ==
Resto % Diferente de !=
Condicional ?: E (And) &&
Incremento ++ Ou (Or) ||
Decremento -- Não (Not) !

Além disso, quando trabalhamos em projetos de sistemas embarcados os


operadores bit a bit (bitwise operators) são essenciais.

Representação Representação
Operador Operador
Simbólica Simbólica
E (And) & Ou (Or) |
Ou Exclusivo (Xor) ^ Não (Not) !
Deslocamento a Deslocamento a
<< >>
Esquerda Direita

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 9


Observe este exemplo:

#include <stdio.h>

int main (void) {


unsigned char x = 3;
x = (x << 2);
printf("Valor de x: %d\n", x);
}

Vamos considerar o valor 3 para a variável x e, em seguida, aplicar o


deslocamento dos bits. Como a operação é realizada sobre os bits, devemos considerar
o valor binário de 3:

27 26 25 24 23 22 21 20
0 0 0 0 0 0 1 1

Ao aplicar o deslocamento de dois bits a esquerda, temos:

27 26 25 24 23 22 21 20
0 0 0 0 1 1 0 0

Obtendo, desta forma, o valor 12 que será exibido através da função printf.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 10


Decisão e Repetição

As instruções de decisão (seleção) permitem determinar a execução ou não de


determinado bloco de instruções, na Linguagem C temos disponíveis as instruções if-
else e switch-case. A estrutura if-else realiza a avaliação da expressão lógica fornecida
na condição, caso ela seja verdadeira o bloco de instruções associado a instrução if é
executado. Caso contrário, o bloco associado à instrução else, que é opcional, será
executado. Vamos ilustrar o uso de if-else:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main (void) {


srand(time(NULL));
unsigned char num = (rand() % 100);
if (num % 2 == 0) {
printf("%d e par.\n", num);
}
else {
printf("%d e impar.\n", num);
}
}

Observe que sorteamos um número e armazenamos na variável num. Em


seguida, determinamos se o número armazenado é par ou ímpar.

No exemplo seguinte, vamos ilustrar o uso da instrução switch-case. Neste


caso, vamos sortear um número e, caso ele seja igual a 1, 2 ou 3, exibimos o valor por
extenso. Para isso, a instrução switch-case recebe como parâmetro a variável num, ou
seja, a variável que se deseja testar. Em seguida, cada cláusula case indica o valor
necessário para que o respectivo bloco de comandos seja executado.

No switch-case um bloco de instruções deve, obrigatoriamente, ser finalizado


com a instrução break, a qual provoca o término do switch-case. Neste exemplo, a
instrução case 1 será falsa e o bloco não é executado. Em seguida, case 2 resulta em
verdadeiro, a mensagem será exibida e ao encontrar o comando break a estrutura é
finalizada, ou seja, case 3 não será verificado e o bloco default não será executado.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main (void) {


srand(time(NULL));
unsigned char num = (rand() % 4);
switch (num) {
case 1:

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 11


printf("Um\n");
break;
case 2:
printf("Dois\n");
break;
case 3:
printf("Três\n");
break;
default:
printf("Não sei!\n");
}
}

A Linguagem C oferece as instruções while, for e do-while que possibilitam a


repetição de blocos de instruções. A estrutura while possibilita realizar a repetição de
um bloco de instruções até que a expressão lógica fornecida em condição seja avaliada
como falsa, neste momento o while é encerrado e a execução do programa prossegue
com a instrução imediatamente posterior ao comando. Como exemplo iremos mostrar
um programa que realiza a exibição dos números inteiros entre 1 e 10:

#include <stdio.h>

int main() {
int i = 1;
while (i <= 10) {
printf("%d, ", i++);
}
}

Observe que o bloco de instruções irá se repetir enquanto a variável i for


menor ou igual a 10. Quando i atingir o valor 11 a condição torna-se falsa, ou seja, a
repetição é encerrada.

A instrução do-while tem funcionamento similar ao while, porém apresenta


uma diferença fundamental. Enquanto no while primeiro realiza-se a verificação da
expressão lógica e depois a execução do bloco de instruções, na estrutura do while
ocorre o contrário, inicialmente é realizada a execução do bloco e depois é realizada a
verificação da expressão lógica. Desta maneira, o bloco de instruções sempre é
executado pelo menos uma vez, independente de condição ser verdadeira ou falsa.

#include <stdio.h>

int main() {
int i = 1;
do {
printf("%d, ", i++);
} while (i <= 10);
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 12


A instrução de repetição for apresenta como características particulares a
possibilidade de realizar tarefas de inicialização e pós-execução no próprio corpo da
instrução, permitindo um código mais conciso e com maior facilidade de
entendimento.

#include <stdio.h>

int main() {
for (int i = 1; i <= 10; i++) {
printf("%d, ", i);
}
}

Observando o exemplo, a instrução for tem início com execução de int i = 0, em


seguida realiza-se a avaliação da expressão lógica (i <= 10). Caso ela seja verdadeira o
bloco de instruções é executado. Caso a expressão lógica seja falsa, a estrutura será
encerrada e a execução do programa irá prosseguir na instrução imediatamente
posterior a estrutura for. Após a execução do bloco de instrução executa-se a instrução
i++ e, em seguida, a expressão lógica é novamente verificada.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 13


Vetores e Matrizes

Os vetores podem ser entendidos como um conjunto de elementos de um


mesmo tipo de dado e que exploram a contiguidade da memória. Desta forma,
qualquer elemento do vetor pode ser acessado instantaneamente através de um
índice. Quando os vetores possuem mais de uma dimensão, ou seja, precisam de mais
do que um índice para acesso à determinado elemento, eles passam a ser conhecidos
como matrizes. Desta forma, os vetores e matrizes devem ser compostos apenas por
elementos de um mesmo tipo de dados e são declarados da seguinte maneira:

int v[5];

Observe que realizamos a declaração de um vetor que poderá receber até cinco
números inteiros. Em vetores e matrizes o primeiro elemento é acessado com o índice
recebendo o valor 0 (zero). Para acessar um elemento dentro de um vetor devemos
referenciar entre colchetes o índice da posição desejada, por exemplo, para atribuir
um valor para a primeira posição do vetor devemos realizar o seguinte comando:

v[0] = 45;

Quando previamente disponíveis, os elementos de um vetor podem ser


inseridos no momento da criação dele, conforme ilustrado no trecho de programa a
seguir.

int v[] = {64, 87, 32, 07, 86};

Vamos agora montar um exemplo completo, nele armazenamos 10 números


inteiros em um vetor e exibimos esses valores.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TAM 10

int main (void) {


int v[TAM];
srand(time(NULL));
for (int i = 0; i < TAM; i++) {
v[i] = (rand() % 100);
printf("%d, ", v[i]);
}
}

Observe que utilizamos uma diretiva chamada #define para o tamanho do


vetor. Quando precisamos trabalhar com valores fixos, ou seja, que não serão
alterados durante a execução do programa, é recomendável utilizar diretivas ao invés
de variáveis.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 14


Funções

Na medida em que os programas se tornam mais complexos, é importante


estruturar o código em partes menores com funcionalidades específicas. Outra
vantagem da modularização consiste em permitir a reutilização de parte do programa,
evitando assim que um mesmo trecho do código tenha que ser reescrito diversas
vezes. Na Linguagem C o conceito de função deve ser aplicado com o intuito de
modularizar um programa.

Como exemplo, vamos criar uma função chamada somar que irá receber, como
parâmetros, dois números reais. Em seguida, realizará a soma deles e retornará o
resultado obtido, ou seja:

float somar(float num_a, float num_b) {


return (num_a + num_b);
}

Uma vez declarada (criada) a função é só realizar a chamada sempre que for
necessário o seu uso, como mostrado a seguir.

#include <stdio.h>

float somar(float num_a, float num_b) {


return (num_a + num_b);
}

int main (void) {


float res = somar (4.5, 6.6);
printf("%f, ", res);
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 15


Ponteiros

Ponteiros consistem em um tipo de variável que, ao invés de armazenar um


determinado valor, indicam (apontam) para um endereço de memória. Observe no
código-fonte que declaramos a variável p como um ponteiro para valores inteiros. Na
declaração o asterisco (*) que antecede o nome da variável indica que se trata de um
ponteiro.

Este ponteiro irá apontar para o endereço de memória alocado para a variável
x. O uso do símbolo “e” comercial antes do nome da variável indica que estamos
obtendo “o endereço” da variável ao invés do seu conteúdo. Desta forma, na linha p =
&x, temos que o ponteiro p recebeu o endereço da variável x, ou seja, aponta para o
endereço de x.

#include <stdio.h>

int main(void) {
int x = 10;
int *p;
p = &x;
printf("O endereco de X é: %p\n", p);
printf("O valor armazenado no endereco: %d\n", *p);
}

Quando acessamos diretamente o ponteiro, temos o seu endereço. Por outro


lado, quando precisamos acessar o conteúdo armazenado no endereço de memória
devemos usar *p.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 16


Linguagem C para Microcontroladores AVR
Vamos iniciar este capítulo adotando como referência o tradicional programa
Blink, que está disponível como exemplo no ambiente desenvolvimento do Arduino.
Este programa acende a apaga o LED da placa do Arquino em intervalos de 1 segundo.
Porém, é importante entender que, fisicamente, este LED está conectado à Porta PB5
do ATmega328P, que também corresponde ao Pino 13 na placa Arduino. O programa
usou o framework Wiring para simplificar (e esconder) detalhes da arquitetura do
microcontrolador. As vantagens de usar um framework estão relacionadas a facilidade
de uso e portabilidade.

void setup() {
pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}

Após escrever o programa no ambiente de desenvolvimento do Arduino,


compilar e transferir o programa para o microcontrolador, vamos obter uma saída
similar à mostrada pela Figura 8. Observe a quantidade de memória usada (924 bytes).

Figura 8: Uso da memória (Usando Wiring)

Acesso aos Registradores do ATmega328P

Usando a própria IDE do Arduino podemos reescrever o programa apresentado


usando apenas elementos da Linguagem C, sem usar o framework Wiring. Desta
forma, é possível acessar diretamente os endereços de memória que controlam os
registradores associados às portas do ATmega328P.

#define F_CPU 16000000UL


#include <util/delay.h>

int main(void) {
// Special Function Registers (SFR): DDRB e PORTB

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 17


// DDRB – The Port B Data Direction Register
unsigned char *direcao_porta_b = (unsigned char *) 0x24;
// PORTB – The Port B Data Register
unsigned char *dado_porta_b = (unsigned char *) 0x25;
*direcao_porta_b |= 0b00100000;
while (1) {
*dado_porta_b |= 0b00100000;
_delay_ms(1000);
*dado_porta_b &= 0b11011111;
_delay_ms(1000);
}
}

Compile e transfira o programa para a placa Arduino e observe as mensagens


(Figura 9) produzidas pela saída da IDE do Arduino. Observe novamente a quantidade
de memória usada pelo programa (176 bytes).

Figura 9: Uso da memória (Apenas Linguagem C)

Então, vamos comparar nas Figuras 8 e 9 a quantidade de memória usada pelos


programas, visto que ambos realizam a mesma função. O programa desenvolvido com
Wiring ocupou 924 bytes, enquanto o programa implementado apenas na Linguagem
C usou apenas 176 bytes. Uma diferença significativa considerando o espaço reduzido
de armazenamento do ATmega328P (32.256 bytes).

Por outro lado, note que é necessário o conhecimento da arquitetura do


microcontrolador para realizarmos o desenvolvimento do programa usando apenas a
Linguagem C. Para isso, será necessário acessar o manual (datasheet) do ATmega238P.

No exemplo desenvolvido vamos precisar, especificamente, de detalhes dos


registradores DDRB (The Port B Data Direction Register) e PORTB (The Port B Data
Register), pois o LED se conecta fisicamente ao pino PB5 do ATmega328P. Desta forma,
conforme ilustra a Figura 10 que contém um fragmento da página 72 do manual do
microcontrolador, onde podemos identificar o endereço de acesso aos registradores e
a função de cada bit.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 18


Figura 10: Organização dos Registros PORTB e DDRB. Fonte: Manual do ATmega328P

Com base nestas informações devemos definir um ponteiro para acessar e


manipular cada registrador (DDRB e PORTB), conforme mostra o seguinte trecho do
código-fonte. Note que devido ao fato dos registradores do ATmega328P serem de 8
bits, definimos um ponteiro para unsigned char.

// DDRB – The Port B Data Direction Register


unsigned char *direcao_porta_b = (unsigned char *) 0x24;
// PORTB – The Port B Data Register
unsigned char *dado_porta_b = (unsigned char *) 0x25;

Na sequência vamos definir o Pino PB5 como saída, para isso devemos ligar o
bit 5 (DDB5) do registrador DDRB, conforme mostra a instrução a seguir. Com o intuito
de preservar a configuração dos demais bits do registrador DDRB, usamos a operação
lógica OU (OR) ao atualizarmos o conteúdo.

*dado_porta_b |= 0b00100000;

Sendo relevante salientar que a mesma operação pode ser especificada desta
outra maneira:

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 19


*dado_porta_b = *dado_porta_b | 0b00100000;

O LED será aceso quando o bit 5 do registrador PORTB for ligado, desta forma,
temos:

*dado_porta_b |= 0b00100000;

Por outro lado, para desligar o LED, devemos colocar zero no respectivo bit, ou
seja:

*dado_porta_b &= 0b11011111;

O compilador da Linguagem C para a família AVR possui a biblioteca avr/io.h


que facilita o acesso aos registradores do microcontrolador. Então, do mesmo modo
que no programa anterior, note que iremos usar o acesso aos registradores DDRB e
PORTB e que a porta a ser ligada e desligada corresponde à PB5, que está conectada
ao pino 13 do Arduino.

Como queremos usar este pino como saída (OUTPUT), na linha 6 do programa,
precisamos ligar (1) o bit 5 do registrador DDRB (Port B Data Direction Register).
Salientando que iniciamos a contagem dos bits em zero e da direita para a esquerda.
Além disso, para preservar a configuração dos demais bits do registrador DDRB,
usamos a operação lógica OU (OR) ao atualizarmos o conteúdo.

DDRB = DDRB | 0b00100000;

Ou:

DDRB |= 0b00100000;

Em seguida, o pino 13 (PB5) será ativado, ligando o LED que está conectado a
ele. Então, o registrador PORTB será usado, note que usamos a operação de OU de
modo a preservar o estado dos demais pinos.

PORTB |= 0b00100000;

Após aguardar 1 segundo desligamos o pino PB5 (Linha 10). Para fazer isso o bit
5 deverá ser colocado em zero. Neste caso, para preservar o estado das demais pinos,
usamos a operação de AND, sendo que apenas o bit a ser desligado (PB5) deverá
conter o valor zero.

PORTB &= 0b11011111;

A seguir temos o código-fonte completo do programa que foi desenvolvido.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 20


#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

int main(void) {
DDRB |= 0b00100000;
while (true) {
PORTB |= 0b00100000;
_delay_ms(1000);
PORTB &= 0b11011111;
_delay_ms(1000);
}
}

Com o objetivo de evitar o uso de valores binários no programa podemos


realizar algumas operações com bits, facilitando o desenvolvimento e diminuindo a
incidência de erros. Então, o programa apresentado anteriormente pode ser
implementado da maneira mostrada no seguinte código-fonte.

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
while (1) {
PORTB |= (1 << PORTB5);
_delay_ms(1000);
PORTB &= ~(1 << PORTB5);
_delay_ms(1000);
}
}

Neste caso, DDB5 e PORTB5 possuem o valor 5, que representa o bit 5 dos
registradores DDRB e PORTB, respectivamente. Então, observe a linha:

DDRB |= (1 << DDB5);

Note que foi usado o operador << (deslocamento de bits à esquerda) sobre o
valor 1. Para entender o uso do operador, temos que 1 é representado em binário do
seguinte modo:

0 0 0 0 0 0 0 1

Ao realizar a operação 1 << DDB5, isto é, 1 << 5, teremos o deslocamento de 5


bits, obtendo a seguinte representação binária, que corresponde à posição do pino
PB5 no registrador DDRB.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 21


0 0 1 0 0 0 0 0

Com o intuito de preservar o valor dos demais bits do registrador DDRB,


usamos a operação lógica OU (OR) ao atualizarmos o registrador. Além das operações
para ligar e desligar uma porta digital do Arduino, apresentadas neste exemplo,
também temos a opção de alternar o estado de uma porta digital (toggle).

PORTB ^= (1 << PORTB5);

Então, usamos o operador OU-EXCLUSIVO (XOR), desta maneira, quando a


porta digital está com a saída com o valor 1, ao executarmos a instrução acima, a porta
irá para o nível lógico 0 e vice-versa. Desta maneira, podemos reescrever o programa
que alterna o estado do LED (pisca-pisca):

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
while (1) {
PORTB ^= (1 << PORTB5);
_delay_ms(1000);
}
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 22


Compilação e Carga dos Programas no Microcontrolador

O ambiente de desenvolvimento do Arduino automatiza e deixa transparente


para o usuário todo o processo de compilação e carga dos programas no ATmega328P.
Desta forma, vamos detalhar como ocorre esse processo para que seja possível
usarmos outros ambientes de desenvolvimento ou até mesmo a linha de comando
para desenvolver nossos programas.

A primeira etapa consiste na compilação, onde é usado o avr-gcc para, a partir


do código-fonte em Linguagem C, gerar o módulo objeto. No exemplo a seguir, temos
o arquivo meu_programa.c sendo compilado e dando origem ao módulo objeto
meu_programa.o.

avr-gcc -c -std=gnu99 -Os -Wall -ffunction-sections -fdata-


sections -mmcu=atmega328p meu_programa.c -o meu_programa.o

A etapa seguinte consiste em realizar a linkedição (ligação) dos vários módulos


objetos, que podem ser usados durante o desenvolvimento do programa, resultando
em um único arquivo no formato ELF (Executable and Linking Format).

avr-gcc -Os -mmcu=atmega328p -ffunction-sections -fdata-sections


-Wl --gc-sections meu_programa.o -o meu_programa.elf

O utilitário avr-objcopy irá, a partir do arquivo gerado na etapa anterior, gerar


um novo arquivo com o código otimizado e no formato Intel Hex, tornando o arquivo
suportado pelo ATmega328P.

avr-objcopy -O ihex -R .eeprom meu_programa.elf


meu_programa.ihex

Neste ponto, o programa está pronto para ser transferido para a memória flash
do ATmega328P, para realizar este passo vamos utilizar o utilitário avrdude da
maneira indicada pela linha de comando mostrada a seguir. Onde porta corresponde à
porta no qual o Arquino está conectado ao computador, por exemplo, COM3.

avrdude -c arduino -p atmega328p -P porta -U


flash:w:meu_programa.ihex

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 23


Saída Digital

Aplicando os conceitos já abordados anteriormente sobre acesso às portas


digitais através da Linguagem C, podemos desenvolver um projeto que irá simular o
funcionamento de um sinal de trânsito.

Material necessário:
• 1 Arduino.
• 3 Resistores de 220 Ohms (vermelho, vermelho, marrom) ou de 330 Ohms
(laranja, laranja, marrom).
• 3 LEDs (1 vermelho, 1 verde e 1 amarelo).
• 1 Protoboard.
• Cabos de ligação.

A montagem deve ser realizada adotando, como referência, a Figura 11.

Figura 11: Conexões


Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 24
Crie o seguinte programa, faça a compilação e o carregue no Arduino. Note que
usaremos os pinos PB3 (Pino 11 do Arduino), PB4 (12) e PB5 (13) para controlar o
funcionamento dos três LEDs.

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
DDRB |= (1 << DDB4);
DDRB |= (1 << DDB3);
while (1) {
PORTB |= (1 << PORTB3);
PORTB &= ~ (1 << PORTB5);
_delay_ms(1000);
PORTB &= ~ (1 << PORTB3);
PORTB |= (1 << PORTB4);
_delay_ms(1000);
PORTB &= ~ (1 << PORTB4);
PORTB |= (1 << PORTB5);
_delay_ms(1000);
}
}

Analisando o código-fonte podemos ver que iremos determinar a sequência


com que os LEDs são acesos e apagados, simulando os três estados de um sinal de
trânsito.

No projeto a seguir serão aplicados os conceitos de saída digital para exibir os


dígitos de 0 a 9 em um display de LEDs de 7 segmentos.

Material necessário:
• 1 Arduino.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 Display de LEDs de 7 segmentos (cátodo ou ânodo comum).
• 1 Protoboard.
• Cabos de ligação.

IMPORTANTE: O circuito eletrônico e programa mostrados a seguir consideram


o uso de um display de LEDs de 7 segmentos com cátodo comum, mas podem ser
facilmente alterados para o modelo com ânodo comum.

Realize a montagem dos componentes da maneira indicada pela Figura 12,


certificando que o pino PD7 do ATmega328p está conectado ao segmento “a” do
display, que o pino PB0 está conectado ao segmento “b” do display, que PB1 está
ligado ao segmento “c” e assim sucessivamente.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 25


Figura 12: Conexões

Após desenvolver o seguinte programa, realize a compilação e a carga no


Arduino. Visto que a rotina é extensa, pois é necessário controlar 7 pinos digitais do
microcontrolador, note o uso de funções para organizar e reutilizar o código-fonte.

#define F_CPU 16000000UL


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

unsigned char digito[10] = {


0b01111110, // = 0
0b00110000, // = 1
0b01101101, // = 2
0b01111001, // = 3
0b00110011, // = 4
0b01011011, // = 5

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 26


0b01011111, // = 6
0b01110000, // = 7
0b01111111, // = 8
0b01110011 // = 9
};

void limpar() {
PORTD &= ~(1 << PORTD7);
PORTB &= ~(1 << PORTB0);
PORTB &= ~(1 << PORTB1);
PORTB &= ~(1 << PORTB2);
PORTB &= ~(1 << PORTB3);
PORTB &= ~(1 << PORTB4);
PORTB &= ~(1 << PORTB5);
}

void exibir(int valor) {


// Segmento "a"
if (digito[valor] & (1 << PORTD7) != 0)
PORTD |= (1 << PORTD7);
else
PORTD &= ~(1 << PORTD7);
// Segmento "b"
if (digito[valor] & (1 << PORTB0) != 0)
PORTB |= (1 << PORTB0);
else
PORTB &= ~(1 << PORTB0);
// Segmento "c"
if (digito[valor] & (1 << PORTB1) != 0)
PORTB |= (1 << PORTB1);
else
PORTB &= ~(1 << PORTB1);
// Segmento "d"
if (digito[valor] & (1 << PORTB2) != 0)
PORTB |= (1 << PORTB2);
else
PORTB &= ~(1 << PORTB2);
// Segmento "e"
if (digito[valor] & (1 << PORTB3) != 0)
PORTB |= (1 << PORTB3);
else
PORTB &= ~(1 << PORTB3);
// Segmento "f"
if (digito[valor] & (1 << PORTB4) != 0)
PORTB |= (1 << PORTB4);
else
PORTB &= ~(1 << PORTB4);
// Segmento "g"
if (digito[valor] & (1 << PORTB5) != 0)

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 27


PORTB |= (1 << PORTB5);
else
PORTB &= ~(1 << PORTB5);
}

int main(void) {
DDRD |= (1 << DDD7); // Segmento "a"
DDRB |= (1 << DDB0); // Segmento "b"
DDRB |= (1 << DDB1); // Segmento "c"
DDRB |= (1 << DDB2); // Segmento "d"
DDRB |= (1 << DDB3); // Segmento "e"
DDRB |= (1 << DDB4); // Segmento "f"
DDRB |= (1 << DDB5); // Segmento "g"

while (1) {
limpar();
for (int cont = 9; cont >= 0; cont--) {
exibir(cont);
_delay_ms(1000);
}
}
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 28


Entrada Digital

Neste projeto vamos demonstrar como ler o estado de um sensor digital


através de um dos pinos digitais do ATmega328P, configurado como entrada. Para
isso, utilizaremos uma chave táctil (push button) para explorar o conceito de entradas
digitais.

Material necessário:
• 1 Arduino.
• 1 Chave Táctil (push button).
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 Resistor de 10k Ohms (marrom, preto laranja).
• 1 LED (qualquer cor).
• 1 Protoboard.
• Cabos de ligação.

Realize a montagem dos componentes da maneira indicada pela Figura 13.

Figura 13: Conexões


Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 29
Crie o seguinte programa, faça a compilação e o carregue no Arduino. Note que
usaremos os pinos PB5 (Pino 13 do Arduino) para conectar o LED e o PD2 (Pino 2 do
Arduino) para ligar a chave táctil.

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
DDRD &= ~(1<< DDD2);
while (1) {
if ((PIND & (1 << PIND2)) == (1 << PIND2))
PORTB |= (1 << PORTB5);
else
PORTB &= ~(1 << PORTB5);
_delay_ms(50);
}
}

Inicialmente definimos o pino PB5 (Pino 13 do Arduino) como saída, colocando


o valor 1 no 5 bit do registrador DDRB e o pino PD2 (Pino 2 do Arduino) como entrada,
atribuindo o valor 0 ao 2 bit do registrador DDRD. Em seguida, para determinar o
estado da entrada digital devemos verificar o valor do bit 2 do registrador PIND (Port D
Input Pins Register). Caso o valor seja 1 acendemos o LED, caso contrário, o LED será
apagado.

Neste caso, o LED permanece aceso apenas enquanto a chave táctil estiver
pressionada. Podemos alterar a lógica do programa de modo a manter o estado, ou
seja, ao pressionar e soltar o botão o LED permanece acesso, até que um novo
pressionamento ocorre. Para isso, usaremos uma técnica chamada debounce que,
basicamente, consiste em identificar quando ocorre a transição de estado da chave
táctil, comparando o estado atual com o estado anterior.

Observe neste código-fonte o uso das variáveis valor e anterior, sendo que
apenas ocorrerá mudança no estado do LED quando ocorre uma transição na chave
táctil, ou seja, a variável valor contém 1 (o botão foi pressionado) e a variável anterior
tem o valor 0 (o botão não estava pressionado).

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
DDRD &= ~(1<< DDD2);
char valor;
char estado = 0;

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 30


char anterior = 0;
while (1) {
valor = (PIND & (1 << PIND2)) == (1 << PIND2);
if (valor == 1 && anterior == 0) {
if (estado == 1)
estado = 0;
else
estado = 1;
}

if (estado == 1)
PORTB |= (1 << PORTB5);
else
PORTB &= ~(1 << PORTB5);
anterior = valor;
_delay_ms(50);
}
}

Com base nos conceitos abordados podemos criar um contador binário de 3


bits que será incrementado quando a chave táctil for pressionada.

Material necessário:
• 1 Arduino.
• 1 Chave Táctil (push button).
• 3 Resistores de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms
(laranja, laranja, marrom).
• 1 Resistor de 10k Ohms (marrom, preto laranja).
• 3 LEDs (qualquer cor).
• 1 Protoboard.
• Cabos de ligação.

Realize a montagem dos componentes da maneira indicada pela Figura 14.

Figura 14: Conexões


Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 31
Desenvolva o seguinte programa, realize a compilação e a carga no Arduino.

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
DDRB |= (1 << DDB4);
DDRB |= (1 << DDB3);
DDRD &= ~(1<< DDD2);
char valor;
char estado = 0;
char anterior = 0;
while (1) {
valor = (PIND & (1 << PIND2)) == (1 << PIND2);
if (valor == 1 && anterior == 0) {
estado++;
if (estado == 8)
estado = 0;
}
PORTB &= 0b000111;
PORTB |= (estado << PORTB3);
anterior = valor;
_delay_ms(50);
}
}

Ao analisar o código-fonte, note que a cada pressionamento da chave táctil


incrementamos a variável estado até atingir o valor 8, neste caso a variável recebe o
valor zero. Desta maneira temos, os valores de 0 até 7, que são possíveis representar
com 3 bits. Na instrução PORTB &= 0b000111 atribuímos zero aos bits responsáveis
pelos pinos PB3, PB4 e PB5, mantendo intacto os demais bits do registrador PORTB.

Na instrução seguinte, PORTB |= (estado << PORTB3) atribuímos o valor dos


três bits menos significativos da variável estado nos bits 3, 4 e 5 do registrador PORTB.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 32


Comunicação Serial

O ATmega328P pode estabelecer comunicação serial com outros dispositivos


através do padrão USART (Universal Synchronous Asynchronous Receiver Transmitter).
A maioria das placas Arduino implementa um circuito que permite realizar a
comunicação do microcontrolador com um computador através de uma porta de
comunicação USB (Universal Serial Bus), conforme ilustra a Figura 15. Quando usar
apenas o ATmega328P a comunicação pode ser realizada através de um módulo
conversor USB/Serial externo.

Figura 15: Comunicação Serial Bidirecional

A seguir detalhamos o conjunto de registradores que são usados na


configuração, transmissão e recepção de dados. Os registradores UBRR0L e UBRR0H
são usados para realizar a definição da velocidade de transmissão (Baud Rate). Sendo
que o registrador UBRR0L irá conter os 8 bits menos significativos.

UBRR0L - USART Baud Rate Register Lower 8 bits


7 6 5 4 3 2 1 0
D D D D D D D D
D = Bits de Dados

Enquanto o registrador UBRR0H armazena os 4 bits mais significativos (Bits 0, 1,


2 e 3), sendo que os demais bits não são usados.

UBRR0H - USART Baud Rate Register Upper 4 bits


7 6 5 4 3 2 1 0
- - - - D D D D
D = Bits de Dados

O valor que deve ser armazenado (BAUD_PRESCALE) nos registradores UBRR0L


e UBRR0H é determinado através da seguinte fórmula:

BAUD_PRESCALE = ((F_CPU / (16UL * BAUD)) - 1),

Onde F_CPU corresponde ao clock do ATmega328P, isto é 16 MHz e BAUD é a


velocidade de transmissão desejada.

O registrador UCSR0A é utilizado, durante a transmissão de dados, para


monitorar o estado e as falhas de comunicação.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 33


UCSR0A - USART Control and Status Register A
7 6 5 4 3 2 1 0
RXC0 TXC0 UDRE0 FE0 DOR0 PE0 U2X0 MPCM0

Onde:

• RXC0 (Receive Complete) apresentará o valor 1 quando os dados forem


recebidos e estiverem disponíveis no registrador UDR0.
• TXC0 (Transmission Complete) terá o valor 1 quando uma transmissão é
concluída e não há mais dados para enviar.
• UDRE0 (Data Register Empty) é definido com valor 1 quando o registrador
UDR0 (USART Data Register) está vazio estando pronto para ser gravado.
• FE0 (Frame Error) terá o valor 1 se o próximo bit no buffer de recepção
apresentar erro de quadro. Quando o buffer de recepção for lido, o bit FE0 será
ajustado para zero.
• DOR0 (Data Over Run) apresentará o valor 1 quando o buffer de recepção
estiver cheio (2 caracteres). O bit terá o valor 0 quando o registrador UDR0 for
lido.
• UPE0 (Parity Error) é definido com o valor 1 se o próximo caractere no buffer
de recepção apresentar erro de paridade. O bit é alterado para zero quando o
registrador UDR0 for lido.
• U2X0 (DoubleTX Speed) identifica se a comunicação assíncrona em dupla
velocidade está ativa.
• MPCM0 (Multi-processor Communication Mode) identifica se o modo de
comunicação multi-processador está ativo.

O registrador UCSR0B é usado na configuração dos parâmetros de comunicação.

UCSR0B - USART Control and Status Register B


7 6 5 4 3 2 1 0
RXCIE0 TXCIE0 UDRIE0 RXEN0 TXEN0 UCSZ02 RXB80 TXB80

Onde:

• RXCIE0 (RX Complete Interrupt Enable) permite habilitar interrupção com RXC0.
• TXCIE0 (TX Complete Interrupt Enable) possibilita habilitar a interrupção com
TXC0.
• UDRIE0 (Data Register Empty Interrupt Enable) habilitar a interrupção com
UDRE0.
• RXEN0 (Receiver Enable) permite a recepção de dados através da USART.
• TXEN0 (Transmitter Enable): habilita a transmissão de dados através da USART.
• UCSZ02 (UART Character Size): em conjunto com UCSZ00 e UCSZ01 determina
o tamanho do quadro de dados.
• RXB80 (Receive Data Bit 8): nono bit recebido para quadros de dados de 9 bits.
• TXB80 (Transmit Data Bit 8): nono bit transmitido para quadros de dados de 9
bits.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 34


O registrador UCSR0C também é usado na configuração dos parâmetros de
comunicação.

UCSR0C - USART Control and Status Register C


7 6 5 4 3 2 1 0
UMSEL01 UMSEL00 UPM01 UPM00 USBS0 UCSZ01 UCSZ00 UCPOL0

Sendo que os bits UMSEL01 e UMSEL00 (USART Mode Select) definem o modo
de operação considerando a seguinte tabela:

Modo de Operação UMSEL01 UMSEL00


Assíncrono 0 0
Síncrono 0 1
Reservado 1 0
SPI Mestre 1 1

Os bits UPM01 e UPM00 (USART Parity Mode) permitem configurar a paridade,


considerando os seguintes modos:

Paridade UPM01 UPM00


Desabilitado 0 0
Reservado 0 1
Par 1 0
Ímpar 1 1

O bit USBS0 (USART Stop Bit Select) deve conter o valor 0 para selecionar um
bit de parada ou 1 para definir 2 bits de parada (Stop Bit).

Os bits UCSZ01 e UCSZ00 (USART Character Size), em conjunto com o bit


UCSZ02 do registrador UCSR0B, determina quantos bits de dados serão usados na
comunicação, considerando a tabela apresentada a seguir.

UCSR0B UCSR0C
Bits de Dados UCSZ02 UCSZ01 UCSZ00
5-bit 0 0 0
6-bit 0 0 1
7-bit 0 1 0
8-bit 0 1 1
Reservado 1 0 0
Reservado 1 0 1
Reservado 1 1 0
9-bit 1 1 1

O bit UCPOL0 (USART Clock Polarity) é usado apenas no modo de comunicação


síncrona, sendo:

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 35


UCPOL0 TxD0 Data Change RxD0 Data Sampled
0 Borda de Subida do XCK Borda de Descida do XCK
1 Borda de Descida do XCK Borda de Subida do XCK
XCK = Sinal de Clock

O registrador UDR0 (USART Data Register) contém os bits de dados que serão
transmitidos ou que foram recebidos.

UDR0 – USART Data Register


7 6 5 4 3 2 1 0
D D D D D D D D
D = Bits de Dados

Com o intuito de aplicar os conceitos que envolvem a comunicação serial,


vamos desenvolver um projeto que irá usar a comunicação serial para controlar o
funcionamento de um LED conectado à porta digital PB5 (Pino 13 do Arduino).

Material necessário:
• 1 Arduino.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 Led (qualquer cor).
• 1 Protoboard.
• Cabos de ligação.

Após obter o material necessário, realize a montagem da maneira mostrada na


Figura 16.

Figura 16: Conexões

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 36


Implemente o programa como mostrado a seguir. Analisando o código-fonte
observamos que a função USART_Init é criada com o intuito de configurar os
parâmetros de transmissão, sendo que os registradores UBRR0L e UBRR0H irão conter
o resultado do cálculo do prescale da velocidade de transmissão. Na sequência o
registrador UCSR0B é configurado para habilitar tanto a transmissão quanto a
recepção de dados através da USART. Finalizando a função de inicialização o
registrador UCSR0C é configurado para usar 8 bits de no envio e na recepção. Desta
forma, temos os parâmetros de comunicação mais comumente usados em placas
Arduino, ou seja, baud rate de 9600 bps, 8 bits de dados, sem paridade, 1 bit de parada
e sem controle de fluxo.

#define F_CPU 16000000UL


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

#define BAUD 9600


#define BAUD_PRESCALE ((F_CPU / (16UL * BAUD)) - 1)

void USART_Init() {
UBRR0L = BAUD_PRESCALE;
UBRR0H = (BAUD_PRESCALE >> 8);
UCSR0B = (1 << TXEN0) | (1 << RXEN0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

void USART_Tx(unsigned char dado) {


while (!(UCSR0A & (1<<UDRE0)));
UDR0 = dado;
}

void USART_Tx_String (char *texto) {


while (*texto != '\0')
USART_Tx (*texto++);
}

char USART_Rx() {
while (!(UCSR0A & (1<<RXC0)));
return (UDR0);
}

int main(void) {
DDRB |= (1 << DDB5);
USART_Init();
USART_Tx_String("Pressione A para acender ou apagar o
LED:\n\r");
while (1) {
switch (USART_Rx()) {
case 'A':
case 'a':

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 37


PORTB ^= (1 << PORTB5);
break;
default:
break;
}
_delay_ms(10);
}
}

A função USART_Tx foi criada para permitir o envio de um bit de dados. A


função inicialmente monitora o bit UDRE0 (Data Register Empty) no registrador
UCSR0A (while (!(UCSR0A & (1<<UDRE0)));). Quanto este bit assume o valor 1,
indicando que o registrador UDR0 está vazio e pronto para receber dados, a instrução
de repetição (while) é encerrada e o dado a ser transmitido é carregado no registrador
UDR0 (USART Data Register).

A função USART_Tx_String foi criada com o intuito de facilitar o envio de cadeia


de caracteres (strings) e, como podemos observar, usa a própria função USART_Tx
para envio dos dados, sendo um caractere enviado de cada vez.

Por outro lado, a função USART_Rx monitora o bit RXC0 (Receive Complete) do
registrador UCSR0A. Este bit apresentará o valor 1 quando os dados forem recebidos e
estiverem disponíveis no registrador UDR0 (USART Data Register). Quando isto ocorre
a inscrição while é encerrada e o conteúdo o registrador UDR0 e retornado pela
função.

A função principal do programa (main) inicia a comunicação serial através da


chamada à função USART_Init, na sequência transmite uma cadeia de caracteres
através porta serial. Depois aguarda a recepção de dados, sendo que sempre que a
tecla A é pressionada o estado de LED é alterado.

Após compilar e enviar o programa para o microcontrolador podemos usar um


software de comunicação serial como o PuTTY, ou qualquer outro programa
equivalente, para estabelecer a comunicação serial com o ATmega328P. Na primeira
janela do programa (Figura 17) escolha a opção Serial, defina qual porta o Arduino está
conectado e a velocidade que, neste exemplo, deve ser 9600 bps. Em seguida, clique
na opção Serial que aparece do lado esquerdo da janela, em Category.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 38


Figura 17: PuTTY

Na Figura 18, vamos definir os parâmetros de comunicação. Note que estes


devem ser os mesmos que usamos no programa que está carregado no ATmega328P.
Em seguida, clique no botão Open para realizar a conexão.

Figura 18: Configuração dos Parâmetros de Comunicação

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 39


Após a conexão a janela contendo o terminal será exibida (Figura 19). Observe
que a mensagem enviada pelo programa, que está executando no ATmega328P, será
exibida. Experimente pressionar a tecla A para controlar o funcionamento do LED.

Figura 19: Terminal

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 40


Conversor Analógico Digital

O Conversor Analógico Digital ou ADC, que é a sigla para o termo em inglês


Analogue to Digital Converter, possibilita a obtenção de dados de sensores analógicos,
por exemplo, termistores e resistores dependentes de luz (LDR).

Figura 20: Diagrama Simplificado do ADC do ATmega328P

Conforme ilustra a Figura 20, o ATmega328P possui um ADC de 10 bits e, desta


maneira, pode retornar valores de zero até 210 (0 a 1023). Conforme podemos ver na
fórmula a seguir, o valor é determinado com base na tensão recebida pelo ADC (Vin)
em relação à tensão de referência (Vref).

ADC = (Vin * 210) / Vref

Com base neste conceito, podemos usar a seguinte fórmula para determinar a
tensão que está sendo recebida pelo ADC.

Vin = ((float) ADC + 0.5) / 1024.0 * Vref

O ATmega328P apresenta 3 registradores para controle do ADC, sendo eles


identificados como ADMUX, ADCSRA e ADCSRB. Temos também os registradores ADCH
e ADCL que são usados para armazenar o resultado da leitura realizada pelo ADC.

O ADMUX é o registrador responsável pela seleção da entrada e da referência


de tensão que será usada pelo comparador. Voltando à Figura 20 notamos que o
ATmega328P possui 8 entradas analógicas que serão multiplexadas, onde 6 (ADC0 até
ADC5) estão disponíveis para uso, quando considerada a placa do Arduino (Pinos A0
até A5). Além disso, temos o ADC8 que se conecta ao sensor interno de temperatura
do microcontrolador. Assim, sendo o ADMUX está organizado da seguinte forma:

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 41


ADMUX – ADC Multiplexer Selection Register
7 6 5 4 3 2 1 0
REFS1 REFS0 ADLAR - MUX3 MUX2 MUX1 MUX0

Onde o valor binário especificado nos bits MUX3, MUX2, MUX1 e MUX0
representa o canal a ser selecionado, ou seja:

3 2 1 0
MUX3 MUX2 MUX1 MUX0 Canal
0 0 0 0 ADC0
0 0 0 1 ADC1
0 0 1 0 ADC2
0 0 1 1 ADC3
0 1 0 0 ADC4
0 1 0 1 ADC5
0 1 1 0 ADC6
0 1 1 1 ADC7
1 0 0 0 ADC8

Em seguida, usamos o bit 5 (ADLAR) para definir como os 10 bits do valor digital
da conversão vai ser armazenado nos registradores ADCH e ADCL.

ADLAR = 0
Registrador 7 6 5 4 3 2 1 0
ADCH - - - - - - Bit9 Bit8
ADCL Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0

ADLAR = 1
Registrador 7 6 5 4 3 2 1 0
ADCH Bit9 Bit8 Bit7 Bit6 Bit5 Bit4 Bit3 Bit2
ADCL Bit1 Bit0 - - - - - -

Por fim, os bits nos bits 6 (REFS0) e 7 (REFS1) definimos a tensão de referência
que será adotada pelo circuito comparador.

REFS1 REFS0 Seleção da Tensão de Referência


0 0 AREF, desliga a Vref interna
0 1 AVCC com um capacitor externo conectado ao pino AREF
1 0 Reservado
1 1 Referência interna de 1,1 V de tensão, com um capacitor externo
conectado ao pino AREF

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 42


Na sequência, passamos para a análise do registrador ADCSRA.

ADCSRA – ADC Control and Status Register A


7 6 5 4 3 2 1 0
ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0

Os bits 0, 1 e 2 determinam fator de divisão do sinal de relógio do ADC


(Prescaler) em relação ao sinal de relógio do ATmega328P que é 16 MHz. Porém, o
ADC do ATmega328P não pode funcionar com uma frequência superior a 1MHz, desta
forma, o fator tem que ser, no mínimo, 16. O framework Wiring usa o fator de divisão
128, que é o mais recomendado para o ATmega328P.

ADPS2 ADPS1 ADPS0 Fator de Divisão


1 0 0 16
1 0 1 32
1 1 0 64
1 1 1 128

Nos demais bits do registrador ADCSRA, temos:

Bit Função
ADIE Gerar uma interrupção ao final da conversão.
ADIF Indicar o final da conversão.
ADATE Habilitar o Auto Triggering do ADC.
ADSC Iniciar a conversão.
ADEN Ativar o ADC.

Com base nos conceitos apresentados, iremos usar um potenciômetro,


conectado à uma das entradas analógicas do Arduino, para com base no valor obtido
pelo ADC, ajustar a frequência na qual um LED irá piscar.

Material necessário:
• 1 Arduino.
• 1 Potenciômetro de 10k Ohms.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 LED de qualquer cor.
• 1 Protoboard.
• Cabos de ligação.

Realize a montagem da maneira indicada pela Figura 21.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 43


Figura 21: Conexões

Crie o seguinte programa, faça a compilação e o carregue no Arduino. Note que


usaremos os pinos PB3 (Pino 11 do Arduino) para conectar o LED e o ADC0 (Pino A0 do
Arduino) para ligar o potenciômetro.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 44


#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

void esperar_ms(int ms) {


while (ms > 0) {
_delay_ms(1);
ms--;
}
}

int main(void) {
DDRB |= (1 << DDB3);
ADMUX = 0b01000000;
ADCSRA = 0b10000111;
int valor_adc;
while (1) {
PORTB ^= (1 << PORTB3);

ADCSRA = ADCSRA | 0b01000000;


while(ADCSRA & 0b01000000);

valor_adc = ADCL;
valor_adc |= (ADCH << 8);
esperar_ms(valor_adc);
}
}

Conforme já abordado, observe no programa que em primeiro lugar devemos


realizar a configuração no registrador ADMUX, note que usamos o valor binário
01000000, ou seja:

ADMUX – ADC Multiplexer Selection Register


7 6 5 4 3 2 1 0
REFS1 REFS0 ADLAR - MUX3 MUX2 MUX1 MUX0
0 1 0 0 0 0 0 0

Note que nos bits 0, 1, 2 e 3 (MUX0 até MUX3) selecionamos o canal ADC0. O
bit 5 (ADLAR) definido como zero indica que os 10 bits contendo o resultado da
conversão estarão organizados da seguinte maneira:

ADCH ADCL
Bit9 Bit8 Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0

Os bits 6 (REFS0) e 7 (REFS1) mostram que será usada como referência a tensão
de AVCC. Em seguida, passamos para a configuração do registrador ADCSRA que
recebeu o valor binário 10000111, ou seja:

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 45


ADCSRA – ADC Control and Status Register A
7 6 5 4 3 2 1 0
ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0
1 0 0 0 0 1 1 1

Então, nos bits 0 (ADPS0), 1 (ADPS1) e 2 (ADPS2) definimos o fator de divisão


com o valor 128, que é o recomendado para o ATmega328P e habilitamos o ADC,
colocando o valor 1 no bit 7 (ADEN). Para realizar a conversão, observe o seguinte
trecho do código fonte:

ADCSRA = ADCSRA | 0b01000000;


while(ADCSRA & 0b01000000);

O bit 6 (ADSC) é ajustado para 1, iniciando o processo de conversão. Este bit


permanecerá com o valor 1 durante a conversão. Ao término o bit é ajustado para
zero, por isso, usamos a instrução while para garantir que os registradores que irão
armazenar o resultado (ADCH e ADCL) apenas serão lidos após o término da
conversão.

O resultado é obtido realizando a junção dos registradores ADCH e ADCL.


Observe o fragmento do código-fonte mostrado a seguir, note que a ideia básica é
armazenar, em uma variável inteira ( 2bytes = 16 bits) o conteúdo do registrador que
contém a parte menos significativa do resultado (ADCL). Em seguida, realizamos o
deslocamento de 8 bits do registrador que possui a parte mais significativa do
resultado (ADCH) e usamos a operação de OR (OU) para realizar a junção.

valor_adc = ADCL;
valor_adc |= (ADCH << 8);

Como exemplo, vamos imaginar que, após a término da conversão, o


registrador ADCL possua o valor binário 00010011 e o registrador ADCH apresente o
valor binário 00000001. Assim sendo, temos a variável valor_adc após receber o valor
do registrador ADCL:

Bits
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
valor_adc = 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1

Na operação seguinte temos o deslocamento de 8 bits em ADCH o que


produzirá (em memória) o seguinte valor:

Bits
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
ADCH<<8 = 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0

Por último é realizada a operação OU entre os dois valores apresentados acima


e o valor de conversão é armazenado na variável valor_adc:
Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 46
Bits
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
valor_adc = 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1

Resultando no valor decimal 275, lembrando que a faixa de valores é entre 0 e


1023 (210), pois o ADC do ATmega328P é de 10 bits. No programa desenvolvido, esse
valor definirá o tempo de espera, antes que o estado de LED seja alterado, isto é, com
base no valor obtido definimos a frequência com que o LED irá acender e apagar.

É possível reescrever o programa evitando trabalhar diretamente com valores


binários, conforme mostra o programa apresentado a seguir. Note que ele apresenta a
mesma funcionalidade, que é usar o potenciômetro para determinar a frequência com
a qual o LED irá piscar.

#define F_CPU 16000000UL


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

void esperar_ms(int ms) {


while (ms > 0) {
_delay_ms(1);
ms--;
}
}

int main(void) {
DDRB |= (1 << DDB3);
ADMUX |= (1 << REFS0);
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRA |= (1 << ADEN);
int valor_adc;
while (1) {
PORTB ^= (1 << PORTB3);

ADCSRA = ADCSRA | (1 << ADSC);


while(ADCSRA & (1 << ADSC));

valor_adc = ADCL;
valor_adc |= (ADCH << 8);
esperar_ms(valor_adc);
}
}

Aplicando os conceitos abordados em comunicação serial, podemos alterar o


programa que acabamos de desenvolver de modo a enviar o valor obtido pelo ADC
através da porta de comunicação serial, conforme podemos notar no código-fonte a
seguir.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 47


#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#define BAUD 9600


#define BAUD_PRESCALE ((F_CPU / (16UL * BAUD)) - 1)

void USART_Init() {
UBRR0L = BAUD_PRESCALE;
UBRR0H = (BAUD_PRESCALE >> 8);
UCSR0B = (1 << TXEN0) | (1 << RXEN0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

void USART_Tx(unsigned char dado) {


while (!(UCSR0A & (1<<UDRE0)));
UDR0 = dado;
}

void USART_Tx_String(char *texto) {


while (*texto != '\0')
USART_Tx (*texto++);
}

void USART_Tx_Integer(int valor) {


char str_valor[10];
sprintf(str_valor, "%d", valor);
USART_Tx_String (str_valor);
}

char USART_Rx() {
while (!(UCSR0A & (1<<RXC0)));
return (UDR0);
}

void esperar_ms(int ms) {


while (ms > 0) {
_delay_ms(1);
ms--;
}
}

int main(void) {
USART_Init();
DDRB |= (1 << DDB3);
ADMUX |= (1 << REFS0);
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 48


ADCSRA |= (1 << ADEN);
int valor_adc;
while (1) {
PORTB ^= (1 << PORTB3);

ADCSRA = ADCSRA | (1 << ADSC);


while(ADCSRA & (1 << ADSC));

valor_adc = ADCL;
valor_adc |= (ADCH << 8);

USART_Tx_String ("Valor: ");


USART_Tx_Integer (valor_adc);
USART_Tx_String ("\r\n");
esperar_ms(valor_adc);
}
}

Após compilar a transferir o programa para o ATmega328P, usamos o PuTTY ou


qualquer outro programa similar, para se comunicar com o Arduino e podemos
observar a transmissão dos valores lidos pelo ADC (Figura 22).

Figura 22: Terminal

O ATmega328P possui um sensor interno da temperatura e podemos usar o


ADC para realizar a leitura deste sensor. Desta forma, não precisamos montar um
circuito eletrônico para executar o programa a seguir. Salientamos que este sensor
mostra a temperatura interna do microcontrolador e não deve ser usado para medir a
temperatura ambiente.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 49


Porém, como pretendemos enviar a temperatura através da USART, vamos
primeiro criar uma biblioteca que irá conter as funções que desenvolvemos
anteriormente. Desta forma, quando necessário, apenas realizamos a chamada da
biblioteca ao invés de copiarmos o código-fonte das funções para cada programa que
irá usar comunicação serial. Então, crie um arquivo chamado lib-serial.h e implemente
o código-fonte mostrado a seguir.

#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#ifndef BAUD
#define BAUD 9600
#endif
#define BAUD_PRESCALE ((F_CPU / (16UL * BAUD)) - 1)

#include <avr/io.h>
#include <stdio.h>

void USART_Init() {
UBRR0L = BAUD_PRESCALE;
UBRR0H = (BAUD_PRESCALE >> 8);
UCSR0B = (1 << TXEN0) | (1 << RXEN0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

void USART_Tx(unsigned char dado) {


while (!(UCSR0A & (1<<UDRE0)));
UDR0 = dado;
}

void USART_Tx_String(char *texto) {


while (*texto != '\0')
USART_Tx (*texto++);
}

void USART_Tx_Integer(int valor) {


char str_valor[10];
sprintf(str_valor, "%d", valor);
USART_Tx_String (str_valor);
}

char USART_Rx() {
while (!(UCSR0A & (1<<RXC0)));
return (UDR0);
}

No programa principal apenas precisamos carregar a biblioteca através da


diretiva #include, conforme podemos observar a seguir.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 50


#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include "../lib-serial/lib-serial.h"

int temp;

int main(void) {
USART_Init();
ADMUX = (1 << REFS1) | (1 << REFS0) | (1 << MUX3);
ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0)| (1 <<
ADEN);
_delay_ms(100);

while (1) {
ADCSRA |= (1 << ADSC);
while ((ADCSRA & (1 << ADSC)) != 0);
temp = (ADC - 324.31) / 1.22;
USART_Tx_String("Temperatura: ");
USART_Tx_Integer(temp);
USART_Tx_String("C\r\n");
_delay_ms(5000);
}
}

Após carregar a nossa biblioteca para comunicação serial, passamos para a


configuração do ADC. Para acessar o sensor interno de temperatura devemos usar
como referência 1,1V, então os bits REFS1 e REFS0 do registrador ADMUX foram
ligados. Também selecionamos o canal 8 (ADC8), ligando o bit MUX3 e deixando os
demais (MUX2, MUX1 e MUX0) desligados. Como já abordado anteriormente, o sensor
interno de temperatura do microcontrolador está conectado ao ADC8.

Em seguida, no registrador ADCSRA ligamos os bits ADPS2, ADPS1 e ADPS0 para


definir um prescale de 128 e ativamos o ADC ativando o bit ADEN.

Após realizar a leitura, utilizamos o ponteiro ADC (16 bits) que contém a junção
dos registradores ADCH e ADCL para obter o valor da leitura. Na sequência aplicamos a
fórmula para conversão em graus Celsius.

No próximo projeto vamos indicar através de três LEDs a intensidade da


luminosidade do ambiente. Para realizar esta tarefa vamos usar um sensor LDR (Light
Dependent Resistor) conectado à um dos canais do ADC.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 51


Material necessário:
• 1 Arduino.
• 3 Resistores de 220 Ohms (vermelho, vermelho, marrom) ou de 330 Ohms
(laranja, laranja, marrom).
• 3 LEDs (1 vermelho, 1 verde e 1 amarelo).
• 1 LDR.
• 1 Resistor de 10k Ohms (marrom, preto, laranja).
• 1 Protoboard.
• Cabos de ligação.

Na Figura 23 temos a montagem do circuito eletrônico.

Figura 23: Conexões


Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 52
Após a montagem do circuito desenvolva o programa mostrado a seguir. Note
que a configuração do ADC é a mesma usada no exemplo anterior.

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
DDRB |= (1 << DDB4);
DDRB |= (1 << DDB3);
ADMUX |= (1 << REFS0);
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRA |= (1 << ADEN);
int valor_adc;
while (1) {
PORTB ^= (1 << PORTB5);

ADCSRA = ADCSRA | (1 << ADSC);


while(ADCSRA & (1 << ADSC));

valor_adc = ADCL;
valor_adc |= (ADCH << 8);

if (valor_adc < (1023.0 * 0.25)) {


PORTB &= ~(1 << PORTB5);
PORTB &= ~(1 << PORTB4);
PORTB &= ~(1 << PORTB3);
}
else if (valor_adc < (1023.0 * 0.5)) {
PORTB |= (1 << PORTB5);
PORTB &= ~(1 << PORTB4);
PORTB &= ~(1 << PORTB3);
}
else if (valor_adc < (1023.0 * 0.75)) {
PORTB |= (1 << PORTB5);
PORTB |= (1 << PORTB4);
PORTB &= ~(1 << PORTB3);
}
else {
PORTB |= (1 << PORTB5);
PORTB |= (1 << PORTB4);
PORTB |= (1 << PORTB3);
}
_delay_ms(50);
}
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 53


Com base no resultado da conversão realizada pelo ADC, definimos quais LEDs
devem ser acesos, obedecendo a seguinte lógica:

• Leitura abaixo de 25% do valor máximo do ADC (1023), todos os LEDs ficam
apagados.
• Leitura entre 25% e menor que 50% do valor máximo do ADC (1023), apenas o
LED Verde conectado a PORTB5 acende.
• Leitura entre 50% e menor que 75% do valor máximo do ADC (1023), o LED
Verde conectado a PORTB5 e o LED Amarelo conectado a PORTB4 acendem.
• Leitura igual ou superior a 75% do valor máximo do ADC (1023), todos os LEDs
ficam acesos.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 54


Interrupções

Considere um circuito eletrônico microcontrolado que possui uma chave táctil


(botão). O pressionamento da chave táctil é um evento assíncrono, isto é, pode
ocorrer a qualquer momento, não sendo possível prever quando irá ocorrer. Desta
forma, podemos criar uma rotina que irá ficar verificando constantemente se o botão
foi pressionado. Trata-se uma rotina que aplica uma técnica chamada varredura
(polling) e já foi utilizada quando tratamos do assunto sobre entradas digitais. Apesar
de ser de simples implementação, acaba gerando um desperdício de recursos de
processamento, pois o programa fica constantemente realizando o monitoramento.

Uma alternativa é aplicar o conceito de interrupção. Neste caso o processador


recebe um sinal quando ocorre um evento que pode ser interno ou externo. Então, a
rotina em execução é suspensa para que a rotina de serviço de interrupção (ISR -
Interrupt Service Routine) seja executada. Desta maneira, o uso de interrupção evita a
necessidade de rotinas para realizar o monitoramento.

Existem três tipos de interrupções:

• Internas: São geradas por eventos internos, como exemplo podemos citar o
reset do microcontrolador, estouro (overflow) de um temporizador,
transferência completa de dados em uma das interfaces de comunicação, entre
outros.
• Externas: São interrupções geradas por sensores conectados aos pinos PD2
(INT0) e PD3 (INT1) e possibilitam diversas configurações, podendo ser ativadas
nas bordas de subida ou descida, em ambas ou com o nível lógico baixo. Além
disso, tem prioridade de execução. São configuradas através dos registradores
EICRA e EIMSK e monitoradas pelo registrador EIFR.
• Mudança de Nível no Pino (Pin Change Interrupts): Disponível em todos os
pinos digitais e podem ser ativadas quando ocorre mudança de nível em um
dos pinos. São configuradas nos registradores PCMSK0, PCMSK1, PCMSK2 e
PCICR e monitoradas pelo registrador PCIFR.

No projeto a seguir vamos aplicar os conceitos de interrupção externa e de


interrupção por mudança de nível no pino.

Material necessário:
• 1 Arduino.
• 1 Chave Táctil (push button).
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 Resistor de 10k Ohms (marrom, preto laranja).
• 1 LED (qualquer cor).
• 1 Protoboard.
• Cabos de ligação.

Realize a montagem dos componentes da maneira indicada pela Figura 24.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 55


Figura 24: Conexões

Neste programa vamos usar uma interrupção externa que será gerada pelo
pressionamento da chave táctil. Observe que na função main configuramos os pinos e
a interrupção que, neste exemplo, será gerada na borda de descida (bit ISC01 = 1). Em
seguida o programa ficará executando a instrução while indefinidamente. A ideia
básica consiste na execução da função ISR quando ocorre uma interrupção. Neste
momento, a execução do while e, por consequência, a função main é suspensa até o
término da função ISR.

#define F_CPU 16000000UL


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

int main(void) {
DDRB |= (1 << DDB5);
DDRD &= ~(1 << DDD2);

EICRA |= (1 << ISC01);


EIMSK |= (1 << INT0);
SREG |= (1 << SREG_I);

while(1);
}

ISR(INT0_vect) {
PORTB ^= (1 << PORTB5);
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 56


Analisando detalhadamente o código-fonte, note que configuramos o
registrador EICRA que apresenta a seguinte estrutura:

EICRA – External Interrupt Control Register A


7 6 5 4 3 2 1 0
- - - - ISC11 ISC10 ISC01 ISC00

Sendo que:

Interrupt 1 Sense Control (PD3 - INT1) ISC11 ISC10


Interrupção ativada com nível baixo 0 0
Qualquer mudança de nível gera a interrupção 0 1
Interrupção gerada na borda de descida 1 0
Interrupção gerada na borda de subida 1 1

De maneira análoga:

Interrupt 0 Sense Control (PD2 – INT0) ISC01 ISC00


Interrupção ativada com nível baixo 0 0
Qualquer mudança de nível gera a interrupção 0 1
Interrupção gerada na borda de descida 1 0
Interrupção gerada na borda de subida 1 1

Desta forma, como usamos a instrução EICRA |= (1 << ISC01), configuramos a


interrupção para ocorrer quando for detectada borda de descida em PD2 (INT0). Na
sequência do programa, observe que também foi configurado o registrador EIMSK.

EIMSK – External Interrupt Mask Register


7 6 5 4 3 2 1 0
- - - - - - INT1 INT0

Onde:
• Bit 1 - INT1 (External Interrupt Request 1 Enable)
• Bit 0 - INT0 (External Interrupt Request 0 Enable)

Então, através da instrução EIMSK |= (1 << INT0), configuramos INT0. A


instrução seguinte SREG |= (1 << SREG_I) vai ativar o flag I (Global Interrupt Enable) no
registrador SREG para que as interrupções ocorram.

Não foi usado neste programa, mas temos também o registrador EIFR –
External Interrupt Flag Register.

EIFR – External Interrupt Flag Register


7 6 5 4 3 2 1 0
- - - - - - INTF1 INTF0

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 57


Sendo que quando os bits relacionados às interrupções INT1 e INT0 estiverem
ligados (nível lógico 1), eles estão indicando a ocorrência da interrupção.

Note neste próximo exemplo que as demais variáveis e instruções são usadas
para implementar a rotina de debounce na chave táctil, rotina esta já abordada no
assunto alusivo às entradas digitais.

#define F_CPU 16000000UL


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

char valor;
char estado = 0;
char anterior = 0;

int main(void) {
DDRB |= (1 << DDB5);
DDRD &= ~(1 << DDD2);

EICRA |= (1 << ISC00);


EIMSK |= (1 << INT0);
SREG |= (1 << SREG_I);

while(1);
}

ISR(INT0_vect) {
valor = (PIND & (1 << PIND2)) == (1 << PIND2);
if (valor == 1 && anterior == 0) {
if (estado == 1)
estado = 0;
else
estado = 1;
}

if (estado == 1)
PORTB |= (1 << PORTB5);
else
PORTB &= ~(1 << PORTB5);
anterior = valor;
_delay_ms(50);
}

Analisando o código-fonte, note que configuramos o registrador EICRA. Desta


forma, como usamos a instrução EICRA |= (1 << ISC00), configuramos a interrupção
para ocorrer sempre que houver qualquer mudança de nível em PD2 (INT0). Na
sequência do programa, observe que também foi configurado o registrador EIMSK
através da instrução EIMSK |= (1 << INT0), configuramos INT0.
Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 58
O programa seguinte usará uma interrupção de mudança de nível no pino que
será ativada ao se pressionar a chave táctil. O circuito eletrônico é o mesmo que já
usamos para exemplificar as interrupções externas. Porém, antes de iniciar o
programa, vamos identificar qual interrupção deverá ser ativada.

Figura 25: Identificação das Interrupções

Considerando que o botão está conectado ao pino PD2 (Pino 2 do Arduino),


veja na Figura 25 que a interrupção por mudança de nível do pino associada é a
PCINT18.

#define F_CPU 16000000UL


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

char valor;
char estado = 0;
char anterior = 0;

int main(void) {
DDRB |= (1 << DDB5);
DDRD &= ~(1 << DDD2);

PCICR |= (1 << PCIE2);


PCMSK2 |= (1 << PCINT18);
SREG |= (1 << SREG_I);

while(1);
}

ISR(PCINT2_vect) {
valor = (PIND & (1 << PIND2)) == (1 << PIND2);
if (valor == 1 && anterior == 0) {
if (estado == 1)
estado = 0;
else
estado = 1;
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 59


if (estado == 1)
PORTB |= (1 << PORTB5);
else
PORTB &= ~(1 << PORTB5);
anterior = valor;
_delay_ms(50);
}

Analisando o código-fonte, note que configuramos o registrador PCICR que


apresenta a seguinte estrutura:

PCICR – Pin Change Interrupt Control Register


7 6 5 4 3 2 1 0
- - - - - PCIE2 PCIE1 PCIE0

Onde:

• Bit 2 - PCIE2 (Pin Change Interrupt Enable 2): Habilita das interrupções de
PCINT23 até PCINT16.
• Bit 1 - PCIE1 (Pin Change Interrupt Enable 1): Habilita das interrupções de
PCINT14 até PCINT8.
• Bit 0 - PCIE0 (Pin Change Interrupt Enable 0): Habilita das interrupções de
PCINT7 até PCINT0.

Neste projeto precisamos habilitar PCINT18, desta maneira, o bit PCIE2 deverá
ser ligado através da instrução PCICR |= (1 << PCIE2). Em seguida, temos 3
registradores que são usados para identificar a interrupção de forma específica.

PCMSK2 – Pin Change Mask Register 2


7 6 5 4 3 2 1 0
PCINT23 PCINT22 PCINT21 PCINT20 PCINT19 PCINT18 PCINT17 PCINT16

PCMSK1 – Pin Change Mask Register 1


7 6 5 4 3 2 1 0
- PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8

PCMSK0 – Pin Change Mask Register 0


7 6 5 4 3 2 1 0
PCINT7 PCINT6 PCINT5 PCINT4 PCINT3 PCINT2 PCINT1 PCINT0

Desta maneira, para habilitar PCINT18 é necessário ligar o respectivo bit no


registrador PCMSK2, usando a instrução PCMSK2 |= (1 << PCINT18).

Apesar de não ser usado neste programa o registrador PCIFR pode ser usado
para identificar a ocorrência uma interrupção.

PCIFR – Pin Change Interrupt Flag Register

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 60


7 6 5 4 3 2 1 0
- - - - - PCIF2 PCIF1 PCIF0

Onde:

• Bit 2: PCIF2 (Pin Change Interrupt Flag 2) – Quando ligado, indica que ocorreu
uma interrupção de PCINT23 até PCINT16.
• Bit 1: PCIF1 (Pin Change Interrupt Flag 1) – Indica que ocorreu uma interrupção
de PCINT14 até PCINT8.
• Bit 0: PCIF0 (Pin Change Interrupt Flag 0) – Indica uma interrupção de PCINT7
até PCINT0.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 61


Temporizadores e Contadores

O ATmega328P apresenta 3 conjuntos de circuitos temporizadores/contadores,


sendo dois de 8 bits (TIMER0 e TIMER2) e um de 16 bits (TIMER1). No TIMER0 temos
os seguintes registradores associados:

• TCCR0A (Timer/Counter 0 Control Register A)


• TCCR0B (Timer/Counter 0 Control Register B)
• TIMSK0 (Timer/Counter 0 Interrupt Mask Register)
• TIFR0 (Timer/Counter 0 Interrupt Flag Register)
• TCNT0 (Timer/Counter 0 Register)
• OCR0A (Timer/Counter 0 Output Compare Register A)
• OCR0B (Timer/Counter 0 Output Compare Register B).

Sendo que TCCR0A (Timer/Counter 0 Control Register A) e TCCR0B


(Timer/Counter 0 Control Register B) responsáveis pela configuração.

TCCR0A – Timer/Counter 0 Control Register A


7 6 5 4 3 2 1 0
COM0A1 COM0A0 COM0B1 COM0B0 - - WGM01 WGM00

TCCR0B – Timer/Counter 0 Control Register B


7 6 5 4 3 2 1 0
FOC0A FOC0B - - WGM02 CS02 CS01 CS00

Os bits 7 e 6 (OC0A Compare Output Mode) do registrador TCCR0A determinam


o modo de comparação da saída OC0A, sendo:

Modo de Comparação COM0A1 COM0A0


OC0A Desabilitado 0 0
WGM02 = 0: Operação normal da porta, OC0A Desconectado 0 1
WGM02 = 1: Mudar estado de OC0A na comparação (Compare
Match)
Modo Não Invertido (Nível HIGH na borda inferior, LOW na 1 0
igualdade)
Modo Invertido (LOW na borda inferior, HIGH na igualdade) 1 1

Os bits 5 e 4 (OC0B Compare Output Mode) do registrador TCCR0A determinam


o modo de comparação da saída OC0B, sendo:

Modo de Comparação COM0B1 COM0B0


OC0B Desabilitado 0 0
Reservado 0 1
Modo Não Invertido (Nível HIGH na borda inferior, LOW na 1 0
igualdade)
Modo Invertido (LOW na borda inferior, HIGH na igualdade) 1 1

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 62


Os bits 1 (WGM01) e 0 (WGM00) do registrador TCCR0A e bit 3 (WGM02) do
registrador TCCR0B definem a forma de onda gerada (Wave Form Generator Mode),
conforme a tabela a seguir.

Valor
Função Modo WGM02 WGM01 WGM00
Máximo
Normal 0 0xFF 0 0 0
PWM com Fase Corrigida 1 0xFF 0 0 1
Limpar temporizador na
2 OCRA 0 1 0
comparação (CTC)
PWM Rápido 3 0xFF 0 1 1
Reservado 4 - 1 0 0
PWM com Fase Corrigida 5 OCRA 1 0 1
Reservado 6 - 1 1 0
PWM Rápido 7 OCRA 1 1 1

Os bits 7 e 6 (Force Output Compare) do registrador TCCR0B, quando


apresentam o valor 1 nos respectivos bits, são utilizados para realizar a comparação
imediata da saída do temporizador. Aplicável apenas quando o temporizador é usado
em modo não PWM.

Os bits 2, 1 e 0 (Clock Source) do registrador TCCR0B permitem a configuração


da origem do sinal de relógio (clock), conforme mostrado na próxima tabela.

Função CS02 CS01 CS00


Temporizador/Contador desabilitado 0 0 0
Não usar prescaling 0 0 1
Clock / 8 0 1 0
Clock / 64 0 1 1
Clock / 256 1 0 0
Clock / 1024 1 0 1

No registrador TIMSK0 (Timer/Counter 0 Interrupt Mask Register) é realizada a


configuração das interrupções, definindo o valor 1 no bit correspondente à interrupção
a ser habilitada.

TIMSK0 – Timer/Counter 0 Interrupt Mask Register


7 6 5 4 3 2 1 0
- - - - - OCIE0B OIE0A TOIE0

Onde:

• Bit 2 (OCIE0B - Timer/Counter 0 Output Compare Match B Interrupt Enable)


• Bit 1 (OCIE0A - Timer/Counter 0 Output Compare Match A Interrupt Enable)
• Bit 0 (TOIE0 - Timer/Counter 0 Overflow Interrupt Enable)

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 63


Enquanto no registrador TIFR0 (Timer/Counter 0 Interrupt Flag Register) temos
os flags associados às interrupções. O respectivo bit receberá o valor 1 quando ocorrer
a interrupção.

TIFR0 – Timer/Counter 0 Interrupt Flag Register


7 6 5 4 3 2 1 0
- - - - - OCF0B OCF0A TOV0

Sendo:

• Bit 2 (OCF0B - Timer/Counter 0 Output Compare B Match Flag)


• Bit 1 (OCF0A - Timer/Counter 0 Output Compare A Match Flag)
• Bit 0 (TOV0 - Timer/Counter 0 Overflow Flag)

O registrador TCNT0 (Timer/Counter 0 Register) armazena o valor do contador.

TCNT0 – Timer/Counter 0 Register


7 6 5 4 3 2 1 0
D D D D D D D D
D – Bits de Dados

Os registradores OCR0A (Timer/Counter 0 Output Compare Register A) e OCR0B


(Timer/Counter 0 Output Compare Register B) armazenam o valor comparado,
apresentando a mesma estrutura:

OCR0A – Timer/Counter 0 Output Compare Register A /


OCR0B – Timer/Counter 0 Output Compare Register B
7 6 5 4 3 2 1 0
D D D D D D D D
D – Bits de Dados

Inicialmente vamos aplicar estes conceitos criando um contador, usando o


TIMER0, que irá alternar o estado de LED em intervalos regulares de tempo. No
exemplo, vamos considerar o LED conectado ao pino PB5 (Pino 13) do ATmega328P.

#define F_CPU 16000000UL


#include <avr/io.h>

int main(void) {
DDRB |= (1 << DDB5);
TCCR0B |= (1 << CS02) | (1 << CS00);
TCNT0 = 0;
unsigned char tempoextra = 0;
while(1) {
if (TCNT0 >= 0xFF) {
tempoextra++;
TCNT0 = 0;

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 64


}
if (tempoextra > 77) {
PORTB ^= (1 << PORTB5);
tempoextra = 0;
}
}
}

Observe que configuramos o registrador TCCR0B com prescaler de Clock / 1024


e na sequência zeramos o contador (TCNT0 = 0). Também devemos considerar que o
contador irá chegar rapidamente ao seu valor máximo (TCNT0 = 0xFF), fato este, que
não nos permitiria ver a transição de estado do LED se usássemos somente o
registrador TCNT0. Desta maneira, criamos uma variável (tempoextra) que apenas irá
realizar a mudança de estado do LED, após uma determinada quantidade de vezes que
o contador chegou ao seu valor máximo e foi zerado, gerando um novo ciclo.

Outra possibilidade, é usarmos uma interrupção associada ao TIMER0, como


demonstrado no programa a seguir. Neste caso, no registrador TIMSK0 habilitamos o
bit TOIE0 (Timer/Counter0 Overflow Interrupt Enable) e, quando ocorrer a interrupção
por conta do overflow do contador, a função ISR é executada. Salientando que
devemos ligar o flag I (Global Interrupt Enable) no registrador SREG para habilitar o uso
de interrupções.

#define F_CPU 16000000UL


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

unsigned char tempoextra = 0;

int main(void) {
TCCR0B |= (1 << CS02) | (1 << CS00);
TIMSK0 |= (1 << TOIE0);
SREG |= (1 << SREG_I);
DDRB |= (1 << DDB5);
while(1);
}

ISR(TIMER0_OVF_vect) {
tempoextra++;
if(tempoextra > 77){
PORTB ^= (1 << PORTB5);
tempoextra = 0;
}
}
O overflow do temporizador/contador em conjunto com a variável tempoextra
contendo o valor 77 irá realizar a mudança do estado do LED aproximadamente a cada
1 segundo.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 65


O TIMER1 é de 16 bits apresentando 2 saídas e possui o seguinte conjunto de
registradores:

• TCCR1A (Timer/Counter 1 Control Register A)


• TCCR1B (Timer/Counter 1 Control Register B)
• TCCR1C (Timer/Counter 1 Control Register C)
• TIMSK1 (Timer/Counter 1 Interrupt Mask Register)
• TIFR1 (Timer/Counter 1 Interrupt Flag Register)
• TCNT1H (Timer/Counter 1 Register - High)
• TCNT1L (Timer/Counter 1 Register - Low)
• OCR1AH (Timer/Counter 1 Output Compare Register A - High)
• OCR1AL (Timer/Counter 1 Output Compare Register A - Low)
• OCR1BH (Timer/Counter 1 Output Compare Register B - High)
• OCR1BL (Timer/Counter 1 Output Compare Register B - Low).

Sendo que TCCR1A (Timer/Counter 1 Control Register A), TCCR1B


(Timer/Counter 1 Control Register B) e TCCR1C (Timer/Counter 1 Control Register C)
são responsáveis pela configuração do temporizador/contador.

TCCR1A – Timer/Counter 1 Control Register A


7 6 5 4 3 2 1 0
COM1A1 COM1A0 COM1B1 COM1B0 - - WGM11 WGM10

TCCR1B – Timer/Counter 1 Control Register B


7 6 5 4 3 2 1 0
ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10

TCCR1C – Timer/Counter 1 Control Register C


7 6 5 4 3 2 1 0
FOC1A FOC1B - - - - - -

Os bits 7 e 6 (OC1A Compare Output Mode) e bits 5 e 4 (OC1B Compare Output


Mode) do registrador TCCR1A determinam o modo de comparação das saídas OC1A e
OC1B, sendo:

COM1A1 COM1A0
Modo de Comparação
COM1B1 COM1B1
Modo normal de operação, OC1A/OC1B desconectados. 0 0
Modos 9, 11, 14 e 15* apenas: Habilita OC1A e OC1B permanece 0 1
desconectado.
Modo Não Invertido (Nível HIGH na borda inferior, LOW na 1 0
igualdade)
Modo Invertido (LOW na borda inferior, HIGH na igualdade) 1 1
* Modos definidos pelos bits bits 1 (WGM11) e 0 (WGM10) do registrador TCCR1A e
bits 4 (WGM13) e 3 (WGM12) do registrador TCCR1B.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 66


No registrador TCCR1B, o bit 7 (ICNC1 - Input Capture Noise Canceler) ativa o
filtro de ruídos e o bit 6 (ICES1 - Input Capture Edge Select) seleciona a forma de
capture.

Os bits 1 (WGM11) e 0 (WGM10) do registrador TCCR1A e bits 4 (WGM13) e 3


(WGM12) do registrador TCCR1B definem a forma de onda gerada (Wave Form
Generator Mode), conforme a tabela a seguir.

Valor
Função Modo WGM13 WGM12 WGM11 WGM10
Máximo
Normal 0 0xFFFF 0 0 0 0
PWM com Fase
1 0x00FF 0 0 0 1
Corrigida, 8 bits
PWM com Fase
2 0x01FF 0 0 1 0
Corrigida, 9 bits
PWM com Fase
3 0x03FF 0 0 1 1
Corrigida, 10 bits
Reservado 4 - 0 1 0 0
PWM Rápido, 8bits 5 0x00FF 0 1 0 1
PWM Rápido, 9 bits 6 0x01FF 0 1 1 0
PWM Rápido, 10 bits 7 0x03FF 0 1 1 1
PWM com Frequência
8 ICR1 1 0 0 0
e Fase Corrigida
PWM com Frequência
9 OCR1A 1 0 0 1
e Fase Corrigida
PWM com Fase
10 ICR1 1 0 1 0
Corrigida
PWM com Fase
11 OCR1A 1 0 1 1
Corrigida
Reservado 12 - 1 1 0 0
Reservado 13 - 1 1 0 1
PWM Rápido 14 ICR1 1 1 1 0
PWM Rápido 15 OCR1A 1 1 1 1

Os bits 2, 1 e 0 (Clock Source) do registrador TCCR1B permitem a configuração


da origem do sinal de relógio (clock), conforme mostrado na próxima tabela.

Função CS12 CS11 CS10


Temporizador/Contador desabilitado 0 0 0
Não usar prescaling 0 0 1
Clock / 8 0 1 0
Clock / 64 0 1 1
Clock / 256 1 0 0
Clock / 1024 1 0 1

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 67


Os bits 7 e 6 (Force Output Compare) do registrador TCCR1C, quando
apresentam o valor 1 nos respectivos bits, são utilizados para realizar a comparação
imediata da saída do temporizador.

No registrador TIMSK1 (Timer/Counter 1 Interrupt Mask Register) é realizada a


configuração das interrupções, definindo o valor 1 no bit correspondente à interrupção
a ser habilitada.

TIMSK1 – Timer/Counter 1 Interrupt Mask Register


7 6 5 4 3 2 1 0
- - ICIE1 - - OCIE1B OCIE1A TOIE1

Onde:

• Bit 5 (ICIE1 - Timer/Counter 1, Input Capture Interrupt Enable)


• Bit 2 (OCIE1B - Timer/Counter 1 Output Compare Match B Interrupt Enable)
• Bit 1 (OCIE1A - Timer/Counter 1 Output Compare Match A Interrupt Enable)
• Bit 0 (TOIE1 - Timer/Counter 1 Overflow Interrupt Enable)

Enquanto no registrador TIFR1 (Timer/Counter 1 Interrupt Flag Register) temos


os flags associados às interrupções. O respectivo bit receberá o valor 1 quando ocorrer
a interrupção.

TIFR1 – Timer/Counter 1 Interrupt Flag Register


7 6 5 4 3 2 1 0
- - ICF1 - - OCF1B OCF1A TOV1

Sendo:

• Bit 5 (ICF1 - Timer/Counter 1 Input Capture Flag)


• Bit 2 (OCF1B - Timer/Counter 1 Output Compare B Match Flag)
• Bit 1 (OCF1A - Timer/Counter 1 Output Compare A Match Flag)
• Bit 0 (TOV1 - Timer/Counter 1 Overflow Flag)

Os registradores TCNT1H e TCNT1L (Timer/Counter Registers) armazenam o


valor do contador (16 bits).

Timer/Counter Registers
TCNT1H TCNT1L
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
D D D D D D D D D D D D D D D D
D – Bits de Dados

Os registradores OCR1AH (Output Compare Registers A - High) e OCR1AL


(Output Compare Register A - Low) armazenam o valor comparado (16 bits).

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 68


Output Compare Registers A
OCR1AH OCR1AL
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
D D D D D D D D D D D D D D D D
D – Bits de Dados

Os registradores OCR1BH (Output Compare Registers B - High) e OCR1BL


(Output Compare Register B - Low) armazenam o valor comparado (16 bits).

Output Compare Registers B


OCR1BH OCR1BL
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
D D D D D D D D D D D D D D D D
D – Bits de Dados

No TIMER2 (8 bits) temos os seguintes registradores associados:

• TCCR2A (Timer/Counter 2 Control Register A)


• TCCR2B (Timer/Counter 2 Control Register B)
• TIMSK2 (Timer/Counter 2 Interrupt Mask Register)
• TIFR2 (Timer/Counter 2 Interrupt Flag Register)
• TCNT2 (Timer/Counter 2 Register)
• OCR2A (Timer/Counter 2 Output Compare Register A)
• OCR2B (Timer/Counter 2 Output Compare Register B).

Sendo que TCCR2A (Timer/Counter 2 Control Register A) e TCCR2B


(Timer/Counter 2 Control Register B) são usados para a configuração.

TCCR2A – Timer/Counter 2 Control Register A


7 6 5 4 3 2 1 0
COM2A1 COM2A0 COM2B1 COM2B0 - - WGM21 WGM20

TCCR0B – Timer/Counter 0 Control Register B


7 6 5 4 3 2 1 0
FOC2A FOC2B - - WGM22 CS22 CS21 CS20

Os bits 7 e 6 (OC2A Compare Output Mode) do registrador TCCR2A determinam


o modo de comparação da saída OC2A, sendo:

Modo de Comparação COM2A1 COM2A0


OC2A Desabilitado 0 0
WGM22 = 0: Operação normal da porta, OC2A Desconectado 0 1
WGM22 = 1: Mudar estado de OC2A na comparação (Compare
Match)
Modo Não Invertido (Nível HIGH na borda inferior, LOW na 1 0
igualdade)
Modo Invertido (LOW na borda inferior, HIGH na igualdade) 1 1
Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 69
Os bits 5 e 4 (OC2B Compare Output Mode) do registrador TCCR2A determinam
o modo de comparação da saída OC2B, sendo:

Modo de Comparação COM2B1 COM2B0


OC2B Desabilitado 0 0
Reservado 0 1
Modo Não Invertido (Nível HIGH na borda inferior, LOW na 1 0
igualdade)
Modo Invertido (LOW na borda inferior, HIGH na igualdade) 1 1

Os bits 1 (WGM21) e 0 (WGM20) do registrador TCCR2A e bit 3 (WGM22) do


registrador TCCR2B definem a forma de onda gerada (Wave Form Generator Mode),
conforme a tabela a seguir.

Valor
Função Modo WGM22 WGM21 WGM20
Máximo
Normal 0 0xFF 0 0 0
PWM com Fase Corrigida 1 0xFF 0 0 1
Limpar temporizador na
2 OCRA 0 1 0
comparação (CTC)
PWM Rápido 3 0xFF 0 1 1
Reservado 4 - 1 0 0
PWM com Fase Corrigida 5 OCRA 1 0 1
Reservado 6 - 1 1 0
PWM Rápido 7 OCRA 1 1 1

Os bits 7 e 6 (Force Output Compare) do registrador TCCR2B, quando


apresentam o valor 1 nos respectivos bits, são utilizados para realizar a comparação
imediata da saída do temporizador. Aplicável apenas quando o temporizador é usado
em modo não PWM.

Os bits 2, 1 e 0 (Clock Source) do registrador TCCR2B permitem a configuração


da origem do sinal de relógio (clock), conforme mostrado na próxima tabela.

Função CS22 CS21 CS20


Temporizador/Contador desabilitado 0 0 0
Não usar prescaling 0 0 1
Clock / 8 0 1 0
Clock / 32 0 1 1
Clock / 64 1 0 0
Clock / 128 1 0 1
Clock / 256 1 1 0
Clock / 1024 1 1 1

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 70


No registrador TIMSK2 (Timer/Counter 2 Interrupt Mask Register) é realizada a
configuração das interrupções, definindo o valor 1 no bit correspondente à interrupção
a ser habilitada.

TIMSK2 – Timer/Counter 2 Interrupt Mask Register


7 6 5 4 3 2 1 0
- - - - - OCIE2B OIE2A TOIE2

Onde:

• Bit 2 (OCIE2B - Timer/Counter 2 Output Compare Match B Interrupt Enable)


• Bit 1 (OCIE2A - Timer/Counter 2 Output Compare Match A Interrupt Enable)
• Bit 0 (TOIE2 - Timer/Counter 2 Overflow Interrupt Enable)

Enquanto no registrador TIFR2 (Timer/Counter 2 Interrupt Flag Register) temos


os flags associados às interrupções. O respectivo bit receberá o valor 1 quando ocorrer
a interrupção.

TIFR2 – Timer/Counter 2 Interrupt Flag Register


7 6 5 4 3 2 1 0
- - - - - OCF2B OCF2A TOV2

Sendo:

• Bit 2 (OCF2B - Timer/Counter 2 Output Compare B Match Flag)


• Bit 1 (OCF2A - Timer/Counter 2 Output Compare A Match Flag)
• Bit 0 (TOV2 - Timer/Counter 2 Overflow Flag)

O registrador TCNT2 (Timer/Counter 2 Register) armazena o valor do contador.

TCNT2 – Timer/Counter 2 Register


7 6 5 4 3 2 1 0
D D D D D D D D
D – Bits de Dados

Os registradores OCR2A (Timer/Counter 2 Output Compare Register A) e OCR2B


(Timer/Counter 2 Output Compare Register B) armazenam o valor comparado,
apresentando a mesma estrutura:

OCR2A – Timer/Counter 2 Output Compare Register A /


OCR2B – Timer/Counter 2 Output Compare Register B
7 6 5 4 3 2 1 0
D D D D D D D D
D – Bits de Dados

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 71


Modulação por Largura de Pulso

A Modulação por Largura de Pulso (Pulse Width Modulation - PWM) consiste


em uma técnica que permite produzir um conjunto de sinais digitais (pulsos) enviados
durante um determinado período. Se plotarmos em um gráfico um sinal digital ao
longo do tempo, vamos obter uma forma de onda quadrada que alterna seu estado
em nível lógico 1 (HIGH) e um nível lógico 0 (LOW).

A razão entre o período de pico e o período total da onda é chamada de razão


de ciclo (Duty Cycle). Dessa forma, podemos entender que se durante um período
temos apenas o nível lógico 0 (LOW) a saída PWM produzirá uma razão de ciclo de 0%,
resultando em no valor zero, conforme podemos observar na Figura 26:

Figura 26: Razão de ciclo de 0%.

Por outro lado, se durante todo o período o sinal permanecer apenas um nível
1 (HIGH), teremos 100% da razão de ciclo (Figura 27), resultando no valor 255 se
considerarmos, nesse exemplo, uma saída digital do Arduino.

Figura 27: Razão de ciclo de 100%.

Do mesmo modo podemos obter valores intermediários entre esses dois


extremos. Por exemplo, se a razão de ciclo for 25% do tempo em nível 1 (HIGH) e 75%
do tempo em nível 0 (LOW), teremos uma razão de ciclo de 25% (Figura 28), a qual
resultará no valor 64, ou seja, um quarto (25%) de 256.

Figura 28: Razão de ciclo de 25%.

Desta forma, podemos calcular a razão de ciclo em função do tempo que o sinal
permanece um nível 1 (HIGH) e em nível 1 (LOW):

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 72


Razão de Ciclo = (Tempo_HIGH / (Tempo_HIGH + Tempo_LOW)) * 100

Sendo que a tensão de saída poderá ser determinada a partir da fórmula:

Tensão de Saída = Razão de Ciclo * Tensão de Entrada

O ATmega328P possui 6 saídas PWM que são PD3, PD5, PD6, PB1, PB2 e PB3,
correspondendo respectivamente aos Pinos 3, 5, 6, 9, 10 e 11 do Arduino. Cada
temporizador (TIMER0, TIMER1 e TIMER2) possui duas saídas PWM conectadas. Desta
forma, no TIMER0 temos conectados PD5 e PD6, enquanto no TIMER1 temos
conectados PB1 e PB2 e no TIMER2 temos PD3 e PB3.

Com base nos conceitos apresentados, vamos desenvolver um projeto que irá
mostrar a variação de intensidade de luz emitida por um LED conectado à uma porta
digital, configurada no modo PWM.

Material necessário:
• 1 Arduino.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 LED de qualquer cor.
• 1 Protoboard.
• Cabos de ligação.

Realize a montagem da maneira indicada pela Figura 29.

Figura 29: Conexões

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 73


Crie o seguinte programa, faça a compilação e o carregue no Arduino. Note que
usaremos o pino PB1 (Pino 9 do Arduino) para conectar o LED. Lembrando que o pino
PB1 está conectado ao TIMER1 do ATmega328P.

#define F_CPU 16000000UL


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

int main (void) {


DDRB |= (1 << DDB1);
TCCR1A |= (1 << COM1A1) | (1 << WGM10);
TCCR1B |= (1 << CS12) | (1 << CS10) | (1 << WGM12);

while(1) {
for (unsigned int pwm = 0; pwm < 256; pwm++) {
OCR1A = pwm;
_delay_ms(10);
}
}
}

Inicialmente, configuramos o pino PB1 como saída realizamos a


configuração do temporizador/contador usando os registradores TCCR1A e TCCR1B. A
ligar o bit COM1A1 no registrador TCCR1A definimos o modo não invertido, os bits
WGM10 (TCCR1A) e WGM12(TCCR1B) define o modo PWM Rápido de 8 bits. Por fim,
ao ativar os bits CS12 e CS10 do registrador TCCR1B determinamos um prescale de
Clock / 1024.

No modo PWM Rápido de 8 bits o valor mínimo do PWM é 0 e o máximo 255.


Desta forma, criamos uma estrutura de repetição usando a instrução for que irá
produzir os valores inteiros nesta faixa, aplicando ao registrador de resultado OCR1A
em intervalos de 10ms. Isto fará com que o LED acenda gradativamente até atingir o
valor máximo e depois reinicia o ciclo partindo o valor mínimo.

No projeto seguinte, iremos usar um potenciômetro, conectado à uma das


entradas analógicas do Arduino para, com base no valor obtido pelo ADC, ajustar a
intensidade de luz emitida por um LED conectado à uma das saídas PWM.

Material necessário:
• 1 Arduino.
• 1 Potenciômetro de 10k Ohms.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 LED de qualquer cor.
• 1 Protoboard.
• Cabos de ligação.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 74


Realize a montagem da maneira indicada pela Figura 30.

Figura 30: Conexões

Crie o seguinte programa, faça a compilação e o carregue no Arduino. Note que


usaremos os pinos PB3 (Pino 11 do Arduino) para conectar o LED e o ADC0 (Pino A0 do
Arduino) para ligar o potenciômetro. Como usamos o pino PB3 e ele está conectado ao
Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 75
TIMER2 do ATmega328P, após definir o pino como saída iniciamos a configuração do
temporizador/contador através dos registradores TCCR2A e TCCR2B. Em resumo,
configuramos em modo não invertido (Bit COM2A1), PWM Rápido (Bits WGM21 e
WGM22) e com prescale de Clock / 1024 (Bits CS22, CS21 e CS20). Na sequência do
programa, configuramos o ADC, já abordado anteriormente.

#define F_CPU 16000000UL


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

int main (void) {


DDRB |= (1 << DDB3);
TCCR2A |= (1 << COM2A1) | (1 << WGM21) | (1 << WGM20);
TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);

ADMUX |= (1 << REFS0);


ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRA |= (1 << ADEN);
int valor_adc;

while(1) {
ADCSRA = ADCSRA | (1 << ADSC);
while(ADCSRA & (1 << ADSC));

valor_adc = ADCL;
valor_adc |= (ADCH << 8);
OCR2A = valor_adc / 4;
_delay_ms(10);
}
}

Note que conforme giramos o eixo do potenciômetro, alteramos o valor da


conversão realizada pelo ADC (entre 0 e 1023). Este valor é dividido por 4 e aplicado ao
registrador de resultado OCR2A, que pode receber valores entre 0 e 255, que irá
determinar a intensidade de luz que será emitida pelo LED.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 76


Interface entre o Controlador HD44780 e o ATMEGA328P

O HD44780 é um controlador/driver para displays de cristal líquido


amplamente utilizado em conjunto com microcontroladores de 4 bits ou 8bits. Uma
vez que todas as funções, como memória para exibição, geração de símbolos
(caracteres) e driver de cristal líquido, necessárias para acionar o display de cristal
líquido baseado em matriz de pontos estão implementadas no HD44780, simplifica
bastante as tarefas e serem realizadas pelo microcontrolador.

A seguir vamos desenvolver um projeto que mostra a conexão do display LCD


ao Arduino e seu funcionamento.

Material necessário:
• 1 Arduino.
• 1 Display LCD1602.
• 1 Potenciômetro de 10k Ohms.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 Protoboard.
• Cabos de ligação.

Após providenciar o material necessário, monte o circuito eletrônico mostrado


na Figura 31.

Figura 31: Conexões

Desta forma, temos:

• Pino 1 (VSS) do LCD ligado ao GND do Arduino.


• Pino 2 (VDD) do LCD ligado ao 5V do Arduino.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 77


• Pino 3 (V0) do LCD ligado ao pino central do potenciômetro (controle de
contraste).
• Pino 4 (RS) do LCD ligado ao pino PD2 (Pino 2) do Arduino.
• Pino 5 (RW) do LCD ligado ao GND do Arduino.
• Pino 6 (E) do LCD ligado ao pino PD3 (Pino 3) do Arduino.
• Pino 11 (D4) do LCD ligado ao PD3 (Pino 4) do Arduino.
• Pino 12 (D5) do LCD ligado ao PD3 (Pino 5) do Arduino.
• Pino 13 (D6) do LCD ligado ao PD3 (Pino 6) do Arduino.
• Pino 14 (D7) do LCD ligado ao PD3 (Pino 7) do Arduino.
• Pino 15 (A) do LCD ligado ao 5V do Arduino com um resistor de 220 ohms
(controle do brilho).
• Pino 16 (K) do LCD ligado ao GND do Arduino.

Passamos agora para a programação. Devemos ter em mente que as


informações necessárias estão disponíveis no manual do HD44780. Desta maneira,
iremos adotá-lo como base para o desenvolvimento das funções para configuração e
uso do display de LCD. Levando em consideração que o display de LCD pode ser usado
em inúmeros projetos vamos colocar as funções desenvolvidas em uma biblioteca, que
iremos chamar de lib-lcd.h.

#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>

#ifndef LCD_Port
#define LCD_Port PORTD
#endif
#ifndef LCD_DPin
#define LCD_DPin DDRD
#endif
#ifndef RSPIN
#define RSPIN PD2
#endif
#ifndef ENPIN
#define ENPIN PD3
#endif

void LCD_StartRW() {
LCD_Port |= (1 << ENPIN);
_delay_us(1);
LCD_Port &= ~ (1 << ENPIN);
}

void LCD_Cmd( unsigned char cmd ) {


LCD_Port = (LCD_Port & 0x0F) | (cmd & 0xF0);
LCD_Port &= ~(1 << RSPIN);

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 78


LCD_StartRW();
_delay_us(200);
LCD_Port = (LCD_Port & 0x0F) | (cmd << 4);
LCD_StartRW();
_delay_ms(2);
}

void LCD_Init() {
LCD_DPin |= (1 << PD7) | (1 << PD6) | (1 << PD5) | (1 << PD4)
| (1 << PD3) | (1 << PD2);
_delay_ms(15);
LCD_Cmd(0x03);
_delay_ms(4.1);
LCD_Cmd(0x03);
_delay_us(100);
LCD_Cmd(0x03);
LCD_Cmd(0x02);
LCD_Cmd(0x0C);
LCD_Cmd(0x06);
LCD_Cmd(0x01);
_delay_ms(2);
}

void LCD_Clear() {
LCD_Cmd (0x01);
_delay_ms(2);
LCD_Cmd (0x80);
}

void LCD_Print (char *str) {


for(int i = 0; str[i] != 0; i++) {
LCD_Port = (LCD_Port & 0x0F) | (str[i] & 0xF0);
LCD_Port |= (1 << RSPIN);
LCD_StartRW();
_delay_us(200);
LCD_Port = (LCD_Port & 0x0F) | (str[i] << 4);
LCD_StartRW();
_delay_ms(2);
}
}

void LCD_PrintXY (unsigned char col, unsigned char lin, char


*str) {
unsigned char end_lin[] = {0x80, 0xC0};
if (lin < 2 && col < 16)
LCD_Cmd((col & 0x0F) | end_lin[lin]);
LCD_Print(str);
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 79


Analisando o código-fonte da biblioteca, notamos que as operações no display
LCD devem sempre ser habilitadas através de um pulso de 1us no pino E (Enable) do
HD44780, desta forma, criamos a função LCD_StartRW().

void LCD_StartRW() {
LCD_Port |= (1 << ENPIN);
_delay_us(1);
LCD_Port &= ~ (1 << ENPIN);
}

Em seguida, a função LCD_Cmd(unsigned char cmd) foi implementada de modo


a permitir a conexão do LCD no Arduino usando apenas 4 bits de dados. Então, o
comando (8 bits), que é passado como parâmetro da função, é dividido sendo
enviados 4 bits (1 nibble) e após 200us são enviados os 4 bits restantes. Observe que
devemos desligar o bit RS (Register Select) antes de realizar a operação.

void LCD_Cmd(unsigned char cmd) {


LCD_Port = (LCD_Port & 0x0F) | (cmd & 0xF0);
LCD_Port &= ~(1 << RSPIN);
LCD_StartRW();
_delay_us(200);
LCD_Port = (LCD_Port & 0x0F) | (cmd << 4);
LCD_StartRW();
_delay_ms(2);
}

A função LCD_Init(), mostrada a seguir, realiza a inicialização da maneira que é


indicada no manual do fabricante (Figura 32).

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 80


Figura 32: Fluxograma de Inicialização do HD44780
(Fonte: Página 46 do Manual do Fabricante)

Nesta função também podem ser definidos o modo de comunicação (4 ou 8


bits), a fonte usada, quantidade de linhas e colunas do display e modo de exibição do
cursor, sendo relevante destacar:

Código Instrução
0x03 Preparar para receber os comandos
0x02 Modo de 4 bits
0x0C Display ligado e cursor desligado
0x06 Mover o cursor à direita, desligar o deslocamento do display
0x01 Apagar o conteúdo do display

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 81


A função LCD_Clear() será usada para apagar o conteúdo que está sendo
exibido no display (0x01) e retornar o cursor para o início (0x80), ou seja, linha 0 e
coluna 0.

void LCD_Clear() {
LCD_Cmd (0x01);
_delay_ms(2);
LCD_Cmd (0x80);
}

A função LCD_Print(char *str) irá exibir na posição corrente do cursor no LCD a


cadeia de caracteres (string) passada como parâmetro da função. Neste caso, o pino RS
(Register Select) deverá estar com valor 1. Além disso, respeitando o modo de
comunicação de 4 bits, cada byte que compõe cada um dos caracteres da string é
divido e enviado em nibbles (4 bits) separados.

void LCD_Print (char *str) {


for(int i = 0; str[i] != 0; i++) {
LCD_Port = (LCD_Port & 0x0F) | (str[i] & 0xF0);
LCD_Port |= (1 << RSPIN);
LCD_StartRW();
_delay_us(200);
LCD_Port = (LCD_Port & 0x0F) | (str[i] << 4);
LCD_StartRW();
_delay_ms(2);
}
}

A última função definida na nossa biblioteca é a LCD_PrintXY que irá posicionar


o cursor na linha e coluna que foram passadas como parâmetro. Na sequência, a
função LCD_Print, definida anteriormente, é executada e vai realizar a exibição da
cadeia de caracteres passada como parâmetro.

void LCD_PrintXY (unsigned char col, unsigned char lin, char


*str) {
unsigned char end_lin[] = {0x80, 0xC0};
if (lin < 2 && col < 16)
LCD_Cmd((col & 0x0F) | end_lin[lin]);
LCD_Print(str);
}

Note que, no caso de um display de 16 colunas e duas linhas, a coluna é


selecionada realizando uma operação de E (AND) entre o número da coluna desejada e
o valor 0x0F. Depois a primeira linha é selecionada com o valor 0x80, enquanto a
segunda linha é acessada através do valor 0xC0.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 82


Considere os valores que colocamos no vetor end_lin[] e observe na Figura 33
que o endereço da DDRAM e ajustado colocando o valor 1 no bit DB7, sendo que os
bits DB6 até DB0 deverão conter o endereço desejado.

Figura 33: Conjunto de Instruções


(Fonte: Página 24 do Manual do Fabricante)

Desta maneira, o valor para acessar a linha 0 e coluna 0 é 0x80, pois:

DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0


1 0 0 0 0 0 0 0

Conforme pode ser observado na Figura 34, a segunda linha do display inicia no
endereço 0x40.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 83


Figura 34: Posição no LCD
(Fonte: Página 12 do Manual do Fabricante)

Resultando, desta maneira, no valor 0xC0:

DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0


1 1 0 0 0 0 0 0

Como exemplo, vamos colocar o cursor na linha 1 (segunda linha) e na coluna


5. Então, considerando a instrução LCD_Cmd((col & 0x0F) | end_lin[lin]), temos:

DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 Instrução


0 0 0 0 0 1 0 1 col = 5
0 0 0 0 0 1 0 1 col & 0x0F
1 1 0 0 0 1 0 1 (col & 0x0F) | end_lin[1]

Ou seja, a instrução a ser enviada para o HD44780 é:

LCD_Cmd(0xC5);

Após implementar a biblioteca, o uso do LCD nos programas acaba ficando


bastante simples, como podemos observar no código-fonte apresentado a seguir.

#define F_CPU 16000000UL


#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "../lib-lcd/lib-lcd.h"

int main() {
LCD_Init();
LCD_PrintXY(6, 0, "Ola!");

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 84


LCD_PrintXY(3, 1, "ATmega328P");
while(1);
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 85


Sensor de Temperatura e Umidade DHT11

Neste projeto vamos mostrar como realizar a comunicação entre o


ATmega328P e o sensor DHT11. A ideia central consiste, a partir das informações
contidas no manual do sensor, elaborar uma rotina para obter a temperatura e
umidade, que foram medidas pelo DHT11, e exibi-las em um display de LCD.

Material necessário:
• 1 Arduino.
• 1 Display LCD1602.
• 1 Sensor de Temperatura e Umidade dht11.
• 1 Potenciômetro de 10k Ohms.
• 1 Resistor de 220 Ohms (vermelho, vermelho, marrom) ou 330 Ohms (laranja,
laranja, marrom).
• 1 Protoboard.
• Cabos de ligação.

Realize a montagem do circuito conforme ilustrado pela Figura 35.

Figura 35: Conexões

Desta maneira, de acordo com a Figura 35, notamos que o pino de dados do
DHT11 deverá ser conectado ao pino PB0 (Pino 8 do Arduino), enquanto o display LCD
será ligado do seguinte modo:

• Pino 1 (VSS) do LCD ligado ao GND do Arduino.


• Pino 2 (VDD) do LCD ligado ao 5V do Arduino.
• Pino 3 (V0) do LCD ligado ao pino central do potenciômetro (controle de
contraste).
• Pino 4 (RS) do LCD ligado ao pino PD2 (Pino 2) do Arduino.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 86


• Pino 5 (RW) do LCD ligado ao GND do Arduino.
• Pino 6 (E) do LCD ligado ao pino PD3 (Pino 3) do Arduino.
• Pino 11 (D4) do LCD ligado ao PD3 (Pino 4) do Arduino.
• Pino 12 (D5) do LCD ligado ao PD3 (Pino 5) do Arduino.
• Pino 13 (D6) do LCD ligado ao PD3 (Pino 6) do Arduino.
• Pino 14 (D7) do LCD ligado ao PD3 (Pino 7) do Arduino.
• Pino 15 (A) do LCD ligado ao 5V do Arduino com um resistor de 220 ohms
(controle do brilho).
• Pino 16 (K) do LCD ligado ao GND do Arduino.

Antes de iniciarmos o programa, vamos analisar o manual do DHT11 para


entender o seu funcionamento. Basicamente, temos um processo de inicialização do
sensor e outro processo para leitura e verificação dos dados obtidos. Na Figura 36
observamos o processo de inicialização do sensor.

Figura 36: Inicialização do Sensor DHT11


(Fonte: Manual do Fabricante do Sensor DHT11)

Com base nas informações apresentadas podemos elaborar uma rotina para
realizar essa tarefa. Observe, nos comentários ao longo do trecho de programa
apresentado na sequência, a explicação de cada uma das etapas.

// Resetar a porta
DHT_DDR |= (1 << DHT_DATAPIN); // Definir como Saída
DHT_PORT |= (1 << DHT_DATAPIN); // Colocar Nível 1
_delay_ms(100);

// Enviar requisição para o DHT11


DHT_PORT &= ~(1 << DHT_DATAPIN); // Colocar Nível 0
_delay_ms(18); // Manter Nível 0 por 18ms
DHT_PORT |= (1 << DHT_DATAPIN); // Colocar Nível 1
DHT_DDR &= ~(1 << DHT_DATAPIN); // Definir como Entrada
_delay_us(40); // Aguardar a resposta do DHT por 40us

// Verificar a resposta do DHT (1)


if((DHT_PIN & (1 << DHT_DATAPIN))) { // Se conter Nível 1
return -1; // Retornar com erro

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 87


}
_delay_us(80);
// Verificar a resposta do DHT (2)
if(!(DHT_PIN & (1 << DHT_DATAPIN))) { // Se conter Nível 0
return -1; // Retornar com erro
}
_delay_us(80);

Após isso o DHT está pronto para realizar a transmissão de dados. Sendo que
devemos interpretar como um “bit 0” o pino de dados recebendo nível 1 por um
período entre 26us e 28us (Figura 37).

Figura 37: Transmissão de um “bit 0”


(Fonte: Manual do Fabricante do Sensor DHT11)

Enquanto devemos interpretar como um “bit 1” o pino de dados recebendo


nível 1 por 70us, conforme podemos observar na Figura 38.

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 88


Figura 38: Transmissão de um “bit 1”
(Fonte: Manual do Fabricante do Sensor DHT11)

Serão transmitidos 5 bytes de dados, sendo:

• Byte 0: Umidade (Parte Inteira).


• Byte 1: Umidade (Parte Decimal).
• Byte 2: Temperatura (Parte Inteira).
• Byte 3: Temperatura (Parte Decimal).
• Byte 4: Verificação (Checksum).

Devemos notar no manual do fabricante do sensor que sua resolução é de 1%


nas medidas de umidade e 1°C em relação a temperatura. Desta forma, apesar de
precisarmos realizar a leitura dos 5 bytes, podemos desprezar os Bytes 1 e 3 na
apresentação dos valores medidos, pois eles contêm a parte decimal da leitura. Esses
bytes são reservados para o sensor DHT22 que possui maior resolução, quando
comparado ao DHT11. No fragmento de programa mostrado a seguir temos a rotina
que irá obter receber os dados do sensor.

unsigned char dados[] = {0x00, 0x00, 0x00, 0x00, 0x00};


unsigned char i, j = 0;
int contador = 0;
for (j = 0; j < 5; j++) { // Ler 5 bytes
unsigned char result = 0;
for(i = 0; i < 8; i++) { // Ler cada bit
contador = 0;
while(!(DHT_PIN & (1 << DHT_DATAPIN))) { // Aguardar a
entrada ir para nível 1
contador++;
if(contador > DHT_TIMEOUT) {
return -1; // Retornar com Erro (Timeout)
}
}
_delay_us(30);

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 89


if(DHT_PIN & (1 << DHT_DATAPIN)) // Se a entrada continua em
nível 1 após 30us, agregar um bit com valor 1 ao resultado
result |= (1 << (7 - i));
contador = 0;
while(DHT_PIN & (1<<DHT_DATAPIN)) { // Aguardar a entrada ir
para o nível 0
contador++;
if(contador > DHT_TIMEOUT) {
return -1; // Retornar com Erro (Timeout)
}
}
}
dados[j] = result;
}

A última etapa consiste em realizar a verificação dos dados (checksum), para


isso devemos somar os valores dos 4 primeiros bytes e comparar com o valor
armazenado no quinto byte, ou seja:

if ((unsigned char)(dados[0] + dados[1] + dados[2] + dados[3])


== dados[4]) {
*t = dados[2];
*u = dados[0];
return 0;
}

Após estes conceitos, podemos apresentar a rotina completa, que poderá ser
colocada no arquivo lib-dht11.h, permitindo sua reutilização.

#define F_CPU 16000000UL


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

#define DHT_DDR DDRB


#define DHT_PORT PORTB
#define DHT_PIN PINB
#define DHT_DATAPIN PB0
#define DHT_TIMEOUT 200

int8_t DHT_Ler(unsigned char *t, unsigned char *u) {


// Resetar a porta
DHT_DDR |= (1 << DHT_DATAPIN);
DHT_PORT |= (1 << DHT_DATAPIN);
_delay_ms(100);

// Enviar requisição para o DHT11


DHT_PORT &= ~(1 << DHT_DATAPIN);
_delay_ms(18);
DHT_PORT |= (1 << DHT_DATAPIN);
Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 90
DHT_DDR &= ~(1 << DHT_DATAPIN);
_delay_us(40);

// Verificar a resposta do DHT (1)


if((DHT_PIN & (1 << DHT_DATAPIN))) {
return -1;
}
_delay_us(80);
// Verificar a resposta do DHT (2)
if(!(DHT_PIN & (1 << DHT_DATAPIN))) {
return -1;
}
_delay_us(80);

// Ler os dados
unsigned char dados[] = {0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char i, j = 0;
int contador = 0;
for (j = 0; j < 5; j++) {
unsigned char result = 0;
for(i = 0; i < 8; i++) {
contador = 0;
while(!(DHT_PIN & (1 << DHT_DATAPIN))) {
contador++;
if(contador > DHT_TIMEOUT) {
return -1;
}
}
_delay_us(30);
if(DHT_PIN & (1 << DHT_DATAPIN))
result |= (1 << (7 - i));
contador = 0;
while(DHT_PIN & (1<<DHT_DATAPIN)) {
contador++;
if(contador > DHT_TIMEOUT) {
return -1;
}
}
}
dados[j] = result;
}

// Realizar o checksum
if ((unsigned char)(dados[0] + dados[1] + dados[2] + dados[3])
== dados[4]) {
*t = dados[2];
*u = dados[0];
return 0;
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 91


return -1;
}

No programa a seguir usamos as rotinas desenvolvidas para o DHT e para o


LCD.

#define F_CPU 16000000UL


#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "../lib-lcd/lib-lcd.h"
#include "../lib-dht11/lib-dht11.h"

int main() {
LCD_Init();
LCD_PrintXY(0, 0, "DHT11");
_delay_ms(500);
unsigned char temp = 0;
unsigned char umid = 0;

while(1) {
if(DHT_Ler(&temp, &umid) != -1) {
LCD_Clear();
LCD_PrintXY(0, 0, "Temp: ");
char str_valor[10];
sprintf(str_valor, "%d", temp);
LCD_Print(str_valor);
sprintf(str_valor, "%c", 0b11011111);
LCD_Print(str_valor);
LCD_Print("C");
LCD_PrintXY(0, 1, "Umid: ");
sprintf(str_valor, "%d", umid);
LCD_Print(str_valor);
LCD_Print("%");
}
else {
LCD_Clear();
LCD_PrintXY(0, 0, "DHT11: Erro!");
}
_delay_ms(5000);
}
}

Linguagem C para Arduino e ATmega328P / Cláudio Luís V. Oliveira 92

Você também pode gostar