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

Mittwoch, 17. Januar 2018

Zeichnen auf dem TFT mit dem XBox Controller

 
Im ersten Beispiel wurde beschrieben, wie man die Daten von einem Xbox Controller zu dem Arduino sendet und dabei einen Servo ansteuerte. Nachteil an der Code Konstellation war, dass nur ein Byte Wert übermittelt wurde und auch sehr instabil lief. Mit ein paar Anpassungen lässt sich dies einfach beheben. (siehe: Servo mit dem Xbox Controller steuern)

Hinweis
Das folgende Beispiel ist vom Aufbau relativ simple gehalten, so dass bei der Ausgabe Kompromisse eingegangen werden, wie z.B. die Daten Qualität über die Serielle Schnittstelle.

Mehr Daten Senden
Das MonoGame Projekt muss nur in der Update Methode angepasst werden. Zum einen wird geprüft, ob noch Bytes geschrieben werden. Dann können die Eingabewerte vom Xbox Controller eingelesen werden und über die Serielle Verbindung versendet werden.

 protected override void Update(GameTime gameTime)  
 {  
   GamePadState state = GamePad.GetState(PlayerIndex.One);  
   if (state.Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))  
   {  
     this.Exit();  
   }  
   if (this._serialPort.BytesToWrite == 0)  
   {  
     byte stickValueX = (byte)((state.ThumbSticks.Left.X + 1) * 90);  
     byte stickValueY = (byte)((state.ThumbSticks.Left.Y + 1) * 90);  
     byte buttonValuePaint = (byte)(state.Buttons.A == ButtonState.Pressed ? 10 : 0);  
     byte buttonValueReset = (byte)(state.Buttons.B == ButtonState.Pressed ? 10 : 0);  
     this._serialPort.Write(new byte[] { stickValueX, stickValueY, buttonValuePaint, buttonValueReset }, 0, 4);  
     this._serialPort.WriteLine(string.Empty);  
   }  
   Task.Delay(5);  
   base.Update(gameTime);  
 }  

Mehr Daten Empfangen
Die Lösung ist simple. Um mehrere Bytes einzulesen, müssen diese über einen Schleifen Vorgang vom Serial gelesen werden. Die Byte Werte werden dann zunächst in eine lokales Byte Array zugewiesen. Anschließend kann noch geprüft werden ob alle Bytes gelesen wurden. Die ersten zwei Werte sollten zumindest da sein, ansonsten kann der zu zeichnende Punkt (eigentlich ein Kreuz) nicht bewegt werden.

 bool ReadReceivedBytes() {  
  byte received[4];  
  int index = 0;  
  while(Serial.available() > 0) {  
   if(index < 5) {  
    received[index] = Serial.read();  
   }  
   index++;  
  }  
  if(index < 4) {  
   return false;  
  }  
  mReceivedStickX = received[0];  
  mReceivedStickY = received[1];  
  mReceivedButtonA = received[2];  
  mReceivedButtonB = received[3];  
  return true;  
 }  

Ausgabe und Fehler
Die Ausgabe erfolgt diesmal nicht an einem Servo, sondern an einem TFT Display mit einer Auflösung von 160x128 Pixeln, der auch für den Arduino Esplora ebenfalls verwendet wird.
Die Übertragung ist nicht immer Fehlerfrei, womit für genauere Ergebnisse eine Fehlerbehandlung notwendig wird. Aber das wird in einem anderen Blog Post beschrieben. :)



Freitag, 16. Januar 2015

Arduino Control (Teil 4) – Potentiometer Wert am PC ausgeben

image

Der Arduino kann auch analoge Signale einlesen. Dieser Wert kann zwischen 0 und 1023 liegen. Für dieses Beispiel wird ein Potentiometer verwendet, der möglichst hochohmig sein sollte. In meinem Fall habe ich einen 470 Ohm Stellwiederstand verwendet. Empfohlen ist 1k Ohm.

image

Der Quellcode ähnelt dem Beispiel für den Button. Nur müssen hier für den Empfang zwei Bytes zu einem Wert zusammengesetzt werden. Das Stichwort ist hier Byte Shifting. Das erste Byte wird um acht Bits nach links verschoben, um die Bits von 9 bis 16 abzubilden. Das zweite Byte bleibt für die ersten acht Bits.

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;

    // Bytes einlesen vom Empfang und auf die Console wiedergeben.
    byte[] buffer = new byte[2];
    if (sp.Read(buffer, 0, 2) != 0)
    {
        Console.Clear();
        // Die zwei empfangenen Byte müssen wieder zusammengelegt werden,
        // um den Eigentlichen Wert wiederzugeben.
        Console.WriteLine("Received: {0}", (int)buffer[0] << 8 | buffer[1]);
    }
}

Der Input verwendet diesmal analogRead(), statt des digitalRead(). Unterscheidet sich der Wert zu dem zuletzt versendeten, dann soll der neue gemessene Wert gesendet werden. Der Wert kann bis zu 1023 betragen und passt nicht in ein Byte. Daher wird dieser in zwei Bytes aufgeteilt und anschließend versendet. Am Ende wird eine Verzögerung von 100 Millisekunden festgelegt, um zwischen den Sendungen eine Lücke zu erzeugen. Ansonsten kann nicht erkannt werden, welches das erste und das zweite Byte ist.

// Pin Nummer A0 vom Arduino
int pinPegel = A0;

// Enthält den letzten Button Zustand.
int lastValue = 0;

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

void loop()
{
  // Status am Pin abfragen
  int readValue = analogRead(pinPegel);
 
  // Nur Inhalt im If ausführen,
  // wenn der Status sich geändert hat.
  if(readValue != lastValue)
  {
   // Member Field überschreiben.
    lastValue = readValue;
  
   // Buffer vorbereiten und
   // den Wert in zwei bytes aufteilen.
    byte ba[2];
    ba[0] = (byte)((readValue >> 8) & 0xff);
    ba[1] = (byte)(readValue & 0xff);
   
    // Buffer Inhalt von zwei Bytes senden.
    Serial.write(ba, 2);
       
    // Ermöglicht das Erkennen vom 1. und 2. Byte
    // in dem zwischen den Sendungen eine Pause gesetzt wird.

    delay(100);
  }
}

Damit ist auch schon alles getan.

 

Quellcode zum Download

Dienstag, 13. Januar 2015

Arduino Control (Teil 3) – LED mit dem PC Dimmen

image

Nicht alles lässt sich immer sinnvoll mit Ein- und Ausschalten lösen. Möchte man eine LED dimmen, so sollte ein PWM Signal verwendet werden. Ein PWM verwendet einen Timer, um das Rechtecksignal abzubilden. Daher können nur sechs bestimmte Ausgänge am Arduino (D3, D5, D6, D9, D10, D11) verwendet werden. Die Pins sind daran zu erkennen, dass neben den Anschlüssen ein kleines Wellensymbol zu sehen ist (oder sie sind direkt mit PWM beschriftet).

image

Der Programm Code ist dem ersten Beispiel Ähnlich. Beim Senden jedoch, kann ein Wert von 0 bis 255 angegeben werden.

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();

    while (true)
    {
        Console.Write("Bitte geben sie einen Wert von 0 bis 255 ein und drücken sie anschließend die Enter Taste: ");

        // Abfrage eines Wertes.
        string readline = Console.ReadLine();

        // Zeichenkette in einen Integer Wert umwandeln.
        byte value = byte.Parse(readline);

       // Sendet den Wert.
        sp.Write(new byte[] { value }, 0, 1);
    }
}

Für den Empfang im Arduino wird diesmal für die LED Ansteuerung der Pin D3 verwendet. Und anstatt eines digitalWrite wird ein analogWrite verwendet.

// Pin 3 für die LED Beleuchtung
int pinPwmOut = 3;

void setup()
{
  // Baudrate wie bei dem C# Programm
  Serial.begin(115200);
 
  // Pin 3 für die LED
  pinMode(pinPwmOut, OUTPUT);
}

void loop()
{
  // Wenn neue Daten verfügbar sind.
  if(Serial.available() > 0)
  {
    // Liest den empfangen Byte Wert ein
    byte value = Serial.read();
   
    // Empfangener Byte Wert für das PWM Signal verwenden.
    analogWrite(pinPwmOut, value);
  }
}

Nach Eingabe eines Wertes von 0 bis 255 sollte die LED weniger oder heller leuchten. Durchschnittlich haben die LEDs eine Stromaufnahme von 20mA. Der Unterschied liegt in der Farbe, die unterschiedliche Spannungen für den Betrieb benötigen.

Farbe Spannung Berechneter Vorwiderstand Widerstand aus der E6 Reihe
Rot 1,6V 170 220 Ohm
Grün 2,1 145 150 Ohm
Blau 2,9 105 100 Ohm

Wenn die technischen Daten der verwendeten LED bekannt sind, dann kann der Widerstand mit der Formel R = U / I berechnet werden.

image

Das Schaltbild weist keine Besonderheiten auf, stelle dies jedoch zur Vollständigkeit mit in den Post ein.

image

Wie zu sehen ist, lässt sich dieses Beispiel schnell umsetzten.

 

Quellcode zum Downloaden

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

Sonntag, 23. Juni 2013

Zeitkritische Momente mit dem Netduino

Quadrocopter_48
USB TTL UART, Netduino mit Shield und Schaltnetzteil für Tests.

Bei einigen Anwendungen kommt es vor, dass eine Iteration möglichst wenig Zeit benötigen soll. Das bedeutet, dass man sich mit Code Optimierung beschäftigen muss, in dem man zumeist die gewohnte Art zu Programmieren überdenken muss. Grundsätzlich sollte man das nicht tun, wenn man Anfänger ist, und dann auch nicht wenn man professioneller Entwickler ist. Es sei denn, die Anforderung erfordert diese Optimierung. In der Entwicklung für meinen Quadrocopter stieß ich auf dieses Problem, dass eine Iteration viel zu lange dauert. Zwar sauber geschrieben, jedoch nicht Performance-optimiert, kam ich auf ca. 40ms bis 45ms pro Iteration. Das ergibt pro Sekunde 22 bis 25 Durchläufe. Nach den Optimierungen kam ich auf Iterationslaufzeiten von 11ms bis 22ms. Diese weite Zeitspanne entstand durch dynamische Verarbeitung von Byte Werten beim Senden und Empfangen, was jedoch ein anderes Thema abbilden würde. Zunächst benötigen wir eine Methode, um herauszufinden, wie lange ein Vorgang an Zeit benötigt.

public class Stopwatch
{
    private static long _Ticks_Start = 0;
    private static long _TicksPerMillisecound = System.TimeSpan.TicksPerMillisecond;

    public static long GetElapsedMillisecounds()
    {
         long result = (Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks
            - _Ticks_Start) / _TicksPerMillisecound;

         _Ticks_Start = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks;

        return result;
    }

    public static long GetElapsedMicrosecounds()
    {
         long result = (Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks
            - _Ticks_Start) / (_TicksPerMillisecound / 1000);

         _Ticks_Start = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks;

         return result;
    }
}

Die Klasse habe ich aus dem Netduino Forum und ist für dieses Beispiel angepasst.

Kommen wir zu einigen Beispielen, in dem einfach einer Integer-variablen ein Wert zugewiesen wird der dann durch einen anderen dividiert wird. Nach 100 Durchläufen werden die Durchschnittszeiten der 6 Beispieel ausgegeben. Der Grund ist, dass das Ergebnis nicht konstant bleibt und durch weitere Einflüsse beeinträchtigt ist.

public class Program
{
    public static void Main()
    {
        long timeResult = 0;
        int result = 0;

        long countIteration = 1;
        long avarageExample1 = 0;
        long avarageExample2 = 0;
        long avarageExample3 = 0;
        long avarageExample4 = 0;
        long avarageExample5 = 0;
        long avarageExample6 = 0;

        while (true)
        {

           // Beispiel mit einer Devisions Rechnung in Millisekunden
            Stopwatch.GetElapsedMillisecounds();

            result = 12345;
            result = result / 67890;

            timeResult = Stopwatch.GetElapsedMillisecounds();
            Debug.Print("Beispiel 1: " + timeResult +
                " Millisekunde, Freier Speicher: " + Debug.GC(true) + "kb");
            avarageExample1 += timeResult;

           // Beispiel mit einer Devisions Rechnung in Mikrosekunden
            Stopwatch.GetElapsedMillisecounds();

            result = 12345;
            result = result / 67890;

            timeResult = Stopwatch.GetElapsedMicrosecounds();
            Debug.Print("Beispiel 2: " + timeResult +
                " Mikrosekunden, Freier Speicher: " + Debug.GC(true) + "kb");
            avarageExample2 += timeResult;

            // Beispiel mit einer Devisions Rechnung
            // über eine Methode und den Integer kopieren.

            Stopwatch.GetElapsedMillisecounds();

            result = 12345;
            result = GetDivided(result);

            timeResult = Stopwatch.GetElapsedMicrosecounds();
            Debug.Print("Beispiel 3: " + timeResult +
                " Mikrosekunden, Freier Speicher: " + Debug.GC(true) + "kb");
            avarageExample3 += timeResult;

            // Beispiel mit einer Devisions Rechnung
            // über eine Methode und den Integer referenzieren.
            Stopwatch.GetElapsedMillisecounds();

            result = 12345;
            GetDividedByRef(ref result);

            timeResult = Stopwatch.GetElapsedMicrosecounds();
            Debug.Print("Beispiel 4: " + timeResult +
                " Mikrosekunden, Freier Speicher: " + Debug.GC(true) + "kb");
            avarageExample4 += timeResult;

            // Beispiel mit einer Devisions Rechnung
            // über eine Methode aus einer anderen Klasse und den Integer kopieren.
            Stopwatch.GetElapsedMillisecounds();

            result = 12345;
            result = MethodeInOtherClass.GetDivided(result);

            timeResult = Stopwatch.GetElapsedMicrosecounds();
            Debug.Print("Beispiel 5: " + timeResult +
                " Mikrosekunden, Freier Speicher: " + Debug.GC(true) + "kb");
            avarageExample5 += timeResult;

            // Beispiel mit einer Devisions Rechnung
            // über eine Methode aus einer anderen Klasse und den Integer referenzieren.

            Stopwatch.GetElapsedMillisecounds();

            result = 12345;
            MethodeInOtherClass.GetDividedByRef(ref result);

            timeResult = Stopwatch.GetElapsedMicrosecounds();
            Debug.Print("Beispiel 6: " + timeResult +
                " Mikrosekunden, Freier Speicher: " + Debug.GC(true) + "kb");
            avarageExample6 += timeResult;

            if (countIteration > 100)
            {
                // Durchschnitts Ergebnisse
                Debug.Print("Beispiel 1, Durchscnittliche Zeit: " +
                    (avarageExample1 / countIteration) + " Millisekunde");
                Debug.Print("Beispiel 2, Durchscnittliche Zeit: " +
                    (avarageExample2 / countIteration) + " Mikrosekunden");
                Debug.Print("Beispiel 3, Durchscnittliche Zeit: " +
                    (avarageExample3 / countIteration) + " Mikrosekunden");
                Debug.Print("Beispiel 4, Durchscnittliche Zeit: " +
                    (avarageExample4 / countIteration) + " Mikrosekunden");
                Debug.Print("Beispiel 5, Durchscnittliche Zeit: " +
                    (avarageExample5 / countIteration) + " Mikrosekunden");
                Debug.Print("Beispiel 6, Durchscnittliche Zeit: " +
                    (avarageExample6 / countIteration) + " Mikrosekunden");
            }

            countIteration++;
        }
    }

   private static int GetDivided(int result)
    {
        return result / 67890;
    }
    private static void GetDividedByRef(ref int result)
    {
        result = result / 67890;
    }
}

public static class MethodeInOtherClass
{
    public static int GetDivided(int result)
    {
        return result / 67890;
    }
    public static void GetDividedByRef(ref int result)
    {
        result = result / 67890;
    }
}

image
Ermittelt eine Durchschnittszeit, da das Ergebnis schwankt.

Das erste Beispiel zeigt das Ergebnis in Millisekunden, jedoch reicht das für mein zeitkritisches Thema nicht aus. Daher zeigt das zweite Beispiel das Ergebnis in Mikrosekunden an. Sieht man sich die Durchschnittszeit an, dann passt diese Vorgangsrechnung praktisch 3 mal pro Millisekunde. Die Beispiele 3 bis 6 zeigen eher unwesentliche Unterschiede. Hier zeigt der Vorgang über eine Methode eine längere Zeitspanne von ca. 60 Mikrosekunden, egal wo sich die Methode befindet. Geht man die mehrmals errechneten Durchschnittszeiten durch, ist zu erkennen, dass die Ergebnisse schwanken. Vielleicht irre ich mich da, aber ich konnte erkennen, dass es nur beim Debuggen auftrat. Stabil blieben die Werte, nachdem ich die Ergebnisse immer über eine UART Verbindung an den PC gesendet habe und noch ein wenig besser wurden die Ergebnisse, wenn der Netduino über eine andere Spannungsversorgung von mindestens 7,5V angeschlossen wurde. Ich selbst verwendete ein 12V / 1A Schalt Netzteil oder einen 9V / mAh Block Akku für meine Anwendungen und Tests. Für die folgende Ausgabe habe ich noch 3 weitere Arten des Methodenaufrufes eingesetzt.

  • Beispiel 7 ist das Ergebnis über einen Methodenaufruf in einer anderen DLL.
  • Beispiel 8 ist das Ergebnis über einen Methodenaufruf mit zwei übergebenen Werten.
  • Beispiel 9 ist das Ergebnis über einen Methodenaufruf, wobei der referenzierte Integer vorher angelegt wird.
image
Ergebnis ohne Debug und Netduino weiterhin an USB angeschlossen.

Seltsamerweise war das Ergebnis unwesentlich besser.

image
Ergebnis mit einem 12V Schaltnetzteil, mit dem Akku sah die Ausgabe geradezu identisch aus.

Bei diesen Beispielen mit den verschiedenen Spannungsquellen, lohnt sich der Test allerdings erst, wenn der Netduino aufwendigere Aufgaben zu erledigen hat, auf dem weitere Module gesteckt sind und viele Algorithmen verwendet werden.

image
Akkus und Schaltnetzteile stellen zum Glück vernachlässigbare Unterschiede.

Zum Abschluss diesen Blog Eintrages bildet sich eines auf jeden Fall klar ab: Es kostet Zeit! Und je aufwendiger das Programm, um so exponentiell steigt die Möglichkeit der Optimierung und macht erst Sinn, wenn das Ziel diese Anforderung unausweichlich benötigt.

Beispiel Projekt zum Downloaden. (.NET Micro Framework 4.2)

Sonntag, 3. März 2013

Daten vom Arduino verarbeiten und visualisieren

image
Arduino Nano 3.0 und der Magnet Sensor HMC5883L.
Gelb=SDA (Pin4), Orange=SCL (Pin5), Rot=+5V und Schwarz=GND

Wenn ich keine Lösung für dem Netduino habe, dann finde ich eine für Arduino. Eines meiner ersten Programme war eines zum Lesen der Sensordaten. Zunächst verwendete ich den Arduino Monitor, der allerdings nichts anderes tat, als eine serielle Verbindung herzustellen. Mit ein “wenig” C# und .NET Framework Kenntnis kann auch ein eigenes Programm geschrieben werden, das die Daten ebenfalls lesen kann. Mein neues Bespiel zeigt die magnetischen Einflüsse auf 3 Achsen an, was ich mit Hilfe von Windows Forms visualisiere.

image
Windows Forms mit analoger Anzeige

Warum Windows Forms? Die GUI ist schon etwas in die Jahre gekommen und bietet kaum Neuerungen, dennoch kann man grafisch immer noch etwas an Ergebnissen erreichen. Also wäre es doch schön, eine analoge Anzeige auf digitalem Boden zu schaffen. Aber erstmal eins nach dem anderen.

Auf der Seite Gihub stellt der Entwickler Jeff Roberg seine Bibliotheken zur Verfügung für Zahlreiche Sensoren die mit dem Arduino angesprochen werden können. Für mein Beispiel wird der Ordner HMC5883L und die I2Cdev Inhalt benötigt, die dann in den Order “libraries” vom Arduino Compiler kopiert werden. Anschließend kann das mitgelieferte Beispiel “HMC5883L_Example” geöffnet und auf dem Arduino hochgeladen werden. Die Ausgabe erfolgt über die serielle Verbindung, die auf dem Arduino Monitor betrachtet werden kann.

image
Ausgabe über den Monitor

Nun zur PC Anwendung, die die Daten auswertet und sie in einer visuellen Form darstellt. Als erstes stellen wir die Verbindung sicher, und hier hilft uns das .NET Framework mit der Klasse “SerialPort”. Damit wir die Einstellungen auch später in der Anwendung anpassen können, benötigen wir eine kleine Setup Möglichkeit. Hier reichen die Auswahl des Ports und der Baudrate.

image
Das kleine Setup mit einem Button zum “Aktualisieren” und einem für “Verbinden”.

In “Form1.cs” Code Seite kommt zunächst die Event Methode, die durch einen Doppelklick auf dem Button im Designer erstellt wird. Dort schreiben wir etwas Code, um die Auswahl über die ComboBoxen auszufüllen.

private void buttonUpdate_Click(object sender, EventArgs e)
{
    comboBoxChooseCom.Items.Clear();
    foreach (string item in SerialPort.GetPortNames())
    {
       comboBoxChooseCom.Items.Add(item);
    }

    comboBoxChooseBaud.Items.Clear();
    comboBoxChooseBaud.Items.Add(9600);
    comboBoxChooseBaud.Items.Add(38400);
    comboBoxChooseBaud.Items.Add(115200);
}

Füllt die ComboBox mit den Einstellungen.

Die SerielPort Klasse wird nun als Member in der “Form1.cs” angelegt. Einen weiteren Button, der den Text “Verbinden” erhält, erstellen wir ebenfalls durch Doppelklick auf die Event Methode. Hier initialisieren wir die Klasse mit den Einstellungen aus der ComboBox. Damit die Anwendung fehlerfrei läuft, sollte vorher geprüft werden, ob eine Auswahl besteht. Nach erfolgreicher Verbindung muss noch ein neuer Event erzeugt werden, der immer ausgeführt wird, sobald am seriellen Port neue Daten verfügbar sind.

private void buttonConnect_Click(object sender, EventArgs e)
{
     if (!string.IsNullOrEmpty(comboBoxChooseCom.Text) &
          !string.IsNullOrEmpty(comboBoxChooseBaud.Text) &

          _SerialPort == null)
     {
         _SerialPort = new SerialPort(
                 comboBoxChooseCom.Text,
                 (int)comboBoxChooseBaud.SelectedItem,
                 Parity.None, 8, StopBits.One);
         _SerialPort.DataReceived +=
                 new SerialDataReceivedEventHandler(
                 _SerialPort_DataReceived);

         _SerialPort.Open();
     }
}

private void _SerialPort_DataReceived(
     object sender, SerialDataReceivedEventArgs e)
{

}

Mit den Einstellungen die Verbindung herstellen.

Jetzt kommen wir zum visuellen Teil der Anwendung, wofür wir ein neues UserControl anlegen, indem wir in unserem Projekt mit Rechtsklick das Kontextmenü öffnen und dann über Hinzufügen >  Neues Element.. das Benutzersteuerelement auswählen. Zuvor sollte ein entsprechender Name verwendet werden wie “AnalogView.cs” oder ähnliches. In meinem Bespiel hat das UserControl eine Größe von 200x100 Punkten, was über die Layout Eigenschaften festgelegt werden kann. Anschließend geht es auf der Code Seite weiter.

public partial class UserControlAnalogView : UserControl
{
   private Point _PointerValue = new Point(100, 20);
   private double _DisplayRawValue = 0.0;
    private double _MaximumValue = 90;

   public double Maxi_MinimumValue
    {
        set
        {
            _MaximumValue = value;
            _MinimumValue = 0 - _MaximumValue;
        }
       get { return _MaximumValue; }
    }

    public UserControlAnalogView()
    {
        InitializeComponent();
    }

    public void RenderAnalogDisplay()
    {
        SuspendLayout();

       Graphics g = CreateGraphics();
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

        Pen p = new Pen(new SolidBrush(Color.Black), 2);
        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;

        g.FillPie(Brushes.WhiteSmoke, 5, 5, 190, 190, 0, -180);

        g.DrawPie(p, 5, 5, 190, 190, 0, -180);

        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
        p.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
        g.DrawLine(p, new Point(100, 100), _PointerValue);

        ResumeLayout(false);
    }

    public double DisplayValue
    {
        set
        {
            _DisplayRawValue = value;

            double alpha = _DisplayRawValue / _MaximumValue;

            double w1 = Math.Cos(alpha) * 90;
            double w2 = Math.Sin(alpha) * 90;

            _PointerValue = new Point((100 + Convert.ToInt32(w2)), 100 - Convert.ToInt32(w1));

            RenderAnalogDisplay();
        }
        get { return _DisplayRawValue; }
    }
}

Der Codeabschnitt für das Erstellen der Analoganzeige. Die Methode “RenderAnalogDisplay()” zeichnet die Anzeige, und über “DisplayValue” wird die Zeigerposition neu ausgerichtet.

Das Benutzersteuerelement ist fertig und nach dem Drücken der F6 Taste steht es auch schon in unser Toolbox, wo wir es in der Designeransicht auf die Form1 ziehen können. Für jede Achse wird eine analoge Anzeige verwendet; man stelle in den Eigenschaften das Maximum auf 4000 ein.

image
Die Analoge Anzeige wird erst gerendert, wenn die Methode im UserControl “RenderAnalogDisplay()” ausgeführt wird.

Als nächstes kommt das Auslesen der empfangen Werte. Der Inhalt sind Konstanten und können durch eine Methode einfach ausgewertet werden.

Raw: 455  39  668  Scaled:  418.60  35.88  614.56  Heading:  0.13 Radians  7.52 Degrees 
Die Zeichenkette, die uns der Arduino nach dem lesen des Sensor übermittelt.

Im Beispiel beschränken wir uns auf die Raw Daten und programmieren entsprechend in die Event Methode vom SerialPort den Lesevorgang und schreiben das Ergebnis an die analogen Anzeigen.

private void _SerialPort_DataReceived(
      object sender, SerialDataReceivedEventArgs e)
{
        string content = _SerialPort.ReadLine();

       string[] sa = content.Split('\t');
       string[] sa2 = sa[1].Split(' ');

        int[] ia = new int[3];
        int axis = 0;
        for (int i = 0; i < sa2.Length; i++)
        {
            if (!string.IsNullOrEmpty(sa2[i]))
            {
               int.TryParse(sa2[i], out ia[axis]);
                axis++;
            }
        }

        userControlAnalogView1.DisplayValue = ia[0];
        userControlAnalogView2.DisplayValue = ia[1];
        userControlAnalogView3.DisplayValue = ia[2];
    }
}

Methode für das Einlesen der Raw Daten.

An dieser Stelle sind wir mit dem Beispiel fertig und die Anwendung kann nun über F5 gestartet werden. Mit Aktualisieren erhalten wir die verfügbaren COM Verbindungen und wählen natürlich die vom Arduino.

Nähert man sich auf einige Zentimeter an den Sensor bewegen sich die Zeiger.

Mit einem Magneten können wir das Magnetfeld um den Sensor manipulieren und sehen, wie die Zeiger ausschlagen. Mit ein paar weiteren Codezeilen lässt sich auch der Rest der Daten anzeigen, allerdings verbleibe ich dabei, mich möglichst kurz zu halten.

Zuletzt darf die bereits fertiggestellte und kommentierte Solution des kleinen Tools nicht fehlen:

Arduino Nano 3.0 und MPU6050 in Quellcode Kurzfassung

image
Verkabelungsbeschreibung ist am ende dieses Blogs.

Bereits Anfang des Jahres habe ich eine Lösung in C#  und .NET Micro Framework geschrieben. Ursprünglich habe ich die Lösung aus der in C++ geschriebenen Library portiert (Library für Arduino). Zuvor suchte ich nach einem kürzeren und funktionalem Beispiel, aber ohne Erfolg. Da ich nun selbst relativ gut mit dem Sensor vertraut bin, konnte ich ein kürzeres Beispiel für den Arduino schreiben.

#include <Wire.h>

#define sensor_address 0x68

void setup()
{
  // 9600 Baut reichen zum Betrachten aus.

  Serial.begin(9600);
  Wire.begin();

  delay(1000);
 
  // Powermanagement aufrufen
  // Sensor schlafen und Reset, Clock wird zunächst von Gyro-Achse Z verwendet
 
  Serial.println("Powermanagement aufrufen - Reset");
  SetConfiguration(0x6B, 0x80);
 
  // Kurz warten
  delay(500);
 
  // Powermanagement aufrufen
  // Sleep beenden und Clock von Gyroskopeachse X verwenden

  Serial.println("Powermanagement aufrufen - Clock festlegen");
  SetConfiguration(0x6B, 0x03);
 
  delay(500);
 
  // Konfigruation  aufrufen
  // Default => Acc=260Hz, Delay=0ms, Gyro=256Hz, Delay=0.98ms, Fs=8kHz

  Serial.println("Konfiguration aufrufen - Default Acc = 260Hz, Delay = 0ms");
  SetConfiguration(0x1A, 0x00);
 
  // Leerzeichen
  Serial.println("");
}

void loop()
{
  byte result[14];
  // Anfangs Adresse von Beschleunigungssensorachse X
  result[0] = 0x3B;
 
   // Aufruf des MPU6050 Sensor
  Wire.beginTransmission(sensor_address);
  // Anfangsadresse verwenden.
  Wire.write(result[0]);
  Wire.endTransmission();
  // 14 Bytes kommen als Antwort
  Wire.requestFrom(sensor_address, 14);
  // Bytes im Array ablegen
  for(int i = 0; i < 14; i++)
  {
    result[i] = Wire.read();
  }
 
  // Zwei Bytes ergeben eine Achsen Wert und könenn

// per Bit Shifting  zusammengelegt werden.
 
  // Beschleunigungssensor
  int acc_X = (((int)result[0]) << 8) | result[1];
  int acc_Y = (((int)result[2]) << 8) | result[3];
  int acc_Z = (((int)result[4]) << 8) | result[5];
 
  // Temperatur sensor
  int temp = (((int)result[6]) << 8) | result[7];
 
  // Gyroskopesensor
  int gyr_X = (((int)result[8]) << 8) | result[9];
  int gyr_Y = (((int)result[10]) << 8) | result[11];
  int gyr_Z = (((int)result[12]) << 8) | result[13];
 
  // Ausgabe
  Serial.print("ACC X:\t");
  Serial.print(acc_X); Serial.print("\t");
  Serial.print("Y:\t");
  Serial.print(acc_Y); Serial.print("\t");
  Serial.print("Z:\t");
  Serial.print(acc_Z); Serial.print("\t");
  Serial.print("Temperatur:\t");
  Serial.print(temp); Serial.print("\t");
  Serial.print("Gyroskope X:\t");
  Serial.print(gyr_X); Serial.print("\t");
  Serial.print("Y:\t");
  Serial.print(gyr_Y); Serial.print("\t");
  Serial.print("Z:\t");
  Serial.print(gyr_Z); Serial.println("\t");
}

void SetConfiguration(byte reg, byte setting)
{
   // Aufruf des MPU6050 Sensor
  Wire.beginTransmission(sensor_address);
  // Register Aufruf
  Wire.write(reg);
  // Einstellungsbyte für das Register senden
  Wire.write(setting);
  Wire.endTransmission();
}

Kurzfassung für den Sensor Aufruf, ohne weitere Library.

Mit dem Quellcode wird der Sensor mit den Standardwerten initialisiert, so dass die Empfindlichkeit des Gyroskop bei 250°/s und der Beschleunigungssensor bei 2g liegt. Steht der Sensor still auf dem Tisch sollten die Werte für das Gyroskop und den Beschleunigungssensor für X und Y Achse nahe bei 65536 oder 0 liegen. Beschleunigung auf der Z-Achse bei ca. 16000.

image
Ausgabe über dem Arduino Monitor

Ich hoffe, ich konnte somit dem einen oder anderen mit diesem kleinen Bespiel weiterhelfen. Bei Fragen kann gerne die Kommentarfunktion verwendet werden.

Anschluss Beschreibung:

  • Arduino Nano 3.0 – Pin4 –> MPU6050 Pin SDA
  • Arduino Nano 3.0 – Pin5 –> MPU6050 Pin SCL
  • Arduino Nano 3.0 – Pin5V –> MPU6050 Pin VCC
  • Arduino Nano 3.0 – PinGND –> MPU6050 Pin GND

 

Zuletzt geändert 20.11.2014

Sonntag, 3. Februar 2013

UART WiFi Server Client Module kann auch senden

Netduino_WiFi_01
Netduino Plus, Shield, WiFi Module und der Sensor

Diesmal möchte ich ein Beispiel beschreiben, in dem nicht der Netduino der Empfänger ist, sondern der Sender, ohne dass auf die Serverfunktion verzichtet werden muss. Der Quellcode vom letzten Blog-Post wird an Server und Client seitig erweitert. Am Server (Netduino) Quellcode ist dies sogar recht simpel, da man an dieser Stelle nur vier weitere Zeilen Code hinzufügen muss:

while (true)
{
      byte[] message = Encoding.UTF8.GetBytes("DateTime: " + DateTime.Now.ToString());
      _SerialPort.Write(message, 0, message.Length);
      Thread.Sleep(1000);
}

Relativ simpel fällt der Code für das Senden aus.

In der “While”-Schleife ist nun zu sehen, dass ich die aktuelle Zeit als String wiedergebe und anschließend in ein Byte Array umwandle, um es für den Schreibvorgang vorzubereiten. Die “Write”-Methode selbst erledigt den Rest und gibt den Inhalt an das WiFi Modul weiter.

Am Client jedoch ist ein wenig mehr Arbeit notwendig. Wenn die Daten ankommen, muss auch ein Event erfolgen, um die Daten zu verarbeiten und schließlich an die Anwendung auszugeben.

image
Das Programm mit einem zusätzlichen TextBlock für den Empfang.

Für den Empfang der PC Anwendung habe ich eine recht einfache Lösung verwendet, die auch leicht nachvollziehbar ist. Leider ist diese aber nicht ideal, da es eventuell zur Vollauslastung kommt, wenn hier die Schleife nicht etwas gebremst wird. Ein besseres Beispiel gibt es auf MSDN, wie hier mit Hilfe der Socket Klasse ein Event erstellt werden kann.

Nun zur Erweiterung in der Client Socket Klasse, wo eine weitere Methode später im Thread ausgeführt wird. Die Thread-Klasse selbst kommt in den Member Bereich mit dem Namen “_ThreadWaitReceive”. Der Grund hierfür ist, dass später beim Schließen der Klasse der Thread beendet werden muss. Sonst läuft der Thread weiter, obwohl die Anwendung selbst lange beendet wurde.

private Thread _ThreadWaitReceive;

private void WaitReceive(object obj)
{
     byte[] buffer = new byte[1024];

     while (_Socket != null && buffer.Length > _Socket.Receive(buffer, SocketFlags.None))
     {
          string message = Encoding.UTF8.GetString(buffer).Replace('\0', ' ').Trim();
          ReceiveEvent(message);
          buffer = new byte[1024];
     }
}

Die zusätzliche Methode für den Empfang.

Mit der Encoding wird der String eingelesen und über die Methoden Replace() und Trim()  werden die leeren Inhalte gefiltert. Mit der folgenden Zeile wird dann der Inhalt an die Event Methode übergeben.

public delegate void NetworkReceiveEventHandler(object sender, NetworkStatusEventArgs e);
public event NetworkReceiveEventHandler NetworkReceiveEvent;
public virtual void ReceiveEvent(string message)
{
    if (NetworkReceiveEvent != null & !string.IsNullOrEmpty(message))
    {
         NetworkReceiveEvent(this, new NetworkStatusEventArgs(message.Trim()));
    }
}

Die Methode, mit Handler und Event.

Mit dem Event geht es weiter an die Oberfläche, um den Inhalt des Empfang in einem TextBlock lesen zu können.

public MainWindow()
{
    InitializeComponent();

    _Client.NetworkReceiveEvent += new ClientController.NetworkReceiveEventHandler(_Client_NetworkReceiveEvent);
}

private void _Client_NetworkReceiveEvent(object sender, NetworkStatusEventArgs e)
{
    Dispatcher.BeginInvoke(new WriteReceiveHandler(WriteReceive), new object[] { e.Message });
}

private delegate void WriteReceiveHandler(string message);

private void WriteReceive(string message)
{
    textBlockDataReceive.Text = message;
}

Event zugewiesen, Handler für die Oberfläche und Methode, um in die Textbox zu schreiben.

Mit ein paar Code Zeilen Ergänzungen ist nun die PC Anwendung und der Netduino imstande, in beide Richtungen zu kommunizieren. Sicherlich gibt es hier etwas besseres, als sich nur das DateTime anzuzeigen. Zwinkerndes Smiley

Wie bereits zuvor gibt es wieder das Ganze in zwei fertigen Solutions, mit den Quelltexten und natürlich auch den Kommentaren.

Die Solutions für den Download:

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