Cool!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.
Yeah, UI design seems fun and there seems to be limitless potential to hone in on making it super handy.
I wanted to share a few other random tidbits, as I hang things up in wait for my stepper to come in on Friday:
1. @AlanB , maybe of interest to you, I found that someone re-implemented the jump table from the gitjer c pio encoder example:
PIO program for quadrature encoder and absolute addresses - MicroPython Forum (Archive)
forum.micropython.org
Python:
# Quadrature encoder for RPi 2040 Pio
# Has to be at address 0 of PIO programm space
#
# Original version (c) 2021 pmarques-dev @ github
# (https://github.com/raspberrypi/pico-examples/blob/master/pio/quadrature_encoder/quadrature_encoder.pio)
# Adapted and modified for micropython 2022 by rkompass
#
# SPDX-License-Identifier: BSD-3-Clause
#
# This program was reduced to take 'only' 24 of 32 available PIO instructions.
#
# Quadrature encoding uses a state table in form of a jump table
# which is fast and has no interrupts.
# The counter x is permanently pushed nonblockingly to the FIFO.
# To read the actual value empty the FIFO then wait for and get the next pushed value.
# The worst case sampling loop takes 14 cycles, so this program is able to read step
# rates up to sysclk / 14 (e.g., sysclk 125MHz, max step rate = 8.9 Msteps/sec).
from machine import Pin
from rp2 import PIO, StateMachine, asm_pio
from time import sleep_ms
class PIO_QENC:
def __init__(self, sm_id, pins, freq=10_000_000):
if not isinstance(pins, (tuple, list)) or len(pins) != 2:
raise ValueError('2 successive pins required')
pinA = int(str(pins[0]).split(')')[0].split('(')[1].split(',')[0])
pinB = int(str(pins[1]).split(')')[0].split('(')[1].split(',')[0])
if abs(pinA-pinB) != 1:
raise ValueError('2 successive pins required')
in_base = pins[0] if pinA < pinB else pins[1]
self.sm_qenc = StateMachine(sm_id, self.sm_qenc, freq=freq, in_base=in_base, out_base=in_base)
self.sm_qenc.exec("set(x, 1)") # we once decrement at the start
self.sm_qenc.exec("in_(pins, 2)")
self.sm_qenc.active(1)
@staticmethod
@rp2.asm_pio(in_shiftdir=PIO.SHIFT_LEFT, out_shiftdir=PIO.SHIFT_RIGHT)
def sm_qenc():
jmp("read") # 0000 : from 00 to 00 = no change
jmp("decr") # 0001 : from 00 to 01 = backward
jmp("incr") # 0010 : from 00 to 10 = orward
jmp("read") # 0011 : from 00 to 11 = error
jmp("incr") # 0100 : from 01 to 00 = forward
jmp("read") # 0101 : from 01 to 01 = no change
jmp("read") # 0110 : from 01 to 10 = error
jmp("decr") # 0111 : from 01 to 11 = backward
jmp("decr") # 1000 : from 10 to 00 = backward
jmp("read") # 1001 : from 10 to 01 = error
jmp("read") # 1010 : from 10 to 10 = no change
jmp("incr") # 1011 : from 10 to 11 = forward
jmp("read") # 1100 : from 11 to 00 = error
jmp("incr") # 1101 : from 11 to 01 = forward
label("decr")
jmp(x_dec, "read") # 1110 : from 11 to 10 = backward
label("read") # 1111 : from 11 to 11 = no change
mov(osr, isr) # save last pin input in OSR
mov(isr, x)
push(noblock)
out(isr, 2) # 2 right bits of OSR into ISR, all other 0
in_(pins, 2) # combined with current reading of input pins
mov(pc, isr) # jump into jump-table at addr 0
label("incr") # increment x by inverting, decrementing and inverting
mov(x, invert(x))
jmp(x_dec, "here")
label("here")
mov(x, invert(x))
jmp("read")
nop()
nop()
nop()
nop()
nop()
nop()
nop()
def read(self):
for _ in range(self.sm_qenc.rx_fifo()):
self.sm_qenc.get()
n = self.sm_qenc.get()
return n if n < (1<<31) else n - (1<<32)
pinA = Pin(15, Pin.IN, Pin.PULL_UP)
pinB = Pin(16, Pin.IN, Pin.PULL_UP)
qenc = PIO_QENC(0, (pinA, pinB))
print('starting....')
for i in range(120):
print('x:', qenc.read())
sleep_ms(500)
qenc.sm_qenc.active(0)
print('stop')
Apparently (and I've tested this and it seems true), python loads the assembly program last to first, so spacing (NOP)s are required to ensure the origin is 0. I was able to test/adapt the first example given and it works as expected with my rotary encoder (except for seeming to generate 4k pulses at incr per rotation, instead of 1k? I haven't compared to the original jump table, I imagined INCR would only get called every 4 physical pulses which equates to one actual pulse. There is a shortened example as well, at the cost of only decrementing twice per rotation.
2. I somehow didn't realize that EACH pio block has 32 word instruction memory, and each of those two 32 word instruction memories are shared between the respective state machines (0-3, 4-7). This is handy: although IRQs are not shared, I should easily be able to raise a pin high to signal to the other PIO block that an encoder step needs to be interpreted and action is ready to be taken. This is cool at least because I can move the entire fractional step handler to another PIO block that's less cramped.
3. I'm looking into DMA as I only have the foggiest understanding of how it's handled or works, but I also was curious about implementing "read gearing ratio from memory" functionality so that it's easy to load new gearing. As shown, I'm currently running the gear ratio with constants and just changing things manually. I know the FIFO/(and ISR?) bits that are removed are when they are pulled and I don't imagine python can fill the FIFOs fast enough to never run out, if the gearing is determined from the ISR.
4. I explored a bit, out of curiosity, what compiling native C code to import as a MPY library in python looks like. I came upon an old example which didn't work, but an example now seems integrated into this doc, so maybe it's easier now: https://docs.micropython.org/en/latest/develop/natmod.html#minimal-example . A caveat is that it only pulls what libraries you explicity include or create, so no division. But that's not difficult to implement. But just more tidbits along the "have C do its thing while python is responsible for config" route.
5. I had another really random and potentially less important thought, in line with the fact that I can run the stepper ratio code on the second PIO block and am not short on memory in that block. It seems that I could pretty easily, in line with my simple numerator/denominator pulse reconfiguration experiment, flip/flop between two gearing ratios to simulate an average intermediate numerator/denominator gearing between n1/m1 and n2/m2. I know maybe it seems silly, but it seems potentially appealing to explore since I could do math with python to figure out initialization values that would potentially get non-integer n/m gearing, on average, by alternating between two gearing states. Initializing with different values isn't too bad, and hey, maybe figuring out what that opens up would be easy to calculate.
Again, my original interest was just hopefully a minimal goal of exploring and potentially getting power feed, so I'm just playing around and hopefully seeing what might be fun to explore. I'll try to give an update if I ran into issues with the most basic setup, once things actually start attaching to the actual lathe.