Introduction: How to Build a Custom "bed of Nails" Tester for Your 3D Printer Electronics

If you are manufacturing a lot of PCBs (Printed Circuit Board), you will want to have some sort of QA to ensure your boards are functional and without defects. The most popular method for in-circuit testing is the use of a bed of nails fixture. There are many very expensive solutions to that but you will learn here how to create your own bed of nails tester by simply using a 3D printer.

I've written a lot of the design and software needed for testing boards and it's all open source, so you might as well take advantage of that and use it (bur respect the license please, if you modify it, release your modifications!).

Step 1: The RAMBo Controller

In this Instructable, I will go over how to build a test jig for a RAMBo 3D printer controller as an example of how to design a test jig for your electronics. I will however also concentrate on how to adapt my design for your own boards.

I have built two test jigs, one for the RAMBo 1.1B and later, one for the RAMBo 1.3L controllers. The second design is slightly different from the first, with just a few improvements that makes it easier to build and to use but I will show images/videos mostly of my RAMBo 1.1B design since that's the one I have the most documentation for.

Obviously, while this instructable will focus on the design for the RAMBo, you must take into account that each PCB will be different and will therefore have different requirements. In our case, if you're building a 3D printer or CNC controller, most of the things here will fit, otherwise, you'll have to adapt. For example, you may not need to use motors for testing your stepper drivers, or you may need different resistor values for testing your power rails.

Step 2: Gather Your Materials

First things first, you need to have some materials on hand before you can build a PCB tester :

  1. A PCB to test (obviously)
  2. A 3D printer for your test jig's 3D printed parts. (another obvious requirement!)
  3. A 3D model of your PCB
  4. Some pogo pins and receptacles
  5. Thermoplastic heatset insert (optional)
  6. A test controller
  7. Epoxy glue
  8. A wooden plate for your fixture (I used a 300x300 mm plate)
  9. Female-Female Jumper wires
  10. Stackable (for arduino shields) headers (optional)
  11. Threaded rods
  12. Some basic nuts & bolts to hold everything down
  13. Any extra hardware needed for testing your specific board (motors, resistors, copper board, etc..)

If you are building a test jig for a RAMBo, then the following extra items are needed :

  1. 100 KOhm resistors x4
  2. 4.7 KOhm resistors x3
  3. 47 KOhm resistors x3
  4. NEMA17 motors x5
  5. Optical endstops x5
  6. A small proto board
  7. Male headers
  8. A power supply

The first non-obvious item on the list is a 3D model of your PCB. You will need to generate an OpenSCAD model of the PCB you want to test. There is an EagleToOpenScad ULP (Written by Steve Kelly) for Eagle which is very useful in this case, simply run the ULP, select (20) Dimension, Extrude, Regular holes, Board Only and Skip vias then generate the scad model of the PCB (see images above). Other PCB design software may have a similar script, or you may be able to export your files into the Eagle format.

You will also need some pogo pins and their receptacles, I have used two types of pogo-pins, the P100-A2 and P100-F1, as well as the R100-2W receptacle. The P100-A2 has a flat head (concave) for making contact with soldered pins from beneath the board, while the P100-F1 has a pointy (convex) tip for poking into unsoldered pins or vias.

I have also used heatset inserts 94180A331 from McMaster-Carr for M3 bolts which I've found very useful for holding down pieces of printed parts together. The heatset inserts are not necessary though, as you can still use a standard nut&bolt system, but I prefer to use the heatsets as they are very practical and reliable.

Finally, you will need a controller. Basically what this means is that you will need some kind of electronic controller with plenty of GPIO in order to peek and poke at the various connections you will make with the DUT (Device Under Test). This can be anything from an arduino to a beaglebone or your own board used as a controller. In the case of our RAMBo test jig example, I've used a second RAMBo board to act as a controller since it has plenty of expansion ports.

Watch the video above for an overview of all the items needed to build the RAMBo 1.1B test jig. Note the blank PCB listed there is not required anymore with my updated design, but it can still be useful for making everything cleaner and easier to connect with the controller (if using the same board as the controller). Also note the use of ICSP programmers, motors and opto-endstops that are extras specific to testing the RAMBo, you may not need them for your PCB if it doesn't require them. Also, the ICSP programmers are not needed for RAMBo unless you need to test blank boards, fresh out of the assembly line.

Step 3: Create Your 3D Printed Parts

There are quite a few items that need to be printed, and it will take some time, so you can start printing some while you prepare your 3D model of your PCB.

Source SCAD code for all 3D printed parts (with pre-generated STL files) are available on my github here :

The README file has been updated to explain what each file is for and how everything fits together, but we'll go into more details about that here.

So, first, start by printing the following items :

  1. foot.stl x 5
  2. clamp.stl x 2
  3. controller_spacer.stl x 4 (if you have 4 holes for threaded rods)
  4. bottom_spacer.stl x 8 (if you have 4 holes for threaded rods)
  5. board_spacer.stl x 4 (if you have 4 holes for threaded rods)
  6. motor_mount.stl x 5 (If you need to test 5 motors)
  7. opto_mount.stl x 5 (If you need to test 5 motors)
  8. simple_encoder.stl x 5 (If you need to test 5 motors)

If you are building a RAMBo test jig, then you would also want to print the following :

  1. RAMBo_bottom_plate.stl x1
  2. RAMBo_middle_plate.stl x1
  3. RAMBo_top_plate.stl x1

The pogo pins will need a stable 'rail' system in order to be aligned with your board. For that purpose we're going to be printing 2 copies of your PCB, a bottom one that will hold the receptacle's base, and a middle one that will hold the top of the receptacle.

By connecting those two plastic boards with a constant sized spacer, you can then insert your pogo pins and they will be perfectly aligned with the vias on your PCB and make contact with your board once it's inserted. We will also need a top board for pressing down on your PCB to make a good contact with the pogo pins. You can also put on the top board, pogo pins for any test points on the PCB. However, I would suggest any test point that needs to be tested to be made into a small via so it can be poked by the pogo pins from the bottom of the PCB as it will make everything much easier.

If you are building a RAMBo test jig, you can continue to the next step, otherwise, keep on reading..

While all the common parts are printing, what you need to do is to create a 3D design of your PCB. Use the EagleToOpenSCAD ULP script mentioned earlier to create your board.scad file.

Then, you should have a look at the RAMBo.scad file, then edit your generated board.scad (from the previous step) and make it look somewhat similar. For instance, add the include at the top of the file, then change the module definition to add the board_thickness and hole_size as function arguments. You should then change the holes for the threaded rods into using the poly_circle function and set the radius to M3_RADIUS (or a different value depending on what type of mounting holes you use). Also, change every circle into a call to :

hole (r=hole_size); 

The hole function is defined in common.scad and it will just create a square, you can also edit it to use circles or poly_circles, but I've found square holes to be much more effective for holding the pogo pins with a good hole size accuracy when 3D printed.

You will notice that I have grouped each hole types in different sections, for example :

	if (ENDSTOP_YMAX) {<br>		translate([13.335,36.830])hole(r=hole_size); 

You don't really need to do that, but I thought it was practical to separate each hole that way, it also allowed me to enable/disable holes and print boards with only the required holes in order to speed up the printing process. What you may want to do however is to group the holes by vias versus test points in case you have test points that you want to poke from above the board.

Once you have adapted your board.scad, then open RAMBo_bottom_plate.scad and save it to a board_bottom_plate.scad and replace the call to the RAMBo function into a call to your board's function name. Basically what this file does is to print a union of two boards, the first one 1 mm thick with 1.5x1.5 mm holes and the other 4.5 mm thick with 1.65x1.65 mm holes. The smaller 1.5 mm holes will be just large enough to let the pogo pin's receptacle pin go through but will not let the larger portion of the receptacle through while the 1.65 mm holes will be just large enough for the larger portion of the receptacle. With this combination of 1.5 mm and 1.65 mm holes, the pogo pin receptacle will be a tight fit into the holes.

You should then edit the RAMBo_middle_plate.scad file, save it to a new file and change the call to RAMBo into a call to your board's name.

I have set the hole size for the middle_plate to 1.65x1.65 mm holes which is not large enough to hold the top of the receptacles, but it was enough to drive a drill through and a 5/64" drill makes it the perfect size. You can probably modify it to use 1.95 or 2.0 mm holes and see if the receptacle fits, that's what the calibration_test.scad file is for, to test various sizes (the accuracy and calibration of each printer is different, so it might be needed to do that calibration first anyway).

Once you are done, you should modify the RAMBo_top_plate.scad to fit your board. This file does a few things, first, it will print a board with everything disabled (now would be the time to disable bottom holes and enable top holes for test points), it adds a hole for the capacitor and the fuse holder which stick out of the RAMBo, you may need to remove those and add different holes somewhere for your board if it also has huge capacitors. It adds bigger holes for the M3 threaded rods because the top plate will be inserted and removed quite often so bigger holes are required here. If you use pogo pins on the top board though, you may want to keep the M3 holes the same size as they should be, but it will be slightly harder to push it through the threaded rods. It also adds spacers to push down on the board, and a protrusion for the clamping system. It will add M3 holes for the clamps and a nut hole, or if you enabled the use of heatset inserts for the clamp (enabled by default in configuration.scad), it will only add holes for the M3 heatset insert.

Once you're done with all the modifications for your board, generate the STL file from the scad and print those parts as well.

Step 4: Preparing the 3D Printed Parts

The next step is to prepare the 3D printed parts for use with the test jig. The top board for example will need to have heat-set inserts installed into it. The same applies for the motor mount and opto-endstop mount. If you don't want to use heat-set inserts, you can disable it in the configuration.scad file and regenerate the STL, then you can use bolts and nuts to secure all those components.

The middle board will also need to be prepared by drilling into it with a 5/64" drill. I actually think the hole size should be modified in the scad to 2.0 mm before printing, but I haven't tested it personally. It will mainly depend on how good your X/Y/E steps calibration of your printer is.

Note that contrary to what the above video says, you should not drill the bottom board as that design has changed and the bottom board has two sizes for the hole depending on the layer height, it is better to use tweezers to make sure the bottom hole is large enough to fit the pogo pin.

Step 5: Drill the Wood Plate

The next step will be to drill the wood for our 3d printed pieces. The video above shows a summary of my first design, but it has significantly changed since.

The first picture shows the completed redesign and you will notice a few differences. Here are the differences to note when doing the drilling :

  1. The controller sits under the test jig tower
  2. There are 5 feet instead of 4, with one right in the center of the plate
  3. The motors are now oriented towards the inside of the plate
  4. The motors are now on each side of the plate, instead of being in an 'L' formation
  5. There are holes below the endstop mount and directly below the center of the motor mount for wiring beneath the plate
  6. There are holes on one side of the jig tower for the endstop/motor wires to go through

First of all, the controller and the test jig tower are in the same spot, so you don't need to make space for two boards in the plate, and it will make it cleaner and less of a mess with wires.

Having the motors pointing towards the center makes it easier to ship the test jig as it will be more robust, it also helps for the wiring of the endstops. As you can see the motor and endstop wiring goes below the plate to give us a cleaner look.

I would suggest leaving the drilling of the area for the proto board until the end after everything was placed on the plate.

Step 6: Preparing the Proto Board

The proto board will contain anything extra that you might need and will have the voltage divider for testing the power rails voltages. In the case of your own board, you will have to figure out what needs to go in there, but in the case of our RAMBo test jig, here's what we're doing :

A voltage divider for the Heat-bed, Heater and 5V power supply rails using a 4.7KOhm and a 47KOhm resistor, so we will have solder a 3x1 male header, then three 4.7KOhm resistors, another 3x1 male header, three 47KOhm resistors then another 3x1 male header, as you can see in the video above.

For the thermistor readings, we will use four 100KOhm resistors and connect them between the pins of the thermistors, so we'll use a 4x1 male header, four 100KOhm resitors and another 4x1 male header.

In the new test jig design, I have also soldered on the proto board a USB cable with male headers so I could connect the USB directly to the board through the test ig.

Step 7: Assembling the Test Jig Tower

The next step is to assemble the test jig's tower, by inserting the threaded rods in the mounting holes of our printed boards with the board spacer in between. Make sure the boards are oriented correctly, top face up and aligned. Then secure using nuts to make sure it's tight.

You will then insert the pogo pin receptacle into the holes until all the receptacles are inserted and have the same height as they need to be aligned.

Note that in the video, I am showing a blank PCB at the bottom of the assembly, however, the new bottom_board design will have a larger and a smaller hole that will hold the receptacles in place without the need for a PCB. This is mostly useful in case you want to poke on small vias or test points and the pogo pin won't fit in the bare PCB (in the case of the RAMBo, the vias for the stepper's VRef is indeed too small so it wouldn't fit a pogo pin, that's why I had to remove the PCB from there).

Once all the receptacles are inserted, insert the pogo pins into them. Note that if you need to poke at a soldered pin, use the flat head pogo pins, if you need to poke at an unsoldered pin or a small via, use the pogo pins with a pointy head.

Once done, insert your DUT on top of the assembly and make sure all the pogo pins are correctly aligned and touching the board.

You can then use epoxy glue to make sure the pogo pins are secured in place and won't fall out of the assembly. Use epoxy glue on the top of the bottom board, not on the middle board because once dry, the epoxy glue will most certainly prevent the pogo pins from moving, and you wouldn't want to do it on the bottom as it can prevent good contact with the wires later on.

Step 8: Installing the Motors

The remaining item here is the motors. In our case with the RAMBo, we want to test the stepper drivers, so we need to test it with an encoding flag on motors and an optical endstop.

We just need to install the motors on the wooden plate using the motor mount. Screw the opto mount onto the motor and install the optical endstop on it. We then insert the simple_encoder flag in the motor's shaft and hold it with a M3 set-screw or with a M3x5 screw.

We insert the wires into the holes in the wooden plate and take them out from the side of the jig tower's location where the motors will connect.

You can see how the wiring looks from below the wooden plate in the image above.

Step 9: Preparing the Controller

Depending on your choice of controller, you may need to do some preparation. If you're using an arduino, beaglebone or other type of controller with direct GPIO, then you probably won't need to do anything. If however you're using your own board as a controller, like is our case here with the RAMBo, then you may need to do some preparation.

We will be using the T0, T1, T2 and T3 I/O for analog input on the controller but those are tainted by resistors and capacitors on the board's circuitry, so we need to desolder those to make sure the reported analog value is the exact value we want it to be.

We'll also need to use the analog-Ext expansion board, so we'll solder a header to it.

You will then need to upload the testing firmware to your controller. Make sure you keep the hex file of the compiled test jig firmware as you will need it also for testing the DUT devices.

Step 10: Connecting the Wires

Possibly the hardest task here is to connect every cable correctly without making any mess.

A big help will be the source code of the RAMBo-Uploader software which was made to be easily adaptable for other boards and provides a pin mapping class :

<p>class RAMBoPinMapping(TestPinMapping):<br>    """ The pin mappings for the various RAMBo functions """</p><p>    # [X-min, Y-min, Z-min, X-max, Y-max, Z-max]
    EndstopTargetPins = [12, 11, 10, 24, 23, 30]
    # [EXT2-10, EXT2-12, EXT2-14, EXT2-16, EXT2-18, EXT2-20 ]
    EndstopControllerPins = [83, 82, 81, 80, 79, 78]
    EndstopNames = ["X-Min", "Y-Min", "Z-Min", "X-Max", "Y-Max", "Z-Max"]</p><p>    # [Bed, Fan2, Fan1, Heat1, Fan0, Heat0]
    MosfetTargetPins = [3, 2, 6, 7, 8, 9]
    # [MX1-5, MX1-4, MX2-5, MX2-4, MX3-5, MX3-4]
    MosfetControllerPins = [44, 32, 45, 31, 46, 30]
    MosfetNames = ["Heat-Bed","Fan-2","Fan-1","Heat-1","Fan-0","Heat-0"]</p><p>    # For stepper's trigger/monitor pins, we use existing mosfet connections
    # The steppers's opto-endstops are however monitored by the firmware
    # using bits [2..6] of Arduino PORTJ which is :
    # [EXT2-9, EXT2-11, EXT2-15, EXT2-17, EXT2-19]
    StepperTriggerPin = MosfetTargetPins[0] # = 3 = Bed
    StepperMonitorPin = MosfetControllerPins[0] # = 44 = MX1-5</p><p>    # [Analog-EXT-8, Analog-EXT-6, Analog-EXT-5, Analog-EXT-4, Analog-EXT-3]
    VRefPins = [8, 6, 5, 4, 3]
    AxisNames = ["X","Y","Z","E0","E1"]</p><p>    # [T0, T1, T2, T3]
    ThermistorPins = [0, 1, 2, 7];
    ThermistorNames = ["T0","T1","T2","T3"]</p><p>    # [T3, T2, T0]
    PowerRailPins = [7, 2, 0]</p><p>    PowerRailNames = ["Extruder rail", "Bed rail", "5V rail"]</p><p>    # [I2C_SDA, I2C_SCL]
    I2CTargetPins = [20, 21]</p><p>    I2CControllerPins = [20, 21]</p><p>    # [SPI_SCK, SPI_SS, SPI_MISO, SPI_MOSI]
    SPITargetPins = [52, 53, 50, 51]
    SPIControllerPins = [52, 53, 50, 51]</p><p>    # Bed on controller
    PowerPin = 3</p>

You can simply match the pins from the target (the DUT) to the controller to connect everything. There are a couple of things to note though :

  • The motors will connect directly to the DUT via the pogo pins
  • The opto-endstops will need to connect the VCC and GND pins to any available pins on the controller
  • The Ground from the power supply must connect to the controller as well as the target (both target and controller must share the ground)
  • The thermistors from the target will connect to the proto board between the pins of the 100KOhm resistors
  • The power rail pins actually refer to the power after going through the voltage divider on the proto board (otherwise, you will fry the controller).
  • The power supply must be an ATX style power supply and its PS-ON wire (the green wire) must connect to the PowerPin on the controller (The ground pin on the bed mosfet) so the controller can turn on and off the power supply when needed.

Once you connect everything, you are ready for a test run.

Step 11: Assembling Everything

Once everything is connected and installed on the wooden plate, your test jig is ready.

I have however found it very useful to connect the wires on a blank PCB using stackable arduino headers which you can then easily insert on your controller.

Have a look at the video above that shows an overview of the test jig for RAMBo 1.3L and how to connect everything when using the bare PCBs.

Step 12: Testing Your Board

Once you connect everything, all that remains is to test your board. You will need the RAMBo-Uploader software from my github repository (make sure you checkout the 'v2' branch).

The script is the main executable for testing a RAMBo board.

You can run the tester with :

sudo ./ <br>

You can also give it specific tests to run as arguments, for example :

sudo ./ "Motors 1/16 Step" "Supply Voltages" "Mosfets Low" 

If you need to disable a test, you can do it programmaticaly in by editing the file and adding a line similar to :

r.GetTest("Program Vendor Firmware").enabled = False

There is also a lot of configuration options you can play with in the config/ file.

Step 13: Tester Software

If you are not using a RAMBo board however, you will need to adapt the software to your board. The software was recently rewritten from scratch to allow an easy port into other boards. Basic understanding of Python programming is required though.

The new design allows for a more modular approach where each test is represented by a unique object and each test type is implemented in its own class. A few base classes are available to take care of the basic functionality of the test cases, logging mechanism and testing logic.

The tests/ module contains base classes and testing modules. The base classes are :

  • TestCase: This is the base class for a test case implemented to test a specific feature
  • TestContext: This is the base class for the context given to the tests while they are running
  • TestRunner: This is the base class of the runner class that will run every test in the test suite.

There are a few generic tests, such as the TestGPIO in and TestAnalog which themselves have more specific test classes that derive from them such as TestMosfets and TestEndstops that derive from TestGPIO and TestThermistors, TestVRefs and TestSupplyRails that derive from TestAnalog. There are also some specific tests such as the ProgramFirmware which is a test wrapper for avrdude and TestMotors that will test stepper motors and verify they all work correctly.

To implement a new test, you must derive from the TestCase class, and override the _test and _verify methods. The _test method needs to execute the test using whatever is available in the context object (more on that later) while the _verify method needs to set the 'self.status' to a value in the TestStatus enum.

The context object that gets passed to the _test and _verify methods is an object that derives from TestContext, its purpose is to pass around information between the tests that can be used by the test cases. For example, the interface to access the DUT is available in while the controller is available as context.controller. Also, most tests will require access to specific fields from the object context.pinmapping.

You will need to derive a class from TestContext in order to provide the required information to the test cases. Most specifically, in your derived class, you will need to instantiate a self.pinmapping object. If you need to power on/off the power supply before and after a test run for example, you can do it by overriding the TestingStarted and TestingEnded methods.

Finally, you would need to implement a new class for your board that derives from TestRunner. This one is simple, you simply need to define a self.tests variable containing a list of TestCase objects and return the context you created through the context property.

You can then create your TestRunner object and call the Run method to run all the tests, or you can give it a list of specific tests to run if you wish.

There is a lot more that can be said about the software, such as fatal, required, _finally test cases, or how to configure the logging system to use a database, or how to get the summary of the tests execution, or to disable specific tests, what each test status means, etc.. However, the source code is fairly simple and well documented and you can discover a lot of things by reading the code.

I'm leaving you with a mockup of a potential GUI for the test software in which the GUI can be generated dynamically from any TestRunner instance and which gives control of each test to the user.

Step 14: Conclusion

There are so many electronics options for 3D printers these days and for each one, many more clones. Unfortunately, the majority of electronics people buy are barely tested and will often break easily due to missing QA. Hopefully this project will make it easier for electronics manufacturers, resellers or printer manufacturers to test their electronics before sending them to the costumer, thus improving the reliability of their boards, reputation of their brand, and satisfaction of their customers.

Testing a board takes about 50 seconds of which over 40 seconds are for uploading the firmware and verifying the upload. It only takes a few seconds to switch boards, so this is a very fast and useful method, especially when the manufacturer already needs to upload their own firmware to the board, add 30 seconds per board and you make sure there are no defects that can cause the customer some frustration and you avoid returns.

Overall, I have spent close to 2 months working on the design of this machine as well as software development. There were many iterations and many failed attempts. I hope that the wider community can benefit from my efforts and embrace the founding principle behind open source which is to stop reinventing the wheel and reuse the work of others and improve upon it.

Protected Contest

Participated in the
Protected Contest

3D Design Contest

Participated in the
3D Design Contest

Formlabs Contest

Participated in the
Formlabs Contest