Does TyCommander handle FTDI? Or am I doing something wrong?

If angle is more likely the issue, can you explain how you determine the absolute angle of the spindle encoder and how you restore it to the same position for multiple passes? I assume this is what you mean by sync. Does your angle encoder have an index pulse?
Technically, it's relative angle. We can assign count = angle = 0 at start up. While the power is on, angle is preserved.
Angle is simply the (count % 4096) * 360.0f/4096.0f. Count is an integer.

My encoder does have an index pulse, but the EncoderTool modes that are defined make things tough to use it. Basically, I don't want the counts to be modulo 4096, and enabling the index pulse forces that mode.

I set a sync angle trigger to say 2/4096 *360 degrees (0.16 degrees). If I am within 360/4096 degrees of that, I call it "sync". That's 0.08 degrees roughly.

Dunno, I still suspect the while conditional. Angle math always messes me up.
C:
 while (abs(spindle_relative_angle - syncangle) >= dtheta/2.0f) {
     // Wait til sync. Relatively uninteresting stuff here, but a place to monitor or datalog.
 }
Tried lots of versions of the conditional, trying to avoid difference errors, but suspect this is getting me. Due to weird math things, spindle_relative_angle could be positive, or negative, or something else. If the spindle is at -1 degree, but it shows as 359, the difference with 0.16 degrees IS different, in one case it's (359-0.08) = 358.92, or (-1-0.08) = -1.08. One case says we have a long way to go, the other says sync occurs in about 12 ticks. Yeah, 179 degrees is also -181, etc. And yes, I need to get this to work in CW and CCW spindle rotation, so the count goes both positive and negative.
 
Technically, it's relative angle. We can assign count = angle = 0 at start up. While the power is on, angle is preserved.
Angle is simply the (count % 4096) * 360.0f/4096.0f. Count is an integer.

My encoder does have an index pulse, but the EncoderTool modes that are defined make things tough to use it. Basically, I don't want the counts to be modulo 4096, and enabling the index pulse forces that mode.

I set a sync angle trigger to say 2/4096 *360 degrees (0.16 degrees). If I am within 360/4096 degrees of that, I call it "sync". That's 0.08 degrees roughly.

Dunno, I still suspect the while conditional. Angle math always messes me up.
C:
 while (abs(spindle_relative_angle - syncangle) >= dtheta/2.0f) {
     // Wait til sync. Relatively uninteresting stuff here, but a place to monitor or datalog.
 }
Tried lots of versions of the conditional, trying to avoid difference errors, but suspect this is getting me. Due to weird math things, spindle_relative_angle could be positive, or negative, or something else. If the spindle is at -1 degree, but it shows as 359, the difference with 0.16 degrees IS different, in one case it's (359-0.08) = 358.92, or (-1-0.08) = -1.08. One case says we have a long way to go, the other says sync occurs in about 12 ticks. Yeah, 179 degrees is also -181, etc. And yes, I need to get this to work in CW and CCW spindle rotation, so the count goes both positive and negative.

The numerical difference between two angles A and B, both in the range 0-360, will be in the range -360..360. Because angle is periodic, there are two angular differences, one CW and one CCW. For your purpose, you want the shorter of the two, which will always be in the range -180..180.

Code:
diff = target - current;
if (diff > 180)
  diff = diff - 360;
else if (diff < -180)
  diff = diff + 360;

target = 359 and current = 1 --> diff = (359-1)-360 = -2
target = 1 and current = 359 --> diff = (1-359)+360 = 2

And your loop can be:
Code:
while (fabs(diff) >= dtheta/2.0f) {
  ...
}
 
I just pulled apart my ELS enclosure. In a stroke of luck I had both installed a PSRAM chip, and there's room to sneak out an ethernet cable if I have to. The not so lucky part is I had soldered the Teensy headers to the PCB instead of using a socket. (Sockets are potential issues on vibrating machinery.) I'm going to solder in some wires on RX5 and TX5 and hope this will do.
 
I just pulled apart my ELS enclosure. In a stroke of luck I had both installed a PSRAM chip, and there's room to sneak out an ethernet cable if I have to. The not so lucky part is I had soldered the Teensy headers to the PCB instead of using a socket. (Sockets are potential issues on vibrating machinery.) I'm going to solder in some wires on RX5 and TX5 and hope this will do.
Don't forget you need GND as well.
 
The numerical difference between two angles A and B, both in the range 0-360, will be in the range -360..360. Because angle is periodic, there are two angular differences, one CW and one CCW. For your purpose, you want the shorter of the two, which will always be in the range -180..180.

Code:
diff = target - current;
if (diff > 180)
  diff = diff - 360;
else if (diff < -180)
  diff = diff + 360;

target = 359 and current = 1 --> diff = (359-1)-360 = -2
target = 1 and current = 359 --> diff = (1-359)+360 = 2

And your loop can be:
Code:
while (fabs(diff) >= dtheta/2.0f) {
  ...
}
Good stuff! Current is a volatile variable, being updated by the encoder ISR. Is there a way to evaluate diff in the while conditional statement? If evaluated before, I'll get stuck since diff won't be updated after the fact.
 
Don't forget you need GND as well.
I screwed up on the mag connector I bought, should have been 3 contacts, but I ordered only two. Oops. Ground will have to be through the USB cables. Not ideal, but both devices will be powered from a common computer, should be ok. I have two USB cables, both the same length, and from the same manufacturer (just differing in color).
 
Code:
float compute_error(float current, float target) {
  float error = target - current;
  if (error > 180.0f)
    error -= 360.0f;
  else if (error < -180.0f)
    error += 360.0f
  return error;
}

while (fabs(compute_error(current,target)) >= dtheta/2.0f) {
  ...
}
 
Code:
float compute_error(float current, float target) {
  float error = target - current;
  if (error > 180.0f)
    error -= 360.0f;
  else if (error < -180.0f)
    error += 360.0f
  return error;
}

while (compute_error(current,target) >= dtheta/2.0f) {
  ...
}
Gee, I should of thought of that. So simple. Thanks!

I'll add the fabs in on the return statement, since I don't care which side it's on, just within +/- dtheta/2. I'll run a separate test on this to make sure it behaves well for a whole pile of uniformly distributed angles.
 
Gee, I should of thought of that. So simple. Thanks!

I'll add the fabs in on the return statement, since I don't care which side it's on, just within +/- dtheta/2. I'll run a separate test on this to make sure it behaves well for a whole pile of uniformly distributed angles.

I edited my post to have the fabs() in the while loop, which I think makes more sense, because you might be interested in knowing the sign of the error.

I work on rotating machines, so I've done a lot of this angular math, originally with processors without floating point units, and one very useful technique that I learned was to map the angular range 0-360 onto the unsigned integer range 0-65536. If you use 16-bit variables, rollover occurs at 65535, so a variable containing the angle 0 has the same value as one containing 360, which is logical, and even better, you can add and subtract angles and everything works. For example, if you take the 16-bit unsigned difference between any two angles represented this way, and cast to a signed 16-bit angle, you magically have a signed difference in the range -32768..32768, which is equivalent to -180..180. It's not immediately obvious, but any 16-bit value, if interpreted as unsigned is in the range 0-360, and the same value interpreted as signed is the equivalent angle in the range -180..180. For example, angles of 359 and -1 have the same binary representation, as do -179 and +181, etc.

The resulting C code looks really simple:

Code:
uint16_t target, current, threshold;
while (labs(current-target) >= threshold) {
  ...
}

If you need more precision, you can do the same thing with 32-bit integers.
 
Here's some sawtooth math using floating point (in degrees):
f(x) = x - floor((x + 180)/360)*360
(Note that in this form, the range is [-180,180), i.e. 180 is excluded.)
The advantage here is no if-statements/branching, but I don't really know for sure if this is superior without measurement.

But if all the native math from the encoder is done where the full range is represented by 0-(2^N-1) instead of degrees, then @joepasquariello's approach is great. i.e. "1" means "1/(2^N)th" of a full revolution.
 
I edited my post to have the fabs() in the while loop, which I think makes more sense, because you might be interested in knowing the sign of the error.

I work on rotating machines, so I've done a lot of this angular math, originally with processors without floating point units, and one very useful technique that I learned was to map the angular range 0-360 onto the unsigned integer range 0-65536. If you use 16-bit variables, rollover occurs at 65535, so a variable containing the angle 0 has the same value as one containing 360, which is logical, and even better, you can add and subtract angles and everything works. For example, if you take the 16-bit unsigned difference between any two angles represented this way, and cast to a signed 16-bit angle, you magically have a signed difference in the range -32768..32768, which is equivalent to -180..180. It's not immediately obvious, but any 16-bit value, if interpreted as unsigned is in the range 0-360, and the same value interpreted as signed is the equivalent angle in the range -180..180. For example, angles of 359 and -1 have the same binary representation, as do -179 and +181, etc.

The resulting C code looks really simple:

Code:
uint16_t target, current, threshold;
while (labs(current-target) >= threshold) {
  ...
}

If you need more precision, you can do the same thing with 32-bit integers.
Good tip. My intent for this while statement is to wait while the absolute value angle difference is greater than or equal to dtheta/2. When the while loop exits, I restart the stepper motor at the correct speed relative to the spindle speed. (It's really a positional loop.) The stepper position is slaved to the angle of the spindle. (With some ratio.). Just before the while, I block all display updates. The ISR does position, but I prevent writing new values to the display, because it takes too much time.

I don't fully understand the ILI9341_t3n code, and I don't know if there's some hidden ISR blanking going on, or not. It gets too complicated for me to follow every routine, especially when it refers to SPI and uses other libraries. I'm not a programmer by training, so I find that kind of code sniffing about to be difficult. So I played it safe and prevented display updates during the critical sync time. It really doesn't matter to the operator (me) if the display update is temporarily paused for 450ms or three rotations of the spindle at 400 RPM.
 
The way I look at it, is I have to beat back any and all possible issues, to eventually reveal what's the real problem. It's quite possible there's a multiplicity of them, possibly of the same magnitude. Could be my lack of coding skills, or it's a systemic problem, where I'm postulating a truth, and it isn't as I think it is. I'll get there, I hope, but this has been a slow process so far.
 
Here's some sawtooth math using floating point (in degrees):
f(x) = x - floor((x + 180)/360)*360
(Note that in this form, the range is [-180,180), i.e. 180 is excluded.)
The advantage here is no if-statements/branching, but I don't really know for sure if this is superior without measurement.

Good point, @shawn . There is so much useful stuff in the standard library.

But if all the native math from the encoder is done where the full range is represented by 0-(2^N-1) instead of degrees, then @joepasquariello's approach is great. i.e. "1" means "1/(2^N)th" of a full revolution.

Yes, it works with 8/16/32/64-bit integers, so whatever precision you need can be achieved. This technique also opens the door to very fast approximations to all trig functions (sine, cosine, etc.) via lookup tables and other methods. I first encountered this stuff in articles by Jack Crenshaw in Embedded Systems Programming magazine, which was an amazing resource. He pulled together and shared a lot of his code in a very useful book called Math Toolkit for Real-time Programming.
 
I've been spectating on this thread 🍿 for a while, having nothing very useful to add up until now. But a few thoughts have occurred...

Assuming you could tell from logged data that things have gone wrong, is there any need to risk a workpiece, tool or the lathe itself by actually cutting metal?

Is the use of USB serial a complete no-no, or it is only during triggering and/or cutting that it causes issues? I was wondering if you could just log to PSRAM during sync / trigger / "cut", and dump data during the Z return time, possibly prolonged in order to complete the dump prior to the next pass.

With PSRAM, you can store a lot of data. Say you create a log record every spindle tick, i.e. at 400/60*4096 ~= 27,307Hz - that's not a huge data rate into memory. If you log at every tick for every turn of a 20-turn thread, plus a few extra at each end, for say a total of 32 turns, you need 4096*32=131,072 records, which with 8MB of PSRAM allows each record to be up to 64 bytes, or 16x 32-bit values. You'd presumably want a timestamp, spindle count, and Z position (possibly with its own timestamp), which is only 12 or 16 of your available 64 bytes/record.

Some completely untested code to show the principle:
C++:
// Modify this record structure to your requirements, 
// adjusting the logging and dumping code accordingly:
struct logRecord
{
  uint32_t timeStampS;
  uint32_t spindleCount;
  uint32_t timeStampZ;
  int32_t zPosition;
};

#define LOG_RECORD_COUNT (int)(8*1024*1024/sizeof(logRecord))

bool logging;
int logIndex;
EXTMEM logRecord logRecords[LOG_RECORD_COUNT]; // fill PSRAM

// Placeholder for your Z-encoder stuff
int32_t zPosition;
uint32_t zTimeStamp;
volatile bool directionPos;
void zISR(void)
{
  if (directionPos) 
    zPosition++;
  else
    zPosition--;
  zTimeStamp = micros();
}


// Placeholder for your spindle encoder stuff
uint32_t spindleCount;
void spindleISR(void)
{
  spindleCount++; // wrap at 4096? can it decrement?
 
  if (logging)
  {
    logRecords[logIndex] = {micros(),spindleCount,zTimeStamp,zPosition};
    logIndex++;
    if (logIndex >= LOG_RECORD_COUNT)
      logIndex=0; // wrap - or you could stop
  }
}

void dumpLog(void)
{
  for (int i=0;i<logIndex;i++)
    Serial.printf("%u,%u,%u,%d\n", // or use Serial5 if you need to
                  logRecords[i].timeStampS,
                  logRecords[i].spindleCount,
                  logRecords[i].timeStampZ,
                  logRecords[i].zPosition);
}

void stopLog(void) { logging = false; }
void startLog(void) { logIndex = 0; logging = true; }

Final thought ... if this is implemented, then you should easily be able to see any lost encoder counts, because they will have an unexpected timestamp. The spindle should have timestamps every 36.62µs (so, 36 or 37 in reality).
 
Is the use of USB serial a complete no-no, or it is only during triggering and/or cutting that it causes issues?

Yes, because it occasionally disables interrupts long enough to miss encoder edges.

With PSRAM, you can store a lot of data. Say you create a log record every spindle tick, i.e. at 400/60*4096 ~= 27,307Hz - that's not a huge data rate into memory. If you log at every tick for every turn of a 20-turn thread, plus a few extra at each end, for say a total of 32 turns, you need 4096*32=131,072 records, which with 8MB of PSRAM allows each record to be up to 64 bytes, or 16x 32-bit values. You'd presumably want a timestamp, spindle count, and Z position (possibly with its own timestamp), which is only 12 or 16 of your available 64 bytes/record.

I think this is good advice. UART Serial logging adds a lot more interrupts and may affect system timing in ways that further confuse the situation.
 
Could the encoder index pulse be temporarily enabled to recover absolute spindle position? I don’t know enough about the encoder library…
 
Could the encoder index pulse be temporarily enabled to recover absolute spindle position? I don’t know enough about the encoder library…
He's using EncoderTool, not the Encoder library. EncoderTool has its own features and approach, which I don't fully understand. I think what you suggest could be done, but adding complexity before finding the source of the problem might not be helpful.
 
He's using EncoderTool, not the Encoder library
There you go, told you I don't know enough! I had a quick Google for EncoderTool and couldn't find any reference to the index pulse at all, so I may have found the wrong library.
adding complexity before finding the source of the problem might not be helpful
So very true. Though quite often one has to add something to the system in order to find the source of a problem ... but it should ideally be as simple as possible (but no simpler). In this case, I think I'd want to log a timestamp for the index pulse.
 
I've been spectating on this thread 🍿 for a while, having nothing very useful to add up until now. But a few thoughts have occurred...

Assuming you could tell from logged data that things have gone wrong, is there any need to risk a workpiece, tool or the lathe itself by actually cutting metal?

Is the use of USB serial a complete no-no, or it is only during triggering and/or cutting that it causes issues? I was wondering if you could just log to PSRAM during sync / trigger / "cut", and dump data during the Z return time, possibly prolonged in order to complete the dump prior to the next pass.

With PSRAM, you can store a lot of data. Say you create a log record every spindle tick, i.e. at 400/60*4096 ~= 27,307Hz - that's not a huge data rate into memory. If you log at every tick for every turn of a 20-turn thread, plus a few extra at each end, for say a total of 32 turns, you need 4096*32=131,072 records, which with 8MB of PSRAM allows each record to be up to 64 bytes, or 16x 32-bit values. You'd presumably want a timestamp, spindle count, and Z position (possibly with its own timestamp), which is only 12 or 16 of your available 64 bytes/record.

Some completely untested code to show the principle:
C++:
// Modify this record structure to your requirements,
// adjusting the logging and dumping code accordingly:
struct logRecord
{
  uint32_t timeStampS;
  uint32_t spindleCount;
  uint32_t timeStampZ;
  int32_t zPosition;
};

#define LOG_RECORD_COUNT (int)(8*1024*1024/sizeof(logRecord))

bool logging;
int logIndex;
EXTMEM logRecord logRecords[LOG_RECORD_COUNT]; // fill PSRAM

// Placeholder for your Z-encoder stuff
int32_t zPosition;
uint32_t zTimeStamp;
volatile bool directionPos;
void zISR(void)
{
  if (directionPos)
    zPosition++;
  else
    zPosition--;
  zTimeStamp = micros();
}


// Placeholder for your spindle encoder stuff
uint32_t spindleCount;
void spindleISR(void)
{
  spindleCount++; // wrap at 4096? can it decrement?
 
  if (logging)
  {
    logRecords[logIndex] = {micros(),spindleCount,zTimeStamp,zPosition};
    logIndex++;
    if (logIndex >= LOG_RECORD_COUNT)
      logIndex=0; // wrap - or you could stop
  }
}

void dumpLog(void)
{
  for (int i=0;i<logIndex;i++)
    Serial.printf("%u,%u,%u,%d\n", // or use Serial5 if you need to
                  logRecords[i].timeStampS,
                  logRecords[i].spindleCount,
                  logRecords[i].timeStampZ,
                  logRecords[i].zPosition);
}

void stopLog(void) { logging = false; }
void startLog(void) { logIndex = 0; logging = true; }

Final thought ... if this is implemented, then you should easily be able to see any lost encoder counts, because they will have an unexpected timestamp. The spindle should have timestamps every 36.62µs (so, 36 or 37 in reality).
Thanks for following along. I've been eating popcorn alone for about 4 months now, so it's good to have some company ;)

I hadn't realized I had installed PSRAM on this controller, until I had just opened up my enclosure today and saw one of those chips attached underneath the Teensy. So some sort of dump log is very possible, and the dumping can occur when there's no high intensity computing that's active. Since I just completed wiring my two Teensy's with a little nifty mag connector, I can dump the data to file that way. I can simply create a new command that enables the dump when it's safe to do so. Not during a cutting operation or slowing to a stop, but after the experiment is over. The thread cutting operation consists of 8-10 passes, and spans maybe 5 minutes of operation, but only 100 seconds of cutting.

Thanks for your suggestion. I'll do this in stages. Logging every count after the thread cutting is past the first couple of mm really isn't necessary, since the threads are individually beautiful helices. It's the phase shift of a thread that is the issue. The phase shift should be 0. However, from the beginning of the thread from sync to first contact of the tool to the work piece, that's what I need to concentrate on.

Without having a first thread cut, there is no reference line to measure phase shift from. So at least a single cut needs to be done. I usually cut one very shallow depth scratch thread, then retract the cutter by a fraction of a mm and then watch to see what happens on successive trials. The correct behavior is that the cutter follows the scratch thread exactly, with no phase shift or offset. So usually I do a scratch pass then air passes, to minimize wasting material. But if I start a brand new thread, I need to "clean off" the old one by turning it. I use some cheap plastic usually for this testing, metal is getting too expensive to waste.

Under other operations, Teensy can follow significantly higher spindle speeds. But for threading coarse threads automatically (that's the goal) I would never operate over 400 RPM. It's astonishing how fast the carriage moves when cutting a coarse thread, and scary to think I'm depending on the Teensy to automatically stop before crashing into something. It does stop, but it's still hard for me to trust it, even after 4 months of testing. I started at very slow RPMs at first, gradually turning it up. Comes to a stop within 5um every time on my lathe, but it's still scary to watch!
 
Last edited:
Great, sounds like we might yet get a handle on this thing!

The untested skeleton code in post #89 could easily be adapted to log the first couple of mm for each of your 8-10 passes, then as you say dump the result when all is done and safe. You'd probably want some sort of flag record to indicate the start of a new pass, though I guess the Z position would tell you that anyway.
 
There you go, told you I don't know enough! I had a quick Google for EncoderTool and couldn't find any reference to the index pulse at all, so I may have found the wrong library.

So very true. Though quite often one has to add something to the system in order to find the source of a problem ... but it should ideally be as simple as possible (but no simpler). In this case, I think I'd want to log a timestamp for the index pulse.
Luni had helped me a lot with getting this going. Seem to remember trying the index pulse, but can't seem to find any docs for it at the moment.
 
Yup, that's what I found. Seems to take two A/B quadrature pins on construction, and I'd've thought there'd be a third optional one for an index pulse if it was natively supported. But I can't find it, or anything like it ... odd.
 
Yup, that's what I found. Seems to take two A/B quadrature pins on construction, and I'd've thought there'd be a third optional one for an index pulse if it was natively supported. But I can't find it, or anything like it ... odd.
Might have been in an earlier version? Don't know. Most rotary encoders have an index pulse. I probably tried it at some time - long before I wised up and made a git repo for my ELS code. Can't seem to find a record of using the index with his sw. Luni doesn't appear to be active here lately so can't ask him.
 
If your encoder’s index is wired in, it wouldn’t be too hard to attach an interrupt to it, at least to log (in the first instance). But as @joepasquariello noted, there’s no sense adding stuff in until you have a clear and present need for it.
 
If your encoder’s index is wired in, it wouldn’t be too hard to attach an interrupt to it, at least to log (in the first instance). But as @joepasquariello noted, there’s no sense adding stuff in until you have a clear and present need for it.
Pin 0 = A, Pin 1 = B, Pin 4 = Index. Through cables and a PCB. Already connected.

Could be instructive to run a separate function to check if there's apparent angle drift between the index, which is absolute, and whatever angle was the arbitrary origin. The encoder count modulo 4096 ought to be static at the index pulse rising edge (or falling). If it isn't static, there's loss of counts. Won't know the cause, but will know there's an issue. It's good to verify what's real and what is merely conjecture.

Have the two Teensy's wired and working together (not with full logging, through Serial5) but comms are working between the two down in the shop. Now to write something sensible for logging.

I really like the idea of logging to PSRAM during the critical synchronization time. And a simultaneous check for index drift. Index timing should look like:
Screenshot 2025-07-24 at 6.00.01 PM.png

So if I can figure out how to wait for the next count after the rising edge of index (rising edge of A) it should always be the same count (modulo 4096).

Time to write more code...
 
Back
Top