iiOTESP32 Brasil
Modo de contingência: páginas públicas e downloads seguem disponíveis; login, comunidade e dados do servidor podem ficar indisponíveis.
Voltar para catálogo de tutoriais
Ilustração: duas placas ESP32 com antena LoRa, uma em cada ponto de uma paisagem com lago e colinas, trocando sinal de rádio de longo alcance
Imagem ilustrativa gerada por IA
IntermediárioIIoTLeitura: 14 min

LoRa no ESP32: comunicação ponto a ponto de longo alcance, sem LoRaWAN

Duas Heltec WiFi LoRa 32 V2 conversando a centenas de metros: SF, banda e tempo no ar, o que medimos em laboratório, as armadilhas de antena, cristal e ACK, e a perda de pacotes por sequência.

Tecnologias:#ESP32#LoRa#SX1276#Heltec#RadioLib#915 MHz

Requisitos de hardware:

  • Duas placas Heltec WiFi LoRa 32 V2 (ESP32 + SX1276), versão 915 MHz, com antena
  • Dois cabos USB e o PlatformIO

LoRa não é Wi-Fi com mais alcance

O Wi-Fi do ESP32 resolve uma sala, uma casa, um galpão pequeno. Quando o sensor está do outro lado do pátio, na caixa d'água do fundo do terreno ou num poço sem energia nem roteador, o Wi-Fi acaba, e é aí que o LoRa entra: um rádio de longo alcance e baixo consumo, que leva poucos bytes a centenas de metros, e em campo aberto a quilômetros.

O preço é a velocidade. LoRa é uma modulação de rádio (da Semtech) que troca taxa de dados por sensibilidade: você manda algumas dezenas de bytes por vez, de vez em quando, a uma taxa que vai de centenas de bits por segundo a poucos kilobits. Serve para leitura de temperatura, nível, contagem, estado de uma porta, alarme. Não serve para foto, áudio, atualização de firmware grande nem para qualquer coisa que dependa de uma conversa rápida de ida e volta.

Duas confusões atrapalham quem está começando, e vale desfazer logo:

  • LoRa não é LoRaWAN. LoRa é a camada de rádio. LoRaWAN é um protocolo de rede em cima dela, com gateways, servidor, chaves e regras de uso. Neste artigo você fala de LoRa puro, de um ESP32 para outro, sem gateway e sem conta em serviço nenhum. É o jeito mais simples de entender o rádio, e serve a muitos projetos de campo.
  • LoRa não tem confirmação de entrega. Você transmite no ar e quem estiver ouvindo, com os mesmos parâmetros, recebe. Se o pacote se perder, ninguém avisa. Quem precisa de garantia constrói a sua: um contador de sequência e, se for o caso, uma resposta de volta.

O que está dentro da conta: fator de espalhamento, banda e tempo no ar

Três parâmetros definem o comportamento do rádio, e os dois lados precisam usar os mesmos, senão um não entende o outro:

  • Fator de espalhamento (SF, de 7 a 12). Quanto maior, mais sensível é o receptor e maior o alcance, e muito mais lento é o pacote.
  • Largura de banda (BW). O comum é 125 kHz. Banda maior é mais rápida, mas ouve menos longe.
  • Taxa de código (CR). Redundância contra erro, de 4/5 a 4/8. Mais redundância tolera mais interferência e gasta mais tempo.

O que mais surpreende é o tempo no ar. Para um pacote de 20 bytes com banda de 125 kHz e CR 4/5, o cálculo da Semtech dá:

  • SF7: cerca de 57 ms, ou uns 5,5 kbps
  • SF8: cerca de 103 ms
  • SF9: cerca de 185 ms
  • SF10: cerca de 371 ms
  • SF11: cerca de 741 ms
  • SF12: cerca de 1,3 s, ou uns 300 bps

Cada passo de SF dobra o tempo no ar. Isso pesa em três lugares: na bateria (o rádio transmitindo é o que mais gasta), na chance de colisão com outros dispositivos e nas regras de uso do espectro. Escolha o menor SF que ainda fecha o enlace, e não o maior por precaução.

As armadilhas que queimam um fim de semana

1. Transmitir sem antena. O amplificador de saída de um módulo LoRa foi feito para ver uma antena. Transmitir sem ela devolve a energia ao próprio chip e pode danificá-lo. Conecte a antena antes de ligar a placa, sempre.

2. A frequência do módulo. Há módulos de 433, 868 e 915 MHz, e a diferença está no casamento de impedância e no filtro: não é só trocar um número no código. Muita placa barata vem de loja da Europa, em 868 MHz. No Brasil, o módulo e o código precisam ficar na faixa de 915 MHz. Confira o que está escrito na placa antes de comprar e antes de configurar.

3. Os parâmetros diferentes. SF, banda, CR, frequência e a palavra de sincronismo (sync word) iguais nos dois lados. Um bit diferente e o receptor fica em silêncio, sem erro nenhum.

4. A alimentação. O pico de corrente na transmissão é alto para o padrão de um sensor, na casa de uma centena de miliamperes na potência máxima. Fonte fraca, fio fino ou um regulador no limite derrubam o ESP32 na hora de transmitir, e o sintoma é um reinício que só aparece quando o rádio dispara. Um capacitor perto do módulo ajuda. Nos nossos testes, o nó (ESP32 com Wi-Fi e rádio ligados) teve picos de 250 a 350 mA, e um nó alimentado por uma fonte fraca reiniciou (brownout) no meio de uma troca de configuração. Atenção também a certos power banks: eles desligam sozinhos quando o consumo cai abaixo de 50 a 100 mA, e um nó em baixo consumo é exatamente isso.

5. Os pinos do SPI. O rádio conversa com o ESP32 por SPI, e os pinos de chip select, de interrupção (DIO0) e de reset precisam ser exatamente os que o código declara. Cada placa tem os seus: a Heltec WiFi LoRa 32 V2 não usa os pinos padrão do SPI do ESP32, e a V3 troca até o chip de rádio. Leia o esquema da sua placa em vez de copiar números de outro tutorial.

6. As regras de uso do espectro. A faixa de 915 MHz é de uso regulado. Use equipamento adequado, com a potência dentro do que a regulamentação da Anatel permite para a sua aplicação, e confira a norma vigente antes de instalar algo em campo. Na Europa, a faixa de 868 MHz tem limite de tempo no ar por hora (o duty cycle de 1%); no Brasil as regras são outras, mas a boa prática vale em qualquer lugar: transmita só o necessário.

7. Qualquer um pode ouvir. Quem tiver um receptor com os mesmos parâmetros lê tudo o que você transmite. LoRa puro não cifra nem autentica nada. Não envie dado sensível, e não deixe um comando de campo (ligar bomba, abrir porta) depender de um pacote que qualquer pessoa pode copiar e repetir.

8. O cristal que não é TCXO. O cristal da Heltec V2 não é compensado em temperatura, e duas placas diferentes ficam com a frequência um pouco deslocada uma da outra. Medimos uma diferença de cerca de 8 kHz entre as duas, uns 8,7 ppm. Com 125 kHz de banda o receptor tolera uns ±31 kHz e isso passa sem notar. Com bandas mais estreitas, como 62,5 kHz, a folga cai pela metade e a deriva com a temperatura em campo passa a importar. Quem tentar o ganho teórico de 3 dB de uma banda mais estreita precisa corrigir a frequência (AFC) ou usar módulos com TCXO.

9. O tempo de espera do ACK. Se você confirma pacotes, a espera pelo ACK tem que acompanhar o SF. O SX1276 só avisa "pacote recebido" depois de demodular o último símbolo, e o símbolo dura 1 ms no SF7 e uns 33 ms no SF12. Uma margem fixa de 150 ms, que funciona no SF7, fica apertada em SF alto e gera retransmissões. No nosso projeto, 30 a 40% dos pacotes eram retransmitidos até corrigirmos a margem para acompanhar o símbolo; depois, 8 comandos de 8 passaram sem nenhuma retransmissão.

O que você precisa

  • Duas placas Heltec WiFi LoRa 32 V2: ESP32, rádio SX1276 e display OLED numa peça só, na versão de 915 MHz, com uma antena cada
  • Dois cabos USB e o PlatformIO, como no artigo de primeiros passos no VS Code

Como o rádio já vem soldado na placa, não há fio nenhum para ligar. O que existe é a pinagem interna entre o ESP32 e o SX1276, e é ela que o código precisa declarar:

  • SCK no GPIO 5, MISO no GPIO 19, MOSI no GPIO 27
  • NSS (chip select) no GPIO 18
  • DIO0 no GPIO 26 e DIO1 no GPIO 35
  • RESET no GPIO 14

Esta pinagem é da V2. Outras versões da Heltec (a V3, por exemplo) trazem outro chip de rádio e outros pinos: leia o esquema da sua placa antes de copiar os números. Quem usar um módulo solto, como o RFM95W, liga os mesmos sinais a pinos livres do ESP32 e ajusta as constantes.

Código: o projeto

A biblioteca usada é a RadioLib, que cobre os chips SX127x e SX126x com a mesma interface. O platformio.ini:

code.cpp
cpp
1[env:heltec-v2]
2platform = espressif32
3board = heltec_wifi_lora_32_V2
4framework = arduino
5monitor_speed = 115200
6lib_deps =
7 jgromes/RadioLib@^7.0.0

Os parâmetros que os dois lados compartilham ficam no topo de cada programa, e precisam ser idênticos:

code.cpp
cpp
1#include <Arduino.h>
2#include <SPI.h>
3#include <RadioLib.h>
4const int PIN_SCK = 5;
5const int PIN_MISO = 19;
6const int PIN_MOSI = 27;
7const int PIN_NSS = 18;
8const int PIN_RST = 14;
9const int PIN_DIO0 = 26;
10const int PIN_DIO1 = 35;
11const float FREQ_MHZ = 915.0;
12const float BW_KHZ = 125.0;
13const uint8_t SF = 9;
14const uint8_t CR = 5;
15const uint8_t SYNC_WORD = 0x12;
16const int8_t POTENCIA_DBM = 10;
17SX1276 radio = new Module(PIN_NSS, PIN_DIO0, PIN_RST, PIN_DIO1);

O SPI desta placa usa pinos diferentes do padrão do ESP32, por isso o SPI.begin() com os pinos explícitos antes de iniciar o rádio. A potência de 10 dBm é de propósito: na bancada, com os dois módulos a um metro, a potência máxima só satura o receptor.

Código: o emissor

O emissor manda um número de sequência e a temperatura interna do ESP32, só para ter um dado que muda:

code.cpp
cpp
1uint32_t seq = 0;
2void setup() {
3 Serial.begin(115200);
4 SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_NSS);
5 int st = radio.begin(FREQ_MHZ, BW_KHZ, SF, CR, SYNC_WORD, POTENCIA_DBM, 8);
6 if (st != RADIOLIB_ERR_NONE) {
7 Serial.printf("radio.begin falhou: %d\n", st);
8 while (true) delay(1000);
9 }
10 radio.setCRC(true);
11}
12void loop() {
13 char msg[32];
14 snprintf(msg, sizeof(msg), "%lu,%.1f", (unsigned long)seq, temperatureRead());
15 int st = radio.transmit(msg);
16 Serial.printf("[TX] %s -> %s (%d)\n", msg, st == RADIOLIB_ERR_NONE ? "ok" : "falhou", st);
17 seq++;
18 delay(10000);
19}

O transmit() bloqueia até o pacote sair, o que leva centenas de milissegundos no SF9. Para uma demonstração, está bem. Num projeto com outras tarefas, a transmissão deve ficar fora do laço principal.

Código: o receptor

O receptor não faz laço de espera: o módulo avisa pelo pino DIO0 quando chega um pacote, e o loop só trata o aviso. É o mesmo princípio do artigo de reconexão MQTT: nada bloqueia o firmware esperando o mundo.

code.cpp
cpp
1volatile bool chegou = false;
2uint32_t ultimaSeq = 0;
3uint32_t perdidos = 0;
4bool primeiro = true;
5void IRAM_ATTR aoChegarPacote() {
6 chegou = true;
7}
8void setup() {
9 Serial.begin(115200);
10 SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_NSS);
11 int st = radio.begin(FREQ_MHZ, BW_KHZ, SF, CR, SYNC_WORD, POTENCIA_DBM, 8);
12 if (st != RADIOLIB_ERR_NONE) {
13 Serial.printf("radio.begin falhou: %d\n", st);
14 while (true) delay(1000);
15 }
16 radio.setCRC(true);
17 attachInterrupt(digitalPinToInterrupt(PIN_DIO0), aoChegarPacote, RISING);
18 radio.startReceive();
19}

E o tratamento do pacote, com a conta de perdas pelo número de sequência:

code.cpp
cpp
1void loop() {
2 if (!chegou) return;
3 chegou = false;
4 String texto;
5 int st = radio.readData(texto);
6 if (st == RADIOLIB_ERR_NONE) {
7 uint32_t seq = strtoul(texto.c_str(), nullptr, 10);
8 if (!primeiro && seq > ultimaSeq + 1) perdidos += seq - ultimaSeq - 1;
9 primeiro = false;
10 ultimaSeq = seq;
11 Serial.printf("[RX] %s rssi=%.0f snr=%.1f perdidos=%lu\n", texto.c_str(), radio.getRSSI(), radio.getSNR(), (unsigned long)perdidos);
12 } else if (st == RADIOLIB_ERR_CRC_MISMATCH) {
13 Serial.println("[RX] pacote corrompido (CRC)");
14 } else {
15 Serial.printf("[RX] erro %d\n", st);
16 }
17 radio.startReceive();
18}

Duas coisas valem comentar. O RSSI é a potência recebida, em dBm, e quanto mais negativo, mais fraco o sinal. O SNR é a relação sinal-ruído: o LoRa decodifica sinal abaixo do ruído, então um SNR negativo, dentro dos limites do SF, ainda funciona. E o startReceive() no fim reabre o ouvido: sem ele, o módulo recebe um pacote e fica surdo.

Tutorial: do zero ao primeiro pacote

Passo 1. Monte os dois conjuntos, com antena em cada módulo, e deixe as placas a uns dois metros uma da outra.

Passo 2. Grave o programa do emissor numa placa e o do receptor na outra. Se compilar os dois no mesmo projeto, use dois ambientes no platformio.ini, cada um com seu arquivo principal.

Passo 3. Abra o monitor serial de cada uma. No emissor, a cada dez segundos:

code.cpp
cpp
1[TX] 0,41.7 -> ok (0)
2[TX] 1,41.9 -> ok (0)

No receptor, o que prova o enlace:

code.cpp
cpp
1[RX] 0,41.7 rssi=-52 snr=9.8 perdidos=0
2[RX] 1,41.9 rssi=-53 snr=9.5 perdidos=0

Seus valores de RSSI vão variar, o que importa é aparecer o pacote com os mesmos números da outra placa.

Passo 4. O que fazer se nada chegar. O emissor imprime "ok" e o receptor não escreve nada: confira, nesta ordem, os parâmetros iguais nos dois lados (frequência, SF, banda, CR e sync word), as antenas conectadas, e o módulo na frequência certa. Se o radio.begin falhar com erro, o problema é o SPI: o código de erro e a pinagem dizem onde procurar, e um chip que não responde costuma ser fio solto, alimentação ou pino errado.

Passo 5. Meça o alcance de verdade. Leve o receptor para longe, com a antena na vertical, e vá afastando enquanto observa o RSSI e a contagem de perdidos. Em ambiente com obstáculo, o alcance real é muito menor que o dos folhetos, que medem em campo aberto, com linha de visão. Registre os resultados para o seu terreno. Um cuidado: o RSSI só existe quando chega um pacote. Em SF12, com um envio por minuto, o número na tela só muda de minuto em minuto e o teste de alcance parece travado. Durante a caminhada, envie a cada poucos segundos (uns 8 s) e volte ao intervalo normal depois: nos nossos testes isso ocupa uns 30% do tempo no ar e gasta mais corrente.

Passo 6. Troque o SF. Suba o SF de 9 para 11 nos dois lados e compare o RSSI mínimo em que o pacote ainda chega. Você vai ver o ganho de alcance e o custo: o tempo no ar quadruplica em relação ao SF9, e a bateria sente.

O que medimos em laboratório com duas Heltec V2

Estes números saíram de um projeto próprio com duas Heltec V2 (915 MHz), em testes de laboratório e não numa instalação real. Valem para aquele ambiente e aquela antena, e não como promessa para o seu:

  • Sensibilidade típica do SX1276 com 125 kHz e CR 4/5: cerca de −123 dBm no SF7, −129 dBm no SF9, −134 dBm no SF11 e −137 dBm no SF12.
  • Tempo no ar: um pacote de 56 bytes leva uns 100 ms no SF7 e 2,6 s no SF12.
  • Alcance (teste de laboratório, não de campo): com o receptor dentro de um prédio e o outro lado a pé, o alcance horizontal foi de uns 50 m no SF7 para uns 150 m no SF12, três vezes mais. A conta teórica, com ambiente interno, previa algo como 3,7 vezes. O ambiente urbano com obstáculos ficou muito abaixo dos quilômetros dos folhetos.
  • O limite aparece nos números: no ponto mais distante o RSSI passou de −128 dBm e o SNR chegou a −20 dB, que é o limite do SF12. Foi aí que o enlace caiu.
  • Ruído do canal: cerca de −121 dBm, quase sem tempo acima de −105 dBm: canal limpo.
  • Atualização do RSSI: no perfil de SF12 o nó enviava a cada 60 s (mais uns 20 s de retentativas) e o sinal se atualizava a cada 62 s. Com um modo de caminhada que envia a cada uns 8 s, passou a cada 10 s (entre 9 e 14 s), seis vezes mais rápido, ao custo de uns 30% do tempo no ar. Uma média móvel que dava peso de 25% ao pacote novo atrasava ainda mais; subir para 50% ajudou.
  • Troca de SF pelo ar (o receptor manda o comando, o outro lado troca): do SF7 ao SF12 funcionou, cada troca em 2 a 4 s, com volta automática ao perfil anterior se a confirmação não chega.

O que isso ensina, em ordem: o SF ajuda e custa tempo no ar; o ambiente manda mais que o SF; e todo temporizador do seu protocolo (ACK, nova tentativa, "nó offline") precisa acompanhar o tempo no ar do SF escolhido.

Quando algo não funciona

Reinício toda vez que transmite. A alimentação não aguenta o pico de corrente. Use uma fonte melhor, fios mais curtos e um capacitor no módulo.

Chegam pacotes com CRC inválido. Interferência, antena ruim ou distância no limite. Aproxime, troque a antena, suba o SF.

Funcionou um dia e parou. Antena frouxa ou oxidada, bateria fraca ou outro dispositivo na mesma frequência. Meça o RSSI de novo e compare com o primeiro teste.

O receptor acusa perdidos o tempo todo. Pode ser colisão com outro transmissor, o receptor surdo por falta do startReceive() ou o enlace no limite. Aumentar o intervalo entre pacotes diminui a chance de colisão.

O que este artigo não cobre

  • LoRaWAN. Gateway, servidor de rede, chaves e o plano de frequências regional (no Brasil, o AU915). É o caminho quando há muitos dispositivos e a internet no meio, e merece um artigo próprio.
  • Confirmação e retransmissão. O contador de sequência detecta a perda, mas não a corrige. Um ACK de volta, com tempo limite e nova tentativa, é o passo seguinte, e a armadilha 9 mostra por que o tempo limite precisa acompanhar o SF. É também a hora de pensar em quanto tempo no ar o seu projeto pode gastar.
  • Cifra e autenticação do pacote. Necessárias quando o dado ou o comando importam. O ESP32 tem aceleração de AES em hardware, que ajuda.
  • Baixo consumo. O ESP32 em sono profundo acordando só para transmitir é o que faz uma bateria durar meses, e depende de um projeto de energia cuidadoso.

Nota de honestidade

O que está dito sobre o rádio (alcance, sensibilidade, tempo no ar, cristal, ACK, alimentação) vem de um projeto próprio testado em laboratório em duas placas Heltec WiFi LoRa 32 V2, em ESP-IDF e com um driver próprio, não de folheto. Foi um teste de laboratório: ainda não há validação numa instalação de campo. O código de exemplo deste artigo é outro: uma versão mínima em Arduino com a biblioteca RadioLib, escrita para ser lida, e, embora a versão do kit compile com o PlatformIO nos dois programas (emissor e receptor), ele não foi rodado em placa. Os nomes das funções podem mudar entre versões da biblioteca. Antes de levar a campo, compile no seu ambiente, confirme a pinagem do seu módulo e meça o alcance no seu terreno. As referências à regulamentação são orientações gerais: a norma vigente da Anatel é quem manda.

Para levar o dado recebido adiante, o receptor pode publicar em MQTT, como no artigo de ESP32 publicando dados no Mosquitto, e com TLS se a rede não for confiável. E para o emissor sobreviver a falhas, veja o watchdog.

Ver mais tutoriais técnicosIIoT ESP32 Brasil — Documentação Prática