clough42 / electronic-leadscrew

Lathe electronic leadscrew controller
MIT License
323 stars 117 forks source link

Using a LUT #5

Closed johnbbradley0 closed 5 years ago

johnbbradley0 commented 5 years ago

One example you gave in youtube comments was 13 tpi with a 12 tpi lead screw.

I'll start with a 12 tpi to 12 tpi example: 4096 pulses = 1600 steps So in the init use LUT[n] = map(n, 0, 4096, 0, 1600) to fill a look up table to use in the interrupt.
( 0 or 1 to start the range? Anywho... (actually I'm pretty sure this is really important to keep accuracy but then I'd actually have to write code and do a unit test or something) ) This is the best accuracy you can get.

Now the 13 tpi to 12 tpi example: 4096 pulses x 12 rev = 1600 steps x 13 rev 49152 pulses = 20800 steps you don't want a LUT this big maybe? gcd( 49152, 20800) = 64 768 pulses = 325 steps so do the map again LUT[n] = map(n, 0, 768, 0, 325)

Oh hey, maybe we should've done a gcd for the first example gcd(4096, 1600) = 64
hmm... you don't think? Nah, couldn't be important. so LUT[n] = map(n, 0, 64, 0, 25) and stranger ratios will just increase your LUT size.

I won't be able to implement this until next week, but I have a teensy, stepper, encoder, and drive motor to test it on. The G0602 is in storage though :(

clough42 commented 5 years ago

After reading your description, I thought this might work, so I've been writing test code tonight to look at it in detail, and I'm not getting the same results you did.

First, I think you have the ratio backwards. It should be 13 turns of the spindle (13 TPI) to 12 turns of the leadscrew (one inch). So, this gives us:

4096 13 pulses = 1600 12 steps multiplying... 53248 pulses = 19200 steps reducing... 208 pulses = 75 steps

This would make a suitably small lookup table (416 bytes) but how would you use this lookup table? 208 doesn't divide evenly into 4096, so you'd have to do a long division to handle the multiple revolutions anyway. Something like:

*desired_steps = count75/208 + LUT[count%208]**

You could alternatively try to make the table bigger and track multiple revolutions of the spindle, but in this case, the LCM of 4096 and 208 is 53248, or 13 revolutions of the spindle. The metric threads end up requiring you to track 127 revolutions of the spindle before they come out even.

Now I think it would be possible to not worry about the LCM and instead accumulate and subtract in intervals of the denominator, but that's starting to add a lot of complexity and opportunity for human error in the code--especially as it gets more complex, like accelerating a stopped leadscrew to sync with an existing thread for automating multiple passes. Maybe that case is hard anyway...

clough42 commented 5 years ago

If you want to take a look at my calculations, I hacked up a test in Java:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

@RunWith(Parameterized.class)
public class GCDTest {

  private static int ENCODER_COUNT = 4096;
  private static int STEPPER_STEPS = 200 * 8;

  private final int numerator;
  private final int denominator;

  public GCDTest(int numerator, int denominator) {
    this.numerator = numerator;
    this.denominator = denominator;
  }

  @Parameterized.Parameters
  public static Collection<Integer[]> data() {
    return Arrays.asList(new Integer[][] {
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 80 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 90 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 100 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 115 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 120 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 130 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 140 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 160 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 180 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 200 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 240 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 260 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 280 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 320 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 360 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 400 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 440 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 480 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 560 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 640 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 720 },
      { 12 * STEPPER_STEPS * 10, ENCODER_COUNT * 800 },

      { 20 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 25 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 30 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 35 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 40 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 45 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 50 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 60 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 70 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 75 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 80 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 100 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 125 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 150 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 175 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 200 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 250 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 300 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 350 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 400 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 450 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},
      { 500 * 10 * 12 * STEPPER_STEPS, ENCODER_COUNT * 254 * 100},

    });
  }

  @Test
  public void testGCD() {
    int gcd = gcd(numerator, denominator);

    int rn = numerator / gcd;
    int rd = denominator / gcd;

    int lcm = lcm(4096, rd);

    System.out.println("Reduced fraction: " + rn + "/" + rd);
    System.out.println("Minimum LUT length: " + lcm + ", revolutions: " + lcm/4096);

    int remainder = ENCODER_COUNT % rd;

    System.out.println("  remainder=" + remainder);

    assertThat(remainder).isZero();
  }

  private int gcd(int i, int j) {
    BigInteger I = BigInteger.valueOf(i);
    BigInteger J = BigInteger.valueOf(j);

    return I.gcd(J).intValue();
  }

  private int lcm(int i, int j) {
    return i * j / gcd(i,j);
  }
}
johnbbradley0 commented 5 years ago

It shouldn't divide into 4096, it should divide into 4096*13. My plan is to have 209 wrap to 0, and -1 to 208, then use the LUT to get the number of steps to take. If you need a rev count you could keep that separately. If you needed RPMs, you could have a low priority clock interrupt maybe? On Apr 25, 2019 11:05 PM, "clough42" notifications@github.com wrote:

After reading your description, I thought this might work, so I've been writing test code tonight to look at it in detail, and I'm not getting the same results you did.

First, I think you have the ratio backwards. It should be 13 turns of the spindle (13 TPI) to 12 turns of the leadscrew (one inch). So, this gives us:

4096 13 pulses = 1600 12 steps multiplying... 53248 pulses = 19200 steps reducing... 208 pulses = 75 steps

This would make a suitably small lookup table (416 bytes) but how would you use this lookup table? 208 doesn't divide evenly into 4096, so you'd have to do a long division to handle the multiple revolutions anyway. Something like:

desired_steps = count75/208 + LUT[count%208]*

You could alternatively try to make the table bigger and track multiple revolutions of the spindle, but in this case, the LCM of 4096 and 208 is 53248, or 13 revolutions of the spindle. The metric threads end up requiring you to track 127 revolutions of the spindle before they come out even.

Now I think it would be possible to not worry about the LCM and instead accumulate and subtract in intervals of the denominator, but that's starting to add a lot of complexity and opportunity for human error in the code--especially as it gets more complex, like accelerating a stopped leadscrew to sync with an existing thread for automating multiple passes. Maybe that case is hard anyway...

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/clough42/electronic-leadscrew/issues/5#issuecomment-486918361, or mute the thread https://github.com/notifications/unsubscribe-auth/ACYRZ5PY2SAF6FDI4V3I4EDPSJ5RZANCNFSM4HIRTFLQ .

johnbbradley0 commented 5 years ago

I did leave out a step. after you map pulses to steps you want the change in steps in the table. Something like newLUT[n] = LUT[n] - LUT[n-1] So on each pulse it will act like "do I step? no yes no yes no no yes no".

Also stop doing math in the interrupt. When I say wrap I don't mean if n > 208 then n = 0, I mean I'm using a looped linked list so I either get the next value in memory or the previous value depending on direction.

On Thu, Apr 25, 2019 at 11:49 PM john bradley johnbbradley0@gmail.com wrote:

It shouldn't divide into 4096, it should divide into 4096*13. My plan is to have 209 wrap to 0, and -1 to 208, then use the LUT to get the number of steps to take. If you need a rev count you could keep that separately. If you needed RPMs, you could have a low priority clock interrupt maybe? On Apr 25, 2019 11:05 PM, "clough42" notifications@github.com wrote:

After reading your description, I thought this might work, so I've been writing test code tonight to look at it in detail, and I'm not getting the same results you did.

First, I think you have the ratio backwards. It should be 13 turns of the spindle (13 TPI) to 12 turns of the leadscrew (one inch). So, this gives us:

4096 13 pulses = 1600 12 steps multiplying... 53248 pulses = 19200 steps reducing... 208 pulses = 75 steps

This would make a suitably small lookup table (416 bytes) but how would you use this lookup table? 208 doesn't divide evenly into 4096, so you'd have to do a long division to handle the multiple revolutions anyway. Something like:

desired_steps = count75/208 + LUT[count%208]*

You could alternatively try to make the table bigger and track multiple revolutions of the spindle, but in this case, the LCM of 4096 and 208 is 53248, or 13 revolutions of the spindle. The metric threads end up requiring you to track 127 revolutions of the spindle before they come out even.

Now I think it would be possible to not worry about the LCM and instead accumulate and subtract in intervals of the denominator, but that's starting to add a lot of complexity and opportunity for human error in the code--especially as it gets more complex, like accelerating a stopped leadscrew to sync with an existing thread for automating multiple passes. Maybe that case is hard anyway...

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/clough42/electronic-leadscrew/issues/5#issuecomment-486918361, or mute the thread https://github.com/notifications/unsubscribe-auth/ACYRZ5PY2SAF6FDI4V3I4EDPSJ5RZANCNFSM4HIRTFLQ .

clough42 commented 5 years ago

Ahh... I see. So you wouldn't keep track of the spindle revolutions--just loop through the LUT at whatever number of steps are required to complete it, and the size of the LUT is dictated by the denominator of the reduced fraction?

If I'm understanding correctly, this will work fine for certain use cases, but I really don't want to constrain the implementation to just the convenient cases.

For example, I'm currently looking at using a 4096-count encoder and a 1000-count servo, with 3:1 belt reduction to the leadscrew.

For a convenient thread, like 12TPI, this works out to a reduced fraction of 375/512. A LUT with 500 rows seems doable. But with this drivetrain, a 1.25mm thread reduces to 28125/65024. That seems like an impractical table size for a small microcontroller.

johnbbradley0 commented 5 years ago

For 1.25mm I have 20.32 tpi? Do the calculations below look right?
160012100, 409620.32100. 1920000,8323072. GCD = 1024 1875, 8128. That's close to the 10K limit I was thinking of, but it works. I just realized your ratio is for the 1000 count servo. So 28125/65025 reduces to 125/289, that's not so bad right?

I plan to do what I need to for standard threads (like above), and then do a best effort on custom ones, possibly a search like I did for the 28125/65024 case.

johnbbradley0 commented 5 years ago

Also, 4096/1000 is really shoot you in the foot, even if you don't use a LUT, that's not ideal.

clough42 commented 5 years ago

Yeah, the 1000-count servo does make it more complicated, but it's common, and the floating point in the DSP chip handles it acceptably.

For 1.25mm, I get 28125/65024, which is already reduced.

johnbbradley0 commented 5 years ago

I uploaded 3 files to my branch. The python script prints range values to use in the c++ file. The text file shows errors between ideal and output. There's a ratio with steps higher than pulses that needs the ranges increased manually to get better accuracy. I'm going to use this with a teensy, so I won't integrate it into your code.
I'm looking forward to seeing the stepper and servo in use.