Gas furnace temperature control

Hi,
My foray into Arduino has been relatively recent but I'm studying for 2 or 3 hours a day and have learnt an extraordinary amount. I've assembled my motor driver shield from kit, successfully edited/merged codes and overcome a variety of issues along the way. I don't expect to learn what I need in a day but I am prepared to put in the effort.

My project is fine temperature control of a gas fired furnace from TC signal. I have a MAX6675 breakout board and an Adafruit Motor/Stepper/Servo Shield stacked on a Mega. I have a stepper motor to rotate a ball valve and thereby control the gas rate. Some of the gas will be methane from waste organic matter.

Ok so here's the thing. I want Arduino to look at the furnace TC temperature and incrementally open or close the gas supply valve to suit. The project requires heat that is absolutely constant. Also, at start up I'll require the temperatures to be ramped up progressively in an orderly fashion. The operator may need to change the set point target temperature manually as an option.

A Google search uncovered a number of projects that used electricity as the heat medium so were able to switch an SSR on/off in the same way an electric oven or water cylinder works. My approach presents a lot more of a challenge!

Others have suggested I opt for a servo to operate the gas valve as they have reference points. Also it was pointed out that while a stepper doesn't have reference points as such, they can at initial start up, be made to rotate hard against the a stop and zero itself at that point. Apparently printers work this way.

I started thinking about what sort of code algorithm I'd need. Could the code be such that Arduino looks at the TC signal, compares it to the set point then reasons that say 2 steps more is required for the stepper valve. A minute later Arduino looks again and sees the gap to target has been reduced to half so the stepper gets closed by a step. Still a minute later, Arduino looks again and finds the set point a perfect match and reduces another notch. The play here has great similarity to driving a car. The gas pedal is pushed down till the desired cruise speed is reached, then the pedal is eased back to maintain that speed. No more, no less. Does this sound feasible?? Can you see it working?

Thanks for reading. Please comment. I'd love to hear your thoughts, pointers, etc.

What kind of force is needed for the gas ball valve ?
A stepper motor has little torque, but can rotate many times.
A servo is a motor with a gear and therefor much stronger. A servo can rotate 180 degrees (some do 360 degrees).

You should take a look at a PID control.
http://playground.arduino.cc/Code/PIDLibrary
You could use the analog output value of the PID, to control how much the gas valve is opened.
For a proper PID control, you have to determine the parameters that are right for you.

Thanks for the reply.

The ball valve has been tweaked so there is virtually no friction. This should be good enough for gas and at least as good as a butterfly throttle body.
It turns by 90degrees.

The PID link looks good!

Thanks

Only 90 degrees -> I would for sure use a servo for that.
For a servo, you only need the Servo library, and a good power supply.
The Arduino 5V can not supply the current for the servo.

A servo stays at the position when it is turned of, and if the Arduino is turned on, you can set the angle without problem.
With a steppermotor, you don't know the position if the Arduino is turned on.

Thanks.

I'm liking the look of the servo more and more. They look like a good way to go.

I think I've solved how I can change the set point without interrupting anything. One of these looks like it should do the trick.

Another Google find is this reflow oven controller I'm speculating that this may be adaptable to control the gas valve.

The reflow oven controller is only a TC interface with some connectors.
It is ment to be used with an SSR (Solid State Relay) to turn the heating on or off.

I think, you could also use this, Thermocouple Amplifier MAX31855 breakout board (MAX6675 upgrade) : ID 269 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits

The Adafruit LCD is a display and buttons. The used LCD on that board is used a lot with Arduino, because they are cheap. But they are also sensitive to temperature. If you want a good readable display that will work the same with higher temperatures, I suggest to use a 7-segment led display. Adafruit 0.56 4-Digit 7-Segment Display with I2C Backpack - Red [STEMMA QT / qwiic] : ID 878 : $9.95 : Adafruit Industries, Unique & fun DIY electronics and kits

Thanks.

Yes I have the TC breakout board MAX31855. In fact I have 3 of them. I have them working on the bench.

The temperature sensitivity of the LCD is a good tip. I was planning on using a temperature sensor on the board/s and possibly use that to switch a cooling fan.
My understanding of the LCD shield is that I could use the buttons to alter the setpoint if I wrote some code to monitor for setpoint changes. So, if I used the 7 segment display, would I simply mount buttons to set the temperature and configure code in the same manner?

My main struggle is with code side of things and I'm confused on how to make a start. A study of PID suggests I should start by using the proportional aspect, keeping it simple and work from there.
Otherwise there's a basic sketch attached utilizing the PID_v.h library. Is a modified version of this going to work as a starting point?

PID_Basic.ino (638 Bytes)

The PID library requires the manipulation of three variables: Input, Output, and Setpoint (variable names using in the Basic example).

Setpoint is what you want the temperature to be.

Input is what the temperature is. In the example they just perform an analogRead() to set this but you would be getting this reading from your thermocouple. The units (Celsius, Kelvin, etc.) must be the same as used with Setpoint.

The Output would the position that you're writing to the servo. A servo (using the Servo library) expects a value from 0 to 90 so you need to use the PID Library's SetOutputLimits() function to set those limits from 0 to 90 (from the default of 0 to 255)... or whatever fraction is appropriate. Every time you call myPID.Compute() the PID library will change the value of the Output variable; it's just up to you to write it to the servo.

There are a lot of values you can fiddle with to improve performance but the basics really are that simple.

Thanks Chagrin, that's a good description, it helps.

My first target is to get the most basic example of code up and running, then sort any issues and improve it from there.
I'm working on a draft sketch which I'll post shortly. Actually the task of compiling it is teaching me a lot. I trust any subsequent ones in the future don't take so long but that's the cost of learning I guess, and this is my first.

For those following progress, the link posted earlier pointing to the PIDLibrary is dead.
This one at Github.com is another repository.

This is the ball valve I'm proposing to use for the gas feed. It's 1/2" 12.5mm. I'm looking at this servo as it checks out to have about 3 times the power at 1:1 leverage. This would give 90 positions so appears to be adequate.

Using a ball valve with a servo as the motive force as a gas control valve for a furnace has a real safety issue and would not be used in most any industrial process control application. Such 'control valves' should have a 'fail safe' mode such that in case of some system failure (power loss, control loss, etc.) the valve 'fails' closed. This is usually done by using control valves that have internal springs that close the value on loss of control signal or motive force.

Not paying attention of what could go wrong with a natural gas control application is an invitation to risk of loss of life and or property.

Lefty

Thanks for the reminder.
Agreed, safety is an important feature. I was planning to run an additional normal closed solenoid valve so that should the furnace stray outside of certain parameters, power would be cut to the solenoid. Manual E-stop button was also part of that plan.
Would a stepper with a spring return be a better solution?

Thank you retrolefty, I totally forgot about safety.
Perhaps two (or three) safety shut off circuits would be a good idea.

You can also use an arm with a weight instead of a spring. The counterweight causes a certain amount of force, which is not the case with the spring if you have to replace the spring someday. This is not a problem with valves with an internal spring of course.
Using a stepper motor for this is not a bad idea. But the stepper should be connected without gear and should have enough torque.

The sketch in the Arduino could go wrong, so you need another shut off circuit. For example a mains powered valve that automatically closes with power failure or when a bi-metal temperature switch (or temperature fuse) senses a temperature that is too high.

Excellent ideas Erdin. Thanks.

Safety is paramount. I'm going to great lengths on a number of fronts with back ups and failsafes. The strategy is "Eliminate - Isolate - Minimise".

Multiple shutoff circuits is a great idea. It's like having layers of defense. I'm also are looking at having alarms for when operational parameters move outside of set norms.

The weight-on-arm idea is good and is more reliable as you say. A weight can be moved along the length of the arm to be the perfect force. The question is, does the stepper "hold" in position and does it need code considerations written to do so?

I have the PID control working from TC signal now so am well pleased to finally see it run.

The Arduino Playground - PIDLibrary link started working yesterday, now it's down again...

The stepper wil hold its position, as long as it is powered.

Great, thanks.
I'm struggling somewhat with code for the stepper. I have the PID and TC sorted out but the problem is integrating the stepper details. I haven't been able to find an example that looks close enough to get ideas of how to assemble it.
Will I post the code I have so far here please or in the programming section?
Thanks

Do you use the Adafruit library for the Adafruit Motor Shield ?

Please post the whole sketch between [code] ... [/code] tags.

Please view the code.
Yes, Adafruit library for motor shield is used.
The sketch incorporates PID, Adafruit MAX31855 and Adafruit motor shield. I have had the PID and MAX31855 working with Front-End V_03 Processing.org framework, ControlP5. All was looking good on the graphics.

I don't know what pin to configure the PID output pin here: (Did have it working on 9)
//Define Variables we'll be connecting to
double Setpoint, Input, Output;
int inputPin=3, outputPin=?;

I'm confused about how to set up the analog signal for the stepper.

Also how to write code that deals with stepper in void loop.

Thanks. Most appreciated.

#include <PID_v1.h>
#include "Adafruit_MAX31855.h"
#include <AFMotor.h>

int thermoDO = 3;
int thermoCS = 4;
int thermoCLK = 10;


Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO);

// Connect a stepper motor with 48 steps per revolution (7.5 degree)
// to motor port #2 (M3 and M4)
AF_Stepper motor(48, 2);

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
int inputPin=3, outputPin=?;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);


unsigned long serialTime; //this will help us know when to talk with processing

void setup()
{
  //initialize the serial link with processing
  Serial.begin(9600);
  
  Serial.println("MAX31855 test");
  // wait for MAX chip to stabilize
  delay(500);
  
  Serial.println("Stepper test!");

  motor.setSpeed(10);  // 10 rpm
  
  //initialize the variables we're linked to
  Input = digitalRead(inputPin);
  Setpoint = 60;

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

void loop()
{
  //pid-related code
  Input = thermocouple.readCelsius();
  myPID.Compute();
  analogWrite(outputPin,Output);
  

  
  //send-receive with processing if it's time
  if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime+=500;
  }
  
  
}


/********************************************
 * Serial Communication functions / helpers
 ********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output  
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param
void SerialReceive()
{

  // read the bytes sent from Processing
  int index=0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while(Serial.available()&&index<26)
  {
    if(index==0) Auto_Man = Serial.read();
    else if(index==1) Direct_Reverse = Serial.read();
    else foo.asBytes[index-2] = Serial.read();
    index++;
  } 
  
  // if the information we got was in the correct format, 
  // read it into the system
  if(index==26  && (Auto_Man==0 || Auto_Man==1)&& (Direct_Reverse==0 || Direct_Reverse==1))
  {
    Setpoint=double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the 
                                          //   value of "Input"  in most cases (as 
                                          //   in this one) this is not needed.
    if(Auto_Man==0)                       // * only change the output if we are in 
    {                                     //   manual mode.  otherwise we'll get an
      Output=double(foo.asFloat[2]);      //   output blip, then the controller will 
    }                                     //   overwrite.
    
    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //
    
    if(Auto_Man==0) myPID.SetMode(MANUAL);// * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //
    
    if(Direct_Reverse==0) myPID.SetControllerDirection(DIRECT);// * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}

// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings.  much easier than getting
// floats from processing to here no?
void SerialSend()
{
  Serial.print("PID ");
  Serial.print(Setpoint);   
  Serial.print(" ");
  Serial.print(Input);   
  Serial.print(" ");
  Serial.print(Output);   
  Serial.print(" ");
  Serial.print(myPID.GetKp());   
  Serial.print(" ");
  Serial.print(myPID.GetKi());   
  Serial.print(" ");
  Serial.print(myPID.GetKd());   
  Serial.print(" ");
  if(myPID.GetMode()==AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");  
  Serial.print(" ");
  if(myPID.GetDirection()==DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}

The Output would the position that you're writing to the servo. A servo (using the Servo library) expects a value from 0 to 90 so you need to use the PID Library's SetOutputLimits() function to set those limits from 0 to 90 (from the default of 0 to 255)... or whatever fraction is appropriate. Every time you call myPID.Compute() the PID library will change the value of the Output variable; it's just up to you to write it to the servo.

You'll need to adapt Chagrin's advice. Unlike the servo, the stepper motor has no concept of its position. You will need to keep track of it, which means you need a way to find out what it is. Encoders are one possibility. Simpler is a mechanical limit switch to tell you when the valve is fully closed and you can step towards it at startup until you detect it. When the output gives you an angle between 0 and 90, you need to tell the stepper to make the appropriate number of steps to get there. i.e. if you're currently at 15 degrees and the PID is asking for 30, tell the stepper to step twice (7.5 degrees per step) in the appropriate direction.

An example "StepperTest.pde" is in the Adafruit library. You could try an test sketch with only the stepper control.
https://github.com/adafruit/Adafruit-Motor-Shield-library/blob/master/examples/StepperTest/StepperTest.pde

Perhaps you can make a function for 0 to 90 degrees.

Is there a way to tell that the stepper has reached the 'off' position ?
A printer with a stepper motor often has a opto switch, it is the only way to know where the stepper is.

Thanks for the thoughts.

I have a number of limit switches here I could fit to detect when the closed position has been reached. e.g. Hall, optic and micro switches. Logically, the initial position will always be closed because a spring or weight-on-arm will close the valve the moment power is cut. The sensor switch could be considered an extra safety feature I suppose. Also an additional sensor that detects the full open position might be helpful.

The example "StepperTest.pde" is what I run a few days ago to test out the system. It worked fine. Sorry, I'm not understanding how I can specifically adapt it.

OK, so I put a few more hours in and I have come up with a modified sketch that has some promise. However, once uploaded the TC stopped working. The stepper was rotating but continuous in one direction. To double check I swapped back to the previous sketch and found the TC working. Again back to the new code and the TC wasn't working so it appears there's a error or conflict.
So, that's where I'm up to. Here's the updated code:

Any thoughts please?

#include <PID_v1.h>
#include "Adafruit_MAX31855.h"
#include <AFMotor.h>

int thermoDO = 3;
int thermoCS = 4;
int thermoCLK = 10;


Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO);

// Connect a stepper motor with 48 steps per revolution (7.5 degree)
// to motor port #2 (M3 and M4)
AF_Stepper motor(48, 2);
int pos;
int pre;
int num_step;
int direct;

//Define Variables we'll be connecting to
double Setpoint, Input, Output;
int inputPin=3, outputPin=9;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);


unsigned long serialTime; //this will help us know when to talk with processing

void setup()
{
  //initialize the serial link with processing
  Serial.begin(9600);
  
  Serial.println("MAX31855 test");
  // wait for MAX chip to stabilize
  delay(500);
  
  Serial.println("Stepper test!");

  motor.setSpeed(10);  // 10 rpm
  
  //initialize the variables we're linked to
  Input = digitalRead(inputPin);
  Setpoint = 50;

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

void loop()
{
  //pid-related code
  Input = thermocouple.readCelsius();
  myPID.Compute();
  analogWrite(outputPin,Output);
  
    
  int val = analogRead(9);  // get the sensor value
  int pos = map(val, 0, 1023, 0, 12);
  int num_step = pos - pre;
  pre = pos;
       
       // move a number of steps equal to the change in the
      // sensor reading
  Serial.println("Single coil steps");
  Serial.print("Step Value: ");
  Serial.println(num_step);

  if(num_step > 0)
  {
  direct = FORWARD;
  }
  else
  {
  direct = BACKWARD;
  }
      
  Serial.print("Direction: ");
  Serial.println(direct);

  motor.step(num_step, direct, SINGLE);
  delay(1000);
  
  //send-receive with processing if it's time
  if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime+=500;
  }
  
  
}


/********************************************
 * Serial Communication functions / helpers
 ********************************************/


union {                // This Data structure lets
  byte asBytes[24];    // us take the byte array
  float asFloat[6];    // sent from processing and
}                      // easily convert it to a
foo;                   // float array



// getting float values from processing into the arduino
// was no small task.  the way this program does it is
// as follows:
//  * a float takes up 4 bytes.  in processing, convert
//    the array of floats we want to send, into an array
//    of bytes.
//  * send the bytes to the arduino
//  * use a data structure known as a union to convert
//    the array of bytes back into an array of floats

//  the bytes coming from the arduino follow the following
//  format:
//  0: 0=Manual, 1=Auto, else = ? error ?
//  1: 0=Direct, 1=Reverse, else = ? error ?
//  2-5: float setpoint
//  6-9: float input
//  10-13: float output  
//  14-17: float P_Param
//  18-21: float I_Param
//  22-245: float D_Param
void SerialReceive()
{

  // read the bytes sent from Processing
  int index=0;
  byte Auto_Man = -1;
  byte Direct_Reverse = -1;
  while(Serial.available()&&index<26)
  {
    if(index==0) Auto_Man = Serial.read();
    else if(index==1) Direct_Reverse = Serial.read();
    else foo.asBytes[index-2] = Serial.read();
    index++;
  } 
  
  // if the information we got was in the correct format, 
  // read it into the system
  if(index==26  && (Auto_Man==0 || Auto_Man==1)&& (Direct_Reverse==0 || Direct_Reverse==1))
  {
    Setpoint=double(foo.asFloat[0]);
    //Input=double(foo.asFloat[1]);       // * the user has the ability to send the 
                                          //   value of "Input"  in most cases (as 
                                          //   in this one) this is not needed.
    if(Auto_Man==0)                       // * only change the output if we are in 
    {                                     //   manual mode.  otherwise we'll get an
      Output=double(foo.asFloat[2]);      //   output blip, then the controller will 
    }                                     //   overwrite.
    
    double p, i, d;                       // * read in and set the controller tunings
    p = double(foo.asFloat[3]);           //
    i = double(foo.asFloat[4]);           //
    d = double(foo.asFloat[5]);           //
    myPID.SetTunings(p, i, d);            //
    
    if(Auto_Man==0) myPID.SetMode(MANUAL);// * set the controller mode
    else myPID.SetMode(AUTOMATIC);             //
    
    if(Direct_Reverse==0) myPID.SetControllerDirection(DIRECT);// * set the controller Direction
    else myPID.SetControllerDirection(REVERSE);          //
  }
  Serial.flush();                         // * clear any random data from the serial buffer
}

// unlike our tiny microprocessor, the processing ap
// has no problem converting strings into floats, so
// we can just send strings.  much easier than getting
// floats from processing to here no?
void SerialSend()
{
  Serial.print("PID ");
  Serial.print(Setpoint);   
  Serial.print(" ");
  Serial.print(Input);   
  Serial.print(" ");
  Serial.print(Output);   
  Serial.print(" ");
  Serial.print(myPID.GetKp());   
  Serial.print(" ");
  Serial.print(myPID.GetKi());   
  Serial.print(" ");
  Serial.print(myPID.GetKd());   
  Serial.print(" ");
  if(myPID.GetMode()==AUTOMATIC) Serial.print("Automatic");
  else Serial.print("Manual");  
  Serial.print(" ");
  if(myPID.GetDirection()==DIRECT) Serial.println("Direct");
  else Serial.println("Reverse");
}