++++UPDATED, SEE MY LATEST POST++++
Dear community,
I would like to present to you my first draft of an oxygen control system based on an optical oxygen sensor. So far, I couldn't find any ressources about a comparable setup, so I thought, this may be of interest for you. Also, I'm a biologist and thus not a trained coder. Please, if you may, have a look at my code and help me to clean it up and make it stable.
For a university project, I wanted to develop an arduino based system that:
i) measures dissolved oxygen in fish tanks
ii) logs the measurements (for now on SD card)
iii) controls the oxygen concentration by operating a magnetic valve through a relay.
iiii) can run autonomously for several weeks
I was able to borrow an optical oxygen sensor from Pyro-Science (http://www.pyro-science.com/piccolo2-oem-optical-oxygen-meter.html) that uses 5V/3.3V serial communication.
The dissolved oxygen measurement is triggered by sending a measurement command via the TX of the Arduino. Measurements are read equally by sending a read command. The sensor echoes the command and appends the measured oxygen concentration. If the measured oxygen concentration is above a certain threshold (say 60% air saturation), a magnetic valve should be opened which will bubble nitrogen gas into the water.
I did not expect to run into a lot of difficulties as the principle of the system is quite straightforward:
trigger measure -> trigger read -> extract oxygen values from serial data -> save to SD -> trigger relay output -> repeat
I use an Arduino Uno in combination with the W5100 Ethernet shield, the PyroScience Piccolo2 oxygen sensor and a 5V 2-channel relay module for Arduino that controls a 24V magnetic valve.
After some troubleshooting, I can now run a first overnight test.
However, as this system should be implemented in a big fish holding facility, it should be super stable and safe against disturbances. As I'm not very experienced in i) coding and ii) kybernetics etc. I'd like to hear from you, what I could improve. Especially the SD-card logger needs improvement. Also the relay-control (I skripted a very simple proportional control function, tried to implement the PID library but that did somehow not trigger the relay at all).
Thank you in advance and I hope that you may find something interesting in this project.
This is the sketch that I am currently working with
#include <SD.h>
#include <SoftwareSerial.h>
//### SD card stuff ###
const int chipSelect = 4; // chip pin for SD card (UNO: 4; MEGA: 53)
int a = 0; // temporary counter index for SD card data
//### Oxygen optode Stuff ###
SoftwareSerial mySer(6, 7); // RX TX
const byte numChars = 17; // array length for incoming serial data
char receivedChars[numChars]; // array to store the incoming serial data
char airSatStr[6]; // array to store only air saturation readings from receivedChars
float airSat; // variable for air saturation readings
boolean newData = false; // logical operators
boolean emptyBuffer = false;
//### Relay stuff ###
float airSatThreshold = 60.00;// threshold for relay operation
float difference; // variable for difference between air saturation and threshold
float onTime; // time window during which magnetic valve is open based on difference between air saturation and threshold
float offTime; // time window during which magnetic valve is closed
int delayOn; // onTime converted to int for delay
int delayOff; // offTime converted to int for delay
int relayPin = 12; // control pin for relay operation
//### Subfunctions for void loop() ###
//### Sensor specific commands ###
void toggleMeasurement(){
mySer.write("MSR 1");
mySer.write('\r');
emptyBuffer = false;
}
void toggleRead(){
mySer.write("REA 1 3 4");
mySer.write('\r');
}
//### Receiving serial data ###
void recvWithEndMarker() { //receives serial data into array until endmarker is received
static byte ndx = 0;
char endMarker = '\r';
char rc;
while (mySer.available() > 0 && newData == false && emptyBuffer == true) {
rc = mySer.read();
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
}
}
}
void clearBuffer(){ // similar to recvWithEndMarker(), clears serial buffer
static byte ndx = 0;
char endMarker = '\r';
char rc;
while (mySer.available() > 0 && emptyBuffer == false){
rc = mySer.read();
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
emptyBuffer = true;
}
}
}
void extractAirSat(){ // extracts the last 7 characters from serial data array and transforms to float
static byte i = 0;
while(i < 7) {
airSatStr[i] = receivedChars[(10+i)];
i++;
}
i = 0;
airSat = atol(airSatStr);
airSat = airSat/1000;
difference = airSat - airSatThreshold;
}
void showNewData() { // send data to serial monitor
if (newData == true && emptyBuffer == true) {
Serial.println(airSat);
newData = false;
}
}
//### Toggle relay based on measured airSat values ###
void toggleRelay(){
if(difference >= 1.00){
onTime = 1.00 - (1/(difference));
offTime = (1.00 - onTime);
offTime = 1.00-(difference/(difference+1.00));
onTime = 1.00 - offTime;
onTime = onTime * 5000.00;
offTime = offTime * 5000.00;
delayOn = (int)onTime;
delayOff = (int)offTime;
digitalWrite(relayPin, LOW);
delay(delayOn);
digitalWrite(relayPin, HIGH);
delay(delayOff);
}
else digitalWrite(relayPin, HIGH);
delay(5000);
}
//### Log dissolved oxygen measurements to SD card ###
void writeToSD(){
File dataFile = SD.open("DOlog.csv", FILE_WRITE); //create excel file with air saturation values
a=a+1; // increase a by one for each measurement
dataFile.print(a); // print counting index a
dataFile.print(";"); // print a semicolon to separate values
dataFile.println(airSat); // save air saturation value
dataFile.close(); // close file temporarily
}
boolean startSDCard() { // check whether the SD card is ready
boolean result = false;
pinMode(4, OUTPUT); // UNO: 4, MEGA: 53
if (!SD.begin(chipSelect)) //Can the SD card be read?
{
result = false;
}
else //create file like in the loop
{
File dataFile = SD.open("datalog.csv", FILE_WRITE);
if (dataFile)
{
dataFile.close();
result = true;
}
}
return result;
}
//### Setup ###
void setup() {
// if(startSDCard() == true){
Serial.begin(19200);
mySer.begin(19200);
clearBuffer();
pinMode(relayPin, OUTPUT);
// }
}
//### Loop ###
void loop() {
delay(1000);
toggleMeasurement();
delay(100);
clearBuffer();
toggleRead();
delay(100);
recvWithEndMarker();
delay(100);
extractAirSat();
delay(100);
writeToSD();
delay(100);
showNewData();
delay(1000);
toggleRelay();
//### test of relay functionality ###
// digitalWrite(relayPin, LOW);
// delay(1000);
// digitalWrite(relayPin, HIGH);
delay(52500);
}