Eletrônica Por Equipe DevBras

Capa do artigo: Controle IR parou quando você ligou o servo ou o tone(): é o timer do Arduino

Controle IR parou quando você ligou o servo ou o tone(): é o timer do Arduino

Descubra qual biblioteca está tomando o timer do receptor infravermelho, monte o projeto uma peça por vez e troque o tone() por um buzzer ativo quando os dois brigam.

O sketch de exemplo da IRremote imprime cada tecla do controle no monitor serial. Você junta o servo da fechadura e um bipe com tone(), compila, e o receptor fica surdo, ou a compilação falha com multiple definition of __vector_7. A resposta curta: no Arduino Uno a IRremote usa por padrão o Timer2 para amostrar o sinal, e tone() usa o mesmo Timer2. Duas bibliotecas não podem ser donas do mesmo timer. Escolha uma para cada timer, ou troque o recurso que sobra por um que não precise de timer, como um buzzer ativo ligado com digitalWrite.

Quase nunca é mau contato nem receptor queimado. Se o receptor funciona sozinho e para quando você acrescenta uma peça, o problema está no software que aquela peça trouxe junto.

Por que duas bibliotecas brigam pelo mesmo timer

O ATmega328P do Uno tem três timers: Timer0, Timer1 e Timer2. O Timer0 já é do próprio núcleo do Arduino, que usa esse timer para millis(), delay() e o PWM dos pinos 5 e 6. Sobram dois para todo o resto, e muitas bibliotecas precisam de um.

O receptor infravermelho entrega pulsos de centenas de microssegundos. Para medir isso sem travar o seu loop(), a IRremote programa um timer para gerar uma interrupção periódica e, a cada interrupção, olha o pino do receptor. A função tone() faz algo parecido: programa um timer para inverter o pino do buzzer na frequência pedida. A biblioteca Servo usa o Timer1 no Uno para gerar os pulsos de controle, e por isso desliga o analogWrite nos pinos 9 e 10.

Quando duas bibliotecas querem o mesmo timer, acontece uma de duas coisas:

  • Erro de compilação. As duas definem a mesma rotina de interrupção. No Uno, __vector_7 é a interrupção de comparação A do Timer2. A mensagem de "definição múltipla" é o compilador dizendo que dois arquivos querem atender o mesmo evento.
  • Falha silenciosa. Compila, mas uma biblioteca reconfigura o timer depois que a outra já configurou. Quem configura por último vence. O receptor deixa de ser amostrado no ritmo certo e nenhuma tecla decodifica, ou só decodifica depois que o bipe termina.

O caso silencioso é o pior, porque parece hardware. Você troca o receptor, troca o controle, e nada muda.

Como descobrir quem é dono de cada timer

Não confie na memória nem em post de fórum de dez anos atrás. As bibliotecas mudam de versão e mudam de timer conforme a placa. Três fontes resolvem:

  1. A documentação da versão instalada. O README da IRremote tem uma seção sobre timers e placas, inclusive sobre convivência com tone(). Leia a da versão que está no seu computador, não a do tutorial.
  2. O código-fonte. Abra a pasta da biblioteca e procure por TIMER1_, TIMER2_, TCCR1 e TCCR2. Quem escreve nesses registradores está usando aquele timer.
  3. A saída detalhada da compilação. Ative a saída detalhada nas preferências da IDE. Se aparecer "Multiple libraries were found for IRremote.h", há duas cópias instaladas e a IDE está escolhendo uma por você.

Para listar o que está instalado pela linha de comando:

arduino-cli lib list

Se aparecerem duas bibliotecas de infravermelho, ou uma versão antiga copiada à mão na pasta libraries, remova a que você não usa. Em versões antigas da IDE, havia biblioteca embutida com um arquivo de mesmo nome, e o include pegava a errada sem avisar.

Monte o projeto uma peça por vez

O exemplo que atravessa este texto é uma fechadura de gaveta: receptor IR no pino 2, servo no pino 9, buzzer no pino 8. Tecla 1 do controle abre, tecla 2 fecha, e um bipe curto confirma.

  1. Receptor sozinho. Grave o exemplo de recepção da biblioteca e anote o código de cada tecla que você vai usar. Se nem isso funciona, o problema é fiação ou protocolo, e o guia de controle remoto infravermelho ajuda mais que este.
  2. Acrescente o servo. No Uno, Servo fica no Timer1 e IRremote no Timer2, então os dois convivem. Teste abrir e fechar dez vezes seguidas.
  3. Acrescente o bipe. É aqui que o tone() costuma quebrar tudo. Se quebrar, você sabe exatamente qual peça entrou por último.
  4. Só então junte a lógica. Estados, tempo de travamento, o que for. Lógica nova num projeto que já falha só esconde a causa.

Em cada passo, a pergunta é a mesma: a tecla ainda aparece no monitor serial? Se parou, desfaça o último passo antes de mexer em qualquer outra coisa.

O sketch sem conflito

A saída mais simples para o bipe é um buzzer ativo, que já tem oscilador interno. Ele apita com o pino em HIGH e não precisa de timer nenhum. O tempo do bipe fica por conta de millis(), sem delay() no caminho do receptor.

#include <IRremote.hpp>
#include <Servo.h>

const uint8_t PINO_IR = 2;
const uint8_t PINO_SERVO = 9;
const uint8_t PINO_BUZZER = 8;   // buzzer ativo: apita com HIGH, sem tone()

const uint16_t TECLA_ABRIR = 0x45;   // troque pelos códigos que o seu controle imprimir
const uint16_t TECLA_FECHAR = 0x46;

Servo trava;
bool buzzerLigado = false;
unsigned long buzzerInicio = 0;
unsigned long buzzerDuracao = 0;

void bipe(unsigned long ms) {
  digitalWrite(PINO_BUZZER, HIGH);
  buzzerLigado = true;
  buzzerInicio = millis();
  buzzerDuracao = ms;
}

void setup() {
  Serial.begin(115200);
  pinMode(PINO_BUZZER, OUTPUT);
  digitalWrite(PINO_BUZZER, LOW);
  trava.attach(PINO_SERVO);
  IrReceiver.begin(PINO_IR, ENABLE_LED_FEEDBACK);
  Serial.println(F("Pronto para receber"));
}

void loop() {
  if (IrReceiver.decode()) {
    bool repeticao = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT;
    uint16_t comando = IrReceiver.decodedIRData.command;
    IrReceiver.resume();

    if (!repeticao) {
      Serial.print(F("Comando: 0x"));
      Serial.println(comando, HEX);
      if (comando == TECLA_ABRIR) {
        trava.write(90);
        bipe(80);
      } else if (comando == TECLA_FECHAR) {
        trava.write(0);
        bipe(80);
      }
    }
  }

  if (buzzerLigado && millis() - buzzerInicio >= buzzerDuracao) {
    digitalWrite(PINO_BUZZER, LOW);
    buzzerLigado = false;
  }
}

Três detalhes importam. O resume() vem logo depois de copiar o que você precisa, para o receptor voltar a escutar enquanto o servo se move. A flag de repetição evita que segurar a tecla dispare o servo vinte vezes, o mesmo problema do botão que repete. E a conta millis() - buzzerInicio continua certa quando o contador de millis() dá a volta, depois de uns 49 dias ligado.

Quando você precisa mesmo de tom variável

Se o projeto pede melodia, buzzer ativo não serve, porque ele só toca a própria frequência. Aí você tem três caminhos honestos: seguir o que a documentação da IRremote recomenda para usar tone() na versão instalada, aceitar que o receptor fica surdo enquanto a melodia toca e reiniciar a recepção depois, ou passar para uma placa com mais timers. Placas como ESP32 e as baseadas em ARM têm mais hardware de temporização e a biblioteca usa outro mecanismo nelas.

O que não funciona é deixar os dois ligados ao mesmo tempo e torcer.

Outros vizinhos que disputam timer

O tone() é o caso clássico, mas não o único. Fique atento quando juntar ao receptor IR:

  • Bibliotecas de PWM em frequência personalizada, que reprogramam Timer1 ou Timer2 para tirar o chiado do motor.
  • Bibliotecas de interrupção periódica, do tipo "chame esta função a cada X microssegundos".
  • analogWrite nos pinos 3 e 11 do Uno, que são do Timer2. Se a IRremote está usando esse timer, o PWM nesses pinos pode sair errado.
  • Uma segunda biblioteca de IR, instalada "para testar", que define as mesmas rotinas.

Antes de instalar qualquer biblioteca nova num projeto que já tem IR, procure no código dela qual timer ela usa. Leva dois minutos e poupa uma tarde.

Erros comuns

  • Trocar o receptor quando o problema é software. O receptor novo fica igualmente surdo e você perde a pista de que a falha começou ao acrescentar uma biblioteca.
  • Deixar duas bibliotecas de IR instaladas. A IDE escolhe uma pelo nome do arquivo, e o exemplo que você copiou foi escrito para a outra. Os erros de compilação parecem não ter relação com o seu código.
  • Usar delay() longo no loop. A recepção continua na interrupção, mas você só processa o código depois do atraso, e teclas apertadas nesse intervalo se perdem.
  • Seguir tutorial com a API antiga. Exemplos com IRrecv e decode_results são da IRremote 2.x. Misturar API antiga com biblioteca nova gera aviso, comportamento diferente ou erro.
  • Usar PWM no pino errado. Com Servo ativo, analogWrite nos pinos 9 e 10 do Uno para de funcionar. O LED que deveria dimerizar só acende e apaga.
  • Juntar tudo de uma vez. Quando cinco peças entram juntas e algo falha, você não sabe qual delas causou a falha.

Checklist

  • O receptor sozinho imprime o código de cada tecla no monitor serial.
  • Só uma biblioteca de infravermelho aparece em arduino-cli lib list.
  • Você sabe qual timer cada biblioteca do projeto usa na sua placa.
  • Nenhum par de bibliotecas divide o mesmo timer.
  • O bipe de confirmação não usa tone(), ou segue a orientação da documentação da IRremote.
  • O loop não tem delay() maior que algumas dezenas de milissegundos.
  • Segurar a tecla não dispara a ação repetidas vezes.

Abra o seu sketch agora e liste os includes: para cada um, você consegue dizer qual timer ele usa e se mais alguém usa o mesmo?

Avalie este artigo

Referência: http://labdegaragem.com/forum/topics/problema-com-biblioteca-irremote