Ramped stepper motor movement using live sensor data

Dear community,

since i am new to the community (and arduino alltogether) id like to take a short moment to say hi and introduce myself. I am a 38 year old flight instructor from switzerland, and as a hobby fly radio controlled model jets. Technology is moving along, and me as an old "analog geek" am more and more confronted with digital solutions. A new project is now forcing me towards new technologies, so im taking this as chance to find my way into arduino programming.

Now to my problem (I have tried search function with little to no luck): I want to replicate a motion sensed by a 9 axis IMU (Have a GY9250 and BNO055 on the way to me). The motions that are placed upon the sensor need to be relayed via A4988 stepper controllers to stepper motors. To achieve higher top speeds i want to ramp the motors.

For this i have started using a sourcecode from iforce2d's youtube channel. I have adapted it to my arduino nano and have been able to get my motors running correctly on them.
There are 2 versions of the code online, one using interrupts, the other conventional, and i am not sure which one better suits my needs.

Here to my main questions and problems:
The code is designed to power a cnc unit, which is fed with fixed coordinates that are sequenced one behind the other. I do not need this, i need it to constantly update the target position.
As you can see in the code, at the end there are numerous orders to move the motors. They always ramp up the motor (constantly checking if the halfway point is reached), then move at full speed towards a rampdown point (total steps required minus rampup steps taken), and then stop at the desired at time of start before starting the next motion.
Problem: The motion would ramp up and down at each sensor readind, never ever reaching full speed.
What i want to do is the following:
1.) Instead of a fixed target value, use a constantly updated target value. (Goal: no ramping up and down but creating a smooth motion )
2.) Change the motion adressing from "steps to be taken" towards an absolute adressing system (e.g. heading value, correlating to an absolute step position North = 0, East =200 steps ....much like a cnc coordinate).

My first mayor problem: do i better use the interrupt code, or the "normal" code? I have not quite understood the use of interrupts, but i do understand it frees processing capacity (for example to read new sensor data meanwhile).

Id very much appreciate any hints, helps or program lines to get the project rolling at a bit faster pace than i can work out with google and youtube.

Best regards and many thanks in advance

Stepper4_linearSpeedInterrupt.ino (2.54 KB)

How does the output relate to the IMU?

I am imagining that you are making some kind of motion controller, so hand movement is replicated with the steppers? Am I right?

Your hand naturally accelerates and decellerates. You can't instantly step to 5m/s. So just have the steppers follow the real movement.

If the steppers are too slow to follow your hand movement without skipping steps then you need more power. Either more voltage or bigger motors or both.

Miniflyer:
What i want to do is the following:
1.) Instead of a fixed target value, use a constantly updated target value. (Goal: no ramping up and down but creating a smooth motion )
2.) Change the motion adressing from "steps to be taken" towards an absolute adressing system (e.g. heading value, correlating to an absolute step position North = 0, East =200 steps ....much like a cnc coordinate).

I think it would help if you paint the bigger picture for us first. What are the stepper motors being used for? Then your description of what you want to do might be more easily understood.

Your #2 suggests to me that you want to get a sensor value and use it to set a value between 0 and 200 representing the position the motor should go to. But you also need to determine how fast the motor moves to that position and smooth motion may need acceleration and deceleration.

To my mind the choice between using interrupts or using other (non-interrupt) code will depend on how many steps per second are required and you have not mentioned that. The regular digitalWrite() function is slow but there is a digitalWriteFast library that is very much faster and just as easy to use - it may make the simpler code perform sufficiently well.

...R
Stepper Motor Basics
Simple Stepper Code
Simple acceleration code.

Yes and no. I want the motion controller to pick up absolute aircraft position of an RC jet aircraft, and then output the data via steppers to instruments like compass, g-meter and such.
Usually you are right, most often there will be natural accelleration and decelleration. However maneuvers can be snappy often. Especially in roll, rates of over 700 degrees per second can be reached with instantaneous onset (almost 2 rolls within 1 second). G onset to 9G in 1s also possible with similar initial rates. Normal steps without ramping will either force me to limit speed to a number where it will not stall at start motion (usually about half of what you can run with ramps), or reducing steps per unit to be displayed.....second is not an option. I lack gathered data to really be able to tell precice parameters, but want to prepare an optimum solution to cope with all thats thrown at the system.

It is not just a question of requirements, but i want to learn how to put these things together.....cnc machines work similarly, and i want to learn the "what is behind". I dont just want to get it to run, i want to get it to run good and understand why. :wink:

Sorry, typing at the same time :slight_smile:

Sooo: Bigger picture: i want the instruments in the cockpit to display real time data. The IMU with compass heading is the easiest to test out on the bench, as for for instance for engine temperatures and rpm and fuel pressure i would need either a "simlator" to provide dummy measurements, or to have my engine running, burning 4 liters of diesel per 10 minutes of typing.....the system behind it is the same, i have a sensor input which i multiply with a factor to get a "target step position sensor 123". What it displays later on does not really matter, the pointer needs to point in a certain direction for a certain input, for all instruments.

Steps per second.....good question. I want the unit to display in at least 0,5deg per step and rotate at 2rpm....roughly 1500 steps per second with the 20-step-per-rev nano steppers i have, adding a gearbox. The higher i can rev, the less torque will be needed and the higher the resolution can be. As a start i think 1500 will give realistically acheivable results.....

i hope this helps some

Robin2:
I think it would help if you paint the bigger picture for us first. What are the stepper motors being used for? Then your description of what you want to do might be more easily understood.

Your #2 suggests to me that you want to get a sensor value and use it to set a value between 0 and 200 representing the position the motor should go to. But you also need to determine how fast the motor moves to that position and smooth motion may need acceleration and deceleration.

To my mind the choice between using interrupts or using other (non-interrupt) code will depend on how many steps per second are required and you have not mentioned that. The regular digitalWrite() function is slow but there is a digitalWriteFast library that is very much faster and just as easy to use - it may make the simpler code perform sufficiently well.

...R
Stepper Motor Basics
Simple Stepper Code
Simple acceleration code.

Miniflyer:
Yes and no. I want the motion controller to pick up absolute aircraft position of an RC jet aircraft, and then output the data via steppers to instruments like compass, g-meter and such.

Do you mean that the sensors are on the aircraft and the stepper motors are on the ground?

If so that seems like an important piece of information.

And if the stepper motors are just moving pointers on instruments, what sort of stepper motors are you using? Are they the tiny ones that are used in car instrument panels?

And if my assumptions are correct it suggests to me that you still have a long way to go to paint the overall picture so we understand the context of your questions.

...R

Crossed transmissions again.....

No, the instruments are the ones in the airplane, not on the ground (pleaaase dont ask what sense it makes to display data in a model that noone is sitting in.....it has no sense, i want it anyways). Attached you will find an artificial horizon which will ultimately be the goal to be operational in 1/6 scale size. But this coordinates 3 axis inputs and will be the last step. Right now i want to learn to adress just 1 motor with 1 input.....

The stepper motors vary in size. The smallest will be a 4mm unit taken from a linear actuator, the biggest ones will be the 10mm types found on ebay. The loads are small, just a pointer or the weight of the small plastic ball rotating and turning....

Sounds complex. Is there a camera in the cockpit to check the roll indicator is truthful?

If it was my model I would only have the instruments working on the ground. Save the power and EMI interference from the flight.

Miniflyer:
Crossed transmissions again.....

No, the instruments are the ones in the airplane, not on the ground (pleaaase dont ask what sense it makes to display data in a model that noone is sitting in.....it has no sense, i want it anyways).

So, now that we finally understand what you are trying to achieve can you explain the problem? How do the instruments behave now, and how do you want them to behave?

...R

PS ... I will refrain from asking how you know they are not working properly, given that you can't see them.

Morgan, Robin,

The instruments are not visible or checkable in flight. They can be demonstrated on the ground, and used to monitor the engine start without having to add the data cable and external display. It is more a neat gimmick than a real requirement.

The reason i dont want step losses: it looks weird if all instruments are mispointing after landing (horizon inverted, heading off etc....).

Behavior now: I can adress pointers with step movement. 500 left, 80 right, 70 left. This both ramped and unramped. They will however finish moving to the last designated position before accepting new data, and then starting a new motion sequence.

Desired behavior: Pointers adressable with absolute step position. EG move to step 1200, move back to step 200, move ahead to step 396.....and so on. Desired step position should be constantly updated during the motion so that initial motion will not be stopped before starting towards target 2, 3, 4 and so on.....

Keep track of where the stepper is and where you want it to be. If those are different, take steps to refuce the difference. The step rate can be limited.by acceleration and decelleration parameters.

MorganS:
Keep track of where the stepper is and where you want it to be. If those are different, take steps to refuce the difference. The step rate can be limited.by acceleration and decelleration parameters.

That sounds appropriate.

Have two different functions. One that sets the value to which a stepper should go. And the other that compares the actual position with the desired position and orders the motor to make a small motion to partially close the gap. It may take several calls to the function to get the two positions aligned if the desired value is stable and starts a long way from the actual position. At the same time this system allows the desired position to be updated at any time.

...R

OK i just sat fot 4 hours looking at tutorials and help files.....what am i doing wrong here?

I took the running non-interrupt code and:

-Introduced the float "absolutePosition"
-Enabled the Serial function for the Nano at 115200
-Attempted to print the value of "absolutePosition" everytime the direction changes.

The code is still running, but i am getting absolutely no data on the serial monitor (it is opened, set to 115200 and on the correct port). What am i doing wrong?

My plan:
Interim Goal 1: Introduce and Monitor the absolute Position
Interim Goal 2: Limit the absolute Position to the steps of 1 full instrument rotation (If value = maxsteps set value 0)
Interim Goal 3: use the absolute position to move the motor.
Interim Goal 4: modify the target absolute position using the serial read function of the monitor

Pretty high goals for someone who cant even get the serial monitor to print stuff....

Here where i am now:

float absolutePosition=0;

#define DIR_PIN          3
#define STEP_PIN         6
#define ENABLE_PIN       8

void setup() {
  Serial.begin(115200);
  pinMode(STEP_PIN,   OUTPUT);
  pinMode(DIR_PIN,    OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);
  }

#define STEPS 1000

void constantAccel() {
  int delays[STEPS];
  float angle = 1;
  float accel = 0.01;
  float c0 = 2000 * sqrt( 2 * angle / accel ) * 0.67703;
  float lastDelay = 0;
  int highSpeed = 500;
  for (int i = 0; i < STEPS; i++) {
    float d = c0;
    if ( i > 0 )
      d = lastDelay - (2 * lastDelay) / (4 * i + 1);
    if ( d < highSpeed )
      d = highSpeed;
    delays[i] = d;
    lastDelay = d;
      }

  // use delays from the array, forward
  for (int i = 0; i < STEPS; i++) {
    digitalWrite(STEP_PIN, HIGH);
    delayMicroseconds( delays[i] );
    digitalWrite(STEP_PIN, LOW);
    
  }

  // use delays from the array, backward
  for (int i = 0; i < STEPS; i++) {
    digitalWrite(STEP_PIN, HIGH);
    delayMicroseconds( delays[STEPS-i-1] );
    digitalWrite(STEP_PIN, LOW);
   }
}

void loop() {

  digitalWrite(DIR_PIN, LOW);
  constantAccel();
  Serial.println(absolutePosition);
  digitalWrite(DIR_PIN, HIGH);
  constantAccel();
  Serial.println(absolutePosition);

  while (true);
}

Addition:

i just wrote this very very basic test code using the same functions and same lines as in the full code, and its working great:

float absolutePosition=0;

void setup() {
  // put your setup code here, to run once:
    Serial.begin(115200);
   
}

void loop() {
  // put your main code here, to run repeatedly:
    absolutePosition ++;
    Serial.println(absolutePosition);
    delay(2000);
}

So what am i doing wrong in printing the position in the full code? Theres no difference....

When the code gets to this line it stops doing anything useful - it's a never ending loop

while (true);

...R

Yes it runs the motor back and forth all the time......always the 400 steps. It should at least on 1 run print something useful? edit removing it changes nothing in its behavior......

Anyways, i have started writing from scratch........a basic accelleration ramp.
So far it works just fine......with 1 flaw: on the codes i have used it is usually marginal to have a motor running on delay(200) without ramp, and at delay(80) with ramp. I have my code below at maxspeed delay(1) and it is rather slow......something is wrong with the units the int is putting out.....it is off by (i guess) factor 1000. Why? Is the Serial.print making it that slow?

int absolutePosition=0;
int delayStart=10;
int delayTopspeed=1;
int rampSteps=9;
int delayRamp=(delayStart-delayTopspeed)/rampSteps;
int delayCurrent=delayStart;

#define DIR_PIN          3
#define STEP_PIN         6
#define ENABLE_PIN       8

void setup() {
  Serial.begin(115200);
  pinMode(STEP_PIN,   OUTPUT);
  pinMode(DIR_PIN,    OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);

  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(DIR_PIN, LOW);
  }

void loop() {
  // put your main code here, to run repeatedly:
    digitalWrite(STEP_PIN, LOW);
    delay(delayCurrent);
    digitalWrite(STEP_PIN, HIGH);
    absolutePosition ++;
      if (delayCurrent > delayTopspeed) {
        delayCurrent=delayCurrent-delayRamp;
      }
    Serial.print(absolutePosition);
    Serial.print(";");
    Serial.print(delayRamp);
    Serial.print(";");
    Serial.println(delayCurrent);
}

Perhaps you meant to use delayMicroseconds()?

According to arduino reference delay(ms) gives the delay in microseconds. 1ms gives 1000 steps per second....60000 steps per minute. At 20 steps per rev it should be spinning at 3000rpm. Its nowhere near that.....however arduino also states that the format should be unsigned long and im using int. Could this be the problem?
Ive shut down the pc for today and moved on to wine :slight_smile:

You've misread millis and micros. Easy to do.

For short delays up to 32767 microseconds, delayMicroseconds(int) will work. But that's asking for trouble if you step over that line just a little bit. Get used to using unsigned long for both delay() and delayMicroseconds().

Great, ill switch over to micro and unsigned tomorrow.

I've also moved away from using the ramps of the cnc code, because it requires a larger lag in the pointers to work. I will use live data and limit motion speed. If the motion speed is not enough i will introduce a ramp up from that speed.
This should make slower movement instantaneous, and fast movement with minimal lag....i hope.