Drive a Nema 17 stepper motor with the RpiMotorLib Python library for A4988

drive nema motor raspberrypi a4899 python flask hmtl interface
Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp

The RpiMotorLib library for Python 3 allows you to easily control stepper motors (ULN2003, L298N, A4988, DRV8825, A3967 or TB6612FNG driver) and direct current motors via the GPIO of the Raspberry Pi. Widely used to realize the movements of the axes of 3D printers, laser engravers and numerical controls (CNC).

GitHub logo

Source Code

For this tutorial, we will use a Nema 17 stepper motor (precisely 17HS4401 ) driven by an A4988 microcontroller, two components very widely used in 3D printers and CNC to assemble yourself.

Install the RpiMotorLib and Flask libraries

To be able to function, the RpiMotorLib library requires Python 3.5 or higher. The Desktop version of Raspberry Pi OS is installed with version 2.7 and 3.7 or later. To verify that version 3 is installed, run the following command in a Terminal.

If Python3 is properly installed, you should get the version number in response.

python3 --version
Python 3.7.3

To install the Python libraries, we will need the pip3 script. To verify that it is properly installed, run this command which should return the path to the script

pip3 --version

If pip3 is not installed, run

sudo apt install python3-pip

Now we can install the libraries

sudo pip3 install rpimotorlib
python3 -m pip install Flask

What can we drive with the RpiMotorLib library?

The library developed by Gavin Lyons allows you to control stepper motors, servo motors and direct current motors. For each type of movement, multiple controllers are supported. Here is the list with the cross reference to the online documentation for the wiring and a small sample code.

Stepper motors

EngineControllerCircuit and example
Unipolar 28BYJ-48

28BYJ-48 engine

ULN2003Read
Bipolar Nema

bipolar nema motor

L298N H-BridgeRead
A4988 Stepper Driver CarrierRead
DRV8825 Single H-BridgeRead
A3967 Stepper Driver also known as “easy driver v4.4”Read
TB6612FNG , driver 1 or 2 motorsRead

DC Motors

Controllers supported by the library to drive DC motors

ControllerCircuit and example
L298NL298NRead
transistorTransistorRead
L9110SL9110SRead
DRV8833DRV8833 Single H-BridgeRead
TB6612FNGTB6612FNGRead

List of functions offered by the library

The functions offered by the library vary according to the controller used and the type of motor.

ClassroomFunctionSettings
Source code of the RpiMotorLib.py file
BYJMotorprint_cursor_spinDeprecated function
motor_run(gpiopins, wait=.001, steps=512, ccwise=False, verbose=False, steptype=”half”, initdelay=.001
)
GPIOPins type list of 4 long integers. GPIO pins to which the controller is connected.

wait type float, waiting time (in seconds) between each step (steps)

steps . type int. Number of sequences of steps to be executed. The default is one revolution, 512 for the 28BYJ-48.

ccwise counterclockwise by default. True to reverse the direction

verbose True to activate the focus

steptype type string. half by default. Pilot type. 3 options: full (fullstep), half (half step), wave (wave drive)

initdelay type float, 1ms by default. Wait time after initialization before starting the movement.

A4988

Supported controllers

A4988

DRV8825

motor_go(clockwise=False, steptype=“Full”,steps=200, stepdelay=.005, verbose=False, initdelay=.05)clockwise direction of rotation clockwise by default. True to reverse

steptype string, by default Full. Stepper motor type. 5 options Full, Half, 1/4, 1/8, 1/16, 1/32

steps integer. Number of steps to perform, 200 by default

stepdelay type float, default 0.05 seconds. Waiting time between each step.

verbose True to activate debug messages

initdelay type float, 0.05 s by default. Wait time after initialization before starting the movement.

Source code of rpi_dc_lib.py file
L298NMDc

DRV8833NmDc

initialisation(pin_one, pin_two, freq=50, verbose=False, name=”DCMotorY”)pin_one type int, direction pin connected to IN1 or IN3

pin_two type int, PWM pin of the GPIO for the speed connected to IN2 or IN4

Freq frequency in Hz by default 50

verbose True to activate debug messages

Name no use

forward(duty_cycle=50)Forward
backward(duty_cycle=50)Reverse
stop(duty_cycle=0)Stop the engine
brake(duty_cycle=100)Brakes the motor
cleanup(clean_up=False)Free the GPIO. Use in case of communication problem
TranDcinitialisation(pin, freq=50, verbose=False)Class for controlling a DC motor via a transistor

pin  type int, PWM pin of the GPIO for the speed connected to IN2 or IN4

Freq frequency in Hz by default 50

verbose True to activate debug messages

TB6612FNGDcinitialisation(pin_one, pin_two, pwm_pin, freq=50, verbose=False, name=”DCMotorY”)pin_one GPIO pin connected to AI1 or BI1

pin_two GPIO pin connected to AI2 or BI2

pwm_pin GPIO pin connected to PWA or PWB

Freq frequency in Hz by default 50

verbose True to activate debug messages

Name no use

standby(standby_pin, standby_on=True)
standby_pin GPIO pin connected to the standby pin of the TB661FNG

Enables / disables sleep mode of the TB661FNG controller

forward(duty_cycle=50)Forward
backward(duty_cycle=50)Reverse
stop(duty_cycle=0)Stop the engine
brake(duty_cycle=100)Brakes the motor
cleanup(clean_up=False)Free the GPIO. Use in case of communication problem

Note on stopping stepper motor movements

The RpiMotorLib library does not offer any method to stop the movement in progress for stepper motors.

In an emergency or manually, the movements managed by the BYJMotor, A4988 and DRV8825 microcontrollers cannot be stopped.

Also be careful not to disconnect the pins during a movement. This can damage the GPIO of the Raspberry Pi or the microcontroller.

Circuit

The Raspberry Pi’s GPIO (whatever the model and the generation) does not deliver enough power to directly power a Nema motor (or any other motor for that matter).

The A4988 controller has a 12V input that can be supplied with a battery or from the mains. The A4988 must also be supplied with 5V. If you only have one accessory connected to the GPIO, the Raspberry Pi can deliver enough power.

There are multi-voltage power supply boards to deliver sufficient power for this type of assembly.

Do not forget to connect the Reset and Sleep pins using a jumper otherwise the microcontroller remains in standby. The RpiMotorLib library does not make it possible to manage waking up only when movement is requested. This means that the motor remains energized at all times.

Here is the pin identification in detail

12V power supplyPin A4988
+ 12V from battery or mains powerVMOT
GND from battery or mains powerGND
Raspberry Pi GPIO (any version)
5V from Raspberry PiVDD
Raspberry Pi GNDGND
GPIO 21STP
GPIO 20DIR
To stepper motor

Usually the motor is delivered with a 4-pin cable   fitted with a 2.54mm pitch connector. If the displacement is reversed, reverse the connector or modify the Python code

1A, 1B, 2A, 2B
GPIO 14MS1
GPIO 15MS2
GPIO 18MS3
Connect RESET and SLEEP pins together

Circuit wiring diagram

A4988 Nema 17 raspberry pi python circuit

Example of a Python project allowing to move a Nema 17 Engine

Open the Thonny Python IDE code editor accessible from the Programming menu

raspberry pi os thonny python ide

Paste the Python code below

#! /usr/bin/python
# -*- coding:utf-8 -*-
from RpiMotorLib import RpiMotorLib
from flask import Flask, render_template_string, redirect, request

#define GPIO pins
GPIO_pins = (14, 15, 18)    # Microstep Resolution MS1-MS3 -> GPIO Pin
direction= 20               # Direction Pin, 
step = 21                   # Step Pin
distance = 80               # Default move 1mm => 80 steps per mm


# Declare an named instance of class pass GPIO pins numbers
mymotortest = RpiMotorLib.A4988Nema(direction, step, GPIO_pins, "A4988")

app = Flask(__name__)

#HTML Code 

TPL = '''
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

        <title>Stepper Motor Controller</title>
    </head>
    <body>
      <div class="container">
        <div class="row" style="padding:10">
          <div class="col-sm-1">
            <form method="POST" action="up" class="center-block">
                <button type="submit" class="btn btn-primary">Up</button>
            </form>
          </div>
          <div class="col-sm-1">
            <form method="POST" action="down" class="center-block">
                <button type="submit" class="btn btn-primary">Down</button>
            </form>
          </div> 
        </div>   
            <form method="POST" action="setdistance">
                <div class="btn-group" role="group" >
                  <button name="distance" type="submit" class="btn btn-secondary" value="8">0.1 mm</button>
                  <button name="distance" type="submit" class="btn btn-secondary" value="80">1 mm</button>
                  <button name="distance" type="submit" class="btn btn-secondary" value="800">10 mm</button>
                </div>
            </form>  
        
      </div>   
    </body>
</html>

'''
 
@app.route("/")
def home():
    return render_template_string(TPL)

@app.route("/setdistance", methods=["POST"])
def setdistance():
    global distance
    global distance
    distance = int(request.form["distance"])
    print("set distance to", distance)
    return redirect(request.referrer)
     
@app.route("/up", methods=["POST"])
def up():
    global distance
    print("Move up,", distance, "steps")
    mymotortest.motor_go(False, "Full" , distance, 0.01 , False, .05)
    return redirect(request.referrer)

@app.route("/down", methods=["POST"])
def down():
    global distance
    print("Move down,", distance, "steps")
    mymotortest.motor_go(True, "Full" , distance, 0.01 , False, .05)
    return redirect(request.referrer)
 
# Run the app on the local development server
if __name__ == "__main__":
    app.run()

Click on the green arrow to launch the script

thonny python ide start script

Open Chromium and enter the address http://127.0.0.1:5000 in the address bar.

nema stepper motor drive rapsberry pi python rpimotorlib a4988

Use the selector to choose the displacement to be performed: 0.1mm, 1mm or 10mm

Then the direction of movement:

  • UP to go up
  • DOWN to go down

Here is a small demonstration video

Controller initialization code

As we saw in the previous paragraph, 5 pins must be assigned to the A4988 microcontroller. MS1, MS2, MS3, step and reverse direction.

We then create an RpiMotorLib.A4988Nema object that we can then use in the project code

GPIO_pins = (14, 15, 18)    # Microstep Resolution MS1-MS3 -> GPIO Pin
direction= 20               # Direction Pin,
step = 21 # Step Pin 
mymotortest = RpiMotorLib.A4988Nema(direction, step, GPIO_pins, "A4988")

Explanation of the code

We are going to create a web interface to drive the Nema engine using the Flask library.

from flask import Flask, render_template_string, redirect, request

Here the HTML page is stored directly in the TPL string. We import the Bootstrap 4.0 stylesheet directly from the internet. To change version, go here.

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

With Flask, it only takes a few lines of code to create a web server

app = Flask(__name__)
if __name__ == "__main__":
  app.run()

Like most HTML frameworks, Flask manages endpoints, the address of the web page. By default when you arrive on a website (the root), the page is by /

So we created a route for each page and each action submit a form.

The @ decorator allows you to specify an endpoint and the function (immediately after) to be executed as soon as the page is called by the user. The home method returns the main page whose source code is stored in the TPL chain at the start of the project.

@app.route("/")
def home():
    return render_template_string(TPL)

Rendering of web pages can be done in 3 ways

  • Return the HTML code directly as a string.
return "<h1>Hello World</h1>"
  • The HTML code is stored in a character string. In this case, we use the render_template_string method to perform the rendering. This is the solution used in the project.
  • From an HTML file stored in a separate file using the render_template method

We send commands to Python code using HTTP POST requests. To do this, simply place the commands (the buttons in this case) in an Formular.

Here, the code of the UP button which allows to start the movement of the motor upwards.

<form method="POST" action="up" class="center-block">
  <button type="submit" class="btn btn-primary">Up</button>
</form>

When you want to send parameters to the server, you can for example use the value argument.

The request.form([“name”]) method is used to retrieve the value of the argument on the server side.

distance = int(request.form["distance"])

We create a new route /up that accepts the HTTP Post method.

The distance to be traveled is stored in a global variable distance. To retrieve the value of this in the up() method, the name of the variable must be preceded by the global argument in order to indicate to the Python interpreter that we want to access the global variable.

We use the motor_go method of the RpiMotorLib library to execute the movement. Here are the necessary parameters

  • False indicates the direction of rotation
  • “Full” engine type
  • Distance number of steps to take
  • 0.01 timeout between each step
  • False silent execution of the function
  • 0.05 seconds waiting time before starting the movement

Reminder, the RpiMotorLib library does not offer any function to stop the rotation of the motor

There is no need to redraw the HTML page here, we simply return the user to the original page using the redirect(request.referrer) method.

@app.route("/up", methods=["POST"])
def up():
    global distance
    print("Move up,", distance, "steps")
    mymotortest.motor_go(False, "Full" , distance, 0.01 , False, .05)
    return redirect(request.referrer)

How to calculate the number of steps per millimeter traveled?

It is a recurring problem in 3D printing and CNC. Fortunately, there are many tools to help us.

You can also use these online calculators offered by Prusa, the well-known Spanish 3D printer manufacturer

calculator step per mm leadscrew driven

Updates

2020/11/05 Publication of the article

Version française

Click to rate this post!
[Total: 0 Average: 0]

Are you having a problem with this topic?

Maybe someone has already found the solution, visit the forum before asking your question

Ask your question

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp

Did you like this project ? Don't miss any more projects by subscribing to our weekly newsletter!

We will be happy to hear your thoughts

      Leave a Reply

      DIY Projects