




A comunicação sem fio normalmente é associada ao Wi-Fi, Bluetooth e outras tecnologias baseadas em radiofrequência. Entretanto, existe uma área bastante interessante da telecomunicação chamada Li-Fi (Light Fidelity), onde a transmissão de dados acontece através da luz. Nesse tipo de sistema, LEDs podem ser utilizados para transmitir informações por meio de pulsos luminosos extremamente rápidos, enquanto sensores ópticos detectam essas variações e convertem novamente os sinais em dados digitais.Embora o Li-Fi comercial ainda esteja em pleno desenvolvimento em muitos setores, os princípios básicos dessa tecnologia podem ser reproduzidos utilizando componentes simples e acessíveis. Neste projeto construiremos um sistema Li-Fi didático utilizando dois módulos ESP32, um LED de alto brilho e um LDR (sensor dependente de luz). A comunicação será realizada por meio de Código Morse, permitindo transmitir mensagens digitadas pelo computador ou até mesmo pelo celular através de um aplicativo Android.O projeto é excelente para o aprendizado de:
Li-Fi significa Light Fidelity, uma tecnologia de comunicação baseada em luz visível. Diferente do Wi-Fi, que utiliza ondas de rádio, o Li-Fi transmite dados modulando a intensidade luminosa de LEDs de forma extremamente rápida e imperceptível ao olho humano.Em aplicações reais, sistemas Li-Fi são estudados para uso em:
O sistema é dividido em duas partes independentes, cada uma rodando em um ESP32 diferente:
O ESP32 receptor será responsável por:
Todos os componentes estão disponíveis na loja da Eletrogate.

| Componente | ESP32 |
|---|---|
| LED Anodo | GPIO 26 |
| LED Catodo | Resistor 220Ω → GND |
| Componente | ESP32 |
|---|---|
| LDR perna 1 | 3.3V |
| LDR perna 2 | GPIO 4 |
| Resistor 10k | GPIO 4 → GND |
Em sistemas Li-Fi, o alinhamento entre transmissor e receptor é um fator crítico de desempenho. Diferente do Wi-Fi, onde as ondas de rádio se propagam em todas as direções e atravessam paredes, a comunicação óptica depende diretamente da incidência da luz sobre o sensor.Pequenos desalinhamentos reduzem a intensidade luminosa recebida pelo LDR, aumentando o risco de leituras incorretas dos pulsos. Durante os testes, recomenda-se:


Ambos os firmwares foram desenvolvidos para PlatformIO com o framework Arduino, mas funcionam igualmente na Arduino IDE.
| Símbolo | Duração |
| Ponto (pulso curto) | 80ms |
| Traço (pulso longo) | 240ms |
| Intervalo entre símbolos | 80ms |
| Intervalo entre letras | 240ms |
| Intervalo entre palavras | 560ms |
O buffer buf[] armazena a mensagem completa antes de transmitir, evitando transmissões parciais caso o usuário pause ao digitar.
/** * @file transmitter.cpp * @author Saulo Aislan ([email protected]) * @brief Firmware para o transmissor de sinal Li-Fi. * @version 0.1 * @date 2026-05-21 * * @copyright Copyright (c) 2026 * */ #include <Arduino.h> #include <string.h> #define LED_PIN 26 // Timings (ms) — devem ser identicos ao receiver.cpp #define T_DOT 80 #define T_DASH 240 #define T_ELEM 80 // entre pontos/tracos na mesma letra #define T_LETTER 240 // entre letras #define T_WORD 560 // entre palavras // Tabela Morse ITU struct MorseEntry { char ch; const char* code; }; const MorseEntry MORSE_TABLE[] = { {'A',".-"}, {'B',"-..."}, {'C',"-.-."}, {'D',"-.."}, {'E',"."}, {'F',"..-."}, {'G',"--."}, {'H',"...."}, {'I',".."}, {'J',".---"}, {'K',"-.-"}, {'L',".-.."}, {'M',"--"}, {'N',"-."}, {'O',"---"}, {'P',".--."}, {'Q',"--.-"}, {'R',".-."}, {'S',"..."}, {'T',"-"}, {'U',"..-"}, {'V',"...-"}, {'W',".--"}, {'X',"-..-"},{'Y',"-.--"}, {'Z',"--.."}, {'1',".----"},{'2',"..---"},{'3',"...--"},{'4',"....-"},{'5',"....."}, {'6',"-...."},{'7',"--..."},{'8',"---.."},{'9',"----."},{'0',"-----"}, }; /* * @brief Retorna o código Morse para um caractere. * @param c Caractere a ser convertido. * @return Ponteiro para a string do código Morse ou nullptr se não encontrado. */ const char* getMorse(char c) { c = toupper(c); for (const auto& e : MORSE_TABLE) if (e.ch == c) return e.code; return nullptr; } /** * @brief Envia um pulso de luz por um determinado tempo. * @param ms Tempo em milissegundos. */ void pulse(uint32_t ms) { digitalWrite(LED_PIN, HIGH); delay(ms); digitalWrite(LED_PIN, LOW); } /** * @brief Envia um caractere como código Morse. * @param c Caractere a ser enviado. */ void sendChar(char c) { const char* code = getMorse(c); if (!code) return; Serial.printf("%c", c); for (int i = 0; code[i]; i++) { if (i > 0) { delay(T_ELEM); } if (code[i] == '.') { pulse(T_DOT); } else { pulse(T_DASH); } } } /** * @brief Envia uma string como código Morse. * @param str String a ser enviada. */ void sendString(const char* str) { Serial.println("Transmitindo:"); bool primeiraLetra = true; for (int i = 0; str[i]; i++) { char c = str[i]; if (c == ' ') { delay(T_WORD); // Espaço entre palavras Serial.print(" "); primeiraLetra = true; } else { if (!primeiraLetra) delay(T_LETTER); sendChar(c); primeiraLetra = false; } } Serial.println("\n--- Fim ---\n"); } char buf[128]; int bufLen = 0; /** * @brief Configura o transmissor e aguarda mensagens para enviar. */ void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); Serial.println("=== Li-Fi Transmissor Morse ==="); Serial.printf("LED: GPIO%d\n", LED_PIN); Serial.printf("Ponto:%dms | Traco:%dms | Elem:%dms | Letra:%dms | Palavra:%dms\n\n", T_DOT, T_DASH, T_ELEM, T_LETTER, T_WORD); Serial.println("Digite a mensagem e pressione Enter:"); } /** * @brief Loop principal para ler mensagens do console e transmiti-las. */ void loop() { while (Serial.available()) { char c = Serial.read(); if (c == '\n' || c == '\r') { if (bufLen > 0) { buf[bufLen] = '\0'; bufLen = 0; Serial.printf("\nMensagem: \"%s\"\n", buf); sendString(buf); Serial.println("Digite outra mensagem:"); } } else if (bufLen < 127 && c >= 32) { buf[bufLen++] = c; Serial.print(c); // echo } } }
/** * @file receiver.cpp * @author Saulo Aislan ([email protected]) * @brief Firmware para o receptor de sinal Li-Fi. * @version 0.1 * @date 2026-05-21 * * @copyright Copyright (c) 2026 * */ #include <Arduino.h> #include <string.h> #define LDR_PIN 4 #define THRESHOLD 2500 // Timings (ms) — holecek.pavel.MorseCode #define T_DOT 80 #define T_DASH 240 #define T_ELEM 80 #define T_LETTER 240 #define T_WORD 560 // Limiares de classificacao (ponto medio entre os valores adjacentes) #define LIM_DOT_DASH 160 // pulso ON < 160ms = ponto | >= 160ms = traco (80+240)/2 #define LIM_ELEM_LETTER 160 // gap OFF < 160ms = elem | >= 160ms = nova letra (80+240)/2 #define LIM_LETTER_WORD 400 // gap OFF < 400ms = letra | >= 400ms = palavra (240+560)/2 #define LIM_FIM 1120 // gap OFF >= 1120ms = fim de transmissao (560*2) // Tabela Morse ITU struct MorseEntry { char ch; const char* code; }; const MorseEntry MORSE_TABLE[] = { {'A',".-"}, {'B',"-..."}, {'C',"-.-."}, {'D',"-.."}, {'E',"."}, {'F',"..-."}, {'G',"--."}, {'H',"...."}, {'I',".."}, {'J',".---"}, {'K',"-.-"}, {'L',".-.."}, {'M',"--"}, {'N',"-."}, {'O',"---"}, {'P',".--."}, {'Q',"--.-"}, {'R',".-."}, {'S',"..."}, {'T',"-"}, {'U',"..-"}, {'V',"...-"}, {'W',".--"}, {'X',"-..-"},{'Y',"-.--"}, {'Z',"--.."}, {'1',".----"},{'2',"..---"},{'3',"...--"},{'4',"....-"},{'5',"....."}, {'6',"-...."},{'7',"--..."},{'8',"---.."},{'9',"----."},{'0',"-----"}, }; /** * @brief Retorna o caractere correspondente a um código Morse. * @param code String do código Morse a ser decodificada. * @return Caractere correspondente ou '?' se não encontrado. */ char decodeMorse(const char* code) { for (const auto& e : MORSE_TABLE) if (strcmp(code, e.code) == 0) return e.ch; return '?'; } inline bool lightOn() { return analogRead(LDR_PIN) > THRESHOLD; } bool prevLight = false; uint32_t edgeTime = 0; char symBuf[8] = {}; int symCount = 0; // Imprime a letra decodificada e limpa o buffer de simbolos. // Retorna true se havia algum simbolo acumulado. bool flushLetter() { if (symCount == 0) return false; symBuf[symCount] = '\0'; Serial.printf("%c", decodeMorse(symBuf)); symCount = 0; return true; } /** * @brief Adiciona um simbolo (ponto ou traço) ao buffer atual da letra. * @param s Simbolo a ser adicionado ('.' ou '-'). */ void addSymbol(char s) { if (symCount < 7) { symBuf[symCount++] = s; //Serial.printf("%c ", s); } } void setup() { Serial.begin(115200); Serial.println("=== Li-Fi Receptor Morse ==="); Serial.printf("LDR: GPIO%d | Limiar: %d\n", LDR_PIN, THRESHOLD); Serial.printf("Ponto:%dms | Traco:%dms | Elem:%dms | Letra:%dms | Palavra:%dms\n", T_DOT, T_DASH, T_ELEM, T_LETTER, T_WORD); delay(100); int v = analogRead(LDR_PIN); Serial.printf("LDR: %d (%s)\n\n", v, v > THRESHOLD ? "CLARO - OK" : "ESCURO - ajuste THRESHOLD"); Serial.println("Aguardando transmissao...\n"); prevLight = lightOn(); edgeTime = millis(); } void loop() { bool cur = lightOn(); uint32_t now = millis(); uint32_t elapsed = now - edgeTime; if (cur != prevLight) { if (cur) { // Borda de subida (LED acendeu): classifica o gap que acabou (era OFF) if (elapsed >= LIM_LETTER_WORD) { bool hadLetter = flushLetter(); if (hadLetter) Serial.print(" "); // linha em branco entre palavras } else if (elapsed >= LIM_ELEM_LETTER) { flushLetter(); // fim de letra, proxima comeca } } else { // Borda de descida (LED apagou): classifica o pulso que acabou (era ON) addSymbol(elapsed >= LIM_DOT_DASH ? '-' : '.'); } prevLight = cur; edgeTime = now; } // Flush por timeout: imprime ultima letra se o silencio durar >= LIM_FIM if (!cur && symCount > 0 && elapsed >= LIM_FIM) { flushLetter(); Serial.println("\n--- Fim de transmissao ---\n"); edgeTime = now; // evita flush repetido } }
Antes de transmitir mensagens completas, é importante calibrar o limiar do receptor. Ao inicializar, o firmware exibe a leitura atual do LDR:LDR: 3021 (CLARO - OK)Se a mensagem exibida for ESCURO, significa que o LED não está incidindo sobre o LDR com intensidade suficiente, ou que o valor de THRESHOLD precisa ser reduzido. Uma forma prática de calibrar:
Uma das formas mais interessantes de validar o receptor é usar o celular como transmissor, eliminando completamente o ESP32 transmissor do processo. Para isso, utilizamos o aplicativo Morse Code Torch (desenvolvido por perryOnCrack), disponível gratuitamente na Play Store.O aplicativo converte qualquer texto digitado em pulsos da lanterna do celular, seguindo as temporizações do Código Morse. Como a lanterna do celular é extremamente brilhante, ela se torna um transmissor Li-Fi bastante potente, capaz de acionar o LDR mesmo a distâncias maiores do que o LED do circuito transmissor.
=== Li-Fi Receptor Morse ===
LDR: GPIO4 | Limiar: 2500
LDR: 3150 (CLARO - OK)
Aguardando transmissao...
| Limiar | Valor | Significado |
|---|---|---|
LIM_DOT_DASH | 160ms | Pulsos abaixo = ponto / acima = traço |
LIM_ELEM_LETTER | 160ms | Silêncio abaixo = entre símbolos / acima = nova letra |
LIM_LETTER_WORD | 400ms | Silêncio acima = nova palavra |
LIM_FIM | 1120ms | Silêncio acima = fim de transmissão |
? no Monitor Serial, edite os limiares no receiver.cpp para valores maiores ou modifique a velocidade no aplicativo para valores menores que 80. Ajuste os valores até obter decodificação correta.Abaixo o vídeo demonstrando a comunicação:
Após montar os dois circuitos e gravar os firmwares, o fluxo de operação é o seguinte:
Este projeto demonstra de forma prática e didática como é possível implementar comunicação óptica sem fio utilizando componentes acessíveis e dois módulos ESP32. Ao construir o sistema Li-Fi, exploramos conceitos fundamentais de telecomunicações como modulação, temporização, detecção de bordas e decodificação de sinai, todos aplicáveis a sistemas reais de comunicação por luz.O projeto serve como ponto de partida para evoluções futuras, como:
|
Nesse sistema, utilizaremos LEDs para transmitir informações por meio de pulsos luminosos extremamente rápidos, enquanto sensores ópticos detectam essas variações e convertem novamente os sinais em dados digitais.
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200