https://wiki.gnuradio.org/api.php?action=feedcontributions&user=172.18.0.3&feedformat=atomGNU Radio - User contributions [en]2021-06-23T02:50:12ZUser contributionsMediaWiki 1.28.0https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8682IQ Complex Tutorial2021-06-09T06:01:34Z<p>172.18.0.3: /* GNURadio XLating filter */</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_2= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is close to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex envelope, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since it allows to lower sampling rate as compared to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation helps us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal corresponds to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex envelope is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Envelope, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seem counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex envelope is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercise: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100kHz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f)</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter can perform the following actions<br />
* It can shift the spectrum in the frequency domain<br />
* It can filter the result with the specified filter<br />
* It can decimate the signal <br />
The Xlating filter is useful every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8640IQ Complex Tutorial2021-06-03T06:35:18Z<p>172.18.0.3: /* Bandpass filter */</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_2= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is close to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex envelope, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since it allows to lower sampling rate as compared to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation helps us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal corresponds to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex envelope is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Envelope, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seem counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex envelope is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercise: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100kHz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f)</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Logging&diff=8632Logging2021-05-31T14:39:04Z<p>172.18.0.3: /* Logging from Python */ small typo fix</p>
<hr />
<div>[[Category:Usage Manual]]<br />
GNU Radio has a logging interface to enable various levels of logging<br />
information to be printed to the console or a file. The logger derives<br />
from [http://log4cpp.sourceforge.net log4cpp] which is readily<br />
available in most Linux distributions. This is an optional dependency<br />
and GNU Radio will work without it.<br />
<br />
When configuring GNU Radio, the -DENABLE_GR_LOG=On|Off option to cmake<br />
will allow the user to toggle use of the logger on and off. The logger<br />
defaults to "on" and will use log4cpp if it is available. If log4cpp<br />
is not found, the default logging will output to standard output or<br />
standard error, depending on the level of the log message.<br />
<br />
Logging is useful for blocks to print out certain amounts of data at<br />
different levels. These levels are:<br />
<br />
DEBUG < INFO < WARN < TRACE < ERROR < ALERT < CRIT < FATAL < EMERG<br />
<br />
The order here determines the level of output. These levels are<br />
hierarchical in that specifying any level also includes any level<br />
above it. For example, when using the INFO level, all INFO and<br />
higher messages are logged and DEBUG is ignored. A level NOTSET is provided<br />
to disable a logger.<br />
<br />
=== Logging Configuration ===<br />
<br />
The logging configuration can be found in the '''<prefix>/etc/gnuradio/conf.d/gnuradio-runtime.conf''' file<br />
under the '''[LOG]''' section. This allows us fairly complete control over<br />
the logging facilities. The main configuration functions are to set up<br />
the level of the loggers and set the default output behavior of the<br />
loggers.<br />
<br />
There are two default loggers that all gr_block's have access to:<br />
d_logger and d_debug_logger. The first is a standard logger meant to<br />
output simple information about the block while it is running. The<br />
debug logger is meant for debugging purposes and is added to make it<br />
convenient to use a secondary logger that outputs to a different<br />
stream or file.<br />
<br />
The four main configure options are:<br />
<br />
log_level = debug<br />
debug_level = debug<br />
log_file = stdout<br />
debug_file = stderr<br />
<br />
This establishes the two loggers as having access to all levels of<br />
logging events (DEBUG through EMERG). They are also configured not to<br />
use files but instead output to the console. The standard logger will<br />
output to standard out while the debug logger outputs to standard<br />
error.<br />
<br />
Changing these last two lines to another value will create files that<br />
are used to store the log messages. All messages are appended to the<br />
file.<br />
<br />
When using either standard error or standard out, the messages for the<br />
two different loggers will look like:<br />
<br />
gr::log :<level>: <block alias> - <message><br />
gr::debug :<level>: <block alias> - <message><br />
<br />
When using a file, the only difference in the format is that the<br />
message prefix of "gr::log" or "gr::debug" is not used. Instead, the<br />
time in milliseconds from the start of the program is inserted.<br />
<br />
Remember that a local "~/.gnuradio/config.conf" file can be used to<br />
override any parameter in the global file (see [[Configuration Files]] for more details).<br />
<br />
To use these loggers inside of a GNU Radio block, we use the protected<br />
data members of d_logger and d_debug_logger of gr_block and pass them<br />
to our pre-defined macros:<br />
<br />
GR_LOG_<level>(<logger>, "<Message to print>");<br />
<br />
Where <level> is one of the levels as mentioned above, <logger> is<br />
either d_logger or d_debug_logger, and <Message to print> is the<br />
message we want to output. If we wanted to output an INFO level<br />
message to the standard logger and a WARN level message to the debug<br />
logger, it would look like this:<br />
<br />
GR_LOG_INFO(d_logger, "Some info about the block");<br />
GR_LOG_WARN(d_debug_logger, "Some warning about the block");<br />
<br />
When this is printed to wherever you are directing the output of the<br />
logger, it will look like:<br />
<br />
gr::log :INFO: <block's alias> - Some info about the block<br />
gr::debug :WARN: <block's alias> - Some warning about the block<br />
<br />
This provides us information about where the message came from, the<br />
level of the message, and the block that generated the message. We use<br />
the concept of the block's alias which by default (i.e., unless<br />
otherwise set by the user) includes the name of the block and a unique<br />
ID to distinguish it from other blocks of the same type.<br />
<br />
The various logging macros are defined in gr_logger.h. Here are some<br />
simple examples of using them:<br />
<br />
GR_LOG_DEBUG(LOG, "DEBUG message");<br />
GR_LOG_INFO(LOG, "INFO message");<br />
GR_LOG_NOTICE(LOG, "NOTICE message");<br />
GR_LOG_WARN(LOG, "WARNING message");<br />
GR_LOG_ERROR(LOG, "ERROR message");<br />
GR_LOG_CRIT(LOG, "CRIT message");<br />
GR_LOG_ALERT(LOG, "ALERT message");<br />
GR_LOG_FATAL(LOG, "FATAL message");<br />
GR_LOG_EMERG(LOG, "EMERG message");<br />
<br />
If the logger is not enabled, then these macros become nops and do<br />
nothing (and d_logger and d_debug_logger are NULL pointers). If<br />
logging is enabled but the log4cpp library is not found, then TRACE,<br />
INFO, and NOTICE levels go to stdout and the rest to stderr.<br />
<br />
=== Advanced Configuration Options ===<br />
<br />
If not using the simplified settings discussed above, where we can<br />
direct the logger messages to either a file or one of the standard<br />
outputs, we must use a more complicated configuration file. We do this<br />
by specifying the "log_config" option in the [LOG] section. The<br />
log4cpp documentation will provide more information on how<br />
configuration works and looks. Mostly, a default configuration script<br />
provided with GNU Radio can be used. After installation, the default<br />
configuration script is located at:<br />
<br />
$prefix/etc/gnuradio/gr_log_default.conf<br />
<br />
For the following examples, we will assume that our local<br />
"~/.gnuradio/config.conf" looks like this:<br />
<br />
[LOG]<br />
log_config = /opt/gr/etc/gnuadio/gr_log_default.conf<br />
log_level = debug<br />
debug_level = Off<br />
<br />
Inside of the default configuration file, we define the parameters<br />
for the two loggers, the standard logger and the separate debug logger.<br />
<br />
If the levels of the two loggers are specified in our configuration<br />
file, as in the above example, these levels override any levels<br />
specified in the log_config file. Here, we have turned on the standard logger<br />
(d_logger) to all levels and turned off the debug logger<br />
(d_debug_logger). So even if the debug logger is used in the code, it<br />
will not actually output any information. Conversely, any level of<br />
output passed to the standard logger will output because we have<br />
turned this value to the lowest level "debug."<br />
<br />
If both an log_config configuration file is set and the "log_file" or<br />
"debug_file" options are set at the same time, both systems are<br />
actually used. So you can configure file access and the pattern<br />
through the log_config file while also still outputting to stdout or stderr.<br />
<br />
== Advanced Usage ==<br />
<br />
The description above for using the logging facilities is specific to<br />
GNU Radio blocks. We have put the code necessary to access the<br />
logger into the gr_block parent class to simplify access and make<br />
sure all blocks have the ability to quickly and easily use the logger.<br />
<br />
For non gr_block-based code, we have to get some information about the<br />
logger in order to properly access it. Each logger only exists once as<br />
a singleton in the system, but we need to get a pointer to the right<br />
logger and then set it up for our local use. The following code<br />
snippet shows how to do this to get access to the standard logger,<br />
which has a root of "gr_log." (access to the debug logger is similar<br />
except we would use "gr_log_debug." in the GR_LOG_GETLOGGER call):<br />
<br />
prefs *p = prefs::singleton();<br />
std::string log_file = p->get_string("LOG", "log_config", "");<br />
std::string log_level = p->get_string("LOG", "log_level", "off");<br />
GR_CONFIG_LOGGER(log_file);<br />
GR_LOG_GETLOGGER(LOG, "gr_log." + "my_logger_name");<br />
GR_LOG_SET_LEVEL(LOG, log_level);<br />
<br />
This creates a pointer called LOG (which is instantiated as a<br />
log4cpp:LoggerPtr in the macro) that we can now use locally as the<br />
input to our logging macros like 'GR_LOG_INFO(LOG, "message")'.<br />
<br />
=== Using Logging in Out of Tree Modules ===<br />
<br />
In order to use the logging interface in an out of tree module based on a<br />
gr_modtool template module, several CMake modifications are required.<br />
Without these changes, logging will be disabled.<br />
<br />
GrMiscUtils.cmake module must be included in the OOT module top level<br />
CMakeLists.Texts file, and the GR_LOGGING() function provided by GrMiscUtils<br />
must be called from the same top level CMakeLists.txt file. This will<br />
set the appropriate build environment and during that process, attempt<br />
to find the log4cpp package using the FindLog4Cpp.cmake module.<br />
This module is not included in the module by gr_modtool, but is part of<br />
the GNU Radio codebase and can be copied directly into the cmake/Modules/<br />
directory of the OOT module.<br />
<br />
Once these CMake changes are made, the GR logging interface will function<br />
as documented on this page.<br />
<br />
<br />
<br />
==== Current GNU Radio 3.9 March 2020 ====<br />
With the current ''master'' branch, logging got way easier. In your OOT module, just write:<br />
<syntaxhighlight lang="c++"><br />
GR_LOG_INFO(this->d_logger, "My INFO message");<br />
</syntaxhighlight><br />
for logging to the standard logger with level 'INFO'. More generally, we'd use:<br />
<syntaxhighlight lang="c++"><br />
GR_LOG_<LOGLEVEL>(this->d_logger, "My <LOGLEVEL> message");<br />
</syntaxhighlight><br />
<br />
It is not necessary to apply any CMake changes or to include additional headers.<br />
Be aware that <br />
<syntaxhighlight lang="c++" inline><br />
this->d_logger<br />
</syntaxhighlight><br />
implies that you call the logger within a class inherited from <br />
<syntaxhighlight lang="c++" inline>gr::block</syntaxhighlight>.<br />
<br />
== Logging from Python ==<br />
<br />
The logging capability has been brought out python via swig (<=v3.8) or pybind11 (>=v3.9). The configuration<br />
of the logger can be manipulated via the following calls:<br />
<br />
from gnuradio import gr<br />
gr.logger_config(filename,watch_period) # Configures the logger with conf file filename<br />
names = gr.logger_get_names() # Returns the names of all loggers<br />
gr.logger_reset_config() # Resets logger config by removing all appenders<br />
<br />
Once the logger is configured you can manipulate a logger via a wrapper class gr.logger().<br />
You can instantiate this by the following. (Reference logger.h for list of methods)<br />
<br />
from gnuradio import gr<br />
log=gr.logger("nameOfLogger")<br />
log.debug("Log a debug message")<br />
log.set_level("INFO");</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8630IQ Complex Tutorial2021-05-29T19:47:00Z<p>172.18.0.3: a few typo fixes</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_2= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is close to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex envelope, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since it allows to lower sampling rate as compared to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation helps us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal corresponds to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex envelope is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Envelope, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seem counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex envelope is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercise: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100kHz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=FFT_Filter&diff=8617FFT Filter2021-05-25T16:43:45Z<p>172.18.0.3: /* Outside References */</p>
<hr />
<div>[[Category:Block Docs]]<br />
This block implements a decimating filter using the fast convolution method via an FFT. It is an alternative to the [[Decimating FIR Filter]], useful when there is a large number of taps. <br />
<br />
This filter is implemented by using the FFTW package to perform the required FFTs. An optional argument, nthreads, may be passed to the constructor (or set using the set_nthreads member function) to split the FFT among N number of threads. This can improve performance on very large FFTs (that is, if the number of taps used is very large) if you have enough threads/cores to support it.<br />
<br />
For standard filters such as lowpass, highpass, bandpass, etc., the filter.firdes and filter.optfir classes provide convenient generating methods.<br />
<br />
== Parameters ==<br />
(''R''): <span class="plainlinks">[https://wiki.gnuradio.org/index.php/GNURadioCompanion#Variable_Controls ''Run-time adjustable'']</span><br />
<br />
; Decimation <br />
: Decimation rate. The output stream will have this decimation applied to it. A decimation rate of 1 simply means no decimation. If decimation is set higher than 1, make sure the filter will remove energy outside of the "output region", i.e. -Fs/2 to Fs/2 where Fs is the input sample rate divided by the decimation rate.<br />
<br />
; Taps (''R'')<br />
: Taps to use in FIR filter.<br />
<br />
; Sample Delay<br />
: Number of additional samples to delay signal by.<br />
<br />
; Number of Threads<br />
: Number of threads to use for this block, i.e. to increase performance on multicore CPUs.<br />
<br />
== Example Flowgraph ==<br />
<br />
This flowgraph implements a Broadcast FM stereo receiver using basic blocks.<br />
<br />
[[File:USRP_FM_stereo_fg.png|644px]]<br />
<br />
== Source Files ==<br />
<br />
; C++ files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Header files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Public header files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Block definition<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
== Outside References ==<br />
: [http://www.trondeau.com/blog/2014/2/27/to-use-or-not-to-use-fft-filters.html To Use or Not to Use FFT Filters]<br />
<br />
http://blog.sdr.hu/grblocks/xlating-fir.html</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_C%2B%2B&diff=8594Guided Tutorial GNU Radio in C++2021-05-21T20:07:52Z<p>172.18.0.3: /* Specific functions related to block */</p>
<hr />
<div>[[Category:Guided Tutorials]]<br />
<br />
= Tutorial: Working with GNU Radio in C++ =<br />
<br />
== Objectives ==<br />
<br />
* Extend our knowledge to program GNU Radio using C++.<br />
* An introduction to GNU Radio's C++ API, in particular:<br />
** types<br />
** generic functions<br />
** GNU Radio blocks<br />
* Learn to manage and write our own OOT modules in C++.<br />
** We follow our discussions based on a working example by continuing to work on our OOT module.<br />
** Within the tutorial module, we will build our QPSK demodulator called as '''My QPSK Demodulator''' in C++.<br />
* Understand the nuances of developing an OOT module.<br />
** The tutorial considers some advance topics that are also part of GNU Radio framework.<br />
** These topics find their usage for some specific implementations of the OOT module.<br />
<br />
== Prerequisites ==<br />
<br />
* Knowledge of C++<br />
* Previous tutorials recommended:<br />
** [[Guided_Tutorial_Introduction|'''A brief introduction to GNU Radio, SDR, and DSP''']]<br />
** [[Guided_Tutorial_GRC|'''Intro to GR usage: GRC and flowgraphs''']]<br />
<br />
= Creating our OOT module =<br />
<br />
We will now use gr_modtool to create an OOT module and write our block in C++.<br />
<br />
== Objective ==<br />
<br />
When this tutorial is complete, we will be able to build this flow graph:<br />
<br />
[[File:Tutorial4_fg.png]]<br />
<br />
The flowgraph demonstrates a QPSK transceiver chain with the block '''My QPSK Demodulator''' block module under the OOT '''tutorial'''. We will be building this block using C++. All other blocks are standard GNU Radio blocks.<br />
<br />
'''My QPSK Demodulator''' consumes QPSK symbols which are complex values at the input and produces the alphabets as bytes at output. The output screen looks like this:<br />
<br />
[[File:Tutorial4_out.png|700px]]<br />
<br />
== Step 1: Create an OOT module <code>gr-tutorial</code> ==<br />
<br />
xyz@comp:mydir$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
<br />
Take a look at the directory structure of our <code>gr-tutorial</code><br />
<br />
xyz@comp:mydir$ cd ~/gr-tutorial<br />
xyz@comp:mydir/gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib MANIFEST.md python swig<br />
<br />
Note: The file MANIFEST.md was not added prior to GR 3.8.<br />
<br />
== Step 2: Insert My QPSK Demodulator block into the OOT module ==<br />
<br />
Again using <code>gr_modtool</code>, inside <code>gr-tutorial</code>, we create our <code>my_qpsk_demod</code> block:<br />
<br />
xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb<br />
GNU Radio module name identified: tutorial<br />
('sink', 'source', 'sync', 'decimator', 'interpolator', 'general', 'tagged_stream', 'hier', 'noblock')<br />
Enter block type: general<br />
Language (python/cpp): cpp<br />
Language: C++<br />
Block/code identifier: my_qpsk_demod_cb<br />
Please specify the copyright holder: gnuradio.org<br />
Enter valid argument list, including default arguments: <br />
bool gray_code<br />
Add Python QA code? [Y/n] Y<br />
Add C++ QA code? [y/N] N<br />
Adding file 'lib/my_qpsk_demod_cb_impl.h'...<br />
Adding file 'lib/my_qpsk_demod_cb_impl.cc'...<br />
Adding file 'include/tutorial/my_qpsk_demod_cb.h'...<br />
Editing swig/tutorial_swig.i...<br />
Adding file 'python/qa_my_qpsk_demod_cb.py'...<br />
Editing python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_my_qpsk_demod_cb.block.yml'...<br />
Editing grc/CMakeLists.txt...<br />
<br />
Unlike creating an OOT module, creating a block using <code>gr_modtool</code> demands inputs from the user. To follow the command line user interaction, let's decompose the information above.<br />
<br />
xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb<br />
<br />
<code>my_qpsk_demod_cb</code> represents the class name of the block, where the suffix, 'cb' is added to the block name, which conforms to the GNU Radio nomenclature. 'cb' states the block takes complex data as input and produces bytes as output.<br />
<br />
Enter code type: general<br />
<br />
In GNU Radio, there exist different kinds of blocks with the different possibilities listed above (since 3.8). Depending on the choice of our block, <code>gr_modtool</code> adds the corresponding code and functions. As illustrated, for <code>my_qpsk_demod_cb</code> block, we opt for a general block.<br />
<br />
Please specify the copyright holder: <br />
<br />
Since version 3.8, the tool asks for a copyright holder for the code you are about to write. The implications of what you write in that line are legal and not computational.<br />
<br />
For <code>my_qpsk_demod_cb</code>, gray_code is selected to be "default arguments".<br />
<br />
Enter valid argument list, including default arguments: bool gray_code<br />
<br />
GNU Radio provides an option of writing test cases. This provides quality assurance to the code written. If selected, the <code>gr_modtool</code> adds the quality assurance files corresponding to python and C++.<br />
<br />
Add Python QA code? [Y/n] <br />
Add C++ QA code? [y/N] y<br />
<br />
With this, we have already established the GNU Radio semantics for our block coupled with the OOT module. In the following sections, we will focus on the implementation of our block.<br />
<br />
The detailed description of coding structure for the block can be found [[BlocksCodingGuide|here]].<br />
<br />
== Step 3: Fleshing out the code ==<br />
<br />
The next step is to implement the logic for our block. Use your text editor on the file <code>~/gr-tutorial/lib/my_qpsk_demod_cb_impl.cc</code>.<br />
<br />
The completed code is listed at the end of this section, but study the following first!<br />
<br />
The skeleton of the <code>my_qpsk_demod_cb_impl.cc</code> has the following structure:<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
/*!<br />
* The private constructor<br />
*/<br />
my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)<br />
: gr::block("my_qpsk_demod_cb",<br />
gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>)),<br />
gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>)))<br />
{}<br />
</syntaxhighlight><br />
<br />
* <code>my_qpsk_demod_cb_impl()</code> is the constructor of the block <code>my_qpsk_demod</code>. <code>my_qpsk_demod_cb_impl()</code> calls the constructor of the base class block <code>gr::block(...)</code> defined [http://gnuradio.org/doc/doxygen/basic__block_8h_source.html here].<br />
<br />
* The arguments inside <code>gr::block(...)</code> represents the block name and a call to the make function.<br />
<br />
* The make function <code>gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>))</code> and <code>gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>))</code> is a member function of the class <code>gr::io_signature</code> that signifies the input and output port/s.<br />
<br />
* <''MIN_OUT''> and <''MAX_OUT''> represents the maximum and number of ports.<br />
<br />
* <ITYPE> and <OTYPE> indicates the datatypes for the input and output port/s which needs to be filled out manually.<br /><br />
Next, we need to modify the constructor. After modification, it looks like this:<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
/*!<br />
* The private constructor<br />
*/<br />
my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)<br />
: gr::block("my_qpsk_demod_cb",<br />
gr::io_signature::make(1, 1, sizeof(gr_complex)),<br />
gr::io_signature::make(1, 1, sizeof(char))),<br />
d_gray_code(gray_code)<br />
{}<br />
</syntaxhighlight><br />
<br />
The option <code>gray_code</code> is copied to the class attribute <code>d_gray_code</code>. Note that we need<br /><br />
to declare this a private member of the class in the header file <code>my_qpsk_demod_cb_impl.h</code>,<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
private:<br />
bool d_gray_code;<br />
</syntaxhighlight><br />
<br />
Also inside this class is the method <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. After running <code>gr_modtool</code>, the skeleton version of this function will look something like this:<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
int<br />
my_qpsk_demod_cb_impl::general_work (int noutput_items,<br />
gr_vector_int &ninput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items)<br />
{<br />
const <+ITYPE*> *in = (const <+ITYPE*> *) input_items[0];<br />
<+OTYPE*> *out = (<+OTYPE*> *) output_items[0];<br />
<br />
// Do <+signal processing+><br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}<br />
</syntaxhighlight><br />
<br />
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which processes the items in the input buffer and copies them to the output buffer. Once the demodulation logic is implemented, the structure of the work function has the form<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
int<br />
my_qpsk_demod_cb_impl::general_work (int noutput_items,<br />
gr_vector_int &ninput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items)<br />
{<br />
const gr_complex *in = (const gr_complex *) input_items[0];<br />
unsigned char *out = (unsigned char *) output_items[0];<br />
gr_complex origin = gr_complex(0,0);<br />
// Perform ML decoding over the input iq data to generate alphabets<br />
for(int i = 0; i < noutput_items; i++)<br />
{<br />
// ML decoder, determine the minimum distance from all constellation points<br />
out[i] = get_minimum_distances(in[i]);<br />
}<br />
<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}<br />
</syntaxhighlight><br />
<br />
This work function calls another function <code>get_minimum_distances(const gr_complex &sample)</code>, which we also need to add:<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
unsigned char<br />
my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &sample)<br />
{<br />
if (d_gray_code) {<br />
unsigned char bit0 = 0;<br />
unsigned char bit1 = 0;<br />
// The two left quadrants (quadrature component < 0) have this bit set to 1<br />
if (sample.real() < 0) {<br />
bit0 = 0x01;<br />
}<br />
// The two lower quadrants (in-phase component < 0) have this bit set to 1<br />
if (sample.imag() < 0) {<br />
bit1 = 0x01 << 1;<br />
}<br />
return bit0 | bit1;<br />
} else {<br />
// For non-gray code, we can't simply decide on signs, so we check every single quadrant.<br />
if (sample.imag() >= 0 and sample.real() >= 0) {<br />
return 0x00;<br />
}<br />
else if (sample.imag() >= 0 and sample.real() < 0) {<br />
return 0x01;<br />
}<br />
else if (sample.imag() < 0 and sample.real() < 0) {<br />
return 0x02;<br />
}<br />
else if (sample.imag() < 0 and sample.real() >= 0) {<br />
return 0x03;<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
Note: the get_minimum_distances function declaration also needs to be added to the class header (<code>my_qpsk_demod_cb_impl.h</code>).<br />
<br />
The function <code>get_minimum_distances</code> is a maximum likelihood decoder for the QPSK demodulater. Theoretically, the function should compute the distance from each ideal QPSK symbol to the received symbol (It is mathematically equivalent to determining the Voronoi regions of the received sample). For a QPSK signal, these Voronoi regions are simply four quadrants in the complex plane. Hence, to decode the sample into bits, it makes sense to map the received sample to these quadrants.<br />
<br />
Now, let's consider the <code>forecast()</code> function. The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the <code>forecast()</code> method provides this information, and you must therefore override it anytime you write a <code>gr::block</code> derivative (for sync blocks, this is implicit).<br />
<br />
The default implementation of <code>forecast()</code> says there is a 1:1 relationship between noutput_items and the requirements for each input stream. The size of the items is defined by <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
// default implementation: 1:1<br />
void<br />
gr::block::forecast(int noutput_items,<br />
gr_vector_int &ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i < ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}<br />
</syntaxhighlight><br />
<br />
Although the 1:1 implementation worked for <code>my_qpsk_demod_cb</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from <code>gr::sync_block</code>, <code>gr::sync_interpolator</code> or <code>gr::sync_decimator</code> instead of <code>gr::block</code>, you can often avoid implementing <code>forecast</code>.<br />
<br />
Refilling the private constructor and overriding the <code>general_work()</code> and <code>forecast()</code> will suffice the coding structure of our block. However, in the <code>gr::block</code> class there exists more specific functions. These functions are covered under [http://gnuradio.org/redmine/projects/gnuradio/wiki/Guided_Tutorial_GNU_Radio_in_C++#Advanced-topics advanced topics section]<br />
<br />
Here is the completed source code:<br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
/* -*- c++ -*- */<br />
/* <br />
* my_qpsk_demod_cb_impl.h<br />
*<br />
* This is free software; you can redistribute it and/or modify<br />
* it under the terms of the GNU General Public License as published by<br />
* the Free Software Foundation; either version 3, or (at your option)<br />
* any later version.<br />
* <br />
* This software is distributed in the hope that it will be useful,<br />
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br />
* GNU General Public License for more details.<br />
* <br />
* You should have received a copy of the GNU General Public License<br />
* along with this software; see the file COPYING. If not, write to<br />
* the Free Software Foundation, Inc., 51 Franklin Street,<br />
* Boston, MA 02110-1301, USA.<br />
*/<br />
<br />
#ifndef INCLUDED_TUTORIAL_MY_QPSK_DEMOD_CB_IMPL_H<br />
#define INCLUDED_TUTORIAL_MY_QPSK_DEMOD_CB_IMPL_H<br />
<br />
#include <tutorial/my_qpsk_demod_cb.h><br />
<br />
namespace gr {<br />
namespace tutorial {<br />
<br />
class my_qpsk_demod_cb_impl : public my_qpsk_demod_cb<br />
{<br />
private:<br />
bool d_gray_code;<br />
<br />
public:<br />
my_qpsk_demod_cb_impl(bool gray_code);<br />
~my_qpsk_demod_cb_impl();<br />
unsigned char get_minimum_distances(const gr_complex &sample);<br />
<br />
// Where all the action really happens<br />
void forecast (int noutput_items, gr_vector_int &ninput_items_required);<br />
<br />
int general_work(int noutput_items,<br />
gr_vector_int &ninput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items);<br />
};<br />
<br />
} // namespace tutorial<br />
} // namespace gr<br />
<br />
#endif /* INCLUDED_TUTORIAL_MY_QPSK_DEMOD_CB_IMPL_H */<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight lang="cpp" line="line"><br />
/* -*- c++ -*- */<br />
/* <br />
* my_qpsk_demod_cb_impl.cc<br />
*<br />
* This is free software; you can redistribute it and/or modify<br />
* it under the terms of the GNU General Public License as published by<br />
* the Free Software Foundation; either version 3, or (at your option)<br />
* any later version.<br />
* <br />
* This software is distributed in the hope that it will be useful,<br />
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br />
* GNU General Public License for more details.<br />
* <br />
* You should have received a copy of the GNU General Public License<br />
* along with this software; see the file COPYING. If not, write to<br />
* the Free Software Foundation, Inc., 51 Franklin Street,<br />
* Boston, MA 02110-1301, USA.<br />
*/<br />
<br />
#ifdef HAVE_CONFIG_H<br />
#include "config.h"<br />
#endif<br />
<br />
#include <gnuradio/io_signature.h><br />
#include "my_qpsk_demod_cb_impl.h"<br />
<br />
namespace gr {<br />
namespace tutorial {<br />
<br />
my_qpsk_demod_cb::sptr<br />
my_qpsk_demod_cb::make(bool gray_code)<br />
{<br />
return gnuradio::get_initial_sptr<br />
(new my_qpsk_demod_cb_impl(gray_code));<br />
}<br />
<br />
/*<br />
* The private constructor<br />
*/<br />
my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)<br />
: gr::block("my_qpsk_demod_cb",<br />
gr::io_signature::make(1, 1, sizeof(gr_complex)),<br />
gr::io_signature::make(1, 1, sizeof(char))),<br />
d_gray_code(gray_code)<br />
{<br />
}<br />
<br />
/*<br />
* Our virtual destructor.<br />
*/<br />
my_qpsk_demod_cb_impl::~my_qpsk_demod_cb_impl()<br />
{<br />
}<br />
<br />
void<br />
my_qpsk_demod_cb_impl::forecast(int noutput_items,<br />
gr_vector_int &ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i < ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}<br />
<br />
unsigned char<br />
my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &sample)<br />
{<br />
if (d_gray_code) {<br />
unsigned char bit0 = 0;<br />
unsigned char bit1 = 0;<br />
// The two left quadrants (quadrature component < 0) have this bit set to 1<br />
if (sample.real() < 0) {<br />
bit0 = 0x01;<br />
}<br />
// The two lower quadrants (in-phase component < 0) have this bit set to 1<br />
if (sample.imag() < 0) {<br />
bit1 = 0x01 << 1;<br />
}<br />
return bit0 | bit1;<br />
} else {<br />
// For non-gray code, we can't simply decide on signs, so we check every single quadrant.<br />
if (sample.imag() >= 0 and sample.real() >= 0) {<br />
return 0x00;<br />
}<br />
else if (sample.imag() >= 0 and sample.real() < 0) {<br />
return 0x01;<br />
}<br />
else if (sample.imag() < 0 and sample.real() < 0) {<br />
return 0x02;<br />
}<br />
else if (sample.imag() < 0 and sample.real() >= 0) {<br />
return 0x03;<br />
}<br />
}<br />
}<br />
<br />
int<br />
my_qpsk_demod_cb_impl::general_work (int noutput_items,<br />
gr_vector_int &ninput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items)<br />
{<br />
const gr_complex *in = (const gr_complex *) input_items[0];<br />
unsigned char *out = (unsigned char *) output_items[0];<br />
gr_complex origin = gr_complex(0,0);<br />
// Perform ML decoding over the input iq data to generate alphabets<br />
for(int i = 0; i < noutput_items; i++)<br />
{<br />
// ML decoder, determine the minimum distance from all constellation points<br />
out[i] = get_minimum_distances(in[i]);<br />
}<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}<br />
<br />
} /* namespace tutorial */<br />
} /* namespace gr */<br />
</syntaxhighlight><br />
<br />
== Step 4: Flesh out the XML file ==<br />
<br />
In GNU Radio 3.7 the .xml provides the user interface between the OOT module displayed in the GRC and the source code. For GNU Radio 3.8 and newer, see the next section which describes the YAML format. Moreover, the XML file defines an interface to pass the parameters specific for the module. Hence, to access the module inside GRC, it is important to modify the .xml files manually. The XML file for our block is named as <code>demod_my_qpsk_demod_cb.xml</code> inside the <code>grc/</code> folder. Presently, the <code>gr_modtool</code>'s version looks like:<br />
<br />
Default version:<br />
<pre><br />
<?xml version="1.0"?><br />
<block><br />
<name>my_qpsk_demod_cb</name><br />
<key>tutorial_my_qpsk_demod_cb</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.my_qpsk_demod_cb($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block><br />
<br />
</pre><br />
<br />
<br />
The parameter <code>gray_code</code> can be put under the <code><parameter></code> tag.<br />
<br />
Adding parameter tag:<br />
<br />
<pre><br />
<?xml version="1.0"?><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<value>True</value><br />
<type>bool</type><br />
<option><br />
<name>Yes</name><br />
<key>True</key><br />
</option><br />
<option><br />
<name>No</name><br />
<key>False</key><br />
</option><br />
</param> <br />
</pre><br />
<br />
<br />
Like the work function, the datatypes for the input and output ports represented by <code><sink></code> and <code><nowiki><source></nowiki></code> tags should be modified.<br />
<br />
Modifying source and sink tag:<br />
<br />
<pre> <br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
</pre><br />
<pre> <br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</pre><br />
<br />
<br />
After all the necessary modification the "tutorial_my_qpsk_demod_cb.xml" looks like this:<br />
<br />
Modified version:<br />
<br />
<pre><br />
<?xml version="1.0"?><br />
<block><br />
<name>My QPSK Demodulator</name><br />
<key>tutorial_my_qpsk_demod_cb</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.my_qpsk_demod_cb($gray_code)</make><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<value>True</value><br />
<type>bool</type><br />
<option><br />
<name>Yes</name><br />
<key>True</key><br />
</option><br />
<option><br />
<name>No</name><br />
<key>False</key><br />
</option><br />
</param><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</pre><br />
<br />
== Step 4 bis: Flesh out the YAML file ==<br />
<br />
Since version 3.8, GNU Radio has replaced the XML files by YAML. They work the same but with a different syntax.<br />
<br />
The .yml provides the user interface between the OOT module displayed in the GRC and the source code. Moreover, the YAML file defines an interface to pass the parameters specific for the module. Hence, to access the module inside GRC, it is important to modify the .yml files manually. The YAML file for our block is named as <code>tutorial_my_qpsk_demod_cb.block.yml</code> inside the <code>grc/</code> folder. Presently, the <code>gr_modtool</code>'s version looks like:<br />
<br />
Default version:<br />
<pre><br />
id: tutorial_my_qpsk_demod_cb<br />
label: my_qpsk_demod_cb<br />
category: '[tutorial]'<br />
<br />
templates:<br />
imports: import tutorial<br />
make: tutorial.my_qpsk_demod_cb(${gray_code})<br />
<br />
# Make one 'parameters' list entry for every parameter you want settable from the GUI.<br />
# Keys include:<br />
# * id (makes the value accessible as \$keyname, e.g. in the make entry)<br />
# * label (label shown in the GUI)<br />
# * dtype (e.g. int, float, complex, byte, short, xxx_vector, ...)<br />
parameters:<br />
- id: ...<br />
label: ...<br />
dtype: ...<br />
- id: ...<br />
label: ...<br />
dtype: ...<br />
<br />
# Make one 'inputs' list entry per input and one 'outputs' list entry per output.<br />
# Keys include:<br />
# * label (an identifier for the GUI)<br />
# * domain (optional - stream or message. Default is stream)<br />
# * dtype (e.g. int, float, complex, byte, short, xxx_vector, ...)<br />
# * vlen (optional - data stream vector length. Default is 1)<br />
# * optional (optional - set to 1 for optional inputs. Default is 0)<br />
inputs:<br />
- label: ...<br />
domain: ...<br />
dtype: ...<br />
vlen: ...<br />
optional: ...<br />
<br />
outputs:<br />
- label: ...<br />
domain: ...<br />
dtype: ...<br />
vlen: ...<br />
optional: ...<br />
<br />
# 'file_format' specifies the version of the GRC yml format used in the file<br />
# and should usually not be changed.<br />
file_format: 1<br />
</pre><br />
<br />
<br />
The parameter <code>gray_code</code> can be put under the <code>parameters</code> tag.<br />
<br />
Adding parameter tag:<br />
<br />
<pre><br />
parameters:<br />
- id: gray_code<br />
label: Gray Code<br />
dtype: bool<br />
default: 'True' <br />
</pre><br />
<br />
<br />
Like the work function, the datatypes for the input and output ports represented by <code>input</code> and <code><nowiki>output</nowiki></code> tags should be modified.<br />
<br />
Modifying source and sink tag:<br />
<br />
<pre> <br />
inputs:<br />
- label: in<br />
dtype: complex<br />
</pre><br />
<pre> <br />
outputs:<br />
- label: out<br />
dtype: byte<br />
</pre><br />
<br />
<br />
After all the necessary modification the "tutorial_my_qpsk_demod_cb.block.yml" looks like this:<br />
<br />
Modified version:<br />
<br />
<pre><br />
id: tutorial_my_qpsk_demod_cb<br />
label: My QPSK Demodulator<br />
category: '[tutorial]'<br />
<br />
templates:<br />
imports: import tutorial<br />
make: tutorial.my_qpsk_demod_cb(${gray_code})<br />
<br />
parameters:<br />
- id: gray_code<br />
label: Gray Code<br />
dtype: bool<br />
default: 'True'<br />
<br />
inputs:<br />
- label: in<br />
dtype: complex<br />
outputs:<br />
- label: out<br />
dtype: byte<br />
<br />
file_format: 1<br />
</pre><br />
<br />
== Step 5: Install my_qpsk_demod in grc ==<br />
<br />
Now that we have finished the implementation of our block, we need to build and install it. To do so, execute the following commands:<br />
<br />
<pre><br />
cd ~/gr-tutorial<br />
mkdir build<br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
<br />
== Step 6: Quality Assurance (Unit Testing) ==<br />
<br />
In the previous steps of writing the OOT module, we produced the QPSK demodulator, but it doesn't guarantee the correct working of our block. In this situation, it becomes significant to write a unit test for our module that certifies the clean implementation of the QPSK demodulator.<br />
<br />
Below is the source of code of the <code>qa_qpsk_demod.py</code> can be found under <code>python/</code><br />
<br />
Full QA code<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import tutorial_swig as tutorial<br />
from numpy import array<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_gray_code_enabled (self):<br />
# "Construct the Iphase and Qphase components"<br />
Iphase = array([ 1, -1, -1, 1])<br />
Qphase = array([ 1, 1, -1, -1])<br />
src_data = Iphase + 1j*Qphase;<br />
# "Enable Gray code"<br />
gray_code = True;<br />
# "Determine the expected result"<br />
expected_result = (0,1,3,2)<br />
# "Create a complex vector source"<br />
src = blocks.vector_source_c(src_data)<br />
# "Instantiate the test module"<br />
qpsk_demod = tutorial.my_qpsk_demod_cb(gray_code)<br />
# "Instantiate the binary sink"<br />
dst = blocks.vector_sink_b();<br />
# "Construct the flowgraph"<br />
self.tb.connect(src,qpsk_demod)<br />
self.tb.connect(qpsk_demod,dst)<br />
# "Create the flow graph"<br />
self.tb.run ()<br />
# check data<br />
result_data = dst.data()<br />
self.assertTupleEqual(expected_result, result_data)<br />
self.assertEqual(len(expected_result), len(result_data))<br />
<br />
def test_002_gray_code_disabled (self):<br />
# "Construct the Iphase and Qphase components"<br />
Iphase = array([ 1, -1, -1, 1])<br />
Qphase = array([ 1, 1, -1, -1])<br />
src_data = Iphase + 1j*Qphase;<br />
# "Enable Gray code"<br />
gray_code = False;<br />
# "Determine the expected result"<br />
expected_result = (0,1,2,3)<br />
# "Create a complex vector source"<br />
src = blocks.vector_source_c(src_data)<br />
# "Instantiate the test module"<br />
qpsk_demod = tutorial.my_qpsk_demod_cb(gray_code)<br />
# "Instantiate the binary sink"<br />
dst = blocks.vector_sink_b();<br />
# "Construct the flowgraph"<br />
self.tb.connect(src,qpsk_demod)<br />
self.tb.connect(qpsk_demod,dst)<br />
# "Create the flow graph"<br />
self.tb.run ()<br />
# check data<br />
result_data = dst.data()<br />
self.assertTupleEqual(expected_result, result_data)<br />
self.assertEqual(len(expected_result), len(result_data))<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qa_qpsk_demod.xml")<br />
</syntaxhighlight><br />
<br />
<br />
Obviously the <code>qa_qpsk_demod</code> is implemented in python, in spite of we opted C++ in the first case for writing our blocks. This is because GNU Radio inherits the [https://docs.python.org/2/library/unittest.html python unittest framework] to support quality assurance. And, if you remember it correctly from previous tutorials, swig as part of GNU Radio framework, provides python bindings for the C++ code. Hence, we are able to write the unit test for our block <code>qa_qpsk_demod</code> in Python.<br />
<br />
So lets gather a bit of know how on how to write test cases for the block. Okay, lets consider the header part first:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import tutorial_swig as tutorial<br />
from numpy import array<br />
</syntaxhighlight><br />
<br />
<code>from gnuradio import gr, gr_unittest</code> and <code>from gnuradio import blocks</code> are the standard lines that includes gr, gr_unittest functionality in the <code>qa_</code> file. <code>import tutorial_swig as tutorial</code> import the python bidden version of our module, which provides an access our block <code>my_qpsk_demod_cb</code>. Finally, <code>from numpy import array</code> includes array.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qa_qpsk_demod.xml")<br />
</syntaxhighlight><br />
<br />
The <code>qa_</code> file execution start by calling this function. The <code>gr_unittest</code> automatically calls the functions in a specific order <code>def setUp (self)</code> for creating the top block at the start, <code>tearDown (self)</code> for deleting the top block at the end. In between the <code>setUp</code> and <code>tearDown</code> the test cases defined are executed. The methods starting with prefix <code>test_</code> are recognized as test cases by <code>gr_unittest</code>. We have defined two test cases <code>test_001_gray_code_enabled</code> and <code>test_002_gray_code_disabled</code>. The usual structure of a test cases comprises of a known input data and the expected output. A flowgraph is created to include the source (input data), block to be tested (processor) and sink (resulted output data). In the end the expected output is compared with the resulted output data.<br />
<br />
Finally, the statements in the test cases<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertTupleEqual(expected_result, result_data)<br />
self.assertEqual(len(expected_result), len(result_data))<br />
</syntaxhighlight><br />
determine the result of test cases as passed or failed. The test cases are executed before installation of the module by running <code>make test</code> yielding the following output:<br />
<pre><br />
barry@barry:~/gr-tutorial/build$ make test<br />
Running tests...<br />
Test project /home/barry/gr-tutorial/build<br />
Start 1: test_tutorial<br />
1/2 Test #1: test_tutorial .................... Passed 0.02 sec<br />
Start 2: qa_my_qpsk_demod_cb<br />
2/2 Test #2: qa_my_qpsk_demod_cb .............. Passed 1.01 sec<br />
<br />
100% tests passed, 0 tests failed out of 2<br />
<br />
Total Test time (real) = 1.04 sec<br />
</pre><br />
<br />
Congratulations, we have just finished writing our OOT module <code>gr-tutorial</code> and a C++ block <code>my_qpsk_demodulator</code>.<br />
<br />
= Advanced topics =<br />
<br />
The topics discussed until now have laid the foundation for designing the OOT module independently. However, the GNU Radio jargon extends further beyond these. Therefore, under this section, we drift from the QPSK demodulator and focus on the features that are rarely used or are more specific to the implementation.<br />
<br />
To add physical meaning to the discussion, we have taken assistance of the existing modules. The source code excerpts are included thereof. Enthusiastic readers are suggested to open the source code in parallel and play around with their functionalities.<br />
<br />
== Specific functions related to block ==<br />
<br />
In the last section, we managed out implementation of our block by defining functions like <code>general_work</code> and <code>forecast()</code>. But sometimes special functions need to be defined for the implementation. The list is long, but we try to discuss some of these functions in the following subsections.<br />
<br />
=== set_history() ===<br />
<br />
If your block needs a history (i.e., something like an FIR filter), call this in the constructor.<br /><br />
Here is an example<br />
<br />
<syntaxhighlight lang="cpp"><br />
test::test(const std::string &name,<br />
int min_inputs, int max_inputs,<br />
unsigned int sizeof_input_item,<br />
int min_outputs, int max_outputs,<br />
unsigned int sizeof_output_item,<br />
unsigned int history,<br />
unsigned int output_multiple,<br />
double relative_rate,<br />
bool fixed_rate,<br />
consume_type_t cons_type, produce_type_t prod_type)<br />
: block (name,<br />
io_signature::make(min_inputs, max_inputs, sizeof_input_item),<br />
io_signature::make(min_outputs, max_outputs, sizeof_output_item)),<br />
d_sizeof_input_item(sizeof_input_item),<br />
d_sizeof_output_item(sizeof_output_item),<br />
d_check_topology(true),<br />
d_consume_type(cons_type),<br />
d_min_consume(0),<br />
d_max_consume(0),<br />
d_produce_type(prod_type),<br />
d_min_produce(0),<br />
d_max_produce(0)<br />
{<br />
set_history(history);<br />
set_output_multiple(output_multiple);<br />
set_relative_rate(relative_rate);<br />
set_fixed_rate(fixed_rate);<br />
}<br />
</syntaxhighlight><br />
<br />
<br />
GNU Radio then makes sure you have the given number of 'old' items available.<br />
<br />
The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.<br />
<br />
The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).<br />
<br />
The history is stored in the variable <code>d_history</code>.<br /><br />
The <code>set_history()</code> is defined in <code>gnuradio/gnuradio-runtime/block.cc</code><br />
<br />
<syntaxhighlight lang="cpp"><br />
void block::set_history(unsigned history)<br />
{<br />
d_history = history;<br />
}<br />
</syntaxhighlight><br />
<br />
=== set_output_multiple() ===<br />
<br />
When implementing your <code>general_work()</code> routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call <code>set_output_multiple</code> in your constructor to specify this requirement,<br />
<br />
{{collapse(code)<br />
<br />
<syntaxhighlight lang="cpp"><br />
test::test(const std::string &name,<br />
int min_inputs, int max_inputs,<br />
unsigned int sizeof_input_item,<br />
int min_outputs, int max_outputs,<br />
unsigned int sizeof_output_item,<br />
unsigned int history,<br />
unsigned int output_multiple,<br />
double relative_rate,<br />
bool fixed_rate,<br />
consume_type_t cons_type, produce_type_t prod_type)<br />
: block (name,<br />
io_signature::make(min_inputs, max_inputs, sizeof_input_item),<br />
io_signature::make(min_outputs, max_outputs, sizeof_output_item)),<br />
d_sizeof_input_item(sizeof_input_item),<br />
d_sizeof_output_item(sizeof_output_item),<br />
d_check_topology(true),<br />
d_consume_type(cons_type),<br />
d_min_consume(0),<br />
d_max_consume(0),<br />
d_produce_type(prod_type),<br />
d_min_produce(0),<br />
d_max_produce(0)<br />
{<br />
set_history(history);<br />
set_output_multiple(output_multiple);<br />
set_relative_rate(relative_rate);<br />
set_fixed_rate(fixed_rate);<br />
}<br />
</syntaxhighlight><br />
}}<br />
<br />
by invoking <code>set_output_multiple</code>, we set the value variable to <code>d_output_multiple</code>. The default value of <code>d_output_multiple</code> is 1.<br />
<br />
Lets consider an example, say we want to generate outputs only in a 64 elements chunk, by setting d_output_multiple to 64 we can achieve this, but note that we can also get multiples of 64 i.e. 128, 256 etc<br />
<br />
The definition of <code>set_output_multiple</code> can be found in gnuradio/gnuradio-runtime/block.cc<br />
<br />
<syntaxhighlight lang="cpp">void gr_block::set_output_multiple (int multiple)<br />
{<br />
if (multiple < 1)<br />
throw std::invalid_argument ("gr_block::set_output_multiple");<br />
<br />
d_output_multiple_set = true;<br />
d_output_multiple = multiple;<br />
}</syntaxhighlight><br />
<br />
== Specific block categories ==<br />
<br />
Again the implementation of the <code>my_qpsk_demod_cb</code> was done using a general block. However, GNU Radio includes some blocks with special functionality. A brief overview of these blocks is described in the table.<br />
<br />
{|<br />
| '''Block'''<br />
| '''Functionality'''<br />
|-<br />
| General<br />
| This block a generic version of all blocks<br />
|-<br />
| Source/Sinks<br />
| The source/sink produce/consume the input/output items<br />
|-<br />
| Interpolation/Decimation<br />
| The interpolation/decimation block is another type of fixed rate block where the number of output/input items is a fixed multiple of the number of input/output items.<br />
|-<br />
| Sync<br />
| The sync block allows users to write blocks that consume and produce an equal number of items per port. From the user perspective, the GNU Radio scheduler synchronizes the input and output items, it has nothing to with synchronization algorithms<br />
|-<br />
| Hierarchical blocks<br />
| Hierarchical blocks are blocks that are made up of other blocks.<br />
|}<br />
<br />
In the next subsections we discuss these blocks in detail. Again, enthusiastic readers can find these blocks in the GNU Radio source code.<br />
<br />
=== General ===<br />
<br />
<syntaxhighlight lang="cpp">howto_square_ff::howto_square_ff ()<br />
: gr::block("square_ff",<br />
gr::io_signature::make(MIN_IN, MAX_IN, sizeof (float)),<br />
gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (float)))<br />
{<br />
// nothing else required in this example<br />
}<br />
</syntaxhighlight><br />
<br />
=== Source and Sinks ===<br />
<br />
==== Source ====<br />
<br />
An example of source block in C++<br />
<br />
<syntaxhighlight lang="cpp"><br />
usrp_source_impl::usrp_source_impl(const ::uhd::device_addr_t &device_addr,<br />
const ::uhd::stream_args_t &stream_args):<br />
sync_block("gr uhd usrp source",<br />
io_signature::make(0, 0, 0),<br />
args_to_io_sig(stream_args)),<br />
_stream_args(stream_args),<br />
_nchan(stream_args.channels.size()),<br />
_stream_now(_nchan == 1),<br />
_tag_now(false),<br />
_start_time_set(false)<br />
<br />
</syntaxhighlight><br />
Some observations:<br />
<br />
* <code>io_signature::make(0, 0, 0)</code> sets the input items to 0, in indicates there are no input streams.<br />
* Because it connected with the hardware USRP, the <code>gr uhd usrp source</code> is a sub class of <code>sync_block</code>.<br />
<br />
==== Sink ====<br />
<br />
An example of the sink block in C++<br />
<br />
<syntaxhighlight lang="cpp"><br />
usrp_sink_impl::usrp_sink_impl(const ::uhd::device_addr_t &device_addr,<br />
const ::uhd::stream_args_t &stream_args)<br />
: sync_block("gr uhd usrp sink",<br />
args_to_io_sig(stream_args),<br />
io_signature::make(0, 0, 0)),<br />
_stream_args(stream_args),<br />
_nchan(stream_args.channels.size()),<br />
_stream_now(_nchan == 1),<br />
_start_time_set(false)<br />
<br />
</syntaxhighlight><br />
Some observations:<br />
<br />
* <code>io_signature::make(0, 0, 0)</code> sets the output items to 0, in indicates there are no output streams.<br />
* Because it connected with the hardware USRP, the <code>gr uhd usrp sink</code> is a sub class of <code>sync_block</code>.<br />
<br />
=== Sync ===<br />
<br />
The sync block allows users to write blocks that consume and produce an equal number of items per port. A sync block may have any number of inputs or outputs. When a sync block has zero inputs, its called a source. When a sync block has zero outputs, its called a sink.<br />
<br />
An example sync block in C++:<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gnuradio/sync_block.h><br />
<br />
class my_sync_block : public gr_sync_block<br />
{<br />
public:<br />
my_sync_block(...):<br />
gr_sync_block("my block",<br />
gr::io_signature::make(1, 1, sizeof(int32_t)),<br />
gr::io_signature::make(1, 1, sizeof(int32_t)))<br />
{<br />
//constructor stuff<br />
}<br />
<br />
int work(int noutput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items)<br />
{<br />
//work stuff...<br />
return noutput_items;<br />
}<br />
};<br />
</syntaxhighlight><br />
Some observations:<br />
<br />
* noutput_items is the length in items of all input and output buffers<br />
* an input signature of gr::io_signature::make(0, 0, 0) makes this a source block<br />
* an output signature of gr::io_signature::make(0, 0, 0) makes this a sink block<br />
<br />
=== Rate changing blocks: Interpolation and Decimation ===<br />
<br />
==== Decimation ====<br />
<br />
The decimation block is another type of fixed rate block where the number of input items is a fixed multiple of the number of output items.<br />
<br />
An example decimation block in c++<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gr_sync_decimator.h> <br />
<br />
class my_decim_block : public gr_sync_decimator<br />
{<br />
public:<br />
my_decim_block(...):<br />
gr_sync_decimator("my decim block", <br />
in_sig,<br />
out_sig,<br />
decimation)<br />
{<br />
//constructor stuff<br />
}<br />
<br />
//work function here...<br />
};<br />
</syntaxhighlight><br />
Some observations:<br />
<br />
* The gr_sync_decimator constructor takes a 4th parameter, the decimation factor<br />
* The user must assume that the number of input items = noutput_items*decimation. The value <code>ninput_items</code> is therefore implicit.<br />
<br />
==== Interpolation ====<br />
<br />
The interpolation block is another type of fixed rate block where the number of output items is a fixed multiple of the number of input items.<br />
<br />
An example interpolation block in c++<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gnuradio/sync_interpolator.h> <br />
<br />
class my_interp_block : public gr_sync_interpolator<br />
{<br />
public:<br />
my_interp_block(...):<br />
gr_sync_interpolator("my interp block",<br />
in_sig,<br />
out_sig,<br />
interpolation)<br />
{<br />
//constructor stuff<br />
}<br />
<br />
//work function here...<br />
};<br />
</syntaxhighlight><br />
<br />
Some observations:<br />
<br />
* The gr_sync_interpolator constructor takes a 4th parameter, the interpolation factor<br />
* The user must assume that the number of input items = noutput_items/interpolation<br />
<br />
=== Hierarchical blocks ===<br />
<br />
Hierarchical blocks are blocks that are made up of other blocks. They instantiate the other GNU Radio blocks (or other hierarchical blocks) and connect them together. A hierarchical block has a "connect" function for this purpose.<br />
<br />
When to use hierarchical blocks?<br />
<br />
Hierarchical blocks provides us modularity in our flowgraphs by abstracting simple blocks, that is hierarchical block helps us define our specific blocks at the same time provide us the flexibility to change it, example, we would like to test effect of different modulation schemes for a given channel model. However our synchronization algorithms are specific or newly published. We define our hier block as gr-my_sync that does synchronization followed equalizer and demodulation. We start with BPSK, the flowgraph looks like<br />
<br />
gr-tx ---> gr-channel --> gr-my_sync --> gr-equalizer --> gr-bpsk_demod<br />
<br />
Now, our flowgraph looks decent. Secondly, we abstracted the complex functionality of our synchronization. Shifting to QPSK, where the synchronization algorithm remains the same, we just replace the gr-bpsk_demod with gr-qpsk_demod<br />
<br />
gr-tx ---> gr-channel --> gr-my_sync --> gr-equalizer --> gr-qpsk_demod<br />
<br />
How to build hierarchical blocks in GNU Radio?<br />
<br />
Hierarchical blocks define an input and output stream much like normal blocks. For '''I''' input streams, let '''i''' be a value between 0 and '''I'''-1. To connect input '''i''' to a hierarchical block, the source is (in Python):<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect((self, <i>), <block>)<br />
</syntaxhighlight><br />
<br />
Similarly, to send the signal out of the block on output stream '''o''':<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(<block>, (self, <o>))<br />
</syntaxhighlight><br />
<br />
An typical example of a hierarchical block is OFDM Receiver implemented in python under <pre>gnuradio/gr-digital/python/digital</pre><br />
<br />
The class is defined as:<br />
<br />
<syntaxhighlight lang="python"><br />
class ofdm_receiver(gr.hier_block2)<br />
</syntaxhighlight><br />
<br />
and instantiated as<br />
<br />
<syntaxhighlight lang="python"><br />
gr.hier_block2.__init__(self, "ofdm_receiver",<br />
gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature<br />
gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature<br />
</syntaxhighlight><br />
<br />
Some main tasks performed by the OFDM receiver include channel filtering, synchronization and IFFT tasks. The individual tasks are defined inside the hierarchical block.<br />
<br />
* Channel filtering<br />
<br />
<syntaxhighlight lang="python"><br />
chan_coeffs = filter.firdes.low_pass (1.0, # gain<br />
1.0, # sampling rate<br />
bw+tb, # midpoint of trans. band<br />
tb, # width of trans. band<br />
filter.firdes.WIN_HAMMING) # filter type<br />
self.chan_filt = filter.fft_filter_ccc(1, chan_coeffs)<br />
</syntaxhighlight><br />
<br />
* Synchronization<br />
<br />
<syntaxhighlight lang="python"><br />
self.chan_filt = blocks.multiply_const_cc(1.0)<br />
nsymbols = 18 # enter the number of symbols per packet<br />
freq_offset = 0.0 # if you use a frequency offset, enter it here<br />
nco_sensitivity = -2.0/fft_length # correct for fine frequency<br />
self.ofdm_sync = ofdm_sync_fixed(fft_length,<br />
cp_length,<br />
nsymbols,<br />
freq_offset,<br />
logging)<br />
</syntaxhighlight><br />
<br />
* ODFM demodulation<br />
<br />
<syntaxhighlight lang="python"><br />
self.fft_demod = gr_fft.fft_vcc(fft_length, True, win, True)<br />
</syntaxhighlight><br />
<br />
Finally, the individual blocks along with hierarchical are connected among each to form a flow graph.<br />
<br />
Connection between the hierarchical block OFDM receiver to channel filter block<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(self, self.chan_filt) # filter the input channel<br />
</syntaxhighlight><br />
Connection between the channel filter block to the OFDM synchronization block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(self.chan_filt, self.ofdm_sync)<br />
</syntaxhighlight><br />
and so forth.<br />
<br />
Hierarchical blocks can also be nested, that is blocks defined in hierarchical blocks could also be hierarchical blocks. For example, OFDM sync block is also an hierarchical block. In this particular case it is implemented in <code>C++</code>. Lets have a look into it.<br />
<br />
Underneath is instant of the hierarchical block. Don't panic by looking at its size, we just need to grab the concept behind creating hierarchical blocks.<br />
<br />
OFDM impl<br />
<br />
<syntaxhighlight lang="cpp"><br />
ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)<br />
: hier_block2 ("ofdm_sync_sc_cfb",<br />
io_signature::make(1, 1, sizeof (gr_complex)),<br />
#ifndef SYNC_ADD_DEBUG_OUTPUT<br />
io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))<br />
#else<br />
io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))<br />
#endif<br />
{<br />
std::vector ma_taps(fft_len/2, 1.0);<br />
gr::blocks::delay::sptr delay(gr::blocks::delay::make(sizeof(gr_complex), fft_len/2));<br />
gr::blocks::conjugate_cc::sptr delay_conjugate(gr::blocks::conjugate_cc::make());<br />
gr::blocks::multiply_cc::sptr delay_corr(gr::blocks::multiply_cc::make());<br />
gr::filter::fir_filter_ccf::sptr delay_ma(gr::filter::fir_filter_ccf::make(1, std::vector(fft_len/2, use_even_carriers ? 1.0 : -1.0)));<br />
gr::blocks::complex_to_mag_squared::sptr delay_magsquare(gr::blocks::complex_to_mag_squared::make());<br />
gr::blocks::divide_ff::sptr delay_normalize(gr::blocks::divide_ff::make());<br />
<br />
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());<br />
gr::filter::fir_filter_ccf::sptr delay_ma(gr::filter::fir_filter_ccf::make(1, std::vector(fft_len/2, use_even_carriers ? 1.0 : -1.0)));<br />
gr::blocks::complex_to_mag_squared::sptr delay_magsquare(gr::blocks::complex_to_mag_squared::make());<br />
gr::blocks::divide_ff::sptr delay_normalize(gr::blocks::divide_ff::make());<br />
<br />
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());<br />
gr::filter::fir_filter_fff::sptr normalizer_ma(gr::filter::fir_filter_fff::make(1, std::vector(fft_len, 0.5)));<br />
gr::blocks::multiply_ff::sptr normalizer_square(gr::blocks::multiply_ff::make());<br />
<br />
gr::blocks::complex_to_arg::sptr peak_to_angle(gr::blocks::complex_to_arg::make());<br />
gr::blocks::sample_and_hold_ff::sptr sample_and_hold(gr::blocks::sample_and_hold_ff::make());<br />
<br />
gr::blocks::plateau_detector_fb::sptr plateau_detector(gr::blocks::plateau_detector_fb::make(cp_len));<br />
<br />
// Delay Path<br />
connect(self(), 0, delay, 0);<br />
connect(delay, 0, delay_conjugate, 0);<br />
connect(delay_conjugate, 0, delay_corr, 1);<br />
connect(self(), 0, delay_corr, 0);<br />
connect(delay_corr, 0, delay_ma, 0);<br />
connect(delay_ma, 0, delay_magsquare, 0);<br />
connect(delay_magsquare, 0, delay_normalize, 0);<br />
// Energy Path<br />
connect(self(), 0, normalizer_magsquare, 0);<br />
connect(normalizer_magsquare, 0, normalizer_ma, 0);<br />
connect(normalizer_ma, 0, normalizer_square, 0);<br />
connect(normalizer_ma, 0, normalizer_square, 1);<br />
connect(normalizer_square, 0, delay_normalize, 1);<br />
// Fine frequency estimate (output 0)<br />
connect(delay_ma, 0, peak_to_angle, 0);<br />
connect(peak_to_angle, 0, sample_and_hold, 0);<br />
connect(sample_and_hold, 0, self(), 0);<br />
// Peak detect (output 1)<br />
connect(delay_normalize, 0, plateau_detector, 0);<br />
connect(plateau_detector, 0, sample_and_hold, 1);<br />
connect(plateau_detector, 0, self(), 1);<br />
#ifdef SYNC_ADD_DEBUG_OUTPUT<br />
// Debugging: timing metric (output 2)<br />
connect(delay_normalize, 0, self(), 2);<br />
#endif<br />
}<br />
</syntaxhighlight><br />
<br />
Let's understand the code piece wise. The hierarchical block is C++ is instantiated as follows:<br />
<br />
<syntaxhighlight lang="cpp"><br />
ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)<br />
: hier_block2 ("ofdm_sync_sc_cfb",<br />
io_signature::make(1, 1, sizeof (gr_complex)),<br />
#ifndef SYNC_ADD_DEBUG_OUTPUT<br />
io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))<br />
#else<br />
io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))<br />
</syntaxhighlight><br />
<br />
<br />
where <code>ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl</code> is the constructor with parameters <code>int fft_len, int cp_len, bool use_even_carriers</code> and <code>hier_block2</code> is the base class. The block name <code>"ofdm_sync_sc_cfb"</code> is defined following the GNU Radio block naming style.<br />
<br />
<code>io_signature::make(1, 1, sizeof (gr_complex))</code> defines my input items and the output items are either<br /><br />
<code>io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))</code> or <code>io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))</code><br />
<br />
depending on the preprocessor directive <code>SYNC_ADD_DEBUG_OUTPUT</code>.<br />
<br />
The individual blocks inside the <code>ofdm_sync_sc_cfb</code> block are defined as follows:<br />
<br />
<syntaxhighlight lang="cpp"><br />
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());<br />
</syntaxhighlight><br />
Finally the individual blocks are connected using:<br />
<br />
<syntaxhighlight lang="cpp"><br />
connect(normalizer_magsquare, 0, normalizer_ma, 0);<br />
</syntaxhighlight></div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8583IQ Complex Tutorial2021-05-18T15:13:49Z<p>172.18.0.3: /* complex envelope of a pure sine wave */ Small spelling mistake</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_2= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is closed to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex enveloppe, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since its allow to lower sampling rate as compare to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation help us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal correspond to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex enveloppe is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Enveloppe, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seemed counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex enveloppe is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercise: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100kHz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Symbol_Sync&diff=8575Symbol Sync2021-05-18T09:53:18Z<p>172.18.0.3: Not only link to PDF of GRCON17 talk but the landing page with video as well; minor cosmetics</p>
<hr />
<div>[[Category:Block Docs]]<br />
[[File:symbol_sync_block.png|200px]]<br />
<br />
Symbol Sync is a type of symbol synchronizer that performs a bunch of tasks typically required to receive/decode a digital signal from over the air, listed below. This block acts as an improvement to the Clock Recovery and Polyphase Clock Sync blocks. <br />
<br />
1. Estimates and tracks symbol rate (i.e. number of samples per symbol), given an initial estimate of samples per symbol and an allowable deviation from that estimate.<br />
<br />
2. Performs the timing synchronization needed so that the signal is sampled at exactly the right moment in time, which is when each symbol/pulse is at its max value (see diagram below).<br />
<br />
3. Decimate the signal so that what comes out of the block is 1 sample per symbol (or multiple if the user would like, but it's usually set to 1 or sometimes 2).<br />
<br />
4. Filter signal appropriately<br />
<br />
[[File:symbol_sync_1.png|400px]]<br />
<br />
See the [https://www.gnuradio.org/grcon/grcon17/presentations/symbol_clock_recovery_and_improved_symbol_synchronization_blocks/ GNU Radio Conference 2017 presentation] on this block (PDF slides and Video).<br />
<br />
Example flowgraphs using this block can be found [https://github.com/gnuradio/gnuradio/tree/master/gr-digital/examples/demod here]<br />
<br />
<br />
== Parameters ==<br />
<br />
; Timing Error Detector (TED)<br />
: The enumerated type of timing error detector to use. See enum ted_type for a list of possible types.<br />
<br />
; Samples per Symbol<br />
: User specified nominal clock period in samples per symbol.<br />
<br />
; Expected TED Gain<br />
: Expected gain of the timing error detector, given the TED in use and the anticipated input amplitude, pulse shape, and Es/No. This value is the slope of the TED's S-curve at timing offset tau = 0. This value is normally computed by the user analytically or by simulation in a tool outside of GNURadio. This value must be correct for the loop filter gains to be computed properly from the desired input loop bandwidth and damping factor.<br />
<br />
; Loop BW<br />
: Approximate normalized loop bandwidth of the symbol clock tracking loop. It should nominally be close to 0, but greater than 0. If unsure, start with a number around 2*pi*0.04, and experiment to find the value that works best for your situation. <br />
<br />
; Damping Factor<br />
: Damping factor of the symbol clock tracking loop. Damping < 1.0 is an under-damped loop. Damping = 1.0/sqrt(2.0) is a maximally flat loop response. Damping = 1.0 is a critically-damped loop. Damping > 1.0 is an over-damped loop. Start with critically damped or over-damped. An under-damped loop is usually not desirable for timing recovery. <br />
<br />
; Max Deviation<br />
: Maximum absolute deviation of the average clock period estimate from the user specified nominal clock period in units of samples per symbol. Smaller is better for acquiring lock at start of burst. Too small misses data when symbol clock is far from nominal. <br />
<br />
; Output Samples/Symbol<br />
: The number of output samples per symbol (default=1). Normally set to 1; or to 2 if upstream from an equalizer block.<br />
<br />
; TED Slicer Constellation<br />
: A constellation obj shared pointer that will be used by decision directed timing error detectors to make decisions. I.e. the timing error detector will use this constellation as a slicer, if the particular algorithm needs sliced symbols.<br />
<br />
; Interpolating Resampler Type<br />
: The enumerated type of interpolating resampler to use. See the interpolating resampler type enum for a list of possible types.<br />
<br />
; Num Filters (if using certain resamplers)<br />
: The number of arms in the polyphase filterbank of the interpolating resampler, if using an interpolating resampler that uses a PFB.<br />
<br />
; PFB Taps (if used certain resamplers)<br />
: The prototype filter for the polyphase filterbank of the interpolating resampler, if using an interpolating resampler that uses a PFB.<br />
<br />
== Usage Hints and Gotchas ==<br />
<br />
1. Input signal should be at a consistent amplitude (e.g., +/- 1.0), so consider an AGC before this block. TEDs have specific assumptions about input amplitudes!<br />
<br />
2. For decision directed TEDs (M&M, Modified M&M, Zero Crossing), the input signal amplitude should match constellation. Note that GNU Radio's Constellation Object silently scales your constellation!<br />
<br />
3. Input signal should not have a DC offset<br />
<br />
4. Input signal should be peaked at symbol centers, except for MSK signals and MSK TEDs. This requirement will normally be accomplished by the matched filter which occurs before the Symbol Sync block, unless you use the PFB, MF resampler which does the matched filtering for you. <br />
<br />
== Block Diagram of Block ==<br />
<br />
[[File:symbol_sync_2.png|600px]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Archive_of_Hack_Fests&diff=8570Archive of Hack Fests2021-05-16T19:31:27Z<p>172.18.0.3: Added "place" for virtual hackfest 2021-05</p>
<hr />
<div>* [[Hackfest2105|Virtual Hackfest 05/2021]] (Twitch)<br />
* [[Hackfest1902|Hackfest 02/2019]] (HSBXL/pre-FOSDEM)<br />
* [[Hackfest1811|Hackfest 11/2018]] (Ettus Research)<br />
* [[Hackfest1503|Hackfest 03/2015]] (Ettus Research)<br />
* [[Hackfest1501|Hackfest 01/2015]] (TU Delft)<br />
* [[Hackfest1405|Hackfest 05/2014]] (First EU Hackfest)<br />
* [[Hackfest1403-2|Hackfest 03/2014]]<br />
* [[Hackfest1403|Hackfest 03/2014]] (After WSR '14)<br />
* [[Hackfest1310|Hackfest 10/2013]] (At GRCon '13)<br />
* [[Hackfest1306|Hackfest 06/2013]]<br />
* [[Hackfest1211|Hackfest 11/2012]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8516IQ Complex Tutorial2021-05-03T10:04:57Z<p>172.18.0.3: Might be typo mistake in the equation. I not 100% sure please recheck.</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_2= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is closed to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex enveloppe, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since its allow to lower sampling rate as compare to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation help us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal correspond to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex enveloppe is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Enveloppe, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seemed counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex enveloppe is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercice: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100kHz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Repack_Bits&diff=8505Repack Bits2021-04-26T09:43:03Z<p>172.18.0.3: /* Parameters */</p>
<hr />
<div>[[Category:Block Docs]]<br />
Repack bits from the input stream onto bits of the output stream. No bits are lost here; any value for k and l (within [1, 8]) is allowed. On every fresh input byte, it starts reading on the LSB, and starts copying to the LSB as well.<br />
<br />
== Parameters ==<br />
; Bits per input byte (k)<br />
: Number of relevant bits on the input stream<br />
<br />
; Bits per output byte (l)<br />
: Number of relevant bits on the output stream<br />
<br />
; Length Tag Key<br />
: If not empty, this is the key for the length tag.<br />
<br />
; Endianness<br />
: The endianness of the output data stream (LSB or MSB).<br />
<br />
; Pack Alignment<br />
: When a Length Tag Key is provided, this controls if the input or the output is aligned. Repack Bits operates on tagged streams. In this case, it can happen that the input data or the output data becomes unaligned when k * input length is not equal to l * output length. In this case, the Pack Alignment parameter is used to decide which data packet to align. Usually, Pack Alignment is set to Input for unpacking (k=8, l < 8) and Output for reversing that. For example, say you're tx'ing 8-PSK and therefore set k=8, l=3 on the transmit side before the modulator. Now assume you're transmitting a single byte of data. Your incoming tagged stream has length 1, the outgoing has length 3. However, the third item is actually only carrying 2 bits of relevant data, the bits do not align with the boundaries. So you set Pack Alignment to Input, because the output can be unaligned. Now say you're doing the inverse: packing those three items into full bytes. How do you interpret those three bytes? Without this flag, you'd have to assume there's 9 relevant bits in there, so you'd end up with 2 bytes of output data. But in the packing case, you want the '''output''' to be aligned; all output bits must be useful. By asserting this flag, the packing algorithm tries to do this and in this case assumes that since we have alignment after 8 bits, the 9th can be discarded.<br />
<br />
== Example Flowgraph ==<br />
<br />
This flowgraph can be found at [https://github.com/gnuradio/gnuradio/blob/master/gr-fec/examples/fecapi_polar_encoders.grc]<br />
<br />
[[File:Fecapi_polar_encoders_fg.png|800px]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Flowgraph_Python_Code&diff=8502Flowgraph Python Code2021-04-23T20:13:40Z<p>172.18.0.3: Fix typo</p>
<hr />
<div>== Dial Tone Flowgraph ==<br />
<br />
The following example flowgraph implements a dial-tone:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button from within GRC, the terminal tells us that it produced a &quot;tutorial_three_1.py&quot; file, so let's open it to examine the code.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
Once GRC has created a Python file, the user is free to modify it in any desired manner, such as changing parameters, sample rate, and even connections among the blocks.<br />
<br />
To execute this file from a terminal, enter:<br><br />
<code>python3 tutorial_three_1.py</code><br />
<br />
<p><b>Warning: </b>After the Python file has been modified, running GRC again with that flowgraph will wipe out your changes!</p><br />
<br />
== Dial Tone Python Code Dissected ==<br />
<br />
Let's examine pertinent lines of the code:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
<br />
This tells the shell to use the Python3 interpreter to run this file.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
</syntaxhighlight><br />
<br />
These tell Python what modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal source is included in the analog module.<br />
<br />
<syntaxhighlight lang="python"><br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
</syntaxhighlight><br />
<br />
Define a class called "tutorial_three_1" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
<br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
</syntaxhighlight><br />
<br />
The parent constructor is called.<br />
<br />
<syntaxhighlight lang="python"><br />
self.samp_rate = samp_rate = 32000<br />
</syntaxhighlight><br />
<br />
Variable declaration for sample rate.<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
</syntaxhighlight><br />
<br />
There are 2 inputs to the '''Audio Sink''' block. The first line connects the only output of analog_sig_source_x_0 (440 Hz waveform) to the first input of audio_sink_0. The second line connects the only output of analog_sig_source_x_1 (350 Hz waveform) to the second input of audio_sink_0.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=OutOfTreeModules&diff=8500OutOfTreeModules2021-04-21T18:08:23Z<p>172.18.0.3: steps should be in order and not "step 4. dont forget to do that before that". Prevents execution in order and makes procedure bugprone</p>
<hr />
<div>[[Category:Guide]]<br />
<br />
'''Extending GNU Radio with own functionality and blocks'''<br />
<br />
This article borrows heavily from the original (but very outdated) &quot;How to write a block?&quot; written by Eric Blossom.<br />
<br />
== What is an out-of-tree module? ==<br />
<br />
An out-of-tree module is a GNU Radio component that does not live within the GNU Radio source tree. Typically, if you want to extend GNU Radio with your own functions and blocks, such a module is what you create (i.e. you wouldn't usually add stuff to the actual GNU Radio source tree unless you're planning to submit it to the devs for upstream integration). This allows you to maintain the code yourself and have additional functionality alongside the main code.<br />
<br />
A lot of OOT projects are hosted at [http://cgran.org CGRAN] -- the Comprehensive GNU Radio Archive Network. CGRAN projects are all available through our tool [http://gnuradio.org/pybombs PyBOMBS]. In fact, when you add your project to the [https://github.com/gnuradio/gr-etcetera PyBOMBS recipe repo], it will automatically update the CGRAN website.<br />
<br />
For example of such a module is the [https://www.cgran.org/14680/ GNU Radio Digital Audio Broadcasting module], which extends GNU Radio with everything needed to get audio from DAB and DAB+. When installed, you have more blocks available (e.g. in the [[GNURadioCompanion|GNU Radio companion]]) which behave just like the rest of GNU Radio; however, the developers are different people.<br />
<br />
== Tools and resources at my disposal ==<br />
<br />
There are a couple of tools, scripts, and documents that are available as 3rd-party programs or as part of GNU Radio.<br />
<br />
=== gr_modtool - The swiss army knife of module editing ===<br />
<br />
When developing a module, there's a lot of boring, monotonous work involved: boilerplate code, makefile editing, etc. gr_modtool is a script which aims to help with all these things by automatically editing makefiles, using templates, and doing as much work as possible for the developer such that you can jump straight into the DSP coding.<br />
<br />
Note that gr_modtool makes a lot of assumptions on what the code looks like. The more your module is custom and has specific changes, the less useful gr_modtool will be, but it is probably the best place to start with any new module or block.<br />
<br />
gr_modtool is now available in the GNU Radio source tree and is installed by default.<br />
<br />
=== Developer resources on the wiki ===<br />
<br />
Most important is definitely the [[BlocksCodingGuide|block coding guide]]. While this is written for the GNU Radio main tree, this should also be applied to all modules. Specifically, have a look at the naming conventions!<br />
<br />
=== CMake, make, etc. ===<br />
<br />
GNU Radio uses CMake as a build system. Building a module, therefore, requires you to have cmake installed, and whatever build manager you prefer (most often this is 'make', but you could also be using Eclipse or MS Visual Studio).<br />
<br />
== Tutorial 1: Creating an out-of-tree module ==<br />
<br />
In the following tutorials, we will use an out-of-tree module called '''howto'''. The first step is to create this module.<br />
<br />
With gr_modtool, this is dead easy. Just point your command line wherever you want your new module directory (this should be outside the GNU Radio source tree!), and go:<br />
<br />
<pre>% gr_modtool newmod howto<br />
Creating out-of-tree module in ./gr-howto... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.</pre><br />
If all went well, you now have a new directory called <code>gr-howto</code> in which we will work for the other tutorials.<br />
<br />
== Structure of a module ==<br />
<br />
Let's jump straight into the gr-howto module and see what it's made up of:<br />
<br />
<pre>gr-howto % ls<br />
apps cmake CMakeLists.txt docs examples grc include lib python swig</pre><br />
It consists of several subdirectories. Anything that will be written in C++ (or C, or any language that is not Python) is put into <code>lib/</code>. For C++ files, we usually have headers which are put into <code>include/</code> (if they are to be exported) or also in <code>lib/</code> (if they're only relevant during compile time, but are not installed later, such as <code>_impl.h</code> files. You'll see what that is in the next tutorial).<br />
<br />
Of course, Python stuff goes into the <code>python/</code> directory. This includes unit tests (which are not installed) and parts of the Python module which are installed.<br />
<br />
You probably know already that GNU Radio blocks are available in Python even if they were written in C++. This is done by the help of SWIG, the simplified wrapper and interface generator, which automatically creates glue code to make this possible. SWIG needs some instructions on how to do this, which are put into the <code>swig/</code> subdirectory. Unless doing something extra clever with your block, you will not need to go into the <code>swig/</code> directory; gr_modtool handles all of that for us.<br />
<br />
If you want your blocks to be available in the [[GNURadioCompanion|GNU Radio companion]], the graphical UI for GNU Radio, you need to add descriptions of the blocks and put them into <code>grc/</code>. Prior to version 3.8 these descriptions were XML files, but from 3.8 onward they use [[YAML_GRC|YAML instead]].<br />
<br />
For documentation, <code>docs/</code> contains some instructions on how to extract documentation from the C++ files and Python files (we use Doxygen and Sphinx for this) and also make sure they're available as docstrings in Python. Of course, you can add custom documentation here as well.<br />
<br />
The <code>apps/</code> subdir contains any complete applications (both for GRC and standalone executables) which are installed to the system alongside with the blocks.<br />
<br />
The directory, <code>examples/</code> can be used to save (guess what) examples, which are a great addendum to documentation because other developers can simply look straight at the code to see how your blocks are used.<br />
<br />
The build system brings some baggage along, as well: the <code>CMakeLists.txt</code> file (one of which is present in every subdirectory) and the <code>cmake/</code> folder. You can ignore the latter for now, as it brings along mainly instructions for CMake on how to find GNU Radio libraries etc. The CMakeLists.txt files need to be edited a lot in order to make sure your module builds correctly.<br />
<br />
But one step at a time! Now, let's move on to our next tutorial.<br />
<br />
== Tutorial 2: Writing a block (square_ff) in C++ ==<br />
<br />
For our first example, we'll create a block that computes the square of its single float input. This block will accept a single float input stream and produce a single float output stream, i.e., for every incoming float item, we output one float item which is the square of that input item.<br />
<br />
Following the naming conventions, the block will be called <code>square_ff</code> because it has float inputs, float outputs.<br />
<br />
We are going to arrange that this block, as well as the others that we write in this article, end up in the <code>howto</code> Python module. This will allow us to access it from Python like this:<br />
<br />
<pre>import howto<br />
sqr = howto.square_ff()</pre><br />
=== Creating the files ===<br />
<br />
First step is to create empty files for the block and edit the CMakeLists.txt files.<br /><br />
Again, <code>gr_modtool</code> does the job. On the command line, go to the <code>gr-howto</code> directory and enter:<br />
<br />
<pre>gr-howto % gr_modtool add -t general -l cpp square_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] <br />
Add C++ QA code? [y/N] <br />
Adding file 'square_ff_impl.h'...<br />
Adding file 'square_ff_impl.cc'...<br />
Adding file 'square_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'qa_square_ff.py'...<br />
Editing python/CMakeLists.txt...<br />
Adding file 'howto_square_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
On the command line, we specify that we're adding a block, its type is 'general' (because we don't know what block types are, yet) and it is called <code>square_ff</code>. The block should be created in C++ and it currently has no specified copyright holder (by default, gr-module author is the copyright holder). <code>gr_modtool</code> then asks you if your block takes any arguments (it doesn't, so we leave that empty), whether or not we want QA code for Python (yes, we do) and for C++ (no, we don't right now).<br />
<br />
Now, have another look at the different CMakeLists.txt files and see what <code>gr_modtool</code> did. You can also see a lot of new files, which now have to be edited if we want the block to work.<br />
<br />
=== Test Driven Programming ===<br />
<br />
We could just start banging out the C++ code, but being highly evolved modern programmers, we're going to write the test code first. After all, we do have a good spec for the behavior: take a single stream of floats as the input and produce a single stream of floats as the output. The output should be the square of the input.<br />
<br />
How hard could this be? Turns out that this is easy! So, we open <code>python/qa_square_ff.py</code>, which we edit to look like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig as howto<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_square_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, &quot;qa_square_ff.xml&quot;)</pre><br />
<br />
For Gnuradio v 3.9 and up, use the following.<br />
<pre> <br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
<br />
try:<br />
from howto import square_ff<br />
except ImportError:<br />
import os<br />
import sys<br />
dirname, filename = os.path.split(os.path.abspath(__file__))<br />
sys.path.append(os.path.join(dirname, "bindings"))<br />
from howto import square_ff<br />
<br />
class qa_square_ff(gr_unittest.TestCase):<br />
<br />
def setUp(self):<br />
self.tb = gr.top_block()<br />
<br />
def tearDown(self):<br />
self.tb = None<br />
<br />
def test_instance(self):<br />
# FIXME: Test will fail until you pass sensible arguments to the constructor<br />
instance = square_ff()<br />
<br />
def test_001_square_ff(self): <br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr,dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, "qa_square_ff.yaml")<br />
</pre><br />
<br />
gr_unittest is an extension to the standard Python module unittest. gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. Unittest uses Python's reflection mechanism to find all methods that start with test_ and runs them. Unittest wraps each call to test_* with matching calls to setUp and tearDown. See the [http://docs.python.org/2/library/unittest.html Python unittest documentation] for details.<br />
<br />
When we run the test, gr_unittest.main is going to invoke setUp, test_001_square_ff, and tearDown, in that order.<br />
<br />
<code>test_001_square_ff</code> builds a small graph that contains three nodes. <code>blocks.vector_source_f(src_data)</code> will source the elements of <code>src_data</code> and then say that it's finished. <code>howto.square_ff</code> is the block we're testing. <code>blocks.vector_sink_f</code> gathers the output of <code>howto.square_ff</code>.<br />
<br />
The <code>run()</code> method runs the graph until all the blocks indicate they are finished. Finally, we check that the result of executing <code>square_ff</code> on <code>src_data</code> matches what we expect.<br />
<br />
Note that such a test is usually called before installing the module. This means that we need some trickery to be able to load the blocks when testing. CMake takes care of most things by changing PYTHONPATH appropriately. Also, we import <code>howto_swig</code> instead of <code>howto</code> in this file.<br />
<br />
In order for CMake to actually know this test exists, <code>gr_modtool</code> modified <code>python/CMakeLists.txt</code> with these lines:<br />
<br />
For GR v3.9+, remove the following <code> set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig) </code><br />
<br />
<pre>########################################################################<br />
# Handle the unit tests<br />
########################################################################<br />
include(GrTest)<br />
<br />
set(GR_TEST_TARGET_DEPS gnuradio-howto)<br />
set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig)<br />
GR_ADD_TEST(qa_square_ff ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_square_ff.py)</pre><br />
<br />
=== The C++ code (part 1) ===<br />
<br />
Now that we've got a test case, let's write the C++ code. All signal processing blocks are derived from <code>gr::block</code> or one of its subclasses. Go check out the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html block documentation] on the Doxygen-generated manual.<br />
<br />
<code>gr_modtool</code> already provided us with three files that define the block: <code>lib/square_ff_impl.h</code>, <code>lib/square_ff_impl.cc</code> and <code>include/howto/square_ff.h</code>. All we have to do is modify them to do our bidding. After you've finished with this tutorial ''please'' read and understand the [https://wiki.gnuradio.org/index.php/BlocksCodingGuide Blocks Coding Guide] to find out how these files are structured and why!<br />
<br />
First of all, we have a look at our header files. Because the block we're writing is so simple, we don't have to actually change them (the header file in <code>include/</code> is often quite complete after running <code>gr_modtool</code>, unless we need to add some public methods such as mutator methods, i.e., getters and setters). That leaves us with <code>lib/square_ff_impl.cc</code>.<br />
<br />
<code>gr_modtool</code> hints at where you have to change code by adding <code>&lt;++&gt;</code> symbols.<br /><br />
Let's go through these one at a time:<br />
<br />
<pre> square_ff_impl::square_ff_impl()<br />
: gr::block(&quot;square_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)), // input signature<br />
gr::io_signature::make(1, 1, sizeof (float))) // output signature<br />
{<br />
// empty constructor<br />
}</pre><br />
The constructor itself is empty, as the squaring block has no need to set up anything.<br />
<br />
The only interesting portion is the definition of the input and output signatures: At the input, we have 1 port that allows float inputs. The output port is the same.<br />
<br />
<pre> void<br />
square_ff_impl::forecast (int noutput_items, gr_vector_int &amp;ninput_items_required)<br />
{<br />
ninput_items_required[0] = noutput_items;<br />
}</pre><br />
<code>forecast()</code> is a function which tells the scheduler how many input items are required to produce <code>noutput_items</code> output items. In this case, they're the same. The index 0 indicates that this is for the first port, but we only have one any way. This is generally the case for <code>forecast</code> in a lot of blocks. For examples, you can look at how <code>gr::block</code>, <code>gr::sync_block</code>, <code>gr::sync_decimator</code>, and <code>gr::sync_interpolator</code> define the default forecast functions to account for things like rate changes and history.<br />
<br />
Finally, there's <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. <code>general_work()</code> is the method that does the actual signal processing:<br />
<br />
<pre> int<br />
square_ff_impl::general_work (int noutput_items,<br />
gr_vector_int &amp;ninput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which copies the square of the input buffer to the output buffer.<br />
<br />
=== Using CMake ===<br />
<br />
If you've never used CMake before, this is a good time to give it a try. The typical workflow of a CMake-based project as seen from the command line is this (if using PyBOMBS, first read '''Build Tree vs. Install Tree'''):<br />
<br />
<pre>$ mkdir build # We're currently in the module's top directory<br />
$ cd build/<br />
$ cmake ../ # Tell CMake that all its config files are one dir up<br />
$ make # And start building (should work after the previous section)</pre><br />
<br />
If using GR v3.9 you will need to run:<br />
<pre>$ sudo make install </pre><br />
<br />
===== Build Tree vs. Install Tree =====<br />
<br />
When you run cmake, you usually run it in a separate directory (e.g. <code>build/</code>). This is the build tree. The path to the install tree is <code>$prefix/lib/$pythonversion/dist-packages</code>, where <code>$prefix</code> is whatever you specified to CMake during configuration (usually <code>/usr/local/</code>) with the <code>-DCMAKE_INSTALL_PREFIX</code> switch. (Note: different versions of Python will either use site-packages or dist-packages; dist-packages is the newer way and most likely for newer OSes and installations.)<br />
<br />
If you installed GNU Radio using PyBOMBS, the install tree is located in the <code>target/</code> directory set during the initial PyBOMBS configuration. Make sure to add the -DCMAKE_INSTALL_PREFIX switch for CMake, so that it will correctly locate your GNU Radio installation. The command should look similar to this:<br />
<br />
<pre>$ cmake -DCMAKE_INSTALL_PREFIX=~/prefix-3.8 ../ # should be the configured PyBOMBS target</pre><br />
Now we have a new directory <code>build/</code> in our module's directory. All the compiling etc. is done in here, so the actual source tree is not littered with temporary files. If we change any CMakeLists.txt files, we should re-run <code>cmake ../</code> (although in truth, cmake detects these changes and reruns automatically when you next run <code>make</code>). During compilation, the libraries are copied into the build tree. Only during installation, files are installed to the install tree, thus making our blocks available to GNU Radio apps.<br />
<br />
We write our applications such that they access the code and libraries in the install tree. On the other hand, we want our test code to run on the build tree, where we can detect problems before installation.<br />
<br />
=== Let's try that -- running <code>make test</code> ===<br />
<br />
Because we wrote the QA code before the C++ code, we can immediately see if what we did was correct.<br />
<br />
We use <code>make test</code> to run our tests (run this from the <code>build/</code> subdirectory, after calling <code>cmake</code> and <code>make</code>). This invokes a shell script which sets up the PYTHONPATH environment variable so that our tests use the build tree versions of our code and libraries. It then runs all files which have names of the form qa_*.py and reports the overall success or failure.<br />
<br />
There is quite a bit of behind-the-scenes action required to use the non-installed versions of our code (look at the <code>cmake/</code> directory for a cheap thrill.)<br />
<br />
If you completed the <code>square_ff</code> block, this should work fine:<br />
<br />
<pre>gr-howto/build % make test<br />
Running tests...<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.01 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff ..................... Passed 0.38 sec<br />
<br />
100% tests passed, 0 tests failed out of 2<br />
<br />
Total Test time (real) = 0.39 sec</pre><br />
If something fails during the tests, we can dig a little deeper. When we run <code>make test</code>, we're actually invoking the CMake program <code>ctest</code>, which has a number of options we can pass to it for more detailed information. Say we forgot to multiply <code>in[i] * in[i]</code> and so aren't actually squaring the signal. If we just run <code>make test</code> or even just <code>ctest</code>, we would get this:<br />
<br />
<pre>gr-howto/build $ ctest<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.02 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
50% tests passed, 1 tests failed out of 2<br />
<br />
Total Test time (real) = 0.23 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
To find out what happened with our qa_square_ff test, we run <code>ctest -V -R square</code>. The '-V' flag gives us verbose output and the '-R' flag is a regex expression to only run those tests which match.<br />
<br />
<pre>gr-howto/build $ ctest -V -R square<br />
UpdateCTestConfiguration from :/home/braun/tmp/gr-howto/build/DartConfiguration.tcl<br />
UpdateCTestConfiguration from :/home/bruan/tmp/gr-howto/build/DartConfiguration.tcl<br />
Test project /home/braun/tmp/gr-howto/build<br />
Constructing a list of tests<br />
Done constructing a list of tests<br />
Checking test dependency graph...<br />
Checking test dependency graph end<br />
test 2<br />
Start 2: qa_square_ff<br />
<br />
2: Test command: /bin/sh &quot;/home/bruan/tmp/gr-howto/build/python/qa_square_ff_test.sh&quot;<br />
2: Test timeout computed to be: 9.99988e+06<br />
2: F<br />
2: ======================================================================<br />
2: FAIL: test_001_t (__main__.qa_square_ff)<br />
2: ----------------------------------------------------------------------<br />
2: Traceback (most recent call last):<br />
2: File &quot;/home/braun/tmp/gr-howto/python/qa_square_ff.py&quot;, line 44, in test_001_t<br />
2: self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
2: File &quot;/opt/gr/lib/python2.7/dist-packages/gnuradio/gr_unittest.py&quot;, line 90, in assertFloatTuplesAlmostEqual<br />
2: self.assertAlmostEqual (a[i], b[i], places, msg)<br />
2: AssertionError: 9 != -3.0 within 6 places<br />
2: <br />
2: ----------------------------------------------------------------------<br />
2: Ran 1 test in 0.002s<br />
2: <br />
2: FAILED (failures=1)<br />
1/1 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
0% tests passed, 1 tests failed out of 1<br />
<br />
Total Test time (real) = 0.21 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
This tells us that &quot;9 != -3.0&quot; because we expected the output to be (-3)^2 = 9 but really got the input of -3. We can use this information to go back and fix our block until the tests pass.<br />
<br />
We can also put in debug print statements into our QA code on failures, like printing out <code>expected_result</code> and <code>result_data</code> to compare them to better understand the problem.<br />
<br />
=== More C++ code (but better) - Subclasses for common patterns ===<br />
<br />
<code>gr::block</code> allows tremendous flexibility with regard to the consumption of input streams and the production of output streams. Adroit use of <code>forecast()</code> and <code>consume()</code> (see below) allows variable rate blocks to be built. It is possible to construct blocks that consume data at different rates on each input and produce output at a rate that is a function of the contents of the input data.<br />
<br />
On the other hand, it is very common for signal processing blocks to have a fixed relationship between the input rate and the output rate. Many are 1:1, while others have 1:N or N:1 relationships. You must have thought the same thing in the <code>general_work()</code> function of the previous block: if the number of items consumed is identical the number of items produced, why do I have to tell GNU Radio the exact same number twice?<br />
<br />
Another common requirement is the need to examine more than one input sample to produce a single output sample. This is orthogonal to the relationship between input and output rate. For example, a non-decimating, non-interpolating FIR filter needs to examine N input samples for each output sample it produces, where N is the number of taps in the filter. However, it only consumes a single input sample to produce a single output. We call this concept &quot;history&quot;, but you could also think of it as &quot;look-ahead&quot;.<br />
<br />
* <code>gr::sync_block</code><br />
<br />
gr::sync_block is derived from gr::block and implements a 1:1 block with optional history. Given that we know the input to output rate, certain simplifications are possible. From the implementor's point-of-view, the primary change is that we define a <code>work()</code> method instead of <code>general_work()</code>. <code>work()</code> has a slightly different calling sequence; it omits the unnecessary <code>ninput_items</code> parameter, and arranges for <code>consume_each()</code> to be called on our behalf.<br />
<br />
Let's add another block which derives from <code>gr::sync_block</code> and call it <code>square2_ff</code>. First, we edit <code>qa_square_ff.py</code> to add another test:<br />
<br />
<pre> def test_002_square2_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square2_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)</pre><br />
You can see it's the exact same test as before except for the use of <code>square2_ff</code>.<br />
<br />
Then, we use <code>gr_modtool</code> to add the block files, skipping the QA code (because we already have that):<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l cpp square2_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square2_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Add C++ QA code? [Y/n] n<br />
Adding file 'square2_ff_impl.h'...<br />
Adding file 'square2_ff_impl.cc'...<br />
Adding file 'square2_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_square2_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
<br />
If running GR v3.9+ you will need to bind the new <code>square2_ff</code> files to the existing files.<br />
<pre>gr-howto % gr_modtool bind square2_ff<br />
GNU Radio module name identifiedL howto<br />
Writing binding code to ./python/bindings/square2_ff_python.cc<br />
Writing binding code to ./python/bindings/docstrings/square2_ff_pydoc_template.h</pre><br />
<br />
Whenever howto/square2_ff.h is changed, <code> gr_modtool bind square2_ff </code> needs to be run.<br />
<br />
The constructor in <code>square2_ff_impl.cc</code> is done the same way as before, except for the parent class being <code>gr::sync_block</code>.<br />
<br />
<pre> square2_ff_impl::square2_ff_impl()<br />
: gr::sync_block(&quot;square2_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)),<br />
gr::io_signature::make(1, 1, sizeof (float)))<br />
{}<br />
<br />
// [...] skip some lines ...<br />
<br />
int<br />
square2_ff_impl::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
The <code>work</code> function is the real difference (also, we don't have a <code>forecast()</code> function any more). We'll look at it in greater detail in the next section.<br />
<br />
This gives us fewer things to worry about and less code to write. If the block requires history greater than 1, call <code>set_history()</code> in the constructor or any time the requirement changes.<br />
<br />
<code>gr::sync_block</code> provides a version of <code>forecast</code> that handles the history requirement.<br />
<br />
* <code>gr::sync_decimator</code><br />
<br />
<code>gr::sync_decimator</code> is derived from <code>gr::sync_block</code> and implements a N:1 block with optional history.<br />
<br />
* <code>gr::sync_interpolator</code><br />
<br />
<code>gr::sync_interpolator</code> is derived from <code>gr::sync_block</code> and implements a 1:N block with optional history.<br />
<br />
With this knowledge it should be clear that <code>howto_square_ff</code> should be a <code>gr::sync_block</code> with no history.<br />
<br />
Now, go back into our build directory and run <code>make</code>. Because <code>gr_modtool</code> added the <code>square2_ff</code> block to the necessary CMakeLists.txt files, <code>cmake</code> is automatically rerun for us and followed by <code>make</code>.<br />
<br />
Again, running <code>make test</code> will spawn a test run with of <code>qa_square_ff.py</code> which should not fail.<br />
<br />
=== Inside the <code>work()</code> function ===<br />
<br />
If you're using a sync block (including decimator and interpolator), this is how the skeleton code looks like produced by gr_modtool:<br />
<br />
<pre> int<br />
my_block_name::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
// Do &lt;+signal processing+&gt;<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
So, given history, vectors, multiple input ports etc., is this really all you need? Yes, it is! Because sync blocks have a fixed output to input rate, all you need to know is the number of output items, and you can calculate how many input items are available.<br />
<br />
Example - the adder block: source:gr-blocks/lib/add_XX_impl.cc.t<br />
<br />
This block has an unknown number of inputs and variable vector lengths. The number of connected ports can be checked by <code>input_items.size()</code> and <code>output_items.size()</code>. The outer <code>for</code> loop, which goes over all the available items, goes up to <code>noutput_items*d_vlen</code>. The number of output items is identical to the number of input items because it is a sync block, and you can trust GNU Radio to have this number of items available. In this case, one item is a vector of samples, but we want to add the individual samples, so the for loop considers that.<br />
<br />
Example - interpolation in gr::blocks::unpack_k_bits_bb: source:gr-blocks/lib/unpack_k_bits_bb_impl.cc [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/lib/unpack_k_bits_bb_impl.cc source]<br />
<br />
This is a block which picks apart bytes and produces the individual bits. Again, it is unknown at compile time how many bits per byte there are. However, there's a fixed number of output items per input item, so we can simply divide <code>noutput_items/d_k</code> to get the correct number of input items. It will always be correct because GNU Radio knows the input to output ratio and will make sure that <code>noutput_items</code> is always a multiple of this integer ratio.<br />
<br />
Example - history in source:gr-digital/lib/diff_phasor_cc_impl.cc<br />
<br />
If you use history of length k, GNU Radio will keep k-1 entries of the input buffer instead of discarding them. This means that if GNU Radio tells you the input buffer has N items, it actually has N+k-1 items you may use.<br />
<br />
Consider the example above. We need one previous item, so history is set to k=2. If you inspect the for loop closely, you'll find that out of <code>noutput_items</code> items, <code>noutput_items+1</code> items are actually read. This is possible because there is an extra item in the input buffer from the history.<br />
<br />
After consuming <code>noutput_items</code> items, the last entry is not discarded and will be available for the next call of <code>work()</code>.<br />
<br />
=== Help! My test fails! ===<br />
<br />
Congratulations! If your test fails, your QA code has already paid for itself. Obviously, you want to fix everything before you continue.<br />
<br />
You can use the command <code>ctest -V</code> (instead of <code>make test</code>, again, all in your <code>build/</code> subdirectory) to get all the output from the tests. You can also use <code>ctest -V -R REGEX</code> to only run tests that match REGEX, if you have many tests and want to narrow it down. If you can't figure out the problem from the output of your QA code, put in <code>print</code> statements and show intermediary results.<br />
<br />
=== Making your blocks available in GRC ===<br />
<br />
You can now install your module, but it will not be available in GRC. That's because <code>gr_modtool</code> can't create valid XML files before you've even written a block. The XML code generated when you call <code>gr_modtool add</code> is just some skeleton code.<br />
<br />
Once you've finished writing the block, <code>gr_modtool</code> has a function to help you create the XML code for you. For the howto example, you can invoke it on the <code>square2_ff</code> block by calling<br />
For GNURadio ver >= 3.8:<br />
<pre>gr-howto % gr_modtool makeyaml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
<br />
For GNURadio ver. < 3.8:<br />
<pre>gr-howto % gr_modtool makexml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
Note that <code>gr_modtool add</code> creates an invalid GRC file, so we can overwrite that.<br />
<br />
In most cases, <code>gr_modtool</code> can't figure out all the parameters by itself and you will have to edit the appropriate XML file by hand. The [[GNURadioCompanion|GRC]] wiki site has a description available.<br />
<br />
In this case, because the block is so simple, the XML is actually valid. Have a look at <code>grc/howto_square2_ff.xml</code>:<br />
<br />
<pre><br />
<block> <br />
<name>Square ff</name><br />
<key>howto_square_ff</key><br />
<category>[HOWTO]</category><br />
<import>import howto</import><br />
<make>howto.square_ff()</make><br />
<sink><br />
<name>in</name><br />
<type>float</type><br />
</sink><br />
<source><br />
<name>out</name><br />
<type>float</type><br />
</source><br />
</block><br />
</pre><br />
Perhaps you want to change the autogenerated name to something nicer.<br />
<br />
<b>Note:</b> The category name <b>must</b> be enclosed in square brackets to work!<br />
<br />
If you do a <code>make install</code> from the build directory, you can use the block in GRC. If GRC is already running, you can hit the &quot;Reload Blocks&quot; button in the GRC toolbar; it's a blue circular arrow on the right-hand side. You should now see a &quot;HOWTO&quot; category in the block tree.<br />
<br />
=== There's more: additional <code>gr::block</code>-methods ===<br />
<br />
If you've read the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html gr::block documentation] (which you should have), you'll have noticed there are a great number of methods available to configure your block.<br />
<br />
Here's some of the more important ones:<br />
<br />
==== <code>set_history()</code> ====<br />
<br />
If your block needs a history (i.e., something like an FIR filter), call this in the constructor. GNU Radio then makes sure you have the given number of 'old' items available.<br />
<br />
The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.<br />
<br />
The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).<br />
<br />
==== <code>forecast()</code> ====<br />
<br />
The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the <code>forecast()</code> method provides this information, and you must therefore override it anytime you write a <code>gr::block</code> derivative (for sync blocks, this is implicit).<br />
<br />
The default implementation of <code>forecast()</code> says there is a 1:1 relationship between <code>noutput_items</code> and the requirements for each input stream. The size of the items is defined by <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!<br />
<br />
<pre> // default implementation: 1:1<br />
void<br />
gr::block::forecast(int noutput_items,<br />
gr_vector_int &amp;ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i &lt; ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}</pre><br />
Although the 1:1 implementation worked for <code>square_ff</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from gr::sync_block, gr::sync_interpolator or gr::sync_decimator instead of gr::block, you can often avoid implementing forecast.<br />
<br />
==== <code>set_output_multiple()</code> ====<br />
<br />
When implementing your <code>general_work()</code> routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call <code>set_output_multiple</code> in your constructor to specify this requirement. The default output multiple is 1.<br />
<br />
=== Finalizing your work and installing ===<br />
<br />
First, go through this checklist:<br />
<br />
* Have you written one or more blocks, including QA codes?<br />
* Does <code>make test</code> pass?<br />
* Are there GRC bindings available (if that's what you want)?<br />
<br />
In that case, you can go ahead and install your module. On a Linux machine, this would mean going back to the build directory and calling <code>make install</code>:<br />
<br />
<pre>$ cd build/<br />
$ make install # or sudo make install</pre><br />
With Ubuntu, you may have to call <code>ldconfig</code> as well:<br />
<br />
<pre>$ sudo ldconfig</pre><br />
Otherwise, you'll get an error message that the library you just installed cannot be found.<br />
<br />
== Other types of blocks ==<br />
<br />
=== Sources and sinks ===<br />
<br />
Sources and sinks are derived from <code>gr::sync_block</code>. The only thing different about them is that sources have no inputs and sinks have no outputs. This is reflected in the <code>gr::io_signature::make</code> that are passed to the <code>gr::sync_block</code> constructor. Take a look at [source:gr-blocks/lib/file_source_impl.cc file_source.{h,cc}] and file_sink_impl.{h,cc} for some very straight-forward examples.<br />
<br />
=== Hierarchical blocks ===<br />
<br />
<code>gr_modtool</code> supports skeleton code for hierarchical blocks both in Python and C''++.<br />
<br />
<pre>~/gr-howto % gr_modtool.py add -t hier -l cpp hierblockcpp_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: hierblockcpp_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments:<br />
Add Python QA code? [Y/n]<br />
Add C++ QA code? [y/N]<br />
Adding file 'hierblockcpp_ff_impl.h'...<br />
Adding file 'hierblockcpp_ff_impl.cc'...<br />
Adding file 'hierblockcpp_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_hierblockcpp_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Using the <code>-l python</code> switch creates such a block in Python.<br />
<br />
== Everything at one glance: Cheat sheet for editing modules/components: ==<br />
<br />
Here's a quick list for all the steps necessary to build blocks and out-of-tree modules:<br />
<br />
# Create (do this once per module): <code>gr_modtool newmod MODULENAME</code><br />
# Add a block to the module: <code>gr_modtool add BLOCKNAME</code><br />
# Create a build directory: <code>mkdir build/</code><br />
# Invoke the make process: <code>cd build &amp;&amp; cmake &lt;OPTIONS&gt; ../ &amp;&amp; make</code> (Note that you only have to call cmake if you've changed the CMake files)<br />
# Invoke the testing: <code>make test</code> or <code>ctest</code> or <code>ctest -V</code> for more verbosity<br />
# Call <code>gr_modtool makexml BLOCKNAME</code> or <code>gr_modtool makeyaml BLOCKNAME</code> to generate the xml or yaml file for your blocks. Correct manually if needed.<br />
# Install (only when everything works and no tests fail): <code>sudo make install</code><br />
# Ubuntu users: reload the libs: <code>sudo ldconfig</code><br />
# Delete blocks from the source tree: <code>gr_modtool rm REGEX</code><br />
# Disable blocks by removing them from the CMake files: <code>gr_modtool disable REGEX</code><br />
<br />
== Tutorial 3: Writing a signal processing block in Python ==<br />
<br />
'''Note:''' Writing signal processing blocks in Python comes with a performance penalty. The most common cause for using Python to write blocks is because you want to quickly prototype something without having to argue with C++.<br />
<br />
From the previous tutorials, you already know about blocks and how they work. Lets go through things a bit quicker, and code another squaring block in pure Python, which shall be called <code>square3_ff()</code>.<br />
<br />
=== Adding the test case ===<br />
<br />
So, first of all, we add another test case by editing <code>qa_square_ff.py</code>. Leaving out the test cases for the other two blocks, the QA file now looks like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig<br />
from square3_ff import square3_ff<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
# [...] Skipped the other test cases<br />
<br />
def test_003_square3_ff (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f (src_data)<br />
sqr = square3_ff ()<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, sqr)<br />
self.tb.connect (sqr, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.main ()</pre><br />
The actual test case looks '''exactly''' like the previous ones did, only replacing the block definition with <code>square3_ff()</code>. The only other difference is in the import statements: We are now importing a module called <code>square3_ff</code> from which we pull the new block.<br />
<br />
=== Adding the block code ===<br />
<br />
Having put the unit test in place, we add a file called <code>square3_ff.py</code> into the <code>python/</code> directory using <code>gr_modtool</code>:<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l python square3_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square3_ff<br />
Language: Python<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Adding file 'square3_ff.py'...<br />
Adding file 'howto_square3_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Remember not to add any QA files as we're using the existing one. Next, edit the new file <code>python/square3_ff.py</code>. It should look a bit like this:<br />
<br />
<pre>import numpy<br />
from gnuradio import gr<br />
<br />
class square3_ff(gr.sync_block):<br />
&quot; Squaring block &quot;<br />
def __init__(self):<br />
gr.sync_block.__init__(<br />
self,<br />
name = &quot;square3_ff&quot;,<br />
in_sig = [numpy.float32], # Input signature: 1 float at a time<br />
out_sig = [numpy.float32], # Output signature: 1 float at a time<br />
)<br />
<br />
def work(self, input_items, output_items):<br />
output_items[0][:] = input_items[0] * input_items[0] # Only works because numpy.array<br />
return len(output_items[0])</pre><br />
Some things should immediately stick out:<br />
<br />
* The block class is derived from <code>gr.sync_block</code>, just like the C++ version was derived from gr::sync_block<br />
* It has a constructor where the name and input/output signatures are set and a <code>work()</code> function<br />
<br />
However, there are some major differences to the C++ version:<br />
<br />
* The input and output signatures are simply defined as a list. Every element contains the item size of that port. So in this case, there is one port per input and one port per output and each has an item size of <code>numpy.float32</code> (a single-precision float). If you want a port to operate on vectors, define a tuple, e.g. [(numpy.float32, 4), numpy.float32] means there are two ports: The first one is for vectors of 4 floats, the second is for scalar floats.<br />
* When assigning vectors to <code>output_items</code>, remember to use the <code>[:]</code> operator. This makes sure Python doesn't rebind the variables or does something clever but guarantees that the data is properly copied<br />
* <code>input_items</code> and <code>output_items</code> are numpy arrays, which is why we can do the very simple element-wise multiplication the way it's done here (instead of a list comprehension)<br />
* No recompiling is necessary for the <code>make test</code> (faster development cycles, yay!)<br />
<br />
=== Other types of Python blocks ===<br />
<br />
Just like the C++ variant, there are four types of blocks in Python:<br />
<br />
* <code>gr.sync_block</code><br />
* <code>gr.decim_block</code><br />
* <code>gr.interp_block</code><br />
* <code>gr.basic_block</code> - The Python version of <code>gr::block</code><br />
<br />
Like their C++ versions, these blocks have <code>forecast()</code>, <code>work()</code>, and <code>general_work()</code> methods you can override. The difference is, the argument list for the work functions is always as shown in the previous example:<br />
<br />
<pre> def work(self, input_items, output_items):<br />
# Do stuff<br />
<br />
def general_work(self, input_items, output_items):<br />
# Do stuff</pre><br />
The number of input/output items is obtained through <code>len(input_items[PORT_NUM])</code>.<br />
<br />
=== More examples ===<br />
<br />
Check out the QA code for the Python blocks for some good examples:<br />
<br />
* [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/python/blocks/qa_block_gateway.py gr-blocks/python/blocks/qa_block_gateway.py]<br />
<br />
=== Troubleshooting ===<br />
<br />
<code>ValueError: invalid literal for int() with base 10: '...'</code> This occurs in GRC when attempting to drag a block into a flowgraph. It usually means the <code>.yml</code> file cooresponding to the block located in the <code>grc/</code> folder is not filled out. ("..." is a placeholder).</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Polymorphic_Types_(PMTs)&diff=8498Polymorphic Types (PMTs)2021-04-21T03:08:15Z<p>172.18.0.3: /* Introduction */</p>
<hr />
<div>[[Category:Usage Manual]]<br />
== Introduction ==<br />
<br />
Polymorphic Types are used as the carrier of data from one block/thread to another such as stream tags and message passing interfaces. <br />
PMT data types can represent a variety of data ranging from the Booleans to dictionaries. <br />
They are heavily used in the stream tags and message passing interfaces. <br />
In a sense, PMTs are a way to extend C++' strict typing with something more flexible. <br />
The most complete list of PMT function is, of course, the source code, specifically the header file [https://gnuradio.org/doc/doxygen/pmt_8h.html pmt.h]. <br />
This page summarizes the most important features and points of PMTs.<br />
<br />
Let's dive straight into some Python code and see how we can use PMTs:<br />
<br />
<syntaxhighlight lang="python"><br />
>>> import pmt<br />
>>> P = pmt.from_long(23)<br />
>>> type(P)<br />
<class 'pmt.pmt_swig.swig_int_ptr'><br />
>>> print P<br />
23<br />
>>> P2 = pmt.from_complex(1j)<br />
>>> type(P2)<br />
<class 'pmt.pmt_swig.swig_int_ptr'><br />
>>> print P2<br />
0+1i<br />
>>> pmt.is_complex(P2)<br />
True<br />
</syntaxhighlight><br />
First, the <code>pmt</code> module is imported. We assign two values (<code>P</code> and <code>P2</code>) with PMTs using the <code>from_long()</code> and <code>from_complex()</code> calls, respectively. As we can see, they are both of the same type! This means we can pass these variables to C++ through SWIG, and C++ can handle this type accordingly.<br />
<br />
The same code as above in C++ would look like this:<br />
<syntaxhighlight lang="c++"><br />
#include <pmt/pmt.h><br />
// [...]<br />
pmt::pmt_t P = pmt::from_long(23);<br />
std::cout << P << std::endl;<br />
pmt::pmt_t P2 = pmt::from_complex(gr_complex(0, 1)); // <br />
Alternatively: pmt::from_complex(0, 1)<br />
std::cout << P2 << std::endl;<br />
std::cout << pmt::is_complex(P2) << std::endl;<br />
</syntaxhighlight><br />
Two things stand out in both Python and C++: First, we can simply print the contents of a PMT. How is this possible? Well, the PMTs have in-built capability to cast their value to a string (this is not possible with all types, though). Second, PMTs must obviously know their type, so we can query that, e.g. by calling the <code>is_complex()</code> method.<br />
<br />
Note: When running the above as a standalone, the compiler command will look something like <code>g++ pmt_tutorial.cpp -I$(gnuradio-config-info --prefix)/include -lgnuradio-pmt -o pmt_tutorial</code><br />
<br />
When assigning a non-PMT value to a PMT, we can use the <code>from_*</code> methods, and use the <code>to_*</code> methods to convert back:<br />
<syntaxhighlight lang="c++"><br />
pmt::pmt_t P_int = pmt::from_long(42);<br />
int i = pmt::to_long(P_int);<br />
pmt::pmt_t P_double = pmt::from_double(0.2);<br />
double d = pmt::to_double(P_double);<br />
pmt::pmt_t P_double = pmt::mp(0.2);<br />
</syntaxhighlight><br />
<br />
The last row shows the [http://gnuradio.org/doc/doxygen/namespacepmt.html#a90faad6086ac00280e0cfd8bb541bd64 pmt::mp()] shorthand function. It basically saves some typing, as it infers the correct <code>from_</code> function from the given type.<br />
<br />
String types play a bit of a special role in PMTs, as we will see<br />
later, and have their own converter:<br />
<br />
<syntaxhighlight lang="c++"><br />
pmt::pmt_t P_str = pmt::string_to_symbol("spam");<br />
pmt::pmt_t P_str2 = pmt::intern("spam");<br />
std::string str = pmt::symbol_to_string(P_str);<br />
</syntaxhighlight><br />
The pmt::intern is another way of saying pmt::string_to_symbol.<br />
<br />
See the [https://www.gnuradio.org/doc/doxygen/namespacepmt.html PMT docs] and the header file [http://gnuradio.org/doc/doxygen/pmt_8h.html pmt.h] for a full list of conversion functions.<br />
<br />
In Python, we can make use of the dynamic typing, and there's actually a<br />
helper function to do these conversions (C++ also has a helper<br />
function for converting to PMTs called pmt::mp(), but it's less<br />
powerful, and not quite as useful, because types are always strictly<br />
known in C++):<br />
<br />
<syntaxhighlight lang="python"><br />
P_int = pmt.to_pmt(42)<br />
i = pmt.to_python(P_int)<br />
P_double = pmt.to_pmt(0.2)<br />
d = pmt.to_double(P_double)<br />
</syntaxhighlight><br />
<br />
On a side note, there are three useful PMT constants, which can be<br />
used in both Python and C++ domains. In C++, these can be used as<br />
such:<br />
<br />
<syntaxhighlight lang="c++"><br />
pmt::pmt_t P_true = pmt::PMT_T;<br />
pmt::pmt_t P_false = pmt::PMT_F;<br />
pmt::pmt_t P_nil = pmt::PMT_NIL;<br />
</syntaxhighlight><br />
<br />
In Python:<br />
<br />
<syntaxhighlight lang="python"><br />
P_true = pmt.PMT_T<br />
P_false = pmt.PMT_F<br />
P_nil = pmt.PMT_NIL<br />
</syntaxhighlight><br />
<br />
<code>pmt.PMT_T</code> and <code>pmt.PMT_F</code> are boolean PMT types representing True and False, respectively. The PMT_NIL is like a NULL or None and can be used for default arguments or return values, often indicating an error has occurred.<br />
<br />
To be able to go back to C++ data types, we need to be able to find<br />
out the type from a PMT. The family of <code>is_*</code> methods helps us do that:<br />
<br />
<syntaxhighlight lang="c++"><br />
double d;<br />
if (pmt::is_integer(P)) {<br />
d = (double) pmt::to_long(P);<br />
} else if (pmt::is_real(P)) {<br />
d = pmt::to_double(P);<br />
} else {<br />
// We really expected an integer or a double here, so we don't know what to do<br />
throw std::runtime_error("expected an integer!");<br />
}<br />
</syntaxhighlight><br />
<br />
It is important to do type checking since we cannot unpack a PMT of<br />
the wrong data type.<br />
<br />
We can compare PMTs without knowing their type by using the <code>pmt::equal()</code> function:<br />
<br />
<syntaxhighlight lang="c++"><br />
if (pmt::eq(P_int, P_double)) {<br />
std::cout << "Equal!" << std::endl; // This line will never be reached<br />
</syntaxhighlight><br />
<br />
There are more equality functions, which compare different things: [http://gnuradio.org/doc/doxygen/namespacepmt.html#a5c28635e14287cc0e2f762841c11032f <code>pmt::eq()</code>] and [http://gnuradio.org/doc/doxygen/namespacepmt.html#a25467c81e1c5f4619a9cabad7a88eed5 <code>pmt::eqv()</code>]. We won't need these for this tutorial.<br />
<br />
The rest of this page provides more depth into how to handle different<br />
data types with the PMT library.<br />
<br />
=== More Complex Types ===<br />
<br />
PMTs can hold a variety of types. Using the Python method <code>pmt.to_pmt()</code>, we can convert most of Pythons standard types out-of-the-box:<br />
<br />
<pre>P_tuple = pmt.to_pmt((1, 2, 3, 'spam', 'eggs'))<br />
P_dict = pmt.to_pmt({'spam': 42, 'eggs': 23})</pre><br />
But what does this mean in the C++ domain? Well, there are PMT types that define [http://gnuradio.org/doc/doxygen/namespacepmt.html#a32895cc5a614a46b66b869c4a7bd283c tuples] and [http://gnuradio.org/doc/doxygen/namespacepmt.html#aba10563e3ab43b8d52f9cb13132047cf dictionaries], keys and values being PMTs, again.<br /><br />
So, to create the tuple from the Python example, the C++ code would look like this:<br />
<br />
<pre>pmt::pmt_t P_tuple = pmt::make_tuple(pmt::from_long(1), pmt::from_long(2), pmt::from_long(3), pmt::string_to_symbol(&quot;spam&quot;), pmt::string_to_symbol(&quot;eggs&quot;))</pre><br />
For the dictionary, it's a bit more complex:<br />
<br />
<pre>pmt::pmt_t P_dict = pmt::make_dict();<br />
P_dict = pmt::dict_add(P_dict, pmt::string_to_symbol(&quot;spam&quot;), pmt::from_long(42));<br />
P_dict = pmt::dict_add(P_dict, pmt::string_to_symbol(&quot;eggs&quot;), pmt::from_long(23));</pre><br />
As you can see, we first need to create a dictionary, then assign every key/value pair individually.<br />
<br />
A variant of tuples are ''vectors''. Like Python's tuples and lists, PMT vectors are mutable, whereas PMT tuples are not. In fact, PMT vectors are the only PMT data types that are mutable. When changing the value or adding an item to a dictionary, we are actually creating a new PMT.<br />
<br />
To create a vector, we can initialize it to a certain lengths, and fill all elements with an initial value. We can then change items or reference them:<br />
<br />
<pre>pmt::pmt_t P_vector = pmt::make_vector(5, pmt::from_long(23)); // Creates a vector with 5 23's as PMTs<br />
pmt::vector_set(P_vector, 0, pmt::from_long(42)); // Change the first element to a 42<br />
std::cout &lt;&lt; pmt::vector_ref(P_vector, 0); // Will print 42</pre><br />
In Python, we can do all these steps (using <code>pmt.make_vector()</code> etc.), or convert a list:<br />
<br />
<pre>P_vector = pmt.to_pmt([42, 23, 23, 23, 23])</pre><br />
Vectors are also different from tuples in a sense that we can '''directly''' load data types into the elements, which don't have to be PMTs.<br /><br />
Say we want to pass a series of 8 float values to another block (these might be characteristics of a filter, for example). It would be cumbersome to convert every single element to and from PMTs, since all elements of the vector are the same type.<br />
<br />
We can use special vector types for this case:<br />
<br />
<pre>pmt::pmt_t P_f32vector = pmt::make_f32vector(8, 5.0); // Creates a vector with 8 5.0s's as floats<br />
pmt::f32vector_set(P_f32vector, 0, 2.0); // Change the first element to a 2.0<br />
float f = f32vector_ref(P_f32vector, 0);<br />
std::cout &lt;&lt; f &lt;&lt; std::endl; // Prints 2.0<br />
size_t len;<br />
float *fp = pmt::f32vector_elements(P_f32vector, len);<br />
for (size_t i = 0; i &lt; len; i++)<br />
std::cout &lt;&lt; fp[i] &lt;&lt; std::endl; // Prints all elements from P_f32vector, one after another</pre><br />
Python has a similar concept: [http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html Numpy arrays]. As usual, the PMT library understands this and converts as expected:<br />
<br />
<pre>P_f32vector = pmt.to_pmt(numpy.array([2.0, 5.0, 5.0, 5.0, 5.0], dtype=numpy.float32))<br />
print pmt.is_f32vector(P_f32vector) # Prints 'True'</pre><br />
Here, 'f32' stands for 'float, 32 bits'. PMTs know about most typical fixed-width data types, such as 'u8' (unsigned 8-bit character) or 'c32' (complex with 32-bit floats for each I and Q). Consult the [http://gnuradio.org/doc/doxygen/namespacepmt.html manual] for a full list of types.<br />
<br />
The most generic PMT type is probably the blob (binary large object). Use this with care - it allows us to pass around anything that can be represented in memory.<br />
<br />
== PMT Data Type ==<br />
<br />
All PMTs are of the type pmt::pmt_t. This is an opaque container and<br />
PMT functions must be used to manipulate and even do things like<br />
compare PMTs. PMTs are also ''immutable'' (except PMT vectors). We<br />
never change the data in a PMT; instead, we create a new PMT with the<br />
new data. The main reason for this is thread safety. We can pass PMTs<br />
as tags and messages between blocks and each receives its own copy<br />
that we can read from. However, we can never write to this object, and<br />
so if multiple blocks have a reference to the same PMT, there is no<br />
possibility of thread-safety issues of one reading the PMT data while<br />
another is writing the data. If a block is trying to write new data to<br />
a PMT, it actually creates a new PMT to put the data into. Thus we<br />
allow easy access to data in the PMT format without worrying about<br />
mutex locking and unlocking while manipulating them.<br />
<br />
PMTs can represent the following:<br />
<br />
* Boolean values of true/false<br />
* Strings (as symbols)<br />
* Integers (long and uint64)<br />
* Floats (as doubles)<br />
* Complex (as two doubles)<br />
* Pairs<br />
* Tuples<br />
* Vectors (of PMTs)<br />
* Uniform vectors (of any standard data type)<br />
* Dictionaries (list of key:value pairs)<br />
* Any (contains a boost::any pointer to hold anything)<br />
<br />
The PMT library also defines a set of functions that operate directly<br />
on PMTs such as:<br />
<br />
* Equal/equivalence between PMTs<br />
* Length (of a tuple or vector)<br />
* Map (apply a function to all elements in the PMT)<br />
* Reverse<br />
* Get a PMT at a position in a list<br />
* Serialize and deserialize<br />
* Printing<br />
<br />
The constants in the PMT library are:<br />
<br />
* pmt::PMT_T - a PMT True<br />
* pmt::PMT_F - a PMT False<br />
* pmt::PMT_NIL - an empty PMT (think Python's 'None')<br />
<br />
== Inserting and Extracting Data ==<br />
<br />
Use pmt.h for a complete guide to the list of functions used to create<br />
PMTs and get the data from a PMT. When using these functions, remember<br />
that while PMTs are opaque and designed to hold any data, the data<br />
underneath is still a C++ typed object, and so the right type of<br />
set/get function must be used for the data type.<br />
<br />
Typically, a PMT object can be made from a scalar item using a call<br />
like "pmt::from_<type>". Similarly, when getting data out of a<br />
PMT, we use a call like "pmt::to_<type>". For example:<br />
<syntaxhighlight lang="c++"><br />
double a = 1.2345;<br />
pmt::pmt_t pmt_a = pmt::from_double(a);<br />
double b = pmt::to_double(pmt_a);<br />
<br />
int c = 12345;<br />
pmt::pmt_t pmt_c = pmt::from_long(c);<br />
int d = pmt::to_long(pmt_c);<br />
</syntaxhighlight><br />
As a side-note, making a PMT from a complex number is not obvious:<br />
<syntaxhighlight lang="c++"><br />
std::complex<double> a(1.2, 3.4);<br />
pmt::pmt_t pmt_a = pmt::make_rectangular(a.real(), b.imag());<br />
std::complex<double> b = pmt::to_complex(pmt_a);<br />
</syntaxhighlight><br />
Pairs, dictionaries, and vectors have different constructors and ways<br />
to manipulate them, and these are explained in their own sections.<br />
<br />
== Strings ==<br />
<br />
PMTs have a way of representing short strings. These strings are<br />
actually stored as interned symbols in a hash table, so in other<br />
words, only one PMT object for a given string exists. If creating a<br />
new symbol from a string, if that string already exists in the hash<br />
table, the constructor will return a reference to the existing PMT.<br />
<br />
We create strings with the following functions, where the second<br />
function, pmt::intern, is simply an alias of the first.<br />
<syntaxhighlight lang="c++"><br />
pmt::pmt_t str0 = pmt::string_to_symbol(std::string("some string"));<br />
pmt::pmt_t str1 = pmt::intern(std::string("some string"));<br />
</syntaxhighlight><br />
The string can be retrieved using the inverse function:<br />
<syntaxhighlight lang="c++"><br />
std::string s = pmt::symbol_to_string(str0);<br />
</syntaxhighlight><br />
== Tests and Comparisons ==<br />
<br />
The PMT library comes with a number of functions to test and compare<br />
PMT objects. In general, for any PMT data type, there is an equivalent<br />
"pmt::is_<type>". We can use these to test the PMT before trying<br />
to access the data inside. Expanding our examples above, we have:<br />
<syntaxhighlight lang="c++"><br />
pmt::pmt_t str0 = pmt::string_to_symbol(std::string("some string"));<br />
if(pmt::is_symbol(str0))<br />
std::string s = pmt::symbol_to_string(str0);<br />
<br />
double a = 1.2345;<br />
pmt::pmt_t pmt_a = pmt::from_double(a);<br />
if(pmt::is_double(pmt_a))<br />
double b = pmt::to_double(pmt_a);<br />
<br />
int c = 12345;<br />
pmt::pmt_t pmt_c = pmt::from_long(c);<br />
if(pmt::is_long(pmt_a))<br />
int d = pmt::to_long(pmt_c);<br />
<br />
\\ This will fail the test. Otherwise, trying to coerce \b pmt_c as a<br />
\\ double when internally it is a long will result in an exception.<br />
if(pmt::is_double(pmt_a))<br />
double d = pmt::to_double(pmt_c);<br />
</syntaxhighlight><br />
<br />
== Dictionaries ==<br />
<br />
PMT dictionaries are lists of key:value pairs. They have a<br />
well-defined interface for creating, adding, removing, and accessing<br />
items in the dictionary. Note that every operation that changes the<br />
dictionary both takes a PMT dictionary as an argument and returns a<br />
PMT dictionary. The dictionary used as an input is not changed and the<br />
returned dictionary is a new PMT with the changes made there.<br />
<br />
The following is a list of PMT dictionary functions. View each function in the [https://www.gnuradio.org/doc/doxygen/index.html GNU Radio C++ Manual]<br />
to get more information on what each does.<br />
<br />
* bool pmt::is_dict(const pmt_t &obj)<br />
* pmt_t pmt::make_dict()<br />
* pmt_t pmt::dict_add(const pmt_t &dict, const pmt_t &key, const pmt_t &value)<br />
* pmt_t pmt::dict_delete(const pmt_t &dict, const pmt_t &key)<br />
* bool pmt::dict_has_key(const pmt_t &dict, const pmt_t &key)<br />
* pmt_t pmt::dict_ref(const pmt_t &dict, const pmt_t &key, const pmt_t &not_found)<br />
* pmt_t pmt::dict_items(pmt_t dict)<br />
* pmt_t pmt::dict_keys(pmt_t dict)<br />
* pmt_t pmt::dict_values(pmt_t dict)<br />
<br />
This example does some basic manipulations of PMT dictionaries in<br />
Python. Notice that we pass the dictionary ''a'' and return the results<br />
to ''a''. This still creates a new dictionary and removes the local<br />
reference to the old dictionary. This just keeps our number of<br />
variables small.<br />
<br />
<syntaxhighlight lang="python"><br />
import pmt<br />
<br />
key0 = pmt.intern("int")<br />
val0 = pmt.from_long(123)<br />
val1 = pmt.from_long(234)<br />
<br />
key1 = pmt.intern("double")<br />
val2 = pmt.from_double(5.4321)<br />
<br />
# Make an empty dictionary<br />
a = pmt.make_dict()<br />
<br />
# Add a key:value pair to the dictionary<br />
a = pmt.dict_add(a, key0, val0)<br />
print a<br />
<br />
# Add a new value to the same key;<br />
# new dict will still have one item with new value<br />
a = pmt.dict_add(a, key0, val1)<br />
print a<br />
<br />
# Add a new key:value pair<br />
a = pmt.dict_add(a, key1, val2)<br />
print a<br />
<br />
# Test if we have a key, then delete it<br />
print pmt.dict_has_key(a, key1)<br />
a = pmt.dict_delete(a, key1)<br />
print pmt.dict_has_key(a, key1)<br />
<br />
ref = pmt.dict_ref(a, key0, pmt.PMT_NIL)<br />
print ref<br />
<br />
# The following should never print<br />
if(pmt.dict_has_key(a, key0) and pmt.eq(ref, pmt.PMT_NIL)):<br />
print "Trouble! We have key0, but it returned PMT_NIL"<br />
</syntaxhighlight><br />
<br />
== Vectors ==<br />
<br />
PMT vectors come in two forms: vectors of PMTs and vectors of uniform<br />
data. The standard PMT vector is a vector of PMTs, and each PMT can be<br />
of any internal type. On the other hand, uniform PMTs are of a<br />
specific data type which come in the form:<br />
<br />
* (u)int8<br />
* (u)int16<br />
* (u)int32<br />
* (u)int64<br />
* float32<br />
* float64<br />
* complex 32 (std::complex<float>)<br />
* complex 64 (std::complex<double>)<br />
<br />
That is, the standard sizes of integers, floats, and complex types of<br />
both signed and unsigned.<br />
<br />
Vectors have a well-defined interface that allows us to make, set,<br />
get, and fill them. We can also get the length of a vector with<br />
pmt::length, or in Python:<br />
<br />
pmt_t p1 = pmt_integer(1);<br />
pmt_t p2 = pmt_integer(2);<br />
pmt_t p3 = pmt_integer(3);<br />
<br />
pmt_t p_list = pmt_list3(p1, p2, p3);<br />
<br />
pmt_length(p_list); // Returns 3<br />
<br />
For standard vectors, these functions look like:<br />
<br />
* bool pmt::is_vector(pmt_t x)<br />
* pmt_t pmt::make_vector(size_t k, pmt_t fill)<br />
* pmt_t pmt::vector_ref(pmt_t vector, size_t k)<br />
* void pmt::vector_set(pmt_t vector, size_t k, pmt_t obj)<br />
* void pmt::vector_fill(pmt_t vector, pmt_t fill)<br />
<br />
Uniform vectors have the same types of functions, but they are data<br />
type-dependent. The following list tries to explain them where you<br />
substitute the specific data type prefix for \a dtype (prefixes being:<br />
u8, u16, u32, u64, s8, s16, s32, s64, f32, f64, c32, c64).<br />
<br />
* bool pmt::is_(dtype)vector(pmt_t x)<br />
* pmt_t pmt::make_(dtype)vector(size_t k, (dtype) fill)<br />
* pmt_t pmt::init_(dtype)vector(size_t k, const (dtype*) data)<br />
* pmt_t pmt::init_(dtype)vector(size_t k, const std::vector<dtype> data)<br />
* pmt_t pmt::(dtype)vector_ref(pmt_t vector, size_t k)<br />
* void pmt::(dtype)vector_set(pmt_t vector, size_t k, (dtype) x)<br />
* const dtype* pmt::(dtype)vector_elements(pmt_t vector, size_t &len)<br />
* dtype* pmt::(dtype)vector_writable_elements(pmt_t vector, size_t &len)<br />
<br />
List of available pmt::is_(dtype)vector(pmt_t x) methods from <code>pmt.h</code>:<br><br />
<br />
bool pmt::is_uniform_vector()<br />
bool pmt::is_u8vector()<br />
bool pmt::is_s8vector()<br />
bool pmt::is_u16vector()<br />
bool pmt::is_s16vector()<br />
bool pmt::is_u32vector()<br />
bool pmt::is_s32vector()<br />
bool pmt::is_u64vector()<br />
bool pmt::is_s64vector()<br />
bool pmt::is_f32vector()<br />
bool pmt::is_f64vector()<br />
bool pmt::is_c32vector()<br />
bool pmt::is_c64vector()<br />
<br />
'''Note:''' We break the contract with vectors. The 'set' functions<br />
actually change the data underneath. It is important to keep track of<br />
the implications of setting a new value as well as accessing the<br />
'vector_writable_elements' data. Since these are mostly standard data<br />
types, sets and gets are atomic, so it is unlikely to cause a great<br />
deal of harm. But it's only unlikely, not impossible. Best to use<br />
mutexes whenever manipulating data in a vector.<br />
<br />
=== BLOB ===<br />
<br />
A BLOB is a 'binary large object' type. In PMT's, this is actually<br />
just a thin wrapper around a u8vector.<br />
<br />
== Pairs ==<br />
<br />
A concept that originates in Lisp dialects are [http://en.wikipedia.org/wiki/Cons ''pairs'' and ''cons'']. The simplest explanation is just that: If you combine two PMTs, they form a new PMT, which is a pair (or cons) of those two PMTs (don't worry about the weird name, a lot of things originating in Lisp have weird names. Think of a 'construct').<br />
<br />
Similarly to vectors or tuples, pairs are a useful way of packing several components of a message into a single PMT. Using the PMTs generated in the previous section, we can combine two of these to form a pair, here in Python:<br />
<br />
<pre>P_pair = pmt.cons(pmt.string_to_symbol(&quot;taps&quot;), P_f32vector)<br />
print pmt.is_pair(P_pair) # Prints 'true'</pre><br />
You can combine PMTs as tuples, dictionaries, vectors, or pairs, it's just a matter of taste. This construct is well-established though, and as such used in GNU Radio quite often.<br />
<br />
So how do we deconstruct a pair? That's what the <code>car</code> and <code>cdr</code> functions do. Let's deconstruct that previous pair in C++:<br />
<br />
<pre>pmt::pmt_t P_key = pmt::car(P_pair);<br />
pmt::pmt_t P_f32vector2 = pmt::cdr(P_pair);<br />
std::cout &lt;&lt; P_key &lt;&lt; std::endl; // Will print 'taps' using the PMT automatic conversion to strings</pre><br />
<br />
Here is a summary of the pairs-related functions in C++ and Python:<br />
<br />
* bool pmt::is_pair(const pmt_t &obj): Return true if obj is a pair, else false<br />
* pmt_t pmt::cons(const pmt_t &x, const pmt_t &y): construct new pair<br />
* pmt_t pmt::car(const pmt_t &pair): get the car of the pair (first object)<br />
* pmt_t pmt::cdr(const pmt_t &pair): get the cdr of the pair (second object)<br />
* void pmt::set_car(pmt_t pair, pmt_t value): Stores value in the car field<br />
* void pmt::set_cdr(pmt_t pair, pmt_t value): Stores value in the cdr field<br />
<br />
And in Python we have:<br />
<syntaxhighlight lang="python"><br />
pmt.is_pair(pair_obj) # Return True if is a pair, else False (warning: also returns True for a dict)<br />
pmt.cons(x, y) # Return a newly allocated pair whose car is x and whose cdr is y<br />
pmt.car(pair_obj) # If is a pair, return the car, otherwise raise wrong_type<br />
pmt.cdr(pair_obj) # If is a pair, return the cdr, otherwise raise wrong_type<br />
pmt.set_car(pair_obj, value) # Store value in car field<br />
pmt.set_cdr(pair_obj, value) # Store value in cdr field<br />
</syntaxhighlight><br />
<br />
For more advanced pair manipulation, refer to the [http://gnuradio.org/doc/doxygen/namespacepmt.html#a7ab95721db5cbda1852f13a92eee5362 documentation] and the [https://en.wikipedia.org/wiki/Car_and_cdr Wikipedia page for car and cdr].<br />
<br />
== Serializing and Deserializing ==<br />
<br />
It is often important to hide the fact that we are working with PMTs<br />
to make them easier to transmit, store, write to file, etc. The PMT<br />
library has methods to serialize data into a string buffer or a<br />
string and then methods to deserialize the string buffer or string<br />
back into a PMT. We use this extensively in the metadata files (see<br />
[[Metadata Information]]).<br />
<br />
* bool pmt::serialize(pmt_t obj, std::streambuf &sink)<br />
* std::string pmt::serialize_str(pmt_t obj)<br />
* pmt_t pmt::deserialize(std::streambuf &source)<br />
* pmt_t pmt::deserialize_str(std::string str)<br />
<br />
For example, we will serialize the data above to make it into a string<br />
ready to be written to a file and then deserialize it back to its<br />
original PMT.<br />
<br />
<syntaxhighlight lang="python"><br />
import pmt<br />
<br />
key0 = pmt.intern("int")<br />
val0 = pmt.from_long(123)<br />
<br />
key1 = pmt.intern("double")<br />
val1 = pmt.from_double(5.4321)<br />
<br />
# Make an empty dictionary<br />
a = pmt.make_dict()<br />
<br />
# Add a key:value pair to the dictionary<br />
a = pmt.dict_add(a, key0, val0)<br />
a = pmt.dict_add(a, key1, val1)<br />
<br />
print a<br />
<br />
ser_str = pmt.serialize_str(a)<br />
print ser_str<br />
<br />
b = pmt.deserialize_str(ser_str)<br />
print b<br />
</syntaxhighlight><br />
<br />
The line where we 'print ser_str' will print and parts will be<br />
readable, but the point of serializing is not to make a human-readable<br />
string. This is only done here as a test.<br />
<br />
== Printing ==<br />
<br />
In Python, the __repr__ function of a PMT object is overloaded to call<br />
'pmt::write_string'. This means that any time we call a formatted<br />
printing operation on a PMT object, the PMT library will properly<br />
format the object for display.<br />
<br />
In C++, we can use the 'pmt::print(object)' function or print the<br />
contents is using the overloaded "<<" operator with a stream buffer<br />
object. In C++, we can inline print the contents of a PMT like:<br />
<br />
<syntaxhighlight lang="c++"><br />
pmt::pmt_t a pmt::from_double(1.0);<br />
std::cout << "The PMT a contains " << a << std::endl;<br />
</syntaxhighlight><br />
<br />
=== Collection Notation ===<br />
PMTs use a different bracket notation from what one might be use to in Python.<br />
<br />
<syntaxhighlight lang="plaintext"><br />
>>> my_dict<br />
((meaning . 42))<br />
>>> my_vector<br />
#[1 2 3 4]<br />
>>> pmt.make_tuple(pmt.from_long(321), pmt.from_float(3.14))<br />
{321 3.14}<br />
>>> pmt.cons(pmt.from_long(1), pmt.from_long(2))<br />
(1 . 2)<br />
>>> my_pdu<br />
(() . #[1 2 3 4])<br />
</syntaxhighlight><br />
<br />
== Conversion between Python Objects and PMTs ==<br />
<br />
Although PMTs can be manipulated in Python using the Python versions<br />
of the C++ interfaces, there are some additional goodies that make it<br />
easier to work with PMTs in python. There are functions to automate<br />
the conversion between PMTs and Python types for booleans, strings,<br />
integers, longs, floats, complex numbers, dictionaries, lists, tuples<br />
and combinations thereof.<br />
<br />
Two functions capture most of this functionality:<br />
<br />
<syntaxhighlight lang="python"><br />
pmt.to_pmt # Converts a python object to a PMT.<br />
pmt.to_python # Converts a PMT into a python object.<br />
</syntaxhighlight></div>172.18.0.3https://wiki.gnuradio.org/index.php?title=FM_Demod&diff=8492FM Demod2021-04-17T00:39:38Z<p>172.18.0.3: /* Parameters */ updated Deviation to that of default in GRC 3.8.1.0</p>
<hr />
<div>[[Category:Block Docs]]<br />
Generalized FM demodulation block with deemphasis and audio filtering. This block demodulates a band-limited, complex down-converted FM channel into the the original baseband signal, optionally applying deemphasis. Low pass filtering is done on the resultant signal. It produces an output float stream in the range of [-1.0, +1.0].<br />
<br />
== Parameters ==<br />
; Channel Rate<br />
: Incoming sample rate of the FM baseband (integer)<br />
<br />
; Audio Decimation<br />
: Input to output decimation rate (integer)<br />
<br />
; Deviation<br />
: Maximum FM deviation (default = 75000) (float)<br />
<br />
; Audio Pass<br />
: Audio low pass filter passband frequency (float)<br />
<br />
; Audio Stop<br />
: Audio low pass filter stop frequency (float)<br />
<br />
; Gain<br />
: Gain applied to audio output (default = 1.0) (float)<br />
<br />
; Tau <br />
: Deemphasis time constant (default = 75e-6), specify tau=0.0 to prevent deemphasis (float)<br />
<br />
== Example Flowgraph ==<br />
<br />
Implementing an FM broadcast band receiver is really easy.<br />
<br />
[[File:Pluto_FM_fg.png|800px]]<br />
<br />
== Source Files ==<br />
<br />
; C++ files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Header files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Public header files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Block definition<br />
: [https://github.com/gnuradio/gnuradio TODO]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8470IQ Complex Tutorial2021-04-14T01:17:10Z<p>172.18.0.3: /* AM mod demod example */</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_1= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is closed to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex enveloppe, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since its allow to lower sampling rate as compare to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation help us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal correspond to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex enveloppe is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Enveloppe, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seemed counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex enveloppe is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercice: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100kHz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=GSoCIdeas&diff=8467GSoCIdeas2021-04-11T15:25:01Z<p>172.18.0.3: /* Runtime Benchmarks */</p>
<hr />
<div>== Summer of Code 2021: Project ideas list ==<br />
<br />
This is the list of project ideas for the summer of code 2021 within GNU Radio.<br /><br />
Remember that these are '''ideas''' and are merely meant as an inspiration for you to write your own proposal.<br />
<br />
Students who do not find a fit among these projects are encouraged to engage with us and suggest new ones. The [[MailingLists|GNU Radio discussion mailing list]] is the best place to contact all of us. Please do not contact us off-list for the sake of discussing the summer of code, unless you're contacting a mentor listed here to get feedback on a proposal.<br />
<br />
Reviewing the [https://developers.google.com/open-source/gsoc/faq Google GSoC FAQ] page for a broader understanding of project, mentor, and student responsibilities is recommended.<br />
<br />
If you need a USRP or other radio hardware to complete the project, we will be able to arrange something.<br />
<br />
Please add ideas to this list (you may cannibalize old ideas, of course!).<br />
<br />
Guidelines for good projects (when suggesting projects, please consider these):<br />
<br />
* Clearly defined scope, with a main target that can be done in 3 months at 50% capacity<br />
* Clear benefits for the GNU Radio project<br />
* Not specific to a certain hardware. No specific embedded devices, either, please.<br />
* Both OOTs and in-tree improvements are welcome<br />
<br />
'''The time a student can spend on a GSoC project has been reduced by 50% for 2021 - keep this in mind when submitting your ideas'''<br />
<br />
<br />
=== QT Widgets Improvements ===<br />
<br />
The gr-qtgui in-tree component provides some QT widgets for signal visualization. This component needs some improvement to become more useful.<br /><br />
This project is cleanly divided into several sub-projects:<br />
<br />
* Add a new widget<br />
** Compass display (e.g. for direction-finding applications)<br />
** MPEG display (e.g. for video demod output)<br />
** Matrix sink (e.g. for radar Doppler/range plane visualization, or 2D-equalizer taps visualization)<br />
<br />
* Improve current widgets<br />
** Better code structure to make the current widgets more manageable, extensible and remove code duplication between widgets<br />
** More Control Panels on other widgets (follow lead on the frequency sink)<br />
** Improve UI, make more intuitive, more power to mouse users<br />
** Set trigger point with mouse<br />
<br />
* Integration / Support for QT Creator<br />
** QML design<br />
** Allow to build full GUI applications from, say, GRC<br />
<br />
'''Prerequisites'''<br />
<br />
* Familiarity with QT is essential.<br />
* Widgets are written in C++, so some C++ knowledge is also required.<br />
* Python skills are highly useful.<br />
<br />
'''Mentor(s)'''<br />
<br />
Andrej Rode<br />
<br />
=== Standardized High Throughput FEC Codes ===<br />
<br />
Channel coding is essential to modern communications. Also, it is computationally very heavy. As of now, there exist implementations in GNU Radio which are too slow to be integrated into high throughput applications. GNU Radio would benefit from integration of standardized decoders for Turbo and LDPC codes. These codes would only support a certain subset of the whole code class but would be well optimized. <br />
<br />
'''Prerequisites'''<br />
<br />
* Understanding of ''gr-fec'' API. Knowledge on channel coding. Understanding of C++.<br />
<br />
'''Outcome'''<br />
<br />
* Standardized Codes, e.g. LTE Turbo Codes, 5G Polar Codes, 5G LDPC Codes, CCITT Convolutional Codes etc. are available in ''gr-fec''.<br />
* The preferred goal is to find a highly optimized implementation and integrate these into GNU Radio.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Johannes Demel<br />
<br />
<br />
=== GRC: View-Only Mode (Secure) ===<br />
<br />
When a flowgraph from an untrusted source is opened if GRC, arbitrary Python code can be executed. This poses a potential security risk. Storing the all evaluated values of all parameters within a flow graph (.grc) file would allow us to open such flow graphs without compromising security. No code would be have to executed to draw the flow graph and block parameters can be viewed safely. Only if the flow graph is modified the user would have to choose to trust the flow graph thus enabling normal eval operations.<br />
<br />
'''Prerequisites'''<br />
<br />
* GRC is implemented using Python. So, Python should be known pretty well.<br />
<br />
'''Outcome'''<br />
<br />
* Safely view other people's flowgraphs without putting your PC at risk.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Sebastian Koslowski<br />
<br />
<br />
=== gr-satellites: Viterbi decoder for 8b10b and FOX satellite decoder ===<br />
<br />
Even though the 8b10b line coding is primarily used for byte-level synchronization and spectral shaping, it adds some redundancy to the data, so it can be used as a forward error correction method to fix some bit errors in the received data. From the perspective of the decoder there is one bit of hidden state, so 8b10b line coding is amenable to Viterbi decoding, as hinted in [http://www.bigideatrouble.com/AMSAT%202013%20FOX1%20Paper.pdf this document about the AMSAT FOX satellites]. One goal of this project is to create Viterbi decoder block(s) for 8b10b and possibly other similar line codes, so that these blocks can be eventually upstreamed in-tree. The error correction performance of this method will be studied using simulations with these blocks. The second goal is to use the Viterbi decoder and gr-satellites to create a full decoder for the FOX satellites from AMSAT.<br />
<br />
'''Prerequisites'''<br />
<br />
* Knowledge of C++ and Python. Some basic understanding about FEC in general.<br />
<br />
'''Outcome'''<br />
<br />
* Viterbi decoder block(s) for 8b10b and similar line codes, FOX satellite decoder added to gr-satellites<br />
<br />
'''Mentor(s)'''<br />
<br />
* Daniel Estévez<br />
<br />
<br />
=== Runtime Benchmarks ===<br />
<br />
To facilitate development of a more modern GNU Radio runtime and scheduler, we need a tool to measure its performance (in terms of delay and throughput). This data is required to compare alternate approaches and to become aware of performance regressions early in the process.<br />
<br />
The goal of the project is to provide a tool to benchmark the GNU Radio runtime. Since we are interested in the performance on many platforms and architectures, it should provide an option to submit performance data to our server, allowing us to crowdsource data. (Similar to our online stats for SIMD performance.)<br />
<br />
'''Outcome'''<br />
<br />
* Come up with interesting metrics and, if needed, implement blocks to extract them.<br />
* Come up with interesting flowgraph topologies that should be benchmarked.<br />
* Set up automated experiments that iterate over a given parameter space (repetitions, number of samples, size of the flowgraph).<br />
* Parse, evaluate, and visualize the data.<br />
* Add an option to upload the performance data to our web server.<br />
<br />
'''Prerequisites'''<br />
<br />
* C++ programming<br />
* Data evaluation and visualization<br />
* Automation tools (like GNU Make to run benchmarks)<br />
<br />
'''Mentor(s)'''<br />
<br />
*Bastian Bloessl<br />
<br />
<div class="toccolours mw-collapsible mw-collapsed"><br />
<br />
== Summer of Code 2020: Project ideas list ==<br />
<div class="mw-collapsible-content"><br />
This is the list of project ideas for the summer of code 2020 within GNU Radio.<br /><br />
Remember that these are '''ideas''' and are merely meant as an inspiration for you to write your own proposal.<br />
<br />
Students who do not find a fit among these projects are encouraged to engage with us and suggest new ones. The [[MailingLists|GNU Radio discussion mailing list]] is the best place to contact all of us. Please do not contact us off-list for the sake of discussing the summer of code, unless you're contacting a mentor listed here to get feedback on a proposal.<br />
<br />
Reviewing the [https://developers.google.com/open-source/gsoc/faq Google GSoC FAQ] page for a broader understanding of project, mentor, and student responsibilities is recommended.<br />
<br />
If you need a USRP or other radio hardware to complete the project, we will be able to arrange something.<br />
<br />
Please add ideas to this list (you may cannibalize old ideas, of course!).<br />
<br />
Guidelines for good projects (when suggesting projects, please consider these):<br />
<br />
* Clearly defined scope, with a main target that can be done in 3 months<br />
* Clear benefits for the GNU Radio project<br />
* Not specific to a certain hardware. No specific embedded devices, either, please.<br />
* Both OOTs and in-tree improvements are welcome<br />
<br />
<br />
<br />
=== GRC: Build-in sub flowgraphs ===<br />
<br />
GNU Radio has the hierarchical blocks to build reuseable sub flowgraphs. These hier_blocks can be designed in GRC, however, they have to be compiled to code and GRC bindings, before they can be used in other GRC files. While this is great for reuseablity across flowgraphs, it is quite cumbersome when the main use is to structure a single (larger) flowgraph. The goal of this project is to ease this use-case by embedding sub flowgraphs directly in the main GRC file. Instead of creating bindings and code and then parsing them back again, this process shall be done in-place to allow quickly editing sub flowgraphs on-the-fly. <br />
<br />
'''Prerequisites'''<br />
<br />
* GRC is written in Python which is (almost) all you need to know for this project.<br />
<br />
'''Outcome'''<br />
<br />
* A vastly improved workflow for structuring flowgraphs<br />
<br />
'''Mentor(s)'''<br />
<br />
* Sebastian Koslowski<br />
<br />
<br />
<br />
=== Qt5 GUI Integrations ===<br />
<br />
Idea: Wrap the Qt GUI sinks to appear in QtCreator, including the GUI aspects of their parameterization<br />
<br />
'''Prerequisites'''<br />
<br />
* C++, Python proficiency<br />
* Qt experienced<br />
<br />
'''Outcome'''<br />
<br />
* Qt GUI Sinks usable as widgets in QtCreator (not necessarily already showing an "empty" GUI, just placeholders)<br />
* Possible to import generate Qt GUI description file (UIC) into GRC<br />
* Interface to map placeholders from GUI design to Qt GUI sinks in Flow graph<br />
* Integration of that into GRC-generated Python code<br />
<br />
'''Mentor(s)'''<br />
<br />
* Marcus Müller & Sebastian "GRC-Man" Koslowski<br />
<br />
<br />
<br />
=== Extending and Updating gr-radar ===<br />
<br />
gr-radar (https://github.com/kit-cel/gr-radar/) was a great and successful GSoC project that provided a few methods of radar in GNU Radio. This module is heavily used by academics, researchers, cybersecurity folks, and hobbyists. This project would work to improve upon the concepts already in there as well as add more radar techniques.<br />
<br />
There are uncountable methods and techniques that could be added to this project, such as:<br />
<br />
* SAR / InSAR methods<br />
* Better passive radar support<br />
* Speed camera applications<br />
* Multi-antenna radar techniques<br />
<br />
'''Prerequisites'''<br />
<br />
* Signal processing and some radar basics are required.<br />
* Code is written in C++ with some Python on the side, so the student must be able to handle these languages at the least.<br />
<br />
'''Outcome'''<br />
<br />
* Based on the student's interest, a subset of the radar techniques listed above (or others) are chosen as milestones for this project. <br />
* All code must be merged back into gr-radar by the end of the summer.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Stefan Wunsch, Martin Braun<br />
<br />
<br />
<br />
=== QT Widgets Improvements ===<br />
<br />
The gr-qtgui in-tree component provides some QT widgets for signal visualization. This component needs some improvement to become more useful.<br /><br />
This project is cleanly divided into several sub-projects:<br />
<br />
* Add new widgets<br />
** Compass display (e.g. for direction-finding applications)<br />
** MPEG display (e.g. for video demod output)<br />
** Matrix sink (e.g. for radar Doppler/range plane visualization, or 2D-equalizer taps visualization)<br />
<br />
* Improve current widgets<br />
** Better code structure to make the current widgets more manageable, extensible and remove code duplication between widgets<br />
** More Control Panels on other widgets (follow lead on the frequency sink)<br />
** Improve UI, make more intuitive, more power to mouse users<br />
** Set trigger point with mouse<br />
<br />
* Integration / Support for QT Creator<br />
** QML design<br />
** Allow to build full GUI applications from, say, GRC<br />
<br />
'''Prerequisites'''<br />
<br />
* Familiarity with QT is essential.<br />
* Widgets are written in C+'', so some C''+ knowledge is also required.<br />
* Python skills are highly useful.<br />
<br />
'''Mentor(s)'''<br />
<br />
Tim O'Shea<br />
<br />
<br />
<br />
<br />
<br />
<br />
=== Android ===<br />
<br />
One effort of the past years was to improve Android support for GNU Radio. We're getting to a point where we've figured out '''how''' to do it, so the next step is to make it more accessible to users and developers.<br />
<br />
The Android ecosystem is an entirely different beast from the rest of GNU Radio. To make writing Android/GR apps easy, the following needs to happen (and shall be part of this project):<br />
<br />
* Improve support for development environment<br />
** Create Dockers for easy start of development<br />
* Visualization classes for PSD, spectrogram and oscilloscope<br />
** Easy reuse in other apps, like the gr-qtgui widgets, but for Android SDKs<br />
* Interactivity concepts<br />
** Gestures and config for radio parameters (e.g., freq, gain, bandwidth)<br />
** Create an example FM receiver app that allows easy channel selection etc. through motions and gestures<br />
<br />
You can find a summary of the work that has been done on this (years ago) here: [[Android]]<br />
<br />
'''Prerequisites'''<br />
<br />
* Some Android experience<br />
* Enjoy writing GUI widgets<br />
* C++/Java experience<br />
<br />
'''Mentor(s)'''<br />
<br />
* Bastian Bloessl<br />
<br />
=== Runtime Benchmarks ===<br />
<br />
To facilitate development of a more modern GNU Radio runtime and scheduler, we need a tool to measure its performance (in terms of delay and throughput).<br />
This data is required to compare alternate approaches and to become aware of performance regressions early in the process.<br />
<br />
The goal of the project is to provide a tool to benchmark the GNU Radio runtime. Since we are interested in the performance on many platforms and architectures, it should provide an option to submit performance data to our sever, allowing us to crowdsource data. (Similar to our [http://stats.gnuradio.org/ online stats] for SIMD performance.)<br />
<br />
* Come up with interesting metrics and, if needed, implement blocks to extract them.<br />
* Come up with interesting flowgraph topologies that should be benchmarked.<br />
* Setup automated experiments that iterate over a given parameter space (repetitions, number of samples, size of the flowgraph).<br />
* Parse, evaluate, and visualize the data.<br />
* Add an option to upload the performance data to our web sever.<br />
<br />
'''Prerequisites'''<br />
<br />
* C++ programming<br />
* Data evaluation and visualization<br />
* Automation tools (like GNU Make to run benchmarks)<br />
<br />
'''Mentor(s)'''<br />
<br />
* Bastian Bloessl, Marcus Mueller<br />
<br />
=== Filter Design Tool Enhancements ===<br />
<br />
GNU Radio provides many tools to design and use digital filters. Using these tools requires both some expertise in these areas as well as an understanding of the performance on the given platform. One example is the selection between FIR (convolution-based) and FFT (fast convolution-based) filters for different resampling rates. Another example is doing stages of filter decomposition when doing large down-sampling. Included in this is the polyphase filterbanks, which again are provided as primitive blocks that need tweaking to work.<br />
<br />
This project is to improve our uses of these tools and blocks to make it more obvious to the users as well as automate some of the decisions for optimally using them. Some pointers:<br />
<br />
* When used in GRC, we want to save the results of the tool in a local file or for use in actual blocks.<br />
* It still currently runs on PyQWT, which is obsolete and needs to be updated to Qt5<br />
** See https://github.com/trondeau/gnuradio/tree/filter/design_tool_newgui<br />
* Add more support for filter design concepts and other filters.<br />
** Cascaded filters<br />
** Better support for creating PFB filters<br />
<br />
'''Prerequisites'''<br />
<br />
* Strong DSP background required.<br />
* Python and QT knowledge highly useful (at least one of those is a must).<br />
<br />
'''Mentor(s)'''<br />
<br />
* Marcus Leech<br />
<br />
<br />
<br />
=== Implement SigMF functionality for the GNU Radio Ecosystem ===<br />
<br />
SigMF is the "Signal Metadata Format" that was defined during the 2017 DARPA Hackfest in Brussels. Its purpose is to annotate raw binary dumps of signals with metadata, thus giving meaning to a raw mass of samples.<br /><br />
SigMF is specified and has a minimal reference implementation here: https://github.com/gnuradio/sigmf<br />
There is an out-of-tree module providing SigMF functionality for GNU Radio as well: https://github.com/skysafe/gr-sigmf<br />
<br />
However, SigMF is not represented well in the GNU Radio tooling landscape. Therefore, a subset of tools can be extended by SigMF support. Incomplete lists of possible tools benefitting from SigMF support:<br />
<br />
* qgrx (https://github.com/csete/gqrx)<br />
* inspectrum (https://github.com/miek/inspectrum)<br />
* ...<br />
<br />
Any additional tools are welcome in a proposal.<br />
<br />
'''Prerequisites'''<br />
<br />
* Knowledge of the programming language of the covered tools.<br />
* Hands-on experience with the respective tools.<br />
* Familiarity with the SigMF specification.<br />
<br />
'''Outcome'''<br />
<br />
* The tools worked on have capability to load and save files in the SigMF format.<br />
* Depending on the specific tool, SigMF meta data is displayed within the tool.<br />
* The number of tools worked on needs to be determined by the student, depending on his/her experience.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Sebastian Müller, Andrej Rode<br />
<br />
<br />
<br />
=== Statistical Toolbox for GRC ===<br />
<br />
A statistical toolbox for GRC would enable GUI-based statistical analysis. Currently, such analysis can be done by writing an independent program (e.g., with SciPy), but there is no actual integration with GNU Radio. By developing the statistical toolbox, we provide blocks for probability distribution fitting, hypothesis testing, extracting statistical parameters for one-dimensional as well as multi-dimensional data. This would significantly expand GNU Radio users' ability to perform data-science analysis and modeling on signal data.<br />
<br />
'''Prerequisites'''<br />
<br />
* Understanding of existing GNU Radio tools (e.g., GRC), GNU Radio Out-of-Tree Modules, and statistics / data-science modeling.<br />
<br />
'''Outcome'''<br />
<br />
* An OOT module that provides statistical analysis capabilities for GNU Radio.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Ben Hilburn<br />
<br />
</div><br />
</div><br />
== Application process ==<br />
<br />
Students interested in participating, read the [[GSoCStudentInfo|student instructions]] and the [[GSoCManifest|rules of conduct]].<br />
* Please introduce yourself on the [https://lists.gnu.org/mailman/listinfo/discuss-gnuradio GNU Radio mailing list]<br />
* Fill in the formal application for GNU Radio<br />
* Pick some items from the list above or feel free to suggest another piece of work relevant to this theme. Give us a detailed, week-by-week plan for completing the task over the summer.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Guided_Tutorial_Programming_Topics&diff=8455Guided Tutorial Programming Topics2021-04-08T11:45:46Z<p>172.18.0.3: /* Prerequisites */ Fixed links to message passing wiki</p>
<hr />
<div>Tags, Messages and PMTs.<br />
<br />
== Objectives ==<br />
<br />
* Learn about PMTs<br />
* Understand what tags are / what they do / when to use them<br />
* Understand difference between streaming and message passing<br />
* Point to the manual for more advanced block manipulation<br />
<br />
== Prerequisites ==<br />
<br />
* General familiarity with C++ and Python<br />
* Tutorials:<br />
** [[Guided_Tutorial_Introduction|'''A brief introduction to GNU Radio, SDR, and DSP''']]<br />
** [[Guided_Tutorial_GRC|'''Intro to GR usage: GRC and flowgraphs''']]<br />
<br />
-----<br />
<br />
So far, we have only discussed data streaming from one block to another. The data often consists of samples, and a streaming architecture makes a lot of sense for those. For example, a sound card driver block will constantly produce audio samples once active.<br />
<br />
In some cases, we don't want to pipe a stream of samples, though, but rather pass individual messages to another block, such as &quot;this is the first sample of a burst&quot;, or &quot;change the transmit frequency to 144 MHz&quot;. Or consider a MAC layer on top of a PHY: At higher communication levels, data is usually passed around in PDUs (protocol data units) instead of streams of items.<br />
<br />
In GNU Radio we have two mechanisms to pass these messages:<br />
<br />
* Synchronously to a data stream, using [https://wiki.gnuradio.org/index.php/Stream_Tags '''stream tags''']<br />
* Asynchronously, using the [https://wiki.gnuradio.org/index.php/Message_Passing '''message passing interface''']<br />
<br />
Before we discuss these, let's consider what such a message is from a programming perspective. It could be a string, a vector of items, a dictionary... anything, really, that can be represented as a data type. In Python, this would not be a problem, since it is weakly typed, and a message variable could simply be assigned whatever we need. C++ on the other hand is strongly typed, and it is not possible to create a variable without knowing its type. What makes things harder is that we need to be able to share the same data objects between Python and C++. To circumvent this problem, we introduce ''polymorphic types (PMTs)''.<br />
<br />
== Polymorphic Types (PMT) ==<br />
<br />
<content merged with PMT page in usage manual><br />
<br />
OK, so now we know all about creating messages - but how do we send them from block to block?<br />
<br />
== Stream Tags ==<br />
<br />
<merged with Stream Tags usage manual page><br />
<br />
== Message Passing ==<br />
<br />
<moved to message passing page of the usage manual><br />
<br />
[[Category:Guided Tutorials]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=ModuleNotFoundError&diff=8436ModuleNotFoundError2021-04-05T22:26:48Z<p>172.18.0.3: /* E. Store the commands in a Bash start-up file */ reboot note</p>
<hr />
<div>__NOTOC__<br />
When you start gnuradio-companion or execute grcc, if the system isn't configured properly for GRC to find the GNU Radio Python scripts and/or libraries, then you will see an error message similar to this one:<br />
<br><br><br />
[[File:ModNotFound.png|300px]]<br />
<br><br><br />
<br />
If you get this error message and you're running Linux, try the instructions on this page to see if any of them fix the issue. For issues and settings for OSX, see the [https://wiki.gnuradio.org/index.php/MacInstall#Typical_Errors_and_Warnings MacInstall guide].<br />
<br />
== A. Determine the GNU Radio install prefix ==<br />
<br />
If you don't know or remember your installation prefix, perform the following step:<br />
* on a terminal screen, enter <code>gnuradio-config-info --prefix</code><br />
then use that prefix <b>in place of</b> <code>{your-prefix}</code> in the following commands.<br />
<br />
== B. Finding the Python library ==<br />
<br />
Using a terminal, enter the following command, substituting the prefix you found above <b>in place of</b> <code>{your-prefix}</code>:<br />
<pre><br />
find {your-prefix} -name gnuradio | grep "packages"<br />
</pre><br />
Put the appropriate paths it found into the export commands below. Note that the paths are separated by colons <code>: </code><br />
<br />
== C. Setting PYTHONPATH ==<br />
<br />
- For almost all Debian / Ubuntu (and derivative) systems, and most other 32-bit Unix/Linux systems, the paths will look like this:<br />
<pre><br />
export PYTHONPATH={your-prefix}/lib/{Py-version}/dist-packages:{your-prefix}/lib/{Py-version}/site-packages:$PYTHONPATH<br />
</pre><br />
<br />
- For other 64-bit systems, the paths will look like this:<br />
<pre><br />
export PYTHONPATH={your-prefix}/lib64/{Py-version}/site-packages:$PYTHONPATH<br />
</pre><br />
<br />
== D. Setting LD_LIBRARY_PATH ==<br />
<br />
- For almost all Debian / Ubuntu (and derivative) systems, and most other 32-bit Unix/Linux systems, use:<br />
<pre><br />
export LD_LIBRARY_PATH={your-prefix}/lib:$LD_LIBRARY_PATH<br />
</pre><br />
<br />
- For other 64-bit systems, use:<br />
<pre><br />
export LD_LIBRARY_PATH={your-prefix}/lib64:$LD_LIBRARY_PATH<br />
</pre><br />
<br />
== E. Store the commands in a Bash start-up file ==<br />
<br />
Once you have determined the correct two export commands to use, open your text editor and put them in your <code>~/.bash_aliases</code> or <code>~/.bashrc</code> or <code>~/.profile</code> file. Save the file. On your terminal enter <code>exit</code>. Then start a new terminal.<br><br />
As an example, your entries might be:<br />
<pre><br />
export PYTHONPATH=/usr/local/lib/python3/dist-packages:/usr/local/lib/python3.6/dist-packages:$PYTHONPATH<br />
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH<br />
</pre><br />
<br />
Note that you may need to reboot for these changes to take effect</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=OutOfTreeModules&diff=8428OutOfTreeModules2021-04-03T13:28:52Z<p>172.18.0.3: /* Test Driven Programming */ updated for GRC 3.9+ to reflect the Swig ->PyBind11 change</p>
<hr />
<div>[[Category:Guide]]<br />
<br />
'''Extending GNU Radio with own functionality and blocks'''<br />
<br />
This article borrows heavily from the original (but very outdated) &quot;How to write a block?&quot; written by Eric Blossom.<br />
<br />
== What is an out-of-tree module? ==<br />
<br />
An out-of-tree module is a GNU Radio component that does not live within the GNU Radio source tree. Typically, if you want to extend GNU Radio with your own functions and blocks, such a module is what you create (i.e. you wouldn't usually add stuff to the actual GNU Radio source tree unless you're planning to submit it to the devs for upstream integration). This allows you to maintain the code yourself and have additional functionality alongside the main code.<br />
<br />
A lot of OOT projects are hosted at [http://cgran.org CGRAN] -- the Comprehensive GNU Radio Archive Network. CGRAN projects are all available through our tool [http://gnuradio.org/pybombs PyBOMBS]. In fact, when you add your project to the [https://github.com/gnuradio/gr-etcetera PyBOMBS recipe repo], it will automatically update the CGRAN website.<br />
<br />
For example of such a module is the [https://www.cgran.org/14680/ GNU Radio Digital Audio Broadcasting module], which extends GNU Radio with everything needed to get audio from DAB and DAB+. When installed, you have more blocks available (e.g. in the [[GNURadioCompanion|GNU Radio companion]]) which behave just like the rest of GNU Radio; however, the developers are different people.<br />
<br />
== Tools and resources at my disposal ==<br />
<br />
There are a couple of tools, scripts, and documents that are available as 3rd-party programs or as part of GNU Radio.<br />
<br />
=== gr_modtool - The swiss army knife of module editing ===<br />
<br />
When developing a module, there's a lot of boring, monotonous work involved: boilerplate code, makefile editing, etc. gr_modtool is a script which aims to help with all these things by automatically editing makefiles, using templates, and doing as much work as possible for the developer such that you can jump straight into the DSP coding.<br />
<br />
Note that gr_modtool makes a lot of assumptions on what the code looks like. The more your module is custom and has specific changes, the less useful gr_modtool will be, but it is probably the best place to start with any new module or block.<br />
<br />
gr_modtool is now available in the GNU Radio source tree and is installed by default.<br />
<br />
=== Developer resources on the wiki ===<br />
<br />
Most important is definitely the [[BlocksCodingGuide|block coding guide]]. While this is written for the GNU Radio main tree, this should also be applied to all modules. Specifically, have a look at the naming conventions!<br />
<br />
=== CMake, make, etc. ===<br />
<br />
GNU Radio uses CMake as a build system. Building a module, therefore, requires you to have cmake installed, and whatever build manager you prefer (most often this is 'make', but you could also be using Eclipse or MS Visual Studio).<br />
<br />
== Tutorial 1: Creating an out-of-tree module ==<br />
<br />
In the following tutorials, we will use an out-of-tree module called '''howto'''. The first step is to create this module.<br />
<br />
With gr_modtool, this is dead easy. Just point your command line wherever you want your new module directory (this should be outside the GNU Radio source tree!), and go:<br />
<br />
<pre>% gr_modtool newmod howto<br />
Creating out-of-tree module in ./gr-howto... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.</pre><br />
If all went well, you now have a new directory called <code>gr-howto</code> in which we will work for the other tutorials.<br />
<br />
== Structure of a module ==<br />
<br />
Let's jump straight into the gr-howto module and see what it's made up of:<br />
<br />
<pre>gr-howto % ls<br />
apps cmake CMakeLists.txt docs examples grc include lib python swig</pre><br />
It consists of several subdirectories. Anything that will be written in C++ (or C, or any language that is not Python) is put into <code>lib/</code>. For C++ files, we usually have headers which are put into <code>include/</code> (if they are to be exported) or also in <code>lib/</code> (if they're only relevant during compile time, but are not installed later, such as <code>_impl.h</code> files. You'll see what that is in the next tutorial).<br />
<br />
Of course, Python stuff goes into the <code>python/</code> directory. This includes unit tests (which are not installed) and parts of the Python module which are installed.<br />
<br />
You probably know already that GNU Radio blocks are available in Python even if they were written in C++. This is done by the help of SWIG, the simplified wrapper and interface generator, which automatically creates glue code to make this possible. SWIG needs some instructions on how to do this, which are put into the <code>swig/</code> subdirectory. Unless doing something extra clever with your block, you will not need to go into the <code>swig/</code> directory; gr_modtool handles all of that for us.<br />
<br />
If you want your blocks to be available in the [[GNURadioCompanion|GNU Radio companion]], the graphical UI for GNU Radio, you need to add descriptions of the blocks and put them into <code>grc/</code>. Prior to version 3.8 these descriptions were XML files, but from 3.8 onward they use [[YAML_GRC|YAML instead]].<br />
<br />
For documentation, <code>docs/</code> contains some instructions on how to extract documentation from the C++ files and Python files (we use Doxygen and Sphinx for this) and also make sure they're available as docstrings in Python. Of course, you can add custom documentation here as well.<br />
<br />
The <code>apps/</code> subdir contains any complete applications (both for GRC and standalone executables) which are installed to the system alongside with the blocks.<br />
<br />
The directory, <code>examples/</code> can be used to save (guess what) examples, which are a great addendum to documentation because other developers can simply look straight at the code to see how your blocks are used.<br />
<br />
The build system brings some baggage along, as well: the <code>CMakeLists.txt</code> file (one of which is present in every subdirectory) and the <code>cmake/</code> folder. You can ignore the latter for now, as it brings along mainly instructions for CMake on how to find GNU Radio libraries etc. The CMakeLists.txt files need to be edited a lot in order to make sure your module builds correctly.<br />
<br />
But one step at a time! Now, let's move on to our next tutorial.<br />
<br />
== Tutorial 2: Writing a block (square_ff) in C++ ==<br />
<br />
For our first example, we'll create a block that computes the square of its single float input. This block will accept a single float input stream and produce a single float output stream, i.e., for every incoming float item, we output one float item which is the square of that input item.<br />
<br />
Following the naming conventions, the block will be called <code>square_ff</code> because it has float inputs, float outputs.<br />
<br />
We are going to arrange that this block, as well as the others that we write in this article, end up in the <code>howto</code> Python module. This will allow us to access it from Python like this:<br />
<br />
<pre>import howto<br />
sqr = howto.square_ff()</pre><br />
=== Creating the files ===<br />
<br />
First step is to create empty files for the block and edit the CMakeLists.txt files.<br /><br />
Again, <code>gr_modtool</code> does the job. On the command line, go to the <code>gr-howto</code> directory and enter:<br />
<br />
<pre>gr-howto % gr_modtool add -t general -l cpp square_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] <br />
Add C++ QA code? [y/N] <br />
Adding file 'square_ff_impl.h'...<br />
Adding file 'square_ff_impl.cc'...<br />
Adding file 'square_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'qa_square_ff.py'...<br />
Editing python/CMakeLists.txt...<br />
Adding file 'howto_square_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
On the command line, we specify that we're adding a block, its type is 'general' (because we don't know what block types are, yet) and it is called <code>square_ff</code>. The block should be created in C++ and it currently has no specified copyright holder (by default, gr-module author is the copyright holder). <code>gr_modtool</code> then asks you if your block takes any arguments (it doesn't, so we leave that empty), whether or not we want QA code for Python (yes, we do) and for C++ (no, we don't right now).<br />
<br />
Now, have another look at the different CMakeLists.txt files and see what <code>gr_modtool</code> did. You can also see a lot of new files, which now have to be edited if we want the block to work.<br />
<br />
=== Test Driven Programming ===<br />
<br />
We could just start banging out the C++ code, but being highly evolved modern programmers, we're going to write the test code first. After all, we do have a good spec for the behavior: take a single stream of floats as the input and produce a single stream of floats as the output. The output should be the square of the input.<br />
<br />
How hard could this be? Turns out that this is easy! So, we open <code>python/qa_square_ff.py</code>, which we edit to look like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig as howto<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_square_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, &quot;qa_square_ff.xml&quot;)</pre><br />
<br />
For Gnuradio v 3.9 and up, use the following.<br />
<pre> <br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
<br />
try:<br />
from howto import square_ff<br />
except ImportError:<br />
import os<br />
import sys<br />
dirname, filename = os.path.split(os.path.abspath(__file__))<br />
sys.path.append(os.path.join(dirname, "bindings"))<br />
from howto import square_ff<br />
<br />
class qa_square_ff(gr_unittest.TestCase):<br />
<br />
def setUp(self):<br />
self.tb = gr.top_block()<br />
<br />
def tearDown(self):<br />
self.tb = None<br />
<br />
def test_instance(self):<br />
# FIXME: Test will fail until you pass sensible arguments to the constructor<br />
instance = square_ff()<br />
<br />
def test_001_square_ff(self): <br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr,dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, "qa_square_ff.yaml")<br />
</pre><br />
<br />
gr_unittest is an extension to the standard Python module unittest. gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. Unittest uses Python's reflection mechanism to find all methods that start with test_ and runs them. Unittest wraps each call to test_* with matching calls to setUp and tearDown. See the [http://docs.python.org/2/library/unittest.html Python unittest documentation] for details.<br />
<br />
When we run the test, gr_unittest.main is going to invoke setUp, test_001_square_ff, and tearDown, in that order.<br />
<br />
<code>test_001_square_ff</code> builds a small graph that contains three nodes. <code>blocks.vector_source_f(src_data)</code> will source the elements of <code>src_data</code> and then say that it's finished. <code>howto.square_ff</code> is the block we're testing. <code>blocks.vector_sink_f</code> gathers the output of <code>howto.square_ff</code>.<br />
<br />
The <code>run()</code> method runs the graph until all the blocks indicate they are finished. Finally, we check that the result of executing <code>square_ff</code> on <code>src_data</code> matches what we expect.<br />
<br />
Note that such a test is usually called before installing the module. This means that we need some trickery to be able to load the blocks when testing. CMake takes care of most things by changing PYTHONPATH appropriately. Also, we import <code>howto_swig</code> instead of <code>howto</code> in this file.<br />
<br />
In order for CMake to actually know this test exists, <code>gr_modtool</code> modified <code>python/CMakeLists.txt</code> with these lines:<br />
<br />
For GR v3.9+, remove the following <code> set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig) </code><br />
<br />
<pre>########################################################################<br />
# Handle the unit tests<br />
########################################################################<br />
include(GrTest)<br />
<br />
set(GR_TEST_TARGET_DEPS gnuradio-howto)<br />
set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig)<br />
GR_ADD_TEST(qa_square_ff ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_square_ff.py)</pre><br />
<br />
=== The C++ code (part 1) ===<br />
<br />
Now that we've got a test case, let's write the C++ code. All signal processing blocks are derived from <code>gr::block</code> or one of its subclasses. Go check out the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html block documentation] on the Doxygen-generated manual.<br />
<br />
<code>gr_modtool</code> already provided us with three files that define the block: <code>lib/square_ff_impl.h</code>, <code>lib/square_ff_impl.cc</code> and <code>include/howto/square_ff.h</code>. All we have to do is modify them to do our bidding. After you've finished with this tutorial ''please'' read and understand the [https://wiki.gnuradio.org/index.php/BlocksCodingGuide Blocks Coding Guide] to find out how these files are structured and why!<br />
<br />
First of all, we have a look at our header files. Because the block we're writing is so simple, we don't have to actually change them (the header file in <code>include/</code> is often quite complete after running <code>gr_modtool</code>, unless we need to add some public methods such as mutator methods, i.e., getters and setters). That leaves us with <code>lib/square_ff_impl.cc</code>.<br />
<br />
<code>gr_modtool</code> hints at where you have to change code by adding <code>&lt;++&gt;</code> symbols.<br /><br />
Let's go through these one at a time:<br />
<br />
<pre> square_ff_impl::square_ff_impl()<br />
: gr::block(&quot;square_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)), // input signature<br />
gr::io_signature::make(1, 1, sizeof (float))) // output signature<br />
{<br />
// empty constructor<br />
}</pre><br />
The constructor itself is empty, as the squaring block has no need to set up anything.<br />
<br />
The only interesting portion is the definition of the input and output signatures: At the input, we have 1 port that allows float inputs. The output port is the same.<br />
<br />
<pre> void<br />
square_ff_impl::forecast (int noutput_items, gr_vector_int &amp;ninput_items_required)<br />
{<br />
ninput_items_required[0] = noutput_items;<br />
}</pre><br />
<code>forecast()</code> is a function which tells the scheduler how many input items are required to produce <code>noutput_items</code> output items. In this case, they're the same. The index 0 indicates that this is for the first port, but we only have one any way. This is generally the case for <code>forecast</code> in a lot of blocks. For examples, you can look at how <code>gr::block</code>, <code>gr::sync_block</code>, <code>gr::sync_decimator</code>, and <code>gr::sync_interpolator</code> define the default forecast functions to account for things like rate changes and history.<br />
<br />
Finally, there's <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. <code>general_work()</code> is the method that does the actual signal processing:<br />
<br />
<pre> int<br />
square_ff_impl::general_work (int noutput_items,<br />
gr_vector_int &amp;ninput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which copies the square of the input buffer to the output buffer.<br />
<br />
=== Using CMake ===<br />
<br />
If you've never used CMake before, this is a good time to give it a try. The typical workflow of a CMake-based project as seen from the command line is this (if using PyBOMBS, first read '''Build Tree vs. Install Tree'''):<br />
<br />
<pre>$ mkdir build # We're currently in the module's top directory<br />
$ cd build/<br />
$ cmake ../ # Tell CMake that all its config files are one dir up<br />
$ make # And start building (should work after the previous section)</pre><br />
<br />
If using GR v3.9 you will need to run:<br />
<pre>$ sudo make install </pre><br />
<br />
===== Build Tree vs. Install Tree =====<br />
<br />
When you run cmake, you usually run it in a separate directory (e.g. <code>build/</code>). This is the build tree. The path to the install tree is <code>$prefix/lib/$pythonversion/dist-packages</code>, where <code>$prefix</code> is whatever you specified to CMake during configuration (usually <code>/usr/local/</code>) with the <code>-DCMAKE_INSTALL_PREFIX</code> switch. (Note: different versions of Python will either use site-packages or dist-packages; dist-packages is the newer way and most likely for newer OSes and installations.)<br />
<br />
If you installed GNU Radio using PyBOMBS, the install tree is located in the <code>target/</code> directory set during the initial PyBOMBS configuration. Make sure to add the -DCMAKE_INSTALL_PREFIX switch for CMake, so that it will correctly locate your GNU Radio installation. The command should look similar to this:<br />
<br />
<pre>$ cmake -DCMAKE_INSTALL_PREFIX=~/prefix-3.8 ../ # should be the configured PyBOMBS target</pre><br />
Now we have a new directory <code>build/</code> in our module's directory. All the compiling etc. is done in here, so the actual source tree is not littered with temporary files. If we change any CMakeLists.txt files, we should re-run <code>cmake ../</code> (although in truth, cmake detects these changes and reruns automatically when you next run <code>make</code>). During compilation, the libraries are copied into the build tree. Only during installation, files are installed to the install tree, thus making our blocks available to GNU Radio apps.<br />
<br />
We write our applications such that they access the code and libraries in the install tree. On the other hand, we want our test code to run on the build tree, where we can detect problems before installation.<br />
<br />
=== Let's try that -- running <code>make test</code> ===<br />
<br />
Because we wrote the QA code before the C++ code, we can immediately see if what we did was correct.<br />
<br />
We use <code>make test</code> to run our tests (run this from the <code>build/</code> subdirectory, after calling <code>cmake</code> and <code>make</code>). This invokes a shell script which sets up the PYTHONPATH environment variable so that our tests use the build tree versions of our code and libraries. It then runs all files which have names of the form qa_*.py and reports the overall success or failure.<br />
<br />
There is quite a bit of behind-the-scenes action required to use the non-installed versions of our code (look at the <code>cmake/</code> directory for a cheap thrill.)<br />
<br />
If you completed the <code>square_ff</code> block, this should work fine:<br />
<br />
<pre>gr-howto/build % make test<br />
Running tests...<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.01 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff ..................... Passed 0.38 sec<br />
<br />
100% tests passed, 0 tests failed out of 2<br />
<br />
Total Test time (real) = 0.39 sec</pre><br />
If something fails during the tests, we can dig a little deeper. When we run <code>make test</code>, we're actually invoking the CMake program <code>ctest</code>, which has a number of options we can pass to it for more detailed information. Say we forgot to multiply <code>in[i] * in[i]</code> and so aren't actually squaring the signal. If we just run <code>make test</code> or even just <code>ctest</code>, we would get this:<br />
<br />
<pre>gr-howto/build $ ctest<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.02 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
50% tests passed, 1 tests failed out of 2<br />
<br />
Total Test time (real) = 0.23 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
To find out what happened with our qa_square_ff test, we run <code>ctest -V -R square</code>. The '-V' flag gives us verbose output and the '-R' flag is a regex expression to only run those tests which match.<br />
<br />
<pre>gr-howto/build $ ctest -V -R square<br />
UpdateCTestConfiguration from :/home/braun/tmp/gr-howto/build/DartConfiguration.tcl<br />
UpdateCTestConfiguration from :/home/bruan/tmp/gr-howto/build/DartConfiguration.tcl<br />
Test project /home/braun/tmp/gr-howto/build<br />
Constructing a list of tests<br />
Done constructing a list of tests<br />
Checking test dependency graph...<br />
Checking test dependency graph end<br />
test 2<br />
Start 2: qa_square_ff<br />
<br />
2: Test command: /bin/sh &quot;/home/bruan/tmp/gr-howto/build/python/qa_square_ff_test.sh&quot;<br />
2: Test timeout computed to be: 9.99988e+06<br />
2: F<br />
2: ======================================================================<br />
2: FAIL: test_001_t (__main__.qa_square_ff)<br />
2: ----------------------------------------------------------------------<br />
2: Traceback (most recent call last):<br />
2: File &quot;/home/braun/tmp/gr-howto/python/qa_square_ff.py&quot;, line 44, in test_001_t<br />
2: self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
2: File &quot;/opt/gr/lib/python2.7/dist-packages/gnuradio/gr_unittest.py&quot;, line 90, in assertFloatTuplesAlmostEqual<br />
2: self.assertAlmostEqual (a[i], b[i], places, msg)<br />
2: AssertionError: 9 != -3.0 within 6 places<br />
2: <br />
2: ----------------------------------------------------------------------<br />
2: Ran 1 test in 0.002s<br />
2: <br />
2: FAILED (failures=1)<br />
1/1 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
0% tests passed, 1 tests failed out of 1<br />
<br />
Total Test time (real) = 0.21 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
This tells us that &quot;9 != -3.0&quot; because we expected the output to be (-3)^2 = 9 but really got the input of -3. We can use this information to go back and fix our block until the tests pass.<br />
<br />
We can also put in debug print statements into our QA code on failures, like printing out <code>expected_result</code> and <code>result_data</code> to compare them to better understand the problem.<br />
<br />
=== More C++ code (but better) - Subclasses for common patterns ===<br />
<br />
<code>gr::block</code> allows tremendous flexibility with regard to the consumption of input streams and the production of output streams. Adroit use of <code>forecast()</code> and <code>consume()</code> (see below) allows variable rate blocks to be built. It is possible to construct blocks that consume data at different rates on each input and produce output at a rate that is a function of the contents of the input data.<br />
<br />
On the other hand, it is very common for signal processing blocks to have a fixed relationship between the input rate and the output rate. Many are 1:1, while others have 1:N or N:1 relationships. You must have thought the same thing in the <code>general_work()</code> function of the previous block: if the number of items consumed is identical the number of items produced, why do I have to tell GNU Radio the exact same number twice?<br />
<br />
Another common requirement is the need to examine more than one input sample to produce a single output sample. This is orthogonal to the relationship between input and output rate. For example, a non-decimating, non-interpolating FIR filter needs to examine N input samples for each output sample it produces, where N is the number of taps in the filter. However, it only consumes a single input sample to produce a single output. We call this concept &quot;history&quot;, but you could also think of it as &quot;look-ahead&quot;.<br />
<br />
* <code>gr::sync_block</code><br />
<br />
gr::sync_block is derived from gr::block and implements a 1:1 block with optional history. Given that we know the input to output rate, certain simplifications are possible. From the implementor's point-of-view, the primary change is that we define a <code>work()</code> method instead of <code>general_work()</code>. <code>work()</code> has a slightly different calling sequence; it omits the unnecessary <code>ninput_items</code> parameter, and arranges for <code>consume_each()</code> to be called on our behalf.<br />
<br />
Let's add another block which derives from <code>gr::sync_block</code> and call it <code>square2_ff</code>. First, we edit <code>qa_square_ff.py</code> to add another test:<br />
<br />
<pre> def test_002_square2_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square2_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)</pre><br />
You can see it's the exact same test as before except for the use of <code>square2_ff</code>.<br />
<br />
Then, we use <code>gr_modtool</code> to add the block files, skipping the QA code (because we already have that):<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l cpp square2_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square2_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Add C++ QA code? [Y/n] n<br />
Adding file 'square2_ff_impl.h'...<br />
Adding file 'square2_ff_impl.cc'...<br />
Adding file 'square2_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_square2_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
<br />
If running GR v3.9+ you will need to bind the new <code>square2_ff</code> files to the existing files.<br />
<pre>gr-howto % gr_modtool bind square2_ff<br />
GNU Radio module name identifiedL howto<br />
Writing binding code to ./python/bindings/square2_ff_python.cc<br />
Writing binding code to ./python/bindings/docstrings/square2_ff_pydoc_template.h</pre><br />
<br />
Whenever howto/square2_ff.h is changed, <code> gr_modtool bind square2_ff </code> needs to be run.<br />
<br />
The constructor in <code>square2_ff_impl.cc</code> is done the same way as before, except for the parent class being <code>gr::sync_block</code>.<br />
<br />
<pre> square2_ff_impl::square2_ff_impl()<br />
: gr::sync_block(&quot;square2_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)),<br />
gr::io_signature::make(1, 1, sizeof (float)))<br />
{}<br />
<br />
// [...] skip some lines ...<br />
<br />
int<br />
square2_ff_impl::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
The <code>work</code> function is the real difference (also, we don't have a <code>forecast()</code> function any more). We'll look at it in greater detail in the next section.<br />
<br />
This gives us fewer things to worry about and less code to write. If the block requires history greater than 1, call <code>set_history()</code> in the constructor or any time the requirement changes.<br />
<br />
<code>gr::sync_block</code> provides a version of <code>forecast</code> that handles the history requirement.<br />
<br />
* <code>gr::sync_decimator</code><br />
<br />
<code>gr::sync_decimator</code> is derived from <code>gr::sync_block</code> and implements a N:1 block with optional history.<br />
<br />
* <code>gr::sync_interpolator</code><br />
<br />
<code>gr::sync_interpolator</code> is derived from <code>gr::sync_block</code> and implements a 1:N block with optional history.<br />
<br />
With this knowledge it should be clear that <code>howto_square_ff</code> should be a <code>gr::sync_block</code> with no history.<br />
<br />
Now, go back into our build directory and run <code>make</code>. Because <code>gr_modtool</code> added the <code>square2_ff</code> block to the necessary CMakeLists.txt files, <code>cmake</code> is automatically rerun for us and followed by <code>make</code>.<br />
<br />
Again, running <code>make test</code> will spawn a test run with of <code>qa_square_ff.py</code> which should not fail.<br />
<br />
=== Inside the <code>work()</code> function ===<br />
<br />
If you're using a sync block (including decimator and interpolator), this is how the skeleton code looks like produced by gr_modtool:<br />
<br />
<pre> int<br />
my_block_name::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
// Do &lt;+signal processing+&gt;<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
So, given history, vectors, multiple input ports etc., is this really all you need? Yes, it is! Because sync blocks have a fixed output to input rate, all you need to know is the number of output items, and you can calculate how many input items are available.<br />
<br />
Example - the adder block: source:gr-blocks/lib/add_XX_impl.cc.t<br />
<br />
This block has an unknown number of inputs and variable vector lengths. The number of connected ports can be checked by <code>input_items.size()</code> and <code>output_items.size()</code>. The outer <code>for</code> loop, which goes over all the available items, goes up to <code>noutput_items*d_vlen</code>. The number of output items is identical to the number of input items because it is a sync block, and you can trust GNU Radio to have this number of items available. In this case, one item is a vector of samples, but we want to add the individual samples, so the for loop considers that.<br />
<br />
Example - interpolation in gr::blocks::unpack_k_bits_bb: source:gr-blocks/lib/unpack_k_bits_bb_impl.cc [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/lib/unpack_k_bits_bb_impl.cc source]<br />
<br />
This is a block which picks apart bytes and produces the individual bits. Again, it is unknown at compile time how many bits per byte there are. However, there's a fixed number of output items per input item, so we can simply divide <code>noutput_items/d_k</code> to get the correct number of input items. It will always be correct because GNU Radio knows the input to output ratio and will make sure that <code>noutput_items</code> is always a multiple of this integer ratio.<br />
<br />
Example - history in source:gr-digital/lib/diff_phasor_cc_impl.cc<br />
<br />
If you use history of length k, GNU Radio will keep k-1 entries of the input buffer instead of discarding them. This means that if GNU Radio tells you the input buffer has N items, it actually has N+k-1 items you may use.<br />
<br />
Consider the example above. We need one previous item, so history is set to k=2. If you inspect the for loop closely, you'll find that out of <code>noutput_items</code> items, <code>noutput_items+1</code> items are actually read. This is possible because there is an extra item in the input buffer from the history.<br />
<br />
After consuming <code>noutput_items</code> items, the last entry is not discarded and will be available for the next call of <code>work()</code>.<br />
<br />
=== Help! My test fails! ===<br />
<br />
Congratulations! If your test fails, your QA code has already paid for itself. Obviously, you want to fix everything before you continue.<br />
<br />
You can use the command <code>ctest -V</code> (instead of <code>make test</code>, again, all in your <code>build/</code> subdirectory) to get all the output from the tests. You can also use <code>ctest -V -R REGEX</code> to only run tests that match REGEX, if you have many tests and want to narrow it down. If you can't figure out the problem from the output of your QA code, put in <code>print</code> statements and show intermediary results.<br />
<br />
=== Making your blocks available in GRC ===<br />
<br />
You can now install your module, but it will not be available in GRC. That's because <code>gr_modtool</code> can't create valid XML files before you've even written a block. The XML code generated when you call <code>gr_modtool add</code> is just some skeleton code.<br />
<br />
Once you've finished writing the block, <code>gr_modtool</code> has a function to help you create the XML code for you. For the howto example, you can invoke it on the <code>square2_ff</code> block by calling<br />
For GNURadio ver >= 3.8:<br />
<pre>gr-howto % gr_modtool makeyaml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
<br />
For GNURadio ver. < 3.8:<br />
<pre>gr-howto % gr_modtool makexml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
Note that <code>gr_modtool add</code> creates an invalid GRC file, so we can overwrite that.<br />
<br />
In most cases, <code>gr_modtool</code> can't figure out all the parameters by itself and you will have to edit the appropriate XML file by hand. The [[GNURadioCompanion|GRC]] wiki site has a description available.<br />
<br />
In this case, because the block is so simple, the XML is actually valid. Have a look at <code>grc/howto_square2_ff.xml</code>:<br />
<br />
<pre><br />
<block> <br />
<name>Square ff</name><br />
<key>howto_square_ff</key><br />
<category>[HOWTO]</category><br />
<import>import howto</import><br />
<make>howto.square_ff()</make><br />
<sink><br />
<name>in</name><br />
<type>float</type><br />
</sink><br />
<source><br />
<name>out</name><br />
<type>float</type><br />
</source><br />
</block><br />
</pre><br />
Perhaps you want to change the autogenerated name to something nicer.<br />
<br />
<b>Note:</b> The category name <b>must</b> be enclosed in square brackets to work!<br />
<br />
If you do a <code>make install</code> from the build directory, you can use the block in GRC. If GRC is already running, you can hit the &quot;Reload Blocks&quot; button in the GRC toolbar; it's a blue circular arrow on the right-hand side. You should now see a &quot;HOWTO&quot; category in the block tree.<br />
<br />
=== There's more: additional <code>gr::block</code>-methods ===<br />
<br />
If you've read the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html gr::block documentation] (which you should have), you'll have noticed there are a great number of methods available to configure your block.<br />
<br />
Here's some of the more important ones:<br />
<br />
==== <code>set_history()</code> ====<br />
<br />
If your block needs a history (i.e., something like an FIR filter), call this in the constructor. GNU Radio then makes sure you have the given number of 'old' items available.<br />
<br />
The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.<br />
<br />
The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).<br />
<br />
==== <code>forecast()</code> ====<br />
<br />
The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the <code>forecast()</code> method provides this information, and you must therefore override it anytime you write a <code>gr::block</code> derivative (for sync blocks, this is implicit).<br />
<br />
The default implementation of <code>forecast()</code> says there is a 1:1 relationship between <code>noutput_items</code> and the requirements for each input stream. The size of the items is defined by <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!<br />
<br />
<pre> // default implementation: 1:1<br />
void<br />
gr::block::forecast(int noutput_items,<br />
gr_vector_int &amp;ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i &lt; ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}</pre><br />
Although the 1:1 implementation worked for <code>square_ff</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from gr::sync_block, gr::sync_interpolator or gr::sync_decimator instead of gr::block, you can often avoid implementing forecast.<br />
<br />
==== <code>set_output_multiple()</code> ====<br />
<br />
When implementing your <code>general_work()</code> routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call <code>set_output_multiple</code> in your constructor to specify this requirement. The default output multiple is 1.<br />
<br />
=== Finalizing your work and installing ===<br />
<br />
First, go through this checklist:<br />
<br />
* Have you written one or more blocks, including QA codes?<br />
* Does <code>make test</code> pass?<br />
* Are there GRC bindings available (if that's what you want)?<br />
<br />
In that case, you can go ahead and install your module. On a Linux machine, this would mean going back to the build directory and calling <code>make install</code>:<br />
<br />
<pre>$ cd build/<br />
$ make install # or sudo make install</pre><br />
With Ubuntu, you may have to call <code>ldconfig</code> as well:<br />
<br />
<pre>$ sudo ldconfig</pre><br />
Otherwise, you'll get an error message that the library you just installed cannot be found.<br />
<br />
== Other types of blocks ==<br />
<br />
=== Sources and sinks ===<br />
<br />
Sources and sinks are derived from <code>gr::sync_block</code>. The only thing different about them is that sources have no inputs and sinks have no outputs. This is reflected in the <code>gr::io_signature::make</code> that are passed to the <code>gr::sync_block</code> constructor. Take a look at [source:gr-blocks/lib/file_source_impl.cc file_source.{h,cc}] and file_sink_impl.{h,cc} for some very straight-forward examples.<br />
<br />
=== Hierarchical blocks ===<br />
<br />
<code>gr_modtool</code> supports skeleton code for hierarchical blocks both in Python and C''++.<br />
<br />
<pre>~/gr-howto % gr_modtool.py add -t hier -l cpp hierblockcpp_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: hierblockcpp_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments:<br />
Add Python QA code? [Y/n]<br />
Add C++ QA code? [y/N]<br />
Adding file 'hierblockcpp_ff_impl.h'...<br />
Adding file 'hierblockcpp_ff_impl.cc'...<br />
Adding file 'hierblockcpp_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_hierblockcpp_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Using the <code>-l python</code> switch creates such a block in Python.<br />
<br />
== Everything at one glance: Cheat sheet for editing modules/components: ==<br />
<br />
Here's a quick list for all the steps necessary to build blocks and out-of-tree modules:<br />
<br />
# Create (do this once per module): <code>gr_modtool create MODULENAME</code><br />
# Add a block to the module: <code>gr_modtool add BLOCKNAME</code><br />
# Create a build directory: <code>mkdir build/</code><br />
# Invoke the make process: <code>cd build &amp;&amp; cmake &lt;OPTIONS&gt; ../ &amp;&amp; make</code> (Note that you only have to call cmake if you've changed the CMake files)<br />
# Invoke the testing: <code>make test</code> or <code>ctest</code> or <code>ctest -V</code> for more verbosity<br />
# Install (only when everything works and no tests fail): <code>sudo make install</code>. Remember to call <code>gr_modtool makexml BLOCKNAME</code> or <code>gr_modtool makeyaml BLOCKNAME</code>, to generate and if needed modify yaml file for your blocks before installing the module.<br />
# Ubuntu users: reload the libs: <code>sudo ldconfig</code><br />
# Delete blocks from the source tree: <code>gr_modtool rm REGEX</code><br />
# Disable blocks by removing them from the CMake files: <code>gr_modtool disable REGEX</code><br />
<br />
== Tutorial 3: Writing a signal processing block in Python ==<br />
<br />
'''Note:''' Writing signal processing blocks in Python comes with a performance penalty. The most common cause for using Python to write blocks is because you want to quickly prototype something without having to argue with C++.<br />
<br />
From the previous tutorials, you already know about blocks and how they work. Lets go through things a bit quicker, and code another squaring block in pure Python, which shall be called <code>square3_ff()</code>.<br />
<br />
=== Adding the test case ===<br />
<br />
So, first of all, we add another test case by editing <code>qa_square_ff.py</code>. Leaving out the test cases for the other two blocks, the QA file now looks like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig<br />
from square3_ff import square3_ff<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
# [...] Skipped the other test cases<br />
<br />
def test_003_square3_ff (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f (src_data)<br />
sqr = square3_ff ()<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, sqr)<br />
self.tb.connect (sqr, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.main ()</pre><br />
The actual test case looks '''exactly''' like the previous ones did, only replacing the block definition with <code>square3_ff()</code>. The only other difference is in the import statements: We are now importing a module called <code>square3_ff</code> from which we pull the new block.<br />
<br />
=== Adding the block code ===<br />
<br />
Having put the unit test in place, we add a file called <code>square3_ff.py</code> into the <code>python/</code> directory using <code>gr_modtool</code>:<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l python square3_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square3_ff<br />
Language: Python<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Adding file 'square3_ff.py'...<br />
Adding file 'howto_square3_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Remember not to add any QA files as we're using the existing one. Next, edit the new file <code>python/square3_ff.py</code>. It should look a bit like this:<br />
<br />
<pre>import numpy<br />
from gnuradio import gr<br />
<br />
class square3_ff(gr.sync_block):<br />
&quot; Squaring block &quot;<br />
def __init__(self):<br />
gr.sync_block.__init__(<br />
self,<br />
name = &quot;square3_ff&quot;,<br />
in_sig = [numpy.float32], # Input signature: 1 float at a time<br />
out_sig = [numpy.float32], # Output signature: 1 float at a time<br />
)<br />
<br />
def work(self, input_items, output_items):<br />
output_items[0][:] = input_items[0] * input_items[0] # Only works because numpy.array<br />
return len(output_items[0])</pre><br />
Some things should immediately stick out:<br />
<br />
* The block class is derived from <code>gr.sync_block</code>, just like the C++ version was derived from gr::sync_block<br />
* It has a constructor where the name and input/output signatures are set and a <code>work()</code> function<br />
<br />
However, there are some major differences to the C++ version:<br />
<br />
* The input and output signatures are simply defined as a list. Every element contains the item size of that port. So in this case, there is one port per input and one port per output and each has an item size of <code>numpy.float32</code> (a single-precision float). If you want a port to operate on vectors, define a tuple, e.g. [(numpy.float32, 4), numpy.float32] means there are two ports: The first one is for vectors of 4 floats, the second is for scalar floats.<br />
* When assigning vectors to <code>output_items</code>, remember to use the <code>[:]</code> operator. This makes sure Python doesn't rebind the variables or does something clever but guarantees that the data is properly copied<br />
* <code>input_items</code> and <code>output_items</code> are numpy arrays, which is why we can do the very simple element-wise multiplication the way it's done here (instead of a list comprehension)<br />
* No recompiling is necessary for the <code>make test</code> (faster development cycles, yay!)<br />
<br />
=== Other types of Python blocks ===<br />
<br />
Just like the C++ variant, there are four types of blocks in Python:<br />
<br />
* <code>gr.sync_block</code><br />
* <code>gr.decim_block</code><br />
* <code>gr.interp_block</code><br />
* <code>gr.basic_block</code> - The Python version of <code>gr::block</code><br />
<br />
Like their C++ versions, these blocks have <code>forecast()</code>, <code>work()</code>, and <code>general_work()</code> methods you can override. The difference is, the argument list for the work functions is always as shown in the previous example:<br />
<br />
<pre> def work(self, input_items, output_items):<br />
# Do stuff<br />
<br />
def general_work(self, input_items, output_items):<br />
# Do stuff</pre><br />
The number of input/output items is obtained through <code>len(input_items[PORT_NUM])</code>.<br />
<br />
=== More examples ===<br />
<br />
Check out the QA code for the Python blocks for some good examples:<br />
<br />
* [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/python/blocks/qa_block_gateway.py gr-blocks/python/blocks/qa_block_gateway.py]<br />
<br />
=== Troubleshooting ===<br />
<br />
<code>ValueError: invalid literal for int() with base 10: '...'</code> This occurs in GRC when attempting to drag a block into a flowgraph. It usually means the <code>.yml</code> file cooresponding to the block located in the <code>grc/</code> folder is not filled out. ("..." is a placeholder).</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=OutOfTreeModules&diff=8427OutOfTreeModules2021-04-02T03:43:41Z<p>172.18.0.3: /* More C++ code (but better) - Subclasses for common patterns */</p>
<hr />
<div>[[Category:Guide]]<br />
<br />
'''Extending GNU Radio with own functionality and blocks'''<br />
<br />
This article borrows heavily from the original (but very outdated) &quot;How to write a block?&quot; written by Eric Blossom.<br />
<br />
== What is an out-of-tree module? ==<br />
<br />
An out-of-tree module is a GNU Radio component that does not live within the GNU Radio source tree. Typically, if you want to extend GNU Radio with your own functions and blocks, such a module is what you create (i.e. you wouldn't usually add stuff to the actual GNU Radio source tree unless you're planning to submit it to the devs for upstream integration). This allows you to maintain the code yourself and have additional functionality alongside the main code.<br />
<br />
A lot of OOT projects are hosted at [http://cgran.org CGRAN] -- the Comprehensive GNU Radio Archive Network. CGRAN projects are all available through our tool [http://gnuradio.org/pybombs PyBOMBS]. In fact, when you add your project to the [https://github.com/gnuradio/gr-etcetera PyBOMBS recipe repo], it will automatically update the CGRAN website.<br />
<br />
For example of such a module is the [https://www.cgran.org/14680/ GNU Radio Digital Audio Broadcasting module], which extends GNU Radio with everything needed to get audio from DAB and DAB+. When installed, you have more blocks available (e.g. in the [[GNURadioCompanion|GNU Radio companion]]) which behave just like the rest of GNU Radio; however, the developers are different people.<br />
<br />
== Tools and resources at my disposal ==<br />
<br />
There are a couple of tools, scripts, and documents that are available as 3rd-party programs or as part of GNU Radio.<br />
<br />
=== gr_modtool - The swiss army knife of module editing ===<br />
<br />
When developing a module, there's a lot of boring, monotonous work involved: boilerplate code, makefile editing, etc. gr_modtool is a script which aims to help with all these things by automatically editing makefiles, using templates, and doing as much work as possible for the developer such that you can jump straight into the DSP coding.<br />
<br />
Note that gr_modtool makes a lot of assumptions on what the code looks like. The more your module is custom and has specific changes, the less useful gr_modtool will be, but it is probably the best place to start with any new module or block.<br />
<br />
gr_modtool is now available in the GNU Radio source tree and is installed by default.<br />
<br />
=== Developer resources on the wiki ===<br />
<br />
Most important is definitely the [[BlocksCodingGuide|block coding guide]]. While this is written for the GNU Radio main tree, this should also be applied to all modules. Specifically, have a look at the naming conventions!<br />
<br />
=== CMake, make, etc. ===<br />
<br />
GNU Radio uses CMake as a build system. Building a module, therefore, requires you to have cmake installed, and whatever build manager you prefer (most often this is 'make', but you could also be using Eclipse or MS Visual Studio).<br />
<br />
== Tutorial 1: Creating an out-of-tree module ==<br />
<br />
In the following tutorials, we will use an out-of-tree module called '''howto'''. The first step is to create this module.<br />
<br />
With gr_modtool, this is dead easy. Just point your command line wherever you want your new module directory (this should be outside the GNU Radio source tree!), and go:<br />
<br />
<pre>% gr_modtool newmod howto<br />
Creating out-of-tree module in ./gr-howto... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.</pre><br />
If all went well, you now have a new directory called <code>gr-howto</code> in which we will work for the other tutorials.<br />
<br />
== Structure of a module ==<br />
<br />
Let's jump straight into the gr-howto module and see what it's made up of:<br />
<br />
<pre>gr-howto % ls<br />
apps cmake CMakeLists.txt docs examples grc include lib python swig</pre><br />
It consists of several subdirectories. Anything that will be written in C++ (or C, or any language that is not Python) is put into <code>lib/</code>. For C++ files, we usually have headers which are put into <code>include/</code> (if they are to be exported) or also in <code>lib/</code> (if they're only relevant during compile time, but are not installed later, such as <code>_impl.h</code> files. You'll see what that is in the next tutorial).<br />
<br />
Of course, Python stuff goes into the <code>python/</code> directory. This includes unit tests (which are not installed) and parts of the Python module which are installed.<br />
<br />
You probably know already that GNU Radio blocks are available in Python even if they were written in C++. This is done by the help of SWIG, the simplified wrapper and interface generator, which automatically creates glue code to make this possible. SWIG needs some instructions on how to do this, which are put into the <code>swig/</code> subdirectory. Unless doing something extra clever with your block, you will not need to go into the <code>swig/</code> directory; gr_modtool handles all of that for us.<br />
<br />
If you want your blocks to be available in the [[GNURadioCompanion|GNU Radio companion]], the graphical UI for GNU Radio, you need to add descriptions of the blocks and put them into <code>grc/</code>. Prior to version 3.8 these descriptions were XML files, but from 3.8 onward they use [[YAML_GRC|YAML instead]].<br />
<br />
For documentation, <code>docs/</code> contains some instructions on how to extract documentation from the C++ files and Python files (we use Doxygen and Sphinx for this) and also make sure they're available as docstrings in Python. Of course, you can add custom documentation here as well.<br />
<br />
The <code>apps/</code> subdir contains any complete applications (both for GRC and standalone executables) which are installed to the system alongside with the blocks.<br />
<br />
The directory, <code>examples/</code> can be used to save (guess what) examples, which are a great addendum to documentation because other developers can simply look straight at the code to see how your blocks are used.<br />
<br />
The build system brings some baggage along, as well: the <code>CMakeLists.txt</code> file (one of which is present in every subdirectory) and the <code>cmake/</code> folder. You can ignore the latter for now, as it brings along mainly instructions for CMake on how to find GNU Radio libraries etc. The CMakeLists.txt files need to be edited a lot in order to make sure your module builds correctly.<br />
<br />
But one step at a time! Now, let's move on to our next tutorial.<br />
<br />
== Tutorial 2: Writing a block (square_ff) in C++ ==<br />
<br />
For our first example, we'll create a block that computes the square of its single float input. This block will accept a single float input stream and produce a single float output stream, i.e., for every incoming float item, we output one float item which is the square of that input item.<br />
<br />
Following the naming conventions, the block will be called <code>square_ff</code> because it has float inputs, float outputs.<br />
<br />
We are going to arrange that this block, as well as the others that we write in this article, end up in the <code>howto</code> Python module. This will allow us to access it from Python like this:<br />
<br />
<pre>import howto<br />
sqr = howto.square_ff()</pre><br />
=== Creating the files ===<br />
<br />
First step is to create empty files for the block and edit the CMakeLists.txt files.<br /><br />
Again, <code>gr_modtool</code> does the job. On the command line, go to the <code>gr-howto</code> directory and enter:<br />
<br />
<pre>gr-howto % gr_modtool add -t general -l cpp square_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] <br />
Add C++ QA code? [y/N] <br />
Adding file 'square_ff_impl.h'...<br />
Adding file 'square_ff_impl.cc'...<br />
Adding file 'square_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'qa_square_ff.py'...<br />
Editing python/CMakeLists.txt...<br />
Adding file 'howto_square_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
On the command line, we specify that we're adding a block, its type is 'general' (because we don't know what block types are, yet) and it is called <code>square_ff</code>. The block should be created in C++ and it currently has no specified copyright holder (by default, gr-module author is the copyright holder). <code>gr_modtool</code> then asks you if your block takes any arguments (it doesn't, so we leave that empty), whether or not we want QA code for Python (yes, we do) and for C++ (no, we don't right now).<br />
<br />
Now, have another look at the different CMakeLists.txt files and see what <code>gr_modtool</code> did. You can also see a lot of new files, which now have to be edited if we want the block to work.<br />
<br />
=== Test Driven Programming ===<br />
<br />
We could just start banging out the C++ code, but being highly evolved modern programmers, we're going to write the test code first. After all, we do have a good spec for the behavior: take a single stream of floats as the input and produce a single stream of floats as the output. The output should be the square of the input.<br />
<br />
How hard could this be? Turns out that this is easy! So, we open <code>python/qa_square_ff.py</code>, which we edit to look like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig as howto<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_square_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, &quot;qa_square_ff.xml&quot;)</pre><br />
<br />
For Gnuradio v 3.9 and up, use the following.<br />
<pre> <br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
<br />
try:<br />
from howto import square_ff<br />
except ImportError:<br />
import os<br />
import sys<br />
dirname, filename = os.path.split(os.path.abspath(__file__))<br />
sys.path.append(os.path.join(dirname, "bindings"))<br />
from howto import square_ff<br />
<br />
class qa_square_ff(gr_unittest.TestCase):<br />
<br />
def setUp(self):<br />
self.tb = gr.top_block()<br />
<br />
def tearDown(self):<br />
self.tb = None<br />
<br />
def test_instance(self):<br />
# FIXME: Test will fail until you pass sensible arguments to the constructor<br />
instance = square_ff()<br />
<br />
def test_001_square_ff(self): <br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr,dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, "qa_square_ff.yaml")<br />
</pre><br />
<br />
gr_unittest is an extension to the standard Python module unittest. gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. Unittest uses Python's reflection mechanism to find all methods that start with test_ and runs them. Unittest wraps each call to test_* with matching calls to setUp and tearDown. See the [http://docs.python.org/2/library/unittest.html Python unittest documentation] for details.<br />
<br />
When we run the test, gr_unittest.main is going to invoke setUp, test_001_square_ff, and tearDown, in that order.<br />
<br />
<code>test_001_square_ff</code> builds a small graph that contains three nodes. <code>blocks.vector_source_f(src_data)</code> will source the elements of <code>src_data</code> and then say that it's finished. <code>howto.square_ff</code> is the block we're testing. <code>blocks.vector_sink_f</code> gathers the output of <code>howto.square_ff</code>.<br />
<br />
The <code>run()</code> method runs the graph until all the blocks indicate they are finished. Finally, we check that the result of executing <code>square_ff</code> on <code>src_data</code> matches what we expect.<br />
<br />
Note that such a test is usually called before installing the module. This means that we need some trickery to be able to load the blocks when testing. CMake takes care of most things by changing PYTHONPATH appropriately. Also, we import <code>howto_swig</code> instead of <code>howto</code> in this file.<br />
<br />
In order for CMake to actually know this test exists, <code>gr_modtool</code> modified <code>python/CMakeLists.txt</code> with these lines:<br />
<br />
For GR v3.9+, remove the following <code> set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig) </code><br />
<br />
<pre>########################################################################<br />
# Handle the unit tests<br />
########################################################################<br />
include(GrTest)<br />
<br />
set(GR_TEST_TARGET_DEPS gnuradio-howto)<br />
set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig)<br />
GR_ADD_TEST(qa_square_ff ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_square_ff.py)</pre><br />
<br />
=== The C++ code (part 1) ===<br />
<br />
Now that we've got a test case, let's write the C++ code. All signal processing blocks are derived from <code>gr::block</code> or one of its subclasses. Go check out the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html block documentation] on the Doxygen-generated manual.<br />
<br />
<code>gr_modtool</code> already provided us with three files that define the block: <code>lib/square_ff_impl.h</code>, <code>lib/square_ff_impl.cc</code> and <code>include/howto/square_ff.h</code>. All we have to do is modify them to do our bidding. After you've finished with this tutorial ''please'' read and understand the [https://wiki.gnuradio.org/index.php/BlocksCodingGuide Blocks Coding Guide] to find out how these files are structured and why!<br />
<br />
First of all, we have a look at our header files. Because the block we're writing is so simple, we don't have to actually change them (the header file in <code>include/</code> is often quite complete after running <code>gr_modtool</code>, unless we need to add some public methods such as mutator methods, i.e., getters and setters). That leaves us with <code>lib/square_ff_impl.cc</code>.<br />
<br />
<code>gr_modtool</code> hints at where you have to change code by adding <code>&lt;++&gt;</code> symbols.<br /><br />
Let's go through these one at a time:<br />
<br />
<pre> square_ff_impl::square_ff_impl()<br />
: gr::block(&quot;square_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)), // input signature<br />
gr::io_signature::make(1, 1, sizeof (float))) // output signature<br />
{<br />
// empty constructor<br />
}</pre><br />
The constructor itself is empty, as the squaring block has no need to set up anything.<br />
<br />
The only interesting portion is the definition of the input and output signatures: At the input, we have 1 port that allows float inputs. The output port is the same.<br />
<br />
<pre> void<br />
square_ff_impl::forecast (int noutput_items, gr_vector_int &amp;ninput_items_required)<br />
{<br />
ninput_items_required[0] = noutput_items;<br />
}</pre><br />
<code>forecast()</code> is a function which tells the scheduler how many input items are required to produce <code>noutput_items</code> output items. In this case, they're the same. The index 0 indicates that this is for the first port, but we only have one any way. This is generally the case for <code>forecast</code> in a lot of blocks. For examples, you can look at how <code>gr::block</code>, <code>gr::sync_block</code>, <code>gr::sync_decimator</code>, and <code>gr::sync_interpolator</code> define the default forecast functions to account for things like rate changes and history.<br />
<br />
Finally, there's <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. <code>general_work()</code> is the method that does the actual signal processing:<br />
<br />
<pre> int<br />
square_ff_impl::general_work (int noutput_items,<br />
gr_vector_int &amp;ninput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which copies the square of the input buffer to the output buffer.<br />
<br />
=== Using CMake ===<br />
<br />
If you've never used CMake before, this is a good time to give it a try. The typical workflow of a CMake-based project as seen from the command line is this (if using PyBOMBS, first read '''Build Tree vs. Install Tree'''):<br />
<br />
<pre>$ mkdir build # We're currently in the module's top directory<br />
$ cd build/<br />
$ cmake ../ # Tell CMake that all its config files are one dir up<br />
$ make # And start building (should work after the previous section)</pre><br />
===== Build Tree vs. Install Tree =====<br />
<br />
When you run cmake, you usually run it in a separate directory (e.g. <code>build/</code>). This is the build tree. The path to the install tree is <code>$prefix/lib/$pythonversion/dist-packages</code>, where <code>$prefix</code> is whatever you specified to CMake during configuration (usually <code>/usr/local/</code>) with the <code>-DCMAKE_INSTALL_PREFIX</code> switch. (Note: different versions of Python will either use site-packages or dist-packages; dist-packages is the newer way and most likely for newer OSes and installations.)<br />
<br />
If you installed GNU Radio using PyBOMBS, the install tree is located in the <code>target/</code> directory set during the initial PyBOMBS configuration. Make sure to add the -DCMAKE_INSTALL_PREFIX switch for CMake, so that it will correctly locate your GNU Radio installation. The command should look similar to this:<br />
<br />
<pre>$ cmake -DCMAKE_INSTALL_PREFIX=~/prefix-3.8 ../ # should be the configured PyBOMBS target</pre><br />
Now we have a new directory <code>build/</code> in our module's directory. All the compiling etc. is done in here, so the actual source tree is not littered with temporary files. If we change any CMakeLists.txt files, we should re-run <code>cmake ../</code> (although in truth, cmake detects these changes and reruns automatically when you next run <code>make</code>). During compilation, the libraries are copied into the build tree. Only during installation, files are installed to the install tree, thus making our blocks available to GNU Radio apps.<br />
<br />
We write our applications such that they access the code and libraries in the install tree. On the other hand, we want our test code to run on the build tree, where we can detect problems before installation.<br />
<br />
=== Let's try that -- running <code>make test</code> ===<br />
<br />
Because we wrote the QA code before the C++ code, we can immediately see if what we did was correct.<br />
<br />
We use <code>make test</code> to run our tests (run this from the <code>build/</code> subdirectory, after calling <code>cmake</code> and <code>make</code>). This invokes a shell script which sets up the PYTHONPATH environment variable so that our tests use the build tree versions of our code and libraries. It then runs all files which have names of the form qa_*.py and reports the overall success or failure.<br />
<br />
There is quite a bit of behind-the-scenes action required to use the non-installed versions of our code (look at the <code>cmake/</code> directory for a cheap thrill.)<br />
<br />
If you completed the <code>square_ff</code> block, this should work fine:<br />
<br />
<pre>gr-howto/build % make test<br />
Running tests...<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.01 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff ..................... Passed 0.38 sec<br />
<br />
100% tests passed, 0 tests failed out of 2<br />
<br />
Total Test time (real) = 0.39 sec</pre><br />
If something fails during the tests, we can dig a little deeper. When we run <code>make test</code>, we're actually invoking the CMake program <code>ctest</code>, which has a number of options we can pass to it for more detailed information. Say we forgot to multiply <code>in[i] * in[i]</code> and so aren't actually squaring the signal. If we just run <code>make test</code> or even just <code>ctest</code>, we would get this:<br />
<br />
<pre>gr-howto/build $ ctest<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.02 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
50% tests passed, 1 tests failed out of 2<br />
<br />
Total Test time (real) = 0.23 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
To find out what happened with our qa_square_ff test, we run <code>ctest -V -R square</code>. The '-V' flag gives us verbose output and the '-R' flag is a regex expression to only run those tests which match.<br />
<br />
<pre>gr-howto/build $ ctest -V -R square<br />
UpdateCTestConfiguration from :/home/braun/tmp/gr-howto/build/DartConfiguration.tcl<br />
UpdateCTestConfiguration from :/home/bruan/tmp/gr-howto/build/DartConfiguration.tcl<br />
Test project /home/braun/tmp/gr-howto/build<br />
Constructing a list of tests<br />
Done constructing a list of tests<br />
Checking test dependency graph...<br />
Checking test dependency graph end<br />
test 2<br />
Start 2: qa_square_ff<br />
<br />
2: Test command: /bin/sh &quot;/home/bruan/tmp/gr-howto/build/python/qa_square_ff_test.sh&quot;<br />
2: Test timeout computed to be: 9.99988e+06<br />
2: F<br />
2: ======================================================================<br />
2: FAIL: test_001_t (__main__.qa_square_ff)<br />
2: ----------------------------------------------------------------------<br />
2: Traceback (most recent call last):<br />
2: File &quot;/home/braun/tmp/gr-howto/python/qa_square_ff.py&quot;, line 44, in test_001_t<br />
2: self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
2: File &quot;/opt/gr/lib/python2.7/dist-packages/gnuradio/gr_unittest.py&quot;, line 90, in assertFloatTuplesAlmostEqual<br />
2: self.assertAlmostEqual (a[i], b[i], places, msg)<br />
2: AssertionError: 9 != -3.0 within 6 places<br />
2: <br />
2: ----------------------------------------------------------------------<br />
2: Ran 1 test in 0.002s<br />
2: <br />
2: FAILED (failures=1)<br />
1/1 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
0% tests passed, 1 tests failed out of 1<br />
<br />
Total Test time (real) = 0.21 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
This tells us that &quot;9 != -3.0&quot; because we expected the output to be (-3)^2 = 9 but really got the input of -3. We can use this information to go back and fix our block until the tests pass.<br />
<br />
We can also put in debug print statements into our QA code on failures, like printing out <code>expected_result</code> and <code>result_data</code> to compare them to better understand the problem.<br />
<br />
=== More C++ code (but better) - Subclasses for common patterns ===<br />
<br />
<code>gr::block</code> allows tremendous flexibility with regard to the consumption of input streams and the production of output streams. Adroit use of <code>forecast()</code> and <code>consume()</code> (see below) allows variable rate blocks to be built. It is possible to construct blocks that consume data at different rates on each input and produce output at a rate that is a function of the contents of the input data.<br />
<br />
On the other hand, it is very common for signal processing blocks to have a fixed relationship between the input rate and the output rate. Many are 1:1, while others have 1:N or N:1 relationships. You must have thought the same thing in the <code>general_work()</code> function of the previous block: if the number of items consumed is identical the number of items produced, why do I have to tell GNU Radio the exact same number twice?<br />
<br />
Another common requirement is the need to examine more than one input sample to produce a single output sample. This is orthogonal to the relationship between input and output rate. For example, a non-decimating, non-interpolating FIR filter needs to examine N input samples for each output sample it produces, where N is the number of taps in the filter. However, it only consumes a single input sample to produce a single output. We call this concept &quot;history&quot;, but you could also think of it as &quot;look-ahead&quot;.<br />
<br />
* <code>gr::sync_block</code><br />
<br />
gr::sync_block is derived from gr::block and implements a 1:1 block with optional history. Given that we know the input to output rate, certain simplifications are possible. From the implementor's point-of-view, the primary change is that we define a <code>work()</code> method instead of <code>general_work()</code>. <code>work()</code> has a slightly different calling sequence; it omits the unnecessary <code>ninput_items</code> parameter, and arranges for <code>consume_each()</code> to be called on our behalf.<br />
<br />
Let's add another block which derives from <code>gr::sync_block</code> and call it <code>square2_ff</code>. First, we edit <code>qa_square_ff.py</code> to add another test:<br />
<br />
<pre> def test_002_square2_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square2_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)</pre><br />
You can see it's the exact same test as before except for the use of <code>square2_ff</code>.<br />
<br />
Then, we use <code>gr_modtool</code> to add the block files, skipping the QA code (because we already have that):<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l cpp square2_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square2_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Add C++ QA code? [Y/n] n<br />
Adding file 'square2_ff_impl.h'...<br />
Adding file 'square2_ff_impl.cc'...<br />
Adding file 'square2_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_square2_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
<br />
If running GR v3.9+ you will need to bind the new <code>square2_ff</code> files to the existing files.<br />
<pre>gr-howto % gr_modtool bind square2_ff<br />
GNU Radio module name identifiedL howto<br />
Writing binding code to ./python/bindings/square2_ff_python.cc<br />
Writing binding code to ./python/bindings/docstrings/square2_ff_pydoc_template.h</pre><br />
<br />
Whenever howto/square2_ff.h is changed, <code> gr_modtool bind square2_ff </code> needs to be run.<br />
<br />
The constructor in <code>square2_ff_impl.cc</code> is done the same way as before, except for the parent class being <code>gr::sync_block</code>.<br />
<br />
<pre> square2_ff_impl::square2_ff_impl()<br />
: gr::sync_block(&quot;square2_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)),<br />
gr::io_signature::make(1, 1, sizeof (float)))<br />
{}<br />
<br />
// [...] skip some lines ...<br />
<br />
int<br />
square2_ff_impl::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
The <code>work</code> function is the real difference (also, we don't have a <code>forecast()</code> function any more). We'll look at it in greater detail in the next section.<br />
<br />
This gives us fewer things to worry about and less code to write. If the block requires history greater than 1, call <code>set_history()</code> in the constructor or any time the requirement changes.<br />
<br />
<code>gr::sync_block</code> provides a version of <code>forecast</code> that handles the history requirement.<br />
<br />
* <code>gr::sync_decimator</code><br />
<br />
<code>gr::sync_decimator</code> is derived from <code>gr::sync_block</code> and implements a N:1 block with optional history.<br />
<br />
* <code>gr::sync_interpolator</code><br />
<br />
<code>gr::sync_interpolator</code> is derived from <code>gr::sync_block</code> and implements a 1:N block with optional history.<br />
<br />
With this knowledge it should be clear that <code>howto_square_ff</code> should be a <code>gr::sync_block</code> with no history.<br />
<br />
Now, go back into our build directory and run <code>make</code>. Because <code>gr_modtool</code> added the <code>square2_ff</code> block to the necessary CMakeLists.txt files, <code>cmake</code> is automatically rerun for us and followed by <code>make</code>.<br />
<br />
Again, running <code>make test</code> will spawn a test run with of <code>qa_square_ff.py</code> which should not fail.<br />
<br />
=== Inside the <code>work()</code> function ===<br />
<br />
If you're using a sync block (including decimator and interpolator), this is how the skeleton code looks like produced by gr_modtool:<br />
<br />
<pre> int<br />
my_block_name::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
// Do &lt;+signal processing+&gt;<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
So, given history, vectors, multiple input ports etc., is this really all you need? Yes, it is! Because sync blocks have a fixed output to input rate, all you need to know is the number of output items, and you can calculate how many input items are available.<br />
<br />
Example - the adder block: source:gr-blocks/lib/add_XX_impl.cc.t<br />
<br />
This block has an unknown number of inputs and variable vector lengths. The number of connected ports can be checked by <code>input_items.size()</code> and <code>output_items.size()</code>. The outer <code>for</code> loop, which goes over all the available items, goes up to <code>noutput_items*d_vlen</code>. The number of output items is identical to the number of input items because it is a sync block, and you can trust GNU Radio to have this number of items available. In this case, one item is a vector of samples, but we want to add the individual samples, so the for loop considers that.<br />
<br />
Example - interpolation in gr::blocks::unpack_k_bits_bb: source:gr-blocks/lib/unpack_k_bits_bb_impl.cc [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/lib/unpack_k_bits_bb_impl.cc source]<br />
<br />
This is a block which picks apart bytes and produces the individual bits. Again, it is unknown at compile time how many bits per byte there are. However, there's a fixed number of output items per input item, so we can simply divide <code>noutput_items/d_k</code> to get the correct number of input items. It will always be correct because GNU Radio knows the input to output ratio and will make sure that <code>noutput_items</code> is always a multiple of this integer ratio.<br />
<br />
Example - history in source:gr-digital/lib/diff_phasor_cc_impl.cc<br />
<br />
If you use history of length k, GNU Radio will keep k-1 entries of the input buffer instead of discarding them. This means that if GNU Radio tells you the input buffer has N items, it actually has N+k-1 items you may use.<br />
<br />
Consider the example above. We need one previous item, so history is set to k=2. If you inspect the for loop closely, you'll find that out of <code>noutput_items</code> items, <code>noutput_items+1</code> items are actually read. This is possible because there is an extra item in the input buffer from the history.<br />
<br />
After consuming <code>noutput_items</code> items, the last entry is not discarded and will be available for the next call of <code>work()</code>.<br />
<br />
=== Help! My test fails! ===<br />
<br />
Congratulations! If your test fails, your QA code has already paid for itself. Obviously, you want to fix everything before you continue.<br />
<br />
You can use the command <code>ctest -V</code> (instead of <code>make test</code>, again, all in your <code>build/</code> subdirectory) to get all the output from the tests. You can also use <code>ctest -V -R REGEX</code> to only run tests that match REGEX, if you have many tests and want to narrow it down. If you can't figure out the problem from the output of your QA code, put in <code>print</code> statements and show intermediary results.<br />
<br />
=== Making your blocks available in GRC ===<br />
<br />
You can now install your module, but it will not be available in GRC. That's because <code>gr_modtool</code> can't create valid XML files before you've even written a block. The XML code generated when you call <code>gr_modtool add</code> is just some skeleton code.<br />
<br />
Once you've finished writing the block, <code>gr_modtool</code> has a function to help you create the XML code for you. For the howto example, you can invoke it on the <code>square2_ff</code> block by calling<br />
For GNURadio ver >= 3.8:<br />
<pre>gr-howto % gr_modtool makeyaml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
<br />
For GNURadio ver. < 3.8:<br />
<pre>gr-howto % gr_modtool makexml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
Note that <code>gr_modtool add</code> creates an invalid GRC file, so we can overwrite that.<br />
<br />
In most cases, <code>gr_modtool</code> can't figure out all the parameters by itself and you will have to edit the appropriate XML file by hand. The [[GNURadioCompanion|GRC]] wiki site has a description available.<br />
<br />
In this case, because the block is so simple, the XML is actually valid. Have a look at <code>grc/howto_square2_ff.xml</code>:<br />
<br />
<pre><br />
<block> <br />
<name>Square ff</name><br />
<key>howto_square_ff</key><br />
<category>[HOWTO]</category><br />
<import>import howto</import><br />
<make>howto.square_ff()</make><br />
<sink><br />
<name>in</name><br />
<type>float</type><br />
</sink><br />
<source><br />
<name>out</name><br />
<type>float</type><br />
</source><br />
</block><br />
</pre><br />
Perhaps you want to change the autogenerated name to something nicer.<br />
<br />
<b>Note:</b> The category name <b>must</b> be enclosed in square brackets to work!<br />
<br />
If you do a <code>make install</code> from the build directory, you can use the block in GRC. If GRC is already running, you can hit the &quot;Reload Blocks&quot; button in the GRC toolbar; it's a blue circular arrow on the right-hand side. You should now see a &quot;HOWTO&quot; category in the block tree.<br />
<br />
=== There's more: additional <code>gr::block</code>-methods ===<br />
<br />
If you've read the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html gr::block documentation] (which you should have), you'll have noticed there are a great number of methods available to configure your block.<br />
<br />
Here's some of the more important ones:<br />
<br />
==== <code>set_history()</code> ====<br />
<br />
If your block needs a history (i.e., something like an FIR filter), call this in the constructor. GNU Radio then makes sure you have the given number of 'old' items available.<br />
<br />
The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.<br />
<br />
The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).<br />
<br />
==== <code>forecast()</code> ====<br />
<br />
The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the <code>forecast()</code> method provides this information, and you must therefore override it anytime you write a <code>gr::block</code> derivative (for sync blocks, this is implicit).<br />
<br />
The default implementation of <code>forecast()</code> says there is a 1:1 relationship between <code>noutput_items</code> and the requirements for each input stream. The size of the items is defined by <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!<br />
<br />
<pre> // default implementation: 1:1<br />
void<br />
gr::block::forecast(int noutput_items,<br />
gr_vector_int &amp;ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i &lt; ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}</pre><br />
Although the 1:1 implementation worked for <code>square_ff</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from gr::sync_block, gr::sync_interpolator or gr::sync_decimator instead of gr::block, you can often avoid implementing forecast.<br />
<br />
==== <code>set_output_multiple()</code> ====<br />
<br />
When implementing your <code>general_work()</code> routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call <code>set_output_multiple</code> in your constructor to specify this requirement. The default output multiple is 1.<br />
<br />
=== Finalizing your work and installing ===<br />
<br />
First, go through this checklist:<br />
<br />
* Have you written one or more blocks, including QA codes?<br />
* Does <code>make test</code> pass?<br />
* Are there GRC bindings available (if that's what you want)?<br />
<br />
In that case, you can go ahead and install your module. On a Linux machine, this would mean going back to the build directory and calling <code>make install</code>:<br />
<br />
<pre>$ cd build/<br />
$ make install # or sudo make install</pre><br />
With Ubuntu, you may have to call <code>ldconfig</code> as well:<br />
<br />
<pre>$ sudo ldconfig</pre><br />
Otherwise, you'll get an error message that the library you just installed cannot be found.<br />
<br />
== Other types of blocks ==<br />
<br />
=== Sources and sinks ===<br />
<br />
Sources and sinks are derived from <code>gr::sync_block</code>. The only thing different about them is that sources have no inputs and sinks have no outputs. This is reflected in the <code>gr::io_signature::make</code> that are passed to the <code>gr::sync_block</code> constructor. Take a look at [source:gr-blocks/lib/file_source_impl.cc file_source.{h,cc}] and file_sink_impl.{h,cc} for some very straight-forward examples.<br />
<br />
=== Hierarchical blocks ===<br />
<br />
<code>gr_modtool</code> supports skeleton code for hierarchical blocks both in Python and C''++.<br />
<br />
<pre>~/gr-howto % gr_modtool.py add -t hier -l cpp hierblockcpp_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: hierblockcpp_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments:<br />
Add Python QA code? [Y/n]<br />
Add C++ QA code? [y/N]<br />
Adding file 'hierblockcpp_ff_impl.h'...<br />
Adding file 'hierblockcpp_ff_impl.cc'...<br />
Adding file 'hierblockcpp_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_hierblockcpp_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Using the <code>-l python</code> switch creates such a block in Python.<br />
<br />
== Everything at one glance: Cheat sheet for editing modules/components: ==<br />
<br />
Here's a quick list for all the steps necessary to build blocks and out-of-tree modules:<br />
<br />
# Create (do this once per module): <code>gr_modtool create MODULENAME</code><br />
# Add a block to the module: <code>gr_modtool add BLOCKNAME</code><br />
# Create a build directory: <code>mkdir build/</code><br />
# Invoke the make process: <code>cd build &amp;&amp; cmake &lt;OPTIONS&gt; ../ &amp;&amp; make</code> (Note that you only have to call cmake if you've changed the CMake files)<br />
# Invoke the testing: <code>make test</code> or <code>ctest</code> or <code>ctest -V</code> for more verbosity<br />
# Install (only when everything works and no tests fail): <code>sudo make install</code>. Remember to call <code>gr_modtool makexml BLOCKNAME</code> or <code>gr_modtool makeyaml BLOCKNAME</code>, to generate and if needed modify yaml file for your blocks before installing the module.<br />
# Ubuntu users: reload the libs: <code>sudo ldconfig</code><br />
# Delete blocks from the source tree: <code>gr_modtool rm REGEX</code><br />
# Disable blocks by removing them from the CMake files: <code>gr_modtool disable REGEX</code><br />
<br />
== Tutorial 3: Writing a signal processing block in Python ==<br />
<br />
'''Note:''' Writing signal processing blocks in Python comes with a performance penalty. The most common cause for using Python to write blocks is because you want to quickly prototype something without having to argue with C++.<br />
<br />
From the previous tutorials, you already know about blocks and how they work. Lets go through things a bit quicker, and code another squaring block in pure Python, which shall be called <code>square3_ff()</code>.<br />
<br />
=== Adding the test case ===<br />
<br />
So, first of all, we add another test case by editing <code>qa_square_ff.py</code>. Leaving out the test cases for the other two blocks, the QA file now looks like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig<br />
from square3_ff import square3_ff<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
# [...] Skipped the other test cases<br />
<br />
def test_003_square3_ff (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f (src_data)<br />
sqr = square3_ff ()<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, sqr)<br />
self.tb.connect (sqr, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.main ()</pre><br />
The actual test case looks '''exactly''' like the previous ones did, only replacing the block definition with <code>square3_ff()</code>. The only other difference is in the import statements: We are now importing a module called <code>square3_ff</code> from which we pull the new block.<br />
<br />
=== Adding the block code ===<br />
<br />
Having put the unit test in place, we add a file called <code>square3_ff.py</code> into the <code>python/</code> directory using <code>gr_modtool</code>:<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l python square3_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square3_ff<br />
Language: Python<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Adding file 'square3_ff.py'...<br />
Adding file 'howto_square3_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Remember not to add any QA files as we're using the existing one. Next, edit the new file <code>python/square3_ff.py</code>. It should look a bit like this:<br />
<br />
<pre>import numpy<br />
from gnuradio import gr<br />
<br />
class square3_ff(gr.sync_block):<br />
&quot; Squaring block &quot;<br />
def __init__(self):<br />
gr.sync_block.__init__(<br />
self,<br />
name = &quot;square3_ff&quot;,<br />
in_sig = [numpy.float32], # Input signature: 1 float at a time<br />
out_sig = [numpy.float32], # Output signature: 1 float at a time<br />
)<br />
<br />
def work(self, input_items, output_items):<br />
output_items[0][:] = input_items[0] * input_items[0] # Only works because numpy.array<br />
return len(output_items[0])</pre><br />
Some things should immediately stick out:<br />
<br />
* The block class is derived from <code>gr.sync_block</code>, just like the C++ version was derived from gr::sync_block<br />
* It has a constructor where the name and input/output signatures are set and a <code>work()</code> function<br />
<br />
However, there are some major differences to the C++ version:<br />
<br />
* The input and output signatures are simply defined as a list. Every element contains the item size of that port. So in this case, there is one port per input and one port per output and each has an item size of <code>numpy.float32</code> (a single-precision float). If you want a port to operate on vectors, define a tuple, e.g. [(numpy.float32, 4), numpy.float32] means there are two ports: The first one is for vectors of 4 floats, the second is for scalar floats.<br />
* When assigning vectors to <code>output_items</code>, remember to use the <code>[:]</code> operator. This makes sure Python doesn't rebind the variables or does something clever but guarantees that the data is properly copied<br />
* <code>input_items</code> and <code>output_items</code> are numpy arrays, which is why we can do the very simple element-wise multiplication the way it's done here (instead of a list comprehension)<br />
* No recompiling is necessary for the <code>make test</code> (faster development cycles, yay!)<br />
<br />
=== Other types of Python blocks ===<br />
<br />
Just like the C++ variant, there are four types of blocks in Python:<br />
<br />
* <code>gr.sync_block</code><br />
* <code>gr.decim_block</code><br />
* <code>gr.interp_block</code><br />
* <code>gr.basic_block</code> - The Python version of <code>gr::block</code><br />
<br />
Like their C++ versions, these blocks have <code>forecast()</code>, <code>work()</code>, and <code>general_work()</code> methods you can override. The difference is, the argument list for the work functions is always as shown in the previous example:<br />
<br />
<pre> def work(self, input_items, output_items):<br />
# Do stuff<br />
<br />
def general_work(self, input_items, output_items):<br />
# Do stuff</pre><br />
The number of input/output items is obtained through <code>len(input_items[PORT_NUM])</code>.<br />
<br />
=== More examples ===<br />
<br />
Check out the QA code for the Python blocks for some good examples:<br />
<br />
* [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/python/blocks/qa_block_gateway.py gr-blocks/python/blocks/qa_block_gateway.py]<br />
<br />
=== Troubleshooting ===<br />
<br />
<code>ValueError: invalid literal for int() with base 10: '...'</code> This occurs in GRC when attempting to drag a block into a flowgraph. It usually means the <code>.yml</code> file cooresponding to the block located in the <code>grc/</code> folder is not filled out. ("..." is a placeholder).</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=OutOfTreeModules&diff=8426OutOfTreeModules2021-04-02T01:54:04Z<p>172.18.0.3: /* Test Driven Programming */ updated for GRC 3.9+ to reflect the Swig ->PyBind11 change</p>
<hr />
<div>[[Category:Guide]]<br />
<br />
'''Extending GNU Radio with own functionality and blocks'''<br />
<br />
This article borrows heavily from the original (but very outdated) &quot;How to write a block?&quot; written by Eric Blossom.<br />
<br />
== What is an out-of-tree module? ==<br />
<br />
An out-of-tree module is a GNU Radio component that does not live within the GNU Radio source tree. Typically, if you want to extend GNU Radio with your own functions and blocks, such a module is what you create (i.e. you wouldn't usually add stuff to the actual GNU Radio source tree unless you're planning to submit it to the devs for upstream integration). This allows you to maintain the code yourself and have additional functionality alongside the main code.<br />
<br />
A lot of OOT projects are hosted at [http://cgran.org CGRAN] -- the Comprehensive GNU Radio Archive Network. CGRAN projects are all available through our tool [http://gnuradio.org/pybombs PyBOMBS]. In fact, when you add your project to the [https://github.com/gnuradio/gr-etcetera PyBOMBS recipe repo], it will automatically update the CGRAN website.<br />
<br />
For example of such a module is the [https://www.cgran.org/14680/ GNU Radio Digital Audio Broadcasting module], which extends GNU Radio with everything needed to get audio from DAB and DAB+. When installed, you have more blocks available (e.g. in the [[GNURadioCompanion|GNU Radio companion]]) which behave just like the rest of GNU Radio; however, the developers are different people.<br />
<br />
== Tools and resources at my disposal ==<br />
<br />
There are a couple of tools, scripts, and documents that are available as 3rd-party programs or as part of GNU Radio.<br />
<br />
=== gr_modtool - The swiss army knife of module editing ===<br />
<br />
When developing a module, there's a lot of boring, monotonous work involved: boilerplate code, makefile editing, etc. gr_modtool is a script which aims to help with all these things by automatically editing makefiles, using templates, and doing as much work as possible for the developer such that you can jump straight into the DSP coding.<br />
<br />
Note that gr_modtool makes a lot of assumptions on what the code looks like. The more your module is custom and has specific changes, the less useful gr_modtool will be, but it is probably the best place to start with any new module or block.<br />
<br />
gr_modtool is now available in the GNU Radio source tree and is installed by default.<br />
<br />
=== Developer resources on the wiki ===<br />
<br />
Most important is definitely the [[BlocksCodingGuide|block coding guide]]. While this is written for the GNU Radio main tree, this should also be applied to all modules. Specifically, have a look at the naming conventions!<br />
<br />
=== CMake, make, etc. ===<br />
<br />
GNU Radio uses CMake as a build system. Building a module, therefore, requires you to have cmake installed, and whatever build manager you prefer (most often this is 'make', but you could also be using Eclipse or MS Visual Studio).<br />
<br />
== Tutorial 1: Creating an out-of-tree module ==<br />
<br />
In the following tutorials, we will use an out-of-tree module called '''howto'''. The first step is to create this module.<br />
<br />
With gr_modtool, this is dead easy. Just point your command line wherever you want your new module directory (this should be outside the GNU Radio source tree!), and go:<br />
<br />
<pre>% gr_modtool newmod howto<br />
Creating out-of-tree module in ./gr-howto... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.</pre><br />
If all went well, you now have a new directory called <code>gr-howto</code> in which we will work for the other tutorials.<br />
<br />
== Structure of a module ==<br />
<br />
Let's jump straight into the gr-howto module and see what it's made up of:<br />
<br />
<pre>gr-howto % ls<br />
apps cmake CMakeLists.txt docs examples grc include lib python swig</pre><br />
It consists of several subdirectories. Anything that will be written in C++ (or C, or any language that is not Python) is put into <code>lib/</code>. For C++ files, we usually have headers which are put into <code>include/</code> (if they are to be exported) or also in <code>lib/</code> (if they're only relevant during compile time, but are not installed later, such as <code>_impl.h</code> files. You'll see what that is in the next tutorial).<br />
<br />
Of course, Python stuff goes into the <code>python/</code> directory. This includes unit tests (which are not installed) and parts of the Python module which are installed.<br />
<br />
You probably know already that GNU Radio blocks are available in Python even if they were written in C++. This is done by the help of SWIG, the simplified wrapper and interface generator, which automatically creates glue code to make this possible. SWIG needs some instructions on how to do this, which are put into the <code>swig/</code> subdirectory. Unless doing something extra clever with your block, you will not need to go into the <code>swig/</code> directory; gr_modtool handles all of that for us.<br />
<br />
If you want your blocks to be available in the [[GNURadioCompanion|GNU Radio companion]], the graphical UI for GNU Radio, you need to add descriptions of the blocks and put them into <code>grc/</code>. Prior to version 3.8 these descriptions were XML files, but from 3.8 onward they use [[YAML_GRC|YAML instead]].<br />
<br />
For documentation, <code>docs/</code> contains some instructions on how to extract documentation from the C++ files and Python files (we use Doxygen and Sphinx for this) and also make sure they're available as docstrings in Python. Of course, you can add custom documentation here as well.<br />
<br />
The <code>apps/</code> subdir contains any complete applications (both for GRC and standalone executables) which are installed to the system alongside with the blocks.<br />
<br />
The directory, <code>examples/</code> can be used to save (guess what) examples, which are a great addendum to documentation because other developers can simply look straight at the code to see how your blocks are used.<br />
<br />
The build system brings some baggage along, as well: the <code>CMakeLists.txt</code> file (one of which is present in every subdirectory) and the <code>cmake/</code> folder. You can ignore the latter for now, as it brings along mainly instructions for CMake on how to find GNU Radio libraries etc. The CMakeLists.txt files need to be edited a lot in order to make sure your module builds correctly.<br />
<br />
But one step at a time! Now, let's move on to our next tutorial.<br />
<br />
== Tutorial 2: Writing a block (square_ff) in C++ ==<br />
<br />
For our first example, we'll create a block that computes the square of its single float input. This block will accept a single float input stream and produce a single float output stream, i.e., for every incoming float item, we output one float item which is the square of that input item.<br />
<br />
Following the naming conventions, the block will be called <code>square_ff</code> because it has float inputs, float outputs.<br />
<br />
We are going to arrange that this block, as well as the others that we write in this article, end up in the <code>howto</code> Python module. This will allow us to access it from Python like this:<br />
<br />
<pre>import howto<br />
sqr = howto.square_ff()</pre><br />
=== Creating the files ===<br />
<br />
First step is to create empty files for the block and edit the CMakeLists.txt files.<br /><br />
Again, <code>gr_modtool</code> does the job. On the command line, go to the <code>gr-howto</code> directory and enter:<br />
<br />
<pre>gr-howto % gr_modtool add -t general -l cpp square_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] <br />
Add C++ QA code? [y/N] <br />
Adding file 'square_ff_impl.h'...<br />
Adding file 'square_ff_impl.cc'...<br />
Adding file 'square_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'qa_square_ff.py'...<br />
Editing python/CMakeLists.txt...<br />
Adding file 'howto_square_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
On the command line, we specify that we're adding a block, its type is 'general' (because we don't know what block types are, yet) and it is called <code>square_ff</code>. The block should be created in C++ and it currently has no specified copyright holder (by default, gr-module author is the copyright holder). <code>gr_modtool</code> then asks you if your block takes any arguments (it doesn't, so we leave that empty), whether or not we want QA code for Python (yes, we do) and for C++ (no, we don't right now).<br />
<br />
Now, have another look at the different CMakeLists.txt files and see what <code>gr_modtool</code> did. You can also see a lot of new files, which now have to be edited if we want the block to work.<br />
<br />
=== Test Driven Programming ===<br />
<br />
We could just start banging out the C++ code, but being highly evolved modern programmers, we're going to write the test code first. After all, we do have a good spec for the behavior: take a single stream of floats as the input and produce a single stream of floats as the output. The output should be the square of the input.<br />
<br />
How hard could this be? Turns out that this is easy! So, we open <code>python/qa_square_ff.py</code>, which we edit to look like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig as howto<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_square_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, &quot;qa_square_ff.xml&quot;)</pre><br />
<br />
For Gnuradio v 3.9 and up, use the following.<br />
<pre> <br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
<br />
try:<br />
from howto import square_ff<br />
except ImportError:<br />
import os<br />
import sys<br />
dirname, filename = os.path.split(os.path.abspath(__file__))<br />
sys.path.append(os.path.join(dirname, "bindings"))<br />
from howto import square_ff<br />
<br />
class qa_square_ff(gr_unittest.TestCase):<br />
<br />
def setUp(self):<br />
self.tb = gr.top_block()<br />
<br />
def tearDown(self):<br />
self.tb = None<br />
<br />
def test_instance(self):<br />
# FIXME: Test will fail until you pass sensible arguments to the constructor<br />
instance = square_ff()<br />
<br />
def test_001_square_ff(self): <br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr,dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, "qa_square_ff.yaml")<br />
</pre><br />
<br />
gr_unittest is an extension to the standard Python module unittest. gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. Unittest uses Python's reflection mechanism to find all methods that start with test_ and runs them. Unittest wraps each call to test_* with matching calls to setUp and tearDown. See the [http://docs.python.org/2/library/unittest.html Python unittest documentation] for details.<br />
<br />
When we run the test, gr_unittest.main is going to invoke setUp, test_001_square_ff, and tearDown, in that order.<br />
<br />
<code>test_001_square_ff</code> builds a small graph that contains three nodes. <code>blocks.vector_source_f(src_data)</code> will source the elements of <code>src_data</code> and then say that it's finished. <code>howto.square_ff</code> is the block we're testing. <code>blocks.vector_sink_f</code> gathers the output of <code>howto.square_ff</code>.<br />
<br />
The <code>run()</code> method runs the graph until all the blocks indicate they are finished. Finally, we check that the result of executing <code>square_ff</code> on <code>src_data</code> matches what we expect.<br />
<br />
Note that such a test is usually called before installing the module. This means that we need some trickery to be able to load the blocks when testing. CMake takes care of most things by changing PYTHONPATH appropriately. Also, we import <code>howto_swig</code> instead of <code>howto</code> in this file.<br />
<br />
In order for CMake to actually know this test exists, <code>gr_modtool</code> modified <code>python/CMakeLists.txt</code> with these lines:<br />
<br />
For GR v3.9+, remove the following <code> set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig) </code><br />
<br />
<pre>########################################################################<br />
# Handle the unit tests<br />
########################################################################<br />
include(GrTest)<br />
<br />
set(GR_TEST_TARGET_DEPS gnuradio-howto)<br />
set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig)<br />
GR_ADD_TEST(qa_square_ff ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_square_ff.py)</pre><br />
<br />
=== The C++ code (part 1) ===<br />
<br />
Now that we've got a test case, let's write the C++ code. All signal processing blocks are derived from <code>gr::block</code> or one of its subclasses. Go check out the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html block documentation] on the Doxygen-generated manual.<br />
<br />
<code>gr_modtool</code> already provided us with three files that define the block: <code>lib/square_ff_impl.h</code>, <code>lib/square_ff_impl.cc</code> and <code>include/howto/square_ff.h</code>. All we have to do is modify them to do our bidding. After you've finished with this tutorial ''please'' read and understand the [https://wiki.gnuradio.org/index.php/BlocksCodingGuide Blocks Coding Guide] to find out how these files are structured and why!<br />
<br />
First of all, we have a look at our header files. Because the block we're writing is so simple, we don't have to actually change them (the header file in <code>include/</code> is often quite complete after running <code>gr_modtool</code>, unless we need to add some public methods such as mutator methods, i.e., getters and setters). That leaves us with <code>lib/square_ff_impl.cc</code>.<br />
<br />
<code>gr_modtool</code> hints at where you have to change code by adding <code>&lt;++&gt;</code> symbols.<br /><br />
Let's go through these one at a time:<br />
<br />
<pre> square_ff_impl::square_ff_impl()<br />
: gr::block(&quot;square_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)), // input signature<br />
gr::io_signature::make(1, 1, sizeof (float))) // output signature<br />
{<br />
// empty constructor<br />
}</pre><br />
The constructor itself is empty, as the squaring block has no need to set up anything.<br />
<br />
The only interesting portion is the definition of the input and output signatures: At the input, we have 1 port that allows float inputs. The output port is the same.<br />
<br />
<pre> void<br />
square_ff_impl::forecast (int noutput_items, gr_vector_int &amp;ninput_items_required)<br />
{<br />
ninput_items_required[0] = noutput_items;<br />
}</pre><br />
<code>forecast()</code> is a function which tells the scheduler how many input items are required to produce <code>noutput_items</code> output items. In this case, they're the same. The index 0 indicates that this is for the first port, but we only have one any way. This is generally the case for <code>forecast</code> in a lot of blocks. For examples, you can look at how <code>gr::block</code>, <code>gr::sync_block</code>, <code>gr::sync_decimator</code>, and <code>gr::sync_interpolator</code> define the default forecast functions to account for things like rate changes and history.<br />
<br />
Finally, there's <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. <code>general_work()</code> is the method that does the actual signal processing:<br />
<br />
<pre> int<br />
square_ff_impl::general_work (int noutput_items,<br />
gr_vector_int &amp;ninput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which copies the square of the input buffer to the output buffer.<br />
<br />
=== Using CMake ===<br />
<br />
If you've never used CMake before, this is a good time to give it a try. The typical workflow of a CMake-based project as seen from the command line is this (if using PyBOMBS, first read '''Build Tree vs. Install Tree'''):<br />
<br />
<pre>$ mkdir build # We're currently in the module's top directory<br />
$ cd build/<br />
$ cmake ../ # Tell CMake that all its config files are one dir up<br />
$ make # And start building (should work after the previous section)</pre><br />
===== Build Tree vs. Install Tree =====<br />
<br />
When you run cmake, you usually run it in a separate directory (e.g. <code>build/</code>). This is the build tree. The path to the install tree is <code>$prefix/lib/$pythonversion/dist-packages</code>, where <code>$prefix</code> is whatever you specified to CMake during configuration (usually <code>/usr/local/</code>) with the <code>-DCMAKE_INSTALL_PREFIX</code> switch. (Note: different versions of Python will either use site-packages or dist-packages; dist-packages is the newer way and most likely for newer OSes and installations.)<br />
<br />
If you installed GNU Radio using PyBOMBS, the install tree is located in the <code>target/</code> directory set during the initial PyBOMBS configuration. Make sure to add the -DCMAKE_INSTALL_PREFIX switch for CMake, so that it will correctly locate your GNU Radio installation. The command should look similar to this:<br />
<br />
<pre>$ cmake -DCMAKE_INSTALL_PREFIX=~/prefix-3.8 ../ # should be the configured PyBOMBS target</pre><br />
Now we have a new directory <code>build/</code> in our module's directory. All the compiling etc. is done in here, so the actual source tree is not littered with temporary files. If we change any CMakeLists.txt files, we should re-run <code>cmake ../</code> (although in truth, cmake detects these changes and reruns automatically when you next run <code>make</code>). During compilation, the libraries are copied into the build tree. Only during installation, files are installed to the install tree, thus making our blocks available to GNU Radio apps.<br />
<br />
We write our applications such that they access the code and libraries in the install tree. On the other hand, we want our test code to run on the build tree, where we can detect problems before installation.<br />
<br />
=== Let's try that -- running <code>make test</code> ===<br />
<br />
Because we wrote the QA code before the C++ code, we can immediately see if what we did was correct.<br />
<br />
We use <code>make test</code> to run our tests (run this from the <code>build/</code> subdirectory, after calling <code>cmake</code> and <code>make</code>). This invokes a shell script which sets up the PYTHONPATH environment variable so that our tests use the build tree versions of our code and libraries. It then runs all files which have names of the form qa_*.py and reports the overall success or failure.<br />
<br />
There is quite a bit of behind-the-scenes action required to use the non-installed versions of our code (look at the <code>cmake/</code> directory for a cheap thrill.)<br />
<br />
If you completed the <code>square_ff</code> block, this should work fine:<br />
<br />
<pre>gr-howto/build % make test<br />
Running tests...<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.01 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff ..................... Passed 0.38 sec<br />
<br />
100% tests passed, 0 tests failed out of 2<br />
<br />
Total Test time (real) = 0.39 sec</pre><br />
If something fails during the tests, we can dig a little deeper. When we run <code>make test</code>, we're actually invoking the CMake program <code>ctest</code>, which has a number of options we can pass to it for more detailed information. Say we forgot to multiply <code>in[i] * in[i]</code> and so aren't actually squaring the signal. If we just run <code>make test</code> or even just <code>ctest</code>, we would get this:<br />
<br />
<pre>gr-howto/build $ ctest<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.02 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
50% tests passed, 1 tests failed out of 2<br />
<br />
Total Test time (real) = 0.23 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
To find out what happened with our qa_square_ff test, we run <code>ctest -V -R square</code>. The '-V' flag gives us verbose output and the '-R' flag is a regex expression to only run those tests which match.<br />
<br />
<pre>gr-howto/build $ ctest -V -R square<br />
UpdateCTestConfiguration from :/home/braun/tmp/gr-howto/build/DartConfiguration.tcl<br />
UpdateCTestConfiguration from :/home/bruan/tmp/gr-howto/build/DartConfiguration.tcl<br />
Test project /home/braun/tmp/gr-howto/build<br />
Constructing a list of tests<br />
Done constructing a list of tests<br />
Checking test dependency graph...<br />
Checking test dependency graph end<br />
test 2<br />
Start 2: qa_square_ff<br />
<br />
2: Test command: /bin/sh &quot;/home/bruan/tmp/gr-howto/build/python/qa_square_ff_test.sh&quot;<br />
2: Test timeout computed to be: 9.99988e+06<br />
2: F<br />
2: ======================================================================<br />
2: FAIL: test_001_t (__main__.qa_square_ff)<br />
2: ----------------------------------------------------------------------<br />
2: Traceback (most recent call last):<br />
2: File &quot;/home/braun/tmp/gr-howto/python/qa_square_ff.py&quot;, line 44, in test_001_t<br />
2: self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
2: File &quot;/opt/gr/lib/python2.7/dist-packages/gnuradio/gr_unittest.py&quot;, line 90, in assertFloatTuplesAlmostEqual<br />
2: self.assertAlmostEqual (a[i], b[i], places, msg)<br />
2: AssertionError: 9 != -3.0 within 6 places<br />
2: <br />
2: ----------------------------------------------------------------------<br />
2: Ran 1 test in 0.002s<br />
2: <br />
2: FAILED (failures=1)<br />
1/1 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
0% tests passed, 1 tests failed out of 1<br />
<br />
Total Test time (real) = 0.21 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
This tells us that &quot;9 != -3.0&quot; because we expected the output to be (-3)^2 = 9 but really got the input of -3. We can use this information to go back and fix our block until the tests pass.<br />
<br />
We can also put in debug print statements into our QA code on failures, like printing out <code>expected_result</code> and <code>result_data</code> to compare them to better understand the problem.<br />
<br />
=== More C++ code (but better) - Subclasses for common patterns ===<br />
<br />
<code>gr::block</code> allows tremendous flexibility with regard to the consumption of input streams and the production of output streams. Adroit use of <code>forecast()</code> and <code>consume()</code> (see below) allows variable rate blocks to be built. It is possible to construct blocks that consume data at different rates on each input and produce output at a rate that is a function of the contents of the input data.<br />
<br />
On the other hand, it is very common for signal processing blocks to have a fixed relationship between the input rate and the output rate. Many are 1:1, while others have 1:N or N:1 relationships. You must have thought the same thing in the <code>general_work()</code> function of the previous block: if the number of items consumed is identical the number of items produced, why do I have to tell GNU Radio the exact same number twice?<br />
<br />
Another common requirement is the need to examine more than one input sample to produce a single output sample. This is orthogonal to the relationship between input and output rate. For example, a non-decimating, non-interpolating FIR filter needs to examine N input samples for each output sample it produces, where N is the number of taps in the filter. However, it only consumes a single input sample to produce a single output. We call this concept &quot;history&quot;, but you could also think of it as &quot;look-ahead&quot;.<br />
<br />
* <code>gr::sync_block</code><br />
<br />
gr::sync_block is derived from gr::block and implements a 1:1 block with optional history. Given that we know the input to output rate, certain simplifications are possible. From the implementor's point-of-view, the primary change is that we define a <code>work()</code> method instead of <code>general_work()</code>. <code>work()</code> has a slightly different calling sequence; it omits the unnecessary <code>ninput_items</code> parameter, and arranges for <code>consume_each()</code> to be called on our behalf.<br />
<br />
Let's add another block which derives from <code>gr::sync_block</code> and call it <code>square2_ff</code>. First, we edit <code>qa_square_ff.py</code> to add another test:<br />
<br />
<pre> def test_002_square2_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square2_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)</pre><br />
You can see it's the exact same test as before except for the use of <code>square2_ff</code>.<br />
<br />
Then, we use <code>gr_modtool</code> to add the block files, skipping the QA code (because we already have that):<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l cpp square2_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square2_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Add C++ QA code? [Y/n] n<br />
Adding file 'square2_ff_impl.h'...<br />
Adding file 'square2_ff_impl.cc'...<br />
Adding file 'square2_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_square2_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
The constructor in <code>square2_ff_impl.cc</code> is done the same way as before, except for the parent class being <code>gr::sync_block</code>.<br />
<br />
<pre> square2_ff_impl::square2_ff_impl()<br />
: gr::sync_block(&quot;square2_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)),<br />
gr::io_signature::make(1, 1, sizeof (float)))<br />
{}<br />
<br />
// [...] skip some lines ...<br />
<br />
int<br />
square2_ff_impl::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
The <code>work</code> function is the real difference (also, we don't have a <code>forecast()</code> function any more). We'll look at it in greater detail in the next section.<br />
<br />
This gives us fewer things to worry about and less code to write. If the block requires history greater than 1, call <code>set_history()</code> in the constructor or any time the requirement changes.<br />
<br />
<code>gr::sync_block</code> provides a version of <code>forecast</code> that handles the history requirement.<br />
<br />
* <code>gr::sync_decimator</code><br />
<br />
<code>gr::sync_decimator</code> is derived from <code>gr::sync_block</code> and implements a N:1 block with optional history.<br />
<br />
* <code>gr::sync_interpolator</code><br />
<br />
<code>gr::sync_interpolator</code> is derived from <code>gr::sync_block</code> and implements a 1:N block with optional history.<br />
<br />
With this knowledge it should be clear that <code>howto_square_ff</code> should be a <code>gr::sync_block</code> with no history.<br />
<br />
Now, go back into our build directory and run <code>make</code>. Because <code>gr_modtool</code> added the <code>square2_ff</code> block to the necessary CMakeLists.txt files, <code>cmake</code> is automatically rerun for us and followed by <code>make</code>.<br />
<br />
Again, running <code>make test</code> will spawn a test run with of <code>qa_square_ff.py</code> which should not fail.<br />
<br />
=== Inside the <code>work()</code> function ===<br />
<br />
If you're using a sync block (including decimator and interpolator), this is how the skeleton code looks like produced by gr_modtool:<br />
<br />
<pre> int<br />
my_block_name::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
// Do &lt;+signal processing+&gt;<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
So, given history, vectors, multiple input ports etc., is this really all you need? Yes, it is! Because sync blocks have a fixed output to input rate, all you need to know is the number of output items, and you can calculate how many input items are available.<br />
<br />
Example - the adder block: source:gr-blocks/lib/add_XX_impl.cc.t<br />
<br />
This block has an unknown number of inputs and variable vector lengths. The number of connected ports can be checked by <code>input_items.size()</code> and <code>output_items.size()</code>. The outer <code>for</code> loop, which goes over all the available items, goes up to <code>noutput_items*d_vlen</code>. The number of output items is identical to the number of input items because it is a sync block, and you can trust GNU Radio to have this number of items available. In this case, one item is a vector of samples, but we want to add the individual samples, so the for loop considers that.<br />
<br />
Example - interpolation in gr::blocks::unpack_k_bits_bb: source:gr-blocks/lib/unpack_k_bits_bb_impl.cc [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/lib/unpack_k_bits_bb_impl.cc source]<br />
<br />
This is a block which picks apart bytes and produces the individual bits. Again, it is unknown at compile time how many bits per byte there are. However, there's a fixed number of output items per input item, so we can simply divide <code>noutput_items/d_k</code> to get the correct number of input items. It will always be correct because GNU Radio knows the input to output ratio and will make sure that <code>noutput_items</code> is always a multiple of this integer ratio.<br />
<br />
Example - history in source:gr-digital/lib/diff_phasor_cc_impl.cc<br />
<br />
If you use history of length k, GNU Radio will keep k-1 entries of the input buffer instead of discarding them. This means that if GNU Radio tells you the input buffer has N items, it actually has N+k-1 items you may use.<br />
<br />
Consider the example above. We need one previous item, so history is set to k=2. If you inspect the for loop closely, you'll find that out of <code>noutput_items</code> items, <code>noutput_items+1</code> items are actually read. This is possible because there is an extra item in the input buffer from the history.<br />
<br />
After consuming <code>noutput_items</code> items, the last entry is not discarded and will be available for the next call of <code>work()</code>.<br />
<br />
=== Help! My test fails! ===<br />
<br />
Congratulations! If your test fails, your QA code has already paid for itself. Obviously, you want to fix everything before you continue.<br />
<br />
You can use the command <code>ctest -V</code> (instead of <code>make test</code>, again, all in your <code>build/</code> subdirectory) to get all the output from the tests. You can also use <code>ctest -V -R REGEX</code> to only run tests that match REGEX, if you have many tests and want to narrow it down. If you can't figure out the problem from the output of your QA code, put in <code>print</code> statements and show intermediary results.<br />
<br />
=== Making your blocks available in GRC ===<br />
<br />
You can now install your module, but it will not be available in GRC. That's because <code>gr_modtool</code> can't create valid XML files before you've even written a block. The XML code generated when you call <code>gr_modtool add</code> is just some skeleton code.<br />
<br />
Once you've finished writing the block, <code>gr_modtool</code> has a function to help you create the XML code for you. For the howto example, you can invoke it on the <code>square2_ff</code> block by calling<br />
For GNURadio ver >= 3.8:<br />
<pre>gr-howto % gr_modtool makeyaml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
<br />
For GNURadio ver. < 3.8:<br />
<pre>gr-howto % gr_modtool makexml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
Note that <code>gr_modtool add</code> creates an invalid GRC file, so we can overwrite that.<br />
<br />
In most cases, <code>gr_modtool</code> can't figure out all the parameters by itself and you will have to edit the appropriate XML file by hand. The [[GNURadioCompanion|GRC]] wiki site has a description available.<br />
<br />
In this case, because the block is so simple, the XML is actually valid. Have a look at <code>grc/howto_square2_ff.xml</code>:<br />
<br />
<pre><br />
<block> <br />
<name>Square ff</name><br />
<key>howto_square_ff</key><br />
<category>[HOWTO]</category><br />
<import>import howto</import><br />
<make>howto.square_ff()</make><br />
<sink><br />
<name>in</name><br />
<type>float</type><br />
</sink><br />
<source><br />
<name>out</name><br />
<type>float</type><br />
</source><br />
</block><br />
</pre><br />
Perhaps you want to change the autogenerated name to something nicer.<br />
<br />
<b>Note:</b> The category name <b>must</b> be enclosed in square brackets to work!<br />
<br />
If you do a <code>make install</code> from the build directory, you can use the block in GRC. If GRC is already running, you can hit the &quot;Reload Blocks&quot; button in the GRC toolbar; it's a blue circular arrow on the right-hand side. You should now see a &quot;HOWTO&quot; category in the block tree.<br />
<br />
=== There's more: additional <code>gr::block</code>-methods ===<br />
<br />
If you've read the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html gr::block documentation] (which you should have), you'll have noticed there are a great number of methods available to configure your block.<br />
<br />
Here's some of the more important ones:<br />
<br />
==== <code>set_history()</code> ====<br />
<br />
If your block needs a history (i.e., something like an FIR filter), call this in the constructor. GNU Radio then makes sure you have the given number of 'old' items available.<br />
<br />
The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.<br />
<br />
The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).<br />
<br />
==== <code>forecast()</code> ====<br />
<br />
The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the <code>forecast()</code> method provides this information, and you must therefore override it anytime you write a <code>gr::block</code> derivative (for sync blocks, this is implicit).<br />
<br />
The default implementation of <code>forecast()</code> says there is a 1:1 relationship between <code>noutput_items</code> and the requirements for each input stream. The size of the items is defined by <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!<br />
<br />
<pre> // default implementation: 1:1<br />
void<br />
gr::block::forecast(int noutput_items,<br />
gr_vector_int &amp;ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i &lt; ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}</pre><br />
Although the 1:1 implementation worked for <code>square_ff</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from gr::sync_block, gr::sync_interpolator or gr::sync_decimator instead of gr::block, you can often avoid implementing forecast.<br />
<br />
==== <code>set_output_multiple()</code> ====<br />
<br />
When implementing your <code>general_work()</code> routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call <code>set_output_multiple</code> in your constructor to specify this requirement. The default output multiple is 1.<br />
<br />
=== Finalizing your work and installing ===<br />
<br />
First, go through this checklist:<br />
<br />
* Have you written one or more blocks, including QA codes?<br />
* Does <code>make test</code> pass?<br />
* Are there GRC bindings available (if that's what you want)?<br />
<br />
In that case, you can go ahead and install your module. On a Linux machine, this would mean going back to the build directory and calling <code>make install</code>:<br />
<br />
<pre>$ cd build/<br />
$ make install # or sudo make install</pre><br />
With Ubuntu, you may have to call <code>ldconfig</code> as well:<br />
<br />
<pre>$ sudo ldconfig</pre><br />
Otherwise, you'll get an error message that the library you just installed cannot be found.<br />
<br />
== Other types of blocks ==<br />
<br />
=== Sources and sinks ===<br />
<br />
Sources and sinks are derived from <code>gr::sync_block</code>. The only thing different about them is that sources have no inputs and sinks have no outputs. This is reflected in the <code>gr::io_signature::make</code> that are passed to the <code>gr::sync_block</code> constructor. Take a look at [source:gr-blocks/lib/file_source_impl.cc file_source.{h,cc}] and file_sink_impl.{h,cc} for some very straight-forward examples.<br />
<br />
=== Hierarchical blocks ===<br />
<br />
<code>gr_modtool</code> supports skeleton code for hierarchical blocks both in Python and C''++.<br />
<br />
<pre>~/gr-howto % gr_modtool.py add -t hier -l cpp hierblockcpp_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: hierblockcpp_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments:<br />
Add Python QA code? [Y/n]<br />
Add C++ QA code? [y/N]<br />
Adding file 'hierblockcpp_ff_impl.h'...<br />
Adding file 'hierblockcpp_ff_impl.cc'...<br />
Adding file 'hierblockcpp_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_hierblockcpp_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Using the <code>-l python</code> switch creates such a block in Python.<br />
<br />
== Everything at one glance: Cheat sheet for editing modules/components: ==<br />
<br />
Here's a quick list for all the steps necessary to build blocks and out-of-tree modules:<br />
<br />
# Create (do this once per module): <code>gr_modtool create MODULENAME</code><br />
# Add a block to the module: <code>gr_modtool add BLOCKNAME</code><br />
# Create a build directory: <code>mkdir build/</code><br />
# Invoke the make process: <code>cd build &amp;&amp; cmake &lt;OPTIONS&gt; ../ &amp;&amp; make</code> (Note that you only have to call cmake if you've changed the CMake files)<br />
# Invoke the testing: <code>make test</code> or <code>ctest</code> or <code>ctest -V</code> for more verbosity<br />
# Install (only when everything works and no tests fail): <code>sudo make install</code>. Remember to call <code>gr_modtool makexml BLOCKNAME</code> or <code>gr_modtool makeyaml BLOCKNAME</code>, to generate and if needed modify yaml file for your blocks before installing the module.<br />
# Ubuntu users: reload the libs: <code>sudo ldconfig</code><br />
# Delete blocks from the source tree: <code>gr_modtool rm REGEX</code><br />
# Disable blocks by removing them from the CMake files: <code>gr_modtool disable REGEX</code><br />
<br />
== Tutorial 3: Writing a signal processing block in Python ==<br />
<br />
'''Note:''' Writing signal processing blocks in Python comes with a performance penalty. The most common cause for using Python to write blocks is because you want to quickly prototype something without having to argue with C++.<br />
<br />
From the previous tutorials, you already know about blocks and how they work. Lets go through things a bit quicker, and code another squaring block in pure Python, which shall be called <code>square3_ff()</code>.<br />
<br />
=== Adding the test case ===<br />
<br />
So, first of all, we add another test case by editing <code>qa_square_ff.py</code>. Leaving out the test cases for the other two blocks, the QA file now looks like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig<br />
from square3_ff import square3_ff<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
# [...] Skipped the other test cases<br />
<br />
def test_003_square3_ff (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f (src_data)<br />
sqr = square3_ff ()<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, sqr)<br />
self.tb.connect (sqr, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.main ()</pre><br />
The actual test case looks '''exactly''' like the previous ones did, only replacing the block definition with <code>square3_ff()</code>. The only other difference is in the import statements: We are now importing a module called <code>square3_ff</code> from which we pull the new block.<br />
<br />
=== Adding the block code ===<br />
<br />
Having put the unit test in place, we add a file called <code>square3_ff.py</code> into the <code>python/</code> directory using <code>gr_modtool</code>:<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l python square3_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square3_ff<br />
Language: Python<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Adding file 'square3_ff.py'...<br />
Adding file 'howto_square3_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Remember not to add any QA files as we're using the existing one. Next, edit the new file <code>python/square3_ff.py</code>. It should look a bit like this:<br />
<br />
<pre>import numpy<br />
from gnuradio import gr<br />
<br />
class square3_ff(gr.sync_block):<br />
&quot; Squaring block &quot;<br />
def __init__(self):<br />
gr.sync_block.__init__(<br />
self,<br />
name = &quot;square3_ff&quot;,<br />
in_sig = [numpy.float32], # Input signature: 1 float at a time<br />
out_sig = [numpy.float32], # Output signature: 1 float at a time<br />
)<br />
<br />
def work(self, input_items, output_items):<br />
output_items[0][:] = input_items[0] * input_items[0] # Only works because numpy.array<br />
return len(output_items[0])</pre><br />
Some things should immediately stick out:<br />
<br />
* The block class is derived from <code>gr.sync_block</code>, just like the C++ version was derived from gr::sync_block<br />
* It has a constructor where the name and input/output signatures are set and a <code>work()</code> function<br />
<br />
However, there are some major differences to the C++ version:<br />
<br />
* The input and output signatures are simply defined as a list. Every element contains the item size of that port. So in this case, there is one port per input and one port per output and each has an item size of <code>numpy.float32</code> (a single-precision float). If you want a port to operate on vectors, define a tuple, e.g. [(numpy.float32, 4), numpy.float32] means there are two ports: The first one is for vectors of 4 floats, the second is for scalar floats.<br />
* When assigning vectors to <code>output_items</code>, remember to use the <code>[:]</code> operator. This makes sure Python doesn't rebind the variables or does something clever but guarantees that the data is properly copied<br />
* <code>input_items</code> and <code>output_items</code> are numpy arrays, which is why we can do the very simple element-wise multiplication the way it's done here (instead of a list comprehension)<br />
* No recompiling is necessary for the <code>make test</code> (faster development cycles, yay!)<br />
<br />
=== Other types of Python blocks ===<br />
<br />
Just like the C++ variant, there are four types of blocks in Python:<br />
<br />
* <code>gr.sync_block</code><br />
* <code>gr.decim_block</code><br />
* <code>gr.interp_block</code><br />
* <code>gr.basic_block</code> - The Python version of <code>gr::block</code><br />
<br />
Like their C++ versions, these blocks have <code>forecast()</code>, <code>work()</code>, and <code>general_work()</code> methods you can override. The difference is, the argument list for the work functions is always as shown in the previous example:<br />
<br />
<pre> def work(self, input_items, output_items):<br />
# Do stuff<br />
<br />
def general_work(self, input_items, output_items):<br />
# Do stuff</pre><br />
The number of input/output items is obtained through <code>len(input_items[PORT_NUM])</code>.<br />
<br />
=== More examples ===<br />
<br />
Check out the QA code for the Python blocks for some good examples:<br />
<br />
* [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/python/blocks/qa_block_gateway.py gr-blocks/python/blocks/qa_block_gateway.py]<br />
<br />
=== Troubleshooting ===<br />
<br />
<code>ValueError: invalid literal for int() with base 10: '...'</code> This occurs in GRC when attempting to drag a block into a flowgraph. It usually means the <code>.yml</code> file cooresponding to the block located in the <code>grc/</code> folder is not filled out. ("..." is a placeholder).</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=WindowsInstall&diff=8425WindowsInstall2021-04-01T23:02:21Z<p>172.18.0.3: Fixed some typos; mainly spelling issues.</p>
<hr />
<div>= Windows Installation =<br />
<br />
== Current Windows Status ==<br />
<br />
Binary installers for 64-bit Windows 7/8/10 are now available [http://www.gcndevelopment.com/gnuradio/index.htm here]. These include all dependencies for Windows, a custom python distro, commonly used SDR drivers, and several OOT blocks.<br />
<br />
Windows binaries are also available through [https://conda-forge.org conda-forge] and the <code>conda</code> package manager. See the [[CondaInstall|conda install guide]] for details and instructions. Although this is not a standalone binary release, installation is relatively straightforward and GNU Radio Companion is provided through a link in the Start Menu. (As of 1 December 2020 this is the most up-to-date binary install option).<br />
<br />
There is also a build available at [https://github.com/pothosware/PothosSDR/wiki/Tutorial] that includes GnuRadio, Pothos, CubicSDK and other tools. This option has historically been updated about once or twice per year.<br />
<br />
Installing core GNU Radio and USRP on Windows is becoming more routine. Many OoT modules may still require compiling locally. Please report any success or failures. Patches and enhancements are especially welcome!<br />
<br />
== Windows Porting Issues ==<br />
<br />
Considerable effort has been put into making the GNU Radio code portable among various operating systems, but there are several reasons why it cannot be &quot;simply&quot; compiled and run under Windows:<br />
<br />
* The build and install procedures are based on Linux scripts and tools<br />
* Several third-party libraries are used, each with its own, often system-dependent, installation procedure<br />
* Most GNU Radio applications must interface to hardware (e.g., a sound card or USRP) which require system-dependent drivers and installation procedures<br />
* Because GNU Radio is written as an extension to Python, there are potential problems on Windows if different runtime libraries are used for GNU Radio and Python<br />
<br />
The following sections show how these issues can be addressed.<br />
<br />
== Installation Options ==<br />
<br />
GNU Radio is designed to be flexible. It has a number of modules, capabilities, and options that can be enabled or disabled to suit the needs of the user, and the user can add custom blocks or modules to the system.<br />
<br />
To support this flexibility, it comes with a set of files and scripts to be used with GNU software build tools (sh, make, autoconf, automake, etc.). These tools use Linux-like commands and filenames that are not normally available on Windows systems.<br />
<br />
Fortunately, we are not the first to face this problem, and several solutions exist. These are presented in order of increasing difficulty:<br />
<br />
=== Building on Windows with Native Tools ===<br />
<br />
Ettus Application note [https://kb.ettus.com/Building_and_Installing_the_USRP_Open_Source_Toolchain_(UHD_and_GNU_Radio)_on_Windows] describes how to build from source. Similar is a post at [https://lists.gnu.org/archive/html/discuss-gnuradio/2016-07/msg00108.html] for OOT modules.<br />
<br />
Powershell scripts are now available at https://www.github.com/gnieboer/gnuradio_windows_build_scripts that fully automate the build process for GNURadio 3.7.9.2+. A few build dependencies are required (MSVC 2015, Git, Doxygen, CMake, Perl, Wix) but all are free. The script has two options:<br />
<br />
# Build all dependencies from source (including python itself)<br />
# Download a prebuilt custom dependency package and then build only GNURadio and a few OOT modules on top.<br />
<br />
The binary installers described above are built with these scripts. They ensure that all dependencies are built with the same toolchain against the same runtime libraries, and handle the patches and configuration &quot;tweaks&quot; needed to build them on Windows.<br /><br />
If option 1 is desired, note that to build scipy, the non-free Intel Fortran compiler is required, gfortran cannot build objects that can link with MSVC C objects. If you do not have said compiler, the scripts will download pre-compiled wheels instead.<br />
<br />
More information on the build process is available on the GitHub repo readme, and also at http://www.gcndevelopment.com/gnuradio.<br />
<br />
GNURadio 3.6 has also been compiled on Windows using native tools as well (see http://voltronics.blogspot.com/2013/01/gnu-radio-windows-build-guide.html and https://lists.gnu.org/archive/html/discuss-gnuradio/2013-08/msg00284.html)<br />
<br />
More helpful tips on dependency version information have been reported:<br /><br />
https://lists.gnu.org/archive/html/discuss-gnuradio/2013-12/msg00497.html<br />
<br />
=== [[MinGW]]/MSYS ===<br />
<br />
[[MinGW]] (http://www.mingw.org/) provides GNU compilers and Window-specific header files for compiling native Windows applications.<br /><br />
MSYS (http://www.mingw.org/msys.shtml) is a companion set of Linux-like commands, shell, and build tools.<br /><br />
[[MinGW]] does not include a Linux programming interface; programs should be smaller and faster than with Cygwin (in theory), but will require more Windows-specific code.<br /><br />
MSYS is intended primarily as a build environment, making it more compact than Cygwin.<br />
<br />
Because there is no Linux API emulation, GNU Radio built with [[MinGW]] should be used with standard Windows versions of Python and the third-party libraries.<br /><br />
[[MinGW]] does not provide as much support as Cygwin for installing third-party libraries, but in many cases precompiled binaries are available.<br />
<br />
For detailed installation instructions using [[MinGW]] and MSYS see [[MingwInstallMain|Installing GNU Radio with MinGW]].<br />
<br />
=== Cygwin ===<br />
<br />
Cygwin (http://www.cygwin.com/) is a Linux-like environment for Windows.<br /><br />
It provides the Linux-like shell, file naming, and build tools we need and also makes it easy to install many of the third-party libraries required by GNU Radio. It also provides a Linux programming interface (API); this is not required by GNU Radio, but it lets us use the better-tested Linux versions of some functions.<br />
<br />
Because the Linux API uses its own C runtime library, it is best to use Cygwin versions of Python and the third-party libraries when building GNU Radio with Cygwin.<br />
<br />
For detailed installation instructions using Cygwin see [[CygwinInstallMain|Installing GNU Radio with Cygwin]].<br />
<br />
=== Chocolatey ===<br />
<br />
To quote from the [|https://chocolatey.org/ Chocolatey homepage]: ''Chocolatey NuGet is a Machine Package Manager, somewhat like apt-get, but built with Windows in mind.''.<br />
<br />
There are packages for gnuradio (and its dependencies) available in a separate repository (currently the best known source is: https://github.com/ariovistus/chocolatey-packages)<br />
<br />
To install, open an '''Administrative''' command line session and run:<br />
<br />
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))<br />
<br />
Now you need to install a ''source'' which has the recipes for gnuradio and dependents. The easiest method is to clone the chocolatey-packages from the github repository listed above (https://github.com/ariovistus/chocolatey-packages), then add the local source from within an Administrative command line session:<br />
<br />
choco source add -name gnuradio -source C:\&lt;path-to&gt;\chocolatey-packages<br />
<br />
Create the numpy package:<br />
<br />
cd <path-to>\chocolatey-package\numpy<br />
cpack<br />
<br />
Create the gnuradio package:<br />
<br />
cd <path-to>\chocolatey-package\gnuradio<br />
cpack<br />
<br />
Now install the gnuradio package:<br />
<br />
choco install gnuradio<br />
<br />
Follow the command prompts.<br />
<br />
=== WSL | Ubuntu === <br />
<br />
Enable WSL from windows features. <br />
<br />
Install Ubuntu from Microsoft Store. <br />
<br />
Using the Ubuntu terminal, install gnuradio as you would on linux [https://wiki.gnuradio.org/index.php/InstallingGR#Ubuntu_PPA_Installation] <br />
<br />
Install additional package "libgtk-3-dev" <br />
<br />
sudo apt install libgtk-3-dev<br />
<br />
Install an X server, either VcXsrv [https://sourceforge.net/projects/vcxsrv/] or Xming [https://sourceforge.net/projects/xming/] as WSL does not come with an X server. VcXsrv is recommended as it is open source and self-contained instead of being tied to Cygwin, whereas Xming "asks for donations" to the developer as a dubious "sale" for non-existent support.<br />
<br />
Launch VcXsrv, making sure to select "Disable access control" option in the Extra settings so that any application can export to X11.<br />
<br />
Edit '''bashrc''' to set up the display by adding the following line at the bottom of the file <br />
# X11 forwarding for Windows<br />
export DISPLAY=$(awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null):0<br />
export LIBGL_ALWAYS_INDIRECT=1<br />
<br />
Restart the Ubuntu terminal and run <br />
gnuradio-companion<br />
<br />
== Known Windows Build Issues ==<br />
<br />
So far, we have workarounds for all reported problems:<br />
<br />
* I got the following error after a clean install "This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.". I fixed this by finding qwindows.dll on my PC (for me it was in C:\Program Files\GNURadio-3.8\bin\platforms\), creating a new directory C:\Program Files\GNURadio-3.8\bin\plugins\platforms, and copying the 4 DLLs to C:\Program Files\GNURadio-3.8\bin\plugins\platforms (I had to create the "....plugins\platforms\" sub-directory). I'm sure there's a more elegant fix, but this seems to work.<br />
<br />
[[Category:Installation]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8424IQ Complex Tutorial2021-04-01T05:49:33Z<p>172.18.0.3: /* Why we need complex and IQ signals */ grammar and spelling</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_1= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For a HIFI audio signal, the maximum audio frequency <math>F_{Max Audio}</math> is closed to 20 kHz, so the sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound cards, 8 kHz is used for mobile phones since voice has a lower frequency range than HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, the maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such a signal is not possible with conventional hardware such as a low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much for the computer can handle (higher than some CPU clocks).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex enveloppe, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since its allow to lower sampling rate as compare to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation help us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal correspond to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex enveloppe is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Enveloppe, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seemed counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex enveloppe is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercice: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100Khz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Simulation_example:_AM_transmitter_and_receiver&diff=8409Simulation example: AM transmitter and receiver2021-03-27T06:06:57Z<p>172.18.0.3: /* What do to next */</p>
<hr />
<div><!-- AM_tutorial.mediawiki --><br />
The first section of this tutorial explains how an Amplitude Modulated (AM) signal can be created. Rather than using any real hardware for transmission, the signal is sent via a socket to the second section of the tutorial which explains how to demodulate the received signal. The only actual hardware involved is the computer's microphone input and speaker output. In the case of a Raspberry Pi computer, which has no microphone input, an alternative is presented.<br />
<br />
This tutorial can be performed with either GNU Radio (GR) version 3.7 or 3.8 (and later). The Graphical User Interface gnuradio-companion (GRC) is used to create a flowgraph for each section.<br />
<br />
== Prerequisites ==<br />
<br />
* [[Guided_Tutorial_GRC|'''Intro to GR usage: GRC and flowgraphs''']]<br />
* [[Sample_Rate_Tutorial|'''Understanding sample rate''']]<br />
* [[FAQ#The_Community:_Where_you_get_help.2C_advice_and_code|'''Where to get help''']]<br />
<br />
== AM transmitter ==<br />
<br />
Using gnuradio-companion (GRC) and the following Block descriptions, build this flowgraph of the transmitter section:<br><br />
<br />
[[File:AM_transmit_fg.png|900px]]<br />
<br />
=== Block descriptions ===<br />
<br />
* The Options block identifies the filename for the flowgraph, a title, author, etc.<br />
** id: AM_transmit<br />
** Click on File -> Save As&nbsp;&nbsp;Use the file name 'AM_transmit'. the extension '.grc' is added automatically<br />
* The microphone input is defined by an Audio Source block. The parameters are:<br />
** Sample rate: set to 48khz (use the pull-down)<br />
** Device name: for most microphone jacks built into the computer, the Device name can be left blank; for other cases, see [[Audio_Source#Device_Name]]<br />
** OK to Block: Yes<br />
* For the remainder of the flowgraph, a sample_rate of 768khz is used. This value was chosen to give the 48khz carrier frequency 16 samples per cycle (48000 x 16 = 768000).<br />
* Variable block<br />
** id: samp_rate<br />
** value: 768000<br />
* To boost the 48khz sample rate of the audio input to the 768khz sample rate, a Repeat block with an Interpolation value of 16 is used.<br />
* The QT GUI Range block defines an Audio gain (volume) control<br />
** id: volume<br />
** default value: 1.2<br />
** start: 0<br />
** stop: 10.0<br />
** step: 0.1<br />
** Widget: choose whatever you like<br />
* The value of the volume control is used as a multiplier in the Multiply Const block.<br />
** Constant: volume<br />
* To create an AM signal, the carrier signal is multiplied by the audio signal plus one. The constant 1 creates the carrier when no audio is present. See [https://en.wikipedia.org/wiki/Amplitude_modulation#Simplified_analysis_of_standard_AM].<br />
** Add Const block<br />
*** Constant: 1.0<br />
* The carrier signal (in this example is 48khz) is generated by the Signal Source block.<br><br />
** Note: there is no correlation between the audio sample rate and the carrier signal frequency.<br />
** Sample Rate: samp_rate<br />
** Frequency: 48000<br />
** Amplitude: 0.5<br />
* The QT GUI Time Sink gives a visual representation of the transmitted signal.<br />
** Number of Points: 4096<br />
** Sample Rate: samp_rate<br />
** Number of Inputs: 2<br />
* For a real radio transmitter, the output of the Add Const block would be fed to a low-pass filter and then to Radio Frequency (RF) hardware. See the example at the end of this tutorial. For now, we are sending the transmit signal to a ZMQ PUB Sink data socket connected to the receiver section.<br />
** Address: tcp://127.0.0.1:50001<br />
<br />
=== Note for Raspberry Pi ===<br />
<br />
Since a Raspberry Pi has no audio input jack, there are two alternatives:<br />
<br />
# use a USB audio dongle as is shown in the flowgraph.<br />
# replace the Audio Source block with a Signal Source block (frequency: 600) and a Throttle block.<br />
<br />
=== Test transmitter section ===<br />
<br />
To test the transmitter, generate and run the flowgraph. Speaking into the microphone should show a change in the pattern on the QT GUI Time Sink. The level of modulation can be adjusted with the volume control.<br />
<br />
== AM receiver ==<br />
<br />
Using gnuradio-companion (GRC) and the following Block descriptions, build this separate flowgraph of the receiver section:<br><br />
<br />
[[File:AM_receive_fg.png|900px]]<br />
<br />
=== Block descriptions ===<br />
<br />
* The Options block identifies the filename for the flowgraph, a title, author, etc.<br />
** id: AM_receive<br />
** Click on File -> Save As&nbsp;&nbsp;Use the file name 'AM_receive'. the extension '.grc' is added automatically<br />
* The signal from the transmitter section is received by the ZMQ SUB Source. It is a 48khz carrier with a sample rate of 768khz.<br />
** Address: tcp://127.0.0.1:50001<br />
* A Variable block defines the sample rate.<br />
** id: samp_rate<br />
** value: 768000<br />
* Another Variable block defines the decimation factor. Since the Audio Sink (speaker) uses a sample rate of 48khz, we will reduce (decimate) the incoming sample rate by a factor of 16.<br />
** id: decim<br />
** value: 16<br />
* The Frequency Xlating FIR Filter performs three functions: frequency translation, filtering, and decimation. See [[Frequency_Xlating_FIR_Filter]] for more detail.<br />
** Type: Float->Complex (Real Taps)<br />
** Decimation: decim<br />
** Taps: firdes.low_pass(1,samp_rate,samp_rate/(2*decim), 2000)<br />
** Center Frequency: 48000<br />
** Sample Rate: samp_rate<br />
* The Automatic Gain Control (AGC) block adjusts the input signal to the given reference level (1.0).<br />
** The default settings should be used.<br />
* The Complex to Mag block calculates the magnitude of the complex samples which produces the original modulation signal.<br />
* The Band Pass Filter filters the signal between the low and high cutoff frequencies.<br />
** FIR Type: Float->Float (Real Taps)(Decim)<br />
** Decimation: 1<br />
** Gain: 1<br />
** Sample Rate: int(samp_rate/decim)<br />
** Low Cutoff Freq: 500<br />
** High Cutoff Freq: 6000<br />
** Transition Width: 400<br />
* The QT GUI Range block defines an Audio gain (volume) control<br />
** id: volume<br />
** default value: 0.3<br />
** start: 0<br />
** stop: 1.0<br />
** step: 0.05<br />
** Widget: choose whatever you like<br />
* The value of the volume control is used as a multiplier in the Multiply Const block.<br />
** Constant: volume<br />
* The QT GUI Time Sink gives a visual representation of the received signal.<br />
** Number of Points: 256<br />
** Sample Rate: int(samp_rate/decim)<br />
** Number of Inputs: 1<br />
* The speaker output is defined by an Audio Sink block. The parameters are:<br />
** Sample rate: set to 48khz (use the pull-down)<br />
** Device name: for most speakers (or headphone jacks) built into the computer, the Device name can be left blank; for other cases, see [[Audio_Sink#Device_Name]]<br />
** OK to Block: Yes<br />
<br />
=== Testing ===<br />
<br />
When using GRC, doing a Generate and/or Run creates a Python file with the same name as the .grc file. You can execute the Python file without running GRC again.<br />
<br />
For testing this system we will use two processes, so we will need two terminal windows.<br />
<br />
Terminal 1:<br />
* since you just finished building the AM_receive flowgraph, you can just do a Run. After a few seconds, a GUI window will open with the Volume control and the GUI Time Sink.<br />
<br />
Terminal 2:<br />
Open another terminal window.<br />
* change to whatever directory you used to generate the flowgraph for AM_transmit<br />
* execute the following command:<br />
python3 -u AM_transmit.py<br />
* After a few seconds, a GUI window will open with the Volume control and the GUI Time Sink.<br />
<br />
Speaking into the microphone should show a change in the pattern on both QT GUI Time Sinks. You should hear your voice from the speakers. The speaker volume can be adjusted with the receive volume control. The level of modulation can be adjusted with the transmit volume control. Note that increasing the transmit volume beyond 100% causes distortion.<br />
<br />
To terminate each of the processes cleanly, click on the 'X' in the upper corner of the GUI rather than using Control-C.<br />
<br />
== What to do next ==<br />
<br />
Now that you have a working system, you can experiment:<br />
* change filter parameters<br />
* replace the Audio Source block with a Signal Source block (frequency: 600) and a Throttle block (if you haven't already done that)<br />
* change the carrier frequency - remember that you have to change Interpolation and Decimation factors too!<br />
* use an AM Demod block instead of a Complex to Mag block. See [[AM_Demod]] for the details.<br />
<b>Note:</b> Any time you make a change to a flowgraph, you must do a Generate to rebuild the Python file.<br />
<br />
An example of a transmitter using SDR hardware is:<br />
<br />
[[File:AM_transmit_2_fg.png|700px]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=OutOfTreeModules&diff=8394OutOfTreeModules2021-03-23T09:36:43Z<p>172.18.0.3: /* Replaced outdated link to spectral estimation toolbox with recent example */</p>
<hr />
<div>[[Category:Guide]]<br />
<br />
'''Extending GNU Radio with own functionality and blocks'''<br />
<br />
This article borrows heavily from the original (but very outdated) &quot;How to write a block?&quot; written by Eric Blossom.<br />
<br />
== What is an out-of-tree module? ==<br />
<br />
An out-of-tree module is a GNU Radio component that does not live within the GNU Radio source tree. Typically, if you want to extend GNU Radio with your own functions and blocks, such a module is what you create (i.e. you wouldn't usually add stuff to the actual GNU Radio source tree unless you're planning to submit it to the devs for upstream integration). This allows you to maintain the code yourself and have additional functionality alongside the main code.<br />
<br />
A lot of OOT projects are hosted at [http://cgran.org CGRAN] -- the Comprehensive GNU Radio Archive Network. CGRAN projects are all available through our tool [http://gnuradio.org/pybombs PyBOMBS]. In fact, when you add your project to the [https://github.com/gnuradio/gr-etcetera PyBOMBS recipe repo], it will automatically update the CGRAN website.<br />
<br />
For example of such a module is the [https://www.cgran.org/14680/ GNU Radio Digital Audio Broadcasting module], which extends GNU Radio with everything needed to get audio from DAB and DAB+. When installed, you have more blocks available (e.g. in the [[GNURadioCompanion|GNU Radio companion]]) which behave just like the rest of GNU Radio; however, the developers are different people.<br />
<br />
== Tools and resources at my disposal ==<br />
<br />
There are a couple of tools, scripts, and documents that are available as 3rd-party programs or as part of GNU Radio.<br />
<br />
=== gr_modtool - The swiss army knife of module editing ===<br />
<br />
When developing a module, there's a lot of boring, monotonous work involved: boilerplate code, makefile editing, etc. gr_modtool is a script which aims to help with all these things by automatically editing makefiles, using templates, and doing as much work as possible for the developer such that you can jump straight into the DSP coding.<br />
<br />
Note that gr_modtool makes a lot of assumptions on what the code looks like. The more your module is custom and has specific changes, the less useful gr_modtool will be, but it is probably the best place to start with any new module or block.<br />
<br />
gr_modtool is now available in the GNU Radio source tree and is installed by default.<br />
<br />
=== Developer resources on the wiki ===<br />
<br />
Most important is definitely the [[BlocksCodingGuide|block coding guide]]. While this is written for the GNU Radio main tree, this should also be applied to all modules. Specifically, have a look at the naming conventions!<br />
<br />
=== CMake, make, etc. ===<br />
<br />
GNU Radio uses CMake as a build system. Building a module, therefore, requires you to have cmake installed, and whatever build manager you prefer (most often this is 'make', but you could also be using Eclipse or MS Visual Studio).<br />
<br />
== Tutorial 1: Creating an out-of-tree module ==<br />
<br />
In the following tutorials, we will use an out-of-tree module called '''howto'''. The first step is to create this module.<br />
<br />
With gr_modtool, this is dead easy. Just point your command line wherever you want your new module directory (this should be outside the GNU Radio source tree!), and go:<br />
<br />
<pre>% gr_modtool newmod howto<br />
Creating out-of-tree module in ./gr-howto... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.</pre><br />
If all went well, you now have a new directory called <code>gr-howto</code> in which we will work for the other tutorials.<br />
<br />
== Structure of a module ==<br />
<br />
Let's jump straight into the gr-howto module and see what it's made up of:<br />
<br />
<pre>gr-howto % ls<br />
apps cmake CMakeLists.txt docs examples grc include lib python swig</pre><br />
It consists of several subdirectories. Anything that will be written in C++ (or C, or any language that is not Python) is put into <code>lib/</code>. For C++ files, we usually have headers which are put into <code>include/</code> (if they are to be exported) or also in <code>lib/</code> (if they're only relevant during compile time, but are not installed later, such as <code>_impl.h</code> files. You'll see what that is in the next tutorial).<br />
<br />
Of course, Python stuff goes into the <code>python/</code> directory. This includes unit tests (which are not installed) and parts of the Python module which are installed.<br />
<br />
You probably know already that GNU Radio blocks are available in Python even if they were written in C++. This is done by the help of SWIG, the simplified wrapper and interface generator, which automatically creates glue code to make this possible. SWIG needs some instructions on how to do this, which are put into the <code>swig/</code> subdirectory. Unless doing something extra clever with your block, you will not need to go into the <code>swig/</code> directory; gr_modtool handles all of that for us.<br />
<br />
If you want your blocks to be available in the [[GNURadioCompanion|GNU Radio companion]], the graphical UI for GNU Radio, you need to add descriptions of the blocks and put them into <code>grc/</code>. Prior to version 3.8 these descriptions were XML files, but from 3.8 onward they use [[YAML_GRC|YAML instead]].<br />
<br />
For documentation, <code>docs/</code> contains some instructions on how to extract documentation from the C++ files and Python files (we use Doxygen and Sphinx for this) and also make sure they're available as docstrings in Python. Of course, you can add custom documentation here as well.<br />
<br />
The <code>apps/</code> subdir contains any complete applications (both for GRC and standalone executables) which are installed to the system alongside with the blocks.<br />
<br />
The directory, <code>examples/</code> can be used to save (guess what) examples, which are a great addendum to documentation because other developers can simply look straight at the code to see how your blocks are used.<br />
<br />
The build system brings some baggage along, as well: the <code>CMakeLists.txt</code> file (one of which is present in every subdirectory) and the <code>cmake/</code> folder. You can ignore the latter for now, as it brings along mainly instructions for CMake on how to find GNU Radio libraries etc. The CMakeLists.txt files need to be edited a lot in order to make sure your module builds correctly.<br />
<br />
But one step at a time! Now, let's move on to our next tutorial.<br />
<br />
== Tutorial 2: Writing a block (square_ff) in C++ ==<br />
<br />
For our first example, we'll create a block that computes the square of its single float input. This block will accept a single float input stream and produce a single float output stream, i.e., for every incoming float item, we output one float item which is the square of that input item.<br />
<br />
Following the naming conventions, the block will be called <code>square_ff</code> because it has float inputs, float outputs.<br />
<br />
We are going to arrange that this block, as well as the others that we write in this article, end up in the <code>howto</code> Python module. This will allow us to access it from Python like this:<br />
<br />
<pre>import howto<br />
sqr = howto.square_ff()</pre><br />
=== Creating the files ===<br />
<br />
First step is to create empty files for the block and edit the CMakeLists.txt files.<br /><br />
Again, <code>gr_modtool</code> does the job. On the command line, go to the <code>gr-howto</code> directory and enter:<br />
<br />
<pre>gr-howto % gr_modtool add -t general -l cpp square_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] <br />
Add C++ QA code? [y/N] <br />
Adding file 'square_ff_impl.h'...<br />
Adding file 'square_ff_impl.cc'...<br />
Adding file 'square_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'qa_square_ff.py'...<br />
Editing python/CMakeLists.txt...<br />
Adding file 'howto_square_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
On the command line, we specify that we're adding a block, its type is 'general' (because we don't know what block types are, yet) and it is called <code>square_ff</code>. The block should be created in C++ and it currently has no specified copyright holder (by default, gr-module author is the copyright holder). <code>gr_modtool</code> then asks you if your block takes any arguments (it doesn't, so we leave that empty), whether or not we want QA code for Python (yes, we do) and for C++ (no, we don't right now).<br />
<br />
Now, have another look at the different CMakeLists.txt files and see what <code>gr_modtool</code> did. You can also see a lot of new files, which now have to be edited if we want the block to work.<br />
<br />
=== Test Driven Programming ===<br />
<br />
We could just start banging out the C++ code, but being highly evolved modern programmers, we're going to write the test code first. After all, we do have a good spec for the behavior: take a single stream of floats as the input and produce a single stream of floats as the output. The output should be the square of the input.<br />
<br />
How hard could this be? Turns out that this is easy! So, we open <code>python/qa_square_ff.py</code>, which we edit to look like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig as howto<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_square_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr)<br />
self.tb.connect(sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_square_ff, &quot;qa_square_ff.xml&quot;)</pre><br />
gr_unittest is an extension to the standard Python module unittest. gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. Unittest uses Python's reflection mechanism to find all methods that start with test_ and runs them. Unittest wraps each call to test_* with matching calls to setUp and tearDown. See the [http://docs.python.org/2/library/unittest.html Python unittest documentation] for details.<br />
<br />
When we run the test, gr_unittest.main is going to invoke setUp, test_001_square_ff, and tearDown, in that order.<br />
<br />
<code>test_001_square_ff</code> builds a small graph that contains three nodes. <code>blocks.vector_source_f(src_data)</code> will source the elements of <code>src_data</code> and then say that it's finished. <code>howto.square_ff</code> is the block we're testing. <code>blocks.vector_sink_f</code> gathers the output of <code>howto.square_ff</code>.<br />
<br />
The <code>run()</code> method runs the graph until all the blocks indicate they are finished. Finally, we check that the result of executing <code>square_ff</code> on <code>src_data</code> matches what we expect.<br />
<br />
Note that such a test is usually called before installing the module. This means that we need some trickery to be able to load the blocks when testing. CMake takes care of most things by changing PYTHONPATH appropriately. Also, we import <code>howto_swig</code> instead of <code>howto</code> in this file.<br />
<br />
In order for CMake to actually know this test exists, <code>gr_modtool</code> modified <code>python/CMakeLists.txt</code> with these lines:<br />
<br />
<pre>########################################################################<br />
# Handle the unit tests<br />
########################################################################<br />
include(GrTest)<br />
<br />
set(GR_TEST_TARGET_DEPS gnuradio-howto)<br />
set(GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/swig)<br />
GR_ADD_TEST(qa_square_ff ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/qa_square_ff.py)</pre><br />
=== The C++ code (part 1) ===<br />
<br />
Now that we've got a test case, let's write the C++ code. All signal processing blocks are derived from <code>gr::block</code> or one of its subclasses. Go check out the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html block documentation] on the Doxygen-generated manual.<br />
<br />
<code>gr_modtool</code> already provided us with three files that define the block: <code>lib/square_ff_impl.h</code>, <code>lib/square_ff_impl.cc</code> and <code>include/howto/square_ff.h</code>. All we have to do is modify them to do our bidding. After you've finished with this tutorial ''please'' read and understand the [https://wiki.gnuradio.org/index.php/BlocksCodingGuide Blocks Coding Guide] to find out how these files are structured and why!<br />
<br />
First of all, we have a look at our header files. Because the block we're writing is so simple, we don't have to actually change them (the header file in <code>include/</code> is often quite complete after running <code>gr_modtool</code>, unless we need to add some public methods such as mutator methods, i.e., getters and setters). That leaves us with <code>lib/square_ff_impl.cc</code>.<br />
<br />
<code>gr_modtool</code> hints at where you have to change code by adding <code>&lt;++&gt;</code> symbols.<br /><br />
Let's go through these one at a time:<br />
<br />
<pre> square_ff_impl::square_ff_impl()<br />
: gr::block(&quot;square_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)), // input signature<br />
gr::io_signature::make(1, 1, sizeof (float))) // output signature<br />
{<br />
// empty constructor<br />
}</pre><br />
The constructor itself is empty, as the squaring block has no need to set up anything.<br />
<br />
The only interesting portion is the definition of the input and output signatures: At the input, we have 1 port that allows float inputs. The output port is the same.<br />
<br />
<pre> void<br />
square_ff_impl::forecast (int noutput_items, gr_vector_int &amp;ninput_items_required)<br />
{<br />
ninput_items_required[0] = noutput_items;<br />
}</pre><br />
<code>forecast()</code> is a function which tells the scheduler how many input items are required to produce <code>noutput_items</code> output items. In this case, they're the same. The index 0 indicates that this is for the first port, but we only have one any way. This is generally the case for <code>forecast</code> in a lot of blocks. For examples, you can look at how <code>gr::block</code>, <code>gr::sync_block</code>, <code>gr::sync_decimator</code>, and <code>gr::sync_interpolator</code> define the default forecast functions to account for things like rate changes and history.<br />
<br />
Finally, there's <code>general_work()</code>, which is pure virtual in <code>gr::block</code>, so we definitely need to override that. <code>general_work()</code> is the method that does the actual signal processing:<br />
<br />
<pre> int<br />
square_ff_impl::general_work (int noutput_items,<br />
gr_vector_int &amp;ninput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many input items we consumed on<br />
// each input stream.<br />
consume_each (noutput_items);<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
There is one pointer to the input- and one pointer to the output buffer, respectively, and a for-loop which copies the square of the input buffer to the output buffer.<br />
<br />
=== Using CMake ===<br />
<br />
If you've never used CMake before, this is a good time to give it a try. The typical workflow of a CMake-based project as seen from the command line is this (if using PyBOMBS, first read '''Build Tree vs. Install Tree'''):<br />
<br />
<pre>$ mkdir build # We're currently in the module's top directory<br />
$ cd build/<br />
$ cmake ../ # Tell CMake that all its config files are one dir up<br />
$ make # And start building (should work after the previous section)</pre><br />
===== Build Tree vs. Install Tree =====<br />
<br />
When you run cmake, you usually run it in a separate directory (e.g. <code>build/</code>). This is the build tree. The path to the install tree is <code>$prefix/lib/$pythonversion/dist-packages</code>, where <code>$prefix</code> is whatever you specified to CMake during configuration (usually <code>/usr/local/</code>) with the <code>-DCMAKE_INSTALL_PREFIX</code> switch. (Note: different versions of Python will either use site-packages or dist-packages; dist-packages is the newer way and most likely for newer OSes and installations.)<br />
<br />
If you installed GNU Radio using PyBOMBS, the install tree is located in the <code>target/</code> directory set during the initial PyBOMBS configuration. Make sure to add the -DCMAKE_INSTALL_PREFIX switch for CMake, so that it will correctly locate your GNU Radio installation. The command should look similar to this:<br />
<br />
<pre>$ cmake -DCMAKE_INSTALL_PREFIX=~/prefix-3.8 ../ # should be the configured PyBOMBS target</pre><br />
Now we have a new directory <code>build/</code> in our module's directory. All the compiling etc. is done in here, so the actual source tree is not littered with temporary files. If we change any CMakeLists.txt files, we should re-run <code>cmake ../</code> (although in truth, cmake detects these changes and reruns automatically when you next run <code>make</code>). During compilation, the libraries are copied into the build tree. Only during installation, files are installed to the install tree, thus making our blocks available to GNU Radio apps.<br />
<br />
We write our applications such that they access the code and libraries in the install tree. On the other hand, we want our test code to run on the build tree, where we can detect problems before installation.<br />
<br />
=== Let's try that -- running <code>make test</code> ===<br />
<br />
Because we wrote the QA code before the C++ code, we can immediately see if what we did was correct.<br />
<br />
We use <code>make test</code> to run our tests (run this from the <code>build/</code> subdirectory, after calling <code>cmake</code> and <code>make</code>). This invokes a shell script which sets up the PYTHONPATH environment variable so that our tests use the build tree versions of our code and libraries. It then runs all files which have names of the form qa_*.py and reports the overall success or failure.<br />
<br />
There is quite a bit of behind-the-scenes action required to use the non-installed versions of our code (look at the <code>cmake/</code> directory for a cheap thrill.)<br />
<br />
If you completed the <code>square_ff</code> block, this should work fine:<br />
<br />
<pre>gr-howto/build % make test<br />
Running tests...<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.01 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff ..................... Passed 0.38 sec<br />
<br />
100% tests passed, 0 tests failed out of 2<br />
<br />
Total Test time (real) = 0.39 sec</pre><br />
If something fails during the tests, we can dig a little deeper. When we run <code>make test</code>, we're actually invoking the CMake program <code>ctest</code>, which has a number of options we can pass to it for more detailed information. Say we forgot to multiply <code>in[i] * in[i]</code> and so aren't actually squaring the signal. If we just run <code>make test</code> or even just <code>ctest</code>, we would get this:<br />
<br />
<pre>gr-howto/build $ ctest<br />
Test project /home/braun/tmp/gr-howto/build<br />
Start 1: test_howto<br />
1/2 Test #1: test_howto ....................... Passed 0.02 sec<br />
Start 2: qa_square_ff<br />
2/2 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
50% tests passed, 1 tests failed out of 2<br />
<br />
Total Test time (real) = 0.23 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
To find out what happened with our qa_square_ff test, we run <code>ctest -V -R square</code>. The '-V' flag gives us verbose output and the '-R' flag is a regex expression to only run those tests which match.<br />
<br />
<pre>gr-howto/build $ ctest -V -R square<br />
UpdateCTestConfiguration from :/home/braun/tmp/gr-howto/build/DartConfiguration.tcl<br />
UpdateCTestConfiguration from :/home/bruan/tmp/gr-howto/build/DartConfiguration.tcl<br />
Test project /home/braun/tmp/gr-howto/build<br />
Constructing a list of tests<br />
Done constructing a list of tests<br />
Checking test dependency graph...<br />
Checking test dependency graph end<br />
test 2<br />
Start 2: qa_square_ff<br />
<br />
2: Test command: /bin/sh &quot;/home/bruan/tmp/gr-howto/build/python/qa_square_ff_test.sh&quot;<br />
2: Test timeout computed to be: 9.99988e+06<br />
2: F<br />
2: ======================================================================<br />
2: FAIL: test_001_t (__main__.qa_square_ff)<br />
2: ----------------------------------------------------------------------<br />
2: Traceback (most recent call last):<br />
2: File &quot;/home/braun/tmp/gr-howto/python/qa_square_ff.py&quot;, line 44, in test_001_t<br />
2: self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)<br />
2: File &quot;/opt/gr/lib/python2.7/dist-packages/gnuradio/gr_unittest.py&quot;, line 90, in assertFloatTuplesAlmostEqual<br />
2: self.assertAlmostEqual (a[i], b[i], places, msg)<br />
2: AssertionError: 9 != -3.0 within 6 places<br />
2: <br />
2: ----------------------------------------------------------------------<br />
2: Ran 1 test in 0.002s<br />
2: <br />
2: FAILED (failures=1)<br />
1/1 Test #2: qa_square_ff .....................***Failed 0.21 sec<br />
<br />
0% tests passed, 1 tests failed out of 1<br />
<br />
Total Test time (real) = 0.21 sec<br />
<br />
The following tests FAILED:<br />
2 - qa_square_ff (Failed)<br />
Errors while running CTest</pre><br />
This tells us that &quot;9 != -3.0&quot; because we expected the output to be (-3)^2 = 9 but really got the input of -3. We can use this information to go back and fix our block until the tests pass.<br />
<br />
We can also put in debug print statements into our QA code on failures, like printing out <code>expected_result</code> and <code>result_data</code> to compare them to better understand the problem.<br />
<br />
=== More C++ code (but better) - Subclasses for common patterns ===<br />
<br />
<code>gr::block</code> allows tremendous flexibility with regard to the consumption of input streams and the production of output streams. Adroit use of <code>forecast()</code> and <code>consume()</code> (see below) allows variable rate blocks to be built. It is possible to construct blocks that consume data at different rates on each input and produce output at a rate that is a function of the contents of the input data.<br />
<br />
On the other hand, it is very common for signal processing blocks to have a fixed relationship between the input rate and the output rate. Many are 1:1, while others have 1:N or N:1 relationships. You must have thought the same thing in the <code>general_work()</code> function of the previous block: if the number of items consumed is identical the number of items produced, why do I have to tell GNU Radio the exact same number twice?<br />
<br />
Another common requirement is the need to examine more than one input sample to produce a single output sample. This is orthogonal to the relationship between input and output rate. For example, a non-decimating, non-interpolating FIR filter needs to examine N input samples for each output sample it produces, where N is the number of taps in the filter. However, it only consumes a single input sample to produce a single output. We call this concept &quot;history&quot;, but you could also think of it as &quot;look-ahead&quot;.<br />
<br />
* <code>gr::sync_block</code><br />
<br />
gr::sync_block is derived from gr::block and implements a 1:1 block with optional history. Given that we know the input to output rate, certain simplifications are possible. From the implementor's point-of-view, the primary change is that we define a <code>work()</code> method instead of <code>general_work()</code>. <code>work()</code> has a slightly different calling sequence; it omits the unnecessary <code>ninput_items</code> parameter, and arranges for <code>consume_each()</code> to be called on our behalf.<br />
<br />
Let's add another block which derives from <code>gr::sync_block</code> and call it <code>square2_ff</code>. First, we edit <code>qa_square_ff.py</code> to add another test:<br />
<br />
<pre> def test_002_square2_ff(self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f(src_data)<br />
sqr = howto.square2_ff()<br />
dst = blocks.vector_sink_f()<br />
self.tb.connect(src, sqr, dst)<br />
self.tb.run()<br />
result_data = dst.data()<br />
self.assertFloatTuplesAlmostEqual(expected_result, result_data, 6)</pre><br />
You can see it's the exact same test as before except for the use of <code>square2_ff</code>.<br />
<br />
Then, we use <code>gr_modtool</code> to add the block files, skipping the QA code (because we already have that):<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l cpp square2_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square2_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Add C++ QA code? [Y/n] n<br />
Adding file 'square2_ff_impl.h'...<br />
Adding file 'square2_ff_impl.cc'...<br />
Adding file 'square2_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_square2_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
The constructor in <code>square2_ff_impl.cc</code> is done the same way as before, except for the parent class being <code>gr::sync_block</code>.<br />
<br />
<pre> square2_ff_impl::square2_ff_impl()<br />
: gr::sync_block(&quot;square2_ff&quot;,<br />
gr::io_signature::make(1, 1, sizeof (float)),<br />
gr::io_signature::make(1, 1, sizeof (float)))<br />
{}<br />
<br />
// [...] skip some lines ...<br />
<br />
int<br />
square2_ff_impl::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
for(int i = 0; i &lt; noutput_items; i++) {<br />
out[i] = in[i] * in[i];<br />
}<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
The <code>work</code> function is the real difference (also, we don't have a <code>forecast()</code> function any more). We'll look at it in greater detail in the next section.<br />
<br />
This gives us fewer things to worry about and less code to write. If the block requires history greater than 1, call <code>set_history()</code> in the constructor or any time the requirement changes.<br />
<br />
<code>gr::sync_block</code> provides a version of <code>forecast</code> that handles the history requirement.<br />
<br />
* <code>gr::sync_decimator</code><br />
<br />
<code>gr::sync_decimator</code> is derived from <code>gr::sync_block</code> and implements a N:1 block with optional history.<br />
<br />
* <code>gr::sync_interpolator</code><br />
<br />
<code>gr::sync_interpolator</code> is derived from <code>gr::sync_block</code> and implements a 1:N block with optional history.<br />
<br />
With this knowledge it should be clear that <code>howto_square_ff</code> should be a <code>gr::sync_block</code> with no history.<br />
<br />
Now, go back into our build directory and run <code>make</code>. Because <code>gr_modtool</code> added the <code>square2_ff</code> block to the necessary CMakeLists.txt files, <code>cmake</code> is automatically rerun for us and followed by <code>make</code>.<br />
<br />
Again, running <code>make test</code> will spawn a test run with of <code>qa_square_ff.py</code> which should not fail.<br />
<br />
=== Inside the <code>work()</code> function ===<br />
<br />
If you're using a sync block (including decimator and interpolator), this is how the skeleton code looks like produced by gr_modtool:<br />
<br />
<pre> int<br />
my_block_name::work(int noutput_items,<br />
gr_vector_const_void_star &amp;input_items,<br />
gr_vector_void_star &amp;output_items)<br />
{<br />
const float *in = (const float *) input_items[0];<br />
float *out = (float *) output_items[0];<br />
<br />
// Do &lt;+signal processing+&gt;<br />
<br />
// Tell runtime system how many output items we produced.<br />
return noutput_items;<br />
}</pre><br />
So, given history, vectors, multiple input ports etc., is this really all you need? Yes, it is! Because sync blocks have a fixed output to input rate, all you need to know is the number of output items, and you can calculate how many input items are available.<br />
<br />
Example - the adder block: source:gr-blocks/lib/add_XX_impl.cc.t<br />
<br />
This block has an unknown number of inputs and variable vector lengths. The number of connected ports can be checked by <code>input_items.size()</code> and <code>output_items.size()</code>. The outer <code>for</code> loop, which goes over all the available items, goes up to <code>noutput_items*d_vlen</code>. The number of output items is identical to the number of input items because it is a sync block, and you can trust GNU Radio to have this number of items available. In this case, one item is a vector of samples, but we want to add the individual samples, so the for loop considers that.<br />
<br />
Example - interpolation in gr::blocks::unpack_k_bits_bb: source:gr-blocks/lib/unpack_k_bits_bb_impl.cc [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/lib/unpack_k_bits_bb_impl.cc source]<br />
<br />
This is a block which picks apart bytes and produces the individual bits. Again, it is unknown at compile time how many bits per byte there are. However, there's a fixed number of output items per input item, so we can simply divide <code>noutput_items/d_k</code> to get the correct number of input items. It will always be correct because GNU Radio knows the input to output ratio and will make sure that <code>noutput_items</code> is always a multiple of this integer ratio.<br />
<br />
Example - history in source:gr-digital/lib/diff_phasor_cc_impl.cc<br />
<br />
If you use history of length k, GNU Radio will keep k-1 entries of the input buffer instead of discarding them. This means that if GNU Radio tells you the input buffer has N items, it actually has N+k-1 items you may use.<br />
<br />
Consider the example above. We need one previous item, so history is set to k=2. If you inspect the for loop closely, you'll find that out of <code>noutput_items</code> items, <code>noutput_items+1</code> items are actually read. This is possible because there is an extra item in the input buffer from the history.<br />
<br />
After consuming <code>noutput_items</code> items, the last entry is not discarded and will be available for the next call of <code>work()</code>.<br />
<br />
=== Help! My test fails! ===<br />
<br />
Congratulations! If your test fails, your QA code has already paid for itself. Obviously, you want to fix everything before you continue.<br />
<br />
You can use the command <code>ctest -V</code> (instead of <code>make test</code>, again, all in your <code>build/</code> subdirectory) to get all the output from the tests. You can also use <code>ctest -V -R REGEX</code> to only run tests that match REGEX, if you have many tests and want to narrow it down. If you can't figure out the problem from the output of your QA code, put in <code>print</code> statements and show intermediary results.<br />
<br />
=== Making your blocks available in GRC ===<br />
<br />
You can now install your module, but it will not be available in GRC. That's because <code>gr_modtool</code> can't create valid XML files before you've even written a block. The XML code generated when you call <code>gr_modtool add</code> is just some skeleton code.<br />
<br />
Once you've finished writing the block, <code>gr_modtool</code> has a function to help you create the XML code for you. For the howto example, you can invoke it on the <code>square2_ff</code> block by calling<br />
For GNURadio ver >= 3.8:<br />
<pre>gr-howto % gr_modtool makeyaml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
<br />
For GNURadio ver. < 3.8:<br />
<pre>gr-howto % gr_modtool makexml square2_ff<br />
GNU Radio module name identified: howto<br />
Warning: This is an experimental feature. Don't expect any magic.<br />
Searching for matching files in lib/:<br />
Making GRC bindings for lib/square2_ff_impl.cc...<br />
Overwrite existing GRC file? [y/N] y</pre><br />
Note that <code>gr_modtool add</code> creates an invalid GRC file, so we can overwrite that.<br />
<br />
In most cases, <code>gr_modtool</code> can't figure out all the parameters by itself and you will have to edit the appropriate XML file by hand. The [[GNURadioCompanion|GRC]] wiki site has a description available.<br />
<br />
In this case, because the block is so simple, the XML is actually valid. Have a look at <code>grc/howto_square2_ff.xml</code>:<br />
<br />
<pre><br />
<block> <br />
<name>Square ff</name><br />
<key>howto_square_ff</key><br />
<category>[HOWTO]</category><br />
<import>import howto</import><br />
<make>howto.square_ff()</make><br />
<sink><br />
<name>in</name><br />
<type>float</type><br />
</sink><br />
<source><br />
<name>out</name><br />
<type>float</type><br />
</source><br />
</block><br />
</pre><br />
Perhaps you want to change the autogenerated name to something nicer.<br />
<br />
<b>Note:</b> The category name <b>must</b> be enclosed in square brackets to work!<br />
<br />
If you do a <code>make install</code> from the build directory, you can use the block in GRC. If GRC is already running, you can hit the &quot;Reload Blocks&quot; button in the GRC toolbar; it's a blue circular arrow on the right-hand side. You should now see a &quot;HOWTO&quot; category in the block tree.<br />
<br />
=== There's more: additional <code>gr::block</code>-methods ===<br />
<br />
If you've read the [http://gnuradio.org/doc/doxygen/classgr_1_1block.html gr::block documentation] (which you should have), you'll have noticed there are a great number of methods available to configure your block.<br />
<br />
Here's some of the more important ones:<br />
<br />
==== <code>set_history()</code> ====<br />
<br />
If your block needs a history (i.e., something like an FIR filter), call this in the constructor. GNU Radio then makes sure you have the given number of 'old' items available.<br />
<br />
The smallest history you can have is 1, i.e., for every output item, you need 1 input item. If you choose a larger value, N, this means your output item is calculated from the current input item and from the N-1 previous input items.<br />
<br />
The scheduler takes care of this for you. If you set the history to length N, the first N items in the input buffer include the N-1 previous ones (even though you've already consumed them).<br />
<br />
==== <code>forecast()</code> ====<br />
<br />
The system needs to know how much data is required to ensure validity in each of the input arrays. As stated before, the <code>forecast()</code> method provides this information, and you must therefore override it anytime you write a <code>gr::block</code> derivative (for sync blocks, this is implicit).<br />
<br />
The default implementation of <code>forecast()</code> says there is a 1:1 relationship between <code>noutput_items</code> and the requirements for each input stream. The size of the items is defined by <code>gr::io_signature::make</code> in the constructor of <code>gr::block</code>. The sizes of the input and output items can of course differ; this still qualifies as a 1:1 relationship. Of course, if you had this relationship, you wouldn't want to use a <code>gr::block</code>!<br />
<br />
<pre> // default implementation: 1:1<br />
void<br />
gr::block::forecast(int noutput_items,<br />
gr_vector_int &amp;ninput_items_required)<br />
{<br />
unsigned ninputs = ninput_items_required.size ();<br />
for(unsigned i = 0; i &lt; ninputs; i++)<br />
ninput_items_required[i] = noutput_items;<br />
}</pre><br />
Although the 1:1 implementation worked for <code>square_ff</code>, it wouldn't be appropriate for interpolators, decimators, or blocks with a more complicated relationship between <code>noutput_items</code> and the input requirements. That said, by deriving your classes from gr::sync_block, gr::sync_interpolator or gr::sync_decimator instead of gr::block, you can often avoid implementing forecast.<br />
<br />
==== <code>set_output_multiple()</code> ====<br />
<br />
When implementing your <code>general_work()</code> routine, it's occasionally convenient to have the run time system ensure that you are only asked to produce a number of output items that is a multiple of some particular value. This might occur if your algorithm naturally applies to a fixed sized block of data. Call <code>set_output_multiple</code> in your constructor to specify this requirement. The default output multiple is 1.<br />
<br />
=== Finalizing your work and installing ===<br />
<br />
First, go through this checklist:<br />
<br />
* Have you written one or more blocks, including QA codes?<br />
* Does <code>make test</code> pass?<br />
* Are there GRC bindings available (if that's what you want)?<br />
<br />
In that case, you can go ahead and install your module. On a Linux machine, this would mean going back to the build directory and calling <code>make install</code>:<br />
<br />
<pre>$ cd build/<br />
$ make install # or sudo make install</pre><br />
With Ubuntu, you may have to call <code>ldconfig</code> as well:<br />
<br />
<pre>$ sudo ldconfig</pre><br />
Otherwise, you'll get an error message that the library you just installed cannot be found.<br />
<br />
== Other types of blocks ==<br />
<br />
=== Sources and sinks ===<br />
<br />
Sources and sinks are derived from <code>gr::sync_block</code>. The only thing different about them is that sources have no inputs and sinks have no outputs. This is reflected in the <code>gr::io_signature::make</code> that are passed to the <code>gr::sync_block</code> constructor. Take a look at [source:gr-blocks/lib/file_source_impl.cc file_source.{h,cc}] and file_sink_impl.{h,cc} for some very straight-forward examples.<br />
<br />
=== Hierarchical blocks ===<br />
<br />
<code>gr_modtool</code> supports skeleton code for hierarchical blocks both in Python and C''++.<br />
<br />
<pre>~/gr-howto % gr_modtool.py add -t hier -l cpp hierblockcpp_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: hierblockcpp_ff<br />
Language: C++<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments:<br />
Add Python QA code? [Y/n]<br />
Add C++ QA code? [y/N]<br />
Adding file 'hierblockcpp_ff_impl.h'...<br />
Adding file 'hierblockcpp_ff_impl.cc'...<br />
Adding file 'hierblockcpp_ff.h'...<br />
Editing swig/howto_swig.i...<br />
Adding file 'howto_hierblockcpp_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Using the <code>-l python</code> switch creates such a block in Python.<br />
<br />
== Everything at one glance: Cheat sheet for editing modules/components: ==<br />
<br />
Here's a quick list for all the steps necessary to build blocks and out-of-tree modules:<br />
<br />
# Create (do this once per module): <code>gr_modtool create MODULENAME</code><br />
# Add a block to the module: <code>gr_modtool add BLOCKNAME</code><br />
# Create a build directory: <code>mkdir build/</code><br />
# Invoke the make process: <code>cd build &amp;&amp; cmake &lt;OPTIONS&gt; ../ &amp;&amp; make</code> (Note that you only have to call cmake if you've changed the CMake files)<br />
# Invoke the testing: <code>make test</code> or <code>ctest</code> or <code>ctest -V</code> for more verbosity<br />
# Install (only when everything works and no tests fail): <code>sudo make install</code>. Remember to call <code>gr_modtool makexml BLOCKNAME</code> or <code>gr_modtool makeyaml BLOCKNAME</code>, to generate and if needed modify yaml file for your blocks before installing the module.<br />
# Ubuntu users: reload the libs: <code>sudo ldconfig</code><br />
# Delete blocks from the source tree: <code>gr_modtool rm REGEX</code><br />
# Disable blocks by removing them from the CMake files: <code>gr_modtool disable REGEX</code><br />
<br />
== Tutorial 3: Writing a signal processing block in Python ==<br />
<br />
'''Note:''' Writing signal processing blocks in Python comes with a performance penalty. The most common cause for using Python to write blocks is because you want to quickly prototype something without having to argue with C++.<br />
<br />
From the previous tutorials, you already know about blocks and how they work. Lets go through things a bit quicker, and code another squaring block in pure Python, which shall be called <code>square3_ff()</code>.<br />
<br />
=== Adding the test case ===<br />
<br />
So, first of all, we add another test case by editing <code>qa_square_ff.py</code>. Leaving out the test cases for the other two blocks, the QA file now looks like this:<br />
<br />
<pre>from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import howto_swig<br />
from square3_ff import square3_ff<br />
<br />
class qa_square_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
# [...] Skipped the other test cases<br />
<br />
def test_003_square3_ff (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (9, 16, 30.25, 4, 9)<br />
src = blocks.vector_source_f (src_data)<br />
sqr = square3_ff ()<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, sqr)<br />
self.tb.connect (sqr, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.main ()</pre><br />
The actual test case looks '''exactly''' like the previous ones did, only replacing the block definition with <code>square3_ff()</code>. The only other difference is in the import statements: We are now importing a module called <code>square3_ff</code> from which we pull the new block.<br />
<br />
=== Adding the block code ===<br />
<br />
Having put the unit test in place, we add a file called <code>square3_ff.py</code> into the <code>python/</code> directory using <code>gr_modtool</code>:<br />
<br />
<pre>gr-howto % gr_modtool add -t sync -l python square3_ff<br />
GNU Radio module name identified: howto<br />
Block/code identifier: square3_ff<br />
Language: Python<br />
Please specify the copyright holder:<br />
Enter valid argument list, including default arguments: <br />
Add Python QA code? [Y/n] n<br />
Adding file 'square3_ff.py'...<br />
Adding file 'howto_square3_ff.xml'...<br />
Editing grc/CMakeLists.txt...</pre><br />
Remember not to add any QA files as we're using the existing one. Next, edit the new file <code>python/square3_ff.py</code>. It should look a bit like this:<br />
<br />
<pre>import numpy<br />
from gnuradio import gr<br />
<br />
class square3_ff(gr.sync_block):<br />
&quot; Squaring block &quot;<br />
def __init__(self):<br />
gr.sync_block.__init__(<br />
self,<br />
name = &quot;square3_ff&quot;,<br />
in_sig = [numpy.float32], # Input signature: 1 float at a time<br />
out_sig = [numpy.float32], # Output signature: 1 float at a time<br />
)<br />
<br />
def work(self, input_items, output_items):<br />
output_items[0][:] = input_items[0] * input_items[0] # Only works because numpy.array<br />
return len(output_items[0])</pre><br />
Some things should immediately stick out:<br />
<br />
* The block class is derived from <code>gr.sync_block</code>, just like the C++ version was derived from gr::sync_block<br />
* It has a constructor where the name and input/output signatures are set and a <code>work()</code> function<br />
<br />
However, there are some major differences to the C++ version:<br />
<br />
* The input and output signatures are simply defined as a list. Every element contains the item size of that port. So in this case, there is one port per input and one port per output and each has an item size of <code>numpy.float32</code> (a single-precision float). If you want a port to operate on vectors, define a tuple, e.g. [(numpy.float32, 4), numpy.float32] means there are two ports: The first one is for vectors of 4 floats, the second is for scalar floats.<br />
* When assigning vectors to <code>output_items</code>, remember to use the <code>[:]</code> operator. This makes sure Python doesn't rebind the variables or does something clever but guarantees that the data is properly copied<br />
* <code>input_items</code> and <code>output_items</code> are numpy arrays, which is why we can do the very simple element-wise multiplication the way it's done here (instead of a list comprehension)<br />
* No recompiling is necessary for the <code>make test</code> (faster development cycles, yay!)<br />
<br />
=== Other types of Python blocks ===<br />
<br />
Just like the C++ variant, there are four types of blocks in Python:<br />
<br />
* <code>gr.sync_block</code><br />
* <code>gr.decim_block</code><br />
* <code>gr.interp_block</code><br />
* <code>gr.basic_block</code> - The Python version of <code>gr::block</code><br />
<br />
Like their C++ versions, these blocks have <code>forecast()</code>, <code>work()</code>, and <code>general_work()</code> methods you can override. The difference is, the argument list for the work functions is always as shown in the previous example:<br />
<br />
<pre> def work(self, input_items, output_items):<br />
# Do stuff<br />
<br />
def general_work(self, input_items, output_items):<br />
# Do stuff</pre><br />
The number of input/output items is obtained through <code>len(input_items[PORT_NUM])</code>.<br />
<br />
=== More examples ===<br />
<br />
Check out the QA code for the Python blocks for some good examples:<br />
<br />
* [https://github.com/gnuradio/gnuradio/blob/master/gr-blocks/python/blocks/qa_block_gateway.py gr-blocks/python/blocks/qa_block_gateway.py]<br />
<br />
=== Troubleshooting ===<br />
<br />
<code>ValueError: invalid literal for int() with base 10: '...'</code> This occurs in GRC when attempting to drag a block into a flowgraph. It usually means the <code>.yml</code> file cooresponding to the block located in the <code>grc/</code> folder is not filled out. ("..." is a placeholder).</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=InstallingGR&diff=8395InstallingGR2021-03-23T09:24:07Z<p>172.18.0.3: installing software on almost all linux distros needs root, changed this to reflect it in the installation instructions</p>
<hr />
<div>= From Binaries =<br />
<br />
The recommended way to install GNU Radio on most platforms is using already available binary packages (see [[#Ubuntu_PPA_Installation|Ubuntu PPA Installation]]). For some platforms there are no binaries provided by available package managers or the GNU Radio project. In these cases please contact the maintainer of the package manager or the GNU Radio project to find a sensible way to provide binaries for your platform.<br />
<br />
In addition to using binaries, GNU Radio can be installed:<br />
<br />
# '''[[#From_Source|From source]]''' (for those who want full control)<br />
# '''[[#Using_PyBOMBS|Using PyBOMBS]]''' (for those who want it built from source and/or installed to a specific directory using a script)<br />
# '''[[CondaInstall|Using conda]]''' (for those who want binaries installed to managed environments)<br />
<br />
== Linux ==<br />
<br />
Most distributions contain a package named <code>gnuradio</code> or similar in their standard repositories. For most use cases it is enough to install this package and start developing.<br />
<br />
The development of GNU Radio can be fast-paced, and binaries provided by your distribution may be outdated. '''Do check if the version you're installing is up to date! Sometimes old versions are not updated in the packaging systems.''' If you find a bug in a older GNU Radio version, please check if the bug still exists in the newer version of GNU Radio before filing a new issue.<br />
<br />
If the version shipped in your distribution is outdated please contact the corresponding maintainer to update it in the packaging system.<br />
<br />
Here are examples of how to install GNU Radio in various Linux distributions (click the link under &quot;Distribution&quot; for more installation details):<br />
<br />
{|class="wikitable" style="margin: auto; width: 90%;"<br />
!scope="col"|Distribution<br />
!scope="col"|Command<br />
|-<br />
| [[UbuntuInstall|Debian/Ubuntu and derivates]]<br />
| <pre># apt install gnuradio</pre><br />
|-<br />
| [[FedoraInstall|Fedora]]<br />
| <pre># dnf install gnuradio</pre><br />
|-<br />
| RHEL/CentOS<br />
| <pre># yum install gnuradio</pre><br />
|-<br />
| [[ArchInstall|Archlinux]]<br />
| <pre># pacman -S gnuradio</pre><br />
|-<br />
| [[GentooInstall|Gentoo Linux]]<br />
| <pre># emerge net-wireless/gnuradio</pre><br />
|-<br />
| [[SuseInstall|Suse Linux]]<br />
| <br />
|-<br />
|}<br />
<br />
On other distributions, simply use the appropriate package management command to install the <code>gnuradio</code> package and then please add it to this list. If you need newer versions or have a different platform please contact the package maintainer of your distribution or raise your issue on the mailing list.<br />
<br />
=== Ubuntu PPA Installation ===<br />
For Ubuntu, the latest builds (both released and pulled from master branch) are maintained as PPAs on [https://launchpad.net/~gnuradio launchpad]. Be sure to uninstall any previously installed versions of gnuradio first.<br />
<br />
To access the latest from the master branch, add the gnuradio/gnuradio-master ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-master</pre><br />
<br />
To access the current released version (3.9), add the gnuradio/gnuradio-releases ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases</pre><br />
<br />
To access the 3.8 released version, add the gnuradio/gnuradio-releases-3.8 ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.8</pre><br />
<br />
To access the 3.7 released version (legacy), add the gnuradio/gnuradio-releases-3.7 ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.7</pre><br />
<br />
Then, update the apt sources, and install gnuradio<br />
<pre>$ sudo apt-get update</pre><br />
<pre>$ sudo apt install gnuradio</pre><br />
<br />
==== Modtool on Ubuntu ====<br />
NOTE: On released builds for Ubuntu 18 (bionic), there is an issue using gr_modtool after GNU Radio has been installed from the PPA. This is due to byte-compiled code that remains in the modtool templates after installation. To workaround this issue:<br />
<br />
<pre>$ cd /usr/share/gnuradio/modtool/templates/gr-newmod<br />
$ sudo py3clean .</pre><br />
<br />
This issue does not appear for Ubuntu 19 and later packages<br />
<br />
=== Fedora COPR Installation ===<br />
<br />
Packages are available for Fedora 29,30,31 hosted under COPR:<br />
<br />
https://copr.fedorainfracloud.org/coprs/gnuradio/<br />
<br />
1. Add the repository:<br />
<br />
-- For the latest released version:<br />
<pre>$ sudo dnf copr enable gnuradio/gnuradio-releases </pre><br />
-- For the latest pull from git master:<br />
<pre>$ sudo dnf copr enable gnuradio/gnuradio-master </pre><br />
<br />
2. Install GNU Radio<br />
<pre>$ sudo dnf install gnuradio </pre><br />
<br />
=== Raspberry Pi ===<br />
<br />
There is a pre-built 32 bit SDR flavored Raspberry Pi OS distro, [https://github.com/luigifcruz/pisdr-image PiSDR] that includes GnuRadio and other SDR utilities. There are also instructions for [[InstallingGRFromSource_on_Raspberry_Pi]].<br />
<br />
== Windows ==<br />
<br />
Binary installers are now available for GNU Radio 3.7 and 3.8, download them [http://www.gcndevelopment.com/gnuradio/index.htm here].<br /><br />
Conda packages are an alternative for installing binaries for GNU Radio 3.8+. See the [[CondaInstall|conda install guide]].<br /><br />
If you need to install GNU Radio from source refer to the [[WindowsInstall|Windows install guide]].<br />
<br />
Note: We do not officially support Windows. We do our best to provide installation instructions and work out build bugs on Windows when they are reported and patches received. As new versions of GNU Radio, its dependencies, and Windows itself come out, however, keeping all of them working together is beyond the scope of what the project currently supports. User updates to the above wiki installation instructions are very welcome.<br />
<br />
== Mac OS X ==<br />
<br />
Refer to the [[MacInstall|Mac OS X install guide page]].<br />
<br />
= From Source =<br />
<br />
Binary installation should be sufficient for most users, and certainly for anyone who is new to GNU Radio. However, if you have special requirements, want the latest version, or the binary packages are not working for you, you may want to install GNU Radio from source.<br />
<br />
=== Notes ===<br />
<br />
* By default GNU Radio will be installed in the /usr/local directory. See notes about -DCMAKE_INSTALL_PREFIX to install it elsewhere.<br />
* Running and developing out-of-tree modules does not require GNU Radio to be installed from source. <br />
* If you are using Ubuntu, see [[UbuntuInstall#Install_the_Pre-Requisites]] for installing dependencies / pre-requisites.<br />
* If you want to use GNU Radio with a USRP, you <b>FIRST</b> must clone and install UHD. See [[https://kb.ettus.com/Building_and_Installing_the_USRP_Open-Source_Toolchain_(UHD_and_GNU_Radio)_on_Linux UHD Installation Page]] for more info.<br />
* To install on a Raspberry Pi, see [[InstallingGRFromSource on Raspberry Pi]].<br />
* To build from source from within a conda environment, see [[CondaInstall#Building GNU Radio from source within a conda environment|the conda install guide]].<br />
<br />
=== For the GNU Radio Master Branch ===<br />
<br />
Since Volk is no longer considered as a submodule of GNU Radio (GNU Radio commit #80c04479da962d048d41165081b026aafdaa0316 ),<br> you <b>MUST FIRST</b> install Volk, and then install GNU Radio. <br />
<br />
The basic idea is the same, but instead of building Volk along with GNU Radio, you need to clone and build it separately. For this example, we will start in the home directory. You can, of course, use any directory you wish and the results will be the same.<br />
<br />
==== Step 1: Installing Dependencies ====<br />
<br />
Refer to [[InstallingGR#Linux|this page for your specific Linux distro]] to find how to install dependencies. For example, on Ubuntu 20.04 [[UbuntuInstall#Focal Fossa (20.04)|use this command]].<br />
<br />
==== Step 2: Installing Volk ====<br />
<br />
* <code>cd</code><br />
* <code>git clone --recursive https://github.com/gnuradio/volk.git</code><br />
* <code>cd volk</code><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code><br />
* <code>make</code><br />
* <code>make test</code><br />
* <code>sudo make install</code><br />
<br />
If you're running Linux, then always remember to do the following command after installing any library:<br />
* <code>sudo ldconfig</code><br />
<br />
==== Step 3: Installing GNU Radio ====<br />
* <code>cd</code><br />
* <code>git clone https://github.com/gnuradio/gnuradio.git</code><br />
* <code>cd gnuradio</code><br />
<b>Note:</b> If you want to build the <code>maint-3.9</code> branch rather than the default <code>master</code> branch, enter:<br />
<code>git checkout maint-3.9</code> and then<br><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
<b>Note:</b> In the following command, you can add <code>-DCMAKE_INSTALL_PREFIX=XXX</code> to install GNU Radio into the PREFIX <code>XXX</code>; if not specified, then the PREFIX is <code>/usr/local</code>. See other CMake options in [[#Common_cmake_flags|Common cmake flags]].<br><br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code><br />
* <code>make -j3</code> (e.g. if you want to use 3 CPU cores during the build. To use 8 do -j8, to use 1 leave out the -j flag.)<br />
<b>Note:</b> In the following command, it is very possible that not all tests pass. Generally any error is a sign of a missing dependency such as the Python interface to ZMQ or NumPy or SciPy, none of which are required for building GNU Radio but are required for testing.<br />
* <code>make test</code><br />
* <code>sudo make install</code><br />
If you're running Linux, then always remember to do the following command after installing any library:<br />
* <code>sudo ldconfig</code><br />
If you encounter "Cannot import gnuradio" error, then go to [[ModuleNotFoundError#B._Finding_the_Python_library|Finding the Python library]] to set your PYTHONPATH and LD_LIBRARY_PATH.<br><b>After setting these environment variables, you need to do</b> <code>sudo ldconfig</code> <b>again</b> for the Linux dynamic library loader to find the just-installed GNU Radio libraries.<br><br />
If you have installed in a custom path with <code>-DCMAKE_INSTALL_PREFIX=XXX</code>, you will need to add that path to $PATH in order to find gnuradio-companion.<br />
<br />
=== For GNU Radio 3.8 or Earlier ===<br />
For this example, we will start in the home directory; you can, of course, use any directory you wish and the results will be the same.<br />
<br />
* <code>cd</code><br />
* <code>git clone https://github.com/gnuradio/gnuradio.git</code><br />
* <code>cd gnuradio</code><br />
<br />
<b>Note:</b> In the following command, change <code>maint-3.8</code> to some other branch or tag if you want to build a different version of GNU Radio; see [https://github.com/gnuradio/gnuradio/tags tags] for tagged releases including pre-releases ("rc"). For [https://github.com/gnuradio/gnuradio/branches branches], it's generally wise to stick with "master" (the default after cloning), and, currently: <code>maint-3.7</code> or <code>maint-3.8</code>. Here we checkout the <code>maint-3.8</code> branch, which contains the latest 3.8 release plus any fixes or augmentations to it that will be in the next 3.8 release.<br />
<br />
* <code>git checkout maint-3.8</code><br />
* <code>git submodule update --init --recursive</code><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
<br />
<b>Note:</b> In the following command, you can add <code>-DCMAKE_INSTALL_PREFIX=XXX</code> to install GNU Radio into the PREFIX <code>XXX</code>; if not specified, then the PREFIX is <code>/usr/local</code>. See other CMake options in [[#Common_cmake_flags|Common cmake flags]].<br><br />
<br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code><br />
* <code>make -j3</code> (e.g. if you want to use 3 CPU cores during the build. To use 8 do -j8, to use 1 leave out the -j flag.)<br />
* <code>sudo make install</code><br />
<br />
If you're running Linux, then always remember to do the following command after installing any library:<br />
* <code>sudo ldconfig</code><br />
<br />
Go to [[ModuleNotFoundError#B._Finding_the_Python_library|Finding the Python library]] to set your PYTHONPATH and LD_LIBRARY_PATH.<br><b>After setting these environment variables, you need to do</b> <code>sudo ldconfig</code> <b>again</b> for the Linux dynamic library loader to find the just-installed GNU Radio libraries.<br><br />
If you have installed in a custom path with <code>-DCMAKE_INSTALL_PREFIX=XXX</code>, you will need to add that path to $PATH in order to find gnuradio-companion.<br />
<br />
==== For Ubuntu 18.04 ====<br />
<br />
An easy way to install GNU Radio 3.8 on many Ubuntu systems is to use the following commands (note that this skips the setup for UHD hardware):<br />
<br />
* <code>sudo apt install git cmake g++ libboost-all-dev libgmp-dev swig python3-numpy python3-mako python3-sphinx python3-lxml doxygen libfftw3-dev libsdl1.2-dev libgsl-dev libqwt-qt5-dev libqt5opengl5-dev python3-pyqt5 liblog4cpp5-dev libzmq3-dev python3-yaml python3-click python3-click-plugins python3-zmq python3-scipy python3-pip python3-gi-cairo</code><br />
* <code>pip3 install git+https://github.com/pyqtgraph/pyqtgraph@develop</code><br />
* <code>pip3 install numpy scipy</code><br />
* <code>echo 'export PYTHONPATH=/usr/local/lib/python3/dist-packages:usr/local/lib/python2.7/site-packages:$PYTHONPATH' >> ~/.bashrc</code><br />
* <code>echo 'export LD_LIBRARY_PATH=/user/local/lib:$LD_LIBRARY_PATH' >> ~/.bashrc</code><br />
* <code>echo 'export PYTHONPATH=/usr/local/lib/python3/dist-packages:usr/local/lib/python2.7/site-packages:$PYTHONPATH' >> ~/.profile</code><br />
* <code>echo 'export LD_LIBRARY_PATH=/user/local/lib:$LD_LIBRARY_PATH' >> ~/.profile</code><br />
* <code>cd ~/</code><br />
* <code>git clone --recursive https://github.com/gnuradio/gnuradio</code><br />
* <code>cd gnuradio</code><br />
* <code>git checkout maint-3.8</code><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
* <code>git pull --recurse-submodules=on</code><br />
* <code>git submodule update --init</code><br />
* <code>cmake -DENABLE_GR_UHD=OFF ..</code><br />
* <code>make -j $(nproc --all)</code><br />
* <code>sudo make install</code><br />
* <code>sudo ldconfig</code><br />
<br />
Once this is done, reboot your computer and GNU Radio should be all set for you.<br />
<br />
== Common cmake flags ==<br />
<br />
* <code>-DENABLE_GR_XXX=ON</code> This enables (or disables for =OFF) the GNU Radio component named XXX. You might not need all of them, and this way, you can compile quicker.<br />
* <code>-DCMAKE_INSTALL_PREFIX=XXX</code> Install your stuff to XXX.<br />
* <code>-DCMAKE_BUILD_TYPE=Debug</code> This causes gcc to add debug symbols to all binaries. Useful for debugging (otherwise, it decreases efficiency!)<br />
* <code>-DPYTHON_EXECUTABLE=/usr/bin/python{2,3}</code> This selects the Python version and executable to be used during build time and will determine which Python libraries will be used for building the Python bindings.<br />
<p>For a list of additional cmake flags, as well as minimum versions of dependencies, see [https://www.gnuradio.org/doc/doxygen/build_guide.html]</p><br />
<br />
= Using PyBOMBS =<br />
<br />
PyBOMBS is good at building GNU Radio, UHD, and various Out of Tree (OOT) modules from source and then installing into a specified user directory rather than in the system files. PyBOMBS detects the user's Operating System and loads all of the prerequisites in the first stage of the build.<br />
<br />
The PyBOMBS documentation is in the PyBOMBS [https://github.com/gnuradio/pybombs#pybombs README].<br />
<br />
= OK, it's installed, what now? =<br />
<br />
If the installation worked without any trouble, you're ready to use GNU Radio! If you have no idea how to do that, the best place to start is with the [[Tutorials]].<br />
<br />
Optionally, you may run <code>volk_profile</code> on your terminal to help libvolk to determine the optimal kernels (may speed up GNU Radio). <br />
<br />
[[Category:Installation]]<br />
[[Category:Guide]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=VCO_(complex)&diff=8380VCO (complex)2021-03-17T13:09:09Z<p>172.18.0.3: there's no volts in digital signal processing</p>
<hr />
<div>[[Category:Block Docs]]<br />
VCO - Voltage controlled oscillator. Produces a sinusoid of frequency based on the amplitude of the input. See [[VCO]] for a real (not complex) sinusoidal output.<br />
<br />
input: float stream of control voltages; <br />
<br />
output: complex oscillator output<br />
<br />
== Parameters ==<br />
; Sample Rate<br />
: sampling rate (Hz)<br />
<br />
; Sensitivity<br />
: units are radians/sec/(input unit)<br />
<br />
; Amplitude<br />
: output amplitude<br />
<br />
== Example Flowgraph ==<br />
<br />
This flowgraph shows a Radioteletype (RTTY) transmitter and receiver. The upper portion is the transmitter. The parameters for the VCO are explained in [[VCO]].<br><br />
The lower portion of the flowgraph is a single sideband (SSB) receiver. The audio can be fed into a RTTY decoder such as shown in [[Sample_Rate_Tutorial#Source_hardware_example]].<br />
<br />
[[File:USRP_RTTY_fg.png|710px]]<br />
<br />
== Source Files ==<br />
<br />
; C++ files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Header files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Public header files<br />
: [https://github.com/gnuradio/gnuradio TODO]<br />
<br />
; Block definition<br />
: [https://github.com/gnuradio/gnuradio TODO]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8379IQ Complex Tutorial2021-03-16T20:53:46Z<p>172.18.0.3: /* Why we need complex and IQ signals */</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_1= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > 2F_{Max}</math><br />
<br />
For an HIFI audio signal, maximum audio frequency <math>F_{Max Audio}</math> is closed to 20 kHz, sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound card, 8 kHz is used for mobile phone since voice has a lower frequency range then HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such signal is not possible with conventional hardware such as low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much then computer can handle (higher then some computer clock).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex enveloppe, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since its allow to lower sampling rate as compare to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation help us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal correspond to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex enveloppe is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Enveloppe, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seemed counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex enveloppe is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercice: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100Khz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br />
== Equivalent baseband representation ==<br />
<br />
This diagram sketch a classical Emitter/Receiver (Tx/Rx) transmission including channel noise, and its equivalent baseband representation which could be used for a GNURadio simulation. <br />
[[File:IQ_complex_tutorial_bb_eq.png|thumb|800px|Digital Emitter Receiver based on IQ modulator and its baseband equivalent representation]]<br />
* this transceiver can be used to generate and transmit any modern digital communications like OOK, ASK, PSK, and QAM depending on choosen signal ''i(t)'' and ''q(t)'' <br />
* quantities with an hat like <math>\hat{i}(t)</math> should be similar or equal to corresponding <math>i(t)</math><br />
* signal ''i(t)'' and ''q(t)'' are generated using Dirac impulse waited by amplitudes ''a<sub>k</sub>'' and ''b<sub>k</sub>'' and a shaping filter ''h<sub>1</sub>(t)''<br />
* the considered channel is Average Wait Gaussian Noise (AWGN)<br />
* ID modulator and IQ demodulator are not necessarily synchronized<br />
* Baseband filter are used to limit the bandwidth in the emitter and limit noise in the receiver. In a well construct Transceiver chain, ''h<sub>1</sub>(t)'' and ''h<sub>3</sub>(t)'' should be Root Nyquist filters<br />
* A Bandpass filter is used in the channel, it can represent the channel effect as well as any additional filter used on the modulated signal<br />
<br />
One may observe that this transceiver don't include non linear effect suchs as amplifier intermodulation. In fact, complex baseband representation don't permit precise modelization of non linear effects so they are not considered here. Complex signals on the baseband equivalent representation are represented using double arrow.<br />
<br />
Every linear band limited system has an equivalent baseband which is build using the following rules.<br />
* Replace IQ modulator inputs ''i(t)'' and ''q(t)'' by a complex signal ''i(t) + jq(t)'' <br />
* Similarly replace IQ demodulator outputs by a complex signal<br />
* Keep any baseband filter acting on baseband signal unchanged (filter ''h<sub>1</sub>(t)'' is acting on ''i(t)'' and ''q(t)'' so it can be replaced by a single filter acting on complex signal ''i(t) + jq(t)''. However one could have used two identical filters each acting on one real signal)<br />
* Replace IQ modulator by a multiplication by ''a'' (the modulator carrier is used as a reference to define complex equivalent baseband, no multiplier if ''a''=1)<br />
* Replace IQ demodulator by a multiplication by <math>b e^{j(2\pi \Delta ft+\phi)}</math> (no multiplier if b=1 and demodulator is synchronized to modulator<br />
** This result from section [[#complex_envelope_of_a_pure_sine_wave| complex envelope of a pure sine wave]]<br />
* Replace any bandpass filter by its equivalent baseband complex filter as defined below<br />
* Replace channel bandpass noise by its equivalent baseband complex noise as defined below<br />
<br />
=== Bandpass filter===<br />
<br />
[[File:IQ_complex_tutorial_complex_filter.png|thumb|500px||Equivalent baseband filter of a bandpass filter]]<br />
Any bandpass filter with frequency transfert function <math>H(f)</math> having a limited bandwidth can be represented by an equivalent baseband filter <math>H^{bb}(f)</math> . The baseband filter frequency transfert is the positive part of the transfert function shifted toward 0 (same process as above for AM Spectrum) <br />
: <math>H^{bb}(f)=H^+(f+F_0)</math><br />
<br />
So we observe that the equivalent filter is a lowpass filter. <br />
<br />
As <math>H(f)</math> is not necessarily symmetric around ''F<sub>0</sub>'', <math>H^{bb}(f</math> can be unsymmetric: this correspond to a complex time transfert function, what we will call a complex filter. <br />
<br />
So the baseband equivalent of a band pass filter is a complex filter acting on a complex signal. <br />
<br />
Normally, when filtering complex signal in GNURadio, in most situation complex taps must be used. In case your baseband filter is symmetric around ''F<sub>0</sub>'' it turns to be a real filter, it can be represented using real taps.<br />
<br />
=== Unsynchronized demodulator ===<br />
For a receiver carrier ''p'(t)'' given by<br />
: <math>m(t) = 2b \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
It is found that the equivalent baseband is a multiplication with<br />
: <math>m^{bb}(t) = b e^{2j(\pi (\Delta ft+\phi)}</math><br />
Which is similar (excepted for a factor 2) to the result given for the baseband equivalent of a pure sine wave close to the carrier. The factor 2 difference come from the demodulation process.<br />
<br />
=== Channel noise ===<br />
Any bandpass noise <math>N(t)</math> having a limited bandwidth can be represented by an equivalent baseband noise <math>N^{bb}(t)</math> . The baseband equivalent noise spectrum is the positive part of the bandpass noise spectrum shifted toward 0 (same process as above for AM Spectrum and for filter) <br />
: <math>N^{bb}(f)=N^+(f+F_0)</math><br />
<br />
So we find that the baseband equivalent noise is a low frequency noise. Furthermore, as for filters, the baseband equivalent noise is complex, its real part and imaginary part having the same variance (power).<br />
<br />
== Tx/Rx PSK equivalent baseband simulation ==<br />
[[File:IQ_tutorial_QPSK1.png|thumb|800px|PSK modulation]]<br />
<br />
In this section we will illustrate equivalent baseband blocks introduced above: <br />
* complex noise <br />
* complex filters <br />
* IQ demodulator with carrier asynchronism, <br />
This will be done using a simple QPSK and BPSK transceiver simulation. We will not really investigate demodulation but concentrate on what should be done to compensate for the channel impairments. <br />
<br />
Let us first examine this flowgraph. The upper part of the flowgraph generate a QPSK signal as used in a previous flowgraph. The lower part is a modified version of the first one. <br />
<br />
Questions : <br />
* If we do not account for the multiplier, what type of signal will generate the second part of this flowgraph (at the throttle output)?<br />
* What is the effect the multiplier (source is at 25 Khz which is 1/4 of the sampling rate) ?<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution &nbsp;<br />
|-<br />
| the second signal is a BPSK (''i(t)'' is an NRZ Line code, ''q(t)'' is 0. The two phase states of this BPSK are 0° and 180°. <br />
|-<br />
| Without multiplier this BPSK would be centered at ''F<sub>0</sub>''. As it is multiplied by a complex exponent at 25 kHz, it will be shift at ''F<sub>0</sub>''+ 25 kHz<br />
|}<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX.grc|IQ_tutorial_QPSK_TX_RX.grc]]. Note that BPSK constellation block is connected before the multiplier, while BPSK spectrum is evaluated after the multiplier. <br />
* Simulate the flowgraph. Adjust ''Delta_f (BPSK shift)'' to 0. Observe the constellation and the spectrum of both signals<br />
* Increase noise level, observe the complex noise on BPSK and QPSK constellation<br />
* Change ''Delta_f (BPSK shift)'' and observe how the BPSK Spectrum is shifted. <br />
** What is the BPSK center frequency ?<br />
** Explain why the BPSK constellation in this case is rotating<br />
<br />
We will now investigate the channel frequency offset parameter. This parameter is used to simulate the frequency offset between a receiver and an emitter. <br />
<br />
Stop the simulation. Disable ''delta_f_over_fs'' QT Gui range and enabled the one which was disabled (larger range) and run the flowgraph.<br />
* select the spectrum tab, and increase ''Delta_f (BPSK shift)'' to 25 kHz <br />
* change ''delta_f_over_fs'' value and observe how it shifts the spectrum of the received signal the same way as our multiplier does.<br />
<br />
Let's suppose we receive a modulated at the output of an unsynchronized receiver (hardware). Let's suppose that we have measured the carrier frequency difference between emitter end receiver. The modulated signal spectrum is not centered around ''F<sub>0</sub>''. There is several way to compensate for this effect:<br />
* Specify a receiver frequency which compensate the frequency difference (most SDR Source in GNURadio can specify a frequency shift which is given in PPM (Parts per million).<br />
* Multiply the received signal by <math>e^{-2j(\pi (\Delta ft)}</math><br />
* Use a GNURadio Xlating filter block which perform the previous multiplication and a filtering. <br />
<br />
=== GNURadio XLating filter ===<br />
[[File:IQ_tutorial_QPSK_TX_RX_2.png|thumb|800px|Basic Tx/Rx using a Xlating filter at receiver]]<br />
GNURadio XLating filter perform 3 actions<br />
* it shifts the spectrum in the frequency domain<br />
* it can filter the result with the specified filter<br />
* it can decimate the signal <br />
The Xlating filter is usefull every time a signal spectrum is not centered, and/or when you need to select one signal in a spectrum where several channel are in use.<br />
<br />
Open [[Media:IQ_tutorial_QPSK_TX_RX_2.grc|IQ_tutorial_QPSK_TX_RX_2.grc]]. <br />
<br />
This flowgraph <br />
* generate a single signal composed of: <br />
** our QPSK signal at ''F<sub>0</sub>''<br />
** the BPSK signal at ''F<sub>0</sub>''+25 kHz<br />
* simulate a channel frequency offset<br />
* use a Xlating filter to shift the received signal (compensate the channel and/or select the desired channel)<br />
* filter the signal to select only one signal BPSK or QPSK (low pass with 12 kHz cutt-off frequency)<br />
** note: filtering is performed after Xlating so thaht we can display filtered and unfiltered signals on the same spectrum.<br />
* display the constellation of the demodulated channel (constellation are now different from a ''perfect constellation'' , this is due to the low pass filter.<br />
<br />
Simulate this flowgraph. By default the QPSK signal is demodulated. <br />
* observe that the constellation is close to the QPSK one (excepted for filter effect, at this point we should use Nyquist filters to recover the QPSK constellation)<br />
* Slowly increase Xlating filter frequency offset and observe the spectrum shift<br />
* Select the Xlating filter frequency offset to demodulate the BPSK (25kHz) and observe how it is uneasy or impossible to get a correct constellation.<br />
** You should get approximately 2 set of points, but these point are shift as compared to the perfect BPSK constellation. This is normal, we compensate frequency while in a real receiver it is necessary to compensate both frequency and phase shift<br />
** In fact simulating a delta_f frequency which change during simulation is equivalent to having a phase '''and''' a frequency shift.<br />
<br />
=== Asynchronism in real hardware ===<br />
The above simulation have explained the basis of asynchronism found in any hardware Tx/Rx and some basic method to recover synchronism. <br />
<br />
However keep in mind that dealing with real hardware is more complicated then described here. The frequency and phase shift between emitter and receiver should be considered as dynamic and changing continously and randomly with time. <br />
<br />
As a consequence, we need more robust block to continuously synchronize emitter and receiver. Fortunately, digital signal processing offer many solutions to these impairments. Curious reader are encouraged to read the excellent [[Guided_Tutorial_PSK_Demodulation|GNURadio Guided Tutorial on PSK Demodulation]].<br />
<br />
==References==<br />
Further reading for complex equivalent baseband signal: <br />
<br />
* <span id="ancre1"[1]> [1] Proakis J., ''Digital Communication'', McGraw Hill Series in Electrical and Computer Engineering, Singapore, 1989</span><br />
* <span id="ancre2"[2]> [2] Gallager R., ''Principles of digital communication'', Cambridge University Press Cambridge, UK, 2008</span><br />
* <span id="ancre3"[3]> [3] Benedetto S. and Biglieri E., ''Principles of digital transmission : with wireless applications'', Kluwer Academic/Plenum Publishers, NY, 1999</span><br />
==About figures ==<br />
Most figures were generated from .grc flowgraph referenced in the text, and from .odg and .tex file. For completeness, these files are included in the following compressed archive [[Media:IQ_complex_tutorial_files.zip.grc|IQ_complex_tutorial_files.zip.grc]] so that anyone can easily improve this tutorial. If you modify some figure, please update this archive too.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Hardware&diff=8377Hardware2021-03-16T09:27:57Z<p>172.18.0.3: /* The Catch-All Clause */</p>
<hr />
<div>= A Quick Guide to Hardware and GNU Radio =<br />
<br />
== Can't Buy Hardware? No problem! ==<br />
<br />
GNU Radio can be used on its own, without any hardware, as a simulation &amp; development environment. GNU Radio has several blocks that can generate data or read/write files in different formats, such as binary complex values or even WAV-files. [[SampleData|A lot of prerecorded examples]] exist that can be used to develop applications without the need for hardware. If you are looking for a particular waveform to develop with and don't have a capture, ask on the mailing list and someone can likely help!<br />
<br />
Additionally, GNU Radio is a powerful tool for hardware simulation. You can simulate complete transmitter and receiver chains, including RF, analog, and other relevant impairments that you would encounter in 'real-world' operation.<br />
<br />
Ready for that first step with real hardware? A very low cost (US $10.00+) receive only hardware option is an RTL-SDR described [https://wiki.gnuradio.org/index.php/Hardware#rtl-sdr_TV_tuners below] will allow you to sample (Rx Only) live signals.<br />
<br />
== Commercially Available SDR Platforms ==<br />
<br />
If you want to use real hardware, you have a number of options. The list of hardware vendors that provide GNU Radio support for their products is growing quickly. Hardware ranges from very expensive measurement-quality systems, to very cheap RTL receive only hardware that you can get for less than $50.<br />
<br />
This is not a complete list, but rather provides a rundown of some of the more common options. Manufacturers may currently have more versions and options than those shown here. Specifications are summary and may not reflect the performance across the spectrum - read current full specs from the manufacturer! Some of the designs are open source hardware or have been cloned. Some of the clones are available with improved specs such as improved clocks. Keep in mind that clones do not support the creators of the systems and may not have the same quality control or customer support, if any - [https://en.wikipedia.org/wiki/Caveat_emptor caveat emptor]. The systems described here top out at around $1k, but there are higher-end/higher-cost SDR systems available. In addition to a SDR, you will need an antenna and depending on your application you may need filters, LNA/PA amplifiers, etc. to make a functioning transceiver.<p><br />
To understand the capabilities and performance of a SDR it is often helpful to review the datasheets of the RF IC that is used to understand the best case limits of what the board design can achieve. In designing the particular board the design may be optimized for cost or for a particular frequency that that may result in the RF performance of the board being less than what the RF IC can support. Generally, the boards listed on this page are built on inexpensive consumer grade FR4 material that has [https://www.edn.com/what-pcb-material-do-i-need-to-use-for-rf/ reduced performance above a few GHz]. "Professional" RF equipment above a few GHz tends to be built on very expensive impedance controlled PCB material such as that manufactured by [https://www.pcbgogo.com/blog/What_Is_The_Difference_Between_FR_4_Material_And_Rogers_Material_.html Rogers]. With [https://www.edn.com/make-a-quick-turnaround-pcb-for-rf-parts/ very good engineering] and/or slightly improved FR4-like alternatives consumer PCB technology can be extended to 7 GHz. As WiFi [https://www.everythingrf.com/community/what-frequency-band-does-wi-fi-6e-useWiFi 6E starts to extends into the 5.7-7 GHz range] in the US we can expect to see support for lower cost RF ICs and manufacturing at these higher frequencies. Demand for self-driving car radar is driving development of lower cost RF ICs in the [https://www.ti.com/sensors/mmwave-radar/automotive/overview.html 60-80 GHz range].<br />
<br />
<br />
=== Summary Of Features For Some SDRs ===<br />
{| class="wikitable" <br />
|-<br />
! <br />
! XTRX CS<br />
! XTRX Pro<br />
! USRP B2x0<br />
! bladeRF<br />
! bladeRF Micro 2.0<br />
! LimeSDR<br />
! LimeSDR Mini<br />
! RTL-SDR R820T2<br />
! RTL-SDR E4000<br />
! ADALM-Pluto<br />
! New Horizons<br />
! Hack RF One<br />
|-<br />
| Tuning range<br />
| 30 MHz - 3.7 GHz<br />
| 30 MHz - 3.7 GHz<br />
| 70 MHz - 6 GHz<br />
| 300 MHz - 3.8 GHz<br />
| 47 Mhz - 6 Ghz<br />
| 30 MHz - 3.8 GHz<br />
| 10 MHz - 3.5 GHz<br />
| 22 MHz - 2.2 GHz<br />
| 65 MHz - 2300 MHz, gap@1100 MHz<br />
| 325 MHz - 3800 MHz<br />
| 70 MHz - 6 GHz<br />
| 1 MHz - 6000 MHz<br />
|-<br />
| Duplex<br />
| Full MIMO<br />
| Full MIMO<br />
| Full MIMO<br />
| Full SISO<br />
| Full MIMO<br />
| Full MIMO<br />
| Full SISO<br />
| RX only<br />
| Rx only<br />
| Full SISO<br />
| Full MIMI<br />
| SISO Half Duplex<br />
|-<br />
| ADC/DAC resolution<br />
| 12-bit<br />
| 12-bit<br />
| 12-bit<br />
| 12-bit<br />
| 12-bit<br />
| 12-bit<br />
| 12-bit<br />
| 8-bit<br />
| 8-bit<br />
| 12-bit<br />
| 12-bit<br />
| 8-bit<br />
|-<br />
| Max RF bandwidth<br />
| 120 MHz<br />
| 120 MSPS SISO / 90 MSPS MIMO<br />
| 56 MHz<br />
| 28 MHz<br />
| 56 Mhz<br />
| 61.44 MHz<br />
| 30.72 MHz<br />
| 3.2 Mhz<br />
| <br />
| 20 MHz *limited by USB 2.0 and software to ~4Mhz<br />
| 56 MHz / CH<br />
| <br />
|-<br />
| Rx Noise Figure dB<br />
| <br />
| <br />
| <br />
| <br />
| <br />
| <br />
| <br />
| <br />
| <br />
| <3.5<br />
| <br />
| <br />
|-<br />
| Channels<br />
| 2<br />
| 2<br />
| 1 (2 for B210)<br />
| 1<br />
| 2<br />
| 2<br />
| 1<br />
| 1 Rx only<br />
| 1 Rx only<br />
| 1<br />
| 2<br />
| 1 Half Duplex<br />
|-<br />
| Transmit power<br />
| 0 to 10dBm<br />
| 0 to 10dBm<br />
| 10dBm+<br />
| 6dBm<br />
| 8dBm<br />
| 0 to 10dBm<br />
| 0 to 10dBm<br />
| none<br />
| none<br />
| 7 dBm<br />
| 9.7<br />
| 0 - 15 dBm freq dependent<br />
|-<br />
| RF chipset<br />
| LMS7002M<br />
| LMS7002M<br />
| AD9364 or AD9361<br />
| LMS6002M<br />
| AD9361<br />
| LMS7002M<br />
| LMS7002M<br />
| R820T2<br />
| E4000<br />
| AD9363<br />
| AD9361<br />
| MAX 2837/Max5864<br />
|-<br />
| FPGA<br />
| Xilinx Artix7 35T<br />
| Xilinx Artix7 50T<br />
| Xilinx XC6SLX75<br />
| Altera 40KLE/115KLE<br />
| Intel Cyclone V<br />
| Altera 40KLE<br />
| Altera MAX 10<br />
| none<br />
| None<br />
| Xilinx Zynq 7000<br />
| Zync-7020<br />
| XC2C64A-7VQ100C CPLD<br />
|-<br />
| FPGA Gates, DSP slices<br />
| 33k, 90 DSP<br />
| 52k, 120 DPS<br />
| 75k, 132 DSP<br />
| 40K, 58 DSP opt 115k<br />
| 49-301k, 66-342DSP<br />
| 40K, 58 DSP<br />
| 16k, 45 mult<br />
| 0, 0<br />
| 0, 0<br />
| 28k<br />
| 85k<br />
| 64 Macro cell<br />
|-<br />
| Industrial temperature range<br />
| no<br />
| yes<br />
| no<br />
| Optional<br />
| Optional<br />
| no<br />
| no<br />
| no<br />
| no<br />
| 10-40C<br />
| <br />
| <br />
|-<br />
| Temperature sensors<br />
| yes<br />
| yes<br />
| no<br />
| no<br />
| yes<br />
| yes<br />
| no<br />
| no<br />
| No<br />
| <br />
| <br />
| <br />
|-<br />
| Frequency stability<br />
| ±0.5 w/o <±0.01 ppm w/GPS<br />
| ±0.1 w/o <±0.01 ppm w/GPS<br />
| ±2 ppm<br />
| ±1 ppm<br />
| ±2.5 ppm<br />
| ±2.5 ppm<br />
| ±2.5 ppm<br />
| ±0.5-25 ppm options<br />
| ±0.5-25 ppm options<br />
| ± 25 ppm<br />
| ± 15 ppm<br />
| <br />
|-<br />
| TCXO<br />
| <br />
| <br />
| <br />
| <br />
| VCTCXO<br />
| <br />
| VCTCXO<br />
| Optional<br />
| Optional<br />
| <br />
| VCTCXO 40MHZ (W/16bit DAC 0.2PPM max)<br />
| opt. 0.5ppm TXCO<br />
|-<br />
| GPS synchronization<br />
| on board<br />
| on board<br />
| Addon (+$636)<br />
| no<br />
| no<br />
| no<br />
| no<br />
| no<br />
| no<br />
| No<br />
| optional expansion<br />
| No<br />
|-<br />
| Bus/interface<br />
| PCIe, opt USB 3 adapter $85<br />
| PCIe, opt USB 3 adapter $85<br />
| USB 3<br />
| USB 3<br />
| USB 3<br />
| USB 3<br />
| USB 3<br />
| USB 2<br />
| USB 2<br />
| USB 2.0 OTG<br />
| USB 2.0+ETH<br />
| USB 2.0 HS<br />
|-<br />
| CPU/Bus Interface<br />
| <br />
| <br />
| <br />
| <br />
| Cypress FX3<br />
| CY3014<br />
| <br />
| RTL2832U<br />
| RTL2832U<br />
| Dual A9,667MHz,<br />
| <br />
| LPC4320<br />
|-<br />
| Raw bus bandwidth<br />
| 10 Gbit/s<br />
| 10 Gbit/s<br />
| 5 Gbit/s<br />
| 5 Gbit/s<br />
| 5 Gbit/sec<br />
| 5 Gbit/s<br />
| 5 Gbit/s<br />
| 480 Mbit/s<br />
| 480 Mbit/s<br />
| 480 Mbits/s<br />
| 480 MBit/s 1000 MB/s (ETH)<br />
| 480 MBit/s<br />
|-<br />
| Dimensions<br />
| 30 × 51 mm<br />
| 30 × 51 mm<br />
| 97 x 155 mm<br />
| 87 x 131 mm<br />
| 63 x 102 mm<br />
| 100 x 60 mm<br />
| 69 x 31.4 mm<br />
| 40 x 60 mm typical<br />
| 40 x 60 mm typical<br />
| 117 x 79 mm<br />
| 75mm*102mm<br />
| <br />
|-<br />
| Extra features<br />
| GPIO, GPS, SIM card<br />
| GPIO, GPS, SIM card<br />
| GPIO<br />
| GPIO<br />
| <br />
| GPIO<br />
| GPIO<br />
| SMA optional<br />
| SMA optional<br />
| <br />
| optional LCD, GPS<br />
| GPIO, RTC, opt case LCD<br />
|-<br />
| Clock Sync<br />
| Yes<br />
| Yes<br />
| Yes<br />
| Yes<br />
| Yes, In/Out, Ref<br />
| Yes, In/Out<br />
| Yes, In<br />
| No<br />
| No<br />
| <br />
| connector<br />
| In/Out<br />
|-<br />
| Time stamp Sync<br />
| Yes<br />
| Yes<br />
| Yes<br />
| Yes<br />
| Yes<br />
| No<br />
| No<br />
| No<br />
| No<br />
| <br />
| <br />
| <br />
|-<br />
| Bias T<br />
| No<br />
| No<br />
| No<br />
| Yes<br />
| Optional<br />
| No<br />
| No<br />
| Optional<br />
| Optional<br />
| <br />
| <br />
| No<br />
|-<br />
| ANT/CLK Connectors<br />
| <br />
| <br />
| <br />
| <br />
| 2Rx/2Tx + 3CLK U.FL<br />
| 6 Rx/4Tx+2CLK U.FL<br />
| 2 SMA, 1 U.FL Clk<br />
| Chip optional SMA<br />
| Chip optional SMA<br />
| SMA x 2<br />
| 4 SMA (1 SMA opt GPS)<br />
| 1 ANT + 2 CLK SMA<br />
|-<br />
| Price - typical US$<br />
| $260<br />
| $490<br />
| $686 - $1,119 2 CH clones ~$715<br />
| $415<br />
| $480-$720<br />
| $299<br />
| $139<br />
| $10 - $40<br />
| $10 - $40<br />
| $249<br />
| $642+$220 expansion<br />
| $300 official, down to ~$90 PCBA clones<br />
|}<br />
'''''''This list is in alphabetical order. Please maintain that order if you add new devices.'''''''<br />
<br />
=== Analog Device ADALM-PLUTO ===<br />
Analog Device's [https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/adalm-pluto.html ADALM-PLUTO] AD9363 single channel based SDR with a range of 325-3200 MHz and a Zynq Z-7010 FPGA. <br />
<br />
<br />
=== Ettus Research USRP™ Devices ===<br />
<br />
The Ettus Research USRP™ platform is designed for RF applications from DC to 6 GHz, and provides a wide range of devices. The USRP™ product line spans from affordable hobbyist SDRs to high-end high-bandwidth radios. There are also options for GPS-disciplined synchronization, MIMO configurations, and embedded / headless devices.<br />
<br />
For information regarding the USRP™ product line, [http://www.ettus.com/ see the Ettus Research website].<br />
<br />
All USRPs use the [http://code.ettus.com/redmine/ettus/projects/uhd/wiki USRP Hardware Driver (UHD™) software] to provide device drivers, which can be used in GNU Radio through the `gr-uhd` component. The [https://github.com/EttusResearch/uhd UHD source code is available on GitHub].<br />
<br />
=== Fairwaves XTRX ===<br />
Fairwaves offers the small form-factor dual channel [https://www.crowdsupply.com/fairwaves/xtrx XTRX] that has a sample rate of up to 120 MSPS SISO / 90 MSPS MIMO and a tuning range of 30 MHz - 3.8 GHz with a PCIe interface and optional PCIe to USB interface for development. Based on the Lime Microsystems LMS7002M with a Xilinx Artix 7 35T/50T FPGA. Also offered is a pro version with a larger fpga and extended temperature range.<br />
<br />
=== Fairwaves UmTRX ===<br />
<br />
[http://umtrx.org/ UmTRX] is an open hardware dual-channel wideband transceiver that covers 300MHz to 3.8GHz. It includes a TCXO and GPS for frequency stability, and is designed for use with mobile base stations, but can easily be used with many other applications.<br />
<br />
Host connection is via gigabit Ethernet and a special version of UHD provides a host driver, along with FPGA and ZPU firmware. An alternate version of the firmware, [http://umtrx.org/hardware/4xddc/ 4xDDC], can be used to provide double the number of receive signal paths (4), for receive-only applications.<br />
<br />
Expansion via mezzanine cards is possible and the [http://umtrx.org/hardware/umsel/ UmSEL] daughter board can be used for improved performance with GSM use.<br />
<br />
=== Funcube Pro+ Dongle ===<br />
<br />
The [http://funcubedongle.com Funcube Pro+ Dongle] is a small and inexpensive device for reception only. It covers a frequency range from 150kHz to 240MHz and 420MHz to 1.9GHz. and plugs into a USB port. There are special blocks available on [https://www.cgran.org CGRAN].<br />
<br />
=== KerberosSDR ===<br />
The [https://www.rtl-sdr.com/ksdr/ KerberosSDR] 4 Channel Coherent RTL-SDR For Direction Finding & Passive Radar is [https://othernet.is/products/kerberossdr-4x-coherent-rtl-sdr based on four R820T2 RF ICs]. Status of GnuRadio drives is unknown.<br />
<br />
=== Great Scott Gadgets HackRF ===<br />
<br />
[http://greatscottgadgets.com/hackrf/ HackRF One], designed and manufactured by Great Scott Gadgets, is an open source hardware platform for Software Defined Radio. Operating from 1 MHz to 6 GHz, HackRF One is a half-duplex transceiver peripheral with a Hi-Speed USB 2.0 connection. It is bus-powered, portable, and has a maximum quadrature sample rate of 20 Msps. GNU Radio integration is provided via [http://sdr.osmocom.org/trac/wiki/GrOsmoSDR gr-osmosdr]. As the hardware design is open source there are 3rd parties selling board, kits, enclosures and fully systems integrated with host CPU/LCD that are available on multiple Internet sites including Amazon, eBay, banggood and other China direct websites starting at about $90 each or two for $150 for just the finished boards.<br />
<br />
=== Lime SDR ===<br />
<br />
Lime Micro offers a one and two channel versions (1x1 and 2x2) of their USB powered SDR that has a sampling bandwidth of 61 MHz and a frequency range of 100 kHz to 3800 MHz. It is available from [https://www.crowdsupply.com/lime-micro/limesdr Crowd Supply]<br />
<br />
=== Microtelecom Perseus ===<br />
<br />
The Microtelecom Perseus is a USB 2.0-connected receiver targeted for amateur radio SDR, with a frequency range of 10 kHz to 40 MHz and appropriate preselect filters. See http://www.microtelecom.it/perseus/ for more information.<br /><br />
Andrea Montefusco wrote GNU Radio integration that is provided via [https://github.com/amontefusco/gr-microtelecom gr-microtelecom].<br />
<br />
===New Horizons NH7020 ===<br />
<br />
The [http://gridrf.com/products/detail/id/12.html New Horizons NH7020 from GridRF] is available via [https://www.alibaba.com/product-detail/AD9361-70MHz-6GHz-SDR-Development-Board_62559298197.html?spm=a2700.galleryofferlist.0.0.99cd3851DGv3AL Alibaba] and other internet resellers is based on the AD9361 with an rf tuning range from 70 MHZ to 6 GHz. It appears to have some features of the Analog devices [https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/CN0412.html#rd-overview CNO0412 Reference design]. This appears to be an ADI Pluto like SDR that has been upgraded to a dual channel 6 gHz AD9361. The GridRF website proclaims, ''"this product ideas from ADI PLUTO, the official name is 'New Horizons SDR Platform',as you knew 'New Horizons' is a PLUTO discover spacecraft from NASA. like New Horizons do,hope ours discover from ADI PLUTO can give you a surprise and helping you deep discover with Software Defined Radio."'' There is also an expansion board with GPS, LCB and navigation button available. There are open source [https://github.com/gridrf?tab=repositories git hub repos] for firmware/HDL (no schematics or gerbers) that appears to be forked from ADI's github. Complete kits with expansion board and enclosure are available for less than [https://www.ebay.com/sch/i.html?_from=R40&_trksid=p2380057.m570.l1313&_nkw=AD9361+and+ZYNQ7020&_sacat=0 US$900]<br />
<br />
=== Novena + Myriad-RF module ===<br />
<br />
The [https://www.crowdsupply.com/kosagi/novena Novena] open hardware computing platform plus the companion [https://myriadrf.org/projects/novena-rf/ Myriad-RF SDR module] together provide a system with a quad-core ARM SoC, GPU, FPGA, dual-Ethernet and wideband transceiver that covers from 380MHz to 3.8GHz. This can be used &quot;headless&quot;, with a HDMI monitor, or configured as an all-in-one desktop or a laptop with a flat panel display.<br />
<br />
GNU Radio applications are supported via the [https://github.com/pothosware/SoapySDR SoapySDR] API and [http://sdr.osmocom.org/trac/wiki/GrOsmoSDR gr-osmosdr] blocks, and also via the UHD API and blocks thanks to a SoapySDR module for this.<br />
<br />
=== Nuand BladeRF ===<br />
<br />
[http://nuand.com/ BladeRF] is a wideband transceiver that covers 300MHz to 3.8GHz, with coverage down to 10MHz made possible with the addition of a block up/down-converter.<br />
<br />
Host connection is via USB 3.0 and Nuand support use with Linux, Windows and Mac OS X. GNU Radio integration is provided via [http://sdr.osmocom.org/trac/wiki/GrOsmoSDR gr-osmosdr].<br />
<br />
=== Nuand BladeRF 2.0 ===<br />
<br />
[https://www.nuand.com/bladerf-2-0-micro/ BladeRF 2.0 Micro] is a wideband MIMO transceiver that covers 47MHz to 6GHz with up to 56 Mhz of bandwidth. It has 2x2 MIMO, and can accept an external clock (GPSDO). The channels are phase coherent and can be used for beam forming applications, as well as direction-finding. It has a clock out allowing for daisy-chaining multiple boards. It has an onboard Altera Cyclone V FPGA that's user programmable.<br />
<br />
Host connection is via USB 3.0 and Nuand support use with Linux, Windows and Mac OS X. GNU Radio integration is provided via [http://sdr.osmocom.org/trac/wiki/GrOsmoSDR gr-osmosdr].<br />
<br />
<br />
=== rtl-sdr TV tuners ===<br />
<br />
These are '''receive-only''' USB dongles based on the Realtek RTL2832, E4000 or FC0012 chips which are designed for DAB/DVB/FM. They can be used as SDR receivers over a frequency range that extends beyond popular television frequencies. Further information is available from [http://sdr.osmocom.org/trac/wiki/rtl-sdr osmocom]. GNU Radio integration is provided via [http://sdr.osmocom.org/trac/wiki/GrOsmoSDR gr-osmosdr] or [http://wiki.spench.net/wiki/Gr-baz gr-baz]. These USB dongles are sold from many vendors on the internet including on Amazon and E-Bay starting at around $10, new. There are some models with improved specification versions such as those from [https://www.nooelec.com/store/sdr/sdr-receivers.html Nooelectric] and [[https://www.rtl-sdr.com/buy-rtl-sdr-dvb-t-dongles/ RTL-SDR.COM] that cost up to about $40+. Performance, quality and features can widely vary between different manufactures and unit to unit. Usable performance at higher frequencies may require models with heatsinks, metal cases, etc. Options include RF frequency-range/performance, bias-T, xtal tolerance, TCXO, heatsink, ESD protection, case, ANT as SMA/U.FL/On-board-chip, etc. There are three RF chipsets commonly used in these dongles, the most common is based on the [https://www.rtl-sdr.com/tag/r820t2/ R820T2] with a frequency range of upto 25 MHz ~ 1750 MHz and is often used for [https://www.rtl-sdr.com/gr-air-modes-gnu-radio-ads-b-decoder-for-the-rtl-sdr-updated/ ADS-B monitoring]. While the less common [https://www.rtl-sdr.com/tag/e4000/ E4000] based has an extended range of 55 MHz - 2300 MHz, but the [https://www.nooelec.com/store/sdr/sdr-receivers/nesdr-smartee-xtr-sdr.html E4000 has a dead spot around 1100 MHZ and thus CAN NOT be used for ADS-B]. The least common RF chip is the [https://www.nooelec.com/store/sdr/sdr-receivers/sdr-receivers/eztv645-dvb-t-usb-set-for-sdr.html FC0013] with an RF range of 22 MHz - 2200 MHz. There is a small ecosystem of low-cost add-on [https://www.nooelec.com/store/sdr/sdr-addons/rf-blocks/flamingo-fm.html Filters], [https://www.nooelec.com/store/sdr/sdr-addons/sawbird.html LNAs](bias-T may be required), antennas, [https://www.amazon.com/Hobbyists-Guide-RTL-SDR-Software-Defined-ebook/dp/B00KCDF1QI books], videos, tutorials, bundled kits, etc that are available from multiple vendors on the Internet.<br />
<br />
=== SDRplay RSP family of SDR receivers ===<br />
<br />
These are 12/14bit SDRs giving up to 10MHz spectrum visibility from VLF (1kHz) up to 2GHz with no need for an upconverter. Further information is available from www.sdrplay.com. GNU Radio integration is provided via source blocks developed by HB9FXQ - these are available by following this flow: https://www.sdrplay.com/docs/gr-sdrplay-workflow.pdf<br />
<br />
=== Softrock-like Radio frequency interfaces ===<br />
<br />
Stemming from the amateur radio Softrock (Digital) Direct Conversion devices a family of radio front-ends evolved. The common principle is a direct conversion device that complex mixes the RF signal to base band (a.k.a. audio frequency), using a standard stereo audio interface for input and output. The I and Q channel are mapped to stereo left and right. Advanced devices offer a interface for frequency control and other parameters.<br />
<br />
=== [https://www.nooelec.com/store/sdr/sdr-receivers/sdr-receivers/yard-stick-one.html YARD Stick One] ===<br />
<br />
A sub 1 GHz half duplex transceiver. Status of GnuRadio drivers is unknown.<br />
<br />
== Using your Sound Card with GNU Radio ==<br />
<br />
Most computers nowadays are shipped with a built-in sound interface or sound card. Modern systems universally support input and output with 16 bit resolution at 48 ksps on two channels. Virtually every operating system supports this hardware out of the box, and it's sufficient for a lot of DIY and hobby applications. Additionally, high quality sound interfaces (professional digital audio recording equipment) are available with more than a dozen channels, up to 24bit resolution and 192 ksps.<br />
<br />
GNU Radio can use a sound card for both input and output. One way you can use this capability is to create audio interfaces. Do you remember the wonderful screeching and squawking of modems? You could use GNU Radio to experiment with similar communication techniques over audio.<br />
<br />
Another way to take advantage of GNU Radio's audio capability is to use a hardware device that converts between audio and RF. Platforms such as SoftRock can be used, in conjunction with GNU Radio and a sound card, to implement a complete radio.<br />
<br />
<br />
== IC Manufacturer's Development Boards ==<br />
<br />
Many IC makers create development boards for their ICs. Some of these are simple test fixture that just hold the IC while some are complete systems. ADI and Lime have released complete SDRs, listed above in the complete SDR section. As often the intent of these modules is to prove to customers the performance of the ICs these are often very high quality, if expensive boards that are not intended to be cost optimized solutions. These may be built on very expensive Rodgers PCB stock, while the inexpensive clones are often built on lower RF performing cheap FR4 board stock. <br />
<br />
=== Analog Devices FMCOMMS2/3/4/5 FMC cards + Xilinx Zynq carrier cards. ===<br />
<br />
'''The Analog Devices boards should not be regarded as stand alone SDR products, but as platforms that are used to either build commercial hardware products, or understand things at the lowest level. If you want to just experiment/use GNURadio, you are better off ignoring these boards, and going to [[Hardware#Ettus Research USRP™ Devices|Ettus section]].''' There are commercial systems which are based on Analog Devices's chips, that may be better supported in GNURadio, like the [http://www.ettus.com/product/details/UB200-KIT B200] or [http://www.ettus.com/product/details/UB210-KIT B210] from Ettus, or the [http://www.agile-sdr-solutions.com/#!sdr-starter-kit/c1rn8 ASRP4] from Agile Solutions.<br />
<br />
Analog Devices makes an [http://www.analog.com/AD9364 AD9364] based platform, which is on a evaluation board. The AD9364 is a 1Rx / 1Tx high performance, highly integrated RF Agile Transceiver™. Its programmability and wideband capability make it ideal for a broad range of transceiver applications. The device combines an RF front end with a flexible mixed-signal baseband section and integrated frequency synthesizers, simplifying design-in by providing a configurable digital interface to a processor. The AD9364 operates in the 70 MHz to 6.0 GHz range, covering most licensed and unlicensed bands. Channel bandwidths from less than 200 kHz to 56 MHz are supported.<br />
<br />
* [http://www.analog.com/ad-fmcomms4-ebz AD-FMCOMMS4-EBZ] : The AD-FMCOMMS4-EBZ board is a comes specifically tuned and optimized to 2.4 GHz and due to the limitations of the on-board discrete external components (baluns), it may exhibit diminished RF performance on some other programmed configurations.<br />
<br />
Analog Devices makes an [http://www.analog.com/AD9361 AD9361] based platform, which is on a few different evaluation boards. The AD9361 chip is ideal for a broad range of MIMO (2Rx, 2Tx)transceiver applications. Otherwise it is identical to the AD9364 (70 MHz to 6.0 GHz tuning range, 200 kHz to 56 MHz RF bandwidth). It can be found on:<br />
<br />
* [http://www.analog.com/ad-fmcomms2-ebz AD-FMCOMMS2-EBZ] : The AD-FMCOMMS2-EBZ board is a comes specifically tuned and optimized to 2.4 GHz and due to the limitations of the on-board discrete external components (baluns), it may exhibit diminished RF performance on some other programmed configurations.<br />
* [http://www.analog.com/ad-fmcomms3-ebz AD-FMCOMMS3-EBZ] : The AD-FMCOMMS3-EBZ provides software developers and system architect who want a single platform to operates over a wider tuning range than the AD-FMCOMMS2-EBZ. RF performance expectations of this board must be tempered with the very wide band front end. It does meet the datasheet specifications at 2.4 GHz, but does not over the entire RF tuning range that the board supports. This board is primarily intended for system investigation and bringing up various waveforms from a software team before custom hardware is complete.<br />
* [http://www.analog.com/ad-fmcomms5-ebz AD-FMCOMMS5-EBZ] : The AD-FMCOMMS5-EBZ is a high-speed analog module designed to showcase how to sync two AD9361 in multiple-input, multiple-output (4 Rx, 4 Tx MIMO) applications. The AD-FMCOMMS5-EBZ board has both wideband channels covering the full 6 GHz range, as well as narrowband channels matched to 2.4GHz. The AD-FMCOMMS5-EBZ also contains a calibration matrix between the two AD9361s. This switch matrix hardware, combined with the Analog Devices supplied API software, allow for a full digital and RF synchronization between the two AD9361s.<br />
* ADI also sells more integrated designs including the [https://www.analog.com/en/design-center/reference-designs/circuits-from-the-lab/CN0412.html#rd-overview CN0412 ADRV-PACKRF Robust Portable Radio Design] which is a complete RF SOM module. Carrier and break-out boards are available. See also the Pluto in the complete SDR section, above.<br />
<br />
The AD-FMCOMMSx series boards do not work as SDRs by themselves, and do require a some sort of FPGA platform. There are HDL designs for a few different Xilinx carriers : The [http://zedboard.org ZedBoard], [http://www.xilinx.com/zc702 Xilinx ZC702], [http://www.xilinx.com/zc706 Xilinx ZC706] and [http://zedboard.org/product/mini-itx Avnet's mini-itx], which are all based on Xilinx's [http://www.xilinx.com/products/silicon-devices/soc/zynq-7000.html Zynq]. All of these use an externally built [http://wiki.analog.com/resources/tools-software/linux-software/gnuradio GNURadio block].<br />
<br />
A number of 3rd part SDRs and devlopment boards are listed [https://www.analog.com/en/applications/technology/sdr-radioverse-pavilion-home/wideband-transceivers/wideband-solutions/3rd-party-reference-designs-and-hardware.html at ADI].<br />
<br />
These platforms are built on Linux in kernel drivers using the IIO subsystems, which was reviewed with an SDR use case at [http://ftp.osuosl.org/pub/fosdem//2015/devroom-software_defined_radio/iiosdr.mp4 FOSDEM 2015]<br />
<br />
== Building your Own Hardware ==<br />
<br />
Several designs are available for electronics enthusiasts interested in assembling their own SDR hardware. Open Source Hardware designs known to work with GNU Radio include:<br />
<br />
* [http://greatscottgadgets.com/hackrf/ HackRF]<br />
* [http://umtrx.org/ UmTRX]<br />
* various designs in the SoftRock family<br />
<br />
== Other options ==<br />
<br />
=== Comedi ===<br />
'''comedi support is dropped as of GNU Radio 3.8.'''<br />
<br />
The comedi project aims to offer drivers for many different data acquisition devices. GNU Radio includes a component that uses this library, which enables GNU Radio to use all devices support by comedi. Comedi is based on Linux kernel drivers, which results in good real time capabilities, but binds comedi to the Linux platform.<br />
<br />
=== The Catch-All Clause ===<br />
<br />
Every device that can be accessed from your operating system can be supported by GNU Radio. You can write your own drivers by creating source and sink blocks for your specific hardware.<br />
<br />
A very comprehensive and structured list about Software Defined Radio and Software Radio by Christophe F4DAN can be found at http://f4dan.free.fr/sdr_eng.html (this list has not been edited or updated since 2010 even though it still contains relevant information).<br />
<br />
If you cannot find support for your favourite device, ask at the mailing list for help. Maybe someone already got a working solution or wrote a block, or at least you can get tips and encouraging words for building a block for this hardware.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=WindowsInstall&diff=8375WindowsInstall2021-03-15T13:14:26Z<p>172.18.0.3: /* WSL | Ubuntu */</p>
<hr />
<div>= Windows Installation =<br />
<br />
== Current Windows Status ==<br />
<br />
Binary installers for 64-bit Windows 7/8/10 are now available [http://www.gcndevelopment.com/gnuradio/index.htm here]. These include all dependencies for Windows, a custom python distro, commonly used SDR drivers, and several OOT blocks.<br />
<br />
Windows binaries are also available through [https://conda-forge.org conda-forge] and the <code>conda</code> package manager. See the [[CondaInstall|conda install guide]] for details and instructions. Although this is not a standalone binary release, installation is relatively straightforward and GNU Radio Companion is provided through a link in the Start Menu. (As of 1 December 2020 this is the most up-to-date binary install option).<br />
<br />
There is also a build available at [https://github.com/pothosware/PothosSDR/wiki/Tutorial] that includes GnuRadio, Pothos, CubicSDK and other tools. This option has historically been updated about once or twice per year.<br />
<br />
Installing core GNU Radio and USRP on Windows is becoming more routine. Many OoT modules may still require compiling locally. Please report any success or failures. Patches and enhancements are especially welcome!<br />
<br />
== Windows Porting Issues ==<br />
<br />
Considerable effort has been put into making the GNU Radio code portable among various operating systems, but there are several reasons why it cannot be &quot;simply&quot; compiled and run under Windows:<br />
<br />
* The build and install procedures are based on Linux scripts and tools<br />
* Several third-party libraries are used, each with its own, often system-dependent, installation procedure<br />
* Most GNU Radio applications must interface to hardware (e.g., a sound card or USRP) which require system-dependent drivers and installation procedures<br />
* Because GNU Radio is written as an extension to Python, there are potential problems on Windows if different runtime libraries are used for GNU Radio and Python<br />
<br />
The following sections show how these issues can be addressed.<br />
<br />
== Installation Options ==<br />
<br />
GNU Radio is designed to be flexible. It has a number of modules, capabilities, and options that can be enabled or disabled to suit the needs of the user, and the user can add custom blocks or modules to the system.<br />
<br />
To support this flexibility, it comes with a set of files and scripts to be used with GNU software build tools (sh, make, autoconf, automake, etc.). These tools use Linux-like commands and filenames that are not normally available on Windows systems.<br />
<br />
Fortunately, we are not the first to face this problem, and several solutions exist. These are presented in order of increasing difficulty:<br />
<br />
=== Building on Windows with Native Tools ===<br />
<br />
Ettus Application note [https://kb.ettus.com/Building_and_Installing_the_USRP_Open_Source_Toolchain_(UHD_and_GNU_Radio)_on_Windows] describes how to build from source. Similar is a post at [https://lists.gnu.org/archive/html/discuss-gnuradio/2016-07/msg00108.html] for OOT modules.<br />
<br />
Powershell scripts are now available at https://www.github.com/gnieboer/gnuradio_windows_build_scripts that fully automate the build process for GNURadio 3.7.9.2+. A few build dependencies are required (MSVC 2015, Git, Doxygen, CMake, Perl, Wix) but all are free. The script has two options:<br />
<br />
# Build all dependencies from source (including python itself)<br />
# Download a prebuilt custom dependency package and then build only GNURadio and a few OOT modules on top.<br />
<br />
The binary installers described above are built with these scripts. They ensure that all dependencies are built with the same toolchain against the same runtime libraries, and handle the patches and configuration &quot;tweaks&quot; needed to build them on Windows.<br /><br />
If option 1 is desired, note that to build scipy, the non-free Intel Fortran compiler is required, gfortran cannot build objects that can link with MSVC C objects. If you do not have said compiler, the scripts will download pre-compiled wheels instead.<br />
<br />
More information on the build process is available on the GitHub repo readme, and also at http://www.gcndevelopment.com/gnuradio.<br />
<br />
GNURadio 3.6 has also been compiled on Windows using native tools as well (see http://voltronics.blogspot.com/2013/01/gnu-radio-windows-build-guide.html and https://lists.gnu.org/archive/html/discuss-gnuradio/2013-08/msg00284.html)<br />
<br />
More helpful tips on dependency version information have been reported:<br /><br />
https://lists.gnu.org/archive/html/discuss-gnuradio/2013-12/msg00497.html<br />
<br />
=== [[MinGW]]/MSYS ===<br />
<br />
[[MinGW]] (http://www.mingw.org/) provides GNU compilers and Window-specific header files for compiling native Windows applications.<br /><br />
MSYS (http://www.mingw.org/msys.shtml) is a companion set of Linux-like commands, shell, and build tools.<br /><br />
[[MinGW]] does not include a Linux programming interface; programs should be smaller and faster than with Cygwin (in theory), but will require more Windows-specific code.<br /><br />
MSYS is intended primarily as a build environment, making it more compact than Cygwin.<br />
<br />
Because there is no Linux API emulation, GNU Radio built with [[MinGW]] should be used with standard Windows versions of Python and the third-party libraries.<br /><br />
[[MinGW]] does not provide as much support as Cygwin for installing third-party libraries, but in many cases precompiled binaries are available.<br />
<br />
For detailed installation instructions using [[MinGW]] and MSYS see [[MingwInstallMain|Installing GNU Radio with MinGW]].<br />
<br />
=== Cygwin ===<br />
<br />
Cygwin (http://www.cygwin.com/) is a Linux-like environment for Windows.<br /><br />
It provides the Linux-like shell, file naming, and build tools we need and also makes it easy to install many of the third-party libraries required by GNU Radio. It also provides a Linux programming interface (API); this is not required by GNU Radio, but it lets us use the better-tested Linux versions of some functions.<br />
<br />
Because the Linux API uses its own C runtime library, it is best to use Cygwin versions of Python and the third-party libraries when building GNU Radio with Cygwin.<br />
<br />
For detailed installation instructions using Cygwin see [[CygwinInstallMain|Installing GNU Radio with Cygwin]].<br />
<br />
=== Chocolatey ===<br />
<br />
To quote from the [|https://chocolatey.org/ Chocolately homepage]: ''Chocolatey NuGet is a Machine Package Manager, somewhat like apt-get, but built with Windows in mind.''.<br />
<br />
There are packages for gnuradio (and it's dependencies) available in a separate repository (currently the best known source is: https://github.com/ariovistus/chocolatey-packages)<br />
<br />
To install, open an '''Administrative''' command line session and run:<br />
<br />
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))<br />
<br />
Now you need to install a ''source'' which has the recipes for gnuradio and dependants. The easiest method is to clone the chocolately-packages from the github repository listed above (https://github.com/ariovistus/chocolatey-packages), then add the local source from within an Administrative command line session:<br />
<br />
choco source add -name gnuradio -source C:\&lt;path-to&gt;\chocolatey-packages<br />
<br />
Create the numpy package:<br />
<br />
cd <path-to>\chocolatey-package\numpy<br />
cpack<br />
<br />
Create the gnuradio package:<br />
<br />
cd <path-to>\chocolatey-package\gnuradio<br />
cpack<br />
<br />
Now install the gnuradio package:<br />
<br />
choco install gnuradio<br />
<br />
Follow the command prompts.<br />
<br />
=== WSL | Ubuntu === <br />
<br />
Enable WSL from windows features. <br />
<br />
Install Ubuntu from Microsoft Store. <br />
<br />
Using the Ubuntu terminal, install gnuradio as you would on linux [https://wiki.gnuradio.org/index.php/InstallingGR#Ubuntu_PPA_Installation] <br />
<br />
Install additional package "libgtk-3-dev" <br />
<br />
sudo apt install libgtk-3-dev<br />
<br />
Install an X-server, either VcXsrv [https://sourceforge.net/projects/vcxsrv/] or Xming [https://sourceforge.net/projects/xming/] as WSL do not come with X server. VcXsrv is recommended as it is open source and self-contained instead of being tied to Cygwin, whereas Xming "asks for donations" to the developer as a dubious "sale" for non-existent support.<br />
<br />
Launch VcXsrv, making sure to select "Disable access control" option in the Extra settings so that any application can export to X11.<br />
<br />
Edit '''bashrc''' to setup the display by adding the following line at the bottom of the file <br />
# X11 forwarding for Windows<br />
export DISPLAY=$(awk '/nameserver / {print $2; exit}' /etc/resolv.conf 2>/dev/null):0<br />
export LIBGL_ALWAYS_INDIRECT=1<br />
<br />
Restart the Ubuntu terminal and run <br />
gnuradio-companion<br />
<br />
== Known Windows Build Issues ==<br />
<br />
So far, we have workarounds for all reported problems:<br />
<br />
* I got the following error after a clean install "This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.". I fixed this by finding qwindows.dll on my PC (for me it was in C:\Program Files\GNURadio-3.8\bin\platforms\), creating a new directory C:\Program Files\GNURadio-3.8\bin\plugins\platforms, and copying the 4 DLLs to C:\Program Files\GNURadio-3.8\bin\plugins\platforms (I had to create the "....plugins\platforms\" sub-directory). I'm sure there's a more elegant fix, but this seems to work.<br />
<br />
[[Category:Installation]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=GSoCIdeas&diff=8365GSoCIdeas2021-03-09T19:17:27Z<p>172.18.0.3: /* QT Widgets Improvements */</p>
<hr />
<div>== Summer of Code 2021: Project ideas list ==<br />
<br />
This is the list of project ideas for the summer of code 2021 within GNU Radio.<br /><br />
Remember that these are '''ideas''' and are merely meant as an inspiration for you to write your own proposal.<br />
<br />
Students who do not find a fit among these projects are encouraged to engage with us and suggest new ones. The [[MailingLists|GNU Radio discussion mailing list]] is the best place to contact all of us. Please do not contact us off-list for the sake of discussing the summer of code, unless you're contacting a mentor listed here to get feedback on a proposal.<br />
<br />
Reviewing the [https://developers.google.com/open-source/gsoc/faq Google GSoC FAQ] page for a broader understanding of project, mentor, and student responsibilities is recommended.<br />
<br />
If you need a USRP or other radio hardware to complete the project, we will be able to arrange something.<br />
<br />
Please add ideas to this list (you may cannibalize old ideas, of course!).<br />
<br />
Guidelines for good projects (when suggesting projects, please consider these):<br />
<br />
* Clearly defined scope, with a main target that can be done in 3 months at 50% capacity<br />
* Clear benefits for the GNU Radio project<br />
* Not specific to a certain hardware. No specific embedded devices, either, please.<br />
* Both OOTs and in-tree improvements are welcome<br />
<br />
'''The time a student can spend on a GSoC project has been reduced by 50% for 2021 - keep this in mind when submitting your ideas'''<br />
<br />
<br />
=== QT Widgets Improvements ===<br />
<br />
The gr-qtgui in-tree component provides some QT widgets for signal visualization. This component needs some improvement to become more useful.<br /><br />
This project is cleanly divided into several sub-projects:<br />
<br />
* Add a new widget<br />
** Compass display (e.g. for direction-finding applications)<br />
** MPEG display (e.g. for video demod output)<br />
** Matrix sink (e.g. for radar Doppler/range plane visualization, or 2D-equalizer taps visualization)<br />
<br />
* Improve current widgets<br />
** Better code structure to make the current widgets more manageable, extensible and remove code duplication between widgets<br />
** More Control Panels on other widgets (follow lead on the frequency sink)<br />
** Improve UI, make more intuitive, more power to mouse users<br />
** Set trigger point with mouse<br />
<br />
* Integration / Support for QT Creator<br />
** QML design<br />
** Allow to build full GUI applications from, say, GRC<br />
<br />
'''Prerequisites'''<br />
<br />
* Familiarity with QT is essential.<br />
* Widgets are written in C++, so some C++ knowledge is also required.<br />
* Python skills are highly useful.<br />
<br />
'''Mentor(s)'''<br />
<br />
Andrej Rode<br />
<br />
=== Standardized High Throughput FEC Codes ===<br />
<br />
Channel coding is essential to modern communications. Also, it is computationally very heavy. As of now, there exist implementations in GNU Radio which are too slow to be integrated into high throughput applications. GNU Radio would benefit from integration of standardized decoders for Turbo and LDPC codes. These codes would only support a certain subset of the whole code class but would be well optimized. <br />
<br />
'''Prerequisites'''<br />
<br />
* Understanding of ''gr-fec'' API. Knowledge on channel coding. Understanding of C++.<br />
<br />
'''Outcome'''<br />
<br />
* Standardized Codes, e.g. LTE Turbo Codes, 5G Polar Codes, 5G LDPC Codes, CCITT Convolutional Codes etc. are available in ''gr-fec''.<br />
* The preferred goal is to find a highly optimized implementation and integrate these into GNU Radio.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Johannes Demel<br />
<br />
<br />
=== GRC: View-Only Mode (Secure) ===<br />
<br />
When a flowgraph from an untrusted source is opened if GRC, arbitrary Python code can be executed. This poses a potential security risk. Storing the all evaluated values of all parameters within a flow graph (.grc) file would allow us to open such flow graphs without compromising security. No code would be have to executed to draw the flow graph and block parameters can be viewed safely. Only if the flow graph is modified the user would have to choose to trust the flow graph thus enabling normal eval operations.<br />
<br />
'''Prerequisites'''<br />
<br />
* GRC is implemented using Python. So, Python should be known pretty well.<br />
<br />
'''Outcome'''<br />
<br />
* Safely view other people's flowgraphs without putting your PC at risk.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Sebastian Koslowski<br />
<br />
<br />
=== gr-satellites: Viterbi decoder for 8b10b and FOX satellite decoder ===<br />
<br />
Even though the 8b10b line coding is primarily used for byte-level synchronization and spectral shaping, it adds some redundancy to the data, so it can be used as a forward error correction method to fix some bit errors in the received data. From the perspective of the decoder there is one bit of hidden state, so 8b10b line coding is amenable to Viterbi decoding, as hinted in [http://www.bigideatrouble.com/AMSAT%202013%20FOX1%20Paper.pdf this document about the AMSAT FOX satellites]. One goal of this project is to create Viterbi decoder block(s) for 8b10b and possibly other similar line codes, so that these blocks can be eventually upstreamed in-tree. The error correction performance of this method will be studied using simulations with these blocks. The second goal is to use the Viterbi decoder and gr-satellites to create a full decoder for the FOX satellites from AMSAT.<br />
<br />
'''Prerequisites'''<br />
<br />
* Knowledge of C++ and Python. Some basic understanding about FEC in general.<br />
<br />
'''Outcome'''<br />
<br />
* Viterbi decoder block(s) for 8b10b and similar line codes, FOX satellite decoder added to gr-satellites<br />
<br />
'''Mentor(s)'''<br />
<br />
* Daniel Estévez<br />
<br />
<br />
=== Runtime Benchmarks ===<br />
<br />
To facilitate development of a more modern GNU Radio runtime and scheduler, we need a tool to measure its performance (in terms of delay and throughput). This data is required to compare alternate approaches and to become aware of performance regressions early in the process.<br />
<br />
The goal of the project is to provide a tool to benchmark the GNU Radio runtime. Since we are interested in the performance on many platforms and architectures, it should provide an option to submit performance data to our sever, allowing us to crowdsource data. (Similar to our online stats for SIMD performance.)<br />
<br />
'''Outcome'''<br />
<br />
* Come up with interesting metrics and, if needed, implement blocks to extract them.<br />
* Come up with interesting flowgraph topologies that should be benchmarked.<br />
* Setup automated experiments that iterate over a given parameter space (repetitions, number of samples, size of the flowgraph).<br />
* Parse, evaluate, and visualize the data.<br />
* Add an option to upload the performance data to our web sever.<br />
<br />
'''Prerequisites'''<br />
<br />
* C++ programming<br />
* Data evaluation and visualization<br />
* Automation tools (like GNU Make to run benchmarks)<br />
<br />
'''Mentor(s)'''<br />
<br />
*Bastian Bloessl<br />
<br />
<div class="toccolours mw-collapsible mw-collapsed"><br />
<br />
== Summer of Code 2020: Project ideas list ==<br />
<div class="mw-collapsible-content"><br />
This is the list of project ideas for the summer of code 2020 within GNU Radio.<br /><br />
Remember that these are '''ideas''' and are merely meant as an inspiration for you to write your own proposal.<br />
<br />
Students who do not find a fit among these projects are encouraged to engage with us and suggest new ones. The [[MailingLists|GNU Radio discussion mailing list]] is the best place to contact all of us. Please do not contact us off-list for the sake of discussing the summer of code, unless you're contacting a mentor listed here to get feedback on a proposal.<br />
<br />
Reviewing the [https://developers.google.com/open-source/gsoc/faq Google GSoC FAQ] page for a broader understanding of project, mentor, and student responsibilities is recommended.<br />
<br />
If you need a USRP or other radio hardware to complete the project, we will be able to arrange something.<br />
<br />
Please add ideas to this list (you may cannibalize old ideas, of course!).<br />
<br />
Guidelines for good projects (when suggesting projects, please consider these):<br />
<br />
* Clearly defined scope, with a main target that can be done in 3 months<br />
* Clear benefits for the GNU Radio project<br />
* Not specific to a certain hardware. No specific embedded devices, either, please.<br />
* Both OOTs and in-tree improvements are welcome<br />
<br />
<br />
<br />
=== GRC: Build-in sub flowgraphs ===<br />
<br />
GNU Radio has the hierarchical blocks to build reuseable sub flowgraphs. These hier_blocks can be designed in GRC, however, they have to be compiled to code and GRC bindings, before they can be used in other GRC files. While this is great for reuseablity across flowgraphs, it is quite cumbersome when the main use is to structure a single (larger) flowgraph. The goal of this project is to ease this use-case by embedding sub flowgraphs directly in the main GRC file. Instead of creating bindings and code and then parsing them back again, this process shall be done in-place to allow quickly editing sub flowgraphs on-the-fly. <br />
<br />
'''Prerequisites'''<br />
<br />
* GRC is written in Python which is (almost) all you need to know for this project.<br />
<br />
'''Outcome'''<br />
<br />
* A vastly improved workflow for structuring flowgraphs<br />
<br />
'''Mentor(s)'''<br />
<br />
* Sebastian Koslowski<br />
<br />
<br />
<br />
=== Qt5 GUI Integrations ===<br />
<br />
Idea: Wrap the Qt GUI sinks to appear in QtCreator, including the GUI aspects of their parameterization<br />
<br />
'''Prerequisites'''<br />
<br />
* C++, Python proficiency<br />
* Qt experienced<br />
<br />
'''Outcome'''<br />
<br />
* Qt GUI Sinks usable as widgets in QtCreator (not necessarily already showing an "empty" GUI, just placeholders)<br />
* Possible to import generate Qt GUI description file (UIC) into GRC<br />
* Interface to map placeholders from GUI design to Qt GUI sinks in Flow graph<br />
* Integration of that into GRC-generated Python code<br />
<br />
'''Mentor(s)'''<br />
<br />
* Marcus Müller & Sebastian "GRC-Man" Koslowski<br />
<br />
<br />
<br />
=== Extending and Updating gr-radar ===<br />
<br />
gr-radar (https://github.com/kit-cel/gr-radar/) was a great and successful GSoC project that provided a few methods of radar in GNU Radio. This module is heavily used by academics, researchers, cybersecurity folks, and hobbyists. This project would work to improve upon the concepts already in there as well as add more radar techniques.<br />
<br />
There are uncountable methods and techniques that could be added to this project, such as:<br />
<br />
* SAR / InSAR methods<br />
* Better passive radar support<br />
* Speed camera applications<br />
* Multi-antenna radar techniques<br />
<br />
'''Prerequisites'''<br />
<br />
* Signal processing and some radar basics are required.<br />
* Code is written in C++ with some Python on the side, so the student must be able to handle these languages at the least.<br />
<br />
'''Outcome'''<br />
<br />
* Based on the student's interest, a subset of the radar techniques listed above (or others) are chosen as milestones for this project. <br />
* All code must be merged back into gr-radar by the end of the summer.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Stefan Wunsch, Martin Braun<br />
<br />
<br />
<br />
=== QT Widgets Improvements ===<br />
<br />
The gr-qtgui in-tree component provides some QT widgets for signal visualization. This component needs some improvement to become more useful.<br /><br />
This project is cleanly divided into several sub-projects:<br />
<br />
* Add new widgets<br />
** Compass display (e.g. for direction-finding applications)<br />
** MPEG display (e.g. for video demod output)<br />
** Matrix sink (e.g. for radar Doppler/range plane visualization, or 2D-equalizer taps visualization)<br />
<br />
* Improve current widgets<br />
** Better code structure to make the current widgets more manageable, extensible and remove code duplication between widgets<br />
** More Control Panels on other widgets (follow lead on the frequency sink)<br />
** Improve UI, make more intuitive, more power to mouse users<br />
** Set trigger point with mouse<br />
<br />
* Integration / Support for QT Creator<br />
** QML design<br />
** Allow to build full GUI applications from, say, GRC<br />
<br />
'''Prerequisites'''<br />
<br />
* Familiarity with QT is essential.<br />
* Widgets are written in C+'', so some C''+ knowledge is also required.<br />
* Python skills are highly useful.<br />
<br />
'''Mentor(s)'''<br />
<br />
Tim O'Shea<br />
<br />
<br />
<br />
<br />
<br />
<br />
=== Android ===<br />
<br />
One effort of the past years was to improve Android support for GNU Radio. We're getting to a point where we've figured out '''how''' to do it, so the next step is to make it more accessible to users and developers.<br />
<br />
The Android ecosystem is an entirely different beast from the rest of GNU Radio. To make writing Android/GR apps easy, the following needs to happen (and shall be part of this project):<br />
<br />
* Improve support for development environment<br />
** Create Dockers for easy start of development<br />
* Visualization classes for PSD, spectrogram and oscilloscope<br />
** Easy reuse in other apps, like the gr-qtgui widgets, but for Android SDKs<br />
* Interactivity concepts<br />
** Gestures and config for radio parameters (e.g., freq, gain, bandwidth)<br />
** Create an example FM receiver app that allows easy channel selection etc. through motions and gestures<br />
<br />
You can find a summary of the work that has been done on this (years ago) here: [[Android]]<br />
<br />
'''Prerequisites'''<br />
<br />
* Some Android experience<br />
* Enjoy writing GUI widgets<br />
* C++/Java experience<br />
<br />
'''Mentor(s)'''<br />
<br />
* Bastian Bloessl<br />
<br />
=== Runtime Benchmarks ===<br />
<br />
To facilitate development of a more modern GNU Radio runtime and scheduler, we need a tool to measure its performance (in terms of delay and throughput).<br />
This data is required to compare alternate approaches and to become aware of performance regressions early in the process.<br />
<br />
The goal of the project is to provide a tool to benchmark the GNU Radio runtime. Since we are interested in the performance on many platforms and architectures, it should provide an option to submit performance data to our sever, allowing us to crowdsource data. (Similar to our [http://stats.gnuradio.org/ online stats] for SIMD performance.)<br />
<br />
* Come up with interesting metrics and, if needed, implement blocks to extract them.<br />
* Come up with interesting flowgraph topologies that should be benchmarked.<br />
* Setup automated experiments that iterate over a given parameter space (repetitions, number of samples, size of the flowgraph).<br />
* Parse, evaluate, and visualize the data.<br />
* Add an option to upload the performance data to our web sever.<br />
<br />
'''Prerequisites'''<br />
<br />
* C++ programming<br />
* Data evaluation and visualization<br />
* Automation tools (like GNU Make to run benchmarks)<br />
<br />
'''Mentor(s)'''<br />
<br />
* Bastian Bloessl, Marcus Mueller<br />
<br />
=== Filter Design Tool Enhancements ===<br />
<br />
GNU Radio provides many tools to design and use digital filters. Using these tools requires both some expertise in these areas as well as an understanding of the performance on the given platform. One example is the selection between FIR (convolution-based) and FFT (fast convolution-based) filters for different resampling rates. Another example is doing stages of filter decomposition when doing large down-sampling. Included in this is the polyphase filterbanks, which again are provided as primitive blocks that need tweaking to work.<br />
<br />
This project is to improve our uses of these tools and blocks to make it more obvious to the users as well as automate some of the decisions for optimally using them. Some pointers:<br />
<br />
* When used in GRC, we want to save the results of the tool in a local file or for use in actual blocks.<br />
* It still currently runs on PyQWT, which is obsolete and needs to be updated to Qt5<br />
** See https://github.com/trondeau/gnuradio/tree/filter/design_tool_newgui<br />
* Add more support for filter design concepts and other filters.<br />
** Cascaded filters<br />
** Better support for creating PFB filters<br />
<br />
'''Prerequisites'''<br />
<br />
* Strong DSP background required.<br />
* Python and QT knowledge highly useful (at least one of those is a must).<br />
<br />
'''Mentor(s)'''<br />
<br />
* Marcus Leech<br />
<br />
<br />
<br />
=== Implement SigMF functionality for the GNU Radio Ecosystem ===<br />
<br />
SigMF is the "Signal Metadata Format" that was defined during the 2017 DARPA Hackfest in Brussels. Its purpose is to annotate raw binary dumps of signals with metadata, thus giving meaning to a raw mass of samples.<br /><br />
SigMF is specified and has a minimal reference implementation here: https://github.com/gnuradio/sigmf<br />
There is an out-of-tree module providing SigMF functionality for GNU Radio as well: https://github.com/skysafe/gr-sigmf<br />
<br />
However, SigMF is not represented well in the GNU Radio tooling landscape. Therefore, a subset of tools can be extended by SigMF support. Incomplete lists of possible tools benefitting from SigMF support:<br />
<br />
* qgrx (https://github.com/csete/gqrx)<br />
* inspectrum (https://github.com/miek/inspectrum)<br />
* ...<br />
<br />
Any additional tools are welcome in a proposal.<br />
<br />
'''Prerequisites'''<br />
<br />
* Knowledge of the programming language of the covered tools.<br />
* Hands-on experience with the respective tools.<br />
* Familiarity with the SigMF specification.<br />
<br />
'''Outcome'''<br />
<br />
* The tools worked on have capability to load and save files in the SigMF format.<br />
* Depending on the specific tool, SigMF meta data is displayed within the tool.<br />
* The number of tools worked on needs to be determined by the student, depending on his/her experience.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Sebastian Müller, Andrej Rode<br />
<br />
<br />
<br />
=== Statistical Toolbox for GRC ===<br />
<br />
A statistical toolbox for GRC would enable GUI-based statistical analysis. Currently, such analysis can be done by writing an independent program (e.g., with SciPy), but there is no actual integration with GNU Radio. By developing the statistical toolbox, we provide blocks for probability distribution fitting, hypothesis testing, extracting statistical parameters for one-dimensional as well as multi-dimensional data. This would significantly expand GNU Radio users' ability to perform data-science analysis and modeling on signal data.<br />
<br />
'''Prerequisites'''<br />
<br />
* Understanding of existing GNU Radio tools (e.g., GRC), GNU Radio Out-of-Tree Modules, and statistics / data-science modeling.<br />
<br />
'''Outcome'''<br />
<br />
* An OOT module that provides statistical analysis capabilities for GNU Radio.<br />
<br />
'''Mentor(s)'''<br />
<br />
* Ben Hilburn<br />
<br />
</div><br />
</div><br />
== Application process ==<br />
<br />
Students interested in participating, read the [[GSoCStudentInfo|student instructions]] and the [[GSoCManifest|rules of conduct]].<br />
* Please introduce yourself on the [https://lists.gnu.org/mailman/listinfo/discuss-gnuradio GNU Radio mailing list]<br />
* Fill in the formal application for GNU Radio<br />
* Pick some items from the list above or feel free to suggest another piece of work relevant to this theme. Give us a detailed, week-by-week plan for completing the task over the summer.</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Talk:Guided_Tutorial_PSK_Demodulation&diff=8260Talk:Guided Tutorial PSK Demodulation2021-02-22T05:46:25Z<p>172.18.0.3: Provide non-obvious data needed to duplicate tutorial steps.</p>
<hr />
<div>* '''Implementation Hints'''<br />
I greatly appreciate this guided tutorial.<br />
I had some difficulties following along, so the following might help others using this tutorial.<br />
<br />
* '''Hints for mpsk_rrc_rolloff.grc'''<br />
1. Constellation Modulator property Constellation = qpsk_const<br />
This setting uses the Constellation Rect. Object id qpsk_const (aka <con...(m=4))<br />
<br />
* '''Hints for mpsk_stage1.grc'''<br />
1. Constellation Modulator property Constellation = qpsk_const<br />
This setting uses the Constellation Rect. Object id qpsk_const (aka <con...(m=4))<br />
2. Variable id rrc_taps value is firdes.root_raised_cosine(1.0,samp_rate,samp_rate/sps,excess_bw,11*sps).<br />
See [[file:///usr/share/doc/packages/gnuradio/html/firdes_8h_source.html | GNU Radio Manual and C++ API Reference]]<br />
/*!<br />
* \brief design a Root Cosine FIR Filter (do we need a window?)<br />
*<br />
* \param gain overall gain of filter (typically 1.0)<br />
* \param sampling_freq sampling freq (Hz)<br />
* \param symbol_rate symbol rate, must be a factor of sample rate<br />
* \param alpha excess bandwidth factor<br />
* \param ntaps number of taps<br />
*/</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=Types_of_Blocks&diff=8259Types of Blocks2021-02-21T13:12:46Z<p>172.18.0.3: /* Decimation Block */</p>
<hr />
<div>[[Category:Usage Manual]]<br />
== Introduction ==<br />
<br />
To take advantage of the gnuradio framework, users will create various blocks to implement the desired data processing. There are several types of blocks to choose from:<br />
<br />
* Synchronous Blocks (1:1)<br />
* Decimation Blocks (N:1)<br />
* Interpolation Blocks (1:M)<br />
* Basic (a.k.a. General) Blocks (N:M)<br />
<br />
== Synchronous Block ==<br />
<br />
The sync block allows users to write blocks that consume and produce an equal number of items per port. A sync block may have any number of inputs or outputs. When a sync block has zero inputs, its called a source. When a sync block has zero outputs, its called a sink.<br />
<br />
An example sync block in C++:<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gr_sync_block.h> <br />
<br />
class my_sync_block : public gr_sync_block<br />
{<br />
public:<br />
my_sync_block(...):<br />
gr_sync_block("my block", <br />
gr_make_io_signature(1, 1, sizeof(int32_t)),<br />
gr_make_io_signature(1, 1, sizeof(int32_t)))<br />
{<br />
//constructor stuff<br />
}<br />
<br />
int work(int noutput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items)<br />
{<br />
//work stuff...<br />
return noutput_items;<br />
}<br />
};<br />
</syntaxhighlight><br />
<br />
Some observations:<br />
<br />
* noutput_items is the length in items of all input and output buffers<br />
* an input signature of gr_make_io_signature(0, 0, 0) makes this a source block<br />
* an output signature of gr_make_io_signature(0, 0, 0) makes this a sink block<br />
<br />
An example sync block in Python:<br />
<br />
<syntaxhighlight lang="python"><br />
class my_sync_block(gr.sync_block):<br />
def __init__(self):<br />
gr.sync_block.__init__(self,<br />
name = "my sync block",<br />
in_sig = [numpy.float32, numpy.float32],<br />
out_sig = [numpy.float32],<br />
)<br />
def work(self, input_items, output_items):<br />
output_items[0][:] = input_items[0] + input_items[1]<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
The input_items and output_items are lists of lists. The input_items<br />
contains a vector of input samples for every input stream, and the<br />
output_items is a vector for each output stream where we can place<br />
items. Then length of output_items[0] is equivalent to the<br />
noutput_items concept we are so familiar with from the C++ blocks.<br />
<br />
Some observations:<br />
* The length of all input vector and all output vectors is identical<br />
* in_sig=None would turn this into a source block<br />
* out_sig=None would turn this into a sink block. In this case, use len(input_items [0]) since output_items is empty!<br />
* Unlike in C++ where we use the gr::io_signature class, here we can just create a Python list of the I/O data sizes using numpy data types, e.g.: numpy.int8, numpy.int16, numpy.float32<br />
<br />
== Decimation Block ==<br />
<br />
The decimation block is another type of fixed rate block where the number of input items is a fixed multiple of the number of output items.<br />
<br />
An example decimation block in c++<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gr_sync_decimator.h><br />
<br />
class my_decim_block : public gr_sync_decimator<br />
{<br />
public:<br />
my_decim_block(...):<br />
gr_sync_decimator("my decim block", <br />
in_sig,<br />
out_sig,<br />
decimation)<br />
{<br />
//constructor stuff<br />
}<br />
<br />
//work function here...<br />
};<br />
</syntaxhighlight><br />
<br />
Some observations:<br />
<br />
* The gr_sync_decimator constructor takes a 4th parameter, the decimation factor<br />
* The user should assume that the number of input items = noutput_items*decimation<br />
<br />
An example decimation block in Python:<br />
<br />
<syntaxhighlight lang="python"><br />
class my_decim_block(gr.decim_block):<br />
def __init__(self, args):<br />
gr.decim_block.__init__(self,<br />
name="my block",<br />
in_sig=[numpy.float32],<br />
out_sig=[numpy.float32],<br />
decim = 1.0)<br />
self.set_relative_rate(1.0/decimation)<br />
<br />
#work function here...<br />
</syntaxhighlight><br />
<br />
Some observations:<br />
<br />
* The set_relative_rate call configures the input/output relationship<br />
* To set an interpolation, use self.set_relative_rate(interpolation)<br />
* The following will be true len(input_items[i]) = len(output_items[j])*decimation<br />
<br />
== Interpolation Block ==<br />
<br />
The interpolation block is another type of fixed rate block where the number of output items is a fixed multiple of the number of input items.<br />
<br />
An example interpolation block in c++<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gr_sync_interpolator.h><br />
<br />
class my_interp_block : public gr_sync_interpolator<br />
{<br />
public:<br />
my_interp_block(...):<br />
gr_sync_interpolator("my interp block", <br />
in_sig,<br />
out_sig,<br />
interpolation)<br />
{<br />
//constructor stuff<br />
}<br />
<br />
//work function here...<br />
};<br />
</syntaxhighlight><br />
Some observations:<br />
<br />
* The gr_sync_interpolator constructor takes a 4th parameter, the interpolation factor<br />
* The user should assume that the number of input items = noutput_items/interpolation<br />
<br />
An example interpolation block in Python:<br />
<br />
<syntaxhighlight lang="python"><br />
class my_interp_block(gr.interp_block):<br />
def __init__(self, args):<br />
gr.interp_block.__init__(self,<br />
name="my block",<br />
in_sig=[numpy.float32],<br />
out_sig=[numpy.float32])<br />
self.set_relative_rate(interpolation)<br />
<br />
#work function here...<br />
</syntaxhighlight><br />
<br />
== Basic Block ==<br />
<br />
The basic block provides no relation between the number of input items and the number of output items. All other blocks are just simplifications of the basic block. Users should choose to inherit from basic block when the other blocks are not suitable.<br />
<br />
The adder revisited as a basic block in C++:<br />
<br />
<syntaxhighlight lang="cpp"><br />
#include <gr_block.h><br />
<br />
class my_basic_block : public gr_block<br />
{<br />
public:<br />
my_basic_adder_block(...):<br />
gr_block("another adder block",<br />
in_sig,<br />
out_sig)<br />
{<br />
//constructor stuff<br />
}<br />
<br />
int general_work(int noutput_items,<br />
gr_vector_int &ninput_items,<br />
gr_vector_const_void_star &input_items,<br />
gr_vector_void_star &output_items)<br />
{<br />
//cast buffers<br />
const float* in0 = reinterpret_cast(input_items[0]);<br />
const float* in1 = reinterpret_cast(input_items[1]);<br />
float* out = reinterpret_cast(output_items[0]);<br />
<br />
//process data<br />
for(size_t i = 0; i < noutput_items; i++) {<br />
out[i] = in0[i] + in1[i];<br />
}<br />
<br />
//consume the inputs<br />
this->consume(0, noutput_items); //consume port 0 input<br />
this->consume(1, noutput_items); //consume port 1 input<br />
//this->consume_each(noutput_items); //or shortcut to consume on all inputs<br />
<br />
//return produced<br />
return noutput_items;<br />
}<br />
};<br />
</syntaxhighlight><br />
<br />
Some observations:<br />
<br />
* This class overloads the general_work() method, not work()<br />
* The general work has a parameter: ninput_items<br />
** ninput_items is a vector describing the length of each input buffer<br />
* Before return, general_work must manually consume the used inputs<br />
* The number of items in the input buffers is assumed to be noutput_items<br />
** Users may alter this behaviour by overloading the forecast() method<br />
<br />
The adder revisited as a basic block in Python:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
import gnuradio.extras<br />
<br />
class my_basic_adder_block(gr.basic_block):<br />
def __init__(self, args):<br />
gr.basic_block.__init__(self,<br />
name="another_adder_block",<br />
in_sig=[...],<br />
out_sig=[...])<br />
self.set_auto_consume(False)<br />
<br />
def forecast(self, noutput_items, ninput_items_required):<br />
#setup size of input_items[i] for work call<br />
for i in range(len(ninput_items_required)):<br />
ninput_items_required[i] = noutput_items<br />
<br />
def work(self, input_items, output_items):<br />
#buffer references<br />
in0 = input_items[0][:len(output_items[0])]<br />
in1 = input_items[1][:len(output_items[0])]<br />
out = output_items[0]<br />
<br />
#process data<br />
out[:] = in0 + in1<br />
<br />
//consume the inputs<br />
self.consume(0, len(in0)) //consume port 0 input<br />
self.consume(1, len(in1)) //consume port 1 input<br />
#self.consume_each(len(out)) //or shortcut to consume on all inputs<br />
<br />
#return produced<br />
return len(out)<br />
</syntaxhighlight></div>172.18.0.3https://wiki.gnuradio.org/index.php?title=InstallingGR&diff=8254InstallingGR2021-02-20T20:41:50Z<p>172.18.0.3: Added LiSDR distro</p>
<hr />
<div>= From Binaries =<br />
<br />
The recommended way to install GNU Radio on most platforms is using already available binary packages (see [[#Ubuntu_PPA_Installation|Ubuntu PPA Installation]]). For some platforms there are no binaries provided by available package managers or the GNU Radio project. In these cases please contact the maintainer of the package manager or the GNU Radio project to find a sensible way to provide binaries for your platform.<br />
<br />
In addition to using binaries, GNU Radio can be installed:<br />
<br />
# '''[[#From_Source|From source]]''' (for those who want full control)<br />
# '''[[#Using_PyBOMBS|Using PyBOMBS]]''' (for those who want it built from source and/or installed to a specific directory using a script)<br />
# '''[[CondaInstall|Using conda]]''' (for those who want binaries installed to managed environments)<br />
<br />
== Linux ==<br />
<br />
Most distributions contain a package named <code>gnuradio</code> or similar in their standard repositories. For most use cases it is enough to install this package and start developing.<br />
<br />
The development of GNU Radio can be fast-paced, and binaries provided by your distribution may be outdated. '''Do check if the version you're installing is up to date! Sometimes old versions are not updated in the packaging systems.''' If you find a bug in a older GNU Radio version, please check if the bug still exists in the newer version of GNU Radio before filing a new issue.<br />
<br />
If the version shipped in your distribution is outdated please contact the corresponding maintainer to update it in the packaging system.<br />
<br />
Here are examples of how to install GNU Radio in various Linux distributions (click the link under &quot;Distribution&quot; for more installation details):<br />
<br />
{|class="wikitable" style="margin: auto; width: 90%;"<br />
!scope="col"|Distribution<br />
!scope="col"|Command<br />
|-<br />
| [[UbuntuInstall|Debian/Ubuntu and derivates]]<br />
| <pre>$ apt install gnuradio</pre><br />
|-<br />
| [[FedoraInstall|Fedora]]<br />
| <pre>$ dnf install gnuradio</pre><br />
|-<br />
| RHEL/CentOS<br />
| <pre>$ yum install gnuradio</pre><br />
|-<br />
| [[ArchInstall|Archlinux]]<br />
| <pre>$ pacman -S gnuradio</pre><br />
|-<br />
| [[GentooInstall|Gentoo Linux]]<br />
| <pre>$ emerge net-wireless/gnuradio</pre><br />
|-<br />
| [[SuseInstall|Suse Linux]]<br />
|<br />
|-<br />
|}<br />
<br />
On other distributions, simply use the appropriate package management command to install the <code>gnuradio</code> package and then please add it to this list. If you need newer versions or have a different platform please contact the package maintainer of your distribution or raise your issue on the mailing list.<br />
<br />
=== Ubuntu PPA Installation ===<br />
For Ubuntu, the latest builds (both released and pulled from master branch) are maintained as PPAs on [https://launchpad.net/~gnuradio launchpad]. Be sure to uninstall any previously installed versions of gnuradio first.<br />
<br />
To access the latest from the master branch, add the gnuradio/gnuradio-master ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-master</pre><br />
<br />
To access the current released version (3.9), add the gnuradio/gnuradio-releases ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases</pre><br />
<br />
To access the 3.8 released version, add the gnuradio/gnuradio-releases-3.8 ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.8</pre><br />
<br />
To access the 3.7 released version (legacy), add the gnuradio/gnuradio-releases-3.7 ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.7</pre><br />
<br />
Then, update the apt sources, and install gnuradio<br />
<pre>$ sudo apt-get update</pre><br />
<pre>$ sudo apt install gnuradio</pre><br />
<br />
==== Modtool on Ubuntu ====<br />
NOTE: On released builds for Ubuntu 18 (bionic), there is an issue using gr_modtool after GNU Radio has been installed from the PPA. This is due to byte-compiled code that remains in the modtool templates after installation. To workaround this issue:<br />
<br />
<pre>$ cd /usr/share/gnuradio/modtool/templates/gr-newmod<br />
$ sudo py3clean .</pre><br />
<br />
This issue does not appear for Ubuntu 19 and later packages<br />
<br />
=== Fedora COPR Installation ===<br />
<br />
Packages are available for Fedora 29,30,31 hosted under COPR:<br />
<br />
https://copr.fedorainfracloud.org/coprs/gnuradio/<br />
<br />
1. Add the repository:<br />
<br />
-- For the latest released version:<br />
<pre>$ sudo dnf copr enable gnuradio/gnuradio-releases </pre><br />
-- For the latest pull from git master:<br />
<pre>$ sudo dnf copr enable gnuradio/gnuradio-master </pre><br />
<br />
2. Install GNU Radio<br />
<pre>$ sudo dnf install gnuradio </pre><br />
<br />
===Raspberry Pi ===<br />
<br />
There is a pre-built 32 bit SDR flavored Raspberry Pi OS distro, [https://github.com/luigifcruz/pisdr-image PiSDR] that includes GnuRadio and other SDR utilities.<br />
<br />
== Windows ==<br />
<br />
Binary installers are now available for GNU Radio 3.7 and 3.8, download them [http://www.gcndevelopment.com/gnuradio/index.htm here].<br /><br />
Conda packages are an alternative for installing binaries for GNU Radio 3.8+. See the [[CondaInstall|conda install guide]].<br /><br />
If you need to install GNU Radio from source refer to the [[WindowsInstall|Windows install guide]].<br />
<br />
Note: We do not officially support Windows. We do our best to provide installation instructions and work out build bugs on Windows when they are reported and patches received. As new versions of GNU Radio, its dependencies, and Windows itself come out, however, keeping all of them working together is beyond the scope of what the project currently supports. User updates to the above wiki installation instructions are very welcome.<br />
<br />
== Mac OS X ==<br />
<br />
Refer to the [[MacInstall|Mac OS X install guide page]].<br />
<br />
= From Source =<br />
<br />
Binary installation should be sufficient for most users, and certainly for anyone who is new to GNU Radio. However, if you have special requirements, want the latest version, or the binary packages are not working for you, you may want to install GNU Radio from source.<br />
<br />
=== Notes ===<br />
<br />
* By default GNU Radio will be installed in the /usr/local directory. See notes about -DCMAKE_INSTALL_PREFIX to install it elsewhere.<br />
* Running and developing out-of-tree modules does not require GNU Radio to be installed from source. <br />
* If you are using Ubuntu, see [[UbuntuInstall#Install_the_Pre-Requisites]] for installing dependencies / pre-requisites.<br />
* If you want to use GNU Radio with a USRP, you <b>FIRST</b> must clone and install UHD. See [[https://kb.ettus.com/Building_and_Installing_the_USRP_Open-Source_Toolchain_(UHD_and_GNU_Radio)_on_Linux UHD Installation Page]] for more info.<br />
* To install on a Raspberry Pi, see [[InstallingGRFromSource on Raspberry Pi]].<br />
* To build from source from within a conda environment, see [[CondaInstall#Building GNU Radio from source within a conda environment|the conda install guide]].<br />
<br />
=== For the GNU Radio Master Branch ===<br />
<br />
Since Volk is no longer considered as a submodule of GNU Radio (GNU Radio commit #80c04479da962d048d41165081b026aafdaa0316 ),<br> you <b>MUST FIRST</b> install Volk, and then install GNU Radio. <br />
<br />
The basic idea is the same, but instead of building Volk along with GNU Radio, you need to clone and build it separately. For this example, we will start in the home directory. You can, of course, use any directory you wish and the results will be the same.<br />
<br />
==== Step 1: Installing Dependencies ====<br />
<br />
Refer to [[InstallingGR#Linux|this page for your specific Linux distro]] to find how to install dependencies. For example, on Ubuntu 20.04 [[UbuntuInstall#Focal Fossa (20.04)|use this command]].<br />
<br />
==== Step 2: Installing Volk ====<br />
<br />
* <code>cd</code><br />
* <code>git clone --recursive https://github.com/gnuradio/volk.git</code><br />
* <code>cd volk</code><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code><br />
* <code>make</code><br />
* <code>make test</code><br />
* <code>sudo make install</code><br />
<br />
If you're running Linux, then always remember to do the following command after installing any library:<br />
* <code>sudo ldconfig</code><br />
<br />
==== Step 3: Installing GNU Radio ====<br />
* <code>cd</code><br />
* <code>git clone https://github.com/gnuradio/gnuradio.git</code><br />
* <code>cd gnuradio</code><br />
<b>Note:</b> If you want to build the <code>maint-3.9</code> branch rather than the default <code>master</code> branch, enter:<br />
<code>git checkout maint-3.9</code> and then<br><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
<b>Note:</b> In the following command, you can add <code>-DCMAKE_INSTALL_PREFIX=XXX</code> to install GNU Radio into the PREFIX <code>XXX</code>; if not specified, then the PREFIX is <code>/usr/local</code>. See other CMake options in [[#Common_cmake_flags|Common cmake flags]].<br><br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code><br />
* <code>make -j3</code> (e.g. if you want to use 3 CPU cores during the build. To use 8 do -j8, to use 1 leave out the -j flag.)<br />
<b>Note:</b> In the following command, it is very possible that not all tests pass. Generally any error is a sign of a missing dependency such as the Python interface to ZMQ or NumPy or SciPy, none of which are required for building GNU Radio but are required for testing.<br />
* <code>make test</code><br />
* <code>sudo make install</code><br />
If you're running Linux, then always remember to do the following command after installing any library:<br />
* <code>sudo ldconfig</code><br />
If you encounter "Cannot import gnuradio" error, then go to [[ModuleNotFoundError#B._Finding_the_Python_library|Finding the Python library]] to set your PYTHONPATH and LD_LIBRARY_PATH.<br><b>After setting these environment variables, you need to do</b> <code>sudo ldconfig</code> <b>again</b> for the Linux dynamic library loader to find the just-installed GNU Radio libraries.<br><br />
If you have installed in a custom path with <code>-DCMAKE_INSTALL_PREFIX=XXX</code>, you will need to add that path to $PATH in order to find gnuradio-companion.<br />
<br />
=== For GNU Radio 3.8 or Earlier ===<br />
For this example, we will start in the home directory; you can, of course, use any directory you wish and the results will be the same.<br />
<br />
* <code>cd</code><br />
* <code>git clone https://github.com/gnuradio/gnuradio.git</code><br />
* <code>cd gnuradio</code><br />
<br />
<b>Note:</b> In the following command, change <code>maint-3.8</code> to some other branch or tag if you want to build a different version of GNU Radio; see [https://github.com/gnuradio/gnuradio/tags tags] for tagged releases including pre-releases ("rc"). For [https://github.com/gnuradio/gnuradio/branches branches], it's generally wise to stick with "master" (the default after cloning), and, currently: <code>maint-3.7</code> or <code>maint-3.8</code>. Here we checkout the <code>maint-3.8</code> branch, which contains the latest 3.8 release plus any fixes or augmentations to it that will be in the next 3.8 release.<br />
<br />
* <code>git checkout maint-3.8</code><br />
* <code>git submodule update --init --recursive</code><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
<br />
<b>Note:</b> In the following command, you can add <code>-DCMAKE_INSTALL_PREFIX=XXX</code> to install GNU Radio into the PREFIX <code>XXX</code>; if not specified, then the PREFIX is <code>/usr/local</code>. See other CMake options in [[#Common_cmake_flags|Common cmake flags]].<br><br />
<br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code><br />
* <code>make -j3</code> (e.g. if you want to use 3 CPU cores during the build. To use 8 do -j8, to use 1 leave out the -j flag.)<br />
* <code>sudo make install</code><br />
<br />
If you're running Linux, then always remember to do the following command after installing any library:<br />
* <code>sudo ldconfig</code><br />
<br />
Go to [[ModuleNotFoundError#B._Finding_the_Python_library|Finding the Python library]] to set your PYTHONPATH and LD_LIBRARY_PATH.<br><b>After setting these environment variables, you need to do</b> <code>sudo ldconfig</code> <b>again</b> for the Linux dynamic library loader to find the just-installed GNU Radio libraries.<br><br />
If you have installed in a custom path with <code>-DCMAKE_INSTALL_PREFIX=XXX</code>, you will need to add that path to $PATH in order to find gnuradio-companion.<br />
<br />
==== For Ubuntu 18.04 ====<br />
<br />
An easy way to install GNU Radio 3.8 on many Ubuntu systems is to use the following commands (note that this skips the setup for UHD hardware):<br />
<br />
* <code>sudo apt install git cmake g++ libboost-all-dev libgmp-dev swig python3-numpy python3-mako python3-sphinx python3-lxml doxygen libfftw3-dev libsdl1.2-dev libgsl-dev libqwt-qt5-dev libqt5opengl5-dev python3-pyqt5 liblog4cpp5-dev libzmq3-dev python3-yaml python3-click python3-click-plugins python3-zmq python3-scipy python3-pip python3-gi-cairo</code><br />
* <code>pip3 install git+https://github.com/pyqtgraph/pyqtgraph@develop</code><br />
* <code>pip3 install numpy scipy</code><br />
* <code>echo 'export PYTHONPATH=/usr/local/lib/python3/dist-packages:usr/local/lib/python2.7/site-packages:$PYTHONPATH' >> ~/.bashrc</code><br />
* <code>echo 'export LD_LIBRARY_PATH=/user/local/lib:$LD_LIBRARY_PATH' >> ~/.bashrc</code><br />
* <code>echo 'export PYTHONPATH=/usr/local/lib/python3/dist-packages:usr/local/lib/python2.7/site-packages:$PYTHONPATH' >> ~/.profile</code><br />
* <code>echo 'export LD_LIBRARY_PATH=/user/local/lib:$LD_LIBRARY_PATH' >> ~/.profile</code><br />
* <code>cd ~/</code><br />
* <code>git clone --recursive https://github.com/gnuradio/gnuradio</code><br />
* <code>cd gnuradio</code><br />
* <code>git checkout maint-3.8</code><br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
* <code>git pull --recurse-submodules=on</code><br />
* <code>git submodule update --init</code><br />
* <code>cmake -DENABLE_GR_UHD=OFF ..</code><br />
* <code>make -j $(nproc --all)</code><br />
* <code>sudo make install</code><br />
* <code>sudo ldconfig</code><br />
<br />
Once this is done, reboot your computer and GNU Radio should be all set for you.<br />
<br />
== Common cmake flags ==<br />
<br />
* <code>-DENABLE_GR_XXX=ON</code> This enables (or disables for =OFF) the GNU Radio component named XXX. You might not need all of them, and this way, you can compile quicker.<br />
* <code>-DCMAKE_INSTALL_PREFIX=XXX</code> Install your stuff to XXX.<br />
* <code>-DCMAKE_BUILD_TYPE=Debug</code> This causes gcc to add debug symbols to all binaries. Useful for debugging (otherwise, it decreases efficiency!)<br />
* <code>-DPYTHON_EXECUTABLE=/usr/bin/python{2,3}</code> This selects the Python version and executable to be used during build time and will determine which Python libraries will be used for building the Python bindings.<br />
<p>For a list of additional cmake flags, as well as minimum versions of dependencies, see [https://www.gnuradio.org/doc/doxygen/build_guide.html]</p><br />
<br />
= Using PyBOMBS =<br />
<br />
PyBOMBS is good at building GNU Radio, UHD, and various Out of Tree (OOT) modules from source and then installing into a specified user directory rather than in the system files. PyBOMBS detects the user's Operating System and loads all of the prerequisites in the first stage of the build.<br />
<br />
The PyBOMBS documentation is in the PyBOMBS [https://github.com/gnuradio/pybombs#pybombs README].<br />
<br />
= OK, it's installed, what now? =<br />
<br />
If the installation worked without any trouble, you're ready to use GNU Radio! If you have no idea how to do that, the best place to start is with the [[Tutorials]].<br />
<br />
Optionally, you may run <code>volk_profile</code> on your terminal to help libvolk to determine the optimal kernels (may speed up GNU Radio). <br />
<br />
[[Category:Installation]]<br />
[[Category:Guide]]</div>172.18.0.3https://wiki.gnuradio.org/index.php?title=IQ_Complex_Tutorial&diff=8239IQ Complex Tutorial2021-02-18T01:00:40Z<p>172.18.0.3: Grammar, phrasing, spelling</p>
<hr />
<div><!-- IQ_complex_Signal_Tutorial --><br />
This tutorial originates from discussions on discuss-gnuradio@gnu.org. We will explain why simulating digital communications requires equivalent baseband representation of signals--which in fact are complex signals. For this reason, complex signals are essential in GNURadio. <br />
<br />
This tutorial is also intended for non-specialists, as it involves as little maths as possible while presenting most results using GNURadio's flowgraph. Some examples involving simple modulation schemes used in HAM radio are presented. While introducing complex signals can be seen as increasing complexity, we will see that it drastically simplifies the understanding of certain concepts, such as synchronization. <br />
<br />
If you are searching for more detailed information, please refer to the literature--such as references [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]].<br />
<br />
== Some maths ==<br />
<br />
This section summarizes complex number properties used in this tutorial. More information can be found on <br />
[[wikipedia: Complex number|complex number Wikipedia page]].<br />
<br />
[[File:IQ_complex_tutorial_polar2.png|frame|Complex number ''z''=''a'' + ''jb'']]<br />
A complex number is a number of the form ''a'' + ''jb'', where ''a'' and ''b'' are real numbers, and ''j'' is an indeterminate satisfying ''j''<sup>2</sup>=-1 (Mathematician prefer using ''i'' instead of ''j'' used by physicist and radio engineers). For example, z<sub>1</sub>=2+3''j'' is a complex number. The real part Re{''z''} of z<sub>1</sub> is 2 and its imaginary part Im{''z''} is 3.<br />
<br />
: <math>z=a+jb </math><br />
: <math>\text{Re}\{z\}=a</math><br />
: <math>\text{Im}\{z\}=b</math><br />
<br />
<br />
Complex numbers can be represented in the complex plane as vectors. The modulus or magnitude ''r'' of a complex number ''z'' = ''a'' + ''jb'' is <br />
<br />
: <math>r=|z|=\sqrt{a^2+b^2}</math><br />
<br />
The phase &phi; of ''z'' (referred to as the ''argument'') is the angle of the radial Oz on the positive real axis.<br />
<br />
: <math>\phi=\arg(z)=\arctan(b/a) </math> (for a&ne;0)<br />
<br />
Together, r and &phi; provide another means of representing complex numbers--both the polar and exponential forms.<br />
<br />
: <math>z=r \left(cos(\phi) + j sin(\phi) \right) = r e^{j\phi}</math><br />
[[File:IQ_complex_tutorial_polar1.png|frame|The complex plane]]<br />
<br />
The exponential form is convenient for computing the multiplication of two complex numbers. <br />
<br />
: <math>z_1= r_1 e^{j\phi_1}</math><br />
: <math>z_1= r_2 e^{j\phi_2}</math><br />
: <math>z=z_1z_2= r_1r_2 e^{j(\phi_1+\phi_2)}</math><br />
<br />
The following complex numbers have a unit magnitude ''r''=1 : <br />
<br />
: <math>+1=e^{j0}</math><br />
: <math>+j=e^{j\pi/2}</math><br />
: <math>-1=e^{j\pi}</math><br />
: <math>-j=e^{j3\pi/2} </math><br />
<br />
A complex signal ''c(t)'' can be seen as two real signals ''a(t), b(t)''--often written as ''i(t), q(t)''--and combined to create a complex signal. It can also be represented by its amplitude over time ''r(t)'' as well as its phase variation over time ''&phi;(t)''<br />
<br />
: <math>c(t) = a(t) + j b(t) = r(t) e^{j\phi (t)} </math><br />
<br />
== Why we need complex and IQ signals ==<br />
GNURadio software is mainly used to design and study radio communications. Making high frequency transmission requires modulating a high frequency carrier at frequency ''F<sub>0</sub>''. The most common modulation for analog transmissions are: amplitude modulation (AM), phase modulation (PM), and frequency modulation (FM). <br />
<br />
[[File:IQ_complex_tutorial_AM_spectrum.png|thumb|400px|AM spectrum]]<br />
For analog AM, the modulated signal ''m(t)'' is simply the mathematical product of the carrier ''c(t)'' and the baseband signal ''a(t)''. The corresponding hardware is a mixer whose scheme and mathematical representation is a multiplier. <br />
<br />
: <math>m(t) = a(t) c(t) = a(t) \cos(2\pi f_0t)</math><br />
<br />
We call ''a(t)'' a baseband signal since its spectrum is in a low frequency range--starting near 0 Hz (e.g. [0-20kHz] for a HiFi audio signal). <br />
<br />
The spectrum of an AM modulated signal ''M(f)'' is the translation or the audio spectrum ''A(f)'' around &plusmn;''F<sub>0</sub>'' with ''A(f)'' being the entire spectrum of the modulating signal, using both positive and negative frequencies<br />
<br />
: <math>M(f) = \frac{1}{2}\big(A(f-f_0) + A(f+f_0)\big)</math><br />
<br />
N.B. Negative frequencies are often omitted in spectrum representation since, for real signal (''a(t)'', ''m(t)'' are real) the power spectrum are symmetric around zero (more details on this later). <br />
<br />
Up to now we have been dealing with real signals. The need for complex signals appears in the next step. Simulation requires sampled signal. Sampling is the operation of observing a continuous signal and taking a finite number of samples at a given sampling rate ''f<sub>s</sub>'' (i.e. one sample each 1/''f<sub>s</sub>'' second). Because a simulator can only make calculations on a finite number of samples, it requires a sampled signal. Nyquist Sampling theorem states that the sampling rate must be greater than twice the maximum frequency ''F<sub>Max</sub>'' in order to reconstruct the original signal from the sampled signal.<br />
<br />
: <math>f_s > F_{Max}</math><br />
<br />
For an HIFI audio signal, maximum audio frequency <math>F_{Max Audio}</math> is closed to 20 kHz, sampling rate must be higher then 40 kHz (44.8 kHz is often used in computer sound card, 8 kHz is used for mobile phone since voice has a lower frequency range then HIFI audio). <br />
<br />
For an AM signal modulated by an audio signal, maximum frequency of the modulated spectrum is <math>F_{Max}=F_0+F_{Max Audio}</math>. Direct sampling of such signal is not possible with conventional hardware such as low cost SDR dongle. If the carrier frequency is close to 1 GHz, the sampling rate should be at least 2 GHz. This is obviously too much then computer can handle (higher then some computer clock).<br />
<br />
Flowgraph [[Media:IQ_tutorial_AM_TX_real.grc|IQ_tutorial_AM_TX_real.grc]] illustrates amplitude modulation using only real blocks (excepted for bits source). As a consequence, the maximum carrier frequency is limited to several tens of kHz. <br />
* study the modulator part which simply multiply the baseband signal and the sine carrier<br />
* look at the influence of the carrier frequency on the modulated signal spectrum (carrier frequency must stay lower than half the sampling rate)<br />
* look at the spectrum shape for sawtooth input and random bit sequence (QT GUI chooser and selector)<br />
* When transmitting random bits, you can deactivate the interpolating FIR Filter and replace it by a root raised cosine filter<br />
<br />
== Spectrum properties of signals ==<br />
Amplitude spectrum is calculated using the Fourier Transform. It represents how the power is spread in the frequency domain. It allows for determining the signal bandwidth. Power Spectral Density or PSD correspond to the average magnitude of the Amplitude spectrum.<br />
<br />
In this section we summarize some properties of the phase arg''{X(f)}'' and magnitude |''X(f))''|. We first consider properties for continuous signal. Then we will investigate additional properties of sampled signal (those used in GNURadio).<br />
<br />
Given a signal ''x(t)"" it amplitude/phase spectrum is denoted ''X(f))'' which is a complex function given by : <br />
: <math>X(f) =\int{x(t)e^{-2j\pi ft}}dt</math><br />
<br />
=== continuous real signal ===<br />
Every real signal have a spectrum whose magnitude is symmetric and phase is anti-symmetric. <br />
: <math>x(t) \in \mathbb{R}</math><br />
: <math>X(-f)=X^*(f)</math><br />
: <math>|X(-f)|=|X(f)|</math><br />
: <math>\text{arg}\{X(-f)\}=-\text{arg}\{X(f)\}</math><br />
<br />
[[File:IQ_complex_tutorial_complex_spectrum.png|thumb|500px|Complex signal spectrum and, sampled complex signal spectrum (only first 3 patterns represented)]]<br />
=== continuous complex signal ===<br />
The main difference with real signal is that: <br />
* any complex signal having non null imaginary part exhibits a non-symmetric spectrum.<br />
* as a consequence, every non-symmetric spectrum correspond to a complex signal<br />
<br />
=== sampled signals ===<br />
We consider a signal ''x(t))'' and we note its sampled version ''x<sub>s</sub>(t))'' sampled at frequency ''F<sub>s</sub>''. <br />
<br />
The spectrum ''X<sub>s</sub>(f)'' of the sampled signal is a periodic function of period ''F<sub>s</sub>''.<br />
: <math>X_s(f)=\sum_k X(f-kF_s)</math><br />
So the sampled signal spectrum verify : <br />
: <math>X_s(f+kF_s)=X_s(f)</math><br />
<br />
Generally the bandwidth of ''X(f))'' is lower then ''F<sub>s</sub>''/2. The infinite sum defined above exhibit no aliasing (no superposition of patterns ''X(f), X(f+F<sub>s</sub>)'' and ''X(f-F<sub>s</sub>)'') .In that case, as stated in the Nyquist theorem of sampling, the original spectrum/signal can be recovered by filtering the sampled signal. <br />
<br />
When the maximum frequency of the signal spectrum do not respect Nyquist theorem, one should filter the signal with a low pass filter having a cutt-off frequency lower then ''F<sub>s</sub>''/2 before sampling: this correspond to the anti-aliasing filter used in every SDR hardware.<br />
<br />
== Complex enveloppe, equivalent baseband signal==<br />
<br />
Baseband signals have a spectrum at low frequency near 0 Hz. Audio, video and NRZ line code are baseband signals. <br />
<br />
Bandpass signals have no energy near 0 Hz and a spectrum located near a high frequency (generally the carrier frequency). Analog and digital AM PM and FM modulated signals are bandpass signals.<br />
<br />
A theorem ([[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]]) states that any high frequency bandpass signal having a limited bandwidth ''B'' can be represented by a baseband equivalent signal having the same bandwidth. This baseband equivalent signal also called the complex envelope is used in simulators since its allow to lower sampling rate as compare to directly sampling the bandpass signal. <br />
<br />
The equivalent baseband representation help us for the simulation of bandpass signals. At this step we need some math. We will consider a carrier modulated in phase and/or amplitude (in the sake of simplicity, Frequency modulation is not considered but it can be related to phase modulation.). Such a modulated signal ''m(t)'' and it's complex representation <math>\tilde{m}(t)</math> is :<br />
<br />
: <math>m(t)=a(t) \cos(2\pi F_0t + \phi(t))</math><br />
: <math>\tilde{m}(t)=a(t) e^{j(2\pi F_0t + \phi(t))} = a(t) e^{j \phi(t)} e^{j2\pi F_0t} = m^{bb}(t) e^{j2\pi F_0t}</math><br />
<br />
For modulated signal, the complex representation is obtained by replacing the cos function by an exponent function . For a more general definition see [[#ancre1|[1]]],[[#ancre2|[2]]],[[#ancre3|[3]]] . The real signal correspond to the real part of the complex signal <math>m(t)=\text{Re}(\tilde{m}(t))</math>. <br />
<br />
[[File:IQ_complex_tutorial_EQ_BB.png|thumb|400px|AM spectrum]]<br />
One important property of <math>\tilde{m}(t)</math> spectrum <math>\tilde{M}(f)</math> is that it has only energy in the positive frequency range and verify.<br />
<br />
: <math>\tilde{M}(f)=M^+(f)</math><br />
where <math>=M^+(f)</math> denotes the restriction of <math>=M(f)</math> to the positive frequency range. <br />
<br />
Let us now look at the complex envelope or equivalent baseband signal <math>m^{bb}(t)</math> of bandpass signal <math>m(t)</math> defined by : <br />
: <math>m^{bb}(t)= \tilde{m}(t) e^{-j2\pi F_0t} = a(t) e^{j \phi(t)} </math><br />
<br />
Multiplying a signal by <math>e^{+j2\pi F_0t}</math> correspond to a frequency translation of the spectrum so that we have:<br />
:<math>M^{bb}(f)=M^+(f+F_0)</math><br />
<br />
So the spectrum of the complex enveloppe is a baseband signal whose spectrum has the same shape as <math>=M+(f)</math>. For this reason, knowing <math>\tilde{m}(t)</math> or <math>M^{bb}(f)</math> is sufficient to reconstruct <math>m(t)</math> or <math>M(f)</math>.<br />
<br />
=== Equivalent baseband, Enveloppe, Complex signals ? ===<br />
Terms '''equivalent baseband signal''', '''complex envelope''' and '''complex signal''' can be seen as equivalent terms referring to the same thing. <br />
<br />
While simulating some radio we have mainly two type of signals: <br />
* any low frequency or baseband signal are real signal, they are represented by real signals<br />
* every bandpass signal must be represented by their equivalent baseband complex signal (excepted for systems having very low carrier frequency)<br />
<br />
So in a gnuradio flowgraph, there is no ambiguity: every complex signal (orange interconnections) is implicitly an equivalent representation of a band pass signal so we will simply call it a '''complex signal'''.<br />
<br />
== IQ modulator and demodulator==<br />
[[File:IQ_complex_tutorial_IQ.png|thumb|200px|Complex signal in the IQ plane]]<br />
[[File:IQ_complex_tutorial_IQ_Mod_Demod-crop.png|thumb|400px|ID modulator and demodulator]]<br />
Let us now come to hardware and SDR and first rewrite the equivalent baseband signal and the modulated signal ''m(t)'' : <br />
<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
: <math>m(t)=\text{Re} \left[ \big(i(t)+jq(t)\big) e^{j2\pi F_0t} \right]</math> <br />
: <math>m(t)=i(t) \cos(2\pi F_0t) -q(t) \sin(2\pi F_0t)</math><br />
<br />
The phase ''&phi;(t)'' of the modulated signal ''m(t)'', is identical to the phase of the complex signal ''c(t)=i(t)+jq(t)''. The equivalent baseband signal ''c(t)'' is represented in a complex plane also refeered to as the IQ plane. The resulting ''m(t)'' can be any modulated in AM, PM or even FM signal. <br />
* <math>i(t)=a(t) \cos(\phi(t))</math> <br />
* <math>q(t)=a(t) \sin(\phi(t))</math><br />
<br />
As a result, the modulated signal ''m(t)'' is the addition of : <br />
* <math>i(t) \cos(2\pi F_0t)</math> which is an AM modulated signal, the product of ''i(t)'' by a signal In phase with the carrier (i stand for In phase)<br />
* <math>q(t) \sin(-2\pi F_0t)</math> which is an AM modulated signal, the product of ''q(t)'' by a signal in Quadrature with the carrier (q stand for Quadrature)<br />
<br />
The corresponding hardware is called an IQ modulator. Every modern radio communication uses IQ modulator for emitting and IQ demodulator for receiving. The IQ demodulator is able to recover incoming ''i(t)'' and ''q(t)''. If the amplitude of the recovered carrier is 2, and if modulator and demodulator carrier are synchronous (same frequency and phase) the output of the IQ demodulator correspond to input ''i(t)'' and ''q(t)''.<br />
: <math>\hat{i}(t)=i(t) </math><br />
: <math>\hat{q}(t)=q(t) </math><br />
<br />
In real hardware, carrier are not synchronous and the receiver must compensate any phase and frequency difference between emitter and receiver. This is done using some hardware and/or software such as polyphase clock sync, and Costas loop. <br />
<br />
SDR Module such as USRP [https://www.ettus.com/all-products/usrp-n320/|(USRP N320 block Diagram)] and SDR Dongle [https://www.programmersought.com/article/59454592969/| (RTL-2832)] input and output are the 2 real signals ''i(t)'' and ''q(t)'' combined to form the complex signal ''i(t) + j q(t)'' which turns to be the equivalent baseband of the modulated emitted or received signal. These hardware are based on IQ modulator and IQ demodulator associated with mixers when intermediate frequency (IF) is used.<br />
<br />
== Some examples of EqBB signals ==<br />
In order to get familiar with complex signal, let us consider some basic examples.<br />
<br />
First, we suppose our emitter carrier is <math> \cos (2\pi F_0t)</math> so that every equivalent baseband signal will be defined according to this reference. <br />
<br />
=== complex envelope of a pure sine wave === <br />
We will consider a pure sine wave, close to the carrier having a ''&Delta;f'' frequency shift and ''&phi;'' phase shift as compared to the carrier. After some math we get its complex equivalent signal. <br />
: <math>m(t) = A \cos (2\pi (F_0+\Delta f)t+ \phi)</math> <br />
: <math>\tilde{m}(t) = A e^{j(2\pi (F_0+\Delta f)t + \phi)} </math><br />
: <math>m^{bb}(t) = A e^{j(2\pi\Delta f t+ \phi)} = A e^{j 2\pi \Delta ft} e^{j\phi}</math><br />
<br />
The complex envelope of the carrier itself is found for ''&Delta;f''=0 and ''&phi;''=0 which yields <math>m^{bb}(t) = A</math>. We conclude that in a GNURadio flowgraph, the carrier is represented by a continuous components, a pure DC signal ; this may seemed counter intuitive. The spectrum <math>M^{bb}(f)</math> is a single peak at ''f=0'' which can be represented using the Dirac ''&delta;(f) function (or distribution)'' : <math>M^{bb}(f)=\delta(f))</math>. Shifting this spectrum of <math>+F_0</math> towards positive frequency we get the positive part of the carrier spectrum (a peak at <math>+F_0</math>) : <math>M^+(f)=\delta(f-F_0))</math>. The negative part of the spectrum is simply obtained by symmetry of the real part (a peak at <math>-F_0</math>). <br />
<br />
This can be simulated with GNURadio flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. The spectrum of our carrier is centered at 0 Hz. The Frequency sink has a feature to shift this spectrum around <math>F_0</math> which is a parameter of the sink. In a simulation, it is not necessary to represent the negative part of the spectrum since ''m(t'') being real, it spectrum is obviously symmetric.<br />
<br />
We will now consider ''&Delta;f'' &ne;0 which simulate a signal not exactly synchronous to the carrier. This yieds<br />
: <math>m^{bb}(t) = A e^{j 2\pi \Delta ft}</math><br />
The complex enveloppe is rotating vector (you can simulate this with ''&Delta;f''=1 Hz giving a vector which rotate at 60 rpm or 1 turn per second) , its spectrum is a single peak at ''f=+&Delta;f''. This complex signal exhibits a non symmetric spectrum (no peak at ''f=-&Delta;f''). Change ''&Delta;f'' to -0.5 Hz, the vector now rotates counter clockwise at 30 rpm or 0.5 turn per second.<br />
<br />
Exercice: <br />
Open flowgraph [[media:IQ_tutorial_eq_bb.grc|IQ_tutorial_eq_bb.grc]]. Parameter delta_f is set with an increment of 1/12, which correspond to 5 rotation per minute. <br />
* For ''&Delta;f''=1/12=0.0833, what is the speed of rotation of the complex signal?<br />
* What do you observe when ''&Delta;f''=-1/12=-0.0833 ?<br />
* Slowly increase ''&Delta;f'' to reach ''f<sub>s</sub>/2'' and observe the spectrum really has a single peak at ''&Delta;f''. Explain your observation when ''&Delta;f>f<sub>s</sub>/2'.<br />
* Do the same for a negative ''&Delta;f'' <br />
* For ''&Delta;f =f<sub>s</sub>'' what is the equivelant baseband frequency. What is the carrier frequency ? <br />
* Set ''&Delta;f''=0 and ''&phi;''&ne;0. Discusss the simulated equivalent baseband signal.<br />
<br />
=== AM mod demod example===<br />
This example will consider signal baseband signal ''a(t)'' modulating a carrier at F<sub>0</sub> in AM, and its demodulation. As no phase modulation in used, ''&phi;(t)''=0 and consequently ''q(t)''=0.<br />
: <math>m(t) = a(t) \cos (2\pi F_0 t)=i(t) \cos (2\pi F_0t) -q(t) \sin (2\pi F_0t) </math> <br />
: <math>i(t) = a(t) </math><br />
: <math>q(t) = 0</math><br />
<br />
AM modulation is a special case for which the equivalent baseband complex signal has a null imaginary part and is real. Considering the schematic diagram of an IQ modulator demodulator, when ''q(t))'' is null the diagram is simplified (imaginary path is not used) yielding the well known AM modulation/demodulation scheme. <br />
<br />
Remind that in GNURadio flowgraph : <br />
* orange connections correspond to real signals (float numbers)<br />
* blue connections correspond to complex signals (complex numbers)<br />
<br />
Flowgraph [[media:IQ_tutorial_AM_TX_complex.grc|IQ_tutorial_AM_TX_complex.grc]] contains two equivalent diagram for an AM modulation with a sawtooth input: <br />
* the upper one uses real signals. It is the exact AM modulator uses at the beginning of this tutorial.<br />
** sampling frequency is 200 kHz, 40 times the input rate which equal 5 kHz.<br />
** allowing a maximum carrier frequency close to 100Khz<br />
<br />
[[File:IQ complex tutorial AM TX complex.png|thumb|600px|AM modulator flowgraph]]<br />
* the lower one uses an equivalent baseband representation. <br />
** sampling frequency is 25 kHz, 5 times the input rate which equal 5 kHz.<br />
** the sawtoooth correspond to identical generator in both modulator<br />
** each blue input or output is the baseband equivalent of the corresponding signal in the upper AM modulator.<br />
** the carrier frequency can be any value compatible with connected Hardware<br />
** the carrier equivalent signal equal 1 (as stated in the previous section) so it has been disable and replaced by a complex constant<br />
** the Hardware input is the complex equivalent baseband signal<br />
** the carrier frequency is not needed nor used in complex blocks; excepted in the QT GUI spectrum to label the center frequency which is 0 but correspond to the ''F>sub>0</sub>''.<br />
<br />
Three blocks are unusefull and can be removed from the lower complex modulator: <br />
* save the current flowgraph as IQ_tutorial_AM_TX_complex_2.grc. <br />
* remove the complex multiplier, the carrier equivalent baseband (complex constant=1) and the carrier complex source (the disabled one). Reconnect complex to float and throttle blocks to obtain the flowgraph sketched on the right.<br />
<br />
You can now run this flowgraph and compare signal and their spectrum in both modulator. Once again, these simulations suppose perfectly synchronous emitter and receiver carrier which is quite far from reality.<br />
<br />
==== Further work : construct the AM demodulator flowgraph ====<br />
Use the flowgraph IQ_tutorial_AM_TX_complex_2.grc that you created, add blocks to perform AM demodulation and to recover the input signal ''a(t)'' from the modulated signal. You have to use the equivalent baseband representation of the demodulator. <br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 1st indication &nbsp;<br />
|-<br />
| Remind and use the relation between ''a(t)'' and ''c(t)=i(t)+jq(t)'' given above. <br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| 2nd indication &nbsp;<br />
|-<br />
| An AM demodulator extract the amplitude (magnitude) of the modulated signal...<br />
|}<br />
<br />
{| class="mw-collapsible mw-collapsed wikitable"<br />
| Solution including filter &nbsp; <br />
|-<br />
| [[media:IQ_tutorial_AM_TX_complex_3.grc|IQ_tutorial_AM_TX_complex_3.grc]]<br />
Running this flowgraph, demodulation is obtained taking the real part (imperfect solution), or the magnitude (good solution) of the complex signal. <br />
* these two methods give exactly the same results<br />
* these two methods are close to, but differ from original input signal due to the low-pass filter (only 6 peak are taken from the sawtooth spectrum, 3 for positive frequency and 3 other for negatives ones. )<br />
* in a real system, bandpass filtering before demodulation is used. As will be shown, the lowpass filter is equivalent to a bandpass filter acting on the bandpass modulated signal<br />
* Only taking magnitude is a good AM demodulation since it is less sensitive to frequency and phase lack of synchronism found in real systems. <br />
|}<br />
<br />
=== QPSK example ===<br />
<br />
QPSK digital signal exhibit four phase state <math>\phi \in \{\pi/4, 3\pi/4, -3\pi/4, -\pi/4 \}</math>.<br />
: <math>m^{bb}(t)=a(t) e^{j \phi(t)} =i(t) + j q(t)</math><br />
<br />
And the baseband equivalent signal also exhibit 4 different values each one being used to code a 2 bits sequence 00 01 10 or 11 : <br />
: <math>m^{bb}(t) \in \{1+j , -1+j , -1-j, 1-j \}</math><br />
<br />
Normally, we would use a GNURadio "constellation modulator" to simulate QPSK as is done in the excellent [[Guided_Tutorial_PSK_Demodulation|Guided Tutorial on PSK Demodulation]]. <br />
<br />
[[File:IQ_complex_tutorial_QPSK.png|thumb|600px|QPSK modulator]]<br />
For the present tutorial we will simulate a QPSK without Nyquist filter in order to get phase states which can be simply displayed on a constellation sink. This is not possible with constellation modulator. Our QPSK modulator (complex representation) is build taking into account that the complex signal exhibit 4 different values, its obvious that both ''i(t)'' and ''q(t)'' have only 2 states so they are binary symmetric NRZ line codes:<br />
: <math>i(t), q(t) \in \{+1, -1\}</math><br />
<br />
Flowgraph [[Media:IQ_tutorial_QPSK.grc|IQ_tutorial_QPSK.grc]] generates 2 sequences of bits, interpolates them to get 2 binary symmetric NRZ line codes. The NRZ signals are combined to create the complex equivalent baseband signal of the QPSK which can be transmitted to any SDR emitter. <br />
<br />
Simulate this flowgraph : <br />
* stop the QT GUI spectrum to observe that the complex baseband signal spectrum is no longer symmetric as expected for complex signals. <br />
* use spectrum averaging to see that despite of the previous observation, the power spectral density (average of the magnitude spectrum) is symmetric around 0 (which correspond to ''F<sub>0</sub>'' for the modulated signal. <br />
* Disable the interpolating filters and enable both root raised cosine filter (the filter used in every QPSK emitter). This yields the real spectrum of a QPSK.<br />
<br