AccelStepper and NRF24l01

Can’t get AccelStepper commands to drive a stepper correctly over NRF24L01 radio link. I have 2 momentary buttons and a potentiometer connected to the transmitter arduino and a stepper motor with driver connected to the receiver arduino.

The first two programs ‘receiver_stepper_no_acceleration’ and ‘transmitter_stepper_no_acceleration’ work perfectly together. One button drives the stepper CW, the other changes direction. The potentiometer changes the speed. These programs do not use any stepper libraries.

I wanted to add acceleration, so produces the next two programs using the AccelStepper library. Looks simple but I cannot get the the motor to rotate correctly. It just turns very slowly whenever I apply power without pressing a button. Note : I am sending 3 lots of data from the transmitter for two buttons and a potentiometer but have only added the code steps for one button in the receiver software at the moment. I have tried numerous approaches to get this code right but with little or no improvement.
Can anyone help please.
Thanks

transmitter_stepper_no_acceleration.ino (656 Bytes)

receiver_stepper_no_acceleration.ino (2.34 KB)

receiver_stepper_with_accelstepper.ino (1.58 KB)

transmitter_stepper_with_accelstepper.ino (849 Bytes)

As the two TX programs seem almost identical (why not precisely identical?) I suspect the problem is with your use of AccelStepper and not anything to do with your wireless system.

What is this line in the Rx supposed to do?

buttonState = digitalRead (data[0]);

as far as I can see the values in data[] have come by wireless. Why would you be sending the pin number of a button that needs to be read? I suspect your code should be

buttonState = data[0];

I have no idea whether that might solve the problem.

If it doesn't I suggest you temporarily take the wireless stuff out of the RX with AccelStepper and get the program to work based on values that you include in your program.

if you need more advice please explain the data that you are transmitting and give some examples of it. Also explain what you want the motor to do when it receives that data.

You should NOT have the motor control within the if ( radio.available()). Make the receipt of data and the control of the motor separate. That way you can test them separately.

This line is superfluous

while (!radio.available());

as you have already ascertained that the radio is available

The call to stepper.run() should be in loop() and should happen all the time and as often as possible.

...R Stepper Motor Basics Simple Stepper Code

Thanks for the reply.

I have changed ‘buttonState = digitalRead (data[0]);’ to ‘buttonState = data[0];’ and removed ‘while (!radio.available());’.

However, still not working properly.

Anyway. As stated previously, the first two programs ‘receiver_stepper_no_acceleration’ and ‘transmitter_stepper_no_acceleration’ work perfectly.

Basically I want the stepper motor to rotate CW or ACW using two momentary buttons. I also want to achieve variable speed, which I achieved with the potentiometer.

However, I also want the motor to be able to achieve the fastest speed setting from standstill without stalling. Hence the requirement for acceleration.

So I found some code ’ AccelStepper_with_button’ which basically accelerates a motor to a specified speed while pressing a button. So I attempted to merge this code( shown below) with the code above.

#include <AccelStepper.h>
AccelStepper stepper(1,3,4);
int buttonPin = 5;
int buttonState = 0;

void setup()
{
stepper.setMaxSpeed(1500);;
stepper.setAcceleration(500);
stepper.setMinPulseWidth(100);
pinMode(buttonPin, INPUT);
}

void loop()
{
buttonState = digitalRead(buttonPin);
if (buttonState == HIGH)
stepper. move (20000);
stepper. run ();

buttonState = digitalRead(buttonPin);
if (buttonState == LOW)

{
stepper.stop();
stepper.runToPosition();
}
}

So to recap.

I want to remotely drive a stepper motor CW or ACW at different speeds which I can set with a potentiometer. I achieved this with the first two codes without adding any code for acceleration. Of course, when I attempted to drive the motor from standstill to a maximum pre-set speed, it stalled as expected. Therefore I need to add acceleration so that the motor does not stall when driving to the maximum speed.

I understand some of what you have advised but I really don’t possess sufficient knowledge, at this time, to understand one of your comments which I have repeated below,

“You should NOT have the motor control within the if ( radio.available()). Make the receipt of data and the control of the motor separate. That way you can test them separately.”

Can you please explain how to separate these commands.

I very much appreciate your comments. Thank you.

It seems that your working program receives three pieces of data runFwd, runRev, speed in the array data[]

Have you written a program with data[] defined like this

int data[3];
data[0] = 1;
data[1] = 0;
data[2] = 70; // not sure what value might be sensible

in other words with no button input and no wireless, to see if you can get AccelStepper to do what you want.

When you have that working it will be a simple matter to update the values using the wireless.

...R

Thanks for the sound advice.
I tried using the ‘data[0]’ without the wireless connection and that worked OK. I.e the motor performed as expected.

I then monitored the serial monitor for data[0] for the programs that worked and for the program with AccelStepper added ( that is not working). The program that included the AccelStepper.h showed that ‘data[0]’ was showing a random a mixture of zero’s and one’s. This got me thinking about the timing. Note: the first programs without AccelStepper included a routine which has a built in delay as shown below.

if (data[0] == HIGH) {
// step motor:
digitalWrite(dir,HIGH); // Enables the motor to move in a particular direction
// Makes 20 pulses to avance clock
for(int x = 0; x < 20; x++) {
digitalWrite(stp,HIGH);
delayMicroseconds(tick);
digitalWrite(stp,LOW);
delayMicroseconds(tick);

So I guess the delay in the signal from the transmitter is not critical here. The receiver only has to see a ‘1’ each time it goes through the loop.

Going back to the program which includes the wireless link and AccelStepper, I added a delay of 40 microseconds, initially, which resulted in a steady return of ‘1’ on the serial monitor for data[0].

Finally I increased the data rate to 2MBPS and removed all delay. The motor is working better but still not correctly. When I press the button on the transmitter software, the motor it accelerates up but does not reach the set speed. When I release the button it momentarely increases in speed and then de-accelerates to a stop.

The code is shown below

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <AccelStepper.h>
AccelStepper stepper(1,3,4);

RF24 radio(9, 10); // CE, CSN
const byte address[6] = “00001”;
//boolean button = 0;
int data[3];

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

stepper.setMaxSpeed(1500);
stepper.setAcceleration(200);
stepper.setMinPulseWidth(100);

Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_2MBPS);; // Fast enough… Better range
radio.startListening();
}
void loop()
{
if ( radio.available())
radio.read(&data, sizeof(data));

Serial.print(data[0]);

// step motor:
if (data[0] == HIGH)
stepper. move (20000);
stepper. run ();

if (data[0] == LOW)

{
stepper.stop();
stepper.runToPosition();
}

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <AccelStepper.h>
AccelStepper stepper(1,3,4);

RF24 radio(9, 10); // CE, CSN
const byte address[6] = “00001”;
//boolean button = 0;
int data[3];

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

stepper.setMaxSpeed(1500);
stepper.setAcceleration(200);
stepper.setMinPulseWidth(100);

Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_2MBPS);; // Fast enough… Better range
radio.startListening();
}
void loop()
{
if ( radio.available())
radio.read(&data, sizeof(data));

Serial.print(data[0]);

// step motor:
if (data[0] == HIGH)
stepper. move (20000);
stepper. run ();

if (data[0] == LOW)

{
stepper.stop();
stepper.runToPosition();
}

//delay (1);
}
}

My gut feel is that I need to have the receiver program remember the state of the button on the transmitter rather than sample it continuously. I don’t know if that makes sense or is possible.

Can you advise.

Thanks

To make it easy for people to help you please modify your post and use the code button </> so your code looks like this and is easy to copy to a text editor. See How to use the Forum

Your code is too long for me to study quickly without copying to a text editor.

…R

I’m new to Forums, so not sure how to use them. I have repeated my previous post below but I also have an update. I was sure the problem was timing so the code attached here has the this change “//Serial.print(data[0]);” That seems to have largely cured the problem. Printing the data on the serial monitor was slowing down the motor. I’m sure there must be a better way to do this. I’m concerned that adding more code will slow the motor down again

(Previous Post)

Thanks for the sound advice.
I tried using the ‘data[0]’ without the wireless connection and that worked OK. I.e the motor performed as expected.

I then monitored the serial monitor for data[0] for the programs that worked and for the program with AccelStepper added ( that is not working). The program that included the AccelStepper.h showed that ‘data[0]’ was showing a random a mixture of zero’s and one’s. This got me thinking about the timing. Note: the first programs without AccelStepper included a routine which has a built in delay as shown below.

if (data[0] == HIGH) {
// step motor:
digitalWrite(dir,HIGH); // Enables the motor to move in a particular direction
// Makes 20 pulses to avance clock
for(int x = 0; x < 20; x++) {
digitalWrite(stp,HIGH);
delayMicroseconds(tick);
digitalWrite(stp,LOW);
delayMicroseconds(tick);

So I guess the delay in the signal from the transmitter is not critical here. The receiver only has to see a ‘1’ each time it goes through the loop.

Going back to the program which includes the wireless link and AccelStepper, I added a delay of 40 microseconds, initially, which resulted in a steady return of ‘1’ on the serial monitor for data[0].

Finally I increased the data rate to 2MBPS and removed all delay. The motor is working better but still not correctly. When I press the button on the transmitter software, the motor it accelerates up but does not reach the set speed. When I release the button it momentarely increases in speed and then de-accelerates to a stop.

(note: this code now works but could be improved)

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <AccelStepper.h>
AccelStepper stepper(1,3,4);


RF24 radio(9, 10); // CE, CSN
const byte address[6] = "00001";
//boolean button = 0;
int data[3];


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

stepper.setMaxSpeed(1500);
stepper.setAcceleration(200);
stepper.setMinPulseWidth(100);

  

  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_LOW);
  radio.setDataRate(RF24_2MBPS);; // Fast enough.. Better range
  radio.startListening();
}
void loop()
{
  if ( radio.available())
    radio.read(&data, sizeof(data));

  //Serial.print(data[0]);

  // step motor:
  if (data[0] == HIGH)
    stepper. move (20000);
  stepper. run ();

  if (data[0] == LOW)

  {
    stepper.stop();
    stepper.runToPosition();
  }

  //delay (1);
}

You don't seem to have posted your TX (master) program.

Code is much easier to read if all the IF statements are presented like this

if (data[0] == HIGH) {
    stepper. move (20000);
}

Are you leaving enough time for the motor to move 20,000 steps?

...R

Ok, I now have the Transmitter and receiver programs working correctly .

To recap.

My aim was to remotely drive a stepper motor with two buttons and a potentiometer using NRF2401 radio modules. Button one to drive the motor CW and button two to reverse the motor. The potentiometer was used to vary the speed. I also used the AccelStepper library to ensure that I could drive the motor to the highest speed possible without stalling.

For completeness I used two Arduino Uno’s, two NRF24L01 modules. The motor was a nema 17 stepper using an A4988 driver. Both buttons using pull-down 10k resistors. The potentiometer was 4.7k. I still feel that there is a better solution to overcome this timing issue.
I am intending to scale this arrangement up to a nema 34 motor using a DM600 driver.

First the RX code

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <AccelStepper.h>
AccelStepper stepper(1,3,4);

int tick = 0;


RF24 radio(9, 10); // CE, CSN
const byte address[6] = "00001";
//boolean button = 0;
int data[3];


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

//stepper.setMaxSpeed(1500);
stepper.setAcceleration(500);

//stepper.setSpeed (100);
//stepper.setMinPulseWidth(100);

  

  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_LOW);
  radio.setDataRate(RF24_2MBPS);; // Fast enough.. Better range
  radio.startListening();
}
void loop()
{
  if ( radio.available())
    radio.read(&data, sizeof(data));

  //Serial.print(data[0]);

   tick = map(data[2],0,1023,500,1500);

stepper.setMaxSpeed(tick);

  // step motor:
  if (data[0] == HIGH){
    stepper. move (20000);
  stepper. run ();}

else if (data[1] == HIGH){
    stepper. move (-20000);
  stepper. run ();}


  else 
  
  {
    stepper.stop();
    stepper.runToPosition();
  }

}

Then the TX code

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


int data[3];




RF24 radio(9, 10); // CE, CSN
const byte address[6] = "00001";
//boolean buttonState = 0;

void setup() {
  //pinMode(button, INPUT);
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_LOW);
  radio.stopListening();
  radio.setDataRate(RF24_2MBPS);; // Fast enough.. Better range
}
void loop() {
 

 data[0] = digitalRead(4); // button connected to this pin to rotate motor
 data[1] = digitalRead(5); // button connection this pin to rotate motor
 data[2] = analogRead (A0); // potentiometer to change speed.
 //data[3] = digitalRead(2);
   radio.write(data, sizeof(data));
    delay (5);
}

The most important thing I learned was how much the timing affected the code and operation of the motor. Even reading data via the serial monitor produced enough delay to prevent the motor from driving correctly.

My guess is that you are sending data so often the slave does not have enough time to deal with the stepper motor. Try reducing the sending rate to (say) 5 or 10 times per second.

I find Master and Slave less confusing than Tx and Rx because the nRF24 is a transceiver.

...R

When you state ' Try reducing the sending rate to (say) 5 or 10 times per second.' Do you mean using Delay() in the master. If so, I increased this to a massive Delay(1000) and it actually made no difference at all. To test, I activated 'Serial.print(data[0]);' in the slave program and the motor still runs slower. If I remove the 'Serial.print(data[0]);' , the motor runs normally. I have read that some sort of interupt might solve the problem but i'm hoping the solution is simpler.

This has been going on a long time so please remind me what is not working properly when you use the Master program with delay(1000) in it and without changing the values being sent by the master.

Give as much detail as you can about how the motor on the slave behaves.

I have no experience of using AccelStepper with an nRF24 but I have a fairly complex program for controlling the speed of a DC motor which works perfectly well when the master sends messages about 10 times per second.

…R

Ok,

In post No 8, I included a master and slave code that I stated were working. In other words, when I pressed either of two momentary buttons connected to the master, the stepper motor (connected to the slave) behaved normally, turning CW or ACW. Also the speed was changing correctly when I adjusted the potentiometer on the master.

If I add any additional code to the void loop on the slave the motor speed reduces. For example, if I read any of the incoming data to the slave, on the serial monitor, the motor speed falls.

Now, as a reminder. My first attempt to control the motor did not contain any acceleration, in fact no stepper libraries at all. the two programs were 'transmitter_stepper_no_acceleration.ino' and 'receiver_stepper_no_acceleration.ino ' The motor ran normally with no slowing down. I monitored all the data coming from the master with no effect on the speed of the motor.

The problems started when I modified the code to introduce the AccelStepper.h library.

Now if you examine the two codes that I just mentioned, that do not contain AccelStepper, you will note that there is a routine in the void loop that essentially produces a delay in the sampling of the data from the Master.

So I'm thinking that the Arduino cannot process all this data quickly enough, causing the motor to run slowly. You suggested delaying the data transfer from the master to the slave. So in the TX code shown in post 8, I changed the last line to read 'delay(1000)' instead of delay (5)'. This had no effect on the speed of the motor.

I think I need to find a way of stopping the constant polling of the data that the slave is reading from the master in order to allow the AccelStepper routine enough time to drive the motor correctly.

Not sure if that makes sense but it's the best I can do do describe the problem.

Now, how do you send the data in your code at 10 time per second? Tha't a delay of 100 Milliseconds between samples. My delay of 1 second had no effect.

I wonder if the problem lies with the line in the Slave program

tick = map(data[2],0,1023,500,1500);

The map() function is slow.

Try moving that calculation to the Master program so that it is unnecessary in the Slave.

Alternatively, arrange the Slave code so that that line is only called if the value that is received has changed.

...R

This code 'tick = map(data[2],0,1023,500,1500);' was not in the original sketch when I experienced the problem. However, to make sure it was not adding to the problem, I removed it from the code but it had no effect on the problem. The motor still slows when I read any data on the serial monitor.

I wonder if there are any other commands in the AccelStepper library I can use to achieve my aim. It seems the simple code I am using takes a lot of time to run.

I simply want the motor to run CW or ACW at various set speeds whilst a button is pressed on a remote transmitter. A potentiometer is needed to set the speed. I also need the motor to be able to move at least 20000 revs if required in either direction.

I have been looking at timer interrupts. Do you think I could apply a timer interrupt to the the data read from the transmitter such that I only read the data say every 0.5 seconds which wold allow the AccelStepper part of the code to perform its function without interruption? Does this make sense? I am fairly new to Arduino coding and have never used interrupts. If you think this is feasible, could you recommend a link to a tutorial or web page that might help me understand.

Thanks

Just an update on the above.

I have moved the code "map(data[2],0,1023,500,1500)" to the Master as suggested by Robin2. This has made a difference. Not sure why I concluded otherwise. However, the motor still slows when I read any data on the serial monitor. Maybe, this is to be as expected. Anyway making some progress.

I have added all the functionality that was on the master to the slave also. Since I want to be able to have the same control of the motor at the slave and at the master.

So now I find that the same mapping function used for control of speed at the master is causing the motor to slow down. In other words, without any radio link, the motor is slowing using this function.

Now I have found a link which explains this problem at http://www.schmalzhaus.com/EasyDriver/Examples/EasyDriverExamples.html.

I will try this solution later.

brid: I wonder if there are any other commands in the AccelStepper library I can use to achieve my aim. It seems the simple code I am using takes a lot of time to run.

You said your code works fine without the AccelStepper library so why not drop the library. It is not very difficult to implement your own acceleration code - one way is illustrated here.

...R

I have been looking at timer interrupts. Do you think I could apply a timer interrupt to the the data read from the transmitter such that I only read the data say every 0.5 seconds which wold allow the AccelStepper part of the code to perform its function without interruption? Does this make sense? I am fairly new to Arduino coding and have never used interrupts. If you think this is feasible, could you recommend a link to a tutorial or web page that might help me understand.

Thanks

brid: I have been looking at timer interrupts. Do you think I could apply a timer interrupt to the the data read from the transmitter such that I only read the data say every 0.5 seconds which wold allow the AccelStepper part of the code to perform its function without interruption? Does this make sense? I am fairly new to Arduino coding and have never used interrupts. If you think this is feasible, could you recommend a link to a tutorial or web page that might help me understand.

Sorry, but that makes no sense. It is just adding complication on top of complication - like the Burl Ives song about the lady that swallowed the fly.

If you do want to reduce the frequency with which the nRF24 is checked in the code in the slave, just use millis() - something like this

if (millis() - lastWirelessCheckMillis >= checkIntervalMillis) {
   lastWirelessCheckMillis += checkIntervalMillis;
   // check if there is radio data available
}

However I suspect there may still be motor glitches on the occasions when the wireless read takes place.

How many steps per second do you require for your motor? The AccelStepper library is not designed for high step rates

I guess the "proper" way to solve this problem is to synchronize the wireless reads with the steps. In other words do a read immediately after a step happens as that will give the maximum amount of free time. However with the AccelStepper library I don't know how you would do that. If you are not using the library it would be simple.

...R

I think I have got the master and slave codes sorted now. I can control the stepper motor both remotely and locally without any noticeable delay in the speed of the motor. I have learned, with the help of Robin2 and others, that there are various ways to code, that will avoid these sorts of delays.
For example, code for reading data over a serial monitor, or code for mapping analog data can seriously slow down the code, which of course slows the motor.

So I include the code as follows for the master and salve.

For the slave:

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

AccelStepper stepper(1,3,4);

int tick = 0;// variable values
int ManualLoc = 0;// variable values
int CWlocal = 0;// variable values
int ACWlocal = 0;// variable values

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

int data[3];
const int ManualLocPin = 5;// local button used to decide on local or remote control of motor
const int CWlocalPin =6;// local button to drive motor CW
const int ACWlocalPin =7;// local button to drive motor ACW.
#define PotlocPin 0  //local Potentiomenter to vary speed 


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

 pinMode(ManualLocPin, INPUT); 
 pinMode(CWlocalPin, INPUT);
 pinMode(ACWlocalPin, INPUT_PULLUP);
 

stepper.setAcceleration(500);

  //set_up for radio contact with master 
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);// Change level following commissioning.
  radio.startListening();
  //radio.setDataRate(RF24_2MBPS);; // default 250KBPS
}

void loop()
{

 ManualLoc = digitalRead(ManualLocPin); //Button to select remote or local control of motor
 CWlocal = digitalRead(CWlocalPin);
 ACWlocal = digitalRead(ACWlocalPin);
 
 // Variables for potentiometer to vary maximum speed. 
static float tick2 =0;
static int analog_read_counter =100;
static int  analog_value = 0;
  
  if (ManualLoc ==HIGH){// Used to select local or remote control of motor.  If 'HIGH' 
    // then local user  (slave) selected.
    if (analog_read_counter > 0){  
       analog_read_counter--; 
    }

    else {analog_read_counter = 3000;
    // Now read the pot (from 0 to 1023)
    analog_value = analogRead(PotlocPin);
    // Give the stepper a chance to step if it needs to
    stepper.run();
    //  And scale the pot's value from min to max speeds
   tick2 = map(analog_value,0,1023,500,1500);
        stepper.setMaxSpeed(tick2);
    // Update the stepper to run at this new maximum speed
   
  }

  // This will run the stepper up to maximum  speed
  stepper.run();
       
      //Serial.println(PotlocPin);} Use this line if required to check values on monitor. 
      //Must be removed after check because it slows the motor down.
      
        // step motor:
        if (CWlocal == HIGH){
        stepper. move (20000);
        stepper. run ();}

        else if (ACWlocal == LOW){
        stepper. move (-20000);
        stepper. run ();}


        else 
  
        {
           stepper.stop();
           stepper.runToPosition();
        }
  
       
   }
  else if (ManualLoc ==LOW){ // This section passes control to remote user (master)
    
        if ( radio.available())
         radio.read(&data, sizeof(data));
         //Serial.println(data[2]);

       // tick = map(data[2],0,1023,500,1500);
        stepper.setMaxSpeed(data[2]);

        // step motor:
        if (data[0] == HIGH){
        stepper. move (20000);
         stepper. run ();}

        else if (data[1] == HIGH){
        stepper. move (-20000);
        stepper. run ();}


        else 
  
        {
           stepper.stop();
           stepper.runToPosition();
        }
  }
}

and for the master

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


int data[3]; // To collect three lots of data to send to slave.


RF24 radio(9, 10); // CE, CSN
const byte address[6] = "00001";
//boolean buttonState = 0;

void setup() {
  //set_up for radio contact with slave 
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
  //radio.setDataRate(RF24_2MBPS);; // default 250KBPS
}

void loop() {
 
 data[0] = digitalRead(4); // button connected to this pin to rotate motor
 data[1] = digitalRead(5); // button connection this pin to rotate motor
 data[2] = map(analogRead(A0),0,1023,500,1500); // potentiometer to change speed.
 //data[3] = digitalRead(2);
   radio.write(data, sizeof(data)); // All the abve data sent to slave.
    delay (10);
}