The NRF24L01+ and the AVR xMega processor

Posted by Tom on Jan 14, 2014

There are dozens of pages on the web showing how to use the NRF24L01+. Just google! So why do we need another? Perhaps because I'm a childish brat who wants to say, "Me too! Me too!"

Counterfeit NRF24L01+ is so readily available, so cheap and so easy to use, that it just seems silly to use wires, or even strings, to communicate data between devices. You can find them for a dollar or two on ebay, and for less than 10 dollars, you can find them with amplifiers and antennas that extend the range dramatically. Even with just the PC trace antennae, I've made them work 2 or 3 hundred feet away. Of course, I also paid $10 for a pair from dx.com that have stubby antennae and seem to work half as far as the 1 dollar variety. Go figure.

There is an older chip from Nordic Semiconductor called the NRF24L01. Here, I'm talking specifically about the NRF24L01+ part. I don't think you'll even find the older ones anywhere.

Beware of cheap imitations. I've never actually seen one of these, but if I don't say this, somebody will say I'm not warning you. If I have seen a cheap imitation, it must work pretty well 'cause I don't know it. And indeed, I have! ALL my modules are counterfeit. They might actually be SI24R1 parts fraudulently labled NRF24L01+. See Hackaday NRF24L01+ Real vs Fake

Beware also of Mouser? Now, Mouser is one of my reliable sources, and on small quantities like I buy, they often beat Digikey on price. I have no idea why Mouser thinks NRF24L01+ modules are $40+! Don't believe me? Just search!

xMega

If you're used to AVR tiny and mega processors, the xMega might be a bit of a shock. I know it was to me. Except for the numerous errata, they do work mostly as described in the PDFs. Realize, though, that there are 2 PDFs. One contains all the programming info for the "family" of xMega parts, and one the specifics for the part you have in your hand, but only sketchy programming information. Why? I do not know, go ask your dad.

With many examples on the web for ATTiny and ATMega, I'll show code for the xMega.

Style A

This is the first style I bought, and I paid about 2 bucks each for 10 of them several years ago. Outdoors, these reach about 200 ft. I could set one on my workbench and go out my apartment door, down the stairs, out the front door of the building and a good ways down the driveway.

Style B

These came from dx.com for about 10 bucks. I figured as well as the plain ones work that these should go a long ways! They were a grave disappointment. They reach about 1/2 as far as Style A. There was a long discussion at avrfreaks.net as to why this might be. Some thought they'd work well mounted in a metal box with just the antennae peeking out. I haven't tried that.

Style C

This style has only 8 pins, providing just 1 for +3 and ground. I haven't had a chance to compare their range to Style A, but they seem to work very nicely. They are physically smaller. Nordic Semiconductor shows layouts for both Style A and Style C in their data sheets.

I took a train controlled by a remote and receiver I built with a pair of these to a big train show. I expected a madcap comedy along the lines of keystone cops as I chased a runaway train, or couldn't get it to go, but the silly thing behaved flawlessly all weekend. At one point, somebody asked about the range. "I don't know, it works as far as I've ever tried it." So I started walking away and counting steps. The remote display showed if it get an ack from a packet, and I found a place where I could step forward one step and have contact, back one step and loose contact. I was 300 ft away. I didn't expect it to work that far. This was in a pretty noisy environment with toy trains everywhere and lots of 2.4GHz devices.

Style D

I got this for $6 from ebay. I have an application where Style B modules almost work, so I'm hoping this and a Style B will work at low speed.

Should have got 2 and experimented with them, but budget was horribly limited at the time.

Wiring it up

The NRF24L01+ chip has 6 connections to your processor:

  • SI - Serial in. Goes to your MOSI.
  • SO - Serial Out. Goes to your MISO.
  • SC - Serial Clock. Goes to your SCK.
  • CS - Chip Select. Put it low to talk to the NRF.
  • CE - Chip Enable. Out it to a port pin 'cause you'll need to drive it high and low.
  • INT - Optionally connect this to an interrupt pin so the NRF can tell you when a message has arrived or has been sent.

It's a 3v part

The NRF is a 3v part. While its processor connections can be connected to a 5v system, it needs 3v, not 5 on it's + input.

Cure for the common code

So, here's how I organize my programs: Poorly? I keep a library of .h and .c files in a library folder and include them as "links" in any programs I wish to use them. Any specifics about the project are specified in Config.h. So all the .c files in the library folder include Config.h so they know the specifics of the project. If you want to use library folders this way, see this thread at AVRFreaks.net where CLAWSON instructed me. Otherwise, just put the info in your project and be done with it. What? Don't you use the word "betwixt" in everyday conversation?

SPI in the XMega

spi.h

/*
 * spi.h
 *
 * Created: 9/4/2013 3:31:11 PM
 *  Author: Tom
 */



#ifndef SPI_H_
#define SPI_H_

#include <avr/io.h>

void spiopen(void);
uint8_t spi(uint8_t val);
void spiclose(void);


#endif /* SPI_H_ */

My SPI code provides 3 functions:

  1. void spiopen(void) This prepairs the SPI gizmo for use.
  2. void spiclose(void) This unprepairs the SPI gizmo for use and puts the direction of the port pins back the way they were. You may not need this, but like a knucklehead, I used the top of PortC for another function as well.
  3. uint8_t spi(uint8_t val) This sends a byte out to the spi device and returns the byte that came from the spi device. SPI is full duplex, as you shift bits out, bits shift in.

spi.c

/*
 * spi.c
 *
 * Created: 9/4/2013 3:32:39 PM
 *  Author: Tom
 */


#include "spi.h"
#include "Config.h"

/* Defines needed to make this work
   Usually defined in Config.h

   #define SPIPort PORTC
   #define SPIMO   7
   #define SPIMI   6
   #define SPISC   5
   #define SPISS   4

   */


uint8_t SPIDir = 0 ;

void spiopen(void)
{
    SPIDir = SPIPort.DIR ;
    SPIPort.DIRCLR = (1<<SPIMI);
    SPIPort.DIRSET = (1<<SPIMO) | (1<<SPISC) | (1<<SPISS) ; // Make sure slave select is an output
    SPIC.CTRL = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_MODE_0_gc | SPI_PRESCALER_DIV64_gc;
}



void spiclose(void)
{
    SPIC.CTRL = 0;
    SPIPort.DIR = SPIDir ;
    //SPIPort.DIRSET = (1<<SPIMI);
}

uint8_t spi(uint8_t val)
{
    SPIC.DATA = val ;
    while(!(SPIC.STATUS & (1<<7)));
    return SPIC.DATA;
}

Notice that the .c file includes the Config.h file. Because I always forget, there's a comment there to tell me what it expects to find in the Config.h. You see that on this project, SPI is the top 4 bits of PORT C. Some XMega parts seem to have it on the lower bits of PORT C.

Now, you see that I make use of a feature of the XMega devices, or maybe just of the io.h for them. I don't know, but it works handily. I can specify an equate for one of the ports, then refer to all the registers of that port, since they are just items in a struct.

So, I save the direction control of port C into a local variable so that spiclose can put it back the way it was. Then I set the SPIMI pin to an input, the SPIMO, SPISC and SPISS pins to outputs. Important to set the slave select pin to output or the spi gadget will confuse you because it's trying to be the spi slave, not the spi master. I then set up the SPI.CTRL register. For want of a good reason, I always use SPI_PRESCALER_DIV_64. I probably should make that a parameter, but it seems to work this way in 32MHz systems as well as 2MHz systems, so I don't worry about it.

SPIclose just undoes what spiopen did.

The spi function does the work. You see that in XMega, it's very simple. It puts the byte to SPIC.DATA, waits for bit 7 in the SPIC.STATUS register to go high, then fetches SPIC.DATA to return to the caller.

There you have the basics of spi in an XMega. True, the article is about NRF, but we had to get spi working first.

The code we've all been waiting for

nordic.h

/*
 * nordic.h
 *
 * Created:12/16/2013 3:36:04 PM
 *  Author: Tom
 *
 *    NRF24L01+ Library II
 *
 */
 


#ifndef NORDIC_H_
#define NORDIC_H_


#include <avr/io.h>

uint8_t NRFLastSent[6];

uint8_t NRFReadRegister(uint8_t reg);
uint8_t NRFWriteRegister(uint8_t reg, uint8_t value);

// Initialize SPI and the Nordic

void NRFInit(uint8_t pSpeed);
#define NRFslow 0b00100110
#define NRFfast 0b00001110
#define NRFhalffast 0b00000110


void NRFSetRxAddress(const __memx uint8_t * Bytes5);
void NRFSetChannel(uint8_t Ch) ; // 0 through 83 only!

#define dNRFModePD 0 ;
#define dNRFModeRX 3 ;
#define dNRFModeTX 2 ;

uint8_t NRFGetMode(void) ;

void NRFModePowerDown(void);
void NRFModeTX(void);
void NRFModeRX(void);
uint8_t NRFIsModePowerDown(void);
uint8_t NRFIsModeTX(void);
uint8_t NRFIsModeRx(void);


uint8_t NRFSendPacket(const __memx uint8_t *ToAddress, uint8_t *Packet, uint8_t Length); 
     // return 0 if no ack

void NRFSendPacketNoAck(const __memx uint8_t *ToAddress, uint8_t *Packet, uint8_t Length); 
     // don't wait for ack

uint8_t NRFReceivePacket( uint8_t * Packet); 
     // returns 0 if  no packet to receive. Packet may be up to 32 bytes


uint8_t GetPW(void); // returns width of last packet received

#endif /* NORDIC_H_ */

nordic.c

/*
 * nordic.c
 *
 * Created: 9/4/2013 3:37:25 PM
 *  Author: Tom
 *  Say what?
 */

#include "Nordic.h"
#include "Config.h"
#include "spi.h"
#include <avr/io.h>
#include <string.h>
#include <util/delay.h>

/*    Defines needed to make this work
    Usually defined by something included by Config.h

#define RFcsPORT PORTC
#define RFcsn 4
#define RFcePORT PORTA
#define RFce 0

*/



#define CMD_READ_REG        0x00  // Define read command to register
#define CMD_WRITE_REG       0x20  // Define write command to register
#define CMD_RD_RX_PLOAD     0x61  // Define RX payload register address
#define CMD_WR_TX_PLOAD     0xA0  // Define TX payload register address
#define CMD_FLUSH_TX        0xE1  // Define flush TX register command
#define CMD_FLUSH_RX        0xE2  // Define flush RX register command
#define CMD_REUSE_TX_PL     0xE3  // Define reuse TX payload register command
#define CMD_R_RX_PL_WID        0x60  // Read RX Payload Width
#define CMD_W_ACK_PAYLOAD    0xA8  // Write ACK payload for Pipe (Add pipe number 0-6)
#define CMD_W_TX_PLOADD_NK    0xB0  // Write ACK payload for not ack
#define CMD_NOP             0xFF  // Define No Operation, might be used to read status register

// SPI(nRF24L01) registers(addresses)
#define RG_CONFIG          0x00  // 'Config' register address
#define RG_EN_AA           0x01  // 'Enable Auto Acknowledgment' register address
#define RG_EN_RXADDR       0x02  // 'Enabled RX addresses' register address
#define RG_SETUP_AW        0x03  // 'Setup address width' register address
#define RG_SETUP_RETR      0x04  // 'Setup Auto. Retrans' register address
#define RG_RF_CH           0x05  // 'RF channel' register address
#define RG_RF_SETUP        0x06  // 'RF setup' register address
#define RG_STATUS          0x07  // 'Status' register address
#define RG_OBSERVE_TX      0x08  // 'Observe TX' register address
#define RG_CD              0x09  // 'Carrier Detect' register address
#define RG_RX_ADDR_P0      0x0A  // 'RX address pipe0' register address
#define RG_RX_ADDR_P1      0x0B  // 'RX address pipe1' register address
#define RG_RX_ADDR_P2      0x0C  // 'RX address pipe2' register address
#define RG_RX_ADDR_P3      0x0D  // 'RX address pipe3' register address
#define RG_RX_ADDR_P4      0x0E  // 'RX address pipe4' register address
#define RG_RX_ADDR_P5      0x0F  // 'RX address pipe5' register address
#define RG_TX_ADDR         0x10  // 'TX address' register address
#define RG_RX_PW_P0        0x11  // 'RX payload width, pipe0' register address
#define RG_RX_PW_P1        0x12  // 'RX payload width, pipe1' register address
#define RG_RX_PW_P2        0x13  // 'RX payload width, pipe2' register address
#define RG_RX_PW_P3        0x14  // 'RX payload width, pipe3' register address
#define RG_RX_PW_P4        0x15  // 'RX payload width, pipe4' register address
#define RG_RX_PW_P5        0x16  // 'RX payload width, pipe5' register address
#define RG_FIFO_STATUS     0x17  // 'FIFO Status Register' register address
#define RG_DYNPD            0x1C  // 'Enable dynamic payload length
#define RG_FEATURE            0x1D  // 'Feature register

#define MASK_RX_P_NO        0x0E
#define MASK_IRQ_FLAGS      0x70
#define MASK_RX_DR_FLAG     0x40
#define MASK_TX_DS_FLAG     0x20
#define MASK_MAX_RT_FLAG    0x10
#define MASK_RX_FIFO_EMPTY  0x01

#define ModeBits 0b00111100

#define CSHIGH RFcsPORT.OUTSET = (1<<RFcsn)
#define CSLOW RFcsPORT.OUTCLR = (1<<RFcsn)
#define CEHIGH RFcePORT.OUTSET = (1<<RFce)
#define CELOW RFcePORT.OUTCLR = (1<<RFce)

static __inline__ uint8_t CS(uint8_t hl)
{
    if (hl==1)
    {
        spiopen();
        CSLOW;
    }
    else
    {
        CSHIGH;
        spiclose();
    }
    return hl;
}

#define CHIPSELECT for (uint8_t cs = CS(1); cs==1; cs = CS(0))

// Internal use
uint8_t WriteBuffer(uint8_t reg, const __memx uint8_t *pBuf, uint8_t bytes);
uint8_t ReadBuffer(uint8_t reg, uint8_t *pBuf, uint8_t bytes);
void NRFSetTxAddress( const __memx uint8_t * SendTo);
uint8_t NRFIsRxPacket(void);



uint8_t NRFWriteRegister(uint8_t reg, uint8_t value)
{
    uint8_t status;
    CHIPSELECT
    {
        // Select register
        status = spi(CMD_WRITE_REG+reg);

        // Write value to it
        spi(value);
    }

    return(status);
}
uint8_t NRFReadRegister(uint8_t reg)
{
    uint8_t reg_val;

    CHIPSELECT
    {
        // Select register to read from..
        spi(CMD_READ_REG+reg);

        // ..then read register value
        reg_val = spi(0);

    }
    return(reg_val);
}

uint8_t WriteBuffer(uint8_t reg, const __memx uint8_t *pBuf, uint8_t bytes)
{
    uint8_t status, i;

    // Start SPI comm, set CSN/SS low
    CHIPSELECT
    {
        // Select register to write to and read status byte
        status = spi(CMD_WRITE_REG+reg);

        // Write buffer
        for(i = 0; i < bytes; i++)
        {
            spi(*pBuf++);
        }
    }
    return(status);
}

uint8_t ReadBuffer(uint8_t reg, uint8_t *pBuf, uint8_t bytes)
{
    uint8_t status,
    i;

    CHIPSELECT
    {
        // Select register to write to and read status byte
        status = spi(CMD_READ_REG+reg);

        for(i = 0; i < bytes; i++) {
            pBuf[i] = spi(0);
        }

    }
    return(status);
}

void NRFSetRxAddress(const __memx uint8_t * Bytes5)
{
    WriteBuffer(RG_RX_ADDR_P0, Bytes5, 5);
    WriteBuffer(RG_RX_ADDR_P1, Bytes5, 5);
}

void NRFSetTxAddress(const __memx uint8_t * SendTo)
{
    WriteBuffer(RG_TX_ADDR, SendTo,5);
    WriteBuffer(RG_RX_ADDR_P0,SendTo,5); // the ACK will come to pipe 0
    for (uint8_t i = 0; i<5; i++) NRFLastSent[i] = SendTo[i];
    NRFLastSent[5] = 0;
}


uint8_t NRFGetMode(void)
{
    return NRFReadRegister(RG_CONFIG)&2 ;
}

void NRFModePowerDown(void)
{
    CELOW;
    NRFWriteRegister(RG_CONFIG,ModeBits) ;
}

void NRFModeTX(void)
{
    CELOW;
    uint8_t m = NRFReadRegister(RG_CONFIG);
    NRFWriteRegister(RG_CONFIG,ModeBits|2) ;
    if ((m&2)==0) _delay_ms(5);
}

void NRFModeRX(void)
{
    NRFWriteRegister(RG_CONFIG,ModeBits|3);
    CEHIGH;
    //if ((m&2)==0) _delay_ms(5); You don't need to wait. Just nothing will come for 5ms or more
}

uint8_t NRFIsModePowerDown(void)
{
    uint8_t ret = 0 ;
    if ((NRFReadRegister(RG_CONFIG)&2)==0) ret = 1;
    return ret;
}

uint8_t NRFIsModeTX(void)
{
    uint8_t m = NRFReadRegister(RG_CONFIG);
    if ((m&2)==0) return 0;
    if ((m&1)==0) return 1;
    return 0;
}

uint8_t NRFIsModeRx(void)
{
    uint8_t m = NRFReadRegister(RG_CONFIG);
    if ((m&2)==0) return 0;
    if ((m&1)==0) return 0;
    return 1;
}

void NRFSetChannel(uint8_t Ch)
{
    NRFWriteRegister(RG_RF_CH, Ch);
}

volatile uint8_t pw = 0;

uint8_t GetPW(void)
{
    return pw ;
}

uint8_t NRFReceivePacket( uint8_t * Packet)
{

    if ((NRFReadRegister(RG_STATUS)&(1<<6))==0) return 0;
    CELOW ;
    CHIPSELECT
    {
        spi(CMD_R_RX_PL_WID);
        pw = spi(0);
    }

    if (pw>32)
    {
        CHIPSELECT
        {
            spi(CMD_FLUSH_RX);
        }
        NRFWriteRegister(RG_STATUS,(1<<6)) ;
        return 0;
    }

    CHIPSELECT
    {
        spi(CMD_RD_RX_PLOAD);
        for(uint8_t i = 0; i<32; i++) Packet[i] = spi(0);
    }
    NRFWriteRegister(RG_STATUS,(1<<6)) ; // Clear the RX_DR interrupt
    CEHIGH ;
    return 1;
}

uint8_t NRFIsRxPacket(void)
{
    uint8_t ret = NRFReadRegister(RG_STATUS) & (1<<6) ;
    return ret ;
}

void NRFSendPacketNoAck(const __memx uint8_t *ToAddress, uint8_t *Packet, uint8_t Length)
{
    uint8_t ModeIs = NRFReadRegister(RG_CONFIG);
    NRFModeTX();        // Make sure in TX mode
    NRFSetTxAddress(ToAddress);
    NRFWriteRegister(RG_EN_AA,0);                // Disable auto ack on pipes 0 and 1

    CHIPSELECT { spi(CMD_FLUSH_TX); };

    CHIPSELECT
    {
        spi(CMD_WR_TX_PLOAD);
        for (uint8_t i = 0; i<Length; i++) spi(Packet[i]);
    };

    CEHIGH;
    _delay_us(11) ; // At least 10 us
    CELOW;

    if ((ModeIs & (1<<1)) == 0) NRFModePowerDown() ;
    if ((ModeIs & 3) == 3) NRFModeRX();
}


uint8_t NRFSendPacket(const __memx uint8_t * ToAddress, uint8_t *Packet, uint8_t Length)
{
    uint8_t ModeIs = NRFReadRegister(RG_CONFIG);
    CELOW;
    NRFModeTX();        // Make sure in TX mode
    NRFWriteRegister(RG_EN_AA,3);                // Enable auto ack on pipes 0 and 1
    NRFSetTxAddress(ToAddress);

    CHIPSELECT
    {
        spi(CMD_FLUSH_TX);
    };

    CHIPSELECT
    {
        spi(CMD_WR_TX_PLOAD);
        for (uint8_t i = 0; i<Length; i++) spi(Packet[i]);
    };

    CEHIGH;
    _delay_us(15) ; // At least 10 us
    CELOW;

    uint8_t st = 0;
    while ((st & ((1<<5) | (1<<4)))==0) st = NRFReadRegister(RG_STATUS); // Packet acked or timed out
    NRFWriteRegister(RG_STATUS,st & ((1<<5) | (1<<4))); // Clear the bit
    if ((ModeIs & (1<<1)) == 0) NRFModePowerDown() ;
    if ((ModeIs & 3) == 3) NRFModeRX();
    return (st>>5) & 1;
}

typedef struct
{
    uint8_t r ;
    uint8_t v ;
} initline ;

/*
const __flash initline InitSequence[] =
{
    {RG_CONFIG,ModeBits},
    {RG_EN_AA,3},
    {RG_EN_RXADDR,3},
    {RG_SETUP_AW,3},
    {RG_SETUP_RETR,255},
    {RG_RF_CH,2},
    {RG_DYNPD,3},                // Dynamic packet length on pipes 0 and 1
    {RG_FEATURE,0b101},            // Enable dynamic payload, and no payload in the ack.
    {RG_RX_PW_P0,32},            // Receive 32 byte packets
    {RG_RX_PW_P1,32},
};
*/

void NRFInit(uint8_t pSpeed)
{
    // Set the required output pins
    CSHIGH;
    CELOW;
    _delay_ms(200);
    //for(uint8_t i = 0; i<sizeof(InitSequence)/sizeof(initline); i++)
    //    NRFWriteRegister(InitSequence[i].r, InitSequence[i].v);

    NRFWriteRegister(RG_CONFIG,ModeBits);
    NRFWriteRegister(RG_EN_AA,3);                // Enable auto ack on pipes 0 and 1
    NRFWriteRegister(RG_EN_RXADDR,3);            // Enable Rx on pipes 0 and 1
    NRFWriteRegister(RG_SETUP_AW,3);            // 5 byte addresses
    NRFWriteRegister(RG_SETUP_RETR,0x18);        // 8 retries
    NRFWriteRegister(RG_RF_CH,2);                // channel 2 NO HIGHER THAN 83 in USA!
    //NRFWriteRegister(RG_RF_SETUP,0b00100110);   // 250Kbps, 0db output
    //NRFWriteRegister(RG_RF_SETUP,0b00000110);   // 1Mbps
    //NRFWriteRegister(RG_RF_SETUP,0b00001110);    // 2Mbps

    NRFWriteRegister(RG_RF_SETUP,pSpeed);


    NRFWriteRegister(RG_DYNPD,3);                // Dynamic packet length on pipes 0 and 1
    NRFWriteRegister(RG_FEATURE,0b100);            // Enable dynamic payload, and no payload in the ack.
    NRFWriteRegister(RG_RX_PW_P0,32);            // Receive 32 byte packets
    NRFWriteRegister(RG_RX_PW_P1,32);


    //NRFModePowerDown();                    // Already in power down mode, dummy
}

What do we do with all this?

NRFReadRegister and NRFWriteRegister I exposed these so you can maniuplate the NRF yourself. There are lots of features I'm not using, that you might want.

void NRFInit(uint8_t pSpeed) Before you can do anything with it, you must use NRFInit. This takes a parameter to select which speed, 2000000bps, 1000000bps or 250000bps. There are 3 defines:

  • #define NRFslow 0b00100110 -- NRFInit(NRFslow)
  • #define NRFfast 0b00001110 -- NRFInit(NRFfast)
  • #define NRFhalffast 0b00000110 -- NRFInit(NRFhalffast)

You might wonder why you'd ever want to use a low speed. I can think of 2 reason, either because you're communicating with another device that already runs slow, or you need more range. The receiver sensitivity increses several db at lower speeds. If, at NRFfast, you don't have quite enough range, try NRFslow, but don't forget to change both ends, or you won't get anything through.

There is a _delay_ms(200) call, so your Config.h needs to define F_CPU. The whole reason for this delay is to be sure you don't try to talk to the NRF before it's ready. If you're sure you won't try to init it for 200ms, you can skip this delay.

void NRFSetRxAddress(const __memx uint8_t * Bytes5) I use 5 byte addresses. You can use different address lengths if you like, you'll need to change the NRFInit function.

void NRFSetChannel(uint8_t Ch)This sets the channel, 0 through 255. Note that in USA, only 0-83 are legal for ISM band use. You might get away with it, but I'm not that lucky. Remember, if you're running 2Mbps, you're using 2MHz bandwidth and will interfere with one on an adjacent channel. In most cases, you won't even have to think about this, but don't say I didn't tell you. I've never tried receiving a message an an adjacent channel, so I have no idea if it will work.

The Modes The NRF has 3 modes, Power Down, Transmit and Receive. In Transmit and Receive mode, the part uses like 13mA of power. Since it only transmits 1mW, there wasn't enough difference between tx current and rx current for me to worry about. 6 functions set and test the modes:

  • void NRFModePowerDown(void);
  • void NRFModeTX(void);
  • void NRFModeRX(void);
  • uint8_t NRFIsModePowerDown(void);
  • uint8_t NRFIsModeTX(void);
  • uint8_t NRFIsModeRx(void);

uint8_t NRFSendPacket(const __memx uint8_t *ToAddress, uint8_t *Packet, uint8_t Length) This is the main send packet method. It transmits a packet pointed to by *Packet and waits for an ack, returning 0 if there was no ack. There's something funny about NRFs: The packet doesn't contain a "from" address so the reciever sends the ack to its own receive address. To receive the ack, the sender had to set its receive address to the address it was transmitting to. If you're expecting to go back to receive mode, you'll need to set the receive address after sending the packet.

I init the module for "Dynamic Packet Length," so the receiver doesn't reject packets of different lengths.

__memx Notice that I used __memx for the address. This lets you pass a pointer in flash, ram or even eeprom. Setting NVM_CTRLB |= (1<<3) lets you read (but not write) the eeprom as if it was ram. I just used a plain pointer for the packet itself figuring you'd probably not be sending data out of flash.

void NRFSendPacketNoAck(const __memx uint8_t *ToAddress, uint8_t *Packet, uint8_t Length) This sends a packet without asking for or waiting for an ack from the receiver.

uint8_t NRFReceivePacket( uint8_t * Packet) This checks for a packet in the receive buffer. If there is none, it returns a zero. Otherwise, it stuffs the packet into the memory pointer. It's up to you to be sure the packet will fit. NRF packets are never bigger than 32 bytes.

Examples

To initialize the module NRFInit(NRFFast) -- This puts the module in power down mode so it's not drawing much current.

To set the channel NRFSetChannel(6) The number may be 0 through 255, except in US, only numbers less than 84 are allowed.

To send a packet First, assemble your packet in memory, and call NRFSendPacket. SendPacket can find the send-to address in ram, flash, or even eeprom.

  • NRFSendPacketNoAck(PSTR("ABCDE"), &Packet, 8) -- This will send an 8 byte packet to a fixed address.
  • NRFSendPacketNoAck(&SomeVar, &Packet,16) -- This sends a 16 byte packet to an address stored in a 5 byte area in memory.
  • NRFSendPacket(PSTR("EDCBA"), &Packet, 24) -- Returns non zero if it got a response from the receiver.
  • NRFSendPacket(&SomeVar,&Packet,32) -- Returns non zero if it got a response from the receiver.

These functions set the module to TX mode, then return it to the

Tips

There are a couple things about the NRF that can confuse you. Or at least, they confused me.

1 The receiver can miss packets if you send the same data again. The receiver sends an ack packet to the sender when it receives a packet. Of course, this ack may itself get lost. If the transmitter doesn't see the ack within a specific time, it sends the packet again, so it is possible to receive the same packet twice or more times. The Nordic prevents this by watching for the same packet to come again, and ack's the repeat packet, but doesn't notify your gadget. There are 2 bits of packet identifier in the header so the receiver can tell if this is a retransmitted packet. Except, this doesn't always work. I find if you're sending the same packet repetedly, the receiver often will receive the first, and throw the rest away. To correct this, I reserve one byte of the 32 byte (or less) packet for a "packet number" and increment it on every transmit so I don't actually send the same data over.This seems to be a symptom of the counterfeit parts and doesn't happen with real Nordic parts.

2 Sending a packet sets the nordic's receive address to the address it sent the packet to. You see, the packet doesn't contain a source address, so to ack the packet, the receiver sends an ack to the same address it just received the packet. So to receive the ack, the transmitter had to change the receive address to the address it sent the packet to. So if you intend to listen on one address and send messages to other addresses, don't forget to set the receive address back after sending a packet.

About the counterfeit parts.

It seem there are 3 complaints about the counterfeit parts. 1. They use a little more power than real NORDIC parts. 2. The receiver has a little less sensitivity. 3. The transmitter might be a little more powerful. And E, the frequency might not be as well controled near the ends of the band.