JohnHoward, how are you supplying 3.3v to the RF module from the pro minis? Or are you using the 3.3v version of the mini?
I'm mostly using nano's, but since the pro minis are so cheap on ebay these days, I'm thinking I won't even use headers. I'll just solder wires directly from the mini to the rf24 module and to a temp sensor to minimize cost and space.
Since ultimately I plan to connect some PIR sensors that need a 5v supply, I run the minis at 5v (which means faster clock speed, too). I failed to notice the mini's didn't have 3v3 output when I bought them, otherwise I'd have a bunch of nanos.
JohnHoward:
Sorry if I'm being dense but are you saying:
#define NODEACK(x) (0xCC00000000LL + x)
should be edited to replace 'x' with a number? I compiled verbatim, with the 'x'. I presume #define doesn't know what 'x' is and ends up using zero? I'm astonished there is no compiler error due to x being undefined, which it seems to be as I could not find anything anywhere declaring it in the code files.
Google "C macro tutorial". #define is not just for constant values.
x is an argument in the macro definition, analogous to a argument declared in a function header. It doesn't have any value until you instantiate the macro, e.g., NODEACK(1) gets translated to (0xCC00000000LL + 1) by the preprocessor, and that's what the compiler sees.
Ah, the light comes on. I wasn't thinking in terms of a macro but as a constant declaration. After 15 years of writing macro assembler code on PDP-11's in the olden days, you'd think I'd have made the connection.
I noticed the missing braces yesterday while I was getting rid of the gotos from node.ino. I just couldn't deal with them any longer.
This seems to be working for me. I mostly just added a while loop. Posted in its entirety for clarity.
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
RF24 radio(9,10);
#define RELAYBROADCAST(x) (0xAA00000000LL + x)
#define NODEACK(x) (0xCC00000000LL + x)
struct SENSOR{
float temp;
float humidity;
float pressure;
};
struct HEADER{
long type;
long hops;
long src;
long ID;
SENSOR sensor;
};
HEADER header;
long cnt[10] = {};
byte cntID = 0;
byte retries = 0; //how many times have we tried to rx
const byte MAX_RETRIES = 5; //how many times will we try?
void setup(void){
Serial.begin(57600);
radio.begin();
radio.setRetries(15,15);
radio.enableDynamicPayloads();
randomSeed(analogRead(0));
Serial.println("node starting...");
radio.openReadingPipe(1 ,NODEACK(1));
radio.openWritingPipe(RELAYBROADCAST(2));
radio.startListening();
}
void loop(void){
header.ID = random(1, 0xffff); //this is above the label for testing
while (retries < MAX_RETRIES) {
radio.stopListening();
header.type = 3;
header.hops = 0;
header.src = 0xabcd;
header.sensor.temp = 78.8;
Serial.println(header.ID, HEX);
//send a relay broadcast to any relay that can hear
radio.openWritingPipe(RELAYBROADCAST(1));
radio.write( &header, sizeof(header), true );
radio.startListening();
// Wait here until we get a response, or timeout (250ms)
unsigned long started_waiting_at = millis();
bool timeout = false;
while ( ! radio.available() && ! timeout )
if (millis() - started_waiting_at > 200 )
timeout = true;
// Describe the results
if ( timeout ){
Serial.print("NACK");
retries++;
}
else {
long src; //ack returns just the header.ID, so check what was returned with what was sent
radio.read( &src, radio.getDynamicPayloadSize() );
if (src == header.ID) {
Serial.print("ACK: "); Serial.println(src, HEX);
retries = 0;
break;
}
}
}
retries = 0;
delay(3000);
//testing
if ( Serial.available() )
{
radio.stopListening();
Serial.read();
header.type = 3;
header.hops = 0;
header.src = 0xabcd;
header.ID = random(1, 0xffff);
Serial.println(header.ID, HEX);
//send a relay broadcast
radio.openWritingPipe(RELAYBROADCAST(2));
bool ok = radio.write( &header, sizeof(header), true );
radio.startListening();
}
}
I took the liberty of making the code more structured and (I hope, readable):
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
RF24 radio(9,10);
const long long // Use pipe + 1 for nodes, pipe + 2 for relays
RELAYBROADCAST = 0xAA00000000LL,
NODEACK = 0xCC00000000LL;
struct SENSOR{
float temp;
float humidity;
float pressure;
};
struct HEADER{
long type;
long hops;
long src;
long ID;
SENSOR sensor;
};
HEADER header;
long cnt[10] = {};
byte cntID = 0;
byte retries = 0; // How many times have we tried to rx
const byte MAX_RETRIES = 10; // How many times will we try?
void setup(void){
Serial.begin(57600);
radio.begin();
radio.setRetries(15,15);
radio.enableDynamicPayloads();
randomSeed(analogRead(0));
Serial.println(F("node starting..."));
radio.openReadingPipe(1 ,NODEACK + 1); // Read the 'node' pipe
relay(header, 2); // Send using the 'relay' pipe
}
void loop(void){
header.ID = random(1, 0xffff); // Identify this packet
xmit(header.ID); // Send some data
wait(MAX_RETRIES, header); // Wait for it to be acknowledged
delay(3000); // Pause before repeating
beARelay(); // If serial port connected send some more data (for what purpose?)
}
// Send some data to the base
void xmit(long id) {
HEADER header;
header.ID = id;
header.type = 3;
header.hops = 0;
header.src = 0xabcd;
header.sensor.temp = 18.8;
Serial.print(F("XMIT: "));Serial.println(header.ID, HEX);
relay(header, 1); // Send using the 'node' pipe
}
// Get Ack from relay or timeout
void wait(byte retries, struct HEADER header) {
do {
// Wait here until we get a response, or timeout (250ms)
unsigned long started_waiting_at = millis();
if (millis() - started_waiting_at > 250 ) {
retries = nak();
}
else {
retries = ack();
}
} while (retries > 0);
}
// Signal a NAK
byte nak() {
Serial.print(F("NACK: ")); Serial.println(header.ID, HEX);
return --retries;
}
// Signal an ACK
byte ack() {
if (radio.available()) {
long src; //ack returns just the header.ID, so check what was returned with what was sent
radio.read( &src, radio.getDynamicPayloadSize() );
if (src == header.ID) {
Serial.print(F("ACK: ")); Serial.println(src, HEX);
retries = 0;
}
else {
Serial.print(F("???: ")); Serial.print(src, HEX); Serial.print(" vs "); Serial.println(header.ID, HEX);
}
}
return retries;
}
// If something is typed on the Serial console, pretend to be a relay and send a relay broadcast
void beARelay() {
if ( Serial.available() )
{
Serial.read();
HEADER header;
header.type = 3;
header.hops = 0;
header.src = 0xabcd;
header.ID = random(1, 0xffff);
Serial.print(F("TEST: ")); Serial.println(header.ID, HEX);
relay(header, 2);
}
}
// Send packet to any relay that can hear this node
bool relay(struct HEADER header, byte pipe_id) {
radio.stopListening();
radio.openWritingPipe(RELAYBROADCAST + pipe_id);
bool ok = radio.write( &header, sizeof(header), true );
radio.startListening();
return ok;
}
Curious thing, the node doesn't print out that many ACKs. I get a steady stream of 'XMIT:" output on the node's console without an "ACK:" appearing. However, watching the relay and master, I see the node's packet appearing. So there's something amiss in the relay, it would seem. I tried both increasing and decreasing the 'turnaround time' - delay(20) - as well as the timing in the node, to no avail.
That is more readable, but I don't understand why you reallocate header in xmit() and beARelay()when it is already a global variable. I'd leave the redeclaration of HEADER header out.
Also, as in the original node, the delay(3000) and then checking for Serial.available() just doesn't seem like a good idea. The delay should be removed, and replaced with something like:
long previousMillis = 0;
void loop(void){
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > 3000) {
previousMillis = currentMillis;
header.ID = random(1, 0xffff); // Identify this packet
xmit(header.ID); // Send some data
wait(MAX_RETRIES, header); // Wait for it to be acknowledged
beARelay();
}
}
True I neglected to remove the global header declaration. My preference was to allocate the header within the function that would utilize it, rather than have one globally reused. That's my OCD kicking in -- disliking data structures that lay around and run the risk of getting corrupted if the radio functions screw up. I know there will be an intact, clean header structure each time the radio gets it.
I hoped to clear up the sporadic malformed packets with huge hop counts or other bogus data, assuming it was data corruption of the global structure. But they still show up from time to time.
That's surprising since the nRF24L01 is supposed to be managing the end-to-end data transfers to avoid corruption in-transit.
After having time to think about it (but not enough time to actually test it), the code I posted without the delay missed the point of checking for serial availability without pausing. The call to beARelay()is not supposed to be inside the if(currentMillis... block. That defeats the whole purpose of it. To make it more explicit, I'd move the check for serial out of beARelay, and back into the main loop like this:
long previousMillis = 0;
void loop(void){
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > 3000){
previousMillis = currentMillis;
header.ID = random(1, 0xffff); // Identify this packet
xmit(header.ID); // Send some data
wait(MAX_RETRIES, header); // Wait for it to be acknowledged
}
if (Serial.available()){
beARelay();
}
}
Hopefully, that makes sense.
On large hop counts- I noticed that since the fix to the relay, I'm only seeing large hop counts when I'm at the edge of the radio's range. I was seeing this when I moved a node upstairs. I moved the relay just a few inches closer (and away from a metal lamp), and it ran overnight without a non-zero hop count.
I just ordered a RF24 radio with an external antenna to see how that affect the range.
I resolved my angst over global HEADER declarations!
Also, I found that relay() transmits would fail fairly often (sporadically) when I added another Serial.print to watch the radio.write(). So I implemented a retry there if the radio.write() returns false. Now seems to send/ack flawlessly. I suppose there are collisions with other nodes transmitting simultaneously, as this happened quite randomly.
My latest 'node' code:
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
RF24 radio(9,10);
const long long // Use pipe + 1 for nodes, pipe + 2 for relays
RELAYBROADCAST = 0xAA00000000LL,
NODEACK = 0xCC00000000LL;
struct SENSOR{
float temp;
float humidity;
float pressure;
};
struct HEADER{
long type;
long hops;
long src;
long ID;
SENSOR sensor;
};
long cnt[10] = {};
byte cntID = 0;
byte retries = 0; // How many times have we tried to rx
const byte MAX_RETRIES = 5; // How many times will we try?
long myID;
void setup(void){
Serial.begin(57600);
radio.begin();
radio.setRetries(15,15);
radio.enableDynamicPayloads();
randomSeed(analogRead(0));
Serial.println(F("node starting..."));
radio.openReadingPipe(1 ,NODEACK + 1); // Read the 'node' pipe
myID = random(1, 0xffff); // Identify this packet
xmit(myID, 2); // Send using the 'relay' pipe
}
void loop(void){
myID = random(1, 0xffff); // Identify this packet
xmit(myID, 1); // Send some data
wait(MAX_RETRIES, myID); // Wait for it to be acknowledged
delay(3000); // Pause before repeating
beARelay(); // If serial port connected send some more data (for what purpose?)
}
// Get Ack from relay or timeout
void wait(byte retries, long myID) {
bool reply;
do {
unsigned long started_waiting_at = millis();
while (millis() - started_waiting_at < 250) {
// Wait here until we get a response, or timeout (250ms)
if (reply = radio.available()) break;
}
retries--;
} while (retries > 0 && !reply);
if (reply) {
ack(reply, myID);
}
else {
nak(myID);
}
}
// Signal a NAK
void nak(long myID) {
Serial.print(F("NACK: ")); Serial.println(myID, HEX);
}
// Signal an ACK
void ack(bool reply, long myID) {
if (reply) {
long src; //ack returns just the header.ID, so check what was returned with what was sent
radio.read( &src, radio.getDynamicPayloadSize() );
if (src == myID) {
Serial.print(F("ACK: ")); Serial.println(src, HEX);
}
// else { // Display packets destined for nodes other than us
// Serial.print(src, HEX);Serial.print(" = "); Serial.println(myID, HEX);
// }
}
else {
Serial.println(F("NOT AVAILABLE"));
}
}
// If something is typed on the Serial console, pretend to be a relay and send a relay broadcast
void beARelay() {
if ( Serial.available() )
{
Serial.read();
myID = random(1, 0xffff);
xmit(myID, 2);
Serial.print(F("TEST: ")); Serial.println(myID, HEX);
}
}
// Send some data to the base
void xmit(long myID, byte pipe_id) {
HEADER header;
header.ID = myID;
header.type = 3;
header.hops = 0;
header.src = 0xabcd;
header.sensor.temp = 18.8;
Serial.print(F("XMIT: "));Serial.println(header.ID, HEX);
byte retries = 5;
bool ok;
do {
ok = relay(header, pipe_id); // Send using the 'node' pipe
} while (!ok && --retries > 0);
}
// Send packet to any relay that can hear this node
bool relay(struct HEADER header, byte pipe_id) {
radio.stopListening();
radio.openWritingPipe(RELAYBROADCAST + pipe_id);
bool ok = radio.write( &header, sizeof(header), true );
Serial.print(ok ? F("SENT ") : F("FAILED TO SEND ")); // Seems to fail to transmit often
Serial.println(header.ID, HEX);
radio.startListening();
return ok;
}
"RETRIES" indicates a radio.write() that returned false and was retried (usually it retries several times)
"XMITS" are radio.write() that returned true
"ACKS" are received acknowledgements
The good news is most messages get through. The bad news is it takes lots of trying to get there. For my planned purposes, I don't see this being detrimental. I would only need to send a message at intervals of several minutes. So retrying several times won't matter. And if I have to send out twice as many and just throw out the extras to be sure they get received, I'm OK with that.
I really should try to count the receptions at the base to find out whether ACKS are missed because they were not sent back from the base or were simply not heard by the node for some reason.
I just hope the success rate doesn't fall off rapidly as the number of nodes increases. I still have to prepare 6 more nodes and possibly another relay depending on what I find in actual operation. I will get that question answered once I have them running.
Great work on fixing the relay issue and rewriting the node code.
Anyways, I've updated the github code with the new node and relay code (and given credit where due, of course).
Oh, and as a sidenote. I didn't mention it in my initial post, but the hardware layout for the relays and base (in my head) had them being LNA PA radios, and should be able to reach 1k meters. All the nodes would be in range of one of the relays. If they couldn't reach, another relay would be placed somewhere in range. With such a large range, all the relays should be in range of all the others, adding a lot redundancy. They PA radios are slightly more expensive, but probably less expensive than adding several cheaper ones to make range. They can be found on any of the Chinese sites http://www.aliexpress.com/item/Special-promotions-1100-meter-long-distance-NRF24L01-PA-LNA-wireless-modules-with-antenna/921867287.html
The next thing I'll be working on is the sleep code for nodes. There is no reason for them to be running while they are just waiting for the next time to send. I've already had it working in previous incarnations of this project and is quite simple. I had planned for the relays and nodes to be externally powered, but my nodes will be powered from a coin cell.
There's a bug in the new node code. I added some print statements in the base to print the received temp, humidity, and pressure. In the new node code I added one line to set the pressure to a constant:
In the prior versions of node, I would receive the temp and pressure values on the base, but with the current version I receive an ovf (overflow) for the pressure. The temp is still ok. Seems like a fencepost error somewhere, but I haven't found it yet.
transfinite:
There's a bug in the new node code. I added some print statements in the base to print the received temp, humidity, and pressure. In the new node code I added one line to set the pressure to a constant:
In the prior versions of node, I would receive the temp and pressure values on the base, but with the current version I receive an ovf (overflow) for the pressure. The temp is still ok. Seems like a fencepost error somewhere, but I haven't found it yet.
-transfinite
Every so often, the packet contents has huge numbers in it. Sometimes I find the packet size vary from the normal 28 bytes to 20 or 24. Somehow the framing is off.
printf detects the value it was given is larger than 4294967040 (FFFFFF00) and outputs 'ovf'. Evidently the SPI reads or writes are screwing up, if we are to believe the nRF24L01 communicates error-free (which it apparently does because there are no checksum errors).
Perhaps retrying more slowly is the answer, as I added 'node' code to do so whenever radio.write() returns false.
Bizarre that you consistently get large hop counts. Have you filtered the 3v3 with a 10mF capacitor (or larger) @ the nRF? I would be suspicious of noisy power causing erratic reading of the data.
Notice how they are not merely random but repetitive: 0x10101010 0x20202020, etc.
I was confused, and still am bothered, by the macros (such as BASEBROADCAST().
The original poster started from some code another person wrote and carried through that means of assigning addresses. I thought it was unnecessary and while fiddling around with the code to modularize the code I took them out. I was only working in the node, however, so the macros are still in the relay and base.
However, the three sketches do work together without changing any of the radio pipe addresses.
I'm still refining my approach by writing classes for each of the 3 modes of operation so it becomes easier to drop them into other sketches. Further, my goal is to add some functionality to the base and nodes such that they pull a unique ID from the base and store it in their EEPROM thus facilitating knowing which node is sending data.
I am intending to create some sensor nodes and simply plug in the radio support when I had a sensor doing what I want it to do so that the information would be passed to a base and recorded.