Dienstag, 6. Januar 2015

Arduino Control (Teil 2) – Button Status auf PC wiedergeben

image

Eine Eingabe vom PC zum Arduino zu bringen ist nicht sonderlich schwierig. Das Gleiche gilt auch bei der Umsetzung einer Tasteneingabe vom Arduino zum PC. Am Arduino wird diesmal eine kleine Schaltung mit einem Button zusammengesteckt.

Zusätzlich benötigt: Taster und 10k Ohm Wiederstand

image

Kommen wir zum Consolen Programm. Wie im vorherigen Post, werden die selben Verbindungsparameter verwendet.

Für den Empfang wird noch der hierfür erforderliche Event hinzugefügt. Sobald sich also Daten im Eingangspuffer befinden, wird der Inhalt der Event Methode ausgeführt.

In der Schleife wird diesmal nur ein 'Thread.Sleep' eingesetzt; das soll verhindern, dass die Anwendung zu viel der CPU Zeit beansprucht (und es nicht so wirkt, als wäre sie eingefroren).

static void Main(string[] args)
{
    // Die Verwendete COM Verbindung
    // sollte der aus dem Arduino Programm entsprechen.

    SerialPort sp = new SerialPort("COM16", 115200);

    // Event zuweisen für den Empfang.
    sp.DataReceived += sp_DataReceived;

   // Verbindung öffnen.
    // Wenn das Programm gestartet ist,
    // sollte der Arduino bereits mit dem PC verbunden sein.
    sp.Open();

    while (true)
    {
        // Nur warten.
        Thread.Sleep(1000);
    }
}

// Wird ausgeführt, wenn Daten im Empfangspuffer vorhanden sind.
static void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // SerialPort aus 'sender' casten.
    SerialPort sp = (SerialPort)sender;

    // Byte einlesen vom Empfang und auf die Console wiedergeben.
    Console.WriteLine("Received: {0}", sp.ReadByte() == 0x01);
}

Der Code für den Arduino kann sich den letzten Zustand des Button "Drücken" merken. Damit wird nur gesendet, wenn sich der Zustand geändert hat. Für das Senden wird im Beispiel der selbe Hex Wert verwendet.

// Pin Nummer A0 vom Arduino
// an dem ein HIGH (gedrückt) oder
// LOW (nicht gedrückt) gelesen wird.

int pinButton = A0;

// Enthält den letzten Button Zustand.
boolean buttonState = false;

void setup()
{
  // Baudrate wie bei dem C# Programm
  Serial.begin(115200);
  pinMode(pinButton, INPUT);
}

void loop()
{
  // Status am Pin abfragen
  boolean readState = digitalRead(pinButton);
 
  // Nur Inhalt im If ausführen,
  // wenn der Status sich geändert hat.
  if(readState != buttonState)
  {
   // Member Field überschreiben.
    buttonState = readState;
  
    // Wenn 'true' sende 0x01 für ist gedrückt
    if(buttonState == true)
    {
      Serial.write(0x01);
    }
    else
    {
      Serial.write(0x00);
    }
  }
}

Das war schon auch wieder alles. Nun können mit dem Beispiel Eingaben vom Arduino auf dem PC wiedergegeben werden.

Quellcode zum Downloaden

Sonntag, 4. Januar 2015

Arduino Control (Teil 1) – LED ein und ausschalten

image

Diesmal gehen die folgenden Beispiele darauf ein, einen Arduino mit seinen Eigenschaften anzusteuern. Im Wesentlichen handelt dieser mehrteilige Blog Post davon, wie eine Eingabe vom PC zum Arduino kommt und auch wieder zurück.

Die Anforderung wird sein, dass mit der Enter Taste die rote LED auf dem Arduino ein oder ausgeschaltet wird. Für das erste Beispiel reicht daher ein Consolen Programm aus, um eine Eingabe aufzunehmen und einen Befehl entsprechend zu senden. Beide Programme müssen dieselbe Baudrate haben, die hier bei 115200 Baut liegt.

static void Main(string[] args)
{
    // Die Verwendete COM Verbindung
    // sollte der aus dem Arduino Programm entsprechen.
    SerialPort sp = new SerialPort("COM16", 115200);

    // Verbindung öffnen.
    // Wenn das Programm gestartet ist,
    // sollte der Arduino bereits mit dem PC verbunden sein.
    sp.Open();

    // Wird verwendet um den Zustand zu wechseln.
    bool changeState = true;

    // Der Eigentliche Steuerinhalt wird immer wieder ausgeführt.
    while (true)
    {
        // Programm hält an dieser Stelle,
        // bis die Enter Taste gedrückt wurde.

        Console.ReadLine();

        // Ist der Wechsel Status 'True'...
        if (changeState)
        {
            //... dann soll die LED eingeschaltet werden.
            sp.Write(new byte[] { 0x01 }, 0, 1);
        }
        else
        {
            //... dann soll die LED ausgeschaltet werden.
            sp.Write(new byte[] { 0x00 }, 0, 1);
        }

        // Wechselt den Zustand von True auf False oder umgekehrt.
        changeState = !changeState;
    }
}

Hinweis: Hier im Beispiel verwende ich COM16, der wahrscheinlich angepasst werden muss. Das könnte dann ggf. über dem Arduino Programm gesichtet werden.

image

Nun muss der Arduino die Sendung vom Programm verstehen. Momentan wird erwartet das Hex 0x01 oder Hex 0x00 empfangen wird.

void setup()
{
  
// Baudrate wie bei dem C# Programm
  Serial.begin(115200);

  // Pin 13 für die Rote LED
  pinMode(13, OUTPUT);
}

void loop()
{
  // Wenn neue Daten verfügbar sind.
  if(Serial.available() > 0)
  {
    // Wird am Serial 0x01 gelesen, wird an die
    // Methode digitalWrite ‘true’ übergeben.

    digitalWrite(13, Serial.read() == 0x01);
  }
}

Das war im Grunde schon alles. Wenn nun das C# Programm gestartet wird und der Arduino bereits angeschlossen ist, so kann dann mit der Enter Taste die Rote LED ein und ausgeschaltet werden.

Quellcode zum Downloaden

Dienstag, 14. Oktober 2014

Intervallaufnahme mit der Kamera ohne Interne Funktion

IntervallAufnahme01

Die Funktion für eine Intervallaufnahme ist simpel, daher ist es für mich unverständlich, warum nicht zumindest die Systemkameras von der Nikon 1 Serie in ihren Modellen diese Option anbieten. Nun gut, dies war kein Kaufkriterium, wäre aber schön gewesen. Ich selbst verwende die Nikon 1 J3 (die J1 hat die Intervallaufnahme), allerdings sollte mein Beispiel auch für andere Kameras funktionieren. Grundsätzlich geht es darum einen Servo auf den Auslöser zu drücken. Für diesen Zweck habe ich aus Aluminium Eckschienen eine Vorrichtung gebaut.

image

Wie schon am Bild zu erkennen ist, sollten zum Schutz die Alu Eckschienen nach der Verarbeitung abgerundet und ggf. mit Filzaufklebern beklebt werden.

Die Materialliste:

  • Alu Eckschienen ca. 20cm
  • Filzaufkleber
  • ein paar 3mm Schrauben und entsprechende Muttern
  • Mini Servo mit ca. 10 Ncm
  • Eine passende Schraube zur Kamera dass mit dem Stativ auch verschraubt werden kann. (siehe folgendes Bild)
image

Zum Ansteuern wird der Netduino mit einem passenden Shield zur Ansteuerung für einen Servo verwendet. Die geringe Belastung kann das Board verkraften. Bei Normalen und größeren Servos wird eine Externe Stromversorgung notwendig.

image

Der Aufwand des Programmcodes gestaltet sich übersichtlich. Zusehen ist, dass noch ein Analoger Wert eingelesen wird, worauf ich im nächsten Absatz zu sprechen komme.

public class Program
{
    // Für die Einstellung des Druckpunktes.
    private static SecretLabs.NETMF.Hardware.AnalogInput _Input;

    // Ansteuerung des Servos für den Auslöser.
    private static PWM _Servo;

    // Led Status wiedergeben.
    private static OutputPort _Status;

   // Einen Zähler.
    private static int _IntervallCounter = 0;

   // Nach wie viel Sekunden soll ausgelöst werden.
   private static int _IntervallEnd = 15;//30;

    public static void Main()
    {
        _Input = new SecretLabs.NETMF.Hardware.AnalogInput(Pins.GPIO_PIN_A0);
        _Servo = new PWM(Pins.GPIO_PIN_D5);
        _Status = new OutputPort(Pins.ONBOARD_LED, false);
           
        // 20ms für den Analog Servo.
        uint periode = 20000;

        // Mittelstellung bei 1,5ms
        _Servo.SetPulse(periode, 1500);

       while (true)
        {
           if (_IntervallCounter >= _IntervallEnd)
            {
                for (int i = 0; i < 3; i++)
                {
                    _Status.Write(true);
                    Thread.Sleep(100);
                    _Status.Write(false);
                    Thread.Sleep(100);
                }

                // Hinweis: bei Netduino 1 wird ein Wert gelesen von 0 bis 1023.
                //          Der Netduino 2 hingegen liest bis 4095.
                //uint duration = 1000 + (uint)_Input.Read();         // Ein kommentieren um den Servo
                //Debug.Print("Duration: " + duration.ToString());  // einzustellen am Pin A1.

                _Servo.SetPulse(periode, 1710); // 1710 ist eine Einstellung, an meiner Kamera
                Thread.Sleep(400);

               // Zähler Zurücksetzten.
                _IntervallCounter = 0;
            }
            else
            {
                // Aktivität zeigen
                _Status.Write(true);
                Thread.Sleep(100);
                _Status.Write(false);
               Thread.Sleep(900);

                _IntervallCounter++;
            }

           // Zurückstellen.
            _Servo.SetPulse(periode, 1500);
        }
    }
}

Das gleiche nochmal für den Arduino.

#include <Servo.h>
// Servo Kontrolle anlegen
Servo controlServo;
// Einstellung wie weit sich der Servo drehen darf
int pinAnalogIn = A0;
// Servo ansteuern
int pinServo = 5;
// LED Status
int pinLed = 13;
// Intervall Zähler
int intervall = 0;
// IntervallLänge in Sekunden
int intervallLength = 15; // 30;

void setup(){

  // Dient zu Einstellgunszwecke
  Serial.begin(9600);
  // Pins zuweisen
  pinMode(pinAnalogIn, INPUT);
  controlServo.attach(pinServo);
  pinMode(pinLed, OUTPUT);
}

void loop()
{
  if(intervall >= intervallLength)
  {
    for(int i = 0; i < 3; i++)
    {
      digitalWrite(pinLed, HIGH);
      delay(100);
      digitalWrite(pinLed, LOW);
      delay(100);
    }
   
   // Einkommentieren um die Reichweite des Servos einzustellen,
    // mit einem Potitometer am Pin A1.

    int result = 500; //analogRead(pinAnalogIn);
   
    controlServo.write(map(result, 0, 1023, 0, 179));
    delay(400);
   
    // Zähler zurück setzen
    intervall = 0;
  }
  else
  {
   digitalWrite(pinLed, HIGH);
   delay(100);
    digitalWrite(pinLed, LOW);
    delay(900);
    intervall++;
  }
 
  // Zurück stellen.
  controlServo.write(90);
}

Jetzt fehlt noch ein fester Wert für den Druckpunkt. Der Potentiometer wird an Pin A1 angeschlossen, die Spannungsversorgung muss beim Netduino mit 3,3V angelegt werden. Das Auslesen des Stellwertes erfolgt beim Netduino über Debug.Print() und wird über die Ausgabe angezeigt. Beim Arduino wird der Stellwert über dem Serial Monitor wiedergegeben. Je nach Belastung kann die Versorgung über USB instabil werden. Das zeigt sich dann, wenn der Servo beim Drücken des Auslösers leicht zittert. Später, mit einem 9V Akku oder Netzteil, sollten diese Schwierigkeiten nicht mehr auftauchen. Ansonsten muss der Servo einen eigenen Stromkreis erhalten, um zu verhindern, dass die Versorgung des Mikrocontrollers zu sehr schwankt und so zu einem Neustart des Programms führt.

image image
image image

Zu den Einstellungen der Kamera. Um ein schnelles Auslösen der Kamera zu ermöglichen, sollte auf den Kreativ Modus gewechselt werden. Die Blende, die Verschlusszeit und der Fokus sind am besten manuell festzulegen. Spätestens bei Nachtaufnahmen kommt man an diesen Einstellungen nicht vorbei. Achja, das Dateiformat JPG ist für die Ausgabe zu empfehlen. Mit dem NEF bzw. RAW ist das Bearbeiten im Video Schnittprogramm kaum machbar.

BeiNachtMitSternenhimmel_06

Tipp zur Nachtaufnahme: acht bis fünfzehn Sekunden für die Verschlusszeit bei maximal offener Blende reichen aus. Zu lange Belichtung führt dazu, dass die Sterne Streifen bilden, also praktisch gesehen wird die Erdrotation sichtbar. Die idealen Nächte mit Sternenhimmel sind in der Zeit um Neumond. Die besten Bedingungen sind Orte, die so dunkel sind, dass beim Hinabsehen die eigenen Füße nicht mehr zu sehen sind. Zwinkerndes Smiley

Nun noch ein paar Aufnahmen, die ich mit meiner Apparatur erstellt habe.

Zu guter Letzt auch noch der Quellcode zum Herunterladen.

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, 27. Mai 2014

TCS3200 Farbsensor einlesen (Netduino)

TCS3200_01

Das Licht leuchtet hier in verschiedenen Farben. Mit Hilfe des TCS3200 Sensor, können die verschiedenen Farben gemessen werden. Anders als bei einem lichtempfindlichen Sensor, an dem sich der Leitwert verändert, liegt ein Pulssignal an. Für die Schaltung werden insgesamt fünf Digitale Ports und zwei Pins für die Spannungsversorgung benötigt.

image

Wenn mal kein Breadboard zur verfügung steht, geht das auch so wie im folgendem Bild. Die Pins von S0 bis S3 und OUT auf die digitalen Ausgängen von Pin 2 bis Pin 6 steckt.

TCS3200_Netduino_03

Wie die Schaltung bereits verrät, ist das einlesen deutlich anders. Anstatt über OneWire , I²C oder eines Spannungspegel, kommt ein PWM (oder auch PPM) Signal. Je nach Farbe müssen die Anschlüsse von S0 bis S3 entsprechend geschaltet sein. Das bedeutet wiederum, dass die Farben nacheinander gemessen werden. Im .NET Micro Framework gibt es zum Einlesen die Klasse "InteruptPort", mit der auf das PWM Signal reagiert werden kann. Das erfordert etwas zusätzlichen Programmcode.

// Optionen für Ausgangsfrequenz
public enum
OutputFrequencyScaling
{
    PowerDown,
    TwoPercent,
    TwentyPercent,
    HundredPercent
}

// Filter Auswahl
public enum SelectColor
{
    Red,
    Blue,
    Green,
    Clear
}

public class Program
{
    // Die Klasse wird für das Einlesen des PWM Signals verwendet.
   private static InterruptPort _IntPort = new InterruptPort(
        Pins.GPIO_PIN_D6,
        true,
        Port.ResistorMode.Disabled,
        Port.InterruptMode.InterruptEdgeBoth);

    // Ruft die gemessene Zeit ab,
    // wann die HIGH Pegele begonnen hat oder legt diese fest.
    private static long _HighTicks = 0;

    // S0 und S1 Schaltet die Ausgangs frequenz entsprechend ein (Siehe dazu Datasheet)
    private static OutputPort _S0 = new OutputPort(Pins.GPIO_PIN_D2, false);
   private static OutputPort _S1 = new OutputPort(Pins.GPIO_PIN_D3, false);

   // S2 und S3 Schaltet die verschiedenen Filter Möglichkeiten ein
    private static OutputPort _S2 = new OutputPort(Pins.GPIO_PIN_D4, false);
    private static OutputPort _S3 = new OutputPort(Pins.GPIO_PIN_D5, false);

    // Die Onboard LED als Status Aktivität
    private static OutputPort _StatusLED = new OutputPort(Pins.ONBOARD_LED, false);

    private static string _readColorName = string.Empty;

    private static bool _valueIsSet = false;
    private static long _resultTemp = 0;
    private static int _resultRed = 0;
    private static int _resultBlue = 0;
    private static int _resultGreen = 0;
    private static int _resultClear = 0;

    public static void Main()
    {
        OutputFrequencyScaling scale = OutputFrequencyScaling.TwoPercent;

       while (true)
        {
          
// Status Lesen
            _StatusLED.Write(true);

            // Farben nach einander einlesen.
            ReadPwmSignal(ref _resultRed, SelectColor.Red, scale);
            ReadPwmSignal(ref _resultBlue, SelectColor.Blue, scale);
            ReadPwmSignal(ref _resultGreen, SelectColor.Green, scale);
            ReadPwmSignal(ref _resultClear, SelectColor.Clear, scale);

            // Ausgabe über die Messergebnisse.
            Debug.Print("Red:" + _resultRed.ToString() +
               "\t Blue:" + _resultBlue.ToString() +
               "\t Green:" + _resultGreen.ToString() +
               "\t Clear:" + _resultClear.ToString());

           // Status warten
            _StatusLED.Write(false);
           Thread.Sleep(500);
        }
           
    }

    // Methode mit Filter- und Ausgangsfrequenzschalten und gibt das Ergebnis über "result" zurück.
   private static void ReadPwmSignal(ref int result, SelectColor color, OutputFrequencyScaling scaling)
    {
       // Ausgangsfrequenz schalten
        switch (scaling)
        {
            case (OutputFrequencyScaling.TwoPercent): { _S0.Write(false); _S1.Write(true); break; }
            case (OutputFrequencyScaling.TwentyPercent): { _S0.Write(true); _S1.Write(false); break; }
           case (OutputFrequencyScaling.HundredPercent): { _S0.Write(true); _S1.Write(true); break; }
            default: { _S0.Write(false); _S1.Write(false); break; }
        }

        // Filter einstellen
        switch (color)
        {
            case (SelectColor.Red): { _S2.Write(false); _S3.Write(false); break; }
            case (SelectColor.Blue): { _S2.Write(false); _S3.Write(true); break; }
            case (SelectColor.Green): { _S2.Write(true); _S3.Write(true); break; }
            default: { _S2.Write(true); _S3.Write(false); break; }
        }

       // Member für das erfassen des Messergebnisses.
        _HighTicks = 0;
        _resultTemp = 0;
        _valueIsSet = false;

        // Event zuweisen
        _IntPort.OnInterrupt += _IntPort_OnInterrupt;

        // Warten
        for (int i = 0; i < 5; i++) { Thread.Sleep(2); }

        // Event wieder abziehen
        _IntPort.OnInterrupt -= _IntPort_OnInterrupt;
           
        // Ergebniss kopieren
        result = (int)_resultTemp;
    }

    // Wird ausgeführt, wenn sich der Pegel am Digitalen Port geändert hat.
    private static void _IntPort_OnInterrupt(uint data1, uint data2, DateTime time)
    {
        // Lock verhindert, das ein zweiter unmittelbarer Event nicht erfolgen kann.
        lock (_IntPort)
        {
           // Wenn noch kein Wert erfasst wurde.
            if (!_valueIsSet)
            {
               // Wenn HIGH Pegel anlieg und die Aktuelle
                // Zeit in Ticks noch nicht erfast wurde.
                if (data2 == 1 & _HighTicks == 0)
                {
                    // Aktuelle Zeit in Ticks zwischen speichern
                    _HighTicks = time.Ticks;
                }
                else if (_HighTicks != 0 & !_valueIsSet)
                {
                    // Wenn der Pegel von HIGH nach LOW gewechselt hat
                    _resultTemp = time.Ticks - _HighTicks;
                   // True zuweisen, dass der Messwert erfasst wurde
                    _valueIsSet = true;
                    // Event abziehen
                    _IntPort.OnInterrupt -= _IntPort_OnInterrupt;
                }
            }
        }
    }
}

Als Ausgangsergebnis werden vier Werte wiedergegeben. Wenn nun vor dem Sensor eine Farbige Fläche mit einer der Grundfarben gehalten wird, dann verändern sich die Werte dementsprechend. Stammt die Farbe von einem Bildschirm (wie im folgenden Bild von einem Smart Phone), dann kann der Blaue Jumper herausgezogen werden, um die LED Beleuchtung abzustellen.

TCS3200_Netduino_01

  Bei grob angemalten Flächen wie dieser Karteikarte funktioniert es weniger gut..

TCS3200_Netduino_02

Anhand der Farbe stellen sich die Werte neu ein. Je ein deutiger die Farbe ist, um so deutlicher zeigt sich das Ergebnis.

Also bei durch gehenhenden Blau, Grün oder Rot zeigt sich ein hoher Wert. Wenn die Farbfläche jedoch wie bei meinem Beispiel mit der Karteikarte durchgeführt wird, stehen an jeden Ausgang relativ hohe Werte.

image

Noch ein Hinweis. Die Pulslänge wird kürzer, wenn der Weg des reflecktierten Licht kürzer wird, oder salobt gesagt, wenn die Farbfläche zu nah am Sensor ist, dann ist das Pulssignal sehr kurz. Dies führt dann dazu, dass die letzte Methode sehr häufig ausgeführt wird. Im schlimmsten Fall kommt es zu einem Speicherüberlauf und das Programm bleibt stehen.

Beispiel Programm zum Herunterladen für Netduino mit .NET Micro Framework 4.2

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

Ameisen Simulation und andere Dinge

Wer lange sich schon mit C# beschäftig, hat sicherlich schon mal was von AntMe gelesen oder gehört. Diese Idee hatte ich aufgegriffen und mi...