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:
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() {
}