Optimal way of making timed code

Imagine you have several stepper motors and your drivers are set to a microstep mode, so the delay between pulses will be relatively very small. You also need these motors to move simultaneously.

Which method is the most precise and "conventional"?

  1. delayMicrosteps() between HIGH and LOW writes
  2. micros()
  3. timer interrupts

Also I wonder if there's a way to program PWM pins for that and if it makes any sense at all. Also please mention solutions that I don't know about.

Then you connect all the controller board's step connection to one Arduino pin and all the controller directions to a single Arduino pin and write the code as if for a single stepper motor.

2 Likes

Sorry I cannot answer that as I have no clue as to what you know. A good starting point is the Arduino Cookbook. To help I need to see exactly what you have in front of you. To assist you effectively, please provide the following:
1. Annotated Schematic: Post a detailed schematic of your setup exactly as you have it wired, showing all connections, including power, ground, and power supplies.
2. Technical Information Links: For all hardware devices in your circuit, provide links to technical information. Be aware that many items in the Arduino/Pi world may have the same name but function differently. Links to sales pages like Amazon often lack the necessary technical details, so ensure the links you provide contain the correct specifications and data sheets.
3. Be Thorough: It’s your problem, and the more precise and detailed information you provide, the faster we can help you troubleshoot. This saves everyone time and helps us give you the best possible assistance.

2 Likes

A few questions for you:

  • Why are you doing this project ?

  • What experience do you have in hardware and in writing sketches for the Arduino ?

  • Show us a preliminary schematic for this project.

I generally use interrupts.

Use a library like MobaTools which is interrupt driven and does it all for you in the background.

and presumably each motor may need to step with a different amount of delay and that you want them all to reach that last position simultaneously

so for a particular movement, all motors in motion to move from one x,y,z position to another, you'll need to compute the delay/step for each motor

this delay also determines the speed of movement and needs to be accounted for

simplest (sub-optimal) is to have a delay loop for each motor within which that motor is stepped in the appropriate direction

several layers of code:

  • the highest is moving between a set of coordinates.
  • The next determines the rate/delay and
  • the lowest effects the motion, returning when the final position is reached by all motors.

There may be separate delays between motions at the highest level

  • Hardware Timer generated pulses without interrupt

Note that using an interrupts to generate the pulses is very expensive in terms of processor load. Since the enter and leave to/out the interrupt takes a significant amount of time, interrupts are never used to generate high frequency pulses.

Yes, PWM timers are often used for this, and in many cases this is the optimal method.

1 Like

how would you time between and limit the # of a fixed # of pulse?

It is, but it tends to be the best way when you have to consider multiple motors in motion simultaneously, acceleration ramps, motors running at different speeds, checking for home/end stops, etc.

There are some methods for this on good controllers: for example, on STM32 you can control PWM output of the slave timer with another (master) timer, so you can switching output on and off with precise intervals.
On different controllers PWM allowed you to generate a PWM pulse train with defined # of pulses (WinnerMicro W80x serie).

How simultaneous?

All move the same number of steps at the same time?

or

Some motors move faster than others, but they all move in coordinated motion, like a CNC?

If it is the second, or even the first, the CNC folks use timer interrupts to schedule the steps of the fastest motion, and move the slower motors in in strict proportion to the fastest motion using the Bresenham algorithm.

2 Likes

isn't it pretty common for motors on a robotic arm, for example, to have to move in unison, but moving a different # of steps and all reaching their endpoints at the same time? you just need the appropriate delay between each step on each motor.

Yes, it is very common. It's the "just need the appropriate delay" part that can become complicated as you try to handle acceleration, or smoothly chaining successive motions together.

If all the motors are in lock-step, you can shortcut the coordination by treating them as one and wiring them all together, or writing a full register (on an Uno, for example.) If the ratios of steps are different and you do the ratios with delays, you can run into issues with precision, rounding and scheduling the interlocking delays. At its root, moving steppers in fixed ratios is an integer problem, and it can be done quickly and precisely in integer math with the Bresenham algorithm. And it also helps separate the acceleration problem from the coordination problem.

1 Like

i think a lot of high robotics students are handling this just fine. I hadn't thought about high speed situations.

does it need to be considered here?

Who knows?

How fast does it need to step? How accurate does the path need to be? Does it have a path? Is it a Cartesian robot?

I have Arduino Mega and 5 stepper motors for axes of small articulated robot arm. Trying to figure out the best approach for the control of the motors, because I would need to have acceleration/deceleration and simultaneous motor movement from point to point at different speeds.

look this over


char s [190];

// -----------------------------------------------------------------------------
struct Motor {
    const byte    PinStep;
    const byte    PinDir;
    const char   *desc;

    int           dir;
    int           pos;
    int           posTarg;

    unsigned long msecStep;
    unsigned long msec;
} motor [] = {
    { 10, 7, "A" },
    { 11, 8, "B" },
    { 12, 9, "C" },
};

const int Nmotor = sizeof(motor)/sizeof(Motor);

enum { For = 1, Rev = -1 };

// -------------------------------------
struct Motion {
    int             pos [Nmotor];
    unsigned long   msec;
} motion [] = {
    {{  10,  20, 30 },  800 },
    {{  15,  15, 32 },  300 },
    {{   5,  35, 27 },  700 },
};

int Nmotion = sizeof(motion)/sizeof(Motion);
int idx = 0;

// -----------------------------------------------------------------------------
void
setDir (
    int   mtr,
    int   dir )
{
    digitalWrite (motor [mtr].PinDir, 0 < dir ? HIGH : LOW);
}

// -------------------------------------
void
step (
    int   mtr )
{
#if 0
    Serial.print ("    step: "); Serial.println (motor [mtr].desc);
#endif

    digitalWrite (motor [mtr].PinStep, HIGH);
    delayMicroseconds (5);
    digitalWrite (motor [mtr].PinStep, LOW);

    motor [mtr].pos += motor [mtr].dir;
}

// -----------------------------------------------------------------------------
unsigned long msec;
boolean done;

// step as needed
void
stepChk ()
{
    for (int m = 0; m < Nmotor; m++)  {
#if 0
        sprintf (s, " stepChk: %d, targ %6d, pos %6d"
                    ", msec %8ld, mtr msec %8ld, msecStep %8ld",
                    m, motor [m].posTarg, motor [m].pos,
                    msec, motor [m].msec, motor [m].msecStep);
        Serial.println (s);
#endif

        done = true;
        if (motor [m].pos != motor [m].posTarg)  {
            done = false;

            if (msec - motor [m].msec >= motor [m].msecStep)  {
                motor [m].msec += motor [m].msecStep;
                step (m);
            }
        }
    }
}

// -----------------------------------------------------------------------------

void
loop ()
{
    msec = millis ();

    stepChk ();

    // update targets after each motion
    if (done)  {
        if (Nmotion > idx)  {
#if 0
            sprintf (s, "   loop: update idx %d", idx);
            Serial.println (s);
#endif

            for (int m = 0; m < Nmotor; m++)  {
                motor [m].posTarg  = motion [idx].pos [m];
                int nSteps         = abs(motor [m].posTarg - motor [idx].pos);
                motor [m].msecStep = motion [idx].msec / nSteps;

                motor [m].msec     = msec;
                motor [m].dir      = motor [m].posTarg > motor [idx].pos
                                                            ? For : Rev;

                sprintf (s, "  update: idx %d m %d", idx, m);
                Serial.print   (s);

                sprintf(s,", mot pos %6d, mtr pos %6d",
                                    motion [idx].pos [m], motor [m].pos);
                Serial.print   (s);

                sprintf(s,", mot msec %4ld, nSteps %3d, msecStep %3ld",
                        motor [m].msec, nSteps, motor [m].msecStep);
                Serial.println (s);

                setDir (m, motor [m].dir);
                step (m);
            }

            idx++;
        }
    }
}

// -------------------------------------
void setup ()
{
    Serial.begin (1000000);

    for (int m = 0; m < Nmotor; m++)  {
        pinMode        (motor [m].PinDir,  OUTPUT);
        pinMode        (motor [m].PinStep, OUTPUT);
        digitalWrite   (motor [m].PinStep, LOW);
     // Serial.println (motor [m].desc);
    }
}
1 Like

5 axis coordination with acceleration/deceleration is an interesting problem. Maybe consider some of the popular platforms that do this, like the hardware inside a 3D printer. For example:

Since a robot arm has complicated kinematics, and there aren't cheap ways to do the full inverse kinematics at the microstep resolution, one way way folks solve the problem is by breaking complex motion curves into straight-line point-to-point chunks and passing the chunks to an CNC that is optimized to translate the points-to-point path into to smooth acceleration-aware microstep motion control.

1 Like

There's a 5-axis version of grbl: GitHub - fra589/grbl-Mega-5X: 5/6 Axis version of Grbl, the open source, embedded, high performance g-code-parser and CNC milling controller written in optimized C that will run on an Arduino Mega2560

2 Likes