




Aprenda a capturar comandos de controles remotos, identificar protocolos conhecidos e analisar sinais infravermelhos desconhecidos com ESP32. Controles remotos infravermelhos estão presentes em uma enorme quantidade de equipamentos eletrônicos. Televisores, aparelhos de som, ar-condicionado, ventiladores, luminárias, fitas LED, climatizadores e diversos outros dispositivos utilizam sinais infravermelhos para receber comandos.
À primeira vista, parece que pressionar um botão simplesmente faz o controle enviar um código. Porém, por trás dessa ação existe uma sequência de pulsos cuidadosamente temporizados. Cada botão do controle remoto gera uma determinada sequência de MARKs e SPACEs, e essa sequência segue regras definidas pelo protocolo utilizado pelo fabricante.
Neste projeto, vamos utilizar um ESP32, um receptor infravermelho e um display LCD 16x2 com interface I2C para construir um analisador capaz de capturar esses sinais.
A grande diferença em relação a um simples projeto de leitura de controle remoto é que nosso sistema não trabalha somente com protocolos conhecidos. Quando a biblioteca consegue reconhecer o sinal, o ESP32 apresenta informações como:
Quando o protocolo não é reconhecido, o ESP332 não descarta o sinal. Ele apresenta o sinal como DESCONHECIDO, calcula um identificador em forma de hash e, principalmente, mostra os tempos RAW de MARK e SPACE.
Isso permite estudar controles remotos proprietários e sinais que não possuem um protocolo diretamente reconhecido pela biblioteca. Esse recurso é particularmente útil para investigar controles de equipamentos como fitas LED, humidificadores, climatizadores e outros dispositivos que utilizam protocolos proprietários ou formatos que não são identificados automaticamente pela biblioteca.
Uma simples analisador de sinais IR que utiliza um ESP32 como microcontrolador e um display LCD. O LCD funciona como a interface principal do equipamento. O monitor serial pode ser utilizado principalmente durante o desenvolvimento e a análise dos sinais, nele é apresentado informações adicionais que não cabem no display.
No firmware utilizado neste projeto, os dados relevantes apresentados no LCD são exibidos em hexadecimal, justamente para facilitar a comparação com tabelas de códigos, documentação técnica e informações obtidas durante a análise.
A luz infravermelha, ou IR (Infrared), é uma forma de radiação eletromagnética que não pode ser percebida pelo olho humano. Nos controles remotos, um LED infravermelho é utilizado para transmitir informações para o equipamento controlado. O LED não fica simplesmente ligado durante toda a transmissão. Ele é acionado e desligado em intervalos extremamente rápidos.
Um receptor IR demodulador detecta essa transmissão e transforma a informação em um sinal digital que pode ser interpretado pelo ESP32. É importante entender que o receptor não entrega diretamente algo como:
BOTÃO = POWERO que chega ao microcontrolador é uma sequência temporal. Podemos representar simplificadamente:

A duração de cada MARK e SPACE é importante.
O MARK representa o período em que existe transmissão. No projeto, consideramos o MARK como o período em que o LED IR está ativo, enquanto o SPACE representa o intervalo sem emissão. Um comando completo é formado por várias dessas regiões. Por exemplo:
O protocolo determina quanto tempo cada uma dessas regiões deve durar.Dependendo dessas durações, o sistema pode interpretar a sequência como um determinado protocolo e, posteriormente, como um endereço e um comando. Para mais informações e exemplos dobre Controle e Receptor IR veja o artigo: Guia Completo do Controle Remoto IR + Receptor IR para Arduino veja também esse outro artigo: Entendendo os fundamentos das comunicações no infravermelho.
Um protocolo IR é um conjunto de regras utilizado para representar informações através dos pulsos infravermelhos. Existem diversos protocolos utilizados por fabricantes diferentes. Entre eles estão:
No nosso firmware, diversos desses decodificadores são habilitados explicitamente. O código utiliza DECODE_NEC, DECODE_SONY, DECODE_RC5, DECODE_RC6, DECODE_SAMSUNG, DECODE_LG, DECODE_JVC, DECODE_DENON, DECODE_PANASONIC, DECODE_KASEIKYO, além do decodificador genérico DECODE_DISTANCE_WIDTH e do mecanismo de hash. Isso permite que o analisador reconheça uma grande variedade de controles.
Um ponto importante é que dois controles podem utilizar o mesmo protocolo e ainda assim transmitir comandos diferentes. Por exemplo, dois equipamentos podem utilizar NEC, mas possuir diferentes endereços e diferentes comandos. Podemos imaginar:
CONTROLE A
Protocolo: NEC
Endereço: 0x10
Comando: 0x45e:
CONTROLE B
Protocolo: NEC
Endereço: 0x20
Comando: 0x45O protocolo é o mesmo, mas o endereço permite diferenciar os dispositivos. Por isso, o analisador apresenta separadamente:
Imagine que você encontre um controle remoto que não seja reconhecido pela biblioteca. Em vez de simplesmente mostrar:
UNKNOWNe parar por aí, nosso firmware captura informações adicionais. O resultado pode ser:
PROTOCOLO: DESCONHECIDO
HASH DO SINAL:
0x12345678
TRANSICOES:
0x0032Além disso, o monitor serial pode apresentar os tempos brutos da transmissão. Essa informação é muito mais valiosa do que simplesmente saber que o protocolo é desconhecido.
Existem situações em que queremos automatizar um equipamento, mas não temos acesso ao protocolo utilizado pelo controle remoto. Isso pode acontecer com:
Um exemplo prático é um controle remoto utilizado por determinado equipamento. Não devemos assumir que todo produto de um fabricante utiliza exatamente o mesmo formato de sinal. O procedimento mais seguro é capturar o sinal diretamente do controle utilizado no equipamento.
Se a biblioteca reconhecer o protocolo, teremos o endereço e o comando. Se não reconhecer, ainda teremos os dados RAW para análise.
Para montar o projeto, você precisará de:
Para desenvolver o projeto apresentado neste artigo serão utilizados alguns componentes, todos facilmente encontrados na loja da Eletrogate.
A ligação utilizada pelo firmware é:
| Componente | ESP32 |
|---|---|
| Receptor IR OUT | GPIO 17 |
| Receptor IR VCC | 3V3 |
| Receptor IR GND | GND |
| LCD SDA | GPIO 21 |
| LCD SCL | GPIO 22 |
| LCD VCC | Alimentação do módulo |
| LCD GND | GND |
/******************************************************************************* * ANALISADOR DE SINAIS INFRAVERMELHOS (IR) — ESP32 + LCD 16x2 I2C * ============================================================================ * Artigo para o blog Eletrogate. * ============================================================================ * @author Saulo Aislan ([email protected]) * @version 0.1 * @date 2026-09-19 * @copyright Copyright (c) 2026 */ #include <Arduino.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #define RAW_BUFFER_LENGTH 750 #define DECODE_NEC // NEC / Apple / Onkyo (o mais comum no Brasil) #define DECODE_SONY // Sony SIRC #define DECODE_RC5 // Philips RC5 #define DECODE_RC6 // Philips RC6 #define DECODE_SAMSUNG // Samsung #define DECODE_LG // LG #define DECODE_JVC // JVC #define DECODE_DENON // Denon / Sharp #define DECODE_PANASONIC // Panasonic #define DECODE_KASEIKYO // Familia Kaseikyo #define DECODE_DISTANCE_WIDTH // Decodificador generico Pulse Distance/Width #define DECODE_HASH // Ultimo recurso: gera um hash do sinal #include <IRremote.hpp> /* ============================================================================= * PINOS E PARAMETROS * ===========================================================================*/ #define IR_RECEIVE_PIN 17 // Pino de dados do receptor IR #define FEEDBACK_LED_PIN 27 // LED onboard: pisca a cada recepcao #define I2C_SDA_PIN 21 #define I2C_SCL_PIN 22 #define LCD_ADDRESS 0x3F // Use 0x27 se o scanner I2C indicar esse valor #define LCD_COLUMNS 16 #define LCD_ROWS 2 #define LCD_PAGE_TIME 2500 // Tempo (ms) de cada pagina no display #define LCD_MAX_PAGES 3 #define SHOW_RAW_TIMINGS true // false = nao imprime a lista de tempos brutos /* ============================================================================= * OBJETOS E ESTADO GLOBAL * ===========================================================================*/ LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS); uint32_t signalCount = 0; // Total de sinais novos capturados // Conteudo atualmente escrito no display (usado para espelhar no serial) char lcdBuffer[LCD_ROWS][LCD_COLUMNS + 1]; char lcdPages[LCD_MAX_PAGES][LCD_ROWS][LCD_COLUMNS + 1]; uint8_t lcdPageCount = 0; uint8_t lcdPageIndex = 0; uint32_t lcdPageTimer = 0; /* ============================================================================= * CAMADA DE EXIBICAO DO LCD * ----------------------------------------------------------------------------- * Escrevemos sempre 16 caracteres por linha (completando com espacos) em vez * de usar lcd.clear(). Isso evita o piscar da tela e apaga automaticamente os * restos da mensagem anterior. * ===========================================================================*/ void lcdLine(uint8_t row, const char *text) { char padded[LCD_COLUMNS + 1]; memset(padded, ' ', LCD_COLUMNS); padded[LCD_COLUMNS] = '\0'; size_t length = strlen(text); if (length > LCD_COLUMNS) { length = LCD_COLUMNS; // trunca o que nao cabe } memcpy(padded, text, length); lcd.setCursor(0, row); lcd.print(padded); strcpy(lcdBuffer[row], padded); } // Grava o conteudo de uma pagina (ainda nao exibe) void lcdSetPage(uint8_t page, const char *line1, const char *line2) { if (page >= LCD_MAX_PAGES) { return; } snprintf(lcdPages[page][0], LCD_COLUMNS + 1, "%s", line1); snprintf(lcdPages[page][1], LCD_COLUMNS + 1, "%s", line2); } void lcdRenderPage() { lcdLine(0, lcdPages[lcdPageIndex][0]); lcdLine(1, lcdPages[lcdPageIndex][1]); } // Ativa o conjunto de paginas recem-preenchido, comecando pela primeira void lcdStartPages(uint8_t pageCount) { lcdPageCount = pageCount; lcdPageIndex = 0; lcdPageTimer = millis(); lcdRenderPage(); } // Troca de pagina automaticamente, sem bloquear a recepcao void lcdUpdate() { if (lcdPageCount > 1 && (millis() - lcdPageTimer) >= LCD_PAGE_TIME) { lcdPageIndex = (lcdPageIndex + 1) % lcdPageCount; lcdPageTimer = millis(); lcdRenderPage(); } } // Tela de repouso, mostrada apenas ate a primeira leitura void lcdIdle() { lcdSetPage(0, "ANALISADOR IR", "Aguardando..."); lcdStartPages(1); } /* ============================================================================= * CAMADA DE LOG NO MONITOR SERIAL * ----------------------------------------------------------------------------- * Serial.printf() esta disponivel no core do ESP32 e deixa o log alinhado com * muito menos codigo que varios Serial.print() em sequencia. * ===========================================================================*/ void logRule(char character) { for (uint8_t i = 0; i < 52; i++){ Serial.print(character); } Serial.println(); } void logTitle(const char *title) { Serial.println(); logRule('='); Serial.printf(" %s\n", title); logRule('='); } // Imprime uma linha no formato " Rotulo . . . . . : valor (observacao)" void logField(const char *label, const char *value, const char *note = nullptr) { char dotted[21]; memset(dotted, ' ', sizeof(dotted) - 1); dotted[sizeof(dotted) - 1] = '\0'; size_t length = strlen(label); if (length > sizeof(dotted) - 1) { length = sizeof(dotted) - 1; } memcpy(dotted, label, length); // Pontilhado que guia o olho do rotulo ate o valor for (size_t i = length + 1; i < sizeof(dotted) - 1; i += 2) { dotted[i] = '.'; } if (note == nullptr) { Serial.printf(" %s : %s\n", dotted, value); } else { Serial.printf(" %s : %-14s %s\n", dotted, value, note); } } /* * Desenha no serial exatamente o que esta escrito no LCD. * E o recurso mais didatico do log: o leitor compara, lado a lado, o dado * bruto e a interface que o usuario final enxerga. */ void logLcdMirror() { Serial.println(); Serial.println(" Tela vista pelo usuario:"); Serial.println(" +----------------+"); Serial.printf(" |%s|\n", lcdBuffer[0]); Serial.printf(" |%s|\n", lcdBuffer[1]); Serial.println(" +----------------+"); } /* ============================================================================= * FERRAMENTA DE DIAGNOSTICO: SCANNER I2C * ----------------------------------------------------------------------------- * Modulos de LCD saem de fabrica no endereco 0x27 ou 0x3F. Trocar um pelo * outro e a causa numero 1 de "o display nao acende". * ===========================================================================*/ void scanI2C() { Serial.println(" Procurando dispositivos no barramento I2C..."); uint8_t found = 0; for (uint8_t address = 1; address < 127; address++) { Wire.beginTransmission(address); if (Wire.endTransmission() == 0) { Serial.printf(" -> encontrado em 0x%02X\n", address); found++; } } if (found == 0) { Serial.println(" -> nenhum dispositivo encontrado."); Serial.println(" Verifique SDA, SCL, GND e alimentacao."); } } /* ============================================================================= * TRATAMENTO DE CADA TIPO DE RESULTADO * ===========================================================================*/ /* * Caso 1: estouro de buffer. * O sinal tem mais transicoes do que RAW_BUFFER_LENGTH comporta. */ void handleOverflow() { logTitle("ERRO: ESTOURO DE BUFFER"); Serial.println(" O sinal recebido e maior que o buffer de captura."); Serial.printf(" RAW_BUFFER_LENGTH atual: %d transicoes\n", RAW_BUFFER_LENGTH); Serial.println(" Aumente esse valor e grave o firmware novamente."); Serial.println(" Controles de ar-condicionado podem exigir 1000 ou mais."); lcdSetPage(0, "ERRO: OVERFLOW", "Sinal > buffer"); lcdSetPage(1, "Aumente o valor", "RAW_BUFFER_LEN"); lcdStartPages(2); logLcdMirror(); logRule('='); } /* * Caso 2: repeticao. * Segurando a tecla, o controle envia quadros curtos de "repeat" em vez de * reenviar o comando. O LCD NAO e alterado: o usuario continua lendo o codigo * da tecla que pressionou. */ void handleRepeat() { Serial.println(" [repeticao] tecla mantida pressionada"); } /* * Caso 3: protocolo desconhecido. * Nenhum decodificador reconheceu o formato, mas os tempos brutos descrevem o * sinal por completo e permitem reproduzi-lo com sendRaw(). */ void handleUnknownProtocol() { const uint32_t hash = IrReceiver.decodedIRData.decodedRawData; const uint16_t rawLen = IrReceiver.decodedIRData.rawDataPtr->rawlen; char text[24]; logField("Protocolo", "DESCONHECIDO"); snprintf(text, sizeof(text), "0x%08lX", (unsigned long) hash); logField("Hash do sinal", text, "(identifica a tecla)"); snprintf(text, sizeof(text), "0x%04X", rawLen); logField("Transicoes", text, "(pares MARK/SPACE)"); // ---- LCD: o hash e o unico identificador disponivel neste caso --------- char line[LCD_COLUMNS + 1]; lcdSetPage(0, "PROTOCOLO:", "DESCONHECIDO"); snprintf(line, sizeof(line), "0x%08lX", (unsigned long) hash); lcdSetPage(1, "HASH DO SINAL:", line); snprintf(line, sizeof(line), "0x%04X", rawLen); lcdSetPage(2, "TRANSICOES:", line); lcdStartPages(3); logLcdMirror(); if (SHOW_RAW_TIMINGS) { Serial.println(); logRule('-'); Serial.println(" TEMPOS BRUTOS (microssegundos)"); Serial.println(" MARK = LED infravermelho ligado"); Serial.println(" SPACE = intervalo sem emissao"); logRule('-'); IrReceiver.printIRResultRawFormatted(&Serial, true); Serial.println(); Serial.println(" Array pronto para IrSender.sendRaw():"); IrReceiver.compensateAndPrintIRResultAsCArray(&Serial, true); } logRule('='); } /* * Caso 4: protocolo reconhecido. * Endereco, comando, quadro completo e numero de bits — todos em hexadecimal. */ void handleKnownProtocol() { const uint16_t address = IrReceiver.decodedIRData.address; const uint16_t command = IrReceiver.decodedIRData.command; const uint16_t bits = IrReceiver.decodedIRData.numberOfBits; const uint32_t rawData = IrReceiver.decodedIRData.decodedRawData; // No core do ESP32 (nao AVR) getProtocolString() retorna const char* const char *protocol = getProtocolString(IrReceiver.decodedIRData.protocol); char text[24]; // ---- Log serial --------------------------------------------------------- logField("Protocolo", protocol); snprintf(text, sizeof(text), "0x%04X", address); logField("Endereco", text, "(aparelho / fabricante)"); snprintf(text, sizeof(text), "0x%02X", command); logField("Comando", text, "(tecla pressionada)"); snprintf(text, sizeof(text), "0x%08lX", (unsigned long) rawData); logField("Quadro completo", text, "(endereco + comando)"); snprintf(text, sizeof(text), "0x%02X", bits); logField("Bits", text, "(tamanho do quadro)"); // ---- LCD: tudo em hexadecimal ------------------------------------------ char line0[LCD_COLUMNS + 1]; char line1[LCD_COLUMNS + 1]; // Pagina 1: protocolo e quadro completo snprintf(line0, sizeof(line0), "%s", protocol); snprintf(line1, sizeof(line1), "0x%08lX", (unsigned long) rawData); lcdSetPage(0, line0, line1); // Pagina 2: endereco e comando separados snprintf(line0, sizeof(line0), "ADDR: 0x%04X", address); snprintf(line1, sizeof(line1), "CMD: 0x%02X", command); lcdSetPage(1, line0, line1); // Pagina 3: tamanho do quadro snprintf(line0, sizeof(line0), "BITS: 0x%02X", bits); snprintf(line1, sizeof(line1), " %u bits", bits); lcdSetPage(2, line0, line1); lcdStartPages(3); logLcdMirror(); // ---- Como retransmitir -------------------------------------------------- Serial.println(); Serial.println(" Para retransmitir este sinal:"); Serial.print(" "); IrReceiver.printIRSendUsage(&Serial); logRule('='); } /* ============================================================================= * SETUP * ===========================================================================*/ void setup() { Serial.begin(115200); delay(500); // --- Barramento I2C e display -------------------------------------------- Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); pinMode(FEEDBACK_LED_PIN, OUTPUT); lcd.init(); lcd.backlight(); lcdSetPage(0, "ANALISADOR IR", "Inicializando..."); lcdStartPages(1); // --- Apresentacao -------------------------------------------------------- logTitle("ANALISADOR DE SINAIS INFRAVERMELHOS"); scanI2C(); Serial.println(); IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK, FEEDBACK_LED_PIN); char text[24]; snprintf(text, sizeof(text), "GPIO %d", IR_RECEIVE_PIN); logField("Receptor IR", text); snprintf(text, sizeof(text), "%d", RAW_BUFFER_LENGTH); logField("Buffer de captura", text, "(transicoes)"); snprintf(text, sizeof(text), "0x%02X", LCD_ADDRESS); logField("Endereco do LCD", text); Serial.print(" Protocolos ativos : "); printActiveIRProtocols(&Serial); Serial.println(); logRule('-'); Serial.println(" Aponte o controle para o receptor e pressione uma tecla."); Serial.println(" Todos os valores sao apresentados em HEXADECIMAL."); logRule('-'); lcdIdle(); } /* ============================================================================= * LOOP * ===========================================================================*/ void loop() { if (IrReceiver.decode()) { const bool wasOverflow = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_WAS_OVERFLOW; const bool isRepeat = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT; if (wasOverflow) { handleOverflow(); } else if (isRepeat) { handleRepeat(); } else { signalCount++; const uint32_t now = millis(); char title[48]; snprintf(title, sizeof(title), "SINAL #%lu | t = %lu.%lu s", (unsigned long) signalCount, (unsigned long) (now / 1000), (unsigned long) ((now % 1000) / 100)); logTitle(title); if (IrReceiver.decodedIRData.protocol == UNKNOWN) { handleUnknownProtocol(); } else { handleKnownProtocol(); } } IrReceiver.resume(); } lcdUpdate(); }
Um problema bastante comum em projetos com LCD I2C é utilizar o endereço errado. Existem módulos configurados em:
0x27e outros em:
0x3FPor isso, o firmware foi desenvolvido com um pequeno scanner I2C. Durante a inicialização, o ESP32 percorre os endereços do barramento e informa no monitor serial quais dispositivos foram encontrados. No firmware atual, o endereço configurado é:
#define LCD_ADDRESS 0x3Fe existe inclusive um comentário indicando que 0x27 deve ser utilizado caso esse seja o endereço identificado pelo scanner. Isso facilita bastante a montagem.
O scanner percorre os endereços possíveis do barramento:
for (uint8_t address = 1; address < 127; address++)Quando encontra um dispositivo, informa:
-> encontrado em 0x3FCaso nenhum dispositivo seja encontrado, o programa informa:
-> nenhum dispositivo encontrado.
Verifique SDA, SCL, GND e alimentacao.Essa pequena ferramenta evita uma das causas mais comuns de problemas em projetos com LCD I2C. Para mais informações sobre o display LCD e a conexão I2C veja esses nosso artigos: Guia Completo do Display LCD – Arduino e Simplificando a montagem de sensores no Esp e Arduíno para aulas Makers com I2C.
O LCD não é utilizado apenas para mostrar uma mensagem fixa. O firmware possui uma pequena camada de gerenciamento de páginas. Isso permite mostrar diferentes informações automaticamente. Por exemplo, para um protocolo conhecido, podemos ter:
NEC
0xBA45FF00Depois:
ADDR: 0x0000
CMD: 0x45E finalmente:
BITS: 0x20
32 bitsCada página permanece visível durante um determinado período. No firmware atual:
#define LCD_PAGE_TIME 2500
#define LCD_MAX_PAGES 3Ou seja, cada página é apresentada durante aproximadamente 2,5 segundos.
lcd.clear()?Um detalhe interessante do código é que ele não utiliza lcd.clear() a cada atualização. Em vez disso, a função lcdLine() preenche toda a linha com espaços antes de escrever o novo conteúdo. Isso evita que restos de mensagens anteriores permaneçam no display e também reduz o efeito de "piscar" da tela. O código mantém inclusive um buffer com o conteúdo atualmente exibido para poder reproduzi-lo no monitor serial.
O LCD é a interface principal do usuário. Porém, o monitor serial fornece muito mais espaço para informações. Por isso, o firmware cria um recurso interessante: ele desenha no monitor serial uma representação da tela do LCD. O resultado aparece aproximadamente assim:
Tela vista pelo usuario:+----------------+|NEC ||0xD52AC2EA |+----------------+
Isso torna a depuração mais fácil. É possível acompanhar no computador exatamente o que o usuário está vendo no display.
Com o circuito montado e o programa gravado no ESP32, abra o Monitor Serial em 115200 baud. Durante a inicialização, o projeto verifica o barramento I2C, inicializa o display LCD e prepara o receptor infravermelho para começar as capturas. Após a inicialização, o display apresenta a mensagem:

Agora basta apontar um controle remoto para o receptor infravermelho e pressionar uma tecla. O ESP32 captura o sinal e tenta identificar automaticamente o protocolo utilizado. Para isso, o firmware utiliza diversos decodificadores da biblioteca IRremote (podemos baixar no link IRremote), incluindo protocolos como NEC, Sony, RC5, RC6, Samsung, LG, JVC, Denon, Panasonic e Kaseikyo. Além desses protocolos, o projeto também possui recursos para analisar sinais que não sejam reconhecidos automaticamente, utilizando decodificação genérica e captura dos tempos RAW. Isso permite utilizar o mesmo analisador com controles remotos de diferentes fabricantes e equipamentos.
Quando a biblioteca consegue identificar o protocolo, o ESP32 apresenta no Monitor Serial as principais informações do comando recebido. Uma captura pode apresentar, por exemplo:
Protocolo : NEC
Endereco : 0xC2EA
Comando : 0x2A
Quadro completo : 0xD52AC2EA
Bits : 0x20No LCD, essas informações são distribuídas em três telas. Como o display possui apenas duas linhas, o firmware alterna automaticamente entre as páginas. A primeira tela apresenta o protocolo e o quadro completo:
A segunda apresenta o endereço e o comando:
E a terceira mostra a quantidade de bits do quadro:
Cada tela permanece no display durante aproximadamente 2,5 segundos antes de passar para a próxima. Dessa forma, mesmo utilizando um LCD 16x2, conseguimos visualizar as principais informações do comando capturado.
O protocolo informa qual padrão de comunicação foi identificado. Já o endereço e o comando são informações extraídas do quadro recebido. De forma geral, o endereço está relacionado à identificação do dispositivo, enquanto o comando representa a informação associada à tecla pressionada. Assim, diferentes teclas de um mesmo controle podem gerar comandos diferentes:
POWER → 0x45
VOL+ → 0x46
VOL- → 0x47Os valores acima são apenas exemplos. Os códigos reais dependem do controle remoto utilizado. O programa também apresenta o quadro completo, que pode ser utilizado para registrar e comparar os comandos capturados.
Nem todos os controles remotos utilizam um protocolo que pode ser identificado diretamente pela biblioteca. Isso pode acontecer com equipamentos que utilizam formatos próprios, como determinados controles de fitas LED, humidificadores, climatizadores e outros aparelhos. Quando nenhum dos decodificadores reconhece o sinal, o ESP32 não simplesmente descarta a captura. O firmware passa a tratar o sinal como desconhecido e apresenta informações que permitem continuar a análise.
No display será apresentada a indicação:
Em seguida, o ESP32 apresenta um hash associado ao sinal capturado:
Por fim, é apresentada a quantidade de transições encontradas:
Essas informações permitem identificar e comparar diferentes sinais mesmo quando o protocolo não foi reconhecido.
O hash apresentado pelo display não deve ser confundido com o código que será enviado ao equipamento. Ele funciona como uma forma de identificar a sequência capturada. Isso permite, por exemplo, comparar diferentes teclas de um mesmo controle:
POWER
Hash: 0x12345678LIGHT
Hash: 0x87654321Se os valores forem diferentes, sabemos que as capturas possuem diferenças. Entretanto, o hash não contém todas as informações necessárias para reproduzir o sinal. Para isso, precisamos analisar os tempos de cada pulso infravermelho. É aí que entram os dados RAW.

Quando o protocolo é desconhecido, o firmware imprime no Monitor Serial os tempos brutos da transmissão. Uma captura pode apresentar uma sequência semelhante a:
+9000 -4500
+560 -560
+560 -1690
+560 -560
...Esses valores representam a duração dos períodos de MARK e SPACE, em microssegundos. O MARK corresponde ao período em que o LED infravermelho está emitindo, enquanto o SPACE representa o intervalo sem emissão. O projeto apresenta essa explicação no Monitor Serial:
TEMPOS BRUTOS (microssegundos)
MARK = LED IR ligado
SPACE = LED IR desligado#define SHOW_RAW_TIMINGS trueCom essa opção habilitada, os dados brutos ficam disponíveis para análise e podem posteriormente ser utilizados para desenvolver uma rotina de retransmissão.
Imagine que você tenha um controle remoto cujo protocolo não seja reconhecido pela biblioteca. O ESP32 poderá informar:
PROTOCOLO:
DESCONHECIDO+9000 -4500
+560 -560
+560 -1690
+560 -560
...POWER
→ sequência A
LIGHT
→ sequência B
MODE
→ sequência CCom essas informações, podemos observar quais partes do sinal permanecem iguais e quais são alteradas de acordo com o comando. Essa característica é especialmente interessante para investigar controles que utilizam formatos proprietários ou que não são identificados diretamente pelos protocolos disponíveis na biblioteca IRremote.
Vamos imaginar um controle remoto de um humidificador ou climatizador. Pressionamos o botão POWER e o analisador informa que o protocolo é desconhecido. Em vez de descartar a captura, registramos o hash, o número de transições e os tempos RAW. Depois fazemos o mesmo com outros comandos:
| Tecla | Protocolo | Hash | Transições |
|---|---|---|---|
| POWER | Desconhecido | 0xD52AC2EA | 0x0002 |
| MODE | Desconhecido | 0xA134F133 | 0x0002 |
| FAN | Desconhecido | 0x12345678 | 0x0002 |
| TIMER | Desconhecido | 0xAAAAAAAA | 0x0002 |
| LIGHT | Desconhecido | 0xDADA324D | 0x0002 |
Além dessas informações, cada captura possui sua própria sequência RAW. Dessa maneira, o analisador pode ser utilizado como uma ferramenta para catalogar os comandos de um controle remoto, inclusive quando seu protocolo não é identificado automaticamente.
Uma das características mais interessantes da análise RAW é a possibilidade de utilizar os dados capturados posteriormente em um transmissor infravermelho. Para facilitar esse processo, o firmware utiliza a função:
IrReceiver.compensateAndPrintIRResultAsCArray(&Serial, true);Array pronto para IrSender.sendRaw():uint16_t rawData[] = {
9000, 4500,
560, 560,
560, 1690,
560, 560
};Esse array pode posteriormente ser utilizado com a função IrSender.sendRaw() da biblioteca IRremote para desenvolver um transmissor infravermelho. Dessa forma, mesmo quando o protocolo aparece como DESCONHECIDO, os dados capturados podem servir como base para reproduzir o comando.
Alguns controles remotos enviam um quadro especial quando uma tecla permanece pressionada. Em vez de transmitir novamente o comando completo, o controle pode enviar um quadro de repetição. O firmware identifica esse tipo de transmissão separadamente. Nesse caso, o Monitor Serial informa:
[repeticao] tecla mantida pressionadaO LCD não é alterado nesse caso. Dessa maneira, o usuário continua visualizando o comando original em vez de receber uma nova informação a cada quadro de repetição. Esse comportamento é especialmente útil durante os testes, pois evita que uma tecla mantida pressionada seja interpretada como várias teclas diferentes.
Os sinais infravermelhos podem possuir diferentes quantidades de transições. Alguns controles enviam quadros relativamente curtos, enquanto outros podem transmitir uma quantidade muito maior de informações. Para armazenar essas transições, o projeto utiliza inicialmente um buffer de 750 posições:
#define RAW_BUFFER_LENGTH 750Se o sinal recebido possuir mais transições do que o buffer consegue armazenar, o firmware identifica a situação e apresenta uma mensagem de erro. No LCD será mostrado:
ERRO: OVERFLOW
Sinal > bufferAumente o valor
RAW_BUFFER_LENO Monitor Serial também informa que o sinal recebido é maior que o buffer disponível e mostra o valor configurado atualmente. Em controles que transmitem quadros muito longos, como determinados controles de ar-condicionado, pode ser necessário aumentar o valor de RAW_BUFFER_LENGTH.
Vídeo demonstrando a captura dos dados lidos pelo sensor IR.
O projeto apresentado neste artigo vai além de simplesmente mostrar o código hexadecimal de um controle remoto. Com o ESP32, um receptor IR e um LCD 16x2, criamos uma ferramenta capaz de identificar protocolos conhecidos e investigar sinais que não são reconhecidos automaticamente pela biblioteca IRremote.
Nos protocolos conhecidos, temos acesso ao protocolo, endereço, comando, quadro completo e quantidade de bits. Já nos sinais desconhecidos, o projeto fornece o hash, o número de transições e, principalmente, os timings RAW de MARK e SPACE. A partir dessas informações, é possível estudar o funcionamento do controle e gerar um array em C para utilização posterior com sendRaw(). Isso abre possibilidades interessantes para quem deseja trabalhar com controles de fitas LED, humidificadores, climatizadores e outros equipamentos que utilizam sinais proprietários ou não identificados pelos decodificadores tradicionais.
O projeto também conta com recursos importantes para facilitar os testes, como scanner I2C, indicação visual de recepção através do LED de feedback, tratamento de comandos repetidos e detecção de sinais que ultrapassam o tamanho do buffer. A partir daqui, o próximo passo é transformar os sinais capturados em comandos utilizáveis por um transmissor IR, permitindo integrar equipamentos originalmente controlados apenas por infravermelho a projetos de automação e IoT com ESP32.
Para mais materiais como esse, continue acompanhando as postagens semanais do blog e não deixe de visitar nossa loja. Lá você encontra todos os componentes necessários para desenvolver esse e muitos outros projetos!
Que a força esteja com você! Até mais!
Aprenda a capturar comandos de controles remotos, identificar protocolos conhecidos e analisar sinais infravermelhos desconhecidos com ESP32.
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200