Cruce cero de una onda sinusoidal ruidosa

9

Estoy tratando de encontrar los cruces por cero de una onda sinusoidal para transformar la onda sinusoidal en una onda cuadrada. El único problema es que la onda sinusoidal es ruidosa, por lo que recibo mucha fluctuación de fase y cruces de cero falsos.

¿Alguien puede recomendar algún psuedocódigo simple o materiales relevantes? Hasta ahora tengo algo como esto:

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

¿Alguien puede recomendar un método más robusto?

Kevin Nasto
fuente
¿Cuál es el propósito de que intentes convertirlo en una onda cuadrada? ¿Estás tratando de averiguar dónde comienza y dónde termina la señal? Si es así, puedo recomendar un método.
Spacey
if ((muestra [i] * muestra [i + 1]) <0) zero_crossing ++;
Marius Hrisca

Respuestas:

8

Puede intentar el filtrado de paso bajo de la señal de entrada para obtener cruces por cero más suaves (o incluso el filtrado de paso de banda si tiene una buena idea de la ubicación de frecuencia de la onda sinusoidal). El riesgo es que si la información de fase precisa de la muestra es esencial para su aplicación, el retraso adicional del filtro podría ser un problema.

Otro enfoque: en lugar de tratar de transformar la onda sinusoidal en una onda cuadrada, ¿qué hay de conseguir que un oscilador de onda cuadrada independiente se alinee en fase / frecuencia con la onda sinusoidal? Esto se puede hacer con un bucle de fase bloqueada .

pichenettes
fuente
6

Lo que ha mostrado ciertamente es un detector de cruce por cero. Se me ocurren un par de cosas que podrían mejorar su situación:

  • Si tiene ruido que está fuera de la banda de su señal (lo cual es casi seguro, ya que su entrada es un tono puro), puede mejorar la relación señal / ruido aplicando un filtro de paso de banda alrededor de la señal de interés. . El ancho de banda de paso del filtro debe elegirse en función de la precisión con la que conoce la frecuencia sinusoide a priori . Al reducir la cantidad de ruido presente en la sinusoide, se reducirá el número de cruces falsos a cero y su fluctuación sobre los tiempos de cruce correctos.

    • Como nota al margen, si no tiene buena información por adelantado, podría usar una técnica más sofisticada conocida como potenciador de línea adaptativo , que, como su nombre lo indica, es un filtro adaptativo que mejorará una señal de entrada periódica. Sin embargo, este es un tema algo avanzado y, por lo general, tiene una idea suficientemente buena de la frecuencia de su señal para que este tipo de enfoque no sea necesario.
  • Con respecto al detector de cruce por cero en sí mismo, puede agregar algo de histéresis al proceso. Esto evitaría la generación de cruces medidos espurios adicionales alrededor del instante de cruce correcto. Agregar histéresis al detector podría verse así:

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

    Efectivamente, agrega algún estado a su detector de cruce por cero. Si cree que la señal de entrada tiene un valor positivo, necesita que la señal baje por debajo de un valor umbral elegido -Tpara declarar un cruce por cero real. Del mismo modo, necesita que la señal se eleve nuevamente por encima del umbral Tpara declarar que la señal ha oscilado nuevamente a positivo.

    Puede elegir que los umbrales sean lo que desee, pero para una señal balanceada como una sinusoide, tiene sentido que sean simétricos respecto a cero. Este enfoque puede ayudarlo a obtener un resultado más limpio, pero agregará un cierto retraso debido al hecho de que en realidad está midiendo cruces de umbral distintos de cero en lugar de cruces por cero.

Como pichenettes sugirió en su respuesta, un bucle de fase bloqueada probablemente sería la mejor manera de hacerlo, ya que un PLL hace casi exactamente lo que está tratando de hacer. En resumen, ejecuta un generador de onda cuadrada que funciona en paralelo con la sinusoide de entrada. El PLL realiza mediciones periódicas de fase en la sinusoide, luego filtra esa corriente de mediciones para dirigir la frecuencia instantánea del generador de onda cuadrada. En algún momento, el bucle se bloqueará (con suerte), en ese punto la onda cuadrada debe bloquearse en frecuencia y fase con la sinusoide de la entrada (con cierta cantidad de error, por supuesto; nada en ingeniería es perfecto).

Jason R
fuente
¿Es eso un disparador de Schmitt?
Davorin
De hecho, se podría decir que es una versión de software de un disparador Schmitt . La característica definitoria de un disparador de Schmitt es que es un comparador con histéresis
Jason R
Para evitar no detectar la transición, incluya en cualquiera de ambas condiciones también el umbral T. Significado en lugar de && (sample[i - 1] > -T) && (sample[i] < -T)), uso && (sample[i - 1] >= -T) && (sample[i] < -T)). Esto debe aplicarse a ambos ify else ifdeclaraciones.
marc
2

Tengo buena experiencia con un método muy simple para encontrar los cambios de señal en la señal a veces:

  1. a = diff (signo (señal))! = 0 # esto detecta los cambios de signo
  2. candidatos = veces [a] # estos son todos los puntos candidatos, incluidos los cruces falsos
  3. encontrar grupos de puntos en candidatos
  4. promedio / mediana de cada grupo, este es su cambio de signo

  5. hacer correlación con la función de paso en el punto predicho por 4

  6. ajustar la curva a los resultados de correlación y encontrar el pico

En mi caso 5 y 6 no aumentan la precisión del método. Puede oscurecer su señal con ruido y ver si ayuda.

Dan
fuente
2

Sé que esta pregunta es bastante antigua, pero recientemente tuve que implementar el cruce por cero. Implementé la forma en que Dan sugirió y estoy bastante satisfecho con el resultado. Aquí está mi código de Python, si alguien está interesado. No soy realmente un programador elegante, por favor tengan paciencia conmigo.

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

Nota: mi código no detecta signos y utiliza un poco de conocimiento a priori de una frecuencia objetivo para determinar el umbral de tiempo. Este umbral se usa para agrupar el cruce múltiple (diferentes puntos de color en la imagen) del cual se selecciona el más cercano a la mediana de los grupos (cruces azules en la imagen).

Onda sinusoidal ruidosa con cruces cero marcados

Stefan Schallmeiner
fuente