Tuesday 16 December 2014

Arduino air flow meter "test"

This post is about a device to measure air speed for use in a RC plane when a suitable version is designed. Unlike pressure based devices, I went with a thermistor version that is built with cheap parts. Also, the use of thermistors should result in a sensor with a lower minimum measurement value, but has a smaller range of operation.

Anyway this is only a proof of concept to see what can be done using only an Arduino and no opamps or additional electronics. Suprisingly the device worked quite well, although not really suitable for an air speed meter due to somewhat small dynamic range and slow response ( to be rectified in a future version) The response is of about 5 seconds with a thermistor 5mm diameter.


Basically the arduino is outputting a current to the thermistor ( TH1 ) and thus heating it in order to keep it's temperature a constant amount above air temperature. Any airflow over the thermistor will cool it, therefore requiring a higher current to keep the temperature constant. The second thermistor is used as a reference, in order to keep air temperature changes from appearing as air speed changes.

This is all quite simple, it's actually quite surprising I have not found a similar project. Anyway, the main problem of this design is the current and voltage the arduino can source, so limiting the max power that can be output to the thermistor. This is main reason for the low dynamic range of the device right now.

Due to above limitations, a suitable thermistor has to be found so that it's resistance is not too high ( we only have 5 volts supply) and not too low ( we only have 20 mA) Yeah, perhaps an extra transistor might be the way to go here. Anyway I settled for 2x500 ohms NTC thermistors because this was the shop had, and i'm not waiting months for parts. Note that a suitable range is probably 250 -  750 ohms.

The 1k divider resistor is also not important, it's there as a pull-up for measuring the thermistor temperature. A higher value could have allowed me to use analog 1.1V reference, and possibly add some resolution, but this was the prototype.

The sensitivity is probably better then the human skin and the max readout is somewhere around 5km/h (guess). It could be used for checking if a fan is functioning, or to close a window if there is too much wind coming through. It can also sense airflow made by people walking by it.

For testing it could be covered with a cup, it can easily sense random air currents and breathing.

Some more things about it in no particular order.
  •  not very linear
  • good low speed accuracy
  • low max readout
  • zero reading at an arbitrary offset ( needed to heat the thermistor)
  • slow transient response due to thermistor mass ( 5 seconds )
  • needs setting a pid
  • might be sensitive to infrared if it hits only one thermistor
  • might be sensitive to air temperature to some degree
  • directional to some degree due to thermistor shape
  • needs startup time of several seconds
  • it surprisingly works
  • could connect resistors to vcc to save pins
  • maybe even use internal pullups and 1.1 analog reference.
  • could be improved by accounting for thermistor nonlinearity and also voltage divider nonliniarity
To calibrate:
To find a good start TEMPDIFF write a sketch that sets pwm to about 80 or modify this one to a constant pwm and read the difference between the start temp and the "heated", or between the 2 thermistors.

To set the pid i have no real guidelines since it depends on everything else, but the kd should be left at zero at least until everything is working fine and better transient response is needed. Thats because most of the time the input will change by only one LSB and as such the kd term only adds noise. The kd term is why I tried to add some averaging.

I started my pid with the ki term, which I increased until the sensor heated up in a few seconds.
Of course with only ki term there were large oscillations , so I added some kp to compensate.

too much overshoot over a few seconds means ki is too high , or kp too low.
oscillations of fast period is probably kp too high.
Pids are always fun but the most fun are the ones made by other people :)

The real method involved some calculations, too.

 the sketch:

// released under The MIT License (MIT)
// basically any use permitted - please look up your copy somewhere else
//
// Arduino sketch to detect airflow using 2 thermistors, 2 resistors
// 1k from r1pin to analog1 and pwmpin ; thermistor1 from analog1 and pwmpin to gnd
// 1k from r2pin to analog2 , thermistor2 from analog2 to gnd.
// thermistors 500 ohms in this version http://www.jaycar.com.au/products_uploaded/RN3434.pdf
// silverx 2014
//

const int pwmpin = 5;   // this pin has to be pwm capable
const int r1pin = 7;    // digital out pin
const int r2pin = A5;   // digital out pin
const int analog1 = A0;    // analog input capable pin
const int analog2 = A2;    // analog input capable pin

// PID constants depend on thermistor size, resistance and thermal constant
// this will need to be found for a particular thermistor
const float kp = 20.0;
const float kd = 10.0;         // kd should probably be zero in most cases
const float ki = 1.0;

const int TEMPDIFF = 40; // difference to maintain between thermistors ( in analogread units)
                         // positive for NTCs
void setup()
{
 
  Serial.begin(57600);
  Serial.print(F("Thermistor airflow test sketch\n"));

  pinMode( r1pin , OUTPUT);
  digitalWrite( r1pin , HIGH);
 
 // analogReference(DEFAULT);
 // analogReference(INTERNAL);
 
  pinMode( pwmpin , OUTPUT);
  //digitalWrite( pwmpin , 1);
 
  // we could warm up the thermistor here for a few seconds
  // that will avoid the full scale read at startup
 
  pinMode( r2pin , OUTPUT);
  digitalWrite(r2pin, HIGH);
}

// filter made by online generator at
//http://www.schwietering.com/jayduino/filtuino/index.php?characteristic=bu&passmode=lp&order=1&alphalow=0.01&noteLow=&noteHigh=&pw=pw&calctype=float&run=Send
// this filter is about 0.1 Hz ( 10 seconds) ( 1 / 0.01 * 100ms loop)
//Low pass chebyshev filter order=1 alpha1=0.01
class filter
{
    public:
        filter()
        {
            v[0]=0.0;
        }
    private:
        float v[2];
    public:
        float step(float x) //class II
        {
            v[0] = v[1];
            v[1] = (3.053896819732e-2 * x)
                 + (  0.9389220636 * v[0]);
            return
                 (v[0] + v[1]);
        }
};

filter lpf, reffilter;

float outpwm = 0;
//int treshold = 280;
//int lasterror;
float integralterm;
float lasterror;
float roundingerror = 0;

void loop()
{
  float input = 0;
  float ref;
  int treshold;
  float airflow;
 
  digitalWrite( r2pin , HIGH ); // R2 pullup on
 
  ref = analogRead(analog2);  // read thermistor 2 ( reference)
 
  digitalWrite( r2pin , LOW ); // pullup off ( so it does not heat up the thermistor )
 
  ref = reffilter.step(ref); // filter the reference with a low pass filter,
                             //  otherwise it will add (a little) noise
                            
  treshold = constrain ( ref - TEMPDIFF , 0 , 1024);  // this is what the pid tries to get to
 
  pinMode( pwmpin , INPUT); // turn off the pwm pin or we can't read the thermistor
  pinMode( r1pin , OUTPUT );  // turn on pullup R1
  digitalWrite( r1pin , HIGH );
 
  for ( int i = 0 ; i <10 ; i++) // even a single read works
 {
  input = input + analogRead(analog1); // read thermistor 1
 } 
  input = input / 10;
 
  pinMode( r1pin , INPUT ); // this adds current through the thermistor
                            //  and pwm ( sink) if left on (but still works)

  pinMode( pwmpin , OUTPUT); // we can turn on pwm again
  analogWrite( pwmpin , outpwm);


 // this is for checking
  Serial.print( input );
 
  Serial.print( " " );
  Serial.print( ref );
 
  float volts = (float) 5000.0 * input / 1024 ;
  float resistance = 1000.0 * volts / ( 5000 - volts );  // r1 = 1000 ohms,
  float power = 5.0 * 5.0 * ( (float) outpwm / 255 ) / resistance;
 
//  Serial.print( " v:" );
//  Serial.print( volts/1000 );
 
//  Serial.print( " th1:" );
//  Serial.print( resistance );
 
//  Serial.print( " p:" );
//  Serial.print( power );
 
  Serial.print( " " );
  Serial.print( outpwm );
 

float error = input - treshold; 

// this is the PID which heats up the thermistor
 
  outpwm = ( kp * error  + kd * ( error - lasterror ) + integralterm * ki );
 
  lasterror = error;

  integralterm = integralterm + constrain ( error , -128 , 128 );// not really needed
 
  integralterm = constrain( integralterm , -25 , (float) 255 / ki ) ; // prevents integral windup, especially at startup,
                                                                      // but at full scale output too.
 
//  Serial.print( " " );
//  Serial.print( integralterm );
 
//  Serial.print( " " );

  airflow = lpf . step(outpwm);     // the pid out is the sensor output, the filter removes some of the noise
                                    // the ki term can also be used as an output
  // should be used between 0 - 255
  Serial.print( " airflow:" );                                 
  Serial.print( airflow );
 
  outpwm = constrain( outpwm , 0 , 255 );
  Serial.println();
 
 delay(100); // variable loop time will affect the pid coefficients and the low pass filter
}

// END OF SKETCH