1

I am trying to send a set of pulses equally spaced to 4 stepper motors drivers (on different GPIOs) at the same time.

These pulses I am wanting to be around 10 microseconds in width so more precise timing than time.sleep can offer.

I have tried to use the pigpio library to accomplish this.

I have not used the built in PWM mode as I am wanting to send a certain number of pulses. (I realise that you could turn the PWM on and off in a specified time to get a specified number of pulses but am wanting to avoid this)

I thought the wave_create function would work well but I have multiple issues:

1st: The pulses are twice as long as I set them (I have fudged the code to mask this, but this may be me not understanding the commands)

2nd: Previously setup waveforms are being cancelled when I start another one

Here is a zoom-in on a single pulse (the delay in the pigpio.pulse is set to 50 microseconds yet on the scope it is 100 microseconds)

This shows how the pulsewidth is twice the delay I specified

Here is a simplified section of my code that replicates the error I was having with the waveforms being cancelled

import pigpio

motorStepPins = (18, 23, 24, 25)        # These are the GPIO addresses for the motor wires
motorDirectionPins = (4, 17, 27, 22)
motorEnablePins = (5, 6, 13, 26)

numberOfSteps = (100, 50, 20, 10)       # This is the number of steps that each motor will advance
motorNumbers = (0, 1, 2, 3)             

pulseWidth = 100                        # The pulsewidth in microseconds
cycleTime = 20                          # time that the number of steps needs to be completed in

motors = pigpio.pi()                    # This initiates pigpio
for motorPin in motorStepPins:          # defines the mode of all the motor pins
    motors.set_mode(motorPin, pigpio.OUTPUT)
for motorPin in motorDirectionPins:
    motors.set_mode(motorPin, pigpio.OUTPUT)
for motorPin in motorEnablePins:
    motors.set_mode(motorPin, pigpio.OUTPUT)

motors.wave_clear()

packages = [0, 0, 0, 0]

for motor in motorNumbers:              # iterates through each motor
    pulses = []
    for i in range(numberOfSteps[motor]):      # This generates a certain number of steps equally spaced in the cycletime
        pulses.append(pigpio.pulse(1<<motorStepPins[motor], 0, int(pulseWidth / 2)))
        pulses.append(pigpio.pulse(0, 1<<motorStepPins[motor], int(((cycleTime*1000/numberOfSteps[motor])-pulseWidth)/2)))

    motors.wave_add_generic(pulses)
        
    packages[motor] = motors.wave_create()

for motor in motorNumbers:
    motors.write(motorDirectionPins[motor], 1)
    motors.wave_send_once(packages[motor])



Trace from the scope

Here we can see that after each successive motor.wave_send_once it cancelled the squarewave that had been started on the other pin.


import pigpio

motorStepPins = (18, 23, 24, 25)        # These are the GPIO addresses for the motor wires
motorDirectionPins = (4, 17, 27, 22)
motorEnablePins = (5, 6, 13, 26)

numberOfSteps = (100, 50, 20, 10)       # This is the number of steps that each motor will advance
motorNumbers = (0, 1, 2, 3)             

pulseWidth = 100                        # The pulsewidth in microseconds
cycleTime = 20                          # time that the number of steps needs to be completed in

motor1 = pigpio.pi()                    # This initiates pigpio
motor2 = pigpio.pi()
motor3 = pigpio.pi()
motor4 = pigpio.pi()
motors = [motor1, motor2, motor3, motor4]
for motorPin in enumerate(motorStepPins):          # defines the mode of all the motor pins
    motors[motorPin[0]].set_mode(motorPin[1], pigpio.OUTPUT)
for motorPin in enumerate(motorDirectionPins):          # defines the mode of all the motor pins
    motors[motorPin[0]].set_mode(motorPin[1], pigpio.OUTPUT)
for motorPin in enumerate(motorEnablePins):          # defines the mode of all the motor pins
    motors[motorPin[0]].set_mode(motorPin[1], pigpio.OUTPUT)

for motor in motors:
    motor.wave_clear()

for motor in enumerate(motors):              # iterates through each motor
    pulses = []
    for i in range(numberOfSteps[motor[0]]):      # This generates a certain number of steps equally spaced in the cycletime
        pulses.append(pigpio.pulse(1<<motorStepPins[motor[0]], 0, int(pulseWidth / 2)))
        pulses.append(pigpio.pulse(0, 1<<motorStepPins[motor[0]], int(((cycleTime*1000/numberOfSteps[motor[0]])-pulseWidth)/2)))

motor[1].wave_add_generic(pulses)
        
package = motor[1].wave_create()

motor[1].wave_send_once(package)

This is a trace from the second program

Here we can see that despite each motor (GPIO Channel) being on a different instance of pigpio.pi() the squarewaves were stopped each time a wave_send_once was called.

I believe the increase in number of pulses being displayed is due to it taking more time between each wave_send_once being called.

I am running this on a raspberry pi 3 and python 3.7.3 32-bit.

Any help would be great as I have been stuck on this for a while.

EDIT with the answer:

Joan's answer was extremely helpful and with their input I produced this (probably incredibly inefficient) working program:


import pigpio
import numpy as np

motorStepPins = (18, 23, 24, 25)        # These are the GPIO addresses for the motor wires
motorDirectionPins = (4, 17, 27, 22)
motorEnablePins = (5, 6, 13, 26)

numberOfSteps = (3, 5, 7, 11)           # This is the number of steps that each motor will advance
motorNumbers = (0, 1, 2, 3)             

pulseWidth = 100                        # The pulsewidth in microseconds
cycleTime = 20                          # time that the number of steps needs to be completed in

motors = pigpio.pi()                    # This initiates pigpio
for motorPin in motorStepPins:          # defines the mode of all the motor pins
    motors.set_mode(motorPin, pigpio.OUTPUT)
for motorPin in motorDirectionPins:
    motors.set_mode(motorPin, pigpio.OUTPUT)
for motorPin in motorEnablePins:
    motors.set_mode(motorPin, pigpio.OUTPUT)

motors.wave_clear()


toggleDataType = [('name', int), ('state', int), ('time', float)]   # This is the data frmat for each toggle event
toggles = []

for motor in motorNumbers:                              # iterates through each motor
    for toggle in range(numberOfSteps[motor]):          # for each pulse it creates a toggle high and a toggle low event
        toggles.append((motor, 1, (toggle * 1000 * cycleTime / numberOfSteps[motor])))
        toggles.append((motor, 0, (toggle * 1000 * cycleTime / numberOfSteps[motor] + pulseWidth)))

toggles = np.array(toggles, dtype = toggleDataType)
toggles = np.sort(toggles, order = 'time')              # Here we have converted to a numpy array in oder to sort the list by time
toggles = toggles.tolist()


pulses = []
for event in enumerate(toggles):        # This generates a certain number of steps equally spaced in the cycletime
    if (event[0]==len(toggles)-1):      # This handles the last event not having a period (I don't think entirely necesary but looks nice at least)
        if event[1][1]:
            pulses.append(pigpio.pulse(1<<motorStepPins[event[1][0]], 0, int((cycleTime * 1000 - event[1][2]) / 2)))
        else:
            pulses.append(pigpio.pulse(0, 1<<motorStepPins[event[1][0]], int((cycleTime * 1000 - event[1][2]) / 2)))
    else:
        if event[1][1]:
            pulses.append(pigpio.pulse(1<<motorStepPins[event[1][0]], 0, int((toggles[event[0]+1][2] - event[1][2]) / 2)))
        else:
            pulses.append(pigpio.pulse(0, 1<<motorStepPins[event[1][0]], int((toggles[event[0]+1][2] - event[1][2]) / 2)))

motors.wave_add_generic(pulses)
    
package = motors.wave_create()

motors.wave_send_once(package)

What I wanted all along

and that is the image I wanted all along

1 Answer 1

0

The time doubling is probably a bug which seems to happen on some Pis. No one has found the root cause (as far as I know).

As far as waves are concerned only one can be running at a time. You will need to merge all the stepper GPIO changes into one wave and send that. You will have to construct a few milliseconds worth of pulses in each wave. Send that and construct a new wave as it is being sent. It is not simple.

2
  • 1
    Thank you very much for extremely quick response! I believe what you are saying is to put all of the pulses for each GPIO into a single waveform and then send that waveform off. I see how this can work and will try it out.
    – Nicholas
    Jan 25, 2022 at 21:04
  • 1
    Just wanting to say thank you for providing a correct answer very quickly, I have updated the question with the code that I ended up producing that solved the issue. Couldn't have done it without your help!
    – Nicholas
    Jan 25, 2022 at 22:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.