import time
import pathlib
import numpy as np
from . import base, arduinobase
# TODO Check units of V, I returned by Arduino - graph, file header. Also check
# the order of columns (voltage, amps, time).
# TODO Arduino code writes 2x beyond the size of data arrays POCET_MERENI.
# Probably should fix the conditions, lines 146, 174.
[docs]
class ArduinoProbeBase(arduinobase.ArduinoBase):
"""Arduino controller that handles sweeped measurments of probe
I-V characteristic.
Communication:
- serial interface to Arduino
"""
PROBE_SAMPLES = 300 # Hardcoded in Arduino code, limited size of SRAM
DIR_SAVED_DATA = pathlib.Path.home() / "saved_data"
SWEEP_CMD = "a"
def __init__(self, bus, start_msg="START", **kwargs):
"""Constructor
:param str bus: Name of serial bus where Arduino is connected,
e.g. '/dev/ttyUSB0'
"""
super().__init__(
serial_cfg=arduinobase.SerialConfig(bus, 115200, start_msg),
**kwargs,
)
# ==== Commands ====
[docs]
@base.base_command
def measure_sweep(self, device_client, frequency, amplitude):
"""Execute a sweeped measurement of the I-V characteristic.
Returns numpy array, shape (3, 300), columns [volts, amps, time].
:param float frequency: Approximate frequency of the waveform in kHz.
:param float amplitude: Approximate amplitude of wavefrom 10-100%.
"""
frequency, amplitude = float(frequency) * 1e3, float(amplitude)
if not (1e3 <= frequency <= 100e3):
raise base.CommandError("Value frequency must be between 1-100 kHz")
if not (10.0 <= amplitude <= 100.0):
raise base.CommandError("Value amplitude must be between 10-100%")
# Set initial values. Then we try to optimize them to match the
# requested frequency & amplitude as best as possible.
byte_amp = round(amplitude / 100 * 255)
byte_step = 1
byte_delay = 1 # 1 microsecond
# High frequency requested -> Increase byte_step up to a reasonable max.
while byte_step < (byte_amp // 5):
if byte_delay * (byte_amp // byte_step) <= (0.5 / frequency):
break
byte_step += 1
# Match amplitude to step size
byte_amp -= byte_amp % byte_step
# Low frequency requested -> Increase delay up to max.
while byte_delay < 255:
if (byte_delay + 1) * (byte_amp // byte_step) >= (0.5 / frequency):
break
byte_delay += 1
t0 = time.time()
self._write(f"{self.SWEEP_CMD} {byte_delay:d} {byte_amp:d} {byte_step:d}")
time.sleep(self.PROBE_SAMPLES * byte_delay * 1e-6)
timeout = self._ardu.timeout
self._ardu.timeout = 5
text = ""
for _ in range(self.PROBE_SAMPLES):
recv = self._readline()
if recv == "":
break
text += recv + " "
self._ardu.timeout = timeout
self._last.probe_data = np.fromstring(
text, dtype=float, sep=" ").reshape(-1, 3).T[[2, 0, 1]]
def save_data(data, header=""):
filepath = "trap_probe_{}_{{}}.txt".format(time.strftime("%Y-%m-%d_%H-%M"))
suffix = 1
while (self.DIR_SAVED_DATA / filepath.format(suffix)).exists():
suffix += 1
filepath = self.DIR_SAVED_DATA / filepath.format(suffix)
np.savetxt(str(filepath), data.T, fmt="%.4f", header=header)
return filepath
filepath = save_data(
self._last.probe_data,
header=f"timestamp {t0:.4f}\ntime[s], volts[V?], amps[A?]",
)
if device_client is not None:
# Emit data to server
with filepath.open("r") as file:
device_client.emit_datafile(file, filepath.name)
# Emit graph preview of I-V characteristic
device_client.emit("graph", {
"title": "I-V characteristic",
"x": self._last.probe_data[1].tolist(),
"y": self._last.probe_data[2].tolist(),
"xlabel": "voltage [V?]",
"ylabel": "current [A?]",
"id": "graph_iv_char",
})
return str(filepath)