diff --git a/README.md b/README.md index 88b1059..19684c9 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This is suggested wiring for running the examples unmodified. All the pins below - 100uF capacitor between GND - VMOT - Make sure to set the max current on the driver board to the motor limit (see below). - Have a motor power supply that can deliver that current. -- Make sure the motor power supply is within the range supported by the driver board. +- Make sure the motor power supply voltage is within the range supported by the driver board. Set Max Current =============== @@ -86,8 +86,14 @@ stepper motor like the seconds hand of a watch: #include "A4988.h" // using a 200-step motor (most common) -// pins used are DIR, STEP, MS1, MS2, MS3 in that order -A4988 stepper(200, 8, 9, 10, 11, 12); +#define MOTOR_STEPS 200 +// configure the pins connected +#define DIR 8 +#define STEP 9 +#define MS1 10 +#define MS2 11 +#define MS3 12 +A4988 stepper(MOTOR_STEPS, DIR, STEP, MS1, MS2, MS3); void setup() { // Set target motor RPM to 1RPM and microstepping to 1 (full step mode) diff --git a/examples/AccelTest/AccelTest.ino b/examples/AccelTest/AccelTest.ino index ed3728c..3019411 100644 --- a/examples/AccelTest/AccelTest.ino +++ b/examples/AccelTest/AccelTest.ino @@ -1,5 +1,5 @@ /* - * Using accelerated motion ("linear speed") + * Using accelerated motion ("linear speed") in nonblocking mode * * Copyright (C)2015-2017 Laurentiu Badea * @@ -10,7 +10,12 @@ // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step #define MOTOR_STEPS 200 +// Target RPM for cruise speed #define RPM 120 +// Acceleration and deceleration values are always in FULL steps / s^2 +#define MOTOR_ACCEL 2000 +#define MOTOR_DECEL 1000 + // Microstepping mode. If you hardwired it to save pins, set to the same value here. #define MICROSTEPS 16 @@ -44,7 +49,7 @@ DRV8834 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1); // #define M1 11 // #define TRQ0 6 // #define TRQ1 7 -// DRV8880 stepper(MOTORS_STEPS, DIR, STEP, ENABLE, M0, M1, TRQ0, TRQ1); +// DRV8880 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1, TRQ0, TRQ1); // #include "BasicStepperDriver.h" // generic // BasicStepperDriver stepper(DIR, STEP); @@ -58,13 +63,17 @@ void setup() { // stepper.setCurrent(100); /* - * LINEAR_SPEED profile needs the acceleration and deceleration values - * in full steps / s^2. + * Set LINEAR_SPEED (accelerated) profile. */ - stepper.setSpeedProfile(stepper.LINEAR_SPEED, 2000, 1000); + stepper.setSpeedProfile(stepper.LINEAR_SPEED, MOTOR_ACCEL, MOTOR_DECEL); Serial.println("START"); - stepper.startRotate(360); + /* + * Using non-blocking mode to print out the step intervals. + * We could have just as easily replace everything below this line with + * stepper.rotate(360); + */ + stepper.startRotate(360); } void loop() { diff --git a/examples/BasicStepperDriver/BasicStepperDriver.ino b/examples/BasicStepperDriver/BasicStepperDriver.ino index f2d642f..fef69e1 100644 --- a/examples/BasicStepperDriver/BasicStepperDriver.ino +++ b/examples/BasicStepperDriver/BasicStepperDriver.ino @@ -49,7 +49,7 @@ void loop() { /* * Moving motor to original position using steps */ - stepper.move(-200*MICROSTEPS); + stepper.move(-MOTOR_STEPS*MICROSTEPS); // pause and allow the motor to be moved by hand // stepper.disable(); diff --git a/examples/ClockStepper/ClockStepper.ino b/examples/ClockStepper/ClockStepper.ino index 54d88f4..a3aae91 100644 --- a/examples/ClockStepper/ClockStepper.ino +++ b/examples/ClockStepper/ClockStepper.ino @@ -46,7 +46,7 @@ DRV8834 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1); // #define M1 11 // #define TRQ0 6 // #define TRQ1 7 -// DRV8880 stepper(MOTORS_STEPS, DIR, STEP, ENABLE, M0, M1, TRQ0, TRQ1); +// DRV8880 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1, TRQ0, TRQ1); // #include "BasicStepperDriver.h" // generic // BasicStepperDriver stepper(DIR, STEP); diff --git a/examples/MicroStepping/MicroStepping.ino b/examples/MicroStepping/MicroStepping.ino index caf3efb..765a051 100644 --- a/examples/MicroStepping/MicroStepping.ino +++ b/examples/MicroStepping/MicroStepping.ino @@ -1,7 +1,7 @@ /* * Microstepping demo * - * This requires that M0, M1 be connected in addition to STEP,DIR + * This requires that microstep control pins be connected in addition to STEP,DIR * * Copyright (C)2015 Laurentiu Badea * @@ -44,7 +44,7 @@ DRV8834 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1); // #define M1 11 // #define TRQ0 6 // #define TRQ1 7 -// DRV8880 stepper(MOTORS_STEPS, DIR, STEP, ENABLE, M0, M1, TRQ0, TRQ1); +// DRV8880 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1, TRQ0, TRQ1); // #include "BasicStepperDriver.h" // generic // BasicStepperDriver stepper(DIR, STEP); @@ -52,12 +52,12 @@ DRV8834 stepper(MOTOR_STEPS, DIR, STEP, ENABLE, M0, M1); void setup() { /* * Set target motor RPM. - * Too high will result in a high pitched whine and the motor does not move. */ stepper.begin(RPM); stepper.enable(); - // set current level (for DRV8880 only). Valid percent values are 25, 50, 75 or 100. + // set current level (for DRV8880 only). + // Valid percent values are 25, 50, 75 or 100. // stepper.setCurrent(100); } @@ -65,34 +65,35 @@ void loop() { delay(1000); /* - * Moving motor at full speed is simple: + * Moving motor in full step mode is simple: */ - stepper.setMicrostep(1); // make sure we are in full speed mode + stepper.setMicrostep(1); // Set microstep mode to 1:1 - // these two are equivalent: 180 degrees is 100 steps in full speed mode - stepper.move(100); - stepper.rotate(180); + // One complete revolution is 360° + stepper.rotate(360); // forward revolution + stepper.rotate(-360); // reverse revolution - // one full reverse rotation - stepper.move(-100); - stepper.rotate(-180); + // One complete revolution is also MOTOR_STEPS steps in full step mode + stepper.move(MOTOR_STEPS); // forward revolution + stepper.move(-MOTOR_STEPS); // reverse revolution /* - * Microstepping mode: 1,2,4,8,16 or 32(DRV8834 only) + * Microstepping mode: 1, 2, 4, 8, 16 or 32 (where supported by driver) * Mode 1 is full speed. * Mode 32 is 32 microsteps per step. - * The motor should rotate just as fast (set RPM), - * but movement precision is increased. + * The motor should rotate just as fast (at the set RPM), + * but movement precision is increased, which may become visually apparent at lower RPMs. */ - stepper.setMicrostep(8); + stepper.setMicrostep(8); // Set microstep mode to 1:8 - // 180 degrees now takes 100 * 8 microsteps - stepper.move(100*8); - stepper.rotate(180); - - // as you can see, using degrees is easier - stepper.move(-100*8); - stepper.rotate(-180); + // In 1:8 microstepping mode, one revolution takes 8 times as many microsteps + stepper.move(8 * MOTOR_STEPS); // forward revolution + stepper.move(-8 * MOTOR_STEPS); // reverse revolution + + // One complete revolution is still 360° regardless of microstepping mode + // rotate() is easier to use than move() when no need to land on precise microstep position + stepper.rotate(360); + stepper.rotate(-360); delay(5000); } diff --git a/examples/MultiAxis/MultiAxis.ino b/examples/MultiAxis/MultiAxis.ino index c2da182..b7864d4 100644 --- a/examples/MultiAxis/MultiAxis.ino +++ b/examples/MultiAxis/MultiAxis.ino @@ -11,10 +11,15 @@ */ #include #include "BasicStepperDriver.h" +#include "MultiDriver.h" #include "SyncDriver.h" // Motor steps per revolution. Most steppers are 200 steps or 1.8 degrees/step #define MOTOR_STEPS 200 +// Target RPM for X axis motor +#define MOTOR_X_RPM 30 +// Target RPM for Y axis motor +#define MOTOR_Y_RPM 90 // X motor #define DIR_X 8 @@ -33,14 +38,19 @@ BasicStepperDriver stepperX(MOTOR_STEPS, DIR_X, STEP_X); BasicStepperDriver stepperY(MOTOR_STEPS, DIR_Y, STEP_Y); +// Pick one of the two controllers below +// each motor moves independently, trajectory is a hockey stick +// MultiDriver controller(stepperX, stepperY); +// OR +// synchronized move, trajectory is a straight line SyncDriver controller(stepperX, stepperY); void setup() { /* * Set target motors RPM. */ - stepperX.begin(30, MICROSTEPS); - stepperY.begin(90, MICROSTEPS); + stepperX.begin(MOTOR_X_RPM, MICROSTEPS); + stepperY.begin(MOTOR_Y_RPM, MICROSTEPS); } void loop() { diff --git a/examples/NonBlocking/NonBlocking.ino b/examples/NonBlocking/NonBlocking.ino index a084b81..312cd88 100644 --- a/examples/NonBlocking/NonBlocking.ino +++ b/examples/NonBlocking/NonBlocking.ino @@ -67,8 +67,9 @@ void setup() { Serial.println("START"); // set the motor to move continuously for a reasonable time to hit the stopper - stepper.startMove(20000); // 20000 full steps = 100 full rotations - // stepper.startRotate(36000); // 36000 degrees = 100 rotations + // let's say 100 complete revolutions (arbitrary number) + stepper.startMove(100 * MOTOR_STEPS * MICROSTEPS); // in microsteps + // stepper.startRotate(100 * 360); // or in degrees } void loop() { diff --git a/library.properties b/library.properties index 1aa8493..edc009e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=StepperDriver -version=1.1.2 +version=1.1.3 author=Laurentiu Badea maintainer=Laurentiu Badea sentence=A4988, DRV8825 and generic two-pin stepper motor driver library. diff --git a/src/BasicStepperDriver.cpp b/src/BasicStepperDriver.cpp index cbba092..7b92714 100644 --- a/src/BasicStepperDriver.cpp +++ b/src/BasicStepperDriver.cpp @@ -75,9 +75,12 @@ short BasicStepperDriver::setMicrostep(short microsteps){ * accel and decel are given in [full steps/s^2] */ void BasicStepperDriver::setSpeedProfile(Mode mode, short accel, short decel){ - this->mode = mode; - this->accel = accel; - this->decel = decel; + profile.mode = mode; + profile.accel = accel; + profile.decel = decel; +} +void BasicStepperDriver::setSpeedProfile(struct Profile profile){ + this->profile = profile; } /* @@ -113,23 +116,25 @@ void BasicStepperDriver::startMove(long steps){ } else { // set up new move dir_state = (steps >= 0) ? HIGH : LOW; + last_action_end = 0; steps_remaining = abs(steps); step_count = 0; - switch (mode){ + rest = 0; + switch (profile.mode){ case LINEAR_SPEED: // speed is in [steps/s] speed = rpm * motor_steps / 60; // how many steps from 0 to target rpm - steps_to_cruise = speed * speed * microsteps / (2 * accel); + steps_to_cruise = speed * speed * microsteps / (2 * profile.accel); // how many steps are needed from target rpm to a full stop - steps_to_brake = steps_to_cruise * accel / decel; + steps_to_brake = steps_to_cruise * profile.accel / profile.decel; if (steps_remaining < steps_to_cruise + steps_to_brake){ // cannot reach max speed, will need to brake early - steps_to_cruise = steps_remaining * decel / (accel + decel); + steps_to_cruise = steps_remaining * profile.decel / (profile.accel + profile.decel); steps_to_brake = steps_remaining - steps_to_cruise; } // Initial pulse (c0) including error correction factor 0.676 [us] - step_pulse = (1e+6)*0.676*sqrt(2.0f/(accel*microsteps)); + step_pulse = (1e+6)*0.676*sqrt(2.0f/(profile.accel*microsteps)); break; case CONSTANT_SPEED: @@ -172,7 +177,7 @@ void BasicStepperDriver::startBrake(void){ break; case ACCELERATING: - steps_remaining = step_count * accel / decel; + steps_remaining = step_count * profile.accel / profile.decel; break; default: @@ -190,12 +195,12 @@ void BasicStepperDriver::stop(void){ */ long BasicStepperDriver::getTimeForMove(long steps){ long t; - switch (mode){ + switch (profile.mode){ case LINEAR_SPEED: startMove(steps); - t = sqrt(2 * steps_to_cruise / accel) + + t = sqrt(2 * steps_to_cruise / profile.accel) + (steps_remaining - steps_to_cruise - steps_to_brake) * STEP_PULSE(rpm, motor_steps, microsteps) + - sqrt(2 * steps_to_brake / decel); + sqrt(2 * steps_to_brake / profile.decel); break; case CONSTANT_SPEED: default: @@ -223,9 +228,6 @@ void BasicStepperDriver::startRotate(double deg){ * calculate the interval til the next pulse */ void BasicStepperDriver::calcStepPulse(void){ - // remainder to be fed into successive steps to increase accuracy (Atmel DOC8017) - static long rest; - if (steps_remaining <= 0){ // this should not happen, but avoids strange calculations return; } @@ -233,12 +235,9 @@ void BasicStepperDriver::calcStepPulse(void){ steps_remaining--; step_count++; - if (mode == LINEAR_SPEED){ + if (profile.mode == LINEAR_SPEED){ switch (getCurrentState()){ case ACCELERATING: - if (step_count == 1){ // first step, initialize rest - rest = 0; - } step_pulse = step_pulse - (2*step_pulse+rest)/(4*step_count+1); rest = (step_count < steps_to_cruise) ? (2*step_pulse+rest) % (4*step_count+1) : 0; break; @@ -258,10 +257,8 @@ void BasicStepperDriver::calcStepPulse(void){ * Toggle step and return time until next change is needed (micros) */ long BasicStepperDriver::nextAction(void){ - static unsigned long next_action_time = 0; - long next_action_interval = 0; if (steps_remaining > 0){ - microWaitUntil(next_action_time); + delayMicros(next_action_interval, last_action_end); /* * DIR pin is sampled on rising STEP edge, so it is set first */ @@ -273,19 +270,21 @@ long BasicStepperDriver::nextAction(void){ m = micros() - m; // We should pull HIGH for 1-2us (step_high_min) if (m < step_high_min){ // fast MCPU or CONSTANT_SPEED - DELAY_MICROS(step_high_min-m); + delayMicros(step_high_min-m); m = step_high_min; }; digitalWrite(step_pin, LOW); - // account for calcStepPulse() execution time - next_action_interval = pulse - m; + // account for calcStepPulse() execution time; sets ceiling for max rpm on slower MCUs + last_action_end = micros(); + next_action_interval = (pulse > m) ? pulse - m : 1; } else { // end of move + last_action_end = 0; next_action_interval = 0; } - next_action_time = micros() + next_action_interval; return next_action_interval; } + enum BasicStepperDriver::State BasicStepperDriver::getCurrentState(void){ enum State state; if (steps_remaining <= 0){ diff --git a/src/BasicStepperDriver.h b/src/BasicStepperDriver.h index f38a8c8..4d916aa 100644 --- a/src/BasicStepperDriver.h +++ b/src/BasicStepperDriver.h @@ -22,14 +22,7 @@ #define STEP_PULSE(steps, microsteps, rpm) (60*1000000L/steps/microsteps/rpm) // don't call yield if we have a wait shorter than this -#define MIN_YIELD_MICROS 25 -inline void microWaitUntil(unsigned long target_micros){ - if (target_micros - micros() > MIN_YIELD_MICROS){ - yield(); - } - while (micros() < target_micros); -} -#define DELAY_MICROS(us) microWaitUntil(micros() + us) +#define MIN_YIELD_MICROS 50 /* * Basic Stepper Driver class. @@ -39,14 +32,35 @@ class BasicStepperDriver { public: enum Mode {CONSTANT_SPEED, LINEAR_SPEED}; enum State {STOPPED, ACCELERATING, CRUISING, DECELERATING}; - + struct Profile { + Mode mode = CONSTANT_SPEED; + short accel = 1000; // acceleration [steps/s^2] + short decel = 1000; // deceleration [steps/s^2] + }; + static inline void delayMicros(unsigned long delay_us, unsigned long start_us = 0){ + if (delay_us){ + if (!start_us){ + start_us = micros(); + } + if (delay_us > MIN_YIELD_MICROS){ + yield(); + } + // See https://www.gammon.com.au/millis + while (micros() - start_us < delay_us); + } + } + +private: + // calculation remainder to be fed into successive steps to increase accuracy (Atmel DOC8017) + long rest; + unsigned long last_action_end = 0; + unsigned long next_action_interval = 0; + protected: /* * Motor Configuration */ short motor_steps; // motor steps per revolution (usually 200) - short accel = 1000; // maximum acceleration [steps/s^2] - short decel = 1000; // maximum deceleration [steps/s^2] /* * Driver Configuration @@ -70,7 +84,8 @@ class BasicStepperDriver { /* * Movement state */ - Mode mode = CONSTANT_SPEED; + struct Profile profile; + long step_count; // current position long steps_remaining; // to complete the current move (absolute value) long steps_to_cruise; // steps to reach cruising (max) rpm @@ -104,6 +119,12 @@ class BasicStepperDriver { * Returns new level or previous level if value out of range */ virtual short setMicrostep(short microsteps); + short getMicrostep(void){ + return microsteps; + } + short getSteps(void){ + return motor_steps; + } /* * Set target motor RPM (1-200 is a reasonable range) */ @@ -119,6 +140,16 @@ class BasicStepperDriver { * accel and decel are given in [full steps/s^2] */ void setSpeedProfile(Mode mode, short accel=1000, short decel=1000); + void setSpeedProfile(struct Profile profile); + struct Profile getSpeedProfile(void){ + return profile; + } + short getAcceleration(void){ + return profile.accel; + } + short getDeceleration(void){ + return profile.decel; + } /* * Move the motor a given number of steps. * positive to move forward, negative to reverse diff --git a/src/MultiDriver.cpp b/src/MultiDriver.cpp index eb7b525..7ea00b1 100644 --- a/src/MultiDriver.cpp +++ b/src/MultiDriver.cpp @@ -27,15 +27,14 @@ void MultiDriver::startMove(long steps1, long steps2, long steps3){ } ); ready = false; + last_action_end = 0; } /* * Trigger next step action */ long MultiDriver::nextAction(void){ - static unsigned long next_action_time = 0; - long next_action_interval = 0; - - microWaitUntil(next_action_time); + Motor::delayMicros(next_action_interval, last_action_end); + next_action_interval = 0; // Trigger all the motors that need it (event timer = 0) FOREACH_MOTOR( @@ -60,7 +59,7 @@ long MultiDriver::nextAction(void){ event_timers[i] -= next_action_interval; } ); - next_action_time = micros() + next_action_interval; + last_action_end = 0; return next_action_interval; } /* diff --git a/src/MultiDriver.h b/src/MultiDriver.h index 549e862..97ec61b 100644 --- a/src/MultiDriver.h +++ b/src/MultiDriver.h @@ -37,6 +37,8 @@ class MultiDriver { bool ready = true; // when next state change is due for each motor long event_timers[MAX_MOTORS]; + unsigned long next_action_interval = 0; + unsigned long last_action_end = 0; public: /* @@ -51,6 +53,12 @@ class MultiDriver { MultiDriver(Motor& motor1, Motor& motor2, Motor& motor3) :MultiDriver(3, new Motor* const[3]{&motor1, &motor2, &motor3}) {}; + unsigned short getCount(void){ + return count; + } + Motor& getMotor(short index){ + return *motors[index]; + } /* * Move the motors a given number of steps. * positive to move forward, negative to reverse