Windows 10 IoT Digital I/O Performance, Teil 2

2. November 2016

Im ersten Teil der Serie wurden grundlegende Aspekte der Digital I/O Performance von Windows 10 IoT diskutiert und die rohe Verarbeitungsgeschwindigkeit gemessen. In diesem Teil wird die Fragestellung untersucht, wie schnell Windows 10 IoT auf einem Raspberry Pi 2 oder 3 auf Änderungen eines externen Hardwaresignals reagieren kann.

Dies ist beim Einsatz vieler Hardwarebausteine von entscheidender Bedeutung, da hier häufig vom Datenblatt eng definierte Timings bei der Signalverarbeitung einzuhalten sind.

Implementierung Rotary Encoder

In einem konkreten Projekt des Autors wird ein digitaler Drehgeber oder Rotary Encoder zur Einstellung von Parametern und Navigation im Menü verwendet. Rotary Encoder ersetzen klassische Potentiometer und erzeugen bei Drehen des Gebers digitale Signale, die sich nach Drehrichtung (in oder gegen den Uhrzeigersinn) unterscheiden. Je nach Auflösungsvermögen des Encoders kann eine komplette Drehung um 360° 10000 und mehr Impulse erzeugen. Der aktuell im Projekt verwendete Encoder stellt für die Übermittlung von Drehimpulsen zwei digitale Ausgangssignale Clock und Data zur Verfügung, die vom Raspberry Pi 2 über Digitaleingänge verarbeitet werden müssen. Eine steigende Flanke von Clock leitet einen Drehimpuls ein. Eine fallende Flanke von Clock beendet den Drehimpuls. Die Drehrichtung wird dann vom Level des Data Signals bei fallender Flanke von Clock definiert. Ist Data low, wurde der Encoder im Uhrzeigersinn gedreht. Ist Data high, erfolgte eine Drehung gegen den Uhrzeigersinn. Das folgende Diagramm verdeutlicht den Signalverlauf, wobei A dem Signal Data und B dem Signal Clock entspricht:

Encoder Timing

Der Signalverlauf wird in 4 Phasen unterteilt:

1. Beginn der Drehung, Data wird je nach Drehrichtung eingestellt.
2. Ansteigende Flanke von Clock, Signal an den Rechner, dass eine Drehbewegung stattfindet.
3. Data wird invertiert.
4. Fallende Flanke von Clock, Bewegung ist beendet, Data gibt die Drehrichtung an.

Für die Signalverarbeitung ist entscheidend, dass die steigende Flanke von Clock mit Phase 2 einen Eventhandler im Programmcode anstößt. Der Eventhandler wartet bis zur fallenden Flanke von Clock in Phase 4, liest währenddessen fortwährend den Zustand von Data und signalisiert bei Ende eine Drehbewegung in die ermittelte Drehrichtung. Wie im ersten Teil dargestellt, ermöglicht die Klasse GpioPin über das Event ValueChanged auf Signaländerungen an einem Digitaleingang zu reagieren. Der folgende C# Code implementiert genau dieses Verfahren (Achtung: Das Clock Signal liegt invertiert an):

   1: /// <summary>

   2: /// Event handler triggered on rotary encoder's clock signal changes. Note: Signals have inverted logic.

   3: /// </summary>

   4: /// <param name="sender"></param>

   5: /// The sending rotary encoder clock pin

   6: /// <param name="args">The pin state changed event args</param>

   7: private void PinClockValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)

   8: {

   9:     if (Rotated != null) {

  10:         // Only do something if there are listening event consumers

  11:         if (args.Edge == GpioPinEdge.FallingEdge && this.pinClock.Read() == GpioPinValue.Low) {

  12:             // Only do something, if a falling edge of the pin signal is detected

  13:             GpioPinValue data;

  14:

  15:             // Get valid signal on data pin as long as clock signal remains low. Then rotation direction

  16:             // is determined by data pin signal.

  17:             do {

  18:                 data = this.pinData.Read();

  19:             } while (this.pinClock.Read() == GpioPinValue.Low);

  20:

  21:             // Notify event listeners about switch rotation. Rotation is determined by data pin value on rising edge

  22:             // of the clock pin

  23:             Rotated(data == GpioPinValue.High ? Rotation.CounterClockwise : Rotation.Clockwise);

  24:         }

  25:     }

  26: }

Rotated ist dabei ein Event, das von der Klasse zum Management des Rotary Encoders bereitgestellt wird, um Subscriber über eine Drehbewegung zu benachrichtigen.

Es leuchtet ein, dass die Verarbeitung der der Rotary Encoder Impulse nur funktionieren kann, wenn der Eventhandler sicher in Phase 2 und deutlich vor Phase 4 aufgerufen wird. Nur dann besteht die Chance, Drehimpulse mit ihrer Drehrichtung korrekt zu erkennen. Beim vom Autor eingesetzten Encoder liegt die Dauer von Phase 2 bis Phase 4 bei ca. 500µs. Diese Dauer kann je nach Auflösung des eingesetzten Bausteins variieren.

Messung

Unter Windows 10 IoT 1507 war es nicht möglich, den Rotary Encoder fehlerfrei zu betreiben. Daher wurde eine Messung der Antwortzeiten durchgeführt. Dafür wurde ein Digitalausgang (#5) direkt mit einem Digitaleingang (#6) des Raspberry Pi verbunden, mit Schreiben des Ausgangs eine Stoppuhr gestartet und bei Erreichen des Eventhandlers die abgelaufene Zeit ausgelesen. Dies wird über die folgende Methode erreicht:

   1: /// <summary>

   2: /// Does the event timing measurment.

   3: /// </summary>

   4: /// <returns></returns>

   5: public async Task DoEvents()

   6: {

   7:     var gpioController = await GpioController.GetDefaultAsync();

   8:     AutoResetEvent goOn = new AutoResetEvent(false);

   9:     // Open pin #6 as digital input

  10:     using (var gpioReader = gpioController.OpenPin(6, GpioSharingMode.Exclusive)) {

  11:         gpioReader.SetDriveMode(GpioPinDriveMode.Input);

  12:         // Open pin #5 as digital output

  13:         using (var gpioWriter = gpioController.OpenPin(5, GpioSharingMode.Exclusive)) {

  14:             gpioWriter.SetDriveMode(GpioPinDriveMode.Output);

  15:             // Init output

  16:             var outputValue = GpioPinValue.High;

  17:             gpioWriter.Write(outputValue);

  18:

  19:             // Define event handler measuring time to process input signal change

  20:             gpioReader.ValueChanged += (sender, args) => {

  21:                 this.PerformanceCounter.Measure();

  22:                 Task.Delay(500).Wait();

  23:                 goOn.Set();

  24:             };

  25:             this.PerformanceCounter.Reset();

  26:

  27:             // Loop until measurements are stopped

  28:             while (this.isMeasurementActive) {

  29:                 // Negate output, trigger signal change on input #6

  30:                 outputValue = outputValue == GpioPinValue.High ? GpioPinValue.Low : GpioPinValue.High;

  31:                 this.PerformanceCounter.Pulse();

  32:                 gpioWriter.Write(outputValue);

  33:                 goOn.WaitOne();

  34:             }

  35:         }

  36:     }

  37:     this.stopMeasurement.Set();

  38: }

Die Messung ergibt für alle untersuchten Windows 10 IoT Versionen (1507, 1511 und 1608) einen durchschnittlichen Wert von ca. 300µs als Zeit, die von der Änderung des Eingangssignals bis zum Erreichen des Eventhandlers benötigt wird. Damit ist eine sichere Verarbeitung der Encoder Signale eigentlich sichergestellt. Allerdings ist bekanntermaßen die Implementierung des ValueChanged Events unter 1507 nicht stabil, was die Probleme mit dieser Version erklärt.

Bewertung

Anders als beim direkten Lesen oder Schreiben der Digital I/Os hat Microsoft hier im Versionsverlauf keine Verbesserung der Performance erreicht. Auch hier gilt wie in Teil 1 beschrieben, dass dies unter anderen Betriebssystemen deutlich performanter geht. So sind bei Einsatz eines Kernelmoduls unter Linux Antwortzeiten von 15µs erreichbar. Dies ist um einen Faktor 20 besser als Windows 10 IoT und lässt Letzteres als Alternative bei der Umsetzung von Hardwareprojekten ausscheiden.

Im dritten und letzten Teil der Serie wird die Lightning Bibliothek vorgestellt. Sie hat sich zur Aufgabe gemacht, die Performance von Windows 10 IoT bei der Verarbeitung von Hardwaresignalen deutlich zu verbessern.