User avatar
Cacodemon
Posts: 21
Joined: Mon Jul 09, 2018 4:32 pm

Driving multiple stepper motors with Python

Tue Aug 07, 2018 8:43 am

Edit: Problem solved, it works!


Recently I've been trying to learn how to control stepper motors with Python. I can run one 28BYJ-48-5V at a time easily using PiStep2 Quad using scripts which are readily available (see the list on bottom), however I've been trying to modify the code to run two of these motors simultaneously. Since I'm still quite new to programming with python, I've not been very successful at figuring out how exactly a script running two or more stepper motors simultaneously would be built properly. So, I'd need some advice here.

This is the code I've modified:

Code: Select all

#!/usr/bin/python
# Import required libraries
import sys
import time
import RPi.GPIO as GPIO
 
# Use BCM GPIO references
# instead of physical pin numbers
GPIO.setmode(GPIO.BCM)
 
# Define GPIO signals to use
# Physical pins 11,15,16,18
# GPIO17,GPIO22,GPIO23,GPIO24
StepPins = [17,22,23,24]
StepPins2 = [20,26,16,19]
 
# Set all pins as output
for pin in StepPins + StepPins2:
  print "Setup pins"
  GPIO.setup(pin,GPIO.OUT)
  GPIO.output(pin, False)
 
# Define advanced sequence
# as shown in manufacturers datasheet
Seq = [[1,0,0,1],
       [1,0,0,0],
       [1,1,0,0],
       [0,1,0,0],
       [0,1,1,0],
       [0,0,1,0],
       [0,0,1,1],
       [0,0,0,1]]
        
StepCount = len(Seq)
StepDir = 1 # Set to 1 or 2 for clockwise
            # Set to -1 or -2 for anti-clockwise
 
# Read wait time from command line
if len(sys.argv)>1:
  WaitTime = int(sys.argv[1])/float(1000)
else:
  WaitTime = 1/float(1000)
 
# Initialise variables
StepCounter = 0
 
# Start main loop
while True:
 
  #print (StepCounter,)
 # print (Seq[StepCounter])
 
  for pin in range(0, 8):
    xpin = StepPins[pin]
    if Seq[StepCounter][pin]!=0:
     #print (" Enable GPIO %i" %(xpin))
      GPIO.output(xpin, True)
    else:
      GPIO.output(xpin, False)
 
  StepCounter += StepDir
 
  # If we reach the end of the sequence
  # start again
  if (StepCounter>=StepCount):
    StepCounter = 0
  if (StepCounter<0):
    StepCounter = StepCount+StepDir
 
  # Wait before moving on
  time.sleep(WaitTime)

I have also tried altering the StepPins part like this:

Code: Select all

StepPins = [[17,22,23,24],
		 [20,26,16,19]]
 
# Set all pins as output
for pin in StepPins:
  print "Setup pins"
  GPIO.setup(pin,GPIO.OUT)
  GPIO.output(pin, False)

But every alteration this far results in: Traceback (most recent call last): File *path here* in <module> xpin = StepPins[pin] IndexError: list out of range. So I returned to the first alteration and started wondering how should I add StepPins2 into " xpin = StepPins[pin]" because separating them with , or + will not work.


Here is the resources list I've been using so far:
http://4tronix.co.uk/pistep/stepper.py
http://4tronix.co.uk/pistep/stepctrl.py
https://www.raspberrypi-spy.co.uk/2012/ ... in-python/
I've been looking at projects and scripts of other hobbyists too, but as a beginner I don't yet want to confuse myself with too far-fetched examples. Unless modifying the existing codes is an attempt doomed to fail. Could this still be made to work?
Last edited by Cacodemon on Thu Feb 21, 2019 4:10 pm, edited 2 times in total.

scotty101
Posts: 4149
Joined: Fri Jun 08, 2012 6:03 pm

Re: Driving multiple stepper motors with Python

Tue Aug 07, 2018 8:54 am

With the following syntax

Code: Select all

StepPins = [[17,22,23,24],
		 [20,26,16,19]]
You have a list inside a list. So you first need to use a for loop within a for loop (nested for loops), first to iterate through two items that contain the pins for each motor and then again for each pin.

Code: Select all

StepPins = [[17,22,23,24],
		 [20,26,16,19]]
 
# Set all pins as output
for motor in StepPins
    for pin in motor:
      print "Setup pins"
      GPIO.setup(pin,GPIO.OUT)
      GPIO.output(pin, False)
The more complex part is stepping the motors. I'll edit this in a few minutes once I've thought about it.
EDIT: I think I'd deal with the two motors separately. Personally I'd create a stepper motor class but here is a more simple way to do it (Untested code, concept only)

Code: Select all

#Initialise Step Counters
StepCounterMotor1 = 0
StepCounterMotor2 = 0
  


while True:
    #Step Motor 1 first
    motorPins = StepPins[0]
    
    for idx,pin in enumerate(motorPins):
        if Seq[StepCounterMotor1][idx]:
            GPIO.output(pin,True)
        else:
            GPIO.output(pin,False)
        
        if (StepCounterMotor1>=StepCount):
            StepCounterMotor1 = 0
        if (StepCounterMotor1<0):
            StepCounterMotor1 = StepCount+StepDir
            
    # Same for Motor 2
    motorPins = StepPins[1]
    ...................
    
    time.sleep(WaitTime)
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
B.Goode
Posts: 10868
Joined: Mon Sep 01, 2014 4:03 pm
Location: UK

Re: Driving multiple stepper motors with Python

Tue Aug 07, 2018 9:29 am

Just to second @scotty101's further thought.

My preference would be to solve the problem of driving One stepper, and then generalize it by giving the command the set of pins needed to drive a specific motor.

There is an example, with some background explanation, here:
http://chickbot.club/programming-your-c ... pi-moving/

scotty101
Posts: 4149
Joined: Fri Jun 08, 2012 6:03 pm

Re: Driving multiple stepper motors with Python

Tue Aug 07, 2018 10:01 am

To expand upon my previous answer where I mentioning using a class, here is an example that should do roughly the right thing but obviously not tested with real motors or even GPIO pins.

Code: Select all

import time
import RPi.GPIO as GPIO

## Dummy GPIO Class for testing purposes.
##class GPIO_Dummy:
##    OUT = 'out'
##    IN = 'in'
##    def setup(self, pin, _dir):
##        print(f'Setup {pin} as {_dir}put')
##    def output(self, pin, state):
##        print(f'Setting {pin} to {state}')
##
##
##GPIO = GPIO_Dummy()

class StepperMotor:
    Seq = [[1,0,0,1],
       [1,0,0,0],
       [1,1,0,0],
       [0,1,0,0],
       [0,1,1,0],
       [0,0,1,0],
       [0,0,1,1],
       [0,0,0,1]]
    def __init__(self, pins):
        assert len(pins) == 4, "4 pins must be specified"
        self.pins = pins
        self.direction = 1
        self.stepCount = 0
    def step(self):
        """Call the step command to set the pins for the next step in the sequence"""
        for idx,pin in enumerate(self.pins):
            print(f'####{self.pins} {self.direction} {self.stepCount}####')
            try:
                GPIO.output(pin,self.Seq[self.stepCount][idx])
            except IndexError:
                print(f'Setting pin {idx} to sequence {self.stepCount}')
            
        self.stepCount += self.direction
        
        if (self.stepCount>=len(self.Seq)):
            self.stepCount = 0
        if (self.stepCount<0):
            self.stepCount = len(self.Seq)-1
            #self.stepCount = self.stepCount+self.direction


#Initialise the two motors, specify the GPIO pins as a list
motor1 = StepperMotor([1,2,3,4])
motor2 = StepperMotor([5,6,7,8])
            
#Make 10 steps for each motor
for i in range(0,10):
    motor1.step()
    motor2.step()
    time.sleep(0.5)

#Change the direction of motor1 and move 10 steps.
motor1.direction = -1
for i in range(0,10):
    motor1.step()
    #motor2.step()
    time.sleep(0.5)
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
Cacodemon
Posts: 21
Joined: Mon Jul 09, 2018 4:32 pm

Re: Driving multiple stepper motors with Python

Tue Oct 16, 2018 1:54 pm

Hey, thanks for the replies.

I ran into more errors even with Scotty's lastest code, ofc as it's been few months already I'm thinking of double checking which errors those were to be precise. After long hours of frustration and wondering what's the part I'm not getting or if there's a simpler code to run two stepper motors, I slowly worked my way around it by running two different codes; one being the one posted in OP and another being modified version of that for the second motor... and it worked! :shock:

Now, I got a new problem though. To make these easier to execute, I decided to make a new code to run these two files... and perhaps run some more code once the wheels are rolling. Made some research on how to run these files, found someone saying that I could do it safely by importing them as modules, but I'm not sure how to make my code actually run the two other codes after importing. Would it be better to use execfile or another command instead? What would you suggest?

Edit: Figured out how to make the scripts work as modules. Got another problem already, which I didn't get back when running the codes manually from their own files: Runtimewarning: this channel is already in use. Continuing anyway. *only one motor starts running* What could be done about this?

User avatar
B.Goode
Posts: 10868
Joined: Mon Sep 01, 2014 4:03 pm
Location: UK

Re: Driving multiple stepper motors with Python

Tue Oct 16, 2018 4:21 pm

Got another problem already, which I didn't get back when running the codes manually from their own files: Runtimewarning: this channel is already in use. Continuing anyway. *only one motor starts running* What could be done about this?
It is literally a Warning - it does not affect the running or working of the code. It is not, of itself, the reason for one of the motors not working as expected.

BUT... it is probably an indication that your solution to the requirement is inadequate in some way.

You have two choices: you can use the setwarnings() function to suppress the warning. Or, preferably, you can examine the code closely and resolve the underlying problem that is causing multiple, unexpected, accesses to the same channel.

Hunch: a command that you planned to send to energise the second motor is erroneously being sent to the first: hence the report of a duplicate from the first, and the failure of the second to respond.

Idahowalker
Posts: 593
Joined: Wed Jan 03, 2018 5:43 pm

Re: Driving multiple stepper motors with Python

Tue Oct 16, 2018 11:01 pm

Without looking at the code would a Y-connector cable work?
Idaho, U.S.A.

User avatar
Cacodemon
Posts: 21
Joined: Mon Jul 09, 2018 4:32 pm

Re: Driving multiple stepper motors with Python

Wed Oct 17, 2018 7:51 am

B.Goode, that's true. The warning was my only lead to go on though.

Now my main code looks like this:

Code: Select all

import stepper #import code for stepper motor 1 as module
import stepper2 #do the same for stepper motor 2 code

stepper.roll() #run motor 1
stepper2.roll() #run motor 2
And added these to the original stepper motor codes:

Code: Select all

def roll():
       *stepper motor code*

And the code for second motor is similar, just with different pins. If I comment either stepper.roll() or stepper2.roll() to test the motors, the non-commented one will always work. So, the stepper motor codes should be ok and that should also rule out bad cables.

I even tried

Code: Select all

import stepper #import code for stepper motor 1 as module
import stepper2 #do the same for stepper motor 2 code

stepper.roll() #run motor 1
stepper2.roll2() #run motor 2
and

Code: Select all

def roll2():
       *original stepper motor code*
For the second motor code. I don't think defining them both as roll should be mixing things up, but had to try anyway. Still got only one motor running at a time depending which one is set to run on the line above another.

scotty101
Posts: 4149
Joined: Fri Jun 08, 2012 6:03 pm

Re: Driving multiple stepper motors with Python

Wed Oct 17, 2018 8:52 am

Can you share your entire code file? Odds are that you've got your GPIO pin mapping incorrect.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
Cacodemon
Posts: 21
Joined: Mon Jul 09, 2018 4:32 pm

Re: Driving multiple stepper motors with Python

Wed Oct 17, 2018 9:31 am

For motor 1:

Code: Select all

def roll():
	import sys
	import time
	import RPi.GPIO as GPIO
 
	GPIO.setmode(GPIO.BCM)
 

	StepPins = [20,26,16,19]
 
	for pin in StepPins:
  		print ("Setup pins")
 		GPIO.setup(pin,GPIO.OUT)
 		GPIO.output(pin, False)
 

	Seq = [[1,0,0,1],
	 [1,0,0,0],
	 [1,1,0,0],
         [0,1,0,0],
         [0,1,1,0],
         [0,0,1,0],
         [0,0,1,1],
         [0,0,0,1]]
        
	StepCount = len(Seq)
	StepDir = 1	
	
	if len(sys.argv)>1:
  		WaitTime = int(sys.argv[1])/float(1000)
	else:
  		WaitTime = 1/float(1000)

	StepCounter = 0
 
	while True:
		for pin in range(0, 8):
    		xpin = StepPins[pin]
    		if Seq[StepCounter][pin]!=0:
    			GPIO.output(xpin, True)
    		else:
      			GPIO.output(xpin, False)
 
		  StepCounter += StepDir
		
		  if (StepCounter>=StepCount):
    			StepCounter = 0
  		  if (StepCounter<0):
    			StepCounter = StepCount+StepDir
 		
 		time.sleep(WaitTime)


For motor 2:

Code: Select all

def roll2():
	import sys
	import time
	import RPi.GPIO as GPIO
 
	GPIO.setmode(GPIO.BCM)
 

	StepPins2 = [13,12,6,5]
 
	for pin in StepPins2:
  		print ("Setup pins")
 		GPIO.setup(pin,GPIO.OUT)
 		GPIO.output(pin, False)
 

	Seq = [[1,0,0,1],
	 [1,0,0,0],
	 [1,1,0,0],
	 [0,1,0,0],
	 [0,1,1,0],
	 [0,0,1,0],
	 [0,0,1,1],
	 [0,0,0,1]]
        
	StepCount = len(Seq)
	StepDir = 1	
	
	if len(sys.argv)>1:
  		WaitTime = int(sys.argv[1])/float(1000)
	else:
  		WaitTime = 1/float(1000)

	StepCounter = 0
 
	while True:
		for pin in range(0, 8):
    		xpin = StepPins2[pin]
    		if Seq[StepCounter][pin]!=0:
    			GPIO.output(xpin, True)
    		else:
      			GPIO.output(xpin, False)
 
		  StepCounter += StepDir
		
		  if (StepCounter>=StepCount):
    			StepCounter = 0
  		if (StepCounter<0):
    			StepCounter = StepCount+StepDir
 		
 		time.sleep(WaitTime)

To run both without having to start them separately:

Code: Select all

import stepper #import code for stepper motor 1 as module
import stepper2 #do the same for stepper motor 2 code

stepper.roll() #run motor 1
stepper2.roll2() #run motor 2

User avatar
B.Goode
Posts: 10868
Joined: Mon Sep 01, 2014 4:03 pm
Location: UK

Re: Driving multiple stepper motors with Python

Wed Oct 17, 2018 9:53 am

So can you confirm that the first block of code for motor1 is the content of stepper.py, and the second is the content of stepper2.py?

Not necessarily the root of the problem, but something odd to consider..

In both modules you have

Code: Select all

	while True:
		for pin in range(0, 8):
    		xpin = StepPins[pin]
    		if Seq[StepCounter][pin]!=0:
Two things -

There is no (indented) code block to be performed after the for pin statement.

StepPins has only been defined with 4 elements, but (if the code ran at all) you are examining 8.

scotty101
Posts: 4149
Joined: Fri Jun 08, 2012 6:03 pm

Re: Driving multiple stepper motors with Python

Wed Oct 17, 2018 9:56 am

What was wrong with the class method I share with you a while back?

I appreciate what you've done to split the code across two modules but that isn't an effective way of doing this and serves no useful purpose.
You have essentially the same code in two places rather than a common function that takes different pin numbers as arguments. First rule of programming Don't Repeat Yourself (DRY)

The problem with your two separate "roll" functions, and why commenting one of them out "fixes things" is that they both contain an infinite loop. When the first "roll" function runs, it never ends to the second "roll" function is never executed.

It is generally bad practice to import python modules inside a function too.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
B.Goode
Posts: 10868
Joined: Mon Sep 01, 2014 4:03 pm
Location: UK

Re: Driving multiple stepper motors with Python

Wed Oct 17, 2018 10:18 am

It is generally bad practice to import python modules inside a function too.
Ah! Another possible insight...

The reason it is generally bad practice is that it can be the cause of problems like the one you are seeing.

import in Python is not a passive inclusion of text as with some other Programming Languages: it is an active statement that immediately executes the content of the module referred to.

In the case of RPi.GPIO that will almost certainly clear and rewrite any definitions related to the gpio pins. So when you import stepper2.py you will negate/undo the setup() commands you used in stepper.py

User avatar
Cacodemon
Posts: 21
Joined: Mon Jul 09, 2018 4:32 pm

Re: Driving multiple stepper motors with Python

Mon Oct 22, 2018 7:44 am

scotty101 wrote:
Wed Oct 17, 2018 9:56 am
What was wrong with the class method I share with you a while back?
Mostly myself trying to understand it now. I noticed it was missing pieces like GPIO.setmode which I added and set the pins for motor1 and motor2 in the code accordingly. For some reason I also got syntax error at

Code: Select all

print(f'####{self.pins} {self.direction} {self.stepCount}####')
and

Code: Select all

 print(f'Setting pin {idx} to sequence {self.stepCount}')
Removed the f's to see if the rest of the code would work, which is when I got this:

Image

And then I might have to edit the code too much for my understanding, as the output method I've used this far has been different but your code also has one, would it mix things up?
I appreciate what you've done to split the code across two modules but that isn't an effective way of doing this and serves no useful purpose.
You have essentially the same code in two places rather than a common function that takes different pin numbers as arguments. First rule of programming Don't Repeat Yourself (DRY)

The problem with your two separate "roll" functions, and why commenting one of them out "fixes things" is that they both contain an infinite loop. When the first "roll" function runs, it never ends to the second "roll" function is never executed.

It is generally bad practice to import python modules inside a function too.
Ah! Another possible insight...

The reason it is generally bad practice is that it can be the cause of problems like the one you are seeing.

import in Python is not a passive inclusion of text as with some other Programming Languages: it is an active statement that immediately executes the content of the module referred to.

In the case of RPi.GPIO that will almost certainly clear and rewrite any definitions related to the gpio pins. So when you import stepper2.py you will negate/undo the setup() commands you used in stepper.py
Oh yea, that could be it. Thanks for clarifying! I tried to add threading to the code to avoid that, as someone said in another place that could make two modules run simultaneously. Didn't work tho. If there's a simpler option than messing with modules, I'll take it. Wouldn't be asking help building the code if I didn't need it. :)


Back to trying to figure out a working solution for me, hehe.

Return to “Python”