Por que usar interrupções no Arduino?

Posted by

Vamos ver o porquê usar interrupções no Arduino, incluindo um exemplo real pelo qual passei recentemente. Mas para iniciar, vamos falar da função delay(), adorada pelos iniciantes.

Quando a pessoa inicia nos microcontroladores, inclusive no Arduino, são apresentados exemplos básicos e simples. Um destes exemplos é o “blink”, que vem com a IDE do Arduino.

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(1000);                      // wait for a second
  digitalWrite(LED_BUILTIN, LOW);   // turn the LED off by making the voltage LOW
  delay(1000);                      // wait for a second
}

Observe o código acima, após cada função “digitalWrite” é chamada uma função delay(1000) de 1000 milissegundos. O código fica “parado”, esperando os 1000 milissegundos acabar. Nada é executado enquanto esse tempo não passa.

Uma solução é utilizar as funções millis() ou micros() como eu explico aqui. A ideia não é complicada: conta-se um tempo e entra-se em determinada função somente após este tempo passar. O pedaço de código abaixo foi retirado do meu blog em Inglês.

time = micros();
  if (enterFunction == true){
    previousTime= time;
    Serial.println(previousTime); // for debugging

    // Start your code below 
    //-----------------------




    //-----------------------
    // End of your code
  }
  
  // The DELAY time is adjusted in the constant below >> 
  if (time - previousTime < 999990){ // 1 million microsencods= 1 second delay
    /* I have actually used 0.999990 seconds, in a trial to compensate the time that
       this IF function takes to be executed. this is really a point that
       need improvement in my code */   
    enterFunction= false;
  } 
  else {
    enterFunction= true;
  }

Este método funciona com códigos que executam rápido e em baixa frequência. Vou contar um caso em que descobri que esta função não pode nem deve ser usada para tudo.

Na empresa onde trabalho precisei desenvolver um produto onde deveria-se gerar uma onda quadrada de 55Hz, além de ler entradas digitais, ler um teclado via i2c e controlar um display LCD 20×4.

Usei o código acima para executar as quatro funções (onda quadrada, entradas digitais, teclado i2c, display LCD). Minha surpresa foi na hora de colocar o osciloscópio para medir a frequência de 55Hz.

Chamou atenção o fato de a onda não ser exatamente quadrada, ser um pouco mais parecida com um PWM. A frequência se manteve.

onda não quadrada
Onda não quadrada

Ao abrir um pouco mais o zoom, outra ingrata surpresa: a cada segundo a onda de 55Hz era interrompida por vários milissegundos.

Forma de onda interrompida
Forma de onda interrompida
Onda de 55Hz interrompida
Onda de 55Hz interrompida

Me dei conta de que a cada segundo o display LCD 20×4 é atualizado uma vez. Significa então que a atualização do display a cada segundo faz o programa ficar “parado” por vários milissegundos.

Esse é exatamente o problema de utilizar micros() ou millis() para escalonar o programa, caso alguma das suas funções demore mais que o tempo de entrada na função, o programa vai ficar parado ali.

Entram então as interrupções, que baseadas em algum temporizador timer (ou até mesmo em entradas digitais) param o programa e o levam até onde algum código precisa ser executado. Uma das bibliotecas disponíveis para Arduino que lidam com interrupção de timer é a timerinterrupt, neste link.

Outra forma é utilizar diretamente os registradores, conforme o exemplo abaixo. Basicamente no código abaixo são gerados 55Hz no pino chamado “frequency” (pino 21 do Arduino Mega), através de entrar na função “ISR(TIMER1_COMPA_vect)” e sempre mudar o estado da saída com “digitalWrite(frequency, !digitalRead(frequency))”.

  cli();//stop interrupts

  //set timer1 interrupt at 1Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  // Frequency was calculated on this site https://www.arduinoslovakia.eu/application/timer-calculator
  OCR1A = 18180; // 110.00495022276003 Hz (16000000/((18180+1)*8))
  // Tive que colocar 110Hz porque cada vez que interrompe é contado um ciclo, porém
  // minha onda quadrada tem nível alto e baixo, dois "momentos" porém em um só ciclo
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS12 and CS10 bits for 1024 prescaler
  //TCCR1B |= (1 << CS12) | (1 << CS10);  
  TCCR1B |= (1 << CS11);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  sei();//allow interrupts
}

ISR(TIMER1_COMPA_vect){ //timer1 interrupt 1Hz toggles pin 13 (LED)
//generates pulse wave of frequency 1Hz/2 = 0.5kHz (takes two cycles for full wave- toggle high then toggle low)
    
  digitalWrite(frequency, !digitalRead(frequency));  
    
}

No caso do exemplo acima os 55Hz são críticos, já demais funções (i2c, display LCD, teclado) “podem esperar”.

Pra resumir

Quando iniciamos no aprendizado de uma ferramenta como o Arduino, não temos ideia do que é possível fazer e nem do que não fazer. Abandonar o delay() é somente um passo, começamos então a utilizar funções com micros() e millis().

Porém dependendo do que queremos fazer e de quanto código vamos escrever, estas funções não nos ajudam mais, temos que partir para utilização “pesada” de interrupções. É aí que a brincadeira começa a ficar interessante.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *