Sensor remoto com Arduinos, módulo NRF24L01+ e ESP8266

Um Arduino como nó central, o outro como estação remota: conexão direta via rádio para dados de sensoreamento.

Sabe quando você termina um exercício e percebe que poderia ter ido além? Foi o que me aconteceu quanto concluí o artigo “Servidor Telnet no ESP8266: disponibilizando dados de monitoramento”, no final da semana passada.

Ele fazia algo bem legal: disponibilizar na rede WiFi (via um módulo ESP8266), acessível via Telnet (portanto bem fácil de ler automaticamente a partir de scripts), formatado em CSV (fácil de abrir em planilhas), o conjunto de dados lidos de um sensor em um Arduino.

No final de semana caiu a ficha: seria muito mais legal se os dados em questão fossem obtidos de uma estação remota, conectando-se com a estação central via rádio direto (independentemente da rede WiFi). A estação central acumularia os dados, e continuaria a disponibilizá-los via WiFi.

Foi assim que nasceram as conexões do diagrama acima. Os Arduinos marcados como Uno #1 e Uno #2 continuam fazendo o que já faziam no artigo anterior: o Uno #1 armazena um histórico de dados e os disponibiliza (com ajuda do ESP) na rede sem fio, e o Uno #2 oferece uma maneira fácil de acompanhar no Monitor Serial o que está acontecendo.

A novidade é que agora o Uno #1 também tem um rádio direto NRF24L01+, que usa para receber dados de sensor remoto do Uno #3, que os coleta e envia usando seu próprio NRF24L01+.

É uma topologia bem básica: um sensor, uma estação remota auto-gerenciada. Mas as mesmas técnicas podem ser usadas para redes mais amplas, com múltiplas estações remotas, cada uma com vários sensores, eventualmente aceitando comandos da estação central (no nosso programa de hoje, não há comandos, apenas envio periódico de dados).

A seguir temos um relato do que eu fiz. Se você for reproduzir o projeto, fica desde já a minha sugestão: vá aos poucos, testando separadamente as funcionalidades de debug pelo Uno #2, depois as de comunicação WiFi pelo Uno #1, e só então as da transmissão direta via rádio pelo Uno #3. Use os artigos anteriores sobre cada um desses elementos, linkados ao longo do texto, como referência.

Conhecendo o NRF24L01+

Já falamos bastante do NRF24L01+ no artigo “Arduino e comunicação sem fio: módulo NRF24L01+”, portanto vou apresentá-lo apenas brevemente.

O chip nRF24L01+, da Nordic, é uma dessas maravilhas da tecnologia moderna: reúne transmissão e recepção em um mesmo componente, consome pouca energia (pode funcionar meses ou até anos com uma pilha AA), transmite/recebe a até 2Mbps e encapsula boa parte da complexidade da comunicação digital via rádio, deixando os microcontroladores livres para se preocupar com as demandas da aplicação.

A comunicação usando esse tipo de chip é diretamente entre dispositivos, sem intermediação por infraestrutura de rede, como roteadores ou similares. Ele não é um adaptador WiFi, nem usa protocolos da Internet, como o TCP e o IP.

Normalmente os NRF24L01+ atuam em pares (como em um teclado sem fio conectado a um adaptador USB, por exemplo), ou um mestre controla vários remotos (como em uma rede de sensoreamento na qual um Arduino com esse chip obtém dados de vários outros Arduinos com esse chip, e os registra ou envia para um PC via serial ou WiFi). Outras topologias também são possíveis.

O NRF24L01+ é barato e vem sendo usado em periféricos sem fio para computadores, em acessórios esportivos e de jogos eletrônicos, em brinquedos, controles remotos avançados, headsets, joysticks, e em eletrônicos de consumo em geral.

O envio remoto dos dados de sensores via NRF24L01+

Nossa estação remota (Uno #3, no diagrama) foi montada em um Arduino Uno mas, em condições reais de operação1 eu optaria por um modelo Pro Mini, e ainda desativaria o led de Power dele, para que a bateria durasse ainda mais.

Dependendo do intervalo entre as transmissões, provavelmente faria sentido também usar um transistor para ligar o módulo NRF só na hora de transmitir, e colocar o próprio Arduino em modo de economia de energia quando ele não estiver lendo o sensor nem transmitindo dados. Vimos uma das formas mais simples2 de fazer isso no artigo Arduino e Bateria, mas hoje isso está completamente fora do escopo, e vamos considerar que o Arduino Uno está conectado a uma fonte permanente de energia.

A conexão entre Arduino e o módulo NRF24L01+ segue a mesma ordem de pinos já detalhada no artigo “Arduino e comunicação sem fio: módulo NRF24L01+”, que é a da imagem abaixo:

A comunicação entre o módulo nRF24L01+ e o Arduino ocorre usando o protocolo SPI e, embora o módulo opere a 3,3V (e assim precise ser alimentado por meio do pino 3V3 do Arduino), ele –ao contrário do ESP8266 – aceita e envia sinais compatíveis com o nível lógico de 5V do Arduino Uno e seus similares, sem precisar de conversão.

O programa também é um subconjunto das técnicas já apresentadas no artigo “Arduino e comunicação sem fio: módulo NRF24L01+”, mas agora se baseia em uma versão mais recente (e otimizada) da biblioteca de suporte RF24.

Vamos a ele:

#include "nRF24L01.h"
#include <SPI.h>
#include "RF24.h" // https://github.com/tmrh20/RF24
RF24 radio(9,10);
const uint64_t PIPE_ENVIO = 0xE8E8F0F0E1LL;

int hist_sensor[10];
byte leituras=0;

void setup() {
  Serial.begin(9600);
  Serial.println("NRF24L01+ transmitindo dados de sensor analogico");
  radio.begin();
  radio.openWritingPipe(PIPE_ENVIO);
}

void transmite_historico() {
    radio.stopListening();
    radio.write(&hist_sensor, sizeof(hist_sensor));
    Serial.println(">> ENVIEI HISTORICO");
}

void le_sensor() {
  hist_sensor[leituras]=random(0,1023);
  leituras++;
  Serial.print("-- li o sensor: ");
  Serial.print(leituras);
  Serial.print("=");
  Serial.println(hist_sensor[leituras]);
}

void loop() {
  le_sensor();
  if (leituras==10) {
    transmite_historico(); 
    leituras=0;
  }
  delay(600);
}

Curto, não? Compare com o tamanhão do programa usado no artigo anterior “Arduino e comunicação sem fio: módulo NRF24L01+”.

Esta é a versão diet do programa: saem as solicitações e confirmações, entra o envio frequente e sem verificação de erro além da provida pelo módulo.

A razão de o atual ser bem menor é que eu removi quase toda a inteligência das camadas superiores do protocolo: agora não existe mais um diálogo entre 2 estações, em que uma solicita dados, a outra envia e a solicitante confirma. No lugar dessa conversa clara e segura, entrou um simples envio por parte da estação remota, quando ela se considera pronta para transmitir – e se a estação central não estiver pronta para receber, azar, pois os dados serão perdidos.

Essa abordagem serve para alguns tipos de coletas de dados, e é absurda para outros; se você precisar de maior controle e tolerância a falhas, veja o artigo anterior e reimplemente os trechos que lhe interessarem.

Embora todos os conceitos e técnicas estejam explicados no artigo “Arduino e comunicação sem fio: módulo NRF24L01+”, colorizei na listagem acima alguns trechos para voltar a explicar, com foco na nossa aplicação de hoje.

O trecho em verde, dentro da função setup(), inicializa nosso módulo NRF e define o identificador da pipe (não é a mesma coisa, mas você pode pensar nela como um canal) a ser usada para transmitir os dados. O identificador tem 5 bytes, e o programa que vai receber os dados precisa usar o mesmo valor.

Em marrom, dentro da função transmite_historico(), temos as 2 linhas necessárias para enviar os dados de um array. O stopListening() está ali apenas por garantia, porque o rádio não pode estar na escuta na hora de tentar transmitir; nosso programa não coloca ele em modo de escuta (listening) em nenhum momento mas, caso você implemente algum tipo de protocolo de solicitação/confirmação, essa linha vai ser necessária e já está ali.

Em laranja, dentro da função le_sensor(), temos a linha que finge ler um sensor analógico (mas se limita a gerar um número aleatório – substitua-a- pelo código adequado ao seu sensor), e a que incrementa a variável leituras, absolutamente necessária para indicar o ponto do array para a inserção da próxima leitura.

Finalmente, em vermelho, dentro do loop(), temos a lógica central: o programa chama o le_sensor() e, caso a variável leituras tenha chegado a 10, chama o transmite_historico() e zera a variável, para recomeçar a preencher o array a partir do início.

É assim simples mesmo e, no seu Monitor Serial, aparece como na tela acima.

A recepção dos dados dos sensores no Arduino central, via NRF24L01+

O programa que roda no nosso Arduino central (Uno #1, no diagrama), que tem tanto um módulo de rádio NRF24L01+ quanto um módulo WiFi ESP-01, é uma variação daquele que vimos recentemente no artigo “Servidor Telnet no ESP8266: disponibilizando dados de monitoramento”: depois de receber os dados via NRF do módulo remoto, ele os disponibiliza em formato CSV, via WiFi, usando o ESP-01.

O código completo da versão com suporte ao NRF24L01+ está disponível no meu Github, mas a seguir destaco a parte final, onde estão as principais mudanças (fora o cabeçalho, que tem a inclusão das bibliotecas, a decisão do nome da pipe, etc).

(...)

boolean recebeConexaoTelnet() {
  unsigned long limite=7000;
  unsigned long chegada=millis(); 
  char resp[100]="";
  unsigned long tempo;
  boolean continuar=true; 
  boolean timeout=false;
  int contaChars=0;
  while (continuar) { 
    if (Serial.available()) {
      unsigned long chegada=millis();   // recomeca a contar quando ha recepcao
      resp[contaChars] = Serial.read();
      contaChars++;
      if (contaChars>80) contaChars=0;  // aqui deveria haver uma condicao de erro
#ifdef debug
      monitorSerial.print(resp[contaChars-1]);
#endif
      if (resp[contaChars-1]==10) {  // LF, fim da linha recebida
        if (contaChars>1) {
          resp[contaChars-2]=char(0);
          if (0==strcmp(resp,"OK")) continuar=false;
          else if (0==strcmp(resp,"ERROR")) continuar=false;
          else if (0==strcmp(resp,"0,CONNECT")) {
            continuar=false;
            ESPsendStr("**+ Segue historico, comecando da leitura mais antiga:\n\r");
            for (byte ii=0;ii<80;ii++) {
              byte indice=ponteiro+ii;
              if (indice>79) indice=indice-80;
              char digitos[4];                
              itoa(acumulado_sensor[indice],digitos,10);
              ESPsendStr(digitos);
              if (((indice+1) % 10)==0) {
#ifdef windows
                ESPsendStr("\n\r");   
#else
                ESPsendStr("\n");   
#endif                                                
              }
              else ESPsendStr(";");
            }
            ESPsendStr("**- Fim da lista. Desconectando.\n\n\r");
            cmdESP("AT+CIPCLOSE=0","Encerrando conexao recebida");
          }  
          contaChars=0;
        }  
      }  
    }  
    tempo=millis()-chegada; // tempo sem recepcao
    if (tempo > limite) {
      timeout=true;
      continuar=false;
    }  
  }
}


void recebeHistoricoRadio(int limite=500) {
  unsigned long timein=millis();
  while(abs(millis()-timein)<limite) {    
    if (radio.available()) {
      radio.read( &hist_sensor, sizeof(hist_sensor) );
      for (byte ii=0; ii<=9; ii++) {
        acumulado_sensor[ponteiro]=hist_sensor[ii];
#ifdef debug
        monitorSerial.print(ii);
        monitorSerial.print(":");
        monitorSerial.print(ponteiro);
        monitorSerial.print(" = ");
        monitorSerial.println(acumulado_sensor[ponteiro]);
#endif        
        ponteiro++;
        if (ponteiro>79) ponteiro=0;
      }
      monitorSerial.print("<< recebeHistoricoRadio(): recebi historico ");
      monitorSerial.println(ponteiro);
      break;
    }
  }
}


void setup() {
  monitorSerial.begin(VELOCIDADE);
  radio.begin();
  radio.openReadingPipe(1,PIPE_RADIO);
  radio.startListening();
  monitorSerial.println("*** ESP8266 Static IP / IP fixo - BR-Arduino.org");
  delay(2000);
  Serial.begin(VELOCIDADE);
  Serial.setTimeout(5000);
  conectaIPfixo();
  ativaTelnet();
  monitorSerial.println("Aguardando conexoes");  
}


void loop() {
  recebeHistoricoRadio();
  recebeConexaoTelnet();
}

Repetindo: acima você vê apenas as principais mudanças em relação ao artigo anterior. O código completo da versão com suporte ao NRF24L01+ está disponível no meu Github.

Destaquei em cores as mudanças mais relevantes, que vamos abordar fora de ordem, a seguir.

A função recebeHistoricoRadio é toda nova, e também é herdeira do que já detalhamos no artigo “Arduino e comunicação sem fio: módulo NRF24L01+”. Destaquei em laranja todo o loop que fica tentando, por até 500 milissegundos, receber via NRF uma transmissão do outro Arduino. Se receber, as 10 leituras transferidas estarão no array hist_sensor, e serão transferidas para o array acumulado_sensor, na posição indicada pela variável ponteiro, que será ajustada de acordo.

Pouco acima, em verde, dentro da recebeConexaoTelnet(), temos uma mudança no for que percorre o histórico das leituras acumuladas, para enviá-las em formato CSV pela conexão do ESP8266. Precisamos considerar que, a cada contato recebido via NRF, a variável ponteiro é ajustada para apontar para o ponto do array acumulado_sensor em que deve ocorrer a inserção da próxima leitura, sobrepondo uma leitura antiga a cada 8 conexões; assim, nossa nova versão do for usa essa mesma variável ponteiro para identificar qual o próximo trecho do acumulado_sensor (que é uma fila circular) a ser sobreposto, pois esse será o dado mais antigo, e portanto deve ser o primeiro a ser enviado para o telnet, seguindo a partir daí (circularmente) até chegar ao dado mais recente.

Dentro da função setup() você encontra um trecho em marrom com a inicialização do rádio do NRF24L01+, que é quase igual à inicialização que já vimos no programa que roda no Arduino remoto. A diferença é a última linha, ausente no outro programa, e que coloca nosso módulo NRF no modo de escuta (listening).

Finalmente, temos a função loop(), que ficou bem simples: tenta receber uma conexão via rádio NRF, depois tenta receber uma conexão via Telnet do ESP-01, e depois repete ;-)

No Monitor Serial fica como no exemplo acima...

...e no Telnet fica como na tela acima.

Para saber como levar os dados acima, em formato CSV, do Telnet para a planilha ou outro sistema, releia o artigo anterior: “Servidor Telnet no ESP8266: disponibilizando dados de monitoramento”.

 
  1.  E considerando a premissa de basear o projeto em placas Arduino disponíveis comercialmente!

  2.  E longe de ser a mais econômica...

Comentar

Dos leds ao Arduino, ESP8266 e mais

Aprenda eletrônica com as experiências de um geek veterano dos bits e bytes que nunca tinha soldado um led na vida, e resolveu narrar para você o que descobre enquanto explora esse universo – a partir da eletrônica básica, até chegar aos circuitos modernos.

Por Augusto Campos, autor do BR-Linux e Efetividade.net.

Recomendados

Livro recomendado


Artigos já disponíveis

Comunidade Arduino

O BR-Arduino é integrante da comunidade internacional de entusiastas do Arduino, mas não tem relação com os criadores e distribuidores do produto, nem com os detentores das marcas registradas.

Livros recomendados