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

I have a newer Mac. (M3) Windows doesn't run on it, nor do I wish to use some solution to allow it. Windows has run it's course in my home... Both my wife and I transitioned away from it around 2005. Did the Linux thing until late 2024, then went to a Mac. Every OS has it's issues, seen most of them.

I have used Visual Studio on a Mac, that works, although I've yet to compile Arduino stuff with it. Apparently you need VisualMicro? Dunno, this is just hobby use. Don't know if VisualMicro runs on a mac. Their FAQ page says:

Does Visual Micro work with Visual Studio for Mac?

Sadly the Visual Studio for Mac is a different product which does not support the C++ programming environment required for Visual Micro.


I've compiled C++ on a Mac, not sure what the problem they have. But it appears they don't support a Mac. That's the end of that.
AI says "For Macs with Apple silicon (M1, M2, etc.), you'll need to use virtualization software like Parallels Desktop, VMware Fusion, or UTM to run Windows in a virtual machine."
Using VisualMicro was just a thought. Thought there was a possibility it might help you to solve your problem.
 
I know your using a MAC but if you can use Windows on it you might consider looking at VisualMicro (VM) with Visual Studio.
VM gives you run time debugging, though I must admit I have not tried using that feature.
How does the VM debugging work with Teensys? Does it use that TeensyDebug library or something?
 
How does the VM debugging work with Teensys? Does it use that TeensyDebug library or something?
I don't know. I use VisualMicro all the time but have never tried the built-in debugging, couldn't work out whether it used Usb Serial or hardware Serial, so never tried. The instructions are not too clear to me. They seem to be written by someone who knows how it works for someone who also knows how it works!
 
AI says "For Macs with Apple silicon (M1, M2, etc.), you'll need to use virtualization software like Parallels Desktop, VMware Fusion, or UTM to run Windows in a virtual machine."
Using VisualMicro was just a thought. Thought there was a possibility it might help you to solve your problem.
Thanks for the suggestion, though. Appreciate the thought. I'll have to be more resourceful.
 
How might one transmit in binary, and echo in text? Doesn't that require a parser of some sort?
I'm thinking of logging on Serial5 in binary. Then using a second Teensy to read the binary, and do the printf's on the echo device.

From my weak understanding, the stream is bytes, but I need (on the receive side) to parse the stream somehow, and send bytes, and words converted to string. Is that a fair statement of the issue? (There's nothing worse than coding a wrong concept...)
 
Thanks, that's the easy part of the question... ;).

Really was wondering about the parsing of binary command bytes with a method to recover from an error (garbled command?). Is there a simple way to recover? I mean, if all goes well, everything just works, but if there's a hiccup, then how does one recover/resync to the binary data? This binary data logging will be in the presence of a 1 HP motor being driven by a Variable Frequency Drive controller.
 
Thanks, that's the easy part of the question... ;).

Really was wondering about the parsing of binary command bytes with a method to recover from an error (garbled command?). Is there a simple way to recover? I mean, if all goes well, everything just works, but if there's a hiccup, then how does one recover/resync to the binary data? This binary data logging will be in the presence of a 1 HP motor being driven by a Variable Frequency Drive controller.
You’d probably want to use some framing protocol so that you know where data frames start. Two examples are SLIP and COBS. CRC’s are also useful to validate frame data. For data recovery upon error, there’s a bunch of ways to do error correction, but I have a feeling that with a reliable transport (eg. serial or USB), you probably only need to worry about framing and synchronization.
 
I almost always use a state-driven parser. In each state, the parser knows exactly what is expected (framing/header/trailer byte, data byte, data range, data type, checksum, etc.) & it can change state appropriately (didn't get a correct framing/header/trailer byte when expected ?? then discard any partial frame + data received up to that point & immediately change state as needed to start looking for the next frame). A properly designed & implemented parser will always gather complete & correct frames, as well as throw away everything else that isn't.

Mark J Culross
KD5RXT
 
Here is an ino file I use to receive binary data from an ESP32 onto a Teensy for display/storage.
It automatically synchs up with the sender program.
The file packet for the data transfer has an identifier at it's head. The receive program looks for this identifier to get into synch with the sender.
I apologise for it's appearance but at the moment I don't have time to re-write it as a pretty example program. Hopefully you can understand it. Any questions just get back to me.
Code:
/*
    T4.1 Take readings from UART (from ESP32C3) in text and save to SD card and show on screen.
    Modified to detect SD Card present, or NOT.

    SoftwareId: S.0004.0004.002
    SoftwareId: S.0004.0004.003 Added MTP support
*/
#define softwareId "S.0004.0004.003 - Added MTP support"

#include <SD.h>
#include <timelib.h>

//#include <MTP_Teensy.h>

#define numTargets 4

#define CS_SD BUILTIN_SDCARD  // Works on T_3.6 and T_4.1

#define ledPin 13

const uint32_t identifier = 57767959;

union identifierUnion {
    uint32_t Identifier;
    char     identTxt[sizeof(uint32_t)];
};

identifierUnion identMatch;

struct msgType {  //  <<---- Format of message for sending and receiving
    uint32_t ident;
    uint8_t  id;
    uint16_t temp;
    uint16_t humidity;
};

struct uniMsgType {
    union {
        msgType d;
        char    txt[sizeof(msgType)];
    };
};

uniMsgType recvMsg;

const uint8_t maxArr = numTargets;

typedef struct msgArrType {
    msgType data[maxArr];
    bool    gotData[maxArr];
    uint8_t hour;
    uint8_t min;
} msgArrType;

msgArrType recvMsgArr;
File dataFile;

String GenerateDateStr(char c) {
    String s = "";

    int n = day();
    if (n < 10) s.concat( "0");
    s.concat(n);
    s.concat('/');
    n = month();
    if (n < 10) s.concat("0");
    s.concat(n);
    s.concat('/');
    n = year();
    if (n < 10) s.concat("0");
    s.concat(n);
    if (c != 0x0) s.concat(c);
    return s;
}

String GenerateTimeStr(bool andSeconds, char c ) {
    String s = "";

    int n = hour();
    if (n < 10) s.concat("0");
    s.concat(n);
    s.concat(':');
    n = minute();
    if (n < 10) s.concat("0");
    s.concat(n);
    if (andSeconds) {
        s.concat(':');
        n = second();
        if (n < 10) s.concat("0");
        s.concat(n);
    }
    if (c != 0x0) s.concat(c);
    return s;
}

time_t getTeensy3Time(){
    return Teensy3Clock.get();
}

void send(const msgType* msg)
{
    Serial1.write((const char*)msg, sizeof(msgType));  // 2 bytes.
}

bool receive(uniMsgType* msg)
{
    return (Serial1.readBytes((char*)msg, sizeof(uniMsgType)) == sizeof(uniMsgType));
}

//String t    = "";
bool   sdSetup = true;

void setup()
{

    identMatch.Identifier = identifier;

    setSyncProvider(getTeensy3Time);

    Serial.begin(115200);
    while (!Serial && millis() < 5000);
    Serial1.begin(115200);
    Serial.print("SoftwareId: ");    Serial.println(softwareId);
    Serial.print("Initializing SD card...");

    pinMode(ledPin, OUTPUT);
    // see if the card is present and can be initialized:
    if (SD.begin(CS_SD)) {
        MTP.addFilesystem(SD, "SD Card");
        Serial.println("card initialized.");
    }
    else {
        Serial.println("Card failed, or not present");
        sdSetup = false;
        // No SD card, so don't do anything more - stay stuck here
    }

    if (timeStatus() != timeSet) {
        Serial.println("Unable to sync with the RTC");
    } else Serial.println("RTC has set the system time");

    // mandatory to begin the MTP session.
    MTP.begin();

    Serial.println(GenerateDateStr(0x0));
}

void PrintDec(uint8_t n) {
    if (n < 10) Serial.print("0");
    Serial.print(n);
};

void PrintN(uint16_t n) {
    uint16_t q;

    Serial.print(n / 100);
    Serial.print(".");
    q = n % 100;
    PrintDec(q);
}

void PrintNN(uint16_t n) {
    uint16_t q;

    dataFile.print(n / 100);
    dataFile.print(".");
    q = n % 100;
    if (q < 10) dataFile.print("0");
    dataFile.print(q);
}

void PrintData() {

    String where;
    uint8_t n;
    uint8_t p;

    n = 0;

    PrintDec(recvMsgArr.hour);
    Serial.print(":");
    PrintDec(recvMsgArr.min);
    Serial.print(" ");

    while (n < maxArr) {
        p = recvMsgArr.data[n].id - 1;
        if (recvMsgArr.gotData[n]) {
            switch (p) {
            case 0:
                where = "GBrm:";
                break;
            case 1:
                where = "Lnge:";
                break;
            case 2:
                where = "BBrm:";
                break;
            case 3:
                where = "CBrm:";
                break;
            }
            Serial.print(where);
            PrintN(recvMsgArr.data[n].temp);
            recvMsgArr.data[n].temp = 0;
            Serial.print(" ");
            PrintN(recvMsgArr.data[n].humidity); recvMsgArr.data[n].humidity = 0;
            Serial.print("   ");
        }
        else Serial.print("-----------------  ");
        recvMsgArr.gotData[n] = false;
        n = n + 1;
    }
    Serial.println();
}

String tim;
String date;
uint8_t lastMin = 99;
uint8_t currMin = 98;
uint8_t p;
// Add the main program code into the continuous loop() function
void loop()
{
    MTP.loop();  //This is mandatory to be placed in the loop code.
    while (Serial1.available() >= (int)sizeof(msgType)) {
        digitalWrite(ledPin, HIGH);
        if (receive(&recvMsg)) {
            if (recvMsg.d.ident == identifier) {
                currMin = minute();
                if (currMin != lastMin) {
                    if (lastMin != 99) {
                        PrintData();
                    }
                    lastMin = currMin;
                }

                p = recvMsg.d.id - 1;              //   Serial.print(p); Serial.println(" ");
                recvMsgArr.data[p] = recvMsg.d;
                recvMsgArr.gotData[p] = true;
                recvMsgArr.hour = hour();
                recvMsgArr.min = minute();
//Serial.print("sdSetup:"); Serial.print(sdSetup); Serial.print("  SD.mediaPresent:"); Serial.print(SD.mediaPresent());
                if (sdSetup && SD.mediaPresent()) {
                    
                    dataFile = SD.open("datalog.csv", FILE_WRITE);
//Serial.print("   dataFile:"); Serial.println(dataFile);
                    // if the file is available, write to it:
                    if (dataFile) {
                        for (int q = 0; q < (recvMsg.d.id-1) * 5; q++) {
                            dataFile.print(',');
                        }

                        date = GenerateDateStr(' ');
                        tim = GenerateTimeStr(true, ',');

                        dataFile.print(date);
                        dataFile.print(tim);
                        dataFile.print(recvMsg.d.id); dataFile.print(",");
                        PrintNN(recvMsg.d.temp);      dataFile.print(",");
                        PrintNN(recvMsg.d.humidity);  dataFile.println();
                        dataFile.close();

                    }
                    else Serial.println("error opening datalog.csv");

                }
                else Serial.println("No SD Card (Media) present");
            }
            else {

                Serial.println("Bad data, getting back into synch");
                for (uint n = 0; n < sizeof(msgType); n++) {
                    Serial.print(recvMsg.txt[n]);
                }
                bool insynch = false;
                elapsedMillis outOfSynchTime;

                while (!insynch && outOfSynchTime < 5000) {        // after 5 seconds of no serial1 data should be back in synch
                    while (Serial1.available() && (Serial1.peek() != identMatch.identTxt[0])) {
                        Serial.print((char)Serial1.read());
                        outOfSynchTime = 0;
                    }
                    if (Serial1.available()) insynch = (Serial1.peek() == identMatch.identTxt[0]);
                }
                Serial.println("Back in synch");
            }
        } else Serial.println("Unable to Receive Data");
        digitalWrite(ledPin, LOW);

    }
    delay(1);

}
 
Here is an ino file I use to receive binary data from an ESP32 onto a Teensy for display/storage.
It automatically synchs up with the sender program.
The file packet for the data transfer has an identifier at it's head. The receive program looks for this identifier to get into synch with the sender.
I apologise for it's appearance but at the moment I don't have time to re-write it as a pretty example program. Hopefully you can understand it. Any questions just get back to me.
Thanks.

I'll take a good close look at this and see what I can learn from it. I haven't played with MTP yet, so that's interesting, as well as the basic code architecture.
 
Thanks.

I'll take a good close look at this and see what I can learn from it. I haven't played with MTP yet, so that's interesting, as well as the basic code architecture.
You don't have to use MTP. If you don't want it just delete all the MTP references. I originally wrote it to log to SD disk and to the screen. It was only latterly that I added the MTP. It sometimes makes it easier to look at the results on a PC but does have drawbacks in that new data does not automatically appear on the PC unless you close the directory on the PC and then open the directory again. At least I think that is the way it works. It's been a while since I have used it.
 
You don't have to use MTP. If you don't want it just delete all the MTP references. I originally wrote it to log to SD disk and to the screen. It was only latterly that I added the MTP. It sometimes makes it easier to look at the results on a PC but does have drawbacks in that new data does not automatically appear on the PC unless you close the directory on the PC and then open the directory again. At least I think that is the way it works. It's been a while since I have used it.
But I'd like to get MTP to work, as it might be easier in the end... However, MTP is not native to a Mac. I'm going to try openmtp installed via homebrew. I don't know if it will actually work on a Teensy, as it was written primarily for Android phone to Mac file transfer. But first I need to study the basic message comms stuff. MTP is just something new for me to pick up.

Are there any simple known good MTP test programs around that I could use to check openMTP? Don't see any in the Teensy examples.

Edit: Found some in this thread: https://forum.pjrc.com/index.php?th...ct-and-3-2-that-is-already-serial-midi.74404/
 
Last edited:
But I'd like to get MTP to work, as it might be easier in the end... However, MTP is not native to a Mac. I'm going to try openmtp installed via homebrew. I don't know if it will actually work on a Teensy, as it was written primarily for Android phone to Mac file transfer. But first I need to study the basic message comms stuff. MTP is just something new for me to pick up.

Are there any simple known good MTP test programs around that I could use to check openMTP? Don't see any in the Teensy examples.

Perhaps you could get a cheap Windows laptop to do the MTP file transfer. You can still do all of your development on your MAC.
 
It was only latterly that I added the MTP. It sometimes makes it easier to look at the results on a PC but does have drawbacks in that new data does not automatically appear on the PC unless you close the directory on the PC and then open the directory again. At least I think that is the way it works. It's been a while since I have used it.

MTP.send_DeviceResetEvent() notifies the host that it should refresh its information. If you think of your application as having two states, Logging and NotLogging, this approach has worked for me:

1) Don't call MTP functions while in Logging state
2) Call MTP.send_DeviceResetEvent() once after transition from Logging to NotLogging
3) Call MTP.loop() periodically while in NotLogging state to handle MTP traffic with host
 
Perhaps you could get a cheap Windows laptop to do the MTP file transfer. You can still do all of your development on your MAC.
Rather not go there. If it was $10, sure, over $100, hard no. I still have a Win10 anemic laptop, maybe it will boot... It was a cheap laptop, never again. It had the slowest HDD I've ever experienced. Replaced it with an SSD and things were slightly better, but still slow. It was a matched system - all devices were slow... What a waste of money that was.

I'll try to get openmtp to work. The other avenue mentioned in the thread I referenced, is not available for an ARM64 based system that's not Windows, I went there and checked. If MTP is a dead end for me, oh well.
 
I'll try to get openmtp to work. The other avenue mentioned in the thread I referenced, is not available for an ARM64 based system that's not Windows, I went there and checked. If MTP is a dead end for me, oh well.

Maybe it's time to take a step back and review again. Do I have this right?
  • Your ELS works well most of the time, but occasionally glitches, and you're trying to figure out why.
  • You think the glitch implies that an encoder edge has been missed.
  • You are using the Encoder library, which interrupts on both edges of A and B.
  • You want to log data to determine the source of the problem.
  • You added print statements with encoder counts to determine code location when glitch occurs.
  • You can't log to SD because the T4.1 is not accessible and MTP is not supported on MAC.
  • You can't log to USB Serial because it occasionally disables interrupts, which is disqualifying.
  • You're trying to log to UART Serial, but the data rate may not be high enough.
 
Maybe it's time to take a step back and review again. Do I have this right?
  • Your ELS works well most of the time, but occasionally glitches, and you're trying to figure out why.
It only fails to sync during thread to stop mode. All other modes work flawlessly, (normal thread, normal feed, and feed to stop) and have for three years.
  • You think the glitch implies that an encoder edge has been missed.
Maybe, maybe not. Can't tell. Premature to speculate.
  • You are using the Encoder library, which interrupts on both edges of A and B.
No. I am using EncoderTool, which interrupts on both A and B. Encoder library doesn't seem to have a method that I can use for this. I built my code around EncoderTool, which has an entirely different API from Encoder library.
  • You want to log data to determine the source of the problem.
Yes.
  • You added print statements with encoder counts to determine code location when glitch occurs.
No. I added print statements during sync time, and first step time. They had selected variable values. I did not send out encoder counts, as they occur rapidly, roughly every 33us.
  • You can't log to SD because the T4.1 is not accessible and MTP is not supported on MAC.
I could log to SD, but the T4.1 is not very accessible in the shop, since it need to be in an enclosure. MTP is not supported by Apple, and I have a Mac. I'm willing to try openmtp, it's installed, but have yet to test it. That's why I was asking about example code for mtp-teensy. Need something simple, so I can do a test read back of a file on an SD card.
  • You can't log to USB Serial because it occasionally disables interrupts, which is disqualifying.
Yes. There's no way to stop it from globally disabling (blocking) all interrupts for somewhat lengthy times, at it's whim. It's in the source. If my encoder interrupts are blanked for more than 33 us (at only 400 RPM) then I've lost sync.

The amount of sync error is on the order of 10's of ms, so it may not be USB Serial, but very little else has the potential to globally disable interrupts. All other routines are interruptible and have their priorities set. I even blank updating to the display, during sync time, to prevent some code deep down in the bowels of the display routines from clobbering me. Still getting errors that correspond to long delays.
  • You're trying to log to UART Serial, but the data rate may not be high enough.
Yes, I'm setting up a HWSerial channel. If I'm careful, it might be ok. I'm starting out at 3Mbps. The wire will be about 8-12" long.
If I send out encoder counts, along with lots of variables, like a real dump, then the data rate likely won't be sufficient.
 
Thanks, if I may ask a couple of follow-up questions...

Can you explain what you mean by sync error? Do you mean position error, i.e. your position reading is no longer the actual position? Does it imply that somehow encoder edges have not been counted?

When you say sync error is on the order of 10s of ms, that implies units of time, not position, so can you explain what you mean by that?
 
Thanks, if I may ask a couple of follow-up questions...

Can you explain what you mean by sync error? Do you mean position error, i.e. your position reading is no longer the actual position? Does it imply that somehow encoder edges have not been counted?

When you say sync error is on the order of 10s of ms, that implies units of time, not position, so can you explain what you mean by that?
It's kind of complicated, but simple at the same time. When you cut a threads on a lathe, they are usually cut in multiple passes, since most (99% of them!) lathes are not massive and rigid enough to do it in a single pass. This creates a requirement to have successive helices to overlay the same path as the original cut, but be a little deeper every time. So sync is in effect to the original cut. It's actually a rotational error.

At t=t0, the cutter has to be at z=z0 (the start of the work piece) AND the spindle angle has to be at theta = theta0, and the position of the carriage has to be at a fixed known ratio to the spindle angle. As the spindle changes angle, the carriage and cutting tool progresses linearly. At t=t1, the next pass, z, and theta have to be at z0 and theta0 along with the carriage position locked to the spindle angle at the same ratio as before. I have a DRO (a linear encoder with 1um resolution) on z, and a rotary encoder (4096 counts/rev) on the spindle, so I know very accurately where they are at any moment in time.

At 400 RPM, my test speed, I cut a single light thread in a piece of material. It threads at the correct pitch, and stops automatically at the stop point. I then retract the cutter, so it won't cut, then go back to the initial start point again. Then tell the machine to go. I can observe the cutter doesn't follow the same path. It cuts a helix of the proper pitch, but it is phase shifted, so the cut would no longer be tracing out the previous path. If I repeat this, I find, for the most part, the helix is randomly phase shifted from the starting cut. Maybe 5% of the time, it falls on the original and correct path.

At 400 RPM, the spindle revolves once in 150 ms. So we can express phase error to time error. 75 ms would correspond to 180 degrees of phase error. If there's phase error, the screw is effectively ruined and headed for the scrap bin. If the error occurs in a deep cut, ie a very coarse thread near the bottom, then the lathe can stall, or break, or break the tooling, as the forces can become very large, with an effectively stalled cutter relative to the thread which is still turning. At least on my lathe, I can't cut 1/4" deep in steel in a single pass.

For what it is worth, the phase error (or timing error, take your pick) doesn't seem to be strongly related to RPM, which I find surprising. I tried 200 and 100 RPM and see similar random like phase errors.

Anyways, the problem has been confounding me for 4 months now... It bugs me, since well, it should work. I have all the information to recreate the tooling path vs time.

If this is unclear, please ask more questions.
 
Is your phase error entirely due to error in spindle angle (theta), or is it due to error in cutter position (z), or both?

Are you using EncoderTool only for the spindle (theta) rotary encoder, or are you also using it for the cutter (z) linear encoder?
 
Is your phase error entirely due to error in spindle angle (theta), or is it due to error in cutter position (z), or both?
Unknown really. I suspect it's angle related, as the angle changes quickly. Z changes, relatively slowly. Logging of z shows linear movement down to the micron level. If I want to, I can watch it change micron by micron. I rarely do, save for initial investigations. Z has been boring...

I also suspect angle math, as it's very prone to error, doing what one thinks is simple angle subtraction has lots of pitfalls with functions returning negative angle or angles > 180. I *think* that was beaten into submission, but, it could very well be biting me in the posterior... I have this while loop waiting for sync, it's supposed to exit when the difference between the target angle and measured is less than 1 count. This is a prime suspect. But every "test" I've been able to create seems to pass. Including watching every angle increment and printing out the difference, at least with the limited desk testing, I just don't see an error condition.
Are you using EncoderTool only for the spindle (theta) rotary encoder, or are you also using it for the cutter (z) linear encoder?
Using different instances of EncoderTool for z and theta. Z occurs at 100 Hz rates, theta is more like 10's of KHz. Z encoder routine is quite fast, there's not much going on. Z = countvalue * conversion_factor; That's it. Z is relative, so I can set the zero point (in the main loop). I also have an X encoder, but it doesn't move during an active pass of cutting. X executes quickly too, same as Z. X is the cross dimension, typically the cutter displacement. Z is the axis coaxial with the spindle axis. Theta is the rotation angle about the spindle axis. It's the lathe chuck angle.
 
Wondering about a less destructive faster to repeat test.
> Start per p#70 but retract and quit early - some few revolutions - a tenth of a second?
> restart per p#70 but don't advance the cutter depth - perhaps extend the cut some small amount
> repeat this - going no deeper but repeating the thread alignment for restart

Buffer the data you can for a short p#70 process - on retract send out all the collected data before advancing.
> is there a PSRAM or available RAM1 or RAM2 to fill some less than a second of rotational data at the start
> not sure of thread pitch? 10 to 40 per inch?

If the problem repeats the same at 100, 200 or 400 RPM - start at 100 and see what is needed to work.
 
Wondering about a less destructive faster to repeat test.
> Start per p#70 but retract and quit early - some few revolutions - a tenth of a second?
> restart per p#70 but don't advance the cutter depth - perhaps extend the cut some small amount
> repeat this - going no deeper but repeating the thread alignment for restart

Buffer the data you can for a short p#70 process - on retract send out all the collected data before advancing.
> is there a PSRAM or available RAM1 or RAM2 to fill some less than a second of rotational data at the start
> not sure of thread pitch? 10 to 40 per inch?

If the problem repeats the same at 100, 200 or 400 RPM - start at 100 and see what is needed to work.
Some good ideas. For these tests I have to use a coarse thread, simply because it's easy to see if there's a phase error, the cutter tip is obviously not in the groove. With a fine thread, it's impossible to see. But coarse threads advance VERY quickly, so it's hard to follow by eye. So much so, that I need to have the tip of the cutter 0.005" from the rod, and have to stare watching this very quick event. A 4 TPI thread, advances 1/4" in 150 ms, at 400 RPM. Believe me, I was scared doing this initial testing. But my stopping control loop seems to work ok. (Based on Z.)

Since the threadline (the line the cutter takes on the rod) isn't repeating currently, if I leave the cutter in place, it rapidly makes a mess of the rod, and I no longer can tell where the first line was after a couple passes. It's not like it's a red line, it looks like the other ones. So I have to turn the rod, so I get a fresh surface to observe.

Here's a picture what happened when the phase was off by ~180 degrees. Basically the TPI doubled, and created burs, and ruined the thread.
PXL_20250426_202754255.jpg
I started out with full diameter, and well, you can see that I wasn't so successful that day.
 
Unknown really. I suspect it's angle related, as the angle changes quickly. Z changes, relatively slowly. Logging of z shows linear movement down to the micron level. If I want to, I can watch it change micron by micron. I rarely do, save for initial investigations. Z has been boring...

I also suspect angle math, as it's very prone to error, doing what one thinks is simple angle subtraction has lots of pitfalls with functions returning negative angle or angles > 180. I *think* that was beaten into submission, but, it could very well be biting me in the posterior... I have this while loop waiting for sync, it's supposed to exit when the difference between the target angle and measured is less than 1 count. This is a prime suspect. But every "test" I've been able to create seems to pass. Including watching every angle increment and printing out the difference, at least with the limited desk testing, I just don't see an error condition.

Using different instances of EncoderTool for z and theta. Z occurs at 100 Hz rates, theta is more like 10's of KHz. Z encoder routine is quite fast, there's not much going on. Z = countvalue * conversion_factor; That's it. Z is relative, so I can set the zero point (in the main loop). I also have an X encoder, but it doesn't move during an active pass of cutting. X executes quickly too, same as Z. X is the cross dimension, typically the cutter displacement. Z is the axis coaxial with the spindle axis. Theta is the rotation angle about the spindle axis. It's the lathe chuck angle.

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?
 
Back
Top