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.