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.
  1. // Exemplo de sensor enviando dados através do nRF24L01+   
  2. // Daniel Quadros, 13/10/15  
  3.   
  4. #include <SPI.h>  
  5. #include <RF24.h>  
  6.   
  7. // Conexoes  
  8. const int pinLED = 6;  
  9. const int pinHall = 7;  
  10. const int pinDS18B20 = 8;  
  11. const int pinCE = 9;  
  12. const int pinCSN = 10;  
  13.   
  14. // Este objeto encapsula o acesso ao nRF24L01+  
  15. RF24 radio(pinCE, pinCSN);  
  16.   
  17. // Endereços  
  18. byte addrLogger[6] = "DLogr";  
  19.   
  20. // Estado anterior do sensor de efeito Hall  
  21. const uint8_t hallOFF = '0';  
  22. const uint8_t hallON  = '1';  
  23. const uint8_t hallUNDEF = 'X';  
  24. uint8_t hallAnt = hallUNDEF;  
  25.   
  26. // Tempo em minutos entre transmissões periodicas  
  27. const unsigned long tempoTx = 10;  
  28.   
  29. // Próximo envio automático  
  30. unsigned long proxEnvio;  
  31.   
  32. void setup()   
  33. {  
  34.   // Serial para debug  
  35.   Serial.begin(9600);  
  36.   Serial.println("Sensor com nRF24L01+");  
  37.   
  38.   // Iniciacao dos pinos de LED e sensores  
  39.   pinMode (pinLED, OUTPUT);  
  40.   pinMode (pinHall, INPUT);  
  41.   digitalWrite (pinHall, HIGH);  
  42.   setDQ(HIGH);  
  43.   
  44.   // Inicia o rádio  
  45.   radio.begin();  
  46.   radio.setPayloadSize(3);  
  47.   radio.openWritingPipe(addrLogger);  
  48.     
  49.   // Primeiro envio  
  50.   proxEnvio = millis() + tempoTx * 60000UL;  
  51. }  
  52.   
  53. void loop()   
  54. {  
  55.   uint8_t hall;  
  56.   uint8_t msg[3];  
  57.   uint16_t temp;  
  58.   
  59.   hall = digitalRead(pinHall);  
  60.   if (hall == LOW)  
  61.   {  
  62.     hall = hallON;  
  63.   }  
  64.   else  
  65.   {  
  66.     hall = hallOFF;  
  67.   }  
  68.   if ((hall != hallAnt) || (millis() >= proxEnvio))  
  69.   {  
  70.     // Salva estado do sensor hall  
  71.     hallAnt = hall;  
  72.       
  73.     // Indica transmissão  
  74.     digitalWrite (pinLED, HIGH);  
  75.   
  76.     // Le a temperatura  
  77.     temp = leDS18B20();  
  78.       
  79.     // Monta mensagem a enviar  
  80.     msg[0] = temp >> 8;  
  81.     msg[1]= temp & 0xFF;  
  82.     msg[2] = hall;  
  83.   
  84.     // Envia      
  85.     Serial.print ("Transmitindo (");  
  86.     Serial.print (temp);  
  87.     Serial.print (' ');  
  88.     Serial.print ((char) hall);  
  89.     Serial.print ("): ");  
  90.     if (radio.write(msg, 3))  
  91.        Serial.println ("Ok");  
  92.     else      
  93.        Serial.println ("Erro");  
  94.   
  95.     if (millis() >= proxEnvio)  
  96.     {  
  97.       // Programa próxima transmissão  
  98.       proxEnvio = millis() + tempoTx * 60000UL;  
  99.     }  
  100.   
  101.     // Indica fim da transmissão      
  102.     digitalWrite (pinLED, LOW);  
  103.   }  
  104. }  
  105.   
  106. // Leitura da temperatura  
  107. // Retorna temperatura em décimos de grau  
  108. // Considera que temos apenas um sensor conectado  
  109. // e ele está com a configuração padrão  
  110. // Ref: AN162 da Maxim  
  111. uint16_t leDS18B20()  
  112. {  
  113.   uint16_t valor;  
  114.     
  115.   if (!OW_Reset())  
  116.     Serial.println("Nao achou sensor");  
  117.   OW_WriteByte (0xCC);  // Skip ROM  
  118.   OW_WriteByte (0x44);  // Start conversion  
  119.   delay(750);           // aguarda fim da conversao  
  120.     
  121.   OW_Reset();  
  122.   OW_WriteByte (0xCC);  // Skip ROM  
  123.   OW_WriteByte (0xBE);  // Read ScratchPAD  
  124.   valor = OW_ReadByte();                 // LSB  
  125.   valor = (OW_ReadByte() << 8) + valor;  // MSB  
  126.   OW_Reset();           // Nao queremos o restp  
  127.   
  128.   valor = (valor * 100) / 16;  
  129.   return (valor+5)/10;  
  130. }  
  131.   
  132. // OneWire reset  
  133. // Retorna true se tem um sensor conectado  
  134. uint8_t OW_Reset(void)  
  135. {  
  136.   uint8_t resposta;  
  137.     
  138.   setDQ(LOW);  
  139.   delayMicroseconds(480);  // comanda reset  
  140.   setDQ(HIGH);  
  141.   delayMicroseconds(70);  // aguarda sensor responder  
  142.   resposta = digitalRead(pinDS18B20);  // le a resposta  
  143.   delayMicroseconds(240); // aguarda fim da resposta  
  144.   return resposta == LOW;  
  145. }  
  146.   
  147. // OneWire Read Byte  
  148. uint8_t OW_ReadByte(void)  
  149. {  
  150.   uint8_t i, resp;  
  151.   for (i = 0; i < 8; i++)  
  152.   {  
  153.     // Pulso de 1uS abre janela para resposta  
  154.     setDQ(LOW);  
  155.     delayMicroseconds(1);  
  156.     setDQ(HIGH);  
  157.     // Dá um tempo para sensor colocar a resposta e a lê  
  158.     delayMicroseconds(10);  
  159.     resp = resp >> 1;  
  160.     if (digitalRead(pinDS18B20))  
  161.     {  
  162.       resp = resp | 0x80;  
  163.     }  
  164.     // Aguarda o final da janela  
  165.     delayMicroseconds(50);  
  166.   }  
  167.     
  168.   return resp;  
  169. }  
  170.   
  171. // OneWire Write Byte  
  172. void OW_WriteByte(uint8_t valor)  
  173. {  
  174.   uint8_t i;  
  175.   for (i = 0; i < 8; i++)  
  176.   {  
  177.     // Low inicia a janela  
  178.     setDQ(LOW);  
  179.     delayMicroseconds(1);  
  180.     if (valor & 0x01)  
  181.     {  
  182.       // Volta o nivel alto se for "1"  
  183.       setDQ(HIGH);  
  184.     }  
  185.     // Manter o bit até o final da janela  
  186.     delayMicroseconds(80);  
  187.     // Voltar ao repouso  
  188.     setDQ(HIGH);  
  189.     // Passar para o próximo bit  
  190.     valor = valor >> 1;  
  191.   }  
  192.   // Tempo de recuperação entre bytes  
  193.   delayMicroseconds(1);  
  194. }  
  195.   
  196. // Controle da linha DQ do DS18B20  
  197. void setDQ(uint8_t state)  
  198. {  
  199.   if (state == LOW)  
  200.   {  
  201.     // LOW deve ser forçadp  
  202.     pinMode (pinDS18B20, OUTPUT);  
  203.     digitalWrite (pinDS18B20, LOW);  
  204.   }  
  205.   else  
  206.   {  
  207.     // HIGH é gerado pelo pullup interno  
  208.     pinMode (pinDS18B20, INPUT);  
  209.     digitalWrite (pinDS18B20, HIGH);  
  210.   }  
  211. }  
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:
  1. from nrf24 import NRF24  
  2. import time  
  3. import datetime  
  4.   
  5. addrRx = "DLogr"  
  6.   
  7. radio = NRF24()  
  8. radio.begin(0,0,25,24)  
  9. radio.setPayloadSize(3)  
  10. radio.openReadingPipe(1,addrRx)  
  11. radio.startListening()  
  12. radio.printDetails()  
  13. try:  
  14.     while True:  
  15.         pipe = [0]  
  16.         if radio.available(pipe, False):  
  17.             dado = []  
  18.             radio.read(dado)  
  19.             agora = datetime.datetime.now().strftime("%d/%m/%y %H:%M:%S ")  
  20.             temperatura = ((dado[0]*256.0) + dado[1]) / 10.0  
  21.             linha = "{0} {1:c} {2:.1f}".format(agora, dado[2], temperatura)  
  22.             print linha  
  23.             log = open("dado.log"'a')  
  24.             log.write(linha)  
  25.             log.write('\n')  
  26.             log.close()  
  27.         time.sleep(0.1)  
  28. except KeyboardInterrupt:  
  29.     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:

Unknown 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,

Unknown 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+.