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.
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
loopsafely
- 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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Weather meter kit, SEN-15901
- 1 × Resistor, 10k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Character LCD with I2C backpackIn Claw Machine Kit, Vending Machine Kit, Rhythm Game Kit.
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 pin | Connects to |
|---|---|
| Weather meter kit, SEN-15901 pin WIND1 | Arduino Uno R3 pin 3 |
| Weather meter kit, SEN-15901 pin RAIN1 | Arduino Uno R3 pin 2 |
| Resistor, 10k Ω pin 2 | Arduino Uno R3 pin 5V |
| Character LCD with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Weather meter kit, SEN-15901 pin WIND2 | Arduino Uno R3 pin GND |
| Weather meter kit, SEN-15901 pin VANE2 | Arduino Uno R3 pin GND |
| Weather meter kit, SEN-15901 pin RAIN2 | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Weather meter kit, SEN-15901 pin VANE1 | Arduino Uno R3 pin A0 |
| Resistor, 10k Ω pin 1 | Arduino Uno R3 pin A0 |
| Character LCD with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Character LCD with I2C backpack pin SCL | Arduino 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.
// 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 editorTroubleshooting
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.