Make BTT Eddy great again

There were some amount of issues raised and discussed about the Eddy and how it works in the past.
I would try to sum them up and try to address them.

  1. Homing inconsistency/noise.
  2. Probe offset.
  3. ''Eddy current sensor error"

Let’s start from the beginning. BTT Eddy, Cartographer, Beacon are basically the same sensors.
They are all just LDC1612. They all just use a single LC coil, where the sensor creates resonance and measure it’s frequency, to be more specific amount of peaks in the unit of time.


That is it.
The sensor gives us 28bit some number which we can convert to the frequency, and it is somewhat precies.

Frequency depends on the internal LC tank characteristics, so it should be somewhat consistent between sensors with the same coil. Basically, we can expect that 2 eddies would have a similar frequency response, and 2 cartos and 2 beacons.

Then there are some variables:

  • Temperature would shift the resonance frequency and amplitude lower (probably because of PCB thermal expansion).
  • Distance between the sensor coil and the coupled metal objects (bed in our case). The closer the sensor gets, the higher the frequency would be, and the lower the amplitude would be.

If we read the datasheet carefully, we can spot that the resonance frequency/distance relation is not linear.


It is asymptotic. Frequency goes to “infinity” the closer the sensor gets to the bed, and on the other side, frequency goes flat (technically, it should represent the coil resonance frequency).

I initially did some research on this topic here: Cartographer V3: Reverse engineering - #4 by nefelim4ag. There are carto-related measurements.

Then I was able to humbly ask some people for help gathering data about BTT Eddy.
Many thanks to these people.

Let’s look at some real data, from real BTT Eddy sensors:


The points are the data from the PROBE_EDDY_CURRENT_CALIBRATE, calibration was patched to get more points past 4 mm.
The more the frequency distance between Z points, the better and more stable the resolution we have. Simply because of finite resolution and frequency, it should give a better signal-to-noise ratio overall.

2 lines are from one user with the Naked Eddy board.

Picture

They are important because they are as close as it can get to what it should be.
Our default 4 mm is mapped over the sensitive region of the asymptotic function.
You can see the datapoints with the naked eye and count them, even guess the frequency difference. The sensor does the same. It can easily measure the large difference in frequency even if it is noisy, not stable .

If there is a temperature drift of the coil, the drift is ppm/K, or simply speaking -10 Hz/K (just a random number to show the idea).
For the steep part of the data, it is less a great deal, there are thousands of hertz range between points 0.04mm and 0.08mm.

But if we place the sensor too far away, the data goes to the more flattish region.
(Look at the signal form of the other lines). This exact drift would cause a major shift in the output frequency. Simply because now we have … hundred or two of hertz?

You can try to plot your data with the script from the carto topic.

So, simply speaking from a resolution perspective, purple and red lines are as good as Eddy can get. Orange is better than the green and blue (steeper), and green and blue should perform mostly the same.
(Again, we really care about delta frequency/ delta distance).

It is possible to decrease the z_offset, so homing/probing would be in a slightly more sensitive region. You can even check the difference between values in your config, your calibration data is the data above Z:Frequency, Z1:Frequency1 & etc


There should be something about current and amplitude, but honestly speaking, I’m a little bit unsure. Application manual about drive current: https://www.ti.com/lit/an/snoa950/snoa950.pdf
We do experience “Eddy current sensor error” issues when the amplitude goes above or below thresholds.
The current calibration routine ensures that we never go above 1.8V. So, we can perform homing from an infinite distance. But it does create issues because at a closer distance (lower Z offset) or with higher temps, we can go below 1.2v, and again get the same error.
Because now I think that it is better to use a closer sensor range, and because there are dependent amplitude errors from the sensor.
Taking into account our occasional use of the sensor, it seems that it is possible to overdrive the current by +1 on average (or just calibrate the current with the bed closer to the sensor, it does the same thing), and then simply disable the amplitude error on the MCU side.

About all of the errors: https://www.ti.com/lit/an/snoa959/snoa959.pdf
Probably amplitude error checking could help if the capacitor is lost (as a result of a collision, for example).
Maybe a more sophisticated solution is required.


Homing, probing issues.
The current Klipper homing routine is pretty simple: stream frequency data from the sensor, move the bed, stop moving the bed when the frequency > threshold.
Where the threshold is z_offset, it is converted to the appropriate calibrated frequency.
When it triggers, then Z now is equal to the z_offset.

That is it.
Probing does additional data processing after the trigger, to get the sensor output when the bed is stable.

Now, taking into account the above data about sensor resolution distribution, it would not be stable as long as the sensor is too far away.
Let’s take a look at the homing data from the people above:


(They are both pretty noisy).

  1. Derivative of frequency (delta frequency over delta time, but time = 1) over filtered data.
  2. Derivative of frequency over raw frequency data.
  3. Raw frequency data.
  4. Z distance

Derivative is used here for demonstration, to make frequency spikes much more obvious.

There are spikes around the start/end of the motion, but generally, there is one from the supergun (~2mm), where the sensor is a little bit closer, a little bit less noisy than the right one, where the sensor has a higher distance (~3mm + 1mm of eddy plastic case, I guess).
Even before and after the filtering.
There is a branch with the MCU side SOS filter for homing: GitHub - nefelim4ag/klipper at ldc1612-homing-sos-filter

It should just work, and require MCU flashing to get it to work. To revert, you can revert only the host side, it should affect nothing without host initialization.

Then there is a probe offset.
From my humble experience with a pretty stable output of the carto board.
You can test the homing consistency with the following snippet:

G28 Z
M400
GET_POSITION
// mcu: stepper_x:20 stepper_y:42 stepper_z:4453 stepper_z1:4453

Run it several times, and what we are interested in is what the distribution looks like.
In my case, the max value in steps would be like 4461, and the minimum would be 4450.
Then, I know my rotational distance and motor steps, so:
4 / 200 / 32 * 21 = 0.0131 mm
What sounds okayish to me.
(SOS filter does nothing for me).

Then there is the probe offset. We request data from the LDC1612 at 250Hz.
Then there is the I2C speed.
So, I can estimate the lag for I2C: 5 * 2 * 9 / 400_000 = 0.000225 s
And for the homing move: 1 / 250 = 0.004 s
So, total lag would be: 0.004225 s

2 mm/s * 0.004225 = 0.008 mm
5 mm/s * 0.004225 = 0.021 mm
10 mm/s * 0.004225 = 0.042 mm

And for me, this is close to what I experience, not precise, but pretty close.
(I guess there is a moving bed, which distorts signals a little, and also sample time inconsistencies between runs, we can guarantee that sample N always happens at position/time p/t).

If you do have the stable frequency output and more or less stable homing, you can easily verify it by adjusting:

[stepper_z]
...
homing_speed: 10
second_homing_speed: 2

Second homing speed gives me:

// probe accuracy results: maximum 3.019506, minimum 3.018543, range 0.000963, average 3.018949, median 3.018928, standard deviation 0.000292

So, there is a 0.02mm difference between the homing and the probe.
If I try to decrease the homing speed and secondary speed to 4 and 1, it gives me 0.01mm difference.

Hope that information can be helpful.
Thanks.


Photo of the eddy rectangle coil 7x14 mm

Ref: https://www.ti.com/lit/an/snoa930c/snoa930c.pdf

Well, it should be close to the nozzle by the Z axis.
That is it.


Script allows to live dump values from the Btt Eddy, which can help understand the noise and how frequency behaves, depending on the object distance (or other settings in the config, like current).

Script.py
#!/usr/bin/env python3

import json
import socket
import statistics
# May require: apt install python3-websocket
from websocket import create_connection

# btt_eddy is the name from the config [probe_eddy_current btt_eddy]
ws = create_connection("ws://localhost:7125/klippysocket")
ws.send(json.dumps({"id": "1", "method": "ldc1612/dump_ldc1612", "params": {"sensor": "btt_eddy"}}))

freqs = []
while True:
    params = json.loads(ws.recv()).get("params")
    if params:
        d = params["data"]
        for row in d:
            freqs.append(row[1])

        if len(freqs) > 100:
            mean_freq = statistics.mean(freqs)
            stddev_freq = statistics.stdev(freqs)
            freqs = []

            print(f"Mean frequency: {mean_freq:.3f}, StdDev: {stddev_freq:.3f}")
        # for l in d:
        #     print(f"{l[0]},{l[1]},{l[2]}")
$ python3 ~/printer_data/config/dump.py
Mean frequency: 3260699.726, StdDev: 54.374
Mean frequency: 3260709.724, StdDev: 56.101
Mean frequency: 3260710.413, StdDev: 61.310
Mean frequency: 3260712.727, StdDev: 57.472
Mean frequency: 3260708.085, StdDev: 65.497
Mean frequency: 3260703.406, StdDev: 57.057
Mean frequency: 3260710.348, StdDev: 58.397
...
1 Like

Very interesting. Thank you.

A couple of things.

Was the analysis done with the centre of the bed or did you also work at the corners/sides to see what kind of difference that made?

There doesn’t seem to be the same level of accuracy/deviation that I would expect from other probe types. Maybe I’m not reading your results right but, running at 10mm/s, you typically have errors of 0.01mm or 10μm.

When I consider other sensors, I would expect to get half of that.

This table is from Teaching Tech showing BLTouch vs a number of other sensor types:

Basic behaviour and form of the signal (Hyperbola/Asymptotic) should be the same regardless of what and where we are talking about. As mentioned, these are not my sensors. So it is done in a way that people configured the toolhead, if that’s a question.

I would assume there should be no difference as long as there is enough metal margin between the edge of the bed and the sensor: https://www.researchgate.net/figure/Magnetic-field-and-eddy-current-distribution-of-planar-coil-based-ECSs-a-Magnetic_fig11_277934353

UPD: https://www.ti.com/lit/ta/ssztbx4/ssztbx4.pdf


Circular coil with a diameter = 29 mm.
And there are little to no differences with target sizes >25mm.
So, technically, we can assume that as long as underneath the whole coil is our metal bed, it should perform in the same way.

You are looking at those numbers:

And you are trying to compare it with data from PROBE_ACCURACY from different sensors.

So, it would be correct to compare your table with that data:

The result of the Eddy probe would not really depend on the probing speed, because actual measurements are done AFTER the bed is fully stopped.
The only variable there is the total distance (z_offset, lower → better).
The speed of probing would somewhat affect the output.
Because the bed would oscillate after the absurd stop:


But averaging of output data will smooth this out.

Also, as a note. “Normal” probes are analog with analog output. So technically, we measure the time at which it is triggered with microstep-level accuracy. Literally, we do 1 microstep, check the GPIO output, +1 microstep, check again, and again, until it is triggered.

Hope that explains something.

This is exactly what I’m asking because my understanding of induced eddy currents is that the field produced is dependent on the shape of the material being sensed/where edges and corners are.

Doing a bit of digging I found this:

which is from here:

And that matches my expectation - I’m curious to understand what are the differences based on the closeness to the edge of the print bed. Did you do any testing of this?

I should point out that with other types of sensors, you get a hard specification on the target as having something that is outside of this area will result in changing results:

I learned this the hard (and expensive) way when I was first setting up my Voron 2.4.

I’m a bit confused how exactly measurements are done. If I understand what you’re saying, you’re taking a measurement and then either determining whether or not to move the toolhead (and eddy current sensor closer) or use the value returned to make a measurement.

I looked at the LDC1612 datasheet and found Figure 60. (posted above) and looked at the entire test sheet with conditions:

If I look at this, it seems that precision measurements can only be made when the sensor is close enough to the print bed that the DATA_CH0 value changes significantly with each minute change of the height of the sensor (coil) above the print head. This appears to happen around 2.5mm in this case or approximately 20% of the coil diameter above the print bed. 2.5mm is a good height because measurement errors are insignificant according to Figures 61. and 62.

Is this a correct interpretation of what’s being presented?

If it is, my next question is, how do you determine the precise height of the coil above the print bed? I don’t see a formula provided for the asymptotic cure of distance vs DATA_CHO. I’m guessing you can produce the slope and vertical axis intercept of a tangential line at some point (again, say 2.5mm) above the print bed which will allow acceptably accurate distance measurements for a certain range around 2.5mm.

Once you’re in your measurement range (ie around 2.5mm in my example) you do a couple extra measurements to average them out and ensure that you have as precise measurement of the coil’s height above the print bed as possible?

I think you mean ‘“Normal” probes are analog with DIGITAL output.’

In any case, I presume that with an eddy coil probe, the procedure to calibrate its height above the print bed would be to:

  1. Make measurement(s)
  2. If the DATA_CH0 result is much higher than the asymptotic range indicating that the coil is close enough to start being able to get a precise measurement - let’s call it a “high level window” of 2mm to 4mm - go to step 5.
  3. Else the result is inside the asymptotic range so have to move down 2mm movements and wait for the toolhead to stop
  4. Go to Step 1. (repeat the measurement and see if the coil is in the “high level window”
  5. In the “high level window” of 2mm to 4mm. Using a a basic calculation, determine how far to go to get to essentially 2.5mm (say within +/-10%)
  6. Move to 2.5mm +/-10% and wait for the toolhead to stop
  7. Make measurement(s) to determine the exact height of the toolhead above the print bed

I believe that the height found should be good enough doing a mesh heigh measurement as well as Quad Gantry Level - would this be correct?

1 Like

I do not, but if you are asking.

Graph/Photos


Roughly as close as I can get to the edge.
Take into account that the bed is not perfectly flat, and I simply do G0 Z0.
So there would be measurement variations.



From my data above, it seems to me that within an adequate margin (several mm) it should be fine. My back measurements literally touch the back edge of the bed, and I have to repeat it several times with small XY offset adjustments because of collisions with silicon brushes.

If you had one, it would be much simpler to explain.
If we talk about example measurements, I do exactly what I show in the G-code, you can do the same.
But your measurement with a different type of probe would not make a lot of sense here.

One command shows the low-level microstep position, from which one can guess the homing repeatability (GET_POSITION). In an ideal situation number would be the same every time one issues it.

The second is probe accuracy, which does include offset error from the homing.
(Because of HOW it is currently implemented, it is an LDC1612-specific thing; maybe Load Cell also suffers from that).

Roughly, if z_offset = 3 mm, homing would place everything at 3 mm.
Then the PROBE_ACCURACY would output 3 ± X, where X is the offset created by how it is implemented. If there is no noise, it would be like mine + a constant, speed-dependent constant + some small error.
So, if I see 3.01 in the probing output, that 0.01 is because homing is not precise.

There are no additional steps, except what I have written.

Yes, your interpretation is correct. I may only point out that we can use sligtly larger window, but overall yes.
Yes (this is what probing does, averages data at height). This is the whole of my post.
But in practice, there are 2 offsets.

  1. Probe offset from the nozzle. People do 2-3 mm because it is how it is in the docs. +~1mm of plastic case (-4 mm from the nozzle plane) - https://bttwiki.com/Eddy.html#installation-height.
  2. Then they do configure z_offset = 3 mm, because again this is how it is in the docs (+3 mm from nozzle plane) - Eddy/sample-bigtreetech-eddy-zoffbeta.cfg at master · bigtreetech/Eddy · GitHub.

So we have ~7 mm between where the bed probed/homed and where the sensor coil is.
Because homing would stop at ~+3 mm, and the probe would probe at ~+3 mm from the nozzle.

For example, mine does use -2.5 between the nozzle and the probe, and additional +3 because of amplitude errors. But my coil is a circle with a diameter of 15 mm.
So, this is well within my abilities.

I would not comment about the calibration procedure, for me, if the calibration output of everyone is asymptotic, it would already be a HUGE difference.
It may be beneficial if there were something more user-friendly than my simple scripts.

About height measurements and QGL, yes, it is, as you may have seen in my center/edge graphs, Klipper by default calibrates with steps of 0.04mm, so there is enough resolution to do so.
In my case, there is no difference between my “super pinda” and LDC1612, from Z tilt leveling or bed mesh view.
(but thermal drift is annoying for now).

Thanks.


UPD: Eddy Current Inductive probe - Klipper documentation
Klipper docs suggest to use lower z_offset: 0.5

1 Like

One possible solution would be to change the mcu code to not raise an error on “high amplitude” and “low amplitude” reports if the reported frequency indicates the probe is not close to the bed. That is, we don’t really care if the amplitude is in range if we know the probe is far from the bed. We only care about an accurate measurement when the probe is close to the bed.

If we don’t error on amplitude errors when the probe is far from the bed then ideally users could configure the probe so that the amplitude is in range when the probe is close to the bed.

I think there should be two stages when measuring the Z position during homing: in the first phase we want to descend the toolhead until it is close to the bed while ensuring we stop descending before the nozzle strikes the bed, and in the second phase we want to determine the distance between the nozzle and bed at the position the toolhead stopped at.

The current Klipper probing code does the above in two distinct phases. Unfortunately, due to internal host code organization, the above are not separated into two phases during homing - the Z position is not measured again after the toolhead stops descending.

For what it is worth, I think the solution to improving homing is to rework the host code so that there is a second measurement phase after the toolhead stops descending (just as is done in the probing code). This is the reason for the warning against homing in the documentation ( “Currently, an eddy current probe can not be used for Z homing. The sensor can only be used for Z probing” at Eddy Current Inductive probe - Klipper documentation ). I’ve also written about possible alternatives at eddy_probe_home.md · GitHub .

I don’t think writing elaborate mcu “halt descent on ldc1612 frequency” code is worthwhile or necessary. We don’t really care about the exact position that the toolhead stops at; we only really care about stopping near the bed so that we accurately measure a position.

Cheers,
-Kevin

2 Likes

I believe that this is exactly what I described above and seems to make the most sense to me.

The situation could be illustrated as:

The first step would be to descend (or ascend if the toolhead is very close to the bed) into the what I’ve marked as the Target “Basket” range. The “Basket” would be chosen to allow the “Height Equation” to produce a height value be as accurate to the DATA_CH0/Target Distance curve presented above.

When within the Target “Basket”, another measurement (or multiples to get an average) is made to determine the precise DATA_CH0 value referencing the coil above the print bed. This measurement value would then be run through the “Height Equation” to determine the height of the coil above the print head.

The question I have is, how do you determine the values for the “Height Equation”? I don’t see an equation for the DATA_CH0/Target Distance cure in the datasheet and I would think that the actual curve and its placement, at the very least, is dependent on the diameter of the coil and the bed material.

For a traditional inductive sensor, the sensing threshold is generally well defined for different materials but this is a completely different case.

Sorry for sticking my nose in but it’s an interesting tool and, I can see it improve/simplify/speed up the Z axis homing, Quad Gantry Level and Bed Mesh processes.

1 Like

Klipper calibrates those values using the PROBE_EDDY_CURRENT_CALIBRATE command as described at: Eddy Current Inductive probe - Klipper documentation :

The second step in calibration is to correlate the sensor readings to the corresponding Z heights. Home the printer and navigate the toolhead so that the nozzle is near the center of the bed. Then run an PROBE_EDDY_CURRENT_CALIBRATE CHIP=my_eddy_probe command. Once the tool starts, follow the steps described at “the paper test” to determine the actual distance between the nozzle and bed at the given location. Once those steps are complete one can ACCEPT the position. The tool will then move the the toolhead so that the sensor is above the point where the nozzle used to be and run a series of movements to correlate the sensor to Z positions. This will take a couple of minutes. After the tool completes, issue a SAVE_CONFIG command to save the results to the printer.cfg and restart.

That is, we have the user run the “paper test” to obtain the motor position at Z=0 and the code then measure the ldc1612 frequency at various Z motor heights. This information is then stored and used during future probe requests.

Cheers,
-Kevin

3 Likes

We can enable/disable error reporting to bits from the host without flashing.

# Amplitude report is disabled, 1d high disabled, 1e low disabled
self.set_reg(REG_ERROR_CONFIG, (0x1c << 11) | 1)

(JFYI: Calibration curves with different IDRIVE).

From the application notes, they strongly suggest doing < 1.8V.
From our application-specific use, where we use a close distance.
It may be better to slightly boost the current to be above 1.2V.
So, disabling only high seems logical and appealing.
A probe is used when homing/probing/meshing, which is either short-term and close distance (~z_offset).

Alas, at least in my testing, even this +1 from calibrated current seems not enough, if probe too close again +1, if temperature goes higher +1.
It is annoying because it would fail homing and probe even if they performed completely okay otherwise.
Also, going from calibrated 19 to 22 doesn’t seem right to me.
(IIRC 19 - 1.75V, 20 - 1.9V peak at 25C).

I invested some time to get an idea about how noise would look with different IDRIVE.
I would say that going lower (-2) than the calibrated value or +1 would not change a lot.
But increasing the current further (that would happen if we disable only the Amplitude high bit).
Seems to increase the noise.
(ODR is 1000Hz, because I forgot to revert it =_=, so it is all slightly noisier than normal)

Graphs
./scripts/motan/motan_graph.py -g '[["derivative(sos(ldc1612(carto),filt,lowpass,1,25),ffreader)"],["derivative(ldc1612(carto),ffreader)"],["ldc1612(carto)"],["stepq(stepper_z)"]]' ./ldc1612_IDRIVE_20 -s 0 -d 6 --segment-time=0.004 




I should note that boosting current does increase the overall min/max frequency value a little, but not so much, and by graphs, it seems that noise in the working range 4..0 mm is not so affected by the actual current.

Based on that data, I lean toward simply disabling the amplitude error.
The case where the coil is not oscillating is covered by the watchdog timeout.
Otherwise, I would expect that the calibrated value should perform fine.

I agree, this is just my attempt to get some experience with SOS filters inside the MCU.
I’m not sure right now that I would be able to rework the homing code.
(Small note, because the bed stops abruptly, it seems that the probe averaging code mostly happens within the oscillating region).

Graph

But I somewhat want to try to implement basic tap, and to do so, it seems better to filter the data to stabilize the descending stop of the nozzle.

Hope that information can be useful.

Okay. We can also ignore the bits in the code though, and that way we’ll have the info for debugging. Something like:

--- a/src/sensor_ldc1612.c
+++ b/src/sensor_ldc1612.c
@@ -118,6 +118,7 @@ check_home(struct ldc1612 *ld, uint32_t data)
     uint8_t homing_flags = ld->homing_flags;
     if (!(homing_flags & LH_CAN_TRIGGER))
         return;
+    data &= ~(1<<28); // An amplitude error is not an issue
     if (data > 0x0fffffff) {
         // Sensor reports an issue - cancel homing
         ld->homing_flags = 0;

Ideally the host could then still track which reports have poor amplitude. (The sensor reports used during homing can be manipulated separately from the raw sensor reports sent to the host.)

For what it is worth though, it seems really odd to me that the sensor can’t be configured so that the amplitude is in range around the point where the homing code triggers a stop.

Yeah - for tap an sos filter seems useful.

All that said, it’s been a while since I’ve last looked at the details of the ldc1612, so don’t take anything I’ve said here too seriously.

Cheers,
-Kevin

EDIT: Fix typo in sample patch.

1 Like