Here is the method to obtain our time shift:
- Fourier transform the band-limited, periodic waveform sample frame to the frequency domain;
- Apply a constantly increasing (linear) phase shift to all frequency samples;
- Inverse Fourier transform back to the time domain.
The amount of phase shift applied to each frequency sample is nδ, where n is the index of the frequency sample (0 for the dc frequency sample, 1 for the lowest frequency, etc.), and δ is the phase increment required to produce the desired time shift, up to 360° (2¼ radians) for one full sample period of shift. Now, the only remaining problem is finding the right value for δ—the phase shift increment in the frequency domain that will produce the required time shift in the time domain.
If our underlying analog waveform were a perfectly predictable wave, such as a triangle or sine, we could predict the exact time shift required by interpolating between our original time samples. We could then perform the same interpolation on all samples and avoid the frequency domain processing altogether! Nature, however, usually isn't so cooperative, and our filtered square waves need a different approach.
Because the analog waveform near the zero-crossing forms a nearly straight line, it makes sense to try an iterative method, where we approach the zero-crossing by assuming a straight-line waveform between samples. This approach converges to 16-bit accuracy in just a few iterations.
As shown in Figure 2, points A and B represent two original samples of the analog wave. We want to shift sample B left (earlier) to the zero-crossing at B0. The sample interval is T. If we assume a straight line between A and B, we can specify a time shift of [TB/(B - A)] to shift B to B'. For example, if sample A has a value of -100, and sample B is 200, the time shift becomes:
200T/[200 - (-100)] = (2/3)T
to shift B to B'. We then repeat the linear interpolation, using A and B' as the endpoints, to arrive at a closer estimate. This iteration very quickly converges on the exact time shift required to move B to B0 within the resolution of our sampling system.
A pseudo-C algorithm can be written for acquiring and time aligning a single cycle of a test waveform (see the code listing). The points (x1, y1) and (x2, y2) in the code are the coordinates of the two endpoints of the linear approximation to the filtered square wave (initially A and B, respectively, in Figure 2). The x, or time, axis is normalized such that 0.0 is placed at the initial point B and -1.0 is at A.
This way, one x unit equals one sample interval, and the required time shift always lies between 0.0 and 1.0. At each iteration, (x1, y1) and (x2, y2) are updated to hold the most recent two zero-crossing approximations found. The point (x3, y3) is the current zero-cross approximation (B' in Figure 2 for the first iteration).
The vector multiplication required to perform the frequency-domain phase shift is done in rectangular coordinates:
(A + jB)(C + jD)
= (AC - BD) + j(BC + AD)
where A + jB is the original complex vector and C + jD is a unit-length vector with an angle equal to the desired phase shift (rotation) angle. If θ is the rotation angle, then C = cosθ and D = sinθ.
If the frequency of the square-wave signal source is such that one cycle takes place in approximately a power of two samples of the data-acquisition system, then one cycle of the test waveform may be acquired and processed using a fast and convenient FFT algorithm as shown in the code listing.
My system had a sample rate of 48.8 kHz, so I used 64-point FFTs and a 763-Hz input square wave. One cycle of 763 Hz = 1.31 ms = 64 samples at 1/48.8 kHz. Figure 3 shows an actual sequence of 12-bit 2's complement samples before alignment (X's) and after alignment (O's) to the zero-crossing. This particular sequence required a shift of -0.347 sample to move the first positive sample (number six, originally at value +636) back in time to the zero-crossing.
Once the sampled wave is aligned in time using this technique, accumulating or comparing it with other aligned waveforms is a simple matter of operating on sequential samples that start at the zero-crossing sample. This provides a powerful and convenient method for performing sample time shifting on data that's not synchronized to the sample clock.
In my application, I implemented the technique to align data gathered from dozens of known-good boards to create an averaged reference waveform and a tolerance window. I also used it during board test to align incoming test waveforms with the reference wave and tolerance window stored in memory for go/no-go testing.
To download the listings, click Download the Code.