Get your HBT Growlers, Shirts and Membership before the Rush!


Home Brew Forums > Home Brewing Beer > Automated Brewing Forum > PID settings on arduino based hlt
Reply
 
LinkBack Thread Tools
Old 09-16-2012, 06:40 AM   #51
chuckjaxfl
Feedback Score: 1 reviews
Recipes 
 
Join Date: Feb 2010
Location: Jacksonville, FL
Posts: 327
Liked 19 Times on 12 Posts
Likes Given: 23

Default

Quote:
Originally Posted by cwi View Post
Personally, I think that on/off, or proportional if you want temps tighter than an on/off controller will provide, is more than sufficient for an HLT.
And I think I'm finally joining you on that.
__________________
chuckjaxfl is offline
 
Reply With Quote Quick reply to this message
Old 09-26-2012, 09:08 PM   #52
alien
Feedback Score: 1 reviews
Recipes 
 
Join Date: Apr 2012
Location: Philadelphia, PA
Posts: 1,235
Liked 65 Times on 58 Posts
Likes Given: 61

Default

Nice graphs! (in the link)

__________________
alien is offline
 
Reply With Quote Quick reply to this message
Old 11-03-2012, 05:05 AM   #53
chuckjaxfl
Feedback Score: 1 reviews
Recipes 
 
Join Date: Feb 2010
Location: Jacksonville, FL
Posts: 327
Liked 19 Times on 12 Posts
Likes Given: 23

Default

It just occurred to me that I never actually shared this part of the code:
FWIW, my settings are: 102.89, 0.5, 0.0.

Also, I think some of my changes have made the constrain line irrelevant, but it's also not hurting anything so I didn't take it out.

Code:
  if (mode ==0)
  {
    //compute output
    byte Course=8;
    byte Fine=2;

    if (Input<(Setpoint-Course)) Output=WindowSize;                                                                       
    //Output is 100% until within Course degrees of setpoint

    else if ((Input>=Setpoint-Course)&&(Input<Setpoint-Fine)) Output=(((Setpoint-Input)/Course)*WindowSize);    
    //This starts reducing power at Course degrees away from
    //the setpoint, so that the temperature is closer to stable before the PID takes over

    else if ((Input>=Setpoint-Fine)&&(Input<Setpoint+Fine))  myPID.Compute();    
    //use the PID library to determine an Output, 
    //if temp is in the fine control band

    else if (Input>=Setpoint+Fine) Output=0;                                                                                    
    //a killswitch in case overshoot gets out of hand

    Output = constrain(Output,0,WindowSize);
    //To elimate the possibility of the greater-than-WindowSize 
    //Output from the offset in the Course band

    OutputPercent = 100*Output/WindowSize;
    if ((millis()-markTime)>WindowSize) markTime=millis();
    if ((millis()-markTime)<Output) digitalWrite(RelayPin,HIGH);
    if ((millis()-markTime)>Output) digitalWrite(RelayPin,LOW);
  }
__________________
chuckjaxfl is offline
2
People Like This 
Reply With Quote Quick reply to this message
Old 11-03-2012, 05:07 AM   #54
chuckjaxfl
Feedback Score: 1 reviews
Recipes 
 
Join Date: Feb 2010
Location: Jacksonville, FL
Posts: 327
Liked 19 Times on 12 Posts
Likes Given: 23

Default

And here's how it turned out:



__________________
chuckjaxfl is offline
 
Reply With Quote Quick reply to this message
Old 11-06-2012, 03:56 AM   #55
SimBrew
HBT_SUPPORTER.png
Feedback Score: 0 reviews
Recipes 
 
Join Date: Dec 2010
Location: Montréal, Quebec
Posts: 94
Default

What your are doing is fuzzy logic.
Your choosing a list of criteria that seem logical to your setup.
With the kid I didn't get the time to play with my PID.

Your solution is actually the best result I saw from an arduino control. And it don't go over you setpoint, that is the important part for us brewer as we can heat fast but it take an hour to loose one degre.

But it could be better, you seem to begin to reduce the output too soon.
To find when you should start cutting on the output, start the log, heat 100% from ~120 to ~150 and stop heating, look how far from 150 you go after and that's should be a little over the point where you should start cutting on the heat.

That should reduce your heating time, and that the second most important thing in homebrewing.

By reading all your reply to my post, I understand that the autotune lib is using Z-N in close loop, so basicly raising the gain until the system oscillate. That is a tuning method for pretty fast lineard system.

Our systems are far from linear. And normal system have use for pid as it will stop changing the output at point where the temp doesn't even move anymore. Our system need the temp bascilly turned off when we reach the setpoint.

I think I'll try to use someting like you code as it's now what make the more sens to me.

I'll let you know, just give some time, again.

__________________
SimBrew is offline
 
Reply With Quote Quick reply to this message
Old 11-06-2012, 01:37 PM   #56
crane
Feedback Score: 0 reviews
Recipes 
 
Join Date: Sep 2011
Location: San Diego, CA
Posts: 319
Liked 34 Times on 27 Posts
Likes Given: 1

Default

I wouldn't call this fuzzy logic. This is just a piecewise algorithm. Fuzzy logic would implement something like a piecewise algorithm based on the weighted distribution of multiple inputs. You would have to take into consideration not just how far you are from the setpoint but also how fast the input is changing and what the current integral error is. Depending on how you weight each of those you would choose a different algorithm to maintain optimal control. Therefore with fuzzy logic you would have multiple instances where you are X degrees from the setpoint but you may do different things based on how fast the input is changing, etc.

Quote:
Originally Posted by chuckjaxfl
if ((millis()-markTime)<Output) digitalWrite(RelayPin,HIGH);
if ((millis()-markTime)>Output) digitalWrite(RelayPin,LOW);
}
One more thing. The above code isn't as efficient as it could be and also contains a corner case. If millis- markTime == Output nothing happens. DigitalWrite doesn't get called in that corner case and the pin will retain its previous state. It's not a big deal as it only happens for one ms, but it could be written more cleanly and efficiently.

if ((millis()-markTime)<Output) { digitalWrite(RelayPin,HIGH);
} else {
digitalWrite(RelayPin,LOW);
}
__________________
crane is online now
 
Reply With Quote Quick reply to this message
Old 11-07-2012, 03:43 PM   #57
alien
Feedback Score: 1 reviews
Recipes 
 
Join Date: Apr 2012
Location: Philadelphia, PA
Posts: 1,235
Liked 65 Times on 58 Posts
Likes Given: 61

Default

Seems like a really sensible algorithm (cooling and heating *are* asymmetric) and the results look great.

alien is offline
 
Reply With Quote Quick reply to this message
Old 11-08-2012, 05:58 AM   #58
chuckjaxfl
Feedback Score: 1 reviews
Recipes 
 
Join Date: Feb 2010
Location: Jacksonville, FL
Posts: 327
Liked 19 Times on 12 Posts
Likes Given: 23

Default

Quote:
Originally Posted by SimBrew View Post
But it could be better, you seem to begin to reduce the output too soon.
You would think so, wouldn't you? However, I did some tests setting "coarse" to 6 and 7. In my system, I need to back off at 8. Any more, and I start to overshoot.

Quote:
Originally Posted by SimBrew View Post
To find when you should start cutting on the output, start the log, heat 100% from ~120 to ~150 and stop heating, look how far from 150 you go after and that's should be a little over the point where you should start cutting on the heat.
Which is exactly what I did first. Then I took a day, basically, just trial-and-error all day long with different settings. With the code written as it is now, though, someone else could take it and do it much quicker. Start with ambient temp water, set the temp to 100 and watch the curve. Adjust the two variables, upload the sketch, and set it for 110. Repeat until you're happy, or the pot is boiling.

Quote:
Originally Posted by SimBrew View Post
I think I'll try to use someting like you code as it's now what make the more sens to me.

I'll let you know, just give some time, again.
Please let me know, if you end up using it. It took me a long time to figure out what I *should* have learned by reading mathos' sketch back at the beginning. I just didn't know enough to make use of the information right in front of me.



Quote:
Originally Posted by crane View Post
One more thing. The above code isn't as efficient as it could be and also contains a corner case.
Oops.. you got me!

I actually noted that after reading some other tutorials, but didn't change the sketch on this machine. I was working on that sketch on the laptop out in the garage. The code actually, finally, (I think) wound up as:

Code:
if ((millis()-markTime)>WindowSize) markTime=millis();
if ((millis()-markTime)<=Output) digitalWrite(RelayPin,HIGH);
else digitalWrite(RelayPin,LOW);
__________________
chuckjaxfl is offline
 
Reply With Quote Quick reply to this message
Old 01-06-2013, 12:21 AM   #59
jbrewkeggin
Feedback Score: 0 reviews
Recipes 
 
Join Date: Mar 2012
Location: Baltimore, MD
Posts: 29
Liked 1 Times on 1 Posts

Default

Hey all,

Just wanted to post some info about my Arduino based PID controller that I've been working on. The Arduino is controlled by an old laptop running a Python GUI that I made for pump controls and for setting a target temperature. I decided not to use the autotune library after reading some of the comments in this thread. I'm going to set my constants through trial and error like I would with an off the shelf PID controller. I tested the control loop a few times by duct taping my temperature probes to a fermentation heat belt. I used DS18b20 temperature sensors and they attach to my controller box via DB9 connectors.

Here's the Arduino code. Ignore the PID settings, since I plan to tweak those as I learn more from my data:

Code:
/* jbrewduino */
/* Developed by Jordan Kagan */ 

// This Arduino sketch reads serves 3 purposes:
// 1. Toggle On/Off a HLT recirculation pump 
// 2. Toggle On/Off a Mash recirculation + sparge pump
// 3. Read input from DS18B20 "1-Wire" digital temperature sensors 
// and control attached heating element based on temperature feed back.
// This will be accomplished via a PID control loop.

// Valid messages are as follows from the Python program:
// "1" means toggle pump 1 off/on
// "2" means toggle pump 2 off/on
// "3" means toggle heating element off/on
// Codes 80 - 212 are reserved for fahrenheit temperatures
//#define DEBUGG
// All inputs are provided by a Python brewing GUI that transmits serial messages.
#include <Time.h>
#include <Wire.h>
#include <PID_v1.h>
#include <OneWire.h> /* for reading 1-wire bytes */
#include <DallasTemperature.h> /* For parsing and printing temp from DS18B20 bytes */
#ifdef HAVE_SD
#include <SD.h>
#endif
// Data wire is plugged into pin 3 on the Arduino
#define ONE_WIRE_BUS 3

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
#ifdef HAVE_SD
File dataFile;
char filename[40];
#endif
// Assign the addresses of your 1-Wire temp sensors.

DeviceAddress mashTemp = { 0x28, 0x9E, 0xAC, 0x08, 0x04, 0x00, 0x00, 0x2C };
DeviceAddress hltTemp = { 0x28, 0xFB, 0x1E, 0x15, 0x04, 0x00, 0x00, 0x6D };
String inputString = ""; /* input with temperature from Python GUI */
int newTarget = 0;
int first_write = 0;
int temp = 0;
int bytesSent = 0;
const int heatingPin = 6; /* pin in which the heating element is attached - PID output pin */
int tempLoop = 0; /* This is a global run flag for the temperature feedback loop */
/* We need a valid temperature string because we can also receive messages for toggling 
 * the heating elements and pumps */
int validTemp = 0; 
// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
#ifdef HAVE_SD
const int chipSelect = 4;
#endif
//PID Constants
int k_prop = 500;
int k_int = 0;
int k_dif = 0;

double Setpoint, Input, Output; /* PID variables */

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,k_prop,k_int,k_dif, DIRECT);
int WindowSize = 1000; /* PID adjusts output between 0 and this window size*/

unsigned long windowStartTime;
unsigned long file_start;

//pumps
const int pumpOne = 5;
const int pumpTwo = 7;
int pumpOneOn = 0;
int pumpTwoOn = 0;

void setup(void)
{
  
  
  pinMode(heatingPin, OUTPUT);
  pinMode(pumpOne, OUTPUT);
  pinMode(pumpTwo, OUTPUT); 
  // start serial port
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  
  // Start up the library
  sensors.begin();
  // set the resolution to 10 bit (good enough?)
  sensors.setResolution(mashTemp, 10);
  sensors.setResolution(hltTemp, 10);
  inputString.reserve(30);
  
  //Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
  
  // see if the card is present and can be initialized:
#ifdef HAVE_SD  
  if (!SD.begin(chipSelect)) {
    //Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
#endif
  //Serial.println("card initialized.");

  windowStartTime = millis();
 
}

/* function that prints temperatures in human readable form */
void printTemperature(DeviceAddress deviceAddress)
{
  if(first_write == 0) {
    file_start = millis();
    first_write = 1;
  }
  float tempC = sensors.getTempC(deviceAddress);
  
  if (tempC == -127.00) {
    Serial.print("Error getting temperature");
  } else {
    //Serial.print("C: ");
    //Serial.print(tempC);
      Input = DallasTemperature::toFahrenheit(tempC);

    if (deviceAddress == mashTemp){
#ifdef HAVE_SD
      dataFile.print("MASH");
#endif
#ifdef DEBUGG
      Serial.print("m");
      Serial.print(Input);
      Serial.print("\n");
#endif
      bytesSent = Serial.write("m");
      bytesSent += Serial.write(Serial.print(Input, 2));
      bytesSent += Serial.write("\n");
    }
    if (deviceAddress == hltTemp) {
#ifdef HAVE_SD
      dataFile.print("HLT");
#endif
#ifdef DEBUGG
      Serial.print("h");
      Serial.print(Input);
      Serial.print("\n");
#endif
      bytesSent += Serial.write("h");
      bytesSent += Serial.write(Serial.print(Input,2));
      bytesSent += Serial.write("\n");
      
    }
#ifdef HAVE_SD
    dataFile.print(",");
    dataFile.print(Input);
    dataFile.print(",");
    dataFile.print(millis() - file_start);
    dataFile.print(",");
    dataFile.println(Output);
#endif
  }
}

void temperatureControl(){
  sensors.requestTemperatures();
  //Serial.print("HLT temperature is: ");
  printTemperature(hltTemp);  
 
      
  //Serial.print("Mash temperature is: ");
  printTemperature(mashTemp); 
  
  myPID.Compute();/* This must be called once per loop to compute the new output*/
  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if(millis() - windowStartTime>WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if(Output > millis() - windowStartTime) {
#ifdef DEBUGG    
    Serial.println("turning heating element ON");
#endif
    Serial.write("HE1");
    Serial.write("\n");
    digitalWrite(heatingPin,HIGH); 
  }
  else {
#ifdef DEBUGG
    Serial.println("turning heating element OFF");
#endif
    Serial.write("HE0");
    Serial.write("\n");
    digitalWrite(heatingPin,LOW); 
    
    //Serial.println("turning heating element off");
  }
  
}

/*Loop that receives input temperature from Python program */
void receiveInput() {
  
  while (Serial.available() > 0) {
    /* once we have input we can start temp control loop */
    tempLoop = 1;
    // get the new byte
    char inChar = (char)Serial.read(); 
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      //Serial.println(inputString); 
      newTarget = inputString.toInt();
      if (newTarget > 60 && newTarget < 215) {
#ifdef HAVE_SD
        if(dataFile) {
          dataFile.close();
        }
        sprintf(filename, "LOG-%02d%02d.csv", hour(), minute(), second());
        //Serial.println(filename);
        dataFile = SD.open(filename, FILE_WRITE);
        if(dataFile) {
          //Serial.println("New file opened successfully!");
          dataFile.print("kp = ");
          dataFile.print(k_prop);
          dataFile.print(", ki = ");
          dataFile.print(k_int);
          dataFile.print(", kd = ");
          dataFile.print(k_dif);
          
        } else {
          //Serial.println("Issue opening file..."); 
        }
#endif
        validTemp = 1;
        Setpoint = newTarget;
#ifdef HAVE_SD
        dataFile.print(", SP = ");
        dataFile.println(Setpoint);
#endif
        //tell the PID to range between 0 and the full window size
        myPID.SetOutputLimits(0, WindowSize); /* vary its output within a given range(1 secs here) */  
        //turn the PID on
        myPID.SetMode(AUTOMATIC); /*PID is on, MANUAL for off*/ 
        
      }
      else if (newTarget == 3) {
#ifdef DEBUGG
        Serial.println("Received Start/Stop Message!");
#endif        
        if (validTemp == 1) {
#ifdef HAVE_SD
          dataFile.close();
#endif
          Serial.write("HE0");
          Serial.write("\n");
          digitalWrite(heatingPin, LOW);
          validTemp = 0;
        }
        else {
#ifdef HAVE_SD      
          dataFile = SD.open(filename, FILE_WRITE);
#endif    
          Serial.write("HE1");
          Serial.write("\n");
          digitalWrite(heatingPin, HIGH);
          validTemp = 1;
        }
      } 
      else if (newTarget == 1) {
        if (pumpOneOn == 0) {
#ifdef DEBUGG 
          Serial.println("Turning pump 1 on");
#endif
          digitalWrite(pumpOne, HIGH);
          pumpOneOn = 1;
        }
        else {
#ifdef DEBUGG 
          Serial.println("Turning pump 1 off");
#endif
          digitalWrite(pumpOne, LOW);
          pumpOneOn = 0;
        }
      } 
      else if (newTarget == 2) {

        if (pumpTwoOn == 0) {
#ifdef DEBUGG 
          Serial.println("Turning pump 2 on");
#endif
          digitalWrite(pumpTwo, HIGH);
          pumpTwoOn = 1;
        }
        else {
#ifdef DEBUGG 
          Serial.println("Turning pump 2 off");
#endif
          digitalWrite(pumpTwo, LOW);
          pumpTwoOn = 0;
        }  
        
      } 
      inputString = "";
    } /* if (inChar == '\n') */  
  
  } /* while (Serial.available() > 0) */    
}

void loop(void)
{ 
  receiveInput();  
  if(tempLoop == 1 && validTemp == 1)
    temperatureControl();
  
}/* void loop */
Feel free to critique this or let me know that I need to change something.

Thanks!
2012-07-12_22-30-25_86.jpg   2012-11-14_20-21-40_362.jpg   2012-12-15_18-15-35_41.jpg   2012-12-15_18-18-52_448.jpg   resizedimage_1357422768396.jpg  

__________________
jbrewkeggin is offline
 
Reply With Quote Quick reply to this message
Old 01-06-2013, 04:29 AM   #60
helibrewer
HBT_SUPPORTER.png
Feedback Score: 1 reviews
 
helibrewer's Avatar
Recipes 
 
Join Date: Nov 2011
Location: Santa Rosa, CA
Posts: 3,570
Liked 260 Times on 226 Posts
Likes Given: 70

Default

I think you should send that last picture to your Neighborhood Watch Your ingenuity is awesome

__________________
Something is always fermenting....
"It's Bahl Hornin'"

Primary:
Brite Tank/Lagering:
Kegged: Sour Saison, Pale Ale, Aggie Ale
Bottled: Belgian Quad (Grand Reserve), Derangement (Belgian Dark Strong)
On Deck: Firestone DBA, De Koninck Blonde
My Site: www.restlesscellars.com
helibrewer is offline
 
Reply With Quote Quick reply to this message
Reply



Quick Reply
Message:
Options
Thread Tools




Newest Threads

LATEST SPONSOR DEALS