Vous êtes sur la page 1sur 7

Interrupções

O que é uma interrupção?


Irei agora começar a falar de interrupções a partir do mais básico – o que é uma interrupção?
Uma interrupção é basicamente uma pausa no programa, enquanto o processador trata de outra
coisa mais importante.
Um exemplo da vida real:
http://www.youtube.com/watch?v=A9EP6U0BBrA
Neste caso a interrupção foi o toque com o pato que interrompeu o discurso.

Como funciona uma interrupção no AVR?


Nos AVRs, as interrupções têm várias particularidades, pois é necessário activar as interrupções
globais, as interrupções particulares e criar uma rotina para lidar com cada interrupção.
Cada microcontrolador AVR tem um conjunto de registers cujos bits controlam vários aspectos
do seu funcionamento (por exemplo, no tutorial que coloquei no meu primeiro post, estão
explicados os que controlam os pinos de INPUT/OUTPUT). O mesmo acontece com as
interrupções.
No caso do atmega328, o bit I do register SREG controla as interrupções a nível global. Quando
o seu valor é 1, estas estão ligadas, e vice-versa. No entanto, há uma instrução mais simples para
ligar as interrupções globais do que terem de se lembrar que é no bit I do register SREG, que é
simplesmente “sei” (funciona tanto em assembly como em C, só que em C é uma função incluída
no header <avr/interrupt.h>).
Depois de ligadas as interrupções globais, ainda é necessário ligar interrupções individuais. Para
isso, é necessário encontrar qual o bit que liga certa interrupção (encontra-se na datasheet
facilmente na zona dos registers no capítulo acerca da funcionalidade procurada).
Depois de ligadas as interrupções globais e particulares, o processador procura por mudanças de
estado em bits de certos registers (flags), definidos pelas interrupções individuais. Quando esses bits
tornam-se em 1, a interrupção é gerada (independentemente do que esteja a acontecer, o programa
pára). Mas falta aqui uma coisa … o que acontece quando essa interrupção ocorre? A “Interrupt
Service Routine” (ISR) definida para aquela interrupção é executada. No final disto tudo, a flag fica
com o valor 0 novamente, e o programa continua a sua execução a partir do ponto em que estava.
Nota: Enquanto o processador está a executar a interrupção, as interrupções globais estão
desligadas. Quando acaba-se de executar a interrupção, as interrupções globais são ligadas
novamente.
Como lidar com uma interrupção no AVR?
Agora que já sabemos como funciona uma interrupção, temos de aprender a programar de forma
a lidar com as mesmas.
Primeiro, iremos começar por definir o pseudo-código:

// Iniciar o programa
// … (código que inicialize variáveis, LEDs, etc. aqui)
// Ligar interrupções particulares
// Ligar interrupções globais
// Definir ISR para lidar com as interrupções
particulares ligadas.

Para lidar com registos e interrupções, iremos precisar dos seguintes headers: <avr/io.h> e
<avr/interrupt.h> (já podemos também adicionar a função main(), e um loop eterno):

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void) {
// … (código que inicialize variáveis, LEDs, etc. aqui)
// Ligar interrupções particulares
// Ligar interrupções globais
for(;;);
}
// Definir ISR para lidar com as interrupções
particulares ligadas.

(como a ISR é uma função, definimos fora do main).


Como expliquei anteriormente, ligam-se as interrupções globais através da função sei()

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void) {
// … (código que inicialize variáveis, LEDs, etc. aqui)
// Ligar interrupções particulares
sei();
for(;;);
}
// Definir ISR para lidar com as interrupções
particulares ligadas.

Neste tópico, vamos ignorar as interrupções individuais, pois ainda não falámos de nenhuma
interessante, e vamos concentrar-nos nas ISR.
A biblioteca do avr dá-nos uma macro muito útil para definir uma ISR, e tem o nome ISR()
(espertos, não são? xD), com um argumento: o nome do vector da interrupção. O vector da
interrupção é basicamente o que identifica qual a interrupção com que estamos a lidar. Esta
informação encontra-se na página 57 do datasheet que eu tenho (início do capítulo sobre
interrupções/capítulo 9), numa tabela, na coluna Source.
Por exemplo, no tópico a seguir, vamos lidar com a interrupção que ocorre no pino digital 2. Este
pino tem o nome de INT0. Ao olharmos para a tabela, vemos que a source da interrupção é INT0.
Para usarmos isto como argumento para a macro ISR, basta adicionar “_vect”. Assim, o vector é:
INT0_vect:

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void) {
// … (código que inicialize variáveis, LEDs, etc. aqui)
// Ligar interrupções particulares
sei();
for(;;);
}

ISR(INT0_vect) {
// Definir o que fazer quando acontece esta interrupção
}

(para alguns, esta declaração da função ISR pode ser confusa, pois não tem tipo. No entanto,
lembrem-se que é uma macro, e por isso ISR não é realmente o que fica no código final).
Nota: Se notarem, alguns dos Vectores na datasheet têm espaços ou vírgulas no nome. Basta
substituir esses por _. Por exemplo, para a interrupção gerada quando se recebem dados por serial,
temos o seguinte Source: USART, RX. O argumento que usamos para a macro ISR é:
USART_RX_vect.

Exemplo de interrupção através do pino digital 2 (INT0)


Vamos agora fazer algo mais interessante, e codificar uma interrupção.

Comecemos com o código do tópico anterior:

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
// … (código que inicialize variáveis, LEDs, etc. aqui)
// Ligar interrupções particulares
sei();
for(;;);
}

ISR(INT0_vect) {
// Definir o que fazer quando acontece esta interrupção
}

Vamos usar para este efeito o pino 2 (podia ser feito com o pino 3 também, com poucas
diferenças).
O objectivo deste código vai ser mudar o estado de um LED quando se toca num botão ligado ao
pino 2. O LED vai estar ligado ao pino digital 4 (PD4).
Vamos começar por criar uma variável global, com o estado do pino e inicializar esse pino como
output (vai começar desligado) (para mais informações sobre GPIOs, ler o tutorial que pus no
primeiro post), e já vamos colocar o código necessário para fazer toggle ao pino na ISR.

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>

char output = 0; // Estado do led.

int main(void) {
DDRD |= (1<<PD4); // Inicializar o pino digital 4 como
output.
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como
desligado.
// Ligar interrupções particulares
sei();
for(;;);
}

ISR(INT0_vect) {
// Definir o que fazer quando acontece esta interrupção
output = ~output; // Alterar o estado
PORTD &= ~(1<<PD4); // Desligar o pino – isto é
necessário para quando o output é 0 se poder desligar.
PORTD |= ((output&1)<<PD4) // output&1 pois só nos
interessa o primeiro bit, assim evitamos mexer nos outros
pinos.
}

Agora só nos falta mesmo inicializar a interrupção particular para o INT0.


Ao pesquisarmos na datasheet, podemos observar um pormenor acerca dos interrupts externos
INT0 e INT1: eles podem ser ligados por 4 estados diferentes: quando o pino está low, quando o
pino transita para high, quando o pino transita para low e quando o pino muda de estado (low-high e
vice-versa). Como estamos a usar um botão, o mais fácil é que este ligue o pino à corrente,
colocando-o em HIGH. Assim, queremos gerar o interrupt quando o pino transita para HIGH.
O estado escolhido para o INT0 está nos bits ISC00 e ISC01 do register EICRA (para o pino
INT1, está nesse mesmo register, mas nos bits ISC10 e ISC11). O estado que desejamos
corresponde a colocar ambos os bits em 1. Logo, adicionamos isso ao código:

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>

char output = 0; // Estado do led.

int main(void) {
DDRD |= (1<<PD4); // Inicializar o pino digital 4 como
output.
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como
desligado.
EICRA |= ((1<<ISC00) | (1<<ISC01)); // Configurar
interrupção no pino INT0 para quando este transita para
HIGH
// Ligar interrupções particulares
sei();
for(;;);
}

ISR(INT0_vect) {
// Definir o que fazer quando acontece esta interrupção
output = ~output; // Alterar o estado
PORTD &= ~(1<<PD4); // Desligar o pino – isto é
necessário para quando o output é 0 se poder desligar.
PORTD |= ((output&1)<<PD4) // output&1 pois só nos
interessa o primeiro bit, assim evitamos mexer nos outros
pinos.
}

Agora só nos falta mesmo ligar a interrupção associada ao INT0. O bit que controla isto é o
INT0 no register EIMSK. Assim, é só modificar o código, e fica completo:

// Iniciar o programa
#include <avr/io.h>
#include <avr/interrupt.h>

char output = 0; // Estado do led.


int main(void) {
DDRD |= (1<<PD4); // Inicializar o pino digital 4 como
output.
PORTD &= ~(1<<PD4); // Inicializar o pino digital 4 como
desligado.
EICRA |= ((1<<ISC00) | (1<<ISC01)); // Configurar
interrupção no pino INT0 para quando este transita para
HIGH
EIMSK |= (1<<INT0); // Ligar interrupções particulares
sei();
for(;;);
}

ISR(INT0_vect) {
// Definir o que fazer quando acontece esta interrupção
output = ~output; // Alterar o estado
PORTD &= ~(1<<PD4); // Desligar o pino – isto é
necessário para quando o output é 0 se poder desligar.
PORTD |= ((output&1)<<PD4) // output&1 pois só nos
interessa o primeiro bit, assim evitamos mexer nos outros
pinos.
}

Agora temos um código que, ao clicarmos num botão que liga o pino digital 2 e o pólo positivo,
faz toggle do pino digital 4 (nota: visto que não fizemos nada para tratar do bouncing, podem haver
resultados inesperados. No entanto, como isto é só para exemplo, não considerámos muito
importante).
Para experimentarem, podem montar o seguinte circuito:

Cuidados a ter na utilização de interrupções


Vou deixar aqui duas situações a terem em atenção quando estão a lidar com interrupções:
1. O compilador optimiza bastante o código. Isto quase sempre é uma vantagem, no entanto,
por vezes não é. Utilizando o exemplo do tópico anterior, se tivéssemos de aceder a variável output
no código do main(), não estaríamos a aceder ao valor correcto da variável. Isto acontece porque o
compilador não considera que podemos aceder à função ISR só com aquele código, logo apenas
carrega a variável da memória ram para os registers uma vez, e depois não actualiza o seu valor, que
é alterado na ISR. Para resolver isto, dizemos ao código que a variável output é volatile,
declarando-a assim:
volatile char output;
Isto indica ao compilador que a variável pode ser alterada de formas inesperadas, e por isso deve
sempre actualizar o seu valor da ram.
2. O processador AVR do Arduino funciona a 8 bits. Isto quer dizer que ele só pode lidar com 8
bits de cada vez. Não pode, por exemplo, carregar um int da memória numa só instrução, pois estes
têm 16 bits. Mas as interrupções podem ocorrer em qualquer parte do programa … logo o que
pensam que acontece se tirarmos a primeira metade de um inteiro da memória, e antes de tirarmos a
segunda metade ocorrer uma interrupção que altere essa variável? Resultados não previsíveis
obviamente … Logo, o que podemos fazer para evitar isto? O que podemos fazer é desligar
interrupções nesses blocos de código que não podem ser perturbados (nota: se estiverem a carregar
um char não deve haver problemas, visto ter apenas 8 bits). Isto é feito facilmente com a função
cli() (também no header <avr/interrupt.h>). Depois do código efectuado, basta ligar novamente as
interrupções com sei(). Por exemplo:

//...
int i,j;
for(;;) {
cli();
j = i; // o i é alterado numa interrupção
sei();
// …
}
//...

E com isto acabamos a base das interrupções. Decidi começar com estas, pois nos próximos
tutoriais, explicarei as interrupções individuais de várias funcionalidades.

Vous aimerez peut-être aussi