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

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....