Imported Upstream version 3.0.4
[debian/gnuradio] / gnuradio-core / src / python / gnuradio / blksimpl / dbpsk.py
1 #
2 # Copyright 2005,2006 Free Software Foundation, Inc.
3
4 # This file is part of GNU Radio
5
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
9 # any later version.
10
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING.  If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
20
21
22 # See gnuradio-examples/python/gmsk2 for examples
23
24 """
25 differential BPSK modulation and demodulation.
26 """
27
28 from gnuradio import gr, gru, modulation_utils
29 from math import pi, sqrt
30 import psk
31 import cmath
32 import Numeric
33 from pprint import pprint
34
35 # default values (used in __init__ and add_options)
36 _def_samples_per_symbol = 2
37 _def_excess_bw = 0.35
38 _def_gray_code = True
39 _def_verbose = False
40 _def_log = False
41
42 _def_costas_alpha = None
43 _def_gain_mu = 0.03
44 _def_mu = 0.05
45 _def_omega_relative_limit = 0.005
46
47
48 # /////////////////////////////////////////////////////////////////////////////
49 #                             DBPSK modulator
50 # /////////////////////////////////////////////////////////////////////////////
51
52 class dbpsk_mod(gr.hier_block):
53
54     def __init__(self, fg,
55                  samples_per_symbol=_def_samples_per_symbol,
56                  excess_bw=_def_excess_bw,
57                  gray_code=_def_gray_code,
58                  verbose=_def_verbose,
59                  log=_def_log):
60         """
61         Hierarchical block for RRC-filtered differential BPSK modulation.
62
63         The input is a byte stream (unsigned char) and the
64         output is the complex modulated signal at baseband.
65         
66         @param fg: flow graph
67         @type fg: flow graph
68         @param samples_per_symbol: samples per baud >= 2
69         @type samples_per_symbol: integer
70         @param excess_bw: Root-raised cosine filter excess bandwidth
71         @type excess_bw: float
72         @param gray_code: Tell modulator to Gray code the bits
73         @type gray_code: bool
74         @param verbose: Print information about modulator?
75         @type verbose: bool
76         @param log: Log modulation data to files?
77         @type log: bool
78         """
79
80         self._fg = fg
81         self._samples_per_symbol = samples_per_symbol
82         self._excess_bw = excess_bw
83         self._gray_code = gray_code
84
85         if not isinstance(self._samples_per_symbol, int) or self._samples_per_symbol < 2:
86             raise TypeError, ("sbp must be an integer >= 2, is %d" % self._samples_per_symbol)
87         
88         ntaps = 11 * self._samples_per_symbol
89
90         arity = pow(2,self.bits_per_symbol())
91        
92         # turn bytes into k-bit vectors
93         self.bytes2chunks = \
94           gr.packed_to_unpacked_bb(self.bits_per_symbol(), gr.GR_MSB_FIRST)
95
96         if self._gray_code:
97             self.symbol_mapper = gr.map_bb(psk.binary_to_gray[arity])
98         else:
99             self.symbol_mapper = gr.map_bb(psk.binary_to_ungray[arity])
100
101         self.diffenc = gr.diff_encoder_bb(arity)
102         
103         self.chunks2symbols = gr.chunks_to_symbols_bc(psk.constellation[arity])
104
105         # pulse shaping filter
106         self.rrc_taps = gr.firdes.root_raised_cosine(
107             self._samples_per_symbol, # gain  (samples_per_symbol since we're
108                                       # interpolating by samples_per_symbol)
109             self._samples_per_symbol, # sampling rate
110             1.0,                      # symbol rate
111             self._excess_bw,          # excess bandwidth (roll-off factor)
112             ntaps)
113
114         self.rrc_filter = gr.interp_fir_filter_ccf(self._samples_per_symbol,
115                                                    self.rrc_taps)
116
117         # Connect
118         fg.connect(self.bytes2chunks, self.symbol_mapper, self.diffenc,
119                    self.chunks2symbols, self.rrc_filter)
120
121         if verbose:
122             self._print_verbage()
123             
124         if log:
125             self._setup_logging()
126             
127         # Initialize base class
128         gr.hier_block.__init__(self, self._fg, self.bytes2chunks, self.rrc_filter)
129
130     def samples_per_symbol(self):
131         return self._samples_per_symbol
132
133     def bits_per_symbol(self=None):   # static method that's also callable on an instance
134         return 1
135     bits_per_symbol = staticmethod(bits_per_symbol)      # make it a static method.  RTFM
136
137     def add_options(parser):
138         """
139         Adds DBPSK modulation-specific options to the standard parser
140         """
141         parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw,
142                           help="set RRC excess bandwith factor [default=%default]")
143         parser.add_option("", "--no-gray-code", dest="gray_code",
144                           action="store_false", default=True,
145                           help="disable gray coding on modulated bits (PSK)")
146     add_options=staticmethod(add_options)
147
148     def extract_kwargs_from_options(options):
149         """
150         Given command line options, create dictionary suitable for passing to __init__
151         """
152         return modulation_utils.extract_kwargs_from_options(dbpsk_mod.__init__,
153                                                             ('self', 'fg'), options)
154     extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
155
156
157     def _print_verbage(self):
158         print "bits per symbol = %d" % self.bits_per_symbol()
159         print "Gray code = %s" % self._gray_code
160         print "RRC roll-off factor = %.2f" % self._excess_bw
161
162     def _setup_logging(self):
163         print "Modulation logging turned on."
164         self._fg.connect(self.bytes2chunks,
165                          gr.file_sink(gr.sizeof_char, "bytes2chunks.dat"))
166         self._fg.connect(self.symbol_mapper,
167                          gr.file_sink(gr.sizeof_char, "graycoder.dat"))
168         self._fg.connect(self.diffenc,
169                          gr.file_sink(gr.sizeof_char, "diffenc.dat"))
170         self._fg.connect(self.chunks2symbols,
171                          gr.file_sink(gr.sizeof_gr_complex, "chunks2symbols.dat"))
172         self._fg.connect(self.rrc_filter,
173                          gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat"))
174               
175
176 # /////////////////////////////////////////////////////////////////////////////
177 #                             DBPSK demodulator
178 #
179 #      Differentially coherent detection of differentially encoded BPSK
180 # /////////////////////////////////////////////////////////////////////////////
181
182 class dbpsk_demod(gr.hier_block):
183
184     def __init__(self, fg,
185                  samples_per_symbol=_def_samples_per_symbol,
186                  excess_bw=_def_excess_bw,
187                  costas_alpha=_def_costas_alpha,
188                  gain_mu=_def_gain_mu,
189                  mu=_def_mu,
190                  omega_relative_limit=_def_omega_relative_limit,
191                  gray_code=_def_gray_code,
192                  verbose=_def_verbose,
193                  log=_def_log):
194         """
195         Hierarchical block for RRC-filtered differential BPSK demodulation
196
197         The input is the complex modulated signal at baseband.
198         The output is a stream of bits packed 1 bit per byte (LSB)
199
200         @param fg: flow graph
201         @type fg: flow graph
202         @param samples_per_symbol: samples per symbol >= 2
203         @type samples_per_symbol: float
204         @param excess_bw: Root-raised cosine filter excess bandwidth
205         @type excess_bw: float
206         @param costas_alpha: loop filter gain
207         @type costas_alphas: float
208         @param gain_mu: for M&M block
209         @type gain_mu: float
210         @param mu: for M&M block
211         @type mu: float
212         @param omega_relative_limit: for M&M block
213         @type omega_relative_limit: float
214         @param gray_code: Tell modulator to Gray code the bits
215         @type gray_code: bool
216         @param verbose: Print information about modulator?
217         @type verbose: bool
218         @param debug: Print modualtion data to files?
219         @type debug: bool
220         """
221         
222         self._fg = fg
223         self._samples_per_symbol = samples_per_symbol
224         self._excess_bw = excess_bw
225         self._costas_alpha = costas_alpha
226         self._gain_mu = gain_mu
227         self._mu = mu
228         self._omega_relative_limit = omega_relative_limit
229         self._gray_code = gray_code
230         
231         if samples_per_symbol < 2:
232             raise TypeError, "samples_per_symbol must be >= 2, is %r" % (samples_per_symbol,)
233
234         arity = pow(2,self.bits_per_symbol())
235
236         # Automatic gain control
237         scale = (1.0/16384.0)
238         self.pre_scaler = gr.multiply_const_cc(scale)   # scale the signal from full-range to +-1
239         #self.agc = gr.agc2_cc(0.6e-1, 1e-3, 1, 1, 100)
240         self.agc = gr.feedforward_agc_cc(16, 1.0)
241
242         
243         # Costas loop (carrier tracking)
244         # The Costas loop is not needed for BPSK, though it can help. Turn the Costas loop on
245         # by setting an alpha value not None.
246         if self._costas_alpha is not None:
247             costas_order = 2
248             beta = .25 * self._costas_alpha * self._costas_alpha
249             self.costas_loop = gr.costas_loop_cc(self._costas_alpha, beta, 0.002, -0.002, costas_order)
250
251         # RRC data filter
252         ntaps = 11 * self._samples_per_symbol
253         self.rrc_taps = gr.firdes.root_raised_cosine(
254             1.0,                      # gain 
255             self._samples_per_symbol, # sampling rate
256             1.0,                      # symbol rate
257             self._excess_bw,          # excess bandwidth (roll-off factor)
258             ntaps)
259
260         self.rrc_filter=gr.fir_filter_ccf(1, self.rrc_taps)
261
262         # symbol clock recovery
263         omega = self._samples_per_symbol
264         gain_omega = .25 * self._gain_mu * self._gain_mu
265         self.clock_recovery=gr.clock_recovery_mm_cc(omega, gain_omega,
266                                                     self._mu, self._gain_mu,
267                                                     self._omega_relative_limit)
268
269         # find closest constellation point
270         rot = 1
271         rotated_const = map(lambda pt: pt * rot, psk.constellation[arity])
272         #print "rotated_const =", rotated_const
273
274         self.diffdec = gr.diff_phasor_cc()
275         #self.diffdec = gr.diff_decoder_bb(arity)
276
277         self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity))
278
279         if self._gray_code:
280             self.symbol_mapper = gr.map_bb(psk.gray_to_binary[arity])
281         else:
282             self.symbol_mapper = gr.map_bb(psk.ungray_to_binary[arity])
283         
284         # unpack the k bit vector into a stream of bits
285         self.unpack = gr.unpack_k_bits_bb(self.bits_per_symbol())
286
287         if verbose:
288             self._print_verbage()
289
290         if log:
291             self._setup_logging()
292
293         # Connect and Initialize base class
294         if self._costas_alpha is not None:   # With Costas Loop
295             self._fg.connect(self.pre_scaler, self.agc, self.costas_loop,
296                              self.rrc_filter, self.clock_recovery, self.diffdec,
297                              self.slicer, self.symbol_mapper, self.unpack)
298         else: # Without Costas Loop
299             self._fg.connect(self.pre_scaler, self.agc,
300                              self.rrc_filter, self.clock_recovery, self.diffdec,
301                              self.slicer, self.symbol_mapper, self.unpack)
302
303         gr.hier_block.__init__(self, self._fg, self.pre_scaler, self.unpack)
304
305     def samples_per_symbol(self):
306         return self._samples_per_symbol
307
308     def bits_per_symbol(self=None):   # staticmethod that's also callable on an instance
309         return 1
310     bits_per_symbol = staticmethod(bits_per_symbol)      # make it a static method.  RTFM
311
312     def _print_verbage(self):
313         print "bits per symbol = %d"         % self.bits_per_symbol()
314         print "Gray code = %s"               % self._gray_code
315         print "RRC roll-off factor = %.2f"   % self._excess_bw
316         if self._costas_alpha is not None:
317             print "Costas Loop alpha = %.5f"     % self._costas_alpha
318         else:
319             print "Costas Loop is turned off"
320         print "M&M symbol sync gain = %.5f"  % self._gain_mu
321         print "M&M symbol sync mu = %.5f"    % self._mu
322         print "M&M omega relative limit = %.5f" % self._omega_relative_limit
323
324     def _setup_logging(self):
325         print "Modulation logging turned on."
326         self._fg.connect(self.pre_scaler,
327                          gr.file_sink(gr.sizeof_gr_complex, "prescaler.dat"))
328         self._fg.connect(self.agc,
329                          gr.file_sink(gr.sizeof_gr_complex, "agc.dat"))
330         if self._costas_alpha is not None:
331             self._fg.connect(self.costas_loop,
332                              gr.file_sink(gr.sizeof_gr_complex, "costas_loop.dat"))
333             self._fg.connect((self.costas_loop,1),
334                              gr.file_sink(gr.sizeof_gr_complex, "costas_error.dat"))
335         self._fg.connect(self.rrc_filter,
336                          gr.file_sink(gr.sizeof_gr_complex, "rrc_filter.dat"))
337         self._fg.connect(self.clock_recovery,
338                          gr.file_sink(gr.sizeof_gr_complex, "clock_recovery.dat"))
339         self._fg.connect((self.clock_recovery,1),
340                          gr.file_sink(gr.sizeof_gr_complex, "clock_recovery_error.dat"))
341         self._fg.connect(self.diffdec,
342                          gr.file_sink(gr.sizeof_gr_complex, "diffdec.dat"))        
343         self._fg.connect(self.slicer,
344                         gr.file_sink(gr.sizeof_char, "slicer.dat"))
345         self._fg.connect(self.symbol_mapper,
346                          gr.file_sink(gr.sizeof_char, "symbol_mapper.dat"))
347         self._fg.connect(self.unpack,
348                          gr.file_sink(gr.sizeof_char, "unpack.dat"))
349         
350     def add_options(parser):
351         """
352         Adds DBPSK demodulation-specific options to the standard parser
353         """
354         parser.add_option("", "--excess-bw", type="float", default=_def_excess_bw,
355                           help="set RRC excess bandwith factor [default=%default] (PSK)")
356         parser.add_option("", "--no-gray-code", dest="gray_code",
357                           action="store_false", default=_def_gray_code,
358                           help="disable gray coding on modulated bits (PSK)")
359         parser.add_option("", "--costas-alpha", type="float", default=None,
360                           help="set Costas loop alpha value [default=%default] (PSK)")
361         parser.add_option("", "--gain-mu", type="float", default=_def_gain_mu,
362                           help="set M&M symbol sync loop gain mu value [default=%default] (GMSK/PSK)")
363         parser.add_option("", "--mu", type="float", default=_def_mu,
364                           help="set M&M symbol sync loop mu value [default=%default] (GMSK/PSK)")
365         parser.add_option("", "--omega-relative-limit", type="float", default=_def_omega_relative_limit,
366                           help="M&M clock recovery omega relative limit [default=%default] (GMSK/PSK)")
367     add_options=staticmethod(add_options)
368     
369     def extract_kwargs_from_options(options):
370         """
371         Given command line options, create dictionary suitable for passing to __init__
372         """
373         return modulation_utils.extract_kwargs_from_options(
374                  dbpsk_demod.__init__, ('self', 'fg'), options)
375     extract_kwargs_from_options=staticmethod(extract_kwargs_from_options)
376
377 #
378 # Add these to the mod/demod registry
379 #
380 modulation_utils.add_type_1_mod('dbpsk', dbpsk_mod)
381 modulation_utils.add_type_1_demod('dbpsk', dbpsk_demod)