An Electronic Lead Screw controller using a Teensy 4.1

I believe "encoder resolution" == PPR which for full quadrature is 4 x CPR



Yes for his algo but you can do ratios larger for 1. this may help https://github.com/ashiagarwal73/Bresenham-s-line-algorithm-for-all-quadrants



Stepper resolution is also relevant. you can use less micro steps to get the ratio smaller for larger threads. What kind of lathe will you be using? It has an upper bound on what threads it can cut from a rigidity perspective. If you implement 4 quadrant bresenham this isn't an issue. I haven't bothered yet.



the esp32 is pretty fast so I just use doubles and floats. Likely teensy can do the FP math.


microsteps can be done on the fly but most drivers are set via dipswitch or jumper. you want to save the microsteps as a config var and store in NVRAM if possible.
Thanks for the answers and the pointer to four quadrant Bresenham. I have a Grizzly G0752Z which is a 10 x 22, basically the same as the G0602. The G0752Z has a VFD and a magnetic DRO. I was able to cut 8 TPI threads in cast iron, but I'm not sure I would push it to 4 TPI.

Teensy can do the double precision floating point. But I suppose there can be floating point error, if one is not careful, especially if one uses single precision. I haven't experienced double precision FP error, but it can happen.

By microsteps on the fly, do you mean: 1) set dipswitch to uStep=8, but 2) send 2 microsteps when you need an effective uStep=4 ? I could implement that pretty easily. I have made a configuration table which stores microsteps needed to make the ratio<1 for that setting. Most have 8 microsteps, but the big threads basically have 4 microsteps.
 
Each installation and developer has slightly different goals and constraints. In my case the lathe is new with 3 year warranty so I don't want to hack it much. There is a built in tach that could be improved, and there is some space under the DRO on top of the head where the ELS display would be better in terms of viewing. I frequently use a spindle turns counter that sits there now, but the ELS could easily replace the turns counting and give a better spindle RPM display. I don't care for touch displays in a machine tool environment and prefer an interface designed for the task, with a graphical LCD or OLED and a rotary encoder with push switch plus some dedicated switches as is useful. The controls should be on the front with the lathe's other controls, but the display is hard to view there, like the built in tach, so I'm leaning toward a split implementation. The Pico is very small so it would be in the display module. Any programming would be via USB and no wireless is allowed. Once the thing works I don't want to have to maintain it unless hardware breaks or I have a new upgrade idea. A 3 to 5 inch graphical display should be adequate, no need for something larger. Here's the turns counter the ELS could replace and take over the good display location.

image_2022-05-12_070308521.png

On this lathe (PM-1228) there is a 15 speed gearbox and levers that engage threading, feeding and cross feeding modes. So we want to display RPM, Turns, Thread Pitch, Feed and Crossfeed speeds, and Gearbox settings at the same time as well as max RPM allowed in the current configuration and perhaps the current Electronic Gear Ratio. It would be nice to display both metric and imperial units at the same time where that makes sense. I was just thinking that if there was a magnet on a shaft in the threading/feeding system the Pico could check the gearbox ratio and warn you if it was not set to the expected value, or even adjust the displays to track the actual gearing. A separate physical switch for normal / off / reverse for the ELS motor output would be nice to reduce wear on the feed system when it wasn't needed.
 
Each installation and developer has slightly different goals and constraints. In my case the lathe is new with 3 year warranty so I don't want to hack it much. There is a built in tach that could be improved, and there is some space under the DRO on top of the head where the ELS display would be better in terms of viewing. I frequently use a spindle turns counter that sits there now, but the ELS could easily replace the turns counting and give a better spindle RPM display. I don't care for touch displays in a machine tool environment and prefer an interface designed for the task, with a graphical LCD or OLED and a rotary encoder with push switch plus some dedicated switches as is useful. The controls should be on the front with the lathe's other controls, but the display is hard to view there, like the built in tach, so I'm leaning toward a split implementation. The Pico is very small so it would be in the display module. Any programming would be via USB and no wireless is allowed. Once the thing works I don't want to have to maintain it unless hardware breaks or I have a new upgrade idea. A 3 to 5 inch graphical display should be adequate, no need for something larger. Here's the turns counter the ELS could replace and take over the good display location.

View attachment 406801

On this lathe (PM-1228) there is a 15 speed gearbox and levers that engage threading, feeding and cross feeding modes. So we want to display RPM, Turns, Thread Pitch, Feed and Crossfeed speeds, and Gearbox settings at the same time as well as max RPM allowed in the current configuration and perhaps the current Electronic Gear Ratio. It would be nice to display both metric and imperial units at the same time where that makes sense. I was just thinking that if there was a magnet on a shaft in the threading/feeding system the Pico could check the gearbox ratio and warn you if it was not set to the expected value, or even adjust the displays to track the actual gearing. A separate physical switch for normal / off / reverse for the ELS motor output would be nice to reduce wear on the feed system when it wasn't needed.
My lathe is used, I am the second owner. There's no warranty to worry about or even have. Although I have a Pico, I also have a pair of Teensy 4.1's which probably are far more powerful than a Pico. The processors are the cheapest part of the whole system, and it doesn't matter to me if I spend $5 or $25, when the motors and drivers are far more expensive. If I needed a bunch of processors, perhaps I would have a different opinion. Have already lived through a processor choice solely because of its cost and disregarding performance. Only lost a year's progress due to that management mistake! That being said, if I had a bit of experience with the Pico, I might have started with it. An M7 with 600MHz with HW double precision floating point vs two 133 MHz M0+ cores with double precision float, think I'd take the M7. This of course ignores some Pico capabilities, but honestly, I don't know enough to do a really fair comparison.

It has been my impression, so far, that the PJRC forum is very supportive and helpful, and very technical. I don't get that same feeling on the RPi forum. No need to explain on this forum thread. Perhaps the Pico community is very good. If so, I'd like to know.

Ideally, want to have an equivalent to the Clough42 setup. I don't like the primitive UI, but if I had to, could live with it. A display would be nicer. I'm on the fence on a touch display, since a shop is not a clean place, but I think it could be managed. It's not like the Clough42 implementation which is unsealed, is industrial grade, either.

If I was really feeling fancy, I'd integrate in my DRO's since they are just hooked up to a 1980's LED display. I'm not sure, but they should be just another quadrature encoder, for which I already have code for. I would need level converters, but that's no big deal. Then I'd look at a solenoid driven half nut, and maybe a stepper on the cross-slide and finally be able to control the VFD itself. But I really don't need that capability at this point in time.
 
I don't mean to criticize the choice of the Teensy 4.1 for this project. I have a number of the Teensy boards here, I keep a few around for projects, they are excellent. I have 2's, 3's and 4's on hand. I don't generally find the larger pin count to be necessary so I don't have any 4.1's (and when I looked some weeks back they were out of stock). I'm sure the 4.1 will do this project just fine, or a 4.0, or anything 3.0 and beyond, even a Teensy2 would work with a bit of care. The 3.1's are 5V compliant so that would be a convenient choice.

I've done a few tests with the Picos, and find them to be solid. It's still early days so the libraries are growing but in general the libraries and documentation are amazing considering the young age of the product. The Pi Foundation is a major outfit. In general Pi stuff has the most support and libraries available, but the Pico isn't quite as developed yet as the other Pi's, and is in a different market segment. I generally find any question I have has already been answered so I don't even have to ask. The Picos are also available, which I can't say for many other boards.

The Pico's dual cores are a win for the ELS. One core for realtime, one for UI. It also has a bunch of PIO state machines that can be used to read quadrature encoders and make motor pulses essentially in programmable hardware, which I don't plan to use, but it is there. It might even be possible to fit the essential core state machine into a PIO, then it would run as clocked hardware. There again, I don't plan to do that, but that would basically be a hardware solution. A small FPGA would be better, and that might be fun, but I haven't done those so the learning curve would be significant. We had other people on the team who did the FPGAs.

I have seen Pico support and many examples for using the dual cores in either C++ or MicroPython. Ideally I would use both, C++ in one core for real time and MicroPython in the other for UI. I haven't seen support for that mix yet. Doing it all in C++ is fine too.

I haven't seen support for multiple cores on the Teensys. In the case of the ELS I prefer dedicating a core to the realtime, it simplifies things.

I have done big projects for data acquisition and control, and have had better results with parallel processors rather than one faster CPU. Performance is predictable and scalable that way. A lot of complexity and problems melt away when you can dedicate processors to tasks. But the ELS isn't really that hard - I've heard there is a Russian ELS that works very well with 16 mhz Arduinos. Any of the processors we are considering are far more capable.

The best way to learn a processor is to use it on a project. If the Pico doesn't work out I can switch to another board easily. The investment in peripherals is not wasted. I think the Pico is a great choice, but that doesn't mean the Teensy is not a good choice too.

I was on one project where processor performance was a problem when dealing with some precision mechanical motions. We had just built a new Ti DSP based prototype and it was starting to work, when the project management changed. The new project management told us that "DSPs Don't Work" and cancelled the DSPs. That same year a doll came out that used a similar DSP to understand and generate speech as a children's toy and it was a sold-out Christmas gift. Clearly management had a bad experience with a project that didn't meet schedule involving DSPs. The biggest problem is often the skillset of the people working with the hardware, you need a somewhat different team for a DSP, FPGA, or microcontroller projects. Few are comfortable and efficient with all of them, especially in those days. The final solution there was a motor control chip (really a microcontroller in disguise) and some expensive mechanical hardware to compensate for inadequate processing power, and they never met the performance levels that we had reached in the prototypes with simpler hardware and a more powerful CPU. It was a bit early for FPGAs, today that would certainly be the best approach.
 
@AlanB discussions about processors can get far more lively than I ever want to get into. I have a Teensy 4.1, I'll try it. If it doesn't pan out, it is fine, I'll try something else. I'm not wedded to it! Maybe I will have to port it all to a Pico or ESP32, in the end, don't think it matters too much.

The Teensy has HW quadrature decoders, so there's not much work for me to do, it just works. Already tested it. Just been playing with how fast I can drive this NEMA 11 motor with my driver. I can get 14085 microsteps/second. That is a microstep once per 71us. Faster than this, the motor makes noises and doesn't move. Haven't looked at the motor voltages yet, just the inputs. Not sure if it is the motor not moving or the controller can't follow the commands. Of course, this controller has no documentation, so I don't know its max step rate, or even the duty factor of the PUL+ signal that is allowed. I can report that a 30us on time, with a period of 71us does work, at least under benign conditions. So it would seem I am driving the motor about 528 RPM, or 8.8 rev/second. When I buy my real stepper motor controller, I think I will be a bit more discerning. So buying this cheap controller has helped me learn about its limitations.
 
Some progress. I now can control the stepper based on the encoder position, using the ratio. I also put in a primitive speed sensor which determines how many encoder counts are received in a 20 ms interval. The timer is interrupt driven, as is the encoder. The stepper follows the encoder direction and maintains the rate, according to my poor man's Bresenham implementation. So if I move the encoder by hand the stepper follows at some ratio.

For 8 TPI, I can easily generate more steps/second than the stepper motor driver can accommodate (statically), just spinning the encoder by hand. I understand this means low RPMs for coarse threads. That's fine in practice, (I'd be petrified to thread 8 TPI fast without automatic stops,) but I am concerned that there could be missing steps, and I would never know. Short of another encoder, is there any way to know if I'm missing steps?
Code:
Counts in 20ms: 2
Speed estimate:    1.46 RPM
Stepper pulse/sec estimate:   14.65
Counts in 20ms: 33
Speed estimate:   24.17 RPM
Stepper pulse/sec estimate:  241.72
Counts in 20ms: 54
Speed estimate:   39.55 RPM
Stepper pulse/sec estimate:  395.55
Counts in 20ms: 72
Speed estimate:   52.73 RPM
Stepper pulse/sec estimate:  527.40
Counts in 20ms: 118
Speed estimate:   86.43 RPM
Stepper pulse/sec estimate:  864.35
Counts in 20ms: 212
Speed estimate:  155.27 RPM
Stepper pulse/sec estimate: 1552.90
Counts in 20ms: 406
Speed estimate:  297.36 RPM
Stepper pulse/sec estimate: 2973.95
Counts in 20ms: 621
Speed estimate:  454.83 RPM
Stepper pulse/sec estimate: 4548.82
Counts in 20ms: 756
Speed estimate:  553.71 RPM
Stepper pulse/sec estimate: 5537.70
Counts in 20ms: 448
Speed estimate:  328.12 RPM
Stepper pulse/sec estimate: 3281.60

C:
#include "Arduino.h"
#include "QuadEncoder.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

#define PUL   5
#define DIR   6
#define ENA   7

#define IDX   4
#define A     0
#define B     1

#define chatty 0

uint32_t mCurPosValue;
uint32_t old_position = 0;
uint32_t mCurPosValue1;
uint32_t old_position1 = 0;
uint32_t timeoutpos;

volatile uint32_t val1, val2;
int myacc;

// imperial 32 TPI
int N = 293;
int D = 2000;

//// imperial 8 TPI
//int N = 293;
//int D = 500;

int bb = 0;
bool CW   = false;
bool CCW  = true;
bool mydir = CW;
uint16_t steplen = 5;   // step pulse length in microseconds
unsigned long mytime;
unsigned long oldtime;
int encoder_resolution = 4096;
double instspeed;
double sps;
volatile bool timeout;

OneShotTimer t1(PIT);  // for stepper pulse
OneShotTimer t2(PIT);  // for measuring speed

QuadEncoder myEnc1(1, A, B, 0, IDX);  // Encoder on channel 1 of 4 available
                                   // Phase A (pin0), PhaseB(pin1), Pullups Req(0), Index Z (pin4)
/*
 * N = leadscrewTPI * basestep * microstep [threads x steps / inch]
 * D = TPI * encoder_res [ threads * pulses / ( inch x spindle rev ) ]
 * numsteps = pulses (encoder count) * N/D  // [ pulses x steps /( pulses / rev ) ]=[ steps * rev ]
 *
 * Issue is N/D is rational, so we use a method like Bresenham to calculated when to emit a step
 * Didge has a method, which I am trying to understand.  ESPels by greenail has a method using Didge,
 * but it is a slow crawl through his code, since it is not exhaustively documented.
 */

void pulseme()   // turns off stepper pulse after time out
{
  digitalWriteFast(PUL, LOW);
  digitalWriteFast(LED_BUILTIN, LOW);
}

void doincrement( bool adir )   // sets direction and turns on stepper pulse
{
  digitalWriteFast(DIR, adir);         // set direction
  digitalWriteFast(PUL, HIGH);
  digitalWriteFast(LED_BUILTIN, HIGH);
  t1.trigger(steplen);  // switch off after steplen us (5us)
}

void myms()    // xxms timer callback for estimating spindle speed and step rate
{
  val2 = myEnc1.read();   // read encoder for current position
  // we know the time was 20 ms
  double cps = 50.0 * ( (double)val2 - (double)val1)/(double)encoder_resolution;;   // rev per second
  int counts = (int)val2 - (int)val1;
  sps = cps * (double)encoder_resolution * (double)N/(double)D;
  if (cps != 0.0)  // check for non-zero velocity
  {
    if (abs(cps) < 10000.0) // hack to avoid over underflow
    {
      Serial.printf("Counts in 20ms: %i\n", counts);
      Serial.printf("Speed estimate: %7.2f RPM \n", cps*60.0);
      Serial.printf("Stepper pulse/sec estimate: %7.2f\n", sps);
    }
  }
  timeout = true;
}

void setup() {
  while(!Serial && millis() < 4000);
  for(int i=5; i<8; i++) pinMode(i, OUTPUT);  // init these pins to outputs
  pinMode(LED_BUILTIN, OUTPUT);
  t1.begin(pulseme);            // set up t1 callback function
  t2.begin(myms);               // set up t2 callback function
  digitalWriteFast(DIR, CW);    // sets direction of rotation  LOW =CW, HIGH=CCW
  digitalWriteFast(ENA, LOW);   // active low signal, enables the stepper controller
 
  myEnc1.setInitConfig();       // set up the HW encoder
  myEnc1.init();                // initialize the HW encoder
  myacc = 0;
  instspeed = 0.0;
  timeout = true;               // so we start measuring speed right away
}

void loop()
{
  mCurPosValue = myEnc1.read();   // gets all values in hold registers in encoder
  if (timeout)  // if no timeout, skip this
  {
    timeout = false;  // determine how many counts we get in 20ms, this is the base for a speed estimate
    t2.trigger(20ms);     // set timer to go off in 20ms
    val1 = mCurPosValue;  // save current position
  }
   
  if(mCurPosValue != old_position)
  {
    bb = (int16_t) myEnc1.getHoldDifference();  // needed to cast to get print to work right
    if (chatty) Serial.printf("inc: %d, myacc; %d\n", (int16_t)bb, (int16_t)myacc, (int16_t)D);
    myacc = myacc + bb*N;
    if ( bb >= 0)
    {
      if (chatty) Serial.printf("myacc : %d\n", (int16_t)myacc);
      if (myacc >= D)  // works for positive accumulation only
      {
        myacc = myacc - D;
        mydir = CW;
        doincrement(mydir);
      }
    }
    else
    {
      if (chatty) Serial.printf("myacc : %d\n", (int16_t)myacc);
      if (myacc <= D)  // for negative accumulation
      {
        myacc = myacc + D;
        mydir = CCW;
        doincrement(mydir);
      }
    }
  
    if (chatty)
    {  
      Serial.printf("myacc: %ld\r\n", (int16_t)myacc);  // print out our accumulator
      /* Print out all the encoder position values. */
      Serial.printf("Current position value1: %ld\r\n", mCurPosValue);
      Serial.printf("Position differential value1: %d\r\n", (int16_t)myEnc1.getHoldDifference());
      //Serial.printf("Get Revolution value: %d\r\n", (int16_t)myEnc1.getRevolution());
      Serial.printf("Position HOLD revolution value1: %d\r\n", (int16_t)myEnc1.getHoldRevolution());
      //Serial.printf("Index Counter: %d\r\n", myEnc1.indexCounter);
      Serial.println();
    }
  }
  old_position = mCurPosValue;  // update for next cycle
}
 
Great progress.

If bb is more than 1, or less than -1, I would count those as errors. Never want to miss an edge on the encoder. If it happens something is failing to keep up.

Open loop steppers without encoders are not going to tell you when they miss steps. Closed loop steppers or servos are needed for that, or some sophisticated stepper controllers that monitor the currents and motor state precisely can do that (some 3D printers use this for sensorless homing). On science systems we added encoders to steppers if it was important to know if steps were being missed. Hobbyists tend to use big enough steppers that missing steps is not normally a problem in normal use. Closed loop steppers are not too expensive to consider. Servos are probably not worth their cost for this. Clearpath are about $600 per axis, some other brands are somewhat less.

Is that motor max step rate "stall" from a stop direct to the pulse rate? Or did you accelerate up to the "stall"?

ESP32 would be a good choice if wireless is desired. The hardware quality is more variable than Teensy or Pico, but two cores could be useful there.

I'm planning to calculate the RPM of the stepper and display information to the user about the max RPM the spindle can use depending on the selected gear ratio and a chosen max limit for the stepper. So for something like 8 tpi the user would be warned that the spindle RPM max might be lower, but as you point out, in reality this is not a real issue, just something the operator needs to know about. There are many things that the operator needs to take care of, and selecting spindle RPM is one of them. Just because the lathe can turn a certain RPM doesn't mean the rest of the system can. For example many chucks have RPM limitations that are lower than what the lathe is capable of. When using that chuck one needs to keep that in mind. One day I found that my magnet for turns counting has an RPM limit when it departed the chuck during a demo of how fast the chuck could spin (never a problem in normal use). So as you I don't mind having an RPM limit for certain configurations as long as I know about it, and the interface can conveniently remind me of it. We could also code it so that the ELS shuts down the stepper when our selected limit is exceeded, or perhaps it could beep as the limit is approached in case the operator had not paid attention. Getting some warning before skipping steps would be nice. In real life we tend to operate well within any limits like these.
 
One other thing - if you select the counting period properly the spindle RPM can be the direct result of the count. :)
 
Is that motor max step rate "stall" from a stop direct to the pulse rate? Or did you accelerate up to the "stall"?
That was a stopped motor being instantaneously told to start stepping at 13.5K steps per second. With no load, this NEMA 11 will do that repeatedly. The motor I think could go to 20k steps/sec, but of course it would have very little torque. So it would seem, if the motor is already going, hitting peak step rates over 20 ksteps/sec are possible. My program doesn't mind. On one trial, I clocked it at 1291.26 RPM for an 8 TPI thread, generating stepper pulses at 51.6 Ksteps/second. My scope shows the pulses.

I just put in a trap, if abs(bb>1). It prints out a fault message and sits in a while loop forever. So far I can't get it to trip. Of course, so far everything is interrupt driven, and I think I am barely taxing the processor so far.

Next I think I will add a display. Initially it will be just the spindle RPM, and maybe a button or two to read back. Once I get that going, I will have to think about actually designing the user interface.
 
Well, that was ugly. Yeah, the tft display lib is causing me to miss counts. Will need to come up with a more elegant approach...
 
Back
Top