Interrupts with Yukon and stepper motors

I’ve been trying to use the Yukon and a Dual Motor/Bipolar Step module to build a small machine for a science demo. What I’m trying to do at the moment is get the board to recognise an end-stop button/limit switch, which triggers an interrrupt, and then changes the direction of the Stepper. However, as far as I can tell when you call stepper.move_to(step, duration) there’s no graceful way to interrupt the movement of the motor to reverse the direction without, it seems, releasing the motor, which then seems to need the whole thing re-initialised. I’ve then not been able to get it reinitialised to start driving again in the opposite direction.

Has anyone else been able to get something like this working? I’m tempted to move to a more simple driver board as OkayStepper isn’t well documented and seems to have a few quirks.

Hi!

Yukon developer here. That sounds like a nice project!

As you have noted OkayStepper is not that featureful or well documented at present. This is because my own knowledge of how to drive stepper motors is limited, which is why I gave the class that self-deprecating name. I opened a discussion thread on the product’s Github back in November looking for feedback (Stepper Motor Improvements Discussion · pimoroni/yukon · Discussion #6 · GitHub), though it seems you are the first customer to use this functionality.

For your project, it does appear I neglected to include some kind of stop() command. For the pen plotter project I performed the homing routine using lots of small movements, checking the limit switches in between. This meant I never encountered that gap of functionality in the library. Had I used interrupts I immediately would have.

If you have time this weekend, could you try adding the following function into OkayStepper within stepper.py then calling it from your interrupt?

def stop(self):
    self.__step_timer.deinit()
    self.hold()
    self.__moving = False

Or if I’ve misunderstood your requirements, please let me know. I can also be reached in the Yukon channel on our Discord.

1 Like

Hello!

Cool, I did spot the note that it was called OkayStepper for a reason, but thought I’d have a go at it anyway.

I’ll try to add that function tomorrow and see how it goes.

Thanks!

Hello again!

So, I popped that code into stepper.py and the interrupts mostly work now (also, hurray for having libraries in Python! I know a lot of Pimoroni_pico is a base of C with Python on top for ease of development, but it makes it really hard to tinker with).

An issue I’m having is that sometimes the stepper ignores the sign on the steps changing and simply continues in the same direction. If I manually press the limit switch then sometimes rather than reversing direction the stepper keeps moving, but slows down every time I press the switch. What I assume is happening is that when stop() is called, it is halting the motor but not resetting something like self.__end_microstep, so the next stepper.move_to() command is being added on top of the remaining portion of previous requests giving a different result than everything just starting from fresh?

I’ve tried adding stepper.__end_microstep = 0 to the interrupt but still have the issue. I might tinker more tomorrow if I get time.

The code, in case that's helpful
from pimoroni_yukon import Yukon
from pimoroni_yukon import SLOT6 as SLOT
from pimoroni_yukon.modules import DualMotorModule
from pimoroni_yukon.devices.stepper import OkayStepper
from machine import Pin
import time

# Constants
CURRENT_SCALE = 0.5             # How much to scale the output current to the stepper motor by, between 0.0 and 1.0

# Variables
yukon = Yukon()                 # Create a new Yukon object
module = DualMotorModule()      # Create a DualMotorModule object
stepper = None                  # A variable to store an OkayStepper object created later
# Number of steps to take
stepSize = -10000
# Duration of steps
stepDuration = 5

# Set up limit switch pins
leftStop = Pin(26, Pin.IN, Pin.PULL_UP)
rightStop = Pin(27, Pin.IN, Pin.PULL_UP)

# IRQ for LEFT limit switch, reverse the direction of the platform
def callbackLeft(irq):
    # Halt the motor
    global stepper
    stepper.stop()
    stepper.__end_microstep = 0
    # Reverse the direction, move to other end of device
    global stepSize
    stepSize = 10000

# IRQ for RIGHT limit switch, move the platform left a little and then halt
def callbackRight(irq):
    # Halt the motor
    global stepper
    stepper.stop()
    stepper.__end_microstep = 0
    # Reverse the direction and move a little
    global stepSize
    stepSize = -100
    global stepDuration
    stepDuration = 1
   
# Attach interrupts to limit switch pins
leftStop.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=callbackLeft)
rightStop.irq(trigger=Pin.IRQ_FALLING, handler=callbackRight)

# Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt)
try:
    yukon.register_with_slot(module, SLOT)      # Register the DualMotorModule object with the slot
    yukon.verify_and_initialise()               # Verify that a DualMotorModule is attached to Yukon, and initialise it
    yukon.enable_main_output()                  # Turn on power to the module slots

    # Create a class for controlling the stepper motor, in this case OkayStepper, and provide it with the DualMotorModule's two outputs
    stepper = OkayStepper(module.motor1, module.motor2, current_scale=CURRENT_SCALE)

    # Set the hardware current limit to its maximum (OkayStepper controls current with PWM instead)
    module.set_current_limit(DualMotorModule.MAX_CURRENT_LIMIT)
    #module.set_current_limit(DualMotorModule.MAX_CURRENT_LIMIT)
    module.enable()                             # Enable the motor driver on the DualMotorModule

    # Loop until the BOOT/USER button is pressed
    while not yukon.is_boot_pressed():
        if not stepper.is_moving():
            stepper.move_to(stepSize, stepDuration)                 # Initiate the movement

finally:
    if stepper is not None:
        stepper.release()                       # Release the stepper motor (if not already done so)

    # Put the board back into a safe state, regardless of how the program may have ended
    yukon.reset()

Judging by your variable name stepSize and the comment move a little I think you should be using move_by() rather than move_to() for the movements you want.

  • move_by() is for moving a position relative to where the stepper last was. I use this during the homing phase of the pen plotter.

  • move_to() is for moving to a global position, with zero being where the stepper was powered on at (or where zero_position() was last called). I used this during the drawing phase of the pen plotter.

(I assume you found it, but here is the pen plotter code: yukon/examples/showcase/plotter/main.py at main · pimoroni/yukon · GitHub)

So in your code you are initially moving from zero to -10000, then to either -100 or +10000 (whichever corresponds to those limit switches). That would explain the behaviour you saw, because if it was moving to -10000 and you hit the right switch at -50, it would then try and move to -100.

I acknowledge that more documentation would clarify this difference.

Also 10000 steps is quite a lot to do in 1 second! I think at most the pen plotter had 3000 steps of travel across its 1 metre axis, and that took multiple seconds.

Oh, and another not documented feature that may be useful. When creating the OkayStepper you can specify a steps_per_unit value to convert your steps into useful units like centimetres. (There are also move_to/by_steps() functions that ignore this if needed)

Edit:

I know a lot of Pimoroni_pico is a base of C with Python on top for ease of development, but it makes it really hard to tinker with).

There are performance advantages to having the code in C, though I admit not all of Pimoroni Pico needed that. Ideally this stepper driver would be in C, as MicroPython introduces all sorts of timing quirks that made OkaySteppers development challenging. For example, its not possible to run Yukon’s monitoring system without it causing the stepper movement to stutter. Once OkayStepper becomes ActuallyGoodStepper I will look at porting it.

Ah, ok, so this probably explains all of the issues I’ve been having. Previously when I’ve used steppers (admittedly not that often) the move command was essentially “move to a position X steps clockwise/anticlockwise from the current position”. I wasn’t expecting something which thought in terms of “move to a position which is X steps from the intial position”. I know you have a robotics background, is that the way it is generally done in industry/research? It might be worth just clarifying that in the stepper example for other people like me who’ve come from the Arduino libraries.

Thanks for the undocumented stuff, I’ll tinker with what I’ve got and see if I’ve time to work those into it. I need to get this working reliably and preferably looking fancy for a few weeks’ time, so it might be tight, especially as I’ve not really built machines before!

I know some of my other parameters were a bit weird, I was mostly working through things empirically with what actually got the spindle turning and getting very confused over why the stepper sometimes moved the platform to the perfect position, but sometimes overran it.

Previously when I’ve used steppers (admittedly not that often) the move command was essentially “move to a position X steps clockwise/anticlockwise from the current position ”. I wasn’t expecting something which thought in terms of “move to a position which is X steps from the initial position”.

Ah, that’s useful to know. I came at it from the mindset of “if someone is using steppers, over motors+encoders (like I was), then they are likely using them for their precise position control” and so that functionality should be baked in rather than the user having to write it themselves on top. Perhaps that was a wrong assumption.

The only library for stepper driving I used before was CircuitPython’s, which gave the single function of oneStep() you have to call all the time. And I intentionally avoided looking at other larger stepper libraries from arduino etc to avoid potential issues with code licences.

I know you have a robotics background, is that the way it is generally done in industry/research?

Industry tend not to share what solutions they use, though I am aware that Field Oriented Control (see https://simplefoc.com/) is quite common, which pairs a stepper (or brushless) motor with an encoder to give more efficient position/velocity control, since the motor only needs to be energised when the encoder value changes.

I’ll tinker with what I’ve got and see if I’ve time to work those into it. I need to get this working reliably and preferably looking fancy for a few weeks’ time, so it might be tight, especially as I’ve not really built machines before!

I look forward to seeing the results!