Laptop controlled temperature controller

Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum

Help Support Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

mxman06

Well-Known Member
Joined
Jul 25, 2010
Messages
58
Reaction score
0
Location
omaha NE
I would like to build a temperature controller that can be controlled with a laptop. I am not afraid of scripting a program to do it. I just don’t know what is needed as for the hardware, or if this is even possible. So if you guys can put a basic list of what I would need to build this that would help. If I get this together and working good I will put a build page up, and upload the program if I make one.

Thank you in advance
 
I am currently working up a system based on off the shelf boards connected to a laptop that is what you are looking for. If you can do C# I can share the code built so far, or shift it to Java and tap the library of code from the other automation program I have. Hardware was about $50 for three boards, $25 for some radio shack parts and wall wart for power. This gives you 16 digital channels and 8 analog channels for sensors. Additional digital boards are $13, analog boards are $19, code would be fairly easy to include additional channels.
 
that's exactly what i am looking for. i can do C+, i am a network tech in the air force, so i am pruty tech savy. what boards do you use. if there is a link that would be nice. i am thinking of make a Linux bast program.
 
The 8 channel analog board and digital boards are from Gravitech, analog link http://www.gravitech.us/i2c128anco.html, digital board link http://www.gravitech.us/i2c16gpex.html, and the USB to I2C interface from Ebay http://www.ebay.com/itm/USB-to-I2C-Serial-converter-and-data-acquisition-/230779630464?pt=LH_DefaultDomain_0&hash=item35bb89b380#ht_500wt_1202. The radio shack parts needed are just 5V regulator IC, 3300 uF cap, and a 10K trim pot for use as the 5V reference trim for the analog board, and a 7.5V wall wart power supply. The rest of the work is just soldering iron and programming. In theory you can power everything from the USB port, but testing has shown that it causes fluctuations in the analog side as the voltage is bouncing up and down as things are switched on/off, the Lab Jack interfaces have the same problem with USB power. If you want to expand, then changing jumpers on the boards will let you address additional boards and channels easily.
 
FWIW here is the C# code for communicating with the boards so you do not have to reinvent the wheel:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.IO;
using System.IO.Ports;
using System.Timers;

namespace I2C_Development
{

public sealed class IOworker
{
public static IOworker Instance
{
get
{
lock (typeof(I2C_Development.IOworker))
{
if (_instance == null)
{
_instance = new IOworker();
}
return _instance;
}
}
}

public static IOworker _instance;

public IOworker()
{
Thread IOW = new Thread(new ThreadStart(this.Run));
IOW.IsBackground = true;
IOW.Priority = ThreadPriority.Lowest;
IOW.Start();
}

private static System.Timers.Timer channelDecode;

private static String[,] boardComm = new String[20, 20];
public static int[] analogRead = new int[10];
public static double[,] analogValue = new double[10,5];
public static String[] commandData = new String[30];
private static String[,] boardIoData = new String[10, 5];
public static int[,] digitalWorker = new int[20, 4];
private static int[,] digitalWriter = new int[20, 3];
public static double[,] pidValue = new double[10, 7];
private static int dataIn = 0;
private static StringBuilder digital = new StringBuilder(256);
private static string [] digSend = new string [10];
public static int[] digitalIn = new int[16];
private static int[] digitalOut = new int[16];
public static bool buttonPush = false;
private static byte[,] commands = new byte[20, 20];
private static int step = 0;
private static byte[] writeBuff = new byte[6];
private static byte[] readBuff = new byte[12];
private static StringBuilder readData = new StringBuilder(12);
private static string hexData;
public static string dataSend;
public static int dataRead;
private static int bytesRead;
private static SerialPort IOPort = new SerialPort();


public void Run()
{
TimerBuild();
Configure();
}

public static void IoStart()
{
Connect();
Config();
}

private static void TimerBuild()
{
channelDecode = new System.Timers.Timer(60);
channelDecode.AutoReset = true;
channelDecode.Elapsed += new ElapsedEventHandler(channelDecodeTimedEvent);
channelDecode.Enabled = false;
}

private void Configure()
{
try
{
IOPort.PortName = "COM4"; //= PortName;
IOPort.BaudRate = 9600;
IOPort.DataBits = 8;
IOPort.ReadTimeout = 500;
IOPort.WriteTimeout = 500;
IOPort.Parity = Parity.None;
IOPort.Handshake = System.IO.Ports.Handshake.None;
IOPort.StopBits = StopBits.One;
IOPort.Encoding = System.Text.Encoding.ASCII;
}
catch
(IOException iofail)
{
Console.WriteLine(iofail);
}
}

private static void Connect()
{
try
{
if (!IOPort.IsOpen)
{
IOPort.Open();
IOPort.DtrEnable = true;
IOPort.RtsEnable = true;
} // else already open
}
catch (Exception e1)
{
Console.WriteLine("Error: Connection is in use or is not available: \n\n" + e1);
}
}

private static void channelDecodeTimedEvent(object source, ElapsedEventArgs e) // 60 millisecond scan interval
{
if (step > 7)
{
step = 0;
}
if (buttonPush == false) // Analog scanning section
{
writeBuff[0] = commands[0, 0]; // sends take reading command to analog channel
writeBuff[1] = commands[0, 1];
writeBuff[2] = commands[0, 3];
writeBuff[3] = commands[3,step];

Write();
Thread.Sleep(11);
Read(); // reads result from command

writeBuff[0] = commands[0, 0]; // retieves analog input result as 2 hex bytes
writeBuff[1] = commands[0, 2];
writeBuff[2] = commands[0, 3];
writeBuff[3] = commands[4,step];

Write();
Thread.Sleep(31);
Read();
}
if (buttonPush == true) // Digital writing section, 1 = on 0 = off
{
digital.Remove(0, digital.Length); // for channels 0 - 7
digital.Insert(0, digitalWorker[0, 2]);
digital.Insert(0, digitalWorker[1, 2]);
digital.Insert(0, digitalWorker[2, 2]);
digital.Insert(0, digitalWorker[3, 2]);
digital.Insert(0, digitalWorker[4, 2]);
digital.Insert(0, digitalWorker[5, 2]);
digital.Insert(0, digitalWorker[6, 2]);
digital.Insert(0, digitalWorker[7, 2]);

digSend[0] = String.Format("{0:X2}", Convert.ToInt32(Convert.ToString(digital), 2));
commands[2, 7] = byte.Parse(digSend[0], System.Globalization.NumberStyles.AllowHexSpecifier);

digital.Remove(0, digital.Length); // for channels 8 - 15
digital.Insert(0, digitalWorker[8, 2]);
digital.Insert(0, digitalWorker[9, 2]);
digital.Insert(0, digitalWorker[10, 2]);
digital.Insert(0, digitalWorker[11, 2]);
digital.Insert(0, digitalWorker[12, 2]);
digital.Insert(0, digitalWorker[13, 2]);
digital.Insert(0, digitalWorker[14, 2]);
digital.Insert(0, digitalWorker[15, 2]);

digSend[1] = String.Format("{0:X2}", Convert.ToInt32(Convert.ToString(digital), 2));
commands[2, 8] = byte.Parse(digSend[1], System.Globalization.NumberStyles.AllowHexSpecifier);

writeBuff[0] = commands[2, 0];
writeBuff[1] = commands[2, 1];
writeBuff[2] = commands[2, 2];
writeBuff[3] = commands[2, 3];
writeBuff[4] = commands[2, 7];
writeBuff[5] = commands[2, 8];
Write();
buttonPush = false;
}

analogValue[(Convert.ToInt32(ControlWorker.inPoint[step, 1])-1), 3] =
Math.Round(((analogRead[step] * (Convert.ToDouble(ControlWorker.inPoint[step, 2])))
+ (Convert.ToDouble(ControlWorker.inPoint[step, 3]))), 2, MidpointRounding.ToEven);

step++;
}

private static void Write()
{
try
{
IOPort.Write(writeBuff, 0, writeBuff.Length);
IOPort.BaseStream.Flush();
}
catch (Exception ex)
{
Console.WriteLine("IO write malfunction " + ex);
}
}

private static void Read()
{
bytesRead = IOPort.BytesToRead;
IOPort.BaseStream.Read(readBuff, 0, bytesRead);
IOPort.BaseStream.Flush();
if (bytesRead > 2)
{
foreach (byte b in readBuff)
{
readData.AppendFormat(String.Format("{0:X2}", b));
}

hexData = readData.ToString().Substring(0, 4);
readData.Remove(0, readData.Length);

if (hexData != null)
{
dataRead = 0;
try
{
for (int ac = 0; ac < 1; ac++) // analog data converter from 2 Hex bytes to decimal 0-4095
{
try
{
analogRead[step] = (Convert.ToInt32(hexData.Substring((ac * 4), 4), 16)); // analog values
}
catch (FormatException ConvFail0)
{
Console.WriteLine("ConvFail0 " + ConvFail0 + " dataIn " + dataIn);
}
}
hexData.Remove(0, hexData.Length);
}
catch (FormatException ConvFail0)
{
Console.WriteLine("ConvFail0 " + ConvFail0 + " dataIn " + dataIn);
}
}
}
}


public static void Config()
{
if (IOPort.IsOpen)
{
for (int digset = 0; digset < 16; digset++)
{
IOworker.digitalWorker[digset, 2] = 1;

}
commands[0, 0] = 0x54; // usb board command
commands[0, 1] = 0x90; // analog IO board address and write command
commands[0, 2] = 0x91; // analog IO board address and read channel
commands[0, 3] = 0x03; // usb board bytes to write command

commands[2, 0] = 0x54; // usb board command
commands[2, 1] = 0x40; // digital IO board address and write command
commands[2, 2] = 0x03; // usb board bytes to write command
commands[2, 3] = 0x02; // digital IO board output write command
commands[2, 4] = 0x06; // digital IO board output write command
commands[2, 5] = 0x00; // digital IO board output write command
commands[2, 6] = 0xFF; // digital IO board output write command

commands[3, 0] = 0x84; // analog channel 1 address and take sample command
commands[3, 1] = 0xC4; // analog channel 2 address and take sample command
commands[3, 2] = 0x94; // analog channel 3 address and take sample command
commands[3, 3] = 0xD4; // analog channel 4 address and take sample command
commands[3, 4] = 0xA4; // analog channel 5 address and take sample command
commands[3, 5] = 0xE4; // analog channel 6 address and take sample command
commands[3, 6] = 0xB4; // analog channel 7 address and take sample command
commands[3, 7] = 0xF4; // analog channel 8 address and take sample command

commands[4, 0] = 0x80; // analog channel 1 address and retrieve data command
commands[4, 1] = 0xC0; // analog channel 2 address and retrieve data command
commands[4, 2] = 0x90; // analog channel 3 address and retrieve data command
commands[4, 3] = 0xD0; // analog channel 4 address and retrieve data command
commands[4, 4] = 0xA0; // analog channel 5 address and retrieve data command
commands[4, 5] = 0xE0; // analog channel 6 address and retrieve data command
commands[4, 6] = 0xB0; // analog channel 7 address and retrieve data command
commands[4, 7] = 0xF0; // analog channel 8 address and retrieve data command

commands[5, 0] = 0x94; // usb board PWM command
commands[5, 1] = 0x00; // digital IO board address and write command
commands[5, 2] = 0x0F; // usb board bytes to write command

commands[6, 0] = 0x95; // usb board command for PWM
commands[6, 1] = 0x00; // PWM 3 Khz select


writeBuff[0] = commands[2, 0]; // This configures digital points 0 - 7 as outputs
writeBuff[1] = commands[2, 1]; // Run once at startup
writeBuff[2] = commands[2, 2];
writeBuff[3] = commands[2, 4];
writeBuff[4] = commands[2, 5];
writeBuff[5] = commands[2, 5];
Write();

writeBuff[0] = commands[2, 0]; // This configures digital points 8 - 15 as outputs
writeBuff[1] = commands[2, 1]; // Run once at startup
writeBuff[2] = commands[2, 2];
writeBuff[3] = commands[2, 3];
writeBuff[4] = commands[2, 6];
writeBuff[5] = commands[2, 6];
Write();

Console.WriteLine("digital config completed");
channelDecode.Enabled = true; // this starts timer loop for analog scanning
}
}
}
}
 
While the Atmel micros are cheap, you might as well just do the Brewtroller platform as it has some thought put into it's construction.
With the I2C hardware and an application there are not the same limits on IO channels and 10 bit resolution of the analog inputs on the Atmel and PIC platforms. If an Atmel is all you need then by all means knock yourself out, but after you spend hours progaming for that, what do have to show for the effort.
For nearly the same cost as an Atmel platform, the Raspberry Pi platform has much more capability, and when coupled to the I2C boards would make an all in one platform able to run Android based applications and give you the ability to do so much more. For a bit more you can get cortex 3C2440 setups with touch screen, which can connect to the I2C hardware, and have a system that can run like the lower end PLC's.
 
This is very interesting. I'd like to build a temperature control system that utilizes a central chiller and can feed multiple zones. Ideally the brains of the system would have the ability to preprogram temperature profiles to suit lagering.

Ive briefly looked at fermtroller and 1 wire builds but a PC system with a more robust UI and logging capabilities would better than a simple display.

Ive got a graveyard of old XP pcs in the basement, it would be nice to repurpose one as a brain.
 
Hi

The classic question that always comes up on this is: Do you trust your PC to keep running or your billion lines of embedded code to be bug free? To me it's a toss up as to which one you should trust more. I've seen both fail in pretty dramatic ways. You do indeed run into people with major biasses in one direction or the other.

Bob
 
If you do not plan for failure and build in watchdog timing safety, you deserve the results. With proper design and code you can overcome the awsh*t moments when power goes away and the pc reboots, and application resumes operation where it left off. I doubt anyone else has invested the time and code to make this happen, or even knows how to implement the recovery procedures.
 
Hi

I've seen several designs where the watchdog was the only thing still being reset when the code went out to lunch. There's no one magic bullet that will protect you from bad code.

Bob
 
Hi

I've seen several designs where the watchdog was the only thing still being reset when the code went out to lunch. There's no one magic bullet that will protect you from bad code.

Bob

Given the level of expertise in process control and coding seen here I would not be surprised, but it seems current state of the art is the Atmel and PIC platforms which are pretty limited and do not need a control application.
Only safe way I know of is to run the application in a sensor simulation mode first, then live hardware mode, and introduce failures to determine effects and repairs needed. After 4 years and a couple thousand hours of coding and testing the current automation application, it seems to be as reliable as possible with microsoft operating system on a touch screen.
Do you have an automation application or an automated system to show ?.
Kevin
 
Hi

Most of the stuff I do is the property of my employer. They get a little bothered when people start posting "their" code.

Bob
 
I'm using a USB Bitwhacker and an off the shelf python library for another project... Pretty powerful, and cheap!
 
Have you seen the devices from Linear thet have the precision temp sensor built in and have a programmable PGA for use with most of the thermocouple types. I am working on the 4 channel version LTC2487 for incorporation into the input board.
 
Back
Top