Post

ENG | Arduino, Weather or Environmental Sensors

A guide on using environmental sensors like HTU21D, BMP280, BMP388, and DS18B20 with Arduino boards for monitoring temperature, humidity, and pressure. Includes wiring instructions, code examples, and project ideas for weather stations and data logging.

Introduction

Environmental sensors are crucial components in many Arduino projects aimed at monitoring and gathering data about surrounding conditions. This guide focuses on these specific sensors:

  • HTU21D for measuring temperature and humidity
  • BME280 BMP280 for measuring pressure, temperature, and humidity
  • BMP388 for pressure and temperature
  • DS18B20 for temperature (with gnuplot example) By integrating these sensors with an Arduino board, you can create powerful environmental monitoring systems, weather stations, or indoor air quality assessments.

Connecting I²C Sensors

The I²C (Inter-Integrated Circuit) protocol is a popular communication standard that allows multiple devices to be connected using just two signal wires (SDA and SCL).

Wiring the BME280 to an Arduino is straightforward:

  • VCC (VIN) to 3.3V (sometimes 5V)
  • GND to GND
  • SCL to A5 (or SCL pin)
  • SDA to A4 (or SDA pin)

This simplifies wiring and makes it easier to integrate several sensors on the same Arduino board. Most Arduino boards have I2C support built-in, but you may need to enable it explicitly in some cases by calling Wire.begin() in your setup().

It’s good to find pinout, cause depending on Arduino version and clone, some pins may not have descripion. Sensors are usually powered by 3.3V and all we need is to connect 3.3V, GND, SDA-A4, SCL-A5 using four wires and multiple devices can be connected.

Arduino Nano Pinout Arduino Nano Pinout, image from https://mischianti.org

Wiki article about I²C

I²C Scanner Utility

If I²C address is not clearly specified. There is I2C Scan utility in Examples->Wire->i2c_scanner. When executed it writes something like this:

1
2
3
4
5
I2C Scanner
Scanning...
I2C device found at adress 0x40  !
I2C device found at adress 0x76  !
done

(I had HTU21D and BMP280 connected at the same time)

Some I2C addresses

AddressDevice
0x40HTU21D Temperature and Relative Humidity sensor
0x51RTC: PCF8563
0x57EEPROM on DS3231 RTC module
0x68RTC: DS3231, DS1307, PCF8523
0x76Bosch BMP180 Temperature and Pressure sensor
0x76Bosch BME280 Temperature, Humidity and Pressure sensor
0x77Bosch BMP388 Temperature and Pressure sensor

NOTE: Technically default for BMP388 is 0x76 as well, but it’s programmable and maybe board has different address to differentiate them.

HTU21D Temperature and Relative Humidity I2C sensor

The HTU21D is a compact and low-power sensor capable of measuring both temperature and relative humidity. Its I²C interface makes it simple to integrate with an Arduino board using just four wires (3.3V, GND, SDA, SCL).

This code demonstrates how to read temperature and humidity values from the HTU21D sensor using the I²C protocol. The readValue() function handles the low-level communication, while readTemp() and readHum() calculate the actual temperature and humidity values based on the raw sensor data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/////////////////////////////////////////////////////////////////////////////
// Includes
#include <Wire.h>

/////////////////////////////////////////////////////////////////////////////
// HTU21D temperature / humidity
// Original from sparkfun library 2016
// Rewritten in March 2024, Pavel Perina

class htu21d {
private:
  // Unshifted 7-bit I2C address for the sensor
  static constexpr uint8_t HTDU21D_ADDRESS = 0x40;

  static constexpr uint8_t TRIGGER_TEMP_MEASURE_HOLD   = 0xE3;
  static constexpr uint8_t TRIGGER_HUMD_MEASURE_HOLD   = 0xE5;
  static constexpr uint8_t TRIGGER_TEMP_MEASURE_NOHOLD = 0xF3;
  static constexpr uint8_t TRIGGER_HUMD_MEASURE_NOHOLD = 0xF5;
  static constexpr uint8_t WRITE_USER_REG              = 0xE6;
  static constexpr uint8_t READ_USER_REG               = 0xE7;
  static constexpr uint8_t SOFT_RESET                  = 0xFE;

  static uint16_t readValue(uint8_t cmd)
  {
    // Request a humidity or temperature reading
    Wire.beginTransmission(HTDU21D_ADDRESS);
    Wire.write(cmd);
    Wire.endTransmission();    

    // Hang out while measurement is taken. 50mS max, page 4 of datasheet.
    delay(55);

    // Comes back in three bytes, data(MSB) / data(LSB) / Checksum
    Wire.requestFrom(HTDU21D_ADDRESS, (uint8_t)3);

    // Wait for data to become available
    int counter = 0;
    while (Wire.available() < 3) {
      counter++;
      delay(5);
      if (counter > 20)
        return INVALID_VALUE;
    }

    const uint8_t msb = Wire.read();
    const uint8_t lsb = Wire.read();
    const uint8_t checksum = Wire.read();
    uint16_t rawValue = ((unsigned int)msb << 8) | (unsigned int)lsb;
    rawValue &= 0xFFFC; // Zero out the status bits
    return rawValue;
  }

public:
  static constexpr float INVALID_VALUE = -100.0f;

  static float readHum()
  {
    // Measure humidity with no bus holding
    uint16_t rawValue = readValue(TRIGGER_HUMD_MEASURE_NOHOLD);
    // Given the raw humidity data, calculate the actual relative humidity
    float hum = rawValue / (float)65536;      // 2^16 = 65536
    hum = -6.0f + (125.0f * hum);             // From page 14
    return hum;
  }

  static float readTemp()
  {
    // Request the temperature
    uint16_t rawValue = readValue(TRIGGER_TEMP_MEASURE_NOHOLD);

    // Given the raw temperature data, calculate the actual temperature
    float temp = rawValue / (float)65536;     // 2^16 = 65536
    temp = (float)(-46.85 + (175.72 * temp)); // From page 14
    return temp;
  }
}; // class htu21d


void setup()
{
  // put your setup code here, to run once:
  Serial.begin(9600);
  while (!Serial)
    ;
  Wire.begin();
}


void loop()
{
  // put your main code here, to run repeatedly:
  const float htuTemp = htu21d::readTemp();
  const float htuHum  = htu21d::readHum();
  Serial.print(htuTemp);
  Serial.print(",");
  Serial.print(htuHum);
  Serial.println("");
  delay(500);
}

Example output:

1
2
3
4
5
6
22.11,37.28
22.10,37.27
22.10,37.27
22.11,37.26
22.11,37.24
22.12,37.24

DS18B20 1-Wire Temperature sensor

Schema

Arduino Nano and DS18B20 Arduino Nano and DS18B20

Installing libraries

Install Arduino libraries including dependencies

Arduino IDE Libraries This picture shows where to install libraries from online repository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// DS18B20 1-Wire digital temperature sensor with Arduino example code.

// Include the required Arduino libraries
#include <OneWire.h>
#include <DallasTemperature.h>

// Define to which pin of the Arduino the 1-Wire bus is connected
constexpr int oneWirePin = 4;

// Create a new instance of the oneWire class to communicate with any OneWire device:
OneWire oneWire(oneWirePin);

// Pass the oneWire reference to DallasTemperature library
DallasTemperature sensors(&oneWire);


void setup(void)
{
  // Begin serial communication at a baud rate of 9600
  Serial.begin(9600);
  // Initialize the library
  sensors.begin();
}


void loop(void)
{
  // Send the command for all devices on the bus to perform a temperature conversion
  sensors.requestTemperatures();
  // Fetch the temperature in degrees Celsius for device index (zero for 1st device)
  const float tempC = sensors.getTempCByIndex(0);  
  // Print the temperature in Celsius in the Serial Monitor
  Serial.println(tempC);
  // Wait 1 second:
  delay(1000);
}

Side Note: Plotting Data (on Linux)

Now let’s have some fun.

  • Prepare boiling water
  • Exit Arduino IDE
  • Put sensor into coffee mug
  • On the console type these command. First one reads serial port, second one reads input and writes it into console and file at the same time.
    1
    
    cat /dev/ttyUSB0 | tee coffee &
    
  • Pour in the hot water and watch temperature. Example output (shortened)
    1
    2
    3
    4
    5
    6
    7
    8
    
    22.37
    25.94
    57.19
    70.56
    75.44
    77.44
    77.87
    78.19
    
  • Keep it for few tens of minutes, while resisting to drink it 😀 Then press Ctrl+C to stop logging.
    1
    2
    3
    4
    
    fg23.81
    [1]  + running    cat /dev/ttyUSB0 | tee coffee
    23.81
    ^C
    
  • Write some script to plot data, e.g.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    # Pavel Perina, 2024-03-13
    
    set terminal pngcairo size 640,480 enhanced font "Noto Sans,11,Normal" background "#1b1b1e"
    set output 'arduino_plot_coffee_temp_dark.png'
    set border linecolor rgb "#cccccc"
    set title tc rgb "#cccccc" 'Temperature over Time'
    set xlabel tc rgb "#cccccc" 'Time (minutes)'
    set ylabel tc rgb "#cccccc" 'Temperature [°C]'
    set xr [0:*] # Adjusts x-range based on data, * means it will automatically find the max
    set yr [20:80] # Your specified y-range
    set xtics 15 textcolor rgb "#cccccc" # Major x-ticks every 15 minutes
    set mxtics 3  # Adds minor x-tics between major ones, adjust as needed
    set grid xtics mxtics ytics linecolor rgb "#cccccc" # Enable grid for easier reading
    set nokey
    set style fill transparent solid 0.15 
    # Convert x-values from seconds to minutes for plotting
    plot 'coffee' using ($0/60.0):1 with filledcurves x1 lc rgb "#de6b18", \
         'coffee' using ($0/60.0):1 with lines lc rgb "#de6b18"  
    
  • Enjoy the result Graph of coffee temperature

BME280 and BMP280 Pressure, Temperature(, and Huminidy) Sensor

Difference between BMP280 and BME280 sensors is that BMP280 sensor can’t read humidity.

There is one detailed article about this sensor How To Use BME280 Pressure Sensor With Arduino and SparkFun BME280 Arduino Library

I tried to write some simple code for using sensor without 3rd party library to read data. It was not so simple, because it’s hard to find reference when something does not work. Are readings ok? Are calibration data ok? Are bit operations ok? How to even debug code on Arduino other way than creating debug outputs to terminal? In the end I succeeded.

Some minimal example is on my GitHub: https://github.com/pavel-perina/arduino-bmp280-minimal

BMP388, BMP390 Pressure and Temperature Sensor

From AdaFruit site:

Pinout

PinDescription
VINInput Voltage 3-5V
3Vo3.3V from stabilizer (MIC5219, MIC5225, …)
GNDGround
SCK, SCLClock for both I2C and SPI
SDOSerial Data Out (MISO)
SDI, SDASerial Data In (MOSI), I2C Serial Data
CSSPI Chip Select, 3-5V
INTInterrupt (measurement ready)
1
2
3
4
MOSI = Master Out -> Slave In
       Microcontroler Out -> Sensor In
MISO = Master In <- Slave Out
       Microcontroler In <- Sensor Out

Board uses voltage regulator (MIC5219-3.3YM5-TR), level shifter (2N7002PS on SDI&SCK, diode on CS) and sensor.

When logic levels of Microcontroler is 5V, VIN should be 5V too.

Connection to Arduino via I2C:

SensorArduino 
 VIN5V
 GNDGND
 SCKA5
 SDIA4

Some minimal example is on my GitHub: https://github.com/pavel-perina/arduino-bmp280-minimal

Projects and Applications

With the ability to measure environmental conditions like temperature, humidity, and pressure, these sensors open up a wide range of potential projects:

  • Home/Office Weather Station: Display current weather conditions and log data over time.
  • Indoor Air Quality Monitor: Track temperature, humidity, and other factors affecting indoor air quality.
  • Greenhouse/Terrarium Monitoring: Ensure optimal growing conditions for plants.
  • Portable Environment Logger: Create a battery-powered device to log data during field studies.

The possibilities are endless, and these sensors provide a great starting point for any Arduino project involving environmental monitoring.

Other resources

Side Note

  • Images were created using EasyEDA, Affinity Designer 2
This post is licensed under CC BY 4.0 by the author.