Tutoriais

Controlando sua TV com Keypad e Arduino Mega

Eletrogate 13 de junho de 2024

Introdução

Aprenda neste post como controlar uma TV com um Keypad e um Arduino Mega. Os comandos são inseridos no Keypad e enviados para a TV via LED infravermelho. Um Receptor Infravermelho capta os sinais do controle remoto, facilitado pela biblioteca IRremote para cadastro das teclas do controle remoto no Arduino para posterior envio ao clique no Keypad.

As teclas que serão cadastradas são números de canal e ajustes de volume. Será fornecido ao usuário do sistema um Feedback visual oferecido através de um LED indicativo para cada tipo diferente de ação e status do sistema. Além disso, o post inclui um vídeo demonstrativo para complementar a explicação teórica.


Biblioteca IRremote: envie e receba sinais infravermelhos com vários protocolos

A Biblioteca IRremote é uma ferramenta essencial para se trabalhar com a transmissão e recepção de sinais infravermelhos utilizando microcontroladores, como o Arduino. Essa biblioteca permite a criação de projetos que envolvem a comunicação por infravermelho (IR), abrindo um leque de possibilidades para o controle remoto de dispositivos eletrônicos, automação residencial, e até mesmo para o desenvolvimento de robôs. Através da IRremote, é possível enviar e receber sinais utilizando diversos protocolos, como NEC, Sony, RC5, Panasonic, entre outros, garantindo compatibilidade com uma ampla gama de dispositivos comerciais.

A documentação da biblioteca pode ser vista em seu repositório no Github: github.com/Arduino-IRremote/Arduino-IRremote.

Instalação

Arduino IDE

Para instalar a biblioteca IRremote para utilização na Arduino IDE, siga os passos abaixo:

  1. Com a Arduino IDE aberta, clique em Sketch ➜ Incluir Biblioteca ➜ Gerenciar Bibliotecas (Ctrl+Shift+I);
  2. Pesquise pela biblioteca IRremote e nos resultados da busca, procure pela biblioteca que tenha como autor z3t0 Armin Joachimsmeyer;
  3. Em seguida, no controle de versão da biblioteca, selecione a versão 2.5.0 (neste post é utilizada nesta versão, apesar de haver versões mais recentes).
  4. Após, clique em Instalar para a instalação da biblioteca na Arduino IDE.

PlatformIO

Para instalar a biblioteca IRremote para utilização em projetos no PlatformIO, siga os passos abaixo:

  1. Com um projeto PlatformIO criado (para instruções, acesse Como Programar o Arduino com VS Code e PlatformIO), acesse o arquivo platformio.ini:
  2. Dentro do arquivo platformio.ini, acrescente o comando lib_deps (caso ainda não exista. Este comando permite acrescentar dependências do projeto. Para mais detalhes, consulte a documentação em: Opção libdeps) ao final do arquivo:
  3. Dentro de lib_deps, adicione a biblioteca IRremote com a versão específica 2.5.0 (neste post é utilizada nesta versão, apesar de haver versões mais recentes): https://github.com/Arduino-IRremote/Arduino-IRremote.git#2.5.0
  4. Após isso, salve o arquivo modificado e automaticamente o PlatformIO irá adicionar a biblioteca no projeto.

Protocolos IR suportados

Esta biblioteca suporta os seguintes protocolos:

  • RC-5: desenvolvido pela Philips;
  • RC-6: sucessor do protocolo RC-5 desenvolvido pela Philips;
  • NEC: desenvolvido pela Renesas Electronics Corporation;
  • SONY: desenvolvido pela Sony Corporation;
  • PANASONIC: desenvolvido pela Panasonic Corporation;
  • JVC: desenvolvido pela JVC Kenwood;
  • SAMSUNG: desenvolvido pela Samsung Electronics;
  • WHYNTER: desenvolvido pela Whynter LLC;
  • AIWA_RC_T501: desenvolvido pela Aiwa;
  • LG: desenvolvido pela LG Electronics;
  • SANYO: desenvolvido pela Sanyo Electric Co., Ltd.;
  • MITSUBISHI: desenvolvido pela Mitsubishi Electric Corporation;
  • DISH: desenvolvido pela Dish Network Corporation;
  • SHARP: desenvolvido pela Sharp Corporation;
  • SHARP_ALT: uma alternativa do protocolo Sharp;
  • DENON: desenvolvido pela Denon Electronics;
  • LEGO_PF: protocolo Lego Power Functions;
  • BOSEWAVE: protocolo Bose Wave;
  • MAGIQUEST: protocolo MagiQuest.

Principais funções

  • class IRrecv: Classe para receber IR;
  • int decode(decode_results *results);: Tentativa de decodificar o sinal IR recebido recentemente;
    • Parâmetros:
      • results: instância retornando a decodificação, se houver.
    • Retorno:
      • o resultado da operação.
  • void enableIRIn();: Habilita a recepção IR;
  • void disableIRIn();: Desativa a recepção IR;
  • void resume();: para reativar a recepção IR;
  • bool isIdle();: Retorna o status da recepção;
    • Retorno:
      • true: se nenhuma recepção estiver em andamento.
  • class IRsend: Classe para enviar IR;
  • void sendRaw(const unsigned int buf[], unsigned int len, unsigned int hz);: utilizada para enviar códigos IR (infravermelhos) não codificados;
    • Parâmetros:
      • buf: um ponteiro para um array de inteiros que contém a sequência de tempos em microssegundos. Cada valor neste array representa a duração de um pulso ou de um espaço (tempo entre os pulsos) em microssegundos. A sequência alterna entre pulsos (emissor IR ligado) e espaços (emissor IR desligado).
      • len: indica o número de elementos no array buf.
      • hz: especifica a frequência da portadora IR em hertz. A maioria dos controles remotos utiliza frequências entre 30 e 60 kHz, sendo 38 kHz uma das frequências mais comuns.
  • void sendRC5(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo RC5;

    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código RC5 a ser enviado.
  • void sendRC6(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo RC6;

    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário;
      • nbits: indica o número de bits do código RC6 a ser enviado.
  • void sendNEC(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo NEC;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código NEC a ser enviado.
  • void sendSony(unsigned long data, int nbits);: utilizada para enviar códigos IR (infravermelhos) utilizando o protocolo Sony;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código Sony a ser enviado.
  • void sendPanasonic(unsigned int address, unsigned long data);: utilizada para enviar códigos IR utilizando o protocolo Panasonic;
    • Parâmetros:
      • address: um valor que representa o endereço do dispositivo Panasonic.
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
  • void sendJVC(unsigned long data, int nbits, bool repeat);: utilizada para enviar códigos IR utilizando o protocolo JVC;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código JVC a ser enviado.
      • repeat: um valor booleano que indica se o comando a ser enviado é uma repetição de um comando anterior. Se for verdadeiro (true), o comando será enviado como uma repetição. Se for falso (false), será enviado como um novo comando.
  • void sendSAMSUNG(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo Samsung;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código Samsung a ser enviado.
  • void sendWhynter(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo Whynter;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código Whynter a ser enviado.
  • void sendAiwaRCT501(int code);: utilizada para enviar códigos IR específicos para o controle remoto do modelo Aiwa RCT501;
    • Parâmetros:
      • code: um valor inteiro que representa o código a ser enviado pelo controle remoto. Este código é específico para o modelo Aiwa RCT501 e pode variar dependendo da função desejada (como aumentar o volume, trocar de canal, etc.).
  • void sendLG(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo LG;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código LG a ser enviado.
  • void sendDISH(unsigned long data, int nbits);: utilizada para enviar códigos IR (infravermelhos) utilizando o protocolo Diss;
    • Parâmetros:
      • data: um valor unsigned long que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código Dish a ser enviado.
  • void sendSharp(unsigned int address, unsigned int command);: utilizada para enviar códigos IR utilizando o protocolo Sharp;
    • Parâmetros:
      • address: um valor que representa o endereço do dispositivo Sharp.
      • command: um valor que representa o comando a ser enviado para o dispositivo Sharp.
  • void sendSharpAlt(unsigned int address, unsigned long command);: utilizada para enviar códigos IR utilizando uma alternativa do protocolo Sharp;
    • Parâmetros:
      • address: um valor unsigned int que representa o endereço do dispositivo Sharp.
      • command: um valor unsigned long que representa o comando a ser enviado para o dispositivo Sharp.
  • void sendDenon(unsigned long data, int nbits);: utilizada para enviar códigos IR utilizando o protocolo Denon;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o código IR em formato binário.
      • nbits: indica o número de bits do código Denon a ser enviado.
  • void sendLegoPowerFunctions(uint16_t data, bool repeat = true);: utilizada para enviar comandos IR utilizando o protocolo Lego Power Functions;
    • Parâmetros:
      • data: um valor que contém os dados a serem enviados. Este valor representa o comando IR em formato binário.
      • repeat (opcional): um valor booleano que indica se o comando a ser enviado é uma repetição de um comando anterior. Por padrão, é verdadeiro (true), o que significa que o comando será enviado como uma repetição. Se for definido como falso (false), o comando será enviado como um novo comando.
  • void sendBoseWave(unsigned char code);: utilizada para enviar códigos IR utilizando o protocolo Bose Wave;
    • Parâmetros:
      • code: um valor unsigned char que representa o código a ser enviado pelo controle remoto Bose Wave. Este código é específico para as funções suportadas pelo dispositivo Bose Wave, como ligar/desligar, ajustar o volume, trocar de faixa, etc.
  • void sendMagiQuest(unsigned long wand_id, unsigned int magnitude);: utilizada para enviar comandos IR utilizando o protocolo MagiQuest;
    • Parâmetros:
      • wand_id: um valor que representa o identificador único da varinha utilizada no jogo MagiQuest.
      • magnitude: um valor unsigned int que representa a magnitude do comando a ser enviado. Este valor pode variar dependendo do tipo de comando e da implementação específica do jogo MagiQuest.

Exemplo de Uso – Ler Comando

Abaixo segue um exemplo para ler o código hexadecimal IR de uma tecla de um controle remoto. Crie um projeto PlatformIO (instruções aqui) e cole o seguinte código em main.cpp e faça upload para a placa Arduino Mega:

/******************************************************************************
                Exemplo de Recepção de comandos IR usando IRremote

                         Criado em 06 de Junho de 2024
                     por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                          https://www.eletrogate.com/
******************************************************************************/

#include "Arduino.h"  // Inclui a biblioteca principal do Arduino (para compatibilidade com PlatformIO)
#include <IRremote.h> // Inclui a biblioteca IRremote para comunicação IR

#define IR_RECEIVE_PIN 11 // Define o pino de recebimento do IR como pino 11

IRrecv irrecv(IR_RECEIVE_PIN); // Inicializa o receptor IR no pino definido

// Protótipo das funções

String getProtocol(decode_type_t);

void setup()
{
  Serial.begin(115200); // Inicializa a comunicação serial com uma taxa de 115200 baud
  irrecv.enableIRIn();  // Ativa o receptor IR
}

void loop()
{
  decode_results results; // Declara uma variável para armazenar os resultados decodificados

  if (irrecv.decode(&results))
  { // Verifica se um sinal IR foi recebido, e se recebido, passa
    //                               os dados para a variável results

    // Se um sinal IR foi recebido, imprime os resultados
    Serial.print("Received ");
    Serial.print(results.bits, DEC); // Imprime o número de bits do sinal IR em decimal
    Serial.print(" bits ");
    Serial.print("Hex value: ");
    Serial.println(results.value, HEX); // Imprime o valor hexadecimal do sinal IR
    Serial.print("Protocol: ");
    Serial.println(getProtocol(results.decode_type)); // Imprime o protocolo do sinal IR
    Serial.println("");
    irrecv.resume(); // Reinicia o receptor IR para receber o próximo sinal
  }
}

// Função que retorna o nome do protocolo IR obtido à partir do parâmetro informado
String getProtocol(decode_type_t decode_type) 
{
  switch (decode_type)
  {
  case UNKNOWN:
    return "UNKNOWN";
    break;
  case UNUSED:
    return "UNUSED";
    break;
  case RC5:
    return "RC5";
    break;
  case RC6:
    return "RC6";
    break;
  case NEC:
    return "NEC";
    break;
  case SONY:
    return "SONY";
    break;
  case PANASONIC:
    return "PANASONIC";
    break;
  case JVC:
    return "JVC";
    break;
  case SAMSUNG:
    return "SAMSUNG";
    break;
  case WHYNTER:
    return "WHYNTER";
    break;
  case AIWA_RC_T501:
    return "AIWA_RC_T501";
    break;
  case LG:
    return "LG";
    break;
  case SANYO:
    return "SANYO";
    break;
  case MITSUBISHI:
    return "MITSUBISHI";
    break;
  case DISH:
    return "DISH";
    break;
  case SHARP:
    return "SHARP";
    break;
  case SHARP_ALT:
    return "SHARP_ALT";
    break;
  case DENON:
    return "DENON";
    break;
  case LEGO_PF:
    return "LEGO_PF";
    break;
  case BOSEWAVE:
    return "BOSEWAVE";
    break;
  case MAGIQUEST:
    return "MAGIQUEST";
    break;
  }
}

O seguinte código adicional também é necessário em platformio.ini:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino
monitor_speed = 115200
lib_deps = 
    https://github.com/Arduino-IRremote/Arduino-IRremote.git#2.5.0

Hardware

Monte o circuito com seu Arduino Mega de acordo com o seguinte esquemático:

Demonstração de Funcionamento

Veja no GIF abaixo a saída do Monitor Serial do PlatformIO. Observe que ao clicar em uma tecla de um controle remoto apontando para o Receptor Infravermelho o código hexadecimail é impresso na tela. Caso fique pressionando a mesma tecla sem solta-la, o código hexadecimal fica como 0xFFFFFFFF.

Exemplo de Uso – Enviar Comando

Abaixo segue um exemplo para enviar um código hexadecimal IR através de emissor infravermelho. Crie um projeto PlatformIO (instruções aqui) e cole o seguinte código em main.cpp e faça upload para a placa Arduino Mega.

Nota: o código hexadecimal 0xFFD827 que está na variável hexValue foi obtido através do exemplo anterior (Exemplo de Uso – Ler Comando) após o pressionamento de uma tecla em um controle remoto de TV:

/******************************************************************************
                  Exemplo de Envio de comando IR usando IRremote

                         Criado em 06 de Junho de 2024
                     por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                          https://www.eletrogate.com/
******************************************************************************/

#include "Arduino.h"  // Inclui a biblioteca principal do Arduino (para compatibilidade com PlatformIO)
#include <IRremote.h> // Inclui a biblioteca IRremote para comunicação IR

IRsend irsend;

void setup()
{
  Serial.begin(115200); // Inicializa a comunicação serial com uma taxa de 115200 baud
  delay(100);
  
  Serial.println(F("START " __FILE__ " from " __DATE__));
  Serial.print(F("Ready to send IR signals at pin "));
  Serial.println(IR_SEND_PIN); // mostra na Serial o pino que está o emissor infravermelho
}

void loop()
{
  unsigned long hexValue = 0xFFD827; // Define o valor hexadecimal a ser enviado
  Serial.print("Sending Hex value: "); 
  Serial.println(hexValue, HEX); // Imprime o valor hexadecimal no monitor serial
  
  irsend.sendNEC(hexValue, 32); // Envia o sinal IR com o protocolo NEC e 32 bits
  
  delay(1000); // espera 1 segundo
}

O seguinte código adicional também é necessário em platformio.ini:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino
monitor_speed = 115200
lib_deps = 
    https://github.com/Arduino-IRremote/Arduino-IRremote.git#2.5.0

Hardware

Monte o circuito com seu Arduino Mega de acordo com o seguinte esquemático:

Demonstração de Funcionamento

Veja no GIF abaixo a saída do Monitor Serial do PlatformIO:


Biblioteca EEPROM-Storage: facilite o acesso de variáveis ​​armazenadas na EEPROM

A Biblioteca EEPROM-Storage é uma ferramenta poderosa que simplifica o acesso e o gerenciamento de variáveis armazenadas na EEPROM em plataformas Arduino. Com esta biblioteca, é possível tratar variáveis armazenadas na EEPROM da mesma forma que tratamos variáveis na RAM, tornando o desenvolvimento de projetos que exigem a preservação de dados entre reinicializações muito mais intuitivo e eficiente. Essa funcionalidade é essencial para aplicações onde a persistência de dados é crucial, como em sistemas de automação residencial, contadores de tempo, registros de eventos, entre outros.

Um dos principais benefícios da Biblioteca EEPROM-Storage é a simplicidade com que se pode definir e manipular variáveis. Normalmente, trabalhar com EEPROM requer um cuidado especial para ler e escrever dados de forma precisa e eficiente. No entanto, com esta biblioteca, o processo é simplificado ao ponto de ser quase idêntico ao manuseio de variáveis comuns em RAM.

A documentação da biblioteca pode ser vista em seu repositório no Github: github.com/porrey/EEPROM-Storage.

Instalação

Arduino IDE

Para instalar a biblioteca EEPROM-Storage para utilização na Arduino IDE, siga os passos abaixo:

  1. Com a Arduino IDE aberta, clique em Sketch ➜ Incluir Biblioteca ➜ Gerenciar Bibliotecas (Ctrl+Shift+I);
  2. Pesquise pela biblioteca EEPROM-Storage e nos resultados da busca, procure pela biblioteca que tenha como autor Daniel M. Porrey;
  3. Em seguida, clique em Instalar para a instalação da biblioteca na Arduino IDE.

PlatformIO

Para instalar a biblioteca EEPROM-Storage para utilização em projetos no PlatformIO, siga os passos abaixo:

  1. Com um projeto PlatformIO criado (para instruções, acesse Como Programar o Arduino com VS Code e PlatformIO), acesse o arquivo platformio.ini:
  2. Dentro do arquivo platformio.ini, acrescente o comando lib_deps (caso ainda não exista. Este comando permite acrescentar dependências do projeto. Para mais detalhes, consulte a documentação em: Opção libdeps) ao final do arquivo:
  3. Dentro de lib_deps, adicione a biblioteca EEPROM-Storage modificada para compatibilidade com PlatformIO: https://github.com/MicSG-dev/EEPROM-Storage-PlatformIO:
  4. Após isso, salve o arquivo modificado e automaticamente o PlatformIO irá adicionar a biblioteca no projeto.

Uso

Declaração

Uma instância de variável EEPROMStorage pode ser declarada globalmente, dentro de uma classe ou dentro de uma função.

É possível instanciar mais de uma instância utilizando o mesmo endereço, sendo que ambas as instâncias compartilharão o mesmo valor, mantendo as duas instâncias sincronizadas. Tenha cuidado com esse cenário, pois se duas instâncias forem criadas com um tipo base diferente, o resultado poderá ser inesperado.

A sintaxe para declaração é  EEPROMStorage<tipoDoDado> nomeVariavel(endereco, valorPadrao);, onde:

  • tipoDoDado: especifica o tipo de dados para a instância;
  • nomeVariavel: especifica o nome da variável;
  • endereco: especifica o índice inicial do endereço na EEPROM onde o valor será armazenado;
  • valorPadrao: especifica o valor que será retornado pela instância quando o local da memória EEPROM não tiver sido inicializado (a inicialização é determinada pela soma de verificação. Veja mais sobre soma de verificação abaixo).
Exemplo de Declaração

Para inicializar uma instância de variável com o nome variavelQualquer, com um tipo de dados int, localizado na posição inicial 50 da EEPROM e que tenha um valor padrão de 10 (caso não inicializado), a sintaxe seria a seguinte:

EEPROMStorage<int> variavelQualquer(50, 10);

Soma de Verificação e Definição do Endereço da Próxima Variável

Ao definir uma variável de armazenamento EEPROM, é importante compreender o número de bytes necessários para o tipo utilizado e garantir que as variáveis ​​sejam espaçadas adequadamente para que não colidam.

Cada variável requer memória suficiente para armazenar o tipo utilizado incrementado de mais um byte adicional para uma soma de verificação.

Considere a seguinte declaração de variável:

EEPROMStorage<uint8_t> variavel1(0, 0);

Nesta declaração de variável, a variável possui endereço inicial 0 (zero). Para definir o endereço de outra possível variável que será instanciada posteriormente à essa, é utilizada a seguinte fórmula matemática:

nextAddressVar = sizeVar + checksum + currentAddressVar, onde:

  • nextAddressVar: o resultado do cálculo, sendo este o endereço da próxima variável à ser gravada na memória
  • sizeVar: o tamanho do tipo da variável;
  • checksum: byte adicional para uma soma de verificação (possui sempre o valor 1);
  • currentAddressVar: endereço da variável atual.

Para descobrir o tamanho do tipo da variável, é necessário seguir os seguintes passos:

  1. Abra alguma ferramenta que permita executar um algoritmo Arduino. Há 3 opções:
    • um sketch na Arduino IDE; ou
    • um projeto PlatformIO; ou
    • acesse o simulador Wokwi.
  2. Cole o seguinte algoritmo Arduino:
    #include <Arduino.h>
    
    #define tipoVariavel String // Altere aqui o tipo de dado
    
    void setup() {
      Serial.begin(115200);
      Serial.print("O tamanho do tipo da variável é ");
      Serial.print(sizeof(tipoVariavel));
      Serial.println(" bytes.");
    }
    
    void loop() {
      delay(10);
    }
    
  3. Altere, na linha 2 do código, o tipo de dado ao qual você deseja saber o tamanho (Por exemplo, no exemplo acima, para saber o tamanho do tipo da variável variavel1, substitua String por uint8_t);
  4. Carregue o sketch na placa (ou inicie a simulação, caso tenha escolhida como ferramenta a opção Wokwi).
  5. Abra o Monitor Serial e veja o resultado.

Saída do Monitor Serial do Wokwi

No exemplo anterior, o tipo de dado da variável variavel1 é o uint8_t, o qual possui possui o tamanho de 1 byte.

Aplicando a fórmula matemática anterior, o endereço da próxima variável à ser gravada na memória é 2. Veja o cálculo:

nextAddressVar = sizeVar + checksum + currentAddressVar
nextAddressVar = 1 + 1 + 0
nextAddressVar = 2

A instanciação da próxima variável seria assim:

EEPROMStorage<uint8_t> variavel2(2, 0);

Nota: Não é necessário alinhar variáveis ​​no início da memória, ou seja, posicioná-las consecutivamente. Isso apenas torna mais fácil a implementação. Você é livre para organizá-los da maneira que melhor atenda às suas necessidades.

Mais exemplos de instanciação de variáveis com definição do endereço da próxima variável

Veja abaixo como seria a continuação da instanciação das próximas variáveis após variavel1 (variável do exemplo anterior):

// Fórmula para próximo endereço de variável: nextAddressVar = sizeVar + checksum + currentAddressVar
EEPROMStorage<uint8_t> variavel1(0, 0); // endereço 0. tamanho 1 byte. Próx. endereço: 1 + 1 + 0  = 2
EEPROMStorage<uint8_t> variavel2(2, 0); // endereço 2. tamanho 1 byte. Próx. endereço: 1 + 1 + 2  = 4
EEPROMStorage<uint16_t> variavel3(4, 0); // endereço 4. tamanho 2 bytes. Próx. endereço: 2 + 1 + 4  = 7
EEPROMStorage<uint32_t> variavel4(7, 0); // endereço 7. tamanho 4 bytes. Próx. endereço: 4 + 1 + 7  = 12
EEPROMStorage<float> variavel5(12, 0.0); // endereço 12. tamanho 4 bytes. Próx. endereço: 4 + 1 + 12  = 17
EEPROMStorage<bool> variavel6(17, false); // endereço 17. tamanho 1 byte. Próx. endereço: 1 + 1 + 17  = 19

Atribuindo

Atribuição – como variável

Usando o exemplo anterior, atribuir um valor à instância é tão simples quanto a seguinte atribuição mostrada:

variavelQualquer = 100; // define o valor 100 para a variável

Atribuição – como método

A biblioteca EEPROMStorage também disponibiliza um método para fazer atribuição do valor:

void set(T const& value) const, onde:
  • value: o valor que será salvo na memória EEPROM.

Exemplo:

variavelQualquer.set(100); // define o valor 100 para a variável

Recuperando

Recuperar – como variável

Para obter o valor da instância de uma variável, atribua-o a uma variável, conforme mostrado abaixo:

int outraVariavel = variavelQualquer;

ou passe-o como argumento em qualquer função que receba um argumento int conforme mostrado abaixo:

Serial.print("variavelQualquer= "); Serial.println(variavelQualquer);

Recuperar – como método

A biblioteca EEPROMStorage também disponibiliza um método que pode ser usado para fazer recuperação do valor:

int outraVariavel = variavelQualquer.get();

Restrições

Limites de gravação

Os chips em que a memória EEPROM fica alocada possuem limites de gravações. Por exemplo, o chip ATmega328P (do Arduino Uno) especifica um limite de 100000 (cem mil) gravações por localização de endereço. Por esta questão siga as seguintes recomendações:

  • evite gravações excessivas em uma variável EEPROM;
  • evite escritas das variáveis EEPROM em loops (estruturas de repetição, como for, while, etc.) onde a mesma variável EEPROM é atualizada diversas vezes. Em vez disso, salve o valor uma vez no final do loop;
  • conheça os limites de gravação para seu microcontrolador específico de seu projeto. Escreva seu código de forma que você não exceda o limite durante a vida útil do seu aplicativo.

Velocidade

A velocidade de leitura e gravação da EEPROM é muito mais lenta que a da memória flash. Verifique e otimize o uso de variáveis ​​EEPROM para não afetar o desempenho (velocidade) de seu algoritmo Arduino.

Escrevendo Valores

Ao gravar um valor em uma variável EEPROM e você tentar gravar o mesmo valor novamente, a biblioteca não executará a segunda gravação (pois o valor não é alterado). Lembre-se de que há código extra para realizar essa verificação, o que pode causar alguns problemas de desempenho em seu aplicativo.

Lendo Valores

Ler um valor da EEPROM é mais rápido do que escrever um valor na EEPROM, mas a leitura da EEPROM é mais lenta do que a leitura de uma variável da memória flash.


Esquemático do Projeto

Monte o circuito com seu Arduino Mega de acordo com o seguinte esquemático:


Sketch do Projeto

Crie um projeto PlatformIO (instruções aqui) e cole o seguinte código em main.cpp. Lembre-se também de colar o código de platformio.ini no arquivo de mesmo nome do seu projeto.

Antes de fazer upload para sua placa Arduino Mega, é necessário alterar o protocolo de seu Controle Remoto da sua TV:

  • Na linha 19 de main.cpp, na definição de protocolo, altere para o protocolo IR do controle remoto de sua TV. Neste exemplo, o controle de minha TV possuía o protocolo NEC. Para descobrir o protocolo de seu controle remoto carregue o sketch do Exemplo de Uso IRremote – Ler Comando e veja o protocolo na saída do Monitor Serial após apertar uma tecla de seu controle remoto;

Após a alteração, faça upload para sua placa Arduino Mega.

Código a ser colado no arquivo main.cpp:

/******************************************************************************
                      Controle sua TV com Keypad e Arduino Mega

                                  Sketch Principal

                         Criado em 11 de Março de 2024
                     por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                          https://www.eletrogate.com/
******************************************************************************/

// Inclusão das Bibliotecas
#include <Arduino.h>        // Inclui a biblioteca padrão do Arduino
#include "IRremote.h"       // Inclui a biblioteca IRremote para controle remoto
#include "EEPROM-Storage.h" // Inclui a biblioteca EEPROM-Storage para armazenamento em EEPROM
#include "Keypad.h"         // Inclui a biblioteca Keypad para uso de teclado matricial

#define protocolo NEC // Define o protocolo como NEC

// Estrutura para armazenar códigos de teclas da TV
struct TeclasTv
{
  unsigned long _1 = 999;         // Código da tecla 1
  unsigned long _2 = 999;         // Código da tecla 2
  unsigned long _3 = 999;         // Código da tecla 3
  unsigned long _4 = 999;         // Código da tecla 4
  unsigned long _5 = 999;         // Código da tecla 5
  unsigned long _6 = 999;         // Código da tecla 6
  unsigned long _7 = 999;         // Código da tecla 7
  unsigned long _8 = 999;         // Código da tecla 8
  unsigned long _9 = 999;         // Código da tecla 9
  unsigned long _0 = 999;         // Código da tecla 0
  unsigned long _asterisco = 999; // Código da tecla asterisco
  unsigned long _cerquilha = 999; // Código da tecla cerquilha
};

const byte ROWS = 4; // Quatro linhas do teclado
const byte COLS = 4; // Quatro colunas do teclado

char keys[ROWS][COLS] = {  // Definição das teclas no teclado matricial
    {'1', '2', '3', 'A'},  // Linha 1
    {'4', '5', '6', 'B'},  // Linha 2
    {'7', '8', '9', 'C'},  // Linha 3
    {'*', '0', '#', 'D'}}; // Linha 4

byte rowPins[ROWS] = {10, 12, 8, 7}; // Pinos das linhas do teclado
byte colPins[COLS] = {6, 5, 4, 3};   // Pinos das colunas do teclado

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); // Inicializa o objeto keypad com as configurações fornecidas

#define IR_RECEIVE_PIN 11 // Define o pino de recebimento do IR como pino 11

IRsend irsend; // Criação do objeto para envio de comandos IR

// Definições de armazenamento em EEPROM para códigos de teclas da TV e de tamanho do código de tecla
// -----
// ----- Cálculo do endereço de variável na EEPROM:
// -----    sizeVar + checksum + previousAddressVar = nextAddressVar
// -----
// ----- Variáveis da Fórmula:
// -----    sizeVar: tamanho da variável;
// -----    checksum: byte adicional para uma soma de verificação
// -----    currentAddressVar: endereço da variável atual;
// -----    nextAddressVar: o resultado do cálculo, sendo este o endereço da próxima variável à ser gravada na memória
// -----
EEPROMStorage<int> tamanhoCode(0, 999);               // variável armazenada na EEPROM no endereço 0 e que tem 2 bytes.  Cálculo endereço próxima variável: 2 + 1 + 0  = 3 bytes
EEPROMStorage<unsigned long> tecla1(3, 999);          // variável armazenada na EEPROM no endereço 3 e que tem 4 bytes.  Cálculo endereço próxima variável: 4 + 1 + 3  = 8 bytes
EEPROMStorage<unsigned long> tecla2(8, 999);          // variável armazenada na EEPROM no endereço 8 e que tem 4 bytes.  Cálculo endereço próxima variável: 4 + 1 + 8  = 13 bytes
EEPROMStorage<unsigned long> tecla3(13, 999);         // variável armazenada na EEPROM no endereço 13 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 13 = 18 bytes
EEPROMStorage<unsigned long> tecla4(18, 999);         // variável armazenada na EEPROM no endereço 18 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 18 = 23 bytes
EEPROMStorage<unsigned long> tecla5(23, 999);         // variável armazenada na EEPROM no endereço 23 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 23 = 28 bytes
EEPROMStorage<unsigned long> tecla6(28, 999);         // variável armazenada na EEPROM no endereço 28 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 28 = 33 bytes
EEPROMStorage<unsigned long> tecla7(33, 999);         // variável armazenada na EEPROM no endereço 33 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 33 = 38 bytes
EEPROMStorage<unsigned long> tecla8(38, 999);         // variável armazenada na EEPROM no endereço 38 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 38 = 43 bytes
EEPROMStorage<unsigned long> tecla9(43, 999);         // variável armazenada na EEPROM no endereço 43 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 43 = 48 bytes
EEPROMStorage<unsigned long> tecla0(48, 999);         // variável armazenada na EEPROM no endereço 48 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 48 = 53 bytes
EEPROMStorage<unsigned long> teclaAsterisco(53, 999); // variável armazenada na EEPROM no endereço 53 e que tem 4 bytes. Cálculo endereço próxima variável: 4 + 1 + 53 = 58 bytes
EEPROMStorage<unsigned long> teclaCerquilha(58, 999); // variável armazenada na EEPROM no endereço 58 e que tem 4 bytes.

unsigned long getTeclaControleRemoto();            // Declaração da função para obter código de tecla do controle remoto
void enviarComando(unsigned long data, int nbits); // Declaração da função para enviar comando IR

const int pinoLed = 2; // Define o pino do LED como pino 2

void setup()
{
  pinMode(pinoLed, OUTPUT); // define o pino do LED como saída

  // algoritmo para o STATUS DO LED (Apagado): sem fazer nenhuma ação, esperando o usuário executar algo;
  // o LED fica apagado
  digitalWrite(pinoLed, LOW); // inicia o led no modo APAGADO

  Serial.begin(115200); // Define a taxa de baudrate da interface Serial com 115200 bits por segundo
}

unsigned long timerLED;       // timer para controle de status (aceso/apagado) do LED indicador
const int tempoLimite = 5000; // tempo limite para operações no teclado (5 segundos)
unsigned long timerTeclado;   // timer para controle de tempo limite em operações no teclado

void loop()
{
  char teclaPressionada = keypad.getKey(); // obtém a tecla pressionada pelo usuário

  if (teclaPressionada != NO_KEY) // se alguma tecla foi pressionada, ...
  {

    // algoritmo para o STATUS DO LED (Pisca 1 vez e apaga): ao usuário pressionar algum botão (será aceso o LED) e após soltar o botão (será apagado o LED);
    digitalWrite(pinoLed, HIGH);
    timerLED = millis();
    while (millis() - timerLED <= 250) // durante 250 milissegundos o led ficará acesso
    {
      delay(1);
    }
    digitalWrite(pinoLed, LOW);

    // verifica qual tecla foi pressioanda e executa algum algoritmo dependendo da tecla pressionada
    switch (teclaPressionada)
    {
    case 'A': // caso seja a tecla A, entra no modo de cadastro de códigos IR nas teclas
    {

      timerTeclado = millis();      // armazena o valor atual do temporizador millis
      bool permanecerNoMenu = true; // variável que controla se o loop do 'menu' deve ser interrompido ou não
      Serial.println("Cadastrar teclas. Pressione alguma tecla de 0 a 9, * ou # para iniciar o cadastramento.");

      TeclasTv teclasTv; // instancia a estrutura que armazenará as tecals da TV

      while (permanecerNoMenu) // loop do 'menu' de cadastro de códigos IR nas teclas
      {

        // algoritmo para o STATUS DO LED (Pisca-pisca lento): Cadastrando novas teclas;
        // a cada 1 segundo (1000 milissegundos) o LED inverte seu estado: ora fica aceso, ora fica apagado
        if (millis() - timerLED >= 1000)
        {
          digitalWrite(pinoLed, !digitalRead(pinoLed));
          timerLED = millis();
        }

        // obtém a tecla pressionada pelo usuário
        teclaPressionada = keypad.getKey();

        if (teclaPressionada != NO_KEY) // se alguma tecla foi pressionada, ...
        {
          unsigned long tecla;      // variável para armazenar a tecla do controle da TV pressionada pelo usuário
          switch (teclaPressionada) // se a tecla pressionada do Keypad pelo usuário
          {
          case 'B': // caso a tecla pressionada do Keypad seja a B (FUNÇÃO: Salvar teclas modificadas), ...

            timerLED = millis(); // atualiza o valor do temporizador de controle do estado do LED
            Serial.println("Teclas modificadas foram salvas. Saindo do menu...");

            // algoritmo para o LED indicador de status: Aceso por um período de tempo, Teclas modificadas salvas;
            // durante 2 segundos (2000 millisegundos) o LED permanece aceso. Após este período, o led apaga
            digitalWrite(pinoLed, HIGH);
            while (millis() - timerLED <= 2000)
            {
              delay(1);
            }
            digitalWrite(pinoLed, LOW);

            // se o membro _0 (tecla 0) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._0 != 999)
            {
              tecla0 = teclasTv._0;
            }

            // se o membro _1 (tecla 1) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._1 != 999)
            {
              tecla1 = teclasTv._1;
            }

            // se o membro _2 (tecla 2) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._2 != 999)
            {
              tecla2 = teclasTv._2;
            }

            // se o membro _3 (tecla 3) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._3 != 999)
            {
              tecla3 = teclasTv._3;
            }

            // se o membro _4 (tecla 4) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._4 != 999)
            {
              tecla4 = teclasTv._4;
            }

            // se o membro _5 (tecla 5) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._5 != 999)
            {
              tecla5 = teclasTv._5;
            }

            // se o membro _6 (tecla 6) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._6 != 999)
            {
              tecla6 = teclasTv._6;
            }

            // se o membro _7 (tecla 7) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._7 != 999)
            {
              tecla7 = teclasTv._7;
            }

            // se o membro _8 (tecla 8) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._8 != 999)
            {
              tecla8 = teclasTv._8;
            }

            // se o membro _9 (tecla 9) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._9 != 999)
            {
              tecla9 = teclasTv._9;
            }

            // se o membro _asterisco (tecla diminuir volume) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._asterisco != 999)
            {
              teclaAsterisco = teclasTv._asterisco;
            }

            // se o membro _cerquilha (tecla aumentar volume) da estrutura TeclasTv possuir um valor diferente de 999 (alterado), salva na EEPROM o seu valor
            if (teclasTv._cerquilha != 999)
            {
              teclaCerquilha = teclasTv._cerquilha;
            }

            // reseta os valores dos membros da estrutura TeclasTv
            teclasTv._0 = 999;
            teclasTv._1 = 999;
            teclasTv._2 = 999;
            teclasTv._3 = 999;
            teclasTv._4 = 999;
            teclasTv._5 = 999;
            teclasTv._6 = 999;
            teclasTv._7 = 999;
            teclasTv._8 = 999;
            teclasTv._9 = 999;
            teclasTv._0 = 999;
            teclasTv._asterisco = 999;
            teclasTv._cerquilha = 999;

            permanecerNoMenu = false; // interrompe o loop do 'menu' (sair fora do menu)
            break;
          case '1':
            Serial.println("Pressione a tecla 1 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._1 = tecla;
            }
            break;
          case '2':
            Serial.println("Pressione a tecla 2 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._2 = tecla;
            }
            break;
          case '3':
            Serial.println("Pressione a tecla 3 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._3 = tecla;
            }
            break;
          case '4':
            Serial.println("Pressione a tecla 4 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._4 = tecla;
            }
            break;
          case '5':
            Serial.println("Pressione a tecla 5 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._5 = tecla;
            }
            break;
          case '6':
            Serial.println("Pressione a tecla 6 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._6 = tecla;
            }
            break;
          case '7':
            Serial.println("Pressione a tecla 7 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              Serial.print("0x");
              Serial.println(tecla, HEX);
              teclasTv._7 = tecla;
            }
            break;
          case '8':
            Serial.println("Pressione a tecla 8 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._8 = tecla;
            }
            break;
          case '9':
            Serial.println("Pressione a tecla 9 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._9 = tecla;
            }
            break;
          case '0':
            Serial.println("Pressione a tecla 0 do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._0 = tecla;
            }
            break;
          case '*':
            Serial.println("Pressione a tecla ABAIXAR VOLUME do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._asterisco = tecla;
            }
            break;
          case '#':
            Serial.println("Pressione a tecla AUMENTAR VOLUME do controle remoto");
            tecla = getTeclaControleRemoto();

            if (tecla != 999)
            {
              teclasTv._cerquilha = tecla;
            }
            break;
          }
        }
      }
      digitalWrite(pinoLed, LOW);
      break;
    }
    case 'C': // caso seja a tecla C, entra no modo de exclusão de todos os códigos IR das teclas cadastrados
    {

      timerLED = millis(); // Armazena o tempo atual em milissegundos

      // Exibe mensagem informando que todos os cadastros de teclas estão sendo excluídos
      Serial.println("Excluir cadastro de todas teclas");

      // algoritmo para o STATUS DO LED (Pisca-pisca rápido): Limpando toda memória contendo o cadastro de teclas.
      // a cada 100 milissegundos o LED inverte seu estado: ora fica aceso, ora fica apagado
      while (millis() - timerLED <= 2000)
      {
        for (int i = 0; i < 25; i++) // 2,5 segundos
        {
          delay(100);
          digitalWrite(pinoLed, !digitalRead(pinoLed));
        }
      }
      digitalWrite(pinoLed, LOW);

      // Reseta os códigos IR das teclas para o valor 999, indicando que estão "vazios"
      tecla1 = 999;
      tecla2 = 999;
      tecla3 = 999;
      tecla4 = 999;
      tecla5 = 999;
      tecla6 = 999;
      tecla7 = 999;
      tecla8 = 999;
      tecla9 = 999;
      tecla0 = 999;
      teclaAsterisco = 999;
      teclaCerquilha = 999;

      break;
    }
    case 'D': // caso seja a tecla D, entra no modo de exibição de todos os códigos IR das teclas cadastrados
    {
      Serial.println("Dados das teclas cadastradas:\n"); // Exibe mensagem inicial indicando o início da exibição dos dados

      // Exibe o código cadastrado para a tecla 1
      Serial.print("Tecla 1: ");
      if (tecla1 != 999) // Verifica se a tecla 1 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 1
        Serial.print(tecla1);
        Serial.print(" (0x");
        Serial.print(tecla1, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 1 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 2
      Serial.print("Tecla 2: ");
      if (tecla2 != 999) // Verifica se a tecla 2 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 2
        Serial.print(tecla2);
        Serial.print(" (0x");
        Serial.print(tecla2, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 3
      Serial.print("Tecla 3: ");
      if (tecla3 != 999) // Verifica se a tecla 3 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 3
        Serial.print(tecla3);
        Serial.print(" (0x");
        Serial.print(tecla3, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 3 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 4
      Serial.print("Tecla 4: ");
      if (tecla4 != 999) // Verifica se a tecla 4 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 4
        Serial.print(tecla4);
        Serial.print(" (0x");
        Serial.print(tecla4, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 4 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 5
      Serial.print("Tecla 5: ");
      if (tecla5 != 999) // Verifica se a tecla 5 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 5
        Serial.print(tecla5);
        Serial.print(" (0x");
        Serial.print(tecla5, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 5 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 6
      Serial.print("Tecla 6: ");
      if (tecla6 != 999) // Verifica se a tecla 6 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 6
        Serial.print(tecla6);
        Serial.print(" (0x");
        Serial.print(tecla6, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 6 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 7
      Serial.print("Tecla 7: ");
      if (tecla7 != 999) // Verifica se a tecla 7 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 7
        Serial.print(tecla7);
        Serial.print(" (0x");
        Serial.print(tecla7, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 7 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 8
      Serial.print("Tecla 8: ");
      if (tecla8 != 999) // Verifica se a tecla 8 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 8
        Serial.print(tecla8);
        Serial.print(" (0x");
        Serial.print(tecla8, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 8 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 9
      Serial.print("Tecla 9: ");
      if (tecla9 != 999) // Verifica se a tecla 9 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 9
        Serial.print(tecla9);
        Serial.print(" (0x");
        Serial.print(tecla9, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 9 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla 0
      Serial.print("Tecla 0: ");
      if (tecla0 != 999) // Verifica se a tecla 0 está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla 0
        Serial.print(tecla0);
        Serial.print(" (0x");
        Serial.print(tecla0, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla 0 não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla de reduzir volume (tecla *)
      Serial.print("Tecla REDUZIR VOLUME: ");
      if (teclaAsterisco != 999) // Verifica se a tecla de reduzir volume está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla de reduzir volume
        Serial.print(teclaAsterisco);
        Serial.print(" (0x");
        Serial.print(teclaAsterisco, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla de reduzir volume não está cadastrada
        Serial.println("-");
      }

      // Exibe o código cadastrado para a tecla de aumentar volume (tecla #)
      Serial.print("Tecla AUMENTAR VOLUME: ");
      if (teclaCerquilha != 999) // Verifica se a tecla de aumentar volume está cadastrada
      {
        // Exibe o valor decimal e hexadecimal do código IR da tecla de aumentar volume
        Serial.print(teclaCerquilha);
        Serial.print(" (0x");
        Serial.print(teclaCerquilha, HEX);
        Serial.println(")");
      }
      else // caso a tecla não esteja cadastrada
      {
        // Indica que a tecla de aumentar volume não está cadastrada
        Serial.println("-");
      }

      // Linha em branco para separar a exibição
      Serial.println();
      break;
    }
    case '*': // caso seja a tecla *, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal para reduzir o volume da TV está sendo enviado
      Serial.println("Enviando sinal para Reduzir volume TV");

      if (teclaAsterisco != 999) // Verifica se a tecla de reduzir volume está cadastrada
      {
        enviarComando(teclaAsterisco, tamanhoCode); // Envia o comando IR usando a função enviarComando com o código da tecla e o tamanho do código
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '#': // caso seja a tecla #, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal para aumentar o volume da TV está sendo enviado
      Serial.println("Enviando sinal para Aumentar volume TV");

      if (teclaCerquilha != 999) // Verifica se a tecla de aumentar volume está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla e o tamanho do código
        enviarComando(teclaCerquilha, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '1': // caso seja a tecla 1, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 1 está sendo enviado
      Serial.println("Enviando sinal da tecla 1");

      if (tecla1 != 999) // Verifica se a tecla 1 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 1 e o tamanho do código
        enviarComando(tecla1, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '2': // caso seja a tecla 2, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 2 está sendo enviado
      Serial.println("Enviando sinal da tecla 2");

      if (tecla2 != 999) // Verifica se a tecla 2 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 2 e o tamanho do código
        enviarComando(tecla2, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '3': // caso seja a tecla 3, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 3 está sendo enviado
      Serial.println("Enviando sinal da tecla 3");

      if (tecla3 != 999) // Verifica se a tecla 3 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 3 e o tamanho do código
        enviarComando(tecla3, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '4': // caso seja a tecla 4, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 4 está sendo enviado
      Serial.println("Enviando sinal da tecla 4");

      if (tecla4 != 999) // Verifica se a tecla 4 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 4 e o tamanho do código
        enviarComando(tecla4, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '5': // caso seja a tecla 5, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 5 está sendo enviado
      Serial.println("Enviando sinal da tecla 5");

      if (tecla5 != 999) // Verifica se a tecla 5 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 5 e o tamanho do código
        enviarComando(tecla5, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '6': // caso seja a tecla 6, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 6 está sendo enviado
      Serial.println("Enviando sinal da tecla 6");

      if (tecla6 != 999) // Verifica se a tecla 6 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 6 e o tamanho do código
        enviarComando(tecla6, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '7': // caso seja a tecla 7, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 7 está sendo enviado
      Serial.println("Enviando sinal da tecla 7");

      if (tecla7 != 999) // Verifica se a tecla 7 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 7 e o tamanho do código
        enviarComando(tecla7, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando pois a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '8': // caso seja a tecla 8, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 8 está sendo enviado
      Serial.println("Enviando sinal da tecla 8");

      if (tecla8 != 999) // Verifica se a tecla 8 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 8 e o tamanho do código
        enviarComando(tecla8, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando porque a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '9': // caso seja a tecla 9, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 9 está sendo enviado
      Serial.println("Enviando sinal da tecla 9");

      if (tecla9 != 999) // Verifica se a tecla 9 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 9 e o tamanho do código
        enviarComando(tecla9, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando porque a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    case '0': // caso seja a tecla 0, tenta enviar o código IR desta tecla
    {
      // Exibe mensagem informando que o sinal da tecla 0 está sendo enviado
      Serial.println("Enviando sinal da tecla 0");

      if (tecla0 != 999) // Verifica se a tecla 0 está cadastrada
      {
        // Envia o comando IR usando a função enviarComando com o código da tecla 0 e o tamanho do código
        enviarComando(tecla0, tamanhoCode);
      }
      else // caso não esteja cadastrada
      {
        // Exibe mensagem informando que não foi possível enviar o comando porque a tecla não está cadastrada
        Serial.println("Nao foi possivel enviar esta tecla para a TV. Tente cadastrar a tecla antes.");
      }
      break;
    }
    }
  }
}

// Função para obter a tecla pressionada no controle remoto
unsigned long getTeclaControleRemoto()
{
  timerTeclado = millis();                // Armazena o tempo atual em milissegundos
  IRrecv irrecv(IR_RECEIVE_PIN);          // Inicializa o receptor IR no pino especificado
  irrecv.enableIRIn();                    // Habilita a recepção de sinais IR
  decode_results results;                 // Estrutura para armazenar os resultados da decodificação IR
  unsigned long teclaApertada_temp = 999; // Variável temporária para armazenar a tecla pressionada inicialmente
  unsigned long teclaApertada = 999;      // Variável final para armazenar a tecla confirmada

  bool permanecerNoSubMenu = true;                                      // Variável de controle para manter o loop enquanto estiver no submenu
  while (millis() - timerTeclado <= tempoLimite && permanecerNoSubMenu) // Loop que continua enquanto o tempo limite não for atingido e estiver no submenu
  {
    // algoritmo para o STATUS DO LED (Aceso por um período de tempo): Teclas modificadas salvas;
    // durante 1 segundo (1000 milissegundos) o LED fica aceso e após é apagado
    if (millis() - timerLED >= 1000) // Verifica se 1 segundo se passou para alternar o estado do LED
    {
      digitalWrite(pinoLed, !digitalRead(pinoLed)); // Alterna o estado do LED

      timerLED = millis(); // Atualiza o timer do LED
    }

    if (irrecv.decode(&results)) // Verifica se um comando IR foi recebido
    {
      if (results.value != 0xFFFFFFFF) // Verifica se o valor recebido não é um código repetido
      {
        if (teclaApertada_temp == 999) // Se ainda não há tecla temporária armazenada
        {
          teclaApertada_temp = results.value; // Armazena o valor recebido temporariamente

          // Solicita confirmação da tecla pressionada ao usuário
          Serial.print("Aperte novamente a mesma tecla para confirmar: 0x");
          Serial.println(results.value, HEX);

          timerTeclado = millis(); // Reinicia o timer do teclado
        }
        else // caso já haja uma tecla temporária armazenada
        {
          if (teclaApertada_temp == results.value) // Se o valor recebido é igual ao valor temporário (CONFIRMADO), ...
          {
            // Informa ao usuário sobre a confirmação da tecla pressionada
            Serial.print("Tecla confirmada: 0x");
            Serial.println(results.value, HEX);

            teclaApertada = teclaApertada_temp; // atribui à variável de retorno o valor da tecla confirmada
            permanecerNoSubMenu = false;        // Sai do submenu
          }
          else // caso a confirmação de tecla falhe, ...
          {
            // Informa que a tecla pressionada é diferente
            Serial.println("Chave de Tecla diferente.");

            permanecerNoSubMenu = false; // Sai do submenu
          }
        }
      }

      irrecv.resume(); // Prepara o receptor IR para receber o próximo valor
    }

    delay(100); // Atraso de 100 ms antes de verificar novamente se um comando IR foi recebido
  }

  // Verifica se nenhuma tecla foi confirmada dentro do tempo limite
  if (teclaApertada == 999)
  {
    if (teclaApertada_temp == 999) // Se nenhuma tecla foi pressionada
    {
      Serial.print("Nenhuma tecla pressionada dentro do tempo limite. Tente novamente selecionando ");
    }
    else // se não, caso a tecla pressionada não foi confirmada
    {
      Serial.print("Nao houve confirmacao de tecla. Tente novamente selecionando ");
    }
  }
  else // se não, se ocorreu tudo certo
  {
    Serial.print("Insercao de dado finalizada. Pressione "); // Informa que a inserção de dados foi finalizada
  }

  Serial.println("alguma tecla para cadastrar ou pressione B para salvar."); // Informa ao usuário para continuar cadastrando novas teclas ou para finalizar salvando as tecclas com a opção B

  // Verifica se o protocolo da tecla pressionada é diferente do esperado
  if (results.decode_type != protocolo && teclaApertada_temp == results.value)
  {

    // informa ao usuário que ocorreu um erro
    Serial.print('\n');
    Serial.println("!!! ERRO !!!");
    Serial.print("Nao sera possivel continuar. A codigo da tecla pressionada esta em um protocolo diferente para o qual este sketch foi construido. Por favor, va ate a linha 6 e altere para o protocolo adequado. O protocolo da tecla possui o indice ");
    Serial.print(results.decode_type);
    Serial.print(". Alem disso, altere o conteudo da funcao enviarComando() para o do protocolo compativel\n Se achar que isto é um erro, reinicie o Arduino MEGA.");

    // Liga o LED para indicar erro e entra em loop infinito
    digitalWrite(pinoLed, HIGH);
    while (1)
    {
    }
  }

  tamanhoCode = results.bits; // Armazena o número de bits do código IR

  return teclaApertada; // Retorna a tecla pressionada e confirmada
}

// Função para enviar um comando IR (infravermelho)
void enviarComando(unsigned long data, int nbits)
{
  // ALTERE ESTE MÉTODO CASO O PROTOCOLO DE SEU CONTROLE REMOTO SEJA DIFERENTE DE NEC
  // Chama o método sendNEC para enviar os dados IR usando o protocolo NEC
  // 'data' é o comando que será enviado e 'nbits' é o número de bits desse comando
  irsend.sendNEC(data, nbits);
}

Código a ser colado no arquivo platformio.ini:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:megaatmega2560]
platform = atmelavr
board = megaatmega2560
framework = arduino
monitor_speed = 115200
lib_deps = 
    https://github.com/Arduino-IRremote/Arduino-IRremote.git#2.5.0
    chris--a/Keypad@^3.1.1
    https://github.com/MicSG-dev/EEPROM-Storage-PlatformIO

Funcionamento do Sketch

  • Configurações Iniciais: O programa começa com algumas definições de variáveis, como o pino do LED, e variáveis para controlar os timers do LED e do teclado;
  • Loop Principal: O código principal do programa está dentro da função loop(), que é executada repetidamente enquanto o Arduino está ligado;
  • Detecção de Teclas: O programa usa um teclado keypad para detectar teclas pressionadas pelo usuário. Ele usa a biblioteca Keypad (github.com/Chris–A/Keypad) para isso. Quando uma tecla é pressionada, o programa realiza várias ações com base na tecla pressionada.
  • Ações com Base na Tecla Pressionada: Dependendo da tecla pressionada, o programa pode entrar no modo de cadastro de códigos IR, excluir todos os códigos IR cadastrados, exibir os códigos IR cadastrados, ou enviar um comando IR para controlar a T.
  • Modo de Cadastro de Códigos IR: Se o usuário pressionar a tecla ‘A’, o programa entra no modo de cadastro de códigos IR. Nesse modo, o usuário pode pressionar teclas numéricas do Keypad para associar códigos IR de controle remoto à estas teclas do Keypad. Após um pressionamento de tecla do controle remoto, o software solicita que o usuário pressione novamente a mesma tecla para confirmar. Caso o usuário pressionar a tecla ‘B’, os códigos associados ao teclado são salvos na memória EEPROM do Arduino MEGA;
  • Exclusão de Códigos IR: Se o usuário pressionar a tecla ‘C’, o programa exclui todos os códigos IR cadastrados, restaurando todas as teclas para um valor padrão (999);
  • Exibição de Códigos IR: Se o usuário pressionar a tecla ‘D’, o programa exibe os códigos IR cadastrados para cada tecla;
  • Envio de Comandos IR: Se o usuário pressionar uma tecla numérica (de 0 a 9), ‘*’ ou ‘#’, o programa tenta enviar o código IR associado a essa tecla para controlar algum dispositivo.
  • Exibição de Status: O programa também inclui feedback visual por meio do LED, que pisca ou permanece aceso para indicar diferentes estados, como a detecção de teclas ou o processamento de comandos:
    • Apagado: sem fazer nenhuma ação, esperando o usuário executar algo;
    • Pisca 1 vez e apaga: ao usuário pressionar algum botão (será aceso o LED) e após soltar o botão (será apagado o LED);
    • Pisca-pisca lento: Cadastrando novas teclas;
    • Aceso por um período de tempo: Teclas modificadas salvas;
    • Pisca-pisca rápido: Limpando toda memória contendo o cadastro de teclas.

Demonstração do Funcionamento do Projeto

Veja no vídeo abaixo o projeto funcionando:


Conclusão

Em um passo além do projeto original, pode-se utilizar como base este projeto para automatizar uma variedade de aparelhos domésticos que possuam comunicação por infravermelho. É possível pensar em diversas outras aplicações:

  1. Gerenciamento de Aparelhos de Áudio: Utilize o Arduino para controlar o volume de um sistema de som;
  2. Controle de Iluminação: Controle lâmpadas inteligentes e crie cenários de iluminação que se ajustem automaticamente de acordo com a hora do dia ou com a presença de pessoas no ambiente
  3. Automação de Cortinas e Persianas: Configure o Arduino para abrir ou fechar cortinas e persianas de acordo com a incidência de luz solar ou com horários programados;
  4. Gerenciamento de Aparelhos de Cozinha: Controle o funcionamento de eletrodomésticos como forno, micro-ondas e cafeteiras para preparar alimentos em momentos específicos do dia;
  5. Controle de Ar Condicionado: Utilize o Arduino para automatizar o funcionamento do ar condicionado em sua residência. Você pode programar o Arduino para ligar ou desligar o ar condicionado em horários específicos, ajustar a temperatura de acordo com a temperatura ambiente medida por sensores, ou até mesmo controlar o ar condicionado remotamente por meio de um aplicativo em seu smartphone.

Esses são apenas alguns exemplos de como ampliar o uso deste projeto para tornar a automação residencial mais inteligente e conveniente. Se gostou da postagem, por favor, avalie e deixe um comentário! Não se esqueça de nos seguir também no Instagram e de nos marcar quando realizar algum projeto inspirado em nossos conteúdos: @eletrogate. Até a próxima!


Sobre o Autor


Michel Galvão

Graduando em Engenharia de Software pela UniCV de Maringá/PR. Tem experiência em Automação Residencial e Agrícola. É um Eletrogater Expert. Tem como Hobby Sistemas Embarcados e IoT. É desenvolvedor de software de Sistemas MicSG. Site: https://micsg.com.br/


Eletrogate

13 de junho de 2024

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!