Skip to content

Electronics Production

Group assignment:

Characterize the design rules for your in-house PCB production process: document feeds, speeds, plunge rate, depth of cut (traces and outline) and tooling.

Document the workflow for sending a PCB to a boardhouse
Document your work to the group work page and reflect on your individual page what you learned
Individual assignment:

Make and test a microcontroller development board that you designed

Group Assignment

It was illuminating to see the how the Roland mill handed the different electronic designs with the Mods toolpath workflow. For the board I made, I switched to VCarve as it was more intuitive and I felt more confident in getting the dimensions right. I did use the feeds and speeds for the machine to inform what I did for my own board.

Here is a link to the group assignment page.

Link to group assigment page

Make and Test a Development Board

I made and tested the dev board that I designed in week 6. This board has hexagonal design with onboard button, LED, and addressable LED and connectors for analog, digital, and I2C periferals.

PCB that was made for this assignment

The board was made by exporting the files, creating the toolpath and machining it on the Roland. Then the board was tested by creating a sample program with the Arduino IDE to exercise the onboard and plug-in peripherals.

Exporting the Design

The first step was to export the design files. Since I have a 2 sided board I started with the rear copper of the board. I opened the files in KiCAD and selected File>Fabrication Outputs>Gerbers. This brought up a new menu with options for exporting the files. I changed the "Plot Format" from Gerber to DXF. Then selected the rear copper and Edge.cuts layers in both the menus to Include Layers and Plot on All Layers. The plot on all layers is so that the lines for both layers will output into one file as this will be key to helping to line up the traces to the outside geometry later. Then I pressed the "Generate Drill Files" button to create a dxf of the drill centers. Then I pressed the "Plot" button to create the dxf of the copper layer and edge cuts. Once these files were exported, I did the same process to export a file with the front copper and the edge cuts.

Exporting drill dxf

Selecting options for exporting the files.

Creating the Tool Path

The strategy for creating the tool path was to cut the holes first, then mill the bottom copper traces before flipping the board milling the top copper traces.

I then used VCarve software to create the toolpaths. I opened a new file in VCarve and updated the stock materal to the size of the PCB stock that we have in the lab. Then I imported the dxf of the bottom copper and edge cuts into the workspace. Then I added the drill map file into the workspace. This is when I realized that the drill file was not useful. Instead of exporting all of the thru holes that I expected, it only had the data for the 4 holes in the corners and the outside shape of the board.

Importing the dxf of the bottom copper layer.

Result of the export of the drill map that was not useful.

Then I had to use another technique to get the holes. I went back into Solidworks and created a new sketch. I used the 3-point circle drawing tool on the perimetter of each of through holes to create the hole geometry in a useable format. Then I was able to export the hole map and bring it back into VCarve.

Adding holes manually in SolidWorks.

Importing hole map back into VCarve.

Once the holes were in VCarve, I had to mirror the artwork. The bottom copper layers were exported from the top copper layer view. So I used the mirror tool in VCarve to flip the art. Then I could move forward with the tool paths.

I dragged the group of holes and aligned them to the bottom copper layer. Then I selected all of the holes and selected the hole tool in VCarve. I chose the 1/32" end mill and selected peck drilling to have the tool make short pecking strokes during the creation of the holes.

Next I created the toolpath for the traces. I selected all of the copper traces and selected the profile tool. I chose the 1/64" endmill as the tool, set the cut depth to .008" with 2 passes of .004" each, and set the feed rate to 2mm/s to be very safe on the speed.

Setting up the profile cut for the traces after mirroring the art.

Finally, I selected the outer perimetter of the board and the profile tool in VCarve to setup the cut. I chose a 1/8" endmill for this cut and had it do the full depth of .075" in 5 passes.

Once this file was done, I brought in the top copper layer into the workspace. I aligned it to the holes on the rest of the art to make sure it was properly aligned to the rest of the PCB. Then I selected the traces on the top layer and setup another profile pass with 2 passes of .004" each.

I did 2 passes on all of the traces to make sure that there was no lingering copper in the channels after the cut.

Perimetter cut path developed in VCarve.

Cutting

Now it was time to start the cutting. I got a new piece of FR-1 PCB material, cleaned it with alcohol and put double sided tape down on the clean side of the board. Then I placed it onto the spoil board on the bed of the milling machine. I inserted the 1/32" endmill into the machine and secured it with the setscrew. Then I opened the Roland control software and moved the spindle to a location near the front left edge of the board about 3mm in from the edge of the board. Then I zeroed the X and Y axes at that location. Then I moved the spindle about 10mm toward the right and up to be closer to the center of the board. I unscrewed the setscrew holding the end mill and with the tip of the mill on the surface of the PCB, I re-tightened the screw and set the Z axis to zero. Then I moved the Z axis up about 10mm to keep it from dragging on the PCB.

I went back into VCarve and exported just the drill file. Then I opened the file inside the Roland software and started the cut. The machine moved to the first drilling location and did a peck drill routine before moving to each subsequent location until all of the holes were drilled in the board.

The PCB after the hole drilling operation was complete.

Then I loaded the 1/64" end mill into the machine. I left the XY zero the same, but re-zeroed the Z hight for the new end-mill in the same way I did for the first end mill. I went back to VCarve, exported the bottom copper toolpath, and then loaded it into the machine. Then the machine went and cut the profile around all of the traces.

The PCB after routing the bottom copper layer.

The next step was to cut the board out. I installed the .125" endmill into the machine and zeroed the Z axis to the top of the PCB. Then I went to the Roland software and loaded the cut file. I then started the cut. I let it run for a while but it was really loud and it was making a lot of snow. It felt like the chipload was too high. So I stopped the cut and went back into VCarve. I added two more passes and slowed the speed down to 4mm/s. Then I saved the new file and ran this on the Roland. After that it ran fine, however, it did result in tearing some of the copper arond the perimetter. In retorspect, I should have stuck with the 1/32" end mill from the group assignment.

The partial cut path when I was running too fast on the perimetter.

The finished cut path with some visable tearing on the upper surface.

I removed the PCB from mill, prying it up with a screwdriver to release it from the double-side tape. Since the edges were a bit jagged, I used a razor blade to carfully trim the edges and debur the PCB. After I did that the edges were smooth and it was ready for the next step.

Then I prepared to cut the top side copper layer. In VCarve I used the outside profile of the board and offset it by .005". Then I used the pocket function to make a .125" deep pocket in the spoil board. I used a .125" endmill with 6mm/s speed and .030" cut depths. Then I exported the file and brought it into the Roland control software. I set the Z zero point ot the top of the spoil board but did not change the XY zero point. I ran the file and created the pocket. The result was that I now had a channel with a known X/Y reference that I could place the cut board.

I put double sided tape on the back of the board and placed the PCB in the pocket. Then I exported the top copper cut files from VCarve. I installed the 1/64" bit back into the mill and zeroed it to the top of the PCB as I had before. Then I ran the file for the top traces.

The board after removing from the mill.

Assembly

Then it was time to solder up all the components to the board.

I started with the surface mount components on the top side of the board. I felt that it would be easier to get those components on before dealing with the headers. It had been a while since I tried to solder a surface mount component, so I started with the resistor on the LED. I got out a soldering iron with a fine tip and put a small dot of solder on each of the pads. Then I found the resistor and grabbed it with some tweezers. Holding it over the soldered pads, I gently heated the tip of the resistor to melt it down to the pad. Once the first side cooled, I held the top of the resistor down with the tip of the tweezers and melted the other side so it would go down flat.

Starting the assembly with the first resistor.

I did notice after this first component was placed that I probably should have run a few more perimetters around the traces to leave a little more space to solder without fear of bridging the solder to the rest of the copper and causing a short.

I then continued soldering the rest of the surface mount components. I added the LED, the button, resistors, addressable LED, and capacitor. All went down pretty well and I was able to use the zoom function on my phone to dig down deep and inspect the joints.

Finished with the surface mount components

Zooming to inspect the joints

Then I switched focus to the through hole components. Since I drilled them with the 1/32" end mill I had to open them up for the pins to fit. I found a pin vice and some small drill bits. I selected a .9mm drill bit and chased each of the holes by hand. It was a small adjustment that allowed for the .1 headers to fit into the board perfectly.

Drilling the through holes by hand.

Next I soldered the vias. I made a number of vias in the board to tie key parts of the top side of the board to the bottom where the reliable connections to the microcontroller are. I took male headers and separated individual header pins. Then I soldered them to their respective pads on the upper and lower surface to connect the traces.

Then I added the female headers for the XIAO and the 3-pin male header for the addressable LED strip extension. Before adding any more of the headers, I paused the assembly to do some testing.

Testing

LED

I started the testing by running a program to blink the green LED that I soldered to the board. I inserted the XIAO into the board, updated a simple blink code to the new pin assignments and uploaded it.

Blink
int onboard_led = 20;

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(onboard_led, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(onboard_led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(onboard_led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Unfortunately, when I uploaded the program, the board took the program, but the LED did not blink. So then I transitioned to troubleshooting mode. I inspected the board under a magnifying glass and looked at the joints. It all looked ok, but I did reflow a few that were ugly or looked close to a trace. I thought the LED might be backwards, but I had checked the datasheet before putting it on. Upon further inspection I realized the problem. I never put a via in my design to get from the bottom of the LED data pin to the top side of the board, so there was no connection to the pin. I tested this by using a dupont connector to connect the bottom of the XIAO header to the trace on the top of the board, and the LED worked! So, I carefully drilled a hole through the board near D7 which is the pin connected to my LED. Then I ran a wire through the hole and soldered one side to the bottom of the board onto the header and the other side to the anode of the LED. I turned the board back on and the green LED started to blink as per the program.

Top side of the LED fix.

Bottom side of the LED fix.

Button

Next I turned attention to the button. I used some sample code for a digital read routine and added a for loop in the setup loop for some indication. I uploaded the code and gave it a test.

Button
int pushButton = 21;
int led= 20;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);

  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);
  pinMode(led, OUTPUT);

for (int i =0; i<=2; i++){
  digitalWrite (led, HIGH);
  delay (500);
  digitalWrite (led, LOW);
  delay (500);
}

digitalWrite (led, LOW);
}

void loop() {
  // read the input pin:
  int buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  if (buttonState== 1){
    digitalWrite (led, HIGH);
  }
  else{ 
    digitalWrite (led, LOW);
      }

  delay(10);        // delay in between reads for stability
}

I opened the serial monitor to watch the variable for the button state come through. It was just a string of zeroes even when I pushed the button and the LED was not working. After the LED fix, I know immediately what was wrong. I had once again forgotten to add a via for D6 for the button input pin, so there was no connection to the button. So, I made a very similar fix. I drilled a second hole through the board with the pin vice and soldered a wire from D6 to the button. I plugged the board back in and it worked. The LED came on with the button and I could see the values changing on the serial monitor.

Addressable LED

Then I turned my attention to the addressable LED. The board was designed such that the onboard LED would be the 1st LED of a full strip with the rest of the strip being plugged into the headers to the right of the LED. I started by uploading a simple addressable LED code to cycle a few colors one pixel at a time which used the Adafruit Neopixel library.

Addressable LED Test
// NeoPixel Ring simple sketch (c) 2013 Shae Erisson
// Released under the GPLv3 license to match the rest of the
// Adafruit NeoPixel library

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN   10     // On Trinket or Gemma, suggest changing this to 1

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 5 // Popular NeoPixel ring size

// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels

void setup() {
  // These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
  // Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  // END of Trinket-specific code.

  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
}

void loop() {
  pixels.clear(); // Set all pixel colors to 'off'

  // The first NeoPixel in a strand is #0, second is 1, all the way up
  // to the count of pixels minus one.
  for(int i=0; i<NUMPIXELS; i++) { // For each pixel...

    // pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
    // Here we're using a moderately bright green color:
    pixels.setPixelColor(i, pixels.Color(255, 255, 255));
    pixels.show();   // Send the updated pixel colors to the hardware.
    delay(DELAYVAL); // Pause before next pass through loop

    pixels.setPixelColor(i, pixels.Color(255, 0, 0));
    pixels.show();   // Send the updated pixel colors to the hardware.
    delay(DELAYVAL); // Pause before next pass through loop


    pixels.setPixelColor(i, pixels.Color(0, 255, 0));
    pixels.show();   // Send the updated pixel colors to the hardware.
    delay(DELAYVAL); // Pause before next pass through loop

    pixels.setPixelColor(i, pixels.Color(0, 0, 255));
    pixels.show();   // Send the updated pixel colors to the hardware.
    delay(DELAYVAL); // Pause before next pass through loop

  }
}

As with the last two tests, the addressable LED did not work the first time either. I used my zoom function on my camera and saw that the solder joing on the ground pin looked a little bit weak. So I gently touched the pad with the tip of the soldering iron to reflow the joint. Then I plugged it back in and the on-board LED came to life. Then I plugged a short strip into the header and watched as they too came to life and displayed as aspected.

Ground joint that need to be reflowed.

The repaired LED working properly.

Full strip running a rainbow effect program showing that the full strip is working.

Finish Assembly

Once the addressable LED was tested, all of the surface mount components were vetted so I turned attention to testing the rest of the inputs. I soldered the rest of the headers onto the board, 2 analog, 2 digital, and 2 I2C.

PCB with the rest of the headers soldered on.

Analog

To test the analog pins, I uploaded an analog read program that had a serial output of the raw signal. Then I plugged the X axis of a joystick up to A1.

Analog Read
int sensorPin = 2;    // select the input pin for the potentiometer
int sensorPin2 = 3; 
int ledPin = 20;      // select the pin for the LED
int sensorValueA0 = 0;  // variable to store the value coming from the sensor
int sensorValueA1 = 0; 


void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin, OUTPUT);
  pinMode (sensorPin, INPUT);
  pinMode (sensorPin2, INPUT);

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
}

void loop() {
  // read the value from the sensor:
  sensorValueA0 = analogRead(sensorPin);
  sensorValueA1 = analogRead(sensorPin2);
  Serial.print("A0= ");
  Serial.print(sensorValueA0);
  Serial.print("     A1= ");
  Serial.println(sensorValueA1);

  delay(500);
}

Fortunately, unlike the first 3 tests, this one worked the first time. When the joystick was moved all the way in one direction the analog read 1 count and when it was pushed the other way it read approximately 4095. This makes sense as the ADC converter on the chip has 12 bit resolution which means 4096 counts for the full scale output.

Testing the analog output with a joystick.

Digital

Next I tested the digital pins with a DHT22 temperature sensor. I hooked it up to D9 on my board and downloaded the Adafruit DHT library. The DHT library came with a sample program that I could use for testing. I changed the pin to 8 to correspond to the correct pin on my board and uploaded the program.

DHT Test
// Example testing sketch for various DHT humidity/temperature sensors
// Written by ladyada, public domain

// REQUIRES the following Arduino libraries:
// - DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library
// - Adafruit Unified Sensor Lib: https://github.com/adafruit/Adafruit_Sensor

#include "DHT.h"

#define DHTPIN 8     // Digital pin connected to the DHT sensor

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11   // DHT 11
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

// Initialize DHT sensor.
// Note that older versions of this library took an optional third parameter to
// tweak the timings for faster processors.  This parameter is no longer needed
// as the current DHT reading algorithm adjusts itself to work on faster procs.
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  Serial.println(F("DHTxx test!"));

  dht.begin();
}

void loop() {
  // Wait a few seconds between measurements.
  delay(2000);

  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float f = dht.readTemperature(true);

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t) || isnan(f)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  // Compute heat index in Fahrenheit (the default)
  float hif = dht.computeHeatIndex(f, h);
  // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(t, h, false);

  Serial.print(F("Humidity: "));
  Serial.print(h);
  Serial.print(F("%  Temperature: "));
  Serial.print(t);
  Serial.print(F("°C "));
  Serial.print(f);
  Serial.print(F("°F  Heat index: "));
  Serial.print(hic);
  Serial.print(F("°C "));
  Serial.print(hif);
  Serial.println(F("°F"));
}
Then I brought up the serial monitor in my Aruduino IDE and as is has been the case throughout testing, I received a message that the DHT could not be found. I took a look at my traces on the bottom layer of the board and noticed that some of the pins had iffy connections to their pads and traces. I then reflowed the traces on both of the digital pins and tried again. Fortunately, this fixed the problem and I was able to read temperature and humidity values through the sensor.

Live DHT data after I did a quick repair on the board.

I2C

Finally, I tested the I2C bus with an AHT20 temperature and humidity sensor. I download the Adafruit AHT library and opened one of the sample programs. I hooked up the AHT with jumpers and matched the power, ground, clock, and data pins on the sensor to the board before uploading the program.

DHT Test
#include <Adafruit_AHTX0.h>

Adafruit_AHTX0 aht;

void setup() {
  Serial.begin(115200);
  Serial.println("Adafruit AHT10/AHT20 demo!");

  if (! aht.begin()) {
    Serial.println("Could not find AHT? Check wiring");
    while (1) delay(10);
  }
  Serial.println("AHT10 or AHT20 found");
}

void loop() {
  sensors_event_t humidity, temp;
  aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data
  Serial.print("Temperature: "); Serial.print(temp.temperature); Serial.println(" degrees C");
  Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); Serial.println("% rH");

  delay(500);
}

I opened the serial monitor window in the Arduino environment and the message was that the sensor could not be found. 😠. So, I took a look at the board to see if anything looked wrong. On the bottom copper layer I noticed a lingering finger of copper that seemed to be bridging the clock and data traces. I found the voltmeter and checked continuity and sure enough there was cross talk on the pins. I found a razor blade and scraped in the channel to remove the copper. I retested continuity and the connection was broken.

The tiny remnant piece of copper that was shorting my data and clock traces.

I powered the XIAO back on and after the fix, temperature and humidity data was being succesfully sent to the serial monitor from the sensor.

Live AHT sensor data after removing the bridge.

And finally a shot of the board working on the bench inside of a simple case that I 3D printed to keep it from shorting out during testing.

Finished board!

Files

Front Copper DXF

Rear Copper DXF