Polymerase Chain Reaction Machine

Polymerase chain reaction is the process of DNA replication. It consists of three phases: denaturation, annealing and extension. Those phases are cycled through between 30 and 40 times to exponentially generate copies of a a DNA segment. For more information on the reaction you can read this Wikipedia article or watch the video to the right. The science department at my high school wanted an additional PCR machine and rather than spending thousands of dollars buying one, they tasked me with building one. I created a Github Repository with all of the code, schematics and other necessary files for anyone interested in recreating the machine.

Final Product:

Components and Schematic:

PCB:

In efforts to consolidate the circuitry and build a more robust machine I designed and ordered a printed circuit board. The printed circuit board will also ensure better connections soldered down rather than connected with a breadboard. An image of the circuit board schematic is to the right. If you are interested in recreating this project I highly recommend using a PCB. To order a copy of the one that I made you can download the gerber file (along with all other important files) from my Github Repository and then order the board from a PCB fabricator. I recommend JLCPCB.com.

Testing:

Above is a photograph of all of the components put together without an enclosure.

This is a photograph of all of the components put together without an enclosure/case which I am currently designing and plan to make using a laser cutter.

Cycle Data:

This graph shows the temperature in celsius of the aluminum block throughout one cycle. The three plateaus at 95C, 50C and 72C show the machine holding a steady temperature at each of the 3 PCR stages.

Code:

/* Pin Layout:

* JOYSTICK:

* X to A0

* SW to D2

* LCD

* SDA to A4

* SCL to A5

* Thermocouple

* DO to D3

* CS to D4

* CLK to D5

* Relay

* Pos to D13

*/


//Include libraries required for components

#include <Adafruit_MAX31855.h>

#define MAXDO 3

#define MAXCS 4

#define MAXCLK 5

Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);

#include <Wire.h>

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 2,1,0,4,5,6,7);


// Define variables

long duration;

int cycles = 1;

int currentCycle = 1;

long startTime;

long timeEllapsed;

long firstDenaturationDuration;

long fDDSeconds = 0;

long fDDMins = 02;

long lastExtensionDuration;

long fEDSeconds = 0;

long fEDMins = 05;

long endTime;

long remainder;

double annealingTemp = 50.00;


// Create non-return functions to turn on the heater and fan

void heaterOn() {digitalWrite(13, HIGH);}

void fanOn() {digitalWrite(13, LOW);}


// Non return function to set up how many cycles to run

void getCycles() {

lcd.setCursor(5,0);

lcd.print("CYCLES:");

lcd.setCursor(7,1);

lcd.print(cycles);

// Read Joystick and determine if user is going up or down

if (analogRead(0) < 100 && cycles >= 2) {

cycles -= 1;

delay(200);

}

if (analogRead(0) > 900) {

cycles += 1;

delay(200);

}

// Keep prompting for input

if (digitalRead(2) == 1) {

getCycles();

}

}


// Non return func to determine first denaturation duration (different duration from rest)

void getFirstDenaturationDuration() {

// Set up screen and times

lcd.setCursor(0,0);

lcd.print("1st Denaturation");

lcd.setCursor(0,1);

lcd.print("Time: ");

lcd.setCursor(6,1);

lcd.print(fDDMins);

lcd.setCursor(7,1);

lcd.print(":");

// Handle seconds over 59

if (fDDSeconds > 59) {

fDDMins += 1;

fDDSeconds = 0;

delay(250);

}

// Handle seconds less than 0

if (fDDSeconds == 0 && analogRead(0) < 100) {

fDDMins -= 1;

fDDSeconds = 59;

delay(250);

}

// Handle place value updates

if (fDDSeconds >= 9) {

lcd.setCursor(8,1);

lcd.print(fDDSeconds);

}

// Handle place value updates

if (fDDSeconds < 10 && fDDSeconds > 0) {

lcd.setCursor(8,1);

lcd.print("0");

lcd.setCursor(9,1);

lcd.print(fDDSeconds);

}

// Print seconds when seconds = 0

if (fDDSeconds == 0) {

lcd.setCursor(8,1);

lcd.print("00");

}

// Define duration

firstDenaturationDuration = ((fDDMins * 60000) + (fDDSeconds * 1000));

if (analogRead(0) < 100 && firstDenaturationDuration>30000) {

fDDSeconds -= 1;

delay(200);

}

// Plus 1 second

if (analogRead(0) > 900 && fDDMins < 8) {

fDDSeconds += 1;

delay(200);

}

// Set duration

firstDenaturationDuration = (fDDMins * 60000) + (fDDSeconds * 1000);

// Keep prompting for input

if (digitalRead(2) == 1) {

getFirstDenaturationDuration();

}

}


// Get annealing temp

void getAnnealTemp() {

// Set up LCD

lcd.setCursor(1,0);

lcd.print("ANNEALING TEMP:");

lcd.setCursor(5,1);

lcd.print(annealingTemp);

// Read joystick and inc/dec in decrements of 0.25 degrees C

if (analogRead(0) < 100 && annealingTemp >= 49) {

annealingTemp -= 0.25;

delay(200);

}

if (analogRead(0) > 900) {

annealingTemp += 0.25;

delay(200);

}

// Keep prompting for input

if (digitalRead(2) == 1) {

getAnnealTemp();

}

}


// Get last extension duration function (diff from other extension durrations)

void getLastExtensionDuration() {

// Set up LCD

lcd.setCursor(1,0);

lcd.print("Last Extension");

lcd.setCursor(0,1);

lcd.print("Time: ");

lcd.setCursor(6,1);

lcd.print(fEDMins);

lcd.setCursor(7,1);

lcd.print(":");

// Same as above: Handle joystick input, change times based on input, keep prompting until joystick is clicked

if (fEDSeconds > 59) {

fEDMins += 1;

fEDSeconds = 0;

delay(250);

}

if (fEDSeconds == 0 && analogRead(0) < 100) {

fEDMins -= 1;

fEDSeconds = 59;

delay(250);

}

if (fEDSeconds >= 9) {

lcd.setCursor(8,1);

lcd.print(fEDSeconds);

}

if (fEDSeconds < 10 && fEDSeconds > 0) {

lcd.setCursor(8,1);

lcd.print("0");

lcd.setCursor(9,1);

lcd.print(fEDSeconds);

}

if (fEDSeconds == 0) {

lcd.setCursor(8,1);

lcd.print("00");

}

lastExtensionDuration = ((fEDMins * 60000) + (fEDSeconds * 1000));

if (analogRead(0) < 100 && lastExtensionDuration>60000) {

fEDSeconds -= 1;

delay(200);

}

if (analogRead(0) > 900 && fEDMins < 8) {

fEDSeconds += 1;

delay(200);

}

lastExtensionDuration = (fEDMins * 60000) + (fEDSeconds * 1000);

if (digitalRead(2) == 1) {

getLastExtensionDuration();

}

}


// Configure LCD to prompt for begin command

void enter() {

lcd.setCursor(2,0);

lcd.print("FRIEDSAM PCR");

lcd.setCursor(1,1);

lcd.print("CLICK TO BEGIN");

if (digitalRead(2) == 1) {

enter();

}

}


// Function to run above functions to set up machine

void getInput() {

getCycles();

lcd.clear();

delay(500);

getFirstDenaturationDuration();

lcd.clear();

delay(500);

getAnnealTemp();

lcd.clear();

delay(500);

getLastExtensionDuration();

lcd.clear();

delay(500);

enter();

lcd.clear();

delay(500);

}


//Function to be called recursively until temp for denaturation is reached

void prepDenaturation() {

heaterOn();

double temp = thermocouple.readCelsius();

delay(500);

lcd.clear();

lcd.setCursor(5,0);

lcd.print("HEATING");

lcd.setCursor(2,1);

lcd.print("Temp: ");

if (isnan(temp) == false) {

lcd.setCursor(8,1);

lcd.print(temp);

}

if (temp<95 || isnan(temp)) {

prepDenaturation(); //if it is less than 95 degrees keep heating

}

}


// Function to be called recursivley until denature duration has passed

void denature() {

// Read temo]p

double temp = thermocouple.readCelsius();

// Switch between fan and heater to keep tempt between 94.75 and 95.25

if (temp <= 94.75) {

heaterOn();

}

if (temp >= 95.25) {

fanOn();

}

timeEllapsed = millis();

endTime = startTime + duration;

remainder = endTime - timeEllapsed;

delay(500);

lcd.clear();

lcd.setCursor(0,0);

lcd.print("PHASE: DENATURE");

lcd.setCursor(0,1);

lcd.print("CYCLE:");

lcd.setCursor(7,1);

lcd.print(currentCycle);

lcd.setCursor(10,1);

lcd.print(temp);

lcd.setCursor(15,1);

lcd.print("C");

if (timeEllapsed < endTime) {

denature();

}

}


// Function to cool down until temp is met for annealing

void prepAnnealing() {

fanOn();

double temp = thermocouple.readCelsius();

delay(500);

lcd.clear();

lcd.setCursor(5,0);

lcd.print("COOLING");

lcd.setCursor(2,1);

lcd.print("Temp: ");

if (isnan(temp) == false) {

lcd.setCursor(8,1);

lcd.print(temp);

}

if (temp >= annealingTemp + 0.25 || isnan(temp)) {

prepAnnealing();

}

}


// Anneal until annealing duration has passed (recursion)

void anneal() {

double temp = thermocouple.readCelsius();

if (temp <= annealingTemp - 0.25) {

heaterOn();

}

if (temp >= annealingTemp + 0.25) {

fanOn();

}

timeEllapsed = millis();

endTime = startTime + duration;

remainder = endTime - timeEllapsed;

delay(500);

lcd.clear();

lcd.setCursor(0,0);

lcd.print("PHASE: ANNEAL");

lcd.setCursor(0,1);

lcd.print("CYCLE:");

lcd.setCursor(7,1);

lcd.print(currentCycle);

lcd.setCursor(10,1);

lcd.print(temp);

lcd.setCursor(15,1);

lcd.print("C");

if (timeEllapsed < endTime) {

anneal();

}

}


// Heat back up to prep for extension

void prepExtension() { //heat back up

heaterOn();

double temp = thermocouple.readCelsius();

delay(500);

lcd.clear();

lcd.setCursor(5,0);

lcd.print("HEATING");

lcd.setCursor(2,1);

lcd.print("Temp: ");

if (isnan(temp) == false) {

lcd.setCursor(8,1);

lcd.print(temp);

}

if (temp < 72 || isnan(temp)) {

prepExtension();

}

}


// Maintain extension tempt until extension time duration has passed (recursion)

void extend() {

double temp = thermocouple.readCelsius();

if (temp <= 71.75) {

heaterOn();

}

if (temp >= 72.25) {

fanOn();

}

timeEllapsed = millis();

endTime = startTime + duration;

remainder = endTime - timeEllapsed;

delay(500);

lcd.clear();

lcd.setCursor(0,0);

lcd.print("PHASE: EXTEND");

lcd.setCursor(0,1);

lcd.print("CYCLE:");

lcd.setCursor(7,1);

lcd.print(currentCycle);

lcd.setCursor(10,1);

lcd.print(temp);

lcd.setCursor(15,1);

lcd.print("C");

if (timeEllapsed < endTime) {

extend();

}

}


// Run everything multiple times based on number of cycles defined above

void PCR() {

for (int i = 0; i <= cycles-1; i++) {

prepDenaturation();

lcd.clear();

if (currentCycle == 1) {duration = firstDenaturationDuration;}

else {duration = 30000;}

startTime = millis();

denature();

lcd.clear();

prepAnnealing();

lcd.clear();

duration = 30000;

startTime = millis();

anneal();

lcd.clear();

prepExtension();

lcd.clear();

if (cycles - currentCycle == 0) {duration = lastExtensionDuration;}

else {duration = 60000;}

startTime = millis();

extend();

lcd.clear();

currentCycle += 1;

}

fanOn();

lcd.clear();

lcd.setCursor(2,0);

lcd.print("FRIEDSAM PCR");

lcd.setCursor(0,1);

lcd.print("PHASE: COMPLETE");

}



void setup() {

// Configure I/O

pinMode(13, OUTPUT);

lcd.begin (16,2);

lcd.setBacklightPin(3,POSITIVE);

lcd.setBacklight(HIGH);

Serial.begin(9600);

pinMode(2, INPUT);

digitalWrite(2, HIGH);

// Get input and run machine

getInput();

PCR();

}


void loop() {

}