Você está na página 1de 11

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

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.

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 <=> ms = 65535/16000

<=> 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

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

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

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

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

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:

// 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

} 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:

Você também pode gostar