Seite 1 von 1

Kleine Zeitintervalle messen

Verfasst: Fr 17. Sep 2021, 12:13
von Heinrichs
Kleine Zeitintervalle zu messen, dieses Problem taucht bei der Prozessdatenverarbeitung immer wieder auf. Als Beispiel sei hier nur die Auswertung von PWM-Signalen genannt. Die Vorgehensweise ist im Prinzip recht einfach: Man speichert einen Zeitstempel t0 für den Anfang des Zeitintervalls ab und ebenso einen Zeitstempel t1 für das Ende des Zeitintervalls. Anschließend wird die Differenz t1 - t0 gebildet.

Wie so häufig steckt der Teufel im Detail, insbesondere wenn es es sich um kleine Zeitspannen handelt. Am Beispiel der Auswertung eines PWM-Signals möchte ich dies deutlich machen und natürlich auch eine Lösung vorstellen. Für die folgenden Untersuchungen benutzte ich ein PWM-Signal, welches ich mit einem Atmega328 erzeugte. Mit einem Logik-Analysator bestimmte ich zunächst möglichst genaue Werte für die Dauer der High- bzw. Low-Phase:

Code: Alles auswählen

Pegel      Logikanalysator 
-----------------------------
High       7,510 ms           
Low        0,501 ms           
Ganz bewusst hatte ich dafür gesorgt, dass sich die Längen der beiden Phasen stark unterscheiden.

Die Messung der Zeitspannen für die High und Low-Phasen gliedert sich in mehrere Schritte:
  1. Warten auf den Wechsel des Signal von High auf Low (negative Flanke)]
  2. Länge der Low-Phase bestimmen
  3. Länge der High-Phase bestimmen
  4. Ausgabe der Zeitintervalle auf dem Display
Da die Ausgabe auf den Display Zeit kostet, empfiehlt es sich, für die nächste Messung wieder mit dem Schritt 1 zu beginnen.


Erstes Programm

Code: Alles auswählen

from time import sleep, time_ns
from machine import Pin, SPI
import vga1_8x8 as font1
import st7789

# Konfiguration Eingangspin
pwm_signal = Pin(2, Pin.IN) #PWM-Signal von Atmega328p (Nano-Board)

# Konfiguration/Initialisierung Display
spi = SPI(2, baudrate=20000000, polarity=1, sck=Pin(18), mosi=Pin(19))
display = st7789.ST7789(spi, 135, 240,  reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT), rotation=3)
# Landscape
display.init()
display.fill(0)  # loeschen; Bildschirm schwarz
display.text(font1,'PWM',2,10,st7789.YELLOW)

def detect_H_L(): # auf negative Flanke warten
    # Falls pwm_signal = Low, dann warten, bis pwm_signal = High
    while not pwm_signal.value():
        pass
    # Jetzt genauso das Ende der High-Phase abwarten
    while pwm_signal.value():
        pass
    # jetzt ist das Ende der High-Phase, d. h. die negative Flanke erreicht
    
def measure_low_phase():
    t0 = time_ns()
    while not pwm_signal.value():
        pass
    t1 = time_ns()
    deltat = t1-t0
    return deltat
    
def measure_high_phase():
    t0 = time_ns()
    while pwm_signal.value():
        pass
    t1 = time_ns()
    deltat = t1-t0
    return deltat    

# Hauptprogramm
while True: 
    sleep(zeit) # Zwischen zwei Messungen warten; kann entfernt werden   
    detect_H_L()
    low_phase = measure_low_phase()
    high_phase = measure_high_phase()
    display.text(font1,'Low:  '+str(low_phase/1E6) + ' ms    ',2,30,st7789.YELLOW) # Ausgabe der Zeit in ms
    display.text(font1,'High: '+str(high_phase/1E6) + ' ms    ',2,40,st7789.YELLOW)

Die erhaltenen Werte sind recht ungenau; das mag auch daran liegen, dass die Funktion time_ns recht große Zahlen liefert, für die dann Speicherplatz auf dem Stapel alloziert werden muss.


Zweites Programm

Deswegen habe ich mich dann entscheiden mit der Funktion ticks_us aus dem Modul utime zu arbeiten. Dies liefert Werte im Mikrosekunden-Bereich, die leichter zu verarbeiten sind. Außerdem bietet sie mit der Funktion ticks_diff() eine sehr rasch arbeitende Methode zur Berechnung der Zeitspanne: deltat = ticks_diff(t1, t0). Diese Anweisung muss direkt auf die Bestimmung von t1 folgen!

Die gemessenen Werte schwanken leicht; hier ein typisches Messergebnis:

Code: Alles auswählen

Pegel       Logikanalysator      TTTGO         Differenz
--------------------------------------------------------
High        7,510 ms             7,39 ms       0,12 ms
Low         0,501 ms             0,42 ms       0,08 ms

Der Gebrauch von Funktionen schafft zwar mehr Übersicht, kostet aber auch Zeit. Das folgende ausführlich kommentierte Programm benutzt wieder die Funktionen ticks_us() und ticks_diff(), verzichtet nun aber auf den Einsatz von selbst definierten Funktionen:


Drittes Programm

Code: Alles auswählen

from time import sleep
from utime import ticks_us, ticks_diff

from machine import Pin, SPI
import vga1_8x8 as font1
import st7789

# Konfiguration Eingangspin
pwm_signal = Pin(2, Pin.IN) # PWM-Signal von Atmega328p (Nano-Board)

# Konfiguration/Initialisierung Display
spi = SPI(2, baudrate=20000000, polarity=1, sck=Pin(18), mosi=Pin(19))
display = st7789.ST7789(spi, 135, 240,  reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT), rotation=3)
# Landscape
display.init()
display.fill(0)  # loeschen; Bildschirm schwarz
display.text(font1,'PWM',2,10,st7789.YELLOW)

# Hauptprogramm
zeit = 0.5   # Zeit zwischen zwei Messungen

while True: # Endlosschleife
    sleep(zeit) # Zwischen zwei Messungen warten; kann entfernt werden

    # Auf negative Flanke warten:
    # Mit while-Schleife auf High-Phase warten
    while not pwm_signal.value(): # pwm_signal.value() liefert die Werte 1 (High) bzw. 0 (Low); diese werden von Python auch als True bzw. False gedeutet
        pass # nichts tun (solange wie pwm-signal = Low)
    # Jetzt ist Low-Phase beendet; nun genauso das Ende der High-Phase abwarten
    while pwm_signal.value():
        pass
    
    # Jetzt ist High-Phase beendet und wir messen nun die Dauer der Low-Phase:
    t0 = ticks_us() # erster Zeit-Stempel in Mikrosekunden
    while not pwm_signal.value(): 
        pass # nichts tun (solange wie pwm-signal = Low)
    t1 = ticks_us() # zweiter Zeit-Stempel in Mikrosekunden
    low_phase = ticks_diff(t1, t0) # Dauer des Zeitintervalls in us (Die Mycropython-Dokumentation rät dazu, die Differenz so und nicht mit t1 - t0 zu berechnen.
    
    # Jetzt ist die Low-Phase beendet und wir messen nun die Dauer der High-Phase: 
    t0 = t1 # t1 gibt das Ende der Low-Phase und gleichzeitig den Anfang der High-Phase an.
    while pwm_signal.value():
        pass
    t1 = ticks_us()
    high_phase = ticks_diff(t1, t0)

    # Ausgabe in ms:
    display.text(font1,'Low:  '+str(low_phase/1E3)+' ms    ',2,30,st7789.YELLOW)
    display.text(font1,'High: '+str(high_phase/1E3)+' ms    ',2,40,st7789.YELLOW)
    # Ausgabe der Werte kostet Zeit; deswegen sicherheitshalber am Anfang der Schleife wieder auf die negative Flanke warten...
    # Wenn die Dauer der High-Phase nicht gemessen wird, dann kann man in der Schleife ggf. auf den Programm-Teil "Auf negative Flanke warten:" verzichten.
    # Allerdings sollte man dann diesen Programm-Teil vor die Endlos-Schleife platzieren. 
Auch hier schwanken die Messergebnisse; dies dürfte darauf zurückzuführen sein, dass der Interpreter während der Messung auch andere Aufgaben ausführt. Eine Messreihe mit 100 Messungen ergab für den Mittelwert (MW) der High- und Low-Phasen sowie für die Standardabweichung die folgenden Werte:

Code: Alles auswählen

Pegel      Logikanalysator      TTGO (MW)        Standardabweichung vom MW
---------------------------------------------------------------------------
High       7,510 ms             7,506 ms         0,008 ms 
Low        0,501 ms             0,494 ms         0,009 ms