Proper a4988 driving using streaming values. NRF24, Arduino UNO, Pololu

Hey everyone! I’ve been at this for about 4 weeks now and after running blink for the first time i jumped right into my meat and potatoes. I am building a pan/tilt using stepper motors instead of servos. The reason for my choice in hardware is irrelevant.

Setup is as follows:

-Transmitter: Arduino Nano (Chinese Clone(ch340)) with NRF24L01 module using 3.3v on-board and all plugged into a sunfounder remote robot controller which has power in(battery) and a PS2 style joystick.
The pan and tilt axes are both controlled by the two axes of the joystick with the two values transmitted to the slave/receiver which reads the values as “joystick [0]” and “joystick[1]”.

-Receiver: Arduino UNO (Chinese Clone(ch340)) with NRF24L01 module using 3.3v through a Proteneer CNCSHIELD_V3. Only the X and Y axis motor driver sockets are being utilized. In the two sockets are a pair of Pololu A4988 drivers.

The annoying part is: the code works great and then again doesn’t.

I can’t get proper operation of the motors. They both spin when the joystick’s x-axis is articulated but not the y-xis. On serial monitor, the two transceivers are sending and receiving perfectly.

What I need to accomplish is to move the steppers in somewhat ‘real-time’ using the remote control.

My guess is that I need to be working off of a stored value that is updated with each new packet, but I could never find a stepper library that would to speed-control through an a4988 which would simplify things greatly so I didn’t have to have such huge blocks of code to drive the motors.

Transmitter’s Code

/*----- Import all required Libraries -----*/
 
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
 
/*----- Declare all constant pin to be used ----*/

#define JOYSTICK_X A0
#define JOYSTICK_Y A1
 
 
const uint64_t pipe = 0xE8E8F0F0E1LL; // This is the transmit pipe to communicate the two module
 
 
/*-----Object Declaration ----*/
 
RF24 radio(9, 10); // Activate  the Radio
 
/*-----Declaration of Variables -----*/
 
int joystick[2];  // Two element array holding the Joystick readings
 
void setup()   
{
  Serial.begin(115200); /* Opening the Serial Communication */
  radio.begin();
  radio.setPALevel(RF24_PA_MAX);
  radio.openWritingPipe(pipe);
}//--(end setup )---
 
 
void loop()   /* Runs Continuously */
{
  joystick[0] = analogRead(JOYSTICK_X); // Reading Analog X
  delay(.75);
  joystick[1] = analogRead(JOYSTICK_Y); // Reading Analog Y
  
  radio.write( joystick, sizeof(joystick) );
      Serial.print("X = ");
      Serial.print(joystick[0]);
      Serial.print(" Y = ");      
      Serial.println(joystick[1]);
 
}//--(end main loop )---

Receiver’s Code

/*----- Import all required Libraries -----*/
 
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
 
/*----- Declare all constant pin to be used ----*/

// This is the transmit pipe to communicate the two module 
const uint64_t pipe = 0xE8E8F0F0E1LL; 


//output pins for motion control(based on CNC_SHIELD_V3)
  
  //y-axis
    const int ydir = 6;
    const int ystep = 3;
  
  //enable pin for drivers
    const int enable = 8;
  
  //x-axis
    const int xdir = 7;
    const int xstep = 4;
  
 
 
/*-----Object Declaration ----*/

  // Activate the Radio
    RF24 radio(9, 10); 
 
/*-----Declaration of Variables -----*/

  //  Two element array holding the Joystick readings
    int joystick[2];



void setup()  
{
  Serial.begin(115200); /* Opening the Serial Communication */
  Serial.println("Nrf24L01 Receiver Starting");
  delay(1);
  radio.begin();
  
  /* Set the PA Level low to prevent power supply related issues
  and for close proximity ie: benchtop */
    radio.setPALevel(RF24_PA_MAX);  
  
  /* Open a reading pipe to receive data from remote*/
    radio.openReadingPipe(1,pipe);
  
  /* Start the radio listening for data*/
    radio.startListening();

  /*assign pin activity roles*/
    pinMode (xdir, OUTPUT);
    pinMode (xstep, OUTPUT);
    pinMode (ydir, OUTPUT);
    pinMode (ystep, OUTPUT);
    pinMode (enable, OUTPUT);
    
}
 
 
void loop()   
{
  
  
  if ( radio.available() )
   {
    // Reading the data payload until the RX received everything
    unsigned long message;
    bool done = false;
    while (!done)
    {
      // Fetching the data payload
      done = radio.read( joystick, sizeof(joystick) );

        int x = joystick[0];
        int y = joystick[1];
        int T;

  
       //x-axis control
          /*if (x > 400 and x < 600){
            digitalWrite(enable, HIGH);}
            
          if (x > 600){
            //int T;
            digitalWrite(enable, LOW);
            delay(.5);
            T=map(x, 600, 1023, 5, 0);
            digitalWrite(xdir, HIGH);
            delay(.2*T); // waits for a millisecond increment * the joystick position
            digitalWrite(xstep, HIGH);  // steps motor
            delay(.5); // waits for a millisecond increment
            digitalWrite(xstep, LOW);}

  else
          if (x < 517){
            //int T;
            digitalWrite(enable, LOW);
            delay(.5);
            T=map(x, 0, 400, 0, 5);
            digitalWrite(xdir, LOW);
            delay(.2*T); // waits for a millisecond increment * the joystick position
            digitalWrite(xstep, HIGH);  // steps motor
            delay(.5); // waits for a millisecond increment
            digitalWrite(xstep, LOW);}

    
          //y-axis control

          if (y > 500 and y < 540){
            digitalWrite(enable, HIGH);}
            
          if (y > 539){
            //int T;
            digitalWrite(enable, LOW);
            delay(.5);
            T=map(y, 540, 1023, 5, 0);
            digitalWrite(ydir, HIGH);
            delay(.2*T); // waits for a millisecond increment * the joystick position
            digitalWrite(ystep, HIGH);  // steps motor
            delay(.5); // waits for a millisecond increment
            digitalWrite(ystep, LOW);}

  
          if (y < 501){
            //int T;
            digitalWrite(enable, LOW);
            delay(.5);
            T=map(y, 0, 500, 0, 5);
            digitalWrite(ydir, LOW);
            delay(.2*T); // waits for a millisecond increment * the joystick position
            digitalWrite(ystep, HIGH);  // steps motor
            delay(.5); // waits for a millisecond increment
            digitalWrite(ystep, LOW);}*/
 

      Serial.print("X = ");
      Serial.print(joystick[0]);
      Serial.print(" Y = ");      
      Serial.println(joystick[1]);
      delay(25);
    }


  if(done);
    {
      Serial.println("received");    
    }
  }
  
  else
    {    
      if (!radio.available()){    
        delay(25);
        Serial.println("No radio available");}
    }

 
}

I don't know if it is the root cause of your problem but you can't do fractional delay()s - they will be rounded down to the nearest integer so 0.7 will give 0. Use delayMicroseconds() - or better still don't use any version of delay().

Why is a big chunk of your code commented out?

What is the line T=map(x, 600, 1023, 5, 0); supposed to do.

You don't need to enable/disable the motor repeatedly - maybe not at all. If you disable a stepper motor it can lose position.

There is nothing in principle wrong with controlling the motor the way you are doing it. It seems that you want the motor to keep stepping in a direction as long as the joystick is beyond a certain value. If that is what you want to happen I don't think the AccelStepper library will add any value.

It would be a good idea to reorganize your code into small functions such as in the second example in the simple stepper code linked below.

...R Stepper Motor Basics Simple Stepper Code

Robin2: I don't know if it is the root cause of your problem but you can't do fractional delay()s - they will be rounded down to the nearest integer so 0.7 will give 0. Use delayMicroseconds() - or better still don't use any version of delay().

Why is a big chunk of your code commented out?

//If it appears to be commented out, it should not be. It is possible that I made a transcription error when pasting, somehow.

What is the line T=map(x, 600, 1023, 5, 0); supposed to do.

//The T= function was to create a timing multiplier that could be applied to the step-timing and smooth out the motor operation while maintaining a 4 or 5-speed joystick sweep.

You don't need to enable/disable the motor repeatedly - maybe not at all. If you disable a stepper motor it can lose position.

//The reason for disabling the steppers is to conserve power and reduce heat. The A4988 drivers produce a lot of heat while they run and I found that simply by disabling them between steps, they only ever got warm to the touch rather than hot while running at max amperage.

//The steppers are driving a set of worm-gears to operate two axes in a pan/tilt. With the worm gears in play rather than spur gears, there is no need to use the electromagnetic cogging as a brake. With that being the case, the motors and their drivers need to be disabled to conserve energy. This device with, in some situations, be remote mounted with a solar panel and batteries to power it and it is to costly to keep the motors powered all the time.

There is nothing in principle wrong with controlling the motor the way you are doing it. It seems that you want the motor to keep stepping in a direction as long as the joystick is beyond a certain value. If that is what you want to happen I don't think the AccelStepper library will add any value.

//I lack the experience and knowledge, at this point, to properly utilize libraries based on the README or other documentation provided. They all seem to me to be poorly explained in how to utilize variables and classes to produce desired results. That being said, I am trying to learn how to write/ create my own library in the future to do what is needed provided I don't happen across one between now and then.

It would be a good idea to reorganize your code into small functions such as in the second example in the simple stepper code linked below.

//I will investigate the code below and get back to you. I was really just trying to combine the RF24 communication with something akin to the Speed-Control example-sketch using the A4988 driver.

...R Stepper Motor Basics Simple Stepper Code

If you are using a worm gear I can see that disabling the steppers should not cause a problem. But then I wonder if you need stepper motors at all or if a simple DC motor may be sufficient. Steppers are very inefficient.

Please respond more quickly. I have forgotten why I asked about the T= stuff.

...R

I originally decided on stepper motors to utilize their holding torque with spur-gears. At this point, I agree that they are becoming obsolete. The code ended up going through a few more changes and I have arrived at a cleaner and better functioning solution with only one drive speed for each axis.

Receiver Code:

/*----- Import all required Libraries -----*/
 
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <RF24_config.h>

 /*----- Declare all constant pin to be used ----*/

// This is the transmit pipe to communicate the two module 
const uint64_t pipe = 0xE8E8F0F0E1LL; 

//output pins for motion control(based on CNC_SHIELD_V3)
 //y-axis
    const int ydir = 6, ystep = 3;
  //enable pin for drivers
    const int enable = 8;
  //x-axis
    const int xdir = 5, xstep = 2;

/*-----Object Declaration ----*/

  // Activate the Radio
    RF24 radio(9, 10); 
 
/*-----Declaration of Variables -----*/

  //  Two element array holding the Joystick readings
    int joystick[2];

void setup()  
{
  Serial.begin(19200); /* Opening the Serial Communication */
  Serial.println("Nrf24L01 Receiver Starting");
  delay(1);
  radio.begin();
  
  /* Set the PA Level low to prevent power supply related issues
  and for close proximity ie: benchtop */
    radio.setPALevel(RF24_PA_MAX);  
  
  /* Open a reading pipe to receive data from remote*/
    radio.openReadingPipe(1,pipe);
  
  /* Start the radio listening for data*/
    radio.startListening();

  /*assign pin activity roles*/
    pinMode (xdir, OUTPUT);
    pinMode (xstep, OUTPUT);
    pinMode (ydir, OUTPUT);
    pinMode (ystep, OUTPUT);
    pinMode (enable, OUTPUT);
    
}

//step functions

 
 
void loop(){ 
  if (radio.available()){
    // Reading the data payload until the RX received everything
    bool done = false;
    while (!done){
    // Fetching the data payload
    {done = radio.read( joystick, sizeof(joystick) );}
    }
    //Notify via serial that information was received 
    if(done){
      Serial.println("received");
    //Print the joystick positions as received
      Serial.print("X = ");
      Serial.print(joystick[0]);
      Serial.print(" Y = ");      
      Serial.println(joystick[1]);
      delay(5);
      }
  }

//Notify via serial that radio was unavailable
  else
    {if (!radio.available()){    
       delay(1);
        Serial.println("No radio available");}
        }          
//x-axis control
  int y = joystick[1];
  int x = joystick[0];

    if ((x)>600){
      digitalWrite(enable, LOW);
      digitalWrite(xdir, HIGH);
      stepX();}

    if ((x)<400){
      digitalWrite(enable, LOW);
      digitalWrite(xdir, LOW);
      stepX();}

    
//y-axis control

    if ((y)>600){
      digitalWrite(enable, LOW);        
      digitalWrite(ydir, HIGH);
      stepY();}
            
    if ((y)<400){
      digitalWrite(enable, LOW);                 
      digitalWrite(ydir, LOW);
      stepY();}
    
  else {
    if((y>400 and y<600) and (x<600 and x>400)){
      digitalWrite(enable, HIGH);}
      }//close else command
}//close void loop function

void stepX(){
  digitalWrite(enable, LOW);
  digitalWrite(xstep, HIGH);  // steps motor
  delayMicroseconds(25); // waits for a millisecond increment
  digitalWrite(xstep, LOW);
}

void stepY(){
  digitalWrite(enable, LOW);
  digitalWrite(ystep, HIGH);  // steps motor
  delayMicroseconds(25); // waits for a millisecond increment
  digitalWrite(ystep, LOW);
}

I got on the main site and learned a bit about functions and subroutines in order to reduce the bulk of my code. I’ll continue to optimize it but at this point I think it is definately a better solution that where I was at before. I am still learning the subtleties of programming.

On another side note, I beat my head against the wall for a bit trying to get the function call to quit giving me compiling errors and saying that the function had no effect. The problem is described below.

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

void loop() {
  int i = 2;
  int j = 3;
  int k;

  k = myMultiplyFunction(i, j); // k now contains 6
  Serial.println(k);
  delay(500);
}

int myMultiplyFunction(int x, int y){
  int result;
  result = x * y;
  return result;
}

At the bottom of the loop, the function is declared outside of the loop which allows the loop to call the function from its address at “myMultiplyFunction” with the following parameters “(int x, int Y)” and then the body of the function is between the curly braces and is as follows:

int result;
result = x * y;
return result;

Where i ran into a problem was that when the function is called, if you do not include the parentheses that encapsulate the parameters, even if they are empty, the function is just a reference instead of calling the function.

it should look like this:

  k = myMultiplyFunction(i, j); // k now contains 6
  Serial.println(k);
  delay(500);

The part i am talking about is the letters "i’ and “j” inside parentheses immediately after the function name or address. Typically, if you leave the function empty of parameters and just want to create a canned cycle or block of code to execute, you still need to put the parentheses in with the function address to make your loop “call” the function and execute it. It is really easy to miss.

Robin; thank you so much for all of your help and thank you Arduino Community for being so awesome!