Built-in project · Arduino Uno R3

Wind and rain station on an Arduino Uno

The SparkFun weather meter kit on an Uno: wind speed from the anemometer, direction from the vane and rain from the bucket, on an I2C LCD. The whole build, an Arduino Uno and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 25 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $114. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Counting pulses with interrupts
  • Debouncing switch contacts in software
  • Reading a resistor ladder as a voltage divider
  • Turning counts into speed and rainfall
  • Sharing counters between interrupts and loop safely
  • Difficulty: Intermediate
  • Time: About 25 minutes
  • Board: Arduino Uno R3
  • Parts: 5

How it works

The weather meter kit has no electronics, just magnets and switches. The anemometer on pin 3 closes a switch once a second for every 2.4 km/h of wind. The rain gauge on pin 2 closes one each time its bucket tips, which is 0.2794 mm of rain. Interrupts count these closures.

The wind vane on A0 is a set of resistors. With a 10k resistor to 5 V it forms a voltage divider, so each of 16 compass points gives a different reading. The sketch picks the closest match.

Every two seconds the sketch turns the count into km/h, adds up the rain, and shows both with the direction on the LCD and in the Serial Monitor.

Parts list

5 parts, plus the jumper wires. They retail for about $114; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Weather meter kit, SEN-15901 pin WIND1Arduino Uno R3 pin 3
Weather meter kit, SEN-15901 pin RAIN1Arduino Uno R3 pin 2
Resistor, 10k Ω pin 2Arduino Uno R3 pin 5V
Character LCD with I2C backpack pin VCCArduino Uno R3 pin 5V
Weather meter kit, SEN-15901 pin WIND2Arduino Uno R3 pin GND
Weather meter kit, SEN-15901 pin VANE2Arduino Uno R3 pin GND
Weather meter kit, SEN-15901 pin RAIN2Arduino Uno R3 pin GND
Character LCD with I2C backpack pin GNDArduino Uno R3 pin GND
Weather meter kit, SEN-15901 pin VANE1Arduino Uno R3 pin A0
Resistor, 10k Ω pin 1Arduino Uno R3 pin A0
Character LCD with I2C backpack pin SDAArduino Uno R3 pin A4
Character LCD with I2C backpack pin SCLArduino Uno R3 pin A5

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Wind and rain station: the SparkFun weather meter kit (SEN-15901) and a 16x2 LCD
// on an I2C backpack.
//
//   pin 3    the anemometer's reed (its other wire to GND)
//   pin 2    the rain gauge's reed (its other wire to GND)
//   A0       the wind vane, with a 10 k pull-up to 5 V (its other wire to GND)
//   A4, A5   the LCD backpack's SDA and SCL (address 0x27)
//
// None of the three sensors has any electronics in it. The anemometer's cups
// turn a magnet past a reed switch, which closes once a second for every
// 2.4 km/h of wind. The rain gauge's bucket tips every 0.2794 mm of rain and
// closes another reed as it goes. Both reeds pull a pin with INPUT_PULLUP to
// ground, and an interrupt counts the falling edges.
//
// The vane is eight reeds and eight resistors: the magnet on the vane closes
// one reed, or two side by side, so there are 16 resistances, one for each
// compass point. With the 10 k pull-up it is a voltage divider, and the
// sketch picks the table entry nearest the reading. The datasheet's table
// is for exactly this: 5 V and 10 k.
//
// Real reeds bounce, so each interrupt ignores edges too soon after the last
// one it counted: 5 ms for the anemometer (at 150 km/h the closures are 16 ms
// apart) and 100 ms for the rain gauge.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const int ANEMOMETER = 3;
const int RAIN_GAUGE = 2;
const int VANE = A0;

LiquidCrystal_I2C lcd(0x27, 16, 2);

// The vane's resistance at each of the 16 positions, north first, going
// clockwise, from the datasheet.
const long VANE_OHMS[16] = {33000, 6570, 8200, 891, 1000, 688, 2200, 1410,
                            3900, 3140, 16000, 14120, 120000, 42120, 64900, 21880};
const char *const POINTS[16] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
                                "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"};
int vaneCounts[16];

volatile unsigned long windClicks = 0;
volatile unsigned long rainTips = 0;
volatile unsigned long lastWind = 0;
volatile unsigned long lastRain = 0;

void onWind() {
  unsigned long now = millis();
  if (now - lastWind >= 5) {
    windClicks++;
    lastWind = now;
  }
}

void onRain() {
  unsigned long now = millis();
  if (now - lastRain >= 100) {
    rainTips++;
    lastRain = now;
  }
}

// The vane position whose expected reading is nearest this one.
int vanePosition(int reading) {
  int best = 0;
  for (int i = 1; i < 16; i++) {
    if (abs(reading - vaneCounts[i]) < abs(reading - vaneCounts[best])) best = i;
  }
  return best;
}

unsigned long lastReport = 0;
unsigned long counted = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: wind and rain station");
  // What the ADC reads at each position: 1023 x R / (R + 10 k).
  for (int i = 0; i < 16; i++) {
    vaneCounts[i] = (int)(1023L * VANE_OHMS[i] / (VANE_OHMS[i] + 10000L));
  }
  pinMode(ANEMOMETER, INPUT_PULLUP);
  pinMode(RAIN_GAUGE, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ANEMOMETER), onWind, FALLING);
  attachInterrupt(digitalPinToInterrupt(RAIN_GAUGE), onRain, FALLING);
  lcd.init();
  lcd.backlight();
  lcd.print("Wind and rain");
  lastReport = millis();
}

void loop() {
  // Every two seconds: the clicks in that time make the wind speed.
  if (millis() - lastReport < 2000) return;
  lastReport += 2000;
  noInterrupts();
  unsigned long clicks = windClicks;
  unsigned long tips = rainTips;
  interrupts();
  unsigned long inWindow = clicks - counted;
  counted = clicks;

  // 2.4 km/h per click a second, over two seconds.
  float kmh = inWindow * 2.4 / 2.0;
  float rainMm = tips * 0.2794;
  int dir = vanePosition(analogRead(VANE));

  lcd.setCursor(0, 0);
  lcd.print(kmh, 1);
  lcd.print(" km/h ");
  lcd.print(POINTS[dir]);
  lcd.print("      ");
  lcd.setCursor(0, 1);
  lcd.print("Rain ");
  lcd.print(rainMm, 2);
  lcd.print(" mm     ");

  Serial.print("wind ");
  Serial.print(kmh, 1);
  Serial.print(" km/h from ");
  Serial.print(POINTS[dir]);
  Serial.print(" (");
  Serial.print(dir * 22.5, 1);
  Serial.print(" deg), rain ");
  Serial.print(rainMm, 2);
  Serial.print(" mm, tips ");
  Serial.println(tips);
}

Code walkthrough

The key functions of the sketch, and what each one does.

onWind sketch.ino

Runs on each falling edge from the anemometer. It counts a click only if at least 5 ms have passed since the last one, which throws away contact bounce. At 150 km/h the real closures are 16 ms apart, so none are missed.

void onWind() {
  unsigned long now = millis();
  if (now - lastWind >= 5) {
    windClicks++;
    lastWind = now;
  }
}

onRain sketch.ino

Does the same for the rain gauge, but ignores edges closer than 100 ms. A bucket tip is slow, so a longer lock is safe.

void onRain() {
  unsigned long now = millis();
  if (now - lastRain >= 100) {
    rainTips++;
    lastRain = now;
  }
}

vanePosition sketch.ino

Takes an analog reading and returns the index of the compass point whose expected reading is closest. The expected readings are worked out in setup from the datasheet resistances.

// The vane position whose expected reading is nearest this one.
int vanePosition(int reading) {
  int best = 0;
  for (int i = 1; i < 16; i++) {
    if (abs(reading - vaneCounts[i]) < abs(reading - vaneCounts[best])) best = i;
  }
  return best;
}

loop sketch.ino

Waits until two seconds have passed, then copies the counters with interrupts briefly off. Wind speed is the new clicks times 2.4 over 2. Rain is the tips times 0.2794. It updates the LCD and prints a line.

void loop() {
  // Every two seconds: the clicks in that time make the wind speed.
  if (millis() - lastReport < 2000) return;
  lastReport += 2000;
  noInterrupts();
  unsigned long clicks = windClicks;
  unsigned long tips = rainTips;
  interrupts();
  unsigned long inWindow = clicks - counted;
  counted = clicks;

  // 2.4 km/h per click a second, over two seconds.
  float kmh = inWindow * 2.4 / 2.0;
  float rainMm = tips * 0.2794;
  int dir = vanePosition(analogRead(VANE));

  lcd.setCursor(0, 0);
  lcd.print(kmh, 1);
  lcd.print(" km/h ");
  lcd.print(POINTS[dir]);
  lcd.print("      ");
  lcd.setCursor(0, 1);
  lcd.print("Rain ");
  lcd.print(rainMm, 2);
  lcd.print(" mm     ");

  Serial.print("wind ");
  Serial.print(kmh, 1);
  // ... 9 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The direction is always the same or looks wrong This project
    The vane table assumes a 10k resistor between A0 and 5 V. Check that resistor.
  • Wind speed reads zero in the wind This project
    The anemometer must go to pin 3 and the rain gauge to pin 2, since those pins support interrupts.
  • Wind speed or rain counts look wrong. Weather meter kit
    Count closures with interrupts and a debounce. One closure per second is 2.4 km/h of wind. Each rain closure is 0.2794 mm of rain. Use pull-ups.
  • The wind direction reads the wrong way. Weather meter kit
    The vane is a set of resistors with a divider. Use a 10 kohm resistor in the divider, map each ADC value to a direction, and check the north mark.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.
  • A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
    Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground.
  • The screen stays blank or shows only a row of blocks. Character LCD with I2C backpack
    The I2C address is usually 0x27 or 0x3F. Run an I2C scanner to find yours. Then turn the contrast trimmer on the backpack slowly until text appears.
  • The backlight is on but there is no text, or text is garbled. Character LCD with I2C backpack
    Wire SDA and SCL to the right pins (A4 and A5 on an Uno), power it from 5 V, and call lcd.init() and the backlight call in setup.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.