Reactive Sensor / Response Programming

Hi All,

Hoping someone might be able to point me into the right direction in regards to how best to program for a certain scenario.
I am not after someone to code this for me, but just want to get a better idea of how to understand how i can achieve my objective.

Anyway basically i am looking at conditioning an air stream, to achieve a set humidity within the stream of air. My current setup is that an air source (aquarium pump) moves air through a splitter, which runs two separate streams, one stream of air goes strait to a T piece which joins into a measurement chamber (which will have a DHT style temperature and humidity sensor within it), the second stream of air runs through a bubbler, which connects to the T piece of the measurement chamber.

On the Direct to measurement chamber line, i have attached a proportional valve, which requires a very small amount of movement to adjust the flow of air through the bubbler, and increase the humidity. Basically the theory is that air will follow the easiest path, which is normally not through the bubbler (as it has to move water in part), however when i adjust the valve on the direct line, the flow of air becomes harder down that line, and it gets diverted to the bubbler where it increases the temperature.

Anyway my question is, i can control the valve with a servo, to adjust the amount of air through each stream. But what is the best way to have the system determine the amount the valve should be required to be opened to adjust the humidity to the appropriate value. In essence, rather than an on off system, how is the best way to code it so that if the humidity is close to the set valve (say 50%) it would open the valve only a little bit to keep it within range, but if the humidity was 60% it would close the servo (or move it to 90) to bring the humidity back down quicker.

Appreciate any thoughts on how to handle the response to the sensor, so that the output of the device is proportional to the sensor value. I.e. if it is set at 50% and reads 51 it won't open the valve up fully and the reading drop rapidly to 40, rather open it a tiny bit, and let it drop to 50.

Thanks!

Anyway basically i am looking at conditioning an air stream, to achieve a set humidity within the stream of air.

Ok.

and it gets diverted to the bubbler where it increases the temperature.

Hmmm, I think you missed that left at Albuquerque. How does the bubbler increase temperature?

But what is the best way to have the system determine the amount the valve should be required to be opened to adjust the humidity to the appropriate value.

You know where you are, humidity-wise. You know where you want to be, humidity-wise. If you are below where you want to be, move the servo one way. If you are above, move the servo the other way.

How much to move depends on how far you are from the desired point, and how fast the change in position affects the humidity reading. If a 10 degree change in position results in a 1% increase in humidity a week later, the change in position is hardly critical. If a 1 degree change in position results in a 50% change in humidity in 10 minutes, then the change in position is critical.

If that is the case, you probably want to look at PID control.

We don't know enough about your system to really be able to help.

When designing such control system, the best start is to understand the system behaviour (ie valve position against humidity, and the speed of the humidity change upon valve change). So as a first step I would measure those parameters manualy and depict them on a graph to see the functions (ie. in Excel). The next step is to decide which kind of control loop we have to use (PI, PD, PID)..

Whoops, earlier post i meant humidity. As the air passes through the bubbler it increases humidity not temperature.

Anyways i did some testing, and have a little test rig set up with just a servo which drives a small air valve. Because the valve is small it lets me adjust the control of the bubbler through quite a small window of movement with a small servo. Basically if i send the command 110 (as in 110 Deg) it will have the bubbler off, if i send the command 60 (as in 60 Deg) it will have the bubbler fully on.

What i find is that the humidity increases significantly when on full, say 1 - 3% per 20 - 30 second window, whilst the humidity drops much lower when it is currently off. This fits in with my thoughts as to what was happening with manual control of the system, so the first part would be to monitor the humidity to say 50% +/_ 10%, it would then use this value in its control to determine to keep the servo position the same (if within the appropriate range), or if humidity is to high, it would increase the servo Deg number reducing the flow, say by 10 - 20 each time, and if it was to low, it would decrease the servo Deg number to increase the humidity.

So my question is how is the best way to control that, is it work looking at a case or series of IF statements to work out where to control, for example

If RH = Set Humidity then
do nothing

if RH greater than 5% of set humidity then
Increase Servo Degree's by 10 to a highest count of 110

if RH greater than 10% of set humidity then
Increase Servo Degree's by 20 to a highest count of 110

if RH greater than 15% of set humidity then
Increase Servo Degree's by 30 to a highest count of 110

if RH greater than 20% of set humidity then
Increase Servo Degree's by 40 to a highest count of 110

if RH less than 5% of set humidity then
Decrease Servo Degree's by 10 to a lowest count of 60

if RH less than 10% of set humidity then
Decrease Servo Degree's by 20 to a lowest count of 60

if RH less than 15% of set humidity then
Decrease Servo Degree's by 30 to a lowest count of 60

if RH less than 20% of set humidity then
Decrease Servo Degree's by 40 to a lowest count of 60

then just throw in some form of counter, so that if a lower or increase command is issued, and no movement of humidity occurs within X amount of time, the system would report an error (to prevent infinite loops etc)

if RH greater than 5% of set humidity then
Increase Servo Degree's by 10 to a highest count of 110

If RH is expressed in %, and the set point is to, then you don't want a percentage of the difference between the current value and the set point.

What you should do is measure the actual humidity. Compare that to the set point. If more than 5 points off, increase or decrease the servo position. Then, wait some period of time.

Then, repeat the process. If the actual humidity is still too low, open the valve further. If it is still too high, close it further. If you are only looking at +/- 10%, this is good enough.

If you are looking for tighter control, then, you can base the change of position of the difference between the current value and the set point. Small difference == small change. Big difference == big change.

Or, you can decrease the time between readings. Or both.

Thanks for your help so far guys, I have a long way to go but got a demo version or at least prototype going that does respond to humidity and temperature differences based on the difference between expected (50%) and actual humidity. It has a long way to go, and it tends to be not as reactive as i would like, or often over-reactive so it is a challenge to achieve that fine balance.

// zoomkat 10-4-10 serial servo test
// type servo position 0 to 180 in serial monitor
// for writeMicroseconds, use a value like 1500
// for IDE 0019 and later
// Powering a servo from the arduino usually DOES NOT WORK.

String readString;
#include <Servo.h>
#include "DHT.h"
#define DHTPIN 2     // what pin we're connected to
#define DHTTYPE DHT22   // DHT 22  (AM2302)
DHT dht(DHTPIN, DHTTYPE);

Servo myservo;  // create servo object to control a servo
int p;
int n;
float d;
long c;
int upperlimit;
int lowerlimit;
int valvehigh;
int valvelow;
int proportion;

void setup() {
  valvehigh = 120;
  valvelow = 60;
  proportion = 2000;
  Serial.begin(19200);
  myservo.attach(9);  //the pin for the servo control
  Serial.println("Humidity Measure Controller"); // so I can keep track of what is loaded
  dht.begin();
  n = 120;
  p = 120;
}

void loop() {
  int w = 2000;
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  d = h - 50;

  if (isnan(t) || isnan(h)) {
    Serial.println("Failed to read from DHT");
  } 
  else {
    Serial.print("Humidity: "); 
    Serial.print(h);
    Serial.print("\t %, \t");
    Serial.print("Humidity difference: "); 
    Serial.print(d);
    Serial.print("\t %, \t");
    Serial.print("Temperature: "); 
    Serial.print(t);
    Serial.print("\t *C, ");
    Serial.print("Position: "); 
    Serial.print(n);
    Serial.println("\t , ");
  }

  if (d >= -50 && d <= -41){
    if (n >= valvelow){
      n = n - 6;
      myservo.write(n);
      w = proportion / 1000;
      delay(w);
    }
  }

  if (d >= -40 && d <= -31){
    if (n >= valvelow){
      n = n - 5;
      myservo.write(n);
      w = proportion / 1000;
      delay(w);
    }
  }

  if (d >= -30 && d <= -21){
    if (n >= valvelow){
      n = n - 4;
      myservo.write(n);
      w = proportion / 500;
      delay(w);
    }
  }

  if (d >= -20 && d <= -11){
    if (n >= valvelow){
      n = n - 3;
      w = proportion / 250;
      myservo.write(n);
      delay(w);
    }
  }

  if (d >= -10 && d <= -6){
    if (n >= valvelow){
      n = n - 2;
      myservo.write(n);
      w = proportion / 25;
      delay(w);
    }
  }

  if (d >= -5 && d <= -2.5){
    if (n >= valvelow){
      n = n - 1;
      myservo.write(n);
      w = proportion;
      delay(w);
    }
  }

  if (d >= -2.4 && d <= -1){
    if (n >= valvelow){
      n = n - 0.25;
      myservo.write(n);
      w = proportion;
      delay(w);
    }
  }

  if (d >= -0.9 && d <= 0){
    w = proportion;
    delay(w);
  }



  if (d <= 50 && d >= 41){
    if (n <= valvehigh){
      n = n + 6;
      myservo.write(n);
      w = proportion / 1000;
      delay(w);
    }
  }

  if (d <= 40 && d >= 31){
    if (n <= valvehigh){
      n = n + 5;
      myservo.write(n);
      w = proportion / 1000;
      delay(w);
    }
  }

  if (d <= 30 && d >= 21){
    if (n >= valvehigh){
      n = n + 4;
      myservo.write(n);
      w = proportion / 500;
      delay(w);
    }
  }

  if (d <= 20 && d >= 11){
    if (n <= valvehigh){
      n = n + 3;
      myservo.write(n);
      w = proportion / 250;
      delay(w);
    }
  }

  if (d <= 10 && d >= 6){
    if (n <= valvehigh){
      n = n + 2;
      myservo.write(n);
      w = proportion / 25;
      delay(w);
    }
  }

  if (d <= 5 && d >= -2.5){
    if (n <= valvehigh){
      n = n + 1;
      myservo.write(n);
      w = proportion;
      delay(w);
    }
  }  

  if (d <= 2.5 && d >= 1){
    if (n <= valvehigh){
      n = n + 0.25;
      myservo.write(n);
      w = proportion;
      delay(w);
    }
  }  

  if (d <= 0.9 && d >= 0){
    w = proportion;
    delay(w);
  }  



  delay(1000);  
}

and it tends to be not as reactive as i would like

So, speed things up, wherever you can.

  Serial.begin(19200);

You can send data 6 times that fast. You are sending a lot of data, so sending them faster means you can check sensors again, sooner.

  if (d >= -50 && d <= -41){
  if (d >= -40 && d <= -31){
  if (d >= -30 && d <= -21){
  if (d >= -20 && d <= -11){
  if (d >= -10 && d <= -6){
  if (d >= -5 && d <= -2.5){
  if (d >= -2.4 && d <= -1){
  if (d >= -0.9 && d <= 0){
  if (d <= 50 && d >= 41){
  if (d <= 40 && d >= 31){
  if (d <= 30 && d >= 21){
  if (d <= 20 && d >= 11){
  if (d <= 10 && d >= 6){
  if (d <= 5 && d >= -2.5){
  if (d <= 2.5 && d >= 1){
  if (d <= 0.9 && d >= 0){

These are all mutually exclusive ranges. Using if/else if/else would speed up the loop() function, since the conditional evaluation would stop as soon as a true condition was found.

Of course, the biggest problem is all those delay()s. You won't have reactive code if you twiddle your thumbs, ignoring changes. If you want the system to react quickly, you can not delay() anywhere.

Thanks for your help Paul, it is appreciated.

In terms of sensor speed, since i am using a DHT22 (which from memory) has a read time limit of around 1 second, would much be gained in speeding up the serial communications when the sensor would (i assume) be the limiting factor. I will make some changes to the loop and see how that works.

My question with the loop, is how do i best handle the control of the valve without over adjusting. I had tried without delays and what normally happened was that because I was running Valve + 1 (to a limit of 120) (to close valve or lower humidity), if the loop ran without delays, it would over compensate and fully close the valve, then when it needed more humidity, it would do Valve - 1 (to a limit of 60) and would end up fully opening the valve, and increasing the humidity fully. The effect that this had was that the humidity never remained stable, as it would be doing the close valve, and fully close it letting the humidity drop (too quickly since it has shut the valve off fully), and then to compensate, it would open the valve fully, and humidity would get to high, so it would just run in a forwards and backwards loop, whereby the variation would get up to 20% away from where i want humidity to be, when i really want a 5% tolerance.

Adding the delay helped slightly (in that it prevented the system from being too reactive, and doing the Valve - 1 until the valve was opened or closed), but then it delays the sensor readings and everything else, is there a better way I can get the system to make smaller movements without having to put delays in that would potentially control the valve from being closed too much (e.g. a For loop or something similar?) or is there a better way?

Thanks!

You need to add time elements to your sketch. It may take seconds for a control change to make full impact on the air stream while your program may run through loop() multiple times per millisecond, unless you've introduced delays or blocking code that ruins control response. :grin:

You have a system clock in the form of the millis() function that returns an unsigned long. Study the Blink Without Delay example that shows how to use millis() to determine when a time interval is done. Note that subtracting unsigned integer types (byte, unsigned int, unsigned long) gives you the difference every time, even across roll-over.

How long should you wait between change and the next read? How long does the change take if you control the valves by hand? Even if the relationships are complex, you should be able to put together a table and store that in PROGMEM then use it to gauge your valve changes and how long to wait for things to settle enough to check.

I'd go so far as to say it's better to make change towards a goal more gradual as you get closer rather than pendulum across it. Think of how you control the gas pedal (aka accelerator) while you drive, unless you're a racing fan.

So that I understand, is the best way to do it to essentially have an IF / IF Else / ELSE statement which picks the number of the range of the difference to work out what sort of movement is required, that is how far away from the set point the humidity is, and then to have something like the blink without delay example that checks sensor readings, and if the sensor has not moved, it waits in its own loop still taking the readings and pseudo 'waiting' for the period of time, but if the sensor has changed, to not wait and go back to the parent IF / IF Else / ELSE loop. That way it would not be waiting for too long and missing significant changes in the humidity, but also not reading right away and responding to quickly to under or overshoot the mark.

long time = 0;
boolean timeHasCome = false;
void setup() {
	}

void loop() {

	doReadings();

	timeHasCome = millis() - time >= 1000; // 1000 milliseconds equals 1 second
	if (timeHasCome) {
		// update timer
		time = millis();
		doSomething();
	}
	else {
		doSomethingElse();
	}

}

Yes, you have time on Arduino down nicely. It's a good tool. It lets you wait on one action while the rest of your code (doSomethingElse) still runs.

Instead of a fixed-length 1 second between reads,
timeHasCome = millis() - time >= wait; // allows other code to set, change or end the wait

timeHasCome should be an unsigned long as should time and wait. The last two should have more meaningful names like startTime and readInterval as well that relate what they are used for.

Perhaps you read 1x or 10x or 100x a second (because it possibly could read maybe 10,000 times a second) and adjust the valve only when the reading changes. How many times a second depends on how fast humidity can change in your rig.
You have to test for that and maybe you have. Sense about twice as often as that. In fact you can code that if your reads are giving you back-to-back changes then it makes the read interval smaller and be self-optimizing as to response.

Your valve adjustments are now incremental, like n = n - 6. How about setting valve position as a function of the value d? Perhaps use the map() function like n = ( d, -50.0, 50.0, valvelow, valvehigh )? Or maybe that will be too linear, I dunno. You won't need the if-else-etc block at all.

Your adjustments can become very small so you might set a lower limit on valve adjustment even if just to keep the servo from vibrating once humidity is within tolerance. And do set a tolerance, exact only exists in perfect worlds.

What you are doing is a kind of shortcut PID controller. PID can go from complicated to an old style house thermostat. If this above don't cut it then start reading up on PID.