diff options
author | Ben Reynwar <ben@reynwar.net> | 2013-05-19 02:55:33 -0700 |
---|---|---|
committer | Ben Reynwar <ben@reynwar.net> | 2013-05-19 02:55:33 -0700 |
commit | bb01988e75d50d82cbb44c1a49c86c1d08f05665 (patch) | |
tree | 0528dac14476d37f2cde7374a8fcb3428f879c69 /gr-digital/python/digital/ofdm_txrx.py | |
parent | e4f0319eced22c112f7e6a4cc45bc2036d285332 (diff) | |
parent | 0fa219774dcf9141ae91204f948c029b05673f3f (diff) |
Merged in next_docs branch.
Diffstat (limited to 'gr-digital/python/digital/ofdm_txrx.py')
-rw-r--r-- | gr-digital/python/digital/ofdm_txrx.py | 280 |
1 files changed, 202 insertions, 78 deletions
diff --git a/gr-digital/python/digital/ofdm_txrx.py b/gr-digital/python/digital/ofdm_txrx.py index 37c4086cc3..8f1b4c5f02 100644 --- a/gr-digital/python/digital/ofdm_txrx.py +++ b/gr-digital/python/digital/ofdm_txrx.py @@ -1,5 +1,5 @@ # -# Copyright 2005-2007,2013 Free Software Foundation, Inc. +# Copyright 2013 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -25,6 +25,9 @@ For simple configurations, no need to connect all the relevant OFDM blocks to form an OFDM Tx/Rx--simply use these. """ +# Reminder: All frequency-domain stuff is in shifted form, i.e. DC carrier +# in the middle! + import numpy from gnuradio import gr import digital_swig as digital @@ -44,20 +47,59 @@ except ImportError: _def_fft_len = 64 _def_cp_len = 16 _def_frame_length_tag_key = "frame_length" -_def_packet_length_tag_key = "frame_length" -_def_packet_num_tag_key = "" -_def_occupied_carriers=(range(1, 27) + range(38, 64),) -_def_pilot_carriers=((0,),) -_def_pilot_symbols=((100,),) +_def_packet_length_tag_key = "packet_length" +_def_packet_num_tag_key = "packet_num" +# Data and pilot carriers are same as in 802.11a +_def_occupied_carriers = (range(-26, -21) + range(-20, -7) + range(-6, 0) + range(1, 7) + range(8, 21) + range(22, 27),) +_def_pilot_carriers=((-21, -7, 7, 21,),) +_pilot_sym_scramble_seq = ( + 1,1,1,1, -1,-1,-1,1, -1,-1,-1,-1, 1,1,-1,1, -1,-1,1,1, -1,1,1,-1, 1,1,1,1, 1,1,-1,1, + 1,1,-1,1, 1,-1,-1,1, 1,1,-1,1, -1,-1,-1,1, -1,1,-1,-1, 1,-1,-1,1, 1,1,1,1, -1,-1,1,1, + -1,-1,1,-1, 1,-1,1,1, -1,-1,-1,1, 1,-1,-1,-1, -1,1,-1,-1, 1,-1,1,1, 1,1,-1,1, -1,1,-1,1, + -1,-1,-1,-1, -1,1,-1,1, 1,-1,1,-1, 1,1,1,-1, -1,1,-1,-1, -1,1,1,1, -1,-1,-1,-1, -1,-1,-1 +) +_def_pilot_symbols= tuple([(x, x, x, -x) for x in _pilot_sym_scramble_seq]) _seq_seed = 42 -def _make_sync_word(fft_len, occupied_carriers, constellation): - """ Makes a random sync sequence """ - occupied_carriers = list(occupied_carriers[0]) - occupied_carriers = [occupied_carriers[x] + fft_len if occupied_carriers[x] < 0 else occupied_carriers[x] for x in range(len(occupied_carriers))] + +def _get_active_carriers(fft_len, occupied_carriers, pilot_carriers): + active_carriers = list() + for carrier in list(occupied_carriers[0]) + list(pilot_carriers[0]): + if carrier < 0: + carrier += fft_len + active_carriers.append(carrier) + return active_carriers + +def _make_sync_word1(fft_len, occupied_carriers, pilot_carriers): + """ Creates a random sync sequence for fine frequency offset and timing + estimation. This is the first of typically two sync preamble symbols + for the Schmidl & Cox sync algorithm. + The relevant feature of this symbols is that every second sub-carrier + is zero. In the time domain, this results in two identical halves of + the OFDM symbols. + Symbols are always BPSK symbols. Carriers are scaled by sqrt(2) to keep + total energy constant. + Carrier 0 (DC carrier) is always zero. If used, carrier 1 is non-zero. + This means the sync algorithm has to check on odd carriers! + """ + active_carriers = _get_active_carriers(fft_len, occupied_carriers, pilot_carriers) numpy.random.seed(_seq_seed) - sync_sequence = [constellation.map_to_points_v(numpy.random.randint(constellation.arity()))[0] * numpy.sqrt(2) if x in occupied_carriers and x % 3 else 0 for x in range(fft_len)] - return sync_sequence + bpsk = {0: numpy.sqrt(2), 1: -numpy.sqrt(2)} + sw1 = [bpsk[numpy.random.randint(2)] if x in active_carriers and x % 2 else 0 for x in range(fft_len)] + return numpy.fft.fftshift(sw1) + +def _make_sync_word2(fft_len, occupied_carriers, pilot_carriers): + """ Creates a random sync sequence for coarse frequency offset and channel + estimation. This is the second of typically two sync preamble symbols + for the Schmidl & Cox sync algorithm. + Symbols are always BPSK symbols. + """ + active_carriers = _get_active_carriers(fft_len, occupied_carriers, pilot_carriers) + numpy.random.seed(_seq_seed) + bpsk = {0: 1, 1: -1} + sw2 = [bpsk[numpy.random.randint(2)] if x in active_carriers else 0 for x in range(fft_len)] + sw2[0] = 0j + return numpy.fft.fftshift(sw2) def _get_constellation(bps): """ Returns a modulator block for a given number of bits per symbol """ @@ -73,22 +115,28 @@ def _get_constellation(bps): exit(1) class ofdm_tx(gr.hier_block2): - """ - Hierarchical block for OFDM modulation. + """ Hierarchical block for OFDM modulation. The input is a byte stream (unsigned char) and the output is the complex modulated signal at baseband. Args: fft_len: The length of FFT (integer). - cp_len: The length of cyclic prefix (integer). - occupied_carriers: ?? - pilot_carriers: ?? - pilot_symbols: ?? - length_tag_key: The name of the tag giving packet length. + cp_len: The length of cyclic prefix in total samples (integer). + packet_length_tag_key: The name of the tag giving packet length at the input. + occupied_carriers: A vector of vectors describing which OFDM carriers are occupied. + pilot_carriers: A vector of vectors describing which OFDM carriers are occupied with pilot symbols. + pilot_symbols: The pilot symbols. + bps_header: Bits per symbol (header). + bps_payload: Bits per symbol (payload). + sync_word1: The first sync preamble symbol. This has to be with zeros on alternating carriers. + Used for fine and coarse frequency offset and timing estimation. + sync_word2: The second sync preamble symbol. This has to be filled entirely. Also used for + coarse frequency offset and channel estimation. + rolloff: The rolloff length in samples. Must be smaller than the CP. """ def __init__(self, fft_len=_def_fft_len, cp_len=_def_cp_len, - frame_length_tag_key=_def_frame_length_tag_key, + packet_length_tag_key=_def_packet_length_tag_key, occupied_carriers=_def_occupied_carriers, pilot_carriers=_def_pilot_carriers, pilot_symbols=_def_pilot_symbols, @@ -96,84 +144,112 @@ class ofdm_tx(gr.hier_block2): bps_payload=1, sync_word1=None, sync_word2=None, - rolloff=0 + rolloff=0, + debug_log=False ): gr.hier_block2.__init__(self, "ofdm_tx", gr.io_signature(1, 1, gr.sizeof_char), gr.io_signature(1, 1, gr.sizeof_gr_complex)) + ### Param init / sanity check ######################################## self.fft_len = fft_len self.cp_len = cp_len - self.frame_length_tag_key = frame_length_tag_key + self.packet_length_tag_key = packet_length_tag_key self.occupied_carriers = occupied_carriers self.pilot_carriers = pilot_carriers self.pilot_symbols = pilot_symbols self.bps_header = bps_header self.bps_payload = bps_payload n_sync_words = 1 - header_constellation = _get_constellation(bps_header) - header_mod = digital.chunks_to_symbols_bc(header_constellation.points()) self.sync_word1 = sync_word1 if sync_word1 is None: - self.sync_word1 = _make_sync_word(fft_len, occupied_carriers, header_constellation) + self.sync_word1 = _make_sync_word1(fft_len, occupied_carriers, pilot_carriers) else: if len(sync_word1) != self.fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") - self.sync_words = [sync_word1,] + self.sync_words = [self.sync_word1,] self.sync_word2 = () - if sync_word2 is not None: - if len(sync_word2) != fft_len: + if sync_word2 is None: + self.sync_word2 = _make_sync_word2(fft_len, occupied_carriers, pilot_carriers) + if len(self.sync_word2): + if len(self.sync_word2) != fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") - self.sync_word2 = sync_word2 + self.sync_word2 = list(self.sync_word2) n_sync_words = 2 self.sync_words.append(self.sync_word2) - crc = digital.crc32_bb(False, self.frame_length_tag_key) + ### Header modulation ################################################ + crc = digital.crc32_bb(False, self.packet_length_tag_key) + header_constellation = _get_constellation(bps_header) + header_mod = digital.chunks_to_symbols_bc(header_constellation.points()) formatter_object = digital.packet_header_ofdm( occupied_carriers=occupied_carriers, n_syms=1, - bits_per_sym=self.bps_header + bits_per_header_sym=self.bps_header, + bits_per_payload_sym=self.bps_payload ) - header_gen = digital.packet_headergenerator_bb(formatter_object.base(), self.frame_length_tag_key) - header_payload_mux = blocks.tagged_stream_mux(gr.sizeof_gr_complex*1, self.frame_length_tag_key) + header_gen = digital.packet_headergenerator_bb(formatter_object.base(), self.packet_length_tag_key) + header_payload_mux = blocks.tagged_stream_mux(gr.sizeof_gr_complex*1, self.packet_length_tag_key) self.connect(self, crc, header_gen, header_mod, (header_payload_mux, 0)) + if debug_log: + self.connect(header_gen, blocks.file_sink(1, 'tx-hdr.dat')) + ### Payload modulation ############################################### payload_constellation = _get_constellation(bps_payload) payload_mod = digital.chunks_to_symbols_bc(payload_constellation.points()) self.connect( crc, - blocks.repack_bits_bb(8, bps_payload, frame_length_tag_key), + blocks.repack_bits_bb( + 8, # Unpack 8 bits per byte + bps_payload, + self.packet_length_tag_key + ), payload_mod, (header_payload_mux, 1) ) + ### Create OFDM frame ################################################ allocator = digital.ofdm_carrier_allocator_cvc( self.fft_len, occupied_carriers=self.occupied_carriers, pilot_carriers=self.pilot_carriers, pilot_symbols=self.pilot_symbols, sync_words=self.sync_words, - len_tag_key=self.frame_length_tag_key + len_tag_key=self.packet_length_tag_key + ) + ffter = fft.fft_vcc( + self.fft_len, + False, # Inverse FFT + (), # No window + True # Shift ) - ffter = fft.fft_vcc(self.fft_len, False, (), True) cyclic_prefixer = digital.ofdm_cyclic_prefixer( self.fft_len, self.fft_len+self.cp_len, rolloff, - self.frame_length_tag_key + self.packet_length_tag_key ) self.connect(header_payload_mux, allocator, ffter, cyclic_prefixer, self) + if debug_log: + self.connect(allocator, blocks.file_sink(8*64, 'tx-post-allocator.dat')) + self.connect(cyclic_prefixer, blocks.file_sink(8, 'tx-signal.dat')) class ofdm_rx(gr.hier_block2): - """ - Hierarchical block for OFDM demodulation. + """ Hierarchical block for OFDM demodulation. - The input is a byte stream (unsigned char) and the - output is the complex modulated signal at baseband. + The input is a complex baseband signal (e.g. from a UHD source). + The detected packets are output as a stream of packed bits on the output. Args: fft_len: The length of FFT (integer). - cp_len: The length of cyclic prefix (integer). - occupied_carriers: ?? - pilot_carriers: ?? - pilot_symbols: ?? - length_tag_key: The name of the tag giving packet length. + cp_len: The length of cyclic prefix in total samples (integer). + frame_length_tag_key: Used internally to tag the length of the OFDM frame. + packet_length_tag_key: The name of the tag giving packet length at the input. + occupied_carriers: A vector of vectors describing which OFDM carriers are occupied. + pilot_carriers: A vector of vectors describing which OFDM carriers are occupied with pilot symbols. + pilot_symbols: The pilot symbols. + bps_header: Bits per symbol (header). + bps_payload: Bits per symbol (payload). + sync_word1: The first sync preamble symbol. This has to be with zeros on alternating carriers. + Used for fine and coarse frequency offset and timing estimation. + sync_word2: The second sync preamble symbol. This has to be filled entirely. Also used for + coarse frequency offset and channel estimation. """ def __init__(self, fft_len=_def_fft_len, cp_len=_def_cp_len, frame_length_tag_key=_def_frame_length_tag_key, @@ -185,11 +261,13 @@ class ofdm_rx(gr.hier_block2): bps_header=1, bps_payload=1, sync_word1=None, - sync_word2=None + sync_word2=None, + debug_log=False ): gr.hier_block2.__init__(self, "ofdm_rx", gr.io_signature(1, 1, gr.sizeof_gr_complex), gr.io_signature(1, 1, gr.sizeof_char)) + ### Param init / sanity check ######################################## self.fft_len = fft_len self.cp_len = cp_len self.frame_length_tag_key = frame_length_tag_key @@ -198,44 +276,60 @@ class ofdm_rx(gr.hier_block2): self.bps_header = bps_header self.bps_payload = bps_payload n_sync_words = 1 - header_constellation = _get_constellation(bps_header) if sync_word1 is None: - self.sync_word1 = _make_sync_word(fft_len, occupied_carriers, header_constellation) + self.sync_word1 = _make_sync_word1(fft_len, occupied_carriers, pilot_carriers) else: if len(sync_word1) != self.fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") self.sync_word1 = sync_word1 self.sync_word2 = () - if sync_word2 is not None: + if sync_word2 is None: + self.sync_word2 = _make_sync_word2(fft_len, occupied_carriers, pilot_carriers) + n_sync_words = 2 + elif len(sync_word2): if len(sync_word2) != fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") self.sync_word2 = sync_word2 n_sync_words = 2 - else: - sync_word2 = () - # Receiver path + ### Sync ############################################################ sync_detect = digital.ofdm_sync_sc_cfb(fft_len, cp_len) + delay = blocks.delay(gr.sizeof_gr_complex, fft_len+cp_len) oscillator = analog.frequency_modulator_fc(-2.0 / fft_len) - delay = gr.delay(gr.sizeof_gr_complex, fft_len+cp_len) - mixer = gr.multiply_cc() - hpd = digital.header_payload_demux(n_sync_words, fft_len, cp_len, - frame_length_tag_key, "", True) + mixer = blocks.multiply_cc() + hpd = digital.header_payload_demux( + n_sync_words+1, # Number of OFDM symbols before payload (sync + 1 sym header) + fft_len, cp_len, # FFT length, guard interval + frame_length_tag_key, # Frame length tag key + "", # We're not using trigger tags + True # One output item is one OFDM symbol (False would output complex scalars) + ) self.connect(self, sync_detect) - self.connect((sync_detect, 0), oscillator, (mixer, 0)) - self.connect(self, delay, (mixer, 1)) - self.connect(mixer, (hpd, 0)) + self.connect(self, delay, (mixer, 0), (hpd, 0)) + self.connect((sync_detect, 0), oscillator, (mixer, 1)) self.connect((sync_detect, 1), (hpd, 1)) - # Header demodulation - header_fft = fft.fft_vcc(self.fft_len, True, (), True) - chanest = digital.ofdm_chanest_vcvc(self.sync_word1, self.sync_word2, 1) - header_equalizer = digital.ofdm_equalizer_simpledfe( - fft_len, header_constellation.base(), - occupied_carriers, pilot_carriers, pilot_symbols - ) - header_eq = digital.ofdm_frame_equalizer_vcvc(header_equalizer.base(), frame_length_tag_key, True) - header_serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers) + if debug_log: + self.connect((sync_detect, 0), blocks.file_sink(gr.sizeof_float, 'freq-offset.dat')) + self.connect((sync_detect, 1), blocks.file_sink(gr.sizeof_char, 'sync-detect.dat')) + ### Header demodulation ############################################## + header_fft = fft.fft_vcc(self.fft_len, True, (), True) + chanest = digital.ofdm_chanest_vcvc(self.sync_word1, self.sync_word2, 1) header_constellation = _get_constellation(bps_header) - header_demod = digital.constellation_decoder_cb(header_constellation.base()) + header_equalizer = digital.ofdm_equalizer_simpledfe( + fft_len, header_constellation.base(), + occupied_carriers, pilot_carriers, pilot_symbols, 0, 0 + ) + header_eq = digital.ofdm_frame_equalizer_vcvc( + header_equalizer.base(), + cp_len, + self.frame_length_tag_key, + True, + 1 # Header is 1 symbol long + ) + header_serializer = digital.ofdm_serializer_vcc( + fft_len, occupied_carriers, + self.frame_length_tag_key + ) + header_demod = digital.constellation_decoder_cb(header_constellation.base()) header_formatter = digital.packet_header_ofdm( occupied_carriers, 1, packet_length_tag_key, @@ -246,16 +340,46 @@ class ofdm_rx(gr.hier_block2): header_parser = digital.packet_headerparser_b(header_formatter.formatter()) self.connect((hpd, 0), header_fft, chanest, header_eq, header_serializer, header_demod, header_parser) self.msg_connect(header_parser, "header_data", hpd, "header_data") - # Payload demodulation + if debug_log: + self.connect((chanest, 1), blocks.file_sink(512, 'channel-estimate.dat')) + self.connect((chanest, 0), blocks.file_sink(512, 'post-hdr-chanest.dat')) + self.connect(header_eq, blocks.file_sink(512, 'post-hdr-eq.dat')) + self.connect(header_serializer, blocks.file_sink(8, 'post-hdr-serializer.dat')) + self.connect(header_demod, blocks.file_sink(1, 'post-hdr-demod.dat')) + self.connect(header_demod, blocks.tag_debug(1, 'post-hdr-demod.dat')) + ### Payload demod #################################################### payload_fft = fft.fft_vcc(self.fft_len, True, (), True) + payload_constellation = _get_constellation(bps_payload) payload_equalizer = digital.ofdm_equalizer_simpledfe( - fft_len, header_constellation.base(), - occupied_carriers, pilot_carriers, pilot_symbols, 1 + fft_len, payload_constellation.base(), + occupied_carriers, + pilot_carriers, + pilot_symbols, + 1 # Skip 1 symbol (that was already in the header) + ) + payload_eq = digital.ofdm_frame_equalizer_vcvc( + payload_equalizer.base(), + cp_len, + self.frame_length_tag_key + ) + payload_serializer = digital.ofdm_serializer_vcc( + fft_len, occupied_carriers, + self.frame_length_tag_key, + self.packet_length_tag_key, + 1 # Skip 1 symbol (that was already in the header) ) - payload_eq = digital.ofdm_frame_equalizer_vcvc(payload_equalizer.base(), frame_length_tag_key) - payload_serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers) - payload_constellation = _get_constellation(bps_payload) payload_demod = digital.constellation_decoder_cb(payload_constellation.base()) - bit_packer = blocks.repack_bits_bb(bps_payload, 8, packet_length_tag_key, True) - self.connect((hpd, 1), payload_fft, payload_eq, payload_serializer, payload_demod, bit_packer, self) + repack = blocks.repack_bits_bb(bps_payload, 8, self.packet_length_tag_key, True) + crc = digital.crc32_bb(True, self.packet_length_tag_key) + self.connect((hpd, 1), payload_fft, payload_eq, payload_serializer, payload_demod, repack, crc, self) + if debug_log: + self.connect((hpd, 1), blocks.tag_debug(8*64, 'post-hpd')); + self.connect(payload_fft, blocks.file_sink(8*64, 'post-payload-fft.dat')) + self.connect(payload_eq, blocks.file_sink(8*64, 'post-payload-eq.dat')) + self.connect(payload_serializer, blocks.file_sink(8, 'post-payload-serializer.dat')) + self.connect(payload_demod, blocks.file_sink(1, 'post-payload-demod.dat')) + self.connect(repack, blocks.file_sink(1, 'post-payload-repack.dat')) + self.connect(crc, blocks.file_sink(1, 'post-payload-crc.dat')) + self.connect(crc, blocks.tag_debug(1, 'post-payload-crc')) + |