Updated FSF address in all files. Fixes ticket:51
[debian/gnuradio] / gnuradio-examples / python / gmsk2 / qpsk.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 2, 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 PSK and differential PSK modulation and demodulation.
26 """
27
28 from gnuradio import gr, gru
29 from math import pi, sqrt
30 import cmath
31 import Numeric
32 from pprint import pprint
33
34 _use_gray_code = True
35
36 def make_constellation(m):
37     return [cmath.exp(i * 2 * pi / m * 1j) for i in range(m)]
38         
39 # Common definition of constellations for Tx and Rx
40 constellation = {
41     2 : make_constellation(2),           # BPSK
42     4 : make_constellation(4),           # QPSK
43     8 : make_constellation(8)            # 8PSK
44     }
45
46 if 0:
47     print "const(2) ="
48     pprint(constellation[2])
49     print "const(4) ="
50     pprint(constellation[4])
51     print "const(8) ="
52     pprint(constellation[8])
53
54
55 if _use_gray_code:
56     # -----------------------
57     # Do Gray code
58     # -----------------------
59     # binary to gray coding
60     binary_to_gray = {
61         2 : (0, 1),
62         4 : (0, 1, 3, 2),
63         8 : (0, 1, 3, 2, 7, 6, 4, 5)
64         }
65     
66     # gray to binary
67     gray_to_binary = {
68         2 : (0, 1),
69         4 : (0, 1, 3, 2),
70         8 : (0, 1, 3, 2, 6, 7, 5, 4)
71         }
72 else:
73     # -----------------------
74     # Don't Gray code
75     # -----------------------
76     # identity mapping
77     binary_to_gray = {
78         2 : (0, 1),
79         4 : (0, 1, 2, 3),
80         8 : (0, 1, 2, 3, 4, 5, 6, 7)
81         }
82     
83     # identity mapping
84     gray_to_binary = {
85         2 : (0, 1),
86         4 : (0, 1, 2, 3),
87         8 : (0, 1, 2, 3, 4, 5, 6, 7)
88         }
89
90
91 # /////////////////////////////////////////////////////////////////////////////
92 #            mPSK mod/demod with steams of bytes as data i/o
93 # /////////////////////////////////////////////////////////////////////////////
94
95
96 class mpsk_mod(gr.hier_block):
97
98     def __init__(self, fg, spb, arity, excess_bw, diff=False):
99         """
100         Hierarchical block for RRC-filtered PSK modulation.
101
102         The input is a byte stream (unsigned char) and the
103         output is the complex modulated signal at baseband.
104
105         @param fg: flow graph
106         @type fg: flow graph
107         @param spb: samples per baud >= 2
108         @type spb: integer
109         @param excess_bw: Root-raised cosine filter excess bandwidth
110         @type excess_bw: float
111         @param arity: whick PSK: 2, 4, 8
112         @type arity: int in {2, 4, 8}
113         @param diff: differential PSK if true
114         @type diff: bool
115         """
116         if not isinstance(spb, int) or spb < 2:
117             raise TypeError, "sbp must be an integer >= 2"
118         self.spb = spb
119
120         if not arity in (2, 4):
121             raise ValueError, "n must be 2, 4, or 8"
122
123         ntaps = 11 * spb
124
125         bits_per_symbol = int(gru.log2(arity))
126         self.bits_per_symbol = bits_per_symbol
127         print "bits_per_symbol =", bits_per_symbol
128         
129         # turn bytes into k-bit vectors
130         self.bytes2chunks = \
131           gr.packed_to_unpacked_bb(bits_per_symbol, gr.GR_MSB_FIRST)
132
133         if True or arity > 2:
134             self.gray_coder = gr.map_bb(binary_to_gray[arity])
135         else:
136             self.gray_coder = None
137
138         if diff:
139             self.diffenc = gr.diff_encoder_bb(arity)
140         else:
141             self.diffenc = None
142
143         self.chunks2symbols = gr.chunks_to_symbols_bc(constellation[arity])
144
145         # pulse shaping filter
146         self.rrc_taps = gr.firdes.root_raised_cosine(
147                 spb,            # gain  (spb since we're interpolating by spb)
148                 spb,            # sampling rate
149                 1.0,            # symbol rate
150                 excess_bw,      # excess bandwidth (roll-off factor)
151                 ntaps)
152
153         self.rrc_filter = gr.interp_fir_filter_ccf(spb, self.rrc_taps)
154
155         # Connect
156         if self.gray_coder:
157             fg.connect(self.bytes2chunks, self.gray_coder)
158             t = self.gray_coder
159         else:
160             t = self.bytes2chunks
161
162         if diff:
163             fg.connect(t, self.diffenc, self.chunks2symbols, self.rrc_filter)
164         else:
165             fg.connect(t, self.chunks2symbols, self.rrc_filter)
166
167         if 1:
168             fg.connect(self.gray_coder,
169                        gr.file_sink(gr.sizeof_char, "graycoder.dat"))
170             if diff:
171                 fg.connect(self.diffenc,
172                            gr.file_sink(gr.sizeof_char, "diffenc.dat"))
173             
174         # Initialize base class
175         gr.hier_block.__init__(self, fg, self.bytes2chunks, self.rrc_filter)
176
177     def samples_per_baud(self):
178         return self.spb
179
180     def bits_per_baud(self):
181         return self.bits_per_symbol
182
183
184 class mpsk_demod__coherent_detection_of_differentially_encoded_psk(gr.hier_block):
185     def __init__(self, fg, spb, arity, excess_bw, diff=False, costas_alpha=0.005, gain_mu=0.05):
186         """
187         Hierarchical block for RRC-filtered PSK demodulation
188
189         The input is the complex modulated signal at baseband.
190         The output is a stream of bits packed 1 bit per byte (LSB)
191
192         @param fg: flow graph
193         @type fg: flow graph
194         @param spb: samples per baud >= 2
195         @type spb: float
196         @param excess_bw: Root-raised cosine filter excess bandwidth
197         @type excess_bw: float
198         @param arity: whick PSK: 2, 4, 8
199         @type arity: int in {2, 4, 8}
200         @param diff: differential PSK if true
201         @type diff: bool
202         @param costas_alpha: loop filter gain
203         @type costas_alphas: float
204         @param gain_mu:
205         @type gain_mu: float
206         """
207         if spb < 2:
208             raise TypeError, "sbp must be >= 2"
209         self.spb = spb
210
211         if not arity in (2, 4):
212             raise ValueError, "n must be 2 or 4"
213
214         if not diff and arity==4:
215             raise NotImplementedError, "non-differential QPSK not supported yet"
216
217         bits_per_symbol = int(gru.log2(arity))
218         print "bits_per_symbol =", bits_per_symbol
219
220         # Automatic gain control
221         self.agc = gr.agc_cc(1e-3, 1, 1)
222         
223         # Costas loop (carrier tracking)
224         # FIXME: need to decide how to handle this more generally; do we pull it from higher layer?
225         if arity == 2:
226             costas_order = 2
227             costas_alpha *= 15   # 2nd order loop needs more gain
228         else:
229             costas_order = 4
230         beta = .25 * costas_alpha * costas_alpha
231         self.costas_loop = gr.costas_loop_cc(costas_alpha, beta, 0.05, -0.05, costas_order)
232
233         # RRC data filter
234         ntaps = 11 * spb
235         self.rrc_taps = gr.firdes.root_raised_cosine(
236             1.0,                # gain 
237             spb,                # sampling rate
238             1.0,                # symbol rate
239             excess_bw,          # excess bandwidth (roll-off factor)
240             ntaps)
241
242         self.rrc_filter=gr.fir_filter_ccf(1, self.rrc_taps)
243
244         # symbol clock recovery
245         omega = spb
246         gain_omega = .25 * gain_mu * gain_mu
247         omega_rel_limit = 0.5
248         mu = 0.05
249         gain_mu = 0.1
250         self.clock_recovery=gr.clock_recovery_mm_cc(omega, gain_omega,
251                                                     mu, gain_mu, omega_rel_limit)
252
253         # find closest constellation point
254         #rot = .707 + .707j
255         rot = 1
256         rotated_const = map(lambda pt: pt * rot, constellation[arity])
257         print "rotated_const =", rotated_const
258
259         if(diff):
260             self.diffdec = gr.diff_phasor_cc()
261             #self.diffdec = gr.diff_decoder_bb(arity)
262
263         self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity))
264         self.gray_decoder = gr.map_bb(gray_to_binary[arity])
265         
266         # unpack the k bit vector into a stream of bits
267         self.unpack = gr.unpack_k_bits_bb(bits_per_symbol)
268
269         if(diff):
270             fg.connect(self.agc, self.costas_loop, self.rrc_filter, self.clock_recovery,
271                        self.diffdec, self.slicer, self.gray_decoder, self.unpack)
272         else:
273             fg.connect(self.agc, self.costas_loop, self.rrc_filter, self.clock_recovery,
274                        self.slicer, self.gray_decoder, self.unpack)
275
276         #fg.connect(self.agc, self.costas_loop, self.rrc_filter, self.clock_recovery,
277         #           self.slicer, self.diffdec, self.gray_decoder, self.unpack)
278         
279         # Debug sinks
280         if 1:
281             fg.connect(self.agc,
282                        gr.file_sink(gr.sizeof_gr_complex, "agc.dat"))
283             fg.connect(self.costas_loop,
284                        gr.file_sink(gr.sizeof_gr_complex, "costas_loop.dat"))
285             fg.connect(self.rrc_filter,
286                        gr.file_sink(gr.sizeof_gr_complex, "rrc.dat"))
287             fg.connect(self.clock_recovery,
288                        gr.file_sink(gr.sizeof_gr_complex, "clock_recovery.dat"))
289             fg.connect(self.slicer,
290                        gr.file_sink(gr.sizeof_char, "slicer.dat"))
291             if(diff):
292                 fg.connect(self.diffdec,
293                            gr.file_sink(gr.sizeof_gr_complex, "diffdec.dat"))
294                 #fg.connect(self.diffdec,
295                 #          gr.file_sink(gr.sizeof_char, "diffdec.dat"))
296             fg.connect(self.unpack,
297                        gr.file_sink(gr.sizeof_char, "unpack.dat"))
298
299         # Initialize base class
300         gr.hier_block.__init__(self, fg, self.agc, self.unpack)
301
302     def samples_per_baud(self):
303         return self.spb
304
305     def bits_per_baud(self):
306         return self.bits_per_symbol
307
308
309 #########################################################################
310
311 class mpsk_demod__coherent_detection_of_nondifferentially_encoded_psk(gr.hier_block):
312     def __init__(self, fg, spb, arity, excess_bw, diff=False, costas_alpha=0.005, gain_mu=0.05):
313         """
314         Hierarchical block for RRC-filtered PSK demodulation
315
316         The input is the complex modulated signal at baseband.
317         The output is a stream of bits packed 1 bit per byte (LSB)
318
319         @param fg: flow graph
320         @type fg: flow graph
321         @param spb: samples per baud >= 2
322         @type spb: float
323         @param excess_bw: Root-raised cosine filter excess bandwidth
324         @type excess_bw: float
325         @param arity: whick PSK: 2, 4, 8
326         @type arity: int in {2, 4, 8}
327         @param diff: differential PSK if true
328         @type diff: bool
329         @param costas_alpha: loop filter gain
330         @type costas_alphas: float
331         @param gain_mu:
332         @type gain_mu: float
333         """
334         if spb < 2:
335             raise TypeError, "sbp must be >= 2"
336         self.spb = spb
337
338         if not arity in (2, 4):
339             raise ValueError, "n must be 2 or 4"
340
341         bits_per_symbol = int(gru.log2(arity))
342         print "bits_per_symbol =", bits_per_symbol
343
344         # Automatic gain control
345         self.agc = gr.agc_cc(1e-3, 1, 1)
346         
347         # Costas loop (carrier tracking)
348         # FIXME: need to decide how to handle this more generally; do we pull it from higher layer?
349         if arity == 2:
350             costas_order = 2
351             costas_alpha *= 15   # 2nd order loop needs more gain
352         else:
353             costas_order = 4
354         beta = .25 * costas_alpha * costas_alpha
355         self.costas_loop = gr.costas_loop_cc(costas_alpha, beta, 0.05, -0.05, costas_order)
356
357         # RRC data filter
358         ntaps = 11 * spb
359         self.rrc_taps = gr.firdes.root_raised_cosine(
360             1.0,                # gain 
361             spb,                # sampling rate
362             1.0,                # symbol rate
363             excess_bw,          # excess bandwidth (roll-off factor)
364             ntaps)
365
366         self.rrc_filter=gr.fir_filter_ccf(1, self.rrc_taps)
367
368         # symbol clock recovery
369         omega = spb
370         gain_omega = .25 * gain_mu * gain_mu
371         omega_rel_limit = 0.5
372         mu = 0.05
373         gain_mu = 0.1
374         self.clock_recovery=gr.clock_recovery_mm_cc(omega, gain_omega,
375                                                     mu, gain_mu, omega_rel_limit)
376
377         # find closest constellation point
378         #rot = .707 + .707j
379         rot = 1
380         rotated_const = map(lambda pt: pt * rot, constellation[arity])
381         print "rotated_const =", rotated_const
382
383         self.slicer = gr.constellation_decoder_cb(rotated_const, range(arity))
384         self.gray_decoder = gr.map_bb(gray_to_binary[arity])
385         
386         # unpack the k bit vector into a stream of bits
387         self.unpack = gr.unpack_k_bits_bb(bits_per_symbol)
388
389         fg.connect(self.agc, self.costas_loop, self.rrc_filter, self.clock_recovery,
390                    self.slicer, self.gray_decoder, self.unpack)
391         
392         # Debug sinks
393         if 1:
394             fg.connect(self.agc,
395                        gr.file_sink(gr.sizeof_gr_complex, "agc.dat"))
396             fg.connect(self.costas_loop,
397                        gr.file_sink(gr.sizeof_gr_complex, "costas_loop.dat"))
398             fg.connect(self.rrc_filter,
399                        gr.file_sink(gr.sizeof_gr_complex, "rrc.dat"))
400             fg.connect(self.clock_recovery,
401                        gr.file_sink(gr.sizeof_gr_complex, "clock_recovery.dat"))
402             fg.connect(self.slicer,
403                        gr.file_sink(gr.sizeof_char, "slicer.dat"))
404             fg.connect(self.unpack,
405                        gr.file_sink(gr.sizeof_char, "unpack.dat"))
406
407         # Initialize base class
408         gr.hier_block.__init__(self, fg, self.agc, self.unpack)
409
410     def samples_per_baud(self):
411         return self.spb
412
413     def bits_per_baud(self):
414         return self.bits_per_symbol
415
416
417 mpsk_demod = mpsk_demod__coherent_detection_of_differentially_encoded_psk
418 #mpsk_demod = mpsk_demod__coherent_detection_of_nondifferentially_encoded_psk