Experiments in PSK31
PSK31 Documents:
Fundamentals
G3PLX
Root raised cosine filter
This set of scripts will read a keyboard and generate PSK31 audio suitable
for a SSB transmitter. I've tested it looped back to 'linpsk' and adjusted
the pulse filter for IMD of -42 to -50. There are three parts: one called
keyboard.py works in a terminal window and sets
it for raw input, polls the keyboard and translates characters into varicode
using varicode_table.py then into a phase-shift
stream and sends them to
a pipe named /tmp/pskpipe. Type Ctrl-C to end transmission. You'll have to run #mkfifo /tmp/pskpipe to use
it. The third section called psk.py reads the phase-shift
stream, interpolates it to 256 samples / symbol ( for 31.25 baud at 8000
samples / second ), filters the pulse for low IMD then uses it to modulate
an audio tone. You can set the audio tone frequency with an option argument to psk.py
like $ psk.py -t 1500 for 1500hz.
The next step will be sending the psk signal directly to the USRP, amplifiers
and antenna and eleminate the audio / ssb transmitter.
The following programs can translate an ASCII text file to a 'phase shift'
format file and then send it as PSK31 receivable by any psk31 program.
varicode_table.py is used by
ascii_varicode.py to translate an ASCII
file named 'text' into a phase shift file named 'psk'. Then
psk_send-file.py will read 'psk' and generate
the PSK audio to send the original text file.
Test code:
samp_freq = 8000
data_rate = 62.5 # 31.25? This looks better
svec = []
# following sends 'a' repeatedly with '00' gap
# for p in ( -1., 1., 1., 1., 1., 1., 1., -1., -1., \
1., -1., -1., -1., -1., -1., -1., 1., 1. ):
# this sends an idle signal, continuous 0's
for p in ( -1., 1., -1., 1., -1., 1., -1., 1. ):
# 31.25 baud = 8000 sps / 256 samples
for k in range(0,256):
svec += [p]
src = gr.vector_source_f(svec,1)
ps_coeffs = gr.firdes_root_raised_cosine ( \
1, # gain
samp_freq, #
data_rate, # = baud for bpsk
.36, # .36 alpha = 1 - 16/25
255 ) # guess
pulse_shaper = gr.fir_filter_fff(1,ps_coeffs)
tone = gr.sig_source_f(samp_freq, gr.GR_SIN_WAVE,1e3,.9,0)
mixer = gr.multiply_ff()
# dst = audio.sink(samp_freq)
dst = gr.file_sink(gr.sizeof_float,"test_rrc_out")
# inp = gr.file_sink(gr.sizeof_float, "input");
# outp = gr.file_sink(gr.sizeof_float, "output");
# bytes_to_syms = gr.bytes_to_syms()
# bpsk = gr.bpsk_ff(samps_per_bit)
self.connect(src, pulse_shaper)
self.connect(pulse_shaper,(mixer,0))
self.connect(tone, (mixer,1))
self.connect(mixer,dst)
idle signal:
In psk31, a '0' is a phase reversal, while a '1' is no phase reversal.
Characters always have '00' between them, and a = '1111101', so this should be
the letter 'a' repeatedly sent:
The above DOES work - getting -25db IMD which could be better.
Interestingly, the big block on the left is phase-A, and the next block would
be phase-B - that is, each letter 'a' is opposite phase from the preceeding
and following character - which is why we need 18 symbols for two letters
in the code instead of just 9.
The following addition to the above code is an improvement in creating the signal
from a manually created Varicode string using bytes_to_sym (parallel to serial
w/ bits 1 & 0 translated to 1 & -1) and Joshua Lackey's method of extending
the samples per symbol to get 256 samples for each symbol, which comes out to
31.25 baud (8000 samples/second / 256 samples = 31.25 symbols / second ).
This manually created message repeats " DE KB4NEW" over and over.
sps = 256
samp_freq = 8000
data_rate = 62.5 # 31.25? This looks better
svec = [78,111,9,63,33,137,225,30,67,217,229,2] # DE KB4NEW
src = gr.vector_source_b(svec,1)
bytes_to_syms = gr.bytes_to_syms()
interp_taps = (1,) * sps
interp = gr.interp_fir_filter_fff(sps, interp_taps)
ps_coeffs = gr.firdes_root_raised_cosine ( \
1, # gain
samp_freq, #
data_rate, # = baud for bpsk
.36, # .36 alpha = 1 - 16/25
255 ) # guess
pulse_shaper = gr.fir_filter_fff(1,ps_coeffs)
tone = gr.sig_source_f(samp_freq, gr.GR_SIN_WAVE,1e3,.9,0)
mixer = gr.multiply_ff()
dst = audio.sink(samp_freq)
self.connect(src, bytes_to_syms)
self.connect(bytes_to_syms, interp)
self.connect(interp,pulse_shaper)
self.connect(pulse_shaper,(mixer,0))
self.connect(tone, (mixer,1))
self.connect(mixer,dst)
Another interesting thing is that with two notebooks connected with an audio cable, one
running the above gnuradio psk31 generator and the other running gpsk31,
the text comes thru even with the transmitter line-out turned down to the
minimum that the slider will go. The next step is to write some python
that will automatically translate an ascii string or text file to Varicode
and send the psk31 signal.