quarta-feira, outubro 14, 2015

Usando o nRF24L01+ com o Raspberry Pi: Um datalogger - Parte 2

Continuando o post anterior, vamos ver agora a aplicação em si. Depois de todo o trabalho para colocar a biblioteca pynrf24 para funcionar, agora é simples.


No lado do sensor vou usar um Arduino com dois sensores: um sensor de temperatura DS18B20 ligado ao pino D8 e um sensor de efeito Hall A1120 ligado ao pino D7. A minha primeira ideia era usar o sensor LM35, mas ele não funciona com 3V inviabilizando o meu próximo passo. O rádio é ligado da mesmo forma que o meu teste anterior.

O software no Arduino transmitirá em duas situações:
  • De 10 em 10 minutos.
  • Quando detectar mudança no sensor de efeito Hall (indicando que um imã foi colocado ou retirado da frente dele).
O meu pacote de dados conterá:
  • Dois bytes com a leitura do sensor de temperatura (temperatura em décimo de graus centígrados)
  • Um byte contendo 0 ou 1 conforme o sensor de efeito Hall estiver acionado.
Abaixo o sketch do Arduino. A maior parte diz respeito à leitura da temperatura, usei um esquema simples, sem endereçamento, para interagir com o DS18B20.
// Exemplo de sensor enviando dados através do nRF24L01+ 
// Daniel Quadros, 13/10/15

#include <SPI.h>
#include <RF24.h>

// Conexoes
const int pinLED = 6;
const int pinHall = 7;
const int pinDS18B20 = 8;
const int pinCE = 9;
const int pinCSN = 10;

// Este objeto encapsula o acesso ao nRF24L01+
RF24 radio(pinCE, pinCSN);

// Endereços
byte addrLogger[6] = "DLogr";

// Estado anterior do sensor de efeito Hall
const uint8_t hallOFF = '0';
const uint8_t hallON  = '1';
const uint8_t hallUNDEF = 'X';
uint8_t hallAnt = hallUNDEF;

// Tempo em minutos entre transmissões periodicas
const unsigned long tempoTx = 10;

// Próximo envio automático
unsigned long proxEnvio;

void setup() 
{
  // Serial para debug
  Serial.begin(9600);
  Serial.println("Sensor com nRF24L01+");

  // Iniciacao dos pinos de LED e sensores
  pinMode (pinLED, OUTPUT);
  pinMode (pinHall, INPUT);
  digitalWrite (pinHall, HIGH);
  setDQ(HIGH);

  // Inicia o rádio
  radio.begin();
  radio.setPayloadSize(3);
  radio.openWritingPipe(addrLogger);
  
  // Primeiro envio
  proxEnvio = millis() + tempoTx * 60000UL;
}

void loop() 
{
  uint8_t hall;
  uint8_t msg[3];
  uint16_t temp;

  hall = digitalRead(pinHall);
  if (hall == LOW)
  {
    hall = hallON;
  }
  else
  {
    hall = hallOFF;
  }
  if ((hall != hallAnt) || (millis() >= proxEnvio))
  {
    // Salva estado do sensor hall
    hallAnt = hall;
    
    // Indica transmissão
    digitalWrite (pinLED, HIGH);

    // Le a temperatura
    temp = leDS18B20();
    
    // Monta mensagem a enviar
    msg[0] = temp >> 8;
    msg[1]= temp & 0xFF;
    msg[2] = hall;

    // Envia    
    Serial.print ("Transmitindo (");
    Serial.print (temp);
    Serial.print (' ');
    Serial.print ((char) hall);
    Serial.print ("): ");
    if (radio.write(msg, 3))
       Serial.println ("Ok");
    else    
       Serial.println ("Erro");

    if (millis() >= proxEnvio)
    {
      // Programa próxima transmissão
      proxEnvio = millis() + tempoTx * 60000UL;
    }

    // Indica fim da transmissão    
    digitalWrite (pinLED, LOW);
  }
}

// Leitura da temperatura
// Retorna temperatura em décimos de grau
// Considera que temos apenas um sensor conectado
// e ele está com a configuração padrão
// Ref: AN162 da Maxim
uint16_t leDS18B20()
{
  uint16_t valor;
  
  if (!OW_Reset())
    Serial.println("Nao achou sensor");
  OW_WriteByte (0xCC);  // Skip ROM
  OW_WriteByte (0x44);  // Start conversion
  delay(750);           // aguarda fim da conversao
  
  OW_Reset();
  OW_WriteByte (0xCC);  // Skip ROM
  OW_WriteByte (0xBE);  // Read ScratchPAD
  valor = OW_ReadByte();                 // LSB
  valor = (OW_ReadByte() << 8) + valor;  // MSB
  OW_Reset();           // Nao queremos o restp

  valor = (valor * 100) / 16;
  return (valor+5)/10;
}

// OneWire reset
// Retorna true se tem um sensor conectado
uint8_t OW_Reset(void)
{
  uint8_t resposta;
  
  setDQ(LOW);
  delayMicroseconds(480);  // comanda reset
  setDQ(HIGH);
  delayMicroseconds(70);  // aguarda sensor responder
  resposta = digitalRead(pinDS18B20);  // le a resposta
  delayMicroseconds(240); // aguarda fim da resposta
  return resposta == LOW;
}

// OneWire Read Byte
uint8_t OW_ReadByte(void)
{
  uint8_t i, resp;
  for (i = 0; i < 8; i++)
  {
    // Pulso de 1uS abre janela para resposta
    setDQ(LOW);
    delayMicroseconds(1);
    setDQ(HIGH);
    // Dá um tempo para sensor colocar a resposta e a lê
    delayMicroseconds(10);
    resp = resp >> 1;
    if (digitalRead(pinDS18B20))
    {
      resp = resp | 0x80;
    }
    // Aguarda o final da janela
    delayMicroseconds(50);
  }
  
  return resp;
}

// OneWire Write Byte
void OW_WriteByte(uint8_t valor)
{
  uint8_t i;
  for (i = 0; i < 8; i++)
  {
    // Low inicia a janela
    setDQ(LOW);
    delayMicroseconds(1);
    if (valor & 0x01)
    {
      // Volta o nivel alto se for "1"
      setDQ(HIGH);
    }
    // Manter o bit até o final da janela
    delayMicroseconds(80);
    // Voltar ao repouso
    setDQ(HIGH);
    // Passar para o próximo bit
    valor = valor >> 1;
  }
  // Tempo de recuperação entre bytes
  delayMicroseconds(1);
}

// Controle da linha DQ do DS18B20
void setDQ(uint8_t state)
{
  if (state == LOW)
  {
    // LOW deve ser forçadp
    pinMode (pinDS18B20, OUTPUT);
    digitalWrite (pinDS18B20, LOW);
  }
  else
  {
    // HIGH é gerado pelo pullup interno
    pinMode (pinDS18B20, INPUT);
    digitalWrite (pinDS18B20, HIGH);
  }
}
No lado do Raspberry Pi, a ligação do rádio continua a mesma. A aplicação abaixo recebe os pacotes, decodifica e grava em um arquivo:
from nrf24 import NRF24
import time
import datetime

addrRx = "DLogr"

radio = NRF24()
radio.begin(0,0,25,24)
radio.setPayloadSize(3)
radio.openReadingPipe(1,addrRx)
radio.startListening()
radio.printDetails()
try:
    while True:
        pipe = [0]
        if radio.available(pipe, False):
            dado = []
            radio.read(dado)
            agora = datetime.datetime.now().strftime("%d/%m/%y %H:%M:%S ")
            temperatura = ((dado[0]*256.0) + dado[1]) / 10.0
            linha = "{0} {1:c} {2:.1f}".format(agora, dado[2], temperatura)
            print linha
            log = open("dado.log", 'a')
            log.write(linha)
            log.write('\n')
            log.close()
        time.sleep(0.1)
except KeyboardInterrupt:
    sys.exit(0)
O meu próximo passo vai ser substituir o Arduino por um MSP430 e ver que performance eu consigo operando com uma bateria de 3V.

4 comentários:

ailton silva disse...

Boa tarde Daniel... primeiramente gostaria de parabeniza-lo pelo poste e pelo otimo trabalho que foi feito. No entanto apesar de esta muito bem feito eu tenho duvidas.
1- eu preciso fazer alguma configuração nos pinos SPI do Raspberry?

2-Voce poderia dizer qual pino do módulo foi conectado em qual pino do RPI?

Grato.

Daniel Quadros disse...

Ailton, dê uma olhada na Parte 1: http://dqsoft.blogspot.com.br/2015/10/usando-o-nrf24l01-com-o-raspberry-pi-um.html. Aqui no blog tem também mais informações sobre o nRF24L01, dê uma olhada nos post antigos,

ailton silva disse...

Ola Daniel... Depois de muito penar conseguir com a ajuda de seu post fazer o codigo ser executado. Porem estou usando um raspberry pi 2 e na impressao dos detalhes do modulo esta todos os endereços zerados, vc saberia como devo conectar o mudulo a esse raspi? ja olhei em varios sites e sempre fica zerado.
Grato.

Daniel Quadros disse...

Em princípio a conexão é a mesma em todos os modelo. Eu fiz o teste com um B+.