Zero cruzamento de uma onda senoidal ruidosa

9

Estou tentando encontrar os cruzamentos zero de uma onda senoidal para transformar a onda senoidal em uma onda quadrada. O único problema é que a onda senoidal é barulhenta, então estou recebendo muitas passagens de jitter e falso zero.

Alguém pode recomendar qualquer psuedocode simples ou materiais relevantes? Até agora eu tenho algo parecido com isto:

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

Alguém pode recomendar um método mais robusto?

Kevin Nasto
fonte
Qual é o objetivo de você tentar torná-lo uma onda quadrada? Você está tentando descobrir onde o sinal começa e termina? Se você é, posso recomendar um método.
Spacey
if ((amostra [i] * amostra [i + 1]) <0) zero_crossing ++;
Marius Hrisca #

Respostas:

8

Você pode tentar passar com baixa frequência o sinal de entrada para obter passagens de zero mais suaves (ou até mesmo passar com banda se tiver uma boa idéia da localização da frequência da onda senoidal). O risco é que, se as informações precisas da fase da amostra forem essenciais para o seu aplicativo, o atraso adicional do filtro possa ser um problema.

Outra abordagem: em vez de tentar transformar a onda senoidal em uma onda quadrada, que tal fazer um oscilador independente de onda quadrada se alinhar em fase / frequência com a onda senoidal? Isso pode ser feito com um loop de fase bloqueada .

pichenettes
fonte
6

O que você mostrou certamente é um detector de cruzamento zero. Lembre-se de algumas coisas que podem melhorar sua situação:

  • Se você tiver ruído fora da faixa do seu sinal (o que é quase certamente o caso, já que sua entrada é um tom puro), você poderá melhorar a relação sinal / ruído aplicando um filtro passa-banda ao redor do sinal de interesse . A largura da banda passante do filtro deve ser escolhida com base na precisão com que você conhece a frequência senoidal a priori . Ao reduzir a quantidade de ruído presente no sinusóide, o número de cruzamentos de zero falsos e seu jitter sobre os tempos de cruzamento corretos serão reduzidos.

    • Como observação lateral, se você não tiver boas informações antes do tempo, poderá usar uma técnica mais sofisticada, conhecida como aprimorador de linha adaptável , que, como o próprio nome indica, é um filtro adaptável que aprimora um sinal de entrada periódico. No entanto, este é um tópico um pouco avançado, e você geralmente tem uma idéia suficientemente boa da frequência do seu sinal para que esse tipo de abordagem não seja necessária.
  • Com relação ao detector de cruzamento zero, você pode adicionar alguma histerese ao processo. Isso impediria a geração de cruzamentos extra espúrios medidos em torno do instante correto do cruzamento. Adicionar histerese ao detector pode ser algo como isto:

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

    Efetivamente, você adiciona algum estado ao seu detector de cruzamento zero. Se você acredita que o sinal de entrada tem um valor positivo, é necessário que o sinal caia abaixo de um valor limite escolhido -Tpara declarar uma passagem zero real. Da mesma forma, você precisa que o sinal volte acima do limite Tpara declarar que o sinal voltou a ser positivo novamente.

    Você pode escolher os limites para o que quiser, mas para um sinal equilibrado como um senoide, faz sentido que eles sejam simétricos em relação a zero. Essa abordagem pode ajudar a fornecer uma saída com aparência mais limpa, mas adicionará algum atraso de tempo devido ao fato de você estar realmente medindo cruzamentos de limiar diferentes de zero em vez de cruzamentos de zero.

Como as pichenettes sugeriram em sua resposta, um loop de fase bloqueada seria provavelmente o melhor caminho a seguir, pois uma PLL faz exatamente o que você está tentando fazer. Em resumo, você executa um gerador de onda quadrada que funciona paralelamente ao sinusóide de entrada. O PLL faz medições periódicas de fase no sinusóide e depois filtra esse fluxo de medições para direcionar a frequência instantânea do gerador de ondas quadradas. Em algum momento, o loop travará (espero), momento em que a onda quadrada deve ser travada em frequência e fase com o sinusóide da entrada (com alguma quantidade de erro, é claro; nada em engenharia é perfeito).

Jason R
fonte
Isso é um gatilho de Schmitt?
Davorin 23/09/2013
Na verdade, você poderia dizer que é uma versão de software de um gatilho Schmitt . A característica definidora de um disparador de Schmitt é que ele é um comparador com histerese
Jason R
Para evitar não detectar a transição, inclua em qualquer uma das duas condições também o limite T. Significado em vez de && (sample[i - 1] > -T) && (sample[i] < -T)), use && (sample[i - 1] >= -T) && (sample[i] < -T)). Isso precisa ser aplicado às instruções ife else if.
marc
2

Eu tenho uma boa experiência com um método muito simples para encontrar as mudanças de sinal no sinal às vezes:

  1. a = diff (sinal (sinal))! = 0 # isso detecta as alterações do sinal
  2. candidatos = vezes [a] # estes são todos os pontos candidatos, incluindo os cruzamentos falsos
  3. encontre grupos de pontos em candidatos
  4. média / mediana de cada cluster, esta é sua alteração de sinal

  5. correlação com a função step no ponto previsto por 4

  6. ajuste a curva aos resultados da correlação e encontre o pico

No meu caso 5 e 6, não aumente a precisão do método. Você pode diminuir o sinal com ruído e ver se isso ajuda.

Dan
fonte
2

Eu sei que essa pergunta é bastante antiga, mas tive que implementar o cruzamento de zero recentemente. Eu implementei o que Dan sugeriu e estou bastante satisfeito com o resultado. Heres meu código python, se alguém estiver interessado. Eu não sou realmente um programador elegante, por favor, tenha paciência comigo.

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: meu código não detecta sinais e usa um pouco de conhecimento a priori de uma frequência-alvo para determinar o limite de tempo. Esse limite é usado para agrupar o cruzamento múltiplo (pontos de cores diferentes na imagem) a partir do qual o mais próximo à mediana dos grupos é selecionado (cruzes azuis na imagem).

Onda senoidal barulhenta com cruzamentos de zero marcados

Stefan Schallmeiner
fonte