nRF24 on Raspberry Pi

NOTE This post has been quite successful, so I decided to publish a complete series on nRF24 transceivers.

I’m working on a home automation project and I’m planning to use my Raspberry Pi as central node of a network of cheap nRF24 nodes.

First thing is to get the necessary compilation tools, something quite easy to achieve even on my RaspBMC installation:

$> apt-get install build-essential

With the compiler and the other tools at your hand you might want to get a library to get access to the nRF24 hardware, which is not a difficult step to achieve either:

$> git clone https://github.com/stanleyseow/RF24.git
$> cd RF24
$> cd librf24-rpi/librf24
// compile the files
$> make
// install the library
$> sudo make install
// check the library availability
$> sudo ldconfig -v | grep librf
      librf24.so.1 -> librf24.so.1.0

Now, let’s move to the wiring, but DO NOT attempt any connection while your Raspberry is powered up: even a brief short circuit made with a floating cable getting contact for a fraction of millisecond can ruin your day.

I’ve found some small difficulties here, mainly due to misleading information. Please refer to the pictures below and click on them to enlarge.

raspi-nrf24 raspi-nrf24-schema

Once you have everything in place you can power up your Pi and starting to get some fun!

All the code below is available as a Gist on GitHub for your convenience.

You can start with the examples contained within the library itself, but if you feel brave enough here is the code I’m executing on my Pi:

// file payload.h
#ifndef __VIRIDI_NEXU_PAYLOAD__
#define __VIRIDI_NEXU_PAYLOAD__

enum PayloadType {
    HERBA, METEO
};

typedef uint8_t vn_payload_type;
typedef uint8_t vn_payload_version;

typedef struct {
        int16_t humidity;
        int16_t temperature;
        int16_t pressure;
        int16_t altitude;
        int16_t luminosity;
} vn_meteo_t;

typedef struct {
        int16_t moisture;
        int16_t temperature;
} vn_plant_t;

struct Payload {
        vn_payload_type type;
        vn_payload_version version;

        union {
            vn_meteo_t meteo;
            vn_plant_t plant;
        } data;
};

#endif // __VIRIDI_NEXU_PAYLOAD__
// file receiver.cpp
#include <cstdlib>
#include <iostream>
#include "librf24/RF24.h"
#include "payload.h"
using namespace std;

//
// Hardware configuration
//
RF24 radio("/dev/spidev0.0", 8000000 , 25);  //spi device, speed and CSN,only CSN is NEEDED in RPI
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

Payload payload = Payload();

//
// Setup
//
void setup(void) {
  radio.begin();
  radio.setRetries(15, 15);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.setPayloadSize(sizeof(payload));
  radio.openWritingPipe(pipes[1]);
  radio.openReadingPipe(1, pipes[0]);
  radio.startListening();
  radio.printDetails();
}

//
// Loop
//
void loop(void) {
  if (radio.available()) {
    radio.read(&amp;amp;amp;amp;amp;payload, sizeof(payload));
    printf("packet %d %d %d %d %d \n", payload.data.meteo.temperature, payload.data.meteo.humidity, payload.data.meteo.luminosity, payload.data.meteo.altitude, payload.data.meteo.pressure);
  }
}

int main(int argc, char** argv){
        setup();
        while(1)
                loop();

        return 0;
}

To build the above code you just save the two files and execute gcc providing the necessary parameters: if you get an error regarding a missing header file you might have to adjust the librf/RF24.h include directive accordingly to your build location.

$> g++ -Wall -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -L../librf24/ -lrf24 receiver.cpp -o receiver

Remeber you must be have access to the /dev/spidev device to execute your program, the simplest way to get such permission is by running your receiver code as root:

$> sudo ./receiver

Now let me give you one last advice: always take in consideration differences in hardware architecture when you program/compile your communcation software!
In particular, if you send something from an Arduino (8 bit microcontroller) take in consideration there’s an important difference when the same data is read from the Raspberry (32 bit ARM processor):

  • float numbers are 4 bytes long on Arduino and 8 bytes on Raspberry
  • enumeration are 2 bytes on Arduino and 4 bytes on Raspberry
  • any struct on Raspberry is padded to even bytes
  • and so forth

What this means while developing for the nRF24 chips is that you need to take in consideration the architecture differences when transferring data and ensure the data types can be handled by both ends of the communication channel: my suggestion is to avoid floating points and stick to integers. If you need to transfer fractional values, switch the number to a higher scale: for example instead of transferring a temperature sensor reading of 25.12 C°  (Centigrade degrees), multiply the value by 1000 and transfer it as 25120 mC° (milli Centigrade degrees).

On the Arduino side there are many tutorials out there, but for the sake of completeness here is my wiring and the simple software sending out the payload for the Raspberry to read.

rf24_bbrf24_scheme

And here is the Arduino sketch broadcasting the data into the air

#include "payload.h"

#define SERIAL_DEBUG true
#include <SerialDebug.h>

#define LED_DEBUG true
#include <LedDebug.h>

#define SENSE_DELAY 2000
Payload payload = (Payload) { METEO };

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

uint32_t lastSense;
inline bool sense() {
	long now = millis();
	if (now < lastSense || now - lastSense > SENSE_DELAY) {
		payload.data.meteo.humidity = millis();
		payload.data.meteo.temperature = millis() * 50;
		payload.data.meteo.pressure = millis();
		payload.data.meteo.altitude = millis();
		payload.data.meteo.luminosity = map(analogRead(A0), 0, 1024, 100, 0);
		lastSense = now;
		return true;
	} else {
		return false;
	}
}

#define RADIO_CE_PIN 9
#define RADIO_CS_PIN 10
#define RADIO_RETRY_DELAY 15
#define RADIO_RETRY_COUNT 15
RF24 radio = RF24(RADIO_CE_PIN, RADIO_CS_PIN);
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

void setup() {
	SERIAL_DEBUG_SETUP(9600);
	pinMode(A0, INPUT);

	radio.begin();
	radio.setRetries(RADIO_RETRY_DELAY, RADIO_RETRY_COUNT);
	radio.setDataRate(RF24_250KBPS);
	radio.setPALevel(RF24_PA_MAX);
	radio.setPayloadSize(sizeof(payload));
	radio.openWritingPipe(pipes[0]);
	radio.openReadingPipe(1,pipes[1]);
}

void loop() {
	if (sense()) {
		radio.powerUp();
		if (!radio.write(&payload, sizeof(payload))) {
			PULSE(3,75);
		} else {
			PULSE(1,225);
		}
		radio.powerDown();

		DEBUG("payload", sizeof(payload), "enum", sizeof(payload.type));
	}
}

 

Advertisements