Another Electronic Lead Screw using the Pi Pico

GHamsz

Registered
Registered
Joined
Feb 13, 2024
Messages
32
So I got the bug, and finally decided to move forward with an ELS for my 55year old Craftsman 6" lathe. I've had it in the back of my mind for many years, as I just can't stand changing gears. A few similar threads here gave me the confidence to move forward. My concern has always been whether or not a stepper motor with its' discrete step sizes would provide the precision needed. Looks like at least a few folks have made it work. I was never concerned about the design, as in a previous life (I'm a retired EE), I worked in telecom as a hardware engineer, designing embedded processor boards and the associated firmware to run them. That should give you an idea of how far back that goes, as now a days, the hardware and firmware are handled by different groups/individuals. Since I was the guy that understood hardware and software, after a few years and projects, I ended up running the engineering department.

Enough about me, I've used the PI Pico to build a few projects, and I'm fascinated with the onboard programmable state machines (PIO). It's amazing how much you can get out of a handful instructions. The instruction space is limited to 32 instructions, but again, there's a lot you can do with it.

Jump ahead to the spoiler... I've got it working, and can machine common ANSI and Metric threads, and set feed rates from .0005 to well, anything I want, but have arbitrarily feed limited to .0625.

So what's the theory?
first, a major requirement for my method to work...
1) The feed rate must be such that there are always more encoder pulses than stepper steps. In other words, over any interval, there can never be more than one step between encoder pulses. To make this happen, you have to set the gearing of the encoder and stepper based on the greatest feed per revolution you want to support. I selected 5TPI as my maximum.

I decided to see If I could use the state machine to map the rotary encoder phase(s) into stepper pulses. After writing a few simulations I decided it was definitely doable if I could stuff the needed code into 32 instructions. My simulations proved that if you assume the encoder and stepper are perfectly aligned at count 0, meaning step 0 (call it S0) is at the perfect position for encoder pulse 0 (call it E0), that for standard ANSI and Metric threads, there are a finite number of encoder pulses such that at some count Sn, once again the stepper is perfectly aligned (well within .1% of a step size) with an encoder pulse En (see paragraph and pic below). This alignment that occurs at Sn is a point at which I can loop back to the beginning of my algorithm. Between pulse 0 and pulse n, I calculate the next step feed value, and when it lands between two encoder pulses, I create a step at the pulse the step rounds up or down to. I pic is worth a thousand words.

In the pic below, the top line represents encoder pulses, and the bottom line stepper pulses. The circled encoder pulses are positions where I decide to step the stepper motor. Notice that the encoder pulse selected (circled) is the pulse closest to the stepper's actual value. The position error is the difference between the circled encoder position and the actual stepper position, but it's so small, it's negligible. The error is less than half an encoder pulse, and for my case with stepper pulse resolution of .000156" (.0625 feed screw pitch / 400 steps per feed screw rev), the error is much less than the stepper resolution. Also notice that at the last step, the encoder position matches up with the stepper position. This is important because at this point, I can loop back and start from the beginning. This is the case I mentioned above that for ANSI and Metric threads I can find this point
EncoderStepper.jpg
My idea was to use a bitmap that had a one to one correspondence with encoder pulses. Once a feed has been specified, I'd calculate the bitmap (circled encoder positions above), and write it to a buffer that would be DMA'd to the state machine. The state machine would be triggered by the rotary encoder and for every trigger pulse it would load in the next bit in the bitmap, which would indicate whether or not a stepper pulse was needed.

So that's the basis for the design... In subsequent posts (if anyone is interested), I'll go into the state machine code and how I setup the DMA to keep the state machine fed.
 
Keep it up. This is interesting stuff.
 
Very interesting. I have a couple of picos on my desk (one for a camera control project) and thought the PIO would make a great encoder interface and the Pico a great ELS core. It has two fast processors for heaven's sake for about the cost of a couple of beers.

I'll be following! What is your user interface?
 
Very interesting. I have a couple of picos on my desk (one for a camera control project) and thought the PIO would make a great encoder interface and the Pico a great ELS core. It has two fast processors for heaven's sake for about the cost of a couple of beers.

I'll be following! What is your user interface?
The user interface is a four line LCD with three buttons. The buttons are for mode select (ANSI, Metric, UserDefinedFeed and Idle), an increment button and decrement button.
You basically select say ANSI, then bump feed from 5 to 56 TPI. The increment/decrement buttons move into fast advance mode If you hold them down for more than 500ms.
 
I've done several projects with the Pico or Pico W: an analyzer (voltage, current, amplitude, phase and noise) for my three phase converter (RPC), and the controller for my bridge crane are two machining related ones I've tackled. The PIO is rather fun to mess with, you can come up with some surprisingly nice programs in 5 or 6 lines of code. Interesting approach with the DMA/bitmap, I may eventually tackle an ELS as a way to get metric threading on a non-metric old-iron machine.

I keep meaning to move beyond the 4 line LCDs and tackle some of the graphics displays, but I fall back to using the wifi on the Pico W to interact with a computer for fancy stuff.

Anyway, more details along with a few pics are always fun, when you get a chance.
 
Check out this UI (and full ELS / Synchronized machining) It happened to come up in my default youtube page just now. There are some interesting ideas there to feed on.

 
I've done several projects with the Pico or Pico W: an analyzer (voltage, current, amplitude, phase and noise) for my three phase converter (RPC), and the controller for my bridge crane are two machining related ones I've tackled. The PIO is rather fun to mess with, you can come up with some surprisingly nice programs in 5 or 6 lines of code. Interesting approach with the DMA/bitmap, I may eventually tackle an ELS as a way to get metric threading on a non-metric old-iron machine.

I keep meaning to move beyond the 4 line LCDs and tackle some of the graphics displays, but I fall back to using the wifi on the Pico W to interact with a computer for fancy stuff.

Anyway, more details along with a few pics are always fun, when you get a chance.
Same for me... For any project that needs a more complex UI, I serve a web page from a pico W. I've even gone as far as becoming an Apple Developer so I can design and load my own apps on my phone and watch. One example is an autopilot for my boat with a phone and watch app that lets me control the boat while fishing. I can make course corrections while reeling a fish in with a touch on my watch!
 
Out of curiosity are you actually dma transferring a bitmap, or the number of encoder pulses to count? Was just thinking that for a bitmap, if your period isn’t a multiple of 32 (8?) you’d have to also transfer a bit count?

Have you tried bluetooth via the Pico W? Looking at that for my crane project. Currently it is a wired RS-232 pendant.
 
Last edited:
Each 32 bit control word contains from 1 to 26 control bits (the bitmap). The least significant 5 bits contains the number of bits 1-26 to use in the control word. It's limited to 26 instead of 27 for reasons I'll go into when I go over the state machine code.

Since the Bluetooth hardware support is relatively new, I haven't had an opportunity to play around with it.
 
Last edited:
Before going over the state machine code, I'm going to go over the control words the state machine consumes.
Whenever I change the thread or feed of the lathe, I call a routine that calculates a new set of control words. That routine has the option to print out some debug statements. The listings below were generated by that code.

I've included a dump of this debug printout for a few different feed rates below...
The first one with TPI=16 is interesting since my feed screw has 16 TPI. Because of this, the control words reflect the ratio of encoder pulses per spindle revolution to stepper steps per feed screw revolution. My current system is setup with 1200 encoder pulses per spindle rev and 200 pulses per feed screw rev. I changed back to 200 to keep the torque as high as possible.

Each control word as mentioned before consists of up to 26 control bits and a 5 bit count. The 5 bit count specifies the number of control bits in the control word - 1 (I'll discuss why it's -1 when I go over the state machine).
The count occupies the least significant 5 bits of the control word, and the control bits start at bit 5 (0 based). In the listings below, I added a space between the control bits and the bit count to make it easier to differentiate the two blocks. Control bits are tested from right to left (LSB to MSB) in the control word by virtue of a shift function in the state machine. When one control word is exhausted, A DMA transfer occurs and another control word is waiting. From this you can see that when you reach the MSB of one control word, you pick up at the LSB of the next control word. When the state machine reaches the end of the last control word, it initiates another DMA transfer. The DMA is setup to loop back to the beginning when it reaches its last word. The state machine doesn't even know this looping is happening!

A simplified overview of the state machine. A more detailed discussion of the code will follow in a day or two...
The first thing the state machine does when It loads a new control word is shift the 5 count bits into a register. The state machine then waits until an encoder pulse is detected. When one is detected, the state machine shifts the LSB (now a control bit) into a register. If that register is 0, it does nothing. If it's a 1 (non zero), it generates a step. It then checks to see if there are any remaining bits in the control word. If there are, it decrements bit count and waits for another encoder pulse. If it's reached the end of the control word (bit count = 0), it forces a DMA transfer and starts the whole process over.

Note in this set, a control bit occurs every 6'th pulse. That's the 1200/200 ratio mentioned above.
New TPI/Feed = 16.00/0.0625
NewControlWordsTPI words: 2 Execution Time: 0ms (Execution time is the time it took to generate this set)
2 control words...
00100000100000100000100000 11001 (26bits)
1000 00011 (4bits)
Also note that the last bit in the last control word is always a 1. That's the point where the difference between the step position and the calculated encoder position is ~=0

In this set, you see the rounding to the best encoder pulse. Some steps are spaced by 3 and some by 4.
New TPI/Feed = 10.00/0.1000
NewControlWordsTPI words: 3 Execution Time: 0ms
3 control words...
10001001000100010001001000 11001 (26bits)
00010001000100100010001000 11001 (26bits)
10001000100100010001001 10110 (23bits)


New TPI/Feed = 55.00/0.0182
NewControlWordsTPI words: 7 Execution Time: 0ms
7 control words...
00000100000000000000000000 11001 (26bits)
00000000000100000000000000 11001 (26bits)
00000000000000001000000000 11001 (26bits)
01000000000000000000010000 11001 (26bits)
00000010000000000000000000 11001 (26bits)
00000000000010000000000000 11001 (26bits)
100000000 01000 (9bits)

State machine code review to follow in a few days.
 
Back
Top