TMC Adaptive Microstep Table

This is initially brought up because of the Prusa phase stepping.
But looks like this is a completely different solution to motion vibrations.

TMC SPI driver support custom current table for micro steps.
Technically we always assume that everything is not perfect, even decent-quality stepper motors.

So, micro steps are inequal, but this can be “tuned”.
That will increase precision and decrease vibration in theory.
The suggestion from TMC for tuning looks like that:

I made simple CAD calculations. and there is no chance to print something and tune, it because to get 1mm resolution per micro-step on a motor with a 1.8-degree full step, we need a dial indicator around 8 meters long, or a Laser.

The perfect solution is to have some decent encoder, and if the encoder can sense the inequality of 0.007 degrees, there is a chance to automate calibration.

So, I realize all of that is about detecting small signals and amplification of it.
Technically there is already a solution for that resonances for amplification and accelerometer for detection.

Can it work? That is the right question.
I did some hacks like changing driver micro steps to match tested micro steps and making some graph analyzing tools, based on Klippain shake tune (which is based on Klipper internal shaper code).

So, there is a macro:

[gcode_macro PHASE_STEPPING_X]
gcode:
    {% set mstep = 32 %}
    {% set mstep_size = 40 / 200 / mstep %}
    SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0
    SET_INPUT_SHAPER SHAPER_FREQ_X=0 SHAPER_FREQ_Y=0
    {% set shift = 256 // mstep %}
    {% for mscnt in range(0, 256, shift)%}
        ACCELEROMETER_MEASURE NAME=phase-stepping_{mscnt}-{mscnt+shift}
        
        # 0.48 / (40 / 200 / 32) = 76.8 HZ
        {% set FREQ = 75 %}
        {% set FREQ_SPEED = FREQ * (40 / 200 / mstep) %}
        RESPOND MSG="microstepping: {mstep} size: {mstep_size} speed: {FREQ_SPEED}mm/s phase-stepping_{mscnt}-{mscnt+shift}"
        {% for repeat in range(0, FREQ*2) %} # ~2 seconds
            FORCE_MOVE STEPPER=stepper_x DISTANCE={mstep_size*-1} VELOCITY={FREQ_SPEED}
            FORCE_MOVE STEPPER=stepper_x DISTANCE={mstep_size} VELOCITY={FREQ_SPEED}
        {% endfor %}
        ACCELEROMETER_MEASURE NAME=phase-stepping_{mscnt}-{mscnt+shift}
        # Shift forward
        FORCE_MOVE STEPPER=stepper_x DISTANCE={mstep_size*-1} VELOCITY=0.1
    {% endfor %}

There are 2 graphs of micro step oscillation, the same micro steps, and the motor is shifted by ~1.6 mm, so there is also reproducibility.



And on the same position of the shaft, 4 full steps:

So, at least there is a difference in response between microsteps.

But for now, with this graph, I have no clue if the real difference is a match to spectral power density here.

In my simple tests, 32 micro steps is around the lowest limit, where something can be detected by an accelerometer.
Some hacks to do that:

  • If the micro-steps of the driver match the tested step, the Klipper has no way to make a trapezoidal ramp here, because this is literally one micro-step. Disabling interpolation also helps in the same way.
  • If they are a match and below 256 microsteps, there is a problem of getting MSCNT to zero, at least my attempts were unsuccessful.
  • Using 256 driver micro steps allows us to easily get MSCNT to zero, and make some “right” measurements. But looks like there is less oscillation to be detected, even if we try to step with big steps. So, 8/16 works, 32 just does not make measurable oscillation.

For now, there are just my notes and a branch with 2 additional scripts, for the above graph and another for decoding mslut.

IMHO, the next steps here are:

  • Make graphs where PSD peaks are plotted above the MSCNT position instead of Hz - so there is a simple way to compare them. (Done, graphs updated)

  • And if there are some adequate patterns, (I expect a very low distribution of micro-steps PSD like 10%).

  • Try to decode that data back to MSLUT, retest, and compare.

As a last resort, make a lever from the motor shaft to the accelerometer, and oscillate that lever (60 mm long?) instead of the print head, there must be a much more powerful response in my opinion.

Of course, in the end, this madness must be validated somehow, maybe with a laser and a wall :smiley:

Even at 16 microsteps you’re at .0125mm linear resolution. Which is a small fraction of your nozzle width. You’re going to end up with more intrinsic vibrations in your pulleys by their polygonal nature.

It’s interesting what you’re doing but in practical terms, kind of overkill?

At that level of resolution you’d have have to all your steppers tuned absolutely perfect and it assumes there is ZERO vibration in your toolhead mounting. Again, 16 microsteps with a 20 tooth pulley is already .0125mm resolution, which is roughly equivalent to a hairs width.

If you press on your toolhead and it moves more than a fraction of a hairs width, everything is already nil with trying to tune away resonances below that.

You are right, that there are tiny things and we try to work with them.
I perfectly understood what you mean, common I even showed an 8-meter pointer!
In practical terms, I’m just about “how deep is the rabbit hole”.
I have a damn good machine, so there is no practical reason to expect to get more from it.

I’m not pretending to fix the issue in the end, that would be nice, but let’s be realistic, I’m still not sure what I measure, or how I measure, as you mentioned there can be inequality of motor pulley.

For now, I’m just trying to close part of the topic, that initially borrows up from phase stepping and direct phase control.
If direct phase control helps, I expect microstep table will help to some degree.
If the Prusa team succeeds in measuring something from the accelerometer, let’s try to explore this topic.

1 Like

Completely agree, I’m always hesitant to respond the way I did as it seems I’m just being being negative or it comes across as “That’s dumb, don’t do it”. But that’s not the case, Just advocating for realistic goals and keeping perspective on “How much is this really going to change things?”.

My only thought on this, and I’ll fully admit I’m not an expert in the field of it, is trying to use “off the shelf” accelerometers for what is akin to precision measurements.

What I DO know for a fact is that Accelerometers have (usually) a pretty messy signal offset if they’re not calibrated and it has to be done periodically as they gain DC offset drift over time due to temperature fluctuations.

Only those temperature fluctuations are especially bad in 3D printers because of the heating and cooling cycle.

Maybe Prusa (And others) have accommodated for that, I’m not sure, I haven’t looked into their codebase enough to try to see. I’m just wary of making any kind of definitive measurements or declarations about measurements when using a few dollar accelerometer.

Industrial vibration analysis is done using a lot more robust sensors with a lot of calibration, filtering and data analysis for similar resolution measurements.

Not saying it can’t be done, But leads to the immediate question of, if it could be done so cheaply and easily why would anyone spend hundreds or thousands on equipment when they can buy a $5 PCB off Amazon?

Just natural skepticism.

1 Like

3D printing is my hobby (I’m not an expert in anything, to be honest), as someone joked to me:
“Do you even print sometimes?” (yes, sometimes :D)

My expectations here:
Decreased inequality → will decrease noise → That will decrease motor resonance → That will make VFA less noticeable

How much? This will depend on how huge micro-step inequality there is in the first place - motor dependent in practice.
One of the issues here - looks like no one has any clue.

I mean you can find driver datasheet, or motor tuning with an oscilloscope, but no data about precision or precision distribution. The closest thing I saw is that steppers can not hold the exact micro-step position, because of open loop control.

Things like ±%5 of the full step are nice but mostly useless in this case.
*same as trying to set tmc registers by tables without oscilloscope - nice to get the idea, mostly useless in practical terms (because of ±20% of induction distribution).

About the accelerometer, that is exactly the point, I don’t know if it is adequate for that, or if I need another one like lis2dw.
We know that adxl345 is deaf, and (IIRC) lis2dw is noisy.
For now, I can only say that with some settings there are clear graphs. no peaks anywhere, just noise.
So, I am more or less sure the accelerometer picks up something from the drive train even in this inefficient setup.
So, with 32 microstep setup
40 / 200 / 32 = 0.00625 mm per micro-step.
Output depends on driver micro-stepping and interpolation.
Because if interpolation or higher micro stepping in place, accelerometer data are dead silent.

I know that the accelerometer can pick up random noise, so all motors are disabled, and fans are disabled I trying to oscillate around the resonance frequency of the head/belts (77Hz), but received a response of around 83 Hz WTF? That is a problem for future me.

So finalizing, to say something about the exact accelerometer (cheap or not) here we need data and tools (not always physical, specific methods or software is also a tool).
To say something about real performance profits or downsides - we need data and tools in the first place.


About industrial stuff.
From my point of view, the main selling thing about industrial equipment is predictability.
You expect that expensive hardware will follow datasheets and, if supposed to, work till the end of service life.
In a perfect world, if we talk about high precision, you even make accuracy verification sometimes.
Did you try to place rulers in a shop one to another? That made me feel pain last time.

With that said, an accelerometer can be calibrated.
The datasheet to mine specifies only one way, measure, and average offset:

The sad part here, is I’m not sure that in an industrial application, people can have some other magic ways around.

Back to the topic.
Industrial can and will use the same chips, as we can get, the only difference is assembly and calibration, quality in some specific electronic cases (CPU).
I mean, why expect the difference from an Analog Devices (TMC) chip in the first place? If there is the same chip? We did not expect TMC drivers to be different between your 3D printer and medical device.
The same applies to sensors.
They are made for industrial, and accidentally they become cheap enough and we can use them.
I expect a difference in board quality, and EMI, but there is a much different topic here.

So, I expect my accelerometer will have the same performance as if it is used in industrial tooling, and if it has enough quality for something in the past, in my tiny case it can also be decent.

*yes, I still can’t answer if the current accelerometer is enough to measure what we trying to measure. Because even when I have all the data, and some tooling around tuning, I can only say that in my setup in these conditions with this accelerometer it does not work, works somehow, or works nice.
But that possibly allows someone, to test this in different conditions and make something better.

Stupid examples from head:
7.5 degree motors.
Creality K1 with 40T pulley :smiley:


Oh, Now I feel like I should make a self-test and offset calibration of ADXL :smiley:
Just in case

This is the main issue that requires calibration.
NadeauInSituAccelCalICASSP2017.pdf (654.6 KB)

I’ve attached a good read about it, but the gist of it is, at rest if you measured the acceleration on each axis of a 3 axis accelerometer, IDEALLY you’d get +1g, the acceleration due to gravity.

If you measured every possible orientation and plotted it, your points would be on a sphere with a radius of 1(g).

The problem lies with the fact that accelerometers aren’t perfect and have the dc offset and drift mentioned, so when you take a sample measurement of real world accelerometer readings you get an ellipsoid because of the non-linear offets.

The paper describes how to use non-linear least squares to curve fit to that ellipsoid and then use an affine transformation to get back to the ideal sphere. You can then use that calibration matrix to calibrate your data moving forward to handle any offset.

Here is an example of my results when doing that:

image

You can obviously tell the points are not on the surface of a sphere, it’s an ellipsoid with it’s semi major axis stretched along the x axis and slightly angled in the +y direction.

After calibration and applying to the initial raw data
image

Not perfect, need to cleanup outliers. but generally better.

For reference, this was the data. This is from an accelerometer on one of my can boards sitting absolutely still. It’s so noisy as to be nearly useless, required quite a bit of filtering and processing.

(After rotations was because I “virtually” rotated the measurements to align with positive Z because I was comparing multiple boards. Didn’t change the spread of the data though.)

image

Note: I’m not 100% sure I even did all of it right. This was after several days of messing with it and giving myself a crash course in Linear Algebra that I haven’t had to use in decades.

1 Like

*There are my thoughts, this is a nice paper, I got the idea.
That we compute vectors, place them, search for axis screws, and then apply compensation to output data, to get the real vector.
I’m feeling like a caveman here :smiley:

There is my standstill data for several seconds, points are a vector of 3 axes from zero.


I’m not sure how to compute and apply this as calibration data.

There is FFT output, the same script as for my micro-stepping. I made several standstill samples for the different sampling rates at a standstill, for 5-10 seconds.

As the datasheet states, the higher sampling rate produces more noise.
And at looking to these graphs, I’m not sure that some complex filtering will not ruin FFT data.
Let’s check this out.
We can safely assume that PSD is around ~ 200-300 ~ 2…3% ~ 0.294 m/s^2 ~ 20-30 mG is a white Gaussian noize - according to FFT.

According to the datasheet, there is expected noise performance:
image
LSB - least significant bit, 4mg(mG) per bit, so up to 16 mG at 3200 Hz is expected.
So, there is slightly more :frowning:

But in Klipper ADXL uses a 0 static offset table (Klipper does not configure it), let’s emulate it.
Use the same files, but compute the average/mean of all data in the sample, and subtract it according to docs (static offset).

accel_x      365.787004
accel_y      565.510669
accel_z   -10095.977452

It is mostly constant between samples in my case.


Now all vectors are scattered in random directions, but the shape looks mostly the same.

Same PSD graph over filtered data:


I can’t believe it, but there is no difference, literally zero O_O.

So, I think this is a reason, why Klipper does not use an internal static offset table or any filtering for data output from the accelerometer. It feels like for applications where you need to perfectly determine the acceleration vector, it is much more crucial than in ours, where we analyze signals for specific noises.

So, maybe I can take a look at the supply voltage of the accelerometer, increasing it will help with noise.
Temperature offset looks like, can be ignored if data rates above 6 Hz.

Well, Yes, there won’t be any different in your spectral density for a static measurement. The signal has to be time varying.

The offset will cause a difference in MAGNITUDE though. Which is what the calibration would be for. Which SHOULD even out in a PSD but in reality the offset/sensitivity is different PER axis.

Then you have cross-axis sensitivity issues which introduce errors into your measurements.

In other words, the error is already built into your measurements.

Again, I’m no expert, I just know enough to know that accelerometers are fickle little things that shouldn’t be trusted “out of the box”.

Especially when you consider we’re taking measurements in 3 physical dimensions and trying to compare them to each other and by the nature of flaws in manufacturing each can have their own in built errors.

Will it work as a “rough general idea”? Probably.

Should you trust it when you’re trying to quantify vibrations affecting 10 micron level movements? Almost certainly not.

WRT noise and supply voltage, I have done some intensive testing here: ADXL345: Different between RPi Pico and RPi 3B

2 Likes

There are many such papers written on the intrinsic noise in accelerometers as well.

This is why I’m EXTREMELY wary of people making grand claims about precision measurements of “off the shelf” cheap consumer electronics.

This is doubly true when claims start with “Prusa is doing…”, I’m not saying Prusa doesn’t make good products. But they have a vested interest in SELLING those products and a lot of companies CLAIM to do a lot of things to sell products.

I could sell a brick that claims to “reduce high frequency vibrations that cause .001 to .099 mm scale vibrations that cause effects in prints”.

And

1.) How many people have the tools to measure that?
2.) It probably would because of the extra mass

To put that into very intuitive perspective:

image

The gap in the measuring faces is ~.05mm.
That’s an M2 screw for size reference.

Keep in mind, I’m zoomed in as well.

So, updates, for now, I summarize what I’ve said in discord.
I will refer to this topic:

There is my fancy glued together graph :smile:


Between red lines is 1 full step, so 4 of them is full sinusoidal signal.
Technically we can control only 1 quarter of this, TMC2130/5160 does not allow to change offset or mess more than full 1 step. I think the realistic goal here is to smooth out there
variations.

Original datasheet, show different sinusoidal sample here.

So first hypothesis:
if more current at a specific MSCNT position drives acceleration higher (peaks around 90 degrees of sin)
Just by moving the peak backward, before 90 degrees, and a slight decrease at 90 - there is the possibility to slope the acceleration curve.

Second hypothesis:
Calibration of micro-step inequality will smooth that.

I’m not certain here that we can ideally calibrate micro-step inequality with an accelerometer and/or encoder.
But looking at proposed moves by TMC (3th and 4th graphs)
Looks like after that calibration we will have “strange” sinusoidal output, but more steady linear movements and acceleration.

Please excuse me for the long posts and perhaps I am wishful thinking.
So for now, this is what I’m digging into without the assumption that it will succeed or will fix real-world problems.
image
image

I spent some time today, to update mslut decoder with the encoder.
With the power of random, I got this one signal (green):

There are initial values recorded in the same way, as suggested in the topic above.

After I applied custom MSLUT, there were visible and audible changes on the accelerometer output.

So, about the first hypothesis:
I think it is correct, we can shift peaks and slope acceleration/resonance curves.

Your distortions are immediately after the crossing points. This could just be back EMF when the phase switches. Adding current would just make it worse. Reducing current causes you other issues.

Edit:

Yep

Back EMF method detects stepper motor stall: Pt. 1—The basics ...

Edit 2:

Smoothing out your curve is a matter of tuning the SpreadCycle. The issue is, per the datasheet, the driver can’t measure the current at those points because that’s when there are voltage spikes due to the EMF. So it just waits a certain amount of time before measuring current again.

You can decrease that time but it leads to additional switching losses.

I got what you mean, maybe - I still need to dig more,
But at 50-80 mm/s backEMF on low inductance motor should have very low impact.


So even if that is BackEMD, it should not have almost the same amplitude.

And there are micro steps, as mentioned in original post 256, so no drop of current or sudden switching.

Okay, I got the exact mscnt in graphs.
Microsteps are visible, and feel just like a miracle :smiley:


256 are indistinguishable from noise

I played with some waveforms for MSLUT.
To get some feelings about how waveforms will outcome on the graphs.
There are more ways to make microsteps noisier than to make them right.


Green is the custom table. I made it worse, but how much worse! :smiley:

 ./scripts/motan/motan_graph.py -s 0 -d 15 ./microsteps-128 -g '[["step_phase(tmc5160 stepper_x,mscnt)"], ["average_by(adxl345(adxl345,x),step_phase(tmc5160 stepper_x,mscnt))"],["average_by(adxl345(adxl345,y),step_phase(tmc5160 stepper_x,mscnt))"]]'
1 Like

How to gather data

I’ve good macro now, to gather data

[gcode_macro PHASE_STEPPING_X]
gcode:
    {% set MSCNT_START = params.MSCNT_START|default(0)|int %}
    {% set SHIFT = params.SHIFT_OF_256|default(1)|int %}
    {% set MSTEP = params.MSTEP|default(32)|int%}
    {% set mstep_size = 40 / 200 / MSTEP %}
    {% set min_mstep_size = 40 / 200 / 256 %}
    SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0
    SET_INPUT_SHAPER SHAPER_FREQ_X=0 SHAPER_FREQ_Y=0
    {% for MSCNT in range(MSCNT_START, MSCNT_START+1024, SHIFT)%}
        {% set MSCNT_START = MSCNT % 1024 %}
        {% set MSCNT_END = (MSCNT + (256//MSTEP)) % 1024 %}
        ACCELEROMETER_MEASURE NAME=phase-stepping_{MSCNT_START}-{MSCNT_END}
        DUMP_TMC STEPPER=stepper_x REGISTER=MSCNT
        RESPOND MSG="ms: {MSTEP} size: {mstep_size} speed: 5mm/s {MSCNT_START}..{MSCNT_END}"
        {% for repeat in range(0, 64) %} # ~1 seconds
            FORCE_MOVE STEPPER=stepper_x DISTANCE={mstep_size*-1} VELOCITY=5
            FORCE_MOVE STEPPER=stepper_x DISTANCE={mstep_size} VELOCITY=5
        {% endfor %}
        ACCELEROMETER_MEASURE NAME=phase-stepping_{MSCNT_START}-{MSCNT_END}
        FORCE_MOVE STEPPER=stepper_x DISTANCE={(min_mstep_size*-1) * SHIFT} VELOCITY=0.01
    {% endfor %}

I use 256 microsteps on stepper_x, interpolation must not interfere with that, but I disable it.

To simplify things, I move the stepper to MSCNT=0, by:

DUMP_TMC STEPPER=stepper_x REGISTER=MSCNT
// MSCNT: 0000006b mscnt=107
FORCE_MOVE STEPPER=stepper_x DISTANCE=0.08 VELOCITY=5
DUMP_TMC STEPPER=stepper_x REGISTER=MSCNT
// MSCNT: 00000005 mscnt=5
# 40 / 200 / 256 = 0.00078125
# Issue 5 times and there is zero
# You can change this to minus in case your stepper direction is different
FORCE_MOVE STEPPER=stepper_x DISTANCE=0.00078125 VELOCITY=5

You also can just skip that and gather data, by parametrizing macro with current MSCNT value.

Graphs are reproducible, and files are deterministic, so you can change stuff and graph things.
This is my graph with default values.

While or after you gather your data, you can use this script to plot it:

./scripts/calibrate_microsteps.py out/phase-stepping/adxl345-phase-stepping_*-*.csv -o microsteps_custom_32by256.png -s 60 -m 100

My peaks happen at 80Hz, so I filter out other noise.

So, how to mess around with MSLUT

This script will help you out, it will print a new config for klipper with your custom table.
By default, there is just a default Klipper table.

./scripts/tmc-mslut-graph-gen.py
driver_MSLUT0: 4162576335
driver_MSLUT1: 4160749699
driver_MSLUT2: 608774441
driver_MSLUT3: 269500962
driver_MSLUT4: 4227858431
driver_MSLUT5: 3048961917
driver_MSLUT6: 1225561814
driver_MSLUT7: 4211234
driver_W0: 2
driver_W1: 1
driver_W2: 0
driver_W3: 0
driver_X1: 128
driver_X2: 255
driver_X3: 255
driver_START_SIN: 0
driver_START_SIN90: 247

There are default values:

You can’t change the first one, first one is START_SIN, other values can have differences defined by ‘W’, so -1…3. There are up to 4 regions. Each one can represent one of [-1…0, 0…1, 1…2, 2…3] difference in current.

For now, the basic idea is to reduce the difference in amplitude between microstep values.
In my case, as an example, I want to increase the difference between start or end values, to amplify silly microsteps at zero crossing and decrease the difference between steps around half step.
Notice: There is sin and cos, they affect each other.

My current attempts are not very successful.


That is my “custom” graph where I try to amplify silly zero-crossing

So, that’s it, YMMV.

I got a laser!
I’ve some videos, but the forum is not for that.
Looks like there is “dead spots” where micro steps can do nothing.

So, direct measurements from paper, like the distance between 0…16, 16…32 & etc, end 8…24, 24…40 & etc.

For me, they look similar to accelerometer data for that motor.

Raw test setup.


Here are just measurements of the actual pointer position with my hand precision.

I use CAD on notebook display to generate a step grid and tune against it because it is just simpler to more & resize.


I still have another pack of videos, but TLDR:
As shown above, and by testing against the equidistant “ruler”, at least, my motor has shifted around 8 mstep, so the beginning and end of the table have low torque.

Both motors LDO 2504 have similar “issues” and tuned micro step tables of one motor can be used as a starting point for the second.
In my case, the second motor wants an even more steep current curve.
But both of them now have inequality of less than 4 microsteps.
They are not perfectly tuned, just better than default.
*So, the same motor series can have similar curves.

Here is an example of the difference, from default MSLUT values.

About tuning, it is more or less simple:
If the laser pointer undershoots position, increase torque before that position and propagate changes upwards, if overshoots - decrease.
You are limited by the table encoder and the motor has friction & etc.
So, I’m not sure it is possible to make it more precise at least in my setup.

The left side is new, right is old data (easier to compare).
My little lazy laser-tuning changed graph form dramatically.


Looks like I shouldn’t have skipped the farther micro step table tune and should have checked the intermediate position, even if I do not have enough resolution.

Maybe direct tuning by micro-step torque allows avoiding this, but I have no idea how to tune load angle without an angle sensor.

Constant velocity tests show that the micro-step table got worse in the sense of our target metric (lower vibrations).


Constant velocity shows a similar wave pattern to our microstep table.

I also tried to do 32 micro steps graph, because TMC skips zero transitions in that mode.
But the stepping motors do not wish to work properly with this new mslut table, loss of torque - skipped steps.
(I think this is because of pits in the graph)

What about missing zero crossing with reduced micro step count, here are graphs with 32 and 16 micro steps.
TMC does not use 0 and 256 points at reduced micro steps


UPD:
I did another tune iteration of mslut table but the laser battery died.
4 micro-step increments allow tuning more precisely, but slower/harder - now 32 micro-stepping works, 16 still do not.
I’m struggling with proper tune 116-136 positions because there is now a lack of movements, which are clearly shown on the graph.

I got an encoder, and playing around right now.
Klipper builtin sensor calibration, says that:

// angle: stddev=15.007 (0.013 forward / 0.000 reverse) in 49992 queries

If I understood it correctly, it is a deviation for a virtual 16 bit encoder.

15 / 65536 * 360 = 0.0823 degree

The chip is manually calibrated.
This looks reasonable, according to the datasheet after user calibration.

User Auto-Calibration and Distortion Compensation with Target INL < ± 0.07 degree

No one says that my mounts a perfectly aligned and/or centered.

From Klipper’s calibration perspective. It does full-step calibration but does not account for zero offsets.

We already know that TMC with enabled microstepping will avoid full-step positions.
(Driver configured for 64 microsteps).

$ DUMP_TMC STEPPER=stepper_x REGISTER=MSCNT
// MSCNT:      000003fe mscnt=1022
$ FORCE_MOVE STEPPER=stepper_x DISTANCE=-0.003125 VELOCITY=200 ACCEL=10000
$ DUMP_TMC STEPPER=stepper_x REGISTER=MSCNT
// MSCNT:      00000002 mscnt=2

Larger microstepping → closest to zero values.
Zero can be only achieved with 256 microstepping. it’s not intuitive, but even with micro stepping equal to 1, it will use not zero position, like 128, 386 & etc.

On other random start positions:

// angle: stddev=14.621 (0.025 forward / 0.040 reverse) in 49992 queries

Slightly better.

I added some code, to align axis:

diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py
index 0d3522c6..61e162eb 100644
--- a/klippy/extras/angle.py
+++ b/klippy/extras/angle.py
@@ -178,6 +178,17 @@ class AngleCalibration:
         move(mcu_stepper, 2. * rotation_dist, move_speed)
         move(mcu_stepper, -2. * rotation_dist, move_speed)
         move(mcu_stepper, .5 * rotation_dist - full_step_dist, move_speed)
+        # Align stepper to zero
+        toolhead = self.printer.lookup_object('toolhead')
+        zero_offset = 0
+        mscnt_quant = 256 // microsteps
+        mscnt_min = (mscnt_quant // 2)
+        mscnt = self.tmc_module.mcu_tmc.get_register("MSCNT")
+        while mscnt != mscnt_min:
+            move(mcu_stepper, step_dist, move_speed)
+            zero_offset -= step_dist
+            toolhead.wait_moves()
+            mscnt = self.tmc_module.mcu_tmc.get_register("MSCNT")
         # Move to each full step position
         toolhead = self.printer.lookup_object('toolhead')
         times = []
@@ -194,6 +205,7 @@ class AngleCalibration:
                 move(mcu_stepper, -.5 * rotation_dist + samp_dist, move_speed)
                 samp_dist = -samp_dist
         move(mcu_stepper, .5*rotation_dist + align_dist, move_speed)
+        move(mcu_stepper, zero_offset, move_speed)
         toolhead.wait_moves()
         # Finish data collection
         is_finished = True

Now I can be sure, that calibration was done from the same, minimal microstep position.

There are data with different microstepping:

# 8 microsteps
// angle: stddev=15.913 (0.013 forward / 0.075 reverse) in 49990 queries
// angle: stddev=15.889 (0.041 forward / 0.078 reverse) in 49990 queries
// angle: stddev=15.806 (0.000 forward / 0.033 reverse) in 49991 queries
# 16 microsteps
// angle: stddev=14.328 (0.138 forward / 0.195 reverse) in 49989 queries
// angle: stddev=15.709 (0.044 forward / 0.075 reverse) in 49995 queries
// angle: stddev=15.678 (0.031 forward / 0.037 reverse) in 49990 queries
# 32 microsteps
// angle: stddev=15.441 (0.013 forward / 0.028 reverse) in 49989 queries
// angle: stddev=15.383 (0.025 forward / 0.000 reverse) in 49996 queries
// angle: stddev=15.342 (0.054 forward / 0.000 reverse) in 49994 queries
# 64 microsteps
// angle: stddev=15.235 (0.000 forward / 0.028 reverse) in 49991 queries
// angle: stddev=15.216 (0.075 forward / 0.025 reverse) in 49988 queries
// angle: stddev=15.216 (0.055 forward / 0.000 reverse) in 49993 queries
# 128 microsteps
// angle: stddev=15.255 (0.028 forward / 0.000 reverse) in 49990 queries
// angle: stddev=15.160 (0.033 forward / 0.000 reverse) in 49996 queries
// angle: stddev=15.148 (0.031 forward / 0.099 reverse) in 49989 queries
# 256 microsteps
// angle: stddev=14.973 (0.067 forward / 0.130 reverse) in 49994 queries
// angle: stddev=14.901 (0.079 forward / 0.000 reverse) in 49989 queries
// angle: stddev=15.012 (0.054 forward / 0.099 reverse) in 49995 queries

To my great surprise after stabilizing the position in code, stddev also stabilizes.
It feels right that stddev decreases closer to the physical zero of the rotor.


Next question if it possible to​ actually measure micro step error with an encoder?
At least this one.