Buscar

80923070-AVR-C

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 83 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 83 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 83 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Programação em C no AVR
Última revisão: 21/12/2010 1
Rodrigo Toste Gomes a.k.a Cynary
Nuno João a.k.a Njay
Senso
Neste documento tentamos explicar ao leitor as várias funções que um micro-controlador AVR 
disponibiliza, e como pode controlar as mesmas, sem recorrer a bibliotecas de alto nível que muitas 
vezes roubam performance e acrescentam tamanho aos programas, como por exemplo as que o 
arduino disponibiliza.
Assumimos que o leitor tem alguma experiência com programação em C, e que o contacto que 
tem com AVR é com o arduino (logo, todos os exemplos que temos aqui irão funcionar no mesmo. 
No entanto, podem funcionar noutros micro-controladores AVR com poucas ou nenhumas 
alterações).
Todos os programas aqui podem ser compilados através dos seguintes comandos:
avr-gcc -Wall prog.c -Os -mmcu=atmega168 -o prog.out -DF_CPU=16000000 
avr-objcopy -O ihex -R .eeprom prog.out prog.hex
(substituir prog.c pelo nome do ficheiro com o código)
E podem ser transferidos para o arduino com o seguinte comando:
avrdude -p m328p -c avrisp -P /dev/ttyUSB0 -b 57600 -F -U flash:w:prog.hex
É muito fácil alterar estes comandos para funcionarem com outros micro-controladores e 
programadores, estando essa informação provavelmente na datasheet.
Os programas necessários para os comandos acima funcionarem vêm instalados com o IDE do 
arduino, e podem ser utilizados para programar outros micro-controladores que não o usado pelo 
arduino.
Para os utilizadores de windows, a utilização das ferramentas AVRStudio e WinAVR é 
recomendada, mas visto que está além do alcance deste documento, o leitor é incentivado a 
pesquisar, mas garantimos que os comandos acima funcionam.
Qualquer software presente neste documento é oferecido com objectivos didácticos, e não é 
acompanhado de qualquer garantia de performance ou funcionalidade. 
Gostaria de agradecer a todos os membros da lusorobótica que comentaram no tópico respectivo 
a estes tutoriais, e em especial ao membro Njay, que me autorizou a usar o seu Micro-tutorial neste 
documento.
Última revisão: 21/12/2010 2
Índice
Programação em C no AVR..................................................................................................................1
Introdução.............................................................................................................................................5
Programação em C em micro-controladores........................................................................................6
Controlo da funcionalidade do micro-controlador – os registers....................................................6
Pseudo-código/código esqueleto......................................................................................................7
MACROS.........................................................................................................................................8
Variáveis volatile..............................................................................................................................8
Operações bit-wise em C.................................................................................................................8
GPIO – General Purpose Input/Output...............................................................................................14
Entrada Digital Normal..................................................................................................................14
Entrada com “pull-up” (“puxa para cima”)...................................................................................15
Entrada controlada por um periférico............................................................................................16
Saída Digital Normal.....................................................................................................................16
Saída em Colector Aberto (open colector).....................................................................................17
Saída controlada por um periférico................................................................................................17
GPIOs na Arquitectura AVR..........................................................................................................18
Configuração dos Portos em Linguagem C...................................................................................20
Interrupções........................................................................................................................................21
O que é uma interrupção?..............................................................................................................21
Como funciona uma interrupção no AVR?....................................................................................21
Como lidar com uma interrupção no AVR?...................................................................................22
Exemplo de interrupção através do pino digital 2 (INT0).............................................................23
Cuidados a ter na utilização de interrupções.................................................................................26
Timers.................................................................................................................................................28
O que são e como funcionam timers?............................................................................................28
Timers no AVR...............................................................................................................................28
Modos Normal e CTC....................................................................................................................29
Como usar um timer no AVR.........................................................................................................29
Eventos relacionados com timers..................................................................................................32
Interrupções e timers......................................................................................................................36
Timers – Parte 2, Pulse Width Modulation.........................................................................................39
O que é PWM?...............................................................................................................................39
Vários Modos de PWM.................................................................................................................39
Fast PWM......................................................................................................................................41
Phase and Frequency Correct PWM..............................................................................................46
Analog-to-Digital Converter..............................................................................................................52
Formato Analógico e Digital..........................................................................................................52
O que é o ADC?.............................................................................................................................52
Como funciona o ADC no AVR?...................................................................................................52
Como ligar o input ao AVR?..........................................................................................................56
Utilizar o ADC – construir um sensor de distância.......................................................................57
ADC8 – medir a temperatura interna.............................................................................................61
Comunicação Serial no AVR..............................................................................................................62
Como funciona acomunicação Serial?..........................................................................................62
O que é a USART?.........................................................................................................................63
Inicializando a USART do AVR....................................................................................................64
Enviando e Recebendo Dados através da USART........................................................................65
Exemplo de utilização do USART.................................................................................................68
Comunicação por I²C..........................................................................................................................72
O Protocolo I²C..............................................................................................................................72
Última revisão: 21/12/2010 3
I²C no AVR.....................................................................................................................................74
Bibliografia.........................................................................................................................................83
Última revisão: 21/12/2010 4
"Excerto do "Micro Tutorial AVR" de Njay (http://embeddeddreams.com/users/njay/Micro Tutorial 
AVR - Njay.pdf) com alterações/adaptações de Cynary (formatação e conteúdo)"
Introdução
"AVR" é o nome de uma família de micro-controladores de 8 bits comercializada pela ATMEL. 
A arquitectura do AVR foi desenvolvida por 2 estudantes de doutoramento noruegueses em 1992 e 
depois proposta à ATMEL para comercialização. Para quem souber inglês, podem ver uma pequeno 
vídeo sobre os AVR aqui: 
http://www.avrtv.com/2007/09/09/avrtv-special-005/ . 
O AVR consiste, tal como um PIC e outros micro-controladores, num processador (o "core"), 
memórias voláteis e não- voláteis e periféricos. Ao contrário do PIC, o core do AVR foi muito bem 
pensado e implementado desde o inicio, e o core que é usado nos chips desenhados hoje é o mesmo 
que saiu no 1o AVR há mais de 10 anos (o PIC teve "dores de crescimento" e o tamanho das 
instruções aumentou algumas vezes ao longo do tempo de forma a suportar mais funcionalidade). 
Assim de uma forma rápida podemos resumir a arquitectura do AVR nos seguintes pontos: 
– Consiste num core de processamento, memória de programa (não volátil, FLASH), memória 
volátil (RAM estática, SRAM), memória de dados persistentes (não volátil, EEPROM) e bits 
fuse/lock (permitem configurar alguns parâmetros especiais do AVR). 
– Arquitectura de memória Harvard (memória de programa e memória de dados separadas) 
– A memória volátil (SRAM) é contínua 
– A maior parte das instruções têm 16 bits de tamanho, e é este o tamanho de cada palavra na 
memória de programa (FLASH). 
– Execução de 1 instrução por ciclo de relógio para a maior parte das instruções. 
– Existem 32 registos de 8 bits disponíveis e há poucas limitações ao que se pode fazer com 
cada um. 
– Os registos do processador e os de configuração dos periféricos estão mapeados (são 
acessíveis) na SRAM. 
– Existe um vector de interrupção diferente por cada fonte de interrupção. 
– Existem instruções com modos de endereçamento complexo, como base + deslocamento 
seguido de auto- incremento/decremento do endereço. 
– O conjunto de instruções foi pensado para melhorar a conversão de código C em assembly. 
(A introdução do Micro tutorial do Njay mencionava mais alguns tópicos, que considerei como 
irrelevantes para este tutorial, logo cortei-os).
Última revisão: 21/12/2010 5
Programação em C em micro-controladores.
Neste conjunto de tutoriais, tentamos ensinar ao leitor como programar um micro-controlador 
AVR em C “low-level”. Para fazer isso, é especialmente necessário compreender como controlar as 
várias funções do micro-controlador. Os restantes tutoriais concentram-se nisso.
No entanto, para compreender os exemplos dados, e poder aplicar o que é ensinado, o leitor 
necessita de compreender algumas coisas básicas primeiro, respectivamente:
Controlo da funcionalidade do micro-controlador – os registers.
Pseudo-código/código esqueleto
MACROS
Variáveis volatile
Operações bit-wise em C.
É assumido que o leitor sabe programar em C e que domina os seguintes conceitos: comentários, 
bibliotecas, variáveis, funções, ponteiros, ciclos, condições, lógica e bases numéricas.
Controlo da funcionalidade do micro-controlador – os registers
Os AVR têm várias funções: podem ser usados para comparar e ler diferenças de potencial, 
comunicar por serial, …
Todas estas funções são controladas por registers … mas o que são registers?
Todos os CPUs têm uma certa memória interna. Esta funciona quase como a memória ram, 
excepto no uso de ponteiros.
O CPU tem acesso directo a esta memória, o que significa que em termos de performance é 
muito mais eficiente usar registers para armazenamento do que memória ram (o compilador em C 
optimiza automaticamente os programas, dando uso deste “boost” na performance sempre que 
possível – daí a importância de usar variáveis volatile quando se usam interrupções, estudadas mais 
à frente). No entanto, estes não são só usados para armazenamento, mas também para controlar 
várias funções dos micro-controladores. Certos bits em certos registers podem controlar o estado de 
um pino, ligar e desligar o ADC, … Nos AVR todos os registers têm o tamanho de 8 bits. Logo, 
quando é necessário armazenar valores maiores que 255, usam-se mais do que um register. No 
entanto, este pormenor é abstraído pelo compilador, visto que podemos muitas vezes aceder a um 
conjunto de registers como se fosse um só (como por exemplo, o register TCNT1 do timer1 que 
corresponde a dois registers, visto que pode conter um valor de 16 bits).
Agora que sabemos o que é um register, vamos aprender como usá-los.
As bibliotecas do avr dão-nos um header muito útil que nos permite aceder directamente aos 
Última revisão: 21/12/2010 6
registers e bits dos mesmos através dos seus nomes: avr/io.h
Um exemplo:
Para alterar o estado de um pino, alteramos o bit correspondente no register DDRx (em que x 
corresponde à porta. Por exemplo, o pino PB1 está na porta B, logo para alterar o seu estado, 
alteramos o bit PB1 no register DDRB). Logo, utilizamos o código seguinte:
#include <avr/io.h>
int main(void) {
 DDRB |= (1<<PB1); }
(quando alteramos o bit para 1, estamos a colocar o pino em output)
Se não compreende exactamente como alterámos um bit no register, não se preocupe, pois as 
operações bit-wise serão explicadas de seguida.
Pseudo-código/código esqueleto
O pseudo-código é basicamente uma representação abstracta do código, em linguagem natural. 
Muitas vezes começa-se por escrever o pseudo-código, e depois vai-se substituindo por linhas de 
código (muitas vezes o pseudo-código transforma-se nos comentários). Irei usar isto nos meus 
tutoriais para ir construindo os programas passo-a-passo.
Por exemplo, o famoso programa “Hello World”, feito passo-a-passo:
// Iniciar o programa
 // Escrever “Hello World no Ecrã”
// Terminar o programa
Primeiro, fazemos o mais simples: iniciar e terminar o programa. Como vamos precisar de 
funções Input/Output, parte da inicialização é incluir o header stdio.h, o resto é começar a função 
main(), e terminamos com return 0 (sair do programa com sucesso – visto que nos AVRs não existe 
sistema operativo, a função main nunca fará um return, apenas acabará num loop infinito):
// Iniciar o programa:
#include <stdio.h>
int main(void) {
 // Escrever “Hello World” no Ecrã
 return 0; } // Terminar o programa
Agora falta a parte funcionaldo programa: escrever o Hello World no ecrã:
Última revisão: 21/12/2010 7
// Iniciar o programa:
#include <stdio.h>
int main(void) {
 printf(“Hello World”); // Escrever “Hello World” no Ecrã
 return 0; } // Terminar o programa
MACROS
Em quase todos os programas de C, temos instruções começadas por '#'. Estas não são instruções 
em C, mas sim instruções interpretadas apenas pelo pré-processador, antes da compilação. Por 
exemplo, quando fazemos “#include <qualquercoisa.h>”, estamos a indicar ao pré-processador para 
incluir o conteúdo do ficheiro qualquercoisa.h no nosso programa.
Uma MACRO é uma instrução deste tipo, que se comporta como uma função. São úteis quando 
queremos realizar certas tarefas repetidamente, mas não se justifica o custo em performance de 
chamar uma função (para quem programa em C++, isto é equivalente ao inline).
Por exemplo, duas macros que costumo usar são as seguintes:
#define max(I,J) ((I)>(J)?(I):(J))
#define min(I,J) ((I)<(J)?(I):(J))
Antes da compilação, o pré-processador substitui todas as declarações de max(x,y) e min(x,y) 
pelo código correspondente, sem ser assim necessário chamar uma função (as macros são úteis para 
substituir principalmente funções com só uma linha de código). Há vários pormenores envolvidos 
na criação de macro (como por exemplo, abusar das parêntesis para proteger o código), mas não 
interessam para este tutorial. No entanto, visto que são muito úteis, aconselho os interessados a 
pesquisar sobre elas.
Variáveis volatile
Quando declaramos variáveis, podemos controlar certos aspectos de como o código deve acedê-
las. Uma declaração importante quando se programa AVRs, devido à existência de interrupções, é a 
volatile. Mais à frente explicarei a importância disto, por agora é apenas importante reter que 
quando se declara uma variável como volatile, estamos a informar que o seu valor pode ser alterado 
de formas inesperadas, logo deve sempre ir buscar o seu valor actualizado.
Operações bit-wise em C
Muita da programação em micro-controladores consiste principalmente em manipular bits de 
certos registers. Para fazer isso, usamos as operações bit-wise que manipulam valores ao nível dos 
bits.
Última revisão: 21/12/2010 8
Para quem não compreende bases numéricas, e não sabe o que significa manipular bits, 
aconselho a lerem algum livro/tutorial que trate deste assunto. No entanto, explicado de uma forma 
breve, é o seguinte:
Normalmente usamos a base decimal. Isto significa que usamos 10 dígitos diferentes (do 0 ao 9). 
Com combinações deles, fazemos diferentes números. Quando queremos um valor acima do dígito 
maior, transportamos mais um para a posição seguinte (se contarmos desde a direita). Assim 
podemos dar valores a cada posição no número.
Por exemplo, o número 29 tem um 9 na posição 0 e um 2 na posição 1. A posição 0 corresponde 
ao valor 10 (1), e a 1 ao valor 10¹. Assim, podemos chegar ao número através da conta: 2*10¹ +⁰ 
9*10 .⁰
Números de base binária funcionam da mesma forma que os de base decimal, com a 
particularidade de apenas utilizarmos dois algarismos: o 0 e o 1. Assim, cada posição tem um valor 
diferente. Por convenção, chamam-se às posições de um número em base binária de bit. Assim, 
quando falamos em manipular bits, estamos a falar em manipular o valor (0 ou 1) de certas 
posições. Por exemplo, o número 1001 (para facilitar a leitura, costumam-se ler os dígitos 
separados. Assim, em vez de se ler “mil e um”, lê-se “um zero zero um”) corresponde ao número 
em decimal 9. Isto porque o bit 0 tem o valor de 1 (2 ) e o bit 3 tem o valor de 8 (2³). Logo, como⁰ 
esses são os únicos bits com dígitos lá, chegamos ao 9 através da conta: 1*2 +1*2³.⁰
Agora que já conhecemos a base binária, e o que significa manipular bits, vamos ver como 
podemos manipulá-los.
Isto é feito através de operações bit-wise.
Em C, existem cinco operações bit-wise:
| – or
& – and
~ – not
^ – xor
<< – shift left
>> – shift right
As duas primeiras operações funcionam como as operações lógicas ||, &&. No entanto, em vez 
de testarem a variável como um todo lógico, testam bit a bit, e o resultado corresponde a essa 
comparação bit a bit. Por isso, enquanto temos resultados bem definidos com as operações | e &, as 
Última revisão: 21/12/2010 9
operações || e && podem dar um valor aleatório para verdadeiro. Assim, quando se necessitam de 
valores lógicos, devem-se usar as operações || e &&, e para manipulação bit a bit, devem-se usar as 
operações | e & (nota: as operações & e && podem ter resultados diferentes).
Vamos então começar por estudar essas duas operações:
O or retorna 0 quando ambos os bits são 0, e 1 quando pelo menos um dos bits é 1. Olhemos para 
um exemplo:
111000 | 001110 = 111110
Vamos analisar isto bit a bit. Em ambos os números, o bit 0 tem o valor 0. 0 ou 0 = 0. Logo, o bit 
0 do resultado será um 0. No bit 1, o primeiro número tem um 0, mas o segundo tem um 1. 0 ou 1 = 
1. Logo, o resultado terá um 1 no bit 1. A mesma coisa ocorre com o bit 2. No bit 3, ambos os 
números têm um 1. 1 ou 1 = 1. Logo, o resultado terá um 1 no bit 3. Nos restantes bits, o primeiro 
número tem um 1, e o segundo tem um 0. 1 ou 0 = 1. Logo os restantes bits (bits 4 e 5) terão um 1 
no resultado. Assim, chegamos ao número 111110.
Podemos usar isto para colocar o valor 1 numa certa posição num número.
Por exemplo, temos o número 1101 (em decimal é o número 13), e queremos preencher aquele 0 
com um 1. Se fizermos um ou com o número 0010 (em decimal é o número 2), preenchemo-lo. 
Vejamos um exemplo:
#include <stdio.h>
int main(void) {
 int i = 13;
 i = i|2; // Equivalente a fazer i |= 2
 printf(“%d\n”, i); // Imprime o número 15 – em binário 
1111.
 return 0; }
Vamos agora observar a operação &.
O and retorna 0 quando pelo menos um dos bits é 0, e 1 quando os dois bits são 1.
Por exemplo:
1101 & 0111 = 0101
A análise deste exemplo será deixada como um desafio ao leitor.
Última revisão: 21/12/2010 10
O & é muitas vezes usado para colocar a 0 um certo bit.
Por exemplo: se tivermos o número 10111 (em decimal 23), e quisermos a partir dele obter o 
número 10101 (em decimal 21), podemos fazer a seguinte operação: 10111 & 1101 = 10101:
#include <stdio.h>
int main(void) {
 int i = 23;
 i = i&13; // 13 – em binário 1101; equivalente a i &= 13;
 printf(“%d\n”, i); // Imprime 21
 return 0; }
A terceira operação, ~ (not), também tem um comportamento semelhante ao seu equivalente 
lógico, o !. No entanto, foi separado das outras duas operações, pois esta não pode ser usada como 
uma operação lógica, visto que ~(true) pode dar um valor verdadeiro à mesma (interessantemente, 
devido à forma como a aritmética dos CPUs funcionam, fazer o ~ de qualquer número positivo dá 
um número negativo e vice-versa, sendo a única excepção o -1, já que ~(-1) = 0. Não 
aprofundaremos mais isto, visto que não interessa muito para programar micro-controladores).
Vejamos como funciona:
~1101 = 0010
Cada bit do número original é invertido, logo a partir de um número positivo (true), podemos 
não obter 0 (false), que é exactamente o que o ! lógico faz.
O ~ é muitas vezes utilizado em conjunção com o & para pôr um valor 0 num bit. Vejamos 
porquê:
11101 & 10111 = 10101
Sabendo a posição do bit que queremos pôr a 0 (neste caso a posição 4), como chegamos ao seu 
inverso, de forma a manter o resto do número intacto.
Usando o ~, claro!
Neste caso, fazer:
11101 & 10111 = 10101
é igual a fazer:
Última revisão: 21/12/2010 11
11101 & (~01000) = 11101 & 10111 = 10101
(mais à frente iremos estudar como criar um número com apenasum 1 na posição pretendida, 
sabendo apenas essa posição).
Por exemplo, com código agora (reformulação do exemplo do &):
#include <stdio.h>
int main(void) {
 int i = 23;
 i &= ~(8); // 8 – 01000
 printf(“%d\n”, i); // Imprime 21.
 return 0; }
O “exclusive or”, ou como é melhor conhecido, o xor, não tem um equivalente lógico óbvio. É o 
mesmo que !=. O seu comportamento é o seguinte: retorna 0 quando ambos os números são iguais, 
e 1 quando são diferentes. Como o |, quando se procura um resultado lógico, é equivalente usar o ^ 
e o !=.
Vejamos então um exemplo
1101 ^ 0101 = 1001
O xor é muitas vezes usado para fazer “toggle” (alterar o valor de 0 para 1 e vice-versa) de um 
certo bit. Por exemplo, se tivermos um número 11x1, e quisermos alterar o estado do bit 1, sem 
conhecermos o seu valor, basta fazer a seguinte operação:
11x1 ^ 0010
Isto porque quando fazermos um xor com 0, o resultado é sempre igual ao do outro número (1^0 
= 1; 0^0 = 0), e quando fazemos um xor com 1, altera sempre (1^1 = 0; 1^0 = 1).
(visto que o código de exemplo seria semelhante aos anteriores, iremos passar à frente desse 
passo).
Agora só nos falta estudar os operadores de shift.
Estes são muito úteis porque nos permitem pôr um valor em qualquer posição do número, ou 
seja, fazer shift para cima ou para baixo desse mesmo valor.
Vamos utilizar o exemplo do ~ e do &. Sabendo apenas a posição em que queremos pôr o 0, e o 
Última revisão: 21/12/2010 12
número que tem essa posição a 1, e as restantes a 0, já sabemos que operação utilizar.
Mas ainda nos falta uma coisa: como chegamos ao número que tem a posição desejada a 1?
Para isso usam-se os operadores de shift.
Por exemplo, se quisermos colocar o 1 na posição 3, fazemos o seguinte:
1<<3 = 1000
#include <stdio.h>
int main(void) {
 int i = 23;
 i &= ~(1<<3); // 1<<3 = 8 – 01000
 printf(“%d\n”, i); // Imprime 21.
 return 0; }
Esta técnica também é utilizada para chegar aos valores utilizador com o or e o xor, sabendo 
apenas os bits que queremos, respectivamente, colocar a 1, ou alterar o valor.
Também existe o operador de shift >>, que faz o contrário do <<. Por exemplo:
111>>2 = 1
Mas é menos usado quando se programa micro-controladores.
É de notar que qualquer overflow é completamente esquecido.
Por exemplo, se considerarmos um limite de 5 bits:
10111<<3 = 11000
10111>>3 = 00010
Uma pequena curiosidade: dadas as características das bases numéricas, fazer <<x, é equivalente 
a multiplicar por 2^x, e fazer >>x é equivalente a dividir por 2^x.
E assim terminamos o nosso tutorial acerca das bases de programação necessárias para 
programar micro-controladores. Esperamos que o leitor esteja agora preparado para se aventurar no 
mundo da programação “low-level” dos mesmos!
Última revisão: 21/12/2010 13
"Excerto do "Micro Tutorial AVR" de Njay (http://embeddeddreams.com/users/njay/Micro Tutorial 
AVR - Njay.pdf) com alterações/adaptações de Cynary (formatação e conteúdo)"
GPIO – General Purpose Input/Output
O conceito de GPIO surge como uma forma de se tornar um chip mais flexível, e deve ter 
surgido com os chips programáveis. Este conceito consiste em podermos configurar um pino de um 
chip para poder ter uma de entre várias funções, como por exemplo uma entrada ou uma saída. Isto 
tem vantagens óbvias na flexibilidade de um chip, pois o fabricante dá-vos um chip com N pinos 
em que vocês escolhem a função de cada um conforme as necessidades da vossa aplicação. Se a 
função de cada pino fosse sempre fixa, os chips seriam muito menos úteis, e provavelmente 
teríamos chips maiores, com muito mais pinos, numa tentativa de colmatar essa limitação, e não 
poderíamos alterar a função do pino durante o funcionamento do chip. 
A função de base que podemos escolher para um GPIO é se o respectivo o pino é uma entrada ou 
saída, mas não é a única. Existem outras funções que podem ser escolhidas, embora nem todos os 
chips suportem todas. As funções possíveis mais comuns são: 
Normalmente dizemos apenas "GPIO" quando nos estamos a referir a um "pino GPIO" e eu 
assim farei daqui para a frente. Vamos ver com mais detalhe cada função, a que às vezes também 
chamamos "tipo de pino". 
Entrada Digital Normal
Esta é talvez a configuração mais simples que podemos ter. O pino funciona como uma entrada 
digital, ou seja, só podemos ler (em software) um de 2 valores: 0 ou 1. Na prática os valores 0 e 1 
representam uma certa tensão que é aplicada ao pino, resultando numa leitura de 0 ou 1 por parte do 
software. As tensões mais comuns são 0V para representar um 0 e 5V para representar um 1, mas 
podem ser outras como por exemplo 3.3V ou 1.8V para representar um 1, dependendo da tensão de 
alimentação do chip (refiro apenas "chip" porque não são apenas os micro-controladores que têm 
GPIOs; por exemplo as FPGA, outro tipo de chip programável, também têm). 
Última revisão: 21/12/2010 14
Portanto, num sistema que funcione com uma tensão de alimentação de 5V, se aplicarmos 5V a 
um pino configurado como "entrada digital normal", o software irá ler um valor 1 desse pino. Se 
aplicarmos 0V, o software irá ler um 0. A leitura do "estado do pino" é habitualmente efectuada 
lendo-se um registo do chip. Falaremos mais sobre isto no final. 
Configurar um GPIO como entrada digital normal também serve como forma de desligar o pino 
do circuito. Neste caso não estamos interessados em ler valores. Ao configurá-lo como entrada, ele 
não afecta electricamente (de um ponto de vista digital) o circuito exterior ao chip e portanto é 
como se tivéssemos cortado o pino do chip. Diz-se que o pino está em "alta impedancia" ("high-Z" 
em inglês, pois o "Z" é muito usado para designar "impedancia"), "no ar", ou simplesmente 
"desligado do circuito". 
Normalmente dizemos apenas que um pino está "configurado como entrada" ou como input.
Entrada com “pull-up” (“puxa para cima”)
Então se tivermos um pino configurado como input mas não lhe aplicarmos nenhuma tensão, que 
valor lemos no software?... A resposta é: não podemos prever. Tomem bem atenção a isto, vou 
repetir: não podemos prever. Quando temos uma entrada que está no ar, não podemos prever que 
valor vamos ler; o valor pode estar estável em 0 ou 1 ou pode estar sempre a variar, ou mudar de 
vez em quando consoante é dia ou noite ou Marte está alinhado com Júpiter ou o vizinho deitar-se 2 
minutos mais cedo ou mais tarde. Ele pode até mudar só de lhe tocarem com o dedo. 
Em algumas situações queremos ter sempre um valor estável na entrada. Um caso tipico é um 
interruptor (que pode ser um botão). Conectamos o interruptor entre a massa (o negativo da tensão 
de alimentação, "0V") e o pino. Aí, quando ligamos o interruptor (posição "ON"), o pino fica ligado 
aos 0V e portanto o chip lê um 0. Mas, e quando o interruptor está desligado? Aí o pino está no ar 
pois o interruptor desligado é um circuito aberto, e já sabemos que ler um input que está no ar dá-
nos um valor aleatório e portanto nunca vamos saber se o interruptor está mesmo ON ou OFF. É 
aqui que o pull-up entra; ao configurarmos o pino com pull-up, o chip liga internamente uma 
resistência entre o pino e a tensão de alimentação, e portanto, quando não há nada electricamente 
ligado ao pino, o pino "vê" um 1. No caso do interruptor, quando este está OFF, o pull-up coloca um 
1 estável à entrada do pino e fica resolvido o problema. Quando o interruptor está ON, o próprio 
interruptor força um 0 no pino, ligando-o à massa. 
Então mas... se o pull-up puxa o pino "para cima" e o interruptor (quando está ON) puxa para 
baixo, não há aqui uma espécie de conflito? Não há, por uma simples razão: o valor da resistência 
de pull-up é alto(tipicamente mais de 100 KOhms) e portanto tem "pouca força". Como o 
interruptor liga o pino directamente à massa, é esta que "ganha". Diz-se até que o pull-up é um 
Última revisão: 21/12/2010 15
"pull-up fraco", ou "weak pull-up" em inglês. 
Esta expressão pull-up ("puxa para cima") vem de estarmos a ligar à tensão de alimentação 
positiva, que é mais "alta" do que a massa, os 0V. Para este termo contribui ainda o facto de 
geralmente se desenhar a linha de alimentação positiva no topo dos esquemas, e a massa em baixo. 
Também podemos falar em pull-down ("puxa para baixo") quando nos referimos a ligar à massa. 
Podemos criar pull-downs ligando resistências à massa, mas tipicamente os chips não suportam este 
tipo de pull, por razões que fogem ao âmbito deste artigo que se quer simples. 
Entrada controlada por um periférico
Neste caso deixamos de ter controlo sobre o GPIO, passando esse controle para um periférico 
interno do chip. Por exemplo a linha Rx (recepção) de uma porta série (UART). Aqui o pino 
funciona como uma entrada mas quem a controla é o periférico. 
Saída Digital Normal
Na lógica CMOS, com a qual trabalhamos mais hoje em dia, as saídas digitais são baseadas 
numa topologia designada "totem-pole". Este tipo de saída é constituído por 2 interruptores 
electrónicos (transístores) um em cima do outro, e controlados de forma a que: 
1. quando queremos ter um "zero" à saída, liga-se o transístor de baixo, que liga o pino à massa 
(0V); o transístor de cima mantém-se desligado 
2. quando queremos ter um "um" à saída, liga-se o transístor de cima, que liga o pino à tensão 
se alimentação (+V); o transístor de baixo mantém-se desligado 
Na imagem acima podemos ver uma saída totem-pole num dos seus 2 estados mais habituais: 
quando tem um 0 e quando tem um 1. 
Por aqui podemos ver por exemplo porque é que não se devem ligar 2 (ou mais) saídas umas às 
outras. Se uma delas estiver com um "1" e a outra com um "0", estamos a criar um curto-circuito na 
alimentação, ligando +V à massa. Numa das saídas está ligado o interruptor de cima e na outra está 
Última revisão: 21/12/2010 16
ligado o de baixo. Mesmo que isto só aconteça durante um período de tempo muito pequeno 
(milisegundos, microsegundos ou menos), vão passar correntes elevadas, fora das especificações 
dos chips, e se acontecer regularmente, começa um processo de degradação que leva à falha do chip 
em segundos, horas, semanas, meses ou anos. 
Uma saída totem-pole tem ainda um 3o estado: "no ar". É outra forma de desligar um pino, mas 
que é usada quando o pino é sempre uma saída (não configurável). No caso de um GPIO, este pode 
ser configurado como entrada ficando assim desligado do circuito exterior, como vimos atrás. 
Saída em Colector Aberto (open colector)
Este tipo de saída surgiu para resolver o problema de não se poder ter 2 ou mais saídas ligadas. 
Por esta altura vocês podem estar a pensar "mas porque raio é que haveríamos de querer 2 saídas 
ligadas uma à outra?!", e a resposta é simples: pensem por exemplo no I²C, em que temos linhas de 
dados bidireccionais. No I²C há vários chips ligados a um mesmo par de linhas e todos eles têm a 
possibilidade de transmitir dados nessas linhas. Daí que, de alguma forma, há várias saídas ligadas 
entre si. 
A saída em colector aberto consiste num simples interruptor electrónico (transístor) capaz de 
ligar o pino à massa. Quando o interruptor está ligado a saída é 0, e quando está desligado a saída 
é... não sabemos. O pino fica "no ar" e portanto qualquer outro dispositivo exterior ao chip que 
esteja ligado ao pino pode lá colocar a tensão que entender. Num bus I²C o que se passa é que existe 
uma resistência externa que mantém as linhas com tensão positiva quando nenhum dispositivo está 
a transmitir; ou seja, temos um "pull-up fraco". A partir daí, qualquer um dos dispositivos pode 
forçar uma das linhas I²C a ter 0V, se activar o interruptor electrónico na sua saída em colector 
aberto. 
Saída controlada por um periférico
À semelhança do que se passa no caso da entrada controlado por um periférico, neste caso 
deixamos de ter controlo sobre o GPIO, passando esse controle para um periférico interno do chip. 
Por exemplo a linha Tx (transmissão) de uma porta série (UART). Aqui o pino funciona como uma 
saída mas quem a controla é o periférico (no caso da UART, uma saída normal). 
Última revisão: 21/12/2010 17
GPIOs na Arquitectura AVR
Nos AVR os pinos estão agrupados em portos com no máximo 8 pinos cada. Os portos têm a 
designação de letras, A, B, C, etc, e cada AVR tem um conjunto de portos. Cada pino de um porto 
pode ser configurado num de 3 modos: 
1. Entrada normal
2. Entrada com pull-up
3. Saída normal
4. Entrada ou saída controlada por um periférico
Um pino entra no 4º modo quando o respectivo periférico é activado, pelo que não vamos 
debruçar-nos aqui sobre isso. 
A cada porto está associado um conjunto de 3 registos que são usados para configurar, ler e 
definir o estado de cada pino do porto individualmente. Cada bit de cada registo está associado ao 
respectivo pino do chip. 
1. PINx - Lê o estado actual do pino
2. DDRx - Data Direction Register (registo de direcção dos dados)
3. PORTx - Define o estado da saída do porto
O "x" depende da letra do porto, de modo a que temos por exemplo o registo DDRA para o porto 
A. Segue-se um diagrama simplificado da lógica associada a cada pino de um porto do AVR, neste 
caso exemplificado para o pino 3 do porto B (designado B3): 
Todos os pinos do chip têm uma lógica similar a este diagrama.
O registo PINx apresenta sempre o valor lógico ("0" ou "1") que estiver presente num pino 
independentemente da configuração. É como se o registo estivesse ali a medir a tensão directamente 
Última revisão: 21/12/2010 18
no pino e a reportar o seu valor lógico ao software. 
O registo DDRx define, para cada pino do porto, se é uma entrada ou uma saída. Após o reset do 
chip, todos os pinos estão configurados como entradas, e portanto é como se todo o chip estivesse 
desligado do exterior, tem todos os pinos no ar. Para configurar um pino como saída temos que 
colocar a 1 o respectivo bit no registo DDRx. 
Se um pino estiver configurado como uma saída (se o respectivo bit no registo DDRx for 1), 
podemos então definir o estado da saída com o respectivo bit no registo PORTx. 
O PORTx controla ainda o pull-up interno quando um pino está configurado como entrada. Se o 
respectivo bit no PORTx estiver a 1, então o pull-up está activado. 
Cada AVR tem um certo número de portos. Cada porto pode não ter pinos físicos do chip 
associados a todos os seus bits. As datasheets da ATMEL (fabricante dos AVR) apresentam logo na 
2ª página o pinout (a atribuição de funcionalidade aos pinos do chip). (A partir daqui, divergimos 
um pouco do tutorial original do Njay, visto esse tratar de outro micro-controlador AVR. Neste 
documento iremos tratar do atmega168/328 – o AVR do arduino) Vamos pegar na datasheet do 
atmega168/328, e o pinout é o seguinte (com a legenda para os pinos do arduino já incluída):
Isto diz-nos que este modelo de AVR tem 4 portos, A, B, C e D. Logo, para configuração dos 
pinos, este AVR tem os registos DDRA, PORTA, PINA, DDRB, PORTB, PINB, DDRC, PORTC, 
PINC, DDRD, PORTD, e PIND. Os nomes entre parêntesis são os nomes associados aos periféricos 
Última revisão: 21/12/2010 19
do AVR quando estes estão ligados; vamos esquecê-los neste artigo. 
Configuração dos Portos em Linguagem C
É muito fácil aceder aos registos de configuração dos GPIOs (e não só) com este compilador: 
eles têm exactamente os nomes dos registos e usam-se como variáveis. Assim, existe porexemplo a 
variável DDRA e podemos escrever instruções como: 
DDRA = 0xff; // configurar todos os GPIOs do porto A como saídas
Se quisermos configurar apenas alguns GPIOs, temos disponível a macro _BV(index) que cria 
uma máscara de bits para um determinado bit do registo. Esta macro retorna um número de 8 bits 
em que apenas o bit de índice index é 1. Exemplos: _BV(0) é 1, _BV(7) é 128 (0x80), _BV(2) é 4. 
No entanto, ao longo deste documento, iremos principalmente usar o operador bit-wise de shift, já 
que o seu comportamento é igual ao da macro _BV. Por exemplo, _BV(4) = (1<<4).
Agora seguem-se alguns exemplos de configuração. Para configurar apenas o GPIO PA3 como 
saída e todos os restantes como entradas, 
DDRA = _BV(PA3); // configurar apenas o GPIO PA3 (pino 17) 
como saída
e para configurar vários GPIOs como saídas, basta efectuar um OU bit-a-bit
DDRA = _BV(PA3) | _BV(PA6); // configurar os GPIOs PA3 e 
PA6 (pinos 17 e 12) como saídas
Depois bastaria colocar no registo PORTB, no respectivo bit (3), o valor que queremos que 
"apareça" no pino.
Para ligar o pull-up de um GPIO basta garantir que o respectivo bit está a zero no registo DDRx 
e depois colocar a 1 o bit no registo PORTx. Configurar o GPIO PB1 como entrada com pull-up 
seria assim: 
DDRB &= ~_BV(PB1); // configurar PB0 como entrada 
PORTB |= _BV(PB1); // ligar o pull-up 
Portanto é muito fácil configurar os GPIOs em C.
Última revisão: 21/12/2010 20
Interrupções
O que é uma interrupção?
Irei agora começar a falar de interrupções a partir do mais básico – o que é uma interrupção?
Uma interrupção é basicamente uma pausa no programa, enquanto o processador trata de outra 
coisa mais importante.
Um exemplo da vida real:
http://www.youtube.com/watch?v=A9EP6U0BBrA
Neste caso a interrupção foi o toque com o pato que interrompeu o discurso.
Como funciona uma interrupção no AVR?
Nos AVRs, as interrupções têm várias particularidades, pois é necessário activar as interrupções 
globais, as interrupções particulares e criar uma rotina para lidar com cada interrupção.
Cada microcontrolador AVR tem um conjunto de registers cujos bits controlam vários aspectos 
do seu funcionamento (por exemplo, no tutorial que coloquei no meu primeiro post, estão 
explicados os que controlam os pinos de INPUT/OUTPUT). O mesmo acontece com as 
interrupções.
No caso do atmega328, o bit I do register SREG controla as interrupções a nível global. Quando 
o seu valor é 1, estas estão ligadas, e vice-versa. No entanto, há uma instrução mais simples para 
ligar as interrupções globais do que terem de se lembrar que é no bit I do register SREG, que é 
simplesmente “sei” (funciona tanto em assembly como em C, só que em C é uma função incluída 
no header <avr/interrupt.h>).
Depois de ligadas as interrupções globais, ainda é necessário ligar interrupções individuais. Para 
isso, é necessário encontrar qual o bit que liga certa interrupção (encontra-se na datasheet 
facilmente na zona dos registers no capítulo acerca da funcionalidade procurada).
Depois de ligadas as interrupções globais e particulares, o processador procura por mudanças de 
estado em bits de certos registers (flags), definidos pelas interrupções individuais. Quando esses bits 
tornam-se em 1, a interrupção é gerada (independentemente do que esteja a acontecer, o programa 
pára). Mas falta aqui uma coisa … o que acontece quando essa interrupção ocorre? A “Interrupt 
Service Routine” (ISR) definida para aquela interrupção é executada. No final disto tudo, a flag fica 
com o valor 0 novamente, e o programa continua a sua execução a partir do ponto em que estava.
Nota: Enquanto o processador está a executar a interrupção, as interrupções globais estão 
desligadas. Quando acaba-se de executar a interrupção, as interrupções globais são ligadas 
novamente.
Última revisão: 21/12/2010 21
Como lidar com uma interrupção no AVR?
Agora que já sabemos como funciona uma interrupção, temos de aprender a programar de forma 
a lidar com as mesmas.
Primeiro, iremos começar por definir o pseudo-código:
// Iniciar o programa
 // … (código que inicialize variáveis, LEDs, etc. aqui)
 // Ligar interrupções particulares
 // Ligar interrupções globais
 // Definir ISR para lidar com as interrupções 
particulares ligadas.
Para lidar com registos e interrupções, iremos precisar dos seguintes headers: <avr/io.h> e 
<avr/interrupt.h> (já podemos também adicionar a função main(), e um loop eterno):
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
 // … (código que inicialize variáveis, LEDs, etc. aqui)
 // Ligar interrupções particulares
 // Ligar interrupções globais
 for(;;);
}
 // Definir ISR para lidar com as interrupções 
particulares ligadas.
(como a ISR é uma função, definimos fora do main).
Como expliquei anteriormente, ligam-se as interrupções globais através da função sei()
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
 // … (código que inicialize variáveis, LEDs, etc. aqui)
 // Ligar interrupções particulares
 sei();
 for(;;);
}
 // Definir ISR para lidar com as interrupções 
particulares ligadas.
Neste tópico, vamos ignorar as interrupções individuais, pois ainda não falámos de nenhuma 
Última revisão: 21/12/2010 22
interessante, e vamos concentrar-nos nas ISR.
A biblioteca do avr dá-nos uma macro muito útil para definir uma ISR, e tem o nome ISR() 
(espertos, não são? xD), com um argumento: o nome do vector da interrupção. O vector da 
interrupção é basicamente o que identifica qual a interrupção com que estamos a lidar. Esta 
informação encontra-se na página 57 do datasheet que eu tenho (início do capítulo sobre 
interrupções/capítulo 9), numa tabela, na coluna Source.
Por exemplo, no tópico a seguir, vamos lidar com a interrupção que ocorre no pino digital 2. Este 
pino tem o nome de INT0. Ao olharmos para a tabela, vemos que a source da interrupção é INT0.
Para usarmos isto como argumento para a macro ISR, basta adicionar “_vect”. Assim, o vector é: 
INT0_vect:
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
 // … (código que inicialize variáveis, LEDs, etc. aqui)
 // Ligar interrupções particulares
 sei();
 for(;;);
}
ISR(INT0_vect) {
 // Definir o que fazer quando acontece esta interrupção
}
(para alguns, esta declaração da função ISR pode ser confusa, pois não tem tipo. No entanto, 
lembrem-se que é uma macro, e por isso ISR não é realmente o que fica no código final).
Nota: Se notarem, alguns dos Vectores na datasheet têm espaços ou vírgulas no nome. Basta 
substituir esses por _. Por exemplo, para a interrupção gerada quando se recebem dados por serial, 
temos o seguinte Source: USART, RX. O argumento que usamos para a macro ISR é: 
USART_RX_vect.
Exemplo de interrupção através do pino digital 2 (INT0)
Vamos agora fazer algo mais interessante, e codificar uma interrupção.
Comecemos com o código do tópico anterior:
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
Última revisão: 21/12/2010 23
int main(void) {
 // … (código que inicialize variáveis, LEDs, etc. aqui)
 // Ligar interrupções particulares
 sei();
 for(;;);
}
ISR(INT0_vect) {
 // Definir o que fazer quando acontece esta interrupção
}
Vamos usar para este efeito o pino 2 (podia ser feito com o pino 3 também, com poucas 
diferenças).
O objectivo deste código vai ser mudar o estado de um LED quando se toca num botão ligado ao 
pino 2. O LED vai estar ligado ao pino digital 4 (PD4).
Vamos começar por criar uma variávelglobal, com o estado do pino e inicializar esse pino como 
output (vai começar desligado) (para mais informações sobre GPIOs, ler o tutorial que pus no 
primeiro post), e já vamos colocar o código necessário para fazer toggle ao pino na ISR.
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
char output = 0; // Estado do led.
int main(void) {
 DDRD |= (1<<PD4); // Inicializar o pino digital 4 como 
output.
 PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como 
desligado.
 // Ligar interrupções particulares
 sei();
 for(;;);
}
ISR(INT0_vect) {
 // Definir o que fazer quando acontece esta interrupção
 output = ~output; // Alterar o estado
 PORTD &= ~(1<<PD4); // Desligar o pino – isto é 
necessário para quando o output é 0 se poder desligar.
 PORTD |= ((output&1)<<PD4) // output&1 pois só nos 
interessa o primeiro bit, assim evitamos mexer nos outros 
pinos.
}
Agora só nos falta mesmo inicializar a interrupção particular para o INT0.
Última revisão: 21/12/2010 24
Ao pesquisarmos na datasheet, podemos observar um pormenor acerca dos interrupts externos 
INT0 e INT1: eles podem ser ligados por 4 estados diferentes: quando o pino está low, quando o 
pino transita para high, quando o pino transita para low e quando o pino muda de estado (low-high e 
vice-versa). Como estamos a usar um botão, o mais fácil é que este ligue o pino à corrente, 
colocando-o em HIGH. Assim, queremos gerar o interrupt quando o pino transita para HIGH.
O estado escolhido para o INT0 está nos bits ISC00 e ISC01 do register EICRA (para o pino 
INT1, está nesse mesmo register, mas nos bits ISC10 e ISC11). O estado que desejamos 
corresponde a colocar ambos os bits em 1. Logo, adicionamos isso ao código:
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
char output = 0; // Estado do led.
int main(void) {
 DDRD |= (1<<PD4); // Inicializar o pino digital 4 como 
output.
 PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como 
desligado.
 EICRA |= ((1<<ISC00) | (1<<ISC01)); // Configurar 
interrupção no pino INT0 para quando este transita para 
HIGH
 // Ligar interrupções particulares
 sei();
 for(;;);
}
ISR(INT0_vect) {
 // Definir o que fazer quando acontece esta interrupção
 output = ~output; // Alterar o estado
 PORTD &= ~(1<<PD4); // Desligar o pino – isto é 
necessário para quando o output é 0 se poder desligar.
 PORTD |= ((output&1)<<PD4) // output&1 pois só nos 
interessa o primeiro bit, assim evitamos mexer nos outros 
pinos.
}
Agora só nos falta mesmo ligar a interrupção associada ao INT0. O bit que controla isto é o 
INT0 no register EIMSK. Assim, é só modificar o código, e fica completo:
// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
char output = 0; // Estado do led.
Última revisão: 21/12/2010 25
int main(void) {
 DDRD |= (1<<PD4); // Inicializar o pino digital 4 como 
output.
 PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como 
desligado.
 EICRA |= ((1<<ISC00) | (1<<ISC01)); // Configurar 
interrupção no pino INT0 para quando este transita para 
HIGH
 EIMSK |= (1<<INT0); // Ligar interrupções particulares
 sei();
 for(;;);
}
ISR(INT0_vect) {
 // Definir o que fazer quando acontece esta interrupção
 output = ~output; // Alterar o estado
 PORTD &= ~(1<<PD4); // Desligar o pino – isto é 
necessário para quando o output é 0 se poder desligar.
 PORTD |= ((output&1)<<PD4) // output&1 pois só nos 
interessa o primeiro bit, assim evitamos mexer nos outros 
pinos.
}
Agora temos um código que, ao clicarmos num botão que liga o pino digital 2 e o pólo positivo, 
faz toggle do pino digital 4 (nota: visto que não fizemos nada para tratar do bouncing, podem haver 
resultados inesperados. No entanto, como isto é só para exemplo, não considerámos muito 
importante).
Para experimentarem, podem montar o seguinte circuito:
Cuidados a ter na utilização de interrupções
Vou deixar aqui duas situações a terem em atenção quando estão a lidar com interrupções:
1. O compilador optimiza bastante o código. Isto quase sempre é uma vantagem, no entanto, 
por vezes não é. Utilizando o exemplo do tópico anterior, se tivéssemos de aceder a variável output 
Última revisão: 21/12/2010 26
no código do main(), não estaríamos a aceder ao valor correcto da variável. Isto acontece porque o 
compilador não considera que podemos aceder à função ISR só com aquele código, logo apenas 
carrega a variável da memória ram para os registers uma vez, e depois não actualiza o seu valor, que 
é alterado na ISR. Para resolver isto, dizemos ao código que a variável output é volatile, 
declarando-a assim:
volatile char output;
Isto indica ao compilador que a variável pode ser alterada de formas inesperadas, e por isso deve 
sempre actualizar o seu valor da ram.
2. O processador AVR do Arduino funciona a 8 bits. Isto quer dizer que ele só pode lidar com 8 
bits de cada vez. Não pode, por exemplo, carregar um int da memória numa só instrução, pois estes 
têm 16 bits. Mas as interrupções podem ocorrer em qualquer parte do programa … logo o que 
pensam que acontece se tirarmos a primeira metade de um inteiro da memória, e antes de tirarmos a 
segunda metade ocorrer uma interrupção que altere essa variável? Resultados não previsíveis 
obviamente … Logo, o que podemos fazer para evitar isto? O que podemos fazer é desligar 
interrupções nesses blocos de código que não podem ser perturbados (nota: se estiverem a carregar 
um char não deve haver problemas, visto ter apenas 8 bits). Isto é feito facilmente com a função 
cli() (também no header <avr/interrupt.h>). Depois do código efectuado, basta ligar novamente as 
interrupções com sei(). Por exemplo:
//...
 int i,j;
 for(;;) {
 cli();
 j = i; // o i é alterado numa interrupção
 sei();
 // …
 }
//...
E com isto acabamos a base das interrupções. Decidi começar com estas, pois nos próximos 
tutoriais, explicarei as interrupções individuais de várias funcionalidades.
 
Última revisão: 21/12/2010 27
Timers
O que são e como funcionam timers?
Timers são, como o nome sugere, utilizados para contar o tempo.
No mundo dos microprocessadores, funcionam da seguinte forma: a partir de uma fonte de 
pulsos (por exemplo: o clock do AVR), incrementam uma variável a cada pulso.
Se usarmos uma fonte de pulsos com uma frequência conhecida (por exemplo, o clock do AVR 
tem uma frequência de 16MHz), conseguimos contar o tempo. Por exemplo, com um clock de 
16MHz, sabemos que ao chegar ao valor 16000000, passou um segundo.
Timers no AVR
O AVR tem três timers: timer0, timer1 e timer2.
Cada um difere em vários aspectos, mas o mais significativo é o número de bits: o timer0 e 
timer2 têm cada um 8 bits, e o timer1 16 bits.
Os outros aspectos são os modos que suportam, portas que controlam, etc. No entanto, estes não 
afecta muito a sua funcionalidade, visto que para fazer uma mesma coisa em dois timers, só são 
necessárias algumas mudanças nos registers e pinos usados.
Mas o número de bits usados afecta bastante a funcionalidade, pois limitam a resolução que cada 
timer tem. Com 8 bits, só podemos contar até 255, e com 16 até 65535. Isto quer dizer que, com um 
clock de 16MHz, não podemos contar até 1s com nenhum, mas por exemplo, com um clock de 
65kHz, conseguimos contar até 1s com o de 16 bits, e não com o de 8 bits. No entanto, quando 
atingem o seu limite, os timers não param de contar, apenas começam novamente do zero (isto 
significa que é possível utilizar software para contar 1s tanto com os timers de 8 bits e de 16 bits. 
No entanto, isto é geralmente fora do ideal, e mais à frente iremos examinar técnicas decomo fazer 
isto sem necessitar de software).
Os timers podem ser usados em diferentes modos. No AVR, existem três modos: Normal, CTC 
(Clear Timer on Compare Match) e PWM (Pulse-Width-Modulation – este tem alguns sub-modos 
associados). Neste tutorial iremo-nos concentrar nos modos normal e CTC, e deixaremos o PWM 
para o próximo tutorial
Todos os timers oferecem estes três modos. No entanto, os modos CTC e PWM podem ter certos 
pormenores na sua utilização/configuração que diferem entre timers. O timer de 16 bits oferece a 
funcionalidade total destes modos, enquanto os outros dois oferecem um sub-conjunto dos mesmos.
Cada timer funciona incrementando um valor num certo register (no caso do timer de 16 bits, 
esse valor é guardado em dois registers. No entanto, quando programamos em C, podemos acedê-lo 
Última revisão: 21/12/2010 28
como se fosse um), até atingir o seu máximo, e depois volta a 0. A frequência com que incrementa 
esse valor depende do clock utilizado. Cada timer tem um conjunto de clocks disponíveis. Neste 
tutorial vamos utilizar apenas o clock do sistema (clkI/O), e os seus prescalers (um prescaler 
corresponde a dividir a frequência original por um certo valor. Os valores disponíveis no AVR são: 
1, 8, 64, 256 e 1024. A utilização de um prescaler para o timer não afecta o clock do sistema).
Neste tutorial iremos analisar apenas o timer de 16 bits, visto que é o que nos oferece mais 
flexibilidade, tanto em resolução e modos. No entanto, a maior parte das coisas descritas aqui 
podem aplicar-se aos outros se tiverem o modo correspondente disponível, e ajustando-se o código 
à menor resolução que oferecem e aos seus registers.
Modos Normal e CTC
Antes de começar a explicar os modos, vou introduzir alguns conceitos: TOP, BOTTOM, MAX e 
overflow. MAX corresponde ao valor máximo que o timer aguenta. No caso do de 16 bits é 65535. 
TOP corresponde ao valor máximo que o timer atinge. Isto depende do modo. BOTTOM é o valor 
mínimo que o timer tem, e que é sempre 0 (no entanto, podemos mudar isto artificialmente, através 
de software. Não é o mais recomendado, sendo sempre preferível mudar o TOP, por ser mais 
eficiente). Overflow é o nome que se dá ao que acontece quando o timer chega a MAX: 
sobrecarrega o máximo suportado por 16 bits e volta a 0
Iremos começar pelo modo normal.
O modo normal é o mais simples: o timer simplesmente incrementa o register correspondente ao 
mesmo até atingir o seu limite, e nesse momento o register volta a 0.
Neste modo, o TOP corresponde ao MAX.
Agora, o modo CTC.
O modo CTC é um pouco mais complexo, mas também útil. Basicamente, a particularidade deste 
modo é que podemos ajustar o TOP. O timer1 permite-nos escolher dois registers como contendo o 
valor de TOP: OCR1A e ICR1. Iremos ver o impacto disto mais à frente, quando estudarmos 
eventos relacionados com os timers. No entanto, uma coisa a ter cuidado é o seguinte: quando se 
escreve um novo valor para os registers OCR1A e ICR1 devemos ou ter a certeza que é um valor 
maior que o anterior, ou que o valor do timer é menor que esse valor (cuidado quando se usa um 
clock alto, pois durante a escrita, o valor é actualizado) ou que fazemos reset ao timer, pois se 
alterarmos o valor de OCR1A ou ICR1, e o timer já tiver ultrapassado o novo valor, vai até MAX e 
faz overflow, e aí é que volta a funcionar normalmente.
Como usar um timer no AVR
No AVR, os timers são controlados por um conjunto de registers.
Última revisão: 21/12/2010 29
Para poder explicar mais facilmente como usar um timer, vamos estabelecer um objectivo: criar 
uma função que funcione como a função _delay_ms().
Vamos começar com o pseudo-código:
// Iniciar o programa
 // Iniciar a função new_delayms(x)
 // Iniciar o timer
 // Verificar o timer para ver se já se passaram x ms.
 // Terminar a função
Já podemos inserir algumas coisas: a declaração da função, o loop em que se vai verificar o valor 
do timer e os headers necessários para aceder aos registers: <avr/io.h> (vamos esquecer a função 
main neste caso):
//Iniciar o programa
#include <avr/io.h>
void new_delayms(int x) {// Iniciar a função new_delayms(x)
 //Iniciar o timer
 for(;;) {
 //Verificar o timer para ver se já se passaram x ms.
 }
} // Terminar a função
Para começar, vamos compreender como se sabe quanto tempo passou, tendo em conta o valor 
do timer:
Sabemos que o timer incrementa a sua variável de acordo com uma certa frequência, e que a 
fórmula para calcular a frequência é:
f = incrementos/s
Neste caso, visto que queremos os milisegundos, podemos ajustar a fórmula:
f/1000 = incrementos/ms
Nesta fórmula, o único valor que podemos conhecer do início é a frequência (como vamos usar o 
clock do sistema, será 16MHz), logo podemos já ajustar essa parte da fórmula:
16000 = incrementos/ms
O que queremos descobrir é quantos incrementos, logo ajustamos para:
incrementos = 16000*ms
E temos uma forma para descobrir quantos ms passam, se começarmos do 0 no timer.
No entanto, muitos estão a pensar numa coisa, provavelmente: limites.
Sabemos que o limite para 16 bits é: 65535, logo, no máximo podemos medir:
65535 = 16000*ms <=> ms = 65535/16000
Última revisão: 21/12/2010 30
<=> ms ~= 4
4ms (isto tem algum erro, mas vamos ignorá-lo para manter o exemplo simples)! Isso é longe do 
ideal, e se quisermos medir 6, 7 ou 8 ms?
Temos duas formas para aumentar esta resolução: utilizar prescalers, que diminuem o clock, ou 
manipulação por software.
A utilização de prescalers é apropriada para casos particulares. No entanto, a manipulação por 
software é mais apropriada aqui, visto que nos permite calcular o tempo passado para qualquer 
valor possível de int (para fazermos o mesmo com um timer de 16 bits, necessitaríamos de um 
prescaler de 16k, o que não existe).
Vamos então fazer isto passo-a-passo: inicializar o timer.
Os bits de configuração/inicialização do timer (mais respectivamente de selecção de modo e 
clock) encontram-se em dois registers: TCCR1A, TCCR1B e TCCR1C.
Para seleccionar o modo, utilizamos os bits WGM10 a WGM13 (espalhados pelos dois registers
). Neste caso, queremos o modo normal, logo pomos esses 4 bits a 0 (como esse é o valor por 
defeito, não temos de fazer nada).
Para seleccionar o clock, utilizamos os bits CS10 a CS12, no register TCCR1B. Neste caso 
queremos o clock do sistema, sem prescaler. Ao olharmos para a datasheet, vemos que temos de 
colocar o bit CS10 com o valor 1.
Nota: O timer começa sem nenhum clock seleccionado, logo não está a incrementar o register 
correspondente, e esse é inicializado automaticamente a 0. Assim que seleccionamos um clock, o 
timer começa imediatamente a incrementar o register.
Logo, já podemos inicializar o timer:
//Iniciar o programa
#include <avr/io.h>
void new_delayms(int x) {// Iniciar a função new_delayms(x)
 TCCR1B |= (1<<CS10); //Iniciar o timer
 for(;;) {
 //Verificar o timer para ver se já se passaram x ms.
 }
} // Terminar a função
O método por software que vamos usar é o seguinte: numa variável guardamos o valor anterior 
do timer. Se o novo valor for menor, significa que houve um overflow, e incrementamos uma 
variável que nos indica quantas vezes passaram 4ms (aviso: isto apenas se aplica para uma 
frequência de 16MHz. Se querem manipular o vosso programa para se adaptar a outras frequências, 
podem usar a macro F_CPU, que indica a frequência, e utilizar as fórmulas acima, para descobrir 
Última revisão: 21/12/2010 31
quantos ms passam em cada overflow). Depois guardamos o novo valor na variável e vemos quanto 
tempo se passou até então (o valor da variável dos 4ms+os milissegundos passados,cuja fórmula é: 
ms = valor/16000), e caso tenha passado o tempo desejado ou mais (ao executarmos as instruções 
pode passar mais tempo do que o desejado), paramos o ciclo, saindo assim da função.
Os registers onde o timer1 guarda o valor do timer são TCNT1H e TCNT1L (são dois por ter 16 
bits). A datasheet descreve a ordem em que estes devem ser acedidos. No entanto em C não nos 
precisamos de preocupar com isso, pois o seu acesso é simplificado, utilizando-se o nome TCNT1 
como se fosse um só register:
//Iniciar o programa
#include <avr/io.h>
void new_delayms(int x) {// Iniciar a função new_delayms(x)
 int times_4ms = 0;
 int prev_value;
 TCCR1B |= (1<<CS10); //Iniciar o timer
 prev_value = TCNT1;
 for(;;) {
 if(prev_value > TCNT1) times_4ms++; // Incrementar a 
variável dos 4 ms caso tenha havido um overflow
 prev_value = TCNT1;
 if(prev_value/16000 + times_4ms*4 >= x) //Verificar o 
timer para ver se já se passaram x ms.
 break; // Se sim, sair.
 }
} // Terminar a função
E aí temos uma função de delay que funciona com 16MHz. Esta é a forma mais básica de se usar 
timers, e através do código usado, parecem-nos ineficientes e básico.
Usados desta forma, timers não são muito úteis … tornam-se realmente úteis quando começamos 
a explorar a sua utilização com eventos e interrupções, que são os temas dos próximos tópicos.
Eventos relacionados com timers.
Os timers têm uma série de eventos e interrupções associados aos mesmos Com estes é que se 
tornam muito poderosos. Neste tópico, iremos usar principalmente o modo CTC, pois os exemplos 
utilizados são os que melhor demonstram a sua utilidade.
Este tópico irá cobrir os eventos relacionados com os pinos OC1A, OC1B, e ICP1.
Os timers permitem-nos escolher um evento que pode ocorrer nos pinos OC1A/OC1B, quando o 
valor do TCNT1 equivale a OCR1A/OCR1B. Os eventos possíveis dependem do modo escolhido. 
Os possíveis para os modos normal e CTC são equivalentes, logo serão os explorados neste tutorial.
Para explorarmos o que podemos fazer com estes pinos, vamos primeiro estabelecer um 
Última revisão: 21/12/2010 32
objectivo: piscar um LED (ligado 1s, desligado 1s, ligado 1s, desligado 1s, …), sem utilizar 
qualquer controlo por software/interrupções, apenas as configurações dos pinos. O pino usado neste 
exemplo será o pino OC1A (pino digital 9).
Logo, o nosso pseudo-código será assim:
// Iniciar o programa
 // Configurar o pino digital 9 como output (iniciar como desligado – valor por defeito
 // Configurar o timer
 // Configurar os eventos do pino OC1A
 // Loop eterno
Já podemos preencher algumas coisas:
// Iniciar o programa
#include <avr/io.h>
int main(void) {
 DDRB |= (1<<PB1); // Configurar o pino digital 9 como 
output (iniciar como desligado – valor por defeito
 // Configurar o timer
 // Configurar os eventos do pino OC1A
 for(;;); // Loop eterno
}
Agora, para configurar o timer, temos de fazer algumas contas …
Queremos que o LED se ligue e desligue a cada segundo. Para fazer isto, sem ajuda de software, 
temos de usar os eventos no pino OC1A. Cada vez que o timer chega ao valor OCR1A, ocorre um 
evento. Ao usarmos o modo normal, independentemente de qual o valor de OCR1A, a distância de 
tempo em que ocorre esse evento é constante – igual ao tempo que demora para incrementar a 
variável do 0 ao MAX (mesmo que ponhamos OCR1A no meio, ele continua a incrementar até 
chegar ao MAX, e depois vai do 0 até ao MAX/2).
Se testarmos os prescalers, até encontramos um que faz com que esse tempo seja perto de 1s 
(com o prescaler 256, 1s = 62500 iterações, MAX = 65535, nota), mas com um erro grande que 
aumenta em cada evento, logo não é o ideal.
As contas feitas são as seguintes:
FREQ = F_CPU/prescaler
F_CPU = 16000000
prescaler = 256
Última revisão: 21/12/2010 33
FREQ = 62500
t = 1s
f = inc/t <=> 62500 = inc/1
<=> inc = 62500
Aqui é que entra a utilidade do CTC. Para fazer o que queremos, usando o prescaler de 256, 
queremos fazer reset ao timer, cada vez que chega aos 62500. O modo CTC faz exactamente isso. 
Neste caso, 62500 corresponderá ao valor de TOP.
Se forem ver para trás, onde explico este modo, irão notar que eu digo que podemos usar dois 
registers como valor de TOP no modo CTC: ICR1 e OCR1A. Como queremos criar um evento no 
pino OC1A, usar o register OCR1A como TOP é exactamente o que precisamos (se quiséssemos 
gerar o evento no pino OC1B, teríamos de usar ICR1 ou OCR1A como TOP, e colocar o mesmo 
valor no register OCR1B).
Então, já podemos configurar o timer: queremos que o clock utilizado seja o clock do sistema 
com um prescaler de 256, no modo CTC com OCR1A como TOP e com o valor 62500 como TOP:
// Iniciar o programa
#include <avr/io.h>
int main(void) {
 DDRB |= (1<<PB1); // Configurar o pino digital 9 como 
output (iniciar como desligado – valor por defeito
 // Configurar o timer
 TCCR1B |= (1<<WGM12); // Modo: CTC com OCR1A como TOP
 TCCR1B |= (1<<CS12); // Clock do sistema com prescaler de 
256
 OCR1A = 62500; // Valor do TOP, de forma a passar-se um 
segundo.
 // Configurar os eventos do pino OC1A
 for(;;); // Loop eterno
}
Agora só nos falta configurar os eventos no pino OC1A.
Os eventos nos pinos OC1A/OC1B são configurados através dos bits COM1A0/COM1B0 e 
COM1A1/COM1B1 no register TCCR1A. O evento que desejamos é um toggle do pino OC1A.
Ao consultarmos a datasheet, vemos que esse evento corresponde a COM1A0 = 1 e COM1A1 = 
0:
// Iniciar o programa
#include <avr/io.h>
int main(void) {
 DDRB |= (1<<PB1); // Configurar o pino digital 9 como 
Última revisão: 21/12/2010 34
output (iniciar como desligado – valor por defeito
 // Configurar o timer
 TCCR1B |= (1<<WGM12); // Modo: CTC com OCR1A como TOP
 TCCR1B |= (1<<CS12); // Clock do sistema com prescaler de 
256
 OCR1A = 62500; // Valor do TOP, de forma a passar-se um 
segundo.
 TCCR1A |= (1<<COM1A0); // Configurar os eventos do pino 
OC1A
 for(;;); // Loop eterno
}
E aí têm: um programa que faz toggle do pino OC1A (pino digital 9) a cada 1s. Podem testar isto 
com um LED, como no esquema abaixo:
Também podemos forçar o evento que ocorre nos pins OC1A/OC1B escrevendo um 1 para os 
bits FOC1A/FOC1B do register TCCR1C. Isto, no entanto, deve ser feito com cuidado, visto que só 
altera o estado de output do pino, de acordo com a configuração, não gerando quaisquer 
interrupções associadas com uma igualdade ao register OCR1A nem fazendo reset ao timer.
O tipo de eventos que estudámos primeiro, foram os eventos de output. No entanto, também 
temos os eventos de input, associados ao pino ICP1 (pino digital 8; na verdade, podemos ter mais 
do que esse pino como fonte de input, visto que um evento no analog comparator pode gerar um 
evento de input relacionado com o timer. No entanto, visto que o evento comporta-se da mesma 
forma, independentemente do input, só considerarem eventos no pino ICP1). Estes eventos são 
simples de compreender: quando ou o input transita de HIGH para LOW ou de LOW para HIGH 
(este comportamento é definido ICES1 no register TCCR1B: 0 para HIGH-LOW e vice-versa), o 
valor no register TCNT1 é escrito no register ICR1. Atenção à forma como isto se funciona: devem 
lembrar-se que o register ICR1 pode ser usado como valor de TOP no modo CTC (e PWM também, 
mas fica para outro tutorial). Quando isto acontece, estes eventos de input são desligados.
Se se lembram do tutorial anterior, demonstrei como usar um interrupt associado com um pino 
digital para detectar um clique num botão. Este tipo de eventos pode ser usado da mesma forma, 
mas com uma vantagem: ao colocarmos o bit ICNC1 como 1 no register TCCR1C, o hardware faz 
Última revisão: 21/12/2010 35controlo de bouncing automático, ao testar o valor do pino 4 vezes, a ver se é igual nessas 4 vezes 
(nota: esses quatro testes são independentes do prescaler do timer, visto que são feitos de acordo 
com o clock do sistema). No próximo tópico, em que falamos sobre interrupções e timers, 
demonstraremos uma forma de usar este filtro, em vez da forma básica demonstrada no tutorial 
anterior.
E estes são os principais eventos associados aos timers, relacionados com hardware. Para acabar, 
só faltam agora as interrupções.
Interrupções e timers.
Existem 4 interrupções associadas com o timer1: uma que ocorre quando se recebe um input, 
outras duas que ocorrem quando o valor do register TCNT1 é igual aos valores dos registers 
OCR1A/OCR1B e uma que ocorre quando ocorre um overflow do timer (atenção, que no modo 
CTC, o overflow ocorre apenas se o timer chegar ao valor MAX, e não ao top, logo pode nunca 
ocorrer. Esta interrupção pode ser usada para, por exemplo, verificar a ocorrência de erros quando 
se muda o valor de TOP).
Estas interrupções estão todas definidas no register TIMSK1.
Os vectores associados às mesmas são:
TIMER1_CAPT_vect
TIMER1_COMPA_vect
TIMER1_COMPB_vect
TIMER1_OVF_vect
O tutorial anterior mostrou como lidar com interrupções, por isso será deixado à imaginação do 
leitor o que fazer com estas. No entanto, a título de exemplo, iremos cumprir o prometido no tópico 
anterior: codificar um programa com a mesma função que o do tutorial anterior, só que com 
controlo de bouncing feito pelo hardware. Neste caso, não usamos nada relacionado directamente 
com o timer, excepto a interrupção de input. No entanto, isto, em conjunção com outros interrupts, 
pode ser usado para fazer um programa que registe a distância, em tempo entre dois inputs (para 
caber tudo em ints, pode-se criar um int separado para os segundos, minutos e horas – o leitor pode 
fazer isto ser mais eficiente de acordo com qualquer critério). Como não usamos a funcionalidade 
do timer, não chegamos a seleccionar um timer para o clock, logo, o valor escrito em ICR1 será 
sempre 0. Caso o leitor queira usar o timer, tem de seleccionar um clock e modo para o mesmo (o 
valor escrito em ICR1 será o valor de TCNT1 na altura do input).
Comecemos com o pseudo-código:
Última revisão: 21/12/2010 36
// Iniciar programa
 // Iniciar o pino digital 4 como output, e desligado – 
estado por defeito
 // Configurar o input para reconhecer eventos LOW-HIGH
 // Ligar o filtro para o input.
 // Ligar a interrupção para o input.
 // Ligar as interrupções globais.
 // Loop eterno
 // Definir a ISR para o input: TIMER1_CAPT_vect
 // Fazer toggle do pino digital 4
Vamos começar pelo mais básico:
// Iniciar programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
 DDRD |= (1<<PD4); // Iniciar o pino digital 4 como 
output, e desligado – estado por defeito
 // Configurar o input para reconhecer eventos LOW-HIGH
 // Ligar o filtro para o input.
 // Ligar a interrupção para o input.
 sei(); // Ligar as interrupções globais.
 for(;;); // Loop eterno
}
ISR(TIMER1_CAPT_vect) { // Definir a ISR para o input: 
TIMER1_CAPT_vect
 PORTD ^= (1<<PD4); // Fazer toggle do pino digital 4
}
Se se lembram das minhas explicações anteriores, os bits usados para configurar o evento de 
input são ICNC1 (tratar do bouncing automaticamente) e ICES1 (que tipo de evento é registado, 
para LOW-HIGH, queremos o valor 1), no register TCCR1B:
// Iniciar programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
 DDRD |= (1<<PD4); // Iniciar o pino digital 4 como 
output, e desligado – estado por defeito
 TCCR1B |= (1<<ICES1); // Configurar o input para 
reconhecer eventos LOW-HIGH
 TCCR1B |= (1<<ICNC1); // Ligar o filtro para o input.
 // Ligar a interrupção para o input.
 sei(); // Ligar as interrupções globais.
 for(;;); // Loop eterno
Última revisão: 21/12/2010 37
}
ISR(TIMER1_CAPT_vect) { // Definir a ISR para o input: 
TIMER1_CAPT_vect
 PORTD ^= (1<<PD4); // Fazer toggle do pino digital 4
}
Agora só nos ligar a interrupção particular para o input. Como disse antes, as interrupções dos 
timers são definidas no register TIMSK1, e o bit que procuramos é o ICIE1:
// Iniciar programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
 DDRD |= (1<<PD4); // Iniciar o pino digital 4 como 
output, e desligado – estado por defeito
 TCCR1B |= (1<<ICES1); // Configurar o input para 
reconhecer eventos LOW-HIGH
 TCCR1B |= (1<<ICNC1); // Ligar o filtro para o input.
 TIMSK1 |= (1<<ICIE1); // Ligar a interrupção para o 
input.
 sei(); // Ligar as interrupções globais.
 for(;;); // Loop eterno
}
ISR(TIMER1_CAPT_vect) { // Definir a ISR para o input: 
TIMER1_CAPT_vect
 PORTD ^= (1<<PD4); // Fazer toggle do pino digital 4
}
E temos um programa que usa interrupções dos timers!
O circuito que usa isto é o seguinte:
Última revisão: 21/12/2010 38
Timers – Parte 2, Pulse Width Modulation
O que é PWM?
Microcontroladores, por si só, são incapazes de gerar sinais analógicos (voltagens variáveis), 
apenas podendo gerar dois sinais distintos: 0 e 1 (normalmente, 0V e 5V respectivamente). No 
entanto, muitas vezes estes sinais são necessários para controlar vários dispositivos: servos, colunas, 
…
Então, como podemos criar esses sinais? A resposta simples: PWM.
Pensem num carro a andar: se metade de um período de tempo andar a 5 km/h, e a outra metade 
estiver parado, qual é a sua velocidade média ao longo desse período? 2,5 km/h.
Passem esta analogia para electricidade: 5V metade do tempo e 0V a outra metade dão 2,5V.
Para “enganarmos” o circuito, de forma a pensar que é mesmo 2,5V constantes, fazemos isto a 
uma grande frequência (e se utilizarmos alguns componentes externos, até conseguimos estabilizar 
a corrente, mas isso não será discutido aqui).
Vou agora introduzir um conceito ligado ao PWM: o duty cycle. Nem sempre queremos que a 
voltagem seja 2,5V, logo deixamos ligados os 5V por mais ou menos tempo de acordo com o que 
precisamos. À percentagem de tempo em que os 5V ficam ligados, chamamos de duty cycle. Assim, 
quanto maior o duty cycle, maior a voltagem média e vice-versa (assim, duty cycle de 100% = 5V, e 
de 0% = 0V).
Então, como podemos utilizar o PWM no AVR? Existem duas formas de gerar um sinal de PWM 
no AVR: por software (através de delays ou com o auxílio de interrupções) ou por hardware (que é o 
que veremos nesta parte do tutorial).
Um pormenor que convém notar acerca do PWM: ao utilizarem o modo CTC, aconselhei a que 
tivessem cuidado ao actualizar os registers ICR1 e OCR1A, quando os seus valores eram o TOP. No 
PWM, estes registers podem ser o TOP, mas no modo PWM, o register OCR1A tem um buffer 
duplo. Isto significa que ao alterarmo-lo, não o fazemos directamente, mas sim a um buffer. Num 
momento definido pelo modo PWM (bits WGM dos registers de controlo do timer), o register 
OCR1A é actualizado com o valor nesse buffer. Isto dá-nos maior controlo sobre a frequência, pois 
podemos alterar o TOP a qualquer altura (claro que ao utilizarmos o register OCR1A como TOP, 
estamos a sacrificar o pino OC1A como output de PWM). Logo, quando queremos variar a 
frequência do PWM, devemos usar OCR1A como top, e caso queiramos uma frequência fixa, 
podemos usar o ICR1.
Vários Modos de PWM
Ao utilizarmos o AVR, temos várias modos em termos de PWM: Fast PWM, Phase Correct 
Última revisão: 21/12/2010 39
PWM e Phase and Frequency Correct PWM.
Os modos Fast PWM e Phase/Phase and Frequency Correct PWM são os que diferem mais.
Fast PWM funciona da seguinte forma: O timer conta até OCR1x, e nesse momento actualiza o 
pino

Outros materiais

Outros materiais