Controlling multiple stepper motors


#1

Hello all,

I am currently involved in a project to make a 14x14 grid of suspended spheres attached to stepper motors so that the sphere can be moved to form the outline of shapes.

The project will utilise one Arduino mega per line as these can control upto 15 steppers, and I have been tasked with developing a way to control a line of 14 stepper motors so that they are individually addressable and can move in sequence so that ball at the end of strings attached to stepper motor can be set to create an outline.

My initial thoughts were to create an array on each Arduino listing each stepper in the line, is this the most efficient way of addressing each stepper? As for the final resting position of each sphere I presume I would have to convert the z position into steps, which with my current understanding would mean I would have to have custom code for each Arduino controlling their line.

Am I wildly off on my assumptions? Could you give me some advice on were to start looking for the information I require? I must confess the most I have done with Arduinos in the past is controlling servos for animation parts of dioramas.

Any help would be most appreciated.


#2

Wow! That’s a heck of a project.

It depends on the steppers, but I’d probably buy myself a whooole lot of stripboard, 150 ATMega328p ICs, and 150 ULN2803 darlington drivers.

I’d make 98 identical boards, each designed to drive 2 stepper motors.

Each ATMega328p would be configured as follows:

  • 8 IO pins tied directly to the ULN2803A darlington driver
  • 2 IO pins reserved for Data and Clock input
  • 2 IO pins reserved for Data and Clock output
  • 1 IO pin reserved for Latch
  • 1 IO pin reserved for Reset
  • 1 IO pin reserved for an end stop sensor (optional)
  • two Unipolar Steppers connected to +12(or there abouts) and to each ULN2308A
  • an LDO to supply 5v for the ATMega

In order to address all 98 ATMega328p boards, I’d simply clock between 196 and 392 bytes (a trivial amount of data, less than it takes to drive a chain of 98 APA102 LED pixels) of data into the Data pin of the first ATMega328p, this would fill its internal memory and then it would clock out to the next one, and the next one, and the next, etc.

This data would represent the step amount I want each of the 196 steppers to move.

Once it’s all clocked out, I’d assert the Latch pin to simultaneously tell every single chip to start stepping by the prescribed amount.

I’d use a Pi Zero to control the whole array. (Incidentally this is the same way I’d program all the chips)

Whether or not this madness would actually work- I don’t know- but it’s certainly the approach I would take.

Both the listed parts- assuming a ULN2803 is sufficient for driving the steppers you chose- have a price break at 100 units so we’re talking about £200 in parts for these alone. Although I’d buy 150 each for spares, putting you at more like £300.

I don’t know what sort of price you’d get, but 14 Arduino Megas seems to be somewhere in the region of £400-£500

Take my advice with a pinch of salt, since I’m speaking only in terms with which I’m familiar. I don’t know where you’d begin to research how to do this sort of thing properly but the EEVBlog forum might be a good place to ask: http://www.eevblog.com/forum/


#3

Thanks for the reply, I had thought that this project had gone away, though currently there is rumblings about getting funding for the items, I do not have much control over the parts which are going to be used, though I do know that it will be Arduino Mega, stepper motor and pololu a4988 drivers. Currently I have been able to get five motors (as these are what I had to hand) to do a soft stop/start as requested to reduce wobble.

#define DIR_PIN1          2
#define STEP_PIN1         3
#define ENABLE_PIN1       4
#define DIR_PIN2          5
#define STEP_PIN2         6
#define ENABLE_PIN2       7
#define DIR_PIN3          8
#define STEP_PIN3         9
#define ENABLE_PIN3       10
#define DIR_PIN4          11
#define STEP_PIN4         12
#define ENABLE_PIN4       13
#define DIR_PIN5          14
#define STEP_PIN5         15
#define ENABLE_PIN5       16

void setup() {
  pinMode(STEP_PIN1,   OUTPUT);
  pinMode(DIR_PIN1,    OUTPUT);
  pinMode(ENABLE_PIN1, OUTPUT);
  pinMode(STEP_PIN2,   OUTPUT);
  pinMode(DIR_PIN2,    OUTPUT);
  pinMode(ENABLE_PIN2, OUTPUT);
  pinMode(STEP_PIN3,   OUTPUT);
  pinMode(DIR_PIN3,    OUTPUT);
  pinMode(ENABLE_PIN3, OUTPUT);
  pinMode(STEP_PIN4,   OUTPUT);
  pinMode(DIR_PIN4,    OUTPUT);
  pinMode(ENABLE_PIN4, OUTPUT);
  pinMode(STEP_PIN5,   OUTPUT);
  pinMode(DIR_PIN5,    OUTPUT);
  pinMode(ENABLE_PIN5, OUTPUT);
}

#define STEPS 3000

void constantAccel() {
  int delays[STEPS];
  float angle = 6;
  float accel = 0.05;
  float c0 = 5000 * sqrt( 2 * angle / accel ) * 0.9000;
  float lastDelay = 0;
  int highSpeed = 100000;
  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_PIN1, HIGH);
    delayMicroseconds( delays[i] );
    digitalWrite(STEP_PIN1, LOW);
  }

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

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

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

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

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

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

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

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

}
void loop() {

  digitalWrite(DIR_PIN1, LOW);
  constantAccel();
  digitalWrite(DIR_PIN1, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN2, LOW);
  constantAccel();
  digitalWrite(DIR_PIN2, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN3, LOW);
  constantAccel();
  digitalWrite(DIR_PIN3, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN4, LOW);
  constantAccel();
  digitalWrite(DIR_PIN4, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN5, LOW);
  constantAccel();
  digitalWrite(DIR_PIN5, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN4, LOW);
  constantAccel();
  digitalWrite(DIR_PIN4, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN3, LOW);
  constantAccel();
  digitalWrite(DIR_PIN3, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN2, LOW);
  constantAccel();
  digitalWrite(DIR_PIN2, HIGH);
  constantAccel();
  digitalWrite(DIR_PIN1, LOW);
  constantAccel();
  digitalWrite(DIR_PIN1, HIGH);
  constantAccel();

  while (true);
}

Though I’m quite new to Arduino coding, so please let me know if I can tidy up this code? Also trying to work out how to add further control to the motors so I can set it that if motor 1 does x steps then motor 3 does x+y steps.


#4

If your acceleration delay table is never going to change, it would be considerably more efficient to document how it’s produced and simply pre-calculate it. Running this many floating point operations on the ATmega every time you call constantAccel will significantly skew your timings.

Running your constantAccel- which produces delays for 3000 steps- results in 27 delay steps followed by 2973 thrown away calculations.

# Step Delay
00: 69713
01: 41828
02: 32533
03: 27527
04: 24289
05: 21976
06: 20218
07: 18823
08: 17682
09: 16727
10: 15911
11: 15203
12: 14583
13: 14033
14: 13540
15: 13096
16: 12693
17: 12325
18: 11988
19: 11676
20: 11388
21: 11120
22: 10870
23: 10636
24: 10417
25: 10211
26: 10016
28: 10000
29: 10000
30: ... continues

I did a quick-and-dirty conversion to Python to get this table:

import math

delays = []
angle = 6
accel = 0.05
c0 = 5000 * math.sqrt(2 * angle / accel) * 0.9
lastDelay = 0
highSpeed = 10000
for i in range(3000):
    d = c0
    if i > 0:
        d = lastDelay - (2 * lastDelay) / (4 * i + 1)
    if d < highSpeed:
        d = highSpeed
    delays.append(d)
    lastDelay = d
    if d != highSpeed:
        print("{:02d}: {}".format(i, int(d)))

If you assume your 10,000 highSpeed is a constant, you could subtract this value from each delay step and store these 27 values as a uint16t (54byte) lookup table which could easily just sit in the ATmega’s RAM.

Alternatively you could lean on the AccelStepper library to do all the heavy lifting for you: http://www.airspayce.com/mikem/arduino/AccelStepper/


#5

Cheers Phil,

This is really helpful. I’m going to have to look further in to

As currently, this goes right over my head. :)


#6

There’s a few things to watch with embedded programming for devices like Arduino. One of them is that size matters. In some cases quite considerably.

Since your delays[STEPS] array is an array of type int you might run into trouble- see: https://www.arduino.cc/reference/en/language/variables/data-types/int/

In the world of Arduino (on ATmega based boards anyway), int is shorthand for int16t which can store numbers in the range -32,768 to 32,767. Since your code generates values like 69712 this will “overflow” the 32,767 range, most likely wrapping around to a negative number. This is bad since your timings will be off!

Size isn’t just about variable types, either. You have to be careful about how you use things like delayMicroseconds() since they have non-obvious (to someone who doesn’t speak fluent embedded C) caveats. See: https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/

In the case of delayMicroseconds() any value over 16383 (about a third of your timings) will apparently not produce an accurate delay. You have to dig pretty deep to find out why this is the case- but on a 16Mhz AVR part the delay is created by running a loop that takes 4 cycles, or 1/4th of a microsecond so to delay for 16383 microseconds it actually has to loop for 65532 cycles. This is coincidentally close to the limit of a 16bit number (65535). The looping code uses an instruction that operates upon a single word (16bits), so anything >65535 cycles will be capped or, worse, overflow and loop to a smaller delay.

For a large delay you would have to break it down, like so:

delay(69);
delayMicroseconds(713);

Although in practise for acceleration/deceleration of a stepper motor it’s unlikely the microsecond portion of the delay would even be perceptible so you might as well round everything to the nearest millisecond.

If you did this, then each of your delays could be stored in a simple uint8_t, or a single byte each. Halving your memory requirement to 27 bytes.

It’s intricacies like this that make embedded programming fun :D and why it’s easier to use a library that takes care of the implementation details and gotchas for you.

If this all whooshes right over your head- don’t worry! It did the same for me not long ago.


#7

I cant help u on this .I am presenting my issue to both of you .Plz do help on my project

i am not getting that how i can get info about interfacing of my driver board with arduino mega.This board is using STK672-080 driver.4 Motors are being used and are unipolar.This driver board has 4 inputs
1-CLK
2-DIR
3-ENA
4-GND
signal from arduino will be received at these inputs.same driver and arduino has been used to drive single motor using simple stepper motor library.
Now i want to use <AccelStepper.h> library for driving 4 seperate motors with separate similar driver card for each motor.I think i have option of using <AccelStepper.h> library only.
one simple code of stepper with this multi stepper library is given below

// MultiStepper.pde
// -- mode: C++ --
//
// Shows how to multiple simultaneous steppers
// Runs one stepper forwards and backwards, accelerating and decelerating
// at the limits. Runs other steppers at the same time
//
// Copyright © 2009 Mike McCauley
// Id: HRFMessage.h,v 1.1 2009/08/15 05:32:58 mikem Exp mikem

#include <AccelStepper.h>

// Define some steppers and the pins the will use
AccelStepper stepper1; // Defaults to 4 pins on 2, 3, 4, 5
AccelStepper stepper2(4, 6, 7, 8, 9);
AccelStepper stepper3(2, 10, 11);

void setup()
{
stepper1.setMaxSpeed(200.0);
stepper1.setAcceleration(100.0);
stepper1.moveTo(24);

stepper2.setMaxSpeed(300.0);
stepper2.setAcceleration(100.0);
stepper2.moveTo(1000000);

stepper3.setMaxSpeed(300.0);
stepper3.setAcceleration(100.0);
stepper3.moveTo(1000000);
}

void loop()
{
// Change direction at the limits
if (stepper1.distanceToGo() == 0)
stepper1.moveTo(-stepper1.currentPosition());
stepper1.run();
stepper2.run();
stepper3.run();
}

the point is which pins of arduino i have to attach with my driver input pins named as below
1-CLK
2-DIR
3-ENA
4-GND

Thnx in advance for responce