Neopixel and HTU21D Weather Station

Overview

With a handful of components and access to a 3D printer, you can make your own homemade weather station. You’ll always know the temperature and relative humidity at a glance.

For this project, an Arduino samples an HTU21D temperature and humidity sensor and displays both results on a 24 LED NeoPixel ring. To protect your HTU12D breakout board, we’ll be installing the sensor inside a mini 3D printed Stevenson Screen.

Components and Tools

Wiring

3D Printing

Assembly

Software

Customisation


 Required Components

Electronic components

  • 24 LED Neopixel ring
  • HTU21D humidity and temperature breakout – The housing has been designed to suit the the HTU21D breakout from Adafruit
  • Arduino Pro
  • 5V power supply
  • Pin headers
  • 2 pin Molex connector
  • 3 pin Molex connector
  • Computer cable with a minimum of 4 cores
  • Solder

Other components

  • White ABS filament
  • 1x cable gland with 12mm thread
  • 2x 3mm screws to suit
  • Heatshrink

Required Tools

  • Safety glasses
  • Access to a 3D printer (expect about 6 hours of print time)
  • Diagonal cutters
  • Pointy nose pliers
  • Flat blade screwdriver
  • Phillips head screwdriver
  • Soldering iron
  • Drill bits for pilot hole and to suit cable gland
  • Cordless drill
  • Stanley knife – be careful of the sharp blade

Wiring

This is a straightforward circuit to wire.

(Note that the wiring diagram shows an alternate HTU21D breakout board. The Adafruit breakout’s additional 3V3 pin is not shown but it is not required.)

Weather Station wiring

Connections

Arduino HTU21D NeoPixel Ring
VCC VIN Power +5V
GND GND GND
A4 (SDA) SDA
A5 (SCL) SCL
D6 Data Input

Power Supply

Power for the circuit will be provided by a FTDI Basic breakout connected to the programming port. The USB lead for the FTDI Basic will then be plugged into a USB wall adapter.

However, you could use a large capacity LiPo battery or 9V wall adapter.


3D Printing

The latest model files are available for download from Thingiverse.

Weather Station exploded

This model requires support material for the main body of the Stevenson Screen. Supports allow the main body to be printed in one complete piece ensuring strength and durability whilst outside.

To cover the model, there is a small lid that has tabs which align with notches in the top of the main body. Twist the lid to lock it into position.

The stand that has an 18mm internal hole that allows the Stevenson Screen to be attached to a post.

The body sits on the stand with a press fit, but a few dabs of Super Glue area  good idea.

Recommended Print Settings

  • Material:White  ABS
  • Infill: Moderate to high to achieve good rigidity.
  • Speed: Normal
  • Shells: Thick
  • Supports: Required for the main body and support stand.

Stevenson Screen

Long pointy nose pliers and a flat blade screwdriver are recommended to remove the support material.

Stand

Again, pointy nose pliers are good at tearing off the support material.

Lid

Not much to say really.

Stevenson Screen lid

TOP


Assembly

Soldering HTU21D Breakout

Solder 5 male pins to the HTU21D breakout board with the solder connection made on the component side of the breakout board.

HTU21D with header soldered

Soldering NeoPixel Ring

I chose to cut up some male header wires. Install a red wire in the “PWR +5V” terminal, a black wire in the “GND”terminal and a wire in the “Data Input” terminal.

Install Cable Gland

Invert the Stevenson screen and choose a suitable position for the cable gland. Make sure it doesn’t obstruct the stand and is not too close to the edge. Mark the centre with a permanent marker.

Use about a 4 mm drill to make a pilot hole. Then use a drill with the same diameter as the cable gland’s thread to make the final hole.

Press the cable gland into the hole with a twisting motion to secure the gland in position. The cable gland’s lock nut isn’t necessary.

Terminate Cable at Stevenson Screen End

Prepare cable

Pull about 500mm of the computer cable through the cable gland. Then using a sharp knife, carefully strip about 100mm off the outer layer insulation. If your cable has a shield wire, cut it off flush with the cut insulation, the shield wire isn’t necessary at this end. Install some heatshrink over the cable where the insulation has been removed to make the job look tidy.

Terminate Cable

Only four wires are required so decide on the colours that you’ll be using. Strip off about 5mm of insulation. Before terminating the Molex connectors, trim the conductor to the correct length. One set of crimp tabs are for securing the connector to the insulation. The other crimp tabs make the electrical connection. The connector carrier strip can be removed with small cutters.

Insulate Unused Cores

This step is not necessary if your cable only has 4 cores. Unused cores should be insulated from the elements in the event that you may want to use them in the future. Place some short lengths of small heatshrink over the cores, heat the heatshrink and whilst it is still warm, gently crimp the heatshrink with some pliers.

Insert Connectors into Molex Sockets

Once all the 4 Molex connectors have been crimped, insert each connector into their plug. Gently pull the cable back through the cable gland and tighten the gland.

Install HTU21D Breakout Board

Using 2x short 3mm screws, secure the HTU21D to the standoff inside the Stevenson Screen. Firmly twist the screws in so the screw’s thread bites into the plastic. Ensure the wires aren’t squashed by the Molex plugs.

TOP

Terminate Cable for Arduino End

My Arduino Pro has female pin headers installed, so, to terminate the computer cable, male pin headers will be used.

Prepare Cable

Carefully strip back about 100mm of outer insulation with a sharp knife. Remove any foil shielding. This time, keep the shield wire and insulate with heatshrink. Cover the cut outer insulation with heatshrink for tidiness.

Prepare Wires

Strip back about 5mm of insulation from the four wires that you are using. Slide heatshrink over the individual wires. I grouped the black wire (GND) and the shield wire together as they will be soldered together.

heatshrink on all cores

Solder Wires to Header

Using 2 small pieces of Blue-Tac, position the header the wire conductors close to each other. Solder the wires to the header.

Finish Connectors

Slide the heatshrink down and shrink. Then using some cutters, separate the header.

TOP

Wire up HTU21D, Neopixel and Arduino

Connect up all the components as per the wiring diagram.

completed_wiring


Install Housing

Ideally, you should install the Stevenson Screen in a location that is free of shade from trees and buildings to ensure quality data. I’m limited with locations where I can install my sensor. I’ll forego my weather measurements being scientifically correct, I really just want to know the temperature and humidity.

Stevenson Screen in service
Stevenson Screen installed on balcony rail

TOP


Software

Most weather stations display the numerical values of the conditions being monitored. However, for this weather station, both temperature and relative humidity are displayed simultaneous on a 24 LED NeoPixel ring.

Temperature values are ranged between 12 to 36 degrees Celcius  and are displayed in red. Whilst relative humidity values are shown for the full 0-100% range in blue. Both temperature and humidity are shown together on the NeoPixel ring.

The humidity readings are temperature compensated as per the equation provided in the HTU21D datasheet. All calculated measurements are output on the serial port too.

This software utilises the Adafruit NeoPixel library which you can get download and get details on how to use by following their guide.

Once the NeoPixel library is installed, copy the code into a new sketch and compile. Don’t forget to select the correct Arduino board and processor type.

//Code to read the temperature and humidty from a HTU21D temperature sensor and display it on a 24 LED NeoPixel ring.
//Tim Hansen 20/4/15 - www.steelcityelectronics.com
//HTU21D
//Protocol: I2C (TWI)
//Specs - Temperature: -40 to 125 degrees Celcius
// - Humidity: 0 to 100% relative humidity
// - Voltage: 3.8VDC max
//NeoPixel ring
//LEDS = 24x

//What libraries do we need?
#include <Wire.h> //The "Wire.h" library is required to use the I2C commands
#include <Adafruit_NeoPixel.h> //This project requries the use of the Adafruit NeoPixel library
 //Checkout https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library for details

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN 6

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 24

// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void RequestMeas(int Address, int CommandCode)
{
 Wire.beginTransmission(Address); // The HTU21D will respond to its address
 Wire.write(CommandCode); // request the measurement, ie temperature or humidity
 Wire.endTransmission();
 return;
}

long ReadMeas(int Address)
{
 //A measurement from the HTU21D is returned in 3 consecutive bytes. See page 11 of the datasheet
 //the order of returend data is: [DATA (Most Sig Byte)], [DATA (Least Sig Byte)], [Checksum]
 //We will ignore the checksum to save computation time and complexity. Page 14 of the datasheet details how to calculate the checksum

 byte DataHIGH; //The location to store the high measurement byte
 byte DataLOW; //The low measurement byte
 byte CRC; //If you want to compute the CRC feel free to do so.

 long Data; //The temporary memory location we are storing our data in as we receive it from the HTU21D
 //Each packet of data is only 8bits in length, but if we do a bit shift we can store the 2 packets of
 //measurement data in a variable. Remember you can only return 1 variable when a function finishes.
 //Ideally, we'd use an int to store the result, however, at high measurement values in 14bit mode, an int overflows.
 //To prevent overflow, we must use a single long (32bits). 

 Wire.requestFrom(Address, 3); //We are requesting data from the HTU21D and there will be 3x bytes that must be read.
 while(Wire.available()) // Check for data from the HTU21D
 {
 DataHIGH = Wire.read(); // Read high byte
 DataLOW = Wire.read(); // Read low byte
 CRC = Wire.read(); // Read the CRC byte
 } 

 //OK, so now we are going to 'pack' our 2x bytes of data measuremetns into a single int so only 1 variable is returned.
 //To do this, we will use the left bitshift operator '<<'. What this does is push all the bits across to the left.
 //FUN FACT: performing a left bit shift is equivalent to multiplication by 2 for each shift. The reverse is true for right
 //bitshift which is equivalent to dividing by 2. This is a more efficient method to perform multiply or divide by 2.

 Data = DataHIGH; //The data sits in the low byte of 'int Data'.
 //Ie, Data = [00000000][00000000][00000000][8 bits of DataHIGH]
 Data = Data << 8; //Shift all the bits to the left for 8 bits. The old locations fill with zeros.
 //Ie, Data = [00000000][00000000][8 bits of DataHIGH][00000000]
 Data = Data + DataLOW; //Now simply add the low byte of data to the int.
 //Ie, Data = [00000000][00000000][8 bits of DataHIGH][8 bits of DataLOW]

 return Data; //return our measurement.
}

void setup()
{
 Wire.begin(); // join i2c bus (address optional for master)
 Serial.begin(9600); // start serial for output
 pixels.begin(); // This initializes the NeoPixel library.
}

void loop()
{
 //Set the address of the HTU21D. This detail is hidden at the bototm of page 10 of the datasheet.
 const int I2C_address = 0x40; // I2C write address. Note that 'const' has been declared. If you try to write to this variable
 // anywhere else in your code you'll get a compilation error. This prevents you from doing silly things. 

 const int TempCode = 0xE3; // A temperature measurement is executed with this command code. See page 11 of datasheet.
 const int HumidCode = 0xE5; // A humidity measurement is executed with this command code. See page 11 of datasheet.
 const float TempCoefficient = -0.15; //The temperature compensation coefficient value to guaratnee RH accuracy between 20-80%RH

 long Temperature; //Perform our calculation using LONG to prevent overflow at high measuremetn values when using 14bit mode.
 long Humidity; 

 float TemperatureFL; //Variable to store our final calculated temperature measurement
 float HumidityFL;
 float HumidityCompFL; //%RH value that has been temperature compensated to gurantee performance of +-3% between 20-80%RH

 byte const BaseBrightness = 10; //Set the brightness of the LEDs. The highest measurement reading will display this brightness
 byte const ColourBoost = 20; //The lower measuremetn reading needs to be 'boosted' otherwise it gets swamped by the the other higher reading.

 byte TempScaled; //Variable to hold the scaled temerpature valeu
 byte TempMin;
 byte TempMax;
 byte HumidityScaled;

 delay(100); // a short delay to let everything stabilise

 while(true) // execute all the instructions in this loop forever
 {
 delay(10); //add a small delay to slow things down

 RequestMeas(I2C_address, TempCode); //Request the current temperature
 Temperature = ReadMeas(I2C_address); //read the result

 //We must now convert our temp measurement into an actual proper temperature value
 //According to page 15, the conversion equation is: Temp = -46.85 + 175.72 x TeampMeas / 2^16
 //Becasue the result will have decimal places in the result we must use a Float datatype.
 TemperatureFL = -46.85 + 175.72*(Temperature/pow(2, 16)); 

 delay(10); //add a small delay to slow things down

 RequestMeas(I2C_address, HumidCode); //Request the current humidity
 Humidity = ReadMeas(I2C_address); //read the result

 //We must now convert our humidity measurement into an actual proper humidity value
 //According to page 15, the conversion equation is: Humidity = 6 + 125 x TeampMeas / 2^16
 //Because the result will have decimal places in the result we must use a Float datatype.
 HumidityFL = -6 + 125*(Humidity/pow(2, 16)); 

 //The relative humidty value read from directly from the chip is not the optimised value.
 //We must perfrom the 'temperature Coefficient Compensation Equation" specified on page 4 of the datasheet
 //RHcompensated = RHactual + (25 - TEMPactual) * coefficient
 //The coefficient value is -0.15%RH/C - from page 3 of datasheet
 HumidityCompFL = HumidityFL +(25 - TemperatureFL) * TempCoefficient;

 //All the calculations are complete, lets now display our values for temperature and humidity on the serial port.
 Serial.print("Temperature: ");
 Serial.print(TemperatureFL);
 Serial.print("C");

 Serial.print("\t");
 Serial.print("Humidity (raw): ");
 Serial.print(HumidityFL);
 Serial.print("%RH");

 Serial.print("\t");
 Serial.print("Humidity (Comp): ");
 Serial.print(HumidityCompFL);
 Serial.println("%RH");

 //scale temperature
 //temp range is 12C to 36C
 //Make 1 degree equal 1 pixel. Therefore set a minimum temperature and then add 24 pixels to it.
 TempMin = 12;
 TempMax = TempMin + 24; //think about a better way to do this for smaller neopixel products

 TempScaled = byte(TemperatureFL);
 TempScaled = TempScaled - TempMin;

 HumidityScaled = byte(HumidityCompFL)>>2; //divide by 4. each right shift is equivalent to a divide by 2.

 //drive the neopixel ring
 for(int i=0;i<NUMPIXELS;i++)
 {
 if(i<= TempScaled)
 {
 if(i<= HumidityScaled)
 { //Set the temperature and humidity coloured pixel
 //pixels.setPixelColor(i, pixels.Color(10,0,10)); //temperature pixels are red, humidity pixels are blue
 if(TempScaled == HumidityScaled)
 {
 pixels.setPixelColor(i, pixels.Color(BaseBrightness,0,BaseBrightness)); //temperature pixels are red, humidity pixels are blue
 }
 else if(TempScaled >= HumidityScaled) //boost the humidity pixels so they can be seen more easily
 {
 pixels.setPixelColor(i, pixels.Color(BaseBrightness,0,BaseBrightness + ColourBoost)); //temperature pixels are red, humidity pixels are blue
 }
 else //Boost the temperature pixels so they are more easily seen
 {
 pixels.setPixelColor(i, pixels.Color(BaseBrightness + ColourBoost,0,BaseBrightness)); //temperature pixels are red, humidity pixels are blue
 }

 }
 else
 { //Set the temperature coloured pixel, but the humidity pixel is off
 pixels.setPixelColor(i, pixels.Color(BaseBrightness,0,0)); //temperature pixels are red, humidity pixels are OFF
 }
 }

 else
 {
 if(i<= HumidityScaled)
 { //Set the humidity coloured pixel but not the temperature
 pixels.setPixelColor(i, pixels.Color(0,0,BaseBrightness)); //temperature pixels are OFF, humidity pixels are blue
 }
 else
 { //Both the temperature and humidty pixel is off
 pixels.setPixelColor(i, pixels.Color(0,0,0)); //temperature pixels are OFF, humidity pixels are OFF
 }
 }

 pixels.show(); // This sends the updated pixel color to the hardware.
 }
 }

 return;
}

TOP


Customisation

There are many avenues of customisation for this project. Possible enhancements are:

  • implementing an ambient light sensor to brighten or dim the NeoPixel ring
  • a button that will display or reset the daily maximum
  • LCD display to show numerical sensor values

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

6 thoughts on “Neopixel and HTU21D Weather Station

  1. Good job! Thanks man! I’ve been searching for something like this. They just don’t sell it or it costs as much as $100+. I ordered the same housing to print using your 3D models! Much appreciated!

    And sure – thanks for detailed steps descriptions.

    Like

Share your thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.