Fresh Eyes Needed - Stuttering Motor Issues

Have a 48 VDC brushed motor, a Cytron MDS40B controller, Arduino Uno (r3), single axis rocker potentiometer.

Everything seems to be working fine, except the motor has an intermittent (and persistent) stutter at any potentiometer input - slow to fast - and either direction. I've replaced every bit of hardware in an effort to chase down the problem, but it persists, so I'm left to believe it's glitch in the code - the one thing I know essentially nothing about...

Desired operation is pretty simple: The speed and direction of a brushed DC motor is controlled by a single axis potentiometer (center-sprung thumbstick). The motor moves a device in 2 directions on a single axis. There are NC limit switches at either end of the axis. There is an adjustable (via code) speed ramp-down function to prevent instant stopping once a limit switch is hit. There is an emergency stop function. There is also a speed limit function: set a switch in one direction and full throttle only allows 50% power to the motor; set in the other direction full throttle sends 100% power to the motor.

That's it, essentially.

Original coder is unavailable. Any help, guidance or outright fixes will be greatly appreciated (and potentially compensated). Anyone see a glaring glitch (or outright error) in this code:

#include "CytronMotorDriver.h"

#define PWM 5
#define DIR 6
// Configure the motor driver.
CytronMD motor(PWM_DIR, PWM, DIR);  // PWM = Pin 3, DIR = Pin 4.

#define ramp_down_speed 10    //increasing the number will slow the ramp down process and vice versa
#define noise_window 10 //assuming at rest position potentiometer will output voltage VCC/2. When potentiometer is tilt forward it will output in the range VCC/2-VCC and vice versa.
//As per data sheet there is tolerance in accuracy of rest position. noise window will ensure that when potentiometer is at rest motor is also not moving.

const int Pot_Pin = A0;    // select the input pin for the potentiometer
const int FSW_Pin = 7;    // select the input pin for the forward limit switch
const int BSW_Pin = 8;    // select the input pin for the backward limit switch
const int ESW_Pin = 9;    // select the input pin for the emergency stop
const int SLS_Pin=10; //Speed limit switch pin

bool BSW_state = 0;
bool FSW_state = 0;
bool ESW_state = 0;
int power = 0;
int forward_power = 0;
int backward_power = 0;
int last_forward_power = 0;
int last_backward_power = 0;
int last_power = 0;

void setup()
{ 
  pinMode(FSW_Pin, INPUT_PULLUP);
  pinMode(BSW_Pin, INPUT_PULLUP);
  pinMode(ESW_Pin, INPUT_PULLUP);
  pinMode(SLS_Pin, INPUT_PULLUP);
  
  delay(1000);
}
void loop()
{
  ESW_state = digitalRead(ESW_Pin);
  if (ESW_state == LOW)
  {
    BSW_state = digitalRead(BSW_Pin);
    FSW_state = digitalRead(FSW_Pin);
    power = get_direction();
    if (power < 0 && BSW_state == LOW)
    {
      backward_power = power;
    }
    else if (power > 0 && FSW_state == LOW)
    {
      forward_power = power;
    }
    if (FSW_state == HIGH)
    {
      //Serial.println("                   Ramp forward");
      ramp_down_forward(forward_power);
      forward_power = 0;   
    }
    if (BSW_state == HIGH)
    {
      //Serial.println("                   Ramp Backward");
      ramp_down_backward(backward_power);
      backward_power = 0;     
    }
    if (BSW_state == LOW && FSW_state == LOW)
    {
      // Serial.print("Power value=");
      // Serial.println(power);
      motor.setSpeed(power);
      delay(50);
    }
    else if (FSW_state == LOW)
    {
      //  Serial.print("Forward Power value=");
      // Serial.println(forward_power);
      motor.setSpeed(forward_power);
      delay(50);
    }
    else if (BSW_state == LOW)
    {
      // Serial.print("Backward Power value=");
      // Serial.println(backward_power);
      motor.setSpeed(backward_power);
      delay(50);
    }
  }
  else
  {
    // Serial.println("Emergency Mode");
    motor.setSpeed(0);
  }
}
int get_direction()
{
  int x = analogRead(Pot_Pin);
  if((x<511+noise_window)&&(x>511-noise_window))
  {
    return 0;
  }
  if(digitalRead(SLS_Pin)==HIGH)
  {
    return map(x, 0, 1023, -128, 128);//full speed
  }
  else if(digitalRead(SLS_Pin)==LOW)
  {
    return map(x, 0, 1023, -64, 64);//half speed
  }  
}
void ramp_down_backward(int x)
{
  // Ramp down from current value motor to 0
  if (x < 0)
  {
    for (int y = x; y <= 0 ; y ++)
    {
      // Serial.println(y);
      motor.setSpeed(y);
      delay(ramp_down_speed);
    }
    forward_power = 0;
    backward_power = 0;
  }
}
void ramp_down_forward(int x)
{
  if (x > 0)
  {
    for (int y = x; y >= 0 ; y --)
    {
      //Serial.println(y);
      motor.setSpeed(y);
      delay(ramp_down_speed);//
    }
    forward_power = 0;
    backward_power = 0;
  }
}
    if (power < 0 && BSW_state == LOW)
    {
      backward_power = power;
    }
    else if (power > 0 && FSW_state == LOW)
    {
      forward_power = power;
    }

There are 3 variables involved in the conditions here, and you handle 2 of the 8 possible combinations. That leaves 6 un-handled. Not a good idea.

The names of most of your variables leaves a lot to be desired. Maybe they meant something to the original coder, but they mean nothing to me.

There is an adjustable (via code) speed ramp-down function to prevent instant stopping once a limit switch is hit.

How is that supposed to work? Are the limit switches not actually at the end of the travel? Otherwise, how would triggering a limit switch allow time to slow down?

It's code tags, NOT quote tags.

@PaulS

Thanks - fixed the Quote/Code thing.

As to your other comments, well, the original coder (VERY nice guy) didn't speak much English, so yes, his nomenclature is a bit clunky.

Limit switches:

Very good observation! Yes, the limit switches are placed to allow continued movement of the device so that it can effectively coast to a stop. The physical setup keeps the limit switch triggered until the device moves fully away in the opposite direction.

As to the un-handled variables, well, I suppose this is what I'm looking for help with (I can make changes to the code, but I can't write it).

Thanks very much!

Good news
i know your problem: the ramping up and down PWM is lowering voltage too much .
It is explained in a 2012 post on the forum

If you really are trying to run a 24V motor at a fraction of a volt then this is expected behaviour. If the stall current is .75A at 24V then it should be taking about 11mA at 0.375V - the torque at that low current isn't enough to overcome static and dynamic friction so its only just running.

Using PWM from a higher voltage may improve matters but there is a fundamental issue here that you can't run a brushed DC motor at a tiny fraction of its design speed without friction being dominant.

from here

Bad news

Code must mostly be rewritten- which would also fix issues pointed out by @paulS

There are 3 variables involved in the conditions here, and you handle 2 of the 8 possible combinations. That leaves 6 un-handled. Not a good idea.

The names of most of your variables leaves a lot to be desired. Maybe they meant something to the original coder, but they mean nothing to me.

I can help with the rewrite, however, check PM

  • I have added code to introduce accelleration and dcelleration during normal running.
  • I have added code to add hysteresis to normal running speed. This may help with the stuttering.
  • I have added code to not invoke setSpeed() when the motor is already at the speed you want. Maybe this will also help the stuttering.
  • I have removed the ramp functions and the loops in them. Unnecessary: loop() is al the loop anyone needs.
  • I have removed extraneous variables.
//#include "CytronMotorDriver.h"

/** I don't have CytronMotorDriver.h, so I'll just rig up some  dummy variables*/
// ==== START OF DUMMY VARIABLES
const int PWM_DIR = 0;
class CytronMD {
  public:
    CytronMD(int a, int b, int c) {}
    void setSpeed(int) {}
};
// ==== END OF DUMMY VARIABLES

#define PWM 5
#define DIR 6
// Configure the motor driver.
CytronMD motor(PWM_DIR, PWM, DIR);  // PWM = Pin 3, DIR = Pin 4.

#define ramp_down_acceleration 10    //increasing the number will slow the ramp down process and vice versa
#define running_acceleration 50    //increasing the number will slow the rate of normal acelleration
#define noise_window 10 //assuming at rest position potentiometer will output voltage VCC/2. When potentiometer is tilt forward it will output in the range VCC/2-VCC and vice versa.
#define running_noise_window 2 // this is the noise window *after* conversion from pot read to motor speed
//As per data sheet there is tolerance in accuracy of rest position. noise window will ensure that when potentiometer is at rest motor is also not moving.

const int Pot_Pin = A0;    // select the input pin for the potentiometer
const int FSW_Pin = 7;    // select the input pin for the forward limit switch
const int BSW_Pin = 8;    // select the input pin for the backward limit switch
const int ESW_Pin = 9;    // select the input pin for the emergency stop
const int SLS_Pin = 10; //Speed limit switch pin

bool BSW_state = 0;
bool FSW_state = 0;
bool ESW_state = 0;


int power = 0;

void setup()
{
  pinMode(FSW_Pin, INPUT_PULLUP);
  pinMode(BSW_Pin, INPUT_PULLUP);
  pinMode(ESW_Pin, INPUT_PULLUP);
  pinMode(SLS_Pin, INPUT_PULLUP);

  motor.setSpeed(0);
  delay(1000);

}



void loop()
{
  // emergency stop
  ESW_state = digitalRead(ESW_Pin);
  if (ESW_state == HIGH) {
    power = 0;
    motor.setSpeed(power);
   // frobbing the emergency stop stops things for 1 second at least.
   // this give you time to twirl the pot to zero
    delay(1000);
  }
  else {
    BSW_state = digitalRead(BSW_Pin);
    FSW_state = digitalRead(FSW_Pin);

    // handle stop switches
    if (power < 0 && BSW_state == LOW)
    {
      power++;
      motor.setSpeed(power);
      delay(ramp_down_acceleration);
    }
    else if (power > 0 && FSW_state == LOW)
    {
      power--;
      motor.setSpeed(power);
      delay(ramp_down_acceleration);
    }

    // handle normal movement
    else {
      int target_power = get_direction();

      // if the target speed is zero, ignore the running noise window
      // and use "Stop!" acceleration.
      if (target_power == 0) {
        if (power > 0) power --;
        else if (power < 0) power ++;
        else {
          // no action needed - we are at the correct speed;
          return;
        }
        
        motor.setSpeed(power);
        delay(ramp_down_acceleration);
      }
      else {
        // respect the limit switches. don't attempt to go back if the back limt switch is on

        if(target_power < 0 && BSW_state == LOW) 
          return; // do nothing.
        
        if(target_power > 0 && FSW_state == LOW) 
          return; // do nothing.
          
        // otherwise, acellerate within our noise window.
        // this means we never actually get to the target speed, but meh. 
        // the operator of the train will just tweak the pot to get the speed they want

        if (target_power - power > running_noise_window) {
          power++;
        }
        else if (target_power - power < running_noise_window) {
          power--;
        }
        else {
          // no action needed - we are at the correct speed;
          return;
        }
        
        // otherwise, accelerate as required 
        motor.setSpeed(power);
        delay(running_acceleration);
      }
    }
  }
}

int get_direction()
{
  int x = analogRead(Pot_Pin);
  if ((x < 511 + noise_window) && (x > 511 - noise_window))
  {
    return 0;
  }
  if (digitalRead(SLS_Pin) == HIGH)
  {
    return map(x, 0, 1023, -128, 128);//full speed
  }
  else if (digitalRead(SLS_Pin) == LOW)
  {
    return map(x, 0, 1023, -64, 64);//half speed
  }
}