IDEX XY calibration by electrical nozzle contact
-
I am currently a little stuck what might be the best way to implement an automatic XY calibration for my IDEX printer. The hardware is a Snapmaker J1 which has an electrical contact pulling an input to ground on each nozzle when touches the bed. The nozzles have a quick change system which requires the user to recalibrate after each nozzle change.
The bed has two types of contacts that are used with this system:- three z calibration points - these are relatively trivial to use by defining the nozzle contact inputs with M558 as a z sensor
- and a rectangular cutout with conductive edges which is supposed to be used for XY calibration, and this is the one I am stuck with.
The idea behind this is as follows:
- the rough position of the cutout and its size (20x20mm) is known
- Tool1 moves to the cutout, dives into it, touches the four sides several times (in the same way it would do with an endstop) and needs to save the positions somewhere in order to calculate the nozzle centre from these measurements (the outer shape of the nozzle is assumed to be round and needs to be removed from the calculation).
- Then, tool 2 moves to the cutout, dives into it, touches the four sides several times. These values are then used to calculate an offset of tool 2 relative to tool 1 and save this offset.
If I am not mistaken, I need to dive the nozzle into the hole and then do a probe move with e.g. M558 X10 P[no. of z probe of the corresponding nozzle].
But how to I read the value I get into a variable instead of applying it as the documentation of M585 suggests? Is there a value similar to "move.calibration.initial.deviation" for a z probe move to read out the offset value and save it into a variable? -
@NeoDue one way to potentially do this is using the H4 switch on G1, and then reading the move.axes[n].machinePosition to get where the contact was made. you can read those into variables and then calculate the center position from that.
If you want to see something similar in action have a look at @brendon 's autocalibration for the Open5X voron v0 here:
https://github.com/FreddieHong19/Open5x/tree/main/3D_Model/Voron_0/DuetConfig/macros
-
@T3P3Tony Thanks a lot! I was not aware of that option yet. That would also save me from constantly resetting the tool offset.
It seems my attempt is just at the right time for RRF to cope with it - I have the feeling I would probably have been more or less lost with previous versions, considering the amount of comands with a "new in RRF 3.x" remark I use
-
There is also this
https://youtu.be/YLhYf1itSuQ?si=-q-A4dsa06cgPxI6
Starts at 9:15
Macro is in the comments.
-
@oliof Thanks a lot! You might have saved me quite some work!
I guess it is time to dig out some of my rusty knowledge of the french language this weekend
-
@NeoDue if you use a precision metal washer instead of the block, you can use M675 as well. The trick in the video is using the piezo to measure contact which alleviates the need to put a lead to the nozzle for offset measuring.
-
@oliof Thanks, yet another option - this command might make the whole process simpler than I expected
My printer has already a quadratic cutout which is millled out of the PCB that is the heat bed. PCB milling is quite precise, so this should well qualify as a suitable cavity for M675 as it is. No block or washer needed -
@dc42 While working on the calibration code I just stumbled upon your comment from last year at https://forum.duet3d.com/post/272898 that M675 might become obsolete soon and that you might replace it with a macro.
Just to make sure I do not try to ride an almost dead horse here: may I ask what the state of that decision is? If it will be replaced by a macro, I would try to implement that one directly into my code if you already happen to have it at hand.
-
Just in case anyone is interested, here is the working solution:
; XY calibration in a cavity that is normally covered by the print plate ; Variables ; print plate thickness in variable "global.Druckplattendicke" needs to be defined before running the macro, e.g. in config.g var Eintauchtiefe = 1 ; how deep the nozzle shall dive into the cavity var Referenzwert_X = 0 var Referenzwert_Y = 0 var Hotend2_X = 0 var Hotend2_Y = 0 G28 ; Home ; prepare printer - insert whatever you need here... ; heat up hotends and bed M140 S60 ; heat up bed T0 P0 M568 P0 S220 R220 ; heat up Hotend 1 T1 P0 M568 P1 S220 R220 ; heat up Hotend 2 auf 220°C T-1 P0 M116 ; wait ; let the user clean the nozzles G1 H2 X{move.axes[0].min+50} F3000 G1 H2 U{move.axes[3].max-50} F3000 M400 ; wait ; Message M291 "Please clean the nozzles" R"Notice" S3 ; Move print heads back to their parking position G1 H2 X{move.axes[0].min} F3000 G1 H2 U{move.axes[3].max} F3000 ; lift bed close to measuring level G1 H2 Z5 F6000 ; move hotend 1 to cavity and dive in G1 H2 X0 Y0 F6000 M400 G1 H2 Z{-global.Druckplattendicke - var.Eintauchtiefe} F3000 ; begin reference measurement T0 P0 ; activate tool 0 while iterations < 8 ; measure 8 times in X M675 X R1 P0 F100 M400 set var.Referenzwert_X = var.Referenzwert_X + move.axes[0].machinePosition ; save value set var.Referenzwert_X = var.Referenzwert_X / 8 ; calculate final reference value while iterations < 8 ; measure 8 times in Y M675 Y R1 P0 F100 M400 set var.Referenzwert_Y = var.Referenzwert_Y + move.axes[1].machinePosition save value set var.Referenzwert_Y = var.Referenzwert_Y / 8 ; calculate final reference value ; move back hotend 1 G1 H2 Z2 F6000 M400 G1 H2 X{move.axes[0].min} F6000 ; delete old offset from hotend 2: G10 P1 U0 Y0 ; move hotend 1 to cavity and dive in G1 H2 U0 Y0 F6000 M400 G1 H2 Z{-global.Druckplattendicke - var.Eintauchtiefe} F3000 ; measure with hotend 2 T1 P0 ; activate tool 1 while iterations < 8 ; measure 8 times in U M675 U R1 P1 F100 M400 set var.Hotend2_X = var.Hotend2_X + move.axes[3].machinePosition ; save data set var.Hotend2_X = var.Hotend2_X / 8 ; ... and calculate final value while iterations < 8 ; measure 8 times in Y M675 Y R1 P1 F100 M400 set var.Hotend2_Y = var.Hotend2_Y + move.axes[1].machinePosition ; save data set var.Hotend2_Y = var.Hotend2_Y / 8 ; ... and calculate final value ; define new offset and save it to a file that is read from config.g: G10 P1 U{var.Referenzwert_X - var.Hotend2_X} Y{var.Referenzwert_Y - var.Hotend2_Y} echo >"Korrekturwert_xy_offset_tool1.g" "G10 P1 U"^{var.Referenzwert_X - var.Hotend2_X}^" Y"^{var.Referenzwert_Y - var.Hotend2_Y}^"" ; bring printer back to idle state T0 P0 G1 H2 Z5 F6000 G1 H2 U{move.axes[3].max} Y{move.axes[1].min} F3000 G1 Z120 F6000 M568 P0 S0 R0 M568 P1 S0 R0 M140 S0 ; notify user M291 S2 P"XY-Offset of right Hotend is has been saved!"
(edit: added remark, removed unused variables)
-
@NeoDue great stuff! You should consider some way to filter out outliers in your 8 runs (use median or just track lowest/highest and reject runs where the difference is greater than some value you deem an acceptable range).
-
@oliof That is a great idea, especially for the reference value! But for now, I have no clue on how to that in RRF. I would have to do the measurements, save them all and then do some comparison... before I dig into whatever an internet research might yield on this topic: do you happen to have some more information on how to do this with the basic math RRF offers?
-
@oliof said in IDEX XY calibration by electrical nozzle contact:
@NeoDue great stuff! You should consider some way to filter out outliers in your 8 runs (use median or just track lowest/highest and reject runs where the difference is greater than some value you deem an acceptable range).
@oliof Now I finally had the time to dust off and actually use a bit of my old statistics knowledge... Got it - here is a test macro that removes outliers from a given array based on their z-score:
; constraints out of experience: ; - electrical nozzle contact never gets activated too soon (=> too low values will never happen and are not taken into account here) ; if that does not apply, a lower acceptance limit for the z-score needs to be defined as well ; - values that are too high are usually at least 0.15...0.2mm off var Referenzwert = {1,1.02,1.05,1.2,1.22,1.3,1.01,} ; test values - the three values that are too high need to be detected and removed from the mean value var Referenzmittelwert = 0.0 var Referenzstandardabw = 0.0 var ReferenzZScore = vector(#var.Referenzwert,null) var ZScoregrenze = 0.5 var Anzahlgueltig = 0 ; calculate mean value while iterations < #var.Referenzwert set var.Referenzmittelwert = var.Referenzmittelwert + var.Referenzwert[iterations] set var.Referenzmittelwert = var.Referenzmittelwert / #var.Referenzwert ; calculate standard deviation while iterations < #var.Referenzwert set var.Referenzstandardabw = var.Referenzstandardabw + (var.Referenzwert[iterations] - var.Referenzmittelwert) * (var.Referenzwert[iterations] - var.Referenzmittelwert) set var.Referenzstandardabw = sqrt(var.Referenzstandardabw / (#var.Referenzwert - 1)) ; calculate z-score of each measurement value while iterations < #var.Referenzwert set var.ReferenzZScore[iterations] = (var.Referenzwert[iterations] - var.Referenzmittelwert) / var.Referenzstandardabw ; re-calculate mean value without detected outliers set var.Referenzmittelwert = 0 while iterations < #var.Referenzwert if var.ReferenzZScore[iterations] < var.ZScoregrenze set var.Referenzmittelwert = var.Referenzmittelwert + var.Referenzwert[iterations] set var.Anzahlgueltig = var.Anzahlgueltig + 1 set var.Referenzmittelwert = var.Referenzmittelwert / var.Anzahlgueltig echo var.Referenzmittelwert
While I do not use that for XY calibration (at least yet - that one works perfectly well as it is so far...), I did add it to the two macros I wrote for z calibration and z-leveling of the U axis relative to the X axis. It definitely makes sense there since slight drops of filament coming out of the nozzle can cause errors.