-
Analogue Wind Vane With Auto Set Up
04/16/2017 at 11:00 • 0 commentsAs the amazing online GPRS Weather Station project continues, we have yet another upgrade to the vast array of sensors with a professional analogue wind vane, kindly donated by Vector Instruments. This device will eventually be hooked into the new, upgraded, weather station module that I am designing. The current prototype is producing live data here: http://www.goatindustries.co.uk/weather2/ .
Comparing this sensor to my DIY Digital Wind Vane the first thing I noticed was the extreme sensitivity to wind movement - it has very low torque - so it is able to pick up wind direction even in very small wind speeds. Secondly, being made on a CNC lathe, the quality of the engineering is superb - I really must get myself a CNC lathe!
Here, I am going to show how I set up and coded an arduino to read the sensor, made calculations for non-linearity, took account of the 'dead zone' in the potentiometer, turned the readings into a large numerical array, efficiently calculated the mode value and made the whole device set itself up automatically. No pressure!
This analogue wind vane is based on a very expensive potentiometer with three main connections - 5V, ground and wiper. The wiring inside the deice is very thin so great care is needed not to put too much current through the circuits for too long or it could quite easily get burnt out.
I have protected the wiper part of the circuit with a 10k resistor which feeds a signal to the arduino via pin A0. This is then converted into a digital 10 bit code inside the processor with a full range of 0 to 1024. The device is never fed a continuous supply of power and is pulsed in two different ways. Firstly, there is a load of really fast 50 millisecond pulses which gets us 10 readings at a time, next the whole device goes off for 1/2 a second until the next batch of readings is made. By working out an average (mean), we end up with one fairly accurate reading per second.
So now that we've got this digital reading - what else could be simpler? Surely that's the end of it?
Firstly, when working with analogue to digital conversion in the past, I have noticed significant 'drift' around minimum and maximum values and so some code is going to need to be written to counteract this phenomenon. Secondly, wind can sometimes be extremely turbulent which makes it very difficult to get an accurate output value. If the wind was constantly changing from, say, south to west we would want to process our data in such a way that it would give the most common direction, not just the average or 'mean', otherwise we would just get a value of south-west. To explain in more detail - the wind might veer from south to west and back again, but actually it's mostly coming from the south-south-west. If this is beginning to hurt your brain cells, I totally sympathise!
Fortunately we have solutions to both the above problems - we take as many readings as the system memory and speed will allow and build some truly massive arrays of numbers. We then make a continuous log of the number of times each part of the array is hit and then, finally, spit out the most popular. In the digital wind vane project I wrote some rather clunky code for calculating the 'mode' value and now, with the power of some extra nutritious coffee granules, I have reduced that code to something rather more sublime!
Finally, after about ten minutes, just when our arrays are about to explode the arduino into a million fragments of silicon dust, we retrieve a single wind direction value eg 215 with a second arduino and reset all the large numbers to zero.
Now that we're beginning to feel quite glib about our engineering prowess, the analogue wind vane chucks at us 2 more major problems - ONE: With the recommended 100K load in place, it does not produce a true linear output and TWO: it has a dead zone around the north pole of about 3 degrees. At first, my brain cells started to panic at the prospects of solving the non linearity problem but then they remembered that wind is always slightly turbulent so the question arose: How accurate do we really need to be? Do we really need to look into a lot of detail at the non linearity curve? Common sense then came to the rescue and the curve got chopped and straightened into 2 simple straight lines, with an apex at 240 degrees. Simple!
Parts:
- C1 Ceramic Capacitor voltage 6.3V; capacitance 100nF
- J1 Piezo Speaker
- LED1 Green (560nm) LED package 5 mm [THT]; leg yes; color Green (560nm)
- Part1 Arduino Nano (Rev3.0) type Arduino Nano (3.0)
- R1 100 Resistor resistance 100; package THT; bands 4; tolerance ±5%; pin spacing 400 mil
- R2 2k Resistor resistance 2k; package THT; bands 4; tolerance ±5%; pin spacing 400 mil
- R3 8.25k Resistor resistance 8.25k; package THT; bands 4; tolerance ±5%; pin spacing 400 mil
- R5 100k Resistor resistance 100k; package THT; bands 4; tolerance ±5%; pin spacing 400 mil
- S1 Pushbutton package [THT]
- W200P wind vane from Vector Instruments
- ADS1115 ADC
Arduino Setup
:
This is the easy and fun part and we can make lots of reassuring beeping noises and flickering of LEDs to make us feel better after having brutalised our brain cells so much in the previous steps. The LED flickers because the power supply is being pulsed and the micro speaker beeps when we have got the wind vane in the self calibration zone.
After wiring up the breadboard, we connect the power and point the wind vane to due north, which will be in the dead zone. We then move the vane very slightly to the left and right and there should be beeping noises produced in two separate audio frequencies, representing minimum and maximum range settings being discovered. Do this a couple of times and the sensor is set up and ready for use until the power is disconnected again.
As time goes on, but only when the wind is blowing roughly from the north, the maximum calibration setting is very slowly decreased, or pulled back, until it is over-ridden by a new setting. The same happens with the minimum setting, but in the opposite direction, i.e. pushed forwards.
I did also play about with the capacitor on the wiper pin and found that a 100 nano farad capacitor worked better than the 10 nano farad proscribed in the data sheets in terms of the stability of the end point values. This will, however, affect the sensitivity of the device as it sweeps backwards and forwards over the dead band.
const int analogInPin = A0; // Analog input pin that the potentiometer is attached to const int analogOutPin = 9; // Analog output pin that the LED is attached to const int tonePin = 11; int sensorValue = 1; float outputValue = 0; float rawDirection =0; float maxSensorValue =980.9999; float minSensorValue = 5.0001; int finalDirection=0; int biggestAddingDirection=0; int z=0; int sensorValue2=0; int n=0; int modeSize=0; int c=0; int degree =0; int addingDirection[]= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39, 40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119, 120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199, 200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279, 280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319, 320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362}; void setup() { pinMode (12,OUTPUT); // This provides short pulses of power to the sensor from pin 12. pinMode (13,OUTPUT); pinMode(2, INPUT_PULLUP); digitalWrite(13, LOW); Serial.begin(9600); while (degree<362) // Set all 362 values to zero. { addingDirection[degree] = 0; degree++; } maxSensorValue =984.9999; } void loop() { resetValues(); z=0; n++; sensorValue2=0; while (z<10) // Get 10 quick readings. { z++; digitalWrite(12, HIGH); delay(5); // read the analog in value: sensorValue = analogRead(analogInPin); sensorValue2 = sensorValue2 + sensorValue; digitalWrite(12, LOW); delay(45); } delay(500); // set this to 500 so total delay = 1 second. sensorValue = (sensorValue2/z); // assume that dead band starts at 356.5 and ends at 0 // and values of zero correspond to 360 or 0 degrees: // The maximum analogue reading I am getting on the wiper is 981 // out of a possible range of 1024 (10 bits) if (sensorValue == 0) { outputValue =0; } outputValue = ((sensorValue-minSensorValue)*356.5/maxSensorValue)+1.75; selfCalibrate(); // Checks the maximum range of the analoue readings when sensor comes out of dead band. //////////////////////////////////////////////////////////////////////////////////////////////////////// // Non linearity calculations: // Now assume that max non linearity is at 240 degrees and is +0.53 // Also, assume non linearity itself is linear, not a curve: if (outputValue<240 || outputValue==240) { rawDirection = outputValue*0.53/240 + outputValue; } if (outputValue>240) { rawDirection = 0.53*(358-outputValue)/118 + outputValue; } if (sensorValue == minSensorValue) { rawDirection=360; } ////////////////////////////////////////////////////////////////////////////////////////////////////// Serial.print("sensor = "); Serial.print(sensorValue); Serial.print("\t output = "); Serial.print(outputValue,2); Serial.print("\t adjusted output = "); Serial.print(rawDirection,2); Serial.print("\t Max sensor value = "); Serial.print(maxSensorValue,2); Serial.print("\t Min sensor value = "); Serial.print(minSensorValue,2); Serial.print("\t n = "); Serial.println(n); digitalWrite(12, LOW); degree = (int)rawDirection; /////////////////////////////////////////////////////////////////////////////////// // Special case for rawDirection = 360: if (rawDirection ==360) { degree = 359 + c; c++; } if (c>2) { c=0; } if (degree==361) { degree=1; } ///////////////////////////////////////////////////////////////////////////////////// // Calculate the running mode (Some of these variables will need to be reset by a call back from main processor). addingDirection[degree] = addingDirection[degree] +1; if (addingDirection[degree] > modeSize) { modeSize = modeSize +1; finalDirection = degree; } ////////////////////////////////////////////////////////////////////////////////////// if (finalDirection==359 || finalDirection==360 || finalDirection==1) { finalDirection=360; } Serial.print("Mode size = "); Serial.print(modeSize); Serial.print("\t Degree = "); Serial.print(degree); Serial.print("\t Final Mode Direction = "); Serial.print(finalDirection); Serial.print("\t Adding direction[degree] = "); Serial.println(addingDirection[degree]); Serial.println(""); } // 30 days = 2592000 seconds // Each loop of n x 10 is ten seconds // Every ten seconds max sensor value is reduced by 0.0001 // Every 30 days max sensor values reduced by 2592000 / 10 * 0.00001 = 25.92 degrees // In 3 days of northerly winds max sensor value will adjust by as much as 2.592 degrees. // During northerly winds the sensor will self calibrate: void selfCalibrate() { if (sensorValue > maxSensorValue) { maxSensorValue = sensorValue; tone(11,1000,500); } if (sensorValue < minSensorValue) { minSensorValue = sensorValue; tone(11,2000,500); } if ( ((n>10)&&(sensorValue>900)) || ((n>10)&&(sensorValue<100)) ) // Only adjust min and max sensor readings in a northerly wind. { maxSensorValue = maxSensorValue - 0.01; // Slowly pulls back max sensor value. minSensorValue = minSensorValue + 0.01; // Slowly pushes forwards min sensor value. n=0; } } void resetValues() // Requires a call back pulse of 5 seconds to reset key values. { int callBack = digitalRead(2); if (callBack==LOW) { digitalWrite(13, HIGH); modeSize=0; tone(11,2500,500); while (degree<362) { addingDirection[degree] = 0; degree++; } } else { digitalWrite(13, LOW); } }
Upgrade to 15 Bit ADC and Code Improvements
:
After getting some feed back from technical support at Vector Instruments on this instructable, I made a few small changes to the test rig. They noticed that the 10K resistor that I was previously using to protect the wiper circuit was proberly too big for the nano ADC capacitance infrastructure and so this was reduced to 8.25K and kept in place for the upgrade.
The ADC was upgraded from 10 bit to 15 bit with an ADS1115 to give a much improved range. This was particularly useful when looking at the problem with minimum and maximum values by observing how they fluctuated using the serial monitor.
Another thing that technical support pointed me towards was that unless the coding was designed very carefully, we could get values of 180 degrees near the north point instead of 360. This is because we're taking a quick sample batch of 10 or so readings and taking an average, which is fine as long as the sensor does not 'hover' around north and pick up both very small and very large readings in the same batch. The code for dealing with this is fairly simple, it divides all the readings into two groups - 'big' and 'small' - and ignores the 'small' readings if the number of 'big' readings is bigger. Easy!
// This bit of code below takes care of the instance where the weather vane is hovering around north (hovering zone) and picking up big values and small values in the same batch of 11: // Total range is about 0 to 24000. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (adc0 < 12000) { sensorValue4 = sensorValue4 + adc0; small++; } if (adc0 >= 12000) { sensorValue3 = sensorValue3 + adc0; big++; } if (big > small) { sensorValue = sensorValue3 / big; } if (big < small) { sensorValue = sensorValue4 / small; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
I also added a small amount of code to calculate the variability that I was getting in the readings so that I could assess the accuracy of the device:
void variation() { maxValue = max(sensorValue,maxValue); minValue = min(sensorValue,minValue); variability = maxValue - minValue; Serial.print("Variability in the reading = "); Serial.println(variability); }
In the end, the variability equated to about +- 0.5 degrees near the north point. Variability in the mid range e.g. 180 degrees was pretty much zero although there would be some compound errors introduced due to not knowing the ADC range too well.
Just to prove that math can actually be fun I'll tell you how I discovered the non linearity adjustment formula through diagrams.
Initially, I did not set out to discover the formula - I just wanted to visualise the non linearity created by the load resistor (also known as a 'pull down' resistor). I wanted to try and isolate the curve and plot it as a graph in Microsoft excel.
Firstly, I plotted the actual curve, which is the total resistance created by the combination of the wind vane and the load ... and I called it 'Total (RT)'. Initially, I was disappointed as I could not actually see any curve at all, so then I changed both the axes to log10. Hey presto! I can see a curve! (The blue curve on the left in the diagram above).
The total resistance is given by this formula:
RT w * R L ) / (Rw + RL)
- where:
- RT is the total resistance
- RW is the wiper resistance
- RL is the load resistance
OK, so far so good - not too complicated?
Next, I wanted to see how my new fancy curve looked side by side with a boring straight linear curve, just as if the results were not actually a curve at all, but a straight line. The formula for this is:
RLINw * RTMAX)/ RWMAX
- where:
- RLIN is the hypothetical linear resistance (the 'imaginary' resistance)
- RW is the wiper resistance
- RTMAX is the maximum value of the total resistance
- RWMAX is the maximum value of the wiper resistance
This is all well and good, but where are we going to get the value of the total resistance from? I really though this was going to be easier than this, but then realised that we've already calculated this value above, it's just the maximum value of RT . But just for clarification, here is the formula:
RTMAX WMAX * R L ) / (RWMAX + RL)
- so, now, if we substitute out RTMAX we get:
RLINW * RTMAX) / RWMAX = (RW / RWMAX) * (RWMAX * RL) / (RWMAX + RL) = (RW * RL) / (RWMAX + RL)
Now we can plot our linear 'curve' (in red) and see if it's noticeably different from the curvy curve ..... and yes .... as long as we cheat by using log10, we can see the difference. If we open up the excel file, we can change the value of the load resistor to something stupidly small and get some pretty crazy curves produced.
Finally, I realised that we could then subtract the non linear curve from the linear curve and get a final outcome: the actual non linearity, or the 'difference' between the linear and non linear results. This is the pretty blus curve on the right and is given by:
RDIFFT - RLIN = (RW * RL) / (RW + RL) - (RW * RL) / (RWMAX + RL)
This equation could be reduced further, but the arduino nano is already going to struggle with some of the big numbers produced, so we need to help it along a little bit.
Eventually, the formula gets translated into arduino code:
//////////////////////////////////////////////////////////////////////////////////////////////////////// // Non linearity calculations: load2 = load / 1000 * maxSensorValue; Serial.print("load2 = "); Serial.println(load2); long loadz = load2/5; long sensorValuez = sensorValue/5; long maxSensorValuez = maxSensorValue/5; long a = 5*((sensorValuez * loadz) / (sensorValuez + loadz)); long b = 5*((sensorValuez * loadz) / (maxSensorValuez + loadz)); Serial.print("a = "); Serial.println(a); Serial.print("b = "); Serial.println(b); outputValue = sensorValue + (a - b); //////////////////////////////////////////////////////////////////////////////////////////////////////
Final:
This sensor is quite different from the digital wind vane that I built myself - it works approximately ten times better in very light winds and it's smaller and infinitely more robust. The dead zone is a bit of a pain in the proverbial, but after a bit of hard work, I did manage to get rid of all the 'spurious' readings that I got in the beginning. In some respects, the digital wind vane is better - it never needs setting up and there is no dead zone at north and it's much, much easier to code. There are, however, tiny dead zones between each individual 'bits' of data in the encoder that I used and there's so much torque needed to drive the rotary encoder that the vane has to be big and subsequently ugly. I did research other rotary encoders, but they all seem to have similar torque ratings.
There is an easy way to remove the dead zone from this sensor by doubling up on the potentiometer machinery - one pot piggy backed on the other and offset by 90 or 180 degrees - but this would double the torque required, make the device more expensive and infinitely more difficult to code. There's also another possible solution using 'spintronic Tunneling MagnetoResistance (TMR)' which sounds rather like something out of Lord of the Rings so, being a big fan of the hobbits etc. I simply MUST try this!
Although I love the thought of being able to build my own sensors, I have the greatest of respect for the provenance of this commercial one - it has been used in the most severe conditions for over 40 years, so will be replacing the DIY digital one at the earliest opportunity!
-
Analogue Sensors - Calculate the Nonlinearity Introduced by a Load or Pull Down Resistor
04/16/2017 at 10:46 • 0 commentsHave you ever had that terrible feeling that adding a load resistor or 'pull down' to your sensor is messing up all your analogue readings?
Maybe you're wondering why we'd want to spoil a perfectly good circuit by putting in a load resistor at all?
For many years I found that I would get strange, unpredictable, readings from my sensor related projects at the maximum and minimum locations when using analogue digital convertors (ADCs). I always blamed this on poorly designed micro processors and never for once thought that it might be my own circuit designs at fault ..... until now.
To use an analogy, when the sensor goes to maximum or minimum, it does not just reach a maximum point, but quite often actually falls off the edge of the world into a kind of no man's land where it is then prey to all kinds of digital noise and other generally nasty things like Goblins and Elves. Anybody who, like me, who has blamed this on their arduino is totally forgiven!
The example I'm using here is a simple three legged potentiometer with a ground, 5 volt and 'wiper' connection.
Using the correct pull down resistor we can eliminate noisy readings from our projects ....... And ....... just to prove that math can actually be fun .......... I'll tell you how I discovered the non linearity adjustment formula through diagrams and images.
In the circuit above we have are reading a simple potentiometer through it's 'wiper' arm through a 8.25K resistor and 16 bit ADC chip. Crucially, there is also a 100nF capacitor and a 100K resistor going to the ground rail from the wiper. We're going to concentrate on the 100K resistor. 100K is the recommended value from the manufacturers of the instrument.
There's nothing unusual about this circuit and it looks pretty boring until we look at what's happening with the resistor in more detail.
To get rid of the noises (and the Goblins and Elves) we want the resistor to be fairly small in ohms - maybe 10K, but if our pot is, for example 1K, we're going to get a massive amount of non linearity - see for yourself by opening the excel sheet in the files section.
Initially, I did not set out to discover any formulae - I just wanted to visualise the non linearity created by a load resistor in a sensor related circuit. I wanted to try and isolate the curve and plot it as a graph in Microsoft excel. It just seemed like fun.
The Math
:
I was actually working at the time on this Analogue Wind Vane project, which is basically a continuous potentiometer with a small 'dead zone' at north. Initially, the project was plagued with those familiar noisy readings until I looked at the resistor more closely.
Firstly, I plotted the actual resistance curve in Microsoft excel, which is the total resistance created by the combination of the wind vane and the load ... and I called it 'Total (RT)'. Initially, I was disappointed as I could not actually see any curve at all, so then I changed both the axes to log10. Hey presto! I can see a curve! (The blue curve on the left in the diagram above).
The total resistance is given by this well known formula:
RT w * R L ) / (Rw + RL)
- where:
- RT is the total resistance
- RW is the wiper resistance
- RL is the load resistance
OK, so far so good - not too complicated?
Next, I wanted to see how my new fancy curve looked side by side with a boring straight linear curve, just as if the results were not actually a curve at all, but a straight line. The formula for this is:
RLINw * RTMAX)/ RWMAX
- where:
- RLIN is the hypothetical linear resistance (the 'imaginary' resistance)
- RW is the wiper resistance
- RTMAX is the maximum value of the total resistance
- RWMAX is the maximum value of the wiper resistance
This is all well and good, but where are we going to get the value of the total resistance from? I really thought this was going to be easier than this, but then realised that we've already calculated this value above, it's just the maximum value of RT . But just for clarification, here is the formula:
RTMAX WMAX * R L ) / (RWMAX + RL)
- so, now, if we substitute out RTMAX we get:
RLINW * RTMAX) / RWMAX = (RW / RWMAX) * (RWMAX * RL) / (RWMAX + RL) = (RW * RL) / (RWMAX + RL)
Now we can plot our linear 'curve' (in red) and see if it's noticeably different from the curvy curve ..... and yes .... as long as we cheat by using log10, we can see the difference. If we open up the excel file, we can change the value of the load resistor to something stupidly small and get some pretty crazy curves produced.
Finally, I realised that we could then subtract the non linear curve from the linear curve and get a final outcome: the actual non linearity, or the 'difference' between the linear and non linear results. This is the pretty blue curve on the right and is given by:
RDIFFT - RLIN = (RW * RL) / (RW + RL) - (RW * RL) / (RWMAX + RL)
This equation could be reduced further, but the arduino nano is already going to struggle with some of the big numbers produced (we're working with 16 bit ADCs), so we need to help it along a little bit.
Eventually, the formula gets translated into arduino code:
//////////////////////////////////////////////////////////////////////////////////////////////////////// // Non linearity calculations: load2 = load / 1000 * maxSensorValue; Serial.print("load2 = "); Serial.println(load2); long loadz = load2/5; long sensorValuez = sensorValue/5; long maxSensorValuez = maxSensorValue/5; long a = 5*((sensorValuez * loadz) / (sensorValuez + loadz)); long b = 5*((sensorValuez * loadz) / (maxSensorValuez + loadz)); Serial.print("a = "); Serial.println(a); Serial.print("b = "); Serial.println(b); outputValue = sensorValue + (a - b); //////////////////////////////////////////////////////////////////////////////////////////////////////
You'll see that the code is more 'clunky' than the formula and I've had to divide by 5 to reduce the size of some of the numbers. But it works!
Hopefully, I've now also proved not only that maths can be fun but also relevant to the real world? But what did all this work actually achieve?
If we remember from earlier on, the recommended minimum load resistor was 100K, but if we reduce these ohms we can make the readings significantly more stable near the dead zone. I tried lots of different permutations and, using the above formula to negate the non linearity, I ended up using a super accurate (+-0.1%) 30K resistor and still got good readings at due south, which, according to our excel diagram, is where most nonlinearity will occur.
Please see the 'Files' section for the excel spreadsheet.
-
Setting Up an A100LK Anemometer on an Arduino
04/16/2017 at 10:40 • 0 commentsAnybody thinking of installing a wind generator, or even a whole flock of wind generators, would be well advised to monitor the proposed site for at least one whole year before spending a penny more on hardware. This is what the A100LK is designed for.
The first requirement is that it is accurate and calibrated to within very tight tolerances as just a few % 'off' could lose the investors many millions of dollars. My anemometer was very kindly donated to me by Vector Instruments and came with a great swathe of non linearity tables and calibration certificates which needed to be decoded within the arduino to produce super accurate results for my GPRS Weather Station, with live data displayed online at: Meusydd Weather . It also came with a recommendation to install a filter circuit to provide 'low pass filtering with a maximum cut-off frequency of 10khz on the pulsed output'. Surely it can't be any more difficult than filtering home brewed cider?
Apart from this, I've been dying to get started on version 2 of the GPRS weather station project so this was a perfect opportunity to test out my new 'Weather Station Development Board' and link up, yes, THREE arduinos via I2C and create the beginnings of a processor network that will be capable of becoming fully autonomous by the year 2040.
The A100LK is calibrated to give out a pulsed frequency that, when divided by 10, gives out a reading in UK knots to an accuracy of no greater than + or - 1%. The device is wired up for 4.7 to 28v and so is ideal for an arduino and reading the pulses is very straight forwards. The pulsed output is basically a square wave with a maximum output of 4v which goes through our filter circuit and is then read by an arduino nano once every five seconds using the 'pulseIn' command. Nothing could be simpler - except that we've got to design the filter.
The nano then does some calculations to account for the calibration value on the A100LK test certificate and then more calculations to make the output linear. This nice linear output is achieved by writing code to query each and every value against a table of values supplied by Vector Instruments. This may sound difficult, but it is not at all so!
Lastly, the readings in UK knots need to be compiled into 'mean' averages and provide the maximum gust for a ten minute period. These readings are then displayed on a swanky full colour TFT display on the development board.
The development board itself is designed with slots for 3 arduinos - 2 nanos and a mega - and has an option for the TFT screen which is operated by one dedicated nano. The other nano is the 'Master' and can turn off the 'Slave' nano when it's not needed and can also turn on the Mega when that's needed and put it back to sleep again afterwards. The reason for the 3 arduinos is that the TFT screen is power, memory and programming space hungry so needs to be kept in it's own cage, well away from the more essential processing activities. The question to ask here is 'Why have a screen at all?' The answer is that when trying to debug a problem 'in the field' a screen would be really useful - it's not always convenient to be trying to read the serial output on a laptop - especially in bright sunlight.
Just to keep it simple, for now the A100LK is wired up to the same nano as has the TFT screen - it will be wired to the master nano at a future date.
Calculating the Filter Parameters
:
Using a spread sheet to display math in graphical format is really useful and we don't have to get too bogged down in the actual calculations.
I've taken the final formula from the above and created a table with voltages calculated according to whatever values of resistance and capacitance are entered. The voltages are then plotted against frequency to see which frequencies are 'cut off' from the arduino input pin.
The A100LK operates from 0 to 1500 Hz, so any frequencies above 1500 should be removed if possible. In an ideal world, the cut off would be a straight vertical line creating a 'brick wall', but for our purposes, a gentle slope will suffice.
Calibration and Non Linearity Calculations
:
Near the top of the arduino code is this line:
float calibration = 1.0047; // (46.62 / 46.4)
and what it does is it takes the value from the calibration certificate (46.4) and divides it into the nominal value of (46.62) and, hey presto, the device is calibrated! The value 'calibration' is then multiplied against our actual frequency reading, divided by 10 and then corrected against data in a table:
knots = (frequency * calibration /10) + correction;
The actual value 'correction' is calculated by a series of 'if' statements:
void adjustments() { if ((frequency >=1) && (frequency <=100)) {correction = 0.20;} if ((frequency >100) && (frequency <=200)) {correction = 0.10;} if ((frequency >200) && (frequency <=300)) {correction = -0.25;} if ((frequency >300) && (frequency <=400)) {correction = -0.60;} if ((frequency >400) && (frequency <=500)) {correction = -1;} if ((frequency >500) && (frequency <=600)) {correction = -1.4;} if ((frequency >600) && (frequency <=700)) {correction = -1.75;} if ((frequency >700) && (frequency <=800)) {correction = -2;} if ((frequency >800) && (frequency <=900)) {correction = -2.20;} if ((frequency >900) && (frequency <=1000)) {correction = -2.35;} if ((frequency >1000) && (frequency <=1100)) {correction = -2.40;} if ((frequency >1100) && (frequency <=1200)) {correction = -2.50;} if ((frequency >1200) && (frequency <=1300)) {correction = -2.50;} if ((frequency >1300) && (frequency <=1400)) {correction = -2.50;} if ((frequency >1400) && (frequency <=1500)) {correction = -2.45;} if ((frequency >1500) && (frequency <=1600)) {correction = -2.40;} }
and that's it!
Parts:
A100LK Anemometer from Vector Instruments
C1 Ceramic Capacitor capacitance 100nF; voltage 6.3V; package 100 mil [THT, multilayer]
J1 Piezo Speaker Part3 Arduino Nano (Rev3.0) type Arduino Nano (3.0)
R1 100Ω Resistor pin spacing 400 mil; resistance 100Ω; bands 4; tolerance ±5%; package THT
R2 100Ω Resistor pin spacing 400 mil; resistance 100Ω; bands 4; tolerance ±5%; package THT
R3 100Ω Resistor pin spacing 400 mil; resistance 100Ω; bands 4; tolerance ±5%; package THT
TFT1 1.8" TFT Display with uSD
U1 A100LK
B1 Weather station prototyping board
Wiring Up the A100LK
:
Previously, we worked out that a value of 200 ohms and 100nF would be good for our filter circuit, with the resistor in series and the capacitor in parallel going to ground.
Getting the nano working with the TFT screen was slightly more tricky as the standard digital pins can't be used and we have to use the ICSP connector. Fortunately, there's a nice big square hole in the weather station development board right above the connector, so after that it was just a case of identifying which pin was MOSI, MISO and SCK. Strangely, when powering from the nano USB plug, the serial monitor has to be opened for the device to work!
Code for the Nano:
#include <Adafruit_GFX.h> #include <Adafruit_ST7735.h> #include <SD.h> #include <SPI.h> #if defined(__SAM3X8E__) #undef __FlashStringHelper::F(string_literal) #define F(string_literal) string_literal #endif // D4 is frequency input. // D3 is Tone. float calibration = 1.0047; // (46.62 / 46.4) #define SD_CS 4 // Chip select line for SD card #define TFT_CS 10 // Chip select line for TFT display #define TFT_DC 8 // Data/command line for TFT #define TFT_RST -1 // Reset line for TFT (or connect to +5V) Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); int frequency; int z =0; float correction =0; float knots =0; float knotsMax =0; float knotsAv =0; float runningTotal =0; void setup(void) { Serial.begin(9600); // Initialize 1.8" TFT tft.initR(INITR_BLACKTAB); // initialize a ST7735S chip, black tab Serial.println("OK!"); tft.setRotation(3); // Rotate the TFT text tft.fillScreen(ST7735_BLACK); tft.setTextSize(5); tft.setTextColor(ST7735_RED); delay(20); tft.setCursor(20, 10); tft.print("CAT"); pinMode(4,INPUT); } void loop() { z=0; knotsMax =0; knotsAv =0; runningTotal =0; while (z<20) { z++; tone (3,500,100); frequency = 500000/pulseIn(4,HIGH,5000000); if (frequency < 0){frequency=0;} correction =0; adjustments(); knots = (frequency * calibration /10) + correction; if (knots > knotsMax){knotsMax = knots;} runningTotal = runningTotal + knots; knotsAv = runningTotal / z; printing(); delay(5000); } } void adjustments() { if ((frequency >=1) && (frequency <=100)) {correction = 0.20;} if ((frequency >100) && (frequency <=200)) {correction = 0.10;} if ((frequency >200) && (frequency <=300)) {correction = -0.25;} if ((frequency >300) && (frequency <=400)) {correction = -0.60;} if ((frequency >400) && (frequency <=500)) {correction = -1;} if ((frequency >500) && (frequency <=600)) {correction = -1.4;} if ((frequency >600) && (frequency <=700)) {correction = -1.75;} if ((frequency >700) && (frequency <=800)) {correction = -2;} if ((frequency >800) && (frequency <=900)) {correction = -2.20;} if ((frequency >900) && (frequency <=1000)) {correction = -2.35;} if ((frequency >1000) && (frequency <=1100)) {correction = -2.40;} if ((frequency >1100) && (frequency <=1200)) {correction = -2.50;} if ((frequency >1200) && (frequency <=1300)) {correction = -2.50;} if ((frequency >1300) && (frequency <=1400)) {correction = -2.50;} if ((frequency >1400) && (frequency <=1500)) {correction = -2.45;} if ((frequency >1500) && (frequency <=1600)) {correction = -2.40;} } void printing() { tft.fillScreen(ST7735_BLACK); tft.setTextSize(2); tft.setTextColor(ST7735_BLUE); delay(20); tft.setCursor(20, 10); tft.print("Windspeed"); tft.setTextSize(2); tft.setCursor(0, 50); tft.setTextColor(ST7735_YELLOW); tft.print("Frequency:"); tft.setCursor(120, 50); tft.print(frequency); tft.setCursor(0, 70); tft.print("Knots:"); tft.setCursor(80, 70); tft.print(knots); tft.setCursor(0, 90); tft.print("Max:"); tft.setCursor(80, 90); tft.print(knotsMax); tft.setCursor(0, 110); tft.print("Av:"); tft.setCursor(80, 110); tft.print(knotsAv); Serial.print("Frequency = "); Serial.println(frequency); Serial.print("Knots = "); Serial.println(knots,4); Serial.print("Max. = "); Serial.println(knotsMax,4); Serial.print("Av. = "); Serial.println(knotsAv,4); Serial.print("z = "); Serial.println(z); Serial.println(""); }
So now that we've spent so much effort setting up the A100LK, all that remains is to put it somewhere high up and away from trees and buildings to get meaningful results.