Can I do Stepper Motor Control with PWM?


#1

Hello all,

I’m working on a project for a garden control device which includes a couple stepper motors. As it’s a garden and not a 3D Printer, it doesn’t need super accurate stepper motor control down to the microsecond or individual step resolution.

I’m currently driving the steppers through Polulu - DRV8825 Stepper Motor Drivers which is working well, and I appreciate not having to burn 4 pins per motor. I currently have 2 motors wired up and working with the supplied wiring diagram from Polulu.

Somehow I overlooked the Stepper libraries in the Photon Web IDE and programmed them myself using for() loops and setting the step pin High then Low with a 5ms delay in between. This of course causes the program to be blocking while any stepper is turning which I’d rather not do as several gardening functions run for several RPMs or minutes at a time.

I read through several Stepper threads here, but saw a comment that the Stepper library can be blocking as well. It looks like AccelStepperSpark is not blocking, but is a quite long and involved library, as it should be to cover all options/configurations.

So I’m wondering if I can avoid all the High/Low pin writes and delays by attaching the Polulu’s Step pin to a PWM pin on the Photon and set the Frequency on that pin On and Off at predetermined times to get the number or rotations or minutes of operation that I’m looking for.

For example. With my current 5ms delay between each High or Low write, that’s 10ms per cycle, or 100Hz. So if I want to turn a 200-step Stepper Motor 5 full revolutions, could I do something like the following?

// record start time
unsigned long stepperStart = millis();

// Turn PWM pin on with 50% duty cycle and 100Hz
analogWrite(stepper1Pin, 128, 100);

// Turn PWM pin off after completing 5 full revolutions
// (5 revs * 200steps/rev = 1000 steps / 100Hz = 10 seconds).
unsigned long now = millis();
if ((now - stepperStart) >= 10000) {
	analogWrite(stepper1Pin, 0);
}

#2

You could use PWM but since steppers are open loop systems, whenever your code should happen to block, your steppers will just run-away which is probably not what you want.

However there are non-blocking coding techniques.
You could use Software Timers or timed interrupts (SparkIntervalTimer) or just use a simple non-blocking function that performs one step at each visit and keep calling it from loop() at a regular rate.

BTW, I’d not keep the steppers on all the time, but rather control the /SLEEP pin with a GPIO to depower the steppers once you’re done with the movement.

Just from the top of my head (aka not tested) this would be one way to use a Software Timer

const int pinStep  = D0;
const int pinDir   = D1;
const int pinSleep = D2;

int stepCount = 0;
Timer doSteps(5, oneStep);

void oneStep() {
  digitalWriteFast(pinStep, !pinReadFast(pinStep)); // toggle the pin
  if (--stepCount <= 0) {                   // decrease the number of steps to go, and when exhausted 
    doSteps.stop();                         //   stop the Software Timer from being called again,
    pinResetFast(pinSleep);                 //   send the stepper driver to sleep to release the stepper coils, 
    pinResetFast(pinDir);                   //   preinitialise pins for next turn
    pinResetFast(pinStepper);               //   make sure we start off with a LOW pin next time
    // this is optional --------------------------------------------------------------------------------------------
    pinMode(pinSleep, INPUT);               //   put GPIOs back in Hi-Z mode 
    pinMode(pinDir  , INPUT);
    pinMode(pinStep , INPUT);
    // -------------------------------------------------------------------------------------------------------------
  }
}

int moveStepper(int steps) {
  if (steps == 0) return (stepCount = 0);     // stop current run on next timer callback

  // this is optional if done in setup() ---------------------------------------------------------------------------
  pinMode(pinSleep, OUTPUT);                  // activate GPIOs
  pinMode(pinDir, OUTPUT);     
  pinMode(pinStep, OUTPUT);
  // ---------------------------------------------------------------------------------------------------------------
  digitalWrite(pinSleep, HIGH);               // wake the stepper driver
  digitalWrite(pinDir  , steps < 0);          // set direction according to sign of steps
  stepCount = constrain(abs(steps), 1, 200);  // set number of steps, but limit range to 1..200
  doSteps.start();                            // start async stepping
  return stepCount;
}

#3

Thank you for the input. This looks a lot like a small section of the AccelStepper library. I see why that would be a better way to go than to hope that the PWM didn’t run away with something.


#4

No idea, I have not looked at that library, but the tools used for my solution and the library are obviously the same, so similarities won’t be surprising :wink:


#5

BTW, I was at first disheartened in looking at this suggestion because I didn’t want to use additional pins that could be used for sensors, etc. (though I might be able to put several on an I2C chain?). But in thinking more about it, I believe I can save a couple pins on each stepper by having the SLEEP and DIR pins attached to a single Photon pin (each).

Having a common SLEEP pin on the Photon means that all stepper drivers will sleep or wake together, but I think that’s probably ok.

Having a common DIR pin on the Photon means that I either only turn one stepper at a time, via pulses to its STEP pin, or I can turn multiple steppers at the same time as long as they’re turning in the same direction.

So that means that I can run several (n) steppers and only use n+2 pins on the Photon. The only unique pin for each stepper being its STEP pin.