Projects

Unistep2

A non-blocking Arduino library for controlling 28BYJ-48 stepper motors.
Github: https://github.com/reven/Unistep2

Rationale

28BYJ-48’s are little inexpensive 5v geared stepper motors that are particularly pesky in terms of what’s needed to drive them. They are halfstep steppers and require an 8 step control signal to drive them efficiently. They usually come with (equally inexpensive) ULN2003 driver boards that make connecting them to the Arduino much easier.

I had no success driving them with the standard stepper libraries (Stepper or AccelStepper). There was always resistance and stutter, resulting in increased heat and noise. I finally came across two libraries that worked: Tyler Henry’s CheapStepper, which I couldn’t get to drive two steppers simultaneously without issues; and Matthew Jones’s Unistep, which drove two or more steppers perfectly but in a blocking fashion.

So I decided to create a non-blocking version based on Unistep and added some extra functionality, but I wanted to recognize the contributions made by others.

Features

  • Non-blocking
  • Can manage 2 or more steppers
  • Extra functions to control state of steppers
  • More energy efficient
  • Precise timing avoids noise, heat, stutter, stalling, etc.

Installation

Download the zip in Releases on github and use the library manager to add it to your libraries. Alternatively you can install manually into your Arduino library folder.

Use

Just call

#include <Unistep2.h>

at the begining of your sketch and construct the stepper objects with the following call in your setup() function:

Unistep2 stepper(p1, p2, p3, p4, stepsPerRev, stepDelay);

where p1 to p4 are the pins you’ve connected your ULN2003 to, stepsPerRev are the steps that your stepper takes to do one revolution (4096-ish in the 28BYJ-48’s) and stepDelay is the delay between each step in microseconds (900 is the fastest that seems to work without issues).
Then you must call

stepper.run()

in your loop. Delaying functions will interfere with the stepper movement.

Function descriptions

  • move(int steps): Moves the stepper the indicated amount of steps. If steps is negative movement will be counter-clockwise.
  • moveTo(unsigned int pos): Moves the stepper to an absolute position between 0 and stepsPerRev.
  • currentPosition(): Returns the current absolute position of the stepper.
  • stepsToGo(): Returns the amount of steps remaining for the stepper to complete the assigned movement. Negative will mean counter-clockwise.
  • stop(): Stops the current movement and powers down the pins to save energy and avoid heat generation. Is called automatically after each movement, but is available to be called by the user.

To-do

  • Create examples
  • Add metadata and library info
  • Repackage as plugin zip

Tip jar

If you find this useful in any way, feel free to leave a tip in my bitcoin address if you feel so inclined:

bc1qn7zrnkk47fzwkf5uqyaqu9dzl7mtyrh5h2ef33

License

This library is released into the public domain.

22 thoughts on “Unistep2

  1. Hi Reven, I tried out your library and it solves a lot of problems I am facing running stepper motors simultaneously. However, I did face one problem when I was using the library which is if I call moveTo command exceeding 15000 steps, the stepper motor will just move in one direction no matter whether the value is positive or negative. looking forward for your reply. below is my code.

    #include
    // Define some steppers and the pins they will use
    Unistep2 stepperX(2, 3, 4, 5, 20000, 500);

    void setup()
    {
    // Your setup code here
    // The library initializes the pins for you
    }

    void loop()
    {
    // We need to call run() frequently during loop()

    stepperX.moveTo(16000);
    stepperX.run();

    }

    1. Hi Jin,

      So from your example I see a few things:

      • the moveTo() function takes an unsigned int as it’s argument, so the function is ignoring the sign, moveTo() should be used as a movement to the absolute position of the stepper, defined from 0 to the number of steps you have set one revolution as. To say it simply, it’s like telling your stepper “Go to 3 o’clock”. It will try to extrapolate the shortest path.
      • In your example your call to move is in the loop, without any other logic, so you’re calling the same function again and again. You probably simplified your code just to make a point, but you don’t need to call the function constantly, just run() needs to be called in the loop.

      Hope this helps.

    2. Hi Reven,

      Thank you for the reply. Can you please advice me based on the below code? Thanks in advance

      #include “Unistep2 .h”

      Unistep2 stepperX(2, 3, 4, 5, 5000, 1000);

      void setup() {

      }

      void loop() {

      stepperX move to a position

      (do something)

      stepperX move to another position

      }

      1. Hi Jin,

        I don’t really know what you’re trying to acomplish, so it’s hard to give you a based example. When you say move to position I’m guessing you mean an absolute position, like 1500 for example.

        I would probably do something like this in my loop:

        stepperX.run();
        stepeprX.moveTo(1500);
        {check stepper to see if it’s still moving}
        {do something else}
        {check to see if that something else has finished}
        stepperX.moveTo(4500);

        The utility of my library is that it is not blocking, i.e. the stepper moves in the background while your code does something else. If you do not need that and you want to do a linear logic (move, then do something, then move), you could do that with a blocking library -see my post above- and not have to check the status.

  2. Hi, instead of the standard stepper application “make a defined number of steps” i want to make the stepper motor drive continously, speed and direction e.g. controlled from a joystick . Later for 4 motors independently . How is this done with your library?
    Thanks in advance,
    Hans Gruber

    1. Hi Hans,

      I don’t think that this library is a good fit for what you have in mind. A function for continuous movement could be added, but I don’t think stepper motors are an ideal candidate for that application. Stepper motors move… well, in steps. So the library lets you count and control those steps.

      For what you’re doing you would be better off with DC motors or BLDC motors, that could be controlled (normally with a controller board) with a routine that puts them in motion and then listens to user input (analog joystick, for example, for direction and acceleration) and modulates the motors accordingly (extrapolating analog signal to PWM pulse to the controller). I don’t know, something like that?

  3. Hi Reven, great work. But a little hint. To use the moveTo function, there is needed to set the zero position. This function is missing. Example, if powering on, the stepper can start on any position, so it needs to go to a zero position by reading end switches. After found that, there is a need to set the zero position. Only in this way is it always possible to approach a specific position.
    Is it possible that you include this function in the library?

    Greetings, Olli

    1. Hi Olli,

      These steppers don’t have end switches, so a function to zero them is out of the scope of the library, unfortunately. I think that would fall into the scope of the sketch. It would be easy to get the current position when the end switch is triggered (arbitrarily number) and set it as a zero with a local variable or counter. This would also make it possible to track bigger numbers, because the motors steps are bound to 4096 anyway.

  4. Hey man, I dig your library. Modifyingt right now to fit a robotic arm, in a system with multiple mcus that is a pain in the ass on timing. I’m trying to create two stepper objects, and then combine the sequences in 4 bit chunks and send them out to a shift register. The goal is driving two uln2003s, and use 3 wires instead of . I’m curious…. a few of the class functions return a value. Is there a reason you took this approach instead of just reading the cars?

  5. Hello Haven,

    I am using your library but in my application I needed to change the movement speed during the programm execution. So I add the following function to you lib:

    void Unistep2::newSteptime(unsigned long newdelay)
    { steptime=newdelay; }

    Ofcourse I changed .h, .cpp, and keywords

    Maybe you could put something like that in the future versions. I think the number of steps also would be changed outside constructor. Something like newStepsPerRevolution( int newsteps)…

    Thanks,

    Antonio

  6. Hi, thanks for this. I’ve waded through the stepper lib’s for a non-blocker on an attiny85 and this seems the best so far. I do need speed control for the application I have so I added :

    Unistep2.h:
    // Allow the speed to be changed 1000 fast : 10000 slow
    void setspeed(int newdelay);

    and in
    Unistep2.cpp
    // Change speed (delay between steps)
    void Unistep2::setspeed(int newdelay){
    // TODO: Limit the values to the range 1000 to 100000
    steptime = newdelay;
    }

    Seems to work. I know _nothing_ of c let alone c++ so I hope it’s not gauche.

    1. Hi Grant,
      I might get around to test it, but if I remember correctly from the tests I ran, bending delay to change the speed can have unexpected consequences; these steppers are fussy.
      From a project/sketch design point of view, I think it would be a much better idea to find a delay value that makes the stepper happy, and to rely on non blocking timed loops to take each step according to the speed one desires.
      I’ll put it on my to-do list…

  7. Hi Reven!

    I was wondering if there was any way to adjust the stepDelay throughout the sketch in order to change the speed of a stepper motor actively. I’m trying to make a clock face that you can control the speed that the hands move at with an input method.

    1. Hi Grant,
      I’ve been away for some time, but checking your comment I saw that there were other people that had the same need and actually got it to work. Hopefully some of the comments above can be useful to you. Check out the other Grant’s comment. Are you sure you two aren’t the same person? 😀

  8. Hi Robert,

    Thanks for creating this library. I would like to ask a question about myStepper.move(random( )); For my code, i got two commands. The cw one works well, always cw, but my cww one sometimes goes cw instead. I’d really appreciate it if you could help me with it. Thanks in advance!

    here is the cww one’s code:
    //other task2
    case 0xFF22DD: //button |<
    if (itsON[2] == 1){ Serial.println("CH+HIGH");
    myStepper.move(random(-4000,5000));
    itsON[2]=0;
    } else { Serial.println("CH+LOW");
    myStepper.stop();
    itsON[2]=1;
    }
    tIRDeadband = millis();
    break;

    Here is my whole code:
    //Include the library
    #include
    #include
    #include

    enum enumLEDStates
    {
    LED_OFF = 0,
    LED_FADE_IN,
    LED_ON,
    LED_FADE_OUT

    };

    int RECV_PIN = 2;
    int toggleState = 0;
    int itsON[] = {0, 0, 0, 0};

    IRrecv irrecv(RECV_PIN);
    decode_results results;

    //define the pin
    Encoder myEnc(6, 7); //(outA, outB)
    Unistep2 myStepper(8, 9, 10, 11, 2048, 900);
    const int touchPin = 4;
    const int ledPin = 5;
    const int WakePin = 3;
    const int buttonPin = 12;

    //brightness adjustment
    long oldPosition = -999;
    long newPosition;
    int positionDifference = 0;

    //fading led
    int
    fadeMin = 0, // >= 0
    fadeMax = 30, // = 500ul )
    {
    switch(results.value)
    {
    //other task1
    case 0xFFA25D: //buttonCH-
    if (itsON[1] == 1){ Serial.println(“CH-HIGH”);
    myStepper.move(random(4000,5000)); //4000,5000 for whole rack
    itsON[1]=0;
    } else { Serial.println(“CH-LOW”);
    myStepper.stop();
    itsON[1]=1;
    }
    tIRDeadband = millis();
    break;

    //other task2
    case 0xFF22DD: //button |= led_In_Interval )
    {
    tLED = tNow;
    fadeValue += fadeInStep;
    if ( fadeValue > fadeMax )
    {
    fadeValue = fadeMax;
    ledState = LED_ON;
    }
    analogWrite ( ledPin, fadeValue );

    }//if

    break;

    case LED_ON:
    //once on, allow encoder to adjust brightness
    newPosition = myEnc.read();
    if (newPosition != oldPosition)
    {
    positionDifference = newPosition – oldPosition;
    oldPosition = newPosition;

    fadeValue = fadeValue – positionDifference;
    fadeValue = constrain(fadeValue, 1, 30);
    analogWrite(ledPin, fadeValue);

    }//if

    break;

    case LED_FADE_OUT:
    if( (tNow – tLED) >= led_Out_Interval )
    {
    tLED = tNow;
    fadeValue -= fadeOutStep;
    if ( fadeValue <= fadeMin )
    {
    fadeValue = fadeMin;
    ledState = LED_OFF;

    }//if
    analogWrite( ledPin, fadeValue );

    }//if

    break;

    }//switch

    }//DoLEDStateMachine

  9. Hi Reven,

    Is that possible to use this library with the TB6600 stepper motor driver ?
    Since each motor needs only 2 pins, not 4 pins, I’m wondering how to activate multiple stepper motors at the same time with different number of steps per revolution.

  10. FYI I’ve been running Unistep2 on a Nano with no compile problems but then tried it on a QT PY (SAMD21). Arduino compiler threw out this error: error: control reaches end of non-void function (tied to the function Unistep2::run().

    I added the return false line below and it compiles and runs fine. I have no idea if this is programmatically correct but it fixed my problem so I can move forward.

    Just wanted to let others know.

    {
    if (!stepstogo) // will be true if zero (!false = true)
    {//we’re done
    return true;
    } else {
    nextStep();
    return false;
    }
    }

  11. Hi,
    I stumbled on your library and it was exactly what I was looking for in my project where I want to drive a stepper motor from a DMX console.
    But I was missing some functionality although it is questionable whether this functionality is useful for a stepper motor. At least for me it is 🙂
    So I want to control the stepper motor to move continuesly forward or backward or stop. And I want to control the rotation speed. And all this using wireless DMX. For those that are not familiar with DMX, it is a protocol used for lightning equipement. It is actually very simple: DMX outputs 512 channels, each containing a value 0-255. The device will give meaning to each channel. In my case, channel0 = rotation direction, channel1 = speed.
    I adapted the library to accommodate my needs and created a sample program that uses the arnetWifi library for DMX communication. I am very happy with the result thanks to the original unistep2 library

  12. Hi Reven,
    If you like, I can share my adapted version of the library and sample code.
    Thanks again for this nice library.

    Cheers,
    Jan-Willem Tielen

  13. Hello! i am a student using your code because I found it way easier then stepper.h! I am currently working on project that includes a sensor, but whenever I run my code the motor mysteriously stops working! I have tried checking the wiring with your example and it always works until i run my code! i don’t know why it would stop and im too inexperienced to fix it, i thought maybe you could help since you wrote the library? here is my entire set below!

    #include

    Unistep2 stepperX(2, 3, 4, 5, 4096, 1000); // pins for IN1, IN2, IN3, IN4, steps per rev, step delay(in micros)

    const int stepsPerRevolution = 2038;
    const int trigPin = 8;
    const int echoPin = 9;

    void setup()
    {
    Serial.begin(9600);
    pinMode(trigPin, OUTPUT);
    pinMode(echoPin, INPUT);
    }

    void loop()
    {
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10); // this is how oftem the sensor with send out untrasonic pulses
    digitalWrite(trigPin, LOW);

    long duration = pulseIn(echoPin, HIGH);

    float distance_cm = duration * 0.034 / 2;
    Serial.print("Distance: ");
    Serial.print(distance_cm);
    Serial.println(" cm");

    stepperX.run();

    while (distance_cm=150){
    stepperX.move(-200);
    }
    delay(1000);

    1. Hi,

      Yes, so the library is non blocking in order to have things work in the background. Each loop the library is invoked to take care of movement. Right on the “Use” page it says “Delaying functions will interfere with the stepper movement”. That delay you have at the end is breaking the stepper movement. If you need a pause for your sensor, I’d recommend placing a non blocking wait with a check to millis(), like for example:

      if ((millis() - lastTime) > timerDelay) {
      // do the thing that you don't want run every loop
      }

    2. Now that I look at it with formatting, shouldn’t that delay be in the loop? Maybe it’s that? Though the delay in the loop will still mess up the stepper movement.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.