Working OFDM system






The following ofdm generator produces this plot:



Currently this uses an ifft size of 16 channels at a sample rate of 8Khz to combine signals into a multiplex. So far there are 3 signal sources, two psk31 and one gmsk, and they appear to make it thru the system to the receiver in fair shape. The sources are interleaved, run thru a serial-to-parallel, ifft, parallel-to-serial, then interpolated by 30 and mixed up to 60Khz (as shown in the above plot). This signal can be fed thru a channel simulator (noise, distortion, reflections - yet to be done). The receiver translates back from 60Khz to baseband and decimates to 8Khz, then serial-to-parallel, forward fft, parallel-to-serial, then deinterleaved. At this point the gmsk signal is fed to a demodulator, and the psk31 signals can be mixed with audio tones for demodulating with an external ham radio psk31 software package. Currently there is a zero-vector-source between each channel, leaving 8 for bearing a payload - and just feed the two psk signals into channels 1,3,7,9,13 and 15, while the gmsk signals use channels 5 and 11.

This is just a beginning setup to play with - currently the gmsk data comes out one bit off:
hexdump payload.dat:
0000000 2023 6f44 6e20 746f 7220 6d65 766f 2065
0000010 6874 2065 6f66 6c6c 776f 6e69 2067 696c

hexdump gmsk_out:
0000000 4046 de88 dc40 e8de e440 daca ecde 40ca
0000010 d0e8 40ca decc d8d8 eede dcd2 40ce d2d8
and my notebook is just fast enough to play some of the psk31 signals before file access slows it down and break up the audio, but it's an encouraging start.

In fact, here is a plot of the gmsk signal before and after the transmit-interleave (green) and receive-deinterleave (red) steps:



Transmitter code:

#!/usr/bin/env python
#
# 

from gnuradio import gr
from gmsk import gmsk_mod, gmsk_demod
import wx, random, math

def main():
        
        rf_rate = 240000
        sample_rate = 8e3

        fft_size = 16

	vec0 = [0]
	vec1 = [1]

	fg = gr.flow_graph()

# psk31 sources 
# 1st source
	data_rate = 31.25
	sps31 = int (sample_rate / data_rate)

	# this is a prearranged phase-shift format file, made with
	# keyboard.py. See psk31 page
        srca = gr.file_source(gr.sizeof_char,"psk_mess1",1)

        bytes_to_syms1 = gr.bytes_to_syms()

        interp_taps = (1,) * sps31
        interp1 = gr.interp_fir_filter_fff(sps31, interp_taps)

        ps_coeffs = gr.firdes_root_raised_cosine ( \
                1,              # gain
                sample_rate,      #
                data_rate,      # = baud for bpsk
                .36,            # .36 alpha = 1 - 16/25
                720 )           # guess
        pulse_shaper1 = gr.fir_filter_fff(1,ps_coeffs)
	hilbert1 = gr.hilbert_fc(197)
	fg.connect ( srca, bytes_to_syms1, interp1, pulse_shaper1, hilbert1 )


# 2nd psk31 source:
        srcb = gr.file_source(gr.sizeof_char,"psk_mess1",1)
        bytes_to_syms2 = gr.bytes_to_syms()
        interp2 = gr.interp_fir_filter_fff(sps31, interp_taps)
        pulse_shaper2 = gr.fir_filter_fff(1,ps_coeffs)
        hilbert2 = gr.hilbert_fc(197)
        fg.connect ( srcb, bytes_to_syms2, interp2, pulse_shaper2, hilbert2 )

# gmsk source:
        sps_gmsk = 8
        symbol_rate = sample_rate / sps_gmsk
        p_size = 128

	filename="payload.dat"
        srcc_data_file = file(filename, "r")
        srcc_data = srcc_data_file.read()
        srcc_data_len = len(srcc_data)
        srcc = gr.file_source(1, filename, 1)

        # GMSK modulate input bytes to baseband
        mod = gmsk_mod(fg, sps_gmsk, symbol_rate, 0.3, p_size)
        fg.connect(srcc, mod.head)
#	null = gr.null_sink(gr.sizeof_gr_complex)
#	fg.connect(mod.tail,null)

	src1 = gr.vector_source_c(vec0,1)

#	probe1 = gr.file_sink(gr.sizeof_gr_complex,"probe1_trans")
#	fg.connect (mod.tail, probe1)

	# combine all signals
	inter = gr.interleave(gr.sizeof_gr_complex)
	fg.connect ( src1, (inter, 0))		# zero 'guard' band
	fg.connect ( hilbert1, (inter, 1))	# a psk sig
	fg.connect ( src1, (inter, 2))		# guard band
	fg.connect ( hilbert2, (inter, 3))	# psk
	fg.connect ( src1, (inter, 4))		# guard band
	fg.connect ( mod.tail, (inter, 5))	# gmsk sig
	fg.connect ( src1, (inter, 6))		# guard band
	fg.connect ( hilbert2, (inter, 7))	# psk
	fg.connect ( src1, (inter, 8))		# guard
	fg.connect ( hilbert1, (inter, 9))	# psk
	fg.connect ( src1, (inter, 10))		# guard
	fg.connect ( mod.tail, (inter, 11))	# gmsk 
	fg.connect ( src1, (inter, 12))		# guard
	fg.connect ( hilbert1, (inter, 13))	# psk
	fg.connect ( src1, (inter, 14))		# guard
	fg.connect ( hilbert2, (inter, 15))	# psk

	# convert combined signals to parallel for IFFT
        s2p = gr.serial_to_parallel (gr.sizeof_gr_complex, fft_size)

	# IFFT
        ifft1 = gr.fft_vcc (fft_size,False,False) # inverse, no windowing

	# convert back to serial
        p2s = gr.parallel_to_serial (gr.sizeof_gr_complex, fft_size)

	# prepare for rf stage
	interp_coeffs = gr.firdes.low_pass ( 30, rf_rate, 4e3, 1e3, gr.firdes.WIN_HAMMING )
	interp = gr.interp_fir_filter_ccf ( 30, interp_coeffs )

	# mix to rf
	rf_src = gr.sig_source_c (rf_rate,gr.GR_SIN_WAVE,60e3,1,0)
	rf_mix = gr.multiply_cc()

	# and send it
	sink = gr.file_sink(gr.sizeof_gr_complex,"ofdm_out")

        fg.connect ( inter, s2p, ifft1, p2s, interp )
	fg.connect ( interp, (rf_mix, 0))
	fg.connect ( rf_src, (rf_mix, 1))
	fg.connect ( rf_mix, sink )

	fg.start()
	raw_input ("Press Enter to stop:")
	fg.stop()

if __name__ == '__main__':
    main ()


A time domain plot of the transmitter out:






Receiver code:

#!/usr/bin/env python
#
# 

from gnuradio import gr, audio
from gmsk import gmsk_mod, gmsk_demod
import wx, random, math

def main():
        
        rf_rate = 240000
        sample_rate = 8e3

	# match gmsk in transmitter
	sps_gmsk = 8
	symbol_rate = sample_rate / sps_gmsk
	p_size = 128

        fft_size = 16

	fg = gr.flow_graph()

	# signal can come from a file, pipe, socket or hardware
	src = gr.file_source (gr.sizeof_gr_complex,"ofdm_out",0) # don't repeat

	# translate, decimate and amplify
	xlate_coeffs = gr.firdes.low_pass ( 2000, rf_rate, 6e3, 1e3, gr.firdes.WIN_HAMMING )
	xlate = gr.freq_xlating_fir_filter_ccf ( 30, xlate_coeffs, 60e3, rf_rate )

	# prepare for forward FFT
        s2p = gr.serial_to_parallel (gr.sizeof_gr_complex, fft_size)
        fft1 = gr.fft_vcc (fft_size,True,False) # forward, no windowing

        p2s = gr.parallel_to_serial (gr.sizeof_gr_complex, fft_size)

	# break out channels
	deinter = gr.deinterleave(gr.sizeof_gr_complex)

	# this is just to smooth out the psk31 signal
	of_coeffs = gr.firdes.low_pass ( 1, sample_rate, 400, 300, gr.firdes.WIN_HAMMING )
	of1 = gr.fir_filter_ccf (1, of_coeffs)
	c2fa = gr.complex_to_float()

	# then mix psk31 with audio for external decoder
	acarrier = gr.sig_source_f(sample_rate,gr.GR_SIN_WAVE,1e3,.9,0)
	amix = gr.multiply_ff ()
	asink = audio.sink(int(sample_rate))

	fg.connect ( of1, c2fa )
	fg.connect ( c2fa, (amix, 0))
	fg.connect ( acarrier, (amix, 1))
	fg.connect ( amix, asink )

#	probe1 = gr.file_sink(gr.sizeof_gr_complex,"probe1_recv")

	# demod gmsk to binary file
	demod = gmsk_demod(fg, sps_gmsk, symbol_rate, p_size)
	gmsk_file = gr.file_sink (gr.sizeof_char, "gmsk_out")

	# these are for all the unconnected deinterleave outputs
	ns1 = gr.null_sink(gr.sizeof_gr_complex)
        ns2 = gr.null_sink(gr.sizeof_gr_complex)
        ns3 = gr.null_sink(gr.sizeof_gr_complex)
        ns4 = gr.null_sink(gr.sizeof_gr_complex)
        ns5 = gr.null_sink(gr.sizeof_gr_complex)
        ns6 = gr.null_sink(gr.sizeof_gr_complex)
        ns7 = gr.null_sink(gr.sizeof_gr_complex)
        ns8 = gr.null_sink(gr.sizeof_gr_complex)
	ns9 = gr.null_sink(gr.sizeof_gr_complex)
        ns10 = gr.null_sink(gr.sizeof_gr_complex)
        ns11 = gr.null_sink(gr.sizeof_gr_complex)
        ns12 = gr.null_sink(gr.sizeof_gr_complex)
        ns13 = gr.null_sink(gr.sizeof_gr_complex)
        ns14 = gr.null_sink(gr.sizeof_gr_complex)
        ns15 = gr.null_sink(gr.sizeof_gr_complex)
        ns16 = gr.null_sink(gr.sizeof_gr_complex)

	fg.connect ( src, xlate, s2p, fft1, p2s, deinter )

	fg.connect ( (deinter, 0), ns1 )
        fg.connect ( (deinter, 1), ns2 )
        fg.connect ( (deinter, 2), ns3 )
        fg.connect ( (deinter, 3), ns4 )
        fg.connect ( (deinter, 4), ns5 )
        fg.connect ( (deinter, 5), ns6 )
        fg.connect ( (deinter, 6), ns7 )
        fg.connect ( (deinter, 7), of1 )	# just pick a psk signal
        fg.connect ( (deinter, 8), ns9 )
        fg.connect ( (deinter, 9), ns10 )
        fg.connect ( (deinter, 10), ns11 )
        fg.connect ( (deinter, 11), demod.head ) # also a gmsk signal
        fg.connect ( (deinter, 12), ns13 )
        fg.connect ( (deinter, 13), ns14 )
        fg.connect ( (deinter, 14), ns15 )
        fg.connect ( (deinter, 15), ns16 )

	fg.connect ( demod.tail, gmsk_file )

	fg.run()  # just run until EOF on input file, which plays only once

if __name__ == '__main__':
    main ()