6
6
>>> ps = PowerSupply()
7
7
>>> ps.pv1 = 4.5
8
8
>>> ps.pv1
9
- 4.499389499389499
9
+ 4.5
10
10
11
11
>>> ps.pcs = 2e-3
12
12
>>> ps.pcs
13
- 0.00200014652014652
13
+ 0.002
14
14
"""
15
15
16
- import numpy as np
17
-
18
- from pslab .bus .i2c import I2CSlave
16
+ import pslab .protocol as CP
19
17
from pslab .serial_handler import SerialHandler
20
18
21
19
@@ -34,42 +32,73 @@ class PowerSupply:
34
32
instance will be created automatically if not specified.
35
33
"""
36
34
37
- _ADDRESS = 0x60
35
+ _REFERENCE = 3300
36
+ _PV1_CH = 3
37
+ _PV1_RANGE = (- 5 , 5 )
38
+ _PV2_CH = 2
39
+ _PV2_RANGE = (- 3.3 , 3.3 )
40
+ _PV3_CH = 1
41
+ _PV3_RANGE = (0 , 3.3 )
42
+ _PCS_CH = 0
43
+ _PCS_RANGE = (3.3e-3 , 0 )
38
44
39
45
def __init__ (self , device : SerialHandler = None ):
40
46
self ._device = device if device is not None else SerialHandler ()
41
- self ._mcp4728 = I2CSlave (self ._ADDRESS , self ._device )
42
- self ._pv1 = VoltageSource (self ._mcp4728 , "PV1" )
43
- self ._pv2 = VoltageSource (self ._mcp4728 , "PV2" )
44
- self ._pv3 = VoltageSource (self ._mcp4728 , "PV3" )
45
- self ._pcs = CurrentSource (self ._mcp4728 )
47
+ self ._pv1 = None
48
+ self ._pv2 = None
49
+ self ._pv3 = None
50
+ self ._pcs = None
51
+
52
+ def _set_power (self , channel , output ):
53
+ self ._device .send_byte (CP .DAC )
54
+ self ._device .send_byte (CP .SET_POWER )
55
+ print (channel )
56
+ self ._device .send_byte (channel )
57
+ self ._device .send_int (output )
58
+ print (output )
59
+ return self ._device .get_ack ()
60
+
61
+ @staticmethod
62
+ def _bound (value , output_range ):
63
+ return max (min (value , max (output_range )), min (output_range ))
64
+
65
+ def _scale (self , value , output_range ):
66
+ scaled = (value - output_range [0 ]) / (output_range [1 ] - output_range [0 ])
67
+ return int (scaled * self ._REFERENCE )
46
68
47
69
@property
48
70
def pv1 (self ):
49
71
"""float: Voltage on PV1; range [-5, 5] V."""
50
- return self ._pv1 . voltage
72
+ return self ._pv1
51
73
52
74
@pv1 .setter
53
75
def pv1 (self , value : float ):
54
- self ._pv1 .voltage = value
76
+ value = self ._bound (value , self ._PV1_RANGE )
77
+ ret = self ._set_power (self ._PV1_CH , self ._scale (value , self ._PV1_RANGE ))
78
+ self ._pv1 = value
79
+ return ret
55
80
56
81
@property
57
82
def pv2 (self ):
58
83
"""float: Voltage on PV2; range [-3.3, 3.3] V."""
59
- return self ._pv2 . voltage
84
+ return self ._pv2
60
85
61
86
@pv2 .setter
62
87
def pv2 (self , value : float ):
63
- self ._pv2 .voltage = value
88
+ value = self ._bound (value , self ._PV2_RANGE )
89
+ self ._set_power (self ._PV2_CH , self ._scale (value , self ._PV2_RANGE ))
90
+ self ._pv2 = value
64
91
65
92
@property
66
93
def pv3 (self ):
67
94
"""float: Voltage on PV3; range [0, 3.3] V."""
68
- return self ._pv3 . voltage
95
+ return self ._pv3
69
96
70
97
@pv3 .setter
71
98
def pv3 (self , value : float ):
72
- self ._pv3 .voltage = value
99
+ value = self ._bound (value , self ._PV3_RANGE )
100
+ self ._set_power (self ._PV3_CH , self ._scale (value , self ._PV3_RANGE ))
101
+ self ._pv3 = value
73
102
74
103
@property
75
104
def pcs (self ):
@@ -93,136 +122,10 @@ def pcs(self):
93
122
current will be only a few hundred µA instead of the maximum available
94
123
1.65 mA.
95
124
"""
96
- return self ._pcs . current
125
+ return self ._pcs
97
126
98
127
@pcs .setter
99
128
def pcs (self , value : float ):
100
- self ._pcs .current = value
101
-
102
- @property
103
- def _registers (self ):
104
- """Return the contents of the MCP4728's input registers and EEPROM."""
105
- return self ._mcp4728 .read (24 )
106
-
107
-
108
- class Source :
109
- """Base class for voltage/current/power sources."""
110
-
111
- _RANGE = {
112
- "PV1" : (- 5 , 5 ),
113
- "PV2" : (- 3.3 , 3.3 ),
114
- "PV3" : (0 , 3.3 ),
115
- "PCS" : (3.3e-3 , 0 ),
116
- }
117
- _CHANNEL_NUMBER = {
118
- "PV1" : 3 ,
119
- "PV2" : 2 ,
120
- "PV3" : 1 ,
121
- "PCS" : 0 ,
122
- }
123
- _RESOLUTION = 2 ** 12 - 1
124
- _MULTI_WRITE = 0b01000000
125
-
126
- def __init__ (self , mcp4728 : I2CSlave , name : str ):
127
- self ._mcp4728 = mcp4728
128
- self .name = name
129
- self .channel_number = self ._CHANNEL_NUMBER [self .name ]
130
- slope = self ._RANGE [self .name ][1 ] - self ._RANGE [self .name ][0 ]
131
- intercept = self ._RANGE [self .name ][0 ]
132
- self ._unscale = np .poly1d (
133
- [self ._RESOLUTION / slope , - self ._RESOLUTION * intercept / slope ]
134
- )
135
- self ._scale = np .poly1d ([slope / self ._RESOLUTION , intercept ])
136
-
137
- def unscale (self , voltage : float ) -> int :
138
- """Return integer representation of a voltage.
139
-
140
- Parameters
141
- ----------
142
- voltage : float
143
- Voltage in Volt.
144
-
145
- Returns
146
- -------
147
- raw : int
148
- Integer represention of the voltage.
149
- """
150
- return int (round (self ._unscale (voltage )))
151
-
152
- def scale (self , raw : int ) -> float :
153
- """Convert an integer value to a voltage value.
154
-
155
- Parameters
156
- ----------
157
- raw : int
158
- Integer representation of a voltage value.
159
-
160
- Returns
161
- -------
162
- voltage : float
163
- Voltage in Volt.
164
- """
165
- return self ._scale (raw )
166
-
167
- def _multi_write (self , raw : int ):
168
- channel_select = self .channel_number << 1
169
- command_byte = self ._MULTI_WRITE | channel_select
170
- data_byte1 = (raw >> 8 ) & 0x0F
171
- data_byte2 = raw & 0xFF
172
- self ._mcp4728 .write ([data_byte1 , data_byte2 ], register_address = command_byte )
173
-
174
-
175
- class VoltageSource (Source ):
176
- """Helper class for interfacing with PV1, PV2, and PV3."""
177
-
178
- def __init__ (self , mcp4728 : I2CSlave , name : str ):
179
- self ._voltage = 0
180
- super ().__init__ (mcp4728 , name )
181
-
182
- @property
183
- def voltage (self ):
184
- """float: Most recent voltage set on PVx in Volt.
185
-
186
- The voltage on PVx can be set by writing to this attribute.
187
- """
188
- return self ._voltage
189
-
190
- @voltage .setter
191
- def voltage (self , value : float ):
192
- raw = self .unscale (value )
193
- raw = int (np .clip (raw , 0 , self ._RESOLUTION ))
194
- self ._multi_write (raw )
195
- self ._voltage = self .scale (raw )
196
-
197
-
198
- class CurrentSource (Source ):
199
- """Helper class for interfacing with PCS."""
200
-
201
- def __init__ (self , mcp4728 : I2CSlave ):
202
- self ._current = 0
203
- super ().__init__ (mcp4728 , "PCS" )
204
-
205
- @property
206
- def current (self ):
207
- """float: Most recent current value set on PCS in Ampere.
208
-
209
- The current on PCS can be set by writing to this attribute.
210
- """
211
- return self ._current
212
-
213
- @current .setter
214
- def current (self , value : float ):
215
- # Output current is a function of the voltage set on the MCP4728's V+
216
- # pin (assuming negligible load resistance):
217
- # I(V) = 3.3e-3 - V / 1000
218
- # I.e. the lower the voltage the higher the current. However, the
219
- # function is discontinuous in V = 0:
220
- # I(0) = 0
221
- if value == 0 :
222
- self ._multi_write (0 )
223
- self ._current = 0
224
- else :
225
- raw = self .unscale (value )
226
- raw = int (np .clip (raw , 0 , self ._RESOLUTION ))
227
- self ._multi_write (raw )
228
- self ._current = self .scale (raw )
129
+ value = self ._bound (value , self ._PCS_RANGE )
130
+ self ._set_power (self ._PCS_CH , self ._scale (value , self ._PCS_RANGE ))
131
+ self ._pcs = value
0 commit comments