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,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