Wind Direction and Ground Temperature
The last days have seen the final two sensors connected to the Oak, the wind direction and the ground temperature sensors.
Ground Temperature Sensor
This sensor is a DS18B20 sensor that has been encapsulated in a rubber like coating to make it waterproof. The sensor has a 1.8m (6 ft) waterproofed cable attached.
The sensor uses a one wire protocol to communicate with the microcontroller, so only three connections required, power, data and ground. Strictly speaking, only two connections are required, data and ground as the sensor can use power from the data line to provide enough power for the sensor.
Adafruit also provide a library for communicating with the DS18B20 sensor along with some example code. This makes working with the sensor a relatively simple job. Most of the code required to set up and read the sensor was taken from the example code provided and as such will not be discussed is any detail here.
Wind Direction Sensor
The wind direction sensor is an analogue sensor and the last of the sensors on the Weather Station sensor kit.
The sensor consists of a series of reed switches in a star shape. The wind vane uses magnets to close the reed switches and the resulting connections connect a resistors to ground through one of the wires. The second wire should be connected to a a resistor (the example in the data sheet is 10K) with the other end connected to Vcc.
The result is a potential divider circuit. The output from the circuit can be connected to an Analog to Digital Converter (ADC). The datasheet for the sensor is supplied with a list of the resistance values along with expected outputs for a 5V supply. All of the circuitry used so far has been using a 3.3V supply as the Oak is a 3.3V component. A little bit of spreadsheet wizardry converts the values to a 3.3V supply:
Degrees (from North) | Resistance | Reading (5V) | Reading (3.3V) |
0.0 | 33000 | 3.837 | 2.533 |
22.5 | 6570 | 1.982 | 1.308 |
45.0 | 8200 | 2.253 | 1.487 |
67.5 | 891 | 0.409 | 0.270 |
90.0 | 1000 | 0.455 | 0.300 |
112.5 | 688 | 0.322 | 0.212 |
135.0 | 2200 | 0.902 | 0.595 |
157.5 | 1410 | 0.618 | 0.408 |
180.0 | 3900 | 1.403 | 0.926 |
202.5 | 3140 | 1.195 | 0.789 |
225.0 | 16000 | 3.077 | 2.031 |
247.5 | 14120 | 2.927 | 1.932 |
270.0 | 120000 | 4.615 | 3.046 |
292.5 | 42120 | 4.041 | 2.667 |
315.0 | 64900 | 4.332 | 2.859 |
337.5 | 21880 | 3.432 | 2.265 |
This now brings us on to the problem of the number of ADC channels on the Oak, there is only one and we have two analogue devices, the ultraviolet light and the wind direction sensors. One solution is to add a multichannel ADC to the system.
The ADS1115 is a four channel ADC with a an I2C bus. As with the DS18B20, there is a library available in the Arduino library collection, again written by Adafruit.
In the initial experiments with this library using a potentiometer I experienced problems with the conversions appearing to be duplicated / incorrect. It appeared that the conversion was appearing on the next channel (i.e. conversion for A0 was given on A1) or on both channels (i.e. A2 and A3 having the same reading). Perhaps there is not enough time being left for the ADC to complete the sample and conversion. A brief investigation into the library code lead to a pause parameter being used to allow the ADC to perform the conversion. This was 8ms, changing this to 10ms fixed the issue.
Next step is to calibrate the wind direction sensor and confirm the sensor readings with the theoretical values from the spreadsheet.
A painstaking few hours with the sensor, the ADC and the spreadsheet followed. The net result was the following:
Degrees (from North) | Resistance | Theoretical Reading (V) | Actual Reading (V) | Actual Reading |
0.0 | 33000 | 2.533 | 2.4913 | 13287 |
22.5 | 6570 | 1.308 | 1.2873 | 6866 |
45.0 | 8200 | 1.487 | 1.4628 | 7801 |
67.5 | 891 | 0.270 | 0.2647 | 1412 |
90.0 | 1000 | 0.300 | 0.2938 | 1567 |
112.5 | 688 | 0.212 | 0.2084 | 1112 |
135.0 | 2200 | 0.595 | 0.5857 | 2134 |
157.5 | 1410 | 0.408 | 0.4004 | 2136 |
180.0 | 3900 | 0.926 | 0.9106 | 4587 |
202.5 | 3140 | 0.789 | 0.7745 | 4131 |
225.0 | 16000 | 2.031 | 1.9948 | 10639 |
247.5 | 14120 | 1.932 | 1.9953 | 10642 |
270.0 | 120000 | 3.046 | 2.9958 | 15978 |
292.5 | 42120 | 2.667 | 2.6233 | 13991 |
315.0 | 64900 | 2.859 | 2.8132 | 15004 |
337.5 | 21880 | 2.265 | 2.2282 | 11884 |
The final column in the table above is the actual 16-bit reading from the ADC. The intention is to use these values when working out the wind direction, it is quicker (from a processor point of view) to work with integers rather than floating point numbers. Comparisons are also more accurate.
From a code perspective, the best way to deal with the above is to use a lookup table. The table can be ordered on the actual reading, or a mid-point reading.
If the readings are ordered in ascending order then we can calculate the values that are midway between two readings. The idea is to give a spread of possible values for a particular reading. There will be slight variations in the ADC reading around the theoretical value for any particular angle. Rather than define the actual spread, or a tolerance, say 100 points either way, a mid-point value should give a very wide range and should ensure that we have the right result for the particular ADC reading.
So let’s look at some code. There are a couple of basic things we need to set up, an enumeration to represent the wind direction and a data structure for the look up table:
enum WindDirection
{
North, NorthNorthEast, NorthEast, EastNorthEast, East, EastSouthEast, SouthEast, SouthSouthEast,
South, SouthSouthWest, SouthWest, WestSouthWest, West, WestNorthWest, NorthWest, NorthNorthWest
};
//
// Entry in the wind direction lookup table.
//
struct WindDirectionLookup
{
uint16_t midPoint;
uint16_t reading;
float angle;
WindDirection direction;
char *directionAsText;
};
WindDirectionLookup _windDirectionLookupTable[16];
uint8_t _windDirectionLookupEntry;
The last two entries direction and directionAsText are really there to give us a meaningful description within the code and also a textural description should we need to output the wind direction to a display.
Now that the table is declared we should really populate it:
//
// Populate the wind direction lookup table. Each entry has the following form:
//
// midPoint, reading, angle, direction, direction as text
//
// Note that this table is order not in angles/wind direction but in terms of the
// mid points of the ADC readings found empirically. This allows for easier
// searching of the table.
//
_windDirectionLookupTable[0] = { 0, 1112, 112.5, EastSouthEast, (char *) "East-South-East" };
_windDirectionLookupTable[1] = { 1262, 1412, 67.5, EastNorthEast, (char *) "East-North-East" };
_windDirectionLookupTable[2] = { 1489, 1567, 90, East, (char *) "East" };
_windDirectionLookupTable[3] = { 1851, 2136, 157.5, SouthSouthEast, (char *) "South-South-East" };
_windDirectionLookupTable[4] = { 2630, 3124, 135, SouthEast, (char *) "South-East" };
_windDirectionLookupTable[5] = { 3627, 4131, 202.5, SouthSouthWest, (char *) "South-South-West" };
_windDirectionLookupTable[6] = { 4359, 4587, 180, South, (char *) "South" };
_windDirectionLookupTable[7] = { 5726, 6866, 22.5, NorthNorthEast, (char *) "North-North-East" };
_windDirectionLookupTable[8] = { 7333, 7801, 45, NorthEast, (char *) "North-East" };
_windDirectionLookupTable[9] = { 9220, 10639, 225, SouthWest, (char *) "South-West" };
_windDirectionLookupTable[10] = { 10640, 10642, 247.5, WestSouthWest, (char *) "West-South-West" };
_windDirectionLookupTable[11] = { 11263, 11884, 337.5, NorthNorthWest, (char *) "North-North-West" };
_windDirectionLookupTable[12] = { 12585, 13287, 0, North, (char *) "North" };
_windDirectionLookupTable[13] = { 13639, 13991, 292.5, WestNorthWest, (char *) "West-North-West" };
_windDirectionLookupTable[14] = { 14497, 15004, 315, NorthWest, (char *) "North-West" };
_windDirectionLookupTable[15] = { 15491, 15978, 270, West, (char *) "West" };
Reading the sensor is as easy as getting the ADC rading and then walking through the look up table:
//
// Read the wind direction sensor and calculate the direction the vane is pointing.
//
WeatherSensors::WindDirection WeatherSensors::ReadWindDirection()
{
uint16_t windDirection = _adc.readADC_SingleEnded(_windDriectionAnalogChannel);
_windDirectionLookupEntry = 15;
for (int index = 0; index < 15; index++)
{
if ((windDirection > _windDirectionLookupTable[index].midPoint) && (windDirection <= _windDirectionLookupTable[index + 1].midPoint))
{
_windDirectionLookupEntry = index;
break;
}
}
message = "Wind is blowing from ";
message += _windDirectionLookupTable[_windDirectionLookupEntry].directionAsText;
Debugger::DebugMessage(message);
}
//
// Get the last wind direction reading as a textural description.
//
char *WeatherSensors::GetWindDirectionAsString()
{
return(_windDirectionLookupTable[_windDirectionLookupEntry].directionAsText);
}
The principle is to walk through the look up table and compare the reading from the ADC with the midpoint reading. The midpoint for any element in the table is the value halfway between this value and the previous value.
So if a reading x is greater then xm and greater than xm+1 (where xm is the midpoint of the current element in the table xm+1 is the midpoint of the next element in the table) then the program should move on to the next element.
If in doubt walk thorough an example. Take a reading x = 1567, the exact reading for East and walk through the table above. The answer you should reach is East. Now take a reading x = 1566 or x = 1568 and the answer should be the same, namely East. Finally, be a little more extreme, say x = 1530, the answer should still be the same.
Conclusion
The final two sensors have been connected to the Oak (on breadboard) and indoor readings are available through the serial port.
Next steps:
- Fix the rain fall meter issue wher it is possible to produce an incorrect reading when a pulse is generated by the rainfall guage at the same time as the gus=age is being read.
- Add a real time clock (RTC) to the system for times when the internet is not available.
- Send data to the cloud
- Transfer the design to protoboard and start to work on PCB designs
Still plenty to do on the design / coding front. And at some point this needs to be made hardy enough for the great outdoors, and become self powered.
Tags: Electronics, ESP8266, Software Development
Saturday, May 14th, 2016 at 4:17 pm • Electronics, ESP8266, Software Development • RSS 2.0 feed Both comments and pings are currently closed.