hier die Anleitung für einen batteriebetriebenen Funk Wind- und Luftdruckmessers.
Die Station überträgt über einen NRF24 Chip im 2.4GHz Band die erfassten Wetterdaten (aktuell Wind und Luftdruck). Die Daten werden in regelmässigen Intervallen (5min) übertragen. Wird eine eingestellte Schwelle überschritten findet eine zusätzliche Übertragung statt (z.B. als Böen - Alarm zum einziehen von Markisen oder ähnlichem). Zu den übertragenen Werte gehören : Durchschnitts-Windgeschwindigkeit, Maximale-Windgeschwindigkeit, Luftdruck, Batterie Spannung. Durch Sleepmodes des Arduino wird die Stromaufnahme verringert, so sollten die 2 AA Batterien ca ein Jahr oder mehr überdauern. Damit dies möglich ist muss ein möglichst einfacher Arduino verwendet werden, welcher sich einfach von Spannungsregler und LED's befreien lässt. Grössere Arduino haben noch zusätzliche Bauelemente oder Bustreiber drauf, aus diesem Fall habe ich den Pro Mini verwendet und den Spannungswandel sowie die 2 LED entfernt.
Bei mir läuft der Windmesser nun seit einigen Tagen. Es gibt noch die Einschränkung (was Windmesser spezifisch ist), dass mein Windmesser erst ab ca 3km/h erfasst wird.
Ich habe die Teile nun in ein Gehäuse (Spelsberg 65x65x58 -> Link ) eingebaut (oben links Arduino Pro Mini (darauf Huckepack der BMP180 Druckmesser) mit dem 3.3V Step-Up Converter, oben rechts - was dann die Rechte Gehäuseseite im Montierten Zustand ist - das NRF24L01+ Kommunikationsmodul, unten rechts ein Sinterfilter von einem Tauchautomaten als Druckausgleich und die Kabelverschraubung für den Windmesser). Der Batterie Halter wird dann Quer hineingelegt und passt genau +/- 1mm rein : Zuerst mal die Teilleiste:
- Arduino Pro Mini (http://www.arduino.cc/en/Main/ArduinoBoardProMini) Gibt es original oder als Klone in verschiedenen Ausführungen. Ggf mit Programmier Dongle für USB falls nicht vorhanden.
- BMP 180 Barometer (z.B. hier : http://www.aliexpress.com/cheap/cheap-bmp180.html)
- Windmesser mit Puls-Ausgang (z.B. Eltako Windmesser WS z.B. hier : http://www.amazon.de/Eltako-Windsensor-WS/dp/B0018LBFG8 andere Windmesser benötigen eine andere Formel zur Windgeschwindigkeitsberechnung (siehe Methode calcWind() welche nach Bedarf umgeschrieben werden kann.
- 2 AA Batteriehalter
- Step-Up Converter von 3V auf 3.3V (z.B. hier : http://www.aliexpress.com/store/product ... 09053.html)
oder Alternative von SparkFun : https://www.sparkfun.com/products/10967
- NRF24L01+ Funkmodul (http://www.amazon.de/SainSmart-NRF24L01 ... B006CHFPFU)
Schaltplan Grundsätzlich
- (+) Pol Batteriehalter -> A0 Eingang zur Spannungsmessung
- Batteriehalter via StepUp Converter an Vcc / GND Pins vom Arduino
- NRF24L01+ Anschluss über das SPI Interface -> Siehe http://www.arduino.cc je nach verwendetem Arduino Modell. CE/CSN Pins müssen im Code ggf. Angepasst werden. Achtung Max Spannung ist 3.3V!
- BMP180 muss an das I2C Interface (SDA / SCL) auch hier je nach verwendetem Arduino. Achtung Max Spannung ist 3.3V! -> Nachtrag, der BMP180 verträgt nur 3.3V aber auf dem Board ist ein simpler Spannungsregler, somit läuft das Board auch mit 5V Versorgungsspannung. Werden jedoch noch andere I2C Sensoren mit 5V Logik verbaut wird es Probleme geben.
- Impuls des Windmessers muss auf der einen Seite auf GND auf der anderen an einen PIN der einen Interrupt unterstützt, je nach Arduino (meist Pin 2 oder 3).
Sketch für die Aussenstation
Code: Alles auswählen
/* ===================================================================================================================
Der folgende Code wurde fuer einen Arduino Pro Mini mit 3.3V geschrieben. Mit einigen Anpassungen
sollte der Code auch auf anderen Arduino Boards laufen.
!! ACHTUNG : Versuche den Code (Interrupthandling des Windmessers im Zusammenspiel mit dem Stromsparmodus)
auf einem Arduino MEGA 2560 zu testen schlugen fehl. Ich vermute ein anderes IRQ Handling auf dem MEGA
Hilfreiche Links zum Thema Stromsparmodus :
- http://donalmorrissey.blogspot.ch/2010/04/putting-arduino-diecimila-to-sleep-part.html
- http://forum.arduino.cc/index.php?topic=199576.0
Der vorliegende Code ist Open Source nach GPL
Hardware Voraussetzungen / verwendete Hardware:
- ELTAKO Windmesser WS
- Arduino Mini Pro 3.3V -> http://www.arduino.cc/en/Main/ArduinoBoardProMini
- NRF24L01 Funkmodul
- BMP180 Luftdruck Modul
- BH1750 Lichtintensitaets Messer
- 3.3V Step-Up Converter (Anschluss von 2x 1.5V AA Batterien ueber einen Step Up Converter von 0.8 - 3V -> 3.3V
==================================================================================================================*/
#include <SFE_BMP180.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <Wire.h>
#include <BH1750FVI.h>
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
// Programm Konstanten
// Serial Uebertragungsrate
const uint32_t BAUD = 57600;
// Hoehe der Mess-Station damit der Druck auf Meereshoehe normalisiert werden kann.
const uint16_t ALTITUDE = 457;
// Ab welcher Windboen Geschwindigkeit soll eine Funkuebertragung stattfinden?
const uint16_t MAX_WIND_GUST = 25;
// Alle n Sekunden wird eine regulaere AVG Wind Messung uebertragen, zusammen mit allen anderen Werten
const uint32_t WIND_INTERVALL = 300;
// Interrupt welcher fuer den Windsensor verwendet wird. !ACHTUNG Haengt vom Input Pin ab!
const uint16_t WIND_INT = 0;
// Input Pin fuer den Windsensor. !ACHTUNG der Pin muss Interrupt faehig sein -> Siehe Arduino Doku
const uint16_t WIND_CONN_PIN = 2;
// Wieviele Regulaere Datenuebertragungen muessen zwischen zwei Boen Windwarnungen vergehen. WIND_GUST_HOLDOFF x WIND_INTERVALL
const uint16_t WIND_GUST_HOLDOFF = 3;
// Analog Input Pin fuer die Batterie Spannungsmessung
const uint16_t BATTERY_PIN = 0;
// Digitaler Output Pin fuer die Stromversorgung des NRF Moduls
const uint16_t NRF_POWER = 10;
// Digitaler Output Pin fuer die Stromversorgung des BMP Drucksensors
const uint16_t BMP_POWER = 4;
// AD-Converter Controll Register Zwischenspeicher, damit der ADC waehrend dem Sleep deaktiviert werden kann.
byte adcsra;
// Zaehler ab wann wieder Windboeen Warnungen uebertragen werden.
unsigned long nextWindGustTx = 0;
// Aktuelle Uebertragungsnummer
unsigned long txNumber = 0;
// Timeout Multiplikator fuer die Watchdog Timeouts. Wird in Setup() gesetzt und muss zum verwendeten Watchdog Timeout passen.
uint32_t WDT_TIMEOUT;
// Instanzen fuer die ueber I2C Angeschlossenen Sensoren
// BMP180 Sensor angeschlossen ueber I2C (SDA/SCL Pins A4/A5) -> 3.3 Volt Stromversorgung!
// BH1750 Sensor angeschlossen ueber I2C (SDA/SCL Pins A4/A5) -> 3.3 Volt Stromversorgung!
BH1750FVI LightSensor;
// Variablen zur Windmessung. Werte welche innerhalb der InterruptServiceRoutinen (ISR) veraendert werden muessen volatile markiert werden.
// Zaehler der Windsensor Impulse waehrend dem regulaeren Messintervall
volatile uint32_t windCount = 0L;
// Zaehler der Windsensor Impulse waehrend dem Watchdog -> Boen
volatile uint32_t windGustCount = 0L;
// Marker welcher Interrupt ausgeloest hat. TRUE = Watchdog / FALSE = WindSensor
volatile boolean WDT_INT;
// Globale Variable fuer die Max Windboen waehrend der Messperiode
float maxWind;
// Anzahl durchgefuehrter Boen Messungen
unsigned int windCounter;
// NRF24 Variablen zur Funkuebertragung
// NRF24 Modul benoetigt den SPI Bus (Pins auf Mini Pro 11-MOSI / 12-MISO / 13-SCK) plus 2 Pins. Hier 7 und 8. IRQ wird nicht benoetigt.
RF24 radio(8, 9);
uint8_t PIPE_NAME[7] = {"KELLER"};
void setup()
{
Serial.begin(BAUD);
printf_begin();
// Power to NRF24
pinMode(NRF_POWER, OUTPUT);
pinMode(BMP_POWER, OUTPUT);
digitalWrite(NRF_POWER, true);
digitalWrite(BMP_POWER, false);
delay(2000);
Serial.println("Setup V1.1 - 2015-05-13");
Serial.println("Baro Sensor Initialisiert");
//LightSensor.begin();
Serial.println("Licht Sensor Initialisiert");
pinMode(WIND_CONN_PIN, INPUT_PULLUP);
Serial.println("Wind Sensor Input Pin Initialisiert");
radio.begin();
radio.openWritingPipe(PIPE_NAME);
radio.stopListening();
radio.enableDynamicPayloads();
radio.setDataRate(RF24_250KBPS);
radio.printDetails();
Serial.println("Funkuebertragung Initialisiert");
windCount = 0;
windGustCount = 0;
windCounter = 1;
WDT_TIMEOUT = 4000L;
setup_watchdog();
Serial.println("Watchdog Initialisiert");
adcsra = ADCSRA; // ACD ausschalten
ADCSRA = 0;
radio.powerDown();
delay(1000);
}
// Watchdog Vorbereiten und zu verwendenden Timeout einstellen.
void setup_watchdog(void) {
wdt_reset();
MCUSR &= ~bit(WDRF);
WDTCSR |= bit(WDCE) | bit(WDE);
WDTCSR = bit (WDIE) | bit(WDP3) ; /* Bit 3 = 4.0 Sekunden */
}
/* Interrupt Service Routinen. Hier wird der Schlafmodus des Arduino unterbrochen wenn ein solcher ausgeloest wird */
// Interrupt Service Routine fuer den Watchdog.
ISR(WDT_vect)
{
detachInterrupt(WIND_INT);
// Nun soll ein Wind Interrupt nicht mehr registriert werden, da der Erfassungstimer sowieso abgelaufen ist
WDT_INT = true;
// Do Nothing
}
// Interrupt Service Routine fuer den Windmesser
void windUp() {
// Zuerst den Interrupt aufhalten, damit Schalterprellen nicht zu mehrfachen Aufrufen fuehrt.
detachInterrupt(WIND_INT);
windCount++;
windGustCount++;
WDT_INT = false;
}
/* Ende der ISR Methoden */
// Windgeschwindigkeit aus der Anzahl Impulse und der vergangenen Zeit berechnen
float calcWind(uint32_t count, uint32_t time) {
// Bei sehr tiefen Geschwindigkeiten ist die Formel ungenau, deshalb muss im Schnitt mindestens
// 1u/sec erfasst worden sein. Die Erfassunglimite liegt somit bei 3km/h
if (Serial) {
Serial.print("Messzeit = ");
Serial.print(time);
Serial.print("sec / Impulse = ");
Serial.print(count);
}
if (count <= time / 2) {
if (Serial)
Serial.println(". Zu wenige Impulse, gebe 0 kmh zurueck");
return 0.0;
}
float windSpeedAlt = (count / time + 2) / ( 3.0) * 3.6;
return windSpeedAlt;
}
// Stromspar Modus verwenden
void enterSleep(void)
{
// Serial Comm flushen, da es sonst zu Stoerungen auf der Seriellen Schnittstelle kommt
Serial.flush();
Serial.end();
// Keine Interrupts mehr zulassen bis kurz vor dem Sleep
noInterrupts();
if (WDT_INT) // Wenn es ein Watchdog Timeout war, dann RESET auf dem Watchdog.
setup_watchdog(); // Reset Watchdog.
// Interrupt Flag fuer Interrupt 0 loeschen und Wind Interrupt wieder anhaengen
EIFR = bit(INTF0);
attachInterrupt(WIND_INT, windUp, FALLING);
// Sleep Modus einstellen. Max Stromersparnis bei POWER DOWN
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
// Option hier noch Brown-Out Check ausschalten
// Interrupts wieder zulassen. !! Nachfolgender Befehl ist noch vor Interrupts geschtzt und muss nun den yP schlafen legen !!
interrupts();
sleep_mode();
// Nach dem Aufwachen geht es hier weiter. Als erstes Serielle Kommunikation wieder ermoeglichen.
Serial.begin(57600);
sleep_disable();
}
// Aktueller Barometer Druck normalisiert auf Meeresniveau holen
float getSeaLevelPress() {
SFE_BMP180 pressure;
// BMP Sensor initialisieren
pressure.begin();
char status;
double T, P;
// Als erstes muss die Temperatur bestimmt und gelesen werden
status = pressure.startTemperature();
if (status != 0)
{
delay(status);
status = pressure.getTemperature(T);
if (status != 0)
{
delay(status);
// Nun kann der aktuelle Druck ausgelesen werden mit der hoechsten Genauigkeit
status = pressure.startPressure(3);
if (status != 0)
{
delay(status);
status = pressure.getPressure(P, T);
if (status != 0)
{
// Strom zum Drucksensor unterbrechen, da der eingebaute Spannungsregler ziemlich viel Strom brauch (min 8mA).
digitalWrite(BMP_POWER, false);
// Nun wird der gemessene Druck noch auf Meereshoehe "normalisiert"
return pressure.sealevel(P, ALTITUDE);
}
}
}
}
if (Serial)
Serial.println("Fehler bei der Bestimmung des aktuellen Luftdrucks");
}
// Uebertragen der Daten
void transmit(float maxWind, float avgWind, float pressure, uint16_t light, float battery) {
if (Serial) {
Serial.print("Baro=");
Serial.print(pressure, 2);
Serial.print("hPa / Max Wind=");
Serial.print(maxWind, 2);
Serial.print("kmh / Avg Wind=");
Serial.print(avgWind, 2);
Serial.print("kmh / Light=");
Serial.print(light);
Serial.print("lux / Battery=");
Serial.print(battery);
Serial.println("Volt");
Serial.print("Start NRF Transmission");
}
radio.powerUp();
radio.stopListening();
radio.openWritingPipe(PIPE_NAME);
uint16_t data[] = {maxWind * 10, avgWind * 10, pressure * 10, battery * 10, light};
radio.write(&data, sizeof(data));
//radio.txStandBy();
radio.powerDown();
}
// Auslesen des Lichtsensors
uint16_t getLight() {
// LightSensor.SetMode(OneTime_H_resolution_Mode);
return 0;//LightSensor.GetLightIntensity();
}
void collectAndTrasmit() {
powerUpAux();
// Alle Messwerte bestimmen
float pressure = getSeaLevelPress();
float avgWind = calcWind(windCount, windCounter * (WDT_TIMEOUT / 1000));
uint16_t light = getLight();
float battery = getBatteryVoltage();
// Messwerte Uebertragen
transmit(maxWind, avgWind, pressure, light, battery);
txNumber++;
// Alle Messvariablen zuruecksetzen
windCounter = 0L;
windGustCount = 0L;
windCount = 1;
maxWind = 0L;
powerDownAux();
}
// Strom zu den Elementen leiten welche beim Transmit ausgelesen werden resp Baugruppen des Arduino mit Strom versorgen (ADC)
void powerUpAux() {
digitalWrite(BMP_POWER, true);
ADCSRA = adcsra; // ACD Wiedereinschalten
// Delay damit alle Elemente einen stabilen Stand erreicht haben (z.B. BMP180 bentigt mindestens 10ms Power vor dem ersten auslesen).
delay(50);
}
// Zur normalen Windmessung nicht bentigte Baugruppen und Elemente ausschalten
void powerDownAux() {
digitalWrite(BMP_POWER, false);
adcsra = ADCSRA; // ACD Zustand Speichern
ADCSRA = 0; // und ADC Ausschalten da der Stromverbrauch des ADC sehr hoch ist
}
// Verbleibende Batteriespannung auslesen
float getBatteryVoltage() {
int bat = analogRead(BATTERY_PIN); // Level Messen
return bat / 1024.0 * 3.3; // Spannung normalisieren auf AnalogRef Level. Hier 3.3 Volt (Arduino Pro Mini 3.3V)
}
// Main Loop
void loop()
{
if (WDT_INT) { // Falls dies ein Watchdog Timeout war
// Boen Wind waehrend der Messperiode bestimmen.
float tempMaxWind = calcWind(windGustCount, WDT_TIMEOUT / 1000);
// Falls neue Boenspitze in dieser Messperiode den Max Wert neu setzen
if (tempMaxWind > maxWind)
maxWind = tempMaxWind;
// Boenzaehler reset
windGustCount = 0;
// Wenn es eine Windboe gab welche ueber der Alarm Schwelle lag und es schon laenger keine solche Meldung mehr gab
if (maxWind > MAX_WIND_GUST && nextWindGustTx < txNumber) {
// Bei der Alarm Meldung sind alle Werte ausser dem Boenwert = 0
transmit(maxWind, 0, 0, 0, 0);
// Die naechste Boen Alarmmeldung darf erst nach WIND_GUST_HOLDOFF regulaeren Uebermittlungen stattfinden
nextWindGustTx = txNumber + WIND_GUST_HOLDOFF + 1;
}
// Falls der regulaere Messintervall abgelaufen ist
if (WIND_INTERVALL / (WDT_TIMEOUT / 1000) <= windCounter) {
// Alle Werte auslesen und die Daten uebertragen
collectAndTrasmit();
}
// Regulaerer Messintervall noch nicht abgelaufen also nur Durchlaufzaehler erhoehen.
else {
windCounter++;
}
} else {
// Bei einem Windmesser Interrupt noch etwas warten, damit Schalterprellen nicht gleich wieder einen IRQ ausloesst.
// Dies passt fuer meinen ELTAKO Windmesser WS, andere Reedkontakt basierte Windmesser bentigen ggf andere Werte
delayMicroseconds(200);
}
// Verarbeitung Fertig, CPU wieder schlafen legen.
enterSleep();
}
Code: Alles auswählen
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
RF24 radio(7,6);
const int num_reps = 100;
uint8_t PIPE_NAME[7] = {"KELLER"};
void setup()
{
Serial.begin(57600);
delay(3000);
printf_begin();
radio.begin();
radio.openReadingPipe(1, PIPE_NAME);
radio.startListening();
radio.enableDynamicPayloads();
radio.setDataRate(RF24_250KBPS);
radio.printDetails();
delay(3000);
}
void loop()
{
if (radio.available())
readRadio();
delay(1000);
}
void readRadio() {
uint16_t data[5];
radio.read(&data, 10);
float maxWind = float(data[0]) / 10;
float avgWind = float(data[1]) / 10;
float pressure = float(data[2]) / 10;
float battery = float(data[3]) / 10;
uint16_t light = data[4];
Serial.print("Maximaler Wind : ");
Serial.print(maxWind);
Serial.print(" km/h / ");
Serial.print("Durchschnittlicher Wind : ");
Serial.print(avgWind);
Serial.print(" km/h / ");
Serial.print("Barometer : ");
Serial.print(pressure);
Serial.print(" hPa / ");
Serial.print("Batteriespannung : ");
Serial.print(battery);
Serial.println(" V");
}
Verhalten des ELTAKO Windsensors WS (welcher über einen Reedkontakt verfügt, und daher Schalterprallen zeigt). Ich habe das Verhalten des Windmessers mal noch mit dem KO angeschaut, um zu überprüfen ob die Delay Zeit für das Schalterprellen einigermassen korrekt ist. Wie sich gezeigt hat ist die Zeit deutlich zu gross. Ich habe bis max 100us Nachprellen gemessen. Ich reduziere daher im Sketch oben den Delay von vorher 20ms auf 200us (Sicherheitsmarge).
Laut Datenblatt von Bosch zum BMP180 sollte vor der ersten Messung für 10ms Spannung am IC anliegen. Ich habe 250ms Delay eingebaut und reduziere diesen nun auf 50ms. Zudem habe ich nun eigene Methoden für das Ein- und Ausschalten der Spannungsversorgung der nicht verwendeten Bauteile (aktuell BMP180 sowie ADC des Arduino, bei Bedarf können diese um weitere Elemente erweitert werden).