采样和重构数字信号

信息处理 采样 数字的 同步
2022-01-30 15:17:23

假设我有一个数字信号(实际上是比特流),其输出每 100 毫秒变化一次。我不知道它何时相对于我的采样发生变化,只是下一个要捕获的位出现在 100 毫秒之后。(我不是在捕获方波,而是在 100 毫秒后信号可能会或可能不会假设一个新值(1 或 0)。)所以我每 25 毫秒对信号进行一次采样。如何在一段时间内从采样流中再现比特流...我可能会错过捕获的前几位,但之后我想获得输出的每一位?我假设有一个通用的算法可以做到这一点。

顺便说一句,解决方案都可以在软件(Java / C++)中完成,不必是实时的……我可以收集所有样本,然后对其进行后处理以提取信号。

3个回答

如果两个时钟之间没有太大的漂移并且比特流经常有边沿,你可以这样做:

同步算法的可视化

点是每 25 毫秒采集一次的样本,实线表示基于第一次同步每 100 毫秒变化一次的二进制信号时序的已知信息。歧义由多个可能的边缘时间表示。当样本具有与前一个不同的值时,就会发生同步。然后您知道边缘位于当前(蓝色)样本和前一个样本之间。接下来的两个样本(绿色)很好,因为您知道它们代表的信号的哪一位,但后面的两个样本不好,因为如果两者之间甚至有一点点漂移(可视化为可能的边缘时间的扩展)时钟,您将不知道它们是代表当前位还是下一位。

你继续重复:好的样本,好的样本,坏的样本,坏的样本,直到你遇到另一个边缘并且可以重新同步。您可以使用任何一个连续的好样本来重建比特流。实际上蓝色边缘样本也很好用,但谁在乎呢。

就在这里。您必须研究数字通信系统中的一个广泛领域,即同步一般来说,许多同步原理在不同参数上是相同的。具体来说,您需要在数字接收器中实现定时同步(也称为定时恢复或时钟恢复)子系统。您可以以开环方式(前馈)或闭环方式(反馈,基于 PLL)的方式进行。一旦你开始实施它,你就会有一些具体的问题,你可以一次又一次地回到 SE。

在此特定示例中,您的时钟频率没问题(位每 100 毫秒更改一次——与采样间隔的恒定差异)。理想情况下,匹配滤波器的输出在采样周期结束时被采样。由于您不知道理想的采样时刻(最大眼图张开),因此您必须使用数据知识(例如,前导码),或者您可以以非数据辅助方式使用比特流的特征。

编辑:如果您以 25 毫秒采样,这会使您的样本/符号等于 4。如果后处理不是问题,则只需对整个前导码使用数字滤波器和平方时序恢复。封闭式表达式为您提供定时相位偏移的准确估计。

然后,您必须根据上面获得的估计值对数据进行插值,并从 4:1 对信号进行下采样。

我想出了一个简单的 Java 解决方案,它能够使用 3x 过采样重建比特流。下面的示例涉及 2 个信号,但这不是问题陈述的一部分。

import java.util.LinkedList;
import java.util.Queue;

public class SampleQueue {
private double oversampleratio;
private Queue<Character> output;

public SampleQueue(double oversampleratio) {
    this.oversampleratio = oversampleratio;
    this.output = new LinkedList<Character>();
}

public char[] process(String sampled) {
    char currentchar = sampled.charAt(0);
    int charcounter = 0;
    for(int i = 0; i < sampled.length(); i++) {
        if(currentchar == sampled.charAt(i)) {
            charcounter++;
        }
        else {
            process(currentchar, charcounter);
            charcounter = 1;
            currentchar = sampled.charAt(i);
        }
    }
    if(charcounter > 0) {
        process(currentchar, charcounter);
    }
    return getString();
}

private char[] getString() {
    int length = output.size();
    char[] str = new char[length];
    for(int i = 0; i < length; i++) {
        str[i] = output.poll();
    }
    return str;
}

private void process(char currentchar, int charcounter) {
    int actualsamples = (int) Math.round(charcounter / (double) oversampleratio);
    for(int i = 0; i < actualsamples; i++) {
        output.add(currentchar);
    }
}
}


import org.apache.commons.lang3.StringUtils;

public class DataAndClockSignal {
    private double phase;
    private double sampleperiod;
    private String data;
    private String sampleddata;
    private String clock;
    private String sampledclock;
    private int clocklevel;

public DataAndClockSignal(double phase, double sampleperiod) {
    this.clocklevel = 0;
    this.phase = phase;
    this.sampleperiod = sampleperiod;
    this.data = "";
    this.clock = "";
    this.sampledclock = "";
    this.sampleddata = "";
}

public void generateRandomSamples(int numsamples) {
    MersenneTwisterFast mtf = new MersenneTwisterFast();
    int maxvalue = (int) Math.pow(2, 12);
    int generatedsamples = 0;
    while(generatedsamples < numsamples) {
            for(int i = 0; i < 10 + mtf.nextInt(25); i++) {
                addData(mtf.nextInt(maxvalue));
                generatedsamples++;
            }           
    }
    dump();
}


public void addData(int dataint) {
    String str = Integer.toBinaryString(dataint);
    data += StringUtils.leftPad(str, 12, '0');
    transitionClock();
    if(clocklevel == 0) {
        clock += "000000000000";        
    }
    else {
        clock += "111111111111";
    }
}

private void transitionClock() {
    clocklevel = (clocklevel + 1) % 2;
}

public void dump() {
    sampledclock = "";
    sampleddata = "";
    double sampletime = phase;
    for(int i = 0; i < data.length(); ) {
        sampledclock += clock.charAt(i);
        sampleddata += data.charAt(i);
        sampletime += sampleperiod;
        i = (int) sampletime;
    }
    System.out.println("Clock:            " + clock);
    System.out.println("Data:             " + data);
    System.out.println("SampledClock: " + sampledclock);
    System.out.println("SampledData:  " + sampleddata);
}

public String getData() {
    return data;
}

public String getSampleddata() {
    return sampleddata;
}

public String getClock() {
    return clock;
}

public String getSampledclock() {
    return sampledclock;
}

public static void main(String[] args) {
    DataAndClockSignal randomsig = new DataAndClockSignal(0.37, 0.34);
    randomsig.generateRandomSamples(500);
    }
}


public class Reconstruction {
private String data;
private String clock;
private double sampleperiod;

public Reconstruction(double sampleperiod) {
    this.sampleperiod = sampleperiod;
}

public void processRandomData(double timeoffset) {
    DataAndClockSignal dataandclock = new DataAndClockSignal(timeoffset, sampleperiod);
    dataandclock.generateRandomSamples(500);
    data = dataandclock.getData();
    clock = dataandclock.getClock();
    char[] reconstructeddata = reconstruct(dataandclock.getSampleddata());
    char[] reconstructedclock = reconstruct(dataandclock.getSampledclock());
    System.out.print("Reconstructed Clock: ");
    System.out.println(reconstructedclock);
    System.out.print("Reconstructed Data: ");
    System.out.println(reconstructeddata);  
    if(new String(reconstructeddata).equalsIgnoreCase(data)) {
        System.out.println("Data matches!");
    }
    else {
        System.out.println("Data does not match!");
    }
    if(new String(reconstructedclock).equalsIgnoreCase(clock)) {
        System.out.println("Clock matches!");
    }
    else {
        System.out.println("Clock does not match!");
    }
}

private char[] reconstruct(String sampled) {
    SampleQueue samplequeue = new SampleQueue(1 / sampleperiod);
    return samplequeue.process(sampled);
}

public static void main(String[] args) {
    double sampleperiod = 0.34;
    double timeoffset = 0.37;
    Reconstruction reconstruction = new Reconstruction(sampleperiod);
    reconstruction.processRandomData(timeoffset);
}

}