An Electronic Leadscrew Controller using a Pi Pico

Tons of conversation, cool, the links are sweet!

I tested driving a spare stepper with an a4988. Worked as expected. Next, tested adding that simple "one step" code to the PIO micropython encoder reading example I got working earlier. Just in a naive 1 pulse = 1 step manner. Worked, but as expected with a 125MHZ clock running on one state machine to monitor the encoder, the encoder could be turned faster than the stepper could keep up with generating pulses 1:1, it seemed.

Again, just to get familiar since this is my first foray with PIO, I separated the encoder reading assembly into its own state machine, running at 125MHZ. I created a second SM at 125KHZ with a wait(1, irq, 1) to test limiting the max step rate of the stepper to something I had previously found reasonable. I substituted the pulse step gen from the encoder detector assembly with a raise IRQ 1, which the second PIO SM picked up on. One direction only. No specific microstepping selected/config'd.

Just for playing around and making sure I could get the requisite hardware doing something somewhat "sane," things seem to work well enough, video:

I had previously tested adding a variable delay to the simple stepper functionality test, to delay X cycles before a step is taken based on a value pushed into the register. This also seemed to work as expected, but I didn't test extensively. Next I'm going to try adding that to this version, to do a simple 1 : X integer ratio of real-time encoder pulses to stepper pulses. Micropython isn't doing anything aside from activating the state machines and setting initial values.

I'm happy to go through experimenting and realizing a few "wrong approaches" as long as I'm tinkering and interested. As to why not just use an esp32, I'm liking the straightforwardness of PIO stuff and just learning while having fun. It's great to know I could use one of the esp32s I have laying around (ESP32-CAMs, actually, which I managed to install an esp32 implementation of wireguard to a while back while retaining webcam server functionality, so it could join an external VPN which I could monitor remotely, but that's another topic)! I could also just as well spend time setting up the linuxcnc installation I have going on a spare computer and do some rudimentary ELS stuff with that, since I found a module there. Tbh it might be a better investment of time, given all the functionality built in. But definitely not the point or my interest this exact second, I'm just exploring and it's really great to see the same thing implemented on many different uCs. It opens things up to people that might only have what's on hand, gets people chatting and thinking, etc. I mean heck, when I saw that MOV EXEC was a function and could read pins or data and exec that from external sources, I was kinda excited that I could potentially use two cheap picos to do things in tandem for fun.

Regarding simplicity, it seems seems inexorable that people have widely different definitions of it. That's kinda fun, too. It definitely seems evident from the discussion of ELS and just thinking about what the implications are for timing and control isn't something that instantly resolves itself in my head. Like, there's an implication that if a x:x ratio of encoder to stepper pulses is performed, just that this loop is running at a given clock dictates a step speed. "Doing things" in time relates to a velocity, and the discussion around velocity/position/precision needed is interesting. I'm ignorant to all but the basics of lathe stuff anyhow, so my mental concept of "good and responsive enough, assuming one only has to compensate for slight spindle bogging down from tool pressure" versus "threads will have this amount of slop acceptable in control scheme until they are out of spec" is kinda nonexistent.

I'm happy to try to re-familiarize myself enough with C to implement things in C if it comes to that point. The jump table encoder implementation with error handling that is in C seems like it wouldn't be difficult to use with my current experiment in micropython, but that jump table version is running short on spare instruction space. Not sure what 1/X variable electronic gear ratio gets me at this point (if anything useful), since I haven't thought about gearing/pitch/etc yet.
Well done! Glad to see you going at it. Don't think that the platform is limiting you at this point, so keep on going. Remember that on a lathe, with a chuck on it, (and the part) there's quite a bit of rotational inertia. What that means is the angular acceleration isn't that great. So the changes are not instantaneous. If your encoder (and platform) can keep up with smoothed changes, then you are golden.

As you found out, steppers won't start instantaneously at high rates of speed, but if they are following the load (the spindle) even from a dead stop to full speed they will and do come up to speed. Personally, I'd recommend a closed loop stepper, so if the stepper falls behind, it automatically catches up. I use a closed loop stepper and have had excellent results with it. I didn't use a stepper library with controlled acceleration at all. Heck, I used no library, just directly pulsed the stepper driver. I could get away with that because of the relatively high moment of inertia of the headstock and chuck. It wasn't naivete that lead me to this, but more the physics. If you look at what's in your system, you can often make good trade offs that dramatically reduce the system complexity and cost. Why over design when it isn't necessary?

A variable (really fixed) ratio on the stepper is often used to give you more torque to move the lead screw. There's no free lunch though, you have to spin the stepper faster to make up for the gear ratio. Most times, it is to your advantage. It may allow you to use a slightly smaller stepper. I sized my stepper for a 1:1 drive ratio (used a 4 Nm stepper) and it works fine on my 10 x 22.
 
Tons of conversation, cool, the links are sweet!

I tested driving a spare stepper with an a4988. Worked as expected. Next, tested adding that simple "one step" code to the PIO micropython encoder reading example I got working earlier. Just in a naive 1 pulse = 1 step manner. Worked, but as expected with a 125MHZ clock running on one state machine to monitor the encoder, the encoder could be turned faster than the stepper could keep up with generating pulses 1:1, it seemed.

Again, just to get familiar since this is my first foray with PIO, I separated the encoder reading assembly into its own state machine, running at 125MHZ. I created a second SM at 125KHZ with a wait(1, irq, 1) to test limiting the max step rate of the stepper to something I had previously found reasonable. I substituted the pulse step gen from the encoder detector assembly with a raise IRQ 1, which the second PIO SM picked up on. One direction only. No specific microstepping selected/config'd.

Just for playing around and making sure I could get the requisite hardware doing something somewhat "sane," things seem to work well enough, video:

I had previously tested adding a variable delay to the simple stepper functionality test, to delay X cycles before a step is taken based on a value pushed into the register. This also seemed to work as expected, but I didn't test extensively. Next I'm going to try adding that to this version, to do a simple 1 : X integer ratio of real-time encoder pulses to stepper pulses. Micropython isn't doing anything aside from activating the state machines and setting initial values.

I'm happy to go through experimenting and realizing a few "wrong approaches" as long as I'm tinkering and interested. As to why not just use an esp32, I'm liking the straightforwardness of PIO stuff and just learning while having fun. It's great to know I could use one of the esp32s I have laying around (ESP32-CAMs, actually, which I managed to install an esp32 implementation of wireguard to a while back while retaining webcam server functionality, so it could join an external VPN which I could monitor remotely, but that's another topic)! I could also just as well spend time setting up the linuxcnc installation I have going on a spare computer and do some rudimentary ELS stuff with that, since I found a module there. Tbh it might be a better investment of time, given all the functionality built in. But definitely not the point or my interest this exact second, I'm just exploring and it's really great to see the same thing implemented on many different uCs. It opens things up to people that might only have what's on hand, gets people chatting and thinking, etc. I mean heck, when I saw that MOV EXEC was a function and could read pins or data and exec that from external sources, I was kinda excited that I could potentially use two cheap picos to do things in tandem for fun.

Regarding simplicity, it seems seems inexorable that people have widely different definitions of it. That's kinda fun, too. It definitely seems evident from the discussion of ELS and just thinking about what the implications are for timing and control isn't something that instantly resolves itself in my head. Like, there's an implication that if a x:x ratio of encoder to stepper pulses is performed, just that this loop is running at a given clock dictates a step speed. "Doing things" in time relates to a velocity, and the discussion around velocity/position/precision needed is interesting. I'm ignorant to all but the basics of lathe stuff anyhow, so my mental concept of "good and responsive enough, assuming one only has to compensate for slight spindle bogging down from tool pressure" versus "threads will have this amount of slop acceptable in control scheme until they are out of spec" is kinda nonexistent.

I'm happy to try to re-familiarize myself enough with C to implement things in C if it comes to that point. The jump table encoder implementation with error handling that is in C seems like it wouldn't be difficult to use with my current experiment in micropython, but that jump table version is running short on spare instruction space. Not sure what 1/X variable electronic gear ratio gets me at this point (if anything useful), since I haven't thought about gearing/pitch/etc yet.
You might want to checkout the remora project, there are several versions but the most interesting is to take a printer board like a SKR 2.0 or a stm32f4 and use it as the IO for linuxcnc via i2s to a rasperry pi 3/4 https://github.com/scottalford75/Remora
 
Well done! Glad to see you going at it. Don't think that the platform is limiting you at this point, so keep on going. Remember that on a lathe, with a chuck on it, (and the part) there's quite a bit of rotational inertia. What that means is the angular acceleration isn't that great. So the changes are not instantaneous. If your encoder (and platform) can keep up with smoothed changes, then you are golden.

As you found out, steppers won't start instantaneously at high rates of speed, but if they are following the load (the spindle) even from a dead stop to full speed they will and do come up to speed. Personally, I'd recommend a closed loop stepper, so if the stepper falls behind, it automatically catches up. I use a closed loop stepper and have had excellent results with it. I didn't use a stepper library with controlled acceleration at all. Heck, I used no library, just directly pulsed the stepper driver. I could get away with that because of the relatively high moment of inertia of the headstock and chuck. It wasn't naivete that lead me to this, but more the physics. If you look at what's in your system, you can often make good trade offs that dramatically reduce the system complexity and cost. Why over design when it isn't necessary?

A variable (really fixed) ratio on the stepper is often used to give you more torque to move the lead screw. There's no free lunch though, you have to spin the stepper faster to make up for the gear ratio. Most times, it is to your advantage. It may allow you to use a slightly smaller stepper. I sized my stepper for a 1:1 drive ratio (used a 4 Nm stepper) and it works fine on my 10 x 22.
Yeah! It's super interesting how the physical dynamics of the system and sampling or Stepgen rate all contribute to smoothing or jitter.

It's also a bit difficult to reason about dead simply and know exactly what will happen intuitively due to physical damping, inertia, response time, gearing, microsteps, etc. I almost want to plot out the different frequencies of activities, including gear ratios, to see what timings look like physically -- how things are getting sliced up and acted on in parallel. Pretty fun to just play around and see what works.

Thanks a bunch for the specific closed stepper torque recommendation. I have a 9x20, so 4nm that should be sufficient too. I was already looking at 4nm cl steppers for my printnc (https://wiki.printnc.info/en/home diy cnc I built one of), so having it around was in the cards for this or gear experimentation.

Thanks for the remora stuff! I had heard of that before, but forgot. The world of diy cnc control hacking ks great, I almost wish there was a collaborative design effort forum for cnc hardware hacking where everyone got together and maybe focused some distilled efforts on community projects. I've been using a grblhal2k board from an awesome printnc member for my printnc cnc control, but he's since released a flexihal which works with Linuxcnc and the pico Hal is something he to allow custom python scripting of cnc peripheral control, and another thing on the way with encoder/glass scale input
 
Last edited:
Shifting ELS "gears" only when the spindle is stationary and allowing the system to accelerate and decelerate together keeps things simple, and is important for correct thread cutting.

Feeding could be handled differently but most implementations that I have seen use the same code with a different set of rates. One technique I considered is for feeding to use constant denominators in that set of gearing ratios and just vary the numerator which could be done in small steps while in motion to allow the convenience of changing feed rates without stopping and without adding a lot of complexity to the code.
 
Shifting ELS "gears" only when the spindle is stationary and allowing the system to accelerate and decelerate together keeps things simple, and is important for correct thread cutting.

Feeding could be handled differently but most implementations that I have seen use the same code with a different set of rates. One technique I considered is for feeding to use constant denominators in that set of gearing ratios and just vary the numerator which could be done in small steps while in motion to allow the convenience of changing feed rates without stopping and without adding a lot of complexity to the code.
Agree with your first paragraph. With a normal lathe (without a synchronizer automatic transmission) would you ever shift gears while in motion? No. Why over complicate a system. KISS.

Change your feed on the fly? Maybe, but in your normal lathe could you do that? Probably not. I'd probably break a gear doing that. So why implement such a system (when you are just starting out) electronically? Once again, KISS.

That way, you will actually end up with a usable system within 6 months or so. Make it too complicated without the relevant experience, this is the formula for a never finished project...
 
I agree, I wouldn't necessarily start with that, it is something I considered as an enhancement and that certain approaches would simplify adding that feature. I generally use the Unix philosophy - implement something basic to build a good foundation and improve it. Being able to change the feed rate without coming to a complete stop would be a useful feature and it is not difficult to do if it is properly constrained. Just like chess, good software planning is usually thinking a few moves ahead so that the structure is able to accept the improvements with minimal rework. For my project in my perspective the software and electronics are quite straightforward, the hardest part of the project is interfacing it to the physical lathe, and finding the time to actually work on it and take the lathe offline.
 
Some more fun progress today!

I ordered a 4NM closed loop stepperonline stepper.

I also was able to get an integer numerator working as a basic test, I believe.

Basic scheme: encoder pulse raises IRQ. Numerator multiplication factor waits for IRQ, then raises new IRQ for step routine X number of times. Step routine waits for multiplication factor IRQ and generates a pulse every Y clock cycles the multiplication IRQ is raised, clocked at appropriate stepper clock rate. Again, PIO is handling everything and micropython is only setting things up.

I have tested it and it seems to work as expected, though obviously I can't test edge cases yet (and probably doesn't make much sense to get too deep into things until the closed loop stepper is here and I can make observations + calculations).

Here's the awfully rough code snippet, with the caveat that I'm just using a two dead simple pulses just to get things working with the a4988 and nothing has been optimized or been given much thought, just getting something to improve on. Pls be nice about any messiness :)

Python:
#encoder PIO block omitted, used the jamon example and added irq(1) to raise irq on encoder increment


@asm_pio(set_init=rp2.PIO.OUT_LOW)
def numerator_handler():
    set(x, 1) # numerator -1
    wait(1, irq, 1) #IRQ 1 from encoder state machine, encoder pulse received
    label("multiply")
    irq(block, 5)
    jmp(x_dec, "multiply") #generate more step IRQs until X is 0

@asm_pio(set_init=rp2.PIO.OUT_LOW)
def stepit():
    set(y, 0)  # (denominator -1, wait 3 encoder pulses before one step pulse next instruction
    jmp(1,"stepwait")
    label("divide_gear")
    label("stepwait")
    wait(1, irq, 5)
    jmp(y_dec, "divide_gear") #keep waiting until y is 0, then generate step
    set(pins, 1)   [31]
    nop()          [31]
    set(pins, 0)   [31]
    nop()          [31]
    wrap()

sm1 = StateMachine(1, encoder, freq=125_000_000, in_base=Pin(0), jmp_pin=Pin(1))
sm2 = StateMachine(0, stepit, freq=125_500, set_base=step) # 125khz, an appropriate frequency that stepper can respond to
sm3 = StateMachine(3, numerator_handler, freq=125_000_000 , set_base=step)
direction.value(1)
sm2.put(x_delay)

sm2.active(1)
sm1.active(1)
sm3.active(1)

Basically, it seems to work as expected. Setting the numerator and denominator values to "1/1" gearing (aka 0/0 for actual x and Y values since we start counting at 0), I get ~5.25 turns for every rotary encoder turn. Changing it to 3/4 gearing I get ~4 turns. Changing it to 2/3, I get ~3.5. 1/2 I get 2.5. to 2/1 I get 10.5 turns, etc etc. Seems I should be able to start with anything 0-32/0-32 for a ratio, barring other dynamics becoming a limiting factor.

Hope I can do something fun/rudimentary with it!
 
Last edited:
Interesting experiment. Programming PIOs is a bit strange, I'm still learning about that. I suppose the question here is if the necessary ratios will be too limiting to the usable encoder rates. But an interesting simple approach!

Awhile back I made some precise measurements of my lathe to map out some of the gearing, and this led to finding that one wrong gear was installed at the factory. It was off by one tooth out of 90, and that's only one in a cascade of gears needed to do the job. Thread cutting requires surprising precision with large ratios, especially imperial/metric work where an imperial lathe is used for metric threading or vice versa.
 
Yeah, I'm very curious about my gearing and what things would truly divide out into via the qcgb. I did see (I think you?) Discussing characterizing gearing with an encoder earlier in the thread and even potentially measuring that as a setup process to calculate available ratios.

I have to go out and take the encoder + a stepper to the leadscrew to see what 1rev equals in feed at the different gears soon. One issue I'm coming up against is figuring out belt specifics. It seems like an HTD 3M belt is good, but not sure if it matters much in the range of choosing between 8-12mm width, if there are any more readily available, and finding some pulleys with an appropriate bore or undersized (to drill out) bore. If anyone has any suggestions on US suppliers or just a reasonable sizing, lmk please!

Also in line with rates/gearing, here's a photo of my lathes gear box. I am planning on removing the one pulley with an X in it, then I could have the option of experimenting with whatever input pulse rate per turn I'd like. I guess I didn't need to mark out that nany potential encoder measurement points, since I can 3d print whatever ends up being needed. Purple is motor area, generally anywhere.

I think this might be fun enough to get me going somewhere and inspired to do more if I want it. I am happy with starting with variable power feed with no physical gear change, then seeing what thread options happen to line up with the gearing math or could be made to line up with adjustments to certain input frequencies. The only thing I eventually would "need" to thread is m39x4, the very coarse spindle nose thread, so I can mount a different chuck, but it's not a priority. Otherwise I'm happy with whatever lines up to start
 

Attachments

  • jet_feed_gear_1~2.jpg
    jet_feed_gear_1~2.jpg
    104.2 KB · Views: 2
I have a counter on the spindle that I use for winding line on reels or wire on coils so it is easy to do say 100 rotations, and the DRO will read out the carriage motion. So I can make a complete set of measurements in all the gearbox selections to verify the gearing ratios in the machine. I need to swap in the correct gear which finally arrived before starting that project. I have a 91 tooth gear marked 90 that came installed from the factory so I halted the measurement project and they were out of that gear for a couple months. It certainly doesn't matter for feeding, so I haven't bothered. I rarely cut threads on the lathe, but I want it to be accurate of course (I could just compensate with the ELS but would rather get the proper gear installed). There are 15 gearbox settings, and the two different ratios for feeding and threading, plus one more rate for the cross slide. So about 45 different ratios built into the machine, not including change gears. I would want all 45 of those ratios in the UI so I could use any of them. The UI should also calculate and advise the RPM limits and other useful info (perhaps a torque multiplication factor) to help guide the operator in selecting the optimal gearbox ratio for an operation, while setting the ELS ratio appropriately to compensate for the gearbox. For example that would allow any of the 15 gearbox settings to be used for a particular threading operation, however the RPM limits would be different, and the torque multiplication would be different. Some choices would be RPM limited and some would be torque limited, and thus less than ideal gearbox choices. Threading normally is done at low RPM so the gearbox selection can be optimized for torque, which also has the effect of minimizing the effective motor step motion. However when cutting a really coarse thread some gearbox settings may not be feasible at all since the overall ratio needs to reach the thread pitch with fewer motor steps than encoder pulses, at least for the algorithm that I'm planning to start with. If the UI allows the operator to select the thread they want to cut it can then show which gearbox settings are valid for that operation, and the operator can select which they want to use and let the UI know what they have selected so it can set up the appropriate ratio in the encoder to stepper electronic drive subsystem.
 
Back
Top