Skip to content

Commit da1b788

Browse files
committed
Rework PowerSupply API again
1 parent 871457e commit da1b788

File tree

2 files changed

+94
-54
lines changed

2 files changed

+94
-54
lines changed

pslab/instrument/power_supply.py

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
--------
55
>>> from pslab import PowerSupply
66
>>> ps = PowerSupply()
7-
>>> ps.pv1.voltage = 4.5
8-
>>> ps.pv1.voltage
7+
>>> ps.pv1 = 4.5
8+
>>> ps.pv1
99
4.499389499389499
1010
11-
>>> ps.pcs.current = 2e-3
12-
>>> ps.pcs.current
11+
>>> ps.pcs = 2e-3
12+
>>> ps.pcs
1313
0.00200014652014652
1414
"""
1515
import numpy as np
@@ -41,31 +41,38 @@ def __init__(self, device: SerialHandler = None):
4141
self._pv1 = VoltageSource(self._mcp4728, "PV1")
4242
self._pv2 = VoltageSource(self._mcp4728, "PV2")
4343
self._pv3 = VoltageSource(self._mcp4728, "PV3")
44-
self._voltage_sources = {
45-
"PV1": self._pv1,
46-
"PV2": self._pv2,
47-
"PV3": self._pv3,
48-
}
4944
self._pcs = CurrentSource(self._mcp4728)
5045

5146
@property
5247
def pv1(self):
53-
"""VoltageSource: Control the voltage on PV1 between -5 V and 5 V."""
54-
return self._pv1
48+
"""float: Voltage on PV1; range [-5, 5] V."""
49+
return self._pv1.voltage
50+
51+
@pv1.setter
52+
def pv1(self, value: float):
53+
self._pv1.voltage = value
5554

5655
@property
5756
def pv2(self):
58-
"""VoltageSource: Control the voltage on PV2 between -3.3 V and 3.3 V."""
59-
return self._pv2
57+
"""float: Voltage on PV2; range [-3.3, 3.3] V."""
58+
return self._pv2.voltage
59+
60+
@pv2.setter
61+
def pv2(self, value: float):
62+
self._pv2.voltage = value
6063

6164
@property
6265
def pv3(self):
63-
"""VoltageSource: Control the voltage on PV3 between 0 V and 3.3 V."""
64-
return self._pv3
66+
"""float: Voltage on PV3; range [0, 3.3] V."""
67+
return self._pv3.voltage
68+
69+
@pv3.setter
70+
def pv3(self, value: float):
71+
self._pv3.voltage = value
6572

6673
@property
6774
def pcs(self):
68-
"""CurrentSource: Control the current on PCS between 0 and 3.3 mA.
75+
"""float: Current on PCS; range [0, 3.3e-3] A.
6976
7077
Notes
7178
-----
@@ -76,7 +83,7 @@ def pcs(self):
7683
7784
For example, the maximum current that can be driven across a 100 Ω load
7885
is 3.3 V / 1.1 kΩ = 3 mA. If the load is 10 kΩ, the maximum current is
79-
only 3.3 V / 11 kΩ = 300µA.
86+
only 3.3 V / 11 kΩ = 300 µA.
8087
8188
Be careful to not set a current higher than available for a given load.
8289
If a current greater than the maximum for a certain load is requested,
@@ -85,56 +92,86 @@ def pcs(self):
8592
current will be only a few hundred µA instead of the maximum available
8693
1.65 mA.
8794
"""
88-
return self._pcs
95+
return self._pcs.current
96+
97+
@pcs.setter
98+
def pcs(self, value: float):
99+
self._pcs.current = value
89100

90101
@property
91102
def _registers(self):
92103
"""Return the contents of the MCP4728's input registers and EEPROM."""
93104
return self._mcp4728.read(24)
94105

95106

96-
class _Source:
97-
RANGE = {
107+
class Source:
108+
"""Base class for voltage/current/power sources."""
109+
110+
_RANGE = {
98111
"PV1": (-5, 5),
99112
"PV2": (-3.3, 3.3),
100113
"PV3": (0, 3.3),
101114
"PCS": (3.3e-3, 0),
102115
}
103-
CHANNEL_NUMBER = {
116+
_CHANNEL_NUMBER = {
104117
"PV1": 3,
105118
"PV2": 2,
106119
"PV3": 1,
107120
"PCS": 0,
108121
}
109-
RESOLUTION = 2 ** 12 - 1
110-
MULTI_WRITE = 0b01000000
122+
_RESOLUTION = 2 ** 12 - 1
123+
_MULTI_WRITE = 0b01000000
111124

112125
def __init__(self, mcp4728: I2CSlave, name: str):
113126
self._mcp4728 = mcp4728
114127
self.name = name
115-
self.channel_number = self.CHANNEL_NUMBER[self.name]
116-
slope = self.RANGE[self.name][1] - self.RANGE[self.name][0]
117-
intercept = self.RANGE[self.name][0]
128+
self.channel_number = self._CHANNEL_NUMBER[self.name]
129+
slope = self._RANGE[self.name][1] - self._RANGE[self.name][0]
130+
intercept = self._RANGE[self.name][0]
118131
self._unscale = np.poly1d(
119-
[self.RESOLUTION / slope, -self.RESOLUTION * intercept / slope]
132+
[self._RESOLUTION / slope, -self._RESOLUTION * intercept / slope]
120133
)
121-
self._scale = np.poly1d([slope / self.RESOLUTION, intercept])
134+
self._scale = np.poly1d([slope / self._RESOLUTION, intercept])
135+
136+
def unscale(self, voltage: float) -> int:
137+
"""Return integer representation of a voltage.
122138
123-
def unscale(self, current: float):
124-
return int(round(self._unscale(current)))
139+
Parameters
140+
----------
141+
voltage : float
142+
Voltage in Volt.
125143
126-
def scale(self, raw: int):
144+
Returns
145+
-------
146+
raw : int
147+
Integer represention of the voltage.
148+
"""
149+
return int(round(self._unscale(voltage)))
150+
151+
def scale(self, raw: int) -> float:
152+
"""Convert an integer value to a voltage value.
153+
154+
Parameters
155+
----------
156+
raw : int
157+
Integer representation of a voltage value.
158+
159+
Returns
160+
-------
161+
voltage : float
162+
Voltage in Volt.
163+
"""
127164
return self._scale(raw)
128165

129166
def _multi_write(self, raw: int):
130167
channel_select = self.channel_number << 1
131-
command_byte = self.MULTI_WRITE | channel_select
168+
command_byte = self._MULTI_WRITE | channel_select
132169
data_byte1 = (raw >> 8) & 0x0F
133170
data_byte2 = raw & 0xFF
134171
self._mcp4728.write([data_byte1, data_byte2], register_address=command_byte)
135172

136173

137-
class VoltageSource(_Source):
174+
class VoltageSource(Source):
138175
"""Helper class for interfacing with PV1, PV2, and PV3."""
139176

140177
def __init__(self, mcp4728: I2CSlave, name: str):
@@ -152,12 +189,12 @@ def voltage(self):
152189
@voltage.setter
153190
def voltage(self, value: float):
154191
raw = self.unscale(value)
155-
raw = int(np.clip(raw, 0, self.RESOLUTION))
192+
raw = int(np.clip(raw, 0, self._RESOLUTION))
156193
self._multi_write(raw)
157194
self._voltage = self.scale(raw)
158195

159196

160-
class CurrentSource(_Source):
197+
class CurrentSource(Source):
161198
"""Helper class for interfacing with PCS."""
162199

163200
def __init__(self, mcp4728: I2CSlave):
@@ -175,6 +212,6 @@ def current(self):
175212
@current.setter
176213
def current(self, value: float):
177214
raw = 0 if value == 0 else self.unscale(value)
178-
raw = int(np.clip(raw, 0, self.RESOLUTION))
215+
raw = int(np.clip(raw, 0, self._RESOLUTION))
179216
self._multi_write(raw)
180217
self._current = self.scale(raw)

tests/test_power_supply.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from pslab.serial_handler import SerialHandler
1414
from pslab.instrument.multimeter import Multimeter
15-
from pslab.instrument.power_supply import PowerSupply, VoltageSource
15+
from pslab.instrument.power_supply import PowerSupply
1616

1717
RELTOL = 0.05
1818
ABSTOL = 0.05
@@ -30,42 +30,45 @@ def multi(handler: SerialHandler) -> Multimeter:
3030
return Multimeter(handler)
3131

3232

33-
def voltage_tester(
34-
vmin: float,
35-
vmax: float,
36-
step: float,
37-
source: VoltageSource,
38-
channel: str,
39-
multimeter: Multimeter,
40-
):
41-
voltages = np.arange(vmin, vmax, step)
33+
def test_set_voltage_pv1(power: PowerSupply, multi: Multimeter):
34+
voltages = np.arange(-5, 5, 0.1)
4235
measured = np.zeros(len(voltages))
4336

4437
for i, v in enumerate(voltages):
45-
source.voltage = v
46-
measured[i] = multimeter.measure_voltage(channel)
38+
power.pv1 = v
39+
measured[i] = multi.measure_voltage("CH1")
4740

4841
assert measured == pytest.approx(voltages, rel=RELTOL * 2, abs=ABSTOL * 2)
4942

5043

51-
def test_set_voltage_pv1(power: PowerSupply, multi: Multimeter):
52-
voltage_tester(-5, 5.1, 0.1, power.pv1, "CH1", multimeter=multi)
44+
def test_set_voltage_pv2(power: PowerSupply, multi: Multimeter):
45+
voltages = np.arange(-3.3, 3.3, 0.1)
46+
measured = np.zeros(len(voltages))
5347

48+
for i, v in enumerate(voltages):
49+
power.pv2 = v
50+
measured[i] = multi.measure_voltage("CH2")
5451

55-
def test_set_voltage_pv2(power: PowerSupply, multi: Multimeter):
56-
voltage_tester(-3.3, 3.4, 0.1, power.pv2, "CH2", multimeter=multi)
52+
assert measured == pytest.approx(voltages, rel=RELTOL * 2, abs=ABSTOL * 2)
5753

5854

5955
def test_set_voltage_pv3(power: PowerSupply, multi: Multimeter):
60-
voltage_tester(0, 3.4, 0.1, power.pv3, "VOL", multimeter=multi)
56+
voltages = np.arange(0, 3.3, 0.1)
57+
measured = np.zeros(len(voltages))
58+
59+
for i, v in enumerate(voltages):
60+
power.pv3 = v
61+
measured[i] = multi.measure_voltage("VOL")
62+
63+
assert measured == pytest.approx(voltages, rel=RELTOL * 2, abs=ABSTOL * 2)
6164

6265

6366
def test_set_current(power: PowerSupply, multi: Multimeter):
6467
currents = np.arange(0, 3e-3, 1e-4)
6568
measured = np.zeros(len(currents))
6669

6770
for i, c in enumerate(currents):
68-
power.pcs.current = c
71+
power.pcs = c
6972
measured[i] = multi.measure_voltage("CH3")
7073

7174
resistor = 100

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy