Wednesday, 1 June 2016

getting more out of an Adafruit DC & Stepper Motor HAT

As described in an earlier post, this HAT is really only doing stepper motor control as an afterthought, so limited the step rate to just under 80 per second.

However after rummaging around in the code while developing a driver for my telescope mount, I noticed that the Adafruit code is pretty basic and makes no attempt at optimisation.

With some simple changes to the Adafruit code I have managed to improve the step rate to around 280 per second peak. This also means that there is less jitter in the pulse timing as well.

Here are a couple of graphs of the outputs as seen from inside the python code (so the physical signals may vary from this).

This graph is an overview run with one stepper running at about 20 steps per second. I then start the other stepper fairly slow (4 per second) and double the speed a few times. The level of jitter is not good, but probably not enough to cause mis-steps as long as the step rate is well below the motor's maximum.

When the steps to the 2 motors coincide it impacts one or both motors.



The step rates of the varying speed motor here are twice as fast as the previous version, even when the second motor is stepping twice as fast as before the jitter is far lower for both motors. The version can also step significantly faster, but the jitter does start to build up at really fast step rates.

The code changes are all in the Adafruit_I2C.py module. These changes are incompatible with anything other than stepper motors, and could short out the power supply through the HAT if anything goes wrong - which would probably trash the HAT.

add to the constructor:
self.regvals = [None] * 256
and the method write8 should be amended as follows:
def write8(self, reg, value):
    "Writes an 8-bit value to the specified register/address"
    if self.regvals[reg] is None or self.regvals[reg] != value:
        try:
          self.bus.write_byte_data(self.address, reg, value)
          self.regvals[reg] = value
          if self.debug:
            print("I2C: Wrote 0x%02X to register 0x%02X" % (value, reg))
        except IOError as err:
          return self.errMsg()


19 comments:

  1. Hey there, I wanted to let you know that at least two people are appreciating your changes to the original code very much. I took the freedom to contribute the code to the original repository here https://github.com/adafruit/Adafruit-Motor-HAT-Python-Library/pull/10
    I took every chance to say that you are the original author, not me.

    ReplyDelete
  2. oh! well done, I was feeling guilty about not doing that!

    Thanks

    ReplyDelete
  3. Is it possible to get some of your testing code for driving the stepper and increasing and decreasing the speed? Is it on github or bitbucket perhaps? I am looking for example code to help out on a friend's project that requires accelerating and decelerating the stepper speed and when I use adafruit's setSpeed function it seems to sometimes lose steps and not be consistent. I have not yet tried your modified version of the adafruit code. I also saw your post about the Pololu A4988 and it looks very interesting, but I am trying to see what can be done with the hardware he already has. Thanks!

    ReplyDelete
  4. Sounds like you have also run into another bug in the code - if you go to the githib adafruit repo, Hendrik changed my quick mod into a pull request - theres a couple of others in there that might be useful as well. I'll dig out the code I had and stick something on github in the next couple of days.

    ReplyDelete
  5. That will be very helpful to me. Don't worry about cleaning it up or making it presentable - it's for educational purposes, not publication. I tried doing a google search for "Adafruit_MotorHAT()" but did not find hardly any code that varied much from adafruit's StepperTest.py example.

    When I searched the raspberry pi forum for things like "stepper speed", I found some discussions about increasing the i2c bus speed in order to be able to move the steppers faster, like this: https://arduino-pi.blogspot.com/2014/03/speeding-up-i2c-bus-on-raspberry-pi-and.html Is this something that you also experimented with when using the unmodified adafruit code?

    ReplyDelete
  6. I didn't bother with the faster bus thing - there were a few reports of complications arising, and I'd already decided to try the pololu drive boards anyway. I've had a look at the code I used at the time and it is a pile of spaghetti, tangled up with all sorts of other stuff I was trying out. But in principle just use a loop with a timer and use time.sleep() to wait till the next tick is due. You can run this in a separate thread or better still a separate process. The loop needs to be

    nextick = time.time() + interval
    while running:
    call motor.step()
    nexttick += interval
    do anything else needed - adjust interval for speed ramping for example - check if you want to carry on
    time.sleep(nexttick-time.time())

    as long as you do the tick straight after coming back from sleep, and calculate the sleep time immediately before sleeping the timing comes out pretty accurately

    ReplyDelete
  7. Hi

    I have tried your solution in editing the write8 and adding self.regvals=[None]*256 into the _init_ of AdafruitI2C.py. However, this changes nothing at all to the speed of the motor. The max speed for the motor would be 600RPM but I only got it running at 120RPM (this speed will be halved if two motors are running at once!). In your other post in https://www.raspberrypi.org/forums/viewtopic.php?f=45&t=130475, you state something about the motor hat not being thread safe. What do you mean by that ? I am using all Nema 17 stepper motor with 1.8 degree step angle. Help please

    ReplyDelete
    Replies
    1. It all comes down to the steps per second you want to achieve. Even with my code change, you can only get to just under 200 steps per second. If your motor is 1.8 degrees per step, that is 200 steps per rev. To reach 600 rpm you need 2,000 full steps per second, which is way beyond what you can do with the adafruit hat. If you use the pololu driver card instead, and drive it direct from gpio you should be able to go that fast.

      I found the steppers I am using need to use 1/2 step mode to achieve maximum speed, and need to be ramped up to that speed as well. I am issuing close to 2000 steps per second, but I do have occasional jitter problems, so I am looking into using DMA to actually waggle the gpio pins - using pigpio to do the hard work.

      The thread safe note is because with adafruit's code, all calls to the adafruit library must be from the same python thread - if you are not using python threading, then there is no problem.

      Hope this helps!

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
  9. Hi

    I'm trying to test you mod but I can't find Adafruit_I2C.py.

    Where do I go after home/pi/Adafruit-Motor-Hat-Python-Library ??

    Thanks

    ReplyDelete
    Replies
    1. Its not in there - its in the adafruit I2C library:
      Adafruit_GPIO/I2C.py

      Have you downloaded the GPIO library as well as the motor hat module?

      Delete
  10. Hi again.

    I found I2C.py. But it's in a .egg file.
    /usr/local/lib/python3.4/dist-packages/Adafruit_GPIO-1.0.3-py3.4.egg/Adafruit_GPIO/I2C.py/
    When i try sudo nano on that adress it creates a new empty file.

    I also tried cd usr/.../Adafruit_GPIO but it says that it's not a directory. Sorry I'm new to RPi.

    Thanks again

    ReplyDelete
  11. I would like to attempt this modification but it may be out of date. From what I have experienced the original behavior of the stepper hat has not improved since this project's inception.
    Can you provide direction on how to proceed with the latest Python 3.7 iteration?
    Thank you kindly.
    Andrew.

    ReplyDelete
    Replies
    1. I write all code to run in Python3, 3.7 should be fine.

      The adafruit code on github has been updated, but the area I changed looks as it did, so the edit should till be valid.

      If you want anything other than slow, basic use of a stepper motor, then this HAT is not the answer. You should use at least something like an A4988.

      Delete
    2. Hello Pootle. Thank you for the quick reply.
      My project is a DIY Indi Focus driver. So I do not need it to travel very fast. I have been using it with double step and increased I2C bus speed tweak for some time. But that solution is no longer compatible since the addition of an RTC module. Also it sounds like a coffee grinder :p

      So I am happy if your modification will simply allow it to go a bit faster with microstepping for a quieter run.

      Now, I could not find a file called Adafruit_I2C.py. But I do have I2C.py in ~/.local/lib/python3.7/site-packages/Adafruit_GPIO/

      It looks different from the code in the earlier Github pull referred to above so I am having a difficult time understanding where to make the change, or whether I actually have the correct file.
      A final question, if I make this change are the effects immediately obvious or do I have to do any other steps or compilations?

      Thank you.
      Andrew

      Delete
  12. Looks like their code is very different as of a few months ago, so my edits may no longer be relevant, but the new stuff does support microstepping (the default / example code doesn't seem to use it - hence coffee grinder mode). If you're not trying to go too fast try microstep=16 or even higher - looks like they calculate the microstep table in the constructor, so you try really big numbers like 256 - motion probably no smoother, but should be quieter!

    Use style='microstep' in the call to onestep.

    The code is here: https://github.com/adafruit/Adafruit_CircuitPython_MotorKit/blob/master/adafruit_motorkit.py

    and it uses the stepper class here: https://github.com/adafruit/Adafruit_CircuitPython_Motor/blob/master/adafruit_motor/stepper.py

    ReplyDelete
    Replies
    1. Oh, the issue isn't using microstepping per-se, I can already set it to that. It's just very slow, so I must resort to using Single or Double steps which is noisy.
      This is the INDI driver I'm using to control it for context.
      https://github.com/rkaczorek/astroberry-amh


      I was hopeful that your modification would make microstepping run faster based on the last comment left on the Github pull request by danclarke. Re: https://github.com/adafruit/Adafruit-Motor-HAT-Python-Library/pull/10

      Delete
    2. Sorry. It looks to me like my driver is actually using a C++ ported version of the adafruit library. Therefor I would not benefit from your implementation.
      Any modifications would probably have to be adapted into the C++ version.
      https://github.com/threebrooks/AdafruitStepperMotorHAT_CPP

      Delete
  13. This comment has been removed by the author.

    ReplyDelete