Part 1 - The Nodes
In this post - I'm going to look at the wireless node master that will handle the input from the 3 wireless nodes I have around the house (from the previous post) using NRF24L01+ 2.4ghz transceivers.
The wiring of the Arduino for this part is relatively simple as I'm going to use a full size Arduino Uno.
Wiring
The NRF24L01+ wired to the Aruino Uno |
Uno | NRF24L01+ |
---|---|
13 | SCLK |
12 | MOSI |
11 | MISO |
7 | CSN |
6 | CE |
2 | IRQ |
3.3v | Vcc |
GND | GND |
The wiring here differs slightly in the pin numbering from the previous project because I'm leaving room on Pin 8 for a 433Mhz receiver (for another stage of the project). I'm also using Pin 2 connected to the IRQ pin of the transceiver - this will allow us to use an interrupt to process the data as soon as the transceiver receives it, without having to poll the unit constantly.
Interacting with the RF24 Module
My node master code is on Github.
I use the RF24 Library for my Arduino code. Download the Library and include it in your code.
The first thing I wanted to do was figure out what information I wanted to transmit. Basically, I wanted to get the temperature the node was reporting, the internal voltage, the battery voltage, the uptime of node, and the address of the node. To do this, I build a struct data type on both the nodes and the master - this makes it easy to transmit a complete package of information: Structs.h
#ifndef structs_h #define structs_h typedef struct { volatile unsigned long _micros; volatile float temp; volatile long Vcc; volatile int Vbatt; volatile byte address; volatile long uptime; } dataPacket; #endif
The volatile keyword in the data structure means that when the Arduino accesses that variable, it always uses the value in RAM. This allows the value to be updated during an interrupt, and be reflected no matter where or when the value is read.
Now, in the main program, I import all the relevant packages
#include "SPI.h" #include "nRF24L01.h" #include "RF24.h" #include "printf.h" #include "structs.h"
I store the addresses for the nodes in an array, and create the node variables I will store the info in:
byte address[][6] = {"0node","1node","2node","3node"}; // master radio in address[0] byte master = 0; int myAddress; dataPacket dataPackage; dataPacket node1; dataPacket node2; dataPacket node3; dataPacket empty;
Next - initialize the radio
const int rPin1 = 6; // radio pins, SPI plus 7,6 const int rPin2 = 7; RF24 radio(rPin1,rPin2);
In setup():
1. Begin the radio interface
2. Disable auto acknowledge - this means that the sending and receiving radios won't acknowledge packets. It will increase packet loss but save battery life
3. Open up read ports for each address - this will allow each node to write to this unit
4. Open up a writing port for the master address - this isn't being used currently but would allow us to talk back to each node
5. Power the radio on
6. Enable the listening interface
7. Print the radio details for debugging 8. Enable the pin mode interrupt on Pin 0 (digital pin 2 on the Uno) - when this pin is pulled low, the radio has received a packet and it will call the check_radio function
void setup(){ #if defined DEBUG Serial.begin(38400); #endif Serial.println("Start"); printf_begin(); radio.begin(); radio.setAutoAck(false); // open pipes for communication for (int i=1; i < sizeof(address) - 1; i++) { radio.openReadingPipe(i, address[i]); } radio.openWritingPipe(address[master]); // power up radio // this takes up to 5ms radio.powerUp(); delay(10); radio.startListening(); radio.printDetails(); //interrupt on pin 2 (interrupt 0) // Will be pulled LOW when receiving attachInterrupt(0, check_radio, LOW); }
The check_radio function is called whenever an interrupt is received on Digital pin 2 - which means the radio has done something.
- It checks to see what happened - packet sent, or received? This will be received in our case.
- It reads the data in to the dataPackage variable - this contains all the information we want from the node.
- It parses the dataPackage, and depending on the address puts it in one of 3 node variables.
void check_radio(void) { #ifdef DEBUG Serial.println("IRQ received"); #endif bool tx,fail,rx; radio.whatHappened(tx,fail,rx); // What happened? if ( tx ) { // Have we successfully transmitted? // Do nothing } if ( fail ) { // Have we failed to transmit? printf("Failure\n"); } if ( rx || radio.available()){ // Did we receive a message? radio.read(&dataPackage, sizeof(dataPackage)); unsigned long time = millis() / 1000; if (dataPackage.address == 1) { node1 = dataPackage; } else if (dataPackage.address == 2) { node2 = dataPackage; } else if (dataPackage.address == 3) { node3 = dataPackage; } } }
Currently, as this is just a proof of concept/debug setup - the loop() function simply prints the latest information it has every minute.
void printInfo(dataPacket data) { Serial.print("Up: ");Serial.print(data.uptime);Serial.print("s, "); Serial.print(data._micros);Serial.print(", "); Serial.print(data.temp);Serial.print(", "); Serial.print(dataPackage.Vbatt);Serial.print(", "); Serial.print(data.Vcc);Serial.print(", "); Serial.println(data.address); return; } void loop() { startTime = millis()/1000; int elapsed = startTime - endTime; if (elapsed == interval) { Serial.print("heartbeat (s): "); Serial.println(startTime); endTime = startTime; // every interval, display current information if (node1.address) { Serial.print("Node 1 - "); //Serial.println(time); printInfo(node1); } if (node2.address) { Serial.print("Node 2 - "); //Serial.println(time); printInfo(node2); } } }
This gives us this output every minute:
Current information 52140 Node 1 - Up: 3320s, 34056211, 13.00, 7.82, 5.00, 1 Node 2 - Up: 3231s, 24546830, 21.50, 10.04, 5.02, 2
The next step is going to be to clean up the interface, and add in a 433Mhz receiver to receive from a LaCrosse TX9U wireless weather station.