First commit
This commit is contained in:
@@ -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, ¶ms, 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++;
|
||||
}
|
@@ -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
|
@@ -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
|
Reference in New Issue
Block a user