Motivation
Having had a Renishaw MP12 proble for a while, I decided it was time to write some gcode to properly calibrate it after having searched the web without success. The closest thing I could find was a post from eneias_eringer on the topic 18775-probe-for-center-of-hole here on the forum, calibracao.ngc, Buying Renishaw's calibration cycles was of course not an option for me. Running a probe without calibration does not make justice to a probe, much less to a Renishaw.
Concept
With the spindle manually aligned with the centre of the ring and with a precise ring with known diameter any measured radius deviation while touching off at a particular angle represents the trigger error of the probe plus probe run-out plus machine tolerances. When this deviation per angle is stored as calibration values and used when actually probing a part at an equal angle, accurate readings are possible. Probing should then always be carried out with the probe orientation used during calibration. Without this basic calibration any 3D probe is inaccurate, even a Renishaw.
Calibration Procedure - see
video
1) A precision ring is placed perpendicular to the spindle axis on the mill table - goes without saying.
2) The spindle is manually aligned to the centre of the ring using a preferred method - I use a dial indicator mounted on the spindle.
3) MDI T99 M6 - in my tool table, 99 is the probe tool
4) The probe is jogged down in Z so that the probe tip is below the ring top surface
5) The NGCGUI routine p-calibrate.ngc is run with the precision ring's exact diameter as input parameter
Calibration Implementation
For the calibration, two routines are used, p-calibrate.ngc and aux-calibrate-probe-at-angle.ngc.
p-calibrate.ngc will probe the ring at 45 degrees steps (calling aux-calibrate-probe-at-angle.ngc for each angle) storing the results in the linuxcnc variables #621-#628. Note that for linuxcnc to persist #621-#628 one must manually add these variables to the linuxcnc.var file. p-calibrate.ngc reads ini parameters as below and it will validate that the probe tool is loaded..
Code:
[PROBE]
BALL_DIAMETER - precise value, eg, 5.99
BACKOFF - same as latch, e.g 1.5 (mm)
COARSE_FEED - eg. 700 (mm/s)
FINE_FEED - eg. 10 (mm/s)
TOOL_NUMBER - tool table probe tool number, eg. 99
aux-calibrate-probe-at-angle.ngc will do the actual probing based on input parameters, using some simple trig. Measured values are truncated to 3 digits of precision - should be changed to 4 for machines in imperial mode.
Probing Procedure
1) MDI T99 M6 - Load probe tool
2) Call aux-probe-at-angle-args [#<angle>] [#<distance>] [#<backoff>] [#<fine_feed>] [#<search_feed>] [#<rapid_feed>]
or
Call aux-probe-at-angle [#<angle>] [#<distance>]
3) The measured probed distance available as a return value, hence #5001
Probing Implementation
There are two base routines to probe using the calibration data.
aux-probe-at-angle-args [#<angle>] [#<distance>] [#<backoff>] [#<fine_feed>] [#<search_feed>] [#<rapid_feed>]
aux-probe-at-angle [#<angle>] [#<distance>]
While aux-probe-at-angle-args takes all values as args, aux-probe-at-angle will read most values from the ini parameters same as p-calibrate.ngc.
p-calibrate.ngc
Code:
O<p-calibrate> sub (calibrate probe)
#<diam_hole>=#1 (known hole diameter)
#<diam_ball>=#<_ini[PROBE]BALL_DIAMETER>
#<backoff>=#<_ini[PROBE]BACKOFF>
#<coarse_feed>=#<_ini[PROBE]COARSE_FEED>
#<fine_feed>=#<_ini[PROBE]FINE_FEED>
#<probe_tool_number>=#<_ini[PROBE]TOOL_NUMBER>
(info: MP12 3D probe calibration - known probe, known ring hole)
o<p-start> call
(debug, Ensure the diameters for ball and hole are precise and that the machine is in the exact center of the hole)
; capture current position
#<initial_x>=#5420
#<initial_y>=#5421
#<initial_z>=#5422
g21 g17 g40 g80 g94 g90 g49 g92.1
G0 x#<initial_x> y#<initial_y>
g91 g38.5 z-0.00001 f#<fine_feed> (check that probe is active)
;#####################################################################################
;## Measure and store trigger points in 5021..28 for 0,45, 90,135, ... 315 degrees ##
;#####################################################################################
#<angle-step>=45
#<curr-angle>=0
#<curr-step>=0
o<base-angle-touches> do
o<aux-calibrate-probe-at-angle> call [#<curr-angle>] [#<diam_hole>] [#<diam_ball>] [#<coarse_feed>] [#<fine_feed>]
#[421+#<curr-step>]=#<_value>
;(debug, probe-at-angle [421+#<curr-step>]= #<_value>)
g90 g0 x#<initial_x> y#<initial_y>
#<curr-step>=[#<curr-step>+1]
#<curr-angle>=[#<curr-angle>+#<angle-step>]
o<base-angle-touches> while [ #<curr-angle> LT 360 ]
g90 (absolute distance mode)
g0 x#<initial_x> y#<initial_y>
O<p-calibrate> endsub
p-start - prep the probe
Code:
o<p-start> sub
(start probe)
#<probe_tool_number>=#<_ini[PROBE]TOOL_NUMBER>
o<pcheck1> if [ #5400 NE #<probe_tool_number> ]
(abort, Please load probe - T#<probe_tool_number> M6)
o<p-start> endsub [-1]
o<pcheck1> endif
M5 (Stop spindle)
M19 r0 (orient probe)
M65 P0 (activate probe input)
m64 P1 (start 3d probe)
m65 P1 (reset start 3d probe)
g4 p1 ; dwell
m66 P0 l0 (check if probe did activate)
o292 if [#5399 eq 1]
(abort, probe not activated)
o<p-start> endsub [-1]
o292 endif
o<p-start> endsub [0]
M2
aux-calibrate-probe-at-angle.ngc
Code:
;#############################
; probe x,y pos and an angle
o<aux-calibrate-probe-at-angle> sub
;input params
#<angle>=#1
#<diam_hole>=#2
#<diam_ball>=#3
#<feednorm>=#4
#<feedslow>=#5
; setup vars
#<radius> = [#<diam_hole>/2]
#<radius_b> = [[#<diam_hole>/2]-8+[#<diam_ball>/2]]
#<sin_a>=sin[#<angle>]
#<cos_a>=cos[#<angle>]
#<y-pos_b> =[#<sin_a>*#<radius_b>]
#<y-pos_ok> =[#<sin_a>*#<radius>]
#<x-pos_b> =[#<cos_a>*#<radius_b>]
#<x-pos_ok> =[#<cos_a>*#<radius>]
g90 g1 x#<x-pos_b> y#<y-pos_b> f#<feednorm>
g90 g38.2 F#<feednorm> X#<x-pos_ok> Y#<y-pos_ok>
g90 g1 X#<x-pos_b> Y#<y-pos_b> f#<feednorm>
g90 g38.2 F#<feedslow> X#<x-pos_ok> Y#<y-pos_ok>
#<x-touch>=#5420
#<y-touch>=#5421
#<radius_measured>=sqrt[[#<x-touch>*#<x-touch>]+[#<y-touch>*#<y-touch>]]
o<probe-off-300-if>if[#<radius_measured> lt 0]
#<radius_measured>=[#<radius_measured>*-1]
o<probe-off-300-if>endif
#<probe-off-angle>=[#<radius_measured>-[#<diam_hole>/2]+[#<diam_ball>/2]]
#<probe-off-angle>=[fix[#<probe-off-angle>*1000]/1000]
g90 g1 X#<x-pos_b> Y#<y-pos_b> f#<feednorm>
(debug, probe-at-angle #<angle>= #<probe-off-angle>)
o<aux-calibrate-probe-at-angle> endsub [#<probe-off-angle>]
aux-probe-at-angle.ngc
Code:
;#############################
; probe x,y pos and an angle
o<aux-probe-at-angle> sub
#<angle>=#1
#<distance>=#2
#<backoff>=#<_ini[PROBE]BACKOFF>
#<rapid_feed>=#<_ini[PROBE]RAPID_FEED>
#<search_feed>=#<_ini[PROBE]COARSE_FEED>
#<fine_feed>=#<_ini[PROBE]FINE_FEED>
call o<aux-probe-at-angle-args> [#1] [#2] [#<backoff>] [#<fine_feed>] [#<search_feed>] [#<rapid_feed>]
o100 if [ #<_value> == -1 ]
(abort, failed to probe ....)
o100 endif
o<aux-probe-at-angle> endsub [#<_value>]
aux-probe-at-angle-args.ngc
Code:
;#############################
; probe x,y pos and an angle
o<aux-probe-at-angle-args> sub
#<angle>=#1
#<distance>=#2
#<backoff>=#3
#<fine_feed>=#4
#<search_feed>=#5
#<rapid_feed>=#6
#<diam_ball>=#<_ini[PROBE]BALL_DIAMETER>
#<probe_tool_number>=#<_ini[PROBE]TOOL_NUMBER>
;(debug, aux_probe-at-angle-args)
M5 (Stop spindle)
o<pcheck1> if [ #5400 NE #<probe_tool_number> ]
(abort, Please load probe - T#<probe_tool_number> M6)
o<aux-probe-at-angle-args> endsub [-1]
o<pcheck1> endif
m66 P0 l0 (check if probe did activate)
o292 if [#5399 eq 1]
(abort, probe not activated)
o<aux-probe-at-angle-args> endsub [-1]
o292 endif
#<initial_x>=#<_x>
#<initial_y>=#<_y>
#<initial_z>=#<_z>
o<probe-off-301-if> if [#<distance> LT #<backoff> ]
(abort, distance smaller than backoff value, can not make move, distance #<distance>, backoff #<backoff>)
o<aux-probe-at-angle-args> endsub [-1]
o<probe-off-301-if> endif
#<offset_variable>=[fix[#<angle>/45]]
o<probe-off-302-if> if [ [#<offset_variable> NE [#<angle>/45]] or [#<offset_variable> LT 0 or #<offset_variable> GT 8] ]
(abort, angle not in calibration #<angle>)
o<aux-probe-at-angle-args> endsub [-1]
o<probe-off-302-if> endif
#<probe_offset>=#[421+#<offset_variable>]
o<probe-off-303-if> if [[#<probe_offset> EQ 0] or [#<probe_offset> GT [1] ] or [#<probe_offset> LT [-1] ] ]
(abort, probe has not been calibrated for the angle #<angle>, off number #<offset_variable> [421+#<offset_variable>] )
o<aux-probe-at-angle-args> endsub [-1]
o<probe-off-303-if> endif
;(debug, offset_variable=#<offset_variable>, offset=#<probe_offset>)
#<sin_a>=sin[#<angle>]
#<cos_a>=cos[#<angle>]
#<y_pos_b> =[#<sin_a>*#<distance>]
#<x_pos_b> =[#<cos_a>*#<distance>]
#<y_backoff> =[#<sin_a>*#<backoff>]
#<x_backoff> =[#<cos_a>*#<backoff>]
#<y_backoff2> =[#<sin_a>*[#<backoff>+0.3]]
#<x_backoff2> =[#<cos_a>*[#<backoff>+0.3]]
#<y_comp> =[#<sin_a>*#<probe_offset>]
#<x_comp> =[#<cos_a>*#<probe_offset>]
g91 g38.3 x[#<x_pos_b>] y[#<y_pos_b>] f#<search_feed>
#<x_touch>=#5420
o100 if [ #5070 eq 0 ]
g90 g0 x#<initial_x> y#<initial_y> f#<rapid_feed>
(msg, probe did not make contact ...)
o<aux-probe-at-angle-args> endsub [-1]
o100 endif
g91 g1 X[-#<x_backoff>] Y[-#<y_backoff>] f#<rapid_feed>
g91 g38.3 X[#<x_backoff2>] Y[#<y_backoff2>] f#<fine_feed>
#<y_touch>=#5421
o200 if [ #5070 eq 0 ]
g90 g0 x#<initial_x> y#<initial_y> f#<rapid_feed>
(msg, probe did not make contact ...)
o<aux-probe-at-angle-args> endsub [-1]
o200 endif
#5001=[#<x_touch>+#<x_comp>]
#5002=[#<y_touch>+#<y_comp>]
M67 E0 Q#5001 (set hal analog output to x pos)
M67 E1 Q#5002 (set hal analog output to x pos)
;(debug, x=#<x_touch>, x-comp=#<x_comp> y=#<y_touch> y-comp=#<y_comp> )
g90 g0 x#<initial_x> y#<initial_y> f#<rapid_feed>
;(debug, probe-at-angle #<angle> with offset #<probe_offset>)
o<aux-probe-at-angle-args> endsub [0]
M2
Integration with Probe Screen
the files xminus, xplus, yminus, yplus would look like below, one just changes the first argument, the angle or approach.
To get probe screen to use the compensation values from the calibration, I had to use hal analog outputs set by the aux-probe-at-angle-args, M67 E0 Q#5001 (set hal analog output to x pos) M67 E1 Q#5002 (set hal analog output to x pos).
The pins are created in probe screen and connected to motion.analog-out-XX on the postgui hal file.
Finally, the probed_position_with_offsets function was modified to use these pins values instead.
This seems to work well, though to be honest just got it working today 19.02.2021 ...
Code:
o<xplus> sub
o<aux-probe-at-angle-args> call [0] [#<_hal[probe.ps_probe_max]>] [#<_hal[probe.ps_probe_latch]>] [#<_hal[probe.ps_probevel]>] [#<_hal[probe.ps_searchvel]>] [#<_ini[TOOLSENSOR]RAPID_SPEED>]
#<ret_code>=#<_value>
g4 p0.5 ; dwell
o<xplus> endsub [#<ret_code>]
M2
o<xminus> sub
o<aux-probe-at-angle-args> call [180] [#<_hal[probe.ps_probe_max]>] [#<_hal[probe.ps_probe_latch]>] [#<_hal[probe.ps_probevel]>] [#<_hal[probe.ps_searchvel]>] [#<_ini[TOOLSENSOR]RAPID_SPEED>]
#<ret_code>=#<_value>
g4 p0.5 ; dwell
o<xminus> endsub [#<ret_code>]
M2
o<yplus> sub
o<aux-probe-at-angle-args> call [90] [#<_hal[probe.ps_probe_max]>] [#<_hal[probe.ps_probe_latch]>] [#<_hal[probe.ps_probevel]>] [#<_hal[probe.ps_searchvel]>] [#<_ini[TOOLSENSOR]RAPID_SPEED>]
#<ret_code>=#<_value>
g4 p0.5 ; dwell
o<yplus> endsub [#<ret_code>]
M2
o<yminus> sub
o<aux-probe-at-angle-args> call [270] [#<_hal[probe.ps_probe_max]>] [#<_hal[probe.ps_probe_latch]>] [#<_hal[probe.ps_probevel]>] [#<_hal[probe.ps_searchvel]>] [#<_ini[TOOLSENSOR]RAPID_SPEED>]
#<ret_code>=#<_value>
g4 p0.5 ; dwell
o<yminus> endsub [#<ret_code>]
M2
postgui_common.hal
Code:
net probe-x-touchoff => probe.x-touchoff <= motion.analog-out-00
net probe-y-touchoff => probe.y-touchoff <= motion.analog-out-01
net probe-z-touchoff => probe.z-touchoff <= motion.analog-out-02
probe_screen.py changes to __init__
Code:
self.halcomp.newpin( "x-touchoff", hal.HAL_FLOAT, hal.HAL_IN )
self.halcomp.newpin( "y-touchoff", hal.HAL_FLOAT, hal.HAL_IN )
self.halcomp.newpin( "z-touchoff", hal.HAL_FLOAT, hal.HAL_IN )
probe_screen.py changes to probed_position_with_offsets
Code:
def probed_position_with_offsets(self) :
self.stat.poll()
probed_position=list(self.stat.probed_position)
coord=list(self.stat.probed_position)
g5x_offset=list(self.stat.g5x_offset)
g92_offset=list(self.stat.g92_offset)
tool_offset=list(self.stat.tool_offset)
# print "g5x_offset=",g5x_offset
# print "g92_offset=",g92_offset
# print "tool_offset=",tool_offset
# print "actual position=",self.stat.actual_position
# print "position=",self.stat.position
# print "joint_actual position=",self.stat.joint_actual_position
# print "joint_position=",self.stat.joint_position
# print "probed position=",self.stat.probed_position
for i in range(0, len(probed_position)-1):
coord[i] = probed_position[i] - g5x_offset[i] - g92_offset[i] - tool_offset[i]
angl=self.stat.rotation_xy
res=self.rott00_point(coord[0],coord[1],-angl)
#coord[0]=res[0]
#coord[1]=res[1]
coord[0]=self.halcomp["x-touchoff"]
coord[1]=self.halcomp["y-touchoff"]
return coord