Milling PCBs on a CNC

I have a small CNC machine which I mainly used for accurately drilling PCBs. I have done a few experiments on milling PCBs and also here (ie. taking a sheet of copper clad board and cutting away copper where it is not needed).

I realised quite quickly that the bed of the CNC must be very level in order to create evenly milled PCB, otherwise you either get too deep cuts on one side or un-milled copper on the other side. This gets worse for larger PCBs.

Here I discuss options for levelling the bed for accurate milling.

There are two main options for having an even depth for milling:

  • Use a floating head CNC mill which physically stays at a fixed depth even with un-level bed.
  • Use a computer program to ‘read’ the bed depth and then correct the z-axis of the G-code.

Physical levelling

This involves physically keeping the milling bit a fixed distance from the board, no matter how uneven the board is (within reason). This is generally done with a floating head. This keeps a fixed distance by applying a small amount of pressure and having some z-axis movement (such as a spring).

Here are some example videos:

This works well and does not need any additional programming to do as long as the surface is kept clean.

But this requires buying/building a floating head unit (around £200). It can also be affected by any swarf on the cutting bed. Also it requires a larger flat surface area as the floating head usually has a ring around the milling bit which means the effective cut area is larger.

Floating head attachments to buy:

Computer levelling

Rather than physically keep the milling bit at the correct distance from the uneven board, we can also use software to adjust the G-code (the file which tells the CNC machine which XYZ coordinates to move to) so that it is mapped to our uneven board.

Firstly the board is mounted and then ‘probed’ at regular points all over the board. This builds up a map of the level of the board.

The z-axis within the G-code is then adjusted to take account of the variation in the board.

The probe is basically a continuity tested comprised of the metal bit of the CNC mill and the copper clad board. I needed to wire up one input to the LinuxCNC board (just as I set up the limit switches in this blog post). I then clip one connector to the copper clad board and one wire to the spindle (with the milling bit used as the probe)There is a load more information discussing probes here.

There are two main programs for doing probe levelling:

This uses Eagle PCB files as the input, hence it was not useful for me as I use KiCAD.

This is a multi-platform java-based program. It takes in G-code, probes the area and creates modified G-code as the output.

I added a probe input to my CNC control board. I had already written up the process of adding limit inputs to the CNC controller board I have. I followed this process again, but changed the Y-max-limit input into the probe input. This was connected to pin 15. The reason I chose this input is because it is hardly ever used – the machine needs to go very wrong for it ever to hit this limit.

I added a pull up 10k resistor to the input and two long fly wires – one from ground and one from pin 15. These both had croc-clips attached, so I can clip onto the spindle and onto the copper clad board.

I tested the input (using the ‘Show HAL configuration’ option in LinuxCNC) and then watching the “probe in” signal. This changed red to yellow when the two wires are touched together, proving the input works. I added some fly-leads and some large clips so that I could easily clip the probes to the workpiece, as shown here:


Computer levelling process

So I decided to try out Autoleveller and write up my work-flow.

  1. First I created a PCB design in KiCAD. (This was a ATX PSU PCB – simple but quite large).
  2. Then I converted the gerber file created in KiCAD into an isolation path G-code, using Linegrinder.
  3. Then I put the output G-code from Linegrinder into the Autoleveller program.
  4. This new g-code is then used in LinxCNC to probe then cut the board.

The first three steps all went well.

LineGrinder creates the G-code for the isolation paths:

AutoLeveller then takes the G-code and adds a probe section where the surface is probed and the Z-axis is corrected for any differences in level:

Then I took this output and tried to open in in LinuxCNC….

Problem 1: When I opened the G-code in LinuxCNC there was an error. Autoleveller has been checked with LinuxCNC version 2.5.x – I have been using 2.4.x. I downloaded the new LinuxCNC version 2.5.3, but did not use it as I….

I followed these instructions for upgrading my version of LinuxCNC. It was previously called EMC2, but in the new version it is named LinuxCNC. The upgrade actually went very smoothly. I needed to copy the configuration file I had already done for my machine into the linuxcnc folder (rather than the emc2 folder). Everything then worked as it had done before.

The output from Autoleveller then opened in LinuxCNC. I did a test cut using a blunt PCB milling bit. This seemed to go OK and I tried with a fresh milling bit. The result was perfect, as shown in this photo:

Work flow

Now that I had the autoleveller and probe working, I wanted to record the work flow for milling and drilling holes for a single-sided PCB. The process I used was as follows:

  1. First I created a PCB design in KiCAD. (This was a ATX PSU PCB – simple but quite large).
  2. Then I converted the gerber file created in KiCAD into an isolation path G-code, using Linegrinder.
  3. LineGrinder also outputs the drill file for the PCB holes.
  4. Then I put the output G-code from Linegrinder into the Autoleveller program.
  5. Add the ‘bodge’ code which will drill holes through the board (see Problem 2).
  6. This new g-code is then used in LinxCNC to probe then mill the board.
  7. I then change the tool for a drill bit and run the drill G-code (from LineGrinder).
  8. If I was making a double sided PCB then I would also flip the board around an mill the other side.

Problem 2: The output from LineGrinder is designed for a double sided board. You start by milling the top copper, then the bottom copper, then you drill the holes. The output drill file is designed to be run with the top copper facing up. I am doing single sided boards and the bottom copper is facing up, hence I need the x-coordinates to all be mirrored. There is no function to do that in LineGrinder or LinuxCNC. This is an annoying problem. I could follow the LineGrinder work flow (which would work fine for double sided PCBs) and drill reference holes then flip over the board and mill, then flip over again and drill. I don’t want to drill first as this might cause problems for the auto levelling program. This causes too many flips of the board and so makes it inaccurate. I followed this work flow and it produced a reasonable board, but the accurate hole alignment is a hassle. I’d prefer to hold a board in place, do the milling then do the dilling, without moving the board.

So to work around this I have applied the followng bodge. This works, but I will look into better options in the future.

The output from LineGrinder includes the isolation path G-code which includes a ‘peck’ cut for each of the PCB holes. This G-code can be altered to cause it to drill a hole. We first need to adjust the ‘pad touchdown’ settings in LineGrinder. Rather than just ‘peck’ into the copper, we need it to drill all the way through the board. I changed this setting to -0.2 inches:

We then create the isolation path G-code using LineGrinder. The output is then put through AutoLeveller to add the probe code and the z-axis adjustment.

Next we open up the G-code output from AutoLeveller. This contains a section which will start:

(... pad touchdown start ...)

At this point we want to stop the spindle, raise the milling machine to a height where we can change the tool to a drill bit and stop the program to change the tool.

This is done by adding the following code:

(... pad touchdown start ...)

G01 Z1.0

(The program will pause to allow the probe to be detached)
(press cycle start to resume from current line)


This is put before the section where it cuts all the ‘pad touchdowns’. G01 Z1.0 moves the cutting head up to 1″ above the workpiece. M05 stops the spindle. M0 will pause the code until re-started. M03 is a spindle start command.

This code is then run through LinuxCNC. It probes the area and then pauses. You can then unclip the probe and set the spindle to start. Setting the code running again will produce the isolation cuts around the tracks. The machine will then stop again and wait for a tool change. Change the tool to a 0.8mm drill bit and set it going again. A hole will be drilled into every connection point.

The main problem with this bodge is that it is very difficult to do different hole sizes. I only really use this technique for doing prototypes, hence I drill everything with 0.8mm and then expand any holes if needed.

Finished PCB!

I finally managed to get a really nice PCB milled and drilled. I would only use this process for doing prototypes and testing one or two PCBs, but its nice to get it working and the end result is really pretty good and quick with no nasty chemicals to deal with. (And yes I know that I cut it badly at an angle with my PCB shears….)

Don’t let this nice photo fool you – getting to this stage took ages and there were lots of incorrect and badly milled boards along the way….

Future work

The work flow shown above is totally ridiculous – using four different programs plus a bodge just to get a single sided PCB milled!

I would love to work on or find an add-on to the KiCAD package which will produce G-code to mill circuit boards, along with autolevelling. One day I’ll find the time (and develop the skills) to do this…..

Useful resources

One response to “Milling PCBs on a CNC

Leave a Reply

Your email address will not be published. Required fields are marked *