First commit

This commit is contained in:
marcel
2023-12-29 13:49:44 +01:00
commit 7d38f2cb37
261 changed files with 38553 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
# How to Contribute
Thank you so *much* for offering to help out. We truly appreciate it.
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.
Please check the [closed issues](https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/issues?q=is%3Aissue+is%3Aclosed)
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.
If you decide to add a feature to this library, please create a PR and follow these best practices:
* Change as little as possible. Do not submit a PR that changes 100 lines of whitespace. Break up into multiple PRs if necessary.
* 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.
* 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).
* **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
![Contributing.JPG](https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/blob/main/img/Contributing.JPG)
## Style guide
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).

View File

@@ -0,0 +1,43 @@
SparkFun License Information
============================
SparkFun uses two different licenses for our files — one for hardware and one for code.
Hardware
---------
**SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).**
Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode).
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
for any purpose, even commercially.
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
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.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Notices:
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.
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.
Code
--------
**SparkFun code, firmware, and software are released under the [MIT License](http://opensource.org/licenses/MIT).**
The MIT License (MIT)
Copyright (c) 2020 SparkFun Electronics
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.

View File

@@ -0,0 +1,91 @@
SparkFun Weather Meter Kit Arduino Library
========================================
<p align="center">
<a href="https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/issues" alt="Issues">
<img src="https://img.shields.io/github/issues/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library.svg" /></a>
<a href="https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/blob/master/LICENSE" alt="License">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" /></a>
<a href="https://twitter.com/intent/follow?screen_name=sparkfun">
<img src="https://img.shields.io/twitter/follow/sparkfun.svg?style=social&logo=twitter" alt="follow on Twitter"></a>
<a href="https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/actions" alt="Actions">
<img src="https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library/actions/workflows/mkdocs.yml/badge.svg" /></a>
</p>
![Product Image - Weather Meter Kit](https://cdn.sparkfun.com//assets/parts/1/4/5/3/9/15901-Weather_Meter-02.jpg)
[Weather Meter Kit](https://www.sparkfun.com/products/15901) *(SEN-15901)*
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.
- 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.
- 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.
- The wind vane reports wind direction as a voltage, which is produced by the combination of resistors inside the sensor. The vanes 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.
SparkFun labored with love to create this code. Feel like supporting open-source hardware and software? Buy a board from SparkFun!
*This library is intended to be utilized with the [weather meter kit](https://www.sparkfun.com/products/15901) and the following boards:*
<table>
<tr align="center">
<td><a href="https://www.sparkfun.com/products/13956"><img src="https://cdn.sparkfun.com//assets/parts/1/1/6/6/5/13956-01.jpg" alt="Product Image - SparkFun Weather Shield"></a></td>
<td><a href="https://www.sparkfun.com/products/16794"><img src="https://cdn.sparkfun.com//assets/parts/1/5/7/0/3/16794-SparkFun_MicroMod_Weather_Carrier_Board-01b.jpg" alt="Product Image - SparkFun MicroMod Weather Carrier Board"></a></td>
</tr>
<tr align="center">
<td>SparkFun Weather Shield <i>[<a href="https://www.sparkfun.com/products/13956">DEV-13956</a>]</i></td>
<td>SparkFun MicroMod Weather Carrier Board <i>[<a href="https://www.sparkfun.com/products/16794">SEN-16794</a>]</i></td>
</tr>
</table>
## Supported Microcontrollers - Arduino Environment
* Weather Shield
* [ATMega328](https://www.sparkfun.com/products/18158)
* MicroMod Weather Carrier Board
* [Artemis](https://www.sparkfun.com/products/16401)
* [SAMD51](https://www.sparkfun.com/products/16791)
* [ESP32](https://www.sparkfun.com/products/16781)
* [STM32](https://www.sparkfun.com/products/21326)
* [nrf5280](https://www.sparkfun.com/products/16984)
* [Teensy](https://www.sparkfun.com/products/16402)
* [RP2040](https://www.sparkfun.com/products/17720)
Repository Contents
-------------------
* [**/documents**](./documents) - Datasheet and User Manual
* [**/examples**](./examples) - Example sketches for the library (.ino). Run these from the Arduino IDE.
* [**/src**](./src) - Source files for the library (.cpp, .h).
* [**keywords.txt**](./keywords.txt) - Keywords from this library that will be highlighted in the Arduino IDE.
* [**library.properties**](./library.properties) - General library properties for the Arduino package manager.
* [**CONTRIBUTING.md**](./CONTRIBUTING.md) - Guidelines on how to contribute to this library.
Documentation
-------------
* **[Installing an Arduino Library Guide](https://learn.sparkfun.com/tutorials/installing-an-arduino-library)** - Basic information on how to install an Arduino library
* **[Assembly Guide](https://learn.sparkfun.com/tutorials/681)** - A tutorial for assembling the weather meter kit
Products that use this Library
------------------------------
* **[SEN-16794](https://www.sparkfun.com/products/16794)** - SparkFun MicroMod Weather Carrier Board
* **[DEV-13956](https://www.sparkfun.com/products/13956)** - SparkFun Weather Shield
* **[SEN-15901](https://www.sparkfun.com/products/15901)** - Weather Meter Kit
Contributing
------------
If you would like to contribute to this library: please do, we truly appreciate it, but please follow [these guidelines](./CONTRIBUTING.md). Thanks!
License Information
-------------------
SparkFun's source files are ***open source***!
Please review the [`LICENSE.md`](LICENSE.md) file for license information.
Distributed as-is; no warranty is given.
\- Your friends at SparkFun.

View File

@@ -0,0 +1,60 @@
#include "SparkFun_Weather_Meter_Kit_Arduino_Library.h"
// Below are the pin definitions for each sensor of the weather meter kit
// Pins for Weather Carrier with ESP32 Processor Board
int windDirectionPin = 35;
int windSpeedPin = 14;
int rainfallPin = 27;
// Pins for the Weather Shield with SparkFun RedBoard Qwiic or Arduino Uno
// int windDirectionPin = A0;
// int windSpeedPin = 3;
// int rainfallPin = 2;
// Create an instance of the weather meter kit
SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin);
void setup()
{
// Begin serial
Serial.begin(115200);
Serial.println(F("SparkFun Weather Meter Kit Example 1 - Basic Readings"));
Serial.println();
Serial.println(F("Note - this example demonstrates the minimum code required"));
Serial.println(F("for operation, and may not be accurate for your project."));
Serial.println(F("It is recommended to check out the calibration examples."));
// Expected ADC values have been defined for various platforms in the
// library, however your platform may not be included. This code will check
// if that's the case
#ifdef SFE_WMK_PLAFTORM_UNKNOWN
// The platform you're using hasn't been added to the library, so the
// expected ADC values have been calculated assuming a 10k pullup resistor
// and a perfectly linear 16-bit ADC. Your ADC likely has a different
// resolution, so you'll need to specify it here:
weatherMeterKit.setADCResolutionBits(10);
Serial.println(F("Unknown platform! Please edit the code with your ADC resolution!"));
Serial.println();
#endif
// Begin weather meter kit
weatherMeterKit.begin();
}
void loop()
{
// Print data from weather meter kit
Serial.print(F("Wind direction (degrees): "));
Serial.print(weatherMeterKit.getWindDirection(), 1);
Serial.print(F("\t\t"));
Serial.print(F("Wind speed (kph): "));
Serial.print(weatherMeterKit.getWindSpeed(), 1);
Serial.print(F("\t\t"));
Serial.print(F("Total rainfall (mm): "));
Serial.println(weatherMeterKit.getTotalRainfall(), 1);
// Only print once per second
delay(1000);
}

View File

@@ -0,0 +1,102 @@
#include "SparkFun_Weather_Meter_Kit_Arduino_Library.h"
// Below are the pin definitions for each sensor of the weather meter kit
// Pins for Weather Carrier with ESP32 Processor Board
int windDirectionPin = 35;
int windSpeedPin = 14;
int rainfallPin = 27;
// Pins for the Weather Shield with SparkFun RedBoard Qwiic or Arduino Uno
// int windDirectionPin = A0;
// int windSpeedPin = 3;
// int rainfallPin = 2;
// Create an instance of the weather meter kit
SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin);
void setup()
{
// Begin serial
Serial.begin(115200);
Serial.println(F("SparkFun Weather Meter Kit Example 2 - Manual Calibration"));
Serial.println();
Serial.println(F("Note - this example demonstrates how to manually set the"));
Serial.println(F("calibration parameters once you know what they are for your"));
Serial.println(F("set up. If you don't know what values to use, check out"));
Serial.println(F("Example 3, which walks you through it! The values used in"));
Serial.println(F("this example are all defaults, so you may need to change them."));
// Here we create a struct to hold all the calibration parameters
SFEWeatherMeterKitCalibrationParams calibrationParams = weatherMeterKit.getCalibrationParams();
// The wind vane has 8 switches, but 2 could close at the same time, which
// results in 16 possible positions. Each position has a resistor connected
// to GND, so this library assumes a voltage divider is created by adding
// another resistor to VCC. Some of the wind vane resistor values are
// fairly close to each other, meaning an accurate ADC is required. However
// some ADCs have a non-linear behavior that causes this measurement to be
// inaccurate. To account for this, the vane resistor values can be manually
// changed here to compensate for the non-linear behavior of the ADC
calibrationParams.vaneADCValues[WMK_ANGLE_0_0] = 3143;
calibrationParams.vaneADCValues[WMK_ANGLE_22_5] = 1624;
calibrationParams.vaneADCValues[WMK_ANGLE_45_0] = 1845;
calibrationParams.vaneADCValues[WMK_ANGLE_67_5] = 335;
calibrationParams.vaneADCValues[WMK_ANGLE_90_0] = 372;
calibrationParams.vaneADCValues[WMK_ANGLE_112_5] = 264;
calibrationParams.vaneADCValues[WMK_ANGLE_135_0] = 738;
calibrationParams.vaneADCValues[WMK_ANGLE_157_5] = 506;
calibrationParams.vaneADCValues[WMK_ANGLE_180_0] = 1149;
calibrationParams.vaneADCValues[WMK_ANGLE_202_5] = 979;
calibrationParams.vaneADCValues[WMK_ANGLE_225_0] = 2520;
calibrationParams.vaneADCValues[WMK_ANGLE_247_5] = 2397;
calibrationParams.vaneADCValues[WMK_ANGLE_270_0] = 3780;
calibrationParams.vaneADCValues[WMK_ANGLE_292_5] = 3309;
calibrationParams.vaneADCValues[WMK_ANGLE_315_0] = 3548;
calibrationParams.vaneADCValues[WMK_ANGLE_337_5] = 2810;
// The rainfall detector contains a small cup that collects rain water. When
// the cup fills, the water is dumped and the total rainfall is incremented
// by some value. This value defaults to 0.2794mm of rain per count, as
// specified by the datasheet
calibrationParams.mmPerRainfallCount = 0.2794;
// The rainfall detector switch can sometimes bounce, causing multiple extra
// triggers. This input is debounced by ignoring extra triggers within a
// time window, which defaults to 100ms
calibrationParams.minMillisPerRainfall = 100;
// The anemometer contains a switch that opens and closes as it spins. The
// rate at which the switch closes depends on the wind speed. The datasheet
// states that a wind of 2.4kph causes the switch to close once per second
calibrationParams.kphPerCountPerSec = 2.4;
// Because the anemometer generates discrete pulses as it rotates, it's not
// possible to measure the wind speed exactly at any point in time. A filter
// is implemented in the library that averages the wind speed over a certain
// time period, which defaults to 1 second. Longer intervals result in more
// accurate measurements, but cause delay in the measurement
calibrationParams.windSpeedMeasurementPeriodMillis = 1000;
// Now we can set all the calibration parameters at once
weatherMeterKit.setCalibrationParams(calibrationParams);
// Begin weather meter kit
weatherMeterKit.begin();
}
void loop()
{
// Print data from weather meter kit
Serial.print(F("Wind direction (degrees): "));
Serial.print(weatherMeterKit.getWindDirection(), 1);
Serial.print(F("\t\t"));
Serial.print(F("Wind speed (kph): "));
Serial.print(weatherMeterKit.getWindSpeed(), 1);
Serial.print(F("\t\t"));
Serial.print(F("Total rainfall (mm): "));
Serial.println(weatherMeterKit.getTotalRainfall(), 1);
// Only print once per second
delay(1000);
}

View File

@@ -0,0 +1,309 @@
#include "SparkFun_Weather_Meter_Kit_Arduino_Library.h"
// Below are the pin definitions for each sensor of the weather meter kit
// Pins for Weather Carrier with ESP32 Processor Board
int windDirectionPin = 35;
int windSpeedPin = 14;
int rainfallPin = 27;
// Pins for the Weather Shield with SparkFun RedBoard Qwiic or Arduino Uno
// int windDirectionPin = A0;
// int windSpeedPin = 3;
// int rainfallPin = 2;
// Create an instance of the weather meter kit
SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin);
// Here we create a struct to hold all the calibration parameters
SFEWeatherMeterKitCalibrationParams calibrationParams;
void setup()
{
// Begin serial
Serial.begin(115200);
Serial.println(F("SparkFun Weather Meter Kit Example 3 - Calibration Helper"));
Serial.println();
Serial.println(F("This example will help you determine the best calibration"));
Serial.println(F("parameters to use for your project. Once each section is done,"));
Serial.println(F("the values will be printed for you to copy into your sketch."));
// We'll be changing the calibration parameters one at a time, so we'll get
// all the default values now
calibrationParams = weatherMeterKit.getCalibrationParams();
// Begin weather meter kit
weatherMeterKit.begin();
// Run the calibration helper
runCalibrationHelper();
Serial.println();
Serial.println(F("Calibration done! Enter any key to continue"));
waitForUserInput();
}
void loop()
{
// Print data from weather meter kit
Serial.print(F("Wind direction (degrees): "));
Serial.print(weatherMeterKit.getWindDirection(), 1);
Serial.print(F("\t\t"));
Serial.print(F("Wind speed (kph): "));
Serial.print(weatherMeterKit.getWindSpeed(), 1);
Serial.print(F("\t\t"));
Serial.print(F("Total rainfall (mm): "));
Serial.println(weatherMeterKit.getTotalRainfall(), 1);
// Only print once per second
delay(1000);
}
void runCalibrationHelper()
{
// Run the helpers for each sensor
runVaneCalibrationHelper();
runRainfallCalibrationHelper();
runAnemometerCalibrationHelper();
}
void runVaneCalibrationHelper()
{
Serial.println();
Serial.println(F("Wind vane calibration!"));
Serial.println();
Serial.println(F("The wind vane has several switches, each with different"));
Serial.println(F("resistors connected to GND. This library assumes there's an"));
Serial.println(F("external resistor connected to VCC creating a voltage divider;"));
Serial.println(F("the voltage is measured and compared with expected voltages"));
Serial.println(F("for each direction. The expected voltages may need to be tuned,"));
Serial.println(F("which this part walks you through. Hold the wind vane at the"));
Serial.println(F("specified angle, then enter any key once steady. Pay close"));
Serial.println(F("attention to the measured ADC value to see when it changes,"));
Serial.println(F("especially around the 22.5 degree increments, they're very"));
Serial.println(F("narrow! Enter any key to begin."));
// Wait for user to begin
waitForUserInput();
// Loop through all angles
for (int i = 0; i < WMK_NUM_ANGLES; i++)
{
// Compute current angle
float currentAngle = i * SFE_WIND_VANE_DEGREES_PER_INDEX;
// Loop until user requests to continue
clearUserInput();
while (Serial.available() == 0)
{
Serial.print(F("Hold wind vane at "));
Serial.print(currentAngle, 1);
Serial.print(F(" degrees. Enter any key when in position."));
Serial.print(F(" Measured ADC: "));
Serial.print(analogRead(windDirectionPin));
Serial.print(F(" Measured direction (degrees): "));
Serial.println(weatherMeterKit.getWindDirection(), 1);
// Print moderately quickly so user can catch any brief changes
delay(100);
}
// Set this as the new expected ADC value for this angle
uint32_t measuredADC = analogRead(windDirectionPin);
calibrationParams.vaneADCValues[i] = measuredADC;
weatherMeterKit.setCalibrationParams(calibrationParams);
// Print value for user to see
Serial.println();
Serial.print(F("Setting expected ADC value for "));
Serial.print(currentAngle);
Serial.print(F(" degrees to "));
Serial.println(measuredADC);
Serial.println();
// Wait a bit so user can read it
delay(1000);
}
// Print the ADC value saved for each angle again so the user has it all in
// one place
Serial.println();
Serial.println(F("Here are the ADC values set for each angle:"));
Serial.println();
for (int i = 0; i < WMK_NUM_ANGLES; i++)
{
// Compute current angle
float currentAngle = i * SFE_WIND_VANE_DEGREES_PER_INDEX;
// Print this angle / ADC pair
Serial.print(currentAngle, 1);
Serial.print(F(" degrees: "));
Serial.println(calibrationParams.vaneADCValues[i]);
}
Serial.println();
Serial.println(F("Wind vane calibration complete!"));
}
void runRainfallCalibrationHelper()
{
Serial.println();
Serial.println(F("Rainfall calibration!"));
// Rainfall calibration
Serial.println();
Serial.println(F("The rainfall detector contains a small cup that collects rain"));
Serial.println(F("water. When the cup fills, the water gets dumped out and a"));
Serial.println(F("counter is incremented. The exact volume of this cup needs to"));
Serial.println(F("be known to get an accurate measurement of the total rainfall."));
Serial.println(F("To calibrate this value, you'll need to pour a known volume"));
Serial.println(F("of water into the rainfall detector, and the cup volume will"));
Serial.println(F("be calculated. The rate at which the water is poured can"));
Serial.println(F("affect the measurement, so go very slowly to simulate actual"));
Serial.println(F("rain rather than dumping it all at once!"));
Serial.println(F("Enter any key once you're ready to begin"));
// Wait for user to begin
waitForUserInput();
// User is ready, reset the rainfall counter
weatherMeterKit.resetTotalRainfall();
Serial.println();
Serial.println(F("Begin pouring!"));
Serial.println();
// Wait for user to finish
clearUserInput();
while (Serial.available() == 0)
{
Serial.print(F("Enter any key once finished pouring."));
Serial.print(F(" Number of counts: "));
Serial.print(weatherMeterKit.getRainfallCounts());
Serial.print(F(" Measured rainfall (mm): "));
Serial.println(weatherMeterKit.getTotalRainfall(), 1);
// Print slowly
delay(1000);
}
Serial.println();
Serial.println(F("Now enter the volume of water poured in mL"));
waitForUserInput();
int totalWaterML = Serial.parseInt();
// Convert ml to mm^3
int totalWaterMM3 = totalWaterML * 1000;
// Divide by collection area of rainfall detector. It's about 50mm x 110mm,
// resulting in a collection area of about 5500mm^2
float totalRainfallMM = totalWaterMM3 / 5500.0;
// Divide by number of counts
float mmPerCount = totalRainfallMM / weatherMeterKit.getRainfallCounts();
// Set this as the new mm per count
calibrationParams.mmPerRainfallCount = mmPerCount;
weatherMeterKit.setCalibrationParams(calibrationParams);
// Print value for user to see
Serial.println();
Serial.print(F("Setting mm per count to "));
Serial.println(mmPerCount, 4);
Serial.println();
Serial.println(F("Rainfall calibration complete!"));
}
void runAnemometerCalibrationHelper()
{
Serial.println();
Serial.println(F("Anemometer calibration!"));
Serial.println();
Serial.println(F("This part will require you to place the anemometer in a"));
Serial.println(F("constant wind stream for a few seconds, and you'll need to"));
Serial.println(F("know or the wind speed or measure it with a calibrated"));
Serial.println(F("anemometer (these can be purchased for relatively low cost)."));
Serial.println(F("Enter the number of seconds you wish to run this calibration."));
Serial.println(F("Longer will be more accurate, but the wind speed is more"));
Serial.println(F("likely to fluctuate (10 seconds is recommended)"));
waitForUserInput();
int calibrationSeconds = Serial.parseInt();
// Set filter measurement period as requested
calibrationParams.windSpeedMeasurementPeriodMillis = 1000 * calibrationSeconds;
weatherMeterKit.setCalibrationParams(calibrationParams);
Serial.println();
Serial.println(F("Now place the anemometer in a constant wind stream, and"));
Serial.println(F("enter any key when ready to begin calibration"));
waitForUserInput();
// Reset the wind speed filter to start the calibration period
weatherMeterKit.resetWindSpeedFilter();
// Wait for calibration period to end
Serial.println();
for(int i = 0; i < calibrationSeconds; i++)
{
// Print time remaining
Serial.print(F("Seconds remaining: "));
Serial.println(calibrationSeconds - i);
// 1 second intervals
delay(1000);
}
// Wait just a bit longer to make sure the filter window has passed
delay(500);
// Store total number of wind speed counts
uint32_t windCounts = weatherMeterKit.getWindSpeedCounts();
// Reset measurement period back to default
calibrationParams.windSpeedMeasurementPeriodMillis = 1000;
Serial.println();
Serial.println(F("Calibration period finished! Enter the average wind speed"));
Serial.println(F("during the calibration period in kph"));
waitForUserInput();
float windSpeed = Serial.parseFloat();
// Calculate kph per count per second
calibrationParams.kphPerCountPerSec = windSpeed * windCounts / calibrationSeconds;
weatherMeterKit.setCalibrationParams(calibrationParams);
// Print value for user to see
Serial.println();
Serial.print(F("Setting kph per count per second to "));
Serial.println(calibrationParams.kphPerCountPerSec, 2);
Serial.println();
Serial.println(F("Anemometer calibration complete!"));
}
void clearUserInput()
{
// Ensure all previous characters have come through
delay(100);
// Throw away all previous characters
while (Serial.available() != 0)
{
Serial.read();
}
}
void waitForUserInput()
{
// Remove previous user input
clearUserInput();
// Wait for user to input something
while (Serial.available() == 0)
{
// Nothing to do, keep waiting
}
}

View File

@@ -0,0 +1,53 @@
#########################################################
# Syntax Coloring Map for SparkFun Weather Meter Kit #
#########################################################
# Class
#########################################################
SFEWeatherMeterKit KEYWORD1
#########################################################
# Methods and Functions
#########################################################
begin KEYWORD2
getWindDirection KEYWORD2
getWindSpeed KEYWORD2
getTotalRainfall KEYWORD2
getCalibrationParams KEYWORD2
setCalibrationParams KEYWORD2
setADCResolutionBits KEYWORD2
getWindSpeedCounts KEYWORD2
getRainfallCounts KEYWORD2
resetWindSpeedFilter KEYWORD2
resetTotalRainfall KEYWORD2
#########################################################
# Constants
#########################################################
WMK_ANGLE_0_0 LITERAL1
WMK_ANGLE_22_5 LITERAL1
WMK_ANGLE_45_0 LITERAL1
WMK_ANGLE_67_5 LITERAL1
WMK_ANGLE_90_0 LITERAL1
WMK_ANGLE_112_5 LITERAL1
WMK_ANGLE_135_0 LITERAL1
WMK_ANGLE_157_5 LITERAL1
WMK_ANGLE_180_0 LITERAL1
WMK_ANGLE_202_5 LITERAL1
WMK_ANGLE_225_0 LITERAL1
WMK_ANGLE_247_5 LITERAL1
WMK_ANGLE_270_0 LITERAL1
WMK_ANGLE_292_5 LITERAL1
WMK_ANGLE_315_0 LITERAL1
WMK_ANGLE_337_5 LITERAL1
WMK_NUM_ANGLES LITERAL1
SFE_WIND_VANE_DEGREES_PER_INDEX LITERAL1
SFE_WIND_VANE_ADC_RESOLUTION_DEFAULT LITERAL1
#########################################################
# Structs
#########################################################
SFEWeatherMeterKitCalibrationParams LITERAL3

View File

@@ -0,0 +1,9 @@
name=SparkFun Weather Meter Kit Arduino Library
version=1.1.1
author=SparkFun Electronics
maintainer=SparkFun Electronics <sparkfun.com>
sentence=A library to use the SparkFun Weather Meter Kit
paragraph=
category=Sensors
url=https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library
architectures=*

View File

@@ -0,0 +1,309 @@
#include "SparkFun_Weather_Meter_Kit_Arduino_Library.h"
// Static member definitions
SFEWeatherMeterKitCalibrationParams SFEWeatherMeterKit::_calibrationParams;
uint32_t SFEWeatherMeterKit::_windCountsPrevious;
uint32_t SFEWeatherMeterKit::_windCounts;
uint32_t SFEWeatherMeterKit::_rainfallCounts;
uint32_t SFEWeatherMeterKit::_lastWindSpeedMillis;
uint32_t SFEWeatherMeterKit::_lastRainfallMillis;
uint8_t SFEWeatherMeterKit::_windDirectionPin;
uint8_t SFEWeatherMeterKit::_windSpeedPin;
uint8_t SFEWeatherMeterKit::_rainfallPin;
/// @brief Default constructor, sets default calibration values
SFEWeatherMeterKit::SFEWeatherMeterKit(uint8_t windDirectionPin, uint8_t windSpeedPin, uint8_t rainfallPin)
{
// Set sensors pins
_windDirectionPin = windDirectionPin;
_windSpeedPin = windSpeedPin;
_rainfallPin = rainfallPin;
// The wind vane has 8 switches, but 2 could close at the same time, which
// results in 16 possible positions. Each position has a different resistor,
// resulting in different ADC values. The expected ADC values has been
// experiemntally determined for various platforms, see the constants file
_calibrationParams.vaneADCValues[WMK_ANGLE_0_0] = SFE_WMK_ADC_ANGLE_0_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_22_5] = SFE_WMK_ADC_ANGLE_22_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_45_0] = SFE_WMK_ADC_ANGLE_45_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_67_5] = SFE_WMK_ADC_ANGLE_67_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_90_0] = SFE_WMK_ADC_ANGLE_90_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_112_5] = SFE_WMK_ADC_ANGLE_112_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_135_0] = SFE_WMK_ADC_ANGLE_135_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_157_5] = SFE_WMK_ADC_ANGLE_157_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_180_0] = SFE_WMK_ADC_ANGLE_180_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_202_5] = SFE_WMK_ADC_ANGLE_202_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_225_0] = SFE_WMK_ADC_ANGLE_225_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_247_5] = SFE_WMK_ADC_ANGLE_247_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_270_0] = SFE_WMK_ADC_ANGLE_270_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_292_5] = SFE_WMK_ADC_ANGLE_292_5;
_calibrationParams.vaneADCValues[WMK_ANGLE_315_0] = SFE_WMK_ADC_ANGLE_315_0;
_calibrationParams.vaneADCValues[WMK_ANGLE_337_5] = SFE_WMK_ADC_ANGLE_337_5;
// Datasheet specifies 2.4kph of wind causes one trigger per second
_calibrationParams.kphPerCountPerSec = 2.4;
// Wind speed sampling interval. Longer durations have more accuracy, but
// cause delay and can miss fast fluctuations
_calibrationParams.windSpeedMeasurementPeriodMillis = 1000;
// Datasheet specifies 0.2794mm of rain per trigger
_calibrationParams.mmPerRainfallCount = 0.2794;
// Debounce time for rainfall detector
_calibrationParams.minMillisPerRainfall = 100;
// Reset counters to zero
_windCountsPrevious = 0;
_windCounts = 0;
_rainfallCounts = 0;
// Reset timers
_lastWindSpeedMillis = millis();
_lastRainfallMillis = millis();
}
/// @brief Sets up sensor pins
/// @param windDirectionPin Wind direction pin, must have an ADC
/// @param windSpeedPin Wind speed pin, must support interrupts
/// @param rainfallPin Rainfall pin, must support interrupts
void SFEWeatherMeterKit::begin()
{
// Set pins to inputs
pinMode(_windDirectionPin, INPUT);
pinMode(_windSpeedPin, INPUT_PULLUP);
pinMode(_rainfallPin, INPUT_PULLUP);
// Attach interrupt handlers
attachInterrupt(digitalPinToInterrupt(_windSpeedPin), windSpeedInterrupt, CHANGE);
attachInterrupt(digitalPinToInterrupt(_rainfallPin), rainfallInterrupt, RISING);
}
/// @brief Gets the current calibration parameters
/// @return Current calibration parameters
SFEWeatherMeterKitCalibrationParams SFEWeatherMeterKit::getCalibrationParams()
{
return _calibrationParams;
}
/// @brief Sets the new calibration parameters
/// @param params New calibration parameters
void SFEWeatherMeterKit::setCalibrationParams(SFEWeatherMeterKitCalibrationParams params)
{
// Copy the provided calibration parameters
memcpy(&_calibrationParams, &params, sizeof(SFEWeatherMeterKitCalibrationParams));
}
/// @brief Adjusts the expected ADC values for the wind vane based on the
/// provided ADC resolution
/// @param resolutionBits Resolution of ADC in bits (eg. 8-bit, 12-bit, etc.)
void SFEWeatherMeterKit::setADCResolutionBits(uint8_t resolutionBits)
{
for(uint8_t i = 0; i < WMK_NUM_ANGLES; i++)
{
int8_t bitShift = (SFE_WMK_ADC_RESOLUTION) - resolutionBits;
if(bitShift > 0)
{
_calibrationParams.vaneADCValues[i] >>= bitShift;
}
else if(bitShift < 0)
{
_calibrationParams.vaneADCValues[i] <<= -bitShift;
}
}
}
/// @brief Measures the direction of the wind vane
/// @return Wind direction in degrees
float SFEWeatherMeterKit::getWindDirection()
{
// Measure the output of the voltage divider
uint16_t rawADC = analogRead(_windDirectionPin);
// Now we'll loop through all possible directions to find which is closest
// to our measurement, using a simple linear search. closestDifference is
// initialized to max 16-bit signed value (2^15 - 1 = 32,767)
int16_t closestDifference = 32767;
uint8_t closestIndex = 0;
for (uint8_t i = 0; i < WMK_NUM_ANGLES; i++)
{
// Compute the difference between the ADC value for this direction and
// what we measured
int16_t adcDifference = _calibrationParams.vaneADCValues[i] - rawADC;
// We only care about the magnitude of the difference
adcDifference = abs(adcDifference);
// Check if this different is less than our closest so far
if (adcDifference < closestDifference)
{
// This resistance is closer, update closest resistance and index
closestDifference = adcDifference;
closestIndex = i;
}
}
// Now compute the wind direction in degrees
float direction = closestIndex * SFE_WIND_VANE_DEGREES_PER_INDEX;
// Return direction in degrees
return direction;
}
/// @brief Updates the wind speed measurement windows if needed
void SFEWeatherMeterKit::updateWindSpeed()
{
// The anemometer generates interrupts as it spins. Because these are
// discrete pulses, we can't get an instantaneous measurement of the wind
// speed. Instead, we need to track these signals over time and perform some
// filtering to get an estimate of the current wind speed. There's lots of
// ways to do this, but this library uses a modifed version of a moving
// window filter.
//
// A moving window filter would require an array of values to be stored,
// indicating when each pulse occurred. However for a fixed time window, the
// number of pulses is unknown, so we don't know how big the array needs to
// be. There are some solutions to this, but the one used here is to change
// the moving time window to a static time window, which is illustrated in
// this timing diagram with variable time between pulses:
//
// Pulses | | | | | | | | |
// Window Last window Current window
// Time ------|-----------------------|----------------|
// t_last t_now
// |---Measurement Period--|---Measurement Period--|
//
// A counter is used to track the number of pulses detected in the current
// measurement window; when pulses are detected, the counter is incremented.
// When t_now exceeds the measurement period, the total number of pulses is
// used to calculate the average wind speed for that window. This filter
// only outputs wind speed for the previous window, which does result in
// delayed measurements, but is fine for most data logging applications since
// logs can be synced with the measurement widows
// Get current time
uint32_t tNow = millis();
// Compute time since start of current measurement window
uint32_t dt = tNow - _lastWindSpeedMillis;
// Check how long it's been since the start of this measurement window
if (dt < _calibrationParams.windSpeedMeasurementPeriodMillis)
{
// Still within the current window, nothing to do (count is not
// incremented here, that's done by the interrupt handler)
}
else
{
// We've passed the end of the measurement window, so we need to update
// some things. But first, we need to check how long it's been since the
// last time we updated, since it's possible we've not received any
// pulses for a long time
if (dt > (_calibrationParams.windSpeedMeasurementPeriodMillis * 2))
{
// Over 2 measurement periods have passed since the last update,
// meaning the wind speed is very slow or even zero. So we'll reset
// the wind speed and counter, and set the start of the next window
// to be now
_windCountsPrevious = 0;
_windCounts = 0;
_lastWindSpeedMillis = tNow;
}
else
{
// We've only just gone past the end of the measurement period, so
// save the wind counts for the previous window, reset current
// counter, and update time of start of next measurement window
_windCountsPrevious = _windCounts;
_windCounts = 0;
_lastWindSpeedMillis += _calibrationParams.windSpeedMeasurementPeriodMillis;
}
}
}
/// @brief Gets the measured wind speed
/// @return Measured wind speed in kph
float SFEWeatherMeterKit::getWindSpeed()
{
// Check if the wind speed needs to be updated
updateWindSpeed();
// Calculate the wind speed for the previous window. First compute the
// counts per millisecond
float windSpeed = (float) _windCountsPrevious / _calibrationParams.windSpeedMeasurementPeriodMillis;
// Convert milliseconds to seconds, and counts per second to kph. Need to
// divide by 2 to account for using both rising and falling edges
windSpeed *= 1000 * _calibrationParams.kphPerCountPerSec / 2;
// Return wind speed for the previous measurement interval
return windSpeed;
}
/// @brief Gets the number of wind speed counts
/// @return Number of wind speed counts
uint32_t SFEWeatherMeterKit::getWindSpeedCounts()
{
// Return total wind speed counts
return _windCounts;
}
/// @brief Gets the number of rainfall counts
/// @return Number of rainfall counts
uint32_t SFEWeatherMeterKit::getRainfallCounts()
{
// Return total rainfall counts
return _rainfallCounts;
}
/// @brief Gets the total rainfall
/// @return Total rainfall in mm
float SFEWeatherMeterKit::getTotalRainfall()
{
// Return total rainfall in mm
return _rainfallCounts * _calibrationParams.mmPerRainfallCount;
}
/// @brief Resets the wind speed
void SFEWeatherMeterKit::resetWindSpeedFilter()
{
_windCountsPrevious = 0;
_windCounts = 0;
_lastWindSpeedMillis = millis();
}
/// @brief Resets the total rainfall
void SFEWeatherMeterKit::resetTotalRainfall()
{
_rainfallCounts = 0;
}
/// @brief Interrupt handler for wind speed pin
void SFEWeatherMeterKit::windSpeedInterrupt()
{
// Check if the measurement window needs to be updated
updateWindSpeed();
// Increment counts in this measurement window
_windCounts++;
}
/// @brief Interrupt handler for rainfall pin
void SFEWeatherMeterKit::rainfallInterrupt()
{
// Debounce by checking time since last interrupt
if ((millis() - _lastRainfallMillis) < _calibrationParams.minMillisPerRainfall)
{
// There's not been enough time since the last interrupt, so this is
// likely just the switch bouncing
return;
}
// Enough time has passed that this is probably a real signal instead of a
// bounce, so update the time of the last interrupt to be now
_lastRainfallMillis = millis();
// Increment counter
_rainfallCounts++;
}

View File

@@ -0,0 +1,71 @@
#ifndef __SPARKFUN_WEATHER_METER_KIT_H__
#define __SPARKFUN_WEATHER_METER_KIT_H__
#include "Arduino.h"
#include "SparkFun_Weather_Meter_Kit_Constants.h"
// Calibration parameters for each sensor
struct SFEWeatherMeterKitCalibrationParams
{
// Wind vane
uint16_t vaneADCValues[WMK_NUM_ANGLES];
// Wind speed
uint32_t windSpeedMeasurementPeriodMillis;
float kphPerCountPerSec;
// Rainfall
float mmPerRainfallCount;
uint32_t minMillisPerRainfall;
};
class SFEWeatherMeterKit
{
public:
// Constructor
SFEWeatherMeterKit(uint8_t windDirectionPin, uint8_t windSpeedPin, uint8_t rainfallPin);
static void begin();
// Data collection
static float getWindDirection();
static float getWindSpeed();
static float getTotalRainfall();
// Sensor calibration params
static SFEWeatherMeterKitCalibrationParams getCalibrationParams();
static void setCalibrationParams(SFEWeatherMeterKitCalibrationParams params);
// ADC resolution scaling
static void setADCResolutionBits(uint8_t resolutionBits);
// Helper functions. These can be helpful for sensor calibration
static uint32_t getWindSpeedCounts();
static uint32_t getRainfallCounts();
static void resetWindSpeedFilter();
static void resetTotalRainfall();
private:
// Updates wind speed
static void updateWindSpeed();
// Interrupt handlers
static void windSpeedInterrupt();
static void rainfallInterrupt();
// Pins for each sensor
static uint8_t _windDirectionPin;
static uint8_t _windSpeedPin;
static uint8_t _rainfallPin;
// Sensor calibration parameters
static SFEWeatherMeterKitCalibrationParams _calibrationParams;
// Variables to track measurements
static uint32_t _windCounts;
static uint32_t _windCountsPrevious;
static uint32_t _rainfallCounts;
static uint32_t _lastWindSpeedMillis;
static uint32_t _lastRainfallMillis;
};
#endif

View File

@@ -0,0 +1,93 @@
// Enum to define the indexes for each wind direction
enum SFEWeatherMeterKitAnemometerAngles
{
WMK_ANGLE_0_0 = 0,
WMK_ANGLE_22_5,
WMK_ANGLE_45_0,
WMK_ANGLE_67_5,
WMK_ANGLE_90_0,
WMK_ANGLE_112_5,
WMK_ANGLE_135_0,
WMK_ANGLE_157_5,
WMK_ANGLE_180_0,
WMK_ANGLE_202_5,
WMK_ANGLE_225_0,
WMK_ANGLE_247_5,
WMK_ANGLE_270_0,
WMK_ANGLE_292_5,
WMK_ANGLE_315_0,
WMK_ANGLE_337_5,
WMK_NUM_ANGLES
};
// Angle per index of wind vane (360 / 16 = 22.5)
#define SFE_WIND_VANE_DEGREES_PER_INDEX (360.0 / WMK_NUM_ANGLES)
// The ADC of each platform behaves slightly differently. Some have different
// resolutions, some have non-linear outputs, and some voltage divider circuits
// are different. The expected ADV values have been obtained experimentally for
// various platforms below
#ifdef AVR
// Tested with RedBoard Qwiic with Weather Shield
#define SFE_WMK_ADC_ANGLE_0_0 902
#define SFE_WMK_ADC_ANGLE_22_5 661
#define SFE_WMK_ADC_ANGLE_45_0 701
#define SFE_WMK_ADC_ANGLE_67_5 389
#define SFE_WMK_ADC_ANGLE_90_0 398
#define SFE_WMK_ADC_ANGLE_112_5 371
#define SFE_WMK_ADC_ANGLE_135_0 483
#define SFE_WMK_ADC_ANGLE_157_5 430
#define SFE_WMK_ADC_ANGLE_180_0 570
#define SFE_WMK_ADC_ANGLE_202_5 535
#define SFE_WMK_ADC_ANGLE_225_0 812
#define SFE_WMK_ADC_ANGLE_247_5 792
#define SFE_WMK_ADC_ANGLE_270_0 986
#define SFE_WMK_ADC_ANGLE_292_5 925
#define SFE_WMK_ADC_ANGLE_315_0 957
#define SFE_WMK_ADC_ANGLE_337_5 855
#define SFE_WMK_ADC_RESOLUTION 10
#elif ESP32
// Tested with ESP32 processor board installed on Weather Carrier
#define SFE_WMK_ADC_ANGLE_0_0 3118
#define SFE_WMK_ADC_ANGLE_22_5 1526
#define SFE_WMK_ADC_ANGLE_45_0 1761
#define SFE_WMK_ADC_ANGLE_67_5 199
#define SFE_WMK_ADC_ANGLE_90_0 237
#define SFE_WMK_ADC_ANGLE_112_5 123
#define SFE_WMK_ADC_ANGLE_135_0 613
#define SFE_WMK_ADC_ANGLE_157_5 371
#define SFE_WMK_ADC_ANGLE_180_0 1040
#define SFE_WMK_ADC_ANGLE_202_5 859
#define SFE_WMK_ADC_ANGLE_225_0 2451
#define SFE_WMK_ADC_ANGLE_247_5 2329
#define SFE_WMK_ADC_ANGLE_270_0 3984
#define SFE_WMK_ADC_ANGLE_292_5 3290
#define SFE_WMK_ADC_ANGLE_315_0 3616
#define SFE_WMK_ADC_ANGLE_337_5 2755
#define SFE_WMK_ADC_RESOLUTION 12
#else
// Values calculated assuming 10k pullup and perfectly linear 16-bit ADC
#define SFE_WMK_ADC_ANGLE_0_0 50294
#define SFE_WMK_ADC_ANGLE_22_5 25985
#define SFE_WMK_ADC_ANGLE_45_0 29527
#define SFE_WMK_ADC_ANGLE_67_5 5361
#define SFE_WMK_ADC_ANGLE_90_0 5958
#define SFE_WMK_ADC_ANGLE_112_5 4219
#define SFE_WMK_ADC_ANGLE_135_0 11818
#define SFE_WMK_ADC_ANGLE_157_5 8099
#define SFE_WMK_ADC_ANGLE_180_0 18388
#define SFE_WMK_ADC_ANGLE_202_5 15661
#define SFE_WMK_ADC_ANGLE_225_0 40329
#define SFE_WMK_ADC_ANGLE_247_5 38365
#define SFE_WMK_ADC_ANGLE_270_0 60494
#define SFE_WMK_ADC_ANGLE_292_5 52961
#define SFE_WMK_ADC_ANGLE_315_0 56785
#define SFE_WMK_ADC_ANGLE_337_5 44978
#define SFE_WMK_ADC_RESOLUTION 16
// Set macro to indicate that this platform isn't known
#define SFE_WMK_PLAFTORM_UNKNOWN
#endif