Funções de interrupção são muito úteis, entretanto são um pouco desconhecidas. Portanto, vamos aprender o que é e como implementá-las no Arduino.

Na aula anterior, vimos como usar o microcontrolador fora da placa.

O que é

Para entender o que é a interrupção, considere o seguinte caso: vamos supor que queremos saber se um botão foi pressionado ou não.

Da forma como fizemos na aula 4, foi preciso verificar constantemente na função loop se ele foi pressionado. Esse método pode acabar sendo inadequado e atrapalhar a leitura do botão, ainda mais nos casos em que são executadas outras funções no loop. Ou seja, na hora que o botão é pressionado, o programa pode estar “travado” em outra parte do programa (um delay por exemplo) e deixar de fazer a leitura corretamente. Esse método de verificação constante é conhecido como polling.

Tendo em vista essa possível limitação, é necessário utilizar outro método para evitar problemas na leitura. Conforme foi ensinado na aula 9, é possível utilizar uma técnica de executar duas funções ao mesmo tempo. Isso evitaria que o programa ficasse “travado” em uma parte do código. Mesmo assim, a leitura do botão (se foi pressionado ou não) continuaria sendo feita constantemente (novamente o método polling).

Entretanto, existe a interrupção, que é um método contraposto ao polling. A interrupção nada mais é do que uma pausa no programa principal para que uma função de interrupção (específica) seja executada. Considere o seguinte: criamos uma função de interrupção chamada ligarLED, que é associada a um botão. Quando o botão for pressionado, o código principal faz um pausa e executa a função ligarLED.

Ou seja, mesmo que o programa esteja “travado”, por exemplo em um delay, ele faz uma pausa e executa a função de interrupção. Assim que a função de interrupção termina de executar, o programa retorna ao exato ponto que ele parou do código principal. Este processo está ilustrado abaixo:

Rotina de interrupção no Arduino

Exemplo

Suponha que você deseja criar um botão de power semelhante ao que existe nos smartphones. Quando o smartphone está ligado e com a tela acesa, esse botão desliga a tela imediatamente independente do que o celular esteja executando. Portanto, para replicá-lo, o ideal seria criar uma interrupção, que é executada praticamente de imediato a partir do acionamento do botão.

Pinos de interrupção

A primeira coisa necessária para implementar a interrupção é saber quais pinos aceitam essa funcionalidade. Os pinos que aceitam a interrupção têm o nome INT associado a eles. Como neste curso nós focamos no Arduino Uno, vou mostrar quais são os pinos específicos para ele. Mas não é difícil de encontrar quais são os pinos para as demais versões. Veja a imagem abaixo que mostra os pinos do Arduino Uno:

Fonte: Wikipedia

Ao lado dos pinos D2 e D3 é possível verificar que está escrito “INT0” e “INT1” respectivamente. Estes dois são os únicos pinos do Arduino UNO com suporte à interrupção chamada de interrupção externa. Os demais pinos com o nome PCINT__ também têm suporte à interrupção, mas não a externa. 

As duas interrupções, apesar do nome, são relativas a uma mudança feita externamente no estado dos pinos. Entretanto, a diferença entre as duas é que a interrupção externa (pinos D2 e D3) pode ser configuradas para acionar se o pino mudar de nível alto para baixo, se mudar de nível baixo para alto, ou se mudar de nível (independente de qual), enquanto a interrupção normal só aceita a configuração de mudança de nível (independente de qual). Ou seja, a interrupção normal é mais limitada, mas todos os pinos têm suporte a ela.

Na parte superior da imagem, é possível ver a correspondência desses pinos no microcontrolador (atmega328p).

Como implementar

Os comandos mostrados adiante só funcionam para as interrupções externas. O processo de configurar as interrupções via PCINT é mais complicado e não pretendo mostrá-lo nesta aula.

Agora que sabemos quais são os pinos disponíveis (D2 e D3 no Arduino UNO), podemos seguir adiante. Para definir uma interrupção, podemos utilizar o comando:

attachInterrupt(pino, funcao_interrupcao, modo);

Dentre os parâmetros deste comando, temos:

pino

Informa em qual pino ocorrerá a interrupção.

A numeração utilizada é de acordo com os pinos INT. Ou seja, se desejo utilizar uma interrupção no pino digital 2 (INT0), o valor do meu parâmetro deve ser 0 por causa do INT0. E deve ser 1 para o caso do pino D3 (INT1).

Caso deseje utilizar a numeração de acordo com os pinos digitais do Arduino, você deve usar o comando: digitalPinToInterrupt(pino). Desta forma, podemos escrever digitalPinToInterrupt(2) para nos referir ao INT0.

funcao_interrupcao

Informa o nome da função associada à interrupção.

Toda vez que a interrupção for acionada, essa função será chamada. Portanto, é necessário criar essa função no seu programa. E ela deve obedecer a algumas regras:

  1. Ela deve ser do tipo void e não pode receber parâmetros. Sendo assim, é uma função que não recebe e nem retorna valores;
  2. Ela deve ser o mais curta possível para não atrapalhar o funcionamento do restante do código;
  3. Ela não deve utilizar funções como millis(), micros() e delay(), pois essas dependem de outras interrupções para funcionar. De acordo com a documentação do Arduino, o comando delayMicroseconds() funciona sem problemas neste caso;
  4. Comandos da comunicação serial não são recomendados de serem utilizados;
  5. Variáveis globais utilizadas pelo programa principal e pela interrupção devem ser do tipo volatile. Caso contrário, podem haver erros nos valores armazenados. Para uma explicação detalhada, leia aqui.

Observação: enquanto uma interrupção é executada, ela não pode ser interrompida por outra (neste caso).

modo

Informa como a interrupção deve ser ativada. Existem quatro principais modos de detecção, que são:

  • LOW: ativa quando o pino estiver em nível baixo
  • CHANGE: ativa quando o estado do pino mudar (de nível alto para baixo ou vice-versa).
  • RISING: ativa quando o pino for de nível baixo para alto. Detecta uma borda de subida.
  • FALLING: ativa quando o pino for de nível alto para baixo. Detecta uma borda de descida.

Comandos adicionais

Podem existir certos pedaços do seu código que não devem ser interrompidos, pois, se forem, o programa não funcionará como o esperado. Sendo assim, existem dois comandos feitos justamente para ligar/desligar as interrupções quando for conveniente. Os dois comandos estão mostrados abaixo:

noInterrupts(); //Desliga as interrupções
interrupts(); //Liga as interrupções

Não há nenhum mistério na utilização de cada um dos comandos. Basta utilizar diretamente quando desejar ligar ou desligar as interrupções. E é possível também desassociar uma interrupção específica que foi criada. Para isso, é só utilizar o comando:

detachInterrupt(pino);

Sendo que o parâmetro pino obedece as mesmas características do parâmetro do comando attachInterrupt.

Exemplo prático

Agora vamos ver um exemplo prático da interrupção. A ideia é criar programa que mude o estado de um LED toda vez que um botão for pressionado. Entretanto, o programa ficará preso em um loop while. Farei isso para reforçar que a interrupção pode sair de qualquer estado do programa, pois, fazer o programa sair do void loop pode não passar tanta credibilidade, já que o void loop é uma função da própria rotina do Arduino.

Utilizarei dois LEDs e dois botões (um para acionar cada LED). O botão do INT0 acionará um LED verde e o botão do INT1 acionará um LED azul. Minha intenção é acionar a interrupção toda vez que o botão for pressionado. Como utilizarei resistores de pull-up para os pinos não flutuarem, os botões serão ligados no GND. Portanto, a ativação da interrupção será a detecção de nível alto para nível baixo (modo FALLING).

Para demostrar o uso do comando detachInterrupt, vou fazer com que a interrupção do botão 2 seja desassociada assim que ela for executada uma primeira vez. Ou seja, na função da interrupção do botão 2, utilizarei o comando detachInterrupt(1), e o botão funcionará uma única vez

Circuito

circuito leitura botões arduino

O circuito acima é o mesmo da aula 4. Basta ligar um lado dos botões no GND e o outro nos pinos digitais 2 e 3 respectivamente (INT0 e INT1). Para o caso dos LEDs, ligue o cátodo no GND com um resistor (220 Ω) e o ânodo nos pinos 4 e 5 respectivamente.

Código completo

O código abaixo apenas permuta o estado do LED toda vez que o botão correspondente for pressionado. No void loop, conforme informei, criei um loop while com delay e com comando de escrita serial. Dessa forma, é possível abrir o monitor serial e confirmar que o programa pode ser interrompido durante o delay.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#define botao1 2
#define botao2 3
#define ledVerde 4
#define ledAzul 5

void setup() {
  // Define os botoes como entrada e ativa os resistores de pull-up
  pinMode(botao1, INPUT_PULLUP);
  pinMode(botao2, INPUT_PULLUP);

  // Define os LEDs como saida
  pinMode(ledVerde, OUTPUT);
  pinMode(ledAzul, OUTPUT);

  // Inicia a comunicação serial
  Serial.begin(9600);

  // Define as interrupções, ambos como detecção na borda de descida
  attachInterrupt(digitalPinToInterrupt(botao1), acionarLEDVerde, FALLING);
  attachInterrupt(digitalPinToInterrupt(botao2), acionarLEDAzul, FALLING);
}

void loop() {
  // Loop para reforçar o comportamento da interrupção
  while(true){
    Serial.println("Programa dentro do loop while");
    Serial.println("Tempo: " + String(millis()));
    delay(1000);
  }
}

//Função do INT0
void acionarLEDVerde(){
  // Comuta o estado do LED
  digitalWrite(ledVerde, !digitalRead(ledVerde));
}

//Função do INT1
void acionarLEDAzul(){
  // Comuta o estado do LED
  digitalWrite(ledAzul, !digitalRead(ledAzul));
  // Desliga a interrupção do INT1
  // Portanto, esta função será acessada apenas 1 vez
  detachInterrupt(1);
}

Recomendo a leitura desta página, para entender a fundo sobre as interrupções.

Modo sleep com Arduino – Aula 12 – AI