Multiaxis stepper motors using RMT

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
User avatar
OlivierLenoir
Posts: 32
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Multiaxis stepper motors using RMT

Post by OlivierLenoir » Mon Dec 30, 2019 10:13 pm

I've created a class to drive multiple stepper axis using RMT.
https://gitlab.com/olivier.len02/micropython-multiaxis
So far, the max frequency I can rich is around 17kHz. Is there a way to improve the step frequency?

Thanks,

OutoftheBOTS_
Posts: 790
Joined: Mon Nov 20, 2017 10:18 am

Re: Multiaxis stepper motors using RMT

Post by OutoftheBOTS_ » Tue Dec 31, 2019 6:31 am

Wow this is super interesting to me.

I play with steppers for many of my robotics projects. I having mainly found MP problematic for stepper pulse generation especially for multiple steppers so for many of my project I have had to use C and timers for interrupts to generate the step pulses.

I have many questions about your project but will try to keep from asking too many.

First can you generate pulses for many stepper(on separate pins) that are running at different speeds and different accelerations??

Does you code use real time calculations for acceleration profiles or do you use a lookup table??

User avatar
mattyt
Posts: 301
Joined: Mon Jan 23, 2017 6:39 am

Re: Multiaxis stepper motors using RMT

Post by mattyt » Wed Jan 01, 2020 4:41 am

OlivierLenoir wrote:
Mon Dec 30, 2019 10:13 pm
I've created a class to drive multiple stepper axis using RMT.
https://gitlab.com/olivier.len02/micropython-multiaxis
So far, the max frequency I can rich is around 17kHz. Is there a way to improve the step frequency?
You're only using RMT to send a single pulse at a time - see where you're sending a tuple of length one with DRV8825_mp?; this is effectively the same as toggling a regular Pin and is not an effective way to use RMT!

Instead, build up a tuple or list of durations. The length of the list will be the number of times the pin is toggled, the durations will determine how long the interval is between toggling the pins. Then bundle that list to RMT via write_pulses to send them accurately.

Note that the time of each duration is the period determined by the input clock (currently 80MHz) divided by an 8-bit clock_divider (you've specified '8' in your class). For example, if you use your current clock divider:

Code: Select all

r = RMT(0, pin=Pin(step_pin), clock_div=8)
r.write_pulses((5, 1) * 10)
With this clock divider, the duration's resolution is defined as: 1/(80MHz/8) = 1us. Since we're sending a stream of (5, 1, 5, 1...) step_pin will toggle 10 times - 5us high and 1us low. This is much faster than the 17KHz you're currently achieving since now all the timing sensitive activity happens in hardware. If write_pulses was sent the minimum durations for both high and low (ie r.write_pulses((1, 1) * 10)) this frequency would be 10MHz.

Reducing the clock_div to 1 will allow an 80MHz frequency (12.5ns resolution). Probably too fast for steppers but very convenient for some protocols.

Is that clearer?
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
Wow this is super interesting to me.
I thought you might like RMT. ;)
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
I play with steppers for many of my robotics projects. I having mainly found MP problematic for stepper pulse generation especially for multiple steppers so for many of my project I have had to use C and timers for interrupts to generate the step pulses.
RMT ought to make that unnecessary. Just build a step acceleration profile and ask RMT to send it.
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
First can you generate pulses for many stepper(on separate pins) that are running at different speeds and different accelerations??
There are eight RMT channels (0-7, the first parameter passed to RMT at initialisation) and they can all have different clock dividers and be bound to different pins. They can all be run simultaneously though there's currently no way in MicroPython to accurately synchronise them (to start at exactly the same time for example). They just start - and operate in a non-blocking fashion - when you call write_pulses.
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
Does you code use real time calculations for acceleration profiles or do you use a lookup table??
I'll leave that as an exercise for the reader. ;) Do note though that write_pulses can't accept a generator, it must be a tuple or list (I need to know the length of the data in the C module before iterating over it).

If any of that is still not clear please let me know. :)

Oh, finally, I'm working on the next iteration of RMT for MicroPython that will expose a few more features - in particular, different ways to specify the bit pattern (effectively using the same three methods that PyCom implemented). That's currently happening on my esp32_rmt2 branch on my fork. I've been posting updates in the MicroPython slack channel so it may be best to join there if you're interested.

OutoftheBOTS_
Posts: 790
Joined: Mon Nov 20, 2017 10:18 am

Re: Multiaxis stepper motors using RMT

Post by OutoftheBOTS_ » Wed Jan 01, 2020 7:22 am

mattyt wrote:
Wed Jan 01, 2020 4:41 am
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
Wow this is super interesting to me.
I thought you might like RMT. ;)
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
I play with steppers for many of my robotics projects. I having mainly found MP problematic for stepper pulse generation especially for multiple steppers so for many of my project I have had to use C and timers for interrupts to generate the step pulses.
RMT ought to make that unnecessary. Just build a step acceleration profile and ask RMT to send it.
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
First can you generate pulses for many stepper(on separate pins) that are running at different speeds and different accelerations??
There are eight RMT channels (0-7, the first parameter passed to RMT at initialisation) and they can all have different clock dividers and be bound to different pins. They can all be run simultaneously though there's currently no way in MicroPython to accurately synchronise them (to start at exactly the same time for example). They just start - and operate in a non-blocking fashion - when you call write_pulses.
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
Does you code use real time calculations for acceleration profiles or do you use a lookup table??
I'll leave that as an exercise for the reader. ;) Do note though that write_pulses can't accept a generator, it must be a tuple or list (I need to know the length of the data in the C module before iterating over it).

If any of that is still not clear please let me know. :)

Oh, finally, I'm working on the next iteration of RMT for MicroPython that will expose a few more features - in particular, different ways to specify the bit pattern (effectively using the same three methods that PyCom implemented). That's currently happening on my esp32_rmt2 branch on my fork. I've been posting updates in the MicroPython slack channel so it may be best to join there if you're interested.

OK now I am super super interested it appears that RMT should be capable of driving steppers very well.


OK a python function can simply generate the step timing needed for the acceleration write it to a list then pass it to the RMT to carry out.

Next questions I assume there is a max length that the list can be??

Quite often the stepper will be needing to move long distances and with micro stepping the number of pulses will end up being too long to hold in 1 list. Is there a way to prepare a next list that when the RMT finishes a list then it moves on to the next list, this way I can buffer infront of the current list and keep the pulses coming for as long as I want??

Another smaller but harder question, can an odometer be added so that every time the RMT sends a step pulse it increments a counter??

User avatar
mattyt
Posts: 301
Joined: Mon Jan 23, 2017 6:39 am

Re: Multiaxis stepper motors using RMT

Post by mattyt » Wed Jan 01, 2020 8:56 am

OutoftheBOTS_ wrote:
Wed Jan 01, 2020 7:22 am
OK now I am super super interested it appears that RMT should be capable of driving steppers very well.
It's one of the use cases I hope it will prove useful for. Driving Neopixels is another...
OutoftheBOTS_ wrote:
Wed Jan 01, 2020 7:22 am
OK a python function can simply generate the step timing needed for the acceleration write it to a list then pass it to the RMT to carry out.

Next questions I assume there is a max length that the list can be??
Currently the data passed to write_pulses must be contiguous and in-memory. In the background the ESP-IDF will manage sending smaller chunks of that data to the RMT module (which has finite memory to work with). I haven't analysed what the limits are here; I'd appreciate you trying it out!

It would be possible to be more memory efficient but I'm not yet sure if those improvements will be worth the (not insignificant!) effort. I am looking at providing a callback that would fire when the RMT has finished sending your data - but my guess is that it's likely to be a little too late for continuous transmission.

I'd definitely consider ways to improve RMT to support continuous tx...
OutoftheBOTS_ wrote:
Wed Jan 01, 2020 7:22 am
Another smaller but harder question, can an odometer be added so that every time the RMT sends a step pulse it increments a counter??
Yes, I suppose that would be possible; what are you thinking?

OutoftheBOTS_
Posts: 790
Joined: Mon Nov 20, 2017 10:18 am

Re: Multiaxis stepper motors using RMT

Post by OutoftheBOTS_ » Wed Jan 01, 2020 11:54 am

mattyt wrote:
Wed Jan 01, 2020 8:56 am

I'd definitely consider ways to improve RMT to support continuous tx...
OutoftheBOTS_ wrote:
Wed Jan 01, 2020 7:22 am
Another smaller but harder question, can an odometer be added so that every time the RMT sends a step pulse it increments a counter??
Yes, I suppose that would be possible; what are you thinking?
First thanks for working on the RMT peripheral as it is super usefull not only for stepper but all sort of non standard serial coms e.g neopixels

There is 2 basically 2 types of operations that I usually do with steppers (same sort of operations done with servo motors)

1. go to a goal position with a ramp up and ramp down with a max speed.
2. ramp up to a certain speed then hold that speed until told to ramp to a new speed.

In type 1 operation you sort of always know the absolute position of the stepper at the end of the operation because it should be at the goal position but it is nice to be able to check everything is good or if you stop half way though know what position u stopped at.

In type 2 operation it is way more common to want to know how many steps you have done (absolute position of the stepper) because it isn't going to a set goal but rather spinning at certain speed.

With the current system that I am using (standard system by CNC machines) I use a timer and set the ARR to the timing that is needed for the step pulse than have a interrupt that fires when it reaches the ARR count and in that interrupt it calculates the timing needed for next step pules based upon global variables (desired speed and ramp) and updates the ARR and also increments or decrements the odometer based upon the state of the dir pin.

User avatar
OlivierLenoir
Posts: 32
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Re: Multiaxis stepper motors using RMT

Post by OlivierLenoir » Wed Jan 01, 2020 1:13 pm

OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
Wow this is super interesting to me.
First can you generate pulses for many stepper(on separate pins) that are running at different speeds and different accelerations??
You can have up to 8 axis with RMT and as many we want if I don't use RMT. Each axis will then be added in the multiaxis.
I'm using DRV8825 as stepper motor driver. This driver require DIR pin and STEP PIN, other pin will be implemented using I2C IO extension.

Code: Select all

from multiaxis import Axis, MultiAxis

x = Axis(dir_pin=15, step_pin=2)
y = Axis(dir_pin=0, step_pin=4)
z = Axis(dir_pin=16, step_pin=17)

cnc = MultiAxis(x, y, z)

cnc.usleep = 200000
cnc.g01(100, -200, 11)
cnc.usleep = 2000
cnc.g01(117, -170, 60)
cnc.g01(100, -200, 17)
OutoftheBOTS_ wrote:
Tue Dec 31, 2019 6:31 am
Wow this is super interesting to me.
Does you code use real time calculations for acceleration profiles or do you use a lookup table??
So far, acceleration is not implemented.
Roadmap will be:
  • Optimize MultiAxis.g01 using mattyt recommendation
  • Implement circular interpolation MultiAxis.g02 and MultiAxis.g03
  • Manage acceleration
  • Manage mechanic conversion
  • Finally parse GCode file

User avatar
OlivierLenoir
Posts: 32
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Re: Multiaxis stepper motors using RMT

Post by OlivierLenoir » Wed Jan 01, 2020 2:53 pm

mattyt wrote:
Wed Jan 01, 2020 4:41 am

Code: Select all

r = RMT(0, pin=Pin(step_pin), clock_div=8)
r.write_pulses((5, 1) * 10)
With this clock divider, the duration's resolution is defined as: 1/(80MHz/8) = 1us. Since we're sending a stream of (5, 1, 5, 1...) step_pin will toggle 10 times - 5us high and 1us low. This is much faster than the 17KHz you're currently achieving since now all the timing sensitive activity happens in hardware. If write_pulses was sent the minimum durations for both high and low (ie r.write_pulses((1, 1) * 10)) this frequency would be 10MHz.
I've run the following test with the required min pulse step duration of 2µs required by the DRV8825 stepper driver.
  • Works fine for 1000 pulses but I'm getting a memory allocation error when trying to do 10000 pulses.
  • s.write_pulses((2, 100000) * 1000) on time is 2µs, but off time is 1700µs (measured with an oscilloscope) instead of the 100ms expected
Did I miss something? What is your recommendation to prevent memory allocation when doing a lot of pulses?

Code: Select all

MicroPython v1.12 on 2019-12-20; ESP32 module with ESP32
Type "help()" for more information.
>>> from machine import Pin
>>> from esp32 import RMT
>>> s = RMT(0, pin=Pin(2), clock_div=80)
>>> s.write_pulses((2, 1000) * 1000)
>>> s.write_pulses((2, 10000) * 1000)
>>> s.write_pulses((2, 100000) * 1000)
>>> s.write_pulses((2, 10000) * 10000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError: memory allocation failed, allocating 80008 bytes
>>> 

User avatar
Roberthh
Posts: 2144
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Multiaxis stepper motors using RMT

Post by Roberthh » Wed Jan 01, 2020 3:24 pm

The longest pulse is 32768 time units. Using a clock prescaler of 80 this is 32768 ms. 100000 % 32868 is about 1700, and that is what you got.

OutoftheBOTS_
Posts: 790
Joined: Mon Nov 20, 2017 10:18 am

Re: Multiaxis stepper motors using RMT

Post by OutoftheBOTS_ » Wed Jan 01, 2020 8:26 pm

Roberthh wrote:
Wed Jan 01, 2020 3:24 pm
The longest pulse is 32768 time units. Using a clock prescaler of 80 this is 32768 ms. 100000 % 32868 is about 1700, and that is what you got.
This really means we need a way to preload (buffer) the next list of pulses to be loaded to RMT. I assume this feature will be needed to be added at the C level. A C level interrupt that sends the next buffer to RMT then triggers a python level call back for python to setup the next buffer.

Post Reply