0.2.1 PCR

This commit is contained in:
Rob Tillaart 2024-06-01 10:38:04 +02:00
parent febd331002
commit 62230e22d8
14 changed files with 463 additions and 78 deletions

View File

@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.1] - 2024-05-31
- add **setHeatPulseLength(uint16_t len)** to modify the pulse length of the heater / cooler.
- add example **PCR_demo_DS18B20.ino** DS18B20 for PCR.
- add example **PCR_demo_MAX31855.ino** Thermocouple for PCR.
- add example **PCR_demo_array** multiple PCR's in one sketch.
- fix examples (URL)
- fix library.properties category.
- update readme.md
- minor edits
## [0.2.0] - 2024-05-30
- initial class version (published)

View File

@ -3,7 +3,7 @@
// FILE: PCR.h
// AUTHOR: Rob Tillaart
// DATE: 2015-06-10
// VERSION: 0.2.0
// VERSION: 0.2.1
// PURPOSE: Arduino library for PCR process control.
// URL: https://github.com/RobTillaart/PCR
// https://forum.arduino.cc/t/problem-with-arduino-pcr-amplifies-of-dna/314808
@ -12,13 +12,13 @@
#include "Arduino.h"
#define PCR_LIB_VERSION (F("0.2.0"))
#define PCR_LIB_VERSION (F("0.2.1"))
enum PCRSTATE {
PCR_STATE_IDLE = 0,
PCR_STATE_INITIAL,
PCR_STATE_DENATURE,
PCR_STATE_ANNEALING,
enum PCRSTATE {
PCR_STATE_IDLE = 0,
PCR_STATE_INITIAL,
PCR_STATE_DENATURE,
PCR_STATE_ANNEALING,
PCR_STATE_EXTENSION,
PCR_STATE_ELONGATION,
PCR_STATE_HOLD
@ -40,47 +40,47 @@ public:
// PARAMETERS
void setInitial(float temp, uint32_t ms)
void setInitial(float Celsius, uint32_t ms)
{
_initialTemp = temp;
_initialTemp = Celsius;
_initialTime = ms;
}
float getInitialTemp() { return _initialTemp; }
uint32_t getInitialTime() { return _initialTime; }
void setDenature(float temp, uint32_t ms)
void setDenature(float Celsius, uint32_t ms)
{
_denatureTemp = temp;
_denatureTemp = Celsius;
_denatureTime = ms;
}
float getDenatureTemp() { return _denatureTemp; }
uint32_t getDenatureTime() { return _denatureTime; }
void setAnnealing(float temp, uint32_t ms)
void setAnnealing(float Celsius, uint32_t ms)
{
_annealingTemp = temp;
_annealingTemp = Celsius;
_annealingTime = ms;
}
float getAnnealingTemp() { return _annealingTemp; }
uint32_t getAnnealingTime() { return _annealingTime; }
void setExtension(float temp, uint32_t ms)
void setExtension(float Celsius, uint32_t ms)
{
_extensionTemp = temp;
_extensionTemp = Celsius;
_extensionTime = ms;
}
float getExtensionTemp() { return _extensionTemp; }
float getExtensionTime() { return _extensionTime; }
void setElongation(float temp, uint32_t ms)
void setElongation(float Celsius, uint32_t ms)
{
_elongationTemp = temp;
_elongationTemp = Celsius;
_elongationTime = ms;
}
float getElongationTemp() { return _elongationTemp; }
float getElongationTime() { return _elongationTime; }
void setHold(float temp) { _holdTemp = temp; }
void setHold(float Celsius) { _holdTemp = Celsius; }
float getHoldTemp() { return _extensionTemp; }
@ -94,12 +94,21 @@ public:
debug();
}
int iterationsLeft() { return _cycles; };
int iterationsLeft()
{
return _cycles;
}
// returns PCR_STATE_HOLD when ready;
// returns state ==> PCR_STATE_HOLD when ready;
uint8_t process(float temperature)
{
_temperature = temperature;
/* 0.3.0
if (_temperature < temp[_state]) heat();
else if (_temperature > temp[_state]) cool();
else off();
*/
switch(_state)
{
case PCR_STATE_IDLE:
@ -119,7 +128,7 @@ public:
debug();
}
break;
case PCR_STATE_DENATURE:
if (_temperature < _denatureTemp) heat();
else if (_temperature > _denatureTemp) cool();
@ -143,7 +152,7 @@ public:
debug();
}
break;
case PCR_STATE_EXTENSION:
if (_temperature < _extensionTemp) heat();
else if (_temperature > _extensionTemp) cool();
@ -157,7 +166,7 @@ public:
debug();
}
break;
case PCR_STATE_ELONGATION:
if (_temperature < _elongationTemp) heat();
else if (_temperature > _elongationTemp) cool();
@ -181,17 +190,29 @@ public:
// HEATER / COOLER CONTROL
void setHeatPulseLength(uint16_t ms = 10)
{
if (ms > 1000) ms = 1000;
_heatPulseLength = ms;
}
uint16_t getHeatPulseLength()
{
return _heatPulseLength;
}
void heat()
{
digitalWrite(_heatPin, HIGH);
delay(10);
delay(_heatPulseLength);
digitalWrite(_coolPin, LOW);
}
void cool()
{
digitalWrite(_coolPin, HIGH);
delay(10);
delay(_heatPulseLength);
digitalWrite(_coolPin, LOW);
}
@ -204,7 +225,7 @@ public:
// blocking version of single step.
// to be tested what to do with it
// could be a separate class.
// could be a separate class / stand alone version.
/*
void keepTempTime(float temperature, uint32_t ms, float (*getTemp)())
{
@ -231,7 +252,8 @@ public:
return sum;
}
private:
protected:
// development.
void debug()
{
@ -245,16 +267,25 @@ private:
Serial.print(_startTime);
Serial.print("\t");
Serial.print(_cycles);
if (_state == PCR_STATE_IDLE) Serial.println("\tIdle");
if (_state == PCR_STATE_INITIAL) Serial.println("\tInitialization");
if (_state == PCR_STATE_DENATURE) Serial.println("\tDenature");
if (_state == PCR_STATE_ANNEALING) Serial.println("\tAnnealing");
if (_state == PCR_STATE_EXTENSION) Serial.println("\tExtension");
if (_state == PCR_STATE_ELONGATION) Serial.println("\tElongation");
if (_state == PCR_STATE_HOLD) Serial.println("\tHOLD");
// use an array?
if (_state == PCR_STATE_DENATURE) Serial.println("\tDenature");
else if (_state == PCR_STATE_ANNEALING) Serial.println("\tAnnealing");
else if (_state == PCR_STATE_EXTENSION) Serial.println("\tExtension");
// less used
else if (_state == PCR_STATE_ELONGATION) Serial.println("\tElongation");
else if (_state == PCR_STATE_IDLE) Serial.println("\tIdle");
else if (_state == PCR_STATE_INITIAL) Serial.println("\tInitialize");
else if (_state == PCR_STATE_HOLD) Serial.println("\tHold");
}
/*
// simplify the code. 0.3.0
// maybe temperature only?
float _temp[6] = { 94, 94, 54, 76, 76, 14 };
float _time[6] = { 0, 1, 1, 1, 1, -1 };
*/
float _initialTemp = 94;
uint32_t _initialTime = 0;
float _denatureTemp = 94;
@ -274,12 +305,10 @@ private:
PCRSTATE _state = PCR_STATE_IDLE;
int _cycles = 0;
uint32_t _startTime = 0;
uint16_t _heatPulseLength = 10; // milliseconds.
};
// -- END OF FILE --

View File

@ -32,16 +32,16 @@ main PCR cycles.
In short a PCR cycle is a process of controlled heating and cooling to let DNA "reproduce"
to get large quantities. Roughly the amount doubles in every cycle (of step 2,3,4).
This process exists of repeated cycles of the three main steps. (times and temp from wikipedia)
This process exists of repeated cycles of the three main steps. (times and temp from Wikipedia)
| step | name | temperature range | time range |
|:----:|:----------------|:----------------------|:------------:|
| 1 | Initialization | 9498°C = 201208°F | 0010 min. |
| 2 | Denaturation | 9498°C = 201208°F | 2030 sec. |
| 3 | Annealing | 5065°C = 122149°F | 2040 sec. |
| 4 | Extension | 7080°C = 158176°F | ? |
| 5 | Elongation | 7080°C = 158176°F | 0515 min. |
| 6 | Final Hold | 415°C = 3959°F | indefinitely |
| step | cycle | name | temperature range | time range | notes |
|:----:|:-----:|:----------------|:----------------------|:------------:|:--------|
| 1 | N | Initialization | 9498°C = 201208°F | 0010 min. | to heat up system => hot-start PCR.
| 2 | Yes | Denaturation | 9498°C = 201208°F | 2030 sec. |
| 3 | Yes | Annealing | 5065°C = 122149°F | 2040 sec. |
| 4 | Yes | Extension | 7580°C = 167176°F | ? minutes |
| 5 | N | Elongation | 7074°C = 158165°F | 0515 min. |
| 6 | N | Final Hold | 415°C = 3959°F | indefinitely | final storage
The PCR function **process()** takes care of the repeating of step 2,3 and 4.
@ -67,7 +67,8 @@ Typical core code looks like:
```
**Note:** this library is meant for educational purposes and is not meant to replace professional equipment.
**Note:** this library is meant for educational purposes and is not meant
to replace professional equipment.
#### Hardware notes
@ -118,7 +119,7 @@ Some examples:
#### Constructor
- **PCR(uint8_t heatPin, uint8_t coolPin)** constructor defines the haredware pins to which
- **PCR(uint8_t heatPin, uint8_t coolPin)** constructor defines the hardware pins to which
the heater and cooler are connected.
- **void reset(int iterations)** full stop of the process, also stops heating and cooling,
resets the state to IDLE and defines the number of iterations for the next run.
@ -126,8 +127,7 @@ resets the state to IDLE and defines the number of iterations for the next run.
and iterates over the DENATURE, ANNEALING and EXTENSION phase. Returns the current state.
The user **MUST** provide the actual temperature of the sample so process can heat and cool
the sample on a need to basis.
The user **MUST** call this function as often as possible in a tight loop.
Returns the current state.
The user **MUST** call this function as often as possible in a tight loop.
- **int iterationsLeft()** returns the number of iterations left.
- **uint32_t timeLeft()** estimator of the time left to reach the HOLD state.
This function assumes that the duration per phase does not change runtime,
@ -135,42 +135,63 @@ however it will adapt its estimate.
Returns the value in milliseconds.
#### Initial phase
#### About phases
Temperatures are in °Celsius, timing is in milliseconds.
Note that these parameters can change while the process is running.
Temperatures are in °Celsius, timing is in milliseconds (for 0.2.x version).
The timing is the time that the process will be in this state, so it includes
the time to heat / cool to reach the temperature defined.
Note that the parameters of the phases can change while the process is running,
e.g. one can increase the duration of the extension phase per cycle to give
that part of the PCR process more time.
#### 1 Initial phase
This step used in **hot-start PCR** (Wikipedia) to bring the system to starting temperature.
- **void setInitial(float Celsius, uint32_t ms)** Sets temperature and duration.
- **float getInitialTemp()** returns set value.
- **uint32_t getInitialTime()** returns set value.
#### Denature phase
#### 2 Denature phase
This step breaks the double DNA helix into two single strands.
- **void setDenature(float Celsius, uint32_t ms)** Sets temperature and duration.
- **float getDenatureTemp()** returns set value.
- **uint32_t getDenatureTime()** returns set value.
#### Annealing phase
#### 3 Annealing phase
This step let **primers** (Wikipedia) connect to the single strands.
The primers create a starting point for the replication.
The temperature and duration depends on many factors, so very specific for the reaction.
- **void setAnnealing(float Celsius, uint32_t ms)** Sets temperature and duration.
- **float getAnnealingTemp()** returns set value.
- **uint32_t getAnnealingTime()** returns set value.
#### Extension phase
#### 4 Extension phase
This step extends the primers with **dNTP's** nucleotides (Wikipedia) to complete
the duplication process.
- **void setExtension(float Celsius, uint32_t ms)** Sets temperature and duration.
- **float getExtensionTemp()** returns set value.
- **float getExtensionTime()** returns set value.
#### Elongation phase
#### 5 Elongation phase
This step is used to finalize the remaining DNA strands that are not fully extended
in step 4 Extension phase.
- **void setElongation(float Celsius, uint32_t ms)** Sets temperature and duration.
- **float getElongationTemp()** returns set value.
- **float getElongationTime()** returns set value.
#### Hold phase
#### 6 Hold phase
The Hold phase goes on forever ans is meant to store the result on a cool temperature
The Hold phase goes on forever and is meant to store the result on a cool temperature
for final storage.
- **void setHold(float Celsius)** Sets temperature for final phase.
@ -178,11 +199,25 @@ for final storage.
#### Heater, cooler control
These are public functions so the user can control these also from their own code.
The temperature control functions are made public so the user can use these directly
from their own code.
In 0.2.x version the heater / cooler are switched on/off for a short period.
This prevent excessive heating or cooling due to not switching of the heater / cooler in time.
This pulsed heating cooling makes the process safer and a bit slower to heat up / cool down.
The length of the period can be adjusted between 0 and 1000 milliseconds to increase
the efficiency of the process. Be aware that the heat() and cool() will block longer.
- **void heat()** Switches off cooler first, and then switches the heater for (default)
10 milliseconds. Before return the heater is switched off again.
- **void cool()** switch on the cooler for (default) 10 milliseconds. Switches off heater first.
- **void off()** switch off both heater and cooler.
- **void setHeatPulseLength(uint16_t ms = 10)** adjust the timing for heat() and cool().
- The maximum value is 1000 milliseconds == 1 second.
- The minimum value is 0 milliseconds but it would slow down the heating / cooling.
- warning the heat() and cool() will block for the set period.
- **uint16_t getHeatPulseLength()** returns set value.
- **void heat()** switch on the heater for 10 milliseconds.
- **void cool()** switch on the cooler for 10 milliseconds.
- **void off()** switch off all.
#### Debug
@ -196,27 +231,28 @@ Users can patch this function when needed, or make it empty.
- improve documentation
- description of the phases.
- build setup to test
- build hardware setup to test
#### Should
- time of phases should be in seconds ==> breaking change
- **void setAnnealing(float Celsius, float seconds)** Sets temperature and duration.
- investigate the blocking version
- void keepTempTime(temp, time, getTemperature());
- make the 10 milliseconds control pulses configurable (e.g. 10..100 ms)
- investigate continuous heating (unsafe mode)versus the current pulsed heating(safe mode).
#### Could
- PCR scripting language?
- PCR scripting language, simple example?
- add examples
- optimize code
- have an array of times and temperatures to go through.
- stir pin, to control the stirring of the PCR device.
- add continuous heating (unsafe mode) versus the current pulsed heating(safe mode).
- add stir pin, to control the stirring of the PCR device.
- add signalling pin to indicate ready by a buzzer.
- add unit tests
#### Wont
- add callback function when ready (user can check state)

View File

@ -0,0 +1,31 @@
platforms:
rpipico:
board: rp2040:rp2040:rpipico
package: rp2040:rp2040
gcc:
features:
defines:
- ARDUINO_ARCH_RP2040
warnings:
flags:
packages:
rp2040:rp2040:
url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
# - due
# - zero
# - leonardo
- m4
- esp32
- esp8266
# - mega2560
- rpipico
libraries:
- "printHelpers"
- "DS18B20_RT"

View File

@ -0,0 +1,87 @@
//
// FILE: PCR_demo_DS18B20.ino
// AUTHOR: Rob Tillaart
// PURPOSE: PCR demo, using DS18B20 as temperature sensor.
// URL: https://github.com/RobTillaart/PCR
//
// adjust pins to your hardware setup.
#include "PCR.h"
PCR pcr(8, 9); // heatPin, coolPin
// https://github.com/RobTillaart/DS18B20_RT
// http://www.pjrc.com/teensy/td_libs_OneWire
#include "DS18B20.h"
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DS18B20 sensor(&oneWire);
float getTemperature()
{
static float temp = sensor.getTempC();
if (sensor.isConversionComplete())
{
temp = sensor.getTempC();
// request a new measurement.
sensor.requestTemperatures();
}
return temp;
}
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.print("PCR_LIB_VERSION: ");
Serial.println(PCR_LIB_VERSION);
Serial.print("DS18B20_LIB_VERSION: ");
Serial.println(DS18B20_LIB_VERSION);
Serial.println();
// initialize the temperature sensor
sensor.begin();
sensor.setResolution(10); // 10 bit ==> 0.25 degrees precision
sensor.requestTemperatures();
delay(1000); // wait a second to get a first reading.
// configure PCR process
// adjust timing and temperature to your needs.
pcr.setInitial(98, 10000); // temp, ms
pcr.setDenature(94.5, 5000); // temp, ms
pcr.setAnnealing(54.2, 2000); // temp, ms
pcr.setExtension(75.0, 3000); // temp, ms
pcr.setElongation(75.0, 3000); // temp, ms
pcr.setHold(8.0); // temp only
pcr.reset(15); // iterations.
Serial.print("Estimated time (ms): ");
Serial.println(pcr.timeLeft());
// run the PCR process.
while (pcr.iterationsLeft() > 0)
{
float temp = getTemperature();
pcr.process(temp);
}
Serial.println("done");
}
void loop()
{
}
// -- END OF FILE --

View File

@ -0,0 +1,31 @@
platforms:
rpipico:
board: rp2040:rp2040:rpipico
package: rp2040:rp2040
gcc:
features:
defines:
- ARDUINO_ARCH_RP2040
warnings:
flags:
packages:
rp2040:rp2040:
url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
# - due
# - zero
# - leonardo
- m4
- esp32
- esp8266
# - mega2560
- rpipico
libraries:
- "printHelpers"
- "MAX31855_RT"

View File

@ -0,0 +1,76 @@
//
// FILE: PCR_demo_MAX31855.ino
// AUTHOR: Rob Tillaart
// PURPOSE: PCR demo, using a thermocouple MAX31855 as temperature sensor.
// URL: https://github.com/RobTillaart/PCR
//
// adjust pins to your hardware setup.
#include "PCR.h"
PCR pcr(8, 9); // heatPin, coolPin
// https://github.com/RobTillaart/MAX31855_RT
#include "MAX31855.h"
uint8_t selectPin = 7;
MAX31855 thermoCouple(selectPin, &SPI); // HW SPI
float getTemperature()
{
// no status or error handling yet
thermoCouple.read();;
float temp = thermoCouple.getTemperature();
return temp;
}
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.print("PCR_LIB_VERSION: ");
Serial.println(PCR_LIB_VERSION);
Serial.print("MAX31855_VERSION: ");
Serial.println(MAX31855_VERSION);
Serial.println();
// initialize the temperature sensor
SPI.begin();
thermoCouple.begin();
delay(1000); // wait a second to get a first reading.
// configure PCR process
// adjust timing and temperature to your needs.
pcr.setInitial(98, 10000); // temp, ms
pcr.setDenature(94.5, 5000); // temp, ms
pcr.setAnnealing(54.2, 2000); // temp, ms
pcr.setExtension(75.0, 3000); // temp, ms
pcr.setElongation(75.0, 3000); // temp, ms
pcr.setHold(8.0); // temp only
pcr.reset(15); // iterations.
Serial.print("Estimated time (ms): ");
Serial.println(pcr.timeLeft());
// run the PCR process.
while (pcr.iterationsLeft() > 0)
{
float temp = getTemperature();
pcr.process(temp);
}
Serial.println("done");
}
void loop()
{
}
// -- END OF FILE --

View File

@ -0,0 +1,82 @@
//
// FILE: PCR_demo_array.ino
// AUTHOR: Rob Tillaart
// PURPOSE: extended PCR demo, adds initialization, elongation and final hold
// URL: https://github.com/RobTillaart/PCR
//
// adjust timing and temperature.
#include "PCR.h"
// heatPin, coolPin
PCR pcr[3] = { PCR(8, 9), PCR(10, 11), PCR(12, 13) };
float getTemperature(int i)
{
return 65 + i; // just dummy for now.
}
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.print("PCR_LIB_VERSION: ");
Serial.println(PCR_LIB_VERSION);
Serial.println();
// configure all phases
pcr[0].setInitial(98, 10000); // temp, ms
pcr[0].setDenature(94.5, 5000); // temp, ms
pcr[0].setAnnealing(54.2, 2000); // temp, ms
pcr[0].setExtension(75.0, 3000); // temp, ms
pcr[0].setElongation(75.0, 3000); // temp, ms
pcr[0].setHold(8.0); // temp only
pcr[1].setInitial(97, 8000);
pcr[1].setDenature(94, 4000);
pcr[1].setAnnealing(54, 2000);
pcr[1].setExtension(70.0, 2000);
pcr[1].setElongation(70.0, 2000);
pcr[1].setHold(8.0);
pcr[2].setInitial(96, 15000);
pcr[2].setDenature(93, 8000);
pcr[2].setAnnealing(53, 4000);
pcr[2].setExtension(75.0, 4000);
pcr[2].setElongation(75.0, 4000);
pcr[2].setHold(8.0);
pcr[0].reset(15); // iterations.
pcr[1].reset(15);
pcr[2].reset(15);
Serial.print("Estimated time (ms): ");
Serial.print(pcr[0].timeLeft());
Serial.print("\t");
Serial.print(pcr[1].timeLeft());
Serial.print("\t");
Serial.print(pcr[2].timeLeft());
Serial.print("\n");
while (true)
{
for (int i = 0; i < 3; i++)
{
float temp = getTemperature(i);
pcr[i].process(temp);
}
}
Serial.println("done");
}
void loop()
{
}
// -- END OF FILE --

View File

@ -2,7 +2,7 @@
// FILE: PCR_demo_basic.ino
// AUTHOR: Rob Tillaart
// PURPOSE: basic PCR demo
// URL: https://github.com/RobTillaart/ACD10
// URL: https://github.com/RobTillaart/PCR
#include "PCR.h"

View File

@ -2,7 +2,7 @@
// FILE: PCR_demo_extended.ino
// AUTHOR: Rob Tillaart
// PURPOSE: extended PCR demo, adds initialization, elongation and final hold
// URL: https://github.com/RobTillaart/ACD10
// URL: https://github.com/RobTillaart/PCR
//
// adjust timing and temperature.

View File

@ -1,8 +1,8 @@
//
// FILE: PCR_demo_extended.ino
// FILE: PCR_demo_runtime_change.ino
// AUTHOR: Rob Tillaart
// PURPOSE: extended PCR demo, adds initialization, elongation and final hold
// URL: https://github.com/RobTillaart/ACD10
// PURPOSE: PCR demo, changing parameters run time.
// URL: https://github.com/RobTillaart/PCR
//
// adjust timing and temperature.

View File

@ -36,6 +36,8 @@ process KEYWORD2
heat KEYWORD2
cool KEYWORD2
off KEYWORD2
setHeatPulseLength KEYWORD2
getHeatPulseLength KEYWORD2
timeLeft KEYWORD2

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/PCR.git"
},
"version": "0.2.0",
"version": "0.2.1",
"license": "MIT",
"frameworks": "*",
"platforms": "*",

View File

@ -1,10 +1,10 @@
name=PCR
version=0.2.0
version=0.2.1
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino library for PCR process control.
paragraph=polymerase, chain, reaction, denature, annealing, extension.
category=data processing
category=Data Processing
url=https://github.com/RobTillaart/PCR
architectures=*
includes=PCR.h