├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── documents └── Weather_Meter_Kit_Datasheet.pdf ├── examples ├── Example1_BasicReadings │ └── Example1_BasicReadings.ino ├── Example2_ManualCalibration │ └── Example2_ManualCalibration.ino └── Example3_CalibrationHelper │ └── Example3_CalibrationHelper.ino ├── keywords.txt ├── library.properties └── src ├── SparkFun_Weather_Meter_Kit_Arduino_Library.cpp ├── SparkFun_Weather_Meter_Kit_Arduino_Library.h └── SparkFun_Weather_Meter_Kit_Constants.h /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | Thank you so *much* for offering to help out. We truly appreciate it. 4 | 5 | If you'd like to contribute, start by searching through the [issues](https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/issues) and [pull requests](https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/pulls) to see whether someone else has raised a similar idea or question. 6 | Please check the [closed issues](https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/issues?q=is%3Aissue+is%3Aclosed) 7 | and [closed pull requests](https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/pulls?q=is%3Apr+is%3Aclosed) too - you may find that your issue or feature has already been discussed. 8 | 9 | If you decide to add a feature to this library, please create a PR and follow these best practices: 10 | 11 | * Change as little as possible. Do not submit a PR that changes 100 lines of whitespace. Break up into multiple PRs if necessary. 12 | * If you've added a new feature document it with a simple example sketch. This serves both as a test of your PR and as a quick way for users to quickly learn how to use your new feature. 13 | * If you add new functions also add them to _keywords.txt_ so that they are properly highlighted in Arduino. [Read more](https://www.arduino.cc/en/Hacking/libraryTutorial). 14 | * **Important:** Please submit your PR using the [release_candidate branch](https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/tree/release_candidate). That way, we can merge and test your PR quickly without changing the _master_ branch 15 | 16 | ![Contributing.JPG](https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/blob/main/img/Contributing.JPG) 17 | 18 | ## Style guide 19 | 20 | Please read and follow the [Arduino API style guide](https://www.arduino.cc/en/Reference/APIStyleGuide). Also read and consider the [Arduino style guide](https://www.arduino.cc/en/Reference/StyleGuide). 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | SparkFun License Information 2 | ============================ 3 | 4 | SparkFun uses two different licenses for our files — one for hardware and one for code. 5 | 6 | Hardware 7 | --------- 8 | 9 | **SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** 10 | 11 | Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). 12 | 13 | You are free to: 14 | 15 | Share — copy and redistribute the material in any medium or format 16 | Adapt — remix, transform, and build upon the material 17 | for any purpose, even commercially. 18 | The licensor cannot revoke these freedoms as long as you follow the license terms. 19 | Under the following terms: 20 | 21 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 22 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 23 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 24 | Notices: 25 | 26 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 27 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 28 | 29 | 30 | Code 31 | -------- 32 | 33 | **SparkFun code, firmware, and software are released under the [MIT License](http://opensource.org/licenses/MIT).** 34 | 35 | The MIT License (MIT) 36 | 37 | Copyright (c) 2020 SparkFun Electronics 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SparkFun Weather Meter Kit Arduino Library 2 | ======================================== 3 | 4 |

5 | 6 | 7 | 8 | 9 | 10 | follow on Twitter 11 | 12 | 13 |

14 | 15 | ![Product Image - Weather Meter Kit](https://cdn.sparkfun.com//assets/parts/1/4/5/3/9/15901-Weather_Meter-02.jpg) 16 | 17 | [Weather Meter Kit](https://www.sparkfun.com/products/15901) *(SEN-15901)* 18 | 19 | 20 | Whether you're an agriculturalist, a professional meteorologist, or a weather hobbyist, building a weather station can be a rewarding project. This Arduino library allows users to easily record wind speed, wind direction, and rainfall data from our [weather meter kit](https://www.sparkfun.com/products/15901). It should be noted that the sensors in the weather meter kit rely on magnetic reed switches and requires a power source to make any measurements. 21 | 22 | - The rain gauge is a self-emptying bucket-type rain gauge, which activates a momentary button closure for each 0.011" of rain that is collected. 23 | - The anemometer (wind speed meter) encodes the wind speed by simply closing a switch which each rotation. A wind speed of 1.492 MPH produces a switch closure once per second. 24 | - The wind vane reports wind direction as a voltage, which is produced by the combination of resistors inside the sensor. The vane’s magnet could potentially close two switches at once, allowing up to 16 different positions to be indicated, but we have found that 8 positions are more realistic. 25 | 26 | 27 | SparkFun labored with love to create this code. Feel like supporting open-source hardware and software? Buy a board from SparkFun! 28 | *This library is intended to be utilized with the [weather meter kit](https://www.sparkfun.com/products/15901) and the following boards:* 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
Product Image - SparkFun Weather ShieldProduct Image - SparkFun MicroMod Weather Carrier Board
SparkFun Weather Shield [DEV-13956]SparkFun MicroMod Weather Carrier Board [SEN-16794]
40 | 41 | ## Supported Microcontrollers - Arduino Environment 42 | 43 | * Weather Shield 44 | * [ATMega328](https://www.sparkfun.com/products/18158) 45 | * MicroMod Weather Carrier Board 46 | * [Artemis](https://www.sparkfun.com/products/16401) 47 | * [SAMD51](https://www.sparkfun.com/products/16791) 48 | * [ESP32](https://www.sparkfun.com/products/16781) 49 | * [STM32](https://www.sparkfun.com/products/21326) 50 | * [nrf5280](https://www.sparkfun.com/products/16984) 51 | * [Teensy](https://www.sparkfun.com/products/16402) 52 | * [RP2040](https://www.sparkfun.com/products/17720) 53 | 54 | Repository Contents 55 | ------------------- 56 | 57 | * [**/documents**](./documents) - Datasheet and User Manual 58 | * [**/examples**](./examples) - Example sketches for the library (.ino). Run these from the Arduino IDE. 59 | * [**/src**](./src) - Source files for the library (.cpp, .h). 60 | * [**keywords.txt**](./keywords.txt) - Keywords from this library that will be highlighted in the Arduino IDE. 61 | * [**library.properties**](./library.properties) - General library properties for the Arduino package manager. 62 | * [**CONTRIBUTING.md**](./CONTRIBUTING.md) - Guidelines on how to contribute to this library. 63 | 64 | Documentation 65 | ------------- 66 | 67 | * **[Installing an Arduino Library Guide](https://learn.sparkfun.com/tutorials/installing-an-arduino-library)** - Basic information on how to install an Arduino library 68 | * **[Assembly Guide](https://learn.sparkfun.com/tutorials/681)** - A tutorial for assembling the weather meter kit 69 | 70 | 71 | Products that use this Library 72 | ------------------------------ 73 | 74 | * **[SEN-16794](https://www.sparkfun.com/products/16794)** - SparkFun MicroMod Weather Carrier Board 75 | * **[DEV-13956](https://www.sparkfun.com/products/13956)** - SparkFun Weather Shield 76 | * **[SEN-15901](https://www.sparkfun.com/products/15901)** - Weather Meter Kit 77 | 78 | Contributing 79 | ------------ 80 | If you would like to contribute to this library: please do, we truly appreciate it, but please follow [these guidelines](./CONTRIBUTING.md). Thanks! 81 | 82 | License Information 83 | ------------------- 84 | 85 | SparkFun's source files are ***open source***! 86 | 87 | Please review the [`LICENSE.md`](LICENSE.md) file for license information. 88 | 89 | Distributed as-is; no warranty is given. 90 | 91 | \- Your friends at SparkFun. 92 | -------------------------------------------------------------------------------- /documents/Weather_Meter_Kit_Datasheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/ed0488bd4a5bcd666c0af17d8a7d4592b221434d/documents/Weather_Meter_Kit_Datasheet.pdf -------------------------------------------------------------------------------- /examples/Example1_BasicReadings/Example1_BasicReadings.ino: -------------------------------------------------------------------------------- 1 | #include "SparkFun_Weather_Meter_Kit_Arduino_Library.h" 2 | 3 | // Below are the pin definitions for each sensor of the weather meter kit 4 | 5 | // Pins for Weather Carrier with ESP32 Processor Board 6 | int windDirectionPin = 35; 7 | int windSpeedPin = 14; 8 | int rainfallPin = 27; 9 | 10 | // Pins for the Weather Shield with SparkFun RedBoard Qwiic or Arduino Uno 11 | // int windDirectionPin = A0; 12 | // int windSpeedPin = 3; 13 | // int rainfallPin = 2; 14 | 15 | // Create an instance of the weather meter kit 16 | SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin); 17 | 18 | void setup() 19 | { 20 | // Begin serial 21 | Serial.begin(115200); 22 | Serial.println(F("SparkFun Weather Meter Kit Example 1 - Basic Readings")); 23 | Serial.println(); 24 | Serial.println(F("Note - this example demonstrates the minimum code required")); 25 | Serial.println(F("for operation, and may not be accurate for your project.")); 26 | Serial.println(F("It is recommended to check out the calibration examples.")); 27 | 28 | // Expected ADC values have been defined for various platforms in the 29 | // library, however your platform may not be included. This code will check 30 | // if that's the case 31 | #ifdef SFE_WMK_PLAFTORM_UNKNOWN 32 | // The platform you're using hasn't been added to the library, so the 33 | // expected ADC values have been calculated assuming a 10k pullup resistor 34 | // and a perfectly linear 16-bit ADC. Your ADC likely has a different 35 | // resolution, so you'll need to specify it here: 36 | weatherMeterKit.setADCResolutionBits(10); 37 | 38 | Serial.println(F("Unknown platform! Please edit the code with your ADC resolution!")); 39 | Serial.println(); 40 | #endif 41 | 42 | // Begin weather meter kit 43 | weatherMeterKit.begin(); 44 | } 45 | 46 | void loop() 47 | { 48 | // Print data from weather meter kit 49 | Serial.print(F("Wind direction (degrees): ")); 50 | Serial.print(weatherMeterKit.getWindDirection(), 1); 51 | Serial.print(F("\t\t")); 52 | Serial.print(F("Wind speed (kph): ")); 53 | Serial.print(weatherMeterKit.getWindSpeed(), 1); 54 | Serial.print(F("\t\t")); 55 | Serial.print(F("Total rainfall (mm): ")); 56 | Serial.println(weatherMeterKit.getTotalRainfall(), 1); 57 | 58 | // Only print once per second 59 | delay(1000); 60 | } 61 | -------------------------------------------------------------------------------- /examples/Example2_ManualCalibration/Example2_ManualCalibration.ino: -------------------------------------------------------------------------------- 1 | #include "SparkFun_Weather_Meter_Kit_Arduino_Library.h" 2 | 3 | // Below are the pin definitions for each sensor of the weather meter kit 4 | 5 | // Pins for Weather Carrier with ESP32 Processor Board 6 | int windDirectionPin = 35; 7 | int windSpeedPin = 14; 8 | int rainfallPin = 27; 9 | 10 | // Pins for the Weather Shield with SparkFun RedBoard Qwiic or Arduino Uno 11 | // int windDirectionPin = A0; 12 | // int windSpeedPin = 3; 13 | // int rainfallPin = 2; 14 | 15 | // Create an instance of the weather meter kit 16 | SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin); 17 | 18 | void setup() 19 | { 20 | // Begin serial 21 | Serial.begin(115200); 22 | Serial.println(F("SparkFun Weather Meter Kit Example 2 - Manual Calibration")); 23 | Serial.println(); 24 | Serial.println(F("Note - this example demonstrates how to manually set the")); 25 | Serial.println(F("calibration parameters once you know what they are for your")); 26 | Serial.println(F("set up. If you don't know what values to use, check out")); 27 | Serial.println(F("Example 3, which walks you through it! The values used in")); 28 | Serial.println(F("this example are all defaults, so you may need to change them.")); 29 | 30 | // Here we create a struct to hold all the calibration parameters 31 | SFEWeatherMeterKitCalibrationParams calibrationParams = weatherMeterKit.getCalibrationParams(); 32 | 33 | // The wind vane has 8 switches, but 2 could close at the same time, which 34 | // results in 16 possible positions. Each position has a resistor connected 35 | // to GND, so this library assumes a voltage divider is created by adding 36 | // another resistor to VCC. Some of the wind vane resistor values are 37 | // fairly close to each other, meaning an accurate ADC is required. However 38 | // some ADCs have a non-linear behavior that causes this measurement to be 39 | // inaccurate. To account for this, the vane resistor values can be manually 40 | // changed here to compensate for the non-linear behavior of the ADC 41 | calibrationParams.vaneADCValues[WMK_ANGLE_0_0] = 3143; 42 | calibrationParams.vaneADCValues[WMK_ANGLE_22_5] = 1624; 43 | calibrationParams.vaneADCValues[WMK_ANGLE_45_0] = 1845; 44 | calibrationParams.vaneADCValues[WMK_ANGLE_67_5] = 335; 45 | calibrationParams.vaneADCValues[WMK_ANGLE_90_0] = 372; 46 | calibrationParams.vaneADCValues[WMK_ANGLE_112_5] = 264; 47 | calibrationParams.vaneADCValues[WMK_ANGLE_135_0] = 738; 48 | calibrationParams.vaneADCValues[WMK_ANGLE_157_5] = 506; 49 | calibrationParams.vaneADCValues[WMK_ANGLE_180_0] = 1149; 50 | calibrationParams.vaneADCValues[WMK_ANGLE_202_5] = 979; 51 | calibrationParams.vaneADCValues[WMK_ANGLE_225_0] = 2520; 52 | calibrationParams.vaneADCValues[WMK_ANGLE_247_5] = 2397; 53 | calibrationParams.vaneADCValues[WMK_ANGLE_270_0] = 3780; 54 | calibrationParams.vaneADCValues[WMK_ANGLE_292_5] = 3309; 55 | calibrationParams.vaneADCValues[WMK_ANGLE_315_0] = 3548; 56 | calibrationParams.vaneADCValues[WMK_ANGLE_337_5] = 2810; 57 | 58 | // The rainfall detector contains a small cup that collects rain water. When 59 | // the cup fills, the water is dumped and the total rainfall is incremented 60 | // by some value. This value defaults to 0.2794mm of rain per count, as 61 | // specified by the datasheet 62 | calibrationParams.mmPerRainfallCount = 0.2794; 63 | 64 | // The rainfall detector switch can sometimes bounce, causing multiple extra 65 | // triggers. This input is debounced by ignoring extra triggers within a 66 | // time window, which defaults to 100ms 67 | calibrationParams.minMillisPerRainfall = 100; 68 | 69 | // The anemometer contains a switch that opens and closes as it spins. The 70 | // rate at which the switch closes depends on the wind speed. The datasheet 71 | // states that a wind of 2.4kph causes the switch to close once per second 72 | calibrationParams.kphPerCountPerSec = 2.4; 73 | 74 | // Because the anemometer generates discrete pulses as it rotates, it's not 75 | // possible to measure the wind speed exactly at any point in time. A filter 76 | // is implemented in the library that averages the wind speed over a certain 77 | // time period, which defaults to 1 second. Longer intervals result in more 78 | // accurate measurements, but cause delay in the measurement 79 | calibrationParams.windSpeedMeasurementPeriodMillis = 1000; 80 | 81 | // Now we can set all the calibration parameters at once 82 | weatherMeterKit.setCalibrationParams(calibrationParams); 83 | 84 | // Begin weather meter kit 85 | weatherMeterKit.begin(); 86 | } 87 | 88 | void loop() 89 | { 90 | // Print data from weather meter kit 91 | Serial.print(F("Wind direction (degrees): ")); 92 | Serial.print(weatherMeterKit.getWindDirection(), 1); 93 | Serial.print(F("\t\t")); 94 | Serial.print(F("Wind speed (kph): ")); 95 | Serial.print(weatherMeterKit.getWindSpeed(), 1); 96 | Serial.print(F("\t\t")); 97 | Serial.print(F("Total rainfall (mm): ")); 98 | Serial.println(weatherMeterKit.getTotalRainfall(), 1); 99 | 100 | // Only print once per second 101 | delay(1000); 102 | } -------------------------------------------------------------------------------- /examples/Example3_CalibrationHelper/Example3_CalibrationHelper.ino: -------------------------------------------------------------------------------- 1 | #include "SparkFun_Weather_Meter_Kit_Arduino_Library.h" 2 | 3 | // Below are the pin definitions for each sensor of the weather meter kit 4 | 5 | // Pins for Weather Carrier with ESP32 Processor Board 6 | int windDirectionPin = 35; 7 | int windSpeedPin = 14; 8 | int rainfallPin = 27; 9 | 10 | // Pins for the Weather Shield with SparkFun RedBoard Qwiic or Arduino Uno 11 | // int windDirectionPin = A0; 12 | // int windSpeedPin = 3; 13 | // int rainfallPin = 2; 14 | 15 | // Create an instance of the weather meter kit 16 | SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin); 17 | 18 | // Here we create a struct to hold all the calibration parameters 19 | SFEWeatherMeterKitCalibrationParams calibrationParams; 20 | 21 | void setup() 22 | { 23 | // Begin serial 24 | Serial.begin(115200); 25 | Serial.println(F("SparkFun Weather Meter Kit Example 3 - Calibration Helper")); 26 | Serial.println(); 27 | Serial.println(F("This example will help you determine the best calibration")); 28 | Serial.println(F("parameters to use for your project. Once each section is done,")); 29 | Serial.println(F("the values will be printed for you to copy into your sketch.")); 30 | 31 | // We'll be changing the calibration parameters one at a time, so we'll get 32 | // all the default values now 33 | calibrationParams = weatherMeterKit.getCalibrationParams(); 34 | 35 | // Begin weather meter kit 36 | weatherMeterKit.begin(); 37 | 38 | // Run the calibration helper 39 | runCalibrationHelper(); 40 | 41 | Serial.println(); 42 | Serial.println(F("Calibration done! Enter any key to continue")); 43 | waitForUserInput(); 44 | } 45 | 46 | void loop() 47 | { 48 | // Print data from weather meter kit 49 | Serial.print(F("Wind direction (degrees): ")); 50 | Serial.print(weatherMeterKit.getWindDirection(), 1); 51 | Serial.print(F("\t\t")); 52 | Serial.print(F("Wind speed (kph): ")); 53 | Serial.print(weatherMeterKit.getWindSpeed(), 1); 54 | Serial.print(F("\t\t")); 55 | Serial.print(F("Total rainfall (mm): ")); 56 | Serial.println(weatherMeterKit.getTotalRainfall(), 1); 57 | 58 | // Only print once per second 59 | delay(1000); 60 | } 61 | 62 | void runCalibrationHelper() 63 | { 64 | // Run the helpers for each sensor 65 | runVaneCalibrationHelper(); 66 | runRainfallCalibrationHelper(); 67 | runAnemometerCalibrationHelper(); 68 | } 69 | 70 | void runVaneCalibrationHelper() 71 | { 72 | Serial.println(); 73 | Serial.println(F("Wind vane calibration!")); 74 | 75 | Serial.println(); 76 | Serial.println(F("The wind vane has several switches, each with different")); 77 | Serial.println(F("resistors connected to GND. This library assumes there's an")); 78 | Serial.println(F("external resistor connected to VCC creating a voltage divider;")); 79 | Serial.println(F("the voltage is measured and compared with expected voltages")); 80 | Serial.println(F("for each direction. The expected voltages may need to be tuned,")); 81 | Serial.println(F("which this part walks you through. Hold the wind vane at the")); 82 | Serial.println(F("specified angle, then enter any key once steady. Pay close")); 83 | Serial.println(F("attention to the measured ADC value to see when it changes,")); 84 | Serial.println(F("especially around the 22.5 degree increments, they're very")); 85 | Serial.println(F("narrow! Enter any key to begin.")); 86 | 87 | // Wait for user to begin 88 | waitForUserInput(); 89 | 90 | // Loop through all angles 91 | for (int i = 0; i < WMK_NUM_ANGLES; i++) 92 | { 93 | // Compute current angle 94 | float currentAngle = i * SFE_WIND_VANE_DEGREES_PER_INDEX; 95 | 96 | // Loop until user requests to continue 97 | clearUserInput(); 98 | while (Serial.available() == 0) 99 | { 100 | Serial.print(F("Hold wind vane at ")); 101 | Serial.print(currentAngle, 1); 102 | Serial.print(F(" degrees. Enter any key when in position.")); 103 | Serial.print(F(" Measured ADC: ")); 104 | Serial.print(analogRead(windDirectionPin)); 105 | Serial.print(F(" Measured direction (degrees): ")); 106 | Serial.println(weatherMeterKit.getWindDirection(), 1); 107 | 108 | // Print moderately quickly so user can catch any brief changes 109 | delay(100); 110 | } 111 | 112 | // Set this as the new expected ADC value for this angle 113 | uint32_t measuredADC = analogRead(windDirectionPin); 114 | calibrationParams.vaneADCValues[i] = measuredADC; 115 | weatherMeterKit.setCalibrationParams(calibrationParams); 116 | 117 | // Print value for user to see 118 | Serial.println(); 119 | Serial.print(F("Setting expected ADC value for ")); 120 | Serial.print(currentAngle); 121 | Serial.print(F(" degrees to ")); 122 | Serial.println(measuredADC); 123 | Serial.println(); 124 | 125 | // Wait a bit so user can read it 126 | delay(1000); 127 | } 128 | 129 | // Print the ADC value saved for each angle again so the user has it all in 130 | // one place 131 | Serial.println(); 132 | Serial.println(F("Here are the ADC values set for each angle:")); 133 | Serial.println(); 134 | for (int i = 0; i < WMK_NUM_ANGLES; i++) 135 | { 136 | // Compute current angle 137 | float currentAngle = i * SFE_WIND_VANE_DEGREES_PER_INDEX; 138 | 139 | // Print this angle / ADC pair 140 | Serial.print(currentAngle, 1); 141 | Serial.print(F(" degrees: ")); 142 | Serial.println(calibrationParams.vaneADCValues[i]); 143 | } 144 | 145 | Serial.println(); 146 | Serial.println(F("Wind vane calibration complete!")); 147 | } 148 | 149 | void runRainfallCalibrationHelper() 150 | { 151 | Serial.println(); 152 | Serial.println(F("Rainfall calibration!")); 153 | 154 | // Rainfall calibration 155 | Serial.println(); 156 | Serial.println(F("The rainfall detector contains a small cup that collects rain")); 157 | Serial.println(F("water. When the cup fills, the water gets dumped out and a")); 158 | Serial.println(F("counter is incremented. The exact volume of this cup needs to")); 159 | Serial.println(F("be known to get an accurate measurement of the total rainfall.")); 160 | Serial.println(F("To calibrate this value, you'll need to pour a known volume")); 161 | Serial.println(F("of water into the rainfall detector, and the cup volume will")); 162 | Serial.println(F("be calculated. The rate at which the water is poured can")); 163 | Serial.println(F("affect the measurement, so go very slowly to simulate actual")); 164 | Serial.println(F("rain rather than dumping it all at once!")); 165 | Serial.println(F("Enter any key once you're ready to begin")); 166 | 167 | // Wait for user to begin 168 | waitForUserInput(); 169 | 170 | // User is ready, reset the rainfall counter 171 | weatherMeterKit.resetTotalRainfall(); 172 | 173 | Serial.println(); 174 | Serial.println(F("Begin pouring!")); 175 | Serial.println(); 176 | 177 | // Wait for user to finish 178 | clearUserInput(); 179 | while (Serial.available() == 0) 180 | { 181 | Serial.print(F("Enter any key once finished pouring.")); 182 | Serial.print(F(" Number of counts: ")); 183 | Serial.print(weatherMeterKit.getRainfallCounts()); 184 | Serial.print(F(" Measured rainfall (mm): ")); 185 | Serial.println(weatherMeterKit.getTotalRainfall(), 1); 186 | 187 | // Print slowly 188 | delay(1000); 189 | } 190 | 191 | Serial.println(); 192 | Serial.println(F("Now enter the volume of water poured in mL")); 193 | waitForUserInput(); 194 | int totalWaterML = Serial.parseInt(); 195 | 196 | // Convert ml to mm^3 197 | int totalWaterMM3 = totalWaterML * 1000; 198 | 199 | // Divide by collection area of rainfall detector. It's about 50mm x 110mm, 200 | // resulting in a collection area of about 5500mm^2 201 | float totalRainfallMM = totalWaterMM3 / 5500.0; 202 | 203 | // Divide by number of counts 204 | float mmPerCount = totalRainfallMM / weatherMeterKit.getRainfallCounts(); 205 | 206 | // Set this as the new mm per count 207 | calibrationParams.mmPerRainfallCount = mmPerCount; 208 | weatherMeterKit.setCalibrationParams(calibrationParams); 209 | 210 | // Print value for user to see 211 | Serial.println(); 212 | Serial.print(F("Setting mm per count to ")); 213 | Serial.println(mmPerCount, 4); 214 | 215 | Serial.println(); 216 | Serial.println(F("Rainfall calibration complete!")); 217 | } 218 | 219 | void runAnemometerCalibrationHelper() 220 | { 221 | Serial.println(); 222 | Serial.println(F("Anemometer calibration!")); 223 | 224 | Serial.println(); 225 | Serial.println(F("This part will require you to place the anemometer in a")); 226 | Serial.println(F("constant wind stream for a few seconds, and you'll need to")); 227 | Serial.println(F("know or the wind speed or measure it with a calibrated")); 228 | Serial.println(F("anemometer (these can be purchased for relatively low cost).")); 229 | Serial.println(F("Enter the number of seconds you wish to run this calibration.")); 230 | Serial.println(F("Longer will be more accurate, but the wind speed is more")); 231 | Serial.println(F("likely to fluctuate (10 seconds is recommended)")); 232 | waitForUserInput(); 233 | int calibrationSeconds = Serial.parseInt(); 234 | 235 | // Set filter measurement period as requested 236 | calibrationParams.windSpeedMeasurementPeriodMillis = 1000 * calibrationSeconds; 237 | weatherMeterKit.setCalibrationParams(calibrationParams); 238 | 239 | Serial.println(); 240 | Serial.println(F("Now place the anemometer in a constant wind stream, and")); 241 | Serial.println(F("enter any key when ready to begin calibration")); 242 | waitForUserInput(); 243 | 244 | // Reset the wind speed filter to start the calibration period 245 | weatherMeterKit.resetWindSpeedFilter(); 246 | 247 | // Wait for calibration period to end 248 | Serial.println(); 249 | for(int i = 0; i < calibrationSeconds; i++) 250 | { 251 | // Print time remaining 252 | Serial.print(F("Seconds remaining: ")); 253 | Serial.println(calibrationSeconds - i); 254 | 255 | // 1 second intervals 256 | delay(1000); 257 | } 258 | 259 | // Wait just a bit longer to make sure the filter window has passed 260 | delay(500); 261 | 262 | // Store total number of wind speed counts 263 | uint32_t windCounts = weatherMeterKit.getWindSpeedCounts(); 264 | 265 | // Reset measurement period back to default 266 | calibrationParams.windSpeedMeasurementPeriodMillis = 1000; 267 | 268 | Serial.println(); 269 | Serial.println(F("Calibration period finished! Enter the average wind speed")); 270 | Serial.println(F("during the calibration period in kph")); 271 | waitForUserInput(); 272 | float windSpeed = Serial.parseFloat(); 273 | 274 | // Calculate kph per count per second 275 | calibrationParams.kphPerCountPerSec = windSpeed * windCounts / calibrationSeconds; 276 | weatherMeterKit.setCalibrationParams(calibrationParams); 277 | 278 | // Print value for user to see 279 | Serial.println(); 280 | Serial.print(F("Setting kph per count per second to ")); 281 | Serial.println(calibrationParams.kphPerCountPerSec, 2); 282 | 283 | Serial.println(); 284 | Serial.println(F("Anemometer calibration complete!")); 285 | } 286 | 287 | void clearUserInput() 288 | { 289 | // Ensure all previous characters have come through 290 | delay(100); 291 | 292 | // Throw away all previous characters 293 | while (Serial.available() != 0) 294 | { 295 | Serial.read(); 296 | } 297 | } 298 | 299 | void waitForUserInput() 300 | { 301 | // Remove previous user input 302 | clearUserInput(); 303 | 304 | // Wait for user to input something 305 | while (Serial.available() == 0) 306 | { 307 | // Nothing to do, keep waiting 308 | } 309 | } -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ######################################################### 2 | # Syntax Coloring Map for SparkFun Weather Meter Kit # 3 | ######################################################### 4 | # Class 5 | ######################################################### 6 | 7 | SFEWeatherMeterKit KEYWORD1 8 | 9 | ######################################################### 10 | # Methods and Functions 11 | ######################################################### 12 | 13 | begin KEYWORD2 14 | getWindDirection KEYWORD2 15 | getWindSpeed KEYWORD2 16 | getTotalRainfall KEYWORD2 17 | getCalibrationParams KEYWORD2 18 | setCalibrationParams KEYWORD2 19 | setADCResolutionBits KEYWORD2 20 | getWindSpeedCounts KEYWORD2 21 | getRainfallCounts KEYWORD2 22 | resetWindSpeedFilter KEYWORD2 23 | resetTotalRainfall KEYWORD2 24 | 25 | ######################################################### 26 | # Constants 27 | ######################################################### 28 | 29 | WMK_ANGLE_0_0 LITERAL1 30 | WMK_ANGLE_22_5 LITERAL1 31 | WMK_ANGLE_45_0 LITERAL1 32 | WMK_ANGLE_67_5 LITERAL1 33 | WMK_ANGLE_90_0 LITERAL1 34 | WMK_ANGLE_112_5 LITERAL1 35 | WMK_ANGLE_135_0 LITERAL1 36 | WMK_ANGLE_157_5 LITERAL1 37 | WMK_ANGLE_180_0 LITERAL1 38 | WMK_ANGLE_202_5 LITERAL1 39 | WMK_ANGLE_225_0 LITERAL1 40 | WMK_ANGLE_247_5 LITERAL1 41 | WMK_ANGLE_270_0 LITERAL1 42 | WMK_ANGLE_292_5 LITERAL1 43 | WMK_ANGLE_315_0 LITERAL1 44 | WMK_ANGLE_337_5 LITERAL1 45 | WMK_NUM_ANGLES LITERAL1 46 | SFE_WIND_VANE_DEGREES_PER_INDEX LITERAL1 47 | SFE_WIND_VANE_ADC_RESOLUTION_DEFAULT LITERAL1 48 | 49 | ######################################################### 50 | # Structs 51 | ######################################################### 52 | 53 | SFEWeatherMeterKitCalibrationParams LITERAL3 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SparkFun Weather Meter Kit Arduino Library 2 | version=1.1.2 3 | author=SparkFun Electronics 4 | maintainer=SparkFun Electronics 5 | sentence=A library to use the SparkFun Weather Meter Kit 6 | paragraph= 7 | category=Sensors 8 | url=https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/SparkFun_Weather_Meter_Kit_Arduino_Library.cpp: -------------------------------------------------------------------------------- 1 | #include "SparkFun_Weather_Meter_Kit_Arduino_Library.h" 2 | 3 | // Static member definitions 4 | SFEWeatherMeterKitCalibrationParams SFEWeatherMeterKit::_calibrationParams; 5 | uint32_t SFEWeatherMeterKit::_windCountsPrevious; 6 | uint32_t SFEWeatherMeterKit::_windCounts; 7 | uint32_t SFEWeatherMeterKit::_rainfallCounts; 8 | uint32_t SFEWeatherMeterKit::_lastWindSpeedMillis; 9 | uint32_t SFEWeatherMeterKit::_lastRainfallMillis; 10 | uint8_t SFEWeatherMeterKit::_windDirectionPin; 11 | uint8_t SFEWeatherMeterKit::_windSpeedPin; 12 | uint8_t SFEWeatherMeterKit::_rainfallPin; 13 | 14 | /// @brief Default constructor, sets default calibration values 15 | SFEWeatherMeterKit::SFEWeatherMeterKit(uint8_t windDirectionPin, uint8_t windSpeedPin, uint8_t rainfallPin) 16 | { 17 | // Set sensors pins 18 | _windDirectionPin = windDirectionPin; 19 | _windSpeedPin = windSpeedPin; 20 | _rainfallPin = rainfallPin; 21 | 22 | // The wind vane has 8 switches, but 2 could close at the same time, which 23 | // results in 16 possible positions. Each position has a different resistor, 24 | // resulting in different ADC values. The expected ADC values has been 25 | // experiemntally determined for various platforms, see the constants file 26 | _calibrationParams.vaneADCValues[WMK_ANGLE_0_0] = SFE_WMK_ADC_ANGLE_0_0; 27 | _calibrationParams.vaneADCValues[WMK_ANGLE_22_5] = SFE_WMK_ADC_ANGLE_22_5; 28 | _calibrationParams.vaneADCValues[WMK_ANGLE_45_0] = SFE_WMK_ADC_ANGLE_45_0; 29 | _calibrationParams.vaneADCValues[WMK_ANGLE_67_5] = SFE_WMK_ADC_ANGLE_67_5; 30 | _calibrationParams.vaneADCValues[WMK_ANGLE_90_0] = SFE_WMK_ADC_ANGLE_90_0; 31 | _calibrationParams.vaneADCValues[WMK_ANGLE_112_5] = SFE_WMK_ADC_ANGLE_112_5; 32 | _calibrationParams.vaneADCValues[WMK_ANGLE_135_0] = SFE_WMK_ADC_ANGLE_135_0; 33 | _calibrationParams.vaneADCValues[WMK_ANGLE_157_5] = SFE_WMK_ADC_ANGLE_157_5; 34 | _calibrationParams.vaneADCValues[WMK_ANGLE_180_0] = SFE_WMK_ADC_ANGLE_180_0; 35 | _calibrationParams.vaneADCValues[WMK_ANGLE_202_5] = SFE_WMK_ADC_ANGLE_202_5; 36 | _calibrationParams.vaneADCValues[WMK_ANGLE_225_0] = SFE_WMK_ADC_ANGLE_225_0; 37 | _calibrationParams.vaneADCValues[WMK_ANGLE_247_5] = SFE_WMK_ADC_ANGLE_247_5; 38 | _calibrationParams.vaneADCValues[WMK_ANGLE_270_0] = SFE_WMK_ADC_ANGLE_270_0; 39 | _calibrationParams.vaneADCValues[WMK_ANGLE_292_5] = SFE_WMK_ADC_ANGLE_292_5; 40 | _calibrationParams.vaneADCValues[WMK_ANGLE_315_0] = SFE_WMK_ADC_ANGLE_315_0; 41 | _calibrationParams.vaneADCValues[WMK_ANGLE_337_5] = SFE_WMK_ADC_ANGLE_337_5; 42 | 43 | // Datasheet specifies 2.4kph of wind causes one trigger per second 44 | _calibrationParams.kphPerCountPerSec = 2.4; 45 | 46 | // Wind speed sampling interval. Longer durations have more accuracy, but 47 | // cause delay and can miss fast fluctuations 48 | _calibrationParams.windSpeedMeasurementPeriodMillis = 1000; 49 | 50 | // Datasheet specifies 0.2794mm of rain per trigger 51 | _calibrationParams.mmPerRainfallCount = 0.2794; 52 | 53 | // Debounce time for rainfall detector 54 | _calibrationParams.minMillisPerRainfall = 100; 55 | 56 | // Reset counters to zero 57 | _windCountsPrevious = 0; 58 | _windCounts = 0; 59 | _rainfallCounts = 0; 60 | 61 | // Reset timers 62 | _lastWindSpeedMillis = millis(); 63 | _lastRainfallMillis = millis(); 64 | } 65 | 66 | /// @brief Sets up sensor pins 67 | /// @param windDirectionPin Wind direction pin, must have an ADC 68 | /// @param windSpeedPin Wind speed pin, must support interrupts 69 | /// @param rainfallPin Rainfall pin, must support interrupts 70 | void SFEWeatherMeterKit::begin() 71 | { 72 | // Set pins to inputs 73 | pinMode(_windDirectionPin, INPUT); 74 | pinMode(_windSpeedPin, INPUT_PULLUP); 75 | pinMode(_rainfallPin, INPUT_PULLUP); 76 | 77 | // Attach interrupt handlers 78 | attachInterrupt(digitalPinToInterrupt(_windSpeedPin), windSpeedInterrupt, CHANGE); 79 | attachInterrupt(digitalPinToInterrupt(_rainfallPin), rainfallInterrupt, RISING); 80 | } 81 | 82 | /// @brief Gets the current calibration parameters 83 | /// @return Current calibration parameters 84 | SFEWeatherMeterKitCalibrationParams SFEWeatherMeterKit::getCalibrationParams() 85 | { 86 | return _calibrationParams; 87 | } 88 | 89 | /// @brief Sets the new calibration parameters 90 | /// @param params New calibration parameters 91 | void SFEWeatherMeterKit::setCalibrationParams(SFEWeatherMeterKitCalibrationParams params) 92 | { 93 | // Copy the provided calibration parameters 94 | memcpy(&_calibrationParams, ¶ms, sizeof(SFEWeatherMeterKitCalibrationParams)); 95 | } 96 | 97 | /// @brief Adjusts the expected ADC values for the wind vane based on the 98 | /// provided ADC resolution 99 | /// @param resolutionBits Resolution of ADC in bits (eg. 8-bit, 12-bit, etc.) 100 | void SFEWeatherMeterKit::setADCResolutionBits(uint8_t resolutionBits) 101 | { 102 | for(uint8_t i = 0; i < WMK_NUM_ANGLES; i++) 103 | { 104 | int8_t bitShift = (SFE_WMK_ADC_RESOLUTION) - resolutionBits; 105 | 106 | if(bitShift > 0) 107 | { 108 | _calibrationParams.vaneADCValues[i] >>= bitShift; 109 | } 110 | else if(bitShift < 0) 111 | { 112 | _calibrationParams.vaneADCValues[i] <<= -bitShift; 113 | } 114 | } 115 | } 116 | 117 | /// @brief Measures the direction of the wind vane 118 | /// @return Wind direction in degrees 119 | float SFEWeatherMeterKit::getWindDirection() 120 | { 121 | // Measure the output of the voltage divider 122 | uint16_t rawADC = analogRead(_windDirectionPin); 123 | 124 | // Now we'll loop through all possible directions to find which is closest 125 | // to our measurement, using a simple linear search. closestDifference is 126 | // initialized to max 16-bit signed value (2^15 - 1 = 32,767) 127 | int16_t closestDifference = 32767; 128 | uint8_t closestIndex = 0; 129 | for (uint8_t i = 0; i < WMK_NUM_ANGLES; i++) 130 | { 131 | // Compute the difference between the ADC value for this direction and 132 | // what we measured 133 | int16_t adcDifference = _calibrationParams.vaneADCValues[i] - rawADC; 134 | 135 | // We only care about the magnitude of the difference 136 | adcDifference = abs(adcDifference); 137 | 138 | // Check if this different is less than our closest so far 139 | if (adcDifference < closestDifference) 140 | { 141 | // This resistance is closer, update closest resistance and index 142 | closestDifference = adcDifference; 143 | closestIndex = i; 144 | } 145 | } 146 | 147 | // Now compute the wind direction in degrees 148 | float direction = closestIndex * SFE_WIND_VANE_DEGREES_PER_INDEX; 149 | 150 | // Return direction in degrees 151 | return direction; 152 | } 153 | 154 | /// @brief Updates the wind speed measurement windows if needed 155 | void SFEWeatherMeterKit::updateWindSpeed() 156 | { 157 | // The anemometer generates interrupts as it spins. Because these are 158 | // discrete pulses, we can't get an instantaneous measurement of the wind 159 | // speed. Instead, we need to track these signals over time and perform some 160 | // filtering to get an estimate of the current wind speed. There's lots of 161 | // ways to do this, but this library uses a modifed version of a moving 162 | // window filter. 163 | // 164 | // A moving window filter would require an array of values to be stored, 165 | // indicating when each pulse occurred. However for a fixed time window, the 166 | // number of pulses is unknown, so we don't know how big the array needs to 167 | // be. There are some solutions to this, but the one used here is to change 168 | // the moving time window to a static time window, which is illustrated in 169 | // this timing diagram with variable time between pulses: 170 | // 171 | // Pulses | | | | | | | | | 172 | // Window Last window Current window 173 | // Time ------|-----------------------|----------------| 174 | // t_last t_now 175 | // |---Measurement Period--|---Measurement Period--| 176 | // 177 | // A counter is used to track the number of pulses detected in the current 178 | // measurement window; when pulses are detected, the counter is incremented. 179 | // When t_now exceeds the measurement period, the total number of pulses is 180 | // used to calculate the average wind speed for that window. This filter 181 | // only outputs wind speed for the previous window, which does result in 182 | // delayed measurements, but is fine for most data logging applications since 183 | // logs can be synced with the measurement widows 184 | 185 | // Get current time 186 | uint32_t tNow = millis(); 187 | 188 | // Compute time since start of current measurement window 189 | uint32_t dt = tNow - _lastWindSpeedMillis; 190 | 191 | // Check how long it's been since the start of this measurement window 192 | if (dt < _calibrationParams.windSpeedMeasurementPeriodMillis) 193 | { 194 | // Still within the current window, nothing to do (count is not 195 | // incremented here, that's done by the interrupt handler) 196 | } 197 | else 198 | { 199 | // We've passed the end of the measurement window, so we need to update 200 | // some things. But first, we need to check how long it's been since the 201 | // last time we updated, since it's possible we've not received any 202 | // pulses for a long time 203 | if (dt > (_calibrationParams.windSpeedMeasurementPeriodMillis * 2)) 204 | { 205 | // Over 2 measurement periods have passed since the last update, 206 | // meaning the wind speed is very slow or even zero. So we'll reset 207 | // the wind speed and counter, and set the start of the next window 208 | // to be now 209 | _windCountsPrevious = 0; 210 | _windCounts = 0; 211 | _lastWindSpeedMillis = tNow; 212 | } 213 | else 214 | { 215 | // We've only just gone past the end of the measurement period, so 216 | // save the wind counts for the previous window, reset current 217 | // counter, and update time of start of next measurement window 218 | _windCountsPrevious = _windCounts; 219 | _windCounts = 0; 220 | _lastWindSpeedMillis += _calibrationParams.windSpeedMeasurementPeriodMillis; 221 | } 222 | } 223 | } 224 | 225 | /// @brief Gets the measured wind speed 226 | /// @return Measured wind speed in kph 227 | float SFEWeatherMeterKit::getWindSpeed() 228 | { 229 | // Check if the wind speed needs to be updated 230 | updateWindSpeed(); 231 | 232 | // Calculate the wind speed for the previous window. First compute the 233 | // counts per millisecond 234 | float windSpeed = (float) _windCountsPrevious / _calibrationParams.windSpeedMeasurementPeriodMillis; 235 | 236 | // Convert milliseconds to seconds, and counts per second to kph. Need to 237 | // divide by 2 to account for using both rising and falling edges 238 | windSpeed *= 1000 * _calibrationParams.kphPerCountPerSec / 2; 239 | 240 | // Return wind speed for the previous measurement interval 241 | return windSpeed; 242 | } 243 | 244 | /// @brief Gets the number of wind speed counts 245 | /// @return Number of wind speed counts 246 | uint32_t SFEWeatherMeterKit::getWindSpeedCounts() 247 | { 248 | // Return total wind speed counts 249 | return _windCounts; 250 | } 251 | 252 | /// @brief Gets the number of rainfall counts 253 | /// @return Number of rainfall counts 254 | uint32_t SFEWeatherMeterKit::getRainfallCounts() 255 | { 256 | // Return total rainfall counts 257 | return _rainfallCounts; 258 | } 259 | 260 | /// @brief Gets the total rainfall 261 | /// @return Total rainfall in mm 262 | float SFEWeatherMeterKit::getTotalRainfall() 263 | { 264 | // Return total rainfall in mm 265 | return _rainfallCounts * _calibrationParams.mmPerRainfallCount; 266 | } 267 | 268 | /// @brief Resets the wind speed 269 | void SFEWeatherMeterKit::resetWindSpeedFilter() 270 | { 271 | _windCountsPrevious = 0; 272 | _windCounts = 0; 273 | _lastWindSpeedMillis = millis(); 274 | } 275 | 276 | /// @brief Resets the total rainfall 277 | void SFEWeatherMeterKit::resetTotalRainfall() 278 | { 279 | _rainfallCounts = 0; 280 | } 281 | 282 | /// @brief Interrupt handler for wind speed pin 283 | void SFEWeatherMeterKit::windSpeedInterrupt() 284 | { 285 | // Check if the measurement window needs to be updated 286 | updateWindSpeed(); 287 | 288 | // Increment counts in this measurement window 289 | _windCounts++; 290 | } 291 | 292 | /// @brief Interrupt handler for rainfall pin 293 | void SFEWeatherMeterKit::rainfallInterrupt() 294 | { 295 | // Debounce by checking time since last interrupt 296 | if ((millis() - _lastRainfallMillis) < _calibrationParams.minMillisPerRainfall) 297 | { 298 | // There's not been enough time since the last interrupt, so this is 299 | // likely just the switch bouncing 300 | return; 301 | } 302 | 303 | // Enough time has passed that this is probably a real signal instead of a 304 | // bounce, so update the time of the last interrupt to be now 305 | _lastRainfallMillis = millis(); 306 | 307 | // Increment counter 308 | _rainfallCounts++; 309 | } -------------------------------------------------------------------------------- /src/SparkFun_Weather_Meter_Kit_Arduino_Library.h: -------------------------------------------------------------------------------- 1 | #ifndef __SPARKFUN_WEATHER_METER_KIT_H__ 2 | #define __SPARKFUN_WEATHER_METER_KIT_H__ 3 | 4 | #include "Arduino.h" 5 | #include "SparkFun_Weather_Meter_Kit_Constants.h" 6 | 7 | // Calibration parameters for each sensor 8 | struct SFEWeatherMeterKitCalibrationParams 9 | { 10 | // Wind vane 11 | uint16_t vaneADCValues[WMK_NUM_ANGLES]; 12 | 13 | // Wind speed 14 | uint32_t windSpeedMeasurementPeriodMillis; 15 | float kphPerCountPerSec; 16 | 17 | // Rainfall 18 | float mmPerRainfallCount; 19 | uint32_t minMillisPerRainfall; 20 | }; 21 | 22 | class SFEWeatherMeterKit 23 | { 24 | public: 25 | // Constructor 26 | SFEWeatherMeterKit(uint8_t windDirectionPin, uint8_t windSpeedPin, uint8_t rainfallPin); 27 | static void begin(); 28 | 29 | // Data collection 30 | static float getWindDirection(); 31 | static float getWindSpeed(); 32 | static float getTotalRainfall(); 33 | 34 | // Sensor calibration params 35 | static SFEWeatherMeterKitCalibrationParams getCalibrationParams(); 36 | static void setCalibrationParams(SFEWeatherMeterKitCalibrationParams params); 37 | 38 | // ADC resolution scaling 39 | static void setADCResolutionBits(uint8_t resolutionBits); 40 | 41 | // Helper functions. These can be helpful for sensor calibration 42 | static uint32_t getWindSpeedCounts(); 43 | static uint32_t getRainfallCounts(); 44 | static void resetWindSpeedFilter(); 45 | static void resetTotalRainfall(); 46 | 47 | private: 48 | // Updates wind speed 49 | static void updateWindSpeed(); 50 | 51 | // Interrupt handlers 52 | static void windSpeedInterrupt(); 53 | static void rainfallInterrupt(); 54 | 55 | // Pins for each sensor 56 | static uint8_t _windDirectionPin; 57 | static uint8_t _windSpeedPin; 58 | static uint8_t _rainfallPin; 59 | 60 | // Sensor calibration parameters 61 | static SFEWeatherMeterKitCalibrationParams _calibrationParams; 62 | 63 | // Variables to track measurements 64 | static uint32_t _windCounts; 65 | static uint32_t _windCountsPrevious; 66 | static uint32_t _rainfallCounts; 67 | static uint32_t _lastWindSpeedMillis; 68 | static uint32_t _lastRainfallMillis; 69 | }; 70 | 71 | #endif -------------------------------------------------------------------------------- /src/SparkFun_Weather_Meter_Kit_Constants.h: -------------------------------------------------------------------------------- 1 | // Enum to define the indexes for each wind direction 2 | enum SFEWeatherMeterKitAnemometerAngles 3 | { 4 | WMK_ANGLE_0_0 = 0, 5 | WMK_ANGLE_22_5, 6 | WMK_ANGLE_45_0, 7 | WMK_ANGLE_67_5, 8 | WMK_ANGLE_90_0, 9 | WMK_ANGLE_112_5, 10 | WMK_ANGLE_135_0, 11 | WMK_ANGLE_157_5, 12 | WMK_ANGLE_180_0, 13 | WMK_ANGLE_202_5, 14 | WMK_ANGLE_225_0, 15 | WMK_ANGLE_247_5, 16 | WMK_ANGLE_270_0, 17 | WMK_ANGLE_292_5, 18 | WMK_ANGLE_315_0, 19 | WMK_ANGLE_337_5, 20 | WMK_NUM_ANGLES 21 | }; 22 | 23 | // Angle per index of wind vane (360 / 16 = 22.5) 24 | #define SFE_WIND_VANE_DEGREES_PER_INDEX (360.0 / WMK_NUM_ANGLES) 25 | 26 | // The ADC of each platform behaves slightly differently. Some have different 27 | // resolutions, some have non-linear outputs, and some voltage divider circuits 28 | // are different. The expected ADV values have been obtained experimentally for 29 | // various platforms below 30 | #ifdef AVR 31 | // Tested with RedBoard Qwiic with Weather Shield 32 | #define SFE_WMK_ADC_ANGLE_0_0 902 33 | #define SFE_WMK_ADC_ANGLE_22_5 661 34 | #define SFE_WMK_ADC_ANGLE_45_0 701 35 | #define SFE_WMK_ADC_ANGLE_67_5 389 36 | #define SFE_WMK_ADC_ANGLE_90_0 398 37 | #define SFE_WMK_ADC_ANGLE_112_5 371 38 | #define SFE_WMK_ADC_ANGLE_135_0 483 39 | #define SFE_WMK_ADC_ANGLE_157_5 430 40 | #define SFE_WMK_ADC_ANGLE_180_0 570 41 | #define SFE_WMK_ADC_ANGLE_202_5 535 42 | #define SFE_WMK_ADC_ANGLE_225_0 812 43 | #define SFE_WMK_ADC_ANGLE_247_5 792 44 | #define SFE_WMK_ADC_ANGLE_270_0 986 45 | #define SFE_WMK_ADC_ANGLE_292_5 925 46 | #define SFE_WMK_ADC_ANGLE_315_0 957 47 | #define SFE_WMK_ADC_ANGLE_337_5 855 48 | 49 | #define SFE_WMK_ADC_RESOLUTION 10 50 | #elif defined(ESP32) 51 | // Tested with ESP32 processor board installed on Weather Carrier 52 | #define SFE_WMK_ADC_ANGLE_0_0 3118 53 | #define SFE_WMK_ADC_ANGLE_22_5 1526 54 | #define SFE_WMK_ADC_ANGLE_45_0 1761 55 | #define SFE_WMK_ADC_ANGLE_67_5 199 56 | #define SFE_WMK_ADC_ANGLE_90_0 237 57 | #define SFE_WMK_ADC_ANGLE_112_5 123 58 | #define SFE_WMK_ADC_ANGLE_135_0 613 59 | #define SFE_WMK_ADC_ANGLE_157_5 371 60 | #define SFE_WMK_ADC_ANGLE_180_0 1040 61 | #define SFE_WMK_ADC_ANGLE_202_5 859 62 | #define SFE_WMK_ADC_ANGLE_225_0 2451 63 | #define SFE_WMK_ADC_ANGLE_247_5 2329 64 | #define SFE_WMK_ADC_ANGLE_270_0 3984 65 | #define SFE_WMK_ADC_ANGLE_292_5 3290 66 | #define SFE_WMK_ADC_ANGLE_315_0 3616 67 | #define SFE_WMK_ADC_ANGLE_337_5 2755 68 | 69 | #define SFE_WMK_ADC_RESOLUTION 12 70 | #else 71 | // Values calculated assuming 10k pullup and perfectly linear 16-bit ADC 72 | #define SFE_WMK_ADC_ANGLE_0_0 50294 73 | #define SFE_WMK_ADC_ANGLE_22_5 25985 74 | #define SFE_WMK_ADC_ANGLE_45_0 29527 75 | #define SFE_WMK_ADC_ANGLE_67_5 5361 76 | #define SFE_WMK_ADC_ANGLE_90_0 5958 77 | #define SFE_WMK_ADC_ANGLE_112_5 4219 78 | #define SFE_WMK_ADC_ANGLE_135_0 11818 79 | #define SFE_WMK_ADC_ANGLE_157_5 8099 80 | #define SFE_WMK_ADC_ANGLE_180_0 18388 81 | #define SFE_WMK_ADC_ANGLE_202_5 15661 82 | #define SFE_WMK_ADC_ANGLE_225_0 40329 83 | #define SFE_WMK_ADC_ANGLE_247_5 38365 84 | #define SFE_WMK_ADC_ANGLE_270_0 60494 85 | #define SFE_WMK_ADC_ANGLE_292_5 52961 86 | #define SFE_WMK_ADC_ANGLE_315_0 56785 87 | #define SFE_WMK_ADC_ANGLE_337_5 44978 88 | 89 | #define SFE_WMK_ADC_RESOLUTION 16 90 | 91 | // Set macro to indicate that this platform isn't known 92 | #define SFE_WMK_PLAFTORM_UNKNOWN 93 | #endif --------------------------------------------------------------------------------