To the Home Page

Manufacturing PCBs at home

Published on February 4, 2024 · Reading time: 12 minutes

I have bought a CNC router to iterate on electronic designs even faster. Here’s what hardware and software I use, and how to mill, drill and cut the board at home.

Table of Contents

Opinion on PCB fabs

This is a viable solution, but only if you are 100% sure your design is what you want. You get all the basic features, such as a soldermask, silkscreen, plated vias and multiple layers. The price is so low that there is no risk of taxes related to the value of the package. PCB fabs can also offer extra services, such as an extended validity check or board assembly.

However, milling at home is better if you can’t wait more than a week for delivery of the board, but at the same time, you are unable to test the circuit on the breadboard. Shipping options from China are limited (no delivery to parcel lockers), and usually you have to order at least 5 boards you may find no purpose for.

My workflow

I was looking for a simple, portable CNC device with basic safety features such as a height probe (Z probe) and limit switches. I have found a used SainSmart 3018-PROVer V2 online. The router had a scratched spoilboard and the package was incomplete (missing a single clamp and the entire set of V-bits). But hey, the entire thing was inexpensive, and it works fine so far, despite some skill issues on my end.

The downside of the device is the noise it emits. If you set the spindle speed to more than 1000 RPM, the vibrations become so annoying that you have to do some soundproofing – I’ve made a simple enclosure out of a sheet of OSB and some drywall. I’m also slightly disappointed with the Z axis precision. The Y axis guiderails and the spindle mount do bend a little, and all inaccuracies add up.

SainSmart 3018-PROVer V2
SainSmart 3018-PROVer V2

KiCad is my PCB design software of choice. It’s open source, and it works on Linux1 (available on Flathub). It might be annoying to use, but it’s better than paying for EAGLE Fusion 360 or trying to work around the limitations of its non-commercial version. It’s easy to create new symbols and footprints, but usually you’ll find them in the built-in library or online. There are 3D models for select components as well. Finally, you can run batch tasks using the Python scripting console.

People often recommend FlatCAM to postprocess Gerber/Excellon files, and Candle to control the router. These are, in my honest opinion, the worst choices. FlatCAM has an overcomplicated user interface that is changed on every single release, so all video tutorials become useless in no time. Parameter options conflict with each other, and there are no basic optimization features such as milling path reduction or a reasonable drilling order. Candle’s UI does not fit on a smaller screen, auto-leveling seems not to work properly, and you can’t run this app on modern Linux distros even in Xorg mode. I’ve used a Windows virtual machine to work around these issues, but eventually, I switched to pcb2gcode and Universal Gcode Sender (UGS).

KiCad PCB parameters

Initially, I wasn’t using fine V-bits because I was afraid that they would break or wear down easily, but I was wrong.

I prefer to mill PCBs with a 0.2 mm 45° V-bit (which is effectively 0.4 mm for the Z depth of 0.3 mm), cut the outline with a 1.5 mm bit, and drill holes with a 1 mm drill. I also have 1.5 mm, 2.5 mm and 3.175 mm drills for large components, mounting holes and slots.

You should set up board parameters in the File > Board Setup > Design Rules menu right after you create a new KiCad project. These values work for me, but I haven’t tried using any SOIC or QFP components in my designs yet.

KiCad Design rules
KiCad Design rules

Observe that 0.77 + 0.5 = 1.27 and 2.04 + 0.5 = 2.54. Common pin headers have 0.1 inch (2.54 mm) spacing. The values I’ve picked make routing easier, but having parallel routes close to each other may be a bad idea for analog signals.

It’s a good practice to set an unused area as a ground zone. It’s quite difficult in this case because there is only one layer, the bottom one. However, you can draw on the top layer to pass the DRC (design rules check), then add jumper wires where it is necessary. Make sure removing islands (unconnected ground planes) is disabled.

KiCad Copper zone properties
KiCad Copper zone properties

Finally, it may be useful to enlarge pads to make the soldering easier. It is possible to change their size and shape. The maximum valid area is 2.04 mm × 2.04 mm (based on the design rules), but rectangular pads are difficult to work with due to the lack of a soldermask. I would recommend having these oval or circular.

Both changes can be performed manually, but you can use the scripting console (KiPython) as well. This is an experimental feature, so remember to save your changes before running any code, or you may lose progress when KiCad crashes. Open the console, copy the following code, and paste it with Ctrl+Shift+V.

This code was tested with KiCad 7.0.8.

import pcbnew

board = pcbnew.GetBoard()

for p in board.GetPads():
    if p.GetNetname():  # pads without name are just holes
        p.SetSize(pcbnew.VECTOR2I(2040000, 2040000))  # nanometers
        p.SetDrillSize(pcbnew.VECTOR2I(1000000, 1000000))
        p.SetShape(2)  # OVAL

pcbnew.Refresh()

KiCad export

The default (0, 0) point is in the top-left corner of the PCB drawing sheet. It has to be moved to the top-left corner of the actual PCB, or you will run into issues after you send the job to the router. Select the Place origin point and Set the grid origin point (long press) options from the toolbar, and use them to match the result shown in the picture below.

KiCad Origin points
KiCad Origin points

Gerber files (for milling and cutting) and Excellon files (for drilling) can be exported from the File > Plot menu. Make sure the option to respect custom drill/place origins is enabled and that both PTH and NPTH holes are exported to a single file. If you have oval mounting holes in your PCB design, select alternate drill mode, otherwise such holes will be ignored when generating G-code.

Postprocessing

I’ve already mentioned that pcb2gcode is the software I use to further process the KiCad’s output. You can update G-code files with just a single command once you adjust the parameters to match your router’s capabilities.

First, the program itself needs to be compiled from source. I’m using the Docker container to install the dependencies, based on the following Dockerfile. There is an extra user with UID 1000 created to avoid the permission issues. Furthermore, I remove the output SVG files as soon as they are generated (I couldn’t find the option to disable this behavior).

FROM debian:11

RUN apt-get update \
    && apt-get install build-essential automake autoconf autoconf-archive libtool libboost-program-options-dev libgtkmm-2.4-dev gerbv git librsvg2-dev -y

RUN git clone https://github.com/pcb2gcode/pcb2gcode.git \
    && cd pcb2gcode \
    && git checkout v2.5.0 \
    && autoreconf -fvi \
    && ./configure \
    && make \
    && make install

RUN useradd -u 1000 user
USER user

WORKDIR /output
ENTRYPOINT bash -c "pcb2gcode && rm *.svg"

Recent versions of Docker have the Compose plugin built in. Create a compose.yml file and paste this YAML there:

services:
  main:
    build: .
    image: pcb2gcode:2.5.0
    volumes:
      - ./project/output:/output

Now you can run the pcb2gcode with docker compose up.

However, it won’t do anything useful without the millproject configuration file. There are some non-standard settings enabled, such as disabling unsupported commands (nog64, nog81, nom6), reducing milling distance and removing empty areas (voronoi), and adding supports when cutting the outline (bridges). I’ve tried to make the entire manufacturing process as non-disruptive as possible. Consult the program’s documentation for more information and suggestions.

# general

metric = true
metricoutput = true
nog64 = true
nom6 = true
zchange = 2.0mm
zsafe = 2.0mm

back = [project name]-B_Cu.gbr
back-output = [project name]-B_Cu.gbr.ngc
drill = [project name].drl
drill-output = [project name].drl.ngc
outline = [project name]-Edge_Cuts.gbr
outline-output = [project name]-Edge_Cuts.gbr.ngc

# milling

mill-diameters = 0.4mm
mill-feed = 200mm/min
mill-infeed = 0.15mm
mill-speed = 1500rpm
voronoi = true
zwork = -0.3mm

# drilling

drill-feed = 50mm/min
drill-side = back
drill-speed = 1500rpm
drills-available = 1.0mm, 1.5mm, 2.5mm
nog81 = true
zdrill = -[board thickness + 0.4]mm

# cutting

cut-feed = 100mm/min
cut-infeed = 0.15mm
cut-side = back
cut-speed = 1500rpm
cutter-diameter = 1.5mm
zcut = -[board thickness + 0.2]mm
bridges = 1.0mm
bridgesnum = 4
zbridges = -[zcut / 2]mm

You can inspect .ngc files using G-code utilities that run in your browser. NCViewer can simulate the spindle movement for the entire job and show the progress being made. Another G-code simulator can be used to get the approximate job time.

NCViewer simulation in progress
NCViewer simulation in progress

Milling the PCB

WARNING: I take no responsibility for any damage to your health or property.

It’s time to finally turn the copper-clad laminate into a useful circuit board. Secure it onto the spoilboard and make sure that the spindle would be able to move freely in the Z axis. Use double-sided tape for larger or thinner boards if necessary. Insert the milling bit and then attach the Z probe (you may have to modify it and add the second alligator clip).

Open the Universal Gcode Sender (UGS), connect to the router, and move the spindle to the top-right corner of the usable area. Lower the spindle until the probe’s LED turns red. Click Reset Zero and then Return to Zero to make sure the router is calibrated.

UGS Autoleveler
UGS Autoleveler

Auto-leveling: Load the bottom layer file and open the AutoLeveler plugin (Window > Plugins > AutoLeveler). Click Use loaded file to resize the height map. Change the Z axis range to -2 mm to 2 mm. The resolution of about 10 mm should be fine. Start the AutoLeveler and wait for the scanning to be completed. Save the height map for later, it will be useful if UGS crashes. Disconnect the height probe and click Return to Zero. Do not run AutoLeveler again once you have milled the board, it will not work as expected because copper areas are not connected anymore.

Milling: Make sure bottom layer file is loaded and the AutoLeveler’s Apply to Gcode option is checked. Click Send (the “play” button). The spindle will move up a little. Click Send again to actually start the milling. Return to Zero once the job is done. Inspect the board; it may be necessary to run the process again if some tracks look incomplete.

Cutting: Press the emergency button, raise the spindle, insert a new bit, lower the spindle to make it barely touch the board, then release (rotate) the button. Open the outline file and repeat the steps above.

Drilling: Repeat the steps above.

Base material before CNC milling
Base material before CNC milling

CNC milling
CNC milling

At last, detach the PCB from the spoilboard. Use some coarse sandpaper to finish the edges of the PCB if necessary, expand the mounting holes using a power drill, and install all electronic components according to the schematic. Use extra flux to make soldering easier.

Finished and cleaned board
Finished and cleaned board

Soldermask

There are two methods of applying a UV-curable soldermask to the PCB. First, you can add a few drops of such resin onto the finished board, use a thin film and a sharpie pen to mask solder points, expose the resin, and then remove the remaining resin with isopropyl alcohol. This may be the more annoying option because you have to guess how long the board has to be exposed, and the uncured soldermask will get stuck in drilled holes.

Another approach is to drip some resin onto the board right after milling, then remove it mechanically. This option has some disadvantages when paired with my CNC router. The copper layer is really thin, and you will either damage PCB traces or skip some solder points. A 10×10 cm board is far too large. It may work fine for smaller PCBs, though.

I think that it’s not worth it. I’ve spent some time and money, and it didn’t work well. If you really need a soldermask, then make a prototype, test it, and then order PCBs online. Remember that a soldermask is not an insulation layer, and it is only used to make soldering easier and to protect the board from oxidation.

Random tips and tricks

Start with some smaller designs if this is your first time milling PCBs. Make sure to understand basic concepts, such as homing the device. If you consider yourself more experienced, remember that mistakes may happen anyway.

Be aware that your CNC router is a relatively cheap device, and it may be counterintuitive in use. Try out various feed rates and spindle speeds and find the best values for reduced noise and vibrations and prolonged device life. My CNC router’s spindle can reach crazy fast speeds, but in practice, 1500 RPM is good enough.

Try out multiple base materials and understand the difference between them. I prefer the 1.6mm phenolic paper laminate, which I believe is also known as FR1/FR2. It’s super easy to cut due to its soft structure similar to the prototyping perfboard. Another good candidate is a 1.0mm FR4 board, which should be more durable but is harder to work with.

Learn the basics of G-code. It’s possible to move the spindle manually with a knob, but the router will not know about this change. If you run G-code commands, then the internal device state is updated, and UGS actions such as Return to Zero can work properly. You should know how G0, G90/G91 and M3/M4/M5 can be used, and how to modify .ngc files to skip commands. I have found good G-code documentation online to help you get started.


  1. It works, but there are huge performance issues on Wayland if there is at least one maximized KiCad window. You need to open the dconf-editor, set org.gnome.mutter.auto-maximize to false, and then manually resize KiCad windows to the maximum possible size. ↩︎

Check out other blog posts: