Created
August 2, 2011 13:11
-
-
Save 100ideas/1120142 to your computer and use it in GitHub Desktop.
CyclerCan PCR program for Arduino, updated for hairdryer thermocycler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Generalised Finite-State Thermal Cycler Code | |
by Cathal Garvey - [email protected], @onetruecathal | |
Designed for CyclerCan, a project that will be hosted at www.indiebiotech.com | |
Inspired heavily by the Lightbulb PCR Machine from Russel Durret: http://russelldurrett.com/lightbulbpcr.html | |
All original code presented before is co-licensed under the LGPL Lesser GNU Public License and | |
a Creative Commons Attribution, Sharealike license. Attribution is preferentially directed towards | |
"Cathal Garvey, Indiebiotech.com". | |
The point of the code below is to modularise as much of the function of a Thermal Cycler as possible. | |
The temperature-sensing code is independent of everything else, and is designed for use with an LM35 | |
temperature sensor. Readouts are obviously in Celsius, as it's 2011 at time of writing. ;) | |
The device is modelled as a Finite State Machine using the FSM library, which you can grab from here: | |
http://www.arduino.cc/playground/uploads/Code/FSM.zip | |
This is because I'm rubbish at sorting out arduino timing without using delay() and it gives me a headache. | |
All heating, holding and cooling functions should be re-usable without the above library, and I would love | |
to see a standalone version that doesn't need the FSM library. | |
*/ | |
#include <FiniteStateMachine.h> | |
//Pins in use for Cyclercan (change as required): | |
const int TempPin = A0; | |
const int HeatPin = 13; | |
const int CoolPin = 12; | |
const int FirePin = 11; // Set this pin to HIGH (i.e. w/switch) to start the cycler. | |
//How many cycles to run: | |
int Cycles = 2; | |
//Temperatures used for cycling: Change according to enzyme and primers used: | |
const int MeltTemp = 95; | |
const int AnnealTemp = 55; | |
const int ExtendTemp = 70; | |
const int GlobalError = 1; //Within how many degrees to maintain temperature. | |
float TempHolder; //Used to hold temperature readings per-cycle. | |
//Times used for cycling: Change according to enzyme and primers used: | |
const unsigned long MeltTime = 20000; //Time in millis to remain at melting temperature | |
const unsigned long AnnealTime = 8000; //Time in millis to remain at annealing temperature | |
const unsigned long ExtendTime = 50000; //Time in millis to remain at extention temperature | |
unsigned long TimeTracker; //Used to count time against system clock for cycling program | |
//Heat Pulsing parameters; for fine-tuning your setup. I don't know PID, obviously. | |
const int HeatPulseDur = 1000; //Sets the amount of time in milliseconds the hot fan stays on when pulsing | |
const int ColdPulseDur = 1800; //Likewise for the cold fan | |
const int RestPulseDur = 1200; //Sets the time that either fan stays off during pulsing | |
boolean Verbose = false; // Tell me lots about what's happening via serial. | |
boolean DataLogging = true; //Alternative to Verbose; outputs csv. | |
boolean Debug = false; // Tell me everything, including annoying temperature read data | |
int GlobalReads = 10; //How many times to poll the temperature sensor with each reading. | |
// Setting up the "States" that the Finite State Machine can occupy. | |
// Functions referred to here are all presented below loop(). | |
State PreCheck = State(PreCheckState); | |
State Melt = State(MeltState); | |
State Anneal = State(AnnealState); | |
State Extend = State(ExtendState); | |
State Finish = State(FinishState); | |
//Setting up the "FSM" object that does all the handling of everything and such. | |
FSM ThermalCycler = FSM(PreCheck); | |
void setup() | |
{ | |
Serial.begin(9600); //opens serial port, sets data rate to 9600 bps | |
pinMode(FirePin,INPUT); | |
pinMode(TempPin,INPUT); | |
pinMode(HeatPin,OUTPUT); | |
pinMode(CoolPin,OUTPUT); | |
if(DataLogging){ | |
delay(5000); //Gives you time to set up the serial monitor | |
Serial.println("Time,Target,Actual"); | |
} | |
} | |
void loop() | |
{ | |
int CurrentTemp = ReadLM35(TempPin); //Fetch current temperature | |
if(Cycles < 1){ | |
ThermalCycler.transitionTo(Finish); | |
} | |
ThermalCycler.update(); | |
} | |
void PreCheckState(){ | |
do { | |
if(Verbose){ | |
Serial.print("Current Temp: "); | |
Serial.print(ReadLM35(TempPin)); | |
Serial.print("C - Pull FirePin to start! Milliseconds since powerup: "); | |
Serial.println(millis()); | |
} | |
if(DataLogging){PushData(0);} | |
CoolPulse(); | |
delay(1000); | |
} while(digitalRead(FirePin)==LOW); | |
ThermalCycler.transitionTo(Melt); | |
} | |
void MeltState(){ | |
RampTemp(MeltTemp); | |
HoldTemp(MeltTemp,MeltTime); | |
ThermalCycler.transitionTo(Anneal); | |
} | |
void AnnealState(){ | |
RampTemp(AnnealTemp); | |
HoldTemp(AnnealTemp,AnnealTime); | |
ThermalCycler.transitionTo(Extend); | |
} | |
void ExtendState(){ | |
RampTemp(ExtendTemp); | |
HoldTemp(ExtendTemp,ExtendTime); | |
ThermalCycler.transitionTo(Melt); | |
Cycles--; | |
} | |
void FinishState(){ | |
CoolPulse(); | |
if(Verbose){ | |
Serial.println("Job Finished, Holding at RT"); | |
} | |
} | |
void RampTemp(float TargetTemp){ //Separate from HoldTemp to facilitate timekeeping at-temperature | |
TimeTracker = millis(); | |
if(ReadLM35(TempPin) < (TargetTemp - GlobalError)){ | |
while(ReadLM35(TempPin) < (TargetTemp - GlobalError)){ | |
HeatPulse(); | |
if(Verbose){ | |
Serial.print("Ramping up to: "); | |
Serial.print(TargetTemp); | |
Serial.print(" - Current Temperature: "); | |
Serial.print(ReadLM35(TempPin)); | |
Serial.print(" - Time Taken so far: "); | |
Serial.print(millis() - TimeTracker); | |
Serial.print(" - Millis = "); | |
Serial.println(millis()); | |
} | |
if(DataLogging){Serial.print(millis()/1000);Serial.print(",");Serial.print(TargetTemp);Serial.print(",");Serial.println(ReadLM35(TempPin));} | |
} | |
} | |
else if(ReadLM35(TempPin) > (TargetTemp + GlobalError)){ | |
while(ReadLM35(TempPin) > (TargetTemp + GlobalError)){ | |
CoolPulse(); | |
if(Verbose){ | |
Serial.print("Ramping down to: "); | |
Serial.print(TargetTemp); | |
Serial.print(" - Current Temperature: "); | |
Serial.print(ReadLM35(TempPin)); | |
Serial.print(" - Time Taken so far: "); | |
Serial.print(millis() - TimeTracker); | |
Serial.print(" - Millis = "); | |
Serial.println(millis()); | |
} | |
if(DataLogging){PushData(TargetTemp);} | |
} | |
} | |
} | |
void HoldTemp(float TargetTemp,long HoldingTime){ | |
TimeTracker = millis(); | |
while(HoldingTime > (millis() - TimeTracker)){ | |
TempHolder = ReadLM35(TempPin); | |
if(TempHolder < (TargetTemp-GlobalError)){ | |
if(Verbose){ | |
Serial.print("Holding at "); | |
Serial.print(TargetTemp); | |
Serial.print(" - Current Temperature: "); | |
Serial.print(TempHolder); | |
} | |
if(DataLogging){PushData(TargetTemp);} | |
HeatPulse(); | |
} | |
else if(TempHolder > (TargetTemp+(GlobalError*2))){ | |
if(Verbose){ | |
Serial.print("Holding at "); | |
Serial.print(TargetTemp); | |
Serial.print(" - Current Temperature: "); | |
Serial.print(TempHolder); | |
} | |
if(DataLogging){PushData(TargetTemp);} | |
CoolPulse(); | |
} | |
else{ | |
if(Verbose){ | |
Serial.print("Holding at "); | |
Serial.print(TargetTemp); | |
Serial.print(" - Current Temperature: "); | |
Serial.print(TempHolder); | |
} | |
if(DataLogging){PushData(TargetTemp);} | |
} | |
if(Verbose){ | |
Serial.print(" - Time Left (ms): "); | |
Serial.print(HoldingTime - (millis() - TimeTracker)); | |
Serial.print(" - Millis = "); | |
Serial.println(millis()); | |
} | |
} | |
} | |
void HeatPulse(){ | |
digitalWrite(CoolPin,LOW); | |
digitalWrite(HeatPin,HIGH); | |
delay(HeatPulseDur); | |
digitalWrite(HeatPin,LOW); | |
if(Debug){Serial.print("Post-HeatPulse: ");Serial.println(ReadLM35(TempPin));} | |
delay(RestPulseDur); | |
} | |
void CoolPulse(){ | |
digitalWrite(HeatPin,LOW); | |
digitalWrite(CoolPin,HIGH); | |
delay(ColdPulseDur); | |
digitalWrite(CoolPin,LOW); | |
if(Debug){Serial.print("Post-CoolPulse: ");Serial.println(ReadLM35(TempPin));} | |
delay(RestPulseDur); | |
} | |
void PushData(float Targ){ | |
Serial.print(millis()/1000); | |
Serial.print(","); | |
Serial.print(Targ); | |
Serial.print(","); | |
Serial.println(ReadLM35(TempPin)); | |
} | |
float ReadLM35(int tempPin){ | |
float temp; | |
if(Debug){Serial.println();} //Keeps the debug a bit tidier | |
for(int i = 1; i < (GlobalReads+1); i++){ //Read the sensor value five times and add them all up. | |
temp = temp + analogRead(tempPin); | |
if(Debug){Serial.print("Debug: Reading #");Serial.print(i);Serial.print(": ");Serial.println(temp);} //Spews counts | |
} | |
temp = temp / GlobalReads; //Average of five reads (for more accuracy, fewer outliers) | |
temp = (5.0 * temp * 100.0)/1024.0; //convert the analog data to temperature | |
return temp; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment