SURFACE Mapping - using MARLIN - try it out

I modified Marlin so it can use the built-in Auto Bed Leveling feature for use with my MPCNC. I have posted my modified Configuration.h and Marlin_main.cpp files, so you can try it out… CAUTION: Personally i have only dry run tested this myself, but, it seems to work fine (dry run, meaning i create the map with a board that was on a tilt, and after mapping it, i can move the spindle all around the work surface and it automatically keeps the Z to just touching the surface.)

Mapping the work surface is needed for several uses- 1) is PCB etching (carving) 2) V-Carving not so flat boards, especially v-carving smaller text, 3) etching (carving) onto acrylic, or glass. There may be other times it’s needed also, so, if you need it, this should work.

BUT, you would need to have your machine set up for it. I use Z-Max for homing my spindle up when issuing a Home command, and use Z-Min for the Touch Probe / Mapping Probe.

For mapping, I have a Vacuum shoe sweep that I attach with magnets to my spindle, so I printed a bracket to hold a micro sw and i take off the Vacuum shoe sweep and attach my Mapping Probe using those same magnet points. My sw sits right at or below (X Y aligned) the bit - so i don’t need an X or Y offset, but, if your probe sits off to a side, in the configuration.h you can set a Probe X or Y offset. You can also specify a Probe Z offset, but I programmed this so that the Z offset will be calculated Automatically when i do a G30 command to get the top of the surface work piece.

My GCODE Header work flow is as follows - in Gcode – without leveling:
; Beginning of Header
M80; Turn On Power Supply (PC ATX PS = Power for Steppers)

G28 Z0 ; home to limit sw
G28 Y0 ; home to limit sw
G28 X0 ; home to limit sw

M3 S8500; turn Spindle On to Snnn RPM

; Move spindle to desired Start Pos - XY corner of the Work piece
G0 X20 Y40 F12000 ; xy corner of material
G0 Z132 F1200 ; top of material NOTE: in my set up, ZMAX == 210, so I am moving it down from 210 to 132 (This Value is obtained by running a G30 command and is different with each bit change and material height )

G92 X0 Y0 Z0 ; set current LOGICAL location to 0,0,0

G0 Z5.080 F1200; Spindle is now 5mm above the XY starting corner of the material.
;----- End Of Header
; — run the actual Gcode for Cutting ----

So, to use the Surface mapping, here is my new work flow, before I run the above GCode header and cutting file

G28; Home
G0 Xnn Ynn Fnn; send the spindle to the XY corner of the work material, such as X20 Y40
G92 X0 Y0; set logic Zero/ this is same as i do in my normal header

; Issue the Surface Map command and create a map of the work piece surface

G29 L0 F0 Rrr Bbb Xxx Yyy T; Where L and F set the starting corner of the work piece, I am using 0,0 for the start because i moved my spindle to that location and issued a G92 command to set that location as 0,0,0 for the cutting file. Rrr = Right (X) size of work area to map, Bbb= back or Y size of material to map, such as R200 B300 for a work material that is 200mm in the X direction by 300mm in the Y direction. And, i supply X and Y - which in the G29 command sets how many grid points our map will have. X4 Y5 will set a map with 4 points in the X direction and 5 in the Y, 20 points total. If T is given it will print the results out to the serial port so the host software will display the Map data once it is created. (I am currently using Repetier Host for sending the GCode files)

; I put in code so that this data, the LFRB and XY data, MUST be sent with the G29 command, or it will give an error and not create the map. I’ve included a CNC_LEVELING_VALUES define in the configuration.h file, you can comment that out if you don’t want that feature. The only exception is you can issue a “G29 T” and it will not do a map, but, will print out the current map to the serial port

; The G29 command will create the MAP - but, uses the Z Height at whatever Height the Probe is-- which will NOT be the same as the cutting Bit…
; so, after running G29 i move the spindle back to the material XY starting corner, which will be 0, 0 (since I used the G92 command to set 0,0 to the work piece corner)
G0 X0 Y0 F12000

; remove the Probe and if not already installed, install the router bit,

; then place the metal touch-plate at that location and attach the wire to the bit , and issue the G30 command.

This will do a single probe at the starting corner of the work piece.

I Modified the G30. As long as CNC_LEVELING is defined in the configuration.h file, the G30 command will get the height of the work surface, for the bit that is installed. In the Configuration.h file I have also added a define called ZERO_PLATE_THICKNESS which you set to the thickness of the metal touch plate that you are using.

; this way when we do a G30 cmnd it will calculate the Z height (-) (minus) the thickenss of the touchplate, so it will get the exact Z height of the work stock at the starting corner.
; the other I have the G30 command do, is it will calculate the difference between the Bit Z height and the Probed Z height, meaning it will automatically calculate the Probe Z offset value. And, it then goes through the Map data and applys that offset to the remaining Map data points. So, the map get re-aligned to the bit height all just by issuing the G30 command.

; after issuing this G30 we can then go ahead and run the Gcode script to cut the work piece,
; the Gcode can re-Home the spindle, etc, and run as normal, and the mapping data will still be there and mapping will still be enabled (as long as the CNC_LEVELING flag is defined in the configuration.h file).

; After the first bit is done cutting, if another bit is needed, like a rough cut, then final cut or additional cuts,
; go ahead and change the bit and move spindle to Starting corner of work piece (Logical 0,0) and issue another G30 with the touch plate in place and it will get the Z height with the new bit - (minus) the thickness of the touch plate, get the difference in height and apply that new difference to the map - again, and it is ready to cut with the second bit,. Again you can Home, etc, and cut away. An d, you can do this as many times as you need, the map data will not be disabled.

you can issue a G29 T to view the existing map data.

So far i have only DRY run it, i haven’t had time to put on a piece of wood and cut it, but, after mapping, I issue HOME and G30, i can use G0, G1 and move the spindle around (I had purposefully put a board on the work area on a tilt) and as you move around the spindle in the X Y directions, the Z tracks up and down as needed to keep the spindle level to the tilted surface.

I am uploading the Configuration.h and Marlin_main.cpp These are the files from the DualDual_1_1_5 iteration of marlin, that was made for the separate control of the X and Y stepper pairs.

I have noted where i did the changes for the CNC Leveling, so if you want to apply this to another version of Marlin, search for “CNC_Added” and copy the code in those areas to the .cpp file that you use. Same with the Configuration.h file. Let me know if this works for you.
 (130 KB)


Awesome! This is great and well thought out. I am not at my computer but I will look at this and get it on a branch in Ryan’s GitHub. I really want to try this myself on my low rider.

I just realized - when i run the G30 command, after getting the bit to surface height, i then read out the first Map data, z_values[0][0] and subtract our bit Z height to get the difference. What I have the code do next is take the difference value and run through the map data (z_values) and apply the difference to all the map points. Although this works, it may not be the right or best way to do it. There is a Z Probe Offset variable, so, really I think all that would be needed is to set the Z Probe Offset with the difference value, rather then changing all the data points in the map. Either way should work, but, it will probably be cleaner to use the Z Probe Offset variable, and let Marlin’s planner do it’s thing.

Another thing, if the code is going to be submitted to github, a more clean configuration.h file should be used, as i made a few value changes specific to my machine in the Config i attached. Be better to use a clean config file and just add the <CNC_Added> defines to it. Such as the CNC_LEVLEING, CNC_LEVELING_VALUES and ZERO_PLATE_THICKNESS - i think are the only changes, other than enabling Bilinear Bed Leveling

Holy cow. I read that, first email of the day, coffee in hand, 15 minutes after waking up. I have to read that in a bit again, some of that went over my head. I am sure a bunch of people will want to try that.

I like the header idea, just change your piecework values and that modifies the probing and includes work offsets. That has been a major issue as Marlin expects a never changing “bed” size. Nice idea, I appreciate all the work that must to have gone into this already!

In my personal workflow, I would probably want to move the head manually (or with a jog commands) to the lower left corner, and then start the gcode. I would try it with a separate gcode script first, probably.

But at some point, I would probably add some kind of script to my workflow, so that out of EstlCAM or whatever I’m using to do the CAM (might be something else for PCB ((on a Low Rider!?))). Anyway, after tha CAM, I would run it through a script that would calculate the mesh parameters and put them in the top of the gcode. I never have ran the same gcode twice, because everything I do is a one off piece, so I would rather have the location, width, height, etc. determined for me, instead of fiddling with the text editor. The script would just read the gcode, and come up with the parameters for the G29 command.

Been working on this for the last couple of days - My programming ability is really letting me down.

I’ve had a couple of thoughts on it though and what I am currently working on this this;


I’ve got a microswitch on the bottom of a burnt out router bit, means I don’t need an offset, that plugs into the lowrider and can be swapped for a touchplate for setting the offset.

I don’t really want to have end stops, but from what I can see it is a must. My cheap and dirty solution is to have a switch that I can turn on while homing, keeping the X and Y axis where I’ve set them, and convincing Marlin that I’ve installed end stops.

That seems to work okay for the moment.

However the probe when it homes, only seems to want to home on the Z Max pin, not the Z Min. Again, workable. But less than ideal.

The big problem I’m facing is that it seems once the Z Axis homes, the steppers disengage and the Z axis comes tumbling down.

Anyways, I hope to come up with something fruitful by the end of the weekend. I’d like to hide all the Gcode settings in the LCD Menu as I don’t like having a PC attached to the Lowrider.

There is a “safe homing z” setting which might be what you need to turn off. Otherwise, disabling the endstops in firmware would be good.

That’s a setting in the configuration. Something like, “z probe uses z min”

What? Homes or probes? Which gcode are you using? It should definitely not be doing that.

The easy way too do that is to put them on files in the SD card, but that sometimes gets treated a little differently. Alternatively, there was a thread here where someone edited the lcd menus.

I’ve put the setup codes UnLtdSoul provided in the menu along with a return to home - It all seems to be working.

I’ve noticed I’ve been homing from my Max X point and Min Y, I’ve had to change this to have the machine auto home correctly.

So I can map the surface, and all goes well, however when I try to create a CAM file in fusion, I can invert the X axis, to match up to my table, but then it inverts the Z axis, when I invert the Z axis in fusion it then inverts the Y axis, which doesn’t have an option to flip it.

Once I’ve got this sorted I’ll upload the firmware file I have.

I’ve put a switch in for the endstops, basically you turn the switch on, autohome, then turn the switch off, line up the router to the workpiece, then set that as the home position through the option I added in the menu. From there you can map the worksurface (my programming ability is not very good, so I just have a map half size (1200x1200) or full size sheet (1200x2400), Ideally I would like to have a menu option to set the workpiece size and the number of mapping points. I’ve noticed if you leave the endstops engaged, once it maps the first 3 lines, it assumes the router bit is at 0,0 so tried to run it out of the X Axis.


EDIT: Sorry I should mention, I kind of have to home to the bottom right hand corner of the table as firstly, thats where the LCD is, and secondly, the workspace it is in leaves me space on the bottom of the table and the right hand side - the top and left hand side is more or less inaccessible.


EDIT2: Flipped the Y stepper connector and it’s working as I want it to. Keep running into probing failure at the moment, I’m wondering if it’s because the difference in height of the workpiece is too severe


EDIT3: Nope. That still doesn’t solve the problem in Fusion 360. It’s probing after I increased the max bed size.

[attachment file=“DualDual_1_1_5AUTOLEVEL.rar”]

I’ve got it working now - didn’t manage to cut much, cause I broke a bit. But it is cutting much easier, only mapping a 3x3 grid across 1200x1200

I’ve attached the firmware file if anyone wants to give it a go. It’s running off the DualDual1.15 version of Marlin - It seems they got rid of ‘parsing’ somewhere along the line.


As a heads up, this isn’t anywhere near finished or complete, but it does work.

Thanks a lot for this very clear tuto and for the files!

I’ll give it a try soon on my machine, it’s been a while since I didn’t use the autoleveling function and I haven’t been super motivated to actually go through the process of editing Marlin again. This will come in handy :slight_smile:


I’ve got the file, it’s not uploading though!

You can upload zip files.

My understanding is to perform a auto bed leveling you need to home on X and Y. I do not have end stops on my MPCNC and do not plan to add them. The issue I have is I cannot always use the same 0,0 because the shape of the wood dictates how a clamp the piece down and most of the time it is not exactly at 0,0. I line up the spindle at where I designate 0,0 and work from there. Is there a way to use auto leveling by not homing X and Y, just take the original position as home?

You do not need auto leveling, you should be surfacing your material on Z critical cuts, that by default levels your surface. For any other cuts the Z is not critical, and no one will ever notice if your depth is off my the amount the material is warped.

Resurfacing is a big job. I am trying to print an aztec calendar on 24" x 24" . One corner is low and some of the detail was missed. Was also thinking of writing some gcode to probe the surface and then using the lowest reading as o for Z.

You don’t have to surface the whole board, just the part you are using. You are cutting it anyway and moving over the surface, why not take off 0.5mm? This is on the same not as drum sanders, jointers, and planers. Wood always needs work for higher accuracy work.

On your calendar if you are using a Vbit I do not see any other way around it that to surface the material first, with the CNC or otherwise. Throw on a 1/4 or larger bit and take a 10 minute skim over the surface.

Honestly I do not know if you can surface map, I assume you can using they same system as 3D printing but I don’t know if there are any pitfalls. Just chase it around with the Z probe and try it.

I ended up using a switch to trick marlin into thinking it had homed, then once homed, I would turn the switch off and let marlin do it’s mesh leveling. It worked okay, never tried anything with a v bit so I’m unsure how it would work.

I ended up getting rid of the auto mesh leveling, it was way too messy.

I’ve since bought some foam so my 2d cuts can go straight through with no resistance, I’ve also surfaced a small area of that for more 3d operations.

1 Like

I think I may have interpreted the gcode incorrectly. I was using a z touch plate wired to the z limit switch to determine the top of the material. Basically just a feeler gauge 0.406 mm thick. I used the following gcode

G0 Z25 F480
G0 X5.0000 Y5.0000 F2100
M0 Place touch plate
G28 Z
G1 Z2 F150
M0 Rmve touch plate & Spindle On
G1 Z-0.406 F150
G1 Z2 F150
G0 X0.0000 Y0.0000 F2100
G92 X0 Y0 Z0



I thought after I used G28 Z to home zand then further down G1 Z-0.406 F150 to take into account the thickness of my probe and set z home -0.406 below what was homed to. I think this is my error. Z home would be 0.406 mm above my material the entire job. I think I need to add in M851 Z-0.406 to the code but I am not sure. Any advice.

You can set the offset in the firmware, or let me see if I can simplify this a bit I assume you are not using the dual endstops, if you are I have my code posted.

You do not need the first three lines, the steppers are off an do not know where they are so why bother moving them under power. place the head where you want it and have the plate ready when you hit go. Don’t think m17 does anything, positive you do not need it.

G90 ; just in case

G92 X0 Y0 Z0 ; fresh start just in case

G28 Z ; homes Z only

G1 Z2 F150 ; moves the bit to remove the plate

M0 Rmve touch plate & Spindle On ; pause with message

G1 Z-0.406 F150 ; true zero

G92 Z0 ; reset zero set point


1 Like

I have a different take. I think Ryan’s will work and I see the problem in your gcode.

G1 Z-0.406 F150
G1 Z2 F150
G0 X0.0000 Y0.0000 F2100
G92 X0 Y0 Z0

You move to z=0.406 and then you immediately move to z=2.0. I think to do what you were trying to do, you would do G92 Z0 right after moving to -0.406.

If it were me, I would not want to touch the top of the wood with a moving spindle. Even if it’s perfect, I think it would leave a mark. So I would replace this:

G1 Z-0.406 F150


G92 Z2.406

That’s assuming the last place you were was still Z=2.

Ryan’s will also work, and it’s simple, but it still touches the worl with a moving spindle.