Intro: Autoleveling 3D Printer Gantry
This Instructable shows a circuit to automatically level a 3D printer gantry so it's parallel with the print bed.
Like so many other people, I found that designing and building my own 3D printer/plotter/CNC/ etc. is a fun and challenging project. My printer has a fixed bed, so the printhead gantry is is supported by a Z-axis motor at each corner. When the printer is turned off, the motors tend to slip a little. Since the gantry is not equally weighted, some corners slip more than the others, regardless of carefully I park the printhead.
Leveling the gantry by hand is not that difficult, but I wanted to make it automatic. My solution was to make a circuit that enables me to switch each Z-axis motor off by turning on a corresponding digital port on my controller. I have sensors, basically endstops, near each corner. I move the printhead over the sensor, turn the other three motors off and "dip" the selected corner until it homes.
The circuit is built on a PCB and driven using digital pins - 16, 17, 23 and 25. I use stock RAMPS 1.4 and Marlin. This approach doesn't require any modification to Marlin, it's all done by sending G-code to turn on the relevant ports. Homing and leveling takes about a minute.
I am not sure how much of problem is posed by the Z-axis slippage in professionally built printers, but I hope you'll find this Instructable interesting and useful.
Step 1: Circuit Design and Parts
Connecting motors in series vs. parallel
RAMPS provides two connections for Z-axis motors that are wired in parallel. If you use two motors, the current splits, so you get only half the power on each motor. I needed more power, so I connected my motors in series - Pololu shield will drive basically five motors wired in series with full power, since each needs a little over 2V and each gets the full current. As far as RAMPS is concerned, it looks as if I have only one Z-axis motor.
Series-connected motors make for a more complex circuit, since the current has to be shunted around a motor that needs to be turned off. If the motors are connected in parallel, each motor can just be turned off without impacting other motors.
In addition to the circuit for four motors connected in series, I also provide a circuit for two series-connected motors and another circuit that turns a single motor on and off which can be used if you want to keep your motors connected in parallel. Keep in mind, though, that the four-motor-series circuit is the only one I've actually built, so your mileage may vary with the others.
Each stepper motor has two circuits, so we need two switches to turn it on and off. I tried to make the circuit using MOSFETs and diodes, but could not make it work. It almost worked, but I suspect the delays corrupted the waveforms going to the motors. Someone who really understands electronics could no doubt make a solid state circuit work, but I am a software guy and basically clueless and finally used mechanical relays.
The circuit is designed so that the motor is bypassed when the relevant pin is HIGH, so the relays are off unless alignment is in process.
To drive the circuit, we need a motor connection to the Z-axis pins on RAMPS (i.e., 4 wires), then a wire to each digital port. I use a 4-stranded phone cable for each. You will also need a 12V+ wire and a neutral. Make sure that the 12V+ and neutral are from the same power supply you use for your controller, since the digital pin voltages will go to the same neutral.
For each motor you want to turn on and off, you will need:
one 1K ohm resistor
one 2.2K ohm resistor
one STP55NF06 MOSFET (the same MOSFET used on RAMPS 1.4)
two Omron G5LE-1 relays
1K ohm resistor is a pulldown resistor to make sure the MOSFET is off unless explicitly turned on. 2.2k ohm resistor is to limit current pulled from the Arduino digital pin. MOSFET turns the relays on and off.
These relays have two output pins and the output current goes to one when the relay is off and to another when the relay is on. You can use them as a simple on-off switch by wiring just one of the two output pins, but for motors connected in series, one pin will send the current to the motor and the other will send it around.
The control pins for two relays that switch each motor are connected in parallel. While connecting in series would make for a simpler circuit, 12V is not enough voltage for it.
You can use different components if you wish. I used STP55NF06, since I had them left over from the failed solid-state circuit version. I selected the G5LE-1 relay because it was dirt cheap and can also switch 110V current if need be, so I can use it for other projects. I neglected to check the size and these things are pretty large - my 4-motor circuit barely fits onto a 100x70mm PCB. Something like Omron G8QN_QW will work quite as well and is considerably smaller (and more expensive).
Important word of caution
Make sure the motors are turned OFF when any relays are turned on or off. When a motor is on, even if the printer is stationary, the driver sends a waveform for the motors. When the waveform is switched, you'll get a strong electromagnetic pulse. It took me a while to figure out that it's not the relay coils that are messing with my electronics. I even made a steel plate cover for the board, but that was of limited help. My Arduino Mega was actually partially fried by the pulses just before I realized what was going on. You'll see M84 all over G-code.
Here is the finished board during testing - I marked which relay set is for which corner. Notice it's elegantly mounted on scrap wood :)
Step 2: Fritzing
While I made a PCB with the circuit, you could also make it on a prototype board. The only complication will be that the relay contacts are not on the standard 0.1" grid, so you'll have to enlarge or merge relevant holes in the prototype board.
I used Fritzing, a popular free package, to design the PCB. I am attaching three projects: four motors in series, two motors in series and a single motor on/off for motors connected in parallel. Select the one that fits your needs. These projects need two parts I made for the purpose - the header and relay. Import the attached parts files for these parts into Fritzing.
Here is the breadboard view of the four motor circuit:
DP16-DP25 indicate the inputs from the Aruduino Mega/ RAMPS 1.4 digital ports 16-25.
And here is the PCB view:
Note that this fits on a single-sided circuit board. The four motor circuit will cover a 100x72mm PCB.
Attaching wires to the PCB
To attach each wire to the PCB, I use a pair of "via" holes in the PCB. One via is connected to the lead on the PCB, the other is standalone. The wire comes from below, passes up through the standalone via and down through the connected via. The insulation is trimmed so it's level with the PCB surface as the wire emerges from the second via and the wire is then soldered to the lead.
Two wires for the motors are connected to the relay inputs on the left. The other two are connected past the rightmost motor set of pins. This makes the board simpler and smaller, since I don't have to route the leads all the way back.
Bottom wire is neutral. Top wire is 12V+. Four wires from RAMPS digital pins attach next to the each 2.2k ohm resistor.
Step 3: Sensors
For each Z-axis motor, you will need a corresponding sensor. You could just use regular end-stops, but I prefer to have sensors on the actual print bed, since I'll get my gantry parallel to the bed, as opposed to having to make bed parallel to the end stops.
I also use my system as a CNC machine and need to level the gantry relative to whatever workpiece I am about to machine.
I am using contact plates as sensors. My original homing used a pressure-sensitive resistor protected by a piece of PEI (see picture). This, however, requires a bit of pressure to activate. The print bed is mounted on springs and the spring behavior changes with temperature and whatnot, so I had to keep tweaking the post-homing distance. If you elect to use mechanical switches, choose something that takes very little force to activate.
With contact plates, the hot end (I use E3DV6) is wired to the other end stop pin and it only takes a touch to fire - much more precise and repeatable. A side benefit is simpler wiring, since each switch needs a single wire, as opposed to two if I used a mechanical switch. The downside is that you have to make sure your nozzle is clean and has contact when it touches the plate, but so far that hasn't been a problem.
My contacts are reasonably large since I have plenty of space on the bed outside of the heater. If the space is at premium, you could just wire and glue to the bed a small piece of aluminum foil.
I found it makes life easier if the switches at each corner are similar in height to one another and also relatively level with the glass printing surface. Since fractions of a millimeter matter in this context, it's hard to get all the surfaces at the same height. Instead, this corrected in G-code.
Step 4: G-code
Here's the G-code I use to level my printer. The logic is as follows:
- raise the head for traveling
- home x and y
- move to the left-front sensor
- home Z
- raise head to traveling distance
- home each corner in turn
- home Z again
The first general homing of Z is to get to a known point, since the head could be anywhere on startup. Since there is some twisting involved, homing each corner in turn will end with the Z being slightly off from where it should be for printing, which is why we do another general home Z at the end.
For each corner, we do as follows:
- position the head over the sensor
- lower the head to Z=5mm
- turn off the other three motors
- home Z (this moves just the current corner)
- adjust the perceived height to account for the actual sensor level
- raise head to Z=5mm (you'll have to switch the order of steps 5 and 6 if your sensors are below the print bed)
- turn on all the motors
- raise the head to traveling height
I am using Z=5mm, since my gantry is pretty flexible (600mm long 8mm rails have a surprising amount of flex). You might use smaller distance if your printer is stiff.
The code below is the same as in the attached leveling.gcode file.
; G-code for gantry autoleveling.
; *must* use M84 to turn off motors before turning any relays on or off via M42
; make sure all motors are on
M42 P16 S0 M42 P17 S0 M42 P23 S0 M42 P25 S0 M400
; raise for traveling in case this is startup and head is too low G28 X0 Y0 ; home x and y G1 X20 F4800 G92 X0 ; otherwise right edge is beyond the range
; 0,0 G1 X0 Y40 F4800 G28 Z0 ; initial Z-axis homing G1 Z4.2 ; to account for the sensor being above the print bed M400 M84 ; turn off motors at the other three corners M42 P17 S255 M42 P23 S255 M42 P25 S255 ; home current corner G28 Z0 M400 ; adjust for the sensor offset and move back to Z=5mm G92 Z0.4 G1 Z5 M400 M84 ; all motors back on M42 P16 S0 M42 P17 S0 M42 P23 S0 M42 P25 S0 M400 ; move up for traveling G1 Z20
; 0,y G1 X0 Y420 F4800 G1 Z5 M400 M84 ; turn off motors at the other three corners M42 P16 S255 M42 P23 S255 M42 P25 S255 ; home current corner G28 Z0 M400 ; adjust for the sensor offset and move back to Z=5mm G1 Z2.9 G92 Z5 M400 M84 M42 P16 S0 M42 P17 S0 M42 P23 S0 M42 P25 S0 M400 G1 Z20
; x,y G1 X425 Y420 F4800 G1 Z5 M400 M84 ; turn off motors at the other three corners M42 P16 S255 M42 P17 S255 M42 P23 S255 ; home current corner G28 Z0 M400 ; adjust for the sensor offset and move back to Z=5mm G1 Z5.0 G92 Z5 M400 M84 ; all motors back on M42 P16 S0 M42 P17 S0 M42 P23 S0 M42 P25 S0 M400 ; move up for traveling G1 Z20
; x, 0 G1 X425 Y50 F4800 G1 Z5 M400 M84 ; turn off motors at the other three corners M42 P16 S255 M42 P17 S255 M42 P25 S255 ; home current corner G28 Z0 M400 ; adjust for the sensor offset and move back to Z=5mm G1 Z3.2 G92 Z5 M400 M84 ; all motors back on M42 P16 S0 M42 P17 S0 M42 P23 S0 M42 P25 S0 M400
; final level at x,0 G28 Z0 G1 Z8.5 G1 Y33 F4800 G92 X410 Y0 Z10
Notice M84 command to turn the motors off before any M42 commands that turn digital pins (and thus relays) on and off. Also notice M400 commands. Marlin executes G and M commands in different threads and your M commands will get ignored if a G command is in progress. M400 blocks until any movement is completed.
Finally, the code starts by making sure all the motors are on. If you build this system, put "all motors on" sequence at the start of all your jobs, just in case. Octoprint, which I use, has a place for commands to be run before a job starts.