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 ()