Passage à zéro d'une onde sinusoïdale bruyante

9

J'essaie de trouver les passages à zéro d'une onde sinusoïdale pour transformer l'onde sinusoïdale en une onde carrée. Le seul problème est que l'onde sinusoïdale est bruyante, donc j'obtiens beaucoup de gigue et de faux passages à zéro.

Quelqu'un peut-il recommander un pseudo-code simple ou des documents pertinents? Jusqu'à présent, j'ai quelque chose comme ça:

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

Quelqu'un peut-il recommander une méthode plus robuste?

Kevin Nasto
la source
Quel est le but de vous en faire une onde carrée? Essayez-vous de savoir où commence et se termine le signal? Si vous êtes, je peux recommander une méthode.
Spacey
if ((échantillon [i] * échantillon [i + 1]) <0) zero_crossing ++;
Marius Hrisca

Réponses:

8

Vous pouvez essayer le filtrage passe-bas du signal d'entrée pour obtenir des passages par zéro plus fluides (ou même un filtrage passe-bande si vous avez une bonne idée de l'emplacement de fréquence de l'onde sinusoïdale). Le risque est que si des informations de phase précises sur l'échantillon sont essentielles à votre application, le décalage supplémentaire du filtre pourrait être un problème.

Une autre approche: au lieu d'essayer de transformer l'onde sinusoïdale en une onde carrée, qu'en est-il d'obtenir un oscillateur à onde carrée indépendant pour s'aligner en phase / fréquence avec l'onde sinusoïdale? Cela peut être fait avec une boucle à verrouillage de phase .

pichenettes
la source
6

Vous avez certainement montré un détecteur de passage par zéro. Quelques choses me viennent à l'esprit qui pourraient améliorer votre situation:

  • Si vous avez du bruit en dehors de la bande de votre signal (ce qui est presque certainement le cas, car votre entrée est un ton pur), vous pouvez améliorer le rapport signal / bruit en appliquant un filtre passe-bande autour du signal d'intérêt . La largeur de bande passante du filtre doit être choisie en fonction de la précision avec laquelle vous connaissez la fréquence sinusoïdale a priori . En réduisant la quantité de bruit présente sur la sinusoïde, le nombre de faux passages à zéro et leur gigue sur les temps de passage corrects seront réduits.

    • En guise de remarque, si vous ne disposez pas de bonnes informations à l'avance, vous pouvez utiliser une technique plus sophistiquée connue comme un amplificateur de ligne adaptatif , qui, comme son nom l'indique, est un filtre adaptatif qui améliorera un signal d'entrée périodique. Cependant, c'est un sujet quelque peu avancé, et vous avez généralement une assez bonne idée de la fréquence de votre signal pour que ce type d'approche ne soit pas nécessaire.
  • En ce qui concerne le détecteur de passage par zéro lui-même, vous pouvez ajouter une certaine hystérésis au processus. Cela empêcherait la génération de croisements mesurés extra-parasites autour de l'instant de croisement correct. L'ajout d'hystérésis au détecteur pourrait ressembler à ceci:

    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;
    }
    

    En effet, vous ajoutez un état à votre détecteur de passage à zéro. Si vous pensez que le signal d'entrée a une valeur positive, vous devez que le signal descende en dessous d'une valeur de seuil choisie -Tafin de déclarer un vrai passage à zéro. De même, vous exigez que le signal remonte au-dessus du seuil Tafin de déclarer que le signal a de nouveau oscillé vers le positif.

    Vous pouvez choisir les seuils pour être ce que vous voulez, mais pour un signal équilibré comme une sinusoïde, il est logique de les avoir symétriques par rapport à zéro. Cette approche peut vous aider à obtenir une sortie plus propre, mais elle ajoutera un certain délai car vous mesurez en fait des seuils non nuls au lieu de zéro.

Comme les pichenettes l'ont suggéré dans sa réponse, une boucle à verrouillage de phase serait probablement la meilleure façon de procéder, car une PLL fait à peu près exactement ce que vous essayez de faire. En bref, vous exécutez un générateur d'onde carrée qui fonctionne en parallèle avec la sinusoïde d'entrée. La PLL effectue des mesures périodiques de phase sur la sinusoïde, puis filtre ce flux de mesures afin de diriger la fréquence instantanée du générateur d'onde carrée. À un moment donné, la boucle se verrouillera (espérons-le), à ​​quel point l'onde carrée devrait être verrouillée en fréquence et en phase avec la sinusoïde de l'entrée (avec une certaine quantité d'erreur, bien sûr; rien en ingénierie n'est parfait).

Jason R
la source
Est-ce un déclencheur de Schmitt?
Davorin
En effet, on pourrait dire qu'il s'agit d'une version logicielle d'un déclencheur Schmitt . La caractéristique qui définit un déclencheur de Schmitt est qu'il s'agit d'un comparateur avec hystérésis
Jason R
Pour éviter de ne pas détecter la transition, inclure dans l'une des deux conditions également le seuil T. Signification au lieu de && (sample[i - 1] > -T) && (sample[i] < -T)), utilisez && (sample[i - 1] >= -T) && (sample[i] < -T)). Cela doit être appliqué aux déclarations ifet else if.
marc
2

J'ai une bonne expérience avec une méthode très simple pour trouver les changements de signe dans le signal à certains moments:

  1. a = diff (signe (signal))! = 0 # cela détecte les changements de signe
  2. candidats = fois [a] # ce sont tous les points candidats, y compris les faux passages
  3. trouver des groupes de points dans les candidats
  4. moyenne / médiane de chaque cluster, c'est votre changement de signe

  5. faire la corrélation avec la fonction de pas au point prévu par 4

  6. ajuster la courbe aux résultats de corrélation et trouver le pic

Dans mon cas, 5 et 6 n'augmentent pas la précision de la méthode. Vous pouvez trembler votre signal avec du bruit et voir si cela aide.

Dan
la source
2

Je sais que cette question est assez ancienne, mais j'ai dû mettre en œuvre le passage à zéro récemment. J'ai mis en œuvre la façon dont Dan l'a suggéré et je suis plutôt satisfait du résultat. Voici mon code python, si quelqu'un est intéressé. Je ne suis pas vraiment un programmeur élégant, veuillez me supporter.

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

Remarque: mon code ne détecte pas les signes et utilise un peu de connaissance a priori d'une fréquence cible pour déterminer le seuil de temps. Ce seuil est utilisé pour regrouper le croisement multiple (points de couleur différente dans l'image) à partir duquel celui le plus proche de la médiane des groupes est sélectionné (croix bleues dans l'image).

Onde sinusoïdale bruyante avec des croix nulles marquées

Stefan Schallmeiner
la source