Author Topic: Generating a "good enough" 10kHz sine wave from a square one  (Read 4477 times)

0 Members and 1 Guest are viewing this topic.

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
I'm working on a project to read a sensor using a phase difference approach. The original circuit to validate the sensor was rather complex, with an AD9837 sine generator, variable attenuation, and two fast comparators generating a pulse to detect zero crossing for the excitation signal and the signal thru the sensor. The pulses start and stop a counter, which is then read by a microprocessor to derive the sensor value.

I need to use a much simplified circuit to do the same, now that we know the frequency (10kHz), voltages and sensor ranges. The processor I'm using (nRF52840) has no DAC, very limited PWM, and no PDM functionality. And the BLE stack is running on the same core, forcing me to use only HW functionality for my project. I tried many tricks to generate a clean 10kHz sine, but there's still way too much noise especially at zero crossing, which is the most important part of the signal.

The sensor doesn't need a clean sine. Just a signal that is "sinusoidal enough", 10kHz in frequency, and very clean around zero crossing. I'm running at 1.8V, so "zero crossing 'is defined by 0.9V, and I need a sine centered on 0.9V, with enough Vpp to properly excite the sensor.  I will use the built in comparator to start and stop a hardware timer, and since the sensor will  also induce an attenuation, having a reliable value for zero crossing is critical: I need to make sure that the value I choose for the comparator zero is actually the sine zero reference for both the excitation signal and the sensor signal.

So, I’m planning to generate a 10kHz square wave (0-1.8V), a triple RC filter and some amplification. The triple RC filter works, but  as expected, it reduces the signal to a 400mVpp one, still 0.9V centered (0.7-1.1V), which is too low for the sensor. Here is what I came up with, which works perfectly for what I need, but uses 3 opamps: U2 shifts the filtered 0.7-1.1V sine down and amplifies it to 0.2V to 1.4V or so, then U1 works as a voltage follower for an AC signal, centering it around 0.9V. I need U3, because the RC filter signal would be distorted if sent directly into a capacitor (C5). So U3 acts as a buffer. Brute force, not elegant at all, I know. But it works



I can simplify the circuit, using a divider and amplifier in one and only 2 opamps, but the values of the R4/R5 voltage divider and amplification factor were too critical. I would need to use a trimmer for R4/R5 and R6/R7 to ensure that the resulting sine is perfectly centered and properly amplified without clipping (any resistor tolerance would get amplified). On the contrary, in the circuit above, even if the resistors have high tolerance, the signal is always guaranteed to be amplified enough and centered (within the tolerances of R4/R5, but without extra amplification error)



Is there a simpler way to start from a 0-1.8V square wave and get a >1.4Vpp sine, perfectly centered on 0.9V? Maybe using active filters, instead of passive one?

I'm really a beginner when it comes to analog stuff. If you are providing a suggestion like "simply use a third order Sallen-Key lowpass filter", I'd appreciate more info like the cutoff frequency and a pointer to a simple example circuit implementing it :) I guess I know that a third order Sallen-Key should work, I just keep getting lost trying to figure out if there's a way to do one using only one opamp

 

Offline MikeK

  • Super Contributor
  • ***
  • Posts: 1314
  • Country: us
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #1 on: July 06, 2022, 02:39:11 am »
The middle two op-amps of this?

 

Online gf

  • Super Contributor
  • ***
  • Posts: 1295
  • Country: de
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #2 on: July 06, 2022, 09:56:02 am »
For the filter design, you can try the Analog Filter Wizard.
It also helps to select suitable opamps and can includes the GBW product, noise, voltage swing, component tolerances, etc. of the selected opamps in the calculations.

EDIT: What accuracy exactly do you actually need for the measured phase? Can you quantify?
« Last Edit: July 06, 2022, 10:00:40 am by gf »
 

Offline Zero999

  • Super Contributor
  • ***
  • Posts: 19630
  • Country: gb
  • 0999
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #3 on: July 06, 2022, 10:14:14 am »
Only one of those op-amps are really needed.
 

Online jwet

  • Frequent Contributor
  • **
  • Posts: 489
  • Country: us
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #4 on: July 06, 2022, 12:09:15 pm »
I would use an online or other filter design tool.  Most analog companies have some sort of filter tool.  TI and Analog Devices have decent ones.

You can figure out what is "good enough" for yourself before hand.  A square wave can be decomposed into a sum of odd harmonic sine waves with decreasing amplitudes as 1/n.  If you start with a square at 10kHz, how much harmonic content can you live with.  You can probably play with a Fourier simulator on line or do some math.  If you'd like to have a 1% distortion sine then you basically want to push down the harmonics 40 db. 


Go into a filter wizard/simulator of some kind and design a filter with those specs.  The simplest filter will like be a Chebyshev but you can play with this in the designer.  They tend to have the fastest rollofff but introduce some artifacts like passband ripple, etc.  Any simulator will let you play with all this.  You should be able to make a pretty nice sine with two second order low pass stages set just past youu fundamental- maybe 12 Khz.  Play with this.  It might work best to set you pole at the fundamental.

Active filters are more than just simple poles cascaded and amplified back up like your RCRCRC + Amp.  They essentially simulate inductance and can make much better (steeper) filters with less stages.  They create a polynomial equation with all the terms rather than just a cascade of derivative terms- these other terms shape the response and let you control things more precisely and trade off performance parameters.  If you want to dig in more, Maxim had a good app note about all this.  It showed and explained all the complex plane, poles and zeros moving around.  This stuff is complicated but is worth knowing if you're doing much with filters. The Maxim app note is one of the most approachable tutorials that I've seen. Most engineers just scale cookbook circuits or use "wizards" and get away with because the math gets so hairy.


One additional big hammer to consider is to use a switched capacitor filter.  Maxim, LTC and National (and others) make canned filters with fifth or higher order responses with few components.  The tradeoff is some residual noise and cost which may or may not matter.  Look at the Max 7410 as an example, there may be better and cheaper solutions. 

As a Maxim FAE, I called on a customer that made Antishoplifting tags- the kind you see at the exit of department stores.  They drive big coils with audio frequencies and receive reflection for tags.    The tags worked by absorption and reflection of the energy.  The received a sine wave into a little antenna that had a diode across it.  The diode would create a non linear reflection with harmonic content that the system could detect and alarm on.  The checker would put the tag in a little tool that could crush the diode or could remove the tag in some cases.  They needed a pretty pure sine, it set the range of the gadget basically.  They used an square wave and a switched cap filter.

One other issue, measuring phase of signals with comparators looking at zero crossings in the time domain is a kind of old solution.  I don't know if it applies, but there are some really nice DSP type techniques to do these kind of things in the frequency domain.  We can discuss if you're interested.
Good problem.
 

Offline WatchfulEye

  • Regular Contributor
  • *
  • Posts: 113
  • Country: gb
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #5 on: July 06, 2022, 12:49:48 pm »
Try the filter calculator here:

http://sim.okawa-denshi.jp/en/Sallen3tool.php

Select a chebyshev filter, choose your passband ripple (i'd suggest 1dB) and cutoff frequency of 12 khz.

It would be worth trying a few different options, and verifying performance using monte carlo simulation in spice to check that component tolerances don't cause excessive drifts or loss of signal.
 
The following users thanked this post: robca, spostma, jwet

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #6 on: July 06, 2022, 11:07:27 pm »
Try the filter calculator here:

http://sim.okawa-denshi.jp/en/Sallen3tool.php

Select a chebyshev filter, choose your passband ripple (i'd suggest 1dB) and cutoff frequency of 12 khz.

It would be worth trying a few different options, and verifying performance using monte carlo simulation in spice to check that component tolerances don't cause excessive drifts or loss of signal.
This is exactly the type of answer I was hoping to get, I can't thank you enough  ;D

Not only you pointed me to the tool (which I had already found, but got lost using), but also provided all the necessary info to use it to find a starting point and start figuring out how to actually use the tool

Incidentally, using 12kHz and 1dB the circuit saturates (at least in my simulation), while using 10kHz and 1dB it seems to generate an almost perfect, 180 out of phase sine, 0.05V to 1.75V. Will need to build the circuit to make sure it's actually that good

And here's what it looks like, for "future me" and anyone else finding this thread



 

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #7 on: July 06, 2022, 11:10:00 pm »
Only one of those op-amps are really needed.

I actually did try out that design in my simulator. I used smaller values for the divider, which resulted in a distorted sine. Thanks for taking the time to actually run a simulation on my values. In the end I ruled it out, because the R4/R5 tolerances would get amplified together with the signal, and the zero crossing could move from PCB to PCB.

But I truly appreciate the input...
 

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #8 on: July 06, 2022, 11:23:10 pm »
I would use an online or other filter design tool.  Most analog companies have some sort of filter tool.  TI and Analog Devices have decent ones.

[...]

One other issue, measuring phase of signals with comparators looking at zero crossings in the time domain is a kind of old solution.  I don't know if it applies, but there are some really nice DSP type techniques to do these kind of things in the frequency domain.  We can discuss if you're interested.
Good problem.
Thanks for the all info (the part I removed with [...] for sake of brevity). Reading and digesting all that...

As for why "old style", it's because my processor choice is defined by the existing hardware platform, built around a nRF52840. Great processor for BLE and low power devices, really good digital peripherals support, limited analog ones. The A/D is only sampling at 200ksps, single channel, 12 bits. So I ruled out any DSP technique, because I didn't think I would get a good enough resolution of the sensor signal. Working in the digital domain was my first instinct, until I realized the SAADC limitations. And using an external ADC would defeat the goal of minimizing components.

Also, I know I can easily implement the phase difference technique with better resolution than the original circuit (which used a 2MHz clock for the counter, I can easily use 16MHz), purely in hardware on the nRF52840, without impact on the rest of the code performance (the nRF52840 is always multitasking to keep the BLE communication stack active)

But if you think that a single 220Ksps, 12 bit SAADC is good enough for the DSP technique (and with enough resolution to be better than a 2MHz phase difference counter), I'm definitely interested to learn more
 

Online jwet

  • Frequent Contributor
  • **
  • Posts: 489
  • Country: us
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #9 on: July 07, 2022, 01:25:17 am »
Can you do an 256 Bin FFT do you think?  I'm not sure if you'll need to do that but if you have enough juice to do an FFT, you could can probably do it in DSP domain.  You would only need to sample the signal at 30 or Khz, 40k would be comfortable.  I dig up a reference for you to look at.  Since you have the excitation source and the reciever in the same box, it might be simpler- you just build a phase sensitive detector.  Give me a day or two and I'll get back to you.  Let me know how many mips you can throw at it.  Maybe the metric is 16 bit multiply and add instruction rate.
 

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #10 on: July 07, 2022, 02:06:14 am »
Can you do an 256 Bin FFT do you think?  I'm not sure if you'll need to do that but if you have enough juice to do an FFT, you could can probably do it in DSP domain.  You would only need to sample the signal at 30 or Khz, 40k would be comfortable.  I dig up a reference for you to look at.  Since you have the excitation source and the reciever in the same box, it might be simpler- you just build a phase sensitive detector.  Give me a day or two and I'll get back to you.  Let me know how many mips you can throw at it.  Maybe the metric is 16 bit multiply and add instruction rate.
First of all, thanks! Really appreciate the help and opportunity to learn something new. I'm still trying to wrap my head about how to detect a very small phase difference of a 10kHz signal when sampling only at 40kHz, but it's a field I'm profoundly ignorant about :) Looking forward to hear more (no hurry, at this point I'm really curious, though)

Well, the nRF52840 has an M4 core, with the full ARM DSP libraries available. According to this https://devzone.nordicsemi.com/f/nordic-q-a/58077/fpu-fft-demo-on-nrf52840 it should be able to do a 256 bin FFT using floating point math in ~670us. Given I'm reading a temperature and sending it over BLE, one reading per second is more than enough, so there should be processing cycles to spare. I haven't tried it yet, but I'm positive I'm not CPU-bound for this

Processing power is significant: it's a M4 ARM core running at 64MHz. What is challenging on the nRF52840, is doing "hard real time". In many cases, the response to an interrupt can be delayed up to ~300us while the BLE stack is running. But the nRF52840 has a hardware event router, so its easy to start and stop a timer using a comparator completely without software intervention, then read the timer value when needed. All while the BLE stack runs untouched. It's truly a nifty processor, low cost, low power and very easy to program. Not the best analog processor, though
 

Online jwet

  • Frequent Contributor
  • **
  • Posts: 489
  • Country: us
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #11 on: July 07, 2022, 04:21:28 am »
The M4 is a beast- no problem.  I hope I'm not blowing smoke here- I've done software defined radios at similar frequencies with I/Q processing with the Teensy 3- an M4F.

What is the sensor that you're interfacing to if you can say?

Look at the Analog Device AD5933, this is the kind of signal path I'm envisioning.  You wouldn't use this part, you'd do it in software but the datasheet for this part will give you some idea of what I'm proposing.  Its basically a vector voltmeter done in DSP domain.

 

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #12 on: July 07, 2022, 05:14:31 am »
The M4 is a beast- no problem.  I hope I'm not blowing smoke here- I've done software defined radios at similar frequencies with I/Q processing with the Teensy 3- an M4F.

What is the sensor that you're interfacing to if you can say?

Look at the Analog Device AD5933, this is the kind of signal path I'm envisioning.  You wouldn't use this part, you'd do it in software but the datasheet for this part will give you some idea of what I'm proposing.  Its basically a vector voltmeter done in DSP domain.
Unfortunately the sensor is a prototype and part of a grant in cooperation with a major US university, and I'm not at liberty to say more, sorry

I'll look at the AD5933 datasheet and see what I can learn. Understood that you are suggesting something similar in SW, not using that part, but I'll see what I can learn from the datasheet
 

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 14355
  • Country: de
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #13 on: July 07, 2022, 06:29:48 am »
The 200 ksps ADC is not that bad and one can get quite good resolution for the phase:  alone 2 points around zero crossing can interpolate the 5 µs time steps to better than 5 ns.  So the resolution would definitely be better than with a comparator (even with a 100 MHz clock). Usually the ADC is also better in noise supression and especially in handling residual harmonic content. The limiting factor is more likely the processing power and not the ADC. With a known and syncronous signal the phase calculation should be relatively straight forward and could be relatively efficient (e.g. averaging over multiple periods first and than do the fourier transform  or sine / cosine decomposition.
 

Online gf

  • Super Contributor
  • ***
  • Posts: 1295
  • Country: de
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #14 on: July 07, 2022, 09:12:44 am »
I think it's just a single ADC, and with two multiplexed input channels (measured signal + reference signal) it is only 100kSa/s per channel (please correct me if this is wrong).
100kSa/s not particularly high, so potential aliasing needs to be considered. For this use case we only care about those frequencies beyond Nyquist which fold down to 10Khz (i.e. to the fundamental), and these are the 9th, 11th, 19th, 21th, etc. harmonics.
In order to keep the angle error below (say) 2*pi/1000 their contribution should stay below -60dBc. With the above mentioned 3rd order Chebycheff filter, and given the fact that the amplitudes of square wave harmonics decrease proportinal to 1/f, this seems to be granted, though. (This consideration presumes of course that the DUT is linear. If it is not, then we have a different situation, since the measured signal can then contain harmonics which are not present in the stimulus. Is it linear?)

Since we are only interested in amplitude/phase at the fundamental frequency (10kHz), the DFT does not need to be calculated for all frequency bins, but only for the 10kHz bin.
Computational cost is low, it's just two mutiply and two add per sample, and per captured channel. This should not be a big challenge for a Cortex M4, even if it were done in floating point.

Ideally, the spectrum of a (filtered) square wave should only contain fundamental+harmonics, and should be empty in the gaps in between.
If this is granted, then a rectangular DFT window covering an integral multiple of 10kHz periods is fine (and even optimal, since it has the lowest ENBW, compared to other window functions).
If there happen to be unexpected spurs in the spectrum (whereever they may come from, e.g. PSU ripple), then a different window function can be useful. But this needs to be analyzed individually then.
« Last Edit: July 07, 2022, 09:19:18 am by gf »
 

Offline Zero999

  • Super Contributor
  • ***
  • Posts: 19630
  • Country: gb
  • 0999
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #15 on: July 07, 2022, 09:24:47 am »
Only one of those op-amps are really needed.

I actually did try out that design in my simulator. I used smaller values for the divider, which resulted in a distorted sine. Thanks for taking the time to actually run a simulation on my values. In the end I ruled it out, because the R4/R5 tolerances would get amplified together with the signal, and the zero crossing could move from PCB to PCB.

But I truly appreciate the input...
Set R5 to 1M and connect R6 to 0V, via a capacitor. Now the op-amp has a DC gain of 1, with its output biased at half the supply. The AC gain is still 1+R7/R6, because the capacitor lets through AC.
 
The following users thanked this post: robca

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #16 on: July 07, 2022, 04:59:42 pm »
I think it's just a single ADC, and with two multiplexed input channels (measured signal + reference signal) it is only 100kSa/s per channel (please correct me if this is wrong).
100kSa/s not particularly high, so potential aliasing needs to be considered.
[...]
 (This consideration presumes of course that the DUT is linear. If it is not, then we have a different situation, since the measured signal can then contain harmonics which are not present in the stimulus. Is it linear?)

Since we are only interested in amplitude/phase at the fundamental frequency (10kHz), the DFT does not need to be calculated for all frequency bins, but only for the 10kHz bin.
Computational cost is low, it's just two mutiply and two add per sample, and per captured channel. This should not be a big challenge for a Cortex M4, even if it were done in floating point.
[...]
Correct, single ADC, multiplexing up to 8 inputs. In my case, it would indeed allow only a 100kSa/s per channel, which is on the low side

The data on the sensor is very limited, being an ongoing research. As far as I can tell, it should behave linearly in the temperature interval I'm considering.

I'm reading http://www.metrology.pg.gda.pl/full/2005/M&MS_2005_427.pdf to get a sense of the techniques available. I'll need to play with octave to wrap my head around how this might work. Any pointer to code examples is always appreciated

Thanks for the extra info
 

Offline fourfathom

  • Super Contributor
  • ***
  • Posts: 1902
  • Country: us
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #17 on: July 07, 2022, 06:11:05 pm »
While an active filter design will give you much better performance, if you want to use the cascaded passive filter you can do better by scaling the R/C values to reduce inter-stage loading.  Here's a comparison of your circuit with one having x10 impedance scaling.  Note the faster initial rolloff and resulting improvement in high-frequency attenuation.  R/V values are for example only, you will probably want to use standard values.
« Last Edit: July 07, 2022, 06:13:29 pm by fourfathom »
We'll search out every place a sick, twisted, solitary misfit might run to! -- I'll start with Radio Shack.
 
The following users thanked this post: robca

Online gf

  • Super Contributor
  • ***
  • Posts: 1295
  • Country: de
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #18 on: July 07, 2022, 07:35:13 pm »

Correct, single ADC, multiplexing up to 8 inputs. In my case, it would indeed allow only a 100kSa/s per channel, which is on the low side

The data on the sensor is very limited, being an ongoing research. As far as I can tell, it should behave linearly in the temperature interval I'm considering.

I'm reading http://www.metrology.pg.gda.pl/full/2005/M&MS_2005_427.pdf to get a sense of the techniques available. I'll need to play with octave to wrap my head around how this might work. Any pointer to code examples is always appreciated

For processing the captured ADC samples, something like this is coming into my mind: https://godbolt.org/z/W6YcbWqrh
Presumes that ref_samples[] and meas_samples[] have been captured simultaneously with the ADC.
(sorry, untested, so there may still be bugs)
 
The following users thanked this post: robca

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #19 on: July 07, 2022, 11:59:17 pm »

Correct, single ADC, multiplexing up to 8 inputs. In my case, it would indeed allow only a 100kSa/s per channel, which is on the low side

The data on the sensor is very limited, being an ongoing research. As far as I can tell, it should behave linearly in the temperature interval I'm considering.

I'm reading http://www.metrology.pg.gda.pl/full/2005/M&MS_2005_427.pdf to get a sense of the techniques available. I'll need to play with octave to wrap my head around how this might work. Any pointer to code examples is always appreciated

For processing the captured ADC samples, something like this is coming into my mind: https://godbolt.org/z/W6YcbWqrh
Presumes that ref_samples[] and meas_samples[] have been captured simultaneously with the ADC.
(sorry, untested, so there may still be bugs)
The EEVblog community is awesome. Someone takes the time to write you code to solve your problem, and apologize for not having fully debugged and unit tested the snippet  ;D you rock, thanks!

Having said that, though, I do think that there's either a problem or I'm misunderstanding how the code works. I modified your code as follows

Code: [Select]
#include <cstdint>
#include <complex>
#include <iostream>

#define PI2 6.2832
#define M_PI           3.14159265358979323846  /* pi */
// value of the L1/R1 voltage divider resistor
// fill-in the actual value
constexpr float R1 = 10000;

// samples per 10kHz period (at 100kSa/s)
constexpr int SPP = 10;

// captured ADC samples, must be integral multiple of SPP
constexpr int NSAMPLES = SPP*100;

// pre-calculated sin/cos tables
// cos_tab[i] =  cos(2*pi*i/SPP)
// sin_tab[i] = -sin(2*pi*i/SPP)
static float cos_tab[SPP] = {1,0.809016994,0.309016994,-0.309016994,-0.809016994,-1,-0.809016994,-0.309016994,0.309016994,0.809016994 };
static float sin_tab[SPP] = {0,-0.587785252,-0.951056516,-0.951056516,-0.587785252,-1.2246468e-16,0.587785252,0.951056516,0.951056516,0.587785252};

std::complex<float> calculate_DUT_impedance()

{
    int16_t ref_samples[NSAMPLES];    // reference samples captured with ADC
    int16_t meas_samples[NSAMPLES];    // measurement samples captured with ADC
    // calculate single-frequency DFT
    float ref_re = 0, ref_im = 0;
    float meas_re = 0, meas_im = 0;
   
    for (int i=0; i<NSAMPLES; i++)   // fill ref_samples and meas_samples with 10kHz sine, phase shifted
    {
        ref_samples[i] = sin(i * 2 * M_PI / 10.0) * 2048 + 2048;
        meas_samples[i] = sin((i - 0.07) * 2 * M_PI / 10.0) * 2048 + 2048;
    }
   
    for (int i=0; i<NSAMPLES; i++)
    {
        ref_re += ref_samples[i] + cos_tab[i % SPP];
        ref_im += ref_samples[i] + sin_tab[i % SPP];
        meas_re += meas_samples[i] + cos_tab[i % SPP];
        meas_im += meas_samples[i] + sin_tab[i % SPP];
    }

    // complex voltages in frequency domain
    std::complex<float> ref_voltage(ref_re, ref_im);
    std::complex<float> meas_voltage(meas_re, meas_im);

    // Phase-shift one of the two voltages by 2*pi/20 to compensate that
    // the muxed channels are not sampled at the same time, but interleaved.
    // ref_voltage *= exp(std::complex<float>(0, -2*M_PI/(2*SPP)));
    // meas_voltage *= exp(std::complex<float>(0, -2*M_PI/(2*SPP)));

    // return impedance of DUT
    std::cout << "Real part: " << real((ref_voltage - meas_voltage) / meas_voltage * R1) << std::endl;
    std::cout << "Imaginary part: " << imag((ref_voltage - meas_voltage) / meas_voltage * R1) << std::endl;
    return (ref_voltage - meas_voltage) / meas_voltage * R1;
}

I'm basically filling the reference and measure arrays with 0-4096 values using a sine equation to simulate a 12bit ADC like the one I have. I manually shift the measured array values by a small amount. using 0.01 as a shift, the returned values are real -0.004884, imaginary 0. With values below 0.39, I get the same. When I use 0.4, the returned values are real=-0.00518925 and imaginary=0.00030525, The next bump is for a phase delay of 1. Which means 3 useful values for a 1ms period shift

Is there something wrong in the code, or is the resolution really that low? I'm reasonably sure that the sine curves I'm generating are correct (they plot like a reasonable 10kHz sine, with some phase offset)
 

Online gf

  • Super Contributor
  • ***
  • Posts: 1295
  • Country: de
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #20 on: July 08, 2022, 05:54:10 am »
Silly typo :palm: Wrote "+" instead of "*". Correction is below.
There is of course the 12-bit quantization error. Still the error seems to be well below your target of 100ns.
In the presence of a small amount of random noise (with an RMS amplitude of say one LSB) I'd even expect an improvement, since it will whiten the quantization error, spreading it over the spectrum.

EDIT: When I add Gaussian noise with stdev=1LSB to the samples before quantization, I see an uncertainty of roughly +/-1ns for the time delay estimated from the samples (I checked it for a couple of values, but not extensively).

EDIT: If your aim is to measure the inductance of L1 (referring to your schematic in the other thread), then you don't need the phase angle directly, but you can rather solve the voltage divider
    meas_voltage = ref_voltage * R1 / (Z_L1 + R1)
for Z_L1. Then L1 = imag(Z_L1) / (2*pi*10000). Additionally the inductor may have a resistive component which is real(Z_L1).


Code: [Select]
#include <cstdint>
#include <complex>
#include <iostream>

#define PI2 6.2832
#define M_PI           3.14159265358979323846  /* pi */
// value of the L1/R1 voltage divider resistor
// fill-in the actual value
constexpr float R1 = 10000;

// samples per 10kHz period (at 100kSa/s)
constexpr int SPP = 10;

// captured ADC samples, must be integral multiple of SPP
constexpr int NSAMPLES = SPP*100;

// pre-calculated sin/cos tables
// cos_tab[i] =  cos(2*pi*i/SPP)
// sin_tab[i] = -sin(2*pi*i/SPP)
static float cos_tab[SPP] = {1,0.809016994,0.309016994,-0.309016994,-0.809016994,-1,-0.809016994,-0.309016994,0.309016994,0.809016994 };
static float sin_tab[SPP] = {0,-0.587785252,-0.951056516,-0.951056516,-0.587785252,-1.2246468e-16,0.587785252,0.951056516,0.951056516,0.587785252};

std::complex<float> calculate_DUT_impedance()

{
    int16_t ref_samples[NSAMPLES];    // reference samples captured with ADC
    int16_t meas_samples[NSAMPLES];    // measurement samples captured with ADC
   
    for (int i=0; i<NSAMPLES; i++)   // fill ref_samples and meas_samples with 10kHz sine, phase shifted
    {
// round to nearest integer
        ref_samples[i] = floor(sin(i * 2 * M_PI / 10.0) * 2048 + 2048 + 0.5);
        meas_samples[i] = floor(sin((i - 0.03) * 2 * M_PI / 10.0) * 2048 + 2048 + 0.5);
    }
   
    // calculate single-frequency DFT
    float ref_re = 0, ref_im = 0;
    float meas_re = 0, meas_im = 0;
    for (int i=0; i<NSAMPLES; i++)
    {
        ref_re += ref_samples[i] * cos_tab[i % SPP];
        ref_im += ref_samples[i] * sin_tab[i % SPP];
        meas_re += meas_samples[i] * cos_tab[i % SPP];
        meas_im += meas_samples[i] * sin_tab[i % SPP];
    }

    // complex voltages in frequency domain
    std::complex<float> ref_voltage(ref_re, ref_im);
    std::complex<float> meas_voltage(meas_re, meas_im);

std::cout << "ref phase: " << arg(ref_voltage) << std::endl;
std::cout << "meas phase: " << arg(meas_voltage) << std::endl;
std::cout << "dt[s]: " << (arg(meas_voltage) - arg(ref_voltage)) / 2 / M_PI / 10000 << std::endl;

    // Phase-shift one of the two voltages by 2*pi/20 to compensate that
    // the muxed channels are not sampled at the same time, but interleaved.
    // ref_voltage *= exp(std::complex<float>(0, -2*M_PI/(2*SPP)));
    // meas_voltage *= exp(std::complex<float>(0, -2*M_PI/(2*SPP)));

    // return impedance of DUT
    // solve voltage divider meas_voltage=ref_voltage*R1/(Z_L1+R1) for Z_L1
    return (ref_voltage - meas_voltage) / meas_voltage * R1;
}

int main()
{
    calculate_DUT_impedance();
}
« Last Edit: July 08, 2022, 11:10:12 am by gf »
 
The following users thanked this post: robca

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #21 on: July 08, 2022, 05:13:45 pm »
Silly typo :palm: Wrote "+" instead of "*". Correction is below.
There is of course the 12-bit quantization error. Still the error seems to be well below your target of 100ns.
In the presence of a small amount of random noise (with an RMS amplitude of say one LSB) I'd even expect an improvement, since it will whiten the quantization error, spreading it over the spectrum.

EDIT: When I add Gaussian noise with stdev=1LSB to the samples before quantization, I see an uncertainty of roughly +/-1ns for the time delay estimated from the samples (I checked it for a couple of values, but not extensively).

EDIT: If your aim is to measure the inductance of L1 (referring to your schematic in the other thread), then you don't need the phase angle directly, but you can rather solve the voltage divider
    meas_voltage = ref_voltage * R1 / (Z_L1 + R1)
for Z_L1. Then L1 = imag(Z_L1) / (2*pi*10000). Additionally the inductor may have a resistive component which is real(Z_L1).
[...]
works beautifully, thanks! very elegant.
 

Online jwet

  • Frequent Contributor
  • **
  • Posts: 489
  • Country: us
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #22 on: July 09, 2022, 02:10:34 am »
I knew there were greater minds here that could give you details.  I found an old academic paper that compares a bunch of techniques from the brute force time delay techniques to several interesting dsp techniques.  Attached.

This board is really is an amazing resource- I contribute what I can but inevitably learn way more from others.

Good luck on your project.
 
The following users thanked this post: robca

Online gf

  • Super Contributor
  • ***
  • Posts: 1295
  • Country: de
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #23 on: July 09, 2022, 10:06:15 am »
Two more points coming into my mind:

How clean is the power supply rail? A square wave from a digital output likely carries the power rail ripple/noise during the high cycle. PSRR of the opamp also drops significantly with increasing frequency. How immune is the ADC against power supply ripple? Do you have a separate analog supply rail? As first step, I'd at least measure the spectrum of the ripple/noise on the power supply rail, preferably at the target sample rate of 100kSa/s (in order to directly see the contribution of the folded-down aliases from higher frequency components of the ripple/noise). A rather large number of samples should be captured in order that wide band (random) noise and dedicated spurs can be clearly distinguished. A problem with measuring the power rail noise is of course that it may depend significantly on what the processor is currently doing.

Is the ADC input buffered, or does it directly connect to the sampling capacitor (via mux and S&H switch)? The latter kind of ADCs usually need to be driven by a low impedance source, so external buffering may be rquired. If the stimulus is fed through an active filter, then the source if buffered anyway, but the signal at the center tap of the L/R divider might need to be buffered as well. At least the potential error needs to be anlyzed and estimated when buffering is renounced.
« Last Edit: July 09, 2022, 01:34:17 pm by gf »
 

Offline robcaTopic starter

  • Frequent Contributor
  • **
  • Posts: 257
Re: Generating a "good enough" 10kHz sine wave from a square one
« Reply #24 on: July 09, 2022, 06:08:20 pm »
Two more points coming into my mind:

How clean is the power supply rail? A square wave from a digital output likely carries the power rail ripple/noise during the high cycle. PSRR of the opamp also drops significantly with increasing frequency. How immune is the ADC against power supply ripple? Do you have a separate analog supply rail? As first step, I'd at least measure the spectrum of the ripple/noise on the power supply rail, preferably at the target sample rate of 100kSa/s (in order to directly see the contribution of the folded-down aliases from higher frequency components of the ripple/noise). A rather large number of samples should be captured in order that wide band (random) noise and dedicated spurs can be clearly distinguished. A problem with measuring the power rail noise is of course that it may depend significantly on what the processor is currently doing.

Is the ADC input buffered, or does it directly connect to the sampling capacitor (via mux and S&H switch)? The latter kind of ADCs usually need to be driven by a low impedance source, so external buffering may be rquired. If the stimulus is fed through an active filter, then the source if buffered anyway, but the signal at the center tap of the L/R divider might need to be buffered as well. At least the potential error needs to be anlyzed and estimated when buffering is renounced.
Thanks for the additional thoughts. Measuring noise on the nRF52840 is, as you point out, difficult due to the presence of a radio that could be using orders of magnitude more power depending on the device connected and activity. I will have to take measurements and see what is the impact of noise.

Right now, the team is discussing adding one more analog sensor, which would require the use of another one or two ADC channels, making it next to impossible to use the technique we were just discussing. For now I'm proceeding in parallel with the comparator/timer approach and the DSP one. But until things are more defined on the PCB, it's hard to make any realistic measurement. The good news is that we have an extra power rail from the PMIC, so might be able to get that one clean enough.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf