Nulldurchgang einer lauten Sinuswelle

9

Ich versuche, die Nulldurchgänge einer Sinuswelle zu finden, um die Sinuswelle in eine Rechteckwelle umzuwandeln. Das einzige Problem ist, dass die Sinuswelle laut ist, so dass ich viel Jitter und falsche Nulldurchgänge bekomme.

Kann jemand einen einfachen Pseudocode oder relevante Materialien empfehlen? Bisher habe ich so etwas:

if (sample[i]>0 && sample[i+1]<0) || (sample[i]<0 && sample[i+1]>0)

Kann jemand eine robustere Methode empfehlen?

Kevin Nasto
quelle
Was ist der Zweck von dir, es zu einer Rechteckwelle zu machen? Versuchen Sie herauszufinden, wo das Signal beginnt und endet? Wenn Sie sind, kann ich eine Methode empfehlen.
Spacey
if ((sample [i] * sample [i + 1]) <0) zero_crossing ++;
Marius Hrisca

Antworten:

8

Sie können versuchen, das Eingangssignal tief zu filtern, um glattere Nulldurchgänge zu erzielen (oder sogar eine Bandpassfilterung, wenn Sie eine gute Vorstellung vom Frequenzort der Sinuswelle haben). Das Risiko besteht darin, dass die zusätzliche Verzögerung des Filters ein Problem darstellt, wenn probengenaue Phaseninformationen für Ihre Anwendung wesentlich sind.

Ein anderer Ansatz: Anstatt zu versuchen, die Sinuswelle in eine Rechteckwelle umzuwandeln, wie wäre es, wenn ein unabhängiger Rechteckwellenoszillator sich in Phase / Frequenz mit der Sinuswelle ausrichtet? Dies kann mit einem Phasenregelkreis erfolgen .

Pichenettes
quelle
6

Was Sie sicherlich gezeigt haben, ist ein Nulldurchgangsdetektor. Ein paar Dinge fallen Ihnen ein, die Ihre Situation verbessern könnten:

  • Wenn Sie Rauschen haben, das außerhalb des Bandes Ihres Signals liegt (was mit ziemlicher Sicherheit der Fall ist, da Ihr Eingang ein reiner Ton ist), können Sie das Signal-Rausch-Verhältnis verbessern, indem Sie ein Bandpassfilter um das interessierende Signal anwenden . Die Durchlassbandbreite des Filters sollte basierend darauf ausgewählt werden, wie genau Sie die Sinusfrequenz a priori kennen . Durch Reduzieren des auf der Sinuskurve vorhandenen Rauschens wird die Anzahl falscher Nulldurchgänge und deren Jitter um die korrekten Kreuzungszeiten verringert.

    • Nebenbei bemerkt, wenn Sie im Voraus keine guten Informationen haben, können Sie eine ausgefeiltere Technik verwenden, die als adaptiver Linienverstärker bekannt ist. Wie der Name schon sagt, handelt es sich um einen adaptiven Filter, der ein periodisches Eingangssignal verbessert. Dies ist jedoch ein etwas fortgeschrittenes Thema, und Sie haben normalerweise eine gute Vorstellung von der Frequenz Ihres Signals, sodass ein solcher Ansatz nicht erforderlich ist.
  • In Bezug auf den Nulldurchgangsdetektor selbst können Sie dem Prozess eine gewisse Hysterese hinzufügen . Dies würde die Erzeugung von zusätzlichen unechten gemessenen Kreuzungen um den richtigen Kreuzungszeitpunkt verhindern. Das Hinzufügen einer Hysterese zum Detektor könnte ungefähr so ​​aussehen:

    if ((state == POSITIVE) && (sample[i - 1] > -T) && (sample[i] < -T))
    {
        // handle negative zero-crossing
        state = NEGATIVE;
    }
    else if ((state == NEGATIVE) && (sample[i - 1] < T) && (sample[i] > T))
    {
        // handle positive zero-crossing
        state = POSITIVE;
    }
    

    Tatsächlich fügen Sie Ihrem Nulldurchgangsdetektor einen Zustand hinzu. Wenn Sie glauben, dass das Eingangssignal einen positiven Wert hat, muss das Signal unter einen gewählten Schwellenwert fallen, -Tum einen echten Nulldurchgang zu deklarieren. Ebenso müssen Sie das Signal wieder über den Schwellenwert ansteigen lassen, Tum zu erklären, dass das Signal wieder positiv ist.

    Sie können die Schwellenwerte so wählen, wie Sie möchten, aber für ein ausgeglichenes Signal wie eine Sinuskurve ist es sinnvoll, sie symmetrisch um Null zu haben. Dieser Ansatz kann Ihnen dabei helfen, eine sauberere Ausgabe zu erzielen, führt jedoch zu einer gewissen Zeitverzögerung, da Sie tatsächlich Schwellenübergänge ungleich Null anstelle von Nulldurchgängen messen.

Wie Pichenettes in seiner Antwort angedeutet hat, ist ein Phasenregelkreis höchstwahrscheinlich der beste Weg, da eine PLL ziemlich genau das tut, was Sie versuchen. Kurz gesagt, Sie betreiben einen Rechteckwellengenerator, der parallel zur Eingangssinuskurve läuft. Die PLL führt periodische Phasenmessungen an der Sinuskurve durch und filtert dann diesen Messstrom, um die Momentanfrequenz des Rechteckwellengenerators zu steuern. Irgendwann wird die Schleife (hoffentlich) verriegelt. An diesem Punkt sollte die Rechteckwelle in Frequenz und Phase mit der Sinuskurve des Eingangs verriegelt sein (natürlich mit einem gewissen Fehler; nichts in der Technik ist perfekt).

Jason R.
quelle
Ist das ein Schmitt-Trigger?
Davorin
In der Tat könnte man sagen, dass es sich um eine Softwareversion eines Schmitt-Triggers handelt . Das bestimmende Merkmal eines Schmitt-Triggers ist, dass es sich um einen Komparator mit Hysterese handelt
Jason R
Um zu vermeiden, dass der Übergang nicht erkannt wird, muss in keiner der beiden Bedingungen auch der Schwellenwert angegeben werden T. Bedeutung statt && (sample[i - 1] > -T) && (sample[i] < -T))verwenden && (sample[i - 1] >= -T) && (sample[i] < -T)). Dies muss sowohl angewendet werden ifund else ifAussagen.
Marc
2

Ich habe gute Erfahrungen mit einer sehr einfachen Methode gemacht, um die Vorzeichenwechsel im Signal zu finden:

  1. a = diff (Vorzeichen (Signal))! = 0 # Dies erkennt die Vorzeichenwechsel
  2. Kandidaten = Zeiten [a] # Dies sind alle Kandidatenpunkte, einschließlich der falschen Kreuzungen
  3. Finden Sie Punktcluster in Kandidaten
  4. Durchschnitt / Median jedes Clusters, dies ist Ihr Vorzeichenwechsel

  5. Korrelieren Sie mit der Schrittfunktion an dem durch 4 vorhergesagten Punkt

  6. Passen Sie die Kurve an die Korrelationsergebnisse an und finden Sie den Peak

In meinem Fall erhöhen 5 und 6 die Genauigkeit der Methode nicht. Sie können Ihr Signal mit Rauschen zittern und sehen, ob es hilft.

Dan
quelle
2

Ich weiß, dass diese Frage ziemlich alt ist, aber ich musste kürzlich den Nulldurchgang implementieren. Ich habe die von Dan vorgeschlagene Art umgesetzt und bin ziemlich zufrieden mit dem Ergebnis. Hier ist mein Python-Code, wenn jemand interessiert ist. Ich bin nicht wirklich ein eleganter Programmierer, bitte tragen Sie mit mir.

import numpy as np
import matplotlib.pyplot as plt
from itertools import cycle

fig = plt.figure()
ax = fig.add_subplot(111)

sample_time = 0.01
sample_freq = 1/sample_time

# a-priori knowledge of frequency, in this case 1Hz, make target_voltage variable to use as trigger?
target_freq = 1
target_voltage = 0

time = np.arange(0.0, 5.0, 0.01)
data = np.cos(2*np.pi*time)
noise = np.random.normal(0,0.2, len(data))
data = data + noise


line, = ax.plot(time, data, lw=2)

candidates = [] #indizes of candidates (values better?)
for i in range(0, len(data)-1):
    if data[i] < target_voltage and data[i+1] > target_voltage:
        #positive crossing
        candidates.append(time[i])
    elif data[i] > target_voltage and data[i+1] < target_voltage:
        #negative crossing
        candidates.append(time[i])

ax.plot(candidates, np.ones(len(candidates)) * target_voltage, 'rx')
print('candidates: ' + str(candidates))

#group candidates by threshhold
groups = [[]]
time_thresh = target_freq / 8;
group_idx = 0;

for i in range(0, len(candidates)-1):
    if(candidates[i+1] - candidates[i] < time_thresh):
        groups[group_idx].append(candidates[i])
        if i == (len(candidates) - 2):
            # special case for last candidate
            # in this case last candidate belongs to the present group
            groups[group_idx].append(candidates[i+1])
    else:
        groups[group_idx].append(candidates[i])
        groups.append([])
        group_idx = group_idx + 1
        if i == (len(candidates) - 2):
            # special case for last candidate
            # in this case last candidate belongs to the next group
            groups[group_idx].append(candidates[i+1])



cycol = cycle('bgcmk')
for i in range(0, len(groups)):
    for j in range(0, len(groups[i])):
        print('group' + str(i) + ' candidate nr ' + str(j) + ' value: ' + str(groups[i][j]))
    ax.plot(groups[i], np.ones(len(groups[i])) * target_voltage, color=next(cycol), marker='o',  markersize=4)


#determine zero_crosses from groups
zero_crosses = []

for i in range(0, len(groups)):
    group_median = groups[i][0] + ((groups[i][-1] - groups [i][0])/2)
    print('group median: ' + str(group_median))
    #find index that best matches time-vector
    idx = np.argmin(np.abs(time - group_median))
    print('index of timestamp: ' + str(idx))
    zero_crosses.append(time[idx])


#plot zero crosses
ax.plot(zero_crosses, np.ones(len(zero_crosses)) * target_voltage, 'bx', markersize=10) 
plt.show()

Bitte beachten Sie: Mein Code erkennt keine Anzeichen und verwendet ein wenig a priori Kenntnis einer Zielfrequenz, um die Zeitschwelle zu bestimmen. Dieser Schwellenwert wird verwendet, um die Mehrfachkreuzung (verschiedene Farbpunkte im Bild) zu gruppieren, aus der diejenige ausgewählt wird, die dem Gruppenmedian am nächsten liegt (blaue Kreuze im Bild).

Rauschende Sinuswelle mit markierten Nulldurchgängen

Stefan Schallmeiner
quelle