rf24HQ: a new library for the nRF24L01+ RF transceiver.

I've released a library for the nRF24L01 here on github: GitHub - harlequin-tech/rf24HQ: An Arduino library for the Nordic nRF24L01+ RF transceiver.. Its version 0.1, but it seems to be stable and I think its easier to use the Mirf and should be easy to extend.

It includes a Stream class (RFStream) that operates on top of the radio's packet interface. It lets you use the familiar available(), read(), and print() stream functions with the radio. It has example sketches for the streamer, and also for the scan function that lets you implement a basic RF scanner across the radio's 2.4GHz spectrum.

If you try it out and find problems, or its missing features, feel free to open an issue on the github project for support. I'll be working on it to add documentation for the functions, and to add features like full shock-burst support with 6 receive channels.

Here's the streamer example sketch (tested on an UNO):

/*
 * rfstream1.ino rf24HQ stream demo.
 *
 * Sends all characters received from the serial monitor to the partner radio.
 * Sends all characters received from the partner radio to the serial monitor.
 *
 * Run rfstream2.ino on the partner radio to connect to this sketch.
 *
 * Released to the public domain.
 *
 */

/*
 * The nrf24l01+ module should be connected to as follows:
 *
 *    rf24    atmega328
 *    ----    ---------
 *    GND  -> GND
 *    VCC  -> 3.3V
 *    CE   -> digital pin 8
 *    CSN  -> digital pin 9
 *    SCK  -> digital pin 13
 *    MOSI -> digital pin 11
 *    MISO -> digital pin 12
 */

#include <SPI.h>
#include <rf24HQ.h>

rf24 rf(8,9,100,RF24_MAX_SIZE);
RFStream rfstream;

const char myAddress[] = "rad01";			/* The rx address for this radio */
const char txAddress[] = "rad02";			/* Send data to this address */

void setup() 
{
    Serial.begin(115200);
    rf.begin((Print *)&Serial);
    rfstream.begin(&rf, (Print *)&Serial);

    rf.setRxAddr(1, myAddress);
    rf.setTxAddr(txAddress);

    /* Turn on auto-ack, 1000 microsecond timeouts, 15 retries */
    rf.enableAck(1000, 15);

    Serial.print(myAddress);
    Serial.println(" ready");

    rfstream.print(myAddress);
    rfstream.println(F(" ready"));
}

void loop() 
{
    if (rfstream.available()) {
        Serial.write(rfstream.read());
    }
    if (Serial.available()) {
        char ch = Serial.read();
        rfstream.write(ch);
	Serial.write(ch);
    }
}

A quick look (not going to use the lib fro now)

void rf24::tx(const void *data, uint8_t len, uint8_t max)
{
  for (uint8_t ind=0; ind < len; ind++) {
    if (ind < max) {
      transfer(((uint8_t *)data)[ind]);
    } else {
      transfer(0);
    }
  }
}

can be simpler I guess . You don't want to send all those zero's or do I miss something? (RX idem)

void rf24::tx(const void *data, uint8_t len, uint8_t max)
{
  if (len < max) 
  {
    for (uint8_t ind=0; ind < len; ind++) transfer(((uint8_t *)data)[ind]);
  } else {
    transfer(0);
  }
}

functionname mismatches content ? as also an RX ADDR is set,...

void rf24::setTxAddr(const void *addr)
{
    writeReg(RX_ADDR_P0, (const uint8_t *)addr, RF24_ADDR_LEN);
    writeReg(TX_ADDR, (const uint8_t *)addr, RF24_ADDR_LEN);
}

this code is blocking, you might add a test function boolean canSend(); so the user has the choice to test is it would block?

void rf24::send(void *data, uint8_t size)
{
    while (isSending(false));

    if (delay < 1) {
delay = 1;
    }
    delay = (delay + 249) / 250;
    if (delay > 15) {
delay = 15; /* Max 4000us */
    }

delay = constrain(delay,1,15); // simpler ?


why not test the value of start too? > 125?

void rf24::scan(uint8_t *chans, uint8_t start, uint8_t count, uint8_t depth)
{
    uint8_t end = start+count;

    if (end > 125) {
end = 125;
    }

So some small remarks, in general it looks well done.

Thanks for sharing!

Wow! I'd like to try this! Looking forward to improvements and more testing...

Hi Rob,

thanks for taking a look.

robtillaart:
A quick look (not going to use the lib fro now)

void rf24::tx(const void *data, uint8_t len, uint8_t max)

{
  for (uint8_t ind=0; ind < len; ind++) {
    if (ind < max) {
      transfer(((uint8_t *)data)[ind]);
    } else {
      transfer(0);
    }
  }
}




can be simpler I guess . You don't want to send all those zero's or do I miss something? (RX idem)


void rf24::tx(const void *data, uint8_t len, uint8_t max)
{
  if (len < max)
  {
    for (uint8_t ind=0; ind < len; ind++) transfer(((uint8_t *)data)[ind]);
  } else {
    transfer(0);
  }
}





---

The function is intended to be used internally to write data into registers. The case where it is used to transfer zeros is for the send() function where the user provides less data than the current packet length, so the tx() function fill out the rest of the tx buffer with zeros.

For rx(), yes I can probably not bother reading in zero's - I just haven't experimented with reading fewer bytes than the register holds yet.

functionname mismatches content ? as also an RX ADDR is set,...

void rf24::setTxAddr(const void *addr)

{
    writeReg(RX_ADDR_P0, (const uint8_t *)addr, RF24_ADDR_LEN);
    writeReg(TX_ADDR, (const uint8_t *)addr, RF24_ADDR_LEN);
}





---

Good point. The RX_ADDR_P0 address is set for use with the auto-ack feature. I'll update this so it only sets the RX address if auto-ack is enabled, and I'll have the enableAck() function copy the TX address to RX_ADDR_P0.

this code is blocking, you might add a test function boolean canSend(); so the user has the choice to test is it would block?

void rf24::send(void *data, uint8_t size)

{
    while (isSending(false));

Yes it blocks. The user can call isSending() to see if the tx is busy - which I think is the same as canSend()?

I've considered ways to support all three TX buffers, but I haven't worked out a way to let the user work out if a transmit was successful in this case. Using only one TX buffer means gotAck() works correctly.


    if (delay < 1) {

delay = 1;
    }
    delay = (delay + 249) / 250;
    if (delay > 15) {
delay = 15; /* Max 4000us */
    }




delay = constrain(delay,1,15); // simpler ?



---


why not test the value of start too? > 125?


void rf24::scan(uint8_t *chans, uint8_t start, uint8_t count, uint8_t depth)
{
    uint8_t end = start+count;

if (end > 125) {
end = 125;
    }

Okay I can make this a bit safer :-).

So some small remarks, in general it looks well done.

Thanks for sharing!

Cheers!

Hi DHunt,

Just tried your rfscan example and it worked well, but am not sure of the results and how I use them,

At the moment I am using channel 90 with my Arduino and nRF24L01+ module.

Where on the chart produced would channnel 90 be, would it be the 2492MHz result ?
Also is it best to be using the channel with the least Bars or the most Bars ?

And one final question how do you see your rf24HQ library developing in the future ?

Thanks for your great work !

Regards

Gary

Results:
2453MHz = 2 --
2454MHz = 2 --
2455MHz = 4 ----
2456MHz = 2 --
2457MHz = 2 --
2458MHz = 3 ---
2459MHz = 3 ---
2460MHz = 2 --
2461MHz = 3 ---
2462MHz = 4 ----
2463MHz = 2 --
2464MHz = 2 --
2465MHz = 3 ---
2466MHz = 3 ---
2467MHz = 2 --
2468MHz = 2 --
2469MHz = 4 ----
2470MHz = 2 --
2492MHz = 1 -

Hi Quest,

The scanner shows an approximate measure of the signal strength at each frequency by estimating how busy each frequency is. It does this by checking how many times it sees a signal strength of -64dBm or higher when sampling the frequency 200 times.

The frequency is 2400MHz plus the channel, so channel 90 is 2490MHz.

If you have your second radio powered off or not transmitting, then the scan will show you which frequencies are in use in your environment. You should choose a clear frequency for the radio that is not being used, and at least a few channels away from any active channels.

Fancy! I don't own any nRF24L01+'s yet but I have an order on the way (and I've been perusing the datasheet)...
Any functions to set the CRC length? I might peruse the datasheet some more and see if anything else is missing. Looks like a solid foundation for a nice library to me.

edit:
Looks like you'd need to keep track of the 1 vs 2-byte CRC status, and include that with the various setConfig() usages throughout the library (mainly enableRx and enableTx)...

rf24 rf(8,9,100,RF24_MAX_SIZE);

Appears to set the payload size to 32-bytes.

    /* Turn on auto-ack, 1000 microsecond timeouts, 15 retries */
    rf.enableAck(1000, 15);

I didn't look to see which data rate was in use, but this is potentially an issue or at least a gotcha for would-be users. If 250Kbps mode is selected, this can result in erroneous TX errors. In 250Kbps mode, the timeout should be 1500uS or greater given a payload size of 32-bytes. Given the comments of '0'-fill, I assume this means you're using fixed length payloads rather than dynamic payloads.

Now I'm assuming the data mode is NOT 250Kbps, but if anyone is attempting to get maximum range by simply changing their data rate via the demo, they'll definitely be led astray based on errant and possibly non-obvious results.

Given its a demo, you might considering adding comments to reference page 33 (section 7.4.2; Auto Retransmission) of the P-data sheet, and/or simply change the timeout to 1500; making it universally safe regardless of desired data rate.

Hi Thought I would test out this library as it seems more complete then MIRF and could allow more fine tune control over the radio.

So I tested this on some of my nodes and noticed that RX/TX ADDR fields are all out of wack.

If I flash over my existing sketch that uses MIRF and has a RX ADDR of "Nodes" with this library without powering down the radio I can run rf.dumpRegisters and see RX_ADDR_P0 = "Nodes"

if I then
rf.setTxAddr("Nodes");
rf.setRxAddr(1, "Nodes");
rf.setRxAddr(2, "Nodes");
rf.setRxAddr(3, "sedoN");

rf.dumpRegisters shows
RX_ADDR_P0 = "Nodes"
RX_ADDR_P1 = "sedoN"
RX_ADDR_P2 = "s"
RX_ADDR_P3 = "N"
TX_ADDR = "sedoN"

so it seems to show that the TX/RX addresses are reversed and for RX ADDR >P1 are only stored as a single CHAR

if I power completely power down and back up the radio then P0 and P2-P5 defaults to the below
RX_ADDR_P0 (0A): E7 E7 E7 E7 E7 .....
RX_ADDR_P5 (0F): C6 .

Hi Dhunt,

I am unable to get your code working sending/receiving data.
I can get the RFscan working but not much luck with anything else I have this loaded onto 4 nodes and none are receiving data. I currently use MIRF without issue on the same hardware OK.

For the code I use pin10 instead of pin8 for CE and use the same ADDR for both RX&TX

This is the code so far;

//RFnode rf24HQ test sketch RFnode_RF24HQ_test -GR0B 15/05/12

#include <SPI.h>
#include <rf24HQ.h>

const int led_red_pin = 4;            
const int led_green_pin = 5;
uint8_t CE_Pin =10;
uint8_t CSN_Pin =9;
uint8_t RF_channel = 1;
uint8_t RF_packetSize = 16; // max = 32


rf24 rf(CE_Pin,CSN_Pin,RF_channel,RF_packetSize);          //  (CE_Pin,  CSN_pin,  RF_channel,  PacketSize<32)


//Led_R blink
void Led_R(){
  digitalWrite(led_red_pin, HIGH);                                           
  delay(1);            
  digitalWrite(led_red_pin, LOW);   
}

//Led_G blink
void Led_G(){                               
  digitalWrite(led_green_pin, HIGH);                                         
  delay(1);            
  digitalWrite(led_green_pin, LOW);   
}


void setup(){
  Serial.begin(9600);
  Serial.println("Booting RFnode"); 
  pinMode(led_red_pin, OUTPUT);           
  pinMode(led_green_pin, OUTPUT);    
  Led_R();
  Led_G();

    rf.begin((Print *)&Serial);           //start wireless 
    rf.setTxAddr("Nodes");                                //this seems to get reveresed  
    rf.setRxAddr(1, "Nodes");                             //0 is ok, 1 is reversed, 2-5 single char
    rf.setSpeed(RF24_SPEED_MAX);                          //RF24_SPEED_MIN=2=250Kbps  0=1Mbps  RF24_SPEED_MAX=1=2Mbps   note: need to double check this
    rf.dumpRegisters();
    Serial.println("Wireless initialized.");
}

void loop(){
  Led_R();
  Led_G();
//  rf.dumpRegisters();


//send a packet
     char data[16] = "Hello world";
     rf.send(data, sizeof(data));
     //Debug.Dump whole sent packet as HEX
     Serial.print(">>Sent: ");                                           
     for (int i = 0; i < 16; i = i + 1) {
        Serial.print(data[i],HEX);
        Serial.print(",");
     }
     Serial.println(); 


//receive packets
     while (rf.available()) {
	 rf.read(data, sizeof(data));
         //Debug.Dump whole received packet as HEX
         Serial.print("<<Got: ");                                           
         for (int i = 0; i < 16; i = i + 1) {
           Serial.print(data[i],HEX);
           Serial.print(",");
         }
         Serial.println();  
     }
delay(1000);
}