NRF24l01 transmits button press 1-3 times, then fails

OK. I have been working on something off and on for months, have used tutorials, examples, and this forum to try to educate myself. I still consider myself very amateur but feel like I have done quite a bit of (unsuccessful) troubleshooting so far and am finally throwing it out there to see if anyone can point me in a good direction.
I am wanting a stepper motor to move based on button pressed on a "remote" end. The code works when everything is wired to the same microcontroller. Adding in the nrf24l01 component is throwing it off.
I have nano clone boards (says v3.0) that have atpmega328p hooked up to transmitting and receiving ends. Stepper is 28byj-48. I have buck converters going to each nrf24l01 that are currently stable at 3.5-3.8v output and my multimeter does not show a change or dip in voltage during button presses (sent only to nrf board, no other components, motor and nano are powered off another source, the stepper has its own source, not connected "nano's power"). I have soldered a 10uf capacitor to the 8 nrf boards I have tried so far (some electrolytic, some ceramic). I have grounds going from nano to each negative strip on the breadboard as I realized pretty early that the communication between nrf and nano was spotty otherwise. I have added some debugging code and added "stop/start listening" again at the end of the loop as I saw somewhere that may help. I've used low and high PA levels. Setting the channel used specifically did not make a difference either. I have also read over and referenced the "simple nrf24l01 tutorial" as it is mentioned in pretty much all the nrf24 posts on this forum that I've come across.
The serial monitor of the transmitting end says "radio initialized successfuly", "radio setup complete", recognizes the button press, and says it sent them successfully (the first 1-3 times after turning the setup on). After that, when buttons are pressed it says, "failed to send button states...retrying" and just stays there. A sketch that tests the communication between the nrf and nano board comes out ok. A sample repeating hello world sketch transmits well. A single button press to turn on an led works over the nrf ok.
The code was originally constantly sending the button state over so I altered it to only send button data when a change has occurred. That has not helped. I thought maybe it had to do with too much data or too frequently sending was overloading the system. I am clueless at this point. I have a couple nano every boards, an uno, and a leonardo that I will try running the code over soon to try to rule out board issues. I have used 3 different nano clone boards so far.

Any of you more experienced folks have any thoughts or ideas or a direction to point me perhaps? Code is below. I will try to add a crude wiring diagram later on for reference. Thank you.

Transmitter code, pre-debug version:

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

// Define the pins for the buttons
const int buttonPin2 = 2;
const int buttonPin3 = 3;
const int buttonPin4 = 4;
const int buttonPin5 = 5;

// Variables to store button states
int buttonState2 = 0;
int buttonState3 = 0;
int buttonState4 = 0;
int buttonState5 = 0;

// Setup NRF24L01 communication
RF24 radio(9, 10);  // CE, CSN
const byte address[6] = "00001";

void setup() {
  Serial.begin(9600);

  // Initialize the buttons
  pinMode(buttonPin2, INPUT);
  pinMode(buttonPin3, INPUT);
  pinMode(buttonPin4, INPUT);
  pinMode(buttonPin5, INPUT);

  // Initialize NRF24L01
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_HIGH);
  radio.stopListening();
}

void loop() {
  // Read button states
  buttonState2 = digitalRead(buttonPin2);
  buttonState3 = digitalRead(buttonPin3);
  buttonState4 = digitalRead(buttonPin4);
  buttonState5 = digitalRead(buttonPin5);

  // Send the button states
  int message[] = { buttonState2, buttonState3, buttonState4, buttonState5 };
  radio.write(&message, sizeof(message));

  delay(250);  // Small delay to prevent spamming
}

Transmitter with debugging:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <ezButton.h>

// Define the pins for the buttons
const int buttonPin2 = 2;
const int buttonPin3 = 3;
const int buttonPin4 = 4;
const int buttonPin5 = 5;

// Create ezButton objects for each button
ezButton button2(buttonPin2);
ezButton button3(buttonPin3);
ezButton button4(buttonPin4);
ezButton button5(buttonPin5);

// Variables to store the last button states
int lastButtonState2 = 0;
int lastButtonState3 = 0;
int lastButtonState4 = 0;
int lastButtonState5 = 0;

// Setup NRF24L01 communication
RF24 radio(9, 10);  // CE, CSN
const byte address[6] = "00001";

void setup() {
  Serial.begin(9600);

  // Initialize buttons with debounce time
  button2.setDebounceTime(50);  // 50 milliseconds debounce time
  button3.setDebounceTime(50);
  button4.setDebounceTime(50);
  button5.setDebounceTime(50);

  // Initialize NRF24L01
  if (!radio.begin()) {
    Serial.println("Radio hardware not responding!");
    while (1) {}  // Halt execution if radio initialization fails
  } else {
    Serial.println("Radio initialized successfully.");
  }

  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_HIGH);
  radio.setChannel(108);            // Set a specific channel to avoid interference
  radio.setDataRate(RF24_250KBPS);  // Lower data rate for better reliability
  radio.setRetries(15, 15);         // Set retries and delay between retries
  radio.stopListening();
  Serial.println("Radio setup complete.");
}

void loop() {
  // Update the button states (required for debouncing)
  button2.loop();
  button3.loop();
  button4.loop();
  button5.loop();

  // Read current debounced button states
  int buttonState2 = button2.isPressed();
  int buttonState3 = button3.isPressed();
  int buttonState4 = button4.isPressed();
  int buttonState5 = button5.isPressed();

  // Check if any button state has changed
  if (buttonState2 != lastButtonState2 || buttonState3 != lastButtonState3 || buttonState4 != lastButtonState4 || buttonState5 != lastButtonState5) {

    // Send the button states only if there's a change
    int message[] = { buttonState2, buttonState3, buttonState4, buttonState5 };

    if (radio.write(&message, sizeof(message))) {
      Serial.println("Button states sent successfully:");
    } else {
      Serial.println("Failed to send button states. Retrying...");
      radio.begin();  // Attempt to reset the radio
    }

    // Print the states for debugging
    Serial.print("Button 2: ");
    Serial.println(buttonState2);
    Serial.print("Button 3: ");
    Serial.println(buttonState3);
    Serial.print("Button 4: ");
    Serial.println(buttonState4);
    Serial.print("Button 5: ");
    Serial.println(buttonState5);
  }

  // Update the last button states
  lastButtonState2 = buttonState2;
  lastButtonState3 = buttonState3;
  lastButtonState4 = buttonState4;
  lastButtonState5 = buttonState5;

  // Optional: Add a small delay to reduce CPU usage
  delay(10);
}

finally receiver code:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Stepper.h>

const int stepsPerRevolution = 150;  // Change this number to change how far stepper turns
const int stepsPerRevolution2 = 450;
Stepper myStepper = Stepper(stepsPerRevolution, 2, 4, 3, 5);

// Setup NRF24L01 communication
RF24 radio(9, 10);  // CE, CSN
const byte address[6] = "00001";

void setup() {
  myStepper.setSpeed(55);
  Serial.begin(9600);

  // Initialize NRF24L01
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_HIGH);
  radio.setChannel(108);            // Set a specific channel to avoid interference
  radio.setDataRate(RF24_250KBPS);  // Lower data rate for better reliability
  radio.startListening();
}

void loop() {
  if (radio.available()) {
    int message[4];
    radio.read(&message, sizeof(message));

    // Check button states and move stepper accordingly
    if (message[0] == HIGH) {
      Serial.println("clockwise");
      myStepper.step(stepsPerRevolution);
    }

    if (message[1] == HIGH) {
      Serial.println("counterclockwise");
      myStepper.step(-stepsPerRevolution);
    }

    if (message[2] == HIGH) {
      Serial.println("clockwisetriple");
      myStepper.step(stepsPerRevolution2);
    }

    if (message[3] == HIGH) {
      Serial.println("counterclockwisetriple");
      myStepper.step(-stepsPerRevolution2);
    }

    // Reset motor pins to LOW after movement
    digitalWrite(2, LOW);
    digitalWrite(3, LOW);
    digitalWrite(4, LOW);
    digitalWrite(5, LOW);

    delay(250);  // Delay to ensure smooth operation
    radio.startListening();
  }
}

I feel your pain.

As for too many messages throwing things off, the NRF24L01 is used in radio control systems that send dozens of packets per second, dunno how many exact but enough to steer a car around on the floor without the appearance of any latency or dropped commands.

I quickly read your prose and flew fast over your code.

I can only offer one idea - have you tried using the receiver code, but removing from it any use or reference to the radio set, and instead substituted local buttons for control?

You've used ezButton, which has a method getState which woukd be a good clean version identical to what you receive from reading the radio.

  if (testButton.isPressed()) {   // instead of radio available, trigger a test with 5th button
    int message[4];
//    radio.read(&message, sizeof(message));

// instead, jam in what the local buttons say

      message[0] = fakeRadioButton0.getState();
      message[1] = fakeRadioButton1.getState();
      message[2] = fakeRadioButton2.getState();
      message[3] = fakeRadioButton3.getState();

which assumes you have four ezButtons.

With zero other changes, the rest of the code woukd not know you didn't get that data from the air.

Obvsly this test must work if your larger project is to succeed.

The code looks simple and plausible, and you seem to have been careful about the things one must; nevertheless it seems like an issue with power or interference between the radio set and other things attached.

An alternate test, also straightforward, would be to remove everything but the radio from the Arduinos, and just use serial printing to confirm that the flow through your code is properly informed by the received data. Obvsly, again, this must work if your larger goal is to be attained.

Foresight might have led you to design both sketches such as to facilitate this kind of testing, more or less keeping things well separate for a divide and conquer approach.

Good luck. I may try the one I can, that is the idea to test without any radio.

HTH

a7

Thanks A7,
I will try those after I try my code on unos for s's and giggles. So far the serial print does show the buttons as being pressed on the transmit end but it fails to send according to the serial print. I am determined to get this to work.
I'm assuming you mean for me to try the ezbutton test on the receiving end to make sure the states are understood, as I do require it to be wireless when all is said and done?
I will respond with the outcome when I am able to do it this week.

Many times I have seen similar operation where it was a hardware problem. Post your annotated schematic, be sure to show all connections, power sources, any other hardware items. Post links to technical information on the hardware devices.

OK, I did.

Very curiously it functions perfectly only after removing the calls to the stepper.

I do not use that Stepper library; I am reluctant to indict it, but that is my current finding.

I will have to revisit this because I did find this harder than it should have been, perhaps postponing my nap had anything to do with it.

Stupid mistakes like

    //    for (int ii = 0; ii = 3; ii++) message[ii] = LOW;  OMG you idiot

a7

That is not surprising, the stepper draws power when it steps. Schematic please!

LOL! You couldn't have known I have no stepper connected. In fact I am working in the wokwi simulator - which is why I am leaving this for a bit, I can't be seeing what I am seeing.

a7

I'll indict it-- Stepper.h sucks. These sorts of myStepper.step(...) commands end up blocking for 2.72 and 8.18 seconds, which could kneecap the program and prevent it from paying attention to other things.

const int stepsPerRevolution = 150;  // Change this number to change how far stepper turns
const int stepsPerRevolution2 = 450;
...
  myStepper.setSpeed(55);
...

     myStepper.step(stepsPerRevolution);
     ...
     myStepper.step(stepsPerRevolution2);

LOL, THX.

But... I've been promoting ezButton ("it doesn't suck"), but waaay back I had flunked it because I found it didn't like it's update method to be called infrequently.

When I starting using it anyway, I went and read the code for it and failed to see anything that would explain that neediness, and (I think) I wrote some code to test again this theory: ezButton passed! It had no difficulty with being called now and then.

In some kind of desperation with this receiver-less receiver code I replaced ezButton with my own button handling code, which is a back-burner thing where I experiment with stuff I'm not good at. Anyway, let us say that at this point "it doesn't suck", and it certainly can ride over any delays the rest of the code causes, like a blocking stepper call, Of course it would miss a button press, but otherwise functions.

And (wait for it) everything works fine.

I realize now I missed the opportunity to try wokwi's bounceless buttons. Imma have to do, just not now.

a7


A little tough to see, but on the transmitting end, the negative breadboard row is connected to the button with a 10k resistor.

Links to hardware:

stepper
https://www.microcenter.com/product/639726/inland-ks0327-keyestudio-stepper-motor-drive-board-5v-stepper-motor-kit-(3pcs)

nano clones

buck converter (I set voltage with the screw potentiometer onboard. Using their preset 3.3v option put out a little under 3.3v)

nrf24l01 boards, I've tried two so far

buttons

breadboard power supply on receiver

9v power supply on transmitter end (will eventually be replaced with 4AA battery supply and the receiving end will have 5v usbc breakout as power)

nano every boards as another option

Attaching everything to uno clone boards had the same result as the nano boards. I didn't expect anything different, but good to know for certain.

I may have pigeonholed myself by getting the 28byj steppers as the are unipolar and can't be used with a lot of the forward/reverse style motor control boards. The stepper.mystep stuff was the only way I could get it to rotate the fixed distance in both directions that I could find. I also need it to be "free spinning", able to move the item attached to the motor by hand, when not using the remote, so I put the code in to set them all low/0 at the end of the loop.

OK, ezButton sucks.

Even with a debounced pushbutton, I can get two actions for an edge by having the transition cause a delay in the loop.

Here a delay(), elsewhere a, say, blocking stepper motor call.

This demo uses a pushbutton to toggle the LED. A slide switch introduces a delay in one position.

See for yourseves:

Wokwi_badge UA ezButton...


The code
// https://wokwi.com/projects/408186995430496257
// https://forum.arduino.cc/t/nrf24l01-transmits-button-press-1-3-times-then-fails/1298615

# include <ezButton.h>

const byte selectDelay = 7;
const byte theLED = 6;

const byte busyLED = 8;

ezButton button(3);

void setup() {
  Serial.begin(115200);
  Serial.println("Hello ezButton does it suck World!\n");

  button.setDebounceTime(25); // set debounce time to 25 milliseconds PLENTY.

  pinMode(theLED, OUTPUT);
  pinMode(busyLED, OUTPUT);
  pinMode(selectDelay, INPUT_PULLUP);  // closed switch == LOW, no delay
}

static int counter;

void loop() {
  static unsigned long lastTime;
  unsigned long now = millis();

  button.loop();

  if (button.isPressed()) {
    digitalWrite(theLED, digitalRead(theLED) == LOW ? HIGH : LOW);  // toggle the LED
    Serial.print(counter); counter++;

    if (digitalRead(selectDelay) == LOW) {
      Serial.println(" I saw that.");
    }
    else {
      Serial.print(" I saw that. Sleeping for a bit...");
      delay(223);
      digitalWrite(busyLED, HIGH);
      delay(777);
      digitalWrite(busyLED, LOW);      
      Serial.println(" and back on the job.");
    }
  }
}

I can predict spending no (more) time on this today, you may know why and infer something about the weather, or the mood she who must not be kept waiting has been in lately...

But Imma gonna have to run this to ground one day. Especially since I thought I had.

a7

1 Like

Thanks for the schematic. I looked at your links and the processor is 5V, great the radio is 3V3 also good but they are not compatible, putting 5V from the processor port into the radio is bad. You need a level conversion between them. Try that and let us know what happens.

I don't think I understand what you are suggesting to try. I have the nrf boards on buck converters that are not coming from the controller but direct from an external power source. The only power out from a controller is to the buttons only. Is that what you are thinking, do not use power from microcontroller to nrf? Or no power out from microcontroller to anything, not even buttons?
The transmitting controller is serial printing that it recognizes which button is pushed each time. But acknowledges that it "failed to send".
Thanks

What pins of the 5V nano are connected to the NRF01? Are they rated at 5V (9, 10, 11, 12) Yes. What pins of the NRF01 are connected to the Nano? Are they rated at 5V, NO. What happens when you put 5V into a 3V3 port? It tends to damage and or destroy the port and the Arduino. What is a level converter? Are you familiar with locks where ships, boats etc enter/leave one body of water to a different level of water? The locks are the level converters, where are your voltage level converters?

There is a lot of bad information on the web showing the NRF01 and various Arduinos. Many show powering the NRF01 directly from the Arduino and most use a CAP as a crutch to make it work.

Well, I ordered and added a level converter to the ce, csn, sck, and mosi pins, low end to 3.3, high end to 5v. Still reading the button pushes but failing to send them. Each time I try I use an example sketch to test the connection between the nrf and microcontroller and it reads as it should.
Might try using easybutton instead of ezbutton or finding a different button program if that might be the culprit.

1 Like

That should solve your problem if the radios are not damaged.

Unfortunately that has not solved my issue at this point. I have used 4 radios total and I made sure they were new, never attached to anything before, to eliminate the possibility of prior attempts damaging radios. I am going to "play" around with the powering devices and what is powered by what. I'm attaching the serial monitor output from the transmitter below. All buttons will show as 1 when pressed.

Radio initialized successfully.
Radio setup complete.
Failed to send button states. Retrying...
Button 2: 0
Button 3: 0
Button 4: 1
Button 5: 0
Failed to send button states. Retrying...
Button 2: 0
Button 3: 0
Button 4: 0
Button 5: 0

The NRF radios can be a bit picky on power supply etc.

I would suggest testing with only the NRF24 radios connected to the MCU with known working code.

There is a 'gettingStarted' sketch included with the RF24 library that we recommend for troubleshooting. That in combination with the info at RF24/COMMON_ISSUES.md at master · nRF24/RF24 · GitHub should tell you precisely where the problem is.

I use these https://x2robotics.ca/image/cache/catalog/Products/0400/0469-500x500.JPG modules with most of my nanos etc and they work great. (Converts from the Arduino 5v power out to 3.3v for the radios)

Thanks, I'll check those links out. The radios are successful with "hello world" text and button push turning on an led. I have a buck converter dialed in to regulate voltage to them.