Optical Table CNC Lathe - motor selection

I want to be able to operate the lathe manually by moving wheels on the axes. It seemed easiest to implement that using dual shaft steppers. The ballscrew on the x axis already came with a stepper motor with a shaft damper, so I just used that damper as a hand wheel. The y axis had a nema23 mount, so I modified a MDrive23 motor to accept standard stepper drives and added a hand wheel. The brass shaft adapter to make that work was actually the 2nd part the lathe made for itself, after the chuck back plate.

The MDrive makes a lot of hissing noise when driven this way, and both it and the smaller stepper vibrate noticeably when idle.

I am using a KFlop/SnapAmp to drive the motors, so I asked the manufacturer if this is expected behavior. The tech support by Dynomotion is outstanding. After some back and forth communication and different tests, it became clear that the MDrive motor is to blame for the noise. Hooking up a similar motor results in no noise (but still vibrating).

The ballscrew that motor is connected to does have an encoder, but it was damaged in shipping.So to check for the extent of the vibration, I dug up a much smaller stepper with a working encoder mounted directly to its shaft. The encoder is a US Digital E2 unit with 500 lines, yielding a 2000 quadrature counts per revolution. This motor was much smaller, and the vibration seemed a little less, but still noticeably there. Yet, the encoder did not move a single count. Putting torque on the shaft manually did move the encoder count, so the encoder was working. Therefore the vibration, while easily felt, is less than 1/2000 of a rotation.

I wanted to see if the vibration goes away when the motor is configured as a 4-phase servo, but I must have mis-configured something as the motor emitted smoke and is now broken...

For completeness, I wanted to check the extent of the vibration of the larger stepper. The broken encoder was a US Digital E2 unit as well, but the shaft size and line count were different. I wondered for a while whether the US Digital/HEDS electronics can be mixed and matched with different encoder wheel sizes. Using the Quadrature Encoder Tester, this was easy to experiment on.

It turns out that the code wheels cannot be interchanged. A 500 line count wheel has to go with the corresponding sensor.

So, for example, this unit is a HEDS-9100 for use with a E code wheel.

Which matches the 200 Line specification given by the US Digital spec.

Since, I could not use the existing code wheel, I decided to modify the new one. This is not the preferred treatment, but since the small motor was broken, and I have nothing else with that shaft size, there was not much to lose:

It worked out well, and I was left with a working encoder on the ballscrew:

Now, there is a 1:2 reduction on that belt drive, and the belt reduces the vibration greatly anyway, but the result is that the vibration on the stepper still results in less than 1/2000 movement on the ball screw.

One other thing I tried is to lower the voltage of the power supply but replacing it with a lab power supply. As the voltage is lowered from 31V to 5V, the vibration gradually disappears, so that it is directly related to the supply voltage.

Tom Kerekes from Dynomotion had an explanation:

"I think you are at the noise level of SnapAmp. SnapAmp has a 70Amp current measurement range so to control within 100ma quickly is difficult with high-frequency high-voltage switching going on.[...] it controls PWM (Voltage) based on measured and desired currents with a digital current feedback loop. How the system dynamically reacts to current errors depends on the supply voltage, motor parameters (Inductance), and internal gains. Reducing the Supply Voltage will essentially reduce the feedback gain. If the error response is slow the effect of measurement noise will be reduced. SnapAmp has programmable Gains that may make a difference at the expense of performance."

So the vibration is normal. I still want to drive this stepper in 4-phase servo mode to see if it is quieter/smoother.

After much experimentation, and a lot of help from Dynomotion support, I finally got it working. It is not as smooth as I hoped, though.

The way I set this up is to start with a known working stepper configuration. Then, I added the modifications for 4 phase operation at the bottom.

#include "KMotionDef.h"  
 
void main() 
{
     
    WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT0,9);  // current limit
    WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT1,9);  // current limit
    Delay_sec(0.1);  // wait for any fault to clear
 
    ch5->InputMode=NO_INPUT_MODE;
    ch5->OutputMode=MICROSTEP_MODE;
    ch5->Vel=200.000000;
    ch5->Accel=5000.000000;
    ch5->Jerk=50000.000000;
    ch5->P=0.100000;
    ch5->I=0.000000;
    ch5->D=0.000000;
    ch5->FFAccel=0.000000;
    ch5->FFVel=0.000000;
    ch5->MaxI=200.000000;
    ch5->MaxErr=1000000.000000;
    ch5->MaxOutput=100.000000;
    ch5->DeadBandGain=1.000000;
    ch5->DeadBandRange=0.000000;
    ch5->InputChan0=0;
    ch5->InputChan1=5;
    ch5->OutputChan0=10;
    ch5->OutputChan1=11;
    ch5->LimitSwitchOptions=0x0;
    ch5->InputGain0=1.000000;
    ch5->InputGain1=1.000000;
    ch5->InputOffset0=0.000000;
    ch5->InputOffset1=0.000000;
    ch5->invDistPerCycle=1.000000;
    ch5->Lead=0.000000;
    ch5->MaxFollowingError=1000000000.000000;
    ch5->StepperAmplitude=40.000000;
 
    ch5->iir[0].B0=1.000000;
    ch5->iir[0].B1=0.000000;
    ch5->iir[0].B2=0.000000;
    ch5->iir[0].A1=0.000000;
    ch5->iir[0].A2=0.000000;
 
    ch5->iir[1].B0=1.000000;
    ch5->iir[1].B1=0.000000;
    ch5->iir[1].B2=0.000000;
    ch5->iir[1].A1=0.000000;
    ch5->iir[1].A2=0.000000;
 
    ch5->iir[2].B0=1.000000;
    ch5->iir[2].B1=0.000000;
    ch5->iir[2].B2=0.000000;
    ch5->iir[2].A1=0.000000;
    ch5->iir[2].A2=0.000000;
     
     
    DefineCoordSystem(5,-1,-1,-1); //define ch4 as x and ch5 as y; z and a disabled
    EnableAxis(5);
    DisableAxis(5);    // make sure the axis is disabled and not writing to SnapAmp
    Write4PH(ch5,60.0, 0.0);       // energize a pole
    Delay_sec(2.0);                // wait 
    Zero(5);
    ch5->InputMode=ENCODER_MODE;
    ch5->OutputMode=BRUSHLESS_4PH_MODE;
    ch5->CommutationOffset = 5;
    ch5->invDistPerCycle= 1.0 * 50.0/1000.0;
    EnableAxisDest(5, 0.0);
     
}

Ok, it works. It is still very 'jumpy', as if it has a lot of torque ripple. There is a big difference between commutation offsets 4,5,6 and so if the belt stretches even minutely, torque suffers.

This system is in torque mode, and acts a bit like a pendulum. It will let me torque it away from equilibrium and it will return. If I overdo it, it oscillates back and forth rapidly. Proportional only feedback is not sufficient unless there is a lot of friction in the system.

Lessons learned:

- A single encoder count makes a lot of commutation angle difference in this setup. Go with high encoder counts if you want to run a stepper as a 4 phase motor!

- Additionally, belt flex can exacerbate the problem. Put the feedback device on the motor shaft!

I am going to have to order a stepper motor with high-res encoder mounted on the shaft to continue this experiment.

In addition, this gives me some answers to some of the feedback options I have been contemplating (single or dual;servo or stepper). It looks like a feedback device (encoder) used to assist with the commutation of the motor should be connected as directly as possible to the motor axis. This serves to take any sort of drive train flex and machine vibration out of the feedback loop.

So for a stepper, the glass scale can be the sole feedback (though with fairly low gain). The stepper does not use commutation feedback. But if I put a servo motor (3 or 4 phase) on an axis, there should be two encoders - one for the motor axis, and the glass scale.

There is a nice discussion at:

http://www.cnczone.com/forums/stepper-motors-drives/148947-cnc.html

where one of the post states:

"Beware of using too high a voltage, especially with low inductance motors and/or underdamped loads.

High voltage increases the excitation energy per microstep, in other words the motor moves from one microstep to the next much quicker so it violently "bangs" between steps and causes ringing and stress instead of rotating gently from one step to the next. An obvious symptom of too high a voltage will be that the motor "resonance" and noise is greatly increased at low speeds.

So a high voltage PSU allows very fast motor speeds (which is it's ONLY advantage), but at the cost of greatly increased vibration and resonance at ALL lower speeds, where the machine is commonly used. For many people this means using a higher voltage PSU will cause all general cutting jobs (which happen at low motor speeds) to have vibration issues like cutting finish and increased wear on tools, bearings etc."

Which seems to agree nicely with what I have seen here.

I obtained a new encoder (Quantum Devices LP12-5000-0-A-B-L-C-A) that has 5000 lines or 20000 pulses per revolution in order to continue this line of investigation. This encoder requires that the rear shaft of the stepper be a bit longer than what one gets on most motors. I did find a Compumotor with a slightly longer shaft and was able to mount the encoder on that. The motor arrived a bit dirty, so I took it apart and cleaned it. Luckily the debris I found inside of it had come from the outside and was not degradation of the motor proper. After cleaning all the parts looked to be in excellent condition. A quick test of the encoder verified its operation too.

After a lot of tuning and feedback, I then mounted the motor to the cross slide to see what kind of performance I can get from it.

I started running some comparison tests between the stepper mode and servo mode.The video shows me running my cross slide simply in stepper mode, then changing over to servo mode and repeating the test. They don't run at quite the same speed - that is because the units for the feedrate change between modes and I had to play around with it a little. Anyway, informal testing shows that the maximum speed, at least, seems to be the same in either case.

The encoder looks very vulnerable like that, so I made a quick cap on the 3D printer:

The configuration file for this testing was as follows:

#include "KMotionDef.h"
// Configure SnapAMP for 2 stepper motors in Optical Table Lathe; one of them optionally in servo mode
void main() 
{
    WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT0,9);  // current limit
    WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT1,9);  // current limit
    Delay_sec(0.1);  // wait for any fault to clear
 
    ch4->InputMode=NO_INPUT_MODE;
    ch4->OutputMode=MICROSTEP_MODE;
    ch4->Vel=200.000000;
    ch4->Accel=5000.000000;
    ch4->Jerk=50000.000000;
    ch4->P=1.000000;
    ch4->I=0.000000;
    ch4->D=0.000000;
    ch4->FFAccel=0.000000;
    ch4->FFVel=0.000000;
    ch4->MaxI=200.000000;
    ch4->MaxErr=1000000.000000;
    ch4->MaxOutput=200.000000;
    ch4->DeadBandGain=1.000000;
    ch4->DeadBandRange=0.000000;
    ch4->InputChan0=4;
    ch4->InputChan1=4;
    ch4->OutputChan0=8;
    ch4->OutputChan1=9;
    ch4->LimitSwitchOptions=0x0;
    ch4->InputGain0=1.000000;
    ch4->InputGain1=1.000000;
    ch4->InputOffset0=0.000000;
    ch4->InputOffset1=0.000000;
    ch4->invDistPerCycle=1.000000;
    ch4->Lead=0.000000;
    ch4->MaxFollowingError=1000000000.000000;
    ch4->StepperAmplitude=70.000000;
    
    
    ch4->iir[0].B0=1.000000;
    ch4->iir[0].B1=0.000000;
    ch4->iir[0].B2=0.000000;
    ch4->iir[0].A1=0.000000;
    ch4->iir[0].A2=0.000000;
 
    ch4->iir[1].B0=1.000000;
    ch4->iir[1].B1=0.000000;
    ch4->iir[1].B2=0.000000;
    ch4->iir[1].A1=0.000000;
    ch4->iir[1].A2=0.000000;
 
    ch4->iir[2].B0=1.000000;
    ch4->iir[2].B1=0.000000;
    ch4->iir[2].B2=0.000000;
    ch4->iir[2].A1=0.000000;
    ch4->iir[2].A2=0.000000;
 
    EnableAxisDest(4, 0.0);
 
    ch5->InputMode=ENCODER_MODE;
    ch5->OutputMode=MICROSTEP_MODE;
    ch5->Vel=  300;//20000.000000;
    ch5->Accel=2000;//100000.000000;
    ch5->Jerk= 20000;//2000000.000000;
    ch5->P=1;//0.40000;
    ch5->I=0;//.000300;
    ch5->D=0;//.000000;
    ch5->FFAccel=0.000000;
    ch5->FFVel=0.000000;
    ch5->MaxI=200.000000;
    ch5->MaxErr=50.000000;
    ch5->MaxOutput=1000.000000;
    ch5->DeadBandGain=1.000000;
    ch5->DeadBandRange=0.000000;
    ch5->InputChan0=8;
    ch5->InputChan1=9;
    ch5->OutputChan0=10;
    ch5->OutputChan1=11;
    ch5->LimitSwitchOptions=0x0;
    ch5->InputGain0=1.000000;
    ch5->InputGain1=1.000000;
    ch5->InputOffset0=0.000000;
    ch5->InputOffset1=0.000000;
    ch5->invDistPerCycle=1.000000;
    ch5->Lead=0.000000;
    ch5->MaxFollowingError=100.000000;
    ch5->StepperAmplitude=70.000000;
 
    ch5->iir[0].B0=1.000000;
    ch5->iir[0].B1=0.000000;
    ch5->iir[0].B2=0.000000;
    ch5->iir[0].A1=0.000000;
    ch5->iir[0].A2=0.000000;
 
    ch5->iir[1].B0=1.000000;
    ch5->iir[1].B1=0.000000;
    ch5->iir[1].B2=0.000000;
    ch5->iir[1].A1=0.000000;
    ch5->iir[1].A2=0.000000;
 
    ch5->iir[2].B0=1.0;//0.0566048;
    ch5->iir[2].B1=0.0;//0.11321;
    ch5->iir[2].B2=0.0;//0.0566048;
    ch5->iir[2].A1=0.0;//1.22804;
    ch5->iir[2].A2=0.0;//-0.454462;
 
 
     
    DefineCoordSystem(4,5,-1,-1); //define ch4 as x and ch5 as y; z and a disabled
 
    DisableAxis(5);
    ch5->Vel=  50000.000000;
    ch5->Accel=1000000.000000;
    ch5->Jerk= 2000000.000000;
    ch5->P=0.40000;
    ch5->I=0.000500;
    ch5->D=7.000000;
    
    Write4PH(ch5,100,0);      // energize a pole
    Delay_sec(2);             // wait     
    Zero(5);
    ch5->InputMode=ENCODER_MODE;
    ch5->OutputMode=BRUSHLESS_4PH_MODE;
    ch5->CommutationOffset = 0.25 * 20000/50 + 4;  // offset ~1/4 of encoder count per pole 
    // 200 step stepper -> 50 poles; 5000 line encoder -> 20000 counts per revolution.    
    ch5->invDistPerCycle= 1.0 * 50.0/20000.0; 
    EnableAxisDest(5, 0.0);
}

For the next test, I will figure out the feedrate units properly, so that I can run true 1:1 comparison from withing KmotionCNC.

In the KFLOP, the units are in counts or steps. So with the encoder feedback, I have 20000 counts per revolution. With the stepper motor, I have 200 steps, per revolution. The phases are commutated sinusoidally, so, there is no micro-stepping, per se. However, with the sinusoidal commutation, one can just put fractional steps, and it will be done to the best of the SnapAMPs ability. So the scaling between the two modes should just be a factor of 100.

In KMotionCNC, the units are in inches or degrees, but in this case inches. So as a first test, I set up the axis to 100000 counts/inch (because the lead screw has rev/inch. In step mode, for a 200 steps/rev motor, the count should be 1000) in KMotionCNC and commanded a circle using

G20 (inches mode)
F5
G02 I1.0
M2

Then I increased the feedrate until I got a following error. The maximum feedrate the axis still worked was F20 (inch/min). At F25, the motion was interrupted.

The motor is a A/AX-57-201 from Parker Compumotor. According to documentation here:

https://www.parkermotion.com/manuals/a-ax/AX_Chp6.pdf

This motor has 0.85 Nm of torque and should be driven at 0.750A of current.

The motor torque constant K = torque/current = 0.85/0.75 = 1.13 newton-metres per ampere

The motor back emf constant = 30 / (Pi * motor torque constant) = 8.45 (radians/sec)/Volt = 80.69 RPM/Volt

So as a rough estimation I should be able to get 80 RPM/Volt from this motor as back emf. When unloaded, the 54V supply should yield a bit over 4000 RPM max speed.

The 25 inch/min feedrate that I am failing on amounts to 25*5 = 125 rotations/min. So I don't think I am limited by back emf. The current limit is set by WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT0,9); to about 2A, but the analog monitor in KMotion never even comes close to 1A. But if I am not current nor voltage limited, what could cause the following error?

Dynomotion suggested:

"The limiting factor may be the motor inductance. The AX57-102 has 74mH. If I did the math correct:

The 125RPM = 417 full steps/sec = 0.0024 sec/step

The change in current for an ideal inductor is:

I = V T / L = 54V * 0.0024s / 0.074H = 1.75A

So this isn't even enough time to change a coil from +1A to -1A in the theoretical ideal case."

I ordered a new motor. This one has a part number of KL23H2100-35-4BM. I am attaching the datasheet here at the bottom. The inductance here should be 6.4 mH. So following the above analysis:

I = V T / L = 54V * 0.0024s / 0.064H = 20.25A

The motor did not come with an encoder, so I used a unit by CUI, the AMT112Q-V. The resolution on this unit is programmable, with the default setting being 2048 PPR. These are great in another way, namely that they can adapt to just about any shaft size.

The software used to program these units is called AMT Viewpoint, and a special cable is required. Making the changes was very easy and now the encoder is set to 4096 PPR.

The CUI sensor gave me a lot of trouble at first. The signals are differential, and yet unless the grounding tab on the wire is hooked up, even a motor far away will cause noise sufficient to have the encoder tester run away. consider the following setup:

Here, the encoder is simply hooked up to an encoder tester which is powered by a battery. You can see that the grounding tab of the encoder wire is floating. This arrangement is resting on the Y motor. If now even only the X motor is enabled The count will run away. In spite of the fact that the encoder is not connected to the motor or Kflop/SnapAMP in any way. Connecting the encoder to the differential inputs of the SnapAMP yields the same result.

With the entire system powered down and unplugged, I can still make the (battery powered) encoder jump by touching/wiggling the connector on the encoder. I have never had this behavior with any other encoder. I expected better, especially from a differential signal. Usually, my first step in testing an encoder is connecting it to a tester, of course. That is what it is there for. So when even the battery isolated and differential encoder acted funny, I thought that I had bought a bad unit or cable, so I ordered replacements for both. Only when the replacement units acted exactly the same did I work out what the problem was.

As soon as the grounding tab makes contact with the Y motor in any way (even when just pressed to it by a magnet), the problem goes away.

You can also see that I used some lacquer to fix the encoder in place. There are two mechanical issues I found with these units. First, the encoder itself snaps into latches on the plastic mount. These latches are very weak. I broke one when trying to remove the first encoder. I broke the other even with trying to be careful the first time I mounted it. See here (still intact latch on left, broken on right):

The second problem is that even when the latches are intact the encoder can move a little in its plastic mount. This may or may not be a problem. To make sure that this may not be a problem, I used the lacquer.

After all this was sorted out, I made another cap to protect this encoder like I did for the previous one. This also allowed me to mount the grounding tab properly.

The whole purpose of switching the motor was to be able to get a bit more speed out of it. The new ones theoretical limit due to inductance, as calculated above, went up by a factor of more than 10. So I made the configuration changes to account for the fact that this is a 4096 line encoder and not a 5000 line one and ran the tests (81920 counts/inch in KmotionCNC; 16384 counts/rev).

In reality, I did not get a factor of 10. The maximum feed rate went from F20 to F45, but that is still more than a factor of 2. Here a video, where I first run a circle with F20 and then with the new max F45.

As you can tell, I am still experiencing a lot of hissing noise from both of the motors. I can hear the heartbeat LED from the SnapAMP/Kflop modulated in the hissing. I wonder where the problem/limitation is. I have some Yaskawa servos on the Roland conversion mill and they are absolutely quiet.

The SnapAMP can handle 2 stepper motors or 4 brush servo motors. Because of some upgrades I have in mind (adding milling machine mode), I have decided to change out the steppers with brush servos to have more motors.

The ballscrew on the x axis has a 2 mm pitch. I would like to be able to run 1000 inch/min moves for finishing parts with a small endmill. This is probably past what is smart or feasible in reality, but it is a good basis to look at motor requirements. 1000 IPM is 25400 mm/min. At 2 mm pitch, the ball screw has to rotate 12700 times per minute.

On eBay, there are a number of very nice motors from Maxon that are rated at about that speed. Based on a look at their catalog, a RE35 should do. These are rated at 90W and 12000 RPM. They are available at many different voltage ratings. However, most of them are custom part numbers with no good way to determine their characteristics a priori.

The problem with motors with a high voltage rating is that it will not be possible to run at the desired RPM. My power supply right now supplies 56V. The SnapAMP maxes out at 80V anyway. Even with no load at all any of the 48V rated motors will not reach the desired RPM. But there will be load and I need a motor that can supply torque at 12000RPM. Thus, I need one of the lower voltage rated versions.

I gave up trying to find Maxon motors on eBay with standard part numbers and instead focused on motors with a mounted HEDL differential encoder instead. It will be quite laborious and expensive to fit/change encoders if this is not already correct. And then I took a chance. I got two motors with part number 221734.

These I ran off a DC bench power supply with their encoders connected to the KFlop. To measure the RPM I timed a 60 second run and then looked at the encoder count in KMotion. At 24V the approximate RPM is 6375, and at 30V the approximate RPM is 7875. This puts these motors somewhere in between the standard part numbers 323890 and 273753.

I also got a motor with part number 150320. At 5V, this one runs at ~1060 RPM. At ~10V, 2300 RPM. At 20V ~5625 RPM. At 30V ~7150 RPM. This is consistent with regular part number 273753.

The actual torque required depends on the acceleration I want, the weight of the sled, and the friction in the system. I don't want to take the sled off to measure the mass, and measuring friction is laborious too, so I am just going to do this part empirically - increase acceleration until the following error gets too big. As a sanity check, I connected the a motor to the ball screw, held it with my hand and powered it with just the DC power supply. It had no problem moving the sled and I felt little torque in my hand.

The other axis has a different ball pitch 0.2 inch/turn -> 5.08 mm / turn. So here the motor does not have to run quite as fast. Also, acceleration will cause forces which may strictly exceed the ratings of the ball screws - but they are rated conservatively for industrial settings, and in any case are easy to replace. It is also not clear what the maximum RPM of the ball screws themselves is - probably not anywhere close to 10000 RPM - but everything seems to at least be in the correct order of magnitude.

So it's time to make new motor mounts. I wanted to incorporate the cable mounting into the motor mounts. Previously I had already decided on F/FTP or S/FTP network cable for the encoder signal. For the motor power cables, I decided on these:

https://www.markertek.com/product/icomx4-mf-10/tecnec-intercom-straight-extension-cable-4-pin-xlr-m-to-xlr-f-10ft

This is a standard part used in the music industry. The cable is extremely soft, heavily shielded, has thick gauge wires, and heavy duty connectors. And the panel mount versions of the connectors are available from Amazon....

So, for the Z axis:

And for the X axis:

Getting the motors to work was tricky, because the low inductance meant that the SnapAMP drivers I am using could not use them in the standard configuration. As usual, Dynomotion tech support helped me in a hurry:

https://groups.yahoo.com/neo/groups/DynoMotion/conversations/topics/14976?reverse=1

The code to get the two motors running on the SnapAMP/KFlop is this:

#include "KMotionDef.h"
 
#define MPG_INPUT_AXIS 7
 
#define TAU 0.08 // smoothness factor (Low Pass Time constant seconds)
#define FINAL_TIME 1.0 // Set final dest after this amount of time with no change
 
#define ENABLE_MPG 92
 
#define SELECTX 93
#define SELECTZ 86
 
#define FACTOR1 88
#define FACTOR10 89
#define FACTOR100 90
 
 
int counter = 0;
 
 
int main() 
{
    WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT0,11);  // current limit
    WriteSnapAmp(SNAP0+SNAP_PEAK_CUR_LIMIT1,11);  // current limit
    WriteSnapAmp(SNAP0+SNAP_SUPPLY_CLAMP0 ,SNAP_CONVERT_VOLTS_TO_ADC(70.0));
    WriteSnapAmp(SNAP0+SNAP_SUPPLY_CLAMP1 ,SNAP_CONVERT_VOLTS_TO_ADC(70.0));
    WriteSnapAmp(SNAP0+SNAP_SUPPLY_CLAMP_ENA0,1);
    WriteSnapAmp(SNAP0+SNAP_SUPPLY_CLAMP_ENA1,1);
     
    ch7->InputChan0 = 3; // shows encoder count on Axis window using unused channel
     
    ch0->InputMode=ENCODER_MODE;
    ch0->OutputMode=NO_OUTPUT_MODE;
    ch0->Vel=100000;
    ch0->Accel=50000;
    ch0->Jerk=1e+06;
    ch0->P=20.0;
    ch0->I=0.003;
    ch0->D=30;
    ch0->FFAccel=0.000;
    ch0->FFVel=0.0;
    ch0->MaxI=200;
    ch0->MaxErr=200;
    ch0->MaxOutput=400;
    ch0->DeadBandGain=1.0;
    ch0->DeadBandRange=10;
    ch0->InputChan0=10;
    ch0->InputChan1=9;
    ch0->OutputChan0=8;
    ch0->OutputChan1=9;
    ch0->MasterAxis=-1;
    ch0->LimitSwitchOptions=0x100;
    ch0->LimitSwitchNegBit=0;
    ch0->LimitSwitchPosBit=0;
    ch0->SoftLimitPos=1e+09;
    ch0->SoftLimitNeg=-1e+09;
    ch0->InputGain0=-1;
    ch0->InputGain1=1;
    ch0->InputOffset0=0;
    ch0->InputOffset1=0;
    ch0->OutputGain=1;
    ch0->OutputOffset=0;
    ch0->SlaveGain=1;
    ch0->BacklashMode=BACKLASH_OFF;
    ch0->BacklashAmount=0;
    ch0->BacklashRate=0;
    ch0->invDistPerCycle=1;
    ch0->Lead=0;
    ch0->MaxFollowingError=800;
    ch0->StepperAmplitude=35;
    ch0->Position = 0;
    ch0->Dest = 0;
    ch0->iir[0].B0=1;
    ch0->iir[0].B1=0;
    ch0->iir[0].B2=0;
    ch0->iir[0].A1=0;
    ch0->iir[0].A2=0;
 
    ch0->iir[1].B0=1;
    ch0->iir[1].B1=0;
    ch0->iir[1].B2=0;
    ch0->iir[1].A1=0;
    ch0->iir[1].A2=0;
 
    ch0->iir[2].B0=1;
    ch0->iir[2].B1=0;
    ch0->iir[2].B2=0;
    ch0->iir[2].A1=0;
    ch0->iir[2].A2=0;
     
    ch1->InputMode=ENCODER_MODE;
    ch1->OutputMode=NO_OUTPUT_MODE;
    ch1->Vel=100000;
    ch1->Accel=50000;
    ch1->Jerk=1e+5;
    ch1->P=20.0;
    ch1->I=0.003;
    ch1->D=30;
    ch1->FFAccel=0.000;
    ch1->FFVel=0.0;
    ch1->MaxI=200;
    ch1->MaxErr=4e10;
    ch1->MaxOutput=400;
    ch1->DeadBandGain=0.1;
    ch1->DeadBandRange=10;
    ch1->InputChan0=11;
    ch1->InputChan1=9;
    ch1->OutputChan0=8;
    ch1->OutputChan1=9;
    ch1->MasterAxis=-1;
    ch1->LimitSwitchOptions=0x100;
    ch1->LimitSwitchNegBit=0;
    ch1->LimitSwitchPosBit=0;
    ch1->SoftLimitPos=1e+09;
    ch1->SoftLimitNeg=-1e+09;
    ch1->InputGain0=1;
    ch1->InputGain1=1;
    ch1->InputOffset0=0;
    ch1->InputOffset1=0;
    ch1->OutputGain=1;
    ch1->OutputOffset=0;
    ch1->SlaveGain=1;
    ch1->BacklashMode=BACKLASH_OFF;
    ch1->BacklashAmount=0;
    ch1->BacklashRate=0;
    ch1->invDistPerCycle=1;
    ch1->Lead=0;
    ch1->MaxFollowingError=400;
    ch1->StepperAmplitude=35;
    ch1->Position = 0;
    ch1->Dest = 0;
    ch1->iir[0].B0=1;
    ch1->iir[0].B1=0;
    ch1->iir[0].B2=0;
    ch1->iir[0].A1=0;
    ch1->iir[0].A2=0;
 
    ch1->iir[1].B0=1;
    ch1->iir[1].B1=0;
    ch1->iir[1].B2=0;
    ch1->iir[1].A1=0;
    ch1->iir[1].A2=0;
 
    ch1->iir[2].B0=1;
    ch1->iir[2].B1=0;
    ch1->iir[2].B2=0;
    ch1->iir[2].A1=0;
    ch1->iir[2].A2=0;
 
     
    DefineCoordSystem(1,-1,0,-1); //define ch0 as z
    WritePWMR(8,0);    // write PWM directly to channel 8, with power +-400
    WritePWMR(9,0);    // write PWM directly to channel 8, with power +-400
 
  double Change1, NewPos, Pos;
  int InMotion=FALSE,Axis,LastAxis=-1;
  double LastChangeTime=0,Target,Factor=0;
  int lastStatus = 1; //everything okidoki
    while(1)
    {
        //Delay_sec(0.3);  // wait for any fault to ClearBit
         
        int output0 = (ch0->Output);
        int output1 = (ch1->Output);
        //WaitNextTimeSlice();
        if (0 == ch0->Enable)
            WritePWMR(8,0);    // write PWM directly to channel 8, with power +-400
        else
            WritePWMR(8,output0);    // write PWM directly to channel 8, with power +-400
        if (0 == ch1->Enable)
            {
            WritePWMR(9,0);    // write PWM directly to channel 8, with power +-400
            if (lastStatus == 1)
                {
                    lastStatus = 0;
                    //printf("followingError : %f\n",ch1->LastFollowingError );
                }
            }
        else
            {
            WritePWMR(9,output1);    // write PWM directly to channel 8, with power +-400
            }
          counter++;
    NewPos = chan[MPG_INPUT_AXIS].Position;
    Change1 = NewPos - Pos;
    Pos = NewPos;
 
    if (1 == ReadBit(ENABLE_MPG)) // if rotary knob is on 'off' this bit is high
      Change1 = 0;
 
    if (ReadBit(FACTOR1))  // is X1 selected?
      Factor = 1.0;
    else if (ReadBit(FACTOR10))  // is X10 selected?
      Factor = 10.0;
    else if (ReadBit(FACTOR100))  // is X100 selected?
      Factor = 100.0;
    else                  
      Factor = 0.0;
 
    if (ReadBit(SELECTX))  // is x selected?
    {
      Axis=0;
      Factor = Factor *0.1;
      }
    else if (ReadBit(SELECTZ))  // is z selected?
    {
      Axis=1;
      }
 
    // check if the Axis just changed or we have been
    // converging to the target for a long time
    if (Axis != LastAxis ||
      (InMotion && Time_sec() > LastChangeTime+FINAL_TIME))
    {
        printf("%f\n",chan[MPG_INPUT_AXIS].Position);
      if (InMotion)
        Move(LastAxis,Target);  //finalize any motion
 
      LastAxis = Axis;
      InMotion = FALSE;
    }
   
    if (Change1) // did we move?
    {
      if (!InMotion) Target = chan[Axis].Dest;
      Target += Change1 * Factor;
      MoveExp(Axis,Target,TAU);  // note: contains a WaitNextTimeSlice
      LastChangeTime = Time_sec();
      InMotion=TRUE;
    }
    else
    {
      //WaitNextTimeSlice();
    }
    }     
     
}

These motors now allow the system to do circular interpolation with F250. However, the temperature of the Z axis is a concern

After 25 minutes of running a right triangles with the short sides at 7 inches at F50 speed the temperatures of the motors were:

X : 42C

Z : >100C

At which point I interrupted the test. The load seems to be too high for the Z axis motors. I am going to have to replace it at some point.

Here a circle run at F300 (KMotion only shows F250 achieved....)

The Maxon motors don't quite give the performance that I would like. I may have to adjust my expectations with respect to maximum feed rate, but in any case the high temperature on the Z axis as well as the available acceleration on both axes is not what I had hoped for. So I looked around for larger motors that are commonly available with differential encoders on eBay at reasonable prices. One candidate is the Globe Motors IM21 family. They often come with part numbers starting with 537A, and many have differential encoders attached.

These do not have the RPM ratings I that the Maxon motors have, but the torque of the Maxon motors is too low for the desired acceleration anyway, so there is little choice. To compensate somewhat and to deal with some binding issues I had with the ballscrew on the z axis, I replaced the NSK ball screw with a THK linear actuator. This approach worked well for the Y axis. Since the ball screw is in a guided cage, alignment becomes far easier.

The motors are rated at 30V and my power supply gives the SnapAMP 54V. In the end, it turns out that now the Y axis is speed limited more than the X, because the ballscrew has a shorter pitch. Hence I can only get 300 in/min in Y but up to 1000 IPM on the X. I suspect X could go faster if there were more room to accelerate in, but in this short distance, it barely gets there before it has to slow down again.

So here is the result: