OFDM: troubles regarding FFT size and decimation factor

Hello everybody,

I’m trying to do some channel measurements using a setup based on three
PCs with two USRP2 devices (equipped with RFX2400 daughterboards) and
GNU Radio 3.7.
One of the PCs acts as network controller, it creates data the other PCs
should transceive. The network controller therefore iFFT processes some
random data and tells the two daemons what the have to send. If the USRP
of one of the daemons receives something the received data gets
delivered back to the network controller, which FFT processes the data
and does some calculations.

So the GNU Radio action is happening only on the daemons. From the
transmitter point of view it’s just adding the cyclic prefix, on
receivers point of view there is a Schmidl & Cox correlator for
synchronization and a block picking the samples of the payload.
The (i)FFT stuff is happening on the network controller, using NumPys
FFT implementation.

My predecessor (whom I sadly can’t ask) built this communication system
and used a decimation factor of 16 and a FFT size of 64.
Now I wanted to try a different FFT size. I expected this modification
to be straightforward, just changing values and using a proper, low PAPR
preamble.
However, I tried it with FFT sizes of 32, 96 and 128 - still with a
decimation factor of 16 - and had no success. Bit error rates were far
too high (about 40%).

I looked into some log files and found that apparently at least the
Schmidl & Cox correlator and the payload picking block is working, it
catches the frame start and delivers the amount of samples needed for
the FFT. Please notice, as already mentioned above, that I do the FFT
with NumPy on the network controller after transmission has finished.
Now if I compare the post FFT / frequency domain received data with the
sent data, looking at the argument differences I can observe some
strange values:
I would expect a straight line with an slope (which I could correct by
equalizing), as in this figure:

Please ignore the “jumps” by 2*pi (or 360) and keep in mind it’s drawn
using Matlab, this means one-based indexing, therefore ignore subcarrier
17 as it’s the DC carrier.

Instead, I get some random jumps by pi on some of the carriers, as seen
in this figure:

This happens with all FFT sizes I tried except the original FFT size of
64 if I keep the decimation factor of 16.

Now I noticed that there seems to be some correlation with the
decimation factor:
If I choose a decimation factor, so that the ratio between FFT size and
sampling rate is the same as in my original and correctly working setup
(FFT size: 64, decimation factor: 16, so about 100 kHz per subcarrier) ,
everything seems to work properly. The BER is low and comparing the
argument of sent vs. received data I can see the expected line without
any random jumps. This behaviour I could observe with FFT size 32 (using
a decimation factor of 32) and 96 (using a decimation factor of 12) -
with other decimation factors I didn’t work.

Now I’m trying to understand this phenomenon. I expected, that I would
be able to change FFT size without touching decimation factor. At least
I thought that using less FFT bins with the same decimation factor
shouldn’t harm, giving each subcarrier more bandwidth.

It would be great if someone could help me regarding this problem as I’m
quite confused now and don’t really know what to do.

Hi everybody.

(I had some problems with In-Reply-To header, so I hope this gets posted
in the right thread. Otherwise I’m sorry, this message refers to
[Discuss-gnuradio] OFDM: troubles regarding FFT size and decimation fact)

Thank you for your answer, Martin.

Martin B. wrote:

I’m not sure what your decimation factor does.
By “decimation factor” I mean the ratio the USRPs 100 MS/s gets divided
with. So a decimation factor of 16 would result in a sampling rate of
(100 MS/s / 16) = 6.25 MS/s. In the source code I uploaded it’s defined
in send_receive.py and called “samp_rate_div”, using
set_samp_rate(100e6/samp_rate_
div) to set the USRPs sampling rate.

Martin B. wrote:

You’re using your own OFDM codes, not the GNU Radio ones, right? Can
you share these?

I uploaded the relevant code, to give some more details:
https://www.dropbox.com/sh/xdo456tvcqiwdwx/Y-6pcz_rR4
https://www.dropbox.com/sh/xdo456tvcqiwdwx/Y-6pcz_rR4

I already tried to shorten it, but unfortunately it’s still a bit
confusing, as the person who wrote it did somehow bypass GNU Radio a bit
and implemented a lot himself. Therefore, I give my best to describe how
the code works:

To give a short abstract of the system:

https://www.dropbox.com/s/iphz1ix4b1as4pd/network_overview.png
PC1 commands PC2 and PC3 to alternatingly send some data, at first PC2
is the transmitter and PC3 the receiver, then the other way round.
The network controller (generating and evaluating all the data, using
NumPy (I)FFT) commands the network daemons to send data and collects
received data from them via Ethernet.
The network daemons start the send_receive top_block and read from /
push to send_receive.py queues by add_complex_list_to_queue() and
fetch_complex_list_from_queue() functions.
send_receive.py is the top_block, connecting message queues to
jwdiplom.modulator (adding cyclic prefix) / jwdiplom.demodulator
(Schmidl & Cox synchronisation) blocks, which are connected to UHD
source/sink.
The PCs running network daemons each are equipped with two network
cards, one for communication with network controller and one for
communication between send_receive and the USRP2 devices by UHD.

More in detail:
PC 1 is the network controller, running nc_kanalmessung_AB_BA.py. This
Python script at first initializes the network daemons running on PC 2
and 3, it loads the settings dictionary from ofdmsettings.py and chooses
the right preamble. It does an IFFT on the preamble (so it’s in time
domain, line 99) and sends this parameters by Ethernet to the daemons
using send_ethernet_packet() (line 135).
On line 107 it creates random data using modulation.py. Again using
modulation.py, it PSK modulates the random data, adds the pilot and
empty carriers (as defined in ofdmsettings.py) and does the IFFT
(modulation.py, line 46). This ready-to-send time domain data is send
frame by frame to the network daemons (nc_kanalmessung_AB_BA.py, line
114), once more using send_ethernet_packet() function.

Now it’s time to look at network_daemon.py, running on PCs 2 and 3. As
one can see there are several threads running, at first InitThread gets
started, waiting for nc_kanalmessung_AB_BA.py sending the parameters and
preamble. After this happened the GNU Radio action begins: It starts the
top_block of send_receive.py (handing over the parameters).

send_receive.py features two message queues connected to
jwdiplom.modulator and jwdiplom.demodulator.
These message queues are read out / feed with data by
fetch_complex_list_from_queue() (send_receive.py, line 84) and
add_complex_list_to_queue() (send_receive.py, line 91) functions, which
get accessed by InputThread and OutputThread in network_daemon.py.

jwdiplom.modulator just adds a cyclic prefix and is connected to
uhd_usrp_sink_0.
jwdiplom.demodulator implements a Schmidl & Cox synchronization, it is
connected to uhd_usrp_source_0.
The source files are located in /gr-jwdiplom/python but for easier
comprehension I made some .grc files and exported them:

https://www.dropbox.com/s/2s7ssy5ypp0ekjy/ofdm_modulator.grc.png

https://www.dropbox.com/s/34lo5yytx9accgq/ofdm_demodulator.grc.png

The demodulator block uses two custom blocks:

I know that’s a lot of text (and source code) and not relying on GNU
Radios standard OFDM implementation makes it hard for others to review
but it would be great if someone could help.

Regards,
Sebastian