/********************************************************************************* * * weather_station is a weatherstation build around the SparkFun weather meter * It can measure wind speed, wind gust , wind direction, rain fall, temperature, * humidity and air pressure and has an RS-485 ModBus interface for your convenience. * * (C)2023 M.T. Konstapel https://meezenest.nl/mees * * This file is part of weather_station * * weather_station is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * weather_station is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with weather_station. If not, see . * **********************************************************************************/ #include #include "SparkFun_Weather_Meter_Kit_Arduino_Library.h" //I2C #include #include "i2c.h" //Temperature and humidit sensor #include "i2c_SI7021.h" SI7021 si7021; /**************************/ /* Configurable variables */ /**************************/ // Sparkfun weather station int windDirectionPin = A0; int windSpeedPin = 2; int rainfallPin = 3; // RS485 driver #define RS485_RE 11 // Tight to RS485_DE and must be configured as an input to prevent a short circuit #define RS485_DE 12 // Used Pins const int TxenPin = RS485_DE; // -1 disables the feature, change that if you are using an RS485 driver, this pin would be connected to the DE and /RE pins of the driver. // ModBus address const byte SlaveId = 14; /* Modbus Registers Offsets (0-9999) * * 30000: Weater station ID (0x5758) * 30001: Wind direction (degrees) * 30002: Wind speed (average over 10 minutes in km/h) * 30003: Wind gust (peak wind speed in the last 10 minutes in km/h) * 30004: Temperature (degrees Celcius) * 30005: Rain last hour (l/m2) * 30006: Rain last 24 hours (l/m2) * 30007: Rain since midnight (l/m2) * 30008: Humidity (percent) * 30009: Barometric pressure (hPa) * */ const int SensorIDIreg = 0; const int SensorWindDirectionIreg = 1; const int SensorWindSpeedIreg = 2; const int SensorWindGustIreg = 3; const int SensorTemperatureIreg = 4; const int SensorRainIreg = 5; const int SensorRainLast24Ireg = 6; const int SensorRainSinceMidnightIreg = 7; const int SensorHumidityIreg = 8; const int SensorPressureIreg = 9; // RS-485 serial port #define MySerial Serial // define serial port used, Serial most of the time, or Serial1, Serial2 ... if available const unsigned long Baudrate = 9600; /******************************/ /* END Configurable variables */ /******************************/ // Create an instance of the weather meter kit SFEWeatherMeterKit weatherMeterKit(windDirectionPin, windSpeedPin, rainfallPin); // ModbusSerial object ModbusSerial mb (MySerial, SlaveId, TxenPin); unsigned long ts; unsigned long HourTimer; int WindGustData1[30]; unsigned char WindGustData1Counter=0; int WindGustData2[10]; unsigned char WindGustData2Counter=0; int WindAverageData1[30]; unsigned char WindAverageData1Counter=0; int WindAverageData2[10]; unsigned char WindAverageData2Counter=0; int RainPerHour[24]; unsigned char RainPerHourCounter=0; struct MeasuredData { int WindDirection; int WindSpeed; int WindGust; int Rain; int RainLast24; int SensorRainSinceMidnight; int Pressure; float Temperature; float Humidity; bool HeaterStatus = 0; } MeasuredData; // Read Si7021 sensor and process data void ReadSi7021 (void) { si7021.triggerMeasurement(); si7021.getHumidity(MeasuredData.Humidity); si7021.getTemperature(MeasuredData.Temperature); // Scale for more decimal positions when converted to integer value for ModBus MeasuredData.Humidity *= 100; MeasuredData.Temperature *= 100; //If humidity is larger than 80% switch on heater to get more acurate measurement //Switch off when lower than 78% (hysteresis) if (MeasuredData.Humidity > 80 && !MeasuredData.HeaterStatus) { Serial.print(F("Heater on.")); MeasuredData.HeaterStatus = 1; si7021.setHeater(MeasuredData.HeaterStatus); } if (MeasuredData.Humidity < 78 && MeasuredData.HeaterStatus) { Serial.print(F("Heater off.")); MeasuredData.HeaterStatus = 0; si7021.setHeater(MeasuredData.HeaterStatus); } } int MaxOfArray (int array[], unsigned int length) { int maximum_value = 0; while (length--) { if (array[length] > maximum_value) maximum_value = array[length]; } return maximum_value; } int AverageOfArray (int array[], unsigned int length) { int tmp_value = 0; unsigned char tmp_length = length; int average_value = 0; while (length--) { tmp_value += array[length]; } average_value = tmp_value/tmp_length; return average_value; } // Call this function every 2 seconds void ReadSparkfunWeatherStation (void) { unsigned char cnt=0; float tmpRegister; MeasuredData.WindDirection = weatherMeterKit.getWindDirection(); tmpRegister = 100*(weatherMeterKit.getWindSpeed())/3.6; // Use float for conversion to m/s times 100, than put it in integer register for ModBus MeasuredData.WindSpeed = tmpRegister; tmpRegister = 100*weatherMeterKit.getTotalRainfall(); // Use float for conversion to l/m2 times 100, than put it in integer register for ModBus MeasuredData.Rain = tmpRegister; // FIFO for calculating wind gust of last 10 minutes // to preserve valuable RAM we caanot store all measurements of the last 10 minutes. // So we use a hack: store the last 30 values in a FIFO and every minute we store the maximum value from this FIFO in another FIFO. // This second FIFO is 10 deep: it stores the maximum values of the last 10 minutes. // The maximum value from this FIFO is the maximum wind gust of the last 10 minutes. if ( WindGustData1Counter < 29 ) { WindGustData1Counter++; } else { if ( WindGustData2Counter < 9 ) { WindGustData2Counter++; } else { WindGustData2Counter=0; } WindGustData2[WindGustData2Counter] = MaxOfArray(WindGustData1, 30); WindGustData1Counter=0; } WindGustData1[WindGustData1Counter] = MeasuredData.WindSpeed; MeasuredData.WindGust= MaxOfArray(WindGustData2, 10); // Smart FIFO, same as for Wind Gust, but now for average wind speed over 10 minutes if ( WindAverageData1Counter < 29 ) { WindAverageData1Counter++; } else { if ( WindAverageData2Counter < 9 ) { WindAverageData2Counter++; } else { WindAverageData2Counter=0; } WindAverageData2[WindAverageData2Counter] = AverageOfArray(WindAverageData1, 30); WindAverageData1Counter=0; WindAverageData1[WindAverageData1Counter] = MeasuredData.WindSpeed; } WindAverageData1[WindAverageData1Counter] = MeasuredData.WindSpeed; MeasuredData.WindSpeed = AverageOfArray(WindAverageData2, 10); // Record rainfall in one hour, save last 24 readings in FIFO if ( ( millis() - HourTimer) >= 3.6e+6) { HourTimer = millis(); if ( RainPerHourCounter < 23 ) { RainPerHourCounter++; } else { RainPerHourCounter=0; } RainPerHour[RainPerHourCounter] = MeasuredData.Rain; weatherMeterKit.resetTotalRainfall(); // Calculate rain fall in the last 24 hours MeasuredData.RainLast24=0; for (cnt=0; cnt<24;cnt++) { MeasuredData.RainLast24 += RainPerHour[cnt]; } } MeasuredData.Rain = RainPerHour[RainPerHourCounter]; } void setup() { MySerial.begin (Baudrate); // works on all boards but the configuration is 8N1 which is incompatible with the MODBUS standard // prefer the line below instead if possible // MySerial.begin (Baudrate, MB_PARITY_EVEN); //Setup control lines for RS485 driver pinMode(RS485_RE,INPUT); // In hardware connected to RS485_DE. Should be input to prevent a short circuit! pinMode(RS485_DE,OUTPUT); digitalWrite(RS485_DE,LOW); mb.config (Baudrate); mb.setAdditionalServerData ("TEMP_SENSOR"); // for Report Server ID function (0x11) // Add SensorIreg registers - Use addIreg() for analog Inputs mb.addIreg (SensorIDIreg); mb.addIreg (SensorWindDirectionIreg); mb.addIreg (SensorWindSpeedIreg); mb.addIreg (SensorWindGustIreg); mb.addIreg (SensorTemperatureIreg); mb.addIreg (SensorRainIreg); mb.addIreg (SensorRainLast24Ireg); mb.addIreg (SensorRainSinceMidnightIreg); mb.addIreg (SensorHumidityIreg); mb.addIreg (SensorPressureIreg); // Set Weather station ID mb.Ireg (SensorIDIreg, 0x5758); Serial.println(F("Weather station")); //Initialize Si7021 sensor Serial.print(F("Humidity sensor SI7021 ")); if (si7021.initialize()) Serial.println(F("found")); else { Serial.println(F("missing")); while(1) {}; } // 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); #endif // Begin weather meter kit weatherMeterKit.begin(); ts = millis(); RainPerHourCounter = ts; } void loop() { // Call once inside loop() - all magic here mb.task(); // Read each two seconds if ( ( millis() - ts) >= 2000) { ts = millis(); // Read temperature and humidity ReadSi7021(); // Read Wind and rain ReadSparkfunWeatherStation(); // Setting Sparkfun weather station registers mb.Ireg (SensorWindDirectionIreg, MeasuredData.WindDirection); mb.Ireg (SensorWindSpeedIreg, MeasuredData.WindSpeed); mb.Ireg (SensorWindGustIreg, MeasuredData.WindGust); mb.Ireg (SensorRainIreg, MeasuredData.Rain); mb.Ireg (SensorRainLast24Ireg, MeasuredData.RainLast24); mb.Ireg (SensorTemperatureIreg, MeasuredData.Temperature); mb.Ireg (SensorHumidityIreg, MeasuredData.Humidity); } }