Vous êtes sur la page 1sur 83

Programao em C no AVR

Rodrigo Toste Gomes a.k.a Cynary Nuno Joo a.k.a Njay Senso

ltima reviso: 21/12/2010

Neste documento tentamos explicar ao leitor as vrias funes que um micro-controlador AVR disponibiliza, e como pode controlar as mesmas, sem recorrer a bibliotecas de alto nvel que muitas vezes roubam performance e acrescentam tamanho aos programas, como por exemplo as que o arduino disponibiliza. Assumimos que o leitor tem alguma experincia com programao em C, e que o contacto que tem com AVR com o arduino (logo, todos os exemplos que temos aqui iro funcionar no mesmo. No entanto, podem funcionar noutros micro-controladores AVR com poucas ou nenhumas alteraes). Todos os programas aqui podem ser compilados atravs 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 cdigo) 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 fcil alterar estes comandos para funcionarem com outros micro-controladores e programadores, estando essa informao provavelmente na datasheet. Os programas necessrios para os comandos acima funcionarem vm instalados com o IDE do arduino, e podem ser utilizados para programar outros micro-controladores que no o usado pelo arduino. Para os utilizadores de windows, a utilizao das ferramentas AVRStudio e WinAVR recomendada, mas visto que est alm do alcance deste documento, o leitor incentivado a pesquisar, mas garantimos que os comandos acima funcionam. Qualquer software presente neste documento oferecido com objectivos didcticos, e no acompanhado de qualquer garantia de performance ou funcionalidade. Gostaria de agradecer a todos os membros da lusorobtica que comentaram no tpico respectivo a estes tutoriais, e em especial ao membro Njay, que me autorizou a usar o seu Micro-tutorial neste documento.

ltima reviso: 21/12/2010

ndice
Programao em C no AVR..................................................................................................................1 Introduo.............................................................................................................................................5 Programao em C em micro-controladores........................................................................................6 Controlo da funcionalidade do micro-controlador os registers....................................................6 Pseudo-cdigo/cdigo esqueleto......................................................................................................7 MACROS.........................................................................................................................................8 Variveis volatile..............................................................................................................................8 Operaes 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 perifrico............................................................................................16 Sada Digital Normal.....................................................................................................................16 Sada em Colector Aberto (open colector).....................................................................................17 Sada controlada por um perifrico................................................................................................17 GPIOs na Arquitectura AVR..........................................................................................................18 Configurao dos Portos em Linguagem C...................................................................................20 Interrupes........................................................................................................................................21 O que uma interrupo?..............................................................................................................21 Como funciona uma interrupo no AVR?....................................................................................21 Como lidar com uma interrupo no AVR?...................................................................................22 Exemplo de interrupo atravs do pino digital 2 (INT0).............................................................23 Cuidados a ter na utilizao de interrupes.................................................................................26 Timers.................................................................................................................................................28 O que so 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 Interrupes e timers......................................................................................................................36 Timers Parte 2, Pulse Width Modulation.........................................................................................39 O que PWM?...............................................................................................................................39 Vrios Modos de PWM.................................................................................................................39 Fast PWM......................................................................................................................................41 Phase and Frequency Correct PWM..............................................................................................46 Analog-to-Digital Converter..............................................................................................................52 Formato Analgico 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 distncia.......................................................................57 ADC8 medir a temperatura interna.............................................................................................61 Comunicao Serial no AVR..............................................................................................................62 Como funciona a comunicao Serial?..........................................................................................62 O que a USART?.........................................................................................................................63 Inicializando a USART do AVR....................................................................................................64 Enviando e Recebendo Dados atravs da USART........................................................................65 Exemplo de utilizao do USART.................................................................................................68 Comunicao por IC..........................................................................................................................72 O Protocolo IC..............................................................................................................................72 ltima reviso: 21/12/2010 3

IC no AVR.....................................................................................................................................74 Bibliografia.........................................................................................................................................83

ltima reviso: 21/12/2010

"Excerto do "Micro Tutorial AVR" de Njay (http://embeddeddreams.com/users/njay/Micro Tutorial AVR - Njay.pdf) com alteraes/adaptaes de Cynary (formatao e contedo)"

Introduo
"AVR" o nome de uma famlia 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 comercializao. Para quem souber ingls, podem ver uma pequeno vdeo 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"), memrias volteis e no- volteis e perifricos. Ao contrrio 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 instrues aumentou algumas vezes ao longo do tempo de forma a suportar mais funcionalidade). Assim de uma forma rpida podemos resumir a arquitectura do AVR nos seguintes pontos: Consiste num core de processamento, memria de programa (no voltil, FLASH), memria voltil (RAM esttica, SRAM), memria de dados persistentes (no voltil, EEPROM) e bits fuse/lock (permitem configurar alguns parmetros especiais do AVR). Arquitectura de memria Harvard (memria de programa e memria de dados separadas) A memria voltil (SRAM) contnua A maior parte das instrues tm 16 bits de tamanho, e este o tamanho de cada palavra na Execuo de 1 instruo por ciclo de relgio para a maior parte das instrues. Existem 32 registos de 8 bits disponveis e h poucas limitaes ao que se pode fazer com Os registos do processador e os de configurao dos perifricos esto mapeados (so Existe um vector de interrupo diferente por cada fonte de interrupo. Existem instrues com modos de endereamento complexo, como base + deslocamento O conjunto de instrues foi pensado para melhorar a converso de cdigo C em assembly.

memria de programa (FLASH).

cada um. acessveis) na SRAM.

seguido de auto- incremento/decremento do endereo. (A introduo do Micro tutorial do Njay mencionava mais alguns tpicos, que considerei como irrelevantes para este tutorial, logo cortei-os).

ltima reviso: 21/12/2010

Programao 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 necessrio compreender como controlar as vrias funes 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 bsicas primeiro, respectivamente: Controlo da funcionalidade do micro-controlador os registers. Pseudo-cdigo/cdigo esqueleto MACROS Variveis volatile Operaes bit-wise em C. assumido que o leitor sabe programar em C e que domina os seguintes conceitos: comentrios, bibliotecas, variveis, funes, ponteiros, ciclos, condies, lgica e bases numricas.

Controlo da funcionalidade do micro-controlador os registers


Os AVR tm vrias funes: podem ser usados para comparar e ler diferenas de potencial, comunicar por serial, Todas estas funes so controladas por registers mas o que so registers? Todos os CPUs tm uma certa memria interna. Esta funciona quase como a memria ram, excepto no uso de ponteiros. O CPU tem acesso directo a esta memria, o que significa que em termos de performance muito mais eficiente usar registers para armazenamento do que memria ram (o compilador em C optimiza automaticamente os programas, dando uso deste boost na performance sempre que possvel da a importncia de usar variveis volatile quando se usam interrupes, estudadas mais frente). No entanto, estes no so s usados para armazenamento, mas tambm para controlar vrias funes 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 tm o tamanho de 8 bits. Logo, quando necessrio armazenar valores maiores que 255, usam-se mais do que um register. No entanto, este pormenor abstrado 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 do-nos um header muito til que nos permite aceder directamente aos ltima reviso: 21/12/2010 6

registers e bits dos mesmos atravs 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 cdigo 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 no compreende exactamente como altermos um bit no register, no se preocupe, pois as operaes bit-wise sero explicadas de seguida.

Pseudo-cdigo/cdigo esqueleto
O pseudo-cdigo basicamente uma representao abstracta do cdigo, em linguagem natural. Muitas vezes comea-se por escrever o pseudo-cdigo, e depois vai-se substituindo por linhas de cdigo (muitas vezes o pseudo-cdigo transforma-se nos comentrios). 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 funes Input/Output, parte da inicializao incluir o header stdio.h, o resto comear a funo main(), e terminamos com return 0 (sair do programa com sucesso visto que nos AVRs no existe sistema operativo, a funo 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 funcional do programa: escrever o Hello World no ecr:

ltima reviso: 21/12/2010

// 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 instrues comeadas por '#'. Estas no so instrues em C, mas sim instrues interpretadas apenas pelo pr-processador, antes da compilao. Por exemplo, quando fazemos #include <qualquercoisa.h>, estamos a indicar ao pr-processador para incluir o contedo do ficheiro qualquercoisa.h no nosso programa. Uma MACRO uma instruo deste tipo, que se comporta como uma funo. So teis quando queremos realizar certas tarefas repetidamente, mas no se justifica o custo em performance de chamar uma funo (para quem programa em C++, isto equivalente ao inline). Por exemplo, duas macros que costumo usar so as seguintes: #define max(I,J) ((I)>(J)?(I):(J)) #define min(I,J) ((I)<(J)?(I):(J)) Antes da compilao, o pr-processador substitui todas as declaraes de max(x,y) e min(x,y) pelo cdigo correspondente, sem ser assim necessrio chamar uma funo (as macros so teis para substituir principalmente funes com s uma linha de cdigo). H vrios pormenores envolvidos na criao de macro (como por exemplo, abusar das parntesis para proteger o cdigo), mas no interessam para este tutorial. No entanto, visto que so muito teis, aconselho os interessados a pesquisar sobre elas.

Variveis volatile
Quando declaramos variveis, podemos controlar certos aspectos de como o cdigo deve acedlas. Uma declarao importante quando se programa AVRs, devido existncia de interrupes, a volatile. Mais frente explicarei a importncia disto, por agora apenas importante reter que quando se declara uma varivel como volatile, estamos a informar que o seu valor pode ser alterado de formas inesperadas, logo deve sempre ir buscar o seu valor actualizado.

Operaes bit-wise em C
Muita da programao em micro-controladores consiste principalmente em manipular bits de certos registers. Para fazer isso, usamos as operaes bit-wise que manipulam valores ao nvel dos bits. ltima reviso: 21/12/2010 8

Para quem no compreende bases numricas, e no 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 dgitos diferentes (do 0 ao 9). Com combinaes deles, fazemos diferentes nmeros. Quando queremos um valor acima do dgito maior, transportamos mais um para a posio seguinte (se contarmos desde a direita). Assim podemos dar valores a cada posio no nmero. Por exemplo, o nmero 29 tem um 9 na posio 0 e um 2 na posio 1. A posio 0 corresponde ao valor 10 (1), e a 1 ao valor 10. Assim, podemos chegar ao nmero atravs da conta: 2*10 + 9*10. Nmeros de base binria funcionam da mesma forma que os de base decimal, com a particularidade de apenas utilizarmos dois algarismos: o 0 e o 1. Assim, cada posio tem um valor diferente. Por conveno, chamam-se s posies de um nmero em base binria de bit. Assim, quando falamos em manipular bits, estamos a falar em manipular o valor (0 ou 1) de certas posies. Por exemplo, o nmero 1001 (para facilitar a leitura, costumam-se ler os dgitos separados. Assim, em vez de se ler mil e um, l-se um zero zero um) corresponde ao nmero 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 so os nicos bits com dgitos l, chegamos ao 9 atravs da conta: 1*2+1*2. Agora que j conhecemos a base binria, e o que significa manipular bits, vamos ver como podemos manipul-los. Isto feito atravs de operaes bit-wise. Em C, existem cinco operaes bit-wise: | or & and ~ not ^ xor << shift left >> shift right As duas primeiras operaes funcionam como as operaes lgicas ||, &&. No entanto, em vez de testarem a varivel como um todo lgico, testam bit a bit, e o resultado corresponde a essa comparao bit a bit. Por isso, enquanto temos resultados bem definidos com as operaes | e &, as

ltima reviso: 21/12/2010

operaes || e && podem dar um valor aleatrio para verdadeiro. Assim, quando se necessitam de valores lgicos, devem-se usar as operaes || e &&, e para manipulao bit a bit, devem-se usar as operaes | e & (nota: as operaes & e && podem ter resultados diferentes). Vamos ento comear por estudar essas duas operaes: O or retorna 0 quando ambos os bits so 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 nmeros, 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 nmero 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 nmeros tm um 1. 1 ou 1 = 1. Logo, o resultado ter um 1 no bit 3. Nos restantes bits, o primeiro nmero tem um 1, e o segundo tem um 0. 1 ou 0 = 1. Logo os restantes bits (bits 4 e 5) tero um 1 no resultado. Assim, chegamos ao nmero 111110. Podemos usar isto para colocar o valor 1 numa certa posio num nmero. Por exemplo, temos o nmero 1101 (em decimal o nmero 13), e queremos preencher aquele 0 com um 1. Se fizermos um ou com o nmero 0010 (em decimal o nmero 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 nmero 15 em binrio 1111. return 0; }

Vamos agora observar a operao &. O and retorna 0 quando pelo menos um dos bits 0, e 1 quando os dois bits so 1. Por exemplo: 1101 & 0111 = 0101 A anlise deste exemplo ser deixada como um desafio ao leitor. ltima reviso: 21/12/2010 10

O & muitas vezes usado para colocar a 0 um certo bit. Por exemplo: se tivermos o nmero 10111 (em decimal 23), e quisermos a partir dele obter o nmero 10101 (em decimal 21), podemos fazer a seguinte operao: 10111 & 1101 = 10101:

#include <stdio.h> int main(void) { int i = 23; i = i&13; // 13 em binrio 1101; equivalente a i &= 13; printf(%d\n, i); // Imprime 21 return 0; }

A terceira operao, ~ (not), tambm tem um comportamento semelhante ao seu equivalente lgico, o !. No entanto, foi separado das outras duas operaes, pois esta no pode ser usada como uma operao lgica, visto que ~(true) pode dar um valor verdadeiro mesma (interessantemente, devido forma como a aritmtica dos CPUs funcionam, fazer o ~ de qualquer nmero positivo d um nmero negativo e vice-versa, sendo a nica excepo o -1, j que ~(-1) = 0. No aprofundaremos mais isto, visto que no interessa muito para programar micro-controladores). Vejamos como funciona: ~1101 = 0010 Cada bit do nmero original invertido, logo a partir de um nmero positivo (true), podemos no obter 0 (false), que exactamente o que o ! lgico faz. O ~ muitas vezes utilizado em conjuno com o & para pr um valor 0 num bit. Vejamos porqu: 11101 & 10111 = 10101 Sabendo a posio do bit que queremos pr a 0 (neste caso a posio 4), como chegamos ao seu inverso, de forma a manter o resto do nmero intacto. Usando o ~, claro! Neste caso, fazer: 11101 & 10111 = 10101 igual a fazer:

ltima reviso: 21/12/2010

11

11101 & (~01000) = 11101 & 10111 = 10101 (mais frente iremos estudar como criar um nmero com apenas um 1 na posio pretendida, sabendo apenas essa posio). Por exemplo, com cdigo agora (reformulao 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, no tem um equivalente lgico bvio. o mesmo que !=. O seu comportamento o seguinte: retorna 0 quando ambos os nmeros so iguais, e 1 quando so diferentes. Como o |, quando se procura um resultado lgico, equivalente usar o ^ e o !=. Vejamos ento 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 nmero 11x1, e quisermos alterar o estado do bit 1, sem conhecermos o seu valor, basta fazer a seguinte operao: 11x1 ^ 0010 Isto porque quando fazermos um xor com 0, o resultado sempre igual ao do outro nmero (1^ 0 = 1; 0^0 = 0), e quando fazemos um xor com 1, altera sempre (1^1 = 0; 1^0 = 1). (visto que o cdigo de exemplo seria semelhante aos anteriores, iremos passar frente desse passo). Agora s nos falta estudar os operadores de shift. Estes so muito teis porque nos permitem pr um valor em qualquer posio do nmero, ou seja, fazer shift para cima ou para baixo desse mesmo valor. Vamos utilizar o exemplo do ~ e do &. Sabendo apenas a posio em que queremos pr o 0, e o

ltima reviso: 21/12/2010

12

nmero que tem essa posio a 1, e as restantes a 0, j sabemos que operao utilizar. Mas ainda nos falta uma coisa: como chegamos ao nmero que tem a posio desejada a 1? Para isso usam-se os operadores de shift. Por exemplo, se quisermos colocar o 1 na posio 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 tcnica tambm 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. Tambm existe o operador de shift >>, que faz o contrrio 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 caractersticas das bases numricas, 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 programao necessrias para programar micro-controladores. Esperamos que o leitor esteja agora preparado para se aventurar no mundo da programao low-level dos mesmos!

ltima reviso: 21/12/2010

13

"Excerto do "Micro Tutorial AVR" de Njay (http://embeddeddreams.com/users/njay/Micro Tutorial AVR - Njay.pdf) com alteraes/adaptaes de Cynary (formatao e contedo)"

GPIO General Purpose Input/Output


O conceito de GPIO surge como uma forma de se tornar um chip mais flexvel, e deve ter surgido com os chips programveis. Este conceito consiste em podermos configurar um pino de um chip para poder ter uma de entre vrias funes, como por exemplo uma entrada ou uma sada. Isto tem vantagens bvias na flexibilidade de um chip, pois o fabricante d-vos um chip com N pinos em que vocs escolhem a funo de cada um conforme as necessidades da vossa aplicao. Se a funo de cada pino fosse sempre fixa, os chips seriam muito menos teis, e provavelmente teramos chips maiores, com muito mais pinos, numa tentativa de colmatar essa limitao, e no poderamos alterar a funo do pino durante o funcionamento do chip. A funo de base que podemos escolher para um GPIO se o respectivo o pino uma entrada ou sada, mas no a nica. Existem outras funes que podem ser escolhidas, embora nem todos os chips suportem todas. As funes possveis mais comuns so:

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 funo, a que s vezes tambm chamamos "tipo de pino".

Entrada Digital Normal


Esta talvez a configurao 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 prtica os valores 0 e 1 representam uma certa tenso que aplicada ao pino, resultando numa leitura de 0 ou 1 por parte do software. As tenses mais comuns so 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 tenso de alimentao do chip (refiro apenas "chip" porque no so apenas os micro-controladores que tm GPIOs; por exemplo as FPGA, outro tipo de chip programvel, tambm tm).

ltima reviso: 21/12/2010

14

Portanto, num sistema que funcione com uma tenso de alimentao 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 tambm serve como forma de desligar o pino do circuito. Neste caso no estamos interessados em ler valores. Ao configur-lo como entrada, ele no afecta electricamente (de um ponto de vista digital) o circuito exterior ao chip e portanto como se tivssemos cortado o pino do chip. Diz-se que o pino est em "alta impedancia" ("high-Z" em ingls, 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)


Ento se tivermos um pino configurado como input mas no lhe aplicarmos nenhuma tenso, que valor lemos no software?... A resposta : no podemos prever. Tomem bem ateno a isto, vou repetir: no podemos prever. Quando temos uma entrada que est no ar, no podemos prever que valor vamos ler; o valor pode estar estvel 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 Jpiter 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 situaes queremos ter sempre um valor estvel na entrada. Um caso tipico um interruptor (que pode ser um boto). Conectamos o interruptor entre a massa (o negativo da tenso de alimentao, "0V") e o pino. A, quando ligamos o interruptor (posio "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 dnos um valor aleatrio 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 resistncia entre o pino e a tenso de alimentao, e portanto, quando no 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 estvel entrada do pino e fica resolvido o problema. Quando o interruptor est ON, o prprio interruptor fora um 0 no pino, ligando-o massa. Ento mas... se o pull-up puxa o pino "para cima" e o interruptor (quando est ON) puxa para baixo, no h aqui uma espcie de conflito? No h, por uma simples razo: o valor da resistncia de pull-up alto (tipicamente mais de 100 KOhms) e portanto tem "pouca fora". Como o interruptor liga o pino directamente massa, esta que "ganha". Diz-se at que o pull-up um ltima reviso: 21/12/2010 15

"pull-up fraco", ou "weak pull-up" em ingls. Esta expresso pull-up ("puxa para cima") vem de estarmos a ligar tenso de alimentao positiva, que mais "alta" do que a massa, os 0V. Para este termo contribui ainda o facto de geralmente se desenhar a linha de alimentao positiva no topo dos esquemas, e a massa em baixo. Tambm podemos falar em pull-down ("puxa para baixo") quando nos referimos a ligar massa. Podemos criar pull-downs ligando resistncias massa, mas tipicamente os chips no suportam este tipo de pull, por razes que fogem ao mbito deste artigo que se quer simples.

Entrada controlada por um perifrico


Neste caso deixamos de ter controlo sobre o GPIO, passando esse controle para um perifrico interno do chip. Por exemplo a linha Rx (recepo) de uma porta srie (UART). Aqui o pino funciona como uma entrada mas quem a controla o perifrico.

Sada Digital Normal


Na lgica CMOS, com a qual trabalhamos mais hoje em dia, as sadas digitais so baseadas numa topologia designada "totem-pole". Este tipo de sada constitudo por 2 interruptores electrnicos (transstores) um em cima do outro, e controlados de forma a que: 1. quando queremos ter um "zero" sada, liga-se o transstor de baixo, que liga o pino massa

(0V); o transstor de cima mantm-se desligado 2. quando queremos ter um "um" sada, liga-se o transstor de cima, que liga o pino tenso

se alimentao (+V); o transstor de baixo mantm-se desligado

Na imagem acima podemos ver uma sada 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 no se devem ligar 2 (ou mais) sadas umas s outras. Se uma delas estiver com um "1" e a outra com um "0", estamos a criar um curto-circuito na alimentao, ligando +V massa. Numa das sadas est ligado o interruptor de cima e na outra est ltima reviso: 21/12/2010 16

ligado o de baixo. Mesmo que isto s acontea durante um perodo de tempo muito pequeno (milisegundos, microsegundos ou menos), vo passar correntes elevadas, fora das especificaes dos chips, e se acontecer regularmente, comea um processo de degradao que leva falha do chip em segundos, horas, semanas, meses ou anos. Uma sada totem-pole tem ainda um 3o estado: "no ar". outra forma de desligar um pino, mas que usada quando o pino sempre uma sada (no configurvel). No caso de um GPIO, este pode ser configurado como entrada ficando assim desligado do circuito exterior, como vimos atrs.

Sada em Colector Aberto (open colector)


Este tipo de sada surgiu para resolver o problema de no se poder ter 2 ou mais sadas ligadas. Por esta altura vocs podem estar a pensar "mas porque raio que haveramos de querer 2 sadas ligadas uma outra?!", e a resposta simples: pensem por exemplo no IC, em que temos linhas de dados bidireccionais. No IC h vrios chips ligados a um mesmo par de linhas e todos eles tm a possibilidade de transmitir dados nessas linhas. Da que, de alguma forma, h vrias sadas ligadas entre si.

A sada em colector aberto consiste num simples interruptor electrnico (transstor) capaz de ligar o pino massa. Quando o interruptor est ligado a sada 0, e quando est desligado a sada ... no sabemos. O pino fica "no ar" e portanto qualquer outro dispositivo exterior ao chip que esteja ligado ao pino pode l colocar a tenso que entender. Num bus IC o que se passa que existe uma resistncia externa que mantm as linhas com tenso positiva quando nenhum dispositivo est a transmitir; ou seja, temos um "pull-up fraco". A partir da, qualquer um dos dispositivos pode forar uma das linhas IC a ter 0V, se activar o interruptor electrnico na sua sada em colector aberto.

Sada controlada por um perifrico


semelhana do que se passa no caso da entrada controlado por um perifrico, neste caso deixamos de ter controlo sobre o GPIO, passando esse controle para um perifrico interno do chip. Por exemplo a linha Tx (transmisso) de uma porta srie (UART). Aqui o pino funciona como uma sada mas quem a controla o perifrico (no caso da UART, uma sada normal).

ltima reviso: 21/12/2010

17

GPIOs na Arquitectura AVR


Nos AVR os pinos esto agrupados em portos com no mximo 8 pinos cada. Os portos tm a designao 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. 2. 3. 4. Entrada normal Entrada com pull-up Sada normal Entrada ou sada controlada por um perifrico

Um pino entra no 4 modo quando o respectivo perifrico activado, pelo que no vamos debruar-nos aqui sobre isso. A cada porto est associado um conjunto de 3 registos que so 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. 2. 3. PINx - L o estado actual do pino DDRx - Data Direction Register (registo de direco dos dados) PORTx - Define o estado da sada 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 lgica 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 tm uma lgica similar a este diagrama. O registo PINx apresenta sempre o valor lgico ("0" ou "1") que estiver presente num pino independentemente da configurao. como se o registo estivesse ali a medir a tenso directamente

ltima reviso: 21/12/2010

18

no pino e a reportar o seu valor lgico ao software. O registo DDRx define, para cada pino do porto, se uma entrada ou uma sada. Aps o reset do chip, todos os pinos esto 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 sada temos que colocar a 1 o respectivo bit no registo DDRx. Se um pino estiver configurado como uma sada (se o respectivo bit no registo DDRx for 1), podemos ento definir o estado da sada 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, ento o pull-up est activado. Cada AVR tem um certo nmero de portos. Cada porto pode no ter pinos fsicos do chip associados a todos os seus bits. As datasheets da ATMEL (fabricante dos AVR) apresentam logo na 2 pgina o pinout (a atribuio 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 includa):

Isto diz-nos que este modelo de AVR tem 4 portos, A, B, C e D. Logo, para configurao dos pinos, este AVR tem os registos DDRA, PORTA, PINA, DDRB, PORTB, PINB, DDRC, PORTC, PINC, DDRD, PORTD, e PIND. Os nomes entre parntesis so os nomes associados aos perifricos ltima reviso: 21/12/2010 19

do AVR quando estes esto ligados; vamos esquec-los neste artigo.

Configurao dos Portos em Linguagem C


muito fcil aceder aos registos de configurao dos GPIOs (e no s) com este compilador: eles tm exactamente os nomes dos registos e usam-se como variveis. Assim, existe por exemplo a varivel DDRA e podemos escrever instrues como: DDRA = 0xff; // configurar todos os GPIOs do porto A como sadas Se quisermos configurar apenas alguns GPIOs, temos disponvel a macro _BV(index) que cria uma mscara de bits para um determinado bit do registo. Esta macro retorna um nmero 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 configurao. Para configurar apenas o GPIO PA3 como sada e todos os restantes como entradas, DDRA = _BV(PA3); // configurar apenas o GPIO PA3 (pino 17) como sada e para configurar vrios GPIOs como sadas, basta efectuar um OU bit-a-bit DDRA = _BV(PA3) | _BV(PA6); // configurar os GPIOs PA3 e PA6 (pinos 17 e 12) como sadas Depois bastaria colocar no registo PORTB, no respectivo bit (3), o valor que queremos que "aparea" 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 fcil configurar os GPIOs em C.

ltima reviso: 21/12/2010

20

Interrupes
O que uma interrupo?
Irei agora comear a falar de interrupes a partir do mais bsico o que uma interrupo? Uma interrupo 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 interrupo foi o toque com o pato que interrompeu o discurso.

Como funciona uma interrupo no AVR?


Nos AVRs, as interrupes tm vrias particularidades, pois necessrio activar as interrupes globais, as interrupes particulares e criar uma rotina para lidar com cada interrupo. Cada microcontrolador AVR tem um conjunto de registers cujos bits controlam vrios aspectos do seu funcionamento (por exemplo, no tutorial que coloquei no meu primeiro post, esto explicados os que controlam os pinos de INPUT/OUTPUT). O mesmo acontece com as interrupes. No caso do atmega328, o bit I do register SREG controla as interrupes a nvel global. Quando o seu valor 1, estas esto ligadas, e vice-versa. No entanto, h uma instruo mais simples para ligar as interrupes 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 funo includa no header <avr/interrupt.h>). Depois de ligadas as interrupes globais, ainda necessrio ligar interrupes individuais. Para isso, necessrio encontrar qual o bit que liga certa interrupo (encontra-se na datasheet facilmente na zona dos registers no captulo acerca da funcionalidade procurada). Depois de ligadas as interrupes globais e particulares, o processador procura por mudanas de estado em bits de certos registers (flags), definidos pelas interrupes individuais. Quando esses bits tornam-se em 1, a interrupo gerada (independentemente do que esteja a acontecer, o programa pra). Mas falta aqui uma coisa o que acontece quando essa interrupo ocorre? A Interrupt Service Routine (ISR) definida para aquela interrupo executada. No final disto tudo, a flag fica com o valor 0 novamente, e o programa continua a sua execuo a partir do ponto em que estava. Nota: Enquanto o processador est a executar a interrupo, as interrupes globais esto desligadas. Quando acaba-se de executar a interrupo, as interrupes globais so ligadas novamente.

ltima reviso: 21/12/2010

21

Como lidar com uma interrupo no AVR?


Agora que j sabemos como funciona uma interrupo, temos de aprender a programar de forma a lidar com as mesmas. Primeiro, iremos comear por definir o pseudo-cdigo: // Iniciar o programa // (cdigo que inicialize variveis, LEDs, etc. aqui) // Ligar interrupes particulares // Ligar interrupes globais // Definir ISR para lidar com as interrupes particulares ligadas. Para lidar com registos e interrupes, iremos precisar dos seguintes headers: <avr/io.h> e <avr/interrupt.h> (j podemos tambm adicionar a funo main(), e um loop eterno): // Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> int main(void) { // (cdigo que inicialize variveis, LEDs, etc. aqui) // Ligar interrupes particulares // Ligar interrupes globais for(;;); } // Definir ISR para lidar com as interrupes particulares ligadas. (como a ISR uma funo, definimos fora do main). Como expliquei anteriormente, ligam-se as interrupes globais atravs da funo sei() // Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> int main(void) { // (cdigo que inicialize variveis, LEDs, etc. aqui) // Ligar interrupes particulares sei(); for(;;); } // Definir ISR para lidar com as interrupes particulares ligadas. Neste tpico, vamos ignorar as interrupes individuais, pois ainda no falmos de nenhuma

ltima reviso: 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, no so? xD), com um argumento: o nome do vector da interrupo. O vector da interrupo basicamente o que identifica qual a interrupo com que estamos a lidar. Esta informao encontra-se na pgina 57 do datasheet que eu tenho (incio do captulo sobre interrupes/captulo 9), numa tabela, na coluna Source. Por exemplo, no tpico a seguir, vamos lidar com a interrupo que ocorre no pino digital 2. Este pino tem o nome de INT0. Ao olharmos para a tabela, vemos que a source da interrupo 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) { // (cdigo que inicialize variveis, LEDs, etc. aqui) // Ligar interrupes particulares sei(); for(;;); } ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupo } (para alguns, esta declarao da funo ISR pode ser confusa, pois no tem tipo. No entanto, lembrem-se que uma macro, e por isso ISR no realmente o que fica no cdigo final). Nota: Se notarem, alguns dos Vectores na datasheet tm espaos ou vrgulas no nome. Basta substituir esses por _. Por exemplo, para a interrupo 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 interrupo atravs do pino digital 2 (INT0)


Vamos agora fazer algo mais interessante, e codificar uma interrupo. Comecemos com o cdigo do tpico anterior:

// Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> ltima reviso: 21/12/2010 23

int main(void) { // (cdigo que inicialize variveis, LEDs, etc. aqui) // Ligar interrupes particulares sei(); for(;;); } ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupo } Vamos usar para este efeito o pino 2 (podia ser feito com o pino 3 tambm, com poucas diferenas). O objectivo deste cdigo vai ser mudar o estado de um LED quando se toca num boto ligado ao pino 2. O LED vai estar ligado ao pino digital 4 (PD4). Vamos comear por criar uma varivel global, com o estado do pino e inicializar esse pino como output (vai comear desligado) (para mais informaes sobre GPIOs, ler o tutorial que pus no primeiro post), e j vamos colocar o cdigo necessrio 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 interrupes particulares sei(); for(;;); } ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupo output = ~output; // Alterar o estado PORTD &= ~(1<<PD4); // Desligar o pino isto necessrio 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 interrupo particular para o INT0.

ltima reviso: 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 boto, o mais fcil 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 cdigo: // 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 interrupo no pino INT0 para quando este transita para HIGH // Ligar interrupes particulares sei(); for(;;); } ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupo output = ~output; // Alterar o estado PORTD &= ~(1<<PD4); // Desligar o pino isto necessrio 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 interrupo associada ao INT0. O bit que controla isto o INT0 no register EIMSK. Assim, s modificar o cdigo, e fica completo: // Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> char output = 0; // Estado do led. ltima reviso: 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 interrupo no pino INT0 para quando este transita para HIGH EIMSK |= (1<<INT0); // Ligar interrupes particulares sei(); for(;;); } ISR(INT0_vect) { // Definir o que fazer quando acontece esta interrupo output = ~output; // Alterar o estado PORTD &= ~(1<<PD4); // Desligar o pino isto necessrio 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 cdigo que, ao clicarmos num boto que liga o pino digital 2 e o plo positivo, faz toggle do pino digital 4 (nota: visto que no fizemos nada para tratar do bouncing, podem haver resultados inesperados. No entanto, como isto s para exemplo, no considermos muito importante). Para experimentarem, podem montar o seguinte circuito:

Cuidados a ter na utilizao de interrupes


Vou deixar aqui duas situaes a terem em ateno quando esto a lidar com interrupes: 1. O compilador optimiza bastante o cdigo. Isto quase sempre uma vantagem, no entanto, por vezes no . Utilizando o exemplo do tpico anterior, se tivssemos de aceder a varivel output ltima reviso: 21/12/2010 26

no cdigo do main(), no estaramos a aceder ao valor correcto da varivel. Isto acontece porque o compilador no considera que podemos aceder funo ISR s com aquele cdigo, logo apenas carrega a varivel da memria ram para os registers uma vez, e depois no actualiza o seu valor, que alterado na ISR. Para resolver isto, dizemos ao cdigo que a varivel output volatile, declarando-a assim: volatile char output; Isto indica ao compilador que a varivel 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. No pode, por exemplo, carregar um int da memria numa s instruo, pois estes tm 16 bits. Mas as interrupes podem ocorrer em qualquer parte do programa logo o que pensam que acontece se tirarmos a primeira metade de um inteiro da memria, e antes de tirarmos a segunda metade ocorrer uma interrupo que altere essa varivel? Resultados no previsveis obviamente Logo, o que podemos fazer para evitar isto? O que podemos fazer desligar interrupes nesses blocos de cdigo que no podem ser perturbados (nota: se estiverem a carregar um char no deve haver problemas, visto ter apenas 8 bits). Isto feito facilmente com a funo cli() (tambm no header <avr/interrupt.h>). Depois do cdigo efectuado, basta ligar novamente as interrupes com sei(). Por exemplo: //... int i,j; for(;;) { cli(); j = i; // o i alterado numa interrupo sei(); // } //... E com isto acabamos a base das interrupes. Decidi comear com estas, pois nos prximos tutoriais, explicarei as interrupes individuais de vrias funcionalidades.

ltima reviso: 21/12/2010

27

Timers
O que so e como funcionam timers?
Timers so, 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 varivel a cada pulso. Se usarmos uma fonte de pulsos com uma frequncia conhecida (por exemplo, o clock do AVR tem uma frequncia 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 trs timers: timer0, timer1 e timer2. Cada um difere em vrios aspectos, mas o mais significativo o nmero de bits: o timer0 e timer2 tm cada um 8 bits, e o timer1 16 bits. Os outros aspectos so os modos que suportam, portas que controlam, etc. No entanto, estes no afecta muito a sua funcionalidade, visto que para fazer uma mesma coisa em dois timers, s so necessrias algumas mudanas nos registers e pinos usados. Mas o nmero de bits usados afecta bastante a funcionalidade, pois limitam a resoluo 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, no podemos contar at 1s com nenhum, mas por exemplo, com um clock de 65kHz, conseguimos contar at 1s com o de 16 bits, e no com o de 8 bits. No entanto, quando atingem o seu limite, os timers no param de contar, apenas comeam novamente do zero (isto significa que possvel 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 tcnicas de como fazer isto sem necessitar de software). Os timers podem ser usados em diferentes modos. No AVR, existem trs 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 prximo tutorial Todos os timers oferecem estes trs modos. No entanto, os modos CTC e PWM podem ter certos pormenores na sua utilizao/configurao 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 reviso: 21/12/2010 28

como se fosse um), at atingir o seu mximo, e depois volta a 0. A frequncia com que incrementa esse valor depende do clock utilizado. Cada timer tem um conjunto de clocks disponveis. Neste tutorial vamos utilizar apenas o clock do sistema (clkI/O), e os seus prescalers (um prescaler corresponde a dividir a frequncia original por um certo valor. Os valores disponveis no AVR so: 1, 8, 64, 256 e 1024. A utilizao de um prescaler para o timer no 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 resoluo e modos. No entanto, a maior parte das coisas descritas aqui podem aplicar-se aos outros se tiverem o modo correspondente disponvel, e ajustando-se o cdigo menor resoluo que oferecem e aos seus registers.

Modos Normal e CTC


Antes de comear a explicar os modos, vou introduzir alguns conceitos: TOP, BOTTOM, MAX e overflow. MAX corresponde ao valor mximo que o timer aguenta. No caso do de 16 bits 65535. TOP corresponde ao valor mximo que o timer atinge. Isto depende do modo. BOTTOM o valor mnimo que o timer tem, e que sempre 0 (no entanto, podemos mudar isto artificialmente, atravs de software. No o mais recomendado, sendo sempre prefervel mudar o TOP, por ser mais eficiente). Overflow o nome que se d ao que acontece quando o timer chega a MAX: sobrecarrega o mximo suportado por 16 bits e volta a 0 Iremos comear 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 tambm 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 so controlados por um conjunto de registers. ltima reviso: 21/12/2010 29

Para poder explicar mais facilmente como usar um timer, vamos estabelecer um objectivo: criar uma funo que funcione como a funo _delay_ms(). Vamos comear com o pseudo-cdigo: // Iniciar o programa // Iniciar a funo new_delayms(x) // Iniciar o timer // Verificar o timer para ver se j se passaram x ms. // Terminar a funo J podemos inserir algumas coisas: a declarao da funo, o loop em que se vai verificar o valor do timer e os headers necessrios para aceder aos registers: <avr/io.h> (vamos esquecer a funo main neste caso): //Iniciar o programa #include <avr/io.h> void new_delayms(int x) {// Iniciar a funo new_delayms(x) //Iniciar o timer for(;;) { //Verificar o timer para ver se j se passaram x ms. } } // Terminar a funo Para comear, vamos compreender como se sabe quanto tempo passou, tendo em conta o valor do timer: Sabemos que o timer incrementa a sua varivel de acordo com uma certa frequncia, e que a frmula para calcular a frequncia : f = incrementos/s Neste caso, visto que queremos os milisegundos, podemos ajustar a frmula: f/1000 = incrementos/ms Nesta frmula, o nico valor que podemos conhecer do incio a frequncia (como vamos usar o clock do sistema, ser 16MHz), logo podemos j ajustar essa parte da frmula: 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 comearmos do 0 no timer. No entanto, muitos esto a pensar numa coisa, provavelmente: limites. Sabemos que o limite para 16 bits : 65535, logo, no mximo podemos medir: 65535 = 16000*ms ltima reviso: 21/12/2010 <=> ms = 65535/16000 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 resoluo: utilizar prescalers, que diminuem o clock, ou manipulao por software. A utilizao de prescalers apropriada para casos particulares. No entanto, a manipulao por software mais apropriada aqui, visto que nos permite calcular o tempo passado para qualquer valor possvel de int (para fazermos o mesmo com um timer de 16 bits, necessitaramos de um prescaler de 16k, o que no existe). Vamos ento fazer isto passo-a-passo: inicializar o timer. Os bits de configurao/inicializao do timer (mais respectivamente de seleco 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, no 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 comea sem nenhum clock seleccionado, logo no est a incrementar o register correspondente, e esse inicializado automaticamente a 0. Assim que seleccionamos um clock, o timer comea 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 funo new_delayms(x) TCCR1B |= (1<<CS10); //Iniciar o timer for(;;) { //Verificar o timer para ver se j se passaram x ms. } } // Terminar a funo O mtodo por software que vamos usar o seguinte: numa varivel guardamos o valor anterior do timer. Se o novo valor for menor, significa que houve um overflow, e incrementamos uma varivel que nos indica quantas vezes passaram 4ms (aviso: isto apenas se aplica para uma frequncia de 16MHz. Se querem manipular o vosso programa para se adaptar a outras frequncias, podem usar a macro F_CPU, que indica a frequncia, e utilizar as frmulas acima, para descobrir ltima reviso: 21/12/2010 31

quantos ms passam em cada overflow). Depois guardamos o novo valor na varivel e vemos quanto tempo se passou at ento (o valor da varivel dos 4ms+os milissegundos passados, cuja frmula : ms = valor/16000), e caso tenha passado o tempo desejado ou mais (ao executarmos as instrues pode passar mais tempo do que o desejado), paramos o ciclo, saindo assim da funo. Os registers onde o timer1 guarda o valor do timer so TCNT1H e TCNT1L (so dois por ter 16 bits). A datasheet descreve a ordem em que estes devem ser acedidos. No entanto em C no 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 funo 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 varivel 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 funo E a temos uma funo de delay que funciona com 16MHz. Esta a forma mais bsica de se usar timers, e atravs do cdigo usado, parecem-nos ineficientes e bsico. Usados desta forma, timers no so muito teis tornam-se realmente teis quando comeamos a explorar a sua utilizao com eventos e interrupes, que so os temas dos prximos tpicos.

Eventos relacionados com timers.


Os timers tm uma srie de eventos e interrupes associados aos mesmos Com estes que se tornam muito poderosos. Neste tpico, iremos usar principalmente o modo CTC, pois os exemplos utilizados so os que melhor demonstram a sua utilidade. Este tpico 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 possveis dependem do modo escolhido. Os possveis para os modos normal e CTC so equivalentes, logo sero os explorados neste tutorial. Para explorarmos o que podemos fazer com estes pinos, vamos primeiro estabelecer um ltima reviso: 21/12/2010 32

objectivo: piscar um LED (ligado 1s, desligado 1s, ligado 1s, desligado 1s, ), sem utilizar qualquer controlo por software/interrupes, apenas as configuraes dos pinos. O pino usado neste exemplo ser o pino OC1A (pino digital 9). Logo, o nosso pseudo-cdigo 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 distncia de tempo em que ocorre esse evento constante igual ao tempo que demora para incrementar a varivel 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 iteraes, MAX = 65535, nota), mas com um erro grande que aumenta em cada evento, logo no o ideal. As contas feitas so as seguintes: FREQ = F_CPU/prescaler F_CPU = 16000000 prescaler = 256

ltima reviso: 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 trs, onde explico este modo, iro 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 quisssemos gerar o evento no pino OC1B, teramos de usar ICR1 ou OCR1A como TOP, e colocar o mesmo valor no register OCR1B). Ento, 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 so configurados atravs 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 reviso: 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 tm: um programa que faz toggle do pino OC1A (pino digital 9) a cada 1s. Podem testar isto com um LED, como no esquema abaixo:

Tambm podemos forar 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 configurao, no gerando quaisquer interrupes associadas com uma igualdade ao register OCR1A nem fazendo reset ao timer. O tipo de eventos que estudmos primeiro, foram os eventos de output. No entanto, tambm 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 so 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. Ateno forma como isto se funciona: devem lembrar-se que o register ICR1 pode ser usado como valor de TOP no modo CTC (e PWM tambm, mas fica para outro tutorial). Quando isto acontece, estes eventos de input so desligados. Se se lembram do tutorial anterior, demonstrei como usar um interrupt associado com um pino digital para detectar um clique num boto. 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 reviso: 21/12/2010 35

controlo de bouncing automtico, ao testar o valor do pino 4 vezes, a ver se igual nessas 4 vezes (nota: esses quatro testes so independentes do prescaler do timer, visto que so feitos de acordo com o clock do sistema). No prximo tpico, em que falamos sobre interrupes e timers, demonstraremos uma forma de usar este filtro, em vez da forma bsica demonstrada no tutorial anterior. E estes so os principais eventos associados aos timers, relacionados com hardware. Para acabar, s faltam agora as interrupes.

Interrupes e timers.
Existem 4 interrupes 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 (ateno, que no modo CTC, o overflow ocorre apenas se o timer chegar ao valor MAX, e no ao top, logo pode nunca ocorrer. Esta interrupo pode ser usada para, por exemplo, verificar a ocorrncia de erros quando se muda o valor de TOP). Estas interrupes esto todas definidas no register TIMSK1. Os vectores associados s mesmas so: TIMER1_CAPT_vect TIMER1_COMPA_vect TIMER1_COMPB_vect TIMER1_OVF_vect O tutorial anterior mostrou como lidar com interrupes, por isso ser deixado imaginao do leitor o que fazer com estas. No entanto, a ttulo de exemplo, iremos cumprir o prometido no tpico anterior: codificar um programa com a mesma funo que o do tutorial anterior, s que com controlo de bouncing feito pelo hardware. Neste caso, no usamos nada relacionado directamente com o timer, excepto a interrupo de input. No entanto, isto, em conjuno com outros interrupts, pode ser usado para fazer um programa que registe a distncia, 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 critrio). Como no usamos a funcionalidade do timer, no 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-cdigo:

ltima reviso: 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 interrupo para o input. // Ligar as interrupes globais. // Loop eterno // Definir a ISR para o input: TIMER1_CAPT_vect // Fazer toggle do pino digital 4 Vamos comear pelo mais bsico: // 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 interrupo para o input. sei(); // Ligar as interrupes 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 explicaes anteriores, os bits usados para configurar o evento de input so 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 interrupo para o input. sei(); // Ligar as interrupes globais. for(;;); // Loop eterno ltima reviso: 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 interrupo particular para o input. Como disse antes, as interrupes dos timers so 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 interrupo para o input. sei(); // Ligar as interrupes 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 interrupes dos timers! O circuito que usa isto o seguinte:

ltima reviso: 21/12/2010

38

Timers Parte 2, Pulse Width Modulation


O que PWM?
Microcontroladores, por si s, so incapazes de gerar sinais analgicos (voltagens variveis), apenas podendo gerar dois sinais distintos: 0 e 1 (normalmente, 0V e 5V respectivamente). No entanto, muitas vezes estes sinais so necessrios para controlar vrios dispositivos: servos, colunas, Ento, como podemos criar esses sinais? A resposta simples: PWM. Pensem num carro a andar: se metade de um perodo de tempo andar a 5 km/h, e a outra metade estiver parado, qual a sua velocidade mdia ao longo desse perodo? 2,5 km/h. Passem esta analogia para electricidade: 5V metade do tempo e 0V a outra metade do 2,5V. Para enganarmos o circuito, de forma a pensar que mesmo 2,5V constantes, fazemos isto a uma grande frequncia (e se utilizarmos alguns componentes externos, at conseguimos estabilizar a corrente, mas isso no 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 mdia e vice-versa (assim, duty cycle de 100% = 5V, e de 0% = 0V). Ento, como podemos utilizar o PWM no AVR? Existem duas formas de gerar um sinal de PWM no AVR: por software (atravs de delays ou com o auxlio de interrupes) ou por hardware (que o que veremos nesta parte do tutorial). Um pormenor que convm 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, no 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 frequncia, 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 frequncia do PWM, devemos usar OCR1A como top, e caso queiramos uma frequncia fixa, podemos usar o ICR1.

Vrios Modos de PWM


Ao utilizarmos o AVR, temos vrias modos em termos de PWM: Fast PWM, Phase Correct ltima reviso: 21/12/2010 39

PWM e Phase and Frequency Correct PWM. Os modos Fast PWM e Phase/Phase and Frequency Correct PWM so os que diferem mais. Fast PWM funciona da seguinte forma: O timer conta at OCR1x, e nesse momento actualiza o pino OC1x (se o liga ou desliga depende da configurao dos bits COM1x0 e COM1x1). Depois continua a contar at TOP (que pode ter vrios valores: ICR1, OCR1A, 10 bits (1023), 9 bits (511) e 8 bits (255)), e nesse momento volta ao BOTTOM e actualiza o pino OC1x, alterando o seu estado. Phase Correct e Phase and Frequency correct PWM so muito semelhantes, alterando apenas o momento em que o register OCR1x actualizado com o novo buffer. Como o nome sugere, a vantagem do Phase and Frequency correct PWM ser mais apropriado para alterar a frequncia do PWM. De resto, estes modos so iguais (devido a isto, apenas discutirei o modo Phase and Frequency Correct PWM). Funcionam da seguinte forma: o timer conta at ao OCR1x, momento em que actualiza o pino OC1x (dependendo da configurao, ele liga-o ou desliga-o). Depois continua at chegar ao TOP, momento em que comea a contar para trs, at atingir novamente o register OCR1x, momento em que actualiza o pino OC1x, de acordo com a configurao. A vantagem destes modos que as ondas geradas tm sempre a mesma fase (o que necessrio para controlar servos, por exemplo), ou seja, as cristas (parte mais alta da onda) e vales (parte mais baixa da onda) correspondem, independentemente do duty cycle. A desvantagem que isto traz que a frequncia mais alta deste modo metade da possvel no modo Fast PWM.

Abaixo esto alguns grficos que mostram como funcionam estes modos. No modo Fast PWM, o pino ligado quando h um compare match, e desligado em bottom. No modo Phase and Frequency Correct PWM, o pino ligado quando se conta para cima, e desligado quando se conta para cima (sero estas as configuraes usadas neste tutorial):

ltima reviso: 21/12/2010

40

Vamos agora ver como podemos usar estes modos no AVR, atravs de exemplos:

Fast PWM
O modo Fast PWM o mais simples de todos, e muito fcil de configurar. Vamos primeiro definir um objectivo: Fazer variar a luminosidade de um LED, aumentando-a em cada 100 ms (0,1s ), at chegar ao mximo, e depois diminuindo-a, e repetimos este processo at eternidade. O pino que vamos usar para este programa o OC1A (pino digital 9/PB1). Para isto, basta-nos definir uma interrupo a cada 100ms que altere o valor do register OCR1A, para definir um maior/menor duty cycle. Para saber se estamos a subir ou a descer, podemos guardar um 1 ou -1 numa varivel, para depois multiplicar pela variao do OCR1A, e alteramos quando chegamos ao TOP/BOTTOM. Visto que impossvel gerar um interrupt a cada 100 ms num timer de 8 bits, e estamos a usar o timer1, podemos fazer isto de duas formas: ou configurar o perodo do PWM para 100ms, e assim aproveitar o timer1 para essas interrupes (o que no a melhor soluo visto que assim o LED piscaria 10 vezes por segundo, o que j se torna visvel ao olho humano), ou podemos usar uma interrupo no timer0/timer2 (iremos usar o 0) a cada 10ms (o que possvel com um prescaler de 1024, com um certo erro que iremos ignorar), e s fazer uma aco 10 vez que essa interrupo ocorre (iremos manter um contador para verificar isso). O top do timer0 pode ser calculado da seguinte forma (o prescaler usado 1024): 10ms = 0,01s TOP = 16000000*0,01/1024 156 Para calcular a variao necessria do OCR1A, iremos estabelecer um objectivo: o LED ir de desligado at luminosidade mxima em 2s. Para uma frequncia alta, iremos usar o Fast PWM com TOP de 8 bits (255). Assim, OCR1A tem de ir de 0 a 255 em 2s. Visto que actualizado a cada 0,1s, ele varia 2/0,1 = 20 vezes. Logo, a sua variao ser: 255/20 12 (isto tem um erro associado, mas iremos ignor-lo). Vamos ento comear com Pseudo-Cdigo:

//Iniciar o programa // Definir o pino digital 9 como OUTPUT ltima reviso: 21/12/2010 41

// Configurar o timer1 com modo Fast PWM // Configurar PWM para ligar o pino OC1A no compare match, e desligar no BOTTOM // Seleccionar clock no timer1 (sem prescaler, para mxima frequncia). // Configurar o timer0 com prescaler de 1024, TOP de 156, modo CTC // Ligar interrupes de compare match no timer0 // Ligar interrupes globais // Loop eterno // ISR de compare match no timer0 // incrementar contador // Se o contador for igual a 10 // Incrementar/decrementar OCR1A por um factor prestabelecido. // Caso tenhamos atingido o valor mximo de OCR1A/BOTTOM // Alterar a operao de incremento/decremento // Fazer reset ao contador.

Vamos j comear a preencher o que j sabemos (visto que a configurao do timer0 semelhante do timer1, iremos tambm fazer isso):

// Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> #define VARIACAO_OCR1A 12 int mult = -1; int count; int main(void) { DDRB |= (1<<PB1); // Definir o pino digital 9 como OUTPUT // Configurar o timer1 com modo Fast PWM // Configurar PWM para ligar o pino OC1A no compare match, e desligar no BOTTOM TCCR1B |= (1<<CS10); // Seleccionar clock no timer1 (sem ltima reviso: 21/12/2010 42

prescaler, para mxima frequncia). TCCR0A |= (1<<WGM01); TCCR0B |= (1<<CS02) | (1<<CS00); OCR0A = 156; // Configurar o timer0 com prescaler de 1024, TOP de 156, modo CTC TIMSK0 |= (1<<OCIE0A); // Ligar interrupes de compare match no timer0 sei(); // Ligar interrupes globais for(;;); // Loop eterno } ISR(TIMER0_COMPA_vect) { // ISR de compare match no timer0 ++count; // incrementar contador if(count == 10) { // Se o contador for igual a 10 OCR1A += mult*VARIACAO_OCR1A; // Incrementar/Decrementar OCR1A por um factor prestabelecido. if(OCR1A == 20*VARIACAO_OCR1A || OCR1A == 0) { // Caso tenhamos atingido o valor mximo de OCR1A/BOTTOM mult = -mult; // Alterar a operao de incremento/decremento } count = 0; // Fazer reset ao contador. } }

S nos falta mesmo configurar o PWM agora! Vamos ento ver a datasheet O que queremos o modo Fast PWM, com uma resoluo de 8 bits. Vemos l que conseguimos isso colocando os bits WGM12 e WGM10, dos registers TCCR1A e TCCR1B respectivamente, a 1:

// Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> #define VARIACAO_OCR1A 12 int mult = -1; ltima reviso: 21/12/2010 43

int count; int main(void) { DDRB |= (1<<PB1); // Definir o pino digital 9 como OUTPUT TCCR1A |= (1<<WGM10); TCCR1B |= (1<<WGM12); // Configurar o timer1 com modo Fast PWM // Configurar PWM para ligar o pino OC1A no compare match, e desligar no BOTTOM TCCR1B |= (1<<CS10); // Seleccionar clock no timer1 (sem prescaler, para mxima frequncia). TCCR0A |= (1<<WGM01); TCCR0B |= (1<<CS02) | (1<<CS00); OCR0A = 156; // Configurar o timer0 com prescaler de 1024, TOP de 156, modo CTC TIMSK0 |= (1<<OCIE0A); // Ligar interrupes de compare match no timer0 sei(); // Ligar interrupes globais for(;;); // Loop eterno } ISR(TIMER0_COMPA_vect) { // ISR de compare match no timer0 ++count; // incrementar contador if(count == 10) { // Se o contador for igual a 10 OCR1A += mult*VARIACAO_OCR1A; // Incrementar/Decrementar OCR1A por um factor prestabelecido. if(OCR1A == 20*VARIACAO_OCR1A || OCR1A == 0) { // Caso tenhamos atingido o valor mximo de OCR1A/BOTTOM mult = -mult; // Alterar a operao de incremento/decremento } count = 0; // Fazer reset ao contador. } }

Agora s nos falta configurar o comportamento do Fast PWM. Queremos que ele ligue o pino no compare match, e desligue no BOTTOM. Assim, quanto maior o OCR1A, menor o duty cycle. Logo, como queremos comear com o LED desligado, iniciamos o ltima reviso: 21/12/2010 44

OCR1A com o seu maior valor, e a varivel de incremento/decremento com o valor -1. Para definir o comportamento do PWM para o pin OC1A, temos de mexer nos bits COM1A0 e COM1A1 do register TCCR1A (colocar os dois a 1 para o que desejamos):

// Iniciar o programa #include <avr/io.h> #include <avr/interrupt.h> #define VARIACAO_OCR1A 12 int mult = -1; int count; int main(void) { DDRB |= (1<<PB1); // Definir o pino digital 9 como OUTPUT TCCR1A |= (1<<WGM10); TCCR1B |= (1<<WGM12); // Configurar o timer1 com modo Fast PWM OCR1A = 20*VARIACAO_OCR1A; TCCR1A |= (1<<COM1A0) | (1<<COM1A1); // Configurar PWM para ligar o pino OC1A no compare match, e desligar no BOTTOM TCCR1B |= (1<<CS10); // Seleccionar clock no timer1 (sem prescaler, para mxima frequncia). TCCR0A |= (1<<WGM01); TCCR0B |= (1<<CS02) | (1<<CS00); OCR0A = 156; // Configurar o timer0 com prescaler de 1024, TOP de 156, modo CTC TIMSK0 |= (1<<OCIE0A); // Ligar interrupes de compare match no timer0 sei(); // Ligar interrupes globais for(;;); // Loop eterno } ISR(TIMER0_COMPA_vect) { // ISR de compare match no timer0 ++count; // incrementar contador if(count == 10) { // Se o contador for igual a 10 OCR1A += mult*VARIACAO_OCR1A; // Incrementar/Decrementar OCR1A por um factor prestabelecido. ltima reviso: 21/12/2010 45

if(OCR1A == 20*VARIACAO_OCR1A || OCR1A == 0) { // Caso tenhamos atingido o valor mximo de OCR1A/BOTTOM mult = -mult; // Alterar a operao de incremento/decremento } count = 0; // Fazer reset ao contador. } }

E com isto, fizemos o nosso primeiro programa que usa PWM! Este programa pode ser usado com o circuito abaixo:

Phase and Frequency Correct PWM


O modo Phase and Frequency Correct PWM, como mencionei antes, til para controlar servos, devido a manter sempre a mesma fase. Por isso, iremos utiliz-lo para isso mesmo: controlar um servo. Primeiro, temos de compreender o que e como funciona um servo: um servo um conjunto de um motor e electrnica que abstraem muito do controlo de motores. Estes permitem-nos, atravs de um sinal, especificar para onde queremos que o motor se mova, em graus, ou no caso de servos de rotao contnua, controlar a sua direco e velocidade. Logo, esta electrnica abstrai muito daquilo que precisamos para controlar um motor, como pontes-H e leitura e interpretao do feedback do motor. Estes tm trs fios: Vcc, GND e controlo. O Vcc e GND devem ser ligados respectivamente aos terminais positivos e negativos da fonte de alimentao. O controlo ser ligado a um pino do AVR, ltima reviso: 21/12/2010 46

que neste caso ser o pino digital 9 (OC1A, pois temos um output de PWM nesse). Para controlarmos o servo, precisamos de compreender que tipo de sinais l. No geral, os servos usam um sinal de 50Hz, cujo perodo em HIGH determina a o ngulo ou direco e sentido para controlar o motor. O perodo em HIGH pode ser entre 1ms e 2ms, sendo 1ms completamente para a esquerda e 2ms completamente para a direita, e 1.5ms meio. Isto corresponder a respectivamente 0, 180 e 90 num servo com capacidade de rodar at 180 (isto no constante, e pode variar de servo para servo). Este sinal pode parecer confuso, mas a imagem abaixo pode ajudar a esclarecer (para um sinal de 1ms completamente para a esquerda):

Para exemplificar isto, vou definir um objectivo: a criao de uma funo que altera a posio do servo, tendo em conta que este se encontra ligado ao pino OC1A, e que o PWM j foi inicializado para uma frequncia fixa de 50Hz, no timer1 (neste caso, ir receber um valor entre 0 e 100, para definir a posio entre 1ms e 2ms).

// Iniciar o programa // Colocar o pino digital 9/OC1A como output. // Iniciar o timer1 no modo Phase and Frequency Correct PWM // Configurar o timer para ligar o pino OC1A na subida, e deslig-la na descida. // Seleccionar o clock do timer1. // Configurar para uma frequncia de 50Hz // Chamar a funo rodar, para ir todo para a esquerda (valor dado: 0). // Loop eterno.

ltima reviso: 21/12/2010

47

// Funo rodar(x); x valor entre 0 e 100 // Converter o valor entre 0 e 100 para um valor em ms // Converter o valor em ms para um valor de OCR1A, de acordo com o prescaler e clock.

Como devem ter reparado, este cdigo implica muita matemtica. Primeiro, temos de descobrir como fazer uma frequncia de 50Hz: O clock 16MHz, logo, a onda ter de ocorrer a cada 16M/50 = 320K ciclos para ter 50Hz. No entanto, isto no cabe num register de 16 bits, logo usaremos um prescaler. Neste caso 8 suficiente: 320K/8 = 40K No entanto, temos de ter um cuidado especial quando usamos Phase and Frequency Correct PWM: visto que ele primeiro conta para cima, e depois para baixo, a frequncia ser metade daquela obtida em Fast PWM (que nesse caso teria um TOP de 40K para 50Hz). Logo, o TOP ser 40K/2 = 20K, para termos a frequncia 50Hz. Como nunca mudamos a frequncia, iremos usar um TOP fixo, logo seguro usar o register ICR1. Para converter o valor entre 0 e 100, para um valor entre 1 e 2, basta-nos usar a seguinte frmula: (x+100)/100 No entanto, isto no suficiente para colocar no OCR1A. Visto que iremos usar a configurao em que o pino ligado na subida, e desligado na descida, temos de ver quanto tempo demora o timer a chegar ao TOP desde OCR1A, e a descer novamente, at chegar a OCR1A. Assim, para x ms, temos: 1 ms = 16000000/8/1000 = 2000 x ms = 2000*x. OCR1A = 20000-2000*x/2 Logo, podemos chegar frmula geral (x estando entre 0 e 100): OCR1A = 20000-2000*(x+100)/100/2 = 20000-10*(x+100)

Assim, j podemos criar a nossa funo rodar (e j agora, adicionamos aquilo que j sabemos fazer): ltima reviso: 21/12/2010 48

// Iniciar o programa #include <avr/io.h> void rodar(int); int main(void) { DDRB |= (1<<PB1); // Colocar o pino digital 9/OC1A como output. // Iniciar o timer1 no modo Phase and Frequency Correct PWM // Configurar o timer para ligar o pino OC1A na subida, e deslig-la na descida. TCCR1B |= (1<<CS11); // Seleccionar o clock do timer1. ICR1 = 20000; // Configurar para uma frequncia de 50Hz rodar(0); // Chamar a funo rodar, para ir todo para a esquerda (valor dado: 0). for(;;); // Loop eterno. } void rodar(int x) {// Funo rodar(x); x valor entre 0 e 100 // Converter o valor entre 0 e 100 para um valor em ms // Converter o valor em ms para um valor de OCR1A, de acordo com o prescaler e clock. OCR1A = 20000-10*(x+100); }

Agora vamos iniciar o timer1 no modo Phase and Frequency Correct PWM com o TOP em ICR1 (neste caso, bastava o modo Phase Correct PWM, mas so o dois semelhantes o suficiente para no fazer diferena em nada :P Caso queiram usar, basta ver na datasheet as diferenas), e segundo a datasheet, faz-se isso colocando o bit WGM13 a 1, no register TCCR1B.

// Iniciar o programa #include <avr/io.h> void rodar(int);

ltima reviso: 21/12/2010

49

int main(void) { DDRB |= (1<<PB1); // Colocar o pino digital 9/OC1A como output. TCCR1B |= (1<<WGM13); // Iniciar o timer1 no modo Phase and Frequency Correct PWM // Configurar o timer para ligar o pino OC1A na subida, e deslig-la na descida. TCCR1B |= (1<<CS11); // Seleccionar o clock do timer1. ICR1 = 20000; // Configurar para uma frequncia de 50Hz rodar(0); // Chamar a funo rodar, para ir todo para a esquerda (valor dado: 0). for(;;); // Loop eterno. } void rodar(int x) {// Funo rodar(x); x valor entre 0 e 100 // Converter o valor entre 0 e 100 para um valor em ms // Converter o valor em ms para um valor de OCR1A, de acordo com o prescaler e clock. OCR1A = 20000-10*(x+100); }

E para terminar, configurar o comportamento dos pinos. O que desejamos alcanado colocando ambos os bits COM1A0 e COM1A1 a 1, no register TCCR1A:

// Iniciar o programa #include <avr/io.h> void rodar(int); int main(void) { DDRB |= (1<<PB1); // Colocar o pino digital 9/OC1A como output. TCCR1B |= (1<<WGM13); // Iniciar o timer1 no modo Phase and Frequency Correct PWM TCCR1A |= (1<<COM1A0) | (1<<COM1A1); // Configurar o timer para ligar o pino OC1A na subida, e deslig-la na descida. TCCR1B |= (1<<CS11); // Seleccionar o clock do timer1. ICR1 = 20000; // Configurar para uma frequncia de 50Hz ltima reviso: 21/12/2010 50

rodar(0); // Chamar a funo rodar, para ir todo para a esquerda (valor dado: 0). for(;;); // Loop eterno. } void rodar(int x) {// Funo rodar(x); x valor entre 0 e 100 // Converter o valor entre 0 e 100 para um valor em ms // Converter o valor em ms para um valor de OCR1A, de acordo com o prescaler e clock. OCR1A = 20000-10*(x+100); }

E aqui temos, uma funo para controlar servos! Podem usar isto com o circuito abaixo:

E com isto fica concludo o tutorial sobre timers.

ltima reviso: 21/12/2010

51

Analog-to-Digital Converter
Formato Analgico e Digital
No mundo da electrnica, podemos identificar dois tipos de sinais: os digitais e os analgicos. Os sinais digitais distinguem-se por poderem ter apenas dois estados (normalmente designados por desligado/ligado, 0/1, e muitas vezes representados pelas diferenas de potencial 0V e 5V, respectivamente). Os sinais analgicos, no entanto, podem ter vrios estados. Por exemplo, todos os estados entre 0V e 5V (podem incluir 1V, 2V, ). Ao usar o PWM, j convertemos de uma certa forma, um sinal digital para um sinal analgico, j que a diferena de potencial resultante alterada pelo duty cycle do sinal em formato PWM. Neste tutorial, iremos estudar o ADC, que basicamente converte um sinal digital para um sinal analgico.

O que o ADC?
O ADC (Analog-to-Digital Converter) uma funcionalidade do micro-controlador AVR que permite converter uma certa diferena de potencial (entre 0 e um valor de referncia) num valor numrico, de acordo com a resoluo pretendida. O ADC do AVR tem uma resoluo mxima de 10 bits. Isto significa que nos pode dar valores entre 0 e 1023. Se utilizarmos como valor de referncia AVcc (o mais comum um pino do ADC que deve estar sempre ligado a uma diferena de potencial muito prxima do Vcc do microcontrolador, normalmente 5V), significa que temos uma preciso de 5/1023 0,0049, ou seja, de cerca de 4,9 mV. Por exemplo, se o ADC tiver como input uma diferena de potencial de 2,5V, ir retornar o valor decimal 510.

Como funciona o ADC no AVR?


No AVR, o ADC tem vrios pormenores com que nos temos de preocupar: um multiplexer/mux que nos permite escolher o input (permite escolher at 8 inputs, no entanto no atmega328 utilizado s temos 6), a possibilidade de utilizarmos auto-triggers (gatilhos automticos), a escolha da diferena de potencial de referncia e a frequncia com que se faz converses, utilizando um prescaler. O atmega328 d-nos 6 pinos que podemos utilizar como input para o ADC ADC0 (PC0) a ltima reviso: 21/12/2010 52

ADC5 (PC5). Para seleccionar um destes manipulamos os bits MUXn no register ADMUX (podemos tambm ler o input ADC8, que consiste num sensor de temperatura interno, que discutiremos noutro tpico) consultar datasheet para saber quais os valores a usar. Para escolher qual a diferena de potencial utilizada como referncia, temos primeiro de conhecer as opes: AREF (uma diferena de potencial externa, ligada ao pino com esse nome), AVcc (uma referncia interna, que tem de estar prxima do valor de Vcc, normalmente 5V) e uma referncia interna de 1.1V (apesar de parecer intil para a maior parte dos usos, utilizada para o sensor de temperatura interno que explicaremos mais frente). Podemos seleccionar qual das opes a usar, manipulando os bits REFSn no register ADMUX consultar datasheet para saber quais os valores a usar. Outro pormenor que temos de ter em ateno que no podemos usar como referncia as diferenas de potencial internas quando aplicamos uma diferena de potencial no pino AREF. Com o ADC, para fazer uma leitura, temos de dar uma ordem para o fazer. Isto consegue-se colocando o valor 1 no bit ADSC do register ADCSRA. No entanto, nem sempre queremos ter de fazer isso para saber o valor de um input, ainda por cima porque essa converso no ocorre em apenas um ciclo. Por isso -nos til definir gatilhos que iniciem a converso as opes que temos so flags de interrupes, ou seja, funciona como se fosse uma interrupo que ocorre separadamente do nosso cdigo. Ateno que, caso no lidemos com a interrupo utilizada como gatilho, nem que seja s definir uma ISR que no faa nada, de forma a desabilitar a flag de interrupo, a converso s ocorre na primeira vez que a flag mudar de 0 para 1 (j que nunca mudamos de 1 para 0 posteriormente) excepto quando utilizamos free-running mode. Estes gatilhos funcionam mesmo que no liguemos as interrupes globais e particulares. Neste documento apenas iremos utilizar o gatilho free-running mode, j que o funcionamento das interrupes j foi bem explicado, e estes gatilhos funcionam da mesma forma. Para escolher o gatilho, alteramos os bits ADTSn do register ADCSRB. O ADC tem mais um pormenor com que nos temos de preocupar: a frequncia do clock do ADC. Para seleccionarmos esta frequncia, temos de trabalhar com um prescaler, que funciona muito como o dos timers: F_CPU/PRESCALER. Segundo a datasheet, para ter a mxima resoluo, temos de uma frequncia entre 50kHz e 200kHz. Visto que F_CPU = 16000000, o nico valor disponvel que nos d uma frequncia adequada o 128, dando-nos uma frequncia de 125kHz (ateno: se no desejarem a frequncia mxima de 10bits, podem usar um valor mais alto para a frequncia, no entanto, a datasheet no diz mais nada alm disto), logo esse que utilizaremos neste documento. Agora que j tirmos estes pormenores do caminho, vamos criar um programa de exemplo que faz uma coisa muito simples: ler um valor do ADC e guard-lo numa varivel (no ir fazer nada com ele mais frente, iremos explorar como fazer um programa mais til, construindo um ltima reviso: 21/12/2010 53

pequeno sensor de distncia analgico) neste caso iremos usar o pino ADC0. Comecemos com o pseudo-cdigo: // Iniciar programa // Configurar o ADC: // Prescaler de 128 // Referncia AVcc // Configurar o multiplexer para usar o ADC0 // Ligar o ADC // Iniciar uma converso no ADC // Esperar at que a converso esteja terminada // Ler o valor do ADC e guard-lo numa varivel // Ciclo eterno Podemos j preencher algumas coisas: // Iniciar programa #include <avr/io.h> int main(void) { int adc_value; // Configurar o ADC: // Prescaler de 128 // Referncia AVcc // Configurar o multiplexer para usar o ADC0 // Ligar o ADC // Iniciar uma converso no ADC // Esperar at que a converso esteja terminada // Ler o valor do ADC e guard-lo numa varivel for(;;); // Ciclo eterno } Vamos comear por configurar o ADC. Visto que no iremos usar nenhum gatilho automtico, no nos temos de preocupar com isso. Para configurar o prescaler como 128, temos de colocar os bits ADPS2, ADPS1 e ADPS0 todos a 1 (register ADCSRA). Para seleccionar como diferena de potencial de referncia o AVcc temos de colocar o bit REFS0 a 1 (register ADMUX). Visto que para seleccionar o pino ADC0, s precisamos de colocar um 0 nos bits MUX3..0 (register ADMUX), e esse o seu valor por defeito, no temos de fazer nada quanto seleco do pino: // Iniciar programa #include <avr/io.h> int main(void) { int adc_value; // Configurar o ADC: ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // ltima reviso: 21/12/2010 54

Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia AVcc // Configurar o multiplexer para usar o ADC0 feito por defeito // Ligar o ADC // Iniciar uma converso no ADC // Esperar at que a converso esteja terminada // Ler o valor do ADC e guard-lo numa varivel for(;;); // Ciclo eterno } Para ligar o ADC, temos de colocar um 1 no bit ADEN do register ADCSRA: // Iniciar programa #include <avr/io.h> int main(void) { int adc_value; // Configurar o ADC: ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia AVcc // Configurar o multiplexer para usar o ADC0 feito por defeito ADCSRA |= (1<<ADEN); // Ligar o ADC // Iniciar uma converso no ADC // Esperar at que a converso esteja terminada // Ler o valor do ADC e guard-lo numa varivel for(;;); // Ciclo eterno } Para iniciar uma converso, temos de colocar um 1 no bit ADSC (register ADCSRA). Este bit lido como 1 enquanto se est a processar uma converso. Quando fica com o valor 0, significa que converso terminou. Assim, para esperarmos que a converso termine, basta testar o valor deste bit (esta no a melhor forma de fazer isto especialmente neste cdigo em que o melhor seria utilizar uma varivel global e uma interrupo, mas discutiremos isso mais para a frente): // Iniciar programa #include <avr/io.h> int main(void) { int adc_value; // Configurar o ADC: ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia AVcc // Configurar o multiplexer para usar o ADC0 feito por defeito ltima reviso: 21/12/2010 55

ADCSRA |= (1<<ADEN); // Ligar o ADC ADCSRA |= (1<<ADSC); // Iniciar uma converso no ADC while(ADCSRA&(1<<ADSC)); // Esperar at que a converso esteja terminada // Ler o valor do ADC e guard-lo numa varivel for(;;); // Ciclo eterno } Agora s nos falta ler o valor que resultou da converso. Este valor guardado em dois registers: ADCL e ADCH. Estes tm de ser lidos numa ordem especfica (primeiro o ADCL e depois o ADH). Mas como usamos C, isto abstrado pelo compilador, bastando-nos ler o register ADC: // Iniciar programa #include <avr/io.h> int main(void) { int adc_value; // Configurar o ADC: ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia AVcc // Configurar o multiplexer para usar o ADC0 feito por defeito ADCSRA |= (1<<ADEN); // Ligar o ADC ADCSRA |= (1<<ADSC); // Iniciar uma converso no ADC while(ADCSRA&(1<<ADSC)); // Esperar at que a converso esteja terminada adc_value = ADC; // Ler o valor do ADC e guard-lo numa varivel for(;;); // Ciclo eterno }

Como ligar o input ao AVR?


Apesar de parecer uma pergunta simples, foi uma das coisas com que tive mais dificuldade quando comecei a utilizar o ADC. A tcnica que utilizei, foi pensar no ADC como se fosse um voltmetro quando o utilizo, que mede a diferena de potencial entre o ponto onde ligamos o input e o GND (ateno: para se poder utilizar o ADC correctamente, o input analgico e o AVR tm de partilhar o GND). Isto significa que no podemos, por exemplo, medir a diferena de potencial de uma resistncia, se esta est mesmo no incio do circuito, e ligarmos o pino ao espao entre o terminal positivo e a resistncia. Se queremos medir a diferena de potencial da resistncia, ou colocamo-la no final do circuito ou medimos a diferena de potencial do resto do circuito (ligado o pino depois da resistncia), e depois s subtrair diferena de potencial total (o que no funcionar se no a medirmos/conhecermos).

ltima reviso: 21/12/2010

56

Utilizar o ADC construir um sensor de distncia


Neste tpico iremos tentar utilizar o ADC de uma forma mais prtica atravs da construo de um sensor de distncia analgico. Para este sensor, utilizaremos um LED emissor de infravermelhos, um receptor de infravermelhos (foto-transstor) e duas resistncias, uma de 220 ohms e uma de 10k ohms, resultando no seguinte circuito:

AIN0 significa o analog pin 0 do arduino, que corresponde ao pino ADC0 do AVR. No irei discutir como funciona este sensor de distncia, deixando-o como um desafio ao leitor (nota: isto s funciona para detectar distncias, quando a cor constante, e funciona para detectar cores/reflectividade quando a distncia constante). Vamos comear por definir um objectivo: queremos que o ADC mea constantemente o input do sensor de distncia, e guarde o valor que esse nos d numa varivel (mais frente iremos fazer alguma coisa com esse valor). Para isso, iremos configurar o ADC em free-running mode (este modo definido por um gatilho automtico em que o ADC est constantemente a realizar converses, e em que no nos precisamos de preocupar em colocar flags de interrupes a 0. No entanto, enquanto nos outros modos no precisamos de iniciar as converses, neste temos de ordenar ao ADC que faa uma converso primeiro), e utilizaremos interrupes associadas ao ADC para actualizar a varivel. Visto que no precisamos de uma resoluo de 10 bits, iremos apenas utilizar uma de 8 bits. Podemos realizar isto de duas formas: ou lemos o valor do ADC de 10 bits, e retiramos os dois bits da direita, ou podemos utilizar uma funo que o ADC nos d, que a de alinhar o resultado esquerda, guardando os 8 bits mais significativos no register ADCH, e os dois menos significativos no register ADCL, fazendo com que apenas necessitemos de ler o register ADCH. Para ligar esta funcionalidade, temos de colocar o valor 1 no bit ADLAR do register ADMUX (nota: visto que apenas necessitamos de 8 bits, poderamos utilizar uma frequncia maior que 125kHz, mudando para isso o prescaler, mas no o faremos neste caso). Visto que ligamos o sensor ao terminal positivo de 5V do arduino, no necessrio utilizar uma ltima reviso: 21/12/2010 57

referncia de diferena de potencial externa, utilizando-se assim o AVcc. Comecemos com o pseudo-cdigo: // Iniciar programa // Criar uma varivel com o valor do sensor de distncia do ADC // Configurar o ADC: // Resoluo de 8 bits // Prescaler de 128 // Referncia: AVcc // Gatilho automtico: Free-running mode // Input: ADC0 seleccionado por defeito // Ligar o ADC // Ligar a interrupo particular do ADC // Ligar as interrupes globais // Iniciar as converses no ADC // Ciclo eterno // Fazer algo com o valor da varivel do sensor de distncia // Definir uma interrupo que ocorre a cada ocorrncia de uma converso // Ler o valor do ADC, e guard-lo numa varivel J podemos preencher algumas coisas: // Iniciar programa #include <avr/io.h> #include <avr/interrupt.h> // Criar uma varivel com o valor do sensor de distncia do ADC volatile unsigned char adc_distance; // char porque s precisamos de 8 bits int main(void) { // Configurar o ADC: // Resoluo de 8 bits ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia: AVcc // Gatilho automtico: Free-running mode // Input: ADC0 seleccionado por defeito ADCSRA |= (1<<ADEN); // Ligar o ADC // Ligar a interrupo particular do ADC sei(); // Ligar as interrupes globais ADCSRA |= (1<<ADSC); // Iniciar as converses no ADC for(;;) {// Ciclo eterno // Fazer algo com o valor da varivel do sensor de distncia } ltima reviso: 21/12/2010 58

} ISR() {// Definir uma interrupo que ocorre a cada ocorrncia de uma converso adc_distance = ADCH; // Ler o valor do ADC, e guard-lo numa varivel } Para resoluo de 8 bits, colocamos o bit ADLAR a 1 no register ADMUX. A interrupo que utilizaremos a nica relacionada com o ADC a que ocorre quando se realiza uma converso. O seu vector : ADC. Para a ligar, colocamos o valor 1 no bit ADIE do register ADCSRA. Para seleccionar o gatilho automtico de free-running mode, necessitamos primeiro de ligar o modo de gatilho automtico (colocar o valor 1 no bit ADATE do register ADCSRA), e depois necessitamos de manipular os bits ADTSn no register ADCSRB (para Free-running mode no precisamos de de fazer nada j que para este modo s temos de colocar os seus valores a 0 o seu valor por defeito): // Iniciar programa #include <avr/io.h> #include <avr/interrupt.h> // Criar uma varivel com o valor do sensor de distncia do ADC volatile unsigned char adc_distance; // char porque s precisamos de 8 bits int main(void) { // Configurar o ADC: ADMUX |= (1<<ADLAR); // Resoluo de 8 bits ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia: AVcc ADCSRA |= (1<<ADATE); // Gatilho automtico: Freerunning mode // Input: ADC0 seleccionado por defeito ADCSRA |= (1<<ADEN); // Ligar o ADC ADCSRA |= (1<<ADIE); // Ligar a interrupo particular do ADC sei(); // Ligar as interrupes globais ADCSRA |= (1<<ADSC); // Iniciar as converses no ADC for(;;) {// Ciclo eterno // Fazer algo com o valor da varivel do sensor de distncia } } ISR(ADC_vect) {// Definir uma interrupo que ocorre a cada ocorrncia de uma converso ltima reviso: 21/12/2010 59

adc_distance = ADCH; // Ler o valor do ADC, e guard-lo numa varivel } E com isto temos um programa que utiliza o ADC em quase todo o seu potencial! O que foi explicado at agora j permite ao leitor utilizar o ADC. No entanto, os exemplos apresentados no tm nenhum efeito observvel, o que impede o leitor de experimentar. Por isso vamos preencher o interior do ciclo eterno para fazer alguma coisa com o valor do sensor de distncia. Basicamente, vamos fazer com que um LED esteja ligado quando o valor de input seja maior que 2,5V (~128, visto que usamos uma resoluo de 8 bits), e desligado quando menor. Para comear, vamos adicionar ao circuito anterior, estas ligaes:

E agora, o cdigo: // Iniciar programa #include <avr/io.h> #include <avr/interrupt.h> // Criar uma varivel com o valor do sensor de distncia do ADC volatile unsigned char adc_distance; // char porque s precisamos de 8 bits int main(void) { DDRB |= (1<<PB1); // Configurar o pino digital 9 como output // Configurar o ADC: ADMUX |= (1<<ADLAR); // Resoluo de 8 bits ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // Prescaler de 128 ADMUX |= (1<<REFS0); // Referncia: AVcc ADCSRA |= (1<<ADATE); // Gatilho automtico: Freerunning mode // Input: ADC0 seleccionado por defeito ADCSRA |= (1<<ADEN); // Ligar o ADC ADCSRA |= (1<<ADIE); // Ligar a interrupo particular do ADC sei(); // Ligar as interrupes globais ADCSRA |= (1<<ADSC); // Iniciar as converses no ADC for(;;) {// Ciclo eterno if(adc_distance > 128) // Testar o valor da ltima reviso: 21/12/2010 60

distncia, e ligar o pino caso seja maior que 128 e vice versa. PORTB |= (1<<PB1); else PORTB &= ~(1<<PB1); } } ISR(ADC_vect) {// Definir uma interrupo que ocorre a cada ocorrncia de uma converso adc_distance = ADCH; // Ler o valor do ADC, e guard-lo numa varivel } Nota: o ligar e desligar o pino podia ter sido feito na interrupo. Visto que j tnhamos destinado antes o ciclo para isso, e que as interrupes devem ter o mnimo de cdigo possvel, achmos melhor pr no ciclo.

ADC8 medir a temperatura interna


Como mencionmos anteriormente, existe um input extra para o ADC o ADC8. Este est ligado a um termmetro interno que nos permite ver a temperatura interna do micro-controlador. No iremos discutir este input em pormenor, visto que tem maior utilidade em casos de utilizaes em condies extremas, ou com grandes velocidades de relgio. Para utilizar este input, necessrio seleccionar a referncia interna de 1.1V. O valor medido tem uma sensibilidade de 1 mV/C, com um erro de +-10C. Valores tpicos, como descritos na datasheet so: 242mV para -45C, 314mV para 25C e 380mV para 85C, logo podemos estabelecer um valor de 290mV para 0C. No entanto, na datasheet tambm vemos que depende da calibrao do chip, podendo esta ser alterada na EEPROM. E assim terminamos o nosso tutorial sobre o ADC.

ltima reviso: 21/12/2010

61

"Excerto do "Introduo ao avr-gcc usando o AvrStudio Segunda Parte" de Senso (http://lusorobotica.com/index.php?topic=2838.15) com alteraes/adaptaes de Cynary (formatao e contedo)"

Comunicao Serial no AVR


Neste tutorial iremos utilizar um esqueleto bsico para iniciar todos os programas, que tem os includes e a declarao do main: #define F_CPU 16000000UL //permite usar os delays calibrados #include <avr/io.h> //definies gerais de pinos e registos #include <util/delay.h> int main(void){ // inicio da funo main // O vosso programa fica entre as chavetas return 0; // toda a funo no void tem de ter um return } Sempre que quiserem programar podem usar este esqueleto que so sempre uns caracteres a menos que tm de escrever.

Como funciona a comunicao Serial?


Antes de aprendermos a usar a comunicao serial no AVR, convm compreendermos como funciona para sermos capazes de entender o que todas as opes que nos esto disponveis na datasheet significam. A comunicao serial consiste em enviar bits sequencialmente. Esta utilizada para permitir comunicao entre dispositivos (um perifrico, como um modem ou micro-controlador, e um PC, dois PCs, ou at dois perifricos). Para que os dispositivos se possam entender, foram definidos vrios standards para a comunicao serial. No entanto, estes standards tambm exigem que os dispositivos estejam pr-programados para certos parmetros. A comunicao serial tem vrias caractersticas: bits de paridade, stop bits, start bit, e sincronismo/assincronismo. Dados enviados por comunicao serial tm o seguinte formato: Start Bit Dados bit de paridade Stop bit(s). O start bit indica o incio da comunicao. Enquanto o dispositivo no est a receber/enviar dados a linha de comunicao tem sempre o valor 1 (o que define os valores 0/1 depende do hardware o AVR usa os valores 0V/Vcc (normalmente 5V), respectivamente. No entanto, o standard RS232 define os valores 3V/-3V, respectivamente). O start bit tem sempre o valor 0, para sinalizar que estamos a comear uma transmisso. Os dados podem ter entre 7 e 9 bits no caso do AVR. Isto um dos pormenores que tem de ser ltima reviso: 21/12/2010 62

pr-programado nos dispositivos que esto a comunicar. Neste documento, iremos utilizar sempre 8 bits. O bit de paridade utilizado para detectar erros. Normalmente no se usa paridade para isso, pois muito ineficiente. No caso do bit de paridade estar configurado para odd, utilizado para termos um nmero mpar de 1s. Por exemplo, no caso de enviarmos os bits: 1111000, o bit de paridade seria 1, para termos 5 bits. No caso de estar configurado para even, utilizado para termos um nmero par de 1s. No entanto, visto que podemos ter erros no prprio bit de paridade ou em mais do que um bit, este mtodo muito ineficiente e no utilizaremos (no iremos abordar a deteco de erros na comunicao serial neste documento). O stop bit tem sempre o valor de 1, e indica o fim da transmisso. Um ou dois podem ser usados, mas no faz diferena na realidade, portanto iremos sempre configurar o AVR para usar um. A comunicao serial pode ser sncrona ou assncrona. Na comunicao assncrona, os dispositivos so pr-programados com uma velocidade de transmisso (denominada de baud rate), de forma a saberem a distncia entre os bits. No caso da comunicao sncrona, uma linha extra utilizada para indicar um clock comum a ambos os dispositivos. Basicamente, este clock indica aos dispositivos quando que um novo bit transferido, e tambm inicia e termina a comunicao. A comunicao sncrona d-nos a possibilidade de maiores velocidades de transferncia, e tambm retira a necessidade de se utilizar um start e stop bit. No entanto, tem a desvantagem de ser necessrio um fio extra para indicar o clock. Nesta primeira parte acerca de comunicao serial, iremos apenas abordar comunicao assncrona. Quando falarmos de IC, um tipo especial de protocolo de comunicao serial, iremos utilizar sincronidade. Para possibilitar a comunicao serial, necessitamos de trs fios: transferncia, recepo e ground. O fio de ground utilizado de forma a que os GND de ambos os dispositivos seja igual (de forma a que 5V num, seja 5V no outro). Os outros dois fios tm de se ligar de uma forma cruzada o fio ligado transmisso de um dispositivo (pino TXD/PD1 no AVR/pino digital 1 no arduino) tem de estar ligado recepo do outro dispositivo (pino RXD/PD0 no AVR/pino digital 0 no arduino). Agora que j sabemos como funciona a comunicao serial, vamos aprender a programar o AVR para us-la!

O que a USART?
A USART um mdulo de hardware que est dentro dos nossos atmegas, que permite ao nosso chip comunicar com outros dispositivos usando um protocolo serial, isto quer dizer que com apenas dois fios podemos enviar e receber dados. Um dos maiores usos da USART a comunicao serial com o nosso computador, e para isso temos de configurar a USART para ela fazer exactamente aquilo que queremos.

ltima reviso: 21/12/2010

63

Inicializando a USART do AVR


Falando agora especificamente de como activar e usar a nossa USART para falar com o computador, neste tutorial em vez de deixar um monte de linhas de cdigo perdidas no meio no nosso main vou antes criar algumas funes, pois assim podem copiar as funes e usar noutros programas, at porque fica tudo mais limpinho e separado. Em pseudo-cdigo eis o que temos de fazer: // // // // Iniciar e configurar a usart Criar uma funo para enviar um byte/caracter Criar uma funo para receber um byte/caracter Fazer um simples eco na funo main

Como no fao ideia de como a USAR funciona o que devo fazer pegar no datasheet e comear a ler, e como as pessoas na atmel at fizeram um bom trabalho a fazer este datasheet est tudo muito bem organizado com um ndice e marcadores e facilmente descobrimos que a seco sobre a USART comea na pgina 177- Seco 19, e temos at cdigo em C e assembly para configurar a USART, quer ento dizer que o nosso trabalho est facilitado, e pouco mais temos que fazer que ler e passar para o nosso programa o cdigo dado. Vamos comear pela inicializao da nossa USART, tal como est no datasheet: void USART_init(void){ UBRR0H = (uint8_t)(BAUD_PRESCALLER>>8); UBRR0L = (uint8_t)(BAUD_PRESCALLER); UCSR0B = (1<<RXEN0)|(1<<TXEN0); UCSR0C = (3<<UCSZ00); } UBRR0H e UBRR0L so os registos onde colocamos um valor que depende do baud-rate e da frequncia do oscilador que o nosso chip tem, temos duas opes para determinar este valor, ou vemos a tabela que est no datasheet ou usamos uma pequena frmula que juntamos ao cabealho do nosso programa onde esto os outros includes e o compilador determina o valor BAUD_PRESCALLER baseado no baudrate que queremos e no valor de F_CPU, sendo esta frmula a seguinte: #define BAUD 9600 //o baudrate que queremos usar #define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) 1) // a formula que faz as contas para determinar o valor a colocar nos dois registos ltima reviso: 21/12/2010 64

Isto no magia nem nada que se parea, no datasheet so dadas duas frmulas, se juntarmos as duas esta a formula com que ficamos. UCSR0B o registo que nos permite activar os canais de recepo e transmisso de dados da USART assim como activar interrupts, mas isso no nos interessa por agora. E em UCSR0C definimos que queremos 8 bits de dados, sem paridade e um stop bit, em vez de (3<<UCSZ00) podemos fazer ((1<<UCSZ00)|(1<<UCSZ01)) o resultado precisamente o mesmo. Agora para inicializar-mos a nossa USART basta chamar a funo USART_init no nosso main e temos a USART pronta a usar, mas ainda no somos capaz nem de enviar nem de receber dados.

Enviando e Recebendo Dados atravs da USART


Mais uma vez a datasheet tem uma soluo funcional, que a seguinte: void USART_send( unsigned char data){ while(!(UCSR0A & (1<<UDRE0))); UDR0 = data; } A primeira de linha de cdigo pode parecer estranha, mas tudo tem a sua razo e a razo desta linha que o atmega tem um buffer de 3 bytes em hardware e esta linha verifica se ainda existe espao no buffer para colocar mais dados, se no espera que haja espao, se sim, coloca os dados no buffer coisa que tratada na segunda linha da funo. E com apenas duas linhas estamos prontos a enviar dados por serial! Agora falta-nos apenas a funo para receber dados, sendo esta mais uma funo de duas linhas: unsigned char USART_receive(void){ while(!(UCSR0A & (1<<RXC0))); return UDR0; } Na primeira linha, usamos o while para esperar que existam dados recebidos no registo de recepo, quando esses dados chegam ao registo, simplesmente devolvemos os dados e temos a nossa USART a ler dados por serial. Agora como extra, vou mostrar uma pequena funo que permite enviar strings, pois muitas vezes queremos enviar mais que apenas um byte de informao de cada vez. A funo para enviar strings tira partido do facto de em C uma string ser terminada por um carcter nulo(/null) e que feita de muitos caracteres individuais. Assim com um pequeno loop que executado enquanto os dados a enviar no so nulos enviamos um carcter de cada vez. ltima reviso: 21/12/2010 65

void USART_putstring(char* StringPtr){ while(*StringPtr != 0x00){ // Aqui fazemos a verificao de que no chegamos ao fim da string, verificando para isso se o carcter um null USART_send(*StringPtr); // Aqui usamos a nossa funo de enviar um caracter para enviar um dos caracteres da string StringPtr++; } // Aumentamos o indice do array de dados que contem a string } O char* no inicio da funo pode parecer estranho e chama-se um ponteiro, que algo bastante til em C, mas por agora vamos simplificar as coisas, e imaginar as strings como arrays de caracteres, que isso mesmo que elas so em C e que o ponteiro no mais que o inicio desse mesmo array. Nota (Cynary): Esta funo no envia o carcter de terminao de string (NULL), logo, caso o leitor queira escrever um programa no lado do cliente que processe strings enviadas por esta funo, deve ter isto em conta, e, ou alter-la, de forma a enviar o NULL, ou enviar pela linha serial um nmero que indique o nmero de caracteres na string, antes de enviar a prpria string. Agora, pegamos no nosso programa inicial em branco e juntamos tudo, ficando assim: #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #define BAUDRATE 9600 #define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) - 1) int main(void){ return 0; } void USART_init(void){ UBRR0H UBRR0L UCSR0B UCSR0C } = = = = (uint8_t)(BAUD_PRESCALLER>>8); (uint8_t)(BAUD_PRESCALLER); (1<<RXEN0)|(1<<TXEN0); (3<<UCSZ00);

unsigned char USART_receive(void){ while(!(UCSR0A & (1<<RXC0))); ltima reviso: 21/12/2010 66

return UDR0; } void USART_send( unsigned char data){ while(!(UCSR0A & (1<<UDRE0))); UDR0 = data; } void USART_putstring(char* StringPtr){ while(*StringPtr != 0x00){ USART_send(*StringPtr); StringPtr++;} } Se tentar-mos usar as nossas funes o compilador vai dizer que elas no esto definidas e ns ficamos a olhar para ele com cara espantada, porque as nossas funes esto mesmo ali, por baixo do main, e precisamente esse o problema, no arduino podemos declarar funes onde bem nos apetecer, e em C tambem, mas temos que declarar as funes, ou seja a primeira linha da funo que tem o nome dela e que tipo de dados o seu retorno e quais os seus argumentos tm de estar antes do main, para o compilador saber que as funes existem, ficando assim o nosso cdigo com as declaraes das funes antes do main: #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #define BAUDRATE 9600 #define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) - 1) //declarao das nossas funes void USART_init(void); unsigned char USART_receive(void); void USART_send( unsigned char data); void USART_putstring(char* StringPtr); int main(void){ return 0; } void USART_init(void){ UBRR0H = (uint8_t)(BAUD_PRESCALLER>>8); ltima reviso: 21/12/2010 67

UBRR0L = (uint8_t)(BAUD_PRESCALLER); UCSR0B = (1<<RXEN0)|(1<<TXEN0); UCSR0C = (3<<UCSZ00); } unsigned char USART_receive(void){ while(!(UCSR0A & (1<<RXC0))); return UDR0; } void USART_send( unsigned char data){ while(!(UCSR0A & (1<<UDRE0))); UDR0 = data; } void USART_putstring(char* StringPtr){ while(*StringPtr != 0x00){ USART_send(*StringPtr); StringPtr++;} } Nota (Cynary): Havia outra forma de ultrapassar o erro do compilador acerca das funes no estarem definidas, que era declar-las noutra ordem (as funes de que o main depende, devem estar acima deste, e assim progressivamente). No entanto, para evitar quaisquer confuses, e at facilitar a programao (visto que as declaraes das funes esto todas no topo, e assim no temos de as procurar no cdigo), a melhor forma de o fazer esta.

Exemplo de utilizao do USART


Agora que sabemos usar a comunicao serial do AVR, vamos fazer alguma coisa com ela! Vamos usar as nossas funes para escrever a frase "Ol mundo!!!", mas falta-nos umas coisa, no temos um terminal serial, ou seja um programa que receba os dados da porta serial e nos mostre no ecr do computador o que recebeu. No meu caso, recomendo usar este terminal: http://www.smileymicros.com/download/term20040714.zip?&MMN_position=42:42 Nota (Cynary): O IDE do arduino tambm inclui um terminal serial que funciona perfeitamente bem. Por isso, caso o leitor no queira ter de fazer download e instalar um novo programa, ou no use windows (apesar de parecer que este terminal funciona bem com o wine), pode sempre utilizar o terminal do IDE do arduino.

ltima reviso: 21/12/2010

68

Foi feito por um menbro do AvrFreaks e acho-o simples de usar, tambm vai do gosto e existem milhares de terminais pela internet fora, escolham o que mais gostarem. Neste caso basta fazer o download e executar o ficheiro, podem j fazer isso e deixar o terminal aberto que vamos usa-lo mais tarde. Vamos l pegar ento no nosso programa e complet-lo para o nosso "Ol mundo": #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #define BAUDRATE 9600 #define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) - 1) //declarao das nossas funes void USART_init(void); unsigned char USART_receive(void); void USART_send( unsigned char data); void USART_putstring(char* StringPtr); char String[]="Ol mundo!!!"; //String[] que dizer que um array, mas ao colocar-mos o texto entre "" indicamos ao compilador que uma string e ele coloca automticamente o terminador null e temos assim uma string de texto usavel int main(void){ USART_init(); //Inicializar a usart

while(1){ //Loop infinito USART_putstring(String); //Passamos a nossa string funo que a escreve via serial _delay_ms(5000); //E a cada 5s re-enviamos o texto } return 0; } void USART_init(void){ UBRR0H UBRR0L UCSR0B UCSR0C } = = = = (uint8_t)(BAUD_PRESCALLER>>8); (uint8_t)(BAUD_PRESCALLER); (1<<RXEN0)|(1<<TXEN0); (3<<UCSZ00);

unsigned char USART_receive(void){ while(!(UCSR0A & (1<<RXC0))); return UDR0; } ltima reviso: 21/12/2010 69

void USART_send( unsigned char data){ while(!(UCSR0A & (1<<UDRE0))); UDR0 = data; } void USART_putstring(char* StringPtr){ while(*StringPtr != 0x00){ USART_send(*StringPtr); StringPtr++;} } E to simples como isto temos o nosso atmega a enviar dados. Agora, para testar, enviamos o nosso programa para o AVR e vamos agora abrir o nosso Terminal, e tal como com o avrdude tm de ter ateno porta com que o vosso arduino usa, assim como ao baudrate escolhido, no caso deste exemplo 9600 e a forma como a USART est configurada, no nosso caso, 8 bits de dados, 1 bit de stop e sem bit de paridade, nesta imagem mostro como configurar o terminal para receber dados do arduino:

Agora basta carregar em "Connect" e carregar no boto de reset do arduino para sincronizar o programa com arduino e devero ver algo do gnero:

ltima reviso: 21/12/2010

70

E est a comunicao serial a funcionar! Agora deixo um desafio em aberto, usando o outro tutorial e este, desafio-vos a criarem um programa que acende o led do arduino quando recebe o caracter "a" e que o apague quando receber outro carcter qualquer, algo bastante simples de se fazer, no precisam de nenhum hardware extra para alm do arduino e assim aprendem como controlar algo usando dados via serial, para enviar um carcter usando este terminal basta escrever o carcter na caixa marcada a verde da imagem de cima e carregar no ENTER. Boa programao!!!

ltima reviso: 21/12/2010

71

Comunicao por IC
O Protocolo IC
Antes de comearmos a aprender como comunicar por IC no AVR, temos de compreender exactamente como este protocolo funciona. O protocolo IC (Inter-Integrated Circuit) muito til, pois permite que, em teoria, at 127 aparelhos comuniquem entre si, usando apenas dois fios (na prtica, este limite pode no ser muito real, pois depende das caractersticas elctricas do sistema, e tambm porque alguns endereos podem estar reservados limitando o nmero de dispositivos ainda mais). Isto possvel, pois os aparelhos comunicam atravs de endereos, num sistema mestre/escravo. Comeando pelas caractersticas fsicas do protocolo, usam-se duas linhas: a SCL (clock) e a SDA (dados) onde ligamos estas linhas num aparelho depende da sua construo. No caso do AVR, ligamos, respectivamente nos pinos PC5 (analog in 5) e PC4 (analog in 4). Devido a haver apenas uma linha de dados, a comunicao usando o protocolo IC designada como sendo halfduplex, visto que s podemos estar a enviar ou receber dados num certo ponto no tempo, e no os dois ao mesmo tempo. A linha SCL utilizada para sincronizar os diferentes aparelhos. Funciona da seguinte forma: quando est em low, o sinal da linha SDA pode ser alterado, e quando est em high, o sinal da linha SDA no pode ser alterado, logo est pronto a ser lido (logo, uma transio de low para high sinaliza um novo bit na linha SDA). Normalmente apenas um aparelho controla esta linha, mas os outros aparelhos, caso no sejam rpidos o suficiente para utilizarem a frequncia desse aparelho podem controlar a linha, deixando-a em low pelo tempo que quiserem. A linha SDA contm a informao, que lida de acordo com o estado da linha SCL, como explicado no pargrafo anterior. No estado high, esta linha tem um bit 1, e no estado low, esta linha contm um bit 0. Ambas estas linhas tm uma regra especial: os aparelhos que as usam no as podem pr no estado high, apenas em low. Por isso, para se ter um estado high, colocam-se duas resistncias pullup entre o terminal positivo e a linha (uma resistncia para cada linha suficiente). Assim, quando os aparelhos largam a linha, esta est em high, e so responsveis por a pr em low. Isto muito til para, por exemplo, aparelhos que funcionem a 5V poderem comunicar com aparelhos que funcionam a 3.3V 3.3V normalmente aceite como um estado high vlido, e como os outros aparelhos no suportam uma corrente de 5V, isto impede que tenham problemas, sem afectar a comunicao. A regra descrita acima sobre a alterao do estado da linha SDA de acordo com a SCL tem duas excepes: bits de incio e de fim de comunicao. Nem sempre existe um aparelho a usar as linhas ltima reviso: 21/12/2010 72

do IC, por isso necessrio conseguir-se distinguir quando que esta est a ser usada ou no. Quando no est a ser usada, ambas as linhas esto no estado high. Para sinalizar o incio de comunicao, a linha SDA vai para um estado low, enquanto a linha SCL est em high. Para sinalizar o fim da comunicao, a linha SDA vai para um estado high enquanto SCL est em high. Agora que compreendemos como funciona o protocolo a um nvel de hardware, vamos aprender como funciona a um nvel de software. J comemos a falar disso ao mencionarmos a existncia de bits de incio e de fim (neste documento, iremos referir-nos a eles como start bits e stop bits, respectivamente, pois essa a conveno). O protocolo IC muito simples de compreender e utilizar. Existem duas categorias de aparelhos os master e os slave. Os master que tm controlo sobre a linha. So estes que chamam os outros aparelhos, atravs do seu endereo nico, e enviam ou recebem dados dos mesmos. Em qualquer altura, s podem haver dois dispositivos a usar os fios para comunicao, e um desses tem de ser um master. Existe um endereo especial, chamado de general call, que permite ao master falar com todos os slaves. til para, por exemplo, configurar o endereo de um aparelho com um endereo desconhecido, ou escrever um valor para vrios aparelhos diferentes ao mesmo tempo. Este endereo o 0. No entanto, nem todos os aparelhos respondero a este endereo, visto que depende da sua configurao. Podem haver mais do que um master. Para evitar problemas, quando dois master tentam controlar os fios, ocorre um processo denominado arbitration, em que os masters lutam pelo controlo. Aps a arbitration, o aparelho que ganhar o master, e todos os outros funcionam como slaves. Neste documento iremos apenas cobrir o uso de um master. Para mais informaes sobre utilizao de vrios masters, pode-se consultar a datasheet do AVR. O IC define tambm o formato da mensagem na linha: a mensagem comea com um start bit, seguido do endereo do slave e um bit read/write que indica se o master quer ler (bit=1) ou escrever (bit=0) dados para o slave. Isto seguido de um sinal ACK/NACK do slave, a dizer que recebeu a mensagem, e se pode ou no continuar a comunicao (um ACK acknowledge tem o valor 0, e significa que podemos continuar a comunicao; um NACK o contrrio, tendo o valor 1, e significando que a comunicao deve parar, e que o master deve enviar um stop bit). Para cada byte enviado, o aparelho receptor deve enviar um ACK/NACK (visto que um endereo tem 7 bits, ao adicionarmos o bit de leitura/escrita, envimos 8 bits um byte). De acordo com o valor do bit read/write, o master transforma-se num receptor ou num transmissor de dados. Aps esta primeira parte da mensagem, os aparelhos continuam a comunicar entre si atravs de bytes e ACK/NACK. O master pode ainda realizar um repeated start, que basicamente consiste em enviar um novo bit de start, e um novo endereo, sem ter de perder controlo da linha, devido a no ltima reviso: 21/12/2010 73

enviar o bit de stop. Isto til se o master quer enderear outro aparelho, ou mudar o valor do bit read/write (por exemplo, comea por escrever para um aparelho, enviando um comando, e depois l os resultados desse comando). Para finalizar a comunicao, o master envia um bit de stop. Alguns aparelhos no mencionam que trabalham em IC, mas sim que usam SMBus, ou TWI (Two-Wire Interface o caso do AVR). No entanto, estas designaes, apesar de terem algumas diferenas, so semelhantes em funcionalidade ao IC, por isso podem, na prtica, ser interpretadas como sendo este protocolo. Agora que j compreendemos como funciona o IC, vamos aprender como us-lo no AVR, e iremos usar este protocolo para dois AVRs comunicarem entre si.

IC no AVR
No AVR, em vez de IC, temos uma interface TWI (two wire serial interface). No entanto, podemos usar como se fosse IC. Quando utilizamos o AVR como master, devemos comear por gerar o clock na linha SCL. Isto feito alterando o register TWBR. Segundo a datasheet, a frmula para o clock a seguinte: SCL Frequency= CPU Frequency 162TWBR Prescaler Value

Ao isolarmos o TWBR, ficamos com isto: CPU Frequency 16 SCL Frequency TWBR= 2Prescaler Value Neste documento iremos utilizar a frequncia 100kHz, visto que uma frequncia standard do IC, e a maior parte dos aparelhos suporta-a. O valor do prescaler aqui definido no register TWSR (que iremos descrever mais frente). Iremos usar o prescaler 1, visto que, com uma frequncia de 100kHz para o IC, e uma frequncia do CPU de 16 MHz, o valor do TWBR no ultrapassa a capacidade de 8 bits (neste caso, esse valor ser 72), e o valor por defeito (assim no necessitamos de alterar o register TWSR). Ateno que a datasheet recomenda um valor mnimo de 10 para o TWBR, logo para frequncias da SCL maiores, ser necessrio um prescaler maior. Agora que j sabemos como gerar o clock, temos de compreender como realizar aces no IC. Para isto utilizamos o register TWCR. Este register em particular tem muitas particularidades na forma como funciona. Primeiro, para o IC funcionar, temos de colocar o valor 1 no bit TWEN. Em segundo lugar, o bit TWINT determina quando ocorrem aces ou no no IC: quando este tem o valor 0, as aces definidas pelos restantes bits ocorrem, e quando tem o valor 1, no podem ocorrer aces. No entanto, a forma como este assume os valores 0 e 1 diferente dos outros bits: para colocar o valor 0 neste bit, temos de escrever um 1 para l, enquanto ele apenas assume o valor 1 ltima reviso: 21/12/2010 74

aps ocorrer algum evento relacionado com o IC. Os bits TWEA, TWSTA e TWSTO indicam que aco o microcontrolador deve tomar nas linhas IC quando o bit TWINT 0. O bit TWWC constitui uma flag que indica se estamos a realizar uma certa aco proibida mexer no register TWDR quando o bit TWINT 0 (iremos ignorar esta flag). O bit TWIE liga a interrupo ligada ao IC esta interrupo ocorre sempre que o bit TWINT 1. Ateno que esta interrupo no coloca esse bit a 0, logo para evitar que esta se repita eternamente, ou colocamo-lo a 0, ou desligamos a interrupo temporariamente no iremos usar esta interrupo neste documento, no entanto destacamos a sua utilidade enquanto ferramenta para realizar aces relacionadas com o protocolo IC, sem a necessidade de ocupar o microprocessador enquanto se espera que cada operao fique completa; para saber qual a operao a realizar, usam-se os cdigos de estados, discutidos brevemente. At agora, temos usado sempre a operaes binria OR para colocar valores em bits especficos nos registers. No entanto, no prtico fazer isso com o register TWCR, visto que este tem bits que determinam a aco a seguir, e que, se no os apagarmos, faro com que ocorram aces repetidas. Assim, de cada vez que mexermos no register TWCR, faremos um reset a todos os seus valores. Isto obriga a ter sempre em ateno o facto de termos de colocar sempre o bit TWEN com o valor 1. Com o que j sabemos at agora, j somos capazes de enviar um sinal start para as linhas IC! O bit que determina esta aco o TWSTA. Vamos ento definir uma funo que faz isto mesmo, bem como uma que inicia o clock: #define FREQ 100000 #define PRES 1 #define TWBR_value ((F_CPU/FREQ)-16)/(2*PRES) void set_clk() { TWBR = TWBR_value; } void send_start() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTA); } Note que, alm do bit TWSTA, tambm mexemos nos bits TWINT e TWEN. Isto para garantir que o IC est ligado (1 no bit TWEN), e que podemos efectuar aces neste (0 no bit TWINT como foi dito antes, este bit fica com o valor 0 quando escrevemos o valor 1 nele). O cdigo para o envio de um start, no entanto, est incompleto, pois ficamos com o problema de no saber quando o start acabou de ser enviado, e se tivemos sucesso ou no a envi-lo (demora um pouco a enviar um start, e por vezes pode falhar, por exemplo, devido a um processo de arbitration que o nosso AVR perdeu)!

ltima reviso: 21/12/2010

75

Como foi dito antes, o bit TWINT fica com o valor 1 assim que uma aco completada nas linhas IC. Assim, para saber quando se acabou de enviar o start, temos de esperar que este bit fique com o valor 1. No entanto, isto no nos indica se o start foi enviado com sucesso ou no. Para sabermos isto, observamos o register TWSR. J falmos deste register anteriormente quando mencionmos o prescaler. Alm do prescaler, este register contm informao acerca do estado do IC, tal como se o start foi enviado com sucesso visto que as operaes para enviar um start e um repeated start so iguais em termos do register TWCR, mas originam diferentes cdigos de estado no register TWSR, temos de verificar ambos esses cdigos. Estes cdigos de estado podem ser encontrados na datasheet do AVR em quatro tabelas (pginas 229, 232, 235 e 238), respectivamente, para os modos de master transmitter, master receiver, slave receiver e slave transmitter. Apenas o master pode enviar um sinal de start/repeated start, e os cdigos para esses sinais so iguais tanto no modo transmitter como receiver, e so, respectivamente, 0x08 e 0x10. de notar que estes cdigos de estado assumem que os bits do prescaler so ambos 0. No entanto, nem sempre o so. Estes bits so os dois menos significativos. Para os retirar, realizamos a operao AND com o valor 0b11111100, ou 0xF8. Para sabermos se o o envio do start bit foi realizado com sucesso, esta funo devolver um valor verdadeiro ou falso, dependendo respectivamente se teve sucesso ou no. Assim, j podemos completar o cdigo para a funo que envia um sinal de start: unsigned char send_start() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTA); // Enviar o sinal de start while(!(TWCR&(1<<TWINT))); // Esperar que a operao termine return ((TWSR&0xF8) == 0x08 || (TWSR&0xF8) == 0x10); } Depois do start bit, temos de enviar o endereo do aparelho com quem queremos comunicar e o bit read/write. Isto constitui um byte (visto o endereo ser 7 bits + 1 bit read/write). O AVR no distingue entre enviar o endereo/bit read/write, e qualquer outro byte no IC, quando est em modo master. Para fazer isto, apenas colocamos o byte a enviar no register TWDR e ligamos as aces IC. Ateno que, como mencionado anteriormente, no podemos colocar qualquer informao no register TWDR, quando o bit TWINT 0! Vamos ento criar uma funo que envia um byte para as linhas IC. Novamente, verificaremos o estado do IC para saber se tivemos sucesso ou no. Visto que os cdigos de estado para o envio e recepo (ACK devolvido) com sucesso de um endereo de slave e o bit read/write so diferentes dos cdigos de sucesso de um byte enviado (com ack ou nack devolvido), iremos criar uma funo separada para chamar um slave. Os cdigos devolvidos so ainda diferentes quando enviamos um bit read ou um bit write, portanto, temos de testar para ambos os casos (respectivamente, 0x40 e 0x18): ltima reviso: 21/12/2010 76

unsigned char send_slave(unsigned char addr) { TWDR = addr; TWCR = (1<<TWINT) | (1<<TWEN); while(!(TWCR&(1<<TWINT))); return ((TWSR&0xF8) == 0x18 || (TWSR&0xF8) == 0x40); } Quando queremos enderear um slave de endereo addr, com bit read/write B, apenas enviamos o byte ((addr<<1)|B). de notar que algumas datasheets listam dois endereos para um aparelho. Isto significa que, em vez de darem os 7 bits do endereo, do o byte completo, com o bit de read/write. Com o que j sabemos, podemos j definir uma funo para enviar dados, visto que basta alterar os cdigos de estado que verificamos. Como foi explicado antes, quando uma transmisso ocorre com sucesso, o receptor devolve um ACK. O receptor tambm pode devolver um NACK quando a recepo ocorre com sucesso, mas este sinal significa que devemos parar a transmisso e enviar um stop bit imediatamente. Assim, temos de verificar se a transmisso ocorreu com sucesso e se um ACK foi devolvido. Como podemos enviar dados tanto em modo slave como em modo master, temos ainda de verificar os cdigos de estado para estes dois modos. Assim, temos de verificar os cdigos de estado 0xB8 e 0x28. Outra particularidade do modo slave que em muitas operaes, para garantir a sua funcionalidade, devemos colocar o bit TWEA a 1, inclusive na operao de envio de dados. Explicaremos o porqu mais frente, mas iremos incluir este pormenor nesta funo: unsigned char send_data(unsigned char data) { TWDR = data; TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA); while(!(TWCR&(1<<TWINT))); return ((TWSR&0xF8) == 0xB8 || (TWSR&0xF8) == 0x10); } At agora s nos concentrmos em enviar dados. No entanto, tanto aparelhos master como slave tm de ser capazes de receber dados. Ateno que apenas podemos recebe dados em master quando enviamos o bit de read, e em slave quando recebemos o bit de write! O processo semelhante ao de enviar dados. Tem apenas os pormenores de lermos os dados do register TWDR, em vez de os escrevermos l, e de termos de enviar um ACK/NACK, para sinalizar que recebemos os dados com sucesso e se queremos continuar ou no com a comunicao, e dos estados diferirem de acordo com o que devolvemos (ACK/NACK), e de acordo com o endereo utilizado para o slave (general call ou o endereo especfico). Assim, temos 6 cdigos de estado que temos de verificar, 3 para o caso ltima reviso: 21/12/2010 77

de devolvermos um ACK (0x50, 0x80, 0x90), e 3 para o caso de devolvermos um NACK (0x58, 0x88, 0x98). O bit que define se devolvemos um ACK ou um NACK o TWEA (1 para ACK, 0 para NACK). A funo que ir receber dados recebe um parmetro lgico: verdadeiro no caso de querermos devolver um ACK, e falso no caso de querermos devolver um NACK. O valor devolvido ser 0 no caso de haver um erro na recepo, ou o valor recebido caso contrrio (isto no ptimo devido o valor recebido poder ser 0! Apenas fazemos assim para simplificar. No entanto, o ideal seria usar uma flag externa funo para sinalizar um erro ou devolver uma estrutura com duas variveis: uma flag para sinalizar erros e o valor recebido): unsigned char receive_data(unsigned char ack) { TWCR = (1<<TWINT) | (1<<TWEN) | (ack?(1<<TWEA):0); while(!(TWCR&(1<<TWINT))); if((ack && (TWSR&0xF8) != 0x50 && (TWSR&0xF8) != 0x80 && (TWSR&0xF8) != 0x90) || ((!ack) && (TWSR&0xF8) != 0x58 && (TWSR&0xF8) != 0x88 && (TWSR&0xF8) != 0x98)) { return 0; } return TWDR; } At agora j envimos start bits, chamamos slaves, enviamos dados, e recebemos dados, tanto em modo slave como master. Para completarmos a funcionalidade do modo master, apenas nos falta enviar stop bits. Isto feito colocando a 1 o bit TWSTO. Esta aco em particular no tem nenhum cdigo de estado, nem coloca o bit TWINT a 1, logo, podemos criar uma funo muito simples para a efectuar: void send_stop() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); } Agora j temos toda a funcionalidade disponvel ao master! Apenas nos faltam alguns pormenores acerca do slave. Primeiro, vamos compreender a relevncia do bit TWEA. Quando este bit tem o valor 1, e o aparelho ainda no foi endereado, este ir responder quando o seu endereo ou uma general call (caso esteja configurado para responder a uma general call) aparecerem nas linhas IC, com um ACK. Se este bit for 0, ento ignorar estes bytes, desligando-se assim do IC. Anteriormente, mencionei que quando um slave envia dados, deve colocar o bit TWEA a 1. Isto no estritamente necessrio, mas aconselhvel. Quando se coloca o bit TWEA a 0 aps enviar dados, o aparelho est espera de receber um NACK, e no ir enviar mais dados. No entanto, para

ltima reviso: 21/12/2010

78

simplificar, colocamos sempre o bit TWEA a 1, visto que no faz nenhuma diferena nos dados transmitidos, e podemos simplesmente terminar a a conexo como se estivssemos espera do NACK. Alm disto, se no colocarmos o bit TWEA a 1, no podemos enviar mais dados. Aps realizarmos operaes no modo slave, devemos esperar por um stop/start/repeated start do master de acordo com a documentao da datasheet, no modo slave transmitter, os stop bits so ignorados. Assim, para fazer isto, ligamos o IC, espera de que algum evento ocorra. Caso seja um stop bit (cdigo de estado 0xA0), ligamos novamente o IC, para o aparelho esperar que o seu endereo seja enviado para a linha. Caso no seja, um stop bit, significa que um start bit foi enviado, seguido do endereo do aparelho em questo sai-se da funo para o cdigo principal tratar de processar esse endereamento (veremos de seguida como fazer isso). Assim, podemos definir a funo para esperar por um stop/start/repeated start bit da seguinte forma: void wait_stop() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA); while(!(TWCR&(1<<TWINT))); if((TWSR&0xF8) == 0xA0) { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA); } } Para podermos ter o nosso aparelho a funcionar como slave, apenas nos falta configurar o seu endereo, saber como configur-lo para responder general call e saber quando o aparelho endereado, e saber se foi enviado um bit de read ou write. Vamos comear por atribuir um endereo ao aparelho e configurar se ele responde general call ou no. Para isto, apenas temos de de escrever o endereo pretendido nos 7 bits superiores do register TWAR, e o bit inferior, caso seja 0, o aparelho ignora a general call, e caso seja 1, o aparelho responde general call. Vamos ento configurar o nosso aparelho para responder tanto general call como ao endereo 0x10, atravs de uma funo (esta funo, alm de configurar o endereo, tambm far com que o aparelho escute as linhas IC, espera de ser endereado): #define GC 1 #define ADDR 0x10 void set_slave() { TWAR = (ADDR<<1)|GC; TWCR = (1<<TWEN) | (1<<TWEA); } Agora, o que nos falta para ter um slave funcional, saber como verificar quando este ltima reviso: 21/12/2010 79

endereado. Para isto, iremos colocar dois aparelhos a comunicar um com o outro. Basicamente, um AVR ter o papel de master transmitter, e o outro de slave receiver. O slave ir mudar o estado do LED incoroporado o arduino (pino digital 13/PB5) assim que receber dados do master receiver. Neste documento, no iremos usar os modos master receiver e slave transmitter, mas so idntidos, apenas trocando-se as funes de transmisso/recepo, e no caso do slave, os cdigos de estado (para o slave transmitter, so 0xA8 e 0xB0). Iremos comear com o aparelho master. Este comea por enviar um start bit, depois chama o slave (neste caso com o endereo 10, e o bit de write), e envia um byte para o slave (neste caso iremos enviar o byte 'a'), e envia um sinal de stop. Para o efeito no LED ser visvel, iremos enviar dados em intervalos de 1 segundo, recorrendo funo _delay_ms na biblioteca util/delay.h: #include <avr/io.h> #include <util/delay.h> #define FREQ 100000 #define PRES 1 #define TWBR_value ((F_CPU/FREQ)-16)/(2*PRES) void set_clk() { TWBR = TWBR_value; } unsigned char send_start() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTA); // Enviar o sinal de start while(!(TWCR&(1<<TWINT))); // Esperar que a operao termine return ((TWSR&0xF8) == 0x08 || (TWSR&0xF8) == 0x10); } unsigned char send_slave(unsigned char addr) { TWDR = addr; TWCR = (1<<TWINT) | (1<<TWEN); while(!(TWCR&(1<<TWINT))); return ((TWSR&0xF8) == 0x18 || (TWSR&0xF8) == 0x40); } unsigned char send_data(unsigned char data) { TWDR = data; TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA); while(!(TWCR&(1<<TWINT))); return ((TWSR&0xF8) == 0xB8 || (TWSR&0xF8) == 0x10); } void send_stop() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); } #define W 0 ltima reviso: 21/12/2010 80

#define R 1 int main(void) { set_clk(); for(;;) { send_start(); send_slave((0x10<<1)|W); send_data('a'); send_stop(); _delay_ms(1000); } } Agora iremos programar o aparelho slave. Este espera at que seja endereado com um bit de read (cdigo de estado 0x60, 0x68, 0x70 ou 0x78). Assim que o , este recebe os dados, faz toggle do LED, e espera pelo stop bit: #include <avr/io.h> #include <util/delay.h> unsigned char receive_data(unsigned char ack) { TWCR = (1<<TWINT) | (1<<TWEN) | (ack?(1<<TWEA):0); while(!(TWCR&(1<<TWINT))); if((ack && (TWSR&0xF8) != 0x50 && (TWSR&0xF8) != 0x80 && (TWSR&0xF8) != 0x90) || ((!ack) && (TWSR&0xF8) != 0x58 && (TWSR&0xF8) != 0x88 && (TWSR&0xF8) != 0x98)) { return 0; } return TWDR; } void wait_stop() { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA); while(!(TWCR&(1<<TWINT))); if((TWSR&0xF8) == 0xA0) { TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA); } } #define GC 1 #define ADDR 0x10 void set_slave() { TWAR = (ADDR<<1)|GC; TWCR = (1<<TWEN) | (1<<TWEA); } int main(void) { DDRB |= (1<<PB5); for(;;) { ltima reviso: 21/12/2010 81

while((TWSR&0xF8) != 0x60 && (TWSR&0xF8) != 0x68 && (TWSR&0xF8) != 0x70 && (TWSR&0xF8) != 0x78); receive_data(0); PORTB ^= (1<<PB5); wait_stop(); } } E assim completamos o tutorial acerca do protocolo IC e sobre como us-lo com o AVR. Este um dos componentes mais complexos de utilizar no AVR, por isso aconselhamos que experimente vrias vezes com o cdigo, e que consulte a datasheet e este documento sempre que tiver dvidas.

ltima reviso: 21/12/2010

82

Bibliografia
http://www.avrfreaks.net/ http://www.atmel.com/dyn/resources/prod_documents/doc8025.pdf
http://embeddeddreams.com/users/njay/Micro Tutorial AVR Njay.pdf http://www.smileymicros.com/index.php? module=pagemaster&PAGE_user_op=view_page&PAGE_id=70&MMN_position=117:117 http://lusorobotica.com/index.php?topic=2838.15

ltima reviso: 21/12/2010

83

Vous aimerez peut-être aussi