├── M48 repeatability test 2.txt
├── M48 repeatability test 1.txt
├── strain_guage_probe
└── strain_guage_probe.ino
└── README.md
/M48 repeatability test 2.txt:
--------------------------------------------------------------------------------
1 | Send: M48 P10 V2
2 | Recv: M48 Z-Probe Repeatability Test
3 | [...]
4 | Recv: 1 of 10: z: -0.069
5 | [...]
6 | Recv: 2 of 10: z: -0.071
7 | [...]
8 | Recv: 3 of 10: z: -0.076
9 | [...]
10 | Recv: 4 of 10: z: -0.071
11 | [...]
12 | Recv: 5 of 10: z: -0.063
13 | [...]
14 | Recv: 6 of 10: z: -0.070
15 | [...]
16 | Recv: 7 of 10: z: -0.070
17 | [...]
18 | Recv: 8 of 10: z: -0.070
19 | [...]
20 | Recv: 9 of 10: z: -0.069
21 | [...]
22 | Recv: 10 of 10: z: -0.073
23 | Recv: Finished!
24 | Recv: Mean: -0.069850 Min: -0.076 Max: -0.063 Range: 0.013
25 | Recv: Standard Deviation: 0.003075
--------------------------------------------------------------------------------
/M48 repeatability test 1.txt:
--------------------------------------------------------------------------------
1 | Send: M48 P20 V2
2 | Recv: M48 Z-Probe Repeatability Test
3 | [...]
4 | Recv: 1 of 20: z: -0.053
5 | [...]
6 | Recv: 2 of 20: z: -0.060
7 | [...]
8 | Recv: 3 of 20: z: -0.059
9 | [...]
10 | Recv: 4 of 20: z: -0.058
11 | [...]
12 | Recv: 5 of 20: z: -0.061
13 | [...]
14 | Recv: 6 of 20: z: -0.067
15 | [...]
16 | Recv: 7 of 20: z: -0.073
17 | [...]
18 | Recv: 8 of 20: z: -0.077
19 | [...]
20 | Recv: 9 of 20: z: -0.072
21 | [...]
22 | Recv: 10 of 20: z: -0.070
23 | [...]
24 | Recv: 11 of 20: z: -0.070
25 | [...]
26 | Recv: 12 of 20: z: -0.068
27 | [...]
28 | Recv: 13 of 20: z: -0.072
29 | [...]
30 | Recv: 14 of 20: z: -0.071
31 | [...]
32 | Recv: 15 of 20: z: -0.077
33 | [...]
34 | Recv: 16 of 20: z: -0.075
35 | [...]
36 | Recv: 17 of 20: z: -0.076
37 | [...]
38 | Recv: 18 of 20: z: -0.082
39 | [...]
40 | Recv: 19 of 20: z: -0.077
41 | [...]
42 | Recv: 20 of 20: z: -0.075
43 | Recv: Finished!
44 | Recv: Mean: -0.069425 Min: -0.082 Max: -0.053 Range: 0.029
45 | Recv: Standard Deviation: 0.007588
--------------------------------------------------------------------------------
/strain_guage_probe/strain_guage_probe.ino:
--------------------------------------------------------------------------------
1 | /**
2 | Leveling system with load cell, HX711 and Digispark
3 | by ShaoYang Lim a.k.a. Yonggor
4 | last update March 7, 2021
5 |
6 | This sketch utilise HX711 library for Arduino by Bodge
7 | https://github.com/bogde/HX711
8 |
9 | Also check out:
10 | David Pilling's Z probe
11 | https://www.davidpilling.com/wiki/index.php/Zprobe
12 | Z probe using SMD resistor 2512
13 | https://github.com/IvDm/Z-probe-on-smd-resistors-2512
14 | **/
15 |
16 | /*
17 | Attention:
18 | this sketch sets endstop pin high when the probe detects a touchdown,
19 | which is the opposite of many printers including Creality Ender 3 I am working with.
20 | Reconfiguration of printer firmware must be done for Marlin, RRF, Klipper or other.
21 |
22 | this sketch decides whether to trigger the probe_out pin based on two factors:
23 | 1. load cell readings : val0
24 | 2. peaking of reading : de
25 | de is change of gradient of load cell readings, which indicates the sudden change of readings,
26 | instead of drifting (consistant increasing readings)
27 | */
28 | #include "HX711.h"
29 |
30 | // uncomment the board use
31 | #define Digispark
32 | //#define Arduino_Nano
33 |
34 | // choose working mode
35 | // only uncomment one mode
36 | // use mode0 for regular use
37 | #define mode0
38 | //#define mode1
39 | //#define mode2
40 |
41 | /*
42 | HX711 circuit wiring
43 | If using Digispark Board, pls follow the pin here to avoid trouble.
44 | If using Arduno Nano, you can change pin numbers to your own liking.
45 | */
46 | #ifdef Digispark
47 | #define LOADCELL_DOUT_PIN 0
48 | #define LOADCELL_SCK_PIN 2
49 | #define probe_out 4 //Pin to send printer
50 | #define LED_out 1
51 | #endif
52 |
53 | #ifdef Arduino_Nano
54 | #define LOADCELL_DOUT_PIN 2
55 | #define LOADCELL_SCK_PIN 3
56 | #define probe_out 5
57 | #define LED_out 4
58 | #endif
59 |
60 | // variables
61 | long val0 = 0; //latest reading
62 | long val1 = 0; //latest-1 reading
63 | long val2 = 0; //latest-2 reading
64 | long de = 0; //
65 | // define threshold values
66 | long threshold_Val = 50;
67 | long const threshold_gap = threshold_Val; // value to add to threshold_Val in each recalibration of probe trigger
68 | int const threshold_De = 5;
69 | #define panic_val 3000 // force HIGH output if val0 exceed this value, disregard de value.
70 | // The probe recalibrates its threshold value after each tigger.
71 | // Must avoid second probing before recalibration is finished.
72 | // delay_threshold defines time before recalibrate the trigger threshold, allow the nozzle to lift out of bed
73 | #define delay_threshold 700
74 | // enable serial reporting of probe value, doesn't work with Digispark
75 | //#define EN_serial
76 | int i = 0;
77 |
78 | HX711 scale;
79 |
80 | void setup()
81 | {
82 |
83 | #ifdef EN_serial
84 | Serial.begin(57600);
85 | #endif
86 |
87 | pinMode(probe_out, OUTPUT);
88 | pinMode(LED_out, OUTPUT);
89 | digitalWrite(probe_out, LOW);
90 | digitalWrite(LED_out, LOW);
91 | scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
92 | //check if HX711 is working properly, blink rapidly if not.
93 | if (!scale.wait_ready_retry(10))
94 | {
95 | while (1)
96 | {
97 | digitalWrite(LED_out, HIGH);
98 | delay(50);
99 | digitalWrite(LED_out, LOW);
100 | delay(50);
101 | }
102 | }
103 |
104 | scale.set_scale(570.f); // hx711 calibration value, not important in our probe
105 | delay(2000); // pre-warm up strain gauge, reduce drifting
106 | scale.tare(); // reset the scale to 0
107 |
108 | // "End of setup loop" indicator
109 | // indicate the probe is ready for probing.
110 | digitalWrite(LED_out, HIGH);
111 | delay(500);
112 | digitalWrite(LED_out, LOW);
113 | delay(200);
114 | for (int j = 0; j <= 5; j++)
115 | {
116 | digitalWrite(LED_out, HIGH);
117 | delay(50);
118 | digitalWrite(LED_out, LOW);
119 | delay(50);
120 | }
121 | }
122 |
123 | void loop()
124 | {
125 | val2 = val1;
126 | val1 = val0;
127 | getValue();
128 | getDe();
129 |
130 | /*
131 | connection checking,
132 | open or short connection load cell return highest/lowest value of reading
133 | 2^24 = 16 777 216
134 | min reading: -8388607
135 | min reading: 8388607
136 | value of lowest end and highest end are treated as faulty connection
137 | LED blinks slowly
138 | */
139 |
140 | if (scale.read() <= -7000000 || scale.read() >= 7000000)
141 | {
142 | digitalWrite(LED_out, HIGH);
143 | delay(500);
144 | digitalWrite(LED_out, LOW);
145 | delay(750);
146 | }
147 |
148 | /*
149 | * ************** PANIC MODE ***********************************
150 | If the probe is not triggered when the nozzle is already pressing the bed
151 | blast HIGH when val0 >= panic_val to prevent damage
152 | set val0 to a reasonable value, which drifting couldn't reach normally
153 | e.g.: 100*threshlod_Val = 5000
154 | *********************************************************
155 | */
156 | else if (val0 >= panic_val)
157 | {
158 | digitalWrite(probe_out, HIGH);
159 | digitalWrite(LED_out, HIGH);
160 | delay(500);
161 | digitalWrite(probe_out, LOW);
162 | digitalWrite(LED_out, LOW);
163 | delay(500);
164 | recalibrate(3);
165 | }
166 |
167 | /*
168 | * ************** MODE 0 ***********************************
169 | regular working mode impliments pressure and speed of pressure
170 | rising to detect homing
171 | *********************************************************
172 | */
173 | #ifdef mode0
174 |
175 | else if (val0 >= threshold_Val && val0 < panic_val)
176 | {
177 | // sucessful trigger
178 | if (de >= threshold_De)
179 | {
180 | digitalWrite(probe_out, HIGH);
181 | digitalWrite(LED_out, HIGH);
182 | delay(100);
183 | digitalWrite(probe_out, LOW);
184 | digitalWrite(LED_out, LOW);
185 | delay(delay_threshold);
186 | recalibrate(3);
187 | }
188 |
189 | // unsucessful trigger (de smaller than threshlod)
190 | // recalibrate trigger threshlod after certain amount of time (i value)
191 | else if (de < threshold_De)
192 | {
193 | i++;
194 | if (i >= 200)
195 | {
196 | recalibrate(5);
197 | i = 0;
198 | }
199 | }
200 | }
201 |
202 | else
203 | {
204 | digitalWrite(probe_out, LOW);
205 | digitalWrite(LED_out, LOW);
206 | }
207 | #endif
208 |
209 | /*
210 | * ********** MODE 1 **********************************************
211 | Output HIGH when threshold_Val is reached
212 | useful for calibrating threshold_Val
213 | not suitable for actual probing because drifting cause
214 | unexpected trigger
215 | ****************************************************************
216 | */
217 | #ifdef mode1
218 | if (val0 >= threshold_Val)
219 | {
220 | digitalWrite(probe_out, HIGH);
221 | digitalWrite(LED_out, HIGH);
222 | delay(200);
223 | digitalWrite(probe_out, LOW);
224 | digitalWrite(LED_out, LOW);
225 | delay(delay_threshold);
226 | recalibrate(3);
227 | }
228 |
229 | else
230 | {
231 | digitalWrite(probe_out, LOW);
232 | digitalWrite(LED_out, LOW);
233 | }
234 | #endif
235 |
236 | /*
237 | * ************ MODE 2 *************************************
238 | Output HIGH when threshold_De is reached
239 | useful for calibrating threshold_De
240 | not suitable for actual probing because shaking/vibration causes
241 | unexpected trigger
242 | *********************************************************
243 | */
244 | #ifdef mode2
245 | if (de >= threshold_De)
246 | {
247 | digitalWrite(probe_out, HIGH);
248 | digitalWrite(LED_out, HIGH);
249 | delay(200);
250 | digitalWrite(probe_out, LOW);
251 | digitalWrite(LED_out, LOW);
252 | }
253 |
254 | else
255 | {
256 | digitalWrite(probe_out, LOW);
257 | digitalWrite(LED_out, LOW);
258 | }
259 | #endif
260 |
261 | //serial communication
262 | #ifdef EN_serial
263 | Serial.print("reading: ");
264 | Serial.println(scale.read());
265 | Serial.print("Value: ");
266 | Serial.println(val0);
267 | Serial.print("De: ");
268 | Serial.println(de);
269 | #endif
270 | }
271 |
272 | int getValue()
273 | {
274 | val0 = scale.get_units(1);
275 | }
276 |
277 | int getDe()
278 | {
279 | de = val0 + val2 - (2 * val1);
280 | abs(de);
281 | }
282 |
283 | int recalibrate(int num_sample)
284 | {
285 | threshold_Val = scale.get_units(num_sample) + threshold_gap;
286 | for (int k = 0; k < 3; k++)
287 | {
288 | digitalWrite(LED_out, HIGH);
289 | delay(20);
290 | digitalWrite(LED_out, LOW);
291 | delay(50);
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Strain-Gauge-Leveling-Probe
2 | Using strain gauge, HX711 ADC and Arduino/Digispark for homing and automatic bed leveling (ABL) of 3D printer, with improved trigger algorithm.
3 |
4 |
5 |
6 |
7 | I was amazed by the [Creality CR6-SE ABL](https://www.kickstarter.com/projects/3dprintmill/creality-cr-6-se-leveling-free-diy-3d-printer-kit) which use strain gauge to detect load/pressure on nozzle.
8 | This method of probing has no probe-nozzle offset and shouldn't be affected by temperature of the bed.
9 | Strain gauge probing is used in CNC touch probe before 3D printer was invented.
10 |
11 | CNC Touch Probe
12 |
13 |
14 |
15 | Some makers applied this concept on their design in the past
16 | * Palmerr23 - https://www.instructables.com/Reprap-Load-Cell-Z-Probe/
17 | * David Pilling - https://www.davidpilling.com/wiki/index.php/Zprobe#a12
18 | * IvDm - https://github.com/IvDm/Z-probe-on-smd-resistors-2512
19 |
20 | While I made my own strain gauge/load cell contraption, I refered to [IvDm's Arduino sketch](https://github.com/IvDm/Z-probe-on-smd-resistors-2512/blob/master/strain_gauge_switch_ATtiny85_V_1.1.ino) a for trigger algorithm. But soon, I realised couple of weakness with the sketch.
21 | * Drifting
22 | * The strain gauge resistance rises when temperature increase, this will change the strain gauge readings and is known as drifting. Normally strain gauge is not affected much by the hotend or bed temperature since they are isolated from them & [half bridge/full bridge wheatstone](https://www.ni.com/en-my/innovations/white-papers/06/how-is-temperature-affecting-your-strain-measurement-accuracy-.html) compensates the effect.
23 | However, when the strain cell is constantly getting excited for readings, it will emit heat significant enough to cause drifting. If the reading is done with long interval and cut off power between interval (such as [power_down](https://github.com/bogde/HX711/blob/master/keywords.txt) in bogde's HX711 library) then this effect is very minimal.
24 |
25 | * But for our application the probe will read value constantly at 80Hz, drifting will cause the probe to false trigger, giving me failed homing and mesh.
26 |
27 | * Crushing bed
28 | * In some cases drifting eventually make the probe not responding to bed touch, the hotend will crush onto build plate and damaging the nozzle, heatbreak, z rod and motor.
29 |
30 | ## My Improvement
31 | * Trigger algorithm
32 |
33 | *load cell reading + change in gradients*
34 | This sketch will also read change in gradient of readings, denoted as de.
35 | > if de >= threshold_de, probe is touching the bed
36 | > if de < threshold_de, probe not touching the bed or drifting occurs
37 |
38 | * Panic mode
39 |
40 | When a threshold value is reached, Panic mode forces trigger the output to HIGH, preventing damage to the printer.
41 |
42 | * Blinks
43 |
44 | Use led to indicate the working state of the probe
45 | * Start working (finished setup loop)
46 |
47 | digitalWrite(LED_out, HIGH);
48 | delay(50);
49 | digitalWrite(LED_out, LOW);
50 | delay(50);
51 | for (int j = 0; j <= 5; j++)
52 | {
53 | digitalWrite(LED_out, HIGH);
54 | delay(50);
55 | digitalWrite(LED_out, LOW);
56 | delay(50);
57 | }
58 | * Trigger
59 |
60 | digitalWrite(probe_out, HIGH);
61 | digitalWrite(LED_out, HIGH);
62 | delay(100);
63 | digitalWrite(probe_out, LOW);
64 | digitalWrite(LED_out, LOW);
65 | * recalibrating
66 |
67 | for (int k = 0; k < 3; k++)
68 | {
69 | digitalWrite(LED_out, HIGH);
70 | delay(20);
71 | digitalWrite(LED_out, LOW);
72 | delay(50);
73 | }
74 | ## Demonstration
75 | Youtube! : https://youtu.be/V8OPNfr5NjQ
76 | [](https://youtu.be/V8OPNfr5NjQ)
77 |
78 | ## Working principle
79 | A load cell is placed on the hotend heatsink mount. It picks up tiny warp of the mount is read by HX711 24bits ADC. The Arduino (Digispark) compared the value and change-in-value to threshold values to determine if it should fire signal to printer mainboard.
80 |
81 | In my experience, drifting of readings occurs after the probe is powered for an amount of time. The reading climbs and hit threshold from time to time. I used change-in-value (denoted as de) as second input to see if the reading shoots suddenly (when nozzle touch the bed) or it's just rising slowly. If only readling value hits threshold but change-in-value is small, the probe will not trigger and it will recalibrate threshold after a short amount of time.
82 | After each successful trigger, the probe wait a short amount of time for the nozzle to lift back in the air and it recalibrates/tares itself before next probe.
83 | To prevent crashing into the bed if the probe is not firing, a panic value is set to trigger the probe in all instance.
84 |
85 | ## Wiring
86 | Typically HX711 board is shipped with 10Hz sampling rate, some modification is needed to use 80Hz sampling rate.
87 | In my case, it's resoldering the 0ohm resistor on XFW-HX711 board.
88 |
89 | Wiring for Digispark board.
90 |
91 | |Endstop pins | Digispark | HX711 | Remark
92 | |--- | --------- | ----- | --
93 | |5V/V | 5V | Vcc |
94 | |GND/G | GND | GND |
95 | | | 0 | DT/DOUT | serial data
96 | | | 1 | | LED pin
97 | | | 2 | SCK | serial clock
98 | | | 3 | | *used in USB comm*
99 | | IN/S | 4 | | *used in USB comm*
100 | | | 5 | | *low voltage, don't use*
101 |
102 | Check [Digistump documentation](http://digistump.com/wiki/digispark/quickref) for more info
103 |
104 | If you are using other Arduino boards such as Nano or Pro Mini, or using atmel chip for custom pcb, you will need to decide the wiring on your own.
105 |
106 | 
107 |
108 | 
109 |
110 | ## Marlin Configuration
111 | Marlin firmware needed to be adjusted for the probe. Version of marlin I use is 2.0.X bugfix but name of some configurations might change from time to time.
112 |
113 | Define the probe used as NOOZLE_AS_PROBE
114 | 
115 |
116 | 
117 |
118 | 
119 | The pin number varies for all kind of boards. Use the old z-min pin just to be safe.
120 |
121 | 
122 | this part define whether the endstop/probe is active high or active low. Since our probe sends 5v when triggered and 0v when standby, we define ENDSTOPPULLDOWN_ZMIN.
123 |
124 | 
125 | make the probe probes twice rather than once (one quick probing and one slower)
126 |
127 | the rest are behavior of the probing action, need to experiment on your own.
128 | 
129 |
130 | 
131 |
132 | 
133 |
134 | ## Accuracy and repeatability
135 | I attached the M48 repeatability test and it scored as good as 0.005 deviation. Please note that is after a lot of trials and errors before I can really get it to work properly.
136 |
137 | Since the max sampling rate of HX711 is 80Hz, we can calculate the theoritical resolution of the probe like this:
138 |
139 | Sampling rate: 80Hz
140 | Homing feedrate: 6mm/s
141 | Z porbe speed fast: (homing feedrate)/2
142 | Z probe speed fast: (Z porbe speed fast)/4 = 0.75mm/s
143 | Resolution of reading: 2* 0.75/80 = 0.01875mm
144 |
145 | Therefore, 0.01875mm is the worse case scenario for the deviation of the probe, but most of the time I get around 0.004 to 0.008. If you spot mistake in my calculation please let me know.
146 | Theoritically, by lowering the probing speed we can get better resolution, but this also lower the de value. From my test, with slower probe speed, the probe cannot "feel" the hits and give worst deviatioon value (as high as 0.20).
147 |
148 | ## Issue
149 | * Spongy bed & gantry
150 |
151 | the Ender 3 I use has only one Z-rod on the left side, the right side is a little bit spongy
152 | this causes a dampening effect which lower the de value, therefore probing fails occasionally at the right side of bed during ABL.
153 | I suspect this will also happen if a spongy bed with weak spring is used
154 | Eventually this's solved by using lower threshold_de
155 |
156 | * Plastic on the nozzle
157 |
158 | Plastic oozing on the nozzle also lower the de value.
159 | Marlin and slicer is configured to perform automatic nozzle brushing before homing and probing.
160 |
161 | 
162 |
163 | * Filament tugging
164 |
165 | With direct drive extruder and filament spool on top of printer frame, the filament will tug the extruder when extruding, or
166 | during z-axis motion. This could triggers the probe as well as messing with the accuracy of the probe. In some case, the probe homes at mid-air.
167 |
168 | **Solution**: Disable or 'sleep' the probe except when homing. Use bowden tube.
169 |
170 |
--------------------------------------------------------------------------------