Removed Si7021 and added HYT221 humidity sensor

master
marcel 7 months ago
parent 71a3a48af7
commit eeb6bd846c
  1. 10
      CHANGELOG.md
  2. 12
      TODO.md
  3. 32
      build-doc/weather_station.html
  4. 8
      build-doc/weather_station.md
  5. BIN
      datasheet/HYT 221_application_note.pdf
  6. BIN
      datasheet/HYT 221_datasheet.pdf
  7. 180
      firmware/weather_station.ino

@ -85,3 +85,13 @@ All notable changes to this project will be documented in this file.
There is a bug in the Si7021 chip: when the sensor is exposed to extreme environmental conditions for a longer period (very high or very low humidity), the humidity register will wrap around and give wrong values. (https://community.silabs.com/s/question/0D51M00007xeOo9SAE/si7021-relative-humidity-reading-underflowing-?language=en_US).
Workaround: let's assume the humidity will never go below 15%. In the Netherlands a humidity below 20% is rare, but can occur and the minimum value ever measured in De Bilt was 6% (on 1 april 1965). At several other sites in the Netherlands the humidity went as low as 10%. But as this was a one time event and the measurements are doubtful, this event was not registered in the original database. (https://nl.wikipedia.org/wiki/Relatieve_luchtvochtigheid)
In rare cases, the humidity value could creep up above 15% (when the actual humidity is 100%) and still give wrong values.
## [0.3.0] - 2024-05-02
### Added
Support for HYT-221 humidity sensor
### Removed
Support for si7021 humidity sensor (this sensor was not suited for outdoor measurments)

@ -0,0 +1,12 @@
# Todo list for weather station with ModBus over RS-485
## Reset firmware via ModBus coil bit
Soemtimes the weater station needs to be reset. For example if the I2C communication with the sensors is lost. The pressure sensor tends to have this probem occacionally.
## Implement support for new humidity sensor
Heater function of former sensor needs to be disabled as the new sensor does not have (or need) a heater.

@ -5,7 +5,7 @@
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="M.T. Konstapel" />
<meta name="dcterms.date" content="2024-01-22" />
<meta name="dcterms.date" content="2024-05-02" />
<title>Weather station</title>
<link rel="stylesheet" href="./css/mvp.css" />
<style type="text/css">
@ -53,6 +53,9 @@
id="toc-specifications">Specifications</a></li>
<li><a href="#schematic"
id="toc-schematic">Schematic</a></li>
<li><a href="#problems-i-encounter-after-four-months-of-use"
id="toc-problems-i-encounter-after-four-months-of-use">Problems
I encounter after four months of use</a></li>
<li><a href="#software-dependencies"
id="toc-software-dependencies">Software
dependencies</a></li>
@ -75,7 +78,7 @@
<h1 class="title">Weather station</h1>
<p class="subtitle">with ModBus RTU interface</p>
<p class="author">M.T. Konstapel</p>
<p class="date">2024-01-22</p>
<p class="date">2024-05-02</p>
<p><a href="./weather_station.pdf"><i>PDF version</i></a></p>
</header>
<main>
@ -868,6 +871,29 @@ Address : 14</code></pre>
<h1 id="schematic">Schematic</h1>
<p><a href="./images/weather_station_schematic.pdf"><img
src="./images/weather_station_schematic.svg" alt="Schematic" /></a></p>
<h1 id="problems-i-encounter-after-four-months-of-use">Problems I
encounter after four months of use</h1>
<p>The Si7021 humidity sensor is not made for outdoor use. The datasheet
is clear about that. But a lot of people use this cheap sensor for
weather stations anyway. So I choose this sensor for my design. But that
was not smart. After an initial time without any problems, the sensor
started to saturate. This happened during a very wet and mild winter we
had. My first solution was to utilize the build in heater to drive of
the moisture. That worked, but only for a short period. The heater blew
up and the sensor started to report humidity levels above 100% and
because of a bug in the firmware of the sensor, the humidity register
wrapped around and the sensor reported humidity levels of 0-30%. I tried
to find a software solution, which worked for a while, but the sensor
deteriorated even more. To the point of being totally useless. So I
searched for another, better sensor. And I found the HYT-221 from
Innovative Sensor Technology. This sensor is designed to work outdoors
and can even be used in saunas, where the humidity levels are always
high and the air is condensating. The datasheet specifically mentions
outdoor weather stations as an application. The only downside is its
price: it is ten times more expensive than the Si7021.</p>
<p>The sensor can be controlled via the I2C bus, so implementing the new
sensor in the firmware was very easy. From version 0.3.0 onward, this
sensor is used. The Si7021 is removed from the code.</p>
<!---
# Bill of materials
@ -906,7 +932,7 @@ option) any later version.</p>
</main>
<footer>
<p>&copy;
2024-01-22
2024-05-02
M.T. Konstapel
<a href="https://meezenest.nl/mees/">https://meezenest.nl/mees/</a>
</p><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.

@ -2,7 +2,7 @@
title: Weather station
subtitle: with ModBus RTU interface
author: M.T. Konstapel
date: 2024-01-22
date: 2024-05-02
website: https://meezenest.nl/mees/
page_back: https://meezenest.nl/mees/weather_station.html
logo: ./images/mees_logo.svg
@ -392,6 +392,12 @@ As a housing for the prototype, I used an old beehive. These are weatherproof an
[![Schematic](./images/weather_station_schematic.svg)](./images/weather_station_schematic.pdf)
# Problems I encounter after four months of use
The Si7021 humidity sensor is not made for outdoor use. The datasheet is clear about that. But a lot of people use this cheap sensor for weather stations anyway. So I choose this sensor for my design. But that was not smart. After an initial time without any problems, the sensor started to saturate. This happened during a very wet and mild winter we had. My first solution was to utilize the build in heater to drive of the moisture. That worked, but only for a short period. The heater blew up and the sensor started to report humidity levels above 100% and because of a bug in the firmware of the sensor, the humidity register wrapped around and the sensor reported humidity levels of 0-30%. I tried to find a software solution, which worked for a while, but the sensor deteriorated even more. To the point of being totally useless. So I searched for another, better sensor. And I found the HYT-221 from Innovative Sensor Technology. This sensor is designed to work outdoors and can even be used in saunas, where the humidity levels are always high and the air is condensating. The datasheet specifically mentions outdoor weather stations as an application. The only downside is its price: it is ten times more expensive than the Si7021.
The sensor can be controlled via the I2C bus, so implementing the new sensor in the firmware was very easy. From version 0.3.0 onward, this sensor is used. The Si7021 is removed from the code.
<!---
# Bill of materials

Binary file not shown.

@ -32,6 +32,9 @@
* - Changed some variables to the propper standard (uint8_t, uint16_t, etc.)
* - SparkFun wind interrupt now calculates over 3 seconds in stead of 1 second (KNMI standard)
*
* 2-24-05-02: - Removed cope for si7021
* - Added code for HYT221 humidity sensor
*
* See CHANGELOG.md
*
**********************************************************************************/
@ -43,8 +46,7 @@
#include "i2c.h"
//Temperature and humidity sensor
#include "i2c_SI7021.h"
SI7021 si7021;
#define HYT_ADDR 0x28 // I2C address of the HYT 221, 271, 371 and most likely the rest of the family
// Pressure sensor
#include "i2c_BMP280.h"
@ -152,136 +154,49 @@ struct MeasuredData {
bool HeaterStatus = 0;
} MeasuredData;
// State machine implementing smart heater to prevent saturation of the sensor
char HeaterSi7021 (float humidity)
void ReadHYT221 (void)
{
static int state=0;
static unsigned long StatemachineTimer=0;
bool TempValid=1;
bool Heater=0;
// If Smart heater algorithm is disabled, reset the statemachine forever.
// TempValid bit is also forced to 1, but it could be that we just came out of a heater period.
// We assume that the client on the other side of the ModBus is smart enough to understand.
if ( (MeasuredData.StatusBits & 0x04) == 0)
state = 0;
switch (state)
{
// Default state: humidity is below HUMIDITY_THRESHOLD
case 0:
Heater = 0;
if (humidity >= HUMIDITY_THRESHOLD) {
StatemachineTimer = millis();
state = 1;
}
break;
// Humidity went above HUMIDITY_THRESHOLD. See if humidity stays above HUMIDITY_THRESHOLD for more than an hour, if so turn on heater
case 1:
if (humidity >= HUMIDITY_THRESHOLD) {
if ( (millis() - StatemachineTimer) >= 3.6e+6 ) {
//if ( (millis() - StatemachineTimer) >= 300000 ) { // short delay for testing
Heater = 1;
TempValid = 0;
StatemachineTimer = millis();
state = 2;
} else {
Heater = 0;
}
} else {
Heater = 0;
state = 0;
}
break;
// Heater is now on, let the sensor cook for 5 minutes
case 2:
if ( (millis() - StatemachineTimer) >= 300000 ) {
StatemachineTimer = millis();
Heater = 0;
state = 3;
} else {
Heater = 1;
}
TempValid = 0;
break;
// Heater is now off, let the sensor cool for 15 minutes
case 3:
if ( (millis() - StatemachineTimer) >= 900000 ) {
TempValid = 1; // Sensor cooled, so we can take a valid temperature reading
if (humidity >= HUMIDITY_THRESHOLD) {
// Humidity still above HUMIDITY_THRESHOLD, repeat heating/cooling
Heater = 1;
StatemachineTimer = millis();
state = 2;
} else {
// Humidity below HUMIDITY_THRESHOLD, reset statemachine
Heater = 0;
state = 0;
}
} else {
Heater = 0;
TempValid = 0;
}
break;
double humidity;
double temperature;
default:
Heater = 0;
state = 0;
break;
}
Wire.beginTransmission(HYT_ADDR); // Begin transmission with given device on I2C bus
Wire.requestFrom(HYT_ADDR, 4); // Request 4 bytes
return TempValid<<1 | Heater;
// Read the bytes if they are available
// The first two bytes are humidity the last two are temperature
if(Wire.available() == 4) {
int b1 = Wire.read();
int b2 = Wire.read();
int b3 = Wire.read();
int b4 = Wire.read();
Wire.endTransmission(); // End transmission and release I2C bus
// combine humidity bytes and calculate humidity
int rawHumidity = b1 << 8 | b2;
// compound bitwise to get 14 bit measurement first two bits
// are status/stall bit (see intro text)
rawHumidity = (rawHumidity &= 0x3FFF);
humidity = 100.0 / pow(2,14) * rawHumidity;
}
// Read Si7021 sensor and process data
void ReadSi7021 (void)
{
char result=0x2;
float humidity;
si7021.triggerMeasurement();
si7021.getHumidity(humidity);
// There is a bug in the Si7021 chip: when the sensor is exposed to extreme environmental conditions for a longer period (very high or very low humidity),
// the humidity register will wrap around and give wrong values. (https://community.silabs.com/s/question/0D51M00007xeOo9SAE/si7021-relative-humidity-reading-underflowing-?language=en_US).
// Workaround: let's assume the humidity will never go below 15%. In the Netherlands a humidity of below 20% is rare, but can occur and the minimum value ever measured
// in De Bilt was 6% (on 1 april 1965). At several other sites in the Netherlands the humidity went as low as 10%. But as this was a one time event and the
// measurements are doubtful, this event was nor registered in the original database. (https://nl.wikipedia.org/wiki/Relatieve_luchtvochtigheid)
// In rare cases, the humidity value could creep up above 15% (when the actual humidity is 100%) and still give wrong values.
if (humidity>100 || humidity<15)
humidity = 100;
//If humidity is larger than HUMIDITY_THRESHOLD switch on heater to get more acurate measurement and prevent memory offset
result = HeaterSi7021(humidity);
MeasuredData.StatusBits &= 0xFFFC; // Reset heater status bits to zero
MeasuredData.StatusBits |= result; // And set the proper bits to one if there are any. The result is we copied the status bits to the register
// Temperture and humidity readings are valid (as the sensor is not heated)
if (result & 0x2) {
si7021.getTemperature(MeasuredData.Temperature);
// Scale for more decimal positions when converted to integer value for ModBus
MeasuredData.Temperature *= 100;
//Serial.print(F("Valid temp"));
MeasuredData.Humidity = 100 * humidity;
// combine temperature bytes and calculate temperature
b4 = (b4 >> 2); // Mask away 2 least significant bits see HYT 221 doc
int rawTemperature = b3 << 6 | b4;
temperature = 165.0 / pow(2,14) * rawTemperature - 40;
si7021.getHumidity(MeasuredData.Humidity);
// See humidity bug workaround above.
if (MeasuredData.Humidity>100 || MeasuredData.Humidity<15)
MeasuredData.Humidity = 100;
// Scale for more decimal positions when converted to integer value for ModBus
MeasuredData.Humidity *= 100;
}
// Statemachine thinks it is time to switch on the heater
if (result & 0x1) {
//Serial.print(F("Heater on."));
MeasuredData.HeaterStatus = 1;
MeasuredData.Temperature = 100 * temperature;
} else {
//Serial.print(F("Heater off."));
MeasuredData.HeaterStatus = 0;
//Serial.print(MeasuredData.Humidity);
//Serial.print("% - Temperature: ");
//Serial.println(MeasuredData.Temperature);
}
else {
Serial.println("Not enough bytes available on wire.");
}
si7021.setHeater(MeasuredData.HeaterStatus);
}
// Read BMP280
@ -467,26 +382,11 @@ void setup() {
mb.Ireg (SensorRainSinceMidnightIreg, 0);
mb.Ireg (SensorSnowFallIreg, 0);
Serial.println(F("Weather station v0.2.6"));
Serial.println(F("Weather station v0.3.0"));
Serial.println(F("(C)2024 M.T. Konstapel"));
Serial.println(F("This project is free and open source"));
Serial.println(F("More details: https://meezenest.nl/mees/"));
//Initialize Si7021 sensor
Serial.print(F("Humidity sensor SI7021 "));
if (si7021.initialize())
Serial.println(F("found"));
else
{
Serial.println(F("missing"));
while(1) {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(500); // wait for half a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(500);
}
}
// The standard library of the Si7021 sets the heater element to the default 3.1mA, but we want the full power
// We could alter the library, but than we break compatibility. So for this one time we do a raw-write to the
// heater register.
@ -601,7 +501,7 @@ void loop() {
digitalWrite(LED_BUILTIN, HIGH); // LED as heartbeat
// Read temperature and humidity
ReadSi7021();
ReadHYT221();
// Read pressure and temperature
ReadBMP280();

Loading…
Cancel
Save