Skip to content

Instantly share code, notes, and snippets.

@100ideas
Created August 2, 2011 13:11
Show Gist options
  • Save 100ideas/1120142 to your computer and use it in GitHub Desktop.
Save 100ideas/1120142 to your computer and use it in GitHub Desktop.
CyclerCan PCR program for Arduino, updated for hairdryer thermocycler
/*
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