Posts mit dem Label I2C werden angezeigt. Alle Posts anzeigen
Posts mit dem Label I2C werden angezeigt. Alle Posts anzeigen

Samstag, 25. November 2017

Konfigurierbarkeit des Feuchtigkeitssensor HTU21D


Wenn man im Internet sucht, finden sich viele Beispiele zur Programmierung einiger Sensoren, die ich ebenfalls selbst verwende. Die meisten Codeschnipsel funktionieren auch auf dem Wemos. Dennoch muss noch etwas herum probiert werden, um bestimmte Schwierigkeiten anzugehen, damit auch das erwartete Ergebnis kommt.

HTU21D, HTU21, SHT21
Wird ein Sensor gelesen bekommt man nach diesem Vorgang einen Rohwert, der dann in einen für uns bekannten und lesbaren Wert umgerechnet wird. Wir können dies ohne weiteres nach Datenblatt tun oder einen fertigen Beispiel Code verwenden.
Ich wollte meinen Programmcode mit anderen Beispielen Vergleichen, auf Grund einer Konfigurierbarkeit des Sensors. Leider war dazu auf Anhieb nichts zu finden, womit ein Grund bestand sich damit selbst auseinander zu setzten.
In diesem Fall ist es der Feuchtigkeit Sensor HTU21D und ist auch unter HTU21 oder SHT21 zu finden (Nicht ganz sicher, ob alle dieselben sind). Abgesehen Technischer Unterschiede, können alle drei mit dem gleichen Programmcode angesteuert werden. Vor ein paar Jahren hatte ich bereits ein Beispiel Code mit C# und .NET Micro Framework geschrieben, in dem ich damals keine Implementierung der Einstellmöglichkeiten vornahm.

Für den Sensor sind drei Register verfügbar, mit denen die Einstellungen abgefragt, festgelegt oder zurückgesetzt werden können.
  • Write user register 0xE6
  • Read user register 0xE7
  • Soft Reset 0xFE
Grundeinstellung
Wird der Sensor nur gestartet und gelesen, dann bestehen die Grundeinstellungen. Diese sollten im Normalfall reichen. Bei Speziellen Anwendungen könnten dieses Lesevorgänge zu lange dauern. Mit dem Kompromiss der Genauigkeit, kann der Lesevorgang für die Feuchtigkeit von 18ms auf 3ms gesengt werden. Anstatt der 12bit, werden jedoch nur 8bit verwendet.

Genauigkeit
Zeit
12bit
18ms
11bit
9ms
10bit
5ms
8bit
3ms

Abruf und Zuweisen der Einstellung
Im Grunde ist dies so simpel wie der Abruf der Sensor Daten. Für die Einstellwerte ist jedoch etwas umdenken erforderlich, da diese in einem Byte verpackt sind. Ein Byte besteht aus 8Bit und verfügt damit so acht Schalter für bestimmte Einstellungen. Zum Nachvollziehen habe ich aus dem Datenblatt die Tabelle mit den zugehörigen Bit Schaltern abgebildet.


Einstellen und Auswerten
Mit dem 'Read user register' 0xE7 kann die aktuelle Einstellung abgerufen werden. Das kann hilfreich sein, um die geschrieben Einstellungen gegen zu prüfen.

 void Htu21ReadUserRegister() {  
  Wire.beginTransmission(HTU21D_ADDRESS);  
  Wire.write(command);              
  Wire.endTransmission();  
  Wire.requestFrom(HTU21D_ADDRESS, 1);   
  while(Wire.available() < 1) { }  
  byte regCon = Wire.read();  
  Serial.print("\tRegister Result: ");   
  Serial.println(regCon, BIN);         
 }  

Das folgende Beispiel beschreibt die Einstellung 'RH 11Bit / Temp 11Bit', 'On-Chip Header' aus und 'Disable OTP Reload' aus.

 void Htu21WriteRegisterSetting() {  
  byte command = 0xC0;  // RH 11bit / 11Bit Temp  
  command |= 0x01;     // disable otp reload  
  Wire.beginTransmission(HTU21D_ADDRESS);  
  Wire.write(0xE6);       // command for write register command  
  Wire.write(command);  // write the finish command  
  Wire.endTransmission();  
 }  

War das Schreiben der Einstellung erfolgreich, sollte mit der Methode 'Htu21ReadUserRegister()' das Binäre Ergebnis '1100 0001' ausgegeben werden.

Beispiel Projekt Wetterstation
Auf dem Github Repository stelle ich diesmal das kleine Projekt zur Verfügung, in dem ich bereits den HTU21 und den BMP180 Sensor verwende. Dort sind einige Methoden etwas weiter ausgeführt und zum Teil Kommentiert. Soweit ich immer Zeit habe, erweitere ich das Projekt mit Möglichst überschaubaren Funktionen.

Sonntag, 14. September 2014

Feuchtigkeitssensor HTU21D mit dem Netduino (Teil 2)

image_thumb1

Damit die gemessenen Werte auch brauchbare Messeinheiten haben, müssen die Rohdaten zunächst umgerechnet werden. Daher kommen zwei weitere Methoden hinzu, die dies erledigen. Der Inhalt zum Lesen des einzelnen Wertes erhält eine eigene Methode, weil dieser Inhalt zweimal verwendet wird und sich nur im einzelnen zusendenden Byte unterscheidet.

// Luftfeuchtigkeit Messen und Rohwert zurückgeben
public int Read()
{
    // Inhalt in eigene Methode umgezogen
}

// Liest mit der Übergabe des Befehls den Sensor Teil aus.
private int ReadSensorPart(byte command)
{
    // Befehl senden.
    if (this.Write(new byte[] { command }) == 0)
    {
        throw new SystemException("Fehler beim Senden!");
    }

    // Warten bis der Sensor mit dem Lesevorgang fertig ist.
    Thread.Sleep(50);

    byte[] data = new byte[3];

    // Messergebnis abrufen
    if (this.Read(data) == data.Length)
    {
        // Es werden für dieses Beispiel zwei Bytes verwendet
        int raw = (data[0] << 8) | data[1];
        raw &= 0xFFFC;
        return raw;
    }

    Debug.Print("Sensor konnte nicht gelesen werden!");
    return 0;
}

Damit wir die Daten zusammengefasst aus der Methode “Read” erhalten, wird das Ergebnis in einem Daten Objekt zusammengefasst. Daszu wird eine neue Klasse angelegt.

// Das Sensor Daten Objekt stellt die gemessenen Werte zur Verfügung.
public class SensorData
{
    // Ruft die gemessene Luftfeuchtigkeit ab.
    public float Humidity { get; private set; }

    // Ruft die gemessene Temperatur ab.
    public float Temperature { get; private set; }

   // Initialsiert das Daten Objekt mit den Rohwert Messungen
    // und rechnet diese auf bekannte Maßeinheiten.
    public SensorData(int rawHumidity, int rawTemperature)
    {
       
// Umrechnung aus den Rohdaten
    }

    // Überschriebene Methode für die Ausgabe der Berechneten Inhalte.
    public override string ToString()
    {
        return "Luftfeuchtigkeit: " + this.Humidity.ToString() +

                "% - Temperatur: " +     this.Temperature.ToString() + "°C";
    }
}

Im Datenblatt sind die Formel zu entnehmen für die Feuchtigkeit und Temperatur, die mit relativ wenig aufwand in einem Programmcode abgebildet werden können.

image
Feuchtigkeitswert = –6 + (125 * (Rohwert / 65536))

Formel für die Temperatur aus dem Rohwert berechnen.

image

Temperaturwert = –46,85 + (175,72 * (Rohwert / 65536))

Grundsätzlich benötigt die Umrechnung drei Werte. Das Summieren und Multiplizieren ist bei beiden Umrechnung die selbe. Diese Aufstellung lässt sich in eine Methode zusammenfassen und in die Klasse SensorData hinzufügen.

// Rechnet aus dem Rohwert das Zielergebnis um.
public float GetCalculateValueByRange(int raw, float start, float range)
{
    // Gemessenen Rohwert durch den Maximalen Ausgabewert.
   float f = raw / 65536f;

    // Messumfang multiplizieren und an den Startwert verschieben.
   return start + (range * f);
}

Nun kann die eigentliche Umrechnung im Konstruktor erfolgen.

...
// Initialsiert das Daten Objekt mit den Rohwert Messungen
// und rechnet diese auf bekannte Maßeinheiten.

public SensorData(int rawHumidity, int rawTemperature)
{
    // Berechnen der Luftfeuchtigkeit.
   this.Humidity = GetCalculateValueByRange(rawHumidity, -6f, 125f);

    // Berechnen der Temperatur.
    this.Temperature = GetCalculateValueByRange(rawTemperature, -46.85f, 175.72f);
}
...

Damit wäre das Daten Objekt mit der Umrechnung fertig. Jetzt muss in der Hauptklasse HTU21D der Lesevorgang vervollständigt werden.

...
// Luftfeuchtigkeit Messen und Rohwert zurückgeben

public SensorData Read()
{
   // Rohwert abholen für Luftfeuchtigkeit
    int rawHumidity = ReadSensorPart(0xf5);

    // Rohwert abholen für Temperatur
    int rawTemperature = ReadSensorPart(0xf3);

   // Neues Datenobjekt erzeugen und die Rohdaten übergeben.
    return new SensorData(rawHumidity, rawTemperature);
}
...

In der Program.cs beleibt noch eine kleine Anpassung und dann ist der Teil fertig.

public class Program
{
    public static void Main()
    {
        // Klasse Initialisieren und Verbindung herstellen.
        HTU21D sensor = new HTU21D();

        while (true)
        {
            // Sensoren lesen
            SensorData sd = sensor.Read();

           // Ergebnis ausgeben.
            Debug.Print(sd.ToString());
        }
    }
}

Vielleicht ist dem einen oder anderen aufgefallen, dass drei Bytes gelesen werden, aber nur zwei Bytes verwendet werden. Das Dritte beinhaltet den CRC Wert, mit dem sich prüfen lässt, ob das Ergebnis Valide ist. Darauf gehe ich dann im nächsten Post ein.

Sonntag, 17. August 2014

Feuchtigkeitssensor HTU21D mit dem Netduino (Teil 1)

image

Zuvor habe ich für meinem Weather Shield einen Feuchtigkeitssensor DHT11 verwendet. Wer diesen kennt, weiss sicherlich wie ungenau die Messwerte sind. Ich möchte den Sensor nicht schlecht reden, denn immerhin ist er sehr günstig. Möchte man allerdings für eine Anwendung mehr Genauigkeit und eine schnelle Abtastung der aktuellen Feuchtigkeit und Temperatur, bieten andere Sensoren deutlich bessere Ergebnisse.

Ein paar interessante Daten zum Sensor:

  • Schnittstelle I²C
  • Betriebsspannung: 3,3V (max. 3,6V)
  • Stromaufnahme: ca. 10mA
  • Temperaturmessung ca. 50ms bei 14Bit (7ms bei 11Bit) (-40°C bis 125°C)
  • Feuchtigkeitmessung ca. 16ms bei 12bit (3ms bei 8Bit)
  • Kostet ab 6,50€ bei Ebay über einem chinesischen Händler

Weitere Daten findet ihr im Datenblatt.

Ansprechen lässt sich der Sensor über den I²C Bus und kann mit dem .NET Micro Framework mit geringen Aufwand programmiert werden. Das Einstiegsverhalten lässt sich mit dem Luftdruck Sensors BMP085 vergleichen, über den ich bereits geschrieben habe.

Erstellen wir eine neue Klasse mit dem Namen des Sensors, um zunächst die Verbindung herzustellen und die Rohdaten zu erhalten.

public class HTU21D : I2CDevice
{
    // Konstruktor mit vererbten Konstruktor aus der I2CDevice Klasse
    // 0x40 = Adresse des Sensors
    //  100 = Takt in kHz

    public HTU21D()
        : base(new Configuration(0x40, 100))
    {
    }

    // Luftfeuchtigkeit Messen und Rohwert zurückgeben
    public int Read()
    {
        // Befehl senden.
        if (this.Write(new byte[] { 0xf5 }) == 0)
        {
            throw new SystemException("Fehler beim Senden!");
        }

        // Warten bis der Sensor mit dem Lesevorgang fertig ist.
        Thread.Sleep(50);

        byte[] data = new byte[3];

       // Messergebnis abrufen
        if (this.Read(data) == data.Length)
        {
           // Es werden für dieses Beispiel zwei Bytes verwendet
            int raw = (data[0] << 8) | data[1];
            raw &= 0xFFFC;
            return raw;
        }

        Debug.Print("Sensor konnte nicht gelesen werden!");
        return 0;
    }

    // Sendet den Inhalt des Byte Arrays
    private int Write(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateWriteTransaction(buffer)
        };

        return Execute(trans, 1000);
    }

    // Liest den zu empfangene Byte Array
    private int Read(byte[] buffer)
    {
       I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateReadTransaction(buffer)
        };

        return Execute(trans, 1000);
    }
}

Die Rohdaten können nun über die Program.cs Klasse aufgerufen und über die Ausgabe in Visual Studio ausgegeben werden.

public class Program
{
    public static void Main()
    {
        HTU21D sensor = new HTU21D();

        while (true)
        {
            Debug.Print("Humidity RAW Value: " + sensor.Read().ToString());
            Thread.Sleep(1000);
        }
    }

}

Im nächsten Schritt wird das Modul mit dem Feuchtigkeitsensor mit dem Netduino verbunden. Je nach Ausführung des Sensor, werden eventuell zwei Widerstände mit 2,2kOhm benötigt. In der Variante die ich erhalten habe, sind diese aufgelötet, aber mit der 3,3V Zuleitung nicht verbunden. Das Modul habe ich zunächst so belassen und habe für den Testlauf zwei Pull Up Widerstände hinzu gesteckt.

HTU21D_Pullups_bearbeitet-1

Ansonsten hält sich die Schaltung mit den Netduino und dem Feuchtigkeitssensor sehr Übersichtlich.

image

Wie bereits aus den Technischen Grunddaten hervorgeht, gibt es auch einen Temperatursensor zum Messen. Dafür muss nur in der Methode “Read()”, das zu sendende Byte von 0xf5 auf 0xf3 umgeschrieben werden. Damit erhält man den Rohwert der gemessenen Temperatur.

Dienstag, 15. April 2014

Luftdrucksensor BMP085 mit dem Netduino - Höhenangabe (Teil 4)

image_thumb8_thumb_thumb

Mit dem Luftdruck lässt sich in etwa auch die Höhe ermitteln. Allerdings ist dieser Wert bedingt verwendbar. Zum einen muss der Sensor beim einschalten mit der aktuellen Höhe kalibriert werden und zum anderen gibt es verschiedene Formeln für die Berechnung, die spezielle Anforderungen erfüllen.

Wie immer kommt es darauf an, was man möchte. Im Dokument selbst, wird nur der Luftdruck verwendet. Die Formel hierfür ist nicht sonderlich komplex und lässt sich daher in wenigen Codezeilen umsetzen.

altitude = 44330 * (1 – (p/p0) 1/5.255)
Nach dem Dokument auf Seite 14, ist diese Formel zu verwenden.

Auffällig dürften die festen Zahlenwerten sein, die auf den ersten Blick keinen Bezug darstellen. Die Beschreibung für diese Sache ist leider ohne Begründung, was mir selbst etwas Kopfzerbrechen bildet und mich auf den Weg brachte im weiten Internet nach einer plausiblen Antwort zu suchen. Leider mit geringen Erfolg und steht daher noch auf der TODO Liste.

Wer bereits für den Arduino nach einem Beispiel Code gesucht hat, so findet sich oft die folgende Methode mit etwas anderen Werten, die jedoch alle das selbe Ergebnis erzielen.

float calcAltitude(float pressure){
  float A = pressure/101325;
  float B = 1/5.25588;
  float C = pow(A,B);
  C = 1 - C;
  C = C /0.0000225577;
  return c;
}

Bei der Formel aus dem Dokument und das was sich aus dem Arduino Umfeld findet, fällt auf, dass die Temperatur nicht berücksichtigt wird. Das führt bedingt zu Ungenauigkeiten. Nach etwas längerem suchen fand ich die Seite Explorermagazin, auf der verschiedene Formeln vorgestellt werden. Am Ende habe ich mich für die Variante 2b entschieden.

p(h) = p(h0) * (1 - 0.00651 K/m *(h-h0)/T0)**5.255

h: Höhe (in Meter) über NN.
p(h): Luftdruck (in hPa) in Höhe h.
h0: Bezugshöhe (in Meter).
p(h0): Luftdruck (in hPa) auf Bezugshöhe
T0: Temperatur auf Bezugshöhe in Grad Kelvin (K)

Die Höhenberechnung wird in Abhängigkeit der Temperatur berücksichtigt, die hier Minus 0,651 Kelvin pro 100 Meter (-0,65K/100m) sind. Der Wert 5,255 wurde leider hier ebenfalls nicht beschrieben. Daher belasse ich diesen Punkt und stelle die Formel für die Zielumsetzung um.

image

Kommen wir zum Programmcode in C#. Leider oder auch gut so, ist dass die Formel nicht in eine Zeile programmiert werden kann und sich somit in einzelnen Schritten abgebildet werden muss.

// Zweite Variante für die Höhen Berechnung
// Luftdruck bei Meeresspiegel

private double _ph0 = 1013.25;
// Bezugshöhe
private double _h0 = 0;

Anlegen der Member Variablen mit den Werten der Höhe 0 Meter und 1013,25hPa.

Den Quellcode den ich zur Verfügung stelle, hat noch zusätzlich die herkömliche Variante der Höhenberechnung, die nur mit dem Luftdruck berechnet wird. Für den Post verbleibe ich mit der Folgenden Methode, die mit Hilfe des Luftdrucks und der Temperatur ausgeführt wird, die sich aus der Formelumstellung ergab.

// Die Höhenberechnung aus Luftdruck und Temperatur.
private double GetAltitude(double pressure, double temperature)
{

    // Luftdruck in hPa umrechnen
    pressure = pressure / 100.0;

    // aktueller Luftdruck durch Bezugshöhe und dessen Luftdruck
    double p1 = pressure / _ph0;
    // Wurzel ziehen
    double p2 = System.Math.Pow(p1, 1.0 / 5.255);
    // minus 1, Wert Invertieren
    double p3 = (p2 - 1);
    // mal Temperatur ind Kelvin
    double p4 = p3 * (temperature + 273.15);
    // durch Kelvin pro Meter
    double p5 = p4 / 0.00651;

    // Relative Höhe des Vorangehenden Luftdrucks hinzu rechnen
   return p5 + _h0;
}

Eines sollte gleich klar sein. Das richtige Ergebnis ist nur draußen zu erhalten. Luftdruck und Außentemperatur wirken zusammen. Zuletzt noch ein Hinweis zur gemessenen Luftdrucks. Der Wert ist in Pascal und muss für die Berechnung in Hektopascal umgerechnet werden, das am Anfang der Mehtode umgerechnet wird.

Effektiv erzielt die Berechnung der Höhe ähnliche Ergebnisse wie die Formel ohne Temperatur Bezug. Für den Einsatz für Quadrocopter lohnt sich der Einsatz über fünf Meter Höhe. Für genaue und präzise Erfassung der Höhe, bleibt der Einsatz von Ultraschall- oder Optischer Distanzsensoren.

Nun das war meine kleine Serie über den Luftdruck Sensor und dessen Umsetzung. Wenn Fragen sind, können diese über die Kommentar Funktion gestellt werden.

Beispiel Programm zum Herunterladen (C# und .NET Micro Framework)

Wikipedia: Luftdruck

Barometrische Höhenformel

Dienstag, 31. Dezember 2013

Luftdrucksensor BMP085 mit dem Netduino - Kalibrieren (Teil 3)

image_thumb[8]_thumb

Um überhaupt mit den gelesenen Daten etwas anfangen zu können, müssen diese umgerechnet werden. Bevor dies geht, müssen am Anfang Kalibrierungsdaten ermittelt werden. Im Handbuch ist in kurzer Form beschrieben, in welchen Schritten gelesen und umgerechnet wird. Fummelig ist die Umrechnung für den Luftdruck. Es sind eine Menge Variablen, welche die Kalibrierungswerte aufnehmen und andere, um die Ergebnisse zwischen zu speichern. Zu der Klasse “BMP085” aus den zwei vorigen Posts kommen nun weitere Methoden hinzu. Damit der Post  nicht zu sehr in die Länge geht, habe ich nur die neuen und geänderten Inhalte zu der Klasse abgebildet.

Als erstes müssen die Kalibrierungskoeffizienten ermittelt werden, die in den Member abgelegt werden. Die Namen für die Variablen wurden direkt aus dem Datenblatt übernommen, was zugegeben etwas unschön aussieht.

// Kalibrierungswerte
private short _AC1;
private short _AC2;
private short _AC3;
private uint _AC4;
private uint _AC5;
private uint _AC6;
private short _B1;
private short _B2;
private short _MB;
private short _MC;
private short _MD;

private long _B5;
private short _OSS = 0;

Nicht alle Werte werden in ein “short” umgewandelt, denn drei davon kommen als “uint”. Daher muss am Ende der Methode für das Byte Shifting entschieden werden, als welcher Typ zurück gegeben wird.

// Fragt den Sensor den einzelnen Kalibrierungswert ab
private object GetCalibrateValue(byte[] msb_lsb, bool isUnsigned)
{
    byte[] ba = new byte[] { msb_lsb[1], 0x00 };
    if (Write(new byte[]{msb_lsb[0]}) == 0)
    {
        Debug.Print("Abfrage konnte nicht erfolgreich gesendet werden.");
    }
    else if (Read(ba) == 0)
    {
        Debug.Print("Abfrage konnte nicht erfolgreich empfangen werden.");
    }
   else
    {
        if (isUnsigned
)
        {
            return
(uint)(ba[0] << 8 | ba[1]);
        }
        else
        {
            return 
(short)(ba[0] << 8 | ba[1]);
        }
    }

    return 0;
}

Anschließend lassen sich mit einer weiteren Methode die Kalibrierungswerte in einem Block abrufen. Die Rückgabe kommt als Objekt und muss per Unboxing in den entsprechenden Typ angegeben werden, bevor es an die Member Variable zugewiesen wird.

// Kalibrierungswerte einlesen.
public void Calibration()
{
    _AC1 = (short)GetCalibrateValue(new byte[] { 0xAA, 0xAB }, false);
    _AC2 = (short)GetCalibrateValue(new byte[] { 0xAC, 0xAD }, false);
    _AC3 = (short)GetCalibrateValue(new byte[] { 0xAE, 0xAF }, false);
    _AC4 = (uint)GetCalibrateValue(new byte[] { 0xB0, 0xB1 }, true);
    _AC5 = (uint)GetCalibrateValue(new byte[] { 0xB2, 0xB3 }, true);
    _AC6 = (uint)GetCalibrateValue(new byte[] { 0xB4, 0xB5 }, true);
    _B1 = (short)GetCalibrateValue(new byte[] { 0xB6, 0xB7 }, false);
    _B2 = (short)GetCalibrateValue(new byte[] { 0xB8, 0xB9 }, false);
    _MB = (short)GetCalibrateValue(new byte[] { 0xBA, 0xBB }, false);
    _MC = (short)GetCalibrateValue(new byte[] { 0xBC, 0xBD }, false);
    _MD = (short)GetCalibrateValue(new byte[] { 0xBE, 0xBF }, false);
}

Die Temperatur umzurechnen ist relativ trivial, auch wenn die Variablen Namen aus dem Handbuch dies etwas kryptisch wirken lassen. Davon sollten Sie sich nicht abschrecken lassen. Ich hätte da lieber andere Namen, aber halten wir uns an das Handbuch.

// Temperatur Berechnen
private double GetTemperatur(ulong ut)
{
    // x1 = (ut - ac6) * ac5 / (2 hoch 15)
    long x1 = (((long)ut - _AC6) * _AC5) >> 15;
    // x2 = mc * (2 hoch 11) / (x1 + md)
    long x2 = (((long)_MC) << 11) / (x1 + _MD);

    _B5 = x1 + x2;

    // Temperatur wird in 0,1°C Schritten gelesen
    return (double)((_B5 + 8) >> 4) / 10;
}

Den größten Teil bildet die Umrechnung für den Luftdruck, was auf den ersten Blick Augenkrebs verursachen kann. Leider ist im Datenblatt keine genauere Beschreibung darüber vorhanden, wie sich das Ganze zusammensetzt.

// Luftdruck Berechnen
public long GetPressure(ulong up)
{
    long b6 = _B5 - 4000;

    // x1 = (b2 * (b6 * b6) / (2 hoch 12)) / (2 hoch 11)
    long x1 = (_B2 * (b6 * b6) >> 12) >> 11;
    // x2 = ac2 * b6 / (2 hoch 11)
    long x2 = (_AC2 * b6) >> 11;
    long x3 = x1 + x2;
    // ((ac1 * 4 + x3) << oss + 2) / 4
    long b3 = (((((long)_AC1) * 4 + x3) << _OSS) + 2) >> 2;

    // x1 = ac3 * b6 / (2 hoch 13)
    x1 = (_AC3 * b6) >> 13;
    // x2 = (b1 * (b6 * b6 / (2 hoch 12)) / (2 hoch 16)
    x2 = (_B1 * ((b6 * b6) >> 12)) >> 16;
   // x3 = ((x1 + x2) + 2) / (2 hoch 2)
    x3 = ((x1 + x2) + 2) >> 2;
    // b4 = ac4 * (x3 + 32768) / (2 hoch 15)
    ulong b4 = ((ulong)_AC4 * (ulong)(x3 + 32768)) >> 15;

    // b7 = (up - b3) * (50000 >> oss)
    ulong b7 = ((ulong)(up - (ulong)b3) * (ulong)(50000 >> _OSS));

    long p = 0;
    if (b7 < 0x80000000)
    {
        // p = (b7 * 2) / b4
        p = (long)((b7 << 1) / b4);
    }
    else
    {
        // (b7 / b4) * 2
        p = (long)((b7 / b4) << 1);
    }

   // x1 = (p / (2 hoch 8)) * (p / (2 hoch 8))
    x1 = (p >> 8) * (p >> 8);
    // x1 = (x1 * 3038) / (2 hoch 16)
    x1 = (x1 * 3038) >> 16;
    // x2 = (-7357 * p) / (2 hoch 16)
    x2 = (-7357 * p) >> 16;
    // p = p + (x1 + x2 + 3791) / (2 hoch 4)
    p += (x1 + x2 + 3791) >> 4;

    return p;
}

Nun kann in der Klasse BMP085 die Methode “ReadSensorData” mit den zwei Methoden ergänzt werden. Dazu erhält das Daten Objekt SensorData zusätzlich zwei weitere Properties für das Abspeichern von Temperatur und Luftdruck.

public class SensorData
{
    public ulong TemperaturRawValue = 0;
    public ulong PressureRawValue = 0;

    public double Temperatur = 0;
    public double Pressure = 0;

    public override string ToString()
    {
       return "Temperatur Raw Value: " + TemperaturRawValue.ToString() +
            " - Pressure Raw Value: " + PressureRawValue.ToString() +
            "| Real Values - Temperature: " + Temperatur.ToString() +
            " - Pressure: " + Pressure.ToString();
    }
}

 

// Sensor Daten lesen
public void ReadSensorData(ref SensorData data)
{
    // Roh Temperatur lesen
    data.TemperaturRawValue = GetTemperaturRawValue();

    // Roh Luftdruck lesen
    data.PressureRawValue = GetPressureRawValue();

    // Temperatur berechnen
    data.Temperatur = GetTemperatur(data.TemperaturRawValue);

    // Luftdruck Berechnen
    data.Pressure = GetPressure(data.PressureRawValue);
}

Nun ist die Klasse schon fast fertig. Aber es sind noch weitere Möglichkeiten offen, die ich in den nachfolgenden Blog Posts beschreiben werde.

Beispiel Programm zum herunterladen

Datenblatt BMP085

Sonntag, 29. Dezember 2013

Luftdrucksensor BMP085 mit dem Netduino - Temperatur (Teil 2)

image_thumb[8]

Wie bereits in meinem vorigen Post beschrieben, soll nun der Temperatur Wert ausgelesen werden. Der Teil für das Auslesen des Luftdrucks wird in eine Methode ausgelagert. Die Methode “ReadSensorData” gibt die zwei Werte in einem Daten Objekt zurück, das zunächst angelegt wird. Die “ToString” Methode im Daten Objekt wird überschrieben, um die Wiedergabe beider Werte simpel zu gestalten.

public class SensorData
{
    public int TemperaturRawValue = 0;
    public int PressureRawValue = 0;

    public override string ToString()
    {
        return "Temperatur Raw Value: " + TemperaturRawValue.ToString() +
            " - Pressure Value: " + PressureRawValue.ToString();
    }
}

Nach dem das Daten Objekt vorliegt, kann die Methode “ReadSensorData”  für die Rückgabe umgeschrieben werden. Die Methode nimmt nun das Daten Objekt als Referenz an. Für das Einlesen der Temperatur wird ein zweites Byte Array benötigt zum zwischenspeichern. Die Methode “GetTemperature” ähnelt der Ausführung von “GetPressure”. In einen späteren Post wird dort nochmal eingegangen, wie sich diese Sinnvoll zusammenlegen lassen.

public class BMP085 : I2CDevice
{
    // Bestimmt wie lange gewartet wird in Millisekunden
    private int _Timeout = 1000;

  // Standard Mode
    private int _Mode = 1;

    // Empfangspuffer für Luftdruck
    private byte[] _ReceiveP = new byte[3];

    // Empfangspuffer für Temperatur
    private byte[] _ReceiveT = new byte[2];

    // Konstruktor mit vererbten Konstruktor von der I2CDevice Klasse
    public BMP085()
        : base(new Configuration(0x77, 40))
    {
    }

   // Sensor lesen und im Referenz Daten Objekt die Eigenschaftswerte überschreiben.
    public void ReadSensorData(ref SensorData data)
    {
        // Temperatur lesen
        data.TemperaturRawValue = GetTemperatur();

        // Luftdruck lesen
        data.PressureRawValue = GetPressure();
    }

    // Temperatur Wert lesen
    private int GetTemperatur()
    {
        // Status Abfrage senden
        if (Write(new byte[] { 0xF4, 0x2E }) == 0)
        {
            Debug.Print("Status Abfrage für Temperatur konnte nicht durch geführt werden.");
            return 0;
        }

        // Kurz abwarten
        Thread.Sleep(5);

        if (Write(new byte[] { 0xF6 }) == 0)
        {
            Debug.Print("Ergebnis abfrage konnte nicht gesendet werden.");
            return 0;
        }

        if (Read(_ReceiveT) == 0)
        {
            Debug.Print("Es konnte kein Temperatur Wert gelesen werden.");
            return 0;
        }

        return (int)_ReceiveT[0] << 8 | _ReceiveT[1];
    }

    // Luftdruck Wert lesen
    private int GetPressure()
    {
        // Status Abfrage senden
        if (Write(new byte[] { 0xF4, (byte)(0x34 + (_Mode << 6)) }) == 0)
        {
            Debug.Print("Status Abfrage für Luftdruck konnte nicht ausgeführt werden.");
            return 0;
        }

        // Kurz abwarten
        Thread.Sleep(8);

       // Sensor Ergebnis abfragen
       if (Write(new byte[] { 0xF6 }) == 0)
        {
            Debug.Print("Ergebnis Abfrage konnte nicht abgefragt werden.");
            return 0;
        }

        // Ergebnis einlesen
        if (Read(_ReceivePressure) == 0)
        {
            Debug.Print("Es konnte das Ergebnis vom Sensor nicht gelesen werden.");
            return 0;
        }

        // Ergebnis per Byteshifting
       return ((_ReceiveP[0] << 16) | (_ReceiveP[1] << 8) | (_ReceiveP[2])) >> (8 - _Mode);
    }

    // Sendet den Inhalt des Byte Arrays
    private int Write(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateWriteTransaction(buffer)
        };

        return Execute(trans, _Timeout);
    }

    // Liest den zu empfangene Byte Array
   private int Read(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateReadTransaction(buffer)
        };

        return Execute(trans, _Timeout);
    }
}

Für die Ausgabe muss zunächst das Daten Objekt “SensorData” angelegt werden in der “Main” – Methode. In der Schleife wird das Daten Objekt als Referenz an die Methode “ReadSensorData” übergeben, dass nun die Eigenschaftswerte überschreibt. Und am Ende wird die Ausgabe wieder über “Debug.Print()” ausgegeben.

public class Program
{
    private static BMP085 _Airpressur;

    public static void Main()
    {
        _Airpressur = new BMP085();
        SensorData data = new SensorData();

        while (true)
        {
            _Airpressur.ReadSensorData(ref data);
            Debug.Print(data.ToString());

            // halbe Sekunde warten
            Thread.Sleep(500);
        }
    }
}

Die Schaltung bleibt weiterhin so bestehen, wie im ersten Teil. Jetzt dürfte die Ausgabe in Visual Studio wie im folgendem Bild aussehen.

image

Die beiden Werte sind natürlich noch Roh Werte und müssen zunächst umgerechnet werden. Daher wird im nächsten Blog Post das Kalibrieren und Umrechnen behandelt.

Beispiel Programm zum herunterladen.

Datenblatt BMP085

Freitag, 27. Dezember 2013

Luftdrucksensor BMP085 mit dem Netduino - Luftdruck (Teil 1)

image

Für das Erfassen von Wetterdaten eignet sich der Sensor BMP085. In erster Linie misst dieser den Luftdruck, kann jedoch auch die Temperatur messen. Dennoch bleiben wir zunächst bei einem kurzen Testprogramm für den Netduino.

public class BMP085 : I2CDevice
{
   // Bestimmt wie lange gewartet wird in Millisekunden
    private int _Timeout = 1000;

   // Standard Mode
    private int _Mode = 1;

    // Empfangspuffer
    private byte[] _Receive = new byte[3];

    // Konstruktor mit vererbten Konstruktor von der I2CDevice Klasse
    public BMP085() : base(new Configuration(0x77, 40))
    {
    }
    // Sensor lesen
    public int ReadSensorData()
    {
        // Status Abfrage senden
        if (Write(new byte[] { 0xF4, (byte)(0x34 + (_Mode << 6)) }) == 0)
        {
            Debug.Print("Status abfrage konnte nicht ausgeführt werden.");
            return 0;
        }

        // Kurz abwarten
        Thread.Sleep(8);

        // Sensor Ergebnis abfragen
        if (Write(new byte[] { 0xF6 }) == 0)
        {
            Debug.Print("Ergebnis konnte nicht abgefragt werden.");
            return 0;
        }

        // Ergebnis einlesen
        if (Read(_Receive) == 0)
        {
            Debug.Print("Es konnte das Ergebnis vom Sensor nicht gelesen werden.");
            return 0;
        }

        // Ergebnis per Byteshifting
       return ((_Receive[0] << 16) | (_Receive[1] << 8) | (_Receive[2])) >> (8 - _Mode);
    }

    // Sendet den Inhalt des Byte Arrays
   private int Write(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateWriteTransaction(buffer)
        };

        return Execute(trans, _Timeout);
    }

    // Liest den zu empfangene Byte Array
    private int Read(byte[] buffer)
    {
        I2CTransaction[] trans = new I2CTransaction[]
        {
            CreateReadTransaction(buffer)
        };

        return Execute(trans, _Timeout);
    }
}

Der Programmcode ermöglicht den Messwert im Sensor auszulesen. Nun muss im Program.cs die Klasse initialisiert werden und in der Schleife der Sensor immer wieder gelesen werden.

public class Program
{
    private static BMP085 _Airpressur;

    public static void Main()
    {
        _Airpressur = new BMP085();

       while (true)
        {
            Debug.Print("Pressure Value: " + _Airpressur.ReadSensorData().ToString());

            // halbe Sekunde warten
            Thread.Sleep(500);
        }

    }
}

Wie nun die Kalibrierung funktioniert, erkläre ich in einem späteren Post. Nun zu der Verkabelung. Leider war der Sensor in der Fritzing Software in einer anderen Version. Diesmal sollten diese Fotos ausreichen, um zu zeigen, wie dieser Sensor verkabelt wird.

Luftdrucksensor_BMP085_Netduino1_bearbeitet-1
Verkabelung bei einem Netduino Plus 1

Mit dem Netduino 2 und dem Netduino Plus 2 sind die Anschlüsse für den I²C auf die andere Seite gewandert.

Luftdrucksensor_BMP085_bearbeitet-1
Verkabelung bei einem Netduino Plus 2

Wenn alles soweit klappt, dürfte nun im Visual Studio – Ausgabe Fenster der Messwert angezeigt werden.

image

Wie sich zeigt, bildet das kleine Programm eine einfache Möglichkeit den Sensor in Betrieb zu nehmen. Im nächsten Blog Post wird auf den Temperatur Sensor eingegangen, der hier ebenfalls auf dem Sensor Modul enthalten ist.

Beispiel Programm zum herunterladen.

Datenblatt BMP085

Samstag, 14. September 2013

Tippe die Lampe an

HitLamp01

Letztes Jahr habe ich zu dem Sensor BMA020 bereits ein Code Beispiel für den Arduino gepostet. Nun dachte ich, da der so rum liegt, kann man den vielleicht auch mal für etwas verwenden. Also kam mir die Idee eine Lampe einzuschalten, in dem man gegen sie tippt oder leicht schlägt.

Schaltung_HitLamp_bearbeitet-1
Im späteren Verlauf stellte ich fest, dass einige Widerstände zu groß berechnet wurden. Die Abweichungen kommen durch die LED, die eigentlich für 12V ausgelegt sind und ich sie aber mit 9V betreibe. (Für den ATMega fehlt auf dem Bild der Quarz, PIN9 und PIN10)

Zunächst skizzierte ich mir meine Schaltung und berechnete anhand des Schaltbildes die Widerstände. Dann sammelte ich meine benötigten Komponenten zusammen und steckte sie auf meinem Breadboard, um zu sehen ob meine Überlegung richtig ist.

image
Versuchsaufbau der Schaltung

Natürlich funktioniert die Schaltung nicht ohne einen Programmcode und daher musste ich mir zunächst etwas überlegen. Zum Beispiel wie ich aus den drei Achsen einen Wert berechne und wie hoch die Abweichung sein muss, um eine Bewegung als Tippen wahrzunehmen. Die Formel für den Gravitationsvektor half nicht sonderlich weiter. Am Ende kam ich auf ein simples Zusammenrechnen, wie es im Programmcode zu sehen ist.

gravitySum = axisX / 3 + axisY / 3 + axisZ / 3;

Dies habe ich mir auf den Serial Monitor ausgeben lassen und tippte solange gegen meinen Arduino mit dem Sensor, bis ich einen passenden Wert ermittelt hatte.

image
Nach dem gegentippen bekam ich einen Wert von mehr als 9000 (echt Zwinkerndes Smiley)

Dann übernahm ich den Wert in die Bedingung für das Einschalten oder Ausschalten. Nun musste das Ganze noch auf eine Platine gelötet werden und das möglichst im kleinsten Raum und mit den LEDs, die ich dafür verwenden möchte. Als Hülle habe ich einen Teil von einer Plastikflasche verwendet.

image
Die LED Fläche zerlegt. Diese LED Lampe kommt aus einem IKEA Lampen Set.
image
Hier schneide ich den Flaschenhals auseinander. Mit einer Halterung brachte ich den Teil des Flaschenhalses in Rotation und bekam durch diese Methode einen sauberen Schnitt mit dem Cutter Messer.
image
Anschließend noch in einer stylischen Farbe lackieren…
image
Die fertige Schaltung einsetzen und die LED Flächen ankleben.

Am Ende noch den fertigen Programmcode auf den ATMega326 bringen.

#include <Wire.h>

// I²C Adresse vom Beschleunigungssensor
#define AccelerometerAddress 0x38

// Byte Array in dem die Ergebnisse gespeichert werden
byte data[6];
// Ruft die XYZ Achsen Beschleunigung ab oder legt sie fest.
int axisX;
int axisY;
int axisZ;

// Beim Einschalten muss der Sensor Kalibriert werden.
// Der Sensor sollte sich in dem Moment nicht bewegen.

int axisX_offset;
int axisY_offset;
int axisZ_offset;

// Wird nach dem Kalibrieren auf False gesetzt.
boolean isFirstTime = true;

// Die Beschleunigung wird zusammengerechnet,
// durch 3 geteilt und dieser Variable gespeichert.
double gravitySum;

// Zeigt den maximalen berechneten Wert aus 'gravitySum'
int maxValue = 0;

// Ruft ab, ob die Lampen eingeschaltet
// oder ausgeschaltet werden.
boolean isHitting = false;

// An welchem Pin soll die LED
// ein oder ausgeschaltet werden.

int ledPin = 12;

void setup()
{
  // Die serielle Verbindung wird nur zum Ermitteln
  // des 'gravitySum' benötigt.
  Serial.begin(57600);
  // LED Pin zuweisen
  pinMode(ledPin, OUTPUT);
  // I²C Verbindung starten
  Wire.begin();
  // LED Pin zunächst ausschalten.
  digitalWrite(ledPin, LOW);
}

void loop()
{
  // Liest die Sensor Beschleunigungsdaten ein.
  readAcceleration();
 
  // per Byte Shifting den 10Bit Wert zusammenfügen
  axisX = (data[0] >> 1) | data[1] << 8;
  axisY = (data[2] >> 1) | data[3] << 8;
  axisZ = (data[4] >> 1) | data[5] << 8;
 
  // Wenn dies der erste Durchlauf ist
  // dann wird das Offset gesetzt.

  if(isFirstTime)
  {
    axisX_offset = axisX;
    axisY_offset = axisY;
    axisZ_offset = axisZ;
    isFirstTime = false;
  }
  else
  {
    // Abweichung runterrechnen
    axisX = axisX - axisX_offset;
    axisY = axisY - axisY_offset;
    axisZ = axisZ - axisZ_offset;
  }
 
  // Keine Gravitationsberechnung, nur einfaches
  // Zusammenrechnen, um einen Wert später zu erhalten
  // der nur bei Bewegung gemessen wurde.

  if(axisX < 0){ axisX = axisX * -1; }
  if(axisY < 0){ axisY = axisY * -1; }
  if(axisZ < 0){ axisZ = axisZ * -1; }

  gravitySum = axisX / 3 + axisY / 3 + axisZ / 3;
 
  // Ergebnisse ausgeben
  Serial.print("Axis X: ");
  Serial.print(axisX, DEC);
  Serial.print("\tAxis Y: ");
  Serial.print(axisY, DEC);
  Serial.print("\tAxis Z: ");
  Serial.print(axisZ, DEC);
 
  Serial.print("\tGravity: ");
  Serial.print((int)gravitySum, DEC);
 
 
// Der 'maxValue' zeigt den höchsten
  // berechneten Wert aus 'gravitySum'

  if(gravitySum > maxValue)
  {
    maxValue = gravitySum;
  }
 
 
// Maximalen Wert wiedergeben,
  // der später als Schwellwert verwendet werden kann
  Serial.print("\tMax Value: ");
  Serial.println(maxValue, DEC);
 
 
// Wenn der maxValue ermittelt wurde
  // kann der Wert zum Vergleich eingesetzt werden.
  // So soll erst ein Schaltvorgang gesetzt werden,
  // wenn der Schwellwert (hier 9000) erreicht wurde.
  if(gravitySum > 9000)
  {
    // Zustand ändern
    isHitting = !isHitting;
   
   // Einschalten, wenn die Lampe aus ist
    if(isHitting)
    {
      digitalWrite(ledPin, HIGH);
      Serial.println("AN");
    }
   
else
    {
      digitalWrite(ledPin, LOW);
      Serial.println("AUS");
    }
   
   
// Verzögerung zum Ausschwingen,
    // damit nicht gleich die Lampe
    // wieder ausgeschaltet wird.
    delay(1500);
  }
 
  delay(100);
}

// Liest den Beschleunigungssensor ein und schreibt das Ergebnis in ein Byte Array.
void readAcceleration()
{
  Wire.beginTransmission(AccelerometerAddress);
  Wire.write(0x02);
  Wire.endTransmission();
  Wire.requestFrom(AccelerometerAddress, 6);

  for(int i = 0; i < 6; i++)
  {
    data[i] = Wire.read();
  }
}

Mit einem 9V Block Akku lässt sich dies leider nicht lange betreiben, da die LEDs zusammen 1W Leistung verbrauchen. Ein 9V Block ist spätestens nach einer halben Stunde leer. Besser eignet sich hier ein Netzteil (oder eine Batterie mit mehr Kapazität). Hier noch ein kurzer Zusammenschnitt als Video.

Was vielleicht von Interesse sein könnte, sind die verwendeten Bauteile. Alternativ kann nach Bedarf das Aussehen bestimmt werden. Zwinkerndes Smiley

Materialliste:

  • 1 x Arduino Duemilanove (Geht auch mit anderen)
  • 1 x Beschleunigungssensor BMA020
  • 1 x Spannungsregler LM317
  • 1 x Quarz 16Mhz
  • Widerstände: 1 x 47 Ohm, 1 x 4,7kOhm, 1 x 10Ohm, 2 x 2,2kOhm
  • Transistoren: 1 x BC177A (PNP), 3 x BC547B (NPN)
  • 1x Potentiometer 500Ohm
  • 1x Streifenplatine
  • 1 x DIODER (Name des IKEA Produktes mit den LED Flächen)
  • 1 x Breadboard
  • 1 x Platine
  • 1 x 9V Block
  • eine Menge Kabel
  • 1 x Kunststoff Flasche

Gehäuseentwurf für Signalleuchten (ESP32)

RGB LEDs und LIPO Akkus sind bestellt. Weil ich bereits die Abmessungen habe, kann ich schon mal mit dem Gehäuseentwurf in Blender anfangen....