Ramp to speed over defined duration

I am trying to build a program where the motors

  • are not moving
  • ramp to specific speed over a set duration
  • hold target speed
  • ramp to new target speed over set duration
  • then slow down and stop

Once working, a master arduino will define this arduino’s target speed.

Currently I am adjusting the speed at specific intervals (timer2), but I really want to specify a length of duration and have a motor go from speed A to speed B in a defined amount of time. ATM the motors never get to their targetspeed because i’m not sure how to structure the code.

Robin2 had written a short program using numSteps which i tried to rework but didn’t have luck and have removed from the code below. Sorry, but not really sure how to adjust my current code, or this numSteps approach to achieve transitions over a set time period.

I am also trying to change speeds at defined intervals using Switch (this would eventually be coded in a Master arduino and sent via i2c). It appears to run through kind of correctly but goes haywire after 2 proper executions. Based on the serial monitor it looks like after the 2nd successful loop it actively switches Cases all the time (versus waiting for sceneDuration).

#include <Wire.h>
#define MS1 4
#define MS2 5
#define MS3 6
#define EN  8
#define directionPin 9
#define stepPin 10

byte b[3]; // byte array 
bool flag1 = LOW;

// Timers for Ramping
unsigned long targetInterval = 5000; // read speed from master
unsigned long stepIntervalMicros = 2000; //Current # of micros delay. Primary speed control
unsigned long prevStepMicros = 0; //Timer
unsigned long prevTime2 = 0;
unsigned long timer2 = 10000; //delay when ramping to target time
int scene = 0;
int sceneDuration = 5000;
int numScenes = 4;
int prevMillis = 0;

byte direction = 0;

void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
  pinMode(directionPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(EN, OUTPUT); //for other driver
  pinMode(MS1, OUTPUT); 
  pinMode(MS2, OUTPUT);
  pinMode(MS3, OUTPUT);
  digitalWrite(MS1, HIGH); //Micro. 000 = full, 100 = 1/2, 010 = 1/4, 110 = 1/8
  digitalWrite(MS2, LOW);
  digitalWrite(MS3, LOW);
  digitalWrite(EN, LOW);
  digitalWrite(directionPin, direction);
}

void loop() {
if (millis() - prevMillis >= sceneDuration){ //Timer to switch scenes
  prevMillis = millis();
  Serial.print("Scene: "); Serial.println(scene);
  scene ++;
  if (scene >= numScenes){ //reset to scene 0
    scene = 0;
  }
} 

switch (scene){
  case 0: 
    targetInterval = 5000;
    break;
  case 1:
    targetInterval = 1000;
    break;
  case 2:
    targetInterval = 2000;
    break;
  case 3:
    targetInterval = 500;
    break;
//  default:
//    break;
}

Serial.print("targetInterval: "); Serial.println(targetInterval);
Serial.print("currentInterval: "); Serial.println(stepIntervalMicros);

  {
    if (flag1 == HIGH)
    {
      if (b[0] == '*')
      {
        targetInterval = (b[1] << 8) | b[2]; //prev. RPSmicros = b[0] * 256 + b[1]; 
//        Serial.println(highByte(b[0]), HEX); //0x01; upper byte of 500
//        Serial.println(highByte(b[1]), HEX); //0x01; upper byte of 500
//        Serial.println(lowByte(b[2]), HEX); //0xF4; lower byte of 500
        Serial.print("Slave: ");
        Serial.println(targetInterval, DEC); //target speed
        flag1 = LOW;
      }
    }
  }
  moveMotor();
  
//  if (micros() - prevStepMicros >= timer2) { //fire motor at current interval timing
//    prevStepMicros = micros();
//    Serial.print("Slave: ");
//    Serial.println(targetInterval, DEC); //target speed
//  }
  
}

void moveMotor() {
  if (micros() - prevTime2 >= timer2){ //only adjust towards target speed at defined rate or will fly thru too quickly
    prevTime2 = micros();
    if (stepIntervalMicros >= targetInterval){ //if running too slow
      stepIntervalMicros --; //speed up motor by slowly decreasing pause
    }
    else if (stepIntervalMicros < targetInterval){ //if running too fast
      stepIntervalMicros ++; //slow down motor by slowly increasing pause
    }
  }
  if (micros() - prevStepMicros >= stepIntervalMicros) { //fire motor at current interval timing
    prevStepMicros = micros();
    singleStep();
  }
}

void receiveEvent(int howMany){
  for (int i = 0; i < howMany; i++){
    b[i] = Wire.read();
  }
  flag1 = HIGH;
}

void singleStep() {
    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);
}

jingleby: and have removed from the code below.

Can you post the program in which you tried to adapt my example. I think it will be easier to help that way.

I don't think what you want to achieve is complicated to implement.

...R

Sure thing, thanks Robin.

Its pretty much copy/paste from your post but in a simpler program in an attempt to isolate things. I can’t wrap my head around transitioning based on a specific duration vs how you laid it out via numSteps.

Its a bit of a mess. I //'d out my initial attempts to write it out but left in place in case it sheds light on my attempts. Particularly “rampAdjustMicros = (currentStepMicros - targetInterval)/rampInterval” makes sense as it defines how much over a given time does the interval need to change, but have spent hours going in circles.

#define EN 8
#define directionPin 9
#define stepPin 10
//unsigned long currentStepMicros = 4000;
//unsigned long targetInterval = 400;
//unsigned long rampDuration = 3000000; //adjust speed from A to B over 3sec 
//int durationSteps = 100; 
//unsigned long rampInterval = rampDuration/durationSteps; //how often to adjust. ie 30k
//unsigned long rampAdjustMicros; //how much to adjust on each of 100 steps
//unsigned long prevTimer = 0;
//unsigned long prevStepMicros = 0;

//ALT moveMotor
unsigned long curMicros;
unsigned long prevStepMicros = 0;
unsigned long slowMicrosBetweenSteps = 6000; // microseconds
unsigned long fastMicrosBetweenSteps = 1500;
unsigned long stepIntervalMicros;
unsigned long stepAdjustmentMicros;
int numAccelSteps = 800; // how many steps of rotation to adjust speed
int numSteps = 2000;
int stepsToGo;
byte direction = 1;


void setup() {
  pinMode(stepPin, OUTPUT);
  pinMode(directionPin, OUTPUT);
  pinMode(EN, OUTPUT);
  digitalWrite(directionPin, LOW);
  digitalWrite(EN, LOW);
  delay(2000);
  Serial.begin(9600);
  
//  rampAdjustMicros = (currentStepMicros - targetInterval)/rampInterval; 
//  abs(rampAdjustMicros);  //needs to be positive
//  ALT moveMotor variables
    stepAdjustmentMicros = (slowMicrosBetweenSteps - fastMicrosBetweenSteps) / numAccelSteps;
    stepIntervalMicros = slowMicrosBetweenSteps;
    stepsToGo = numSteps;
}

void loop() {
  moveMotor();
}

void moveMotorALTRobin() {
    if (stepsToGo > 0) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();
            stepsToGo --;
            if (stepsToGo <= numAccelSteps) {
                if (stepIntervalMicros < slowMicrosBetweenSteps) {
                    stepIntervalMicros += stepAdjustmentMicros;
                }
            }
            else {
                if (stepIntervalMicros > fastMicrosBetweenSteps) {
                    stepIntervalMicros -= stepAdjustmentMicros;
                }
            }
        }
    }
    else {
        stepsToGo = numSteps;
        prevStepMicros = micros();
    }
}

//void moveMotor (){
//  if (micros() - prevTimer >= rampInterval){ //every Xmicros adjust interval. ie every 30k micros
//    prevTimer = micros();
//    if (currentStepMicros >= targetInterval){ //too slow speed up by less delay
//      currentStepMicros -= rampAdjustMicros; 
//    }
//    else if (currentStepMicros <= targetInterval){
//      currentStepMicros += rampAdjustMicros; 
//    }
//  }
//  if (micros() - prevStepMicros >= currentStepMicros) { //fire motor at current rate
//    prevStepMicros = micros();
//    singleStep();
//  }
////  Serial.print("currentStepMicros ");
////  Serial.println(currentStepMicros);
////  Serial.print("rampAdjustMicros "); 
////  Serial.println(rampAdjustMicros); 
//}

void singleStep() {
    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);
}

This is an adaptation of my code to use time rather than the number of steps

   if (micros() - startMoveMicros >= totalMoveMicros) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();

            if (micros() - prevTimer >= rampInterval) { // every Xmicros adjust interval. ie every 30k micros
                prevTimer += rampInterval; // avoids accumulating errors
                
                if (stepIntervalMicros >= targetInterval){ // too slow speed up by less delay
                    stepIntervalMicros -= rampAdjustMicros; 
                }
                else if (stepIntervalMicros <= targetInterval){
                    stepIntervalMicros += rampAdjustMicros; 
                }
            }
        }
    }

and I think this is equivalent, and may be a bit neater

   if (micros() - startMoveMicros >= totalMoveMicros) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();
         }
        if (micros() - prevTimer >= rampInterval) { // every Xmicros adjust interval. ie every 30k micros
            prevTimer += rampInterval; // avoids accumulating errors
            
            if (stepIntervalMicros >= targetInterval){ // too slow speed up by less delay
                stepIntervalMicros -= rampAdjustMicros; 
            }
            else if (stepIntervalMicros <= targetInterval){
                stepIntervalMicros += rampAdjustMicros; 
            }
        }
    }

I have not tested either of them.

…R

Oh great! Thanks so much, I'm excited to review it tomorrow and give it a spin. (pun intended :P)

Thanks Robin2. I am confused by the first line, could you kindly clarify?

Is totalMoveMicros the difference between target and current or the duration of the transition? What is startMoveMicros?

Pseudo-code seems to be:

  1. unsure
  2. if step speed is at desired, step (should this not be the first thing…else if not at target then do rest?)
  3. if at ramp interval, speed up or slow down interval (stepInterval add/sub rampAdjustMicros)

side note: I added a line that seems to be missing but could be wrong: startMoveMicros = totalMoveMicros

void setup() {
//removed code for simplicity

  rampAdjustMicros = (currentStepMicros - targetInterval)/rampInterval; 
  abs(rampAdjustMicros);  //needs to be positive
}

void loop() {
   if (micros() - startMoveMicros >= totalMoveMicros) {
      startMoveMicros = totalMoveMicros; //???? I added this
      if (micros() - prevStepMicros >= stepIntervalMicros) { //step if at stepInterval
          prevStepMicros += stepIntervalMicros;
          singleStep();
       }
      if (micros() - prevTimer >= rampInterval) { // every Xmicros adjust interval
          prevTimer += rampInterval; // avoids accumulating errors
          
          if (stepIntervalMicros >= targetInterval){ // too slow speed up by less delay
              stepIntervalMicros -= rampAdjustMicros;
          }
          else if (stepIntervalMicros <= targetInterval){
              stepIntervalMicros += rampAdjustMicros;
          }
      }
  }
}

The easiest way to handle speed being a function of time is to implement it exactly that way:

 speed = my_speed_fn (millis());

Then all you have to do is place that in loop() (plus the code to update the motor driver
from the speed variable).

Typically your function will only be changing in a segment of time between two values of millis(),
and you’ll want it to work across wrap-around.

So something like:

unsigned long time_base = 0L ;

int my_speed_fn (unsigned long now)
{
  unsigned long diff = now - time_base ;  // use difference to avoid wrap around issues, so long as time_base isn't too stale
  if (diff < start_point)
    return start_speed ;
  else if (diff > end_point)
    return end_speed ;
  else
    return map (diff, START_POINT, END_POINT, start_speed, end_speed) ;
}

The nice thing about doing things this way is that the only state to be maintained is the value of millis().

With a stepper to drive the speed value then has to be turned into a step-rate, say via DDS.

jingleby: Is totalMoveMicros the difference between target and current [u]or[/u] the duration of the transition? What is startMoveMicros?

I had hoped the names were clear.

totalMoveMicros is the total time needed for the movement until all the accelerations have been done. Maybe "accelerationDurationMicros" would have been a better name

startMoveMicros is the moment at which the movement starts

Of course it is entirely possible that those concepts are not appropriate

...R

Or just use accelStepper and set the acceleration parameter as required to reach your target speed in the time required.

If you want to reach 100 steps per second in 1.5 seconds then use .setAcceleration(100/1.5);

Thanks everyone. The couple additional outlooks are super helpful as well. I'm going to implement some use of libraries as well as the other manual approach Robin2 has largely helped out on. Really looking forward to scaling this up to 10x motors over the next week or two!