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


















Friday 24 October 2014

NRF24L01+ and data whitening (arduino)

25.10.2014

The NRF24L01+ IC is a cheap and relatively versatile way to add radio transmission to a project, however this versatility comes at a price: Some features and controls are not included, such as modulation and encoding types, PLL and IF settings, RSSI adc, etc.

This post investigates if data whiting, which is included in some competitor's chips, would actually provide an improvement at longer range transmissions. Said missing data whitening could be implemented in software,  but the performance benefit must be present in order to make best use of available resources. There is also the possibility that whitening could have a negative effect. ( which turns out to be true)

Whitening works by attempting to break up long chains of zeroes and ones that may occur because of the particular data being sent. This is done by XORing the data to send with a known string of random numbers, and repeating the process at the receiving end with the same numbers.

Anyway, for the practical side of the test I used ( in the first tests) an arduino controlled mobile NRF tx and a similar static rx, with it's accompanying arduino connected to my PC for logging. The speed was set to 250k, no shockburst, 10 bytes payload of which the first and last were zero and soft crc respectively.

For measurement I have logged the percentage of received packets filled with repeating 8bit data. Each packet would be sent a number of times to find a receive rate. For this test I used approx 1000 packets for each value tested.

After doing this for every 8 bit number I hoped I will have a result of the possible effectiveness of data whitening. I had doubts about the effectiveness of this test, because 2.4 Ghz is very susceptable to multipath propagation. While running a test for several hours, things like people and cars moving make differences in reception. I used 2 alternating channels since the software already had that feature.


Each pink dot represents 100 packets.

In the above image a gap just before 128 can be seen, which implies it is caused by multiples ones.
A similar gap is present before 64, too. Also the above data is somewhat repeatable, but a lot of noise is masking a definable pattern, while also not excluding other, non whitening related mechanisms. For instance, the frequency modulation (GFSK) could be subject to different propagation or multiple paths, or interference from other devices  could be the cause ( jamming more ones, for example).

I decided it was probably interference , but I still tried some more tests. At this point I changed the amount of channels the NRF used in order to exclude some frequency related mechanisms. I figured 8 channels should be of benefit.


the highest light blue line is the sum of all channels. the 45 degree line is the binary number transmitted. There were probably 500000 packets sent for this ( measly) graph.

In the above graph "helped" by some image editing, it can be seen that the drop before 127 is still there. But you have to be very optimistic to see a pattern in this graph, and humans are known to see what they want to see, too.

Using 8 channels did not help much, in fact one channel was usually not receiving anything due to positioning, or swinging up and down adding noise to the graph. Anyway, i needed a better method since this is not repeatable, and drops in rf transmission add noise to the actual data needed.

Since using incremented values of payload did not work so well I changed to a different scheme, using random numbers instead as to mask the slow moving propagation changes.

The random number rx software needed a different method of counting packets and so i placed each received packet in a "bin" depending on it's value. I used 256 bins, and stopped the test when a bin reached 255, since arduino is somewhat low on memory. ( 255 was not transmitted due to the arduino random() function not including the top value)

The internal arduino number generator  is used ( c++ ) and as I understand it shouldn't have any problems dividing the data in between 8 channels, since 8 is a pretty low number.


As usual, I thought this wasn't good enough. I thought perhaps, using random numbers was not the best idea since they are basically white noise, hence adding noise to the graph. I didn't know it at the time, but differently arranged the graph makes quite a good point about the efficiency of data whitening.

Next, and last (at least for now) test involved replacing the random number generator with a " random sequence" 255 bytes long. I figured the sequence will reduce the noise overall, since each number will be transmitted the same amount of times as any other. The 255/ 8 channel combination is less then ideal, but at least it is not a multiple. Luckily the LFSR pseudo-random sequence generator can only generate 255 bytes long sequences instead of 256. Zero was not included this time, for a change.



The line type graph makes the data look a lot better, at least to me, an XY graph is actually not so different then the previous test . Of course, I have been staring at lists of binary numbers quite a lot lately, so I can hardly tell.
Arranging the data by number of ones can show exactly what is happening:



In the above graph the values are arranged in order of number of "ones" out of 8, ascending. It becomes clear that sending a lot of ones is a really bad idea. Sadly , for data whitening to work the same has to be true for zeroes, and that is not true here, at least for the settings used.



I originally thought that reception might be affected not only by one/zero ratio but by amount of consecutive ones ( or zeroes) . In the above (unlabeled) graph I plotted the same data arranged by number of consecutive ones, ascending  ( the ones are counted including neighbours ) . This is very similar to the previous graph. In case you can't tell, the linear regression line is at the same angle in both. To me this means the consecutive-ness of ones is not affecting reception much.

In the graphs there are some spikes that may imply a second influencing pattern. However, there is still a lot of noise, and people, I included, are very good at seeing patterns in random data that are not actually there. One such pattern seems to be 01010000 and it's shifted relatives.

In conclusion, the NRF24L01+ chip ( non fake / copy, etc) does not include data whitening because it would achieve not much. For my application , I decided to use RLL codes (  run length limited ) specifically a  GCR: (0,2) RLL code (inverted) that originally was used for magnetic media. I will probably implement them any day now. Since such codes are 4 to 5 encoding, and as such 8 to 10 they will require a slightly longer payload. That opens a whole new can of worms.




















Thursday 21 August 2014

ESC capacitor for RC brushless motors

In the process of upgrading various things on my plane, eventually my attention reached the capacitor connected in parallel with the battery, as part of the ESC. Some questions that arise are: Is it really needed, what is it's optimal size and what ESR should it have.

I proceeded to find out if it could have any effect by finding the impedance of a Li-Poly at frequencies such as those used in this application. The ESC uses 8KHz PWM when under 100%, so if battery ESR degrades after 8Khz a capacitor could, potentially, help. Sadly information about the impedance of Li-poly batteries vs frequency is not that easy to find. In total I only found 2 graphs, but luckily they agreed pretty well. In them the impedance starts rising at just about 10Khz, interestingly enough. That could mean the higher harmonics of the 8KHz PWM could potentially use the capacitor.

 Testing:

Next comes the testing part. I used the RC plane with an adapted 1000uF low ESR capacitor and a switch in between the battery and ESC  . It uses it's own piggyback connectors which I usually use to measure current. Overall the leads are still very small,  about 10 cm. The battery is 7.4 300mA, a rather small size for RC. The motor draws max 3.5A. The ESC still has it's 100uF capacitor.

I used partial throttle and measured RPM to check if any difference exists when the 1000 uF capacitor is switched in and out. I was surprised that a difference in RPM can actually be seen, but it was very small and hard to measure since the the RPM changes by itself to some degree as well. I eventually obtained several values so that the improvement can be quantized.

After plugging the numbers in various wab pages and programs, I came up with the following: The RPM is incresed about 0.15% to 0.30% at partial throttle. I checked only the lower 1/2 of throttle range since measurement accuracy was too low for the top half.

The RPM increase is very ( or is it fairly) small, but the result gets slightly better when the thrust vs rpm formula is used. I looked up the propeller used ( 6030 GWS copy )  and it's thrust (g) =  6.3e-007*(RPM^2.1) .

After some juggling the thrust percentages work out to approximately double: 0.24 to 0.67% . Note this includes errors so the real answer is probably about 0.3 - 0.5% increase. This hardly justifies the addition of a capacitor where lead size is small, but at least is shows an actual amount of improvement exists.

Calculating optimal capacitor size:


The battery internal resistance and the capacitance form a filter, which I wanted under 4KHz so that it may possibly improve impedance at the ESC. As such the capacitor is proportional to the battery ( and leads) internal resistance as in the formula :  C = 1 / ( 2* PI * Rint * frequency  )

The frequency chosen is arbitrary since the battery impedance is not known. As such you could use the actual pwm frequency, or a lower value such as PWM/3 for more extreme cases. If capacitor ESR is not low enough compared to the battery internal resistance ( it should be similar to it or lower)
using larger capacitance values will not do much, as the ESR will be the limiting factor. If cap ESR is higher then say, 2x battery internal resistance the capacitors will likely not do anything.

Scope comes out:


After all this I wanted to see what actually happens, so I hooked up the scope and looked at the waveforms trying to connect what is happening to where. A look at the (unsteady) motor waveform did not show anything changing. I soon realized I was not going to see a 0.5% change on a scope screen. But I reluctantly connected the probe to the battery, where the capacitor is connected. (not a very good place, really) . I could then see a 80mV sawtooth-like voltage drop reduce to a 50mV drop.
As luck would have it, the change is of the right magnitude, i. e. zero point something percent.
I will accept this result due to the fact this has used up more time then necessary for the potential improvements.
 Above waveform with no cap, under waveform with cap. Waveform needs mental inversion.





Overall, it looks like the cap provides some more voltage , like I assumed, by reducing the voltage drop. Since the cap has to charge from the battery on the off cycle , it will not do anything at full throttle, where no PWM is used. But, since the cap increasees thrust at partial throttle, it's effect is no different then increasing the throttle manually by 0.5% . As such , in my case the weight and size of the cap is not useful ( in a plane) in exchange for a throttle offset.

This does not account for long leads, where voltage spikes could damage the ESC ( or the cap). It also does not cover reverse current flow (into battery) from regenerative breaking. Such applications do, and could, respectively, benefit form capacitors.

In other applications, where batteries with much lower internal resistance are used, it is hard to believe the capacitor ESR can account for a high enough fraction to make any difference. In such cases good quality, low ESR capacitors could be paralleled together.

It is likely the capacitor/s could help more in aging, higher internal resistance batteries, but, overall, since there is no efficiency improvement, the capacitor can't overcome the bigger problem that, after all, partial throttle is, well , something arbitrary.





from "Fast Estimation of State of Charge for Lithium-Ion Batteries
Shing-Lih Wu  , Hung-Cheng Chen  and Shuo-Rong Chou "



A schematic of the resistances involved, the 47uF cap is actually 100uF .








































This page has been intentionally left blank






Friday 7 March 2014

Arduino analogWrite with higher resolution

The other day I was troubleshooting a project that did not work as intended, so I eventually put down the problem as inadequate resolution. Even after averaging analogReads, reducing truncation errors and optimising everything it did not work much better.

Of course, ultimately the PWM analogWrite resolution is 8 bit on the Arduino UNO and my project did not even use it's full range, which limited it even more.

After some looking around I actually found out the atmega328 chip supports much higher PWM accuracy, up to 16 bit if necessary, but only on pins controlled by timer1. So I came up with a modified PWM routine that uses 10 bit resolution. (the 16 bit is a bit overkill once considering supply variations)


This is used by calling analogWrite10bit( pin , value); just like the original one but with 0 - 1023 range; Only timer1 pins can be used. That makes for pins 9 , 10 on the UNO and other atmega328 Arduinos. Once called even for one pin you can't use normal pwm on EITHER pins, so you have to use this routine for both.

Not tested on mega, leonardo but might work on corresponding timer1 pins.

//
//      GNU LESSER GENERAL PUBLIC LICENSE
//        Version 2.1, February 1999
//
//
// this was modified (slightly) from the arduino source code
//
// works on pins 9 , 10 on Arduino UNO
// once used *both* pins are set to 10 bit mode and "normal" analogWrite will not give correct results.
// uses timer one

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void analogWrite10bit(uint8_t pin, long val)
{
  // We need to make sure the PWM output is enabled for those pins
  // that support it, as we turn it off when digitally reading or
  // writing with them. Also, make sure the pin is in output mode
  // for consistenty with Wiring, which doesn't require a pinMode
  // call for the analog output pins.

  // set 10 bit mode

  sbi (TCCR1A , WGM11 );
 
  pinMode(pin, OUTPUT);
  if (val == 0)
  {
    digitalWrite(pin, LOW);
  }
  else if (val >= 1023)
  {
    digitalWrite(pin, HIGH);
  }
  else
  {
    switch(digitalPinToTimer(pin))
    {

#if defined(TCCR1A) && defined(COM1A1)
    case TIMER1A:
      // connect pwm to pin on timer 1, channel A
      sbi(TCCR1A, COM1A1);
      OCR1A = val;
      break;
#endif

#if defined(TCCR1A) && defined(COM1B1)
    case TIMER1B:
      // connect pwm to pin on timer 1, channel B
      sbi(TCCR1A, COM1B1);
      OCR1B = val;
      break;
#endif

//    case NOT_ON_TIMER:
//    break;
  
    default:
    analogWrite(pin , val / 4);
 
    }
  }
}