Are there any NRF24 experts ?

I can get communication working with a pair pf NRF24s using ManiacBug's RF24 library.

But I am almost at the stage of tearing my hair out trying to understand how the various options interact with each other. For example the .write() function blocks and the ,startWrite() function does not. But .setAutoAck() seems to interact with them. In some circumstances I found that repeating .write() on two successive program lines worked where a single call would not. And a high transmit repetition rate and a low rate seem to work but intermediate rates would not. Sometimes the receiver gets every message, sometimes, none, sometimes it gets some of them. I guess this is some sort of handshaking issue.

At this stage I am not posting any code because I would probably need to show about 6 different variants and I'm not sure I can even keep them straight in my own head.

I want to use the NRF24 without handshaking so that a message can be received by several slaves. I'm pretty confident that will work. But it would be nice to have a logical explanation of the required settings.

Neither ManiacBug's documentation nor the Nordic datasheet explain the appropriate combinations of settings.

Maybe somebody has a link to a document with a good explanation, or would be kind enough to give me some pointers.


While I have not tried it in code, there seems to be (at least) two ways to achieve a one to many communication.

  • disabling AutoAck for the used pipes (or globally) in sender and reveivers
  • using W_TX_PAYLOAD_NOACK on the sending side The original Maniacbug library cycled the NRF through POWER_DOWN mode on sends, not a good habit. There were a couple of (I think superfluous) flush-fifos in the library. There was no support for interrupts in the library (which I feel is the worst).

I currently am running a program that broadcasts one to many and the many can send data back. I have sailboat autopilot that takes data from a GPS and steers the boat. I control it using a remote control box that that receives data from the main box and displays it on a TFT or an LCD. if I want to change the course or steering mode the remote has a keypad that will send data back to the main box. I have two of these remotes and they run simultaneously receiving data and either one can send keypad input back to the main box.

In addition the sketches will send more than the 32 bytes of data by using a data structure and sending multiple messages in the same format. It uses the first integer to identify which message is received so the data can be parsed.

I can share that if it is helpful. I don't know the best way to share code that has multiple tabs in it.

Thank you both.

I can make my program work - learning by trial and error.

What I am hoping to get is a document that provides a comprehensive description of the "proper" way to make use of all the features of the NRF24. Then I could have more confidence that my "solution" was not teetering on the edge of failure.

As things stand it seems to be working with .setAutoAck(false) and .startWrite()

However I have not found the mechanism to know when the .startWrite() has finished when there is no ACK process. I am just using a short delay(). I have yet to explore just how short that can be.


Yes, avoid all automatic behaviour to start with, then you'll have full control - such things are usually more trouble than they are worth when not carefully documented. Experiment later when you have a working starting point.

Reading the library code and the chip datasheet is the way to proceed I think - or finding a guide someone else has had the kindness to author can be good. At least understand the state-machine on the hardware and how that relates to the library calls...

However I have not found the mechanism to know when the .startWrite() has finished when there is no ACK process.

Why not use the TX-interrupt?

You could test the setting of that flag by using .write (that polls for that flag to appear).

A delay could also be used in this mode, as there are no auto retransmissions nor acks to wait for
and the timing is more predictable than under AutoAck, but…

MarkT: Reading the library code and the chip datasheet is the way to proceed I think

Believe me I have read both many times and they do not answer my questions. Neither author thought it worth taking time to provide more than the mimimum of information.

or finding a guide someone else has had the kindness to author can be good.

That's what I am hoping somebody here will point me to.


Why not use the TX-interrupt?

Thanks for the pointer.

I had clearly misread stuff as I thought the only evidence of a completed TX was a count of the number of retries - right now I don’t know where I got that idea.

I can easily test for the TX_DS status bit.


It is not stated explicitly that the TX_DS will be set if AutoAck is off, but I think it will be set.

Activate PTX mode by setting the CE pin high. If there is a packet present in the TX FIFO the nRF24L01+
enters TX mode and transmits the packet. If Auto Retransmit is enabled, the state machine checks if the
NO_ACK flag is set. If it is not set, the nRF24L01+ enters RX mode to receive an ACK packet. If the
received ACK packet is empty, only the TX_DS IRQ is asserted. If the ACK packet contains a payload,
both TX_DS IRQ and RX_DR IRQ are asserted simultaneously before nRF24L01+ returns to standby-I
If the ACK packet is not received before timeout occurs, the nRF24L01+ returns to standby-II mode. It
stays in standby-II mode until the ARD has elapsed. If the number of retransmits has not reached the ARC,
the nRF24L01+ enters TX mode and transmits the last packet once more.
While executing the Auto Retransmit feature, the number of retransmits can reach the maximum number
defined in ARC. If this happens, the nRF24L01+ asserts the MAX_RT IRQ and returns to standby-I mode.
If the CE is high and the TX FIFO is empty, the nRF24L01+ enters Standby-II mode.

@Whandall, after Reply #5 I was wondering if I was losing my marbles. Happily, not quite. The .write() function in the RF24 library checks the Register OBSERVE_TX when, I think, it should actually be checking the Register STATUS. This meant that I had missed the existence of the TX_DS bit in the STATUS register.

I have not tried it yet but I think you are correct about TX_DS

I have tried it now. I changed the library to read STATUS and tried testing the different bits separately. But it has not made any difference.

I will carry on with my use of the .startWrite() function and a short delay() to allow the TX to complete.

When I am a bit more confident I will post my working code. But it may be a couple of days before I can do that.


I have this in NRF24.cpp (508+)

  // At this point we could return from a non-blocking write, and then call
  // the rest after an interrupt

  // Instead, we are going to block here until we get TX_DS (transmission completed and ack'd)
  // or MAX_RT (maximum retries, transmission failed).  Also, we'll timeout in case the radio
  // is flaky and we get neither.

  // IN the end, the send should be blocking.  It comes back in 60ms worst case, or much faster
  // if I tighted up the retry logic.  (Default settings will be 1500us.
  // Monitor the send
  uint8_t observe_tx;
  uint8_t status;
  uint32_t sent_at = millis();
  const uint32_t timeout = 500; //ms to wait for timeout
    status = read_register(OBSERVE_TX,&observe_tx,1);
  while( ! ( status & ( _BV(TX_DS) | _BV(MAX_RT) ) ) && ( millis() - sent_at < timeout ) );

btw. line 556+ has the IMHO dreadful

  // Power down

  // Flush buffers (Is this a relic of past experimentation, and not needed anymore??)

I do not use the write function, so it doesn’t matter to me.

Thanks @Whandall. I modified Reply #9 while you were writing Reply #10

This piece you posted

    status = read_register(OBSERVE_TX,&observe_tx,1);
  while( ! ( status & ( _BV(TX_DS) | _BV(MAX_RT) ) ) && ( millis() - sent_at < timeout ) );

is the same as what I have. The bits TX_DS and MAX_RT are elements of the STATUS register not the OBSERVE_TX register.

And the datasheet has this cryptic comment about MAX_RT

Maximum number of TX retransmits interrupt
Write 1 to clear bit. If MAX_RT is asserted it must
be cleared to enable further communication.


Thinking some more about the “cryptic comment” I quoted in Reply #11, I wonder if it is necessary to enable interrupts (in Register CONFIG) in order for the TX_DS bit to be set.

The equivalent comment for the TX_DS bit is

Data Sent TX FIFO interrupt. Asserted when
packet transmitted on TX. If AUTO_ACK is acti-
vated, this bit is set high only when ACK is
Write 1 to clear bit.

Maybe I have had enough of this for today.


No unmasking is needed.

The nRF24L01+ has an active low interrupt (IRQ) pin. The IRQ pin is activated when TX_DS IRQ, RX_DR
IRQ or MAX_RT IRQ are set high by the state machine in the STATUS register. The IRQ pin resets when
MCU writes '1' to the IRQ source bit in the STATUS register. The IRQ mask in the CONFIG register is used
to select the IRQ sources that are allowed to assert the IRQ pin. By setting one of the MASK bits high, the
corresponding IRQ source is disabled. By default all IRQ sources are enabled.

I tried both variants (global disable and W_TX_PAYLOAD_NOACK), both result in TX_DS beeing set could only verify the global variant (still investigating). (As described in PTX Operation flowchart both should work.)

I added to the library:

void RF24::startWriteNoAck( const void* buf, uint8_t len )
  // Transmitter
  write_register(CONFIG, ( read_register(CONFIG) | _BV(PWR_UP) ) & ~_BV(PRIM_RX) );
  delayMicroseconds(150); // 150

  // Send the payload
  write_payloadNoAck( buf, len );

  // Allons!
  delayMicroseconds(15); // 15

I have not managed to relate W_TX_PAYLOAD_NOACK to the documentation. Where do I find that?

I don't think I am going to fiddle with the RF24 library any further. I just feel as if I am sticking BandAid on BandAid.

I might see about writing my own code so I don't need to use the library at all. But I won't even think about that before next Wednesday. I did write code to get a Cypress CYRF6936 working so I figure I can get an NRF24 to work also. On the other hand the benefits compared to the RF24 library may not justify the effort. There was no library for the CYRf6936.


Page 29, No Acknowledgment flag (NO_ACK) Page 63, Feature Register *

*that is probably my error, I did not enable it.

But enabling needed a new methode

void RF24::enableDynamicAck(void)
  // enable dynamic ack feature

  write_register(FEATURE,read_register(FEATURE) | _BV(EN_DYN_ACK) );

  // If it didn't work, the features are not enabled
  if ( ! read_register(FEATURE) )
    // So enable them and try again
    write_register(FEATURE,read_register(FEATURE) | _BV(EN_DYN_ACK) );



After calling that in the setup, it works with global enabled AutoAcks and W_TX_PAYLOAD_NOACK.

Thanks for the page reference.

I confess I had been trying to avoid the section on Enhanced Shockburst (ES). That is partly because I started out wondering if I could inter-communicate between the NRF24 and the CYRF6936 and ES is a proprietary Nordic system. It would mean I could use a cheap NRF24 in place of an expensive (very small) board with the CYRF6936.I don’t think inter-communication is possible - certainly not simple.

I need to focus on something else today and tomorrow and I will follow up Wednesday or Thursday.


In spite of my earlier intention to put this aside I have spent the morning (wasted the morning) studying it further - including, I hope, a fairly thorough study of the Nordic datasheet.

I have been testing with radio.setAutoAck(false);

If I add code to the library startWrite() function I can see that the TX_DS bit is set immediately after the data is sent when I call the startWrite() function rather than write() function and almost every packet is received correctly. The code I added to the startWrite() function is copied directly from the write() function.

If I leave that code active and call the write() function (which calls startWrite() before it does anything else) the startWrite() function does not show the TX_DS bit being set. I have to disable all the other code in the write() function to make that happen.

The call to powerDown() is certainly part of the problem. But not all of it.


Why do you want to call the blocking write function at all?