Fermentation Riot All Grain Beer Brewing

DIY Arduino PID and Web Reporting Temp Control

Maybe you have already created a fermentation temperature controller but now you want it to be PID controlled and have the data displayed on your phone real time? Here is a tutorial to create your own arduino based PID controller unit that sends all of its data to a web server. Also included is the HTML/PHP required to pull up the graphs of your data to view it real time.

Shown below is the web based dashboard which will be created from data collected by the arduino. This can be viewed from your PC or phone. The web page updates itself every 30 seconds so it is always up to date. You can see by the chart below that when I pulled the graph I had turned on the heater about 86 minutes prior. Although further work could be done to tune the PID and reduce the variability (although it already is controlling to +/- 0.5F).

Arduino PID and Web Reporting

In this example I am using a light source as the heat which is run off a solid state relay that is triggered by output from the arduino.


However, the only purpose of using the light was to show that the code worked. I am still putting my plans together for my next upgrade but wanted to pass along the code for other brewers who might want to use bits and pieces of what I have used. My Solid State relay is rated for 40Amps as I want to be sure it can handle the load from an 240V heating element.

The Hardware:

Arduino Uno
Ethernet Board, Ethernet Cable, and Working Ethernet Router
Breadboard
Potentiometer
DS18B20 Temperature Sensor & 4.7k ohm resistor
Solid State Relay - 30Amp triggered by 3-32vdc
And some wiring

The Software:

You will need either a home server setup or an external hosting account (domain) to send the data to in the form of a MYSQL table.

Some form of HTML knowledge / PHP may also be beneficial.

 

How To:

1.) Setup Your Arduino and Download the Arduino Software Here. Install the Ethernet shield on top of the arduino board.

2.) Also download the following libraries and place the downloads into the libraries folder: Ethernet.h, OneWire.h, SPI.h, andPID_v1.h

3.) Setup Your Arduino Based on the Picture Below:

Arduino PID Temp Control

Potentiometer - This will be used to adjust setpoint. Place the middle sense pin to analog pin 5 connect the potentiometer to ground and 5v on the other pins.

Solid State Relay - Connect relay pin 6 to the (+) DC side of the SSR. Connect ground to the (-) side. Connect your power source to the other side of the relay. As always be careful with contacting that power side (its hot).

Temp Sensor DS18B20 - Connect as shown by the diagram below:

DS18b20 SensorDS18B20 Temp Controller

Connect Pin 1 to Ground.
Connect Pin 2 to Arduino Digital 2 (this will send the temperature to the arduino)
Connect Pin 3 to 5V
The most important part is to place a 4.7kohm resistor from pin 2 to pin 3

That's it for the hardware side!


Download the following code into the arduino:

Update the following when before uploading to arduino (I've highlighted those bold):

mac address: Enter your specific mac address of your Ethernet shield
IP Server: Enter the IP where the php file will be located.
client.print: change the http://www.example.com to your website.



#include <OneWire.h>
#include <SPI.h>
#include <Ethernet.h>
#include <PID_v1.h>
#define RelayPin 6

int DS18S20_Pin = 2; //DS18S20 Signal pin on digital 2
double Setpoint;
double Input, Output;

int sensorPin = A5; // select the input pin for the potentiometer
int sensorValue = 0; // variable to store the value coming from the sensor

//Specify the links and initial tuning parameters

float Kp = 200;
float Ki = 50;
float Kd = 0;

PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 2000;
unsigned long windowStartTime;

const int numReadings = 30;

double readings[numReadings]; // the readings from the analog input
int index = 0; // the index of the current reading
double total = 0; // the running total
double input = 0; // the average

int inputPin = A0;

//Temperature chip i/o
OneWire ds(DS18S20_Pin); // on digital pin 10
// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = { 0x90, 0xa4, 0xdr, 0x00, 0xa5, 0x59 }; //I made one up here it should be listed on your ethernet shield box
IPAddress server(184, 188, 16, 158); // place your server IP address here, I made on up

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;

void setup(void) {


pinMode(6, OUTPUT);
windowStartTime = millis();
Serial.begin(9600);
Setpoint = 71;
//tell the PID to range between 0 and the full window size
myPID.SetOutputLimits(0, WindowSize);

//turn the PID on
myPID.SetMode(AUTOMATIC);

for (int thisReading = 0; thisReading < numReadings; thisReading++)
readings[thisReading] = 0;

analogReference(EXTERNAL);
}

void loop(void) {
analogReference(DEFAULT);
sensorValue = analogRead(sensorPin)/7.42+75;
Setpoint = sensorValue;
analogReference(EXTERNAL);
total= total - readings[index];
float temperature = getTemp()*9/5+32;
readings[index] = temperature;
total= total + readings[index];

index = index + 1;

// if we're at the end of the array...
if (index >= numReadings)
// ...wrap around to the beginning:
index = 0;

// calculate the average:
Input = total / numReadings;
// send it to the computer as ASCII digits

myPID.Compute();


Serial.print(Input*1);
Serial.print(" ");
Serial.print(Output);
Serial.print(" ");
Serial.print(Setpoint);
Serial.print(" ");
Serial.print(index);
Serial.print(" ");
Serial.print(numReadings);
Serial.print(" ");
Serial.print(total);
Serial.print(" ");
Serial.print(readings[index]);
Serial.print(" ");

Serial.println(temperature);

 

/************************************************
* 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) digitalWrite(RelayPin,LOW);
else digitalWrite(RelayPin,HIGH);

if (index == 29)
{

(Ethernet.begin(mac));
// give the Ethernet shield a second to initialize:
delay(1000);
Serial.println("connecting...");

// if you get a connection, report back via serial:
(client.connect(server, 80));
Serial.println("connected");
// Make a HTTP request:
client.print("GET http://example.com/data.php?Temperature="); //place your server address here
client.print(temperature);
client.print("&Setpoint=");
client.print(Setpoint);
client.print("&Input=");
client.print(Input);
client.print("&Kp=");
client.print(Kp);
client.print("&Ki=");
client.print(Ki);
client.print("&Kd=");
client.print(Kd);
client.print("&Heater=");
client.print(Output/WindowSize*100);
client.println(" HTTP/1.0");
client.println("Host: http://example.com");
client.println();
client.stop();
}
}

float getTemp(){
//returns the temperature from one DS18S20 in DEG Celsius

byte data[12];
byte addr[8];

if ( !ds.search(addr)) {
//no more sensors on chain, reset search
ds.reset_search();
return -1000;
}

if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return -1000;
}

if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Device is not recognized");
return -1000;
}

ds.reset();
ds.select(addr);
ds.write(0x44,1); // start conversion, with parasite power on at the end

delay(1000);

byte present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad

for (int i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
}

ds.reset_search();

byte MSB = data[1];
byte LSB = data[0];

float tempRead = ((MSB << 8) | LSB); //using two's compliment
float TemperatureSum = tempRead / 16;

return TemperatureSum;

}


Now we need to create an SQL database and create a table with the following column structure:

Name the table "temp".

SQL table



The arduino is configured to send a command to a PHP file which is located on the main index of your webpage. The arduino will talk with this code to send data to your MYSQL database.

Create a PHP file on your web server named "data.php" which the arduino will talk to as shown below and make sure to change the bolded parameters for your website:


<?php

$dayz = time();
$aa = (($_GET['Temperature']));
$ab = (($_GET['Setpoint']));
$ac = (($_GET['Input']));
$ad = (($_GET['Kp']));
$ae = (($_GET['Ki']));
$af = (($_GET['Kd']));
$ag = (($_GET['Heater']));

$db="example_data";
$link = mysql_connect('', 'userid_1', 'userid1_password');
if (! $link) die(mysql_error());
mysql_select_db($db , $link) or die("Couldn't open $db: ".mysql_error());

$queryResult = mysql_query("INSERT INTO temp (Time, Temp, Setpoint, Input, Kp, Ki, Kd, Heat) VALUES ('$dayz', '$aa', '$ab', '$ac', '$ad', '$ae', '$af', '$ag')");

?>


And now for the fun part - the PHP code which allows you to view the data collected in the MYSQL table...

Create a "dashboard.php" file for the code shown below and change all the bolded parameters to match your server information. Save this wherever you like as this is the page that will be viewed to see your data. It will refresh every 30 seconds as based on <meta http-equiv="refresh" content="30">.


<html>
<head>
<meta http-equiv="refresh" content="30">
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Time');
data.addColumn('number', 'Temp');
data.addColumn('number', 'Input');
data.addColumn('number', 'Setpoint');

data.addRows([
<?php

$db="example_database";
$link = mysql_connect('', 'user1_ID', 'user1_password');
mysql_query('SET NAMES utf8');
mysql_select_db($db , $link) or die("Couldn't open $db: ".mysql_error());

// Retrieve all the data from the "example" table
$result = mysql_query("SELECT TIMESTAMPDIFF(MINUTE,FROM_UNIXTIME(Time), now()) as Time1, Temp, Setpoint, Input FROM temp WHERE TIMESTAMPDIFF(MINUTE,FROM_UNIXTIME(Time), now()) < 120 Order By Time");
if ($result !== false) {
$num=mysql_numrows($result);

$i=0;

echo"";

while ($i < $num) {

$time=mysql_result($result,$i,"Time1");
$temp=mysql_result($result,$i,"Temp");
$input=mysql_result($result,$i,"Input");
$setpoint=mysql_result($result,$i,"Setpoint");

echo "['";
echo "$time";
echo "',";
echo "$temp";
echo ",";
echo "$input";
echo ",";
echo "$setpoint";
echo "]";
if ($i < ($num - 1))
{
echo ",";
}
$i++;


}
}

 

 

?>
]);

var options = {
width: 1000, height: 300,
hAxis: {title: 'Minutes Ago'},
vAxis: {title: 'Temperature F', maxValue: 100, minValue: 40}
};

var chart = new google.visualization.LineChart(document.getElementById('chart_div1'));
chart.draw(data, options);
}
</script>

<script type='text/javascript' src='https://www.google.com/jsapi'></script>
<script type='text/javascript'>
google.load('visualization', '1', {packages:['gauge']});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Label');
data.addColumn('number', 'Value');
data.addRows([
<?php

$db="example_database";
$link = mysql_connect('', 'user1_ID', 'user1_password');
mysql_query('SET NAMES utf8');
mysql_select_db($db , $link) or die("Couldn't open $db: ".mysql_error());

// Retrieve all the data from the "example" table
$result = mysql_query("SELECT Time, Temp, Setpoint, Input, Kd, Ki, Kp FROM temp Order By Time desc limit 1");
if ($result !== false) {
$num=mysql_numrows($result);

$time=mysql_result($result,0,"Time");
$temp=mysql_result($result,0,"Temp");
$input=mysql_result($result,0,"Input");
$setpoint=mysql_result($result,0,"Setpoint");
$ki=mysql_result($result,0,"Ki");
$kp=mysql_result($result,0,"Kp");
$kd=mysql_result($result,0,"Kd");

echo "['Temp',";
echo "$temp";
echo "],";
echo "['PID Temp',";
echo "$input";
echo "],";
echo "['Setpoint',";
echo "$setpoint";
echo "]";

}

?>
]);

var options2 = {
width: 600, height: 300,
minorTicks: 2, max: 212, min: 60
};

var chart = new google.visualization.Gauge(document.getElementById('chart_div2'));
chart.draw(data, options2);
}
</script>


<script type='text/javascript' src='https://www.google.com/jsapi'></script>
<script type='text/javascript'>
google.load('visualization', '1', {packages:['gauge']});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Label');
data.addColumn('number', 'Value');
data.addRows([
<?php

$db="example_database";
$link = mysql_connect('', 'user1_ID', 'user1_password');
mysql_query('SET NAMES utf8');
mysql_select_db($db , $link) or die("Couldn't open $db: ".mysql_error());

// Retrieve all the data from the "example" table
$result = mysql_query("SELECT Time, Heat FROM temp Order By Time desc limit 1");
if ($result !== false) {
$num=mysql_numrows($result);

$heat=mysql_result($result,0,"Heat");

echo "['Heat %',";
echo "$heat";
echo "]";

}

?>
]);

var options3 = {
width: 200, height: 300,
minorTicks: 2, max: 100, min: 0
};

var chart = new google.visualization.Gauge(document.getElementById('chart_div3'));
chart.draw(data, options3);
}
</script>


</head>
<body>
<div align="center">
<H1 align="center">The Title of Your Choice</H1>
<div align="center">
<table width="900" border="1">
<tr>
<td><div align="center">
<table width="1000" border="0">
<tr>
<td width="141">&nbsp;</td>
<td width="10">&nbsp;</td>
<td width="432"><div id="chart_div1"></div></td>
<td width="141">&nbsp;</td>
<td width="144">&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>
<div align="center">
<table width="800" border="0">
<tr>
<td width="595"><div id="chart_div2"></div></td>
<td width="195"><div id="chart_div3"></div></td>
</tr>
</table>
</div></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td><div align="center">
<?php

$db="example_database";
$link = mysql_connect('', 'user1_ID', 'user1_password');
mysql_query('SET NAMES utf8');
mysql_select_db($db , $link) or die("Couldn't open $db: ".mysql_error());

// Retrieve all the data from the "example" table
$result = mysql_query("SELECT Time, Temp, Setpoint, Input, Kd, Ki, Kp, Heat FROM temp Order By Time desc limit 1");
if ($result !== false) {
$num=mysql_numrows($result);


$time=mysql_result($result,0,"Time");
$temp=mysql_result($result,0,"Temp");
$input=mysql_result($result,0,"Input");
$setpoint=mysql_result($result,0,"Setpoint");
$ki=mysql_result($result,0,"Ki");
$kp=mysql_result($result,0,"Kp");
$kd=mysql_result($result,0,"Kd");
$heat=mysql_result($result,0,"Heat");

 

echo "Kp = ";
echo $kp;
echo " Ki = ";
echo $ki;
echo " Kd = ";
echo $kd;

 

}

 

 

?>
</div></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
</div></td>
</tr>
</table>
</div>
</body>
</html>



That's it!

Plug in your arduino power, connect your ethernet port to your web router, and supply power to your SSR and feed a heating element. Adjust your setpoint with the potentiometer (although better methods do exist for sending setpoint information - but you can work on that on your own). The point is that you should have a working dashboard as shown below.

Hopefully things are working, if not shoot me an email and I will do my best to help you out. My code may not be efficient but it works - that's all I really care about so I thought I would share. By no means am I a controls code nerd either so it probably isn't the effort to harass my work!



BLOG
POSTS:



Yeast Information Database


Beer Glassware Guide


How to Keg Beer


Beer Exposure to Sunlight and UV


Importance of Aerating Wort


Steps to Reduce Chill Haze in Beer


How to Make a Yeast Starter


Beer Fermentation Time Lapse


Full Boil Versus Partial Boil


Why Home Brewers Need a Turkey Burner

 


BEER RECIPE
OF THE WEEK:


Climbing Everest - Experimental Beer (Lager or Ale)


BEER
QUOTES:



As to the way of life of the English, they are somewhat impolite, for they belch at the table without shame. They consume great quantities of beer.