RF Nano behaves differently on Reset vs Power-On

On a RF Nano V1.0 (CSN + CE on pins 9+10) I am seeing strange behavior on power-on vs reset. This a naked board, i.e. no external hardware attached. Code below.

Goal: send a single transmission at power-on (or reset), after that do nothing.

Observed behavior:

  • Everything works as intended on Reset, i.e. a transmission occurs every time the reset button is pushed
  • On the first power-on, the transmission happens.
  • After all subsequent power-ons, no transmission happens. This is true even for long-ish power-off periods, i.e. longer than 2 minutes.
  • After very long power-offs, >5 min, a transmission happens.
  • If the reset button is pushed after power-on, a transmission happens on the next power-on, but only on the next power-on.

Experiments, all without any effect (see commented sections in code below):
[2024-06-26: Updated list and added items 5.-8. to keep collection of failed experiments in one place.]

  1. Add 1s delays at different sections of setup() and/or loop(), in case the transceiver wasn't quite stable.
  2. Bracket the transmission with radio.powerUp() and radio.powerDown(), in the hopes that this would force the transceiver into a defined state.
  3. After power-off ground the 5V and 3V3 pins to discharge any capacitors on the power rails.
  4. Toggled output pins 2+3 to ensure (a) code enters loop() and (b) get past if(!power_on) exactly once.
  5. Resetting status register in case it affects how the MCU reboots. (MCUSR=0x7 after power-on. Makes no difference when reset to 0x0 before next power-on. External reset sets MCUSR:1, i.e. it either stays at 0x7 or goes to 0x2 if set to 0x0 before.)
  6. Inserting SPI.end(); SPI.begin(); or SPI.endTransaction(); in the hopes that there was a pending SPI transaction.
  7. Move radio() instantiation and the various initialization routines from setup() into loop(), in the hopes that this would give the MCU a chance to full initialize before interfacing with the transceiver.
  8. Using a different RF Nano, in case the original board had a subtle fault.

From all that I tentatively conclude that the 328P operates as intended and the NRF24L01 causes the problem.

Questions:
What differs between power-on reset and external reset?
Why would a external reset have an effect beyond power-off?
Does the NRF24L01 (or Si24R1) chip have any non-volatile storage - I didn't see anything in the datasheet.

Code:

//
//  Send short message over 2.4 GHz transceiver.
//
//  B.-O. Schneider, June 2024
//

/*
 * See documentation at https://nRF24.github.io/RF24
 * See License information at root directory of this library
 * Author: Brendan Doherty (2bndy5)
 */

#include <SPI.h>
#include "RF24.h"

#define ONLY_AT_POWER_ON 1

// Pins 11-13 used (MOSI MISO, SCK) ==> LED_BUILTIN(13) not usuable
#if 1
  #define CE_PIN 10   // For Nano RF V1.0
  #define CSN_PIN 9
#else
  #define CE_PIN  7   // For Nano RF V3.0
  #define CSN_PIN 8
#endif

// instantiate an object for the nRF24L01 transceiver
RF24 radio(CE_PIN, CSN_PIN);

uint8_t address[6] = "Rcver";   // Default address width is 5B - can be changed with setAddressWidth()

float payload = 0.0;    // Using a payload containin a single float
bool  power_on = true;  // Flag to indicate whether we are coming out of power-on


void setup() 
{

    Serial.begin(115200);
    // while (!Serial) { /* some boards need to wait to ensure access to serial over USB */ }

    if (!radio.begin())                     // initialize the transceiver on the SPI bus
    {
        Serial.println(F("Sender: radio hardware is not responding!!"));  // TODO signal error
        while (1) {}
    }
    Serial.println(F("Sender started"));

    radio.setPALevel(RF24_PA_MAX);          // RF24_PA_MAX to maximize range
    radio.setPayloadSize(sizeof(payload));  // Set static payload size, agreed between Sender and Receiver

    radio.stopListening();                  // stop listening and switch to TX mode
    radio.openWritingPipe(address);         // open pipe for TX, use default address width (5B)

    pinMode(2, OUTPUT);
    pinMode(3, OUTPUT);

    // For debugging info
    // printf_begin();                      // needed only once for printing details
    // radio.printDetails();                // (smaller) function that prints raw register values
    // radio.printPrettyDetails();          // (larger) function that prints human readable data
}  // setup

void loop()
{
    // digitalWrite(2, 1); delay(500); digitalWrite(2, 0); delay(500);

    if (!power_on) return;                  // only Transmit on power-on

    // digitalWrite(3, 1); delay(500); digitalWrite(3, 0); delay(500);
    // delay(1000);

    // radio.powerUp();
    bool report = radio.write(&payload, sizeof(payload));
    if (report) 
        Serial.println(F("Transmission successful! "));
    else
        Serial.println(F("Transmission failed or timed out"));
    // radio.powerDown();

  #if ONLY_AT_POWER_ON
    power_on = false;
  #else
    delay(1000);  // For debugging, send a message every second
  #endif
}

This appears to be described in chapter 11 of the Atmega328p data sheet. https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf
I guess the "RF Nano" is still based on the classic Nano with the Atmega328P chip.

See also here: Bootloader and MCU reset register

I read through this, but I'm not sure how this applies.
Why would a power-on reset not work the same way every(!) time. (I could understand if behavior for an external reset might vary depending on MCUSR.) Shouldn't MCUSR always be the same after a power-on reset?

All computers throughout history have used control circuitry to ensure each section of the architecture is powered in the correct sequence. Your PC does it and ALL microprocessors do it.

Pushing the reset on a powered up device does not and cannot enable the power-up sequencing electronics. It is a separate logic all it's own.

You have a known problem, the part does not have a reset pin. It does its reset internally based on the power source. Try turning the power off to the module during reset. There are other devices that exhibit the same type of problem. I normally power cycle instead of reset for this reason.

Yes, and that's why I tried shortening 3V3 to GND after power-off, hoping that would truly cause a the chip to do a full power-cycle. Alas, that didn't work.

That can destroy your 3V3 regulator. Post an annotated schematic showing all power sources.

There is a full schematic of the RF Nano here: rf-nano/schematic/rf-nano_sch_v3.0.pdf at master · emakefun/rf-nano · GitHub

I guess I'd be looking here to force a reset of the radio part. Maybe directly manipulating the enable pin on the 3v3 regulator via a spare MCU pin

image

EDIT

Maybe the pin can be lifted and soldered to:

Yes, that's the schematic I'm working off. Thanks.

I measured what happens after power off:
0 ms: power-off
200 ms: 5V rail starts dropping
320 ms: 3.3V rail starts dropping
740 ms: 3.3V rail drops faster
900 ms: both rails at 0V
So, after about 1s, the transmitter chip should be completely off, i.e. disabling the 3.3V regulator shouldn't (!) have any effect.

Are you suggesting, to keep the 3.3V regulator disabled during boot and only enable it inside setup()? (I guess that's possible - use a 1k pull-down so that the pin isn't floating until the connected MCU pin has been configured as an output.)

However, I still think there should be a SW solution. After all, with an external reset everything works ok, i.e. the code executed after the external reset properly configures RF chip.

P.S.: When I said grounding the 3V3 pin, I did that after power-off - that shouldn't hurt the regulator.

More data points but no resolution:

I enabled the radio.printDetails() and radio.printPrettyDetails() calls. There were no differences in these prints between when the a transmission was sent and when it wasn't.

19:03:46.487 -> Sender started
19:03:46.487 -> SPI Speedz	= 10 Mhz
19:03:46.487 -> STATUS		= 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
19:03:46.487 -> RX_ADDR_P0-1	= 0x7265766352 0xc2c2c2c2c2
19:03:46.487 -> RX_ADDR_P2-5	= 0xc3 0xc4 0xc5 0xc6
19:03:46.487 -> TX_ADDR		= 0x7265766352
19:03:46.487 -> RX_PW_P0-6	= 0x04 0x04 0x04 0x04 0x04 0x04
19:03:46.487 -> EN_AA		= 0x3f
19:03:46.487 -> EN_RXADDR	= 0x03
19:03:46.487 -> RF_CH		= 0x4c
19:03:46.487 -> RF_SETUP	= 0x07
19:03:46.487 -> CONFIG		= 0x0e
19:03:46.487 -> DYNPD/FEATURE	= 0x00 0x00
19:03:46.487 -> Data Rate	= 1 MBPS
19:03:46.487 -> Model		= nRF24L01+
19:03:46.487 -> CRC Length	= 16 bits
19:03:46.487 -> PA Power	= PA_MAX
19:03:46.541 -> ARC		= 0
19:03:46.541 -> SPI Frequency		= 10 Mhz
19:03:46.541 -> Channel			= 76 (~ 2476 MHz)
19:03:46.541 -> Model			= nRF24L01+
19:03:46.541 -> RF Data Rate		= 1 MBPS
19:03:46.541 -> RF Power Amplifier	= PA_MAX
19:03:46.541 -> RF Low Noise Amplifier	= Enabled
19:03:46.541 -> CRC Length		= 16 bits
19:03:46.541 -> Address Length		= 5 bytes
19:03:46.541 -> Static Payload Length	= 4 bytes
19:03:46.541 -> Auto Retry Delay	= 1500 microseconds
19:03:46.541 -> Auto Retry Attempts	= 15 maximum
19:03:46.541 -> Packets lost on
19:03:46.541 ->     current channel	= 0
19:03:46.552 -> Retry attempts made for
19:03:46.552 ->     last transmission	= 0
19:03:46.552 -> Multicast		= Disabled
19:03:46.552 -> Custom ACK Payload	= Disabled
19:03:46.552 -> Dynamic Payloads	= Disabled
19:03:46.552 -> Auto Acknowledgment	= Enabled
19:03:46.552 -> Primary Mode		= TX
19:03:46.552 -> TX address		= 0x7265766352
19:03:46.552 -> pipe 0 ( open ) bound	= 0x7265766352
19:03:46.552 -> pipe 1 ( open ) bound	= 0xc2c2c2c2c2
19:03:46.552 -> pipe 2 (closed) bound	= 0xc3
19:03:46.552 -> pipe 3 (closed) bound	= 0xc4
19:03:46.552 -> pipe 4 (closed) bound	= 0xc5
19:03:46.552 -> pipe 5 (closed) bound	= 0xc6
19:03:48.585 -> Transmission successful! 

I'm guessing that during an external reset of the ATmega328P the nrf24L01 register contents are not lost and so it works reliably because power to the radio device is maintained during this period. During a power cycle it could be that the radio device is powered up while the ATmega328P pins are still floating and goes into an unstable state. That you notice different behaviour after long periods of down time in the order of 2 to 5 minutes is, however, difficult to explain.
I suppose one thing you could try is explicitly issuing an SPI.begin() in setup() and pausing before attempting to initialize the radio.

How, incidentally, are you determining that the transmission fails? Is it that nothing is reported on the receiver or by testing return codes on the transmitter ?

If you do attempt to control the power to the radio part via the regulator enable pin, a 1k pull down resistor would consume 5mA during radio operation. 10k or even 100k may be better.

The transmission fails because the receiver does not get any message.
I have spent a fair amount of time making sure that the receiver is unlikely to be the problem here.

Unfortunately, adding { SPI.begin(); delay(1000); } before radio.begin() did not fix the problem.

Agreed, that higher resistance would save power. At this point, it would be nice if it just worked.

I'm a bit stuck for new ideas.
One thing you could try is the RadioHead library. I came across a simple example yesterday which apparently works but not necessarily with your hardware: arduino uno - nRF24L01 simple bidirectional issue - Arduino Stack Exchange

Other than that, you could some tricks which may force it into some sort of reset mode, say changing the power level or channel then back again. Maybe you find something which works.

I get it. I'm pretty stumped, too.
Thank you very much for all your suggestions and for your time thinking about this.

In case anybody still cares or has new ideas ...
The plot thickens - or the soup got even murkier.

  • If the code is changed to send out more than 1 transmission, things behave as expected, i.e. all transmissions go out regardless of power-on or external reset.
  • However, with exactly 1 transmission, the old (weird) behavior remains, i.e. no transmissions after the initial power-on (see original post).

Here is the code that demonstrates this - see for-loop in loop():

//
//  Send short message over 2.4 GHz transceiver.
//
//  B.-O. Schneider, June 2024
//

/*
 * See documentation at https://nRF24.github.io/RF24
 * See License information at root directory of this library
 * Author: Brendan Doherty (2bndy5)
 */

#include <SPI.h>
#include "RF24.h"
#include "printf.h"

#define ONLY_AT_POWER_ON  1


// Pins 11-13 used (MOSI MISO, SCK) ==> LED_BUILTIN(13) not usuable
#if 1
  #define CE_PIN 10   // For Nano RF V1.0
  #define CSN_PIN 9
#else
  #define CE_PIN  7   // For Nano RF V3.0
  #define CSN_PIN 8
#endif

RF24 radio(CE_PIN, CSN_PIN);    // instantiate an object for the nRF24L01 transceiver
uint8_t address[6] = "Rcver";   // default address width is 5B - can be changed with setAddressWidth()
float payload = 0.0;            // payload is a single float

bool  power_on = true;          // flag indicating whether we are coming out of power-on


void setup() 
{
    Serial.begin(115200);
    while (!Serial) { /* some boards need to wait to ensure access to serial over USB */ }

    if (!radio.begin())                     // initialize the transceiver on the SPI bus
    {
        Serial.println(F("Sender: radio hardware is not responding!!"));  // TODO signal error
        while (1) {}
    }
    Serial.println(F("Sender started"));

    radio.setPALevel(RF24_PA_MAX);          // RF24_PA_MAX to maximize range
    radio.setPayloadSize(sizeof(payload));  // Set static payload size, agreed between Sender and Receiver

    radio.stopListening();                  // stop listening and switch to TX mode
    radio.openWritingPipe(address);         // open pipe for TX, use default address width (5B)

    // For debugging info
    // printf_begin();                      // needed only once for printing details
    // radio.printDetails();                // (smaller) function that prints raw register values
    // radio.printPrettyDetails();          // (larger) function that prints human readable data
}  // setup


void loop()
{
    if (!power_on) return;              // only transmit on power-on
   
    // FIXME
    // For NLOOPS=1:
    //              1st     power-on after external reset: Transmission goes out
    //              2nd ... power-on after external reset: No transmission (despite write() returning true)
    //              Every external reset                 : Transmission goes out
    // For NLOOPS>1:
    //              Any power-on or external reset       : Transmission goes out
    // FiXME
    const uint8_t NLOOPS = 3;
    for (uint8_t i=0 ; i < NLOOPS ; i++)
    {   
        bool report = radio.write(&payload, sizeof(payload));
        Serial.print ("Transmission "); Serial.print(i); Serial.println(report ? " successful!" : " failed.");
    }

  #if ONLY_AT_POWER_ON
    power_on = false;
  #else
    delay(1000);  // For debugging, send a message every second
  #endif
}

Ok. So what do you see on the Serial console now after a power fail restart?
One transmission failed message and two transmission successful messages ?

No, as the comment says, the transmission are always reported as succeeding, e.g.:

Sender started
Transmission 0 successful!
Transmission 1 successful!
Transmission 2 successful!

Or:

Sender started
Transmission 0 successful!
Sender started
Transmission 0 successful!
Sender started
Transmission 0 successful!

Except in the latter case (NLOOPS=1), no messages go out.