Skip to content

Commit

Permalink
Add support for four-wire steppers
Browse files Browse the repository at this point in the history
  • Loading branch information
bblanchon committed Aug 7, 2023
1 parent 16935a5 commit 22d4bbe
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ HEAD
----

* Remove deprecated 3-parameter `begin()` (use `setEnablePin()` instead)
* Add support for four-wire stepper motors

2.2.0
-----
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ Contrary to other stepper libraries, this one doesn't provide any function to mo

### Features

* Supports stepper drivers with *step* and *dir* pins
* Optionally supports the *enable* pin
* Supports multiple stepper types:
- stepper drivers with *step* and *dir* pins (and optionally *enable* pin)
- four-wire stepper motors
* Optionally runs with [TimerOne](https://github.com/PaulStoffregen/TimerOne), [TimerThree](https://github.com/PaulStoffregen/TimerThree) or [TeensyTimerTool](https://github.com/luni64/TeensyTimerTool)
* Optionally uses `tone()` instead of `digitalWrite()` for the step pin
* Optionally uses `tone()` instead of `digitalWrite()` for the *step* pin
* Optionally uses PWM with `analogWriteFrequency()` on Teensy 3 and 4
* Optionally uses [Khoi Hoang](https://github.com/khoih-prog)'s PWM libraries (RP2040_PWM, SAMD_PWM, AVR_PWM, STM32_PWM, Teensy_PWM...)
* Accelerates and decelerates smoothly
* Negative speed rotates backward
* Tiny footprint (about 150 lines of code)
* Uses neither `delay()` nor `delayMicroseconds()`

### Suggested applications
Expand Down
13 changes: 13 additions & 0 deletions extras/tests/ContinuousStepperTests/begin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,17 @@ TEST_CASE("ContinuousStepper::begin()") {
});
}
}

WHEN("begin(2, 3, 4, 5) is called") {
stepper.begin(2, 3, 4, 5);

THEN("it should configure the four pins as OUTPUT") {
CHECK_ARDUINO_LOG({
{0'000, "pinMode(2, OUTPUT)"},
{0'000, "pinMode(3, OUTPUT)"},
{0'000, "pinMode(4, OUTPUT)"},
{0'000, "pinMode(5, OUTPUT)"},
});
}
}
}
42 changes: 42 additions & 0 deletions extras/tests/ContinuousStepperTests/powerOff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,46 @@ TEST_CASE("ContinuousStepper::powerOff()") {
}
}
}

GIVEN("begin(2, 3, 4, 5) was called") {
stepper.begin(2, 3, 4, 5);

WHEN("powerOff() is called") {
CLEAR_ARDUINO_LOG();
stepper.powerOff();

THEN("it should set all pins to LOW") {
CHECK_ARDUINO_LOG({
{0'000, "digitalWrite(2, LOW)"},
{0'000, "digitalWrite(3, LOW)"},
{0'000, "digitalWrite(4, LOW)"},
{0'000, "digitalWrite(5, LOW)"},
});
}
}

AND_GIVEN("spin(10) was called") {
stepper.spin(10);
loop_till(stepper, 100'000);
REQUIRE(stepper.isSpinning() == true);
REQUIRE(stepper.speed() == 10);

WHEN("powerOff() is called") {
CLEAR_ARDUINO_LOG();
stepper.powerOff();

THEN("it should set all pins to LOW") {
loop_till(stepper, 100'000);
CHECK(stepper.isSpinning() == false);
CHECK(stepper.speed() == 0);
CHECK_ARDUINO_LOG({
// pin 2 is already LOW
{100'000, "digitalWrite(3, LOW)"},
{100'000, "digitalWrite(4, LOW)"},
// pin 5 is already LOW
});
}
}
}
}
}
65 changes: 65 additions & 0 deletions extras/tests/ContinuousStepperTests/powerOn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,69 @@ TEST_CASE("ContinuousStepper::powerOn()") {
}
}
}

GIVEN("begin(2, 3, 4, 5) was called") {
stepper.begin(2, 3, 4, 5);

AND_GIVEN("powerOff() was called") {
stepper.powerOff();

WHEN("powerOn() is called") {
CLEAR_ARDUINO_LOG();
stepper.powerOn();

THEN("it should set pin 2 and 4 to HIGH") {
CHECK_ARDUINO_LOG({
{0, "digitalWrite(2, HIGH)"},
{0, "digitalWrite(4, HIGH)"},
})
}
}

AND_GIVEN("spin() was called") {
stepper.spin(100);

WHEN("powerOn() is called") {
CLEAR_ARDUINO_LOG();
stepper.powerOn();

THEN("should smoothly restore the rotation") {
loop_for(stepper, 100'000);

REQUIRE(stepper.isSpinning() == true);
CHECK(stepper.speed() == 100);
CHECK_ARDUINO_LOG({
{0'000, "digitalWrite(2, HIGH)"}, // 31.622 ms
{0'000, "digitalWrite(4, HIGH)"}, //
{31'622, "digitalWrite(2, LOW)"}, // 15.811 ms
{31'622, "digitalWrite(3, HIGH)"}, //
{47'433, "digitalWrite(4, LOW)"}, // 12.649 ms
{47'433, "digitalWrite(5, HIGH)"}, //
{60'082, "digitalWrite(2, HIGH)"}, // 10.904 ms
{60'082, "digitalWrite(3, LOW)"}, //
{70'986, "digitalWrite(4, HIGH)"}, // 10 ms
{70'986, "digitalWrite(5, LOW)"}, //
{80'986, "digitalWrite(2, LOW)"}, // 10 ms
{80'986, "digitalWrite(3, HIGH)"}, //
{90'986, "digitalWrite(4, LOW)"}, // ...
{90'986, "digitalWrite(5, HIGH)"}, //
})
}
}
}
}

AND_GIVEN("powerOn() was already called") {
stepper.powerOn();

WHEN("powerOn() is called") {
CLEAR_ARDUINO_LOG();
stepper.powerOn();

THEN("it should do nothing") {
CHECK_ARDUINO_LOG({});
}
}
}
}
}
16 changes: 16 additions & 0 deletions extras/tests/ContinuousStepperTests/setEnablePin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,20 @@ TEST_CASE("ContinuousStepper::setEnablePin()") {
}
}
}

GIVEN("begin(2, 3, 4, 5) was called") {
stepper.begin(2, 3, 4, 5);

WHEN("setEnablePin(6) is called") {
CLEAR_ARDUINO_LOG();
stepper.setEnablePin(6);

THEN("it should set pin 6 to HIGH") {
CHECK_ARDUINO_LOG({
{0, "pinMode(6, OUTPUT)"},
{0, "digitalWrite(6, HIGH)"},
});
}
}
}
}
62 changes: 62 additions & 0 deletions extras/tests/ContinuousStepperTests/spin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,66 @@ TEST_CASE("ContinuousStepper::spin()") {
}
}
}

GIVEN("begin(2, 3, 4, 5) was called") {
stepper.begin(2, 3, 4, 5);

AND_GIVEN("setAcceleration(1000) was called") {
stepper.setAcceleration(1000);

WHEN("spin(100) is called") {
CLEAR_ARDUINO_LOG();
stepper.spin(100);

THEN("it should accelerate and reach target speed") {
loop_till(stepper, 100'000);
REQUIRE(stepper.speed() == 100);

CHECK_ARDUINO_LOG({
{31'622, "digitalWrite(2, LOW)"}, // 15.811 ms
{31'622, "digitalWrite(3, HIGH)"}, //
{31'622, "digitalWrite(4, HIGH)"}, //
{31'622, "digitalWrite(5, LOW)"}, //
{47'433, "digitalWrite(4, LOW)"}, // 12.649 ms
{47'433, "digitalWrite(5, HIGH)"}, //
{60'082, "digitalWrite(2, HIGH)"}, // 10.904 ms
{60'082, "digitalWrite(3, LOW)"}, //
{70'986, "digitalWrite(4, HIGH)"}, // 10 ms
{70'986, "digitalWrite(5, LOW)"}, //
{80'986, "digitalWrite(2, LOW)"}, // 10 ms
{80'986, "digitalWrite(3, HIGH)"}, //
{90'986, "digitalWrite(4, LOW)"}, // ...
{90'986, "digitalWrite(5, HIGH)"}, //
});
}
}

WHEN("spin(-100) is called") {
CLEAR_ARDUINO_LOG();
stepper.spin(-100);

THEN("it should accelerate and reach target speed") {
loop_till(stepper, 100'000);
REQUIRE(stepper.speed() == -100);

CHECK_ARDUINO_LOG({
{31'622, "digitalWrite(2, HIGH)"}, // 15.811 ms
{31'622, "digitalWrite(3, LOW)"}, //
{31'622, "digitalWrite(4, LOW)"}, //
{31'622, "digitalWrite(5, HIGH)"}, //
{47'433, "digitalWrite(2, LOW)"}, // 12.649 ms
{47'433, "digitalWrite(3, HIGH)"}, //
{60'082, "digitalWrite(4, HIGH)"}, // 10.904 ms
{60'082, "digitalWrite(5, LOW)"}, //
{70'986, "digitalWrite(2, HIGH)"}, // 10 ms
{70'986, "digitalWrite(3, LOW)"}, //
{80'986, "digitalWrite(4, LOW)"}, // 10 ms
{80'986, "digitalWrite(5, HIGH)"}, //
{90'986, "digitalWrite(2, LOW)"}, // ...
{90'986, "digitalWrite(3, HIGH)"}, //
});
}
}
}
}
}
35 changes: 35 additions & 0 deletions extras/tests/ContinuousStepperTests/stop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,39 @@ TEST_CASE("ContinuousStepper::stop()") {
}
}
}

GIVEN("begin(2, 3, 4, 5) was called)") {
stepper.begin(2, 3, 4, 5);

AND_GIVEN("spin(100) was called") {
stepper.spin(100);
loop_for(stepper, 100'000);
REQUIRE(stepper.isSpinning() == true);
REQUIRE(stepper.speed() == 100);

WHEN("stop() is called") {
CLEAR_ARDUINO_LOG();
stepper.stop();

THEN("it should deccelerate") {
loop_for(stepper, 100'000);

REQUIRE(stepper.isSpinning() == false);
REQUIRE(stepper.speed() == 0);
CHECK_ARDUINO_LOG({
{100'986, "digitalWrite(2, HIGH)"}, // 11.111 ms
{100'986, "digitalWrite(3, LOW)"}, //
{112'097, "digitalWrite(4, HIGH)"}, // 12.676 ms
{112'097, "digitalWrite(5, LOW)"}, //
{124'773, "digitalWrite(2, LOW)"}, // 15.102 ms
{124'773, "digitalWrite(3, HIGH)"}, //
{139'875, "digitalWrite(4, LOW)"}, // 19.565 ms
{139'875, "digitalWrite(5, HIGH)"}, //
{159'440, "digitalWrite(2, HIGH)"}, // ...
{159'440, "digitalWrite(3, LOW)"}, //
})
}
}
}
}
}
37 changes: 37 additions & 0 deletions src/ContinuousStepper/StepperInterfaces.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class StepperInterface {
virtual bool needsDoubleSpeed() const {
return false;
}

virtual void powerOn(){};
virtual void powerOff(){};
};

class StepperDriver : public StepperInterface {
Expand All @@ -46,4 +49,38 @@ class StepperDriver : public StepperInterface {
OutputPin _stepPin, _dirPin;
};

class FourWireStepper : public StepperInterface {
public:
FourWireStepper(pin_t pin1, pin_t pin2, pin_t pin3, pin_t pin4) : _pins{pin1, pin2, pin3, pin4} {}

void setDirection(bool reversed) override {
_increment = reversed ? 3 : 1;
}

void step() override {
_position = (_position + _increment) % 4;
setPins(_flags[_position]);
}

void powerOn() override {
setPins(_flags[_position]);
}

void powerOff() override {
setPins(0);
}

private:
void setPins(uint8_t flags) {
for (auto &pin : _pins) {
pin.set(flags & 1);
flags >>= 1;
}
}

OutputPin _pins[4];
uint8_t _position = 0, _increment = 1;
uint8_t _flags[4] = {0b0101, 0b0110, 0b1010, 0b1001};
};

} // namespace ArduinoContinuousStepper
6 changes: 6 additions & 0 deletions src/ContinuousStepperBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class ContinuousStepperBase {
begin(new StepperDriver(stepPin, dirPin));
}

void begin(pin_t pin1, pin_t pin2, pin_t pin3, pin_t pin4) {
begin(new FourWireStepper(pin1, pin2, pin3, pin4));
}

void begin(StepperInterface *stepper) {
_stepper = stepper;
_status = WAIT;
Expand All @@ -32,12 +36,14 @@ class ContinuousStepperBase {
if (_status != OFF)
return;

_stepper->powerOn();
_enablePin.set(_enablePinActiveLevel);

updateSpeed();
}

void powerOff() {
_stepper->powerOff();
_enablePin.set(!_enablePinActiveLevel);

_status = OFF;
Expand Down

0 comments on commit 22d4bbe

Please sign in to comment.