Mein Blog    Projekte    Archiv    Impressum

EKGUI 2 - Dynamische Anzeige funktioniert

Zuletzt kam ja die Frage auf, ob das Betriebssystem bei der seriellen Schnittstelle Puffer einsetzt, welche für die Trägheit der Anzeige verantwortlich sind. Das lässt sich beantworten, indem man ein Skript schreibt welches Zeilenweise die erfassten Werte direkt ausgibt.

#!/usr/bin/python3

import serial
# python3 -m pip install pyserial

ser = serial.Serial('/dev/ttyUSB0' , 9600)  # Schnittstelle öffnen.
print(ser.name)         # Kontrollieren welcher Port verwendet wurde.

for i in range(0,100):
    zeile = []
    for j in range(0, 4):
        var = ser.read(1)
        zeile.append(int.from_bytes(var, byteorder='big') )
    print(zeile)

ser.close()             # Schnittstelle schließen.

Dann habe ich das EKG an- und wieder ausgeschalten, und erst dann das Skript gestartet. Wären Werte in einem Puffer gewesen, dann wäre direkt mit einigen Zeilen Ausgabe zu rechnen gewesen. Diese blieben allerdings aus. Von daher muss die Trägheit der Anzeige durch etwas anderes verursacht werden.

Neuer Ansatz

Da die Verzögerung nicht aus dem Buffer stammt, liegt die Vermutung nahe dass das Problem durch eine falsche Benutzung der matplotlib verursacht wurde. Nach einigem Lesen in deren Dokumentation und einem neuen Aufbau meines Skriptes waren Plots dann auch ohne die Verzögerung möglich.

Die Verzögerung kam daher zustande, dass mit jedem neuen Datensatz (d.h. ein Wert pro Kanal) der Graph aktualisiert wurde. Jedoch dauert diese Aktualisierung länger als das holen des nächsten Datensatzes, so dass es zu einem Rückstau kommt (welcher dann immerhin in einem Buffer von pyserial gehalten wird, so dass zumindest keine Daten verloren gehen).

Die Lösung für dieses Problem ist es den Graphen nicht bei jedem Datensatz, sondern nur nach dem Empfang einer bestimmten Anzahl Datensätze, zu aktualisieren. Diese Anzahl ist im folgenden Skript als “Schrittweite” bezeichnet. Die Anzahl an Schritten welche in einem Fenster angezeigt werden (bevor sie mit neueren Werten überschrieben werden) wird durch die Variable “Fensterschritte” angegeben.

Mit der Kombination von 20 Fensterschritten und einer Schrittweite von 50 sind zwar die einzelnen Schritte mit bloßem Auge gut erkennbar, dafür ist die Latenz jedoch kleiner als ein Schritt und somit vernachlässigbar. Sämtliche Einflüsse auf die Elektroden sind sofort auf dem Bildschirm sichtbar, was ja eine der Anforderungen war. Stellt man die Schrittweite auf 5 und lässt dafür 200 Fensterschritte machen (damit weiterhin 1000 Datenpunkte angezeigt werden und der Maßstab der selbe bleibt), so wird anteilig zu viel Zeit auf das erneuern des Graphen verwendet, und die Latenz nimmt mit jedem Schritt zu – ein Modus der fürs Experimentieren völlig ungeeignet ist.

#!/usr/bin/python3
import serial # python3 -m pip install pyserial
import numpy as np
import matplotlib.pyplot as plt

# Konfiguration:
schrittweite = 50
fensterschritte = 20
fensterbreite = schrittweite * fensterschritte

# Serielle Schnittstelle vorbereiten und öffnen:
ser = serial.Serial('/dev/ttyUSB0' , 9600)  # Schnittstelle öffnen.
ser.flushInput()
ser.flushOutput()
print(ser.name)         # Kontrollieren welcher Port verwendet wurde.

# Auf korrekte Startposition warten:
dump = 0
while dump < 230:
    dump  = ser.read(1)[0]
    print(".")

# Datenerfassung vorbereiten:
kanal = [ [0]*fensterbreite , [2]*fensterbreite, [5]*fensterbreite ] 

# Plot vorbereiten
plt.ion()

fig1 = plt.figure(1)

fig1.add_subplot(311)
line1, = plt.plot(kanal[0])
plt.ylim([0, 255])

fig1.add_subplot(312)
line2, = plt.plot(kanal[1])
plt.ylim([0, 255])

fig1.add_subplot(313)
line3, = plt.plot(kanal[2])
plt.ylim([0, 255])

line1.set_xdata(np.arange(fensterbreite))
line2.set_xdata(np.arange(fensterbreite))
line3.set_xdata(np.arange(fensterbreite))

# Daten erfassen und zeichnen:
schrittzaehler = 0
while True:
    if schrittzaehler == fensterschritte:
        schrittzaehler = 0
    for i in range(0, schrittweite):
        zeile = []
        for j in range(0,4):
            zeile.append(ser.read(1)[0])
        del zeile[-1]
        kanal[0][i+schrittweite*schrittzaehler] = zeile[0]
        kanal[1][i+schrittweite*schrittzaehler] = zeile[1]
        kanal[2][i+schrittweite*schrittzaehler] = zeile[2]
    line1.set_ydata(kanal[0])
    line2.set_ydata(kanal[1])
    line3.set_ydata(kanal[2])
    plt.draw()
    schrittzaehler += 1

# Wird wegen der Enlosschleife nie erreicht:
ser.close()             # Schnittstelle schließen.

Todo

Damit ist eine der ursrünglich definierten Anforderungen erfüllt. Es verbleiben noch die Anforderungen Pulsmessung, Datenspeicherung, und Konfiguration über GUI umzusetzen.