├── 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 | [![thumbnail](https://user-images.githubusercontent.com/75633795/110341381-3b4ebc00-8065-11eb-9017-79206bed4013.jpg)](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 | ![wiring](https://user-images.githubusercontent.com/75633795/110344269-46efb200-8068-11eb-8251-2c358f1481de.jpg) 107 | 108 | ![DSC03501](https://user-images.githubusercontent.com/75633795/110344403-6686da80-8068-11eb-885a-7035a17b84f8.jpg) 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 | ![Screenshot 2021-03-08 025822](https://user-images.githubusercontent.com/75633795/110344919-f9c01000-8068-11eb-8dec-f131c877f0fb.png) 115 | 116 | ![Screenshot 2021-03-08 025954](https://user-images.githubusercontent.com/75633795/110345176-43a8f600-8069-11eb-8c91-02b6d9f12edd.png) 117 | 118 | ![Screenshot 2021-03-08 030037](https://user-images.githubusercontent.com/75633795/110345200-4a376d80-8069-11eb-8b11-438b6f3a7b3a.png) 119 | The pin number varies for all kind of boards. Use the old z-min pin just to be safe. 120 | 121 | ![Screenshot 2021-03-08 030933](https://user-images.githubusercontent.com/75633795/110345509-9c788e80-8069-11eb-899f-6fc289b33000.png) 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 | ![Screenshot 2021-03-08 030236](https://user-images.githubusercontent.com/75633795/110346148-3cceb300-806a-11eb-954a-82b23d06804c.png) 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 | ![Screenshot 2021-03-08 030200](https://user-images.githubusercontent.com/75633795/110346344-6a1b6100-806a-11eb-884b-90946e375427.png) 129 | 130 | ![Screenshot 2021-03-08 030327](https://user-images.githubusercontent.com/75633795/110346368-6ee01500-806a-11eb-8c33-f93189ac4516.png) 131 | 132 | ![Screenshot 2021-03-08 031201](https://user-images.githubusercontent.com/75633795/110346404-79021380-806a-11eb-9994-8b3978275ead.png) 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 | ![Screenshot 2021-03-09 002330](https://user-images.githubusercontent.com/75633795/110349500-bc11b600-806d-11eb-8556-dda79681f347.png) 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 | --------------------------------------------------------------------------------