Project - Fan speed controller feasibility

I am using the Arduino Leonardo Ethernet board and my project is to create a fan speed controller using proportional integral control. The controller would be measuring the system pressure (bar) and ramping up/down the condenser fan in order to maintain the system at a constant pressure.

I have an LCD display which shows the system pressure and the controller speaks via Ethernet which also enables me to view the system pressure on the HMI.

I have got as far as the proportional integral control and now have some questions regarding feasibility and programming:

  1. Is the function of this project fully feasible? i.e: can you use an arduino for proportional integral control?

  2. I am used to writing PLC software using ladder so this is new to me. How do I set up for example a 1 second clock and set parts of the program so they are being updated in different time frames.

  3. If it is feasible - I have drawn out the software as a flowchat on paper. For example: in the dead band I need to do (Pressure in - set point) —> (Is the result less than 0?) —> This then has two possible outcomes, yes or no which then branch off in to many more yes or no scenarios. How would you write this using good practice?

I will attach the program I have written thus far and would really appreciate any help with my questions and any feedback on what is already written.

Dan.

#include <LiquidCrystal.h>
#include <SPI.h>
#include <Ethernet2.h>

LiquidCrystal lcd(7, 6, 5, 4, 3, 2); //Setting pins that control LCD screen. 
const float PressureInput = A0; //Setting Pressure sensor input 

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
  0x90, 0xA2, 0xDA, 0x10, 0x89, 0x6D
};
IPAddress ip(192, 168, 0, 248);
IPAddress subnet(255, 255, 255, 0);

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() {
 
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip, subnet);
server.begin();

//-------------------------------------------------
//Setting up serial connection and LCD display 
Serial.begin(9600);
lcd.begin(16, 2);
lcd.print("Pressure in the");
lcd.setCursor(0, 1);
lcd.print("system >0.1 bar");

//Setting up digital pins
pinMode(8, OUTPUT);
}

//-------------------------------------------------

void loop() {
 
EthernetClient client = server.available();
if (client) {
//Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
//Serial.write(c);
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");  // the connection will be closed after completion of the response
client.println("Refresh: 1");  // refresh the page automatically every 5 sec
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");



  //-------------------------------------------------
//Reading and converting analog input to bar. Also printing to PC screen to access all values
float sensorValue = analogRead(PressureInput); //Reading the raw input
Serial.print (" Sensor Value ");
Serial.print (sensorValue);
float voltage = (sensorValue/1023.0) * 5.0; // Converting raw value in to a voltage reading
Serial.print (" Volts ");
Serial.print (voltage);
Serial.print (" BAR: ");
float bar = (voltage *8);
Serial.println(bar);
//-------------------------------------------------
//Writing the pressure value to LCD display
if (bar>0.1){//Value that determines at what stage the LCD display wont show pressure reading
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Pressure (bar) ");
lcd.setCursor(5, 5);
lcd.print(bar);
}
else if (bar<0.1){//Value that determines at what stage the LCD display wont show pressure reading
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Pressure in the");
lcd.setCursor(0, 1);
lcd.print("system <0.1 bar");
}
//------------------------------------------------
//High and low pressure alarms set for LED(pin 7) and sounder(pin 8). (High pressure set at 28 bar, Low pressure set at 2 bar). 
if (bar>=28 or bar<=2){
digitalWrite(8, HIGH); 
}
else if (bar<27.99 or bar>2.01){ 
digitalWrite(8, LOW);
}
//-----------------------------------------------
delay(50);          
          
if ("HTTP/1.1 200 OK"); { 
client.print(" Arduino Status: ");
client.println("
");
client.println("</html>");
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}

// output the value of each analog input pin
for (int analogChannel = 0; analogChannel < 1; analogChannel++) {
int sensorReading = analogRead(analogChannel);
client.print("System pressure is ");
client.print(bar);
client.print(" bar " );
client.println("
");
}
client.println("</html>");
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
//Serial.println("client disconnected");
}
  
}

can you use an arduino for proportional integral control?

Yes.

How do I set up for example a 1 second clock and set parts of the program so they are being updated in different time frames.

What is the clock going to do?

Robin2 has a “Several things at once” thread that you should look for.

if ("HTTP/1.1 200 OK"); {

That code makes no sense. If that string what? Why does the if statement have a semicolon?

for (int analogChannel = 0; analogChannel < 1; analogChannel++) {

Why on earth do you need a for loop that iterates once?

Thank you for your reply Paul!

The clock is for the integral control. After the proportional control section has finished, I would like to do (Has one second passed since we were last here). If yes, carry out the integral control and if no then it just carries on outputting the same signal to the fans. - Thank you, I will look for that now and give it a read.

Thank you, I will address these issues and put them right now.

How do you dictate how often each part of the program gets used?

You use the millis() function. It tells you the number of milliseconds since the Arduino started. By recording the time at which a block of code was last executed in an unsigned long int variable, you can subtract that from millis() to determine how much time has passed and execute the code block if appropriate. When using this technique, you must allow loop() to perform the time check frequently, so avoid using delay() for other than very short periods.

Could you please give me an example of how I would execute part of the program once every second?

Could you please give me an example of how I would execute part of the program once every second?

unsigned long lastTimeSomethingHappenedEverySecond = 0;
unsigned long lastTimeSomethingHappenedEveryTwoSeconds = 0;

void loop()
{
   unsigned long now = millis();
   if(now - lastTimeSomethingHappenedEverySecond >= 1000)
   {
      MakeSomethingHappenOne();
      lastTimeSomethingHappenedEverySecond = now;
   }

   if(now - lastTimeSomethingHappenedEveryTwoSeconds >= 2000)
   {
      MakeSomethingHappenTwo();
      lastTimeSomethingHappenedEveryTwoSeconds = now;
   }
}

It is left as an exercise for you to implement the two functions to make something happen.

If the 1s interval needs to be as accurate as possible, rather than writing

      lastTimeSomethingHappenedEverySecond = now;

it might be better to write

      lastTimeSomethingHappenedEverySecond += 1000;

This is in case any other code delayed the next test of millis() resulting in the delay being slightly over 1000ms. My suggested change would try to keep to the 1s schedule more accurately in the long term.

Thank you again for your help, I’ve made that work by flashing an LED on and off.

So the code below is the start of my PI control. if (FinalInbalance < 5) then I want that to skip back to the start of the loop because there is no need for the proportional control. How would I do this?

void loop() {
  // put your main code here, to run repeatedly:

float sensorValue = analogRead(PressureInput);
float voltage = (sensorValue/1023.0) * 5.0; // Converting raw value in to a voltage reading
float bar = (voltage *8);

  if (bar != Setpoint) { //If the input pressure isn't equal to the setpoint then move to dead band
     (bar - Setpoint); // Carry out input pressure - setpoint
     float Inbalance = (bar - Setpoint); 
    if (Inbalance < 0) { //If the result is less than 0 multiply by -1 to make number positive
       (Inbalance *-1);
       float FinalInbalance = (Inbalance *-1)';
       if (FinalInbalance < 5) {
        
       }
  }
    }

Complete sketch please, and click Tools-->Auto Format before you copy it from the IDE.

This line is doing nothing:

     (bar - Setpoint); // Carry out input pressure - setpoint

same for this line:

       (Inbalance *-1);

Also it's not a good idea to compare a float to another value as you do in this line

  if (bar != Setpoint) {

because of the rounding errors involved. The code in the lines below if seems to make it uneccessary anyway.

Thank you for your help and patience!
The reason I ask is because goto seems to be frowned upon and I am sure there is a better way.

const int Setpoint = 20; //Setting up the pressure setpoint
const float PressureInput = A0; //Setting up the pressure sensor input
unsigned long lastTimeSomethingHappenedEverySecond = 0;

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

  float sensorValue = analogRead(PressureInput);
  float voltage = (sensorValue / 1023.0) * 5.0; // Converting raw value in to a voltage reading
  float bar = (voltage * 8);

  if (bar != Setpoint) { //If the input pressure isn't equal to the setpoint then move to dead band
    (bar - Setpoint); // Carry out input pressure - setpoint
    float Inbalance = (bar - Setpoint);
    if (Inbalance < 0) { //If the result is less than 0 multiply by -1 to make number positive
      (Inbalance * -1);
      float FinalInbalance = (Inbalance * -1);
      if (FinalInbalance < 5) {

      }
    }
  }
}
       float FinalInbalance = (Inbalance *-1)';

What is that single quote there for?

      (Inbalance * -1);

Multiply Inbalance by -1 and throw the result away. Makes me wonder why you bother.

Rather than figuring out how to not do something under some conditions, you should concentrate on the conditions under which you DO want to do something, and write the if statement so that, when it is true, you do those things/that thing.

You can NOT do something in some function by exiting the function. Use return to do that. loop() is a function.

Change the line to

     if (FinalInbalance > 5) {
       // Put your PI control code here
     }

Also see the comments I added to my previous post.

Rather than figuring out how to not do something under some conditions, you should concentrate on the conditions under which you DO want to do something, and write the if statement so that, when it is true, you do those things/that thing.

Well that was some simple advice that just made things much easier.

With regards to comparing the float to another value, I don’t know any other way to compare my analogue input with it’s set point?

Would this be a better way of doing it then?

const int ProportionalBand = 50
                             const int Setpoint = 20; //Setting up the pressure setpoint
const float PressureInput = A0; //Setting up the pressure sensor input
unsigned long lastTimeSomethingHappenedEverySecond = 0;

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

  float sensorValue = analogRead(PressureInput);
  float voltage = (sensorValue / 1023.0) * 5.0; // Converting raw value in to a voltage reading
  float bar = (voltage * 8);

  if (bar != Setpoint) { //If the input pressure isn't equal to the setpoint then move to dead band
    (bar - Setpoint); // Carry out input pressure - setpoint
    float Inbalance = (bar - Setpoint);
    if (Inbalance < 0) { //If the result is less than 0 multiply by -1 to make number positive
      (Inbalance * -1);
      if (Inbalance > 5) {


      }
    }
  }
}
    (bar - Setpoint); // Carry out input pressure - setpoint

Hey Tom, get your calculator. What is 45 minus 18. No, no, don't tell me.

Why would you perform the calculation if you don't care what the result is?

      (Inbalance * -1);

Now, Tom, what is 18 times -1? No, no, don't tell me.

Why would you perform the calculation if you don't care what the result is?

      if (Inbalance > 5) {


      }

Should you do more there than hammer the return key?

I am aware that the if statement isn't complete - I was focussing on the other points.

I want to subtract my incoming value away from my set point, if it is a negative number I want to multiply it by -1 to give me a positive number. If my positive number is less than 5 then I want to start the loop again because I am in my deadband. If it is larger than 5 then I wish to move on to proportional control.

I think this is due to the part where I am setting the internal accumulator to 0 but I don't know how to avoid this.

(bar - Setpoint); // Carry out input pressure - setpoint
float Inbalance = (bar - Setpoint);

Are you saying that because I have made a float called Inbalance which is (bar - Setpoint), there is no need for the calculation?

I still can't see why you're saying I don't need (Inbalance * -1);... This is what should make it positive if its negative? I then move on the next stage which all depends on that value?

Are you saying that because I have made a float called Inbalance which is (bar - Setpoint), there is no need for the calculation?

You do the calculation (again) when you use the result to set the value of Inbalance. The first bit of code is about as useful as

   47;

I still can't see why you're saying I don't need (Inbalance * -1);

I want you to learn for yourself. Print the value of Inbalance before and after that statement. Does the value in Inbalance change?

There is a huge amount of difference between

   Inbalance * -1;

and

   Inbalance *= -1;

There is also a reason for using camelCase for names. It's either imbalance or InBalance.

You can use the abs() function to turn negative numbers into positive numbers

if (abs(bar - Setpoint) >= 5.0) {
  // Do stuff
}

Thank you for your help once again!

Below is what I have so far…
The idea is that the accumulator drops up or down each second (depending on the difference between the pressure and the set point)… this is turn increases or decreases the output to the fan to fan.

I can’t get this to function how I described. Any suggestions?

const int gain = 2;
const int proportionalband = 5;
const int Setpoint = 20; //Setting up the pressure setpoint
const float PressureInput = A0; //Setting up the pressure sensor input
float fanoutput = A9;
unsigned long lastTimeSomethingHappenedEverySecond = 0;
unsigned long lastTimeSomethingHappenedEverySecond1 = 0;
int intacc = 0;



void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

}

void loop() {
  // put your main code here, to run repeatedly:

  float sensorValue = analogRead(PressureInput);
  float voltage = (sensorValue / 1023.0) * 5.0; // Converting raw value in to a voltage reading
  float bar = (voltage * 8);

  Serial.print (" BAR: ");
  Serial.println(bar);

  float inbalance = (bar - Setpoint);  // Carry out input pressure - setpoint
  if (bar != Setpoint) { //If the input pressure isn't equal to the setpoint then move to dead band

    if (inbalance < 0) { //If the result is less than 0 multiply by -1 to make number positive
      inbalance *= -1;
    }
    else if (inbalance >= 0) {
      inbalance *= 1;
    }
    Serial.print (" Difference ");
    Serial.print (inbalance); //Printing the value of inbalance to the serial monitor

  }


  if (inbalance >= 0.5) { //If the inbalance is equal to or greater than 5, move on to proportional control
    if (inbalance > proportionalband) { //If the inbalance is greater than the proportional band, set internal accumilator to 0
      (intacc = 0);
    }
  }
  float propresult = (inbalance * gain * 1.5); //propresult = inbalance times the gain and add 50%
  if (inbalance < proportionalband) { //If the inbalance is less than the proportional band, multiply it by the gain and add 50%
    unsigned long now = millis();
    if ((propresult >= Setpoint) && (now -  lastTimeSomethingHappenedEverySecond >= 1000)) {  //If the propresult is greaterthan or equal to the setpoint and one second has passed since we were last here, add one the internal accumilator
      (++ intacc);
      (lastTimeSomethingHappenedEverySecond += 1000);
    }
  
  else if ((propresult <= Setpoint) && (now -  lastTimeSomethingHappenedEverySecond1 <= 1000)) {  //If the propresult is lessthan or equal to the setpoint and one second hasn't passed since we were last here, subtract one from the internal accumilator
    (-- intacc);
    (lastTimeSomethingHappenedEverySecond1 = now);
  }
    }

  

  if (intacc > 100) { //If the internal accumilator is great than 100, set it to 100
    (intacc = 100);
  }
  if (intacc < -100) { //If the internal accumilator is less than 100, set it to -100
    (intacc = -100);
  }

  Serial.print (" Acc ");
  Serial.print (intacc);
  float finalresult = (propresult + intacc);
  Serial.print (" Final output ");
  Serial.print (finalresult);
  if (finalresult > 100) { //If the final result is greater than 100, make it 100
    (finalresult = 100);
  }
  else if (finalresult < 0) { //If the final result is less than 0, make it 0
    (finalresult = 0);
  }
  (fanoutput = finalresult);//Output the final result


  delay (10);

}

Danhilly:
Any suggestions?

Yes, if you want more suggestions, follow the ones you have already been given first.

Other than not using the abs function I don't see that I have missed anything you have previously suggested?

Please bare in mind I am used to flowcharts with yes/no's and ladder... I am really not finding this easy.