camrasdevices.py 4.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"""HPIB wrappers for devices in the Dwingeloo telescope

This module contains some classes for conveniently working with the devices in the Dwingeloo
Telescope that communicate over GPIB (General Purpuse Interface Bus) or HPIB (presumably the
HP variant of GPIB). Currently, three of these devices exist, on different HPIB addresses:
    * Receiver (address 1)
    * Local Oscillator (address 28), used for mixing signals to downconvert them
    * Clock Generator (address 14), used as the clock for the FPGA

For each of the three devices a class exists. Setting and getting the frequency of these
devices is done by using their `frequency` attribute.

Example:
    >>> import camrasdevices
    >>> lo = LocalOscillator()
    >>> lo.frequency
    <Quantity 1.0 GHz>
    >>> lo.frequency = 1.1e9
    >>> lo.frequency
    <Quantity 1.1 GHz>

By default, the setter method of `frequency` checks if setting the device succeeded. This is a
blocking operation, but the HPIB bus is quite fast.
    >>> lo.frequency = 1.e18
    Traceback (most recent call last):
    RuntimeError: Setting frequency failed: tried to set to 1e+18 Hz, it is now 1100000000.0 Hz
This checking can be disabled:
    >>> non_checking_lo = camrasdevices.LocalOscillator(check_frequency = False)
    >>> non_checking_lo.frequency = 1.e18
    >>> non_checking_lo.frequency
    <Quantity 1.1 GHz>

The CamrasHpibDevice class spawns one thread (shared with all instances) to perform the HPIB
commands. This is to make sure that instances living in separate threads do not interfere with
each other. The thread is a daemon, so it will be killed on program exit.

On initialization of the first CamrasDevice (or subclass), the command thread is initialized, and
some initialization commands are sent to the HPIB bus to tell it to use the appropriate line
endings etc.
"""

42
43
import astropy.units as u
from astropy.units import Quantity
44

45
46
import hpib
import serial.threaded
47

48
49
50

class CamrasHpibDevice(object):
    """Wrapper around HPIB commands"""
51

Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
52
53
    command_thread = None

54
    def __init__(self, address, check_frequency=True):
55
        self.hpib_address = address
56
        self._check_frequency = check_frequency
57

Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
58
        if not CamrasHpibDevice.command_thread:
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
59
            serial_port = serial.Serial("/dev/ttyUSB0")
60
61
            CamrasHpibDevice.command_thread = serial.threaded.ReaderThread(serial_port,
                                                                           hpib.GPIBProtocol)
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
62
            CamrasHpibDevice.command_thread.start()
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
63
            CamrasHpibDevice.command_thread._connection_made.wait()
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
64
            CamrasHpibDevice.command_thread.protocol.init_hpib()
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
65

66
67
    @property
    def frequency(self):
68
69
70
71
72
73
74
75
76
77
78
79
80
        """Frequency of the device as an astropy Quantity.

        Returns:
            Quantity: frequency (in Hz) of the device

        Raises:
            RuntimeError: If the device does not respond
        """
        freq_str = CamrasHpibDevice.command_thread.protocol.command("freq?",
                                                                    address=self.hpib_address)
        if not freq_str or len(freq_str) == 0:
            raise RuntimeError("Camras device at address {} is not responding".format(
                self.hpib_address))
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
81
        return int(float(freq_str)) * u.Hz
82
83
84

    @frequency.setter
    def frequency(self, freq):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
85
86
87
88
        """Set the device to the specified frequency. Throws a RuntimeError if failed

        Args:
            freq (Union[float, Quantity]): new frequency. If no unit present, assume Hz
89
90
91
92

        Raises:
            RuntimeError: If the device sticks to a different frequency (e.g. if the specified
                frequency is outside the accepted range for the device)
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
93
        """
94
95
        if isinstance(freq, Quantity):
            freq = freq.to(u.Hz).value
96
97
        CamrasHpibDevice.command_thread.protocol.command("freq {:d} Hz".format(int(freq)),
                                                         address=self.hpib_address)
98
        new_freq = self.frequency
99
        if self._check_frequency and new_freq.to(u.Hz).value != int(freq):
100
            raise RuntimeError("Setting frequency failed: tried to set to {}, it is now {}".format(
101
102
                freq, new_freq))

103
104
105
106

class Receiver(CamrasHpibDevice):
    """Wrapper around HPIB commands for the Rohde & Schwartz receiver"""

107
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
108
        super(Receiver, self).__init__(1)
109

110

111
class LocalOscillator(CamrasHpibDevice):
112
113
    """Wrapper around HPIB commands for the local oscillator"""

114
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
115
        super(LocalOscillator, self).__init__(28)
116

117

118
119
class ClockGenerator(CamrasHpibDevice):
    """Wrapper around HPIB commands for the clock generator (should be at 140MHz)"""
120

121
    def __init__(self, **kwargs):
Tammo Jan Dijkema's avatar
Tammo Jan Dijkema committed
122
        super(ClockGenerator, self).__init__(14)