Go Down

Topic: Arduino controlled chemistry lab build log (or building a smart hood) (Read 439 times) previous topic - next topic

RPCoyle

Quote
computer sends >Target number, low temp,high temp, scale, alert low, alert high, ramp speed logic, etc.
then Arduino > echo back each value attached to its respective routine for the program to verify.
The amount of memory in the Arduino may cause you to hit a wall if your code gets to very complex. In most major applications of this type the majority of the logic is on the computer side. Having the same or similar code on both sides, so that it can be controlled both at the device and at the computer, seem to me redundant, and not worth the effort.

I built a ceramics kiln controller that ramps temperature up and down at a given rate. I enclosed the code that is used on the Arduino.... real simple... the code on the computer runs about 8K lines, including all control logic and a complete computer interface plus live data output and printing of graphs.

Code: [Select]
#include <MAX31855.h>

float tempOut = 0;
int command = 0;
int value;
// Adruino 1.0 pre-defines these variables
//int SCk = 13;
//int MISO = 12;//SO
//int SS = 11;//CS

// Setup the variables we are going to use.
double tempTC, tempCJC;
bool faultOpen, faultShortGND, faultShortVCC, x;
bool temp_unit = 0; // 0 = Celsius, 1 = Fahrenheit
//temp(SCk,CS,SO)
MAX31855 temp(11, 12, 13);

void setup() {
  
  pinMode(2, OUTPUT);      // SSR relay pin out  
  pinMode(9, OUTPUT);      // alarm
 
  Serial.begin(9600);

}

void Average10() { // average 10 readings
 
  float Ave;
  int AveCt ;
  AveCt =0;
  Ave = 0;
  delay(50);
  do {
  
      temp.readMAX31855(&tempTC, &tempCJC, &faultOpen, &faultShortGND, &faultShortVCC, temp_unit);
      Ave = Ave +  tempTC ; // tempTC ;
      AveCt = AveCt + 1;
      delay(10);
     } while  (AveCt < 10 ) ;
 
    tempOut = (Ave/10);
 }

void loop() {
   if ( analogRead(0)> 1021) {          // it's too high turn it off
          digitalWrite(2,LOW);
      }
     value = 0;
     Serial.flush();
     if (Serial.available()) {      // Look for char in serial que and process if found
       command = Serial.read();
     }
      switch (command) {
      case 84 : {                  // If command = "T" turn it on (unless to high)
          digitalWrite(2,HIGH);
       }
      case 67 : {                 // If command = "C" turn it off
          digitalWrite(2,LOW);
      }
      case 68 : {                 //if command ="D" sound tone
        tone(9,220);
      }
      case 69 : {                 //if command ="E" end tone
        noTone(9);
      }
     }
      command = 0;                 // reset command

    
    delay(50);
    Average10(); // take averaged sample
    delay(50);
    Serial.flush();
  
    Serial.print("@- ");
    Serial.print(tempOut);       // output to computer USB port
    Serial.println(" -Tmp ");
 
 }

tbaggins

Awesome reply, that's what I was looking for. Scanning through your code, it looks like you are printing a string to the serial from your program, and using the Arduino to see which ASCII character it is. Basically, the computer does all of the logic and the Arduino turns on or off the SSR, and/or sounds a tone. I like it, it is a simple solution to what I was going to make overly complex.

I have a PID controller that I am using now to control my oil bath (it was sort of my inspiration for the project), and really like it. I also have a controller on my cooling system, but it basically switches the compressor on until it reaches the set point, and then I have to manually turn everything off because the fan to the condenser will keep running (don't like the system as it is).

After reading the paper I posted, I like the idea of fuzzy logic, and I think in essence that is what I will be shooting for in this portion of the program. I have been mulling over what you have said to me about how much work this is going to take, and I see your point. BUT, I think it is worth it. As it is, the lab is just a hobby lab. This project is not really taking me away from anything per se. The goal of everything I am doing is to learn concepts and to experiment with life. Therefore, all of this is worth it, and like you said, the more I know about all of these processes the better. Plus, if it works, it would be an interesting paper to write and try and get published (open source lab control with arduino).

It appears from your code that your using an ADAfruit thermocouple shield, no? It only supports K type thermocouples correct? What hardware are you using to measure temperature? I have two PT100 theromcouples, and I haven't tried attaching them yet. From what I understand, I don't need a shield to use them. Also, what is the general logic scheme for the control (PID?).

Thanks again for the input, I am listening and trying to integrate your suggestions :). You obviously know much more about this than me.

RPCoyle

Quote
After reading the paper I posted, I like the idea of fuzzy logic, and I think in essence that is what I will be shooting for in this portion of the program
AAAAHHHGGG!!!! ???  Please save this until you learn how to program UNFUZZY logic... believe me, you have a hard enough time doing this at the start.

The MAX thermocouple chips come in a variety of flavors, depending on the thermocouple you are looking for. I got my shield on e-bay. If you get one, make sure it has an on-board voltage divider to drop the logic from 5 volts to 3.3 V of so, otherwise you burn up the chip. I don't know what is on a PT100

here is a code fragment from my ancient VB6 program that contains most of the logic for the temperature ramp.

Code: [Select]
Sub Gradiant(StartTemp As Single, StopTemp As Single, Delta As Single, holdtime As Single, MaxPts As Single)
Dim n As Integer

On Error GoTo errx
Ping ' test to see it the connection is alive
StatusLBL.Caption = "Ramp " & RampIdx + 1 & " Running"
' incrament the gradiant array if it is out of bounds and put the last value into it.
 If (RmpCt + RampTimes(RampIdx, 0) > UBound(GradArr)) Then
   ReDim Preserve GradArr(UBound(GradArr) + 1)
   GradArr(UBound(GradArr)) = GradArr(UBound(GradArr) - 1)
 End If
 'if it is way to low or high then the signal may be lost
 If (CurrentTemp < GradArr((RmpCt + RampTimes(RampIdx, 0))) - 50 Or CurrentTemp > MaxRamp + 30) Then
   ToneOn
   PinOutOff 'make sure it's off
   AbortRun ("Temperature out of range ")
 End If
If RunHold = True Then GoTo TestMax

If LastIdx < RampIdx Then
 HighLiteRamp (RampIdx)
 loadRampParams (RampIdx)
 LastIdx = RampIdx
 RmpCt = 0
End If
'********************************** trouble statement ********************************

If RunHold = False And RmpCt + RampTimes(RampIdx, 0) <= UBound(GradArr) Then
'RampTimes...  (0)= ct at start (1)= ct at end (2)ct hold start (3) ct hold end
 TargetTxt = Round(GradArr(RmpCt + (RampTimes(RampIdx, 0))), 0) & " deg"
ElseIf RunHold = False Then
  TargetTxt = Round(GradArr(UBound(GradArr)), 0) & " deg"
End If
'*************************************************************************************
If RampPositive = True Then
   
    If CurrentTemp < StopTemp And (RmpCt) > RampTimes(RampIdx, 1) Then
       PinOutON
        Exit Sub
    End If
 
    If (RmpCt) < UBound(GradArr) - 1 Then
        If CurrentTemp < GradArr((RmpCt + RampTimes(RampIdx, 0))) Then
          PinOutON
        Else
          PinOutOff
        End If
    End If
    If (RmpCt) > UBound(GradArr) Then
       If CurrentTemp < StopTemp Then
          PinOutON
       Else
          PinOutOff
      End If
    End If
    ' the temperature must be over the max for three counts
    If CurrentTemp >= StopTemp Then OverCt = OverCt + 1
      If OverCt >= 3 Then
        HoldStartTime = Val(DurationTBX.Text)
        RunHold = True
        RunRamp = False
        OverCt = 0
       End If
 
 ElseIf RampPositive = False Then
    'If CurrentTemp > MaxRamp + 30 Then AbortRun
    If (ct) < UBound(GradArr) - 1 Then
       If CurrentTemp < GradArr((RmpCt + RampTimes(RampIdx, 0))) Then
           PinOutON
       Else
           PinOutOff
       End If
    End If
    If (ct) > UBound(GradArr) Then
      If CurrentTemp < StopTemp Then
        PinOutON
      Else
        PinOutOff
      End If
    End If
   If CurrentTemp < StopTemp Then OverCt = OverCt + 1
      If OverCt > 1 Then
        HoldStartTime = Val(DurationTBX.Text)
        RunHold = True
        RunRamp = False
        OverCt = 0
      End If
   
 End If
TestMax:
Exit Sub
errx:
 FailErr = True
 PinOutOff
 PinOutOff
 ToneOn
 AbortRun (Err.Description)
 StatusLBL.Caption = Err.Description & " Rct " & RmpCt & " Ubd " & UBound(GradArr) & " sum " & RmpCt + RampTimes(RampIdx, 0)
End Sub

Sub PinOutON()
Dim n As Integer
On Error GoTo e1
If MSComm1.PortOpen = True Then
 Sleep 50 ' try to prevent switch bounce
 MSComm1.Output = "T" & Chr$(13)
 PinOnLBL.Caption = "Elements ON"
 PinOnSHP.FillColor = vbRed
 KWH = KWH + (Amps * Volts * 0.001) / 3600
 KWHtxt.Text = Round(KWH, 2)
End If
Exit Sub
e1:
MSComm1.PortOpen = False
AbortRun ("lost communications")
End Sub

Sub PinOutOff()
Dim n As Integer
Dim str As String
On Error GoTo e1
 If MSComm1.PortOpen = True Then
 Sleep 50 ' try to prevent switch bounce
  MSComm1.Output = "C" & Chr$(13)
  PinOnLBL.Caption = "Elements OFF"
  PinOnSHP.FillColor = &H80808
End If
Exit Sub
e1:
MSComm1.PortOpen = False
AbortRun ("lost communications")
End Sub

 

tbaggins

First, thanks again for the code. I just browsed through it a second ago, i'll have a better look at it in Visual Studio in a bit.

Do you mid elaborating on your comment about fuzzy logic?

Also, I was asking specifically about your thermocouple as well. You are running a kiln, which is probably pretty hot right? What type of probe are you using? The PT100 is a 3 wire Platinum thermocouple, and from what I understand, doesn't need a shield to run it on the arduino.  It is good from -something to 400C. I am using it with my current PID setup and its great.

I haven't really given a lot of thought until now of the dynamics of the distillation procedure. Really, it doesn't matter if the bath is too hot (within reason), because a given component will distill within a set range, all the extra energy will do is to distill the product faster, or possibly blend some components together if their boiling points are close enough (or azeotropes). Once a drip starts, the temperature of the distillate can be used to adjust the temperature of the bath (with it set a certain percentage above (like 10-15C), which is how my bath seems to need to be set from using the PID).

The fuzzy logic is not hard to grasp conceptually, so I am curious to see what you have to say about it. Even though I post on here with big ideas, all of the work I do is done incrementally like I feel you are trying to get me to do. The next steps of my program is just as you suggested, making sure that I can use my probe with arduino, which means testing output against a known temperature, figuring out difference, etc. Then testing communication of the values with the program, then of the program initiating control of the arduino. Then testing of how to optimize bath ramps, and figuring out how to deal commanded vs. actual (error). I just don't post much about these in between steps because they seem like obvious obstacles and don't seem important to log (unless I do something that I think is cool). I hope it doesn't seem like I am not seeing them.


RPCoyle

I have no problem with fuzzy logic, but for the relatively simple things you are trying to do, it seems like an over kill. Also, most people on the forum are not using it, so if you hit a snag, you will limit the amount of help you will get. Learn it by all means if it interests you. Your incremental approach (with LOTS of testing) is really the only valid way to go, especially with potentially flammable and corrosive chemicals. 


I use a K thermocouple and the kiln gets up to 1300 C. I have also used a AD 595 and a voltage divider to drop the output to the Arduino down to within 5 volts. If you don't need great accuracy in the temperature, you can just use the output of the thermocouple and run it through an op amp to boost the gain, and then imbed a lookup table in the setup of the Arduino code.

thirty cent op amp and a couple of resistors would be all you need to set this up.  here is some code I found that I incorporated into another controller that I have used.

Code: [Select]

#include <stdio.h>
#define POINTS_COUNT 65
int command = 0; 
double temp = 0;
void setup() {
 
  Serial.begin(9600);
}

typedef struct {
long temp;
unsigned long microvolts;
} temp_point;

static temp_point thermocouplePoints[] = {
{ 0 , 0 },
{ 10000 , 397 },
{ 20000 , 798 },
{ 30000 , 1203 },
{ 40000 , 1612 },
{ 50000 , 2023 },
{ 60000 , 2436 },
{ 79000 , 3225 },
{ 98000 , 4013 },
{ 116000 , 4756 },
{ 134000 , 5491 },
{ 139000 , 5694 },
{ 155000 , 6339 },
{ 172000 , 7021 },
{ 193000 , 7859 },
{ 212000 , 8619 },
{ 231000 , 9383 },
{ 250000 , 10153 },
{ 269000 , 10930 },
{ 288000 , 11712 },
{ 307000 , 12499 },
{ 326000 , 13290 },
{ 345000 , 14084 },
{ 364000 , 14881 },
{ 383000 , 15680 },
{ 402000 , 16482 },
{ 421000 , 17285 },
{ 440000 , 18091 },
{ 459000 , 18898 },
{ 478000 , 19707 },
{ 497000 , 20516 },
{ 516000 , 21326 },
{ 535000 , 22137 },
{ 554000 , 22947 },
{ 573000 , 23757 },
{ 592000 , 24565 },
{ 611000 , 25373 },
{ 630000 , 26179 },
{ 649000 , 26983 },
{ 668000 , 27784 },
{ 687000 , 28584 },
{ 706000 , 29380 },
{ 725000 , 30174 },
{ 744000 , 30964 },
{ 763000 , 31752 },
{ 782000 , 32536 },
{ 801000 , 33316 },
{ 820000 , 34093 },
{ 839000 , 34867 },
{ 858000 , 35637 },
{ 877000 , 36403 },
{ 896000 , 37166 },
{ 915000 , 37925 },
{ 934000 , 38680 },
{ 953000 , 39432 },
{ 972000 , 40180 },
{ 991000 , 40924 },
{ 1010000 , 41665 },
{ 1029000 , 42402 },
{ 1048000 , 43134 },
{ 1067000 , 43863 },
{ 1086000 , 44588 },
{ 1105000 , 45308 },
{ 1124000 , 46024 },
{ 1143000 , 46735 },
{ 1200000 , 48838 }
};

static inline unsigned long interpolate(unsigned long val, unsigned long rangeStart, unsigned long rangeEnd, unsigned long valStart, unsigned long valEnd) {
    return rangeStart + (rangeEnd - rangeStart) * (val - valStart) / (valEnd - valStart);
}

static inline unsigned long interpolateVoltage(unsigned long temp, unsigned char i){
    return interpolate(temp, thermocouplePoints[i-1].microvolts, thermocouplePoints[i].microvolts, thermocouplePoints[i-1].temp, thermocouplePoints[i].temp);
}

static inline unsigned long interpolateTemperature(unsigned long microvolts, unsigned char i){
    return interpolate(microvolts, thermocouplePoints[i-1].temp, thermocouplePoints[i].temp, thermocouplePoints[i-1].microvolts, thermocouplePoints[i].microvolts);
}

/**
 * Returns the index of the first point whose temperature value is greater than argument
 **/
static inline unsigned char searchTemp(unsigned long temp) {
unsigned char i;
for(i = 0; i < POINTS_COUNT; i++) {
if(thermocouplePoints[i].temp > temp) {
return i;
}
}
return POINTS_COUNT-1;
}

/**
 * Returns the index of the first point whose microvolts value is greater than argument
 **/
static inline unsigned char searchMicrovolts(unsigned long microvolts) {
unsigned char i;
for(i = 0; i < POINTS_COUNT; i++) {
if(thermocouplePoints[i].microvolts > microvolts) {
return i;
}
}
return POINTS_COUNT-1;
}

/**
 * Returns temperature as a function of the ambient temperature and measured thermocouple voltage.
 * Currently only positive ambient temperature is supported
 **/
long thermocoupleConvertWithCJCompensation(unsigned long microvoltsMeasured, unsigned long ambient) {
//convert ambient temp to microvolts
//and add them to the thermocouple measured microvolts
unsigned long microvolts = microvoltsMeasured + interpolateVoltage(ambient, searchTemp(ambient));
//look up microvolts in The Table and interpolate
return interpolateTemperature(microvolts, searchMicrovolts(microvolts));
}

/**
 * Returns temperature, equivalent to the voltage provided in microvolts
 */
long thermocoupleMvToC(unsigned long microvolts) {
return interpolateTemperature(microvolts, searchMicrovolts(microvolts));
}

/******************************************************************************
 * Additional info
 * ****************************************************************************
 * Changelog:
 * - v. 1.0 (initial release) 2014-04-24 by Albertas Mick├ä--nas mic@wemakethings.net
 *
 * ****************************************************************************
 * Bugs, feedback, questions and modifications can be posted on the github page
 * on https://github.com/Miceuz/k-thermocouple-lib/
 * ****************************************************************************
 * - LICENSE -
 * GNU GPL v2 (http://www.gnu.org/licenses/gpl.txt)
 * This program is free software. You can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 * ****************************************************************************
 */




void loop() {
  unsigned long x;
   int sensorValue = analogRead(A0);
  // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
  float voltage = sensorValue * (5.0 / 1023.0);
  voltage =10.1744*voltage - 0.8191;
   if ( analogRead(0)> 1021) {          // it's too high turn it off
          digitalWrite(13,LOW);
      }
   
     Serial.flush();
     if (Serial.available()) {      // Look for char in serial que and process if found
      command = Serial.read();
      if (command == 84 & analogRead(0)< 1021) { // If command = "T" turn it on (unless to high)
          digitalWrite(13,HIGH);
         // delay (500); // keep it on for at least half a sec to prevent bounce
      }
      if (command == 67) {          // If command = "C" turn it off
          digitalWrite(13,LOW);
         // delay(500); // keep it off for at least half a second to prevent bounce
      }
      if (command==68){                 //if command ="D" sound tone
        tone(9,220);
      }
      if (command==69){                 //if command ="E" end tone
        noTone(9);
      }
          command = 0;                 // reset command
   }
    delay(100);
   
    Serial.flush();
   
    delay(100);
    temp= thermocoupleMvToC(voltage);
    if (temp <=0)  {temp = 0;} // if it goes negative just utput 0
    Serial.print("@- ");  // output to computer USB port
    Serial.print(temp);
    Serial.println(" -Tmp ");
  }
 

Go Up