Open ktritz opened 2 years ago
Thanks for the API writeup. We generally recommend using a PCA9685 or similar to drive steppers, since it takes care of doing this without any timing difficulties. You need motor driver power components anyway, so the PCA9685 is the least of it. What are your reasons for preferring to do it directly?
As far as I can tell, the PCA9585 CP library doesn't really handle any of the specific things I'm looking for, such as generating a precise # of pulses, smooth frequency acceleration/deceleration, and monitoring of limit IO to stop motion. Is there something I'm missing? If I need to separately modify and time the PWM frequency for acceleration, and separately monitor the limit IO, and then communicate over I2C to the PCA9585, that makes my problem even worse with regard to precision stepping.
Typically, we're using standalone stepper drivers which take digital IO for the step/dir/enable control (but don't have limit IO). We're also using a range of steppers from smaller NEMA17 to massive 220V 7A NEMA52s, so a generic stepper motor hat/shield won't quite cut it.
This might be something the seesaw could do well with the proper code.
I understand. Yes, an enhanced seesaw could do something like this. Have you looked at the Tic stepper controllers from Pololu? https://www.pololu.com/category/212/tic-stepper-motor-controllers https://www.pololu.com/docs/0J71
Perhaps a CircuitPython library that talks to them would be useful.
Another interesting possibility would be to use the RP2040 PIO capability to write the short control programs you would need. You can create and run PIO programs from CircuitPython.
Yeah, the Pololu controllers would work, though they are considerably more expensive than the seesaw. They could replace our external stepper drivers for some of our steppers, and we could use the step/dir outputs for the NEMA52 drivers. The Status: Rationed and low stock is a bit concerning :) Not sure of their long term reliability.
I have thought about the RP2040 PIO, and it's a possibility I think, though it's a bit tricky to get the right math for smooth acceleration/deceleration. If JMP on PIN works, that at least could monitor limit switch IO. I've done simple PIO stuff, but I'm not sure if there are enough registers to handle all of the functionality. Come to think of it, JMP on PIN could allow two state machines to talk to each other, no? One could be in charge of counting the steps, and one could be in charge of the time period between steps to handle the acceleration and velocity.
+1 vote for wanting a circuitpython stepper library
I don't know if this is helpful but https://www.airspayce.com/mikem/arduino/AccelStepper/ and https://github.com/luni64/TeensyStep are two libraries that I have used in the past that work well for Arduino.
Interesting PIO stepper links: https://forums.raspberrypi.com/viewtopic.php?t=300874 https://www.youtube.com/watch?v=UJ4JjeCLuaI https://vanhunteradams.com/Pico/Steppers/Lorenz.html https://github.com/DasenB/PicoStepper http://people.ece.cornell.edu/land/courses/ece4760/RP2040/index_rp2040_C.html https://forum.micropython.org/viewtopic.php?t=10199&p=56656
a user contribution library would be welcome!
Just curious, would any of this lead to being able to use the incredibly common "StepStick" drivers that are frequently sold/utilized with 3D Printers?!
I started on a similar path of discovery last night- pwmio + timing resulted in a significant deviation over time, as expected. PulseIO was... better, but the library's API is kind of awkward for this usage and you still missed/over steps.
Fortunately, I can just use digitalIO to toggle a pin on the rp2040 at 500hz which is sufficiently fast for my applications, but not ideal- particularly as I want to do sensor measurement along the movement of the stepper, and throwing blocking i2c reads into the loop is going to be painful. To that end, an async interface is badly needed- particularly given circuitpython's no-threading. A callback that could be scheduled to happen every say, N rotations would be welcome.
I've started playing with PIO for this personally, in part because it looks like it allows me to escape the single-thread issue.
Also, IRT external stepper controllers like the pololu boards- while these would appear to work, a 50$ premium for a stepper controller when I have a microcontroller that should be quite happily capable of doing the operation itself is more than a little frustrating- and if I wanted to use existing common 3d printer hardware is a bit of a no-op. Observationally, there don't appear to be many stepper-controller-controller boards out there either that break out things like the TMC4361 for an affordable price.
PIO would probably be the easiest solution, but of course not generalizable to other boards. A C library like pulseio, but specifically written for stepper control would be the most useful and should be able to handle async calls with limit switch I/O as well.
On Mon, Jun 13, 2022 at 12:05 PM Vexs @.***> wrote:
I started on a similar path of discovery last night- pwmio + timing resulted in a significant deviation over time, as expected. PulseIO was... better, but the library's API is kind of awkward for this usage and you still missed/over steps.
Fortunately, I can just use digitalIO to toggle a pin on the rp2040 at 500hz which is sufficiently fast for my applications, but not ideal- particularly as I want to do sensor measurement along the movement of the stepper, and throwing blocking i2c reads into the loop is going to be painful. To that end, an async interface is badly needed- particularly given circuitpython's no-threading. A callback that could be scheduled to happen every say, N rotations would be welcome.
I've started playing with PIO for this personally, in part because it looks like it allows me to escape the single-thread issue.
— Reply to this email directly, view it on GitHub https://github.com/adafruit/circuitpython/issues/6444#issuecomment-1154106638, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIR6YKZQ6BQ7AEXJC6JIM3VO5L4NANCNFSM5XHOYXSA . You are receiving this because you authored the thread.Message ID: @.***>
Ok, I have some alpha code that does step accurate stepper control using the RP2040 PIO. Right now the state machine outputs steps to a stepper driver with the option of DIR control using either the state machine or manually with digitalIO. There is an option to enable a counter by tying the output to another pin, which can independently verify step counts. Finally, a jmp_pin can be configured to act as a limit switch which will immediately halt the step output (and require a state machine reset).
The files in the lib directory are what goes on the MCU. You can combine the files in the pc and lib directory on a PC to test and plot the step generation. Right now there are two acceleration curves available, a linear ramp and a cos-based S-curve for lower jerk acceleration. The S-curve has more overhead, so there's a small cache to speed up step generation when parameters aren't changing. Feedback is welcome, and I'm going to keep tweaking it.
hi I had the same issue and thought I leave my rough step dir pio here.
this pio is able to vary step frequency and endposition while stepping. So one does not have to wait for it to reach its target.
the current setup is a 32bit input with a delay in the 21msbs and an endposition in the 11lsbs.
Like eg. Delay of 100 and endpos of 100 (1100100 bin)
000000000000001100100 00001100100
suboptimal limits and quirks; for once I tried to keep the step period constant, with label compensate
, this requires endpositions to have a 1 lsb, alias only odd endpositions.
second the delay and endpos share a 32bit limit. at least this can be partitioned relativ freely by changing instruction 2,4.
furthermore the code is not too nice. Im happy for any Tipps or suggestions.
stepdir = adafruit_pioasm.assemble( """
.program stepdir
.side_set 1 opt
loop: ; requires 21delaybits 11endbits in this order
pull block ; 1 pull waits on fresh tx fifo, also here init populates inital y/curpos
out x 21 ; 2 afterwards osr/11endbits-21bufferzeros, x/11bufferzeros-21delaybits
pause: ; pause for bussy cycles of delay count
jmp x-- pause [7] ; 3 x/delay non zero stay in pause and decr x/delay otherwise continue
prepfinddir: ; prep x/endpos for comparison, y/curpos already preped since init or later since finddir
out x 11 ; 4 afterwards osr/32bufferzeros, x/21bufferzeros-11endposbits
mov osr x ; 5 afterwards osr/endpos, also no race condition here since no auto pull
mov isr y side 0 ; 6 afterwards isr/curpos so y/curpos stays persistant while finddir, also side/setppin alias enter lowtime for >100ns
jmp x!=y finddir ; 7 y/curpos not x/endpos finddir otherwise nofinddir alias re loop
jmp preploop ; 8
finddir:
mov x osr ; 1 cycle osr/endpos isr/curpos with x/interim save
mov osr isr ; 2 shift msb bit of here osr/curpos out
mov isr x ; 3 either write carrypin 1 for curpos 0 alias perhaps posdir
out x 1 ; 4 or write carrypin 0 for curpos 1 alias perhaps negdir
mov pins ~x ; 5
mov x osr ; 6 cycle x/osr/isr
mov osr isr ; 7 shift msb bit of here osr/endpos out
mov isr x ; 8 and verifiy possible dir
out x 1 ; 9
jmp pin pssblposdir ; 10 curpos1 alias verify possible negdir
jmp x-- finddir ; 11 but endpos1 re finddir !!! x0 -> xfffff , x1 -> x0
jmp y-- goon [1] ; 12 but endpos0 found dir so decrement y/peristant curpos !!! if scratch Y non-zero, prior to decrement
goon:
set pins 0 ; 13 setpin/dirpin to negdir, also dirpin to steppin time > 20ns tmc2209 13.1
jmp compensate ; 14 nops for same time as posdir case
pssblposdir: ; curpos0 alias verify possible posdir
jmp !x finddir ; 15 but endpos0 re findir
mov y ~y ; 16 but endpos1 found dir so increment y/persitant curpos
jmp y-- proc ; 17 !!! if scratch Y non-zero, prior to decrement
proc:
mov y ~y ; 18
set pins 1 ; 19 setpin/dirpin to posdir, also dirpin to steppin time > 20ns tmc2209 13.1
compensate: ; per leftover bit in osr/endpos do nothing for 11instr, alos only odd endposses valid
out x 1 [4] ; 20 deplete osr/endpos bit after bit
mov x osr [4] ; 21 until osr/endpos zero alias depleted last odd bit then proceede
jmp x-- compensate ; 22 else x non zero stay in compensate
in y 32 side 1 ; 23 this stalls on full rx fifo, also y persists, also enter side/setppin alias enter hightime
preploop:
push noblock ; 9 explicit push solves auto push stall aboveus, also clears isr
""" )
carrypin = board.LED
setppin = board.SCK
dirpin = board.D24
#enablepin = board.D4
smout = rp2pio.StateMachine(
program = stepdir,
frequency = 0,
out_shift_right = False, # out shift left for finddir
in_shift_right = False, # in shift left for priming/init y/curpos, i32
first_in_pin = carrypin , # inpin is carrypin for i5, i10 of finddir
in_pin_count = 1,
jmp_pin = carrypin, # jmppin is carrypin for i10 of finddir
first_out_pin = carrypin, # outpin is carrypin for i5 of finddir
out_pin_count = 1,
initial_out_pin_state = 0,
first_set_pin = dirpin, # setpin is dirpin for i12, i16 of finddir
set_pin_count = 1,
initial_set_pin_state = 0,
first_sideset_pin = setppin, # sidepin is setppin
sideset_pin_count = 1,
initial_sideset_pin_state = 0,
sideset_enable = True,
)
smout.run(adafruit_pioasm.assemble("mov x null"))
smout.run(adafruit_pioasm.assemble("mov y null"))
smout.run(adafruit_pioasm.assemble("mov osr null"))
smout.stop_background_write()
time.sleep(1)
initpos = 10 # initialise/populate y/curpos with a value
for bit in range(initpos.bit_length() - 1, -1, -1):
print(f"{bit}: {(initpos >> bit) & 1}")
smout.run(adafruit_pioasm.assemble(f"set y {(initpos >> bit) & 1}")) # form msb to lsb shift bit per bit of initpos into isr
smout.run(adafruit_pioasm.assemble("in y 1")) # requires left in shift
smout.run(adafruit_pioasm.assemble("mov y isr")) # init y/curpos with isr
to send the stepper to an endposition
intdelay = 524636 # must fit in 21 bits with current setup
intendpos = 11 # must fit in 11 bits with current setup and has to be odd for compensation to work
buf = array.array( 'L', [((intdelay<<11)+intendpos)] )
sm.background_write(loop=buf)
Using CircuitPython to control stepper motors is currently pretty ugly. At the moment, I use PWMOut, precalculate the frequency steps needed to provide pseudo acceleration/deceleration, compute the time necessary for the total steps in each PWM window, sit in a timing loop while monitoring a digital input 'limit switch' and end up sorta close to the total number of steps I wanted. I realize that I may get better precision with PulseIO or AudioPWM, but they have their own issues. It would be pretty nifty if there was a C-level bit banging library that implemented a Stepper library with something like the following capability:
Stepper module:
Pins:
step_pin
(digital): each pulse advances the stepper one stepdir_pin
(digital, optional): pin state controls stepper directionenable_pin
(digital, optional): pin state enables stepper driverforward_limit
(digital, optional): activating pin stops forward motionreverse_limit
(digital, optional): activating pin stops reverse motionNote: ideally select active HIGH/LOW for each pin
Properties/Functions:
acceleration
(property) get/setvelocity_limit
velocity_limit
to 0scale_factor
mode
(property) get/setSTEP
: target position modeRUN
: async rotatation modeengaged
(property) get/setfailsafe
(property) get/setreset_failsafe
is not called withintime
is_moving
(function)RUN
mode or asyncSTEP
modeposition
(property) getscale_factor
(property) get/setmultistep
(property) get/setreset_failsafe
(function)failsafe
is active, reset the timeouttarget_position
(property) get/settarget_position_async
(property) setvelocity
(property) getvelocity_limit
(property) get/setRUN
modeset_limits
(function)False
|True
)get_limits
(function)forward_limit.value == forward_active