Waveform samples and I/Q components

I'm quite new to DSP and I guess my problem it's quite silly but I can't find a clear answer. Basically I'm writing some python code for a test. The test requires me to write a memory with the I/Q components of a specific waveform.

As far as I know it's quite easy in hardware to get the I/Q samples. I just need to multiply the input signal ( my waveform ) with $sin(w_ct)$ and $cos(w_ct)$ ($w_c$ should be the sampling frequency) and then sample it with an ADC.

In my case I'm able to create the samples of the waveform ( it's a sawtooth but I would like to understand the general case as well ) but then I'm quite stuck because I don't know how to extract the I/Q components from each sample.

Any idea ?

Cheers, Stefano.

• Hi Stefan! IQ means a variety of techniques under the same name :-) A number of approaches are available and it's best if you can mathematically specify what output signals you really want to extract from which available signal? Please specify their sampling rates, modulation states and side-band information and complexity/reality of the samples ? Oct 19, 2017 at 10:13
• I'm not sure i'm following. As far as I know I/Q are the In-phase and Quadrature components of a signal. In hardware you just need a complex multiplier and a low pass filter. Now because I'm dealing with hardware I need to use samples. The easy thing to do is to create a sequence of samples which has the shape of a sawtooth : 0,1,2,3,4,5,6,7......255,0,1,2,3,4 and so on. I know that I need to create the samples according to the carrier but I guess I can say that the carrier of that "signal" is $f_c$. Of course this samples are real samples coming from an antenna. Does it make any sense ? Oct 19, 2017 at 11:09
• real samples from antenna makes great sense... ok. I shall look at it now. Oct 19, 2017 at 13:41

Assume that signal that comes to you through your antenna is:

$$s_r[n] = I[n] \cos(\omega_0 n) + Q[n] \sin( \omega_0 n )$$

which is a bandpass, modulated, narrow band signal where the components $I[n]$ and $Q[n]$ are the lowpass baseband in-phase and quadrature-phase components respectively.

Then in order to obtain the I and Q components from $s_r[n]$ you can apply the following just like the analog case:

$$I[n] = h_{LP}[n] \star ( s_r[n] \cos(\omega_0 n) )$$ and $$Q[n] = h_{LP}[n] \star ( s_r[n] \sin(\omega_0 n) )$$

Where the lowpass filter $h_{LP}[n]$ is will have a cutoff frequency larger than the bandwidth of the signals I and Q and less than any adjacent band (if there is any). You may also apply a scaling such as $2x$ to adjust the magnitudes.

If you would like to form the analytic lowpass signal from the I and Q then you can easily form it like:

$$x_a[n] = I[n] + j Q[n]$$

where it can be shown that the analytic signal is also given by

$$x_a[n] = A[n] e^{ j\phi(n) }$$

where $A[n] = \sqrt{ I[n]^2 + Q[n]^2 }$ and $\phi(n) = \tan^{-1}(\frac{Q[n]}{I[n]})$

Depending on the application, information in $A[n]$ (the envelope) or in $\phi(n)$ (the phase) is used.

The criteria for proper (lossless) sampling requires that the total bandwidth of the signal being sampled be below half the sample rate. In the case of complex or IQ sampling, you really get two component values (1 I or real or "in-phase" component plus 1 Q or imaginary or quadrature component) per complex or IQ sample time, so you might be able to properly sample a signal with a higher bandwidth up to almost the full sample rate. For IQ sampling, one normally wants to center the sampling frequency in the middle of the signal's frequency band, so that means all of your signal spectrum should be well within 0.5*Fs to 1.5*Fs, given an IQ sample rate of Fs.

Sorry, but your triangle wave does NOT fit this criteria. It's spectrum bandwidth is way too wide (too high and too low). You need a different kind of test signal, one with a known bandwidth, best centered around the IQ sampling rate frequency. Try some sine waves slightly higher or lower in frequency than the IQ sample rate.

What IQ sampling provides (that just real evenly spaced samples at a rate of Fs or even 2*Fs won't) is that the positive and negative frequencies won't alias.

With strictly real samples, you can't tell a signal that is slightly above half the sample rate from a signal slightly below (e.g. the upper and lower halves of an FFT of strictly real data will be exactly mirror conjugate symmetric, with no difference in magnitudes between the reflected two halves, e.g. informationally redundant).

With IQ samples, you can tell a signal that is a bit above the complex or quadrature-pair sample rate (positive frequency difference) from one a bit below it (negative difference with respect to the sample rate.) An FFT of IQ data can have different values in the two halves (lower and upper), thus allowing the IQ data convey up to twice as much information in its spectrum. This is very useful for representing SSB, FM, QPSK, and many other types of signals which are modulated using a scheme where the upper and lower sidebands are not identical mirror images.

See here for another version of this type of explanation: https://electronics.stackexchange.com/questions/39796/can-somebody-explain-what-iq-quadrature-means-in-terms-of-sdr/99617#99617

• I got it. The problem is the harmonics. When I started to plot the signal everything was fine cause I was lucky that the ratio between the frequency of the sawtooth was a multiple of the sampling frequency. I didn't care at all about the harmonics. I need to sample at a frequency which is more than twice than the bigger harmonics. That should fix the problem. Is it correct? Thank you!! Oct 20, 2017 at 6:45
• Another problem is the absence of the low pass filter after the generation of the I/Q samples I guess. Oct 20, 2017 at 11:23

Thanks to Fat32 I'm starting to understand ( I've realized my ignorance on this subject is huge ). I've spent some time to write some python code in order to understand. It helped to make me understand I hope it can be useful to others:

import sys
import os
import math
import argparse
import itertools
import subprocess

import numpy as np

def plot_samples(samples,name, unique = True):

fp = open(name+".gnuplot","w")
fp.write("set grid\n")
if unique:
fp.write("plot \""+name+".dat\" smooth unique\n")
else:
fp.write("plot \""+name+".dat\"\n")
fp.close()
fp = open(name+".dat","w")
for sample in samples:
fp.write(str(sample[0])+" "+str(sample[1])+"\n")
fp.close()
gnuplot_pid = subprocess.call(["/usr/bin/gnuplot","-persist",name+".gnuplot"])

def sawtooth_wave(amplitude = 1.0, sampling_freq = 5100.0,frequency = 50.0, num_of_periods = 10, num_harmonics = 50):
if frequency * num_harmonics * 2 > sampling_freq:
print("Warning the sampling frequency is less than twice the most high harmonic. This will lead to distortions")
print("Max signal frequency : "+str(frequency)+" x "+str(num_harmonics)+" = "+str(frequency * num_harmonics))
print("Sampling frequency : "+str(sampling_freq))
sys.exit()
#A/2 - A/pi * sum_k(-1^k * sin(2*pi*k*f*t)/k)
num_samples = math.ceil(float(sampling_freq)/float(frequency))
cycle = num_of_periods * 1.0/float(frequency)
time_axis = np.arange(0,cycle,1.0/float(sampling_freq))
samples = []

for t in time_axis:
harmonics = 0
for k in range(1,num_harmonics):
harmonics += math.pow(-1,k)*np.sin(2*np.pi*k*float(frequency)*t)/k
samples.append([t, -amplitude/np.pi * harmonics])
return samples,len(time_axis)

def parser():
parser = argparse.ArgumentParser(description='Shows some stuff')
parser.add_argument('--frequency', default='50', type=int, help='Frequency of the tone in Hz (%(default)s)')
parser.add_argument('--amplitude', default='1.0', type=float, help='Amplitude of the tone (%(default)s)')
parser.add_argument('--sampling_freq', default='2400', type=float, help='Frequency of the sampling (%(default)s)')
parser.add_argument('--carrier', default='2399', type=float, help='Frequency of the carrier (%(default)s)')
parser.add_argument('--harmonic', default='20', type=int, help='Number of harmonics for the sawtooth (%(default)s)')
return parser.parse_args()

def get_fft(samples,sampling_freq):
samples_f = np.fft.fft([i[1] for i in samples])
freqs = np.fft.fftfreq(len(samples)) * sampling_freq
return [[f,np.absolute(i)/len(samples_f)] for i,f in zip(samples_f,freqs)]

def __main__():
f2q1_8 = lambda v: (int((v * float(1 << 8)) + (-0.5 if (v < 0) else 0.5)) & 0x1FF)
args = parser()
samples,num_sample = sawtooth_wave(amplitude = args.amplitude, sampling_freq = args.sampling_freq, frequency = args.frequency, num_harmonics = args.harmonic)
plot_samples(samples,"sawtooth")

samples_f = get_fft(samples,args.sampling_freq)
plot_samples(samples_f,"spectrum_sample")

# I_Q components
I_components = [[i[0],i[1] * np.cos(2*np.pi*args.carrier*i[0])] for i in samples]
Q_components = [[i[0],i[1] * np.sin(2*np.pi*args.carrier*i[0]) * -1.0 ] for i in samples]
plot_samples(I_components,"sawtooth_I")
plot_samples(Q_components,"sawtooth_Q")
I_components_f = get_fft(I_components,args.sampling_freq)
plot_samples(I_components_f,"spectrum_sawtooth_I")
Q_components_f = get_fft(Q_components,args.sampling_freq)
plot_samples(Q_components_f,"spectrum_sawtooth_Q")

# I_Q mixing
sample_after = [[i[0], i[1] * np.cos(2*np.pi*args.carrier*i[0]) - q[1] * np.sin(2*np.pi*args.carrier*q[0])] for i,q in zip(I_components,Q_components)]
plot_samples(sample_after,"sawtooth_after_decomposition")
__main__()


This piece of code basically create some samples of a sawtooth waveforms and print the I/Q waveforms/spectrum. At the moment the model miss the low pass filter and it's a concern because when I put together the I/Q i don't see any evident problem in the shape of the waveform. So I guess there is something I'm still missing.