c008d76e055be0da2cb540286d91bc3e5c70b923
[debian/gnuradio] / gr-usrp / src / db_wbx.py
1 #
2 # Copyright 2007 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 from gnuradio import usrp1
23 import time,math
24
25 from usrpm import usrp_dbid
26 import db_base
27 import db_instantiator
28 from usrpm.usrp_fpga_regs import *
29
30 #debug_using_gui = True                  # Must be set to True or False
31 debug_using_gui = False                  # Must be set to True or False
32
33 #if debug_using_gui:
34 #    import flexrf_debug_gui
35
36 # d'board i/o pin defs
37
38 # TX IO Pins
39 TX_POWER = (1 << 0)         # TX Side Power
40 RX_TXN = (1 << 1)           # T/R antenna switch for TX/RX port
41
42 # RX IO Pins
43 RX2_RX1N = (1 << 0)         # antenna switch between RX2 and TX/RX port
44 RXENABLE = (1 << 1)         # enables mixer
45 PLL_LOCK_DETECT = (1 << 2)  # Muxout pin from PLL -- MUST BE INPUT
46 MReset = (1 << 3)           # NB6L239 Master Reset, asserted low
47 SELA0 = (1 << 4)            # NB6L239 SelA0
48 SELA1 = (1 << 5)            # NB6L239 SelA1
49 SELB0 = (1 << 6)            # NB6L239 SelB0
50 SELB1 = (1 << 7)            # NB6L239 SelB1
51 PLL_ENABLE = (1 << 8)       # CE Pin on PLL
52 AUX_SCLK = (1 << 9)         # ALT SPI SCLK
53 AUX_SDO = (1 << 10)         # ALT SPI SDO
54 AUX_SEN = (1 << 11)         # ALT SPI SEN
55
56 SPI_ENABLE_TX_A = usrp1.SPI_ENABLE_TX_A
57 SPI_ENABLE_TX_B = usrp1.SPI_ENABLE_TX_B
58 SPI_ENABLE_RX_A = usrp1.SPI_ENABLE_RX_A
59 SPI_ENABLE_RX_B = usrp1.SPI_ENABLE_RX_B
60
61
62 """
63 A few comments about the WBX boards:
64   They are half-duplex.  I.e., transmit and receive are mutually exclusive.
65   There is a single LO for both the Tx and Rx sides.
66   The the shared control signals are hung off of the Rx side.
67   The shared io controls are duplexed onto the Rx side pins.
68   The wbx_high d'board always needs to be in 'auto_tr_mode'
69 """
70
71
72 class wbx_base(db_base.db_base):
73     """
74     Abstract base class for all wbx boards.
75
76     Derive board specific subclasses from db_wbx_base_{tx,rx}
77     """
78     def __init__(self, usrp, which):
79         """
80         @param usrp: instance of usrp.source_c
81         @param which: which side: 0 or 1 corresponding to side A or B respectively
82         @type which: int
83         """
84         # sets _u  _which _tx and _slot
85         db_base.db_base.__init__(self, usrp, which)
86
87         self.first = True
88         self.spi_format = usrp1.SPI_FMT_MSB | usrp1.SPI_FMT_HDR_0
89
90         # FIXME figure our right answer here...
91         #self._u._tx_write_oe(0, 0xffff)         # turn off all outputs
92         #self._u._rx_write_oe(0, 0xffff)         # turn off all outputs
93
94         self.set_auto_tr(False)
95         
96         #if debug_using_gui:
97         #    title = "FlexRF Debug Rx"
98         #    if self._tx:
99         #        title = "FlexRF Debug Tx"
100         #    self.gui = flexrf_debug_gui.flexrf_debug_gui(self, title)
101         #    self.gui.Show(True)
102
103
104     def __del__(self):
105         #self._u.write_io(self._which, self.power_off, POWER_UP)   # turn off power to board
106         #self._u._write_oe(self._which, 0, 0xffff)   # turn off all outputs
107         self.set_auto_tr(False)
108
109     def _lock_detect(self):
110         """
111         @returns: the value of the VCO/PLL lock detect bit.
112         @rtype: 0 or 1
113         """
114         if self._u.rx_read_io(self._which) & PLL_LOCK_DETECT:
115             return True
116         else:      # Give it a second chance
117             if self._u.read_io(self._which) & PLL_LOCK_DETECT:
118                 return True
119             else:
120                 return False
121         
122     # Both sides need access to the Rx pins.
123     # Write them directly, bypassing the convenience routines.
124     # (Sort of breaks modularity, but will work...)
125
126     def _tx_write_oe(self, value, mask):
127         return self._u._write_fpga_reg((FR_OE_0, FR_OE_2)[self._which],
128                                        ((mask & 0xffff) << 16) | (value & 0xffff))
129
130     def _rx_write_oe(self, value, mask):
131         return self._u._write_fpga_reg((FR_OE_1, FR_OE_3)[self._which],
132                                        ((mask & 0xffff) << 16) | (value & 0xffff))
133
134     def _tx_write_io(self, value, mask):
135         return self._u._write_fpga_reg((FR_IO_0, FR_IO_2)[self._which],
136                                        ((mask & 0xffff) << 16) | (value & 0xffff))
137
138     def _rx_write_io(self, value, mask):
139         return self._u._write_fpga_reg((FR_IO_1, FR_IO_3)[self._which],
140                                        ((mask & 0xffff) << 16) | (value & 0xffff))
141
142     def _rx_read_io(self):
143         t = self._u._read_fpga_reg((FR_RB_IO_RX_A_IO_TX_A, FR_RB_IO_RX_B_IO_TX_B)[self._which])
144         return (t >> 16) & 0xffff
145
146     def _tx_read_io(self):
147         t = self._u._read_fpga_reg((FR_RB_IO_RX_A_IO_TX_A, FR_RB_IO_RX_B_IO_TX_B)[self._which])
148         return t & 0xffff
149
150
151     def _compute_regs(self, freq):
152         """
153         Determine values of registers, along with actual freq.
154         
155         @param freq: target frequency in Hz
156         @type freq: float
157         @returns: (R, N, func, init, actual_freq)
158         @rtype: tuple(int, int, int, int, float)
159         
160         Override this in derived classes.
161         """
162         raise NotImplementedError
163
164     def _refclk_freq(self):
165         return float(self._u.fpga_master_clock_freq())/self._refclk_divisor()
166
167     def _refclk_divisor(self):
168         """
169         Return value to stick in REFCLK_DIVISOR register
170         """
171         return 1
172     
173     # ----------------------------------------------------------------
174     
175     def set_freq(self, freq):
176         """
177         @returns (ok, actual_baseband_freq) where:
178            ok is True or False and indicates success or failure,
179            actual_baseband_freq is the RF frequency that corresponds to DC in the IF.
180         """
181         raise NotImplementedError
182
183     def gain_range(self):
184         """
185         Return range of gain that can be set by this d'board.
186
187         @returns (min_gain, max_gain, step_size)
188         Where gains are expressed in decibels (your mileage may vary)
189         """
190         raise NotImplementedError
191
192     def set_gain(self, gain):
193         """
194         Set the gain.
195
196         @param gain:  gain in decibels
197         @returns True/False
198         """
199         raise NotImplementedError
200
201     def _set_pga(self, pga_gain):
202         if(self._which == 0):
203             self._u.set_pga (0, pga_gain)
204             self._u.set_pga (1, pga_gain)
205         else:
206             self._u.set_pga (2, pga_gain)
207             self._u.set_pga (3, pga_gain)
208
209     def is_quadrature(self):
210         """
211         Return True if this board requires both I & Q analog channels.
212
213         This bit of info is useful when setting up the USRP Rx mux register.
214         """
215         return True
216
217 # ----------------------------------------------------------------
218
219 class wbx_base_tx(wbx_base):
220     def __init__(self, usrp, which):
221         """
222         @param usrp: instance of usrp.sink_c
223         @param which: 0 or 1 corresponding to side TX_A or TX_B respectively.
224         """
225         wbx_base.__init__(self, usrp, which)
226         self.spi_enable = (SPI_ENABLE_TX_A, SPI_ENABLE_TX_B)[which]
227
228         # power up the transmit side, but set antenna to receive
229         self._u._write_oe(self._which,(TX_POWER|RX_TXN), 0xffff)
230         self._u.write_io(self._which, (TX_POWER|RX_TXN), (TX_POWER|RX_TXN))
231         self._lo_offset = 0e6
232
233         #  Gain is not set by the PGA, but the PGA must be set at max gain in the TX
234         return self._set_pga(self._u.pga_max())
235
236     def __del__(self):
237         # Power down and leave the T/R switch in the R position
238         self._u.write_io(self._which, (RX_TXN), (TX_POWER|RX_TXN))
239         wbx_base.__del__(self)
240
241     def set_auto_tr(self, on):
242         if on:
243             self.set_atr_mask (RX_TXN)
244             self.set_atr_txval(0)
245             self.set_atr_rxval(RX_TXN)
246         else:
247             self.set_atr_mask (0)
248             self.set_atr_txval(0)
249             self.set_atr_rxval(0)
250
251     def set_enable(self, on):
252         """
253         Enable transmitter if on is True
254         """
255         if on:
256             v = 0
257         else:
258             v = RX_TXN
259         self._u.write_io(self._which, v, RX_TXN)
260
261     def set_lo_offset(self, offset):
262         """
263         Set amount by which LO is offset from requested tuning frequency.
264         
265         @param offset: offset in Hz
266         """
267         self._lo_offset = offset
268
269     def lo_offset(self):
270         """
271         Get amount by which LO is offset from requested tuning frequency.
272         
273         @returns Offset in Hz
274         """
275         return self._lo_offset
276         
277 class wbx_base_rx(wbx_base):
278     def __init__(self, usrp, which):
279         """
280         @param usrp: instance of usrp.source_c
281         @param which: 0 or 1 corresponding to side RX_A or RX_B respectively.
282         """
283         wbx_base.__init__(self, usrp, which)
284         self.spi_enable = (SPI_ENABLE_RX_A, SPI_ENABLE_RX_B)[which]
285         
286         self._u._write_oe(self._which, (RX2_RX1N|RXENABLE), 0xffff)
287         self._u.write_io(self._which,  (0|RXENABLE), (RX2_RX1N|RXENABLE))
288
289         # set up for RX on TX/RX port
290         self.select_rx_antenna('TX/RX')
291
292         self.bypass_adc_buffers(True)
293
294         self._lo_offset = -4e6
295
296     def __del__(self):
297         # Power down
298         self._u.write_io(self._which, 0, (RXENABLE))
299         wbx_base.__del__(self)
300     
301     def set_auto_tr(self, on):
302         if on:
303             self.set_atr_mask (ENABLE)
304             self.set_atr_txval(     0)
305             self.set_atr_rxval(ENABLE)
306         else:
307             self.set_atr_mask (0)
308             self.set_atr_txval(0)
309             self.set_atr_rxval(0)
310
311     def select_rx_antenna(self, which_antenna):
312         """
313         Specify which antenna port to use for reception.
314         @param which_antenna: either 'TX/RX' or 'RX2'
315         """
316         if which_antenna in (0, 'TX/RX'):
317             self._u.write_io(self._which, 0,        RX2_RX1N)
318         elif which_antenna in (1, 'RX2'):
319             self._u.write_io(self._which, RX2_RX1N, RX2_RX1N)
320         else:
321             raise ValueError, "which_antenna must be either 'TX/RX' or 'RX2'"
322
323     def set_gain(self, gain):
324         """
325         Set the gain.
326
327         @param gain:  gain in decibels
328         @returns True/False
329         """
330         maxgain = self.gain_range()[1] - self._u.pga_max()
331         mingain = self.gain_range()[0]
332         if gain > maxgain:
333             pga_gain = gain-maxgain
334             assert pga_gain <= self._u.pga_max()
335             agc_gain = maxgain
336         else:
337             pga_gain = 0
338             agc_gain = gain
339         V_maxgain = .2
340         V_mingain = 1.2
341         V_fullscale = 3.3
342         dac_value = (agc_gain*(V_maxgain-V_mingain)/(maxgain-mingain) + V_mingain)*4096/V_fullscale
343         assert dac_value>=0 and dac_value<4096
344         return self._u.write_aux_dac(self._which, 0, int(dac_value)) and \
345                self._set_pga(int(pga_gain))
346
347     def set_lo_offset(self, offset):
348         """
349         Set amount by which LO is offset from requested tuning frequency.
350         
351         @param offset: offset in Hz
352         """
353         self._lo_offset = offset
354
355     def lo_offset(self):
356         """
357         Get amount by which LO is offset from requested tuning frequency.
358         
359         @returns Offset in Hz
360         """
361         return self._lo_offset
362         
363
364 # ----------------------------------------------------------------
365
366 class _ADF410X_common(object):
367     def __init__(self):
368         # R-Register Common Values
369         self.R_RSV = 0   # bits 23,22,21
370         self.LDP = 1   # bit 20     Lock detect in 5 cycles
371         self.TEST = 0  # bit 19,18  Normal
372         self.ABP = 0   # bit 17,16  2.9ns
373
374         # N-Register Common Values
375         self.N_RSV = 0      # 23,22
376         self.CP_GAIN = 0    # 21
377         
378         # Function Register Common Values
379         self.P = 0        # bits 23,22    0 = 8/9, 1 = 16/17, 2 = 32/33, 3 = 64/65
380         self.PD2 = 0      # bit  21       Normal operation
381         self.CP2 = 7      # bits 20,19,18 CP Gain = 5mA
382         self.CP1 = 7      # bits 17,16,15 CP Gain = 5mA
383         self.TC = 0       # bits 14-11    PFD Timeout
384         self.FL = 0       # bit 10,9      Fastlock Disabled
385         self.CP3S = 0     # bit 8         CP Enabled
386         self.PDP = 1      # bit 7         Phase detector polarity, Positive=1
387         self.MUXOUT = 1   # bits 6:4      Digital Lock Detect
388         self.PD1 = 0      # bit 3         Normal operation
389         self.CR = 0       # bit 2         Normal operation
390
391     def _compute_regs(self, freq):
392         """
393         Determine values of R, control, and N registers, along with actual freq.
394         
395         @param freq: target frequency in Hz
396         @type freq: float
397         @returns: (R, control, N, actual_freq)
398         @rtype: tuple(int, int, int, float)
399         """
400
401         #  Band-specific N-Register Values
402         phdet_freq = self._refclk_freq()/self.R_DIV
403         desired_n = round(freq*self.freq_mult/phdet_freq)
404         actual_freq = desired_n * phdet_freq
405         B = math.floor(desired_n/self._prescaler())
406         A = desired_n - self._prescaler()*B
407         self.B_DIV = int(B)    # bits 20:8
408         self.A_DIV = int(A)    # bit 6:2
409         #assert self.B_DIV >= self.A_DIV
410         if self.B_DIV < self.A_DIV:
411             return (0,0,0,0)
412         R = (self.R_RSV<<21) | (self.LDP<<20) | (self.TEST<<18) | \
413             (self.ABP<<16) | (self.R_DIV<<2)
414         
415         N = (self.N_RSV<<22) | (self.CP_GAIN<<21) | (self.B_DIV<<8) | (self.A_DIV<<2)
416
417         control = (self.P<<22) | (self.PD2<<21) | (self.CP2<<18) | (self.CP1<<15) | \
418                   (self.TC<<11) | (self.FL<<9) | (self.CP3S<<8) | (self.PDP<<7) | \
419                   (self.MUXOUT<<4) | (self.PD1<<3) | (self.CR<<2)
420
421         return (R,N,control,actual_freq/self.freq_mult)
422
423     def _write_all(self, R, N, control):
424         """
425         Write all PLL registers:
426             R counter latch,
427             N counter latch,
428             Function latch,
429             Initialization latch
430
431         Adds 10ms delay between writing control and N if this is first call.
432         This is the required power-up sequence.
433         
434         @param R: 24-bit R counter latch
435         @type R: int
436         @param N: 24-bit N counter latch
437         @type N: int
438         @param control: 24-bit control latch
439         @type control: int
440         """
441         self._write_R(R)
442         self._write_func(func)
443         self._write_init(init)
444         if self.first:
445             time.sleep(0.010)
446             self.first = False
447         self._write_N(N)
448
449     def _write_R(self, R):
450         self._write_it((R & ~0x3) | 0)
451
452     def _write_N(self, N):
453         self._write_it((N & ~0x3) | 1)
454
455     def _write_func(self, func):
456         self._write_it((func & ~0x3) | 2)
457
458     def _write_init(self, init):
459         self._write_it((func & ~0x3) | 3)
460
461     def _write_it(self, v):
462         s = ''.join((chr((v >> 16) & 0xff),
463                      chr((v >>  8) & 0xff),
464                      chr(v & 0xff)))
465         self._u._write_spi(0, self.spi_enable, self.spi_format, s)
466         
467     def _prescaler(self):
468         if self.P == 0:
469             return 8
470         elif self.P == 1:
471             return 16
472         else:
473             return 32
474
475 #----------------------------------------------------------------------
476 class _lo_common(_ADF410X_common):
477     def __init__(self):
478         _ADF410X_common.__init__(self)
479
480         # Band-specific R-Register Values
481         self.R_DIV = 16  # bits 15:2
482    
483         # Band-specific C-Register values
484         self.P = 1        # bits 23,22   Div by 16/17
485         self.CP2 = 7      # bits 19:17
486         self.CP1 = 7      # bits 16:14
487
488         # Band specifc N-Register Values
489         self.DIVSEL = 0   # bit 23
490         self.DIV2 = 0     # bit 22
491         self.CPGAIN = 0   # bit 21
492         self.freq_mult = 1
493
494     def freq_range(self):           # FIXME
495         return (50e6, 1000e6, 16e6)
496
497     def set_divider(self, main_or_aux, divisor):
498         if main_or_aux not in (0, 'main', 1, 'aux'):
499             raise ValueError, "main_or_aux must be 'main' or 'aux'"
500         if main_or_aux in (0, 'main'):
501             if value not in (1,2,4,8):
502                 raise ValueError, "Main Divider Must be 1, 2, 4, or 8"
503             for (div,val) in ((1,0),(2,1),(4,2),(8,3)):
504                 if(div == divisor):
505                     self.main_div = val
506         else:
507             if value not in (2,4,8,18):
508                 raise ValueError, "Aux Divider Must be 2, 4, 8 or 16"
509             for (div,val) in ((2,0),(4,1),(8,2),(16,3)):
510                 if(div == divisor):
511                     self.aux_div = val
512         
513         self._u._rx_write_io(self._which, ((self.main_div<<SELA0) | (self.aux_div<<SELB0)),
514                              (SELA0|SELA1|SELB0|SELB1))   # only works on RX
515
516     def set_freq(self, freq):
517         #freq += self._lo_offset
518
519         if(freq < 20e6 or freq > 1200e6):
520             raise ValueError, "Requested frequency out of range"
521         div = 1
522         lo_freq = freq * 2
523         while freq < 1e9 and div < 8:
524             div = div * 2
525             lo_freq = lo_freq * 2
526         print "For RF freq of %f, we set DIV=%d and LO Freq=%f" % (freq, div, lo_freq)
527         self.set_divider('main', div)
528         self.set_divider('aux', 2)
529
530         R, N, control, actual_freq = self._compute_regs(freq)
531         if R==0:
532             return(False,0)
533         self._write_all(R, N, control)
534         return (self._lock_detect(), actual_freq)
535
536         
537 #------------------------------------------------------------    
538 class db_wbx_lo_tx(_lo_common, wbx_base_tx):
539     def __init__(self, usrp, which):
540         self.power_on = ~POWER_UP
541         self.power_off = ~POWER_UP    # powering it off kills the serial bus
542         wbx_base_tx.__init__(self, usrp, which)
543         _lo_common.__init__(self)
544         
545     def gain_range(self):
546         """
547         Return range of gain that can be set by this d'board.
548
549         @returns (min_gain, max_gain, step_size)
550         Where gains are expressed in decibels (your mileage may vary)
551
552         Gain is controlled by a VGA in the output amplifier, not the PGA
553         """
554         return (-56, 0, 0.1)
555
556     def set_gain(self, gain):
557         """
558         Set the gain.
559         
560         @param gain:  gain in decibels
561         @returns True/False
562         """
563         maxgain = self.gain_range()[1]
564         mingain = self.gain_range()[0]
565         if gain > maxgain:
566             txvga_gain = maxgain
567         else:
568             txvga_gain = gain
569         V_maxgain = 1.4
570         V_mingain = 0.1
571         V_fullscale = 3.3
572         dac_value = (txvga_gain*(V_maxgain-V_mingain)/(maxgain-mingain) + V_mingain)*4096/V_fullscale
573         assert dac_value>=0 and dac_value<4096
574         return self._u.write_aux_dac(self._which, 1, int(dac_value))
575
576 class db_wbx_lo_rx(_lo_common, wbx_base_rx):
577     def __init__(self, usrp, which):
578         self.power_on = ~POWER_UP
579         self.power_off = ~POWER_UP   # Powering it off kills the serial bus
580         wbx_base_rx.__init__(self, usrp, which)
581         _lo_common.__init__(self)
582
583     def gain_range(self):
584         """
585         Return range of gain that can be set by this d'board.
586         
587         @returns (min_gain, max_gain, step_size)
588         Where gains are expressed in decibels (your mileage may vary)
589         """
590         return (self._u.pga_min(), self._u.pga_max() + 45, 0.05)
591
592 #------------------------------------------------------------    
593 # hook these daughterboard classes into the auto-instantiation framework
594 db_instantiator.add(usrp_dbid.WBX_LO_TX, lambda usrp, which : (db_wbx_lo_tx(usrp, which),))
595 db_instantiator.add(usrp_dbid.WBX_LO_RX, lambda usrp, which : (db_wbx_lo_rx(usrp, which),))
596
597