DC motor ramping function

Hello, any suggestions will be appreciated.

I’ve been trying to figure out a function to ramp a 12v dc motor up and then down once a specific target is reached. The target is a linear measurement (rack & pinion) correlating to a number of counts from a Hall Effect sensor.

The ramping up works well and is convention (blink without delay approach), using arduino’s timer0_millis function.

The problem with the function I’ve put together, is that it will not proceed beyond one execution of the ramp down if statement. I’ve tried re-arranging braces, else and if else, but cannot seem to get this working as is, and am stuck in my approach, right now.

The code is my attempt at simulating this, in hope to come to a solution.

#define DEBUG_RAMP
#define MAX_PWM 25

int PWM_val = 0;
long last_ramp = 0;
unsigned long ramp = 0;
unsigned long cycleA = 6000; // ramp timing cycle at 6 seconds

static int countA = 0;
static int Decel_target = 25;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(1000); //0 - 999 blocking count

  last_ramp = millis(); // initiate timer0
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(500); //0 - 499 blocking count
  // top of loop statements
  countA += 1; // simulate encoder count
  Serial.print(F("Encoder Count: "));
  Serial.println(countA);

  static bool Ramp = true;
  // check and call until ramping completed.
  if (!Ramp || ramp_PWM())
  {
    // start the ramping function.
    Ramp = false;
  }
  // continue with loop functions,

  // Marker: ramp has completed.
  if (PWM_val == MAX_PWM)
  {
    Serial.println(F("Ramp has completed"));
    Serial.println();
  }

  // Marker: ramp down condition.
  if (countA == Decel_target)
  {
    Serial.println(F("PWM_val is ramping down"));
    Serial.println();
  }
}

int ramp_PWM() {
  ramp = millis(); 
  if (ramp - last_ramp > 0) 
  {
    int Accel = 0; // begin ramp up, interpolate PWM_val's
    Accel = MAX_PWM * float(ramp - last_ramp) / float(cycleA);
    PWM_val = Accel;
#ifdef DEBUG_RAMP
    Serial.println(ramp);
    Serial.println(last_ramp);
    Serial.println(ramp - last_ramp);
#endif

    if (ramp - last_ramp >= cycleA)
      PWM_val = MAX_PWM; // PWM_val set to a limit
#ifdef DEBUG_RAMP
    Serial.print(F("PWM value: "));
    Serial.println(PWM_val);
    Serial.println();
#endif
    //only executes once, when the target condition is met - exactly at count = 25 ??
    if (ramp - last_ramp >= cycleA && countA == Decel_target) 
    {
      int Decel = 0; // PWM_val ramping down
      Decel = MAX_PWM - MAX_PWM * float(ramp - last_ramp - 1 * cycleA) / float(cycleA);
      PWM_val = PWM_val + Decel;
#ifdef DEBUG_RAMP
      Serial.print(F("Decel value: "));
      Serial.println(Decel);
      Serial.print(F("PWM value: "));
      Serial.println(PWM_val);      
#endif
    }
  }
  return 0;
}

Here's an example that uses a Finite State Machine to do the ramping. It ramps up over 6 seconds, holds at full speed until count 25, and then ramps down over 6 seconds. You kick off a sequence by setting RampStartTime to millis(). When RampStartTime is zero the sequence is done.

#define DEBUG_RAMP
#define MAX_PWM 255

int PWM_val = 0;
unsigned long RampStartTime = 0;
unsigned long currentTime = 0;
const unsigned long RampInterval = 6000; // Ramp timing cycle at 6 seconds

int EncoderCount = 0;
const int StartDecelerationCount = 25;

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(1000); //0 - 999 blocking count

  RampStartTime = millis(); // initiate ramp-up
}

void loop()
{
  // put your main code here, to run repeatedly:
  delay(500); //0 - 499 blocking count
  // top of loop statements
  EncoderCount += 1; // simulate encoder count

  Ramp_PWM();
}

enum RampStates {Idle, RampingUp, FullSpeed, RampingDown} RampState = Idle;

int Ramp_PWM()
{
  unsigned long currentTime = millis();
  unsigned long elapsedRampTime = currentTime - RampStartTime;

  switch (RampState)
  {
    case Idle:
      if (RampStartTime != 0)
      {
        EncoderCount = 0;
        PWM_val = 0;
#ifdef DEBUG_RAMP
        Serial.println(F("Starting Ramp Up"));
#endif
        RampState = RampingUp;
      }
      break;

    case RampingUp:
      if (elapsedRampTime <= RampInterval)
      {
        PWM_val = MAX_PWM *
                  (float(elapsedRampTime) / float(RampInterval));
      }
      else
      {
#ifdef DEBUG_RAMP
        Serial.println(F("Reached Full Speed"));
#endif
        RampState = FullSpeed;
      }
      break;

    case FullSpeed:
      PWM_val = MAX_PWM;
      if (EncoderCount >= StartDecelerationCount)
      {
        RampStartTime = currentTime;
#ifdef DEBUG_RAMP
        Serial.println(F("Start Ramping Down"));
#endif
        RampState = RampingDown;
      }
      break;

    case RampingDown:
      if (elapsedRampTime <= RampInterval)
      {
        PWM_val = MAX_PWM - MAX_PWM *
                  (float(elapsedRampTime) / float(RampInterval));
      }
      else
      {
        PWM_val = 0;
        RampStartTime = 0;
#ifdef DEBUG_RAMP
        Serial.println(F("End Ramping Down"));
#endif
        RampState = Idle;
      }
  }

#ifdef DEBUG_RAMP
  if (RampStartTime != 0)
  {
    Serial.print(F("Elapsed time: "));
    Serial.print(elapsedRampTime);
    Serial.print(F(", Encoder Count: "));
    Serial.print(EncoderCount);
    Serial.print(F(", PWM value: "));
    Serial.println(PWM_val);
  }
#endif

  return 0;
}

it should be executed when cycleA equal one value

Thanks kindly to both of you for replying.

@johnwasser - I had been thinking the next options were a switch or enumerating a state machine, but was apprehensive to attempt those, because I just could not understand how to make what I had work.

Perhaps it is a lesson to move along and at least attempt a different approach having backed oneself into a corner.

@gcjr - I see, am to understand the += 1 does not increment beyond a single loop and that it should be a condition that cycleA is at the beginning of its cycle. Perhaps I'm still not getting where it messed it up!

This will most certainly help my progression.

cycleA is incremented each iteration of loop() and therefore is only equal to Decel_target during one iteration

Thank you @gcjr .

I was able to get the simulation working by changing,

    if (ramp - last_ramp >= cycleA && countA >= Decel_target)

and then proceed with another conditional statement to arrest the PWM_val and Decel variables.

I think that this a case of my confusion, by not transferring the rvalue reference of countA to cycleA, being narrowly focused on Decel_target.

Your help has allowed me to accomplish what I had set out to do, understanding how to formulate the conditional statements.

I will now delve into the FSM push start of @johnwasser, as that is more sensible for a 'real program' .

Thank you both again being generous with your time.

i'm confused why the code ramps the motor speed based on the variable described as an "encoder count" when your description describes a position measured with a Hall effect sensor.

why isn't the position measured by the hall effect sensor not used to control the motor? is the purpose of ramping similar to what is done with PID to begin reducing voltage the motor based both on distance from and speed toward a target position?

Hi, Sorry about misleading, not intentional, the ‘encoder’ is made up of two scavenged commercial Hall Effect sensors, on separate circuit boards, arranged 90 degree out of phase or in this case 3.5 poles apart, which read an 8 pole magnet, 4 pulses per revolution (falling) on the motors end shaft. Indeed separate PID’s have controlled position and speed in some of the tests.

Nonetheless simplified testing uses the count of a single Hall Effect sensor to set the target. This is previously determined by manually stopping the motor at a specific point giving the measured distance of travel by count without calculation. This very simplistic and very much a work in progress!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.