segunda-feira, outubro 12, 2015

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

Vamos examinar neste post e no seguinte como ligar o módulo com o nRF24L01+ a um Raspberry Pi e usar o conjunto para registrar em um arquivo os dados recebidos através dele. Parecia um projeto simples, mas deu muito mais trabalho do que eu esperava (apesar de ter vários textos a respeito na internet). Não há nada mais frustrante que um rádio que não comunica: você não enxerga o que está acontecendo (a não ser que tenha ferramentas avançadas, o que não é o meu caso).


Hardware

Como já vimos, a comunicação com o nRF24L01+ é através de SPI. O Raspberry Pi possui uma interface SPI, o que dispensa implementar este protocolo via "bit-banging".

A conexão do módulo ao Raspberry Pi envolve 7 pinos do módulo:
  • GND (1) e VCC (2) são a alimentação e devem ir para GND e 3V3 do Rasp Pi. Usei os pinos 25 e 17, que ficam próximos aos pinos do SPI.
  • CE (3) pode ser ligado a qualquer pino de GPIO (entrada/saída digital). Usei o pino 22 (gpio25).
  • CSN( 4) é ligado ao pino 24 (gpio8), que é o chip select da SPI0.
  • SCK (5) MOSI (6) e MISO (7) são os sinais do SPI e correspondem aos pinos 23, 19 e 21 do Rasp Pi.
  • IRQ (8) pode ser usado pela biblioteca pynrf24 (ver abaixo) e pode ser ligado a qualquer pino de GPIO. Usei o pino 18 (gpio24).
A figura abaixo mostra a montagem, vista pelo lado de cima do Raspberry (cuidado que os modelos B+ e 2 possuem mais pinos para baixo) e do módulo (cuidado que os pinos do módulo saem por baixo).



Software

É aqui que a coisa se complica. Encontrei algumas opções:
  • A biblioteca RF24, que vimos com o Arduino, também suporta o Raspberry Pi. Neste caso a programação deve ser em C++. Esta biblioteca suporta três formas de interagir com o SPI:
    • Através da biblioteca BCM2835, que acessa diretamente os registradores do processador. Este é o default da biblioteca. A biblioteca BCM2835 é sempre usada para acessar os pinos de GPIO.
    • Através do spidev, que é um device padrão do Linux para acesso a interfaces SPI
    • Através da biblioteca MRAA, que foi desenvolvida pela Intel para o Galileu e Edison e também está disponível para o Raspberry.
  • A biblioteca pynrf24, um port para Python da biblioteca RF24 original (a partir da qual foi também desenvolvida a biblioteca acima). Ela permite desenvolver aplicações em python ao custo de uma menor performance (o que não é problema para o uso que pretendo dar). Usa spidev para acesso a interface SPI e RPi.GPIO para acesso ao GPIO

spidev

Se for usado o spidev, ele precisa ser habilitado no Raspberry Pi e o respectivo driver carregado. Uma forma de fazer isto é através do raspi-config:
sudo raspi-config
advanced options
spi
escolha Yes (enable  ARM I2C interface) e Yes ( I2C kernel module loaded by default)
Finish
Yes (reboot)


Você pode verificar que o SPI está disponível através do comando "ls /dev/spidev*". Devem ser apresentados dois dispositivos, o spidev0.0 e o spidev0.1.

Para terminar, vamos dar acesso a estes dispositivos para o usuário pi. A forma mais simples é acrescentar o usuário pi ao grupo spi:
usermod -a -G spi
A documentação da biblioteca RF24 fala que podem ocorrer problemas com o uso da biblioteca BCM2835 com o spidev habilitado, comigo funcionou numa boa.

Numeração do GPIO

Vamos confundir um pouco mais? O RPi.GPIO suporta dois tipos de numeração para os sinais de GPIO. A opção BOARD usa o número do pino no conector. A opção BCM usa o número do gpio conforme a documentação do processador.

Por exemplo, eu liguei o sinal CE ao pino 22 (BOARD) que corresponde ao gpio 25 (BCM). A biblioteca pynrf24 usa a convenção BCM.

A biblioteca BCM2835 usa sempre a convenção BCM e tem defines correspondentes aos pinos na forma RPI_modelo_GPIO_conector_pino onde modelo é o modelo do Raspberry, conector o código do conector na placa e pino o número do pino no conector. Isto pode ser confuso considerando que a maioria dos sinais são os mesmos no conector principal dos vários modelos.

Retomando o meu exemplo do sinal CE, ele corresponde aos defines RPI_GPIO_P1_22, RPI_BPLUS_GPIO_J8_22 e RPI_V2_GPIO_P1_22 todos com o valor 25.

Usando a biblioteca pynrf24

Esta aqui me deixou em transe por mais de uma semana. Alguns bugs e a pouca documentação me fizeram perder muito tempo até conseguir comunicar com um Arduino. No final acabei fazendo um fork do projeto no github com as minhas correções e outras alterações (https://github.com/dquadros/pynrf24). O meu fork ainda é um trabalho em andamento, principalmente a parte de documentação.

A pynrf24 requer (além do spidev que vimos acima) a instalação das bibliotecas RPi.GPIO e py-spidev.
sudo apt-get update
sudo apt-get install python-dev python-rpi.gpio
git clone https://github.com/Gadgetoid/py-spidev.git
cd py-spidev
sudo python setup.py install
cd ..
git clone https://github.com/dquadros/pynrf24.git
cd pynrf24
sudo python setup.py install
 O primeiro programa apenas inicia o módulo e lista os registradores:
from nrf24 import NRF24

radio = NRF24()
radio.begin(0,0,25,24)
radio.printDetails() 
 Reforçando, em begin deve ser usada a numeração BCM (gpio) dos pinos. O resultado que obtive foi:


Note que precisei rodar como root (através do sudo), devido ao acesso ao GPIO.  Uma versão futura do  python-rpi.gpio deve resolver isto.

Para finalizar esta primeira parte, vamos fazer um programa Python para detectar o pressionamento do botão em um Arduino com o meu software de teste e comandar à distância o LED:
from nrf24 import NRF24
import time

addrRx = "2Node" # ou [ 0x32, 0x4E, 0x6F, 0x64, 0x65 ]
addrTx = "1Node" # ou [ 0x31, 0x4E, 0x6F, 0x64, 0x65 ]

radio = NRF24()
radio.begin(0,0,25,24)
radio.openWritingPipe(addrTx) 
radio.openReadingPipe(1,addrRx)
radio.startListening()
radio.printDetails()
try:
    while True:
        pipe = [0]
        if radio.available(pipe, False):
            print "Rx"
            dado = []
            radio.read(dado)
            print dado
            radio.stopListening()
            radio.write(dado)
            radio.startListening()
        time.sleep(0.001)
except KeyboardInterrupt:
    sys.exit(0)    
 O resultado é que você aperta o botão e o LED ao lado acende (e você precisou de um Raspberry, um arduino e dois rádios para isto!).

Referências

Os links abaixo foram úteis durante este estudo:

http://raspi.tv/2013/rpi-gpio-basics-4-setting-up-rpi-gpio-numbering-systems-and-inputs
Uma apresentação rápida da numeração dos pinos de GPIO.

http://www.raspberry-projects.com/pi/pi-operating-systems/raspbian/io-pins-raspbian/spi-pins
Uma descrição curta do uso de SPI no Raspberry Pi.

http://forum.mysensors.org/topic/1151/tutorial-raspberry-pi-nrf24l01-direct-connection
Um exemplo de conexão do nRF24L01 a um Raspberry Pi, usando a biblioteca RF24

http://www.homautomation.org/2014/06/11/communicating-from-low-power-arduino-to-raspberry-pi-via-nrf24l01/
Outro exemplo de conexão do nRF24L01 a um Raspberry Pi, usando a biblioteca RF24, desta vez conversando com um Arduino.

https://www.raspberrypi.org/forums/viewtopic.php?f=45&t=85504
Falando um pouco no uso da pynrf24, no tempo em que ela ainda não suportava  o Raspberry Pi.
 
http://raspberrypi.stackexchange.com/questions/14293/connection-pi-arduino-through-nrf24l01-problem-with-integers Um pergunta simples, mas com um exemplo de uso da pynrf24 que funcionava com a versão da pynrf24 na época (março de 2014). Alterações posteriores da pynrf24 introduziram bugs e diferenças que impedem o funcionamento.

13/02/2016: Alterei as instruções de instalação para usar o meu fork.

22 comentários:

Ale Vecchio disse...

Que legal que você começou a brincar com o nRF24l01. Deixei 1 no Garoa para que pudessem testar a comunicação entre 2 rádios...

Tenho usado a biblioteca deste cara para Arduino. Vi que ele tem algumas coisas para Rasp, no desenvolvimento de rede mesh que ele está fazendo.
http://tmrh20.github.io/

Apareço no Garoa para brincarmos uma hora dessas.

Daniel Quadros disse...

Salve Vecchio! Eu também usei esta biblioteca do tmrh20 no Arduino e ela está mencionada aí no post com o nome RF24. É excelente, mas eu queria fazer uma experiência com Python ao invés de C++. A minha próxima brincadeira é usar o nRF24L01 com o MSP430 para fazer um sensor a bateria.

Unknown disse...

Cara muito legal!
Consegui fz essa conexão e deu um pouquinho de trabalho msm. hehe
Meu desafio agora é conectar Rpi2 + arduíno em uma rede Mesh

Saudações!

Unknown disse...

Ola novamente Daniel... seguir os passos acima porem da um erro quando digito a linha radio.begin(0,0,25,24)
estou usando o raspberry pi2. oque pode esta errado?

Unknown disse...

Ola conseguir resolver o problema anterior,quando dou o comando printDetails(), é exibida toa a configuração do radio, ate ai tudo bem, mas mesmo meu arduino transmitindo para o mesmo canal e endereço que o raspberry esta, o raspberry nao recebe nada ele nao entra na rotina WHILE RADIO.AVAILABLE(PIPE), alguem passou por algo do tipo.
Obrigado

Daniel Quadros disse...

Ailton, você usou os meus programas ou algum outro?

Unknown disse...

usei o seu ja tentei com outros porem acontece a mesma coisa... estou usando um raspberry pi2... sera ja corrigir as conexoes varias vezes sera que pode esta erradas?

Unknown disse...

Esse é o codigo que executei no raspberry
e no Arduino coloquei o mesmo canal e o mesmo endereço , nã definir nenhuma potencia ou velocidade de transmicao.
Quando executo ele a unica cisa que é printada é a frase aguardando dados...


from nrf24 import NRF24
import time

addrRx = [ 0xC2, 0xC2, 0xC2, 0xC2, 0xC3 ]
addrTx = [ 0xC2, 0xC2, 0xC2, 0xC2, 0xC2 ]

radio = NRF24()
radio.begin(0,1,25,21)
radio.setChannel(100)
radio.openWritingPipe(addrRx)
radio.openReadingPipe(1,addrTx)
radio.startListening()
radio.printDetails()
try:
while True:
pipe = [0]
if radio.available(pipe, False):
print "Rx"
dado = []
radio.read(dado)
print dado
radio.stopListening()
radio.write(dado)
radio.startListening()
time.sleep(0.001)
print("Aguardando dados...")
except KeyboardInterrupt:
sys.exit(0)

Daniel Quadros disse...

Ailton, o primeiro passo é você verificar nas informações apresentadas por printDetails se os endereços estão corretos. Se estiverem, provavelmente a ligação ao Raspberry está correta, neste caso você pode postar o código no lado do Arduino para eu dar uma olhada? Qual biblioteca você está usando no Arduino?

Unknown disse...

esse é o codigo do emissor do lado do arduino irei postar os printDetails em outro comentario


#include
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"


float dados[1];


// Inicializa o NRF24L01 nos pinos 9 (CE) e 10 (CS) do Arduino Uno
RF24 radio(9, 10);

// Define o endereco para comunicacao entre os modulos
const uint64_t pipe2 = 0xC2C2C2C2C3LL;
void setup()
{
// Inicializa a serial
Serial.begin(57600);
printf_begin();

// Inicializa a comunicacao do NRF24L01
radio.begin();
radio.setChannel(100);//canal
radio.getDynamicPayloadSize();//tamanho do payload automatico----radio.openWritingPipe(pipe);
radio.openWritingPipe(pipe2);
radio.startListening();
radio.printDetails();
}

void loop()
{
delay(1000)
dados[0] = 99; //////////////////////////////////////////////////////////////////////////
radio.write(dados, 4);
}

Unknown disse...

RASPBERRY:

STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0xc2c2c2c2c3 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xc2c2c2c2c3
RX_PW_P0-6 = 0x05 0x05 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x64
RF_SETUP = 0x04
SETUP_AW = 0x03
OBSERVE_TX = 0x00
CONFIG = 0x0e
FIFO_STATUS = 0x11
DYNPD = 0x00
FEATURE = 0x00
Data Rate = 1MBPS
Model = nRF24l01+
CRC Length = 16 bits
PA Power = PA_HIGH

ARDUINO:

STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0xc2c2c2c2c3 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xc2c2c2c2c3
RX_PW_P0-6 = 0x20 0x00 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x64
RF_SETUP = 0x07


CONFIG = 0x0f
FIFO_STATUS = 0x11
DYNPD = 0x00
FEATURE = 0x00
Data Rate = 1MBPS
Model = nRF24l01+
CRC Length = 16 bits
PA Power = PA_HIGH

quando uso dois Arduinos um enviando e outro recebendo tudo funciona muito bem, ate conseguir receber dados de mais de 6 transmissores. mas quando tento receber no Raspberry nada da certo

caso encontre alguma anormalidade agradeço se me avisa.
de certa forma ja sou grato pela atenção.

Daniel Quadros disse...

Ailton, pelo formato do openReadingPipe, eu suponho que você esteja usando a biblioteca https://github.com/maniacbug/RF24 no Arduino. Eu pessoalmente gostei mais da https://github.com/TMRh20/RF24, mas ela tem algumas diferenças e você vai precisar mexer no seu código. Uma primeira sugestão é colocar um stopListening após o printDetails, pois o Arduino vai transmitir ao invés de receber. Se eu não me atrapalhei ao olhar o código (que é bem confuso) das duas bibliotecas que você está usando, acho que elas codificam os endereços de forma diferente. Experimente colocar pipe2 = 0xC3C2C2C2C2LL no código do Arduino. Se funcionar, avise!

Unknown disse...

Já fiz as alterações que vc sugeriu, e não fez efeito. E sim a biblioteca que estou usando no Arduino é a do maniacbug.

Unknown disse...

É normal esse endereço do CONFIG e do STATUS serem iguais no PrintDetails do raspberry?

Daniel Quadros disse...

Ailton, após algumas horas fazendo testes consegui fazer funcionar.

O primeiro problema é o tamanho dos pacotes. No lado do Arduino você não está especificando o tamanho do pacote, 32 é assumido. Por algum motivo, que não entendi ainda, a biblioteca no Raspberry não trata direito o default, é preciso colocar um radio.setPayloadSize(32) após o setChannel.

O segundo problema são os endereços. No Raspberry, mude para
addrRx = [ 0xC3, 0xC2, 0xC2, 0xC2, 0xC2 ]
radio.openWritingPipe(addrTx)
radio.openReadingPipe(1,addrRx)

No lado do Raspberry fiz os testes com a minha versão da biblioteca, no Arduino usei a versão do maniacbug.

Daniel Quadros disse...

Ailton, acabo de perceber que eu estava usando a biblioteca Python original, a minha versão inicia corretamente o tamanho de pacote com 32. A versão original iniciava com 5.

Unknown disse...

Daniel, muito obrigado pela ajuda voce tinha razão, foi so fazer essas alteracões e comecei a receber os dados, so vou ter que arrumar o programa do raspberry pois o arduino envia dados do tipo float e o raspberry esta com colocando os dos numa variavel do tipo int oq deixa os numeros diferentes, mas acredito que isso nao dará trabalho... fico muito agradecido pelo seu empenho em ajudar-me muito obrigado mesmo. uma pequena duvida: quando mando um valor maior que 255 o receptor gerar um numero diferente em cada posicaodo do vetor que recebe os dados(No receptor), vc usou alguma tecnica para corrigir esse valor? ou vc não precisou enviar um numero tao grande?

Exemplo eu mando o numero 258, e no receptor ele fica [2, 1, 0, 0] preciso que esse valor seja corrigido para o numero 258.(estou usando um payload de 4Bytes)

Grato.

Unknown disse...

Alguem ai tentou receber um numero maior q 1Byte ou algum numero float no raspberry?

Daniel Quadros disse...

Ailton, vamos lá. O nRF24 basicamente transfere bytes. Na hora de transmitir algo mais complexo o melhor é definir um formato a usar na comunicação e converter entre este formato e o formato "nativo" em cada ponta.

Por exemplo, vamos supor que você quer enviar um número inteiro sem sinal do Arduino para o Raspberry Pi. No Arduino, um inteiro tem 16 bits, podendo ir de 0 a 65535. Um formato de comunicação possível é mandar como texto (5 dígitos, 00000 a 65535). Um formato mais eficiente é enviar em binário como dois bytes, por exemplo enviando primeiro (num % 256) e depois (num / 256). No lado do Raspberry, basta multiplicar o segundo byte por 256 e somar o primeiro.

No seu programa você está enviando direto o conteúdo binário de um float, para montar de volta o valor você precisaria entender como os números floats são armazenados no Arduino e no Python. Tem uma explicação na wikipedia https://en.wikipedia.org/wiki/Single-precision_floating-point_format (Arduino) e https://en.wikipedia.org/wiki/Double-precision_floating-point_format (Python), mas é assunto bastante complicado. Existe também a questão da ordem em que os bytes são colocados na memória. Portanto o ideal é você enviar de alguma outra forma.

Unknown disse...

Mais uma vez obrigado pela explicação, tentarei uma outra forma de enviar ,ou tentarei migrar do python para C++ e ver se acontece a mesma coisa pois no arduino funciona corretamente. Muito Obrigado por toda ajuda esclarecimentos.

Unknown disse...

Opa, estou a um bom tempo tentando fazer funcionar no pi2, porem semprre fail no envio, não sei bem se pode ser a conexão das portas ou a configuração das portas, tentei usar

//RF24 radio(RPI_V2_GPIO_P1_15, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ);
//RF24 radio(RPI_BPLUS_GPIO_J8_15,RPI_V2_GPIO_P1_24,BCM2835_SPI_SPEED_8MHZ);
//RF24 radio(22,8,BCM2835_SPI_SPEED_8MHZ);

RF24 radio(RPI_V2_GPIO_P1_22,0,BCM2835_SPI_SPEED_8MHZ);

tentei diversas formas, porem sem sucesso.

Daniel Quadros disse...

Eu não fiz testes com C++, mas
(a) os parâmetros para a declaração dependem da conexão que você fez e a biblioteca usada para acessar o hardware (por default é a BCM2835)
(b) existem várias possibilidades de falha de envio, inclusive o envio ter sido feito mas o receptor não ter recebido corretamente e enviado o ACK
Uma sugestão é usar o printDetails() para confirmar que o programa está conseguindo escrever e ler no nRF24L01.