hvps_lib
1# py-hvps-interface of the Project Peta-pico-Voltron 2# petapicovoltron.com 3# Copyright 2017-2024 Samuel Rosset 4# Distributed under the terms of the GNU General Public License GNU GPLv3 5 6 7# This file is part of py-hvps-interface. 8 9# py-hvps-interface is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13# 14# py-hvps-interface is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with py-hvps-interface. If not, see <http://www.gnu.org/licenses/> 21 22from time import sleep 23 24try: 25 import serial 26 import serial.tools.list_ports 27 import struct 28 import numpy as np 29 from datetime import datetime 30 import configparser 31 import ctypes 32 import json 33 import os 34 from sys import exit 35except ImportError: 36 serial = None 37 struct = None 38 np = None 39 datetime = None 40 configparser = None 41 ctypes = None 42 json = None 43 os = None 44 print("Some of the required packages are missing. Execute \'pip install -r requirements.txt\' " 45 "from the interface folder") 46 sleep(4) 47 exit() 48 49DEBUG = False 50LIB_VER = "1.2" # Version of this library 51LIB_NAME = 'Release the kraken' 52HVPSX_PID = 0x0632 53 54# compatibility dictionary: 55compatibility_dict = { 56 'Lib-1.0': ['Fw-1.0'], 57 'Lib-1.1': ['Fw-1.1'], 58 'Lib-1.2': ['Fw-1.2'], 59 'Fw-1.0': ['Hw-1.1', 'Hw-1.2', 'Hw-1.3'], 60 'Fw-1.1': ['Hw-1.1', 'Hw-1.2', 'Hw-1.3', 'Hw-1.4'], 61 'Fw-1.2': ['Hw-1.1', 'Hw-1.2', 'Hw-1.3', 'Hw-1.4'] 62} 63# Switching mode constants: off=0, on-DC=1, on-Switching=2, on-waveform=3 64SWMODE_OFF = 0 65SWMODE_DC = 1 # only SHVPS 66SWMODE_SW = 2 67SWMODE_WFRM = 3 68SWMODE_HIGH = 4 # only hvps-x 69SWMODE_LOW = 5 # only hvps-x 70 71# Switching source constants: Timer=0,External=1, Button=2, 72SWSRC_TMR = 0 73SWSRC_EXT = 1 74SWSRC_BTTN = 2 75 76# Voltage control mode constants: Regulated=0, External=1, Open loop=2 77VMODE_R = 0 78VMODE_EXT = 1 79VMODE_O = 2 80 81# Stroboscobic illumination mode: Off=0 Fixed position=1, sweeping mode=2 82STMODE_OFF = 0 83STMODE_FIXED = 1 84STMODE_SWEEP = 2 85 86# look-up tables 87LOOKUP_VOUT = 0 88LOOKUP_ADC_P = 1 89LOOKUP_ADC_O = 2 90 91# Error bits 92ERR_FIRM = 0b1 # Incompatibility between library-firmware-hardware 93ERR_TYPE = 0b10 # The connected device is not a SHVPS or a hvpsx 94ERR_JACK = 0b100 # Unused 95ERR_COM = 0b1000 # Communication error: Cannot communicate with connected device 96ERR_CONF = 0b10000 # Configuration error: the HVPS running the hvps software, but appears to be unconfigured 97ERR_PORT = 0b100000 # port cannot be open. Used by another process 98ERR_CMD = 0b1000000 # serial command not understood 99 100# HVPS-X related constants 101# Serial commands and codes 102SERIAL_ERROR = 0x21727245 # int value of 'Err!' 103SERIAL_OK = 0x4B4F4B4F # int value of 'OKOK' 104SERIAL_SVMAX = 0x01 105SERIAL_QVMAX = 0x02 106SERIAL_SVSET = 0x03 107SERIAL_QVSET = 0x04 108SERIAL_SAVE = 0x05 109SERIAL_SCONF = 0x06 110SERIAL_QCONF = 0x07 111SERIAL_QVER = 0x08 112SERIAL_SNAME = 0x09 113SERIAL_QNAME = 0x0a 114SERIAL_SF = 0x0b 115SERIAL_QF = 0x0c 116SERIAL_SSWMODE = 0x0d 117SERIAL_QSWMODE = 0x0e 118SERIAL_SDUTY = 0x0f 119SERIAL_QDUTY = 0x10 120SERIAL_SPID = 0x11 121SERIAL_QPID = 0x12 122SERIAL_SCAL = 0x13 123SERIAL_QCAL = 0x14 124SERIAL_SBTTNCFG = 0x15 125SERIAL_QBTTNCFG = 0x16 126SERIAL_SSWSRC = 0x17 127SERIAL_QSWSRC = 0x18 128SERIAL_SVMODE = 0x19 129SERIAL_QVMODE = 0x1a 130SERIAL_QVNOW = 0x1b 131SERIAL_QKILL = 0x1c 132SERIAL_QVNOWRAW = 0x1d 133SERIAL_SLKUP = 0x1e 134SERIAL_QLKUP = 0x1f 135SERIAL_SST = 0x20 136SERIAL_SHW = 0x21 137SERIAL_SCALMETH = 0x22 138SERIAL_QCALMETH = 0x23 139SERIAL_QMEM = 0x24 140SERIAL_LST = 0x25 141SERIAL_CPYST = 0x26 142SERIAL_XFERST = 0x27 143SERIAL_QTRACE = 0x28 144SERIAL_STRRATE = 0x29 145SERIAL_QTRRATE = 0x2A 146SERIAL_SWFMPTS = 0x2B 147SERIAL_SSATLIM = 0x2C 148SERIAL_QWFMPTS = 0x2D 149SERIAL_SCURR = 0x2E 150SERIAL_QCURR = 0x2F 151SERIAL_QLOAD = 0x30 152SERIAL_SVSETRAW = 0x31 153SERIAL_QVSETRAW = 0x32 154SERIAL_SCURRSEL = 0x33 155SERIAL_QCURRSEL = 0x34 156 157SERIAL_DADC = 0xA0 158SERIAL_WFMPWM = 0xA1 159 160# board configurations 161CONF_UNCONF = 0 # hvpsx unconfigured device 162CONF_BIPOLAR = 1 # hvpsx Bipolar board version 1 163CONF_UNIPOLAR = 2 # hvpsx Unipolar board version 1 164 165# setting types 166SETTINGS_CURRENT = 0 167SETTINGS_BACKUP = 1 168SETTINGS_FACTORY = 2 169 170# calibration methods 171CALMETH_POLYNOMIAL = 0 172CALMETH_LOOKUP = 1 173 174# DCDC converter 175DCDC_POS = 1 # positive DC/DC converter 176DCDC_NEG = 2 # negative DC/DC converter 177DCDC_BOTH = 3 # Both DC/DC converters 178 179VNOW_POS = 0x00 # Voltage reading from positive DC/DC 180VNOW_NEG = 0x01 # Voltage reading from negative DC/DC 181VNOW_OUT = 0x02 # Voltage reading at the hvps-x output 182 183# gains of the different PIDs 184PID_KPP = 0x0 # P for positive voltage DCDC converter 185PID_KIP = 0x1 186PID_KDP = 0x2 187PID_KPN = 0x3 # N for negative voltage DCDC converter 188PID_KIN = 0x4 189PID_KDN = 0x5 190PID_KPO = 0x6 # O for output waveform PID 191PID_KIO = 0x7 192PID_KDO = 0x8 193 194# Calibration constants 195CAL_C0P = 0x00 # P for positive voltage measurement 196CAL_C1P = 0x01 197CAL_C2P = 0x02 198CAL_C0N = 0x03 # N for negative voltage measurement 199CAL_C1N = 0x04 200CAL_C2N = 0x05 201CAL_C0O = 0x06 # O for output waveform measurement 202CAL_C1O = 0x07 203CAL_C2O = 0x08 204 205#Current selection jumper (J2) 206J2_5V = 1 # jumper J2 in position 5V (normal current) 207J2_3V3 = 0 # jumper J2 in position 3V3 (normal current) 208 209memorymap = { 210 'Fw-1.0': [(1, 'hw_major', 'B'), (1, 'hw_minor', 'B'), (1, 'fw_major', 'B'), (1, 'fw_minor', 'B'), 211 (1, 'setting_type', 'B'), (1, 'conf', 'B'), (1, 'cal_meth_p', 'B'), (1, 'cal_meth_o', 'B'), 212 (1, 'cal_meth_n', 'B'), (1, 'bttncfg', 'B'), (1, 'SwMode', 'B'), (1, 'SwSrc', 'B'), (1, 'VMode', 'B'), 213 (1, 'Padding', 'B'), (1, 'pid', 'H'), (1, 'Vmax', 'H'), (1, 'Vsetp_raw', 'H'), (1, 'Vsetn_raw', 'H'), 214 (1, 'Duty', 'H'), (12, 'Name', 's'), (21, 'lookup_ADC_p', 'h'), (21, 'lookup_ADC_n', 'h'), 215 (21, 'lookup_ADC_o', 'h'), (21, 'lookup_Vout', 'h'), (3, 'cp', 'f'), (3, 'cn', 'f'), (3, 'co', 'f'), 216 (1, 'Freq', 'f'), (1, 'kpp', 'f'), (1, 'kip', 'f'), (1, 'kdp', 'f')], 217 'Fw-1.1': [(1, 'hw_major', 'B'), (1, 'hw_minor', 'B'), (1, 'fw_major', 'B'), (1, 'fw_minor', 'B'), 218 (1, 'setting_type', 'B'), (1, 'conf', 'B'), (1, 'cal_meth_p', 'B'), (1, 'cal_meth_o', 'B'), 219 (1, 'cal_meth_n', 'B'), (1, 'bttncfg', 'B'), (1, 'SwMode', 'B'), (1, 'SwSrc', 'B'), (1, 'VMode', 'B'), 220 (1, 'Padding', 'B'), (1, 'pid', 'H'), (1, 'Vmax', 'H'), (1, 'Vsetp_raw', 'H'), (1, 'Vsetn_raw', 'H'), 221 (1, 'Duty', 'H'), (12, 'Name', 's'), (21, 'lookup_ADC_p', 'h'), (21, 'lookup_ADC_n', 'h'), 222 (21, 'lookup_ADC_o', 'h'), (21, 'lookup_Vout', 'h'), (3, 'cp', 'f'), (3, 'cn', 'f'), (3, 'co', 'f'), 223 (1, 'Freq', 'f'), (1, 'kpp', 'f'), (1, 'kip', 'f'), (1, 'kdp', 'f')], 224 'Fw-1.2': [(1, 'hw_major', 'B'), (1, 'hw_minor', 'B'), (1, 'fw_major', 'B'), (1, 'fw_minor', 'B'), 225 (1, 'setting_type', 'B'), (1, 'conf', 'B'), (1, 'cal_meth_p', 'B'), (1, 'cal_meth_o', 'B'), 226 (1, 'cal_meth_n', 'B'), (1, 'bttncfg', 'B'), (1, 'SwMode', 'B'), (1, 'SwSrc', 'B'), (1, 'VMode', 'B'), 227 (1, 'Padding0', 'B'), (1, 'pid', 'H'), (1, 'Vmax', 'H'), (1, 'Vsetp_raw', 'H'), (1, 'Vsetn_raw', 'H'), 228 (1, 'Duty', 'H'), (12, 'Name', 's'), (21, 'lookup_ADC_p', 'h'), (21, 'lookup_ADC_n', 'h'), 229 (21, 'lookup_ADC_o', 'h'), (21, 'lookup_Vout', 'h'), (3, 'cp', 'f'), (3, 'cn', 'f'), (3, 'co', 'f'), 230 (1, 'Freq', 'f'), (3, 'pid_p', 'f'), (3, 'pid_o', 'f'), (2, 'current', 'f'), 231 (1, 'current_selection', 'B'), (1, 'wfm_pt_div', 'B'), (500, 'wfm_pts', 'H'), (1, 'wfm_n_pts', 'H')] 232} 233memory_string = { 234 'Fw-1.0': '<14B5H12s84h13f', 235 'Fw-1.1': '<14B5H12s84h13f', 236 'Fw-1.2': '<14B5H12s84h18f2B501H' 237} 238 239# Waveform parameters 240 241WFM_TP = 0.5e-3 # WFM pid update period 242WFM_MAX_PTS = 500 # maximum number of pts. 243 244class HVPS: 245 """ Class to control a petapicovoltron hvps-x 246 247 The class implements the low-level functions to interface the hvps-x firmware 248 for easy communication with the hvps-x.""" 249 250 # ====Communication functions and class constructor==== 251 def __init__(self, port, init=True): 252 """ Initialises a HVPS object: dev = HVPS('/path/to/serial/port'). 253 254 :param port: COM port to which the HVPS is connected. 255 :param init: if True (default) initialises the hvps-x. Setting to false can be useful if the intended use is 256 to check whether the device connected to the com port is a hvps-x""" 257 self.name = '' 258 self.vmax = 0 259 self.err = 0 260 self.ser = serial.Serial() # the serial connection to the HVPS 261 self.lookup_adc_p = [] # lookup tables for linear interpolation 262 self.lookup_adc_o = [] 263 self.lookup_v_out = [] 264 self.cp = [] # calibration coefficients. 265 self.co = [] 266 self.calmeth_p = -1 267 self.calmeth_n = -1 268 self.calmeth_o = -1 269 270 self.firmware = 0 271 self.hardware = 0 272 self.conf = 0 # configuration of the hvps-x 273 self.is_hvpsx = 0 274 275 try: 276 self.ser = serial.Serial(port, 115200, timeout=20) 277 except serial.SerialException: 278 self.err = self.err | ERR_PORT 279 if DEBUG: 280 print("Cannot connect to serial port. Probably busy.") 281 else: 282 self.ser.reset_input_buffer() 283 if DEBUG: 284 text = "connecting to " + port 285 print(text) 286 print("Serial port open") 287 z = self._send_receive_4bytes(SERIAL_QCONF) 288 if z == 0: # no response: the device connected doesn't talk using this protocol 289 self.err = self.err | ERR_COM 290 if DEBUG: 291 print("Communication error: the connected device doesn't reply to commands") 292 z_hvps = z & 0xFFFF 293 z_conf = (z & 0xFFFF0000) >> 16 294 if z_hvps == HVPSX_PID: 295 self.is_hvpsx = 1 296 if z_conf != CONF_UNCONF: 297 self.conf = z_conf 298 else: 299 self.err = self.err | ERR_CONF # the hvps-x is not configured 300 if DEBUG: 301 print("The hvps is not configured") 302 z = self._send_4bytes_receive_nbytes(SERIAL_QNAME, param1=0, param2=0, fstring='<12s') 303 z = z[0].split(b'\x00', 1)[0] 304 self.name = z 305 else: 306 self.err = self.err | ERR_TYPE # The connected device replied with Err! but is not a hvps-x 307 if DEBUG: 308 print("Type error: the device replies to commands, but is not a hvps-x.") 309 if init and not self.err: 310 self.check_version_compatibility() 311 self.q_vmax() 312 313 def close(self, zero=True): # closes connection with the HVPS 314 """Closes the connection to the HVPS. Sets voltage to 0 if board was connected 315 316 Input (optional): zero. If True, will set the voltage to 0 and the optocouplers to off before closing the 317 communication. If False, the hvps-x is left in its current state""" 318 319 if self.ser.is_open: 320 if not (self.err & ERR_COM): # if connected board is not running the firmware, do not send command 321 if zero: 322 self.s_sw_mode(SWMODE_OFF) # for hvps-x, this will disable both optocouplers. 323 self.s_vset(0, DCDC_BOTH) # set the voltage to 0 as a safety measure 324 self.ser.close() 325 326 if DEBUG: 327 print("Serial port closed") 328 329 def _read_4bytes_uint(self): 330 ans = self.ser.read(4) 331 z = int.from_bytes(ans, byteorder='little') 332 return z 333 334 def _read_4bytes_int16(self): # Read 4 bytes with a 16-bit signed int in bytes 0 and 1 (and ignore bytes 2,3) 335 ans1 = self.ser.read(2) 336 self.ser.read(2) # discard the last 2 bytes 337 z = int.from_bytes(ans1, byteorder='little', signed=True) 338 return z 339 340 def _read_4bytes_float(self): 341 ans = self.ser.read(4) 342 z = struct.unpack('<f', ans) # little endian: string starts with the low index of the array, which 343 # represents the low bits of the float 344 return z[0] 345 346 def _send_receive_4bytes(self, cmd, param1=0, param2=0, typ='uint'): 347 param2 = round(param2) 348 if param2 < 0: 349 param2 = param2 & 0xFFFF # represent is as two's complement 350 cmd = cmd + (param1 << 8) + (param2 << 16) 351 cmd_b = cmd.to_bytes(4, 'little') 352 self.ser.write(cmd_b) 353 if typ == 'uint': 354 z = self._read_4bytes_uint() 355 elif typ == 'int16': 356 z = self._read_4bytes_int16() 357 else: 358 z = self._read_4bytes_float() 359 return z 360 361 def _send_4bytes_receive_nbytes(self, cmd, param1=0, param2=0, fstring='<2B'): 362 n = struct.calcsize(fstring) 363 param2 = round(param2) 364 if param2 < 0: 365 param2 = param2 & 0xFFFF # represent it as two's complement 366 cmd = cmd + (param1 << 8) + (param2 << 16) 367 cmd_b = cmd.to_bytes(4, 'little') 368 self.ser.write(cmd_b) 369 x = self.ser.read(n) 370 z = struct.unpack(fstring, x) 371 return z # returns unpacked tuples 372 373 def _send_nbytes_receive_4bytes(self, cmd, packet, typ='uint'): 374 packet = bytes([cmd]) + packet 375 self.ser.write(packet) 376 if typ == 'uint': 377 z = self._read_4bytes_uint() 378 elif typ == 'int16': 379 z = self._read_4bytes_int16() 380 else: 381 z = self._read_4bytes_float() 382 return z 383 384 def _download_voltage_calibration_data(self): 385 # only download if required. 386 if self.calmeth_p == -1: # calibration method not yet fetched from HVPS 387 self.q_calibration_method(which=VNOW_POS) 388 if self.calmeth_n == -1: # calibration method not yet fetched from HVPS 389 self.q_calibration_method(which=VNOW_NEG) 390 if self.calmeth_o == -1: # calibration method not yet fetched from HVPS 391 self.q_calibration_method(which=VNOW_OUT) 392 393 if (self.calmeth_o == CALMETH_LOOKUP or self.calmeth_p == CALMETH_LOOKUP) and not self.lookup_v_out: 394 self.lookup_v_out = self.q_lookup(0) 395 if self.calmeth_p == CALMETH_LOOKUP and not self.lookup_adc_p: 396 self.lookup_adc_p = self.q_lookup(1) 397 if self.calmeth_o == CALMETH_LOOKUP and not self.lookup_adc_o: 398 self.lookup_adc_o = self.q_lookup(2) 399 400 if self.calmeth_p == CALMETH_POLYNOMIAL and not self.cp: # if we don't know what are the calibration values 401 self.cp.append(self.q_cal(CAL_C0P)) 402 self.cp.append(self.q_cal(CAL_C1P)) 403 self.cp.append(self.q_cal(CAL_C2P)) 404 405 if self.calmeth_o == CALMETH_POLYNOMIAL and not self.co: # if we don't know what are the calibration values 406 self.co.append(self.q_cal(CAL_C0O)) 407 self.co.append(self.q_cal(CAL_C1O)) 408 self.co.append(self.q_cal(CAL_C2O)) 409 410 def _convert_raw_to_voltage(self, vraw, which=VNOW_POS): 411 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 412 which = VNOW_POS 413 # Download calibration values the first time the function is used 414 self._download_voltage_calibration_data() 415 if which == VNOW_POS: 416 if self.calmeth_p == CALMETH_LOOKUP: 417 vcal = np.interp(vraw, self.lookup_adc_p, self.lookup_v_out, left=None, right=None, period=None) 418 else: 419 vcal = self.cp[0] + self.cp[1] * vraw + self.cp[2] * np.square(vraw) 420 elif which == VNOW_NEG: 421 vcal = 0 422 else: 423 if self.calmeth_o == CALMETH_LOOKUP: 424 vcal = np.interp(vraw, self.lookup_adc_o, self.lookup_v_out, left=None, right=None, period=None) 425 else: 426 vcal = self.co[0] + self.co[1] * vraw + self.co[2] * np.square(vraw) 427 return vcal 428 429 def _convert_voltage_to_raw(self, v, which): 430 # this function is not needed and can be removed. 431 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 432 which = VNOW_POS 433 # Download calibration values the first time the function is used 434 self._download_voltage_calibration_data() 435 436 if which == VNOW_POS: 437 if self.calmeth_p == CALMETH_LOOKUP: 438 vraw = np.interp(v, self.lookup_v_out, self.lookup_adc_p, left=None, right=None, period=None) 439 elif self.cp[2] == 0: # if polynomial but linear 440 vraw = (v - self.cp[0]) / self.cp[1] 441 else: # polynomial quadratic 442 vraw = (-self.cp[1] + np.sqrt(np.square(self.cp[1]) - 4 * self.cp[2] * (self.cp[0]-v)))/(2 * self.cp[2]) 443 444 elif which == VNOW_NEG: 445 vraw = 0 # bipolar option not implemented yet 446 else: 447 if self.calmeth_o == CALMETH_LOOKUP: 448 vraw = np.interp(v, self.lookup_v_out, self.lookup_adc_o, left=None, right=None, period=None) 449 elif self.co[2] == 0: # if polynomial but linear 450 vraw = (v - self.co[0]) / self.co[1] 451 else: # polynomial quadratic 452 vraw = (-self.co[1] + np.sqrt(np.square(self.co[1]) - 4 * self.co[2] * (self.co[0]-v)))/(2 * self.co[2]) 453 return vraw 454 455 def is_bipolar(self): # return true if connected device is hvpsx and bipolar unit 456 """ Returns true if the hvps-x configuration is bipolar.""" 457 if self.conf & 1: 458 return True 459 else: 460 return False 461 462 # ====Commands related to voltage and frequency==== 463 def s_vset(self, x, polarity=DCDC_POS): # sets the output voltage 464 """Sets the output voltage of the HVPS. The new parameter remains valid until a new call to this command, or 465 when the HVPS is powered off. Using the 466 save() command enables to save this parameter in memory\n 467 Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is 468 regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the 469 output to the set point; see s_vmode() command. 470 :param x: voltage set point in volt (int) 471 :param polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). 472 This parameter can be ignored given that only unipolar configuration is currently supported. 473 :return: voltage set point accepted by hvps-x 474 """ 475 x = constrain(abs(x), 0, self.vmax) 476 x = int(x) 477 if polarity != DCDC_POS and polarity != DCDC_NEG and polarity != DCDC_BOTH: 478 polarity = DCDC_POS 479 z = self._send_receive_4bytes(SERIAL_SVSET, param1=polarity, param2=x) 480 if x == 0: 481 z = 0 # avoids calibration issue near 0 that would lead to a negative number which is saturated to 0 in 482 # the STM32 (as the stored raw value is uint), leading to a possible large offset error returned value of a 483 # few 10s of volts when back calculated in volts 484 if DEBUG: 485 y = "s_vset(" + str(x) + ") -> " + str(z) 486 print(y) 487 return z 488 489 def q_vset(self, polarity=DCDC_POS): # queries the voltage setpoint 490 """Queries the voltage set point. The returned value is in volts. 491 :param polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for 492 unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported. 493 :return: Voltage set point in volts 494 """ 495 if polarity != DCDC_POS and polarity != DCDC_NEG: 496 polarity = DCDC_POS 497 z = self._send_receive_4bytes(SERIAL_QVSET, param1=polarity) 498 if DEBUG: 499 y = "q_vset -> " + str(z) 500 print(y) 501 502 return z 503 504 def s_vset_raw(self, x, polarity=DCDC_POS): # sets the output voltage 505 """Sets the raw output voltage of the HVPS (0-4095). The new parameter remains valid until a new call to this 506 command, or when the HVPS is powered off. Using the 507 save() command enables to save this parameter in memory\n 508 Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is 509 regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the 510 output to the set point; see s_vmode() command. 511 :param x: raw voltage set point (0-4095) 512 :param polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). 513 This parameter can be ignored given that only unipolar configuration is currently supported. 514 :return: voltage set point accepted by hvps-x 515 """ 516 x = constrain(x, 0, 4095) 517 x = int(x) 518 if polarity != DCDC_POS and polarity != DCDC_NEG and polarity != DCDC_BOTH: 519 polarity = DCDC_POS 520 z = self._send_receive_4bytes(SERIAL_SVSETRAW, param1=polarity, param2=x) 521 if x == 0: 522 z = 0 # avoids calibration issue near 0 that would lead to a negative number which is saturated to 0 in 523 # the STM32 (as the stored raw value is uint), leading to a possible large offset error returned value of a 524 # few 10s of volts when back calculated in volts 525 if DEBUG: 526 y = "s_vset_raw(" + str(x) + ") -> " + str(z) 527 print(y) 528 return z 529 530 def q_vset_raw(self, polarity=DCDC_POS): # queries the voltage setpoint 531 """Queries the raw voltage set point. The returned value is between 0 and 4095. 532 533 :param polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for 534 unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported. 535 :return: Voltage raw set point (0-4095)) 536 """ 537 if polarity != DCDC_POS and polarity != DCDC_NEG: 538 polarity = DCDC_POS 539 z = self._send_receive_4bytes(SERIAL_QVSETRAW, param1=polarity) 540 if DEBUG: 541 y = "q_vset_raw -> " + str(z) 542 print(y) 543 return z 544 545 def q_vnow(self, which=VNOW_POS): # queries the voltage output 546 """Queries the feedback voltage of the HVPS. 547 :param which: which output voltage to read {VNOW_POS (default), VNOW_NEG, VNOW_OUT}. Voltage at the output of the 548 positive DCDC converter, negative DCDC converter, or output of the HVPS 549 :return: voltage value in volts 550 """ 551 552 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 553 which = VNOW_POS 554 z = self._send_4bytes_receive_nbytes(SERIAL_QVNOW, param1=which, fstring='<2h') 555 z_out = z[1] 556 z_main = z[0] 557 if DEBUG: 558 y = "q_vnow -> " + str(z_main) + " / " + str(z_out) 559 print(y) 560 return z_main 561 562 def q_vnow_raw(self, which=VNOW_POS): # queries a raw value of the voltage output 563 """Queries the current feedback voltage of the HVPS. The returned value is a raw 12bit ADC value. This avoids 564 running slow floating point calculations on the 565 microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to 566 execute than q_vnow(). Useful for streaming voltage values 567 :param which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the 568 positive DCDC converter, negative DCDC converter, or output of the HVPS. 569 :return: a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT 570 (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is 571 a 12 bit value that can be converted to voltage using the unit's calibration values (see q_vnow_fast() for a 572 way to do this automatically""" 573 574 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 575 which = VNOW_POS 576 z = self._send_receive_4bytes(SERIAL_QVNOWRAW, param1=which, typ='uint') 577 z_out = z & 0xFFFF 578 z_main = (z & 0xFFFF0000) >> 16 579 if DEBUG: 580 y = "q_vnow_raw -> " + str(z_main) + " , " + str(z_out) 581 print(y) 582 return z_main, z_out 583 584 def q_vnow_fast(self, which=VNOW_POS): # queries the voltage output 585 """Queries the current feedback voltage of the HVPS in a raw format and convert it to a calibrated voltage 586 This avoids running slow floating point calculations on the 587 microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to 588 execute than q_vnow(). Useful for streaming voltage values. This method is similar to q_vnow(), except that 589 the conversion from a raw value to a calibrated value is done 590 on the host rather than on the microcontroller. It can take up to 300us to convert a value on the MUC 591 (it depends on the method used (linear, quadratic, lookup table) 592 :param which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the 593 positive DCDC converter, negative DCDC converter, or output of the HVPS. 594 :return: a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT 595 (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is 596 a 12 bit value that can be converted to voltage using the unit's calibration values The returned values 597 are calibrated voltages in Volt""" 598 599 z_main, z_out = self.q_vnow_raw(which=which) 600 v_main = self._convert_raw_to_voltage(z_main, which=which) 601 v_out = self._convert_raw_to_voltage(z_out, which=VNOW_OUT) 602 603 return v_main, v_out 604 605 def s_f(self, x): # sets the frequency 606 """Sets the frequency of the signal when the HVPS is in switching or waveform mode (SWMODE_SW / SWMODE_SW).\n 607 The value returned is the new frequency, taking quantification into account. 608 :param x: frequency in Hz between 0.001 and 1000. In waveform mode, 0.16 Hz <= f <=100 Hz 609 :return: frequency accepted by hvps-x in Hz 610 """ 611 x = constrain(x, 0.001, 1000.0) 612 613 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 614 # Ex: 10.0: 0x41 0x20 0x00 0x00. 615 # 0 10000010 01000000000000000000000 616 # S=0 Positive 617 # Exp=130-127 (offset)=2^3=8 618 # Fraction=1+2^-2=1.25 619 # number=+1.25*8=10 620 f_byte = struct.pack('<f', x) 621 z = self._send_nbytes_receive_4bytes(SERIAL_SF, f_byte, typ='float') 622 623 if DEBUG: 624 y = "s_f(" + str(x) + ") -> " + str(z) 625 print(y) 626 return z 627 628 def q_f(self): # queries the frequency 629 """Queries the frequency. The returned value is in Hz.""" 630 z = self._send_receive_4bytes(SERIAL_QF, param1=0, param2=0, typ='float') 631 632 if DEBUG: 633 y = "q_f -> " + str(z) 634 print(y) 635 636 return z 637 638 def s_current(self, xnorm, xhigh): # sets the current limit setting 639 """Sets the current output in both current settings (normal and high). This value is used to calculate the PID 640 coefficients for the waveform. Values can be obtained by measuring the charging rate when charging a capacitor 641 of known value. This is a configuration parameter that must be set according to the capabilities of the hvps-x. 642 It is not a user-settable output current. 643 :param xnorm: current in A between 50E-6 and 500E-6 in normal output current config. 644 :param xhigh: current in A between 50E-6 and 500E-6 in high output current config. 645 :return: SERIAL_OK or SERIAL_ERROR 646 """ 647 xnorm = constrain(xnorm, 50E-6, 500E-6) 648 xhigh= constrain(xhigh, 50E-6, 500E-6) 649 f_byte = struct.pack('<2f', xnorm, xhigh) 650 z = self._send_nbytes_receive_4bytes(SERIAL_SCURR, f_byte, typ='uint') 651 652 if DEBUG: 653 y = "s_current({},{}) -> {}".format(xnorm, xhigh, z) 654 print(y) 655 return z 656 657 def q_current(self): # queries the current limit setting 658 """Queries the current limit setting. The returned value is in A. 659 660 :return: A tuple of two current values corresponding to the output current settings in normal and high current 661 modes.""" 662 663 z = self._send_4bytes_receive_nbytes(SERIAL_QCURR, param1=0, param2=0, fstring='<2f') 664 665 if DEBUG: 666 y = "q_curr() -> " + str(z) 667 print(y) 668 return z 669 670 def s_current_selection(self, x): 671 """Sets the position of the current selection jumper 672 :param x: Position of the output current selection jumper. Either J2_3V3 (normal) or J2_5V depending on the 673 jumper position. 3V3 and 5V are written on the silkscreen and respectively correspond to normal and high current 674 modes. After changing the position of the jumper, this command should be run to reflect the change. The new 675 setting will be kept until the unit is power-cycled. To make the selection permanent, use the save() command 676 after the s_current_selection(x) command. 677 :return: Accepted current selection jumper position (J2_3V3 or J2_5V) 678 """ 679 x = int(np.abs(x)) 680 if x > J2_5V: 681 x = J2_5V 682 z = self._send_receive_4bytes(SERIAL_SCURRSEL, param1=x, param2=0, typ='uint') 683 684 if DEBUG: 685 print("s_current_selection({}) --> {}".format(x, z)) 686 return z 687 688 def q_current_selection(self): 689 """Queries the position of the current selection jumper 690 :return: Current selection (J2_3V3 or J2_5V) 691 """ 692 z = self._send_receive_4bytes(SERIAL_QCURRSEL, param1=0, param2=0, typ='uint') 693 694 if DEBUG: 695 print("q_current_selection() --> {}".format(z)) 696 return z 697 698 def s_duty(self, x): # sets the duty cycle of the switching signal 699 """Sets the duty cycle of the switching signal 700 :param x: the duty cycle (float in the range 0-1) 701 :return: the current duty cycle (float between 0 and 1) 702 """ 703 duty = int(x * 1000) # hvps-x is coding duty cycle on a 0-1000 scale 704 705 z = self._send_receive_4bytes(SERIAL_SDUTY, param1=0, param2=duty, typ='uint') 706 z = z / 1000 707 if DEBUG: 708 y = "s_duty(" + str(x) + ") -> " + str(z) 709 print(y) 710 return z 711 712 def q_duty(self): # queries the duty cycle of the switching signal 713 """queries the duty cycle of the switching signal 714 :return: the current duty cycle (float between 0 and 1) 715 """ 716 717 z = self._send_receive_4bytes(SERIAL_QDUTY, param1=0, param2=0, typ='uint') 718 z = float(z) / 1000.0 719 if DEBUG: 720 y = "q_duty() -> " + str(z) 721 print(y) 722 return z 723 724 # ===Commands to change the voltage control and switching behaviour (mode and source)==== 725 def s_sw_mode(self, x): # sets the switching mode 726 """Sets the switching mode of the hvps-x. \n 727 SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. 728 This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV 729 DC/DC converter. 730 SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and 731 SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with 732 the specified duty cycle. 733 SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory 734 The program tab "Waveform" can be used to set the parameters of the waveform. 735 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 736 save() command enables to save this parameter in memory 737 :param x: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM 738 :return: Switching mode set by the HVPS 739 """ 740 741 if x > SWMODE_LOW: 742 x = SWMODE_OFF 743 744 z = self._send_receive_4bytes(SERIAL_SSWMODE, param1=x, param2=0, typ='uint') 745 if DEBUG: 746 y = "s_sw_mode(" + str(x) + ") -> " + str(z) 747 print(y) 748 return z 749 750 def q_sw_mode(self): # queries the switching mode 751 """queries the switching mode of the hvps-x. \n 752 SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. 753 This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV 754 DC/DC converter. 755 SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and 756 SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with 757 the specified duty cycle. 758 SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory 759 The program tab "Waveform" can be used to set the parameters of the waveform. 760 :return: Switching mode set by the HVPS: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM 761 """ 762 z = self._send_receive_4bytes(SERIAL_QSWMODE, param1=0, param2=0, typ='uint') 763 if DEBUG: 764 y = "q_sw_mode -> " + str(z) 765 print(y) 766 return z 767 768 def s_sw_src(self, x): # sets the switching source 769 """Sets the source of the switching signal. 770 771 Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard 772 switching (from internal clock of the board), or SWSRC_BTTN for the push button.\n 773 Using the save() command enables to save this parameter in memory 774 :param x: SWSRC_TMR, or SWSRC_BTTN 775 :return: SWSRC_TMR, or SWSRC_BTTN 776 """ 777 if x > SWSRC_BTTN: 778 x = SWSRC_TMR 779 z = self._send_receive_4bytes(SERIAL_SSWSRC, param1=x, param2=0, typ='uint') 780 if DEBUG: 781 y = "s_sw_src(" + str(x) + ") -> " + str(z) 782 print(y) 783 return z 784 785 def q_sw_src(self): # queries the switching source 786 """queries the source of the switching signal. 787 788 queries the source of the switching signal. Output values are: SWSRC_TMR for onboard 789 switching (from internal clock of the board), or SWSRC_BTTN for the push button.\n 790 Using the save() command enables to save this parameter in memory 791 :return: SWSRC_TMR, or SWSRC_BTTN 792 """ 793 794 z = self._send_receive_4bytes(SERIAL_QSWSRC, param1=0, param2=0, typ='uint') 795 796 if DEBUG: 797 y = "q_sw_src -> " + str(z) 798 print(y) 799 return z 800 801 def s_bttn_cfg(self, x): # sets the configuration of the push button 802 """Defines the behaviour of the push button 803 804 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 805 save() command enables to save this parameter in memory 806 :param x: 2-bit value (0x0 to 0x3). bit 0 - Defines the behaviour of the push button, when the switching source 807 of the HVPS is set to the push button 808 (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour 809 (i.e. the high voltage is turned on as long as the button is pressed), 810 and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time 811 to turn it off).\n 812 bit 1 - State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW 813 :return: the 2-bit button config value 814 """ 815 if x > 3: 816 x = 3 817 z = self._send_receive_4bytes(SERIAL_SBTTNCFG, param1=x, param2=0, typ='uint') 818 if DEBUG: 819 y = "s_bttn_cfg(" + str(x) + ") -> " + str(z) 820 print(y) 821 822 return z 823 824 def q_bttn_cfg(self): # queries the latch mode of the push button 825 """Queries the behaviour of the push button 826 827 :return: the 2-bit button config value. bit 0: Defines the behaviour of the push button, when the switching 828 source of the HVPS is set to the push button (SWSRC_BTTN, c.f. s_sw_src() command above). 829 Values are 0 for a push button behaviour (i.e. the high voltage is turned on as long as the button is pressed), 830 and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time 831 to turn it off).\n 832 bit 1: State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW 833 """ 834 z = self._send_receive_4bytes(SERIAL_QBTTNCFG, param1=0, param2=0, typ='uint') 835 if DEBUG: 836 y = "q_bttn_cfg -> " + str(z) 837 print(y) 838 839 return z 840 841 def q_kill(self): # queries the state of the kill button. Kill=1 means HV is disabled 842 """Queries whether HV is disabled (Kill=1) or enabled (kill=0). When kill = 1 there will not be a HV present at 843 the hvps-x output, irrespective of any software setting. 844 :return: 1 if the Switch S1 on the board is set to 0 (HV output is killed), 0 otherwise. 845 """ 846 z = self._send_receive_4bytes(SERIAL_QKILL, param1=0, param2=0, typ='uint') 847 848 if DEBUG: 849 y = "q_kill -> " + str(z) 850 print(y) 851 return z 852 853 def s_v_mode(self, x): # sets the voltage control mode 854 """Sets the voltage control mode 855 856 Sets the voltage control mode (i.e. how is the value of the output voltage controlled):\n 857 VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).\n 858 VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).\n 859 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 860 save() command enables to save this parameter in memory\n 861 VMODE_O has an internal safeguard that will decrease the setting if the sensing circuit saturates. However, 862 the voltage can still be slightly higher than the DCDC converter upper limit. User must check that the output 863 voltage remains within the allowed range 864 :param x: VMODE_R, VMODE_O 865 :return: VMODE_R, VMODE_O 866 """ 867 868 if x > VMODE_O: 869 x = VMODE_R 870 871 z = self._send_receive_4bytes(SERIAL_SVMODE, param1=x, param2=0, typ='uint') 872 if DEBUG: 873 y = "s_v_mode(" + str(x) + ") -> " + str(z) 874 print(y) 875 return z 876 877 def q_v_mode(self): # queries the switching source 878 """Queries the voltage control mode 879 880 :return: VMODE_R internal voltage regulator, VMODE_O internal 881 open loop control (on-board regulator disconnected). 882 """ 883 884 z = self._send_receive_4bytes(SERIAL_QVMODE, param1=0, param2=0, typ='uint') 885 886 if DEBUG: 887 y = "q_v_mode -> " + str(z) 888 print(y) 889 return z 890 891 # ====Functions to set configuration parameters==== 892 def s_pid(self, x, pid=PID_KPP): 893 """Sets the gains of the PIDs 894 Use save() command to save the new values to memory 895 :param x: the value (float) of the gain. 896 :param pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, 897 for positive voltage PID, output voltage PID (used for the waveform mode). 898 :return: the gain value 899 """ 900 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 901 # Ex: 10.0: 0x41 0x20 0x00 0x00. 902 # 0 10000010 01000000000000000000000 903 # S=0 Positive 904 # Exp=130-127 (offset)=2^3=8 905 # Fraction=1+2^-2=1.25 906 # number=+1.25*8=10 907 pid_byte = struct.pack('<Bf', pid, x) 908 z = self._send_nbytes_receive_4bytes(SERIAL_SPID, pid_byte, typ='float') 909 910 if DEBUG: 911 y = "s_pid(" + str(x) + "," + str(pid) + ") -> " + str(z) 912 print(y) 913 return z 914 915 def q_pid(self, pid=PID_KPP): # queries the frequency 916 """returns the gains of the PIDs 917 918 :param pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, 919 for positive voltage PID, output voltage PID (used for the waveform mode). 920 :return: the gain value of the chosen parameter 921 """ 922 923 z = self._send_receive_4bytes(SERIAL_QPID, param1=pid, param2=0, typ='float') 924 925 if DEBUG: 926 y = "q_pid(" + str(pid) + ") -> " + str(z) 927 print(y) 928 929 return z 930 931 def s_cal(self, x, cal=CAL_C1P): 932 """Sets the calibration constants of the analogue input signals (conversion to calibrated voltage values) 933 Use save() to commit the setting to memory 934 :param x: the value of the calibration coefficient (float) 935 :param cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive 936 voltage measurement, output voltage measurement 937 :return: the calibration value 938 """ 939 940 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 941 # Ex: 10.0: 0x41 0x20 0x00 0x00. 942 # 0 10000010 01000000000000000000000 943 # S=0 Positive 944 # Exp=130-127 (offset)=2^3=8 945 # Fraction=1+2^-2=1.25 946 # number=+1.25*8=10 947 cal_byte = struct.pack('<Bf', cal, x) 948 z = self._send_nbytes_receive_4bytes(SERIAL_SCAL, cal_byte, typ='float') 949 950 if DEBUG: 951 y = "s_cal(" + str(x) + "," + str(cal) + ") -> " + str(z) 952 print(y) 953 return z 954 955 def q_cal(self, cal=CAL_C1P): # queries the frequency 956 """queries the calibration constants of the analogue input signals (conversion to calibrated voltage values) 957 958 :param cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive 959 voltage measurement, output voltage measurement 960 :return: the calibration value 961 """ 962 963 z = self._send_receive_4bytes(SERIAL_QCAL, param1=cal, param2=0, typ='float') 964 965 if DEBUG: 966 y = "q_cal(" + str(cal) + ") -> " + str(z) 967 print(y) 968 return z 969 970 def s_calibration_method(self, which=VNOW_POS, calmeth=CALMETH_POLYNOMIAL): 971 if not (calmeth == CALMETH_POLYNOMIAL or calmeth == CALMETH_LOOKUP): 972 calmeth = CALMETH_POLYNOMIAL 973 if not (which == VNOW_POS or which == VNOW_OUT or which == VNOW_NEG): 974 which = VNOW_POS 975 z = self._send_receive_4bytes(SERIAL_SCALMETH, param1=which, param2=calmeth, typ='uint') 976 977 if DEBUG: 978 y = "s_calibration_method({0}, {1}) -> {2}".format(which, calmeth, z) 979 print(y) 980 if which == VNOW_POS: 981 self.calmeth_p = z 982 elif which == VNOW_OUT: 983 self.calmeth_o = z 984 else: 985 self.calmeth_n = z 986 987 def q_calibration_method(self, which=VNOW_POS): 988 if not (which == VNOW_POS or which == VNOW_OUT or which == VNOW_NEG): 989 which = VNOW_POS 990 z = self._send_receive_4bytes(SERIAL_QCALMETH, param1=which, param2=0, typ='uint') 991 992 if DEBUG: 993 y = "q_calibration_method({0}) -> {1}".format(which, z) 994 print(y) 995 if which == VNOW_POS: 996 self.calmeth_p = z 997 elif which == VNOW_NEG: 998 self.calmeth_n = z 999 else: 1000 self.calmeth_o = z 1001 1002 def s_lookup(self, x, n, table=LOOKUP_ADC_P): 1003 if n > 20: 1004 n = 20 1005 n = n << 2 # shift left 2 bits 1006 if table == LOOKUP_ADC_P: 1007 n = n | 0b01 1008 elif table == LOOKUP_ADC_O: 1009 n = n | 0b10 1010 x = int(x) 1011 z = self._send_receive_4bytes(SERIAL_SLKUP, param1=n, param2=x, typ='int16') 1012 if DEBUG: 1013 y = "s_lookup({0},{1},{2}) - > {3}".format(x, n, table, z) 1014 print(y) 1015 1016 def q_lookup(self, which): # which=0: vout, which=1: ADC_p, which=2: ADC_o 1017 format_string = f'<21h' 1018 z = self._send_4bytes_receive_nbytes(SERIAL_QLKUP, param1=which, param2=0, fstring=format_string) 1019 lookup_list = list(z) 1020 if DEBUG: 1021 y = "q_lookup({0}) - > {1}".format(which, lookup_list) 1022 print(y) 1023 return lookup_list 1024 1025 def s_saturation_limit(self, limit): # limit: 0-> reset to default 2% above Vmax. Otherwise:1-4095 1026 if limit < 0: 1027 limit = 0 1028 elif limit > 4095: 1029 limit = 4095 1030 z = self._send_receive_4bytes(SERIAL_SLKUP, param1=0, param2=limit, typ='int16') 1031 if DEBUG: 1032 y = "s_saturation_limit({0}) - > {1}".format(limit, z) 1033 print(y) 1034 return z 1035 1036 # ====User Waveform Functions==== 1037 def s_wfm_pts(self, n=1, m=500, pts=None): 1038 """Sets the data points of the waveform 1039 Use save() to commit the setting to memory 1040 :param n: The loop counter. The frequency of the signal is f = 1 / (n * m * 0.5E-3) 0 < n < 256. If you don't 1041 want to bother with n, set n = 1, and use s_f() after transferring the points to get the desired wfm frequency. 1042 :param m: The number of points in the waveform 0 <m <= 500 1043 :param pts: a list of integer voltage points. The list must be 500 elements long. If m<500, then the end of the list 1044 must be padded with zeros. 1045 :return: SERIAL_OK or SERIAL_ERROR 1046 """ 1047 # parameters are 1) n (byte B) the counter divider, 2) the number of points m, an unsigned short, and 3) m unsigned short pts 1048 if pts is None: 1049 z = SERIAL_ERROR 1050 if DEBUG: 1051 print("s_wfm_pts(): ERROR: no points given") 1052 elif len(pts) == 500 and all(isinstance(item, int) for item in pts): 1053 n=int(n) 1054 if n > 255: 1055 n=255 1056 m=int(m) 1057 format_string = f'<B{WFM_MAX_PTS+1}H' 1058 buffer = struct.pack(format_string, n, m, *pts) # array pts always has WFM_MAX_PTS points, but with zeros as 1059 # padding at the end. m indicated the effective number of data points. This is to always have the same command 1060 # length 1061 z = self._send_nbytes_receive_4bytes(SERIAL_SWFMPTS, buffer) 1062 if DEBUG: 1063 y = "s_wfm_pts({0}, {1}, {2}) - > {3}".format(n, m, buffer, z) 1064 print(y) 1065 else: 1066 z = SERIAL_ERROR 1067 if DEBUG: 1068 print("s_wfm_pts(): ERROR: number of points not equal to 500, or not all integer values") 1069 return z 1070 1071 def q_wfm_pts(self): 1072 """queries the data points of the waveform 1073 :return : n, z with n: the number of points in the waveform and z: the list of waveform points in Volts 1074 """ 1075 format_string = f'<H500H' # 1 16-bit unsigned value for the number of points and 500 unsigned 16-bit values. Not all points are effective points (some may be padding zeroes) hence why n is required. 1076 z = self._send_4bytes_receive_nbytes(SERIAL_QWFMPTS, param1=0, param2=0, fstring=format_string) 1077 z = list(z) 1078 n = z.pop(0) # the first element is the number of points 1079 del z[n:] #remove all elements of the list after the number of points to keep 1080 if DEBUG: 1081 y = "q_wfm_pts() - > {}/{}".format(n, z) 1082 print(y) 1083 return n, z 1084 1085 1086 # Miscellaneous functions 1087 def save(self): # save current HVPS parameters into the memory 1088 """save active HVPS parameters into the memory 1089 1090 This command saves the active parameters as \'current\' settings. Current setting are the settings that are 1091 loaded when power is applied to the hvps-x 1092 1093 :return: SERIAL_OK or SERIAL_ERROR 1094 """ 1095 1096 z = self._send_receive_4bytes(SERIAL_SAVE) 1097 1098 if DEBUG: 1099 y = "save -> " + str(z) 1100 print(y) 1101 return z 1102 1103 def save_memory_to_file(self, settings=SETTINGS_CURRENT): 1104 """Dumps the content of the memory into a JSON file. 1105 1106 Dumps the content of the memory into a file. This is useful to keep a backup of the parameters on file. 1107 Files will be created in the interface folder and have the following format: Name_settings_type_date_time.json\n 1108 json files with settings can be transferred back to the hvps-x with the transfer_settings() method of the HVPS 1109 class, or the higher-level function transfer_file_to_memory() 1110 :param settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_Backup 1111 :return: the name of the created file (it follows a standard naming scheme with the date, the name of the 1112 hvps-x and the setting type. 1113 """ 1114 1115 memory = self.q_mem(settings) 1116 now = datetime.now() # current date and time 1117 date_time = now.strftime("%Y_%m_%d") 1118 if settings == SETTINGS_CURRENT: 1119 st_string = '_cur_st_' 1120 else: 1121 st_string = '_bk_st_' 1122 file_name = self.name.decode("utf-8") + st_string + date_time + '.json' 1123 with open(file_name, "w") as write_file: 1124 json.dump(memory, write_file, indent=4) 1125 1126 return file_name 1127 1128 def s_settings(self, 1129 settings=SETTINGS_CURRENT): # sets the active setting to a particular type (useful before saving) 1130 if not (settings == SETTINGS_CURRENT or settings == SETTINGS_FACTORY or settings == SETTINGS_BACKUP): 1131 settings = SETTINGS_CURRENT 1132 x = self._send_receive_4bytes(SERIAL_SST, param1=settings) 1133 if DEBUG: 1134 print('s_settings({0}) -> {1}'.format(settings, x)) 1135 return x 1136 1137 def load_settings(self, settings=SETTINGS_CURRENT): # Load a setting set from memory to the active settings 1138 """This function loads one of the two set of settings (current or backup) as active settings used by the hvps-x 1139 1140 :param settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_BACKUP 1141 :return: 1142 """ 1143 1144 if not (settings == SETTINGS_CURRENT or settings == SETTINGS_BACKUP): 1145 settings = SETTINGS_CURRENT 1146 x = self._send_receive_4bytes(SERIAL_LST, param1=settings) 1147 if DEBUG: 1148 print('load_settings({0}) -> {1}'.format(settings, x)) 1149 1150 self._initialise_hvpsx() # need to reread all parameters 1151 1152 return x 1153 1154 def transfer_settings(self, dict_settings): 1155 """ transfer a setting dictionary from the computer to the hvps-x memory 1156 :param dict_settings: a dictionary containing the settings values. 1157 :return: SERIAL_OK or SERIAL_ERROR 1158 The dictionary of settings should be read from a file dumped using the function save_memory_to_file(). Together 1159 these two functions make it possible to backup the settings (this includes calibration and PID settings in a 1160 file, and gives the opportunity to restore the settings""" 1161 1162 error = False 1163 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1164 siz = struct.calcsize(memory_string[fw_string]) 1165 buffer = ctypes.create_string_buffer(siz) 1166 memmap = memorymap[fw_string] 1167 counter = 0 1168 for x in memmap: 1169 n = x[0] 1170 key = x[1] 1171 data_format = x[2] 1172 if data_format == 'B' or data_format == 's': 1173 bytelength = 1 1174 elif data_format == 'h' or data_format == 'H': 1175 bytelength = 2 1176 elif data_format == 'f': 1177 bytelength = 4 1178 else: 1179 bytelength = 1 1180 data = dict_settings[key] 1181 if key == 'Name': 1182 data = bytes(data, 'utf-8') 1183 if len(data) > 12: 1184 error = True 1185 if DEBUG: 1186 print("Error: name should not exceed 12 characters") 1187 else: 1188 format_str = '<{0}{1}'.format(n, data_format) 1189 struct.pack_into(format_str, buffer, counter, data) 1190 else: 1191 format_str = '<{0}'.format(data_format) 1192 if n == 1: 1193 struct.pack_into(format_str, buffer, counter, data) 1194 else: 1195 for i in range(n): 1196 try: 1197 struct.pack_into(format_str, buffer, counter + i * bytelength, data[i]) 1198 except IndexError: 1199 error = True 1200 if DEBUG: 1201 print('setting dictionary does not fit the expected format') 1202 1203 counter = counter + n * bytelength 1204 data_fw = 'Fw-{0}.{1}'.format(dict_settings['fw_major'], dict_settings['fw_minor']) 1205 if data_fw != fw_string: 1206 error = True 1207 if DEBUG: 1208 print('Error: JSON file firmware version does not match firmware currently on hvps-x. Exiting') 1209 exit(0) 1210 if not error: 1211 buffer = b'xxx' + buffer # Adds 3 random bytes. CMD + 3 random bytes means that when mapping the transfer 1212 # buffer to a structure, it will on an aligned memory address 1213 x = self._send_nbytes_receive_4bytes(SERIAL_XFERST, buffer) 1214 else: 1215 x = SERIAL_ERROR 1216 1217 if DEBUG: 1218 print("transfer_settings(...) -> {0}".format(x)) 1219 return x 1220 1221 def copy_settings(self, src, dst): 1222 """Copies one set of settings to another location:\n 1223 Copying BACKUP_SETTINGS to CURRENT_SETTINGS is useful to restore the backup settings as current settings 1224 (for example if some temporary settings were saved as current settings, for example to experiment with new PID 1225 gain values)\ 1226 Copying CURRENT_SETTINGS to BACKUP_SETTINGS is useful after a new calibration of the HVPS to save the new 1227 calibration as a set of back-up settings 1228 :param src: the settings to copy (CURRENT_SETTINGS or BACKUP_SETTINGS) 1229 :param dst: the destination settings (CURRENT_SETTINGS or BACKUP_SETTINGS) (destination will be overwritten by 1230 the source 1231 :return: SERIAL_OK or SERIAL_ERROR 1232 """ 1233 if (src == SETTINGS_CURRENT or src == SETTINGS_BACKUP) and (dst == SETTINGS_CURRENT or dst == SETTINGS_BACKUP): 1234 x = self._send_receive_4bytes(SERIAL_CPYST, param1=src, param2=dst) 1235 else: 1236 x = SERIAL_ERROR 1237 1238 if DEBUG: 1239 print('copy_settings({0},{1}) -> {2}'.format(src, dst, x)) 1240 1241 return x 1242 1243 def q_mem(self, settings=SETTINGS_CURRENT): # queries the content of the memory 1244 """ 1245 :param settings: which of the two sets of setting sets to query. Either SETTINGS_CURRENT or SETTINGS_Backup 1246 :return: A dictionary with the content of the memory. This is similar to the function save_memory_to_file() 1247 except that it doesn't save the content to a file""" 1248 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1249 memmap = memorymap[fw_string] 1250 dict_mem = {} 1251 1252 z = self._send_4bytes_receive_nbytes(SERIAL_QMEM, settings, param2=0, fstring=memory_string[fw_string]) 1253 bytecount = 0 1254 for i in range(len(memmap)): 1255 length = memmap[i][0] 1256 field = memmap[i][1] 1257 if field == 'Name': 1258 length = 1 1259 y = z[bytecount:bytecount + length] 1260 # if field != 'Padding': 1261 if length == 1: 1262 dict_mem[field] = y[0] 1263 else: 1264 dict_mem[field] = y 1265 bytecount = bytecount + length 1266 1267 dict_mem['Name'] = dict_mem['Name'].decode('UTF-8') 1268 dict_mem['Name'] = dict_mem['Name'].split("\0")[0] 1269 1270 if DEBUG: 1271 print(dict_mem) 1272 return dict_mem 1273 1274 def q_ver(self): # queries the firmware version 1275 """returns the current version of the firmware / hardware running on the board.""" 1276 z = self._send_receive_4bytes(SERIAL_QVER) 1277 Firm_minor = z & 0xFF 1278 Firm_major = (z >> 8) & 0xFF 1279 Hard_minor = (z >> 16) & 0xFF 1280 Hard_major = (z >> 24) 1281 self.firmware = Firm_major + Firm_minor / 10 1282 self.hardware = Hard_major + Hard_minor / 10 1283 if DEBUG: 1284 y = "q_ver -> {0} / {1}".format(self.firmware, self.hardware) 1285 print(y) 1286 return self.firmware, self.hardware 1287 1288 # Configuration functions 1289 def q_conf(self): # queries the configuration 1290 """returns the configuration of the board. 1291 :return: the configuration of the board in the high 2 bytes (CONF_UNCONF, CONF_UNIPOLAR, or CONF_BIPOLAR) 1292 and the PID (product ID) in the low 2 bytes. This is 0x0632 for a hvps-x unit. 1293 """ 1294 1295 z = self._send_receive_4bytes(SERIAL_QCONF) 1296 z_hvps = z & 0xFFFF 1297 z_conf = (z & 0xFFFF0000) >> 16 1298 if DEBUG: 1299 y = "q_conf -> " + hex(z_conf) + " / " + hex(z_hvps) 1300 print(y) 1301 self.conf = z_conf 1302 return z 1303 1304 def s_conf(self, bipolar): 1305 """Sets the configuration of the board 1306 :param bipolar: boolean. Is board bipolar (True), or unipolar (false) 1307 :return: bipolar: boolean 1308 """ 1309 if bipolar: 1310 param = CONF_BIPOLAR 1311 else: 1312 param = CONF_UNIPOLAR 1313 z = self._send_receive_4bytes(SERIAL_SCONF, param1=param) 1314 if DEBUG: 1315 y = "s_conf -> " + hex(z) 1316 print(y) 1317 if z == SERIAL_ERROR: 1318 print("Error: this configuration is not recognised") 1319 self.conf = z 1320 return z 1321 1322 def s_vmax(self, x): # sets the maximum voltage rating of the board 1323 """ sets the voltage rating of the hvps-x. Must match the EMCO DC/DC converter rating. 1324 :param x: Voltage rating of hvps-x in Volt 1325 :return: Voltage rating of hvps-x in Volt 1326 """ 1327 1328 x = constrain(x, 0, 6000) 1329 z = self._send_receive_4bytes(SERIAL_SVMAX, param2=x) 1330 if DEBUG: 1331 y = "s_vmax(" + str(x) + ") -> " + str(z) 1332 print(y) 1333 self.vmax = z 1334 return z 1335 1336 def q_vmax(self): # queries the voltage rating of the board 1337 """ Queries the maximal voltage of the board. The returned value is in volts. 1338 :return: board maximal voltage (V) 1339 """ 1340 z = self._send_receive_4bytes(SERIAL_QVMAX) 1341 if DEBUG: 1342 y = "q_vmax -> " + str(z) 1343 print(y) 1344 self.vmax = z 1345 return z 1346 1347 def s_name(self, x): # set the name of the HVPS 1348 """ Sets the name of the HVPS. 1349 :param x: Name of the hvps-x. 11 characters maximum 1350 :return: name accepted by hvps-x 1351 """ 1352 ll = len(x) 1353 if ll <= 12: 1354 x = bytearray(x, 'utf-8') 1355 for i in range(12 - ll): # pad the string with 0s 1356 x = x + b'\0' 1357 self._send_nbytes_receive_4bytes(SERIAL_SNAME, x, typ='uint') 1358 z = self.q_name() 1359 else: 1360 z = 'too long' 1361 if DEBUG: 1362 y = "s_name(" + str(x) + ") -> " + str(z) 1363 print(y) 1364 return z 1365 1366 def q_name(self): # queries the name of the board 1367 """queries the name of the board 1368 :return: Name of the board 1369 """ 1370 1371 # x = self._send_receive_4bytes(SERIAL_QNAME, param1=0) 1372 # x = x + (self._send_receive_4bytes(SERIAL_QNAME, param1=4) << 32) 1373 # x = x + (self._send_receive_4bytes(SERIAL_QNAME, param1=8) << 64) 1374 # x = x.to_bytes(12, 'little') 1375 1376 z = self._send_4bytes_receive_nbytes(SERIAL_QNAME, param1=0, param2=0, fstring='<12s') 1377 z = z[0].split(b'\x00', 1)[0] # take first (only) element of the tuple and remove the 0s at the end of string 1378 1379 self.name = z 1380 if DEBUG: 1381 y = "q_name -> " + str(z) 1382 print(y) 1383 return z 1384 1385 def set_hardware_version(self, hw_major, hw_minor): 1386 param = (hw_major << 8) + hw_minor 1387 self._send_receive_4bytes(SERIAL_SHW, param2=param) 1388 1389 def q_load_parameters(self): 1390 z = self._send_4bytes_receive_nbytes(SERIAL_QLOAD, param1=0, param2=0, fstring='<4H') 1391 if np.isin(0, np.array(z)): 1392 r = 0 1393 c = 0 1394 else: 1395 dv = self._convert_raw_to_voltage(z[0], VNOW_OUT) 1396 pwm_dv = z[1] 1397 ssv = self._convert_raw_to_voltage(z[2], VNOW_OUT) 1398 pwm_ss = z[3] 1399 current_selection = self.q_current_selection() 1400 imax = self.q_current()[current_selection] 1401 i_step = imax * pwm_dv / 4095 1402 i_ss = imax * pwm_ss / 4095 1403 dv_dt = dv / 5E-3 # dv is measured over 20ms 1404 c = i_step / dv_dt * 1E9 # C in nF 1405 r = ssv / i_ss / 1E6 # R in MOhm 1406 if DEBUG: 1407 print('q_load_parameters() --> {} nF, {} MOhm'.format(c, r)) 1408 return r, c 1409 1410 def check_version_compatibility(self): 1411 self.q_ver() 1412 lib_string = 'Lib-' + LIB_VER 1413 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1414 hw_string = 'Hw-{0:.1f}'.format(self.hardware) 1415 1416 list_compatible_fw = compatibility_dict[lib_string] 1417 if fw_string in list_compatible_fw: 1418 list_compatible_hw = compatibility_dict[fw_string] 1419 if hw_string not in list_compatible_hw: 1420 self.err |= ERR_FIRM 1421 else: 1422 self.err |= ERR_FIRM 1423 1424 def diagnostic_ADC(self, which_vnow=VNOW_POS): 1425 x = self._send_4bytes_receive_nbytes(SERIAL_DADC, which_vnow, 0, '<32H') 1426 return x 1427 1428 def q_trace(self, which_vnow=VNOW_POS): 1429 x = self._send_4bytes_receive_nbytes(SERIAL_QTRACE, which_vnow, 0, '<100H') 1430 x = np.array(x) 1431 if DEBUG: 1432 y = "trace({0}) -> {1}".format(which_vnow, x) 1433 print(y) 1434 return x 1435 1436 def s_trace_rate(self, rate=5): 1437 """Sets the rate at which trace points are stored in memory 1438 :param rate: time in ms between point acquisition. This is limited by the time at which the hvps-x is 1439 calculating the voltages; currently evey 5ms. 1440 :return: the rate accepted by the hvps-x""" 1441 1442 rate = int(rate) 1443 if rate < 2: 1444 rate = 2 1445 x = self._send_receive_4bytes(SERIAL_STRRATE, param1=0, param2=rate, typ='uint') 1446 x = x / 10 # the function returns the number of ticks of the counter, which counts at 10kHz. 1447 if DEBUG: 1448 y = "s_trace_rate({0}) -> {1}".format(rate, x) 1449 print(y) 1450 return x 1451 1452 def q_trace_rate(self): 1453 """queries the rate at which trace points are stored in memory 1454 :param 1455 :return the rate at which trace points are stored in memory (in ms)""" 1456 1457 x = self._send_receive_4bytes(SERIAL_QTRRATE, param1=0, param2=0, typ='uint') 1458 x = x / 10 # the function returns the number of ticks of the counter, which counts at 10kHz. 1459 if DEBUG: 1460 y = "q_trace_rate() -> {0}".format(x) 1461 print(y) 1462 return x 1463 1464 def s_waveform_pwm(self, value): 1465 1466 value = constrain(value, -4095, 4095) 1467 value = int(value) 1468 x = self._send_receive_4bytes(SERIAL_WFMPWM, param1=0, param2=value, typ='uint') 1469 if DEBUG: 1470 y = "s_waveform_pwm({0}) -> {1}".format(value, x) 1471 print(y) 1472 return x 1473 1474 1475def list_hvps(list_all=False): 1476 """lists the hvps-x connected to PC in a dictionary.The key is the name of the HVPS, and the parameter is 1477 the associated serial port 1478 :param list_all: if True list all connected HVPS. 1479 if list_all is false, it will only list device that are configured. Using True enable to list unconfigured 1480 devices and give the opportunity to configure them 1481 :return: dictionary of connected hvps-x""" 1482 1483 hvpsx_ports = [ # creates a list with all STM32 VCP devices connected 1484 p.device 1485 for p in serial.tools.list_ports.comports() 1486 if (p.pid == 0x5740 and p.vid == 0x483) 1487 ] 1488 dict_hvps = {} 1489 for port in hvpsx_ports: 1490 dev = HVPS(port, init=False) # Attempts to connect to HVPS but without initialisation 1491 if dev.is_hvpsx: 1492 if dev.conf != CONF_UNCONF or list_all: 1493 dict_hvps[dev.name.decode()] = port # add an entry in the dictionary with the name of the 1494 # HVPS and the port 1495 dev.close(zero=False) 1496 1497 return dict_hvps 1498 1499 1500def connect_to_hvps(unconf=False): 1501 """Scan for connected hvps-x and connects to it if a single hvps-x is found. User can choose to which hvps-x to 1502 connect if more than one is detected. 1503 :param unconf: True/False. If true will connect to an unconfigured hvps-x. If False (default) only connects to a 1504 configured device 1505 :return: a HVPS object (or None if could not connect to a valid hvps-x 1506 If more than 1 hvps-x is connected to the computed, a list of names is displayed and the user must choose which one 1507 they want to connect to""" 1508 cont = False # flag to decide whether to continue the program or close the COM port 1509 dev = None 1510 1511 ports = list_hvps(list_all=unconf) 1512 keys = list(ports.keys()) 1513 if len(ports) == 0: 1514 print("No HVPS connected to the device. Terminating program") 1515 exit() 1516 elif len(ports) == 1: 1517 dev = HVPS(ports[keys[0]]) # connects to the only available HVPS 1518 else: 1519 print("List of connected HVPS:") 1520 for i in range(len(ports)): 1521 print(str(i) + ": " + keys[i]) 1522 print("Enter the number of the board you want to connect to") 1523 connect_to = 0 1524 try: 1525 connect_to = int(input()) 1526 except ValueError: 1527 print("Invalid entry. Terminating program") 1528 exit() 1529 if connect_to >= len(ports): 1530 print("Invalid entry. Terminating program") 1531 exit() 1532 else: 1533 dev = HVPS(ports[keys[connect_to]]) 1534 error = dev.err 1535 if error & ERR_PORT: 1536 print("Cannot open COM port. Probably busy with another process. Terminating program") 1537 if error & ERR_COM: 1538 print("Cannot communicate with hvps-x. Has firmware been flashed?. Terminating program") 1539 if error & ERR_TYPE: 1540 print("Device connected this port is not recognised. Terminating program") 1541 if error & ERR_CONF: 1542 print("This hvps-x is not configured. Please configure unit before using it. " 1543 "Terminating program.") 1544 cont = True 1545 elif error & ERR_FIRM: 1546 print("Warning: your hvps-x library is not optimal for the firmware or the firmware is not optimal for your " 1547 "hardware. Refer to the website for a compatibility table") 1548 cont = True 1549 if error == 0: 1550 cont = True 1551 if cont: 1552 return dev 1553 else: 1554 return None 1555 1556 1557def configure(dev): 1558 """Script to use to for the initial configuration (or re-configuration) of an hvps-x. Follow prompt on the console. 1559 :param dev: an HVPS object 1560 :return: nothing 1561 """ 1562 if dev != -1: 1563 config = configparser.RawConfigParser() 1564 config.read('config.ini') 1565 1566 if dev.conf != CONF_UNCONF: 1567 answer = input("It appears this hvps-x is already configured. (C)ontinue with configuration or (A)bort?: ") 1568 if answer == 'a' or answer == 'A': 1569 print("exiting configuration") 1570 dev.close() 1571 exit(-1) 1572 dev.s_conf(bipolar=False) # This is now a constant as there is no bipolar configuration 1573 answer = input("Enter the version of your hvps-x PCB (hardware version), as printed on the PCB " 1574 "(format: X.Y; e.g. 1.2): ") 1575 hw_list = answer.split('.') 1576 hw = int(hw_list[0]) + int(hw_list[1]) / 10 1577 if len(hw_list) != 2: 1578 print("Error, the format of the hardware string must be X.Y, e.g. 1.2. Exiting") 1579 exit(0) 1580 dev.set_hardware_version(int(hw_list[0]), int(hw_list[1])) 1581 print("The current name of the hvps-x is " + dev.q_name().decode("utf-8")) 1582 answer = input("Do you want to set/change the name of this hvps-x? Y/N?: ") 1583 if answer == 'y' or answer == 'Y': 1584 name = input("Enter hvps-x name (12 char max): ") 1585 if len(name) > 12: 1586 print("Name too long. Ignoring name change") 1587 else: 1588 dev.s_name(name) 1589 else: 1590 print("Leaving name unchanged") 1591 print("\nThe current maximal voltage rating of this HVPS is " + str(dev.q_vmax()) + " V") 1592 answer = input("Do you want to set the maximal voltage of the HVPS? Y/N: ") 1593 if answer == 'y' or answer == 'Y': 1594 answer = input("Enter the maximal voltage of the hvps-x in Volt. It must match the voltage rating of the " 1595 "Emco DC/DC converter: ") 1596 print("Setting Vmax to " + answer + "V") 1597 dev.s_vmax(int(answer)) 1598 else: 1599 print("Leaving Vmax unchanged") 1600 vmax = dev.q_vmax() 1601 conf_section = 'hvps-x-' + str(vmax) 1602 if config.has_section(conf_section): 1603 print("Default values for this voltage found in configuration file\n") 1604 answer = input("Do you want to replace the voltage calibration values stored in the hvps-x by the one from the config file " 1605 "Y/N (choose Y if configuring a new hvps-x): ") 1606 if answer == 'Y' or answer == 'y': 1607 c0p = config.getfloat(conf_section, 'C0P') 1608 c1p = config.getfloat(conf_section, 'C1P') 1609 c2p = config.getfloat(conf_section, 'C2P') 1610 c0o = config.getfloat(conf_section, 'C0O') 1611 c1o = config.getfloat(conf_section, 'C1O') 1612 c2o = config.getfloat(conf_section, 'C2O') 1613 1614 dev.s_cal(c0p, CAL_C0P) 1615 dev.s_cal(c1p, CAL_C1P) 1616 dev.s_cal(c2p, CAL_C2P) 1617 dev.s_cal(c0o, CAL_C0O) 1618 dev.s_cal(c1o, CAL_C1O) 1619 dev.s_cal(c2o, CAL_C2O) 1620 print("Voltage calibration values set...\n") 1621 answer = input( 1622 "Reset PID values to their default values? Y/N (choose Y when configuring a board for the first " 1623 "time): ") 1624 if answer == 'Y' or answer == 'y': 1625 kpp = config.getfloat(conf_section, 'KPP') 1626 kip = config.getfloat(conf_section, 'KIP') 1627 kdp = config.getfloat(conf_section, 'KDP') 1628 1629 dev.s_pid(kpp, PID_KPP) 1630 dev.s_pid(kip, PID_KIP) 1631 dev.s_pid(kdp, PID_KDP) 1632 print("PID values set...\n") 1633 1634 if hw >= 1.4: 1635 answer = input("Do you want to set the output current values? Y/N (if No, then default values will " 1636 "be used): ") 1637 if answer == 'Y' or answer == 'y': 1638 inorm = input("Normal current mode: Enter the output current in uA (e.g. 120): ") 1639 ihigh = input("High current mode: Enter the output current in uA (e.g. 120): ") 1640 inorm = 1e-6 * float(inorm) 1641 ihigh = 1e-6 * float(ihigh) 1642 else: 1643 inorm = config.getfloat(conf_section, 'INORM') 1644 ihigh = config.getfloat(conf_section, 'IHIGH') 1645 dev.s_current(inorm, ihigh) 1646 print("Current values set...\n") 1647 curr_sel = input("Position of the output current selection jumper:\n1: 3V3 (Norm)\n2: 5V (High)?\n" 1648 "Choose 1/2: ") 1649 if curr_sel == '2': 1650 curr_sel = 1 1651 else: 1652 curr_sel = 0 1653 dev.s_current_selection(curr_sel) 1654 print("Current selection jumper position set...\n") 1655 1656 print("hvps-x configured. It is recommended to perform a calibration of the voltage readout circuit!") 1657 print("Saving information to hvps-x memory. Backup-settings and default settings") 1658 dev.s_settings(SETTINGS_BACKUP) 1659 dev.save() 1660 dev.s_settings(SETTINGS_CURRENT) 1661 dev.save() 1662 else: 1663 print("Cannot find a section in the config file for a {0} V hvps-x. You need to enter values for voltage " 1664 "calibration and PID manually ".format(vmax)) 1665 dev.save() # saves previous parameters anyway 1666 1667 1668def check_functionality(dev): 1669 """This script can be used after configuration to test the hvps-x functionality. Follow prompt on the console 1670 1671 :param dev: an HVPS object 1672 :return: nothing 1673 """ 1674 1675 print("Important:\n" 1676 "This function is experimental and the threshold values are based on a limited number of assembled boards." 1677 "In particular:\n" 1678 "- If the test fails with a value very close to the threshold value, then your board is most likely fine.\n" 1679 "- This version of the script must be used with the bill of material revision 1. If you assembled your" 1680 "board with the original bill of material, this script will not return the correct output. You can use the" 1681 "script included with the library v1.0. Otherwise the website describe how the high-voltage functionality " 1682 "can be manually tested.\n" 1683 "- If unsure, contact Peta-pico-Voltron with test results (be sure to specify hardware version of PCB).\n\n") 1684 if not dev.q_kill(): 1685 input("Place HV safety switch (S1) on position 0 and press any key.") 1686 if not dev.q_kill(): 1687 input("Switch S1 appears not to be working. Check Switch S1 functionality and restart the test") 1688 dev.close() 1689 exit() 1690 print("During the course of this test, a moderate (~20% of full scale) voltage will be applied to the output of\n " 1691 "the hvps-x. Make sure that it nothing is connected to the output and that you are not touching the\n " 1692 "instrument\n") 1693 print("Voltage set point 0V. Output off") 1694 dev.s_vset(0, DCDC_BOTH) 1695 dev.s_sw_mode(SWMODE_OFF) 1696 dev.s_v_mode(VMODE_O) 1697 dev.s_sw_src(SWSRC_TMR) 1698 1699 print("---Testing HV enable switch (S1) ---") 1700 input("Place HV enable switch (S1) on position 1 and press any key") 1701 if dev.q_kill(): 1702 print("S1 switch still reads off state. Check functionality of switch S1.\n Test FAILED. Exiting script...") 1703 dev.close() 1704 exit() 1705 else: 1706 print("***PASS\n") 1707 1708 print("---Testing Voltage monitoring at set point 0 ---") 1709 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1710 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1711 if vnow_p < 25: 1712 print("This is within the tolerance range V < 25. Continuing...") 1713 1714 else: 1715 print("This is outside of the tolerance range V < 25. Something appears to be wrong with the positive DCDC " 1716 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1717 dev.close() 1718 exit() 1719 1720 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1721 if vnow_o < 25: 1722 print("This is within the tolerance range V < 25. Continuing...") 1723 else: 1724 print("This is outside of the tolerance range V < 25. Something appears to be wrong with the output " 1725 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1726 dev.close() 1727 exit() 1728 print("***PASS\n") 1729 1730 print("---Testing Voltage monitoring at set point 20% FS Output off ---") 1731 print("Applying 20% PWM value") 1732 dev.s_vset(int(0.2 * dev.vmax), DCDC_POS) 1733 sleep(0.2) 1734 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1735 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1736 if vnow_p > 900: 1737 print("This is within the tolerance range V > 900. Continuing...") 1738 else: 1739 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the positive DCDC " 1740 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1741 dev.close() 1742 exit() 1743 1744 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1745 if vnow_o < 100: 1746 print("This is within the tolerance range V < 100. Continuing...") 1747 else: 1748 print("This is outside of the tolerance range V < 100. Something appears to be wrong with the output " 1749 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1750 dev.close() 1751 exit() 1752 print("***PASS\n") 1753 1754 print("---Testing Voltage monitoring at set point 20% FS Output LOW ---") 1755 print("Output LOW") 1756 dev.s_sw_mode(SWMODE_LOW) 1757 sleep(0.2) 1758 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1759 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1760 if vnow_p > 900: 1761 print("This is within the tolerance range V > 900. Continuing...") 1762 else: 1763 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the positive DCDC " 1764 "voltage monitoring circuit.\n Test Failed. Exiting script...") 1765 dev.close() 1766 exit() 1767 1768 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1769 if vnow_o < 25: 1770 print("This is within the tolerance range V < 25. Continuing...") 1771 else: 1772 print("This is outside of the tolerance range V < 25. Something appears to be wrong with the output " 1773 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1774 dev.close() 1775 exit() 1776 print("***PASS\n") 1777 1778 print("---Testing Voltage monitoring at set point 20% FS Output HIGH ---") 1779 print("Output HIGH") 1780 dev.s_sw_mode(SWMODE_HIGH) 1781 sleep(0.2) 1782 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1783 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1784 if vnow_p > 900: 1785 print("This is within the tolerance range V > 900. Continuing...") 1786 else: 1787 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the positive DCDC " 1788 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1789 dev.close() 1790 exit() 1791 1792 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1793 if vnow_o > 900: 1794 print("This is within the tolerance range V > 900. Continuing...") 1795 else: 1796 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the output " 1797 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1798 dev.close() 1799 exit() 1800 print("***PASS\n") 1801 print("***hvps-x passes all tests***") 1802 1803 dev.s_vset(0, DCDC_BOTH) 1804 dev.close() 1805 1806 1807def main(): 1808 dev = connect_to_hvps(unconf=True) 1809 if dev: 1810 print("Name: {0}".format(dev.q_name())) 1811 print("Vmax: {0} V".format(dev.vmax)) 1812 print("\n Your choices:") 1813 print("[1] Initial configuration (to be performed after assembling the low-voltage components)") 1814 print("[2] Basic functionality test (to be performed after assembling the high-voltage components)") 1815 print("[q] quit\n") 1816 answer = input("Your choice: ") 1817 if answer == '1': 1818 configure(dev) 1819 elif answer == '2': 1820 check_functionality(dev) 1821 dev.close(zero=False) 1822 1823def constrain(val, min_val, max_val): 1824 """A simple implementation to constrain a value between two boundaries""" 1825 return min(max_val, max(min_val, val)) 1826 1827if __name__ == "__main__": # if the library is executed as the main program 1828 main()
245class HVPS: 246 """ Class to control a petapicovoltron hvps-x 247 248 The class implements the low-level functions to interface the hvps-x firmware 249 for easy communication with the hvps-x.""" 250 251 # ====Communication functions and class constructor==== 252 def __init__(self, port, init=True): 253 """ Initialises a HVPS object: dev = HVPS('/path/to/serial/port'). 254 255 :param port: COM port to which the HVPS is connected. 256 :param init: if True (default) initialises the hvps-x. Setting to false can be useful if the intended use is 257 to check whether the device connected to the com port is a hvps-x""" 258 self.name = '' 259 self.vmax = 0 260 self.err = 0 261 self.ser = serial.Serial() # the serial connection to the HVPS 262 self.lookup_adc_p = [] # lookup tables for linear interpolation 263 self.lookup_adc_o = [] 264 self.lookup_v_out = [] 265 self.cp = [] # calibration coefficients. 266 self.co = [] 267 self.calmeth_p = -1 268 self.calmeth_n = -1 269 self.calmeth_o = -1 270 271 self.firmware = 0 272 self.hardware = 0 273 self.conf = 0 # configuration of the hvps-x 274 self.is_hvpsx = 0 275 276 try: 277 self.ser = serial.Serial(port, 115200, timeout=20) 278 except serial.SerialException: 279 self.err = self.err | ERR_PORT 280 if DEBUG: 281 print("Cannot connect to serial port. Probably busy.") 282 else: 283 self.ser.reset_input_buffer() 284 if DEBUG: 285 text = "connecting to " + port 286 print(text) 287 print("Serial port open") 288 z = self._send_receive_4bytes(SERIAL_QCONF) 289 if z == 0: # no response: the device connected doesn't talk using this protocol 290 self.err = self.err | ERR_COM 291 if DEBUG: 292 print("Communication error: the connected device doesn't reply to commands") 293 z_hvps = z & 0xFFFF 294 z_conf = (z & 0xFFFF0000) >> 16 295 if z_hvps == HVPSX_PID: 296 self.is_hvpsx = 1 297 if z_conf != CONF_UNCONF: 298 self.conf = z_conf 299 else: 300 self.err = self.err | ERR_CONF # the hvps-x is not configured 301 if DEBUG: 302 print("The hvps is not configured") 303 z = self._send_4bytes_receive_nbytes(SERIAL_QNAME, param1=0, param2=0, fstring='<12s') 304 z = z[0].split(b'\x00', 1)[0] 305 self.name = z 306 else: 307 self.err = self.err | ERR_TYPE # The connected device replied with Err! but is not a hvps-x 308 if DEBUG: 309 print("Type error: the device replies to commands, but is not a hvps-x.") 310 if init and not self.err: 311 self.check_version_compatibility() 312 self.q_vmax() 313 314 def close(self, zero=True): # closes connection with the HVPS 315 """Closes the connection to the HVPS. Sets voltage to 0 if board was connected 316 317 Input (optional): zero. If True, will set the voltage to 0 and the optocouplers to off before closing the 318 communication. If False, the hvps-x is left in its current state""" 319 320 if self.ser.is_open: 321 if not (self.err & ERR_COM): # if connected board is not running the firmware, do not send command 322 if zero: 323 self.s_sw_mode(SWMODE_OFF) # for hvps-x, this will disable both optocouplers. 324 self.s_vset(0, DCDC_BOTH) # set the voltage to 0 as a safety measure 325 self.ser.close() 326 327 if DEBUG: 328 print("Serial port closed") 329 330 def _read_4bytes_uint(self): 331 ans = self.ser.read(4) 332 z = int.from_bytes(ans, byteorder='little') 333 return z 334 335 def _read_4bytes_int16(self): # Read 4 bytes with a 16-bit signed int in bytes 0 and 1 (and ignore bytes 2,3) 336 ans1 = self.ser.read(2) 337 self.ser.read(2) # discard the last 2 bytes 338 z = int.from_bytes(ans1, byteorder='little', signed=True) 339 return z 340 341 def _read_4bytes_float(self): 342 ans = self.ser.read(4) 343 z = struct.unpack('<f', ans) # little endian: string starts with the low index of the array, which 344 # represents the low bits of the float 345 return z[0] 346 347 def _send_receive_4bytes(self, cmd, param1=0, param2=0, typ='uint'): 348 param2 = round(param2) 349 if param2 < 0: 350 param2 = param2 & 0xFFFF # represent is as two's complement 351 cmd = cmd + (param1 << 8) + (param2 << 16) 352 cmd_b = cmd.to_bytes(4, 'little') 353 self.ser.write(cmd_b) 354 if typ == 'uint': 355 z = self._read_4bytes_uint() 356 elif typ == 'int16': 357 z = self._read_4bytes_int16() 358 else: 359 z = self._read_4bytes_float() 360 return z 361 362 def _send_4bytes_receive_nbytes(self, cmd, param1=0, param2=0, fstring='<2B'): 363 n = struct.calcsize(fstring) 364 param2 = round(param2) 365 if param2 < 0: 366 param2 = param2 & 0xFFFF # represent it as two's complement 367 cmd = cmd + (param1 << 8) + (param2 << 16) 368 cmd_b = cmd.to_bytes(4, 'little') 369 self.ser.write(cmd_b) 370 x = self.ser.read(n) 371 z = struct.unpack(fstring, x) 372 return z # returns unpacked tuples 373 374 def _send_nbytes_receive_4bytes(self, cmd, packet, typ='uint'): 375 packet = bytes([cmd]) + packet 376 self.ser.write(packet) 377 if typ == 'uint': 378 z = self._read_4bytes_uint() 379 elif typ == 'int16': 380 z = self._read_4bytes_int16() 381 else: 382 z = self._read_4bytes_float() 383 return z 384 385 def _download_voltage_calibration_data(self): 386 # only download if required. 387 if self.calmeth_p == -1: # calibration method not yet fetched from HVPS 388 self.q_calibration_method(which=VNOW_POS) 389 if self.calmeth_n == -1: # calibration method not yet fetched from HVPS 390 self.q_calibration_method(which=VNOW_NEG) 391 if self.calmeth_o == -1: # calibration method not yet fetched from HVPS 392 self.q_calibration_method(which=VNOW_OUT) 393 394 if (self.calmeth_o == CALMETH_LOOKUP or self.calmeth_p == CALMETH_LOOKUP) and not self.lookup_v_out: 395 self.lookup_v_out = self.q_lookup(0) 396 if self.calmeth_p == CALMETH_LOOKUP and not self.lookup_adc_p: 397 self.lookup_adc_p = self.q_lookup(1) 398 if self.calmeth_o == CALMETH_LOOKUP and not self.lookup_adc_o: 399 self.lookup_adc_o = self.q_lookup(2) 400 401 if self.calmeth_p == CALMETH_POLYNOMIAL and not self.cp: # if we don't know what are the calibration values 402 self.cp.append(self.q_cal(CAL_C0P)) 403 self.cp.append(self.q_cal(CAL_C1P)) 404 self.cp.append(self.q_cal(CAL_C2P)) 405 406 if self.calmeth_o == CALMETH_POLYNOMIAL and not self.co: # if we don't know what are the calibration values 407 self.co.append(self.q_cal(CAL_C0O)) 408 self.co.append(self.q_cal(CAL_C1O)) 409 self.co.append(self.q_cal(CAL_C2O)) 410 411 def _convert_raw_to_voltage(self, vraw, which=VNOW_POS): 412 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 413 which = VNOW_POS 414 # Download calibration values the first time the function is used 415 self._download_voltage_calibration_data() 416 if which == VNOW_POS: 417 if self.calmeth_p == CALMETH_LOOKUP: 418 vcal = np.interp(vraw, self.lookup_adc_p, self.lookup_v_out, left=None, right=None, period=None) 419 else: 420 vcal = self.cp[0] + self.cp[1] * vraw + self.cp[2] * np.square(vraw) 421 elif which == VNOW_NEG: 422 vcal = 0 423 else: 424 if self.calmeth_o == CALMETH_LOOKUP: 425 vcal = np.interp(vraw, self.lookup_adc_o, self.lookup_v_out, left=None, right=None, period=None) 426 else: 427 vcal = self.co[0] + self.co[1] * vraw + self.co[2] * np.square(vraw) 428 return vcal 429 430 def _convert_voltage_to_raw(self, v, which): 431 # this function is not needed and can be removed. 432 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 433 which = VNOW_POS 434 # Download calibration values the first time the function is used 435 self._download_voltage_calibration_data() 436 437 if which == VNOW_POS: 438 if self.calmeth_p == CALMETH_LOOKUP: 439 vraw = np.interp(v, self.lookup_v_out, self.lookup_adc_p, left=None, right=None, period=None) 440 elif self.cp[2] == 0: # if polynomial but linear 441 vraw = (v - self.cp[0]) / self.cp[1] 442 else: # polynomial quadratic 443 vraw = (-self.cp[1] + np.sqrt(np.square(self.cp[1]) - 4 * self.cp[2] * (self.cp[0]-v)))/(2 * self.cp[2]) 444 445 elif which == VNOW_NEG: 446 vraw = 0 # bipolar option not implemented yet 447 else: 448 if self.calmeth_o == CALMETH_LOOKUP: 449 vraw = np.interp(v, self.lookup_v_out, self.lookup_adc_o, left=None, right=None, period=None) 450 elif self.co[2] == 0: # if polynomial but linear 451 vraw = (v - self.co[0]) / self.co[1] 452 else: # polynomial quadratic 453 vraw = (-self.co[1] + np.sqrt(np.square(self.co[1]) - 4 * self.co[2] * (self.co[0]-v)))/(2 * self.co[2]) 454 return vraw 455 456 def is_bipolar(self): # return true if connected device is hvpsx and bipolar unit 457 """ Returns true if the hvps-x configuration is bipolar.""" 458 if self.conf & 1: 459 return True 460 else: 461 return False 462 463 # ====Commands related to voltage and frequency==== 464 def s_vset(self, x, polarity=DCDC_POS): # sets the output voltage 465 """Sets the output voltage of the HVPS. The new parameter remains valid until a new call to this command, or 466 when the HVPS is powered off. Using the 467 save() command enables to save this parameter in memory\n 468 Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is 469 regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the 470 output to the set point; see s_vmode() command. 471 :param x: voltage set point in volt (int) 472 :param polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). 473 This parameter can be ignored given that only unipolar configuration is currently supported. 474 :return: voltage set point accepted by hvps-x 475 """ 476 x = constrain(abs(x), 0, self.vmax) 477 x = int(x) 478 if polarity != DCDC_POS and polarity != DCDC_NEG and polarity != DCDC_BOTH: 479 polarity = DCDC_POS 480 z = self._send_receive_4bytes(SERIAL_SVSET, param1=polarity, param2=x) 481 if x == 0: 482 z = 0 # avoids calibration issue near 0 that would lead to a negative number which is saturated to 0 in 483 # the STM32 (as the stored raw value is uint), leading to a possible large offset error returned value of a 484 # few 10s of volts when back calculated in volts 485 if DEBUG: 486 y = "s_vset(" + str(x) + ") -> " + str(z) 487 print(y) 488 return z 489 490 def q_vset(self, polarity=DCDC_POS): # queries the voltage setpoint 491 """Queries the voltage set point. The returned value is in volts. 492 :param polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for 493 unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported. 494 :return: Voltage set point in volts 495 """ 496 if polarity != DCDC_POS and polarity != DCDC_NEG: 497 polarity = DCDC_POS 498 z = self._send_receive_4bytes(SERIAL_QVSET, param1=polarity) 499 if DEBUG: 500 y = "q_vset -> " + str(z) 501 print(y) 502 503 return z 504 505 def s_vset_raw(self, x, polarity=DCDC_POS): # sets the output voltage 506 """Sets the raw output voltage of the HVPS (0-4095). The new parameter remains valid until a new call to this 507 command, or when the HVPS is powered off. Using the 508 save() command enables to save this parameter in memory\n 509 Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is 510 regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the 511 output to the set point; see s_vmode() command. 512 :param x: raw voltage set point (0-4095) 513 :param polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). 514 This parameter can be ignored given that only unipolar configuration is currently supported. 515 :return: voltage set point accepted by hvps-x 516 """ 517 x = constrain(x, 0, 4095) 518 x = int(x) 519 if polarity != DCDC_POS and polarity != DCDC_NEG and polarity != DCDC_BOTH: 520 polarity = DCDC_POS 521 z = self._send_receive_4bytes(SERIAL_SVSETRAW, param1=polarity, param2=x) 522 if x == 0: 523 z = 0 # avoids calibration issue near 0 that would lead to a negative number which is saturated to 0 in 524 # the STM32 (as the stored raw value is uint), leading to a possible large offset error returned value of a 525 # few 10s of volts when back calculated in volts 526 if DEBUG: 527 y = "s_vset_raw(" + str(x) + ") -> " + str(z) 528 print(y) 529 return z 530 531 def q_vset_raw(self, polarity=DCDC_POS): # queries the voltage setpoint 532 """Queries the raw voltage set point. The returned value is between 0 and 4095. 533 534 :param polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for 535 unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported. 536 :return: Voltage raw set point (0-4095)) 537 """ 538 if polarity != DCDC_POS and polarity != DCDC_NEG: 539 polarity = DCDC_POS 540 z = self._send_receive_4bytes(SERIAL_QVSETRAW, param1=polarity) 541 if DEBUG: 542 y = "q_vset_raw -> " + str(z) 543 print(y) 544 return z 545 546 def q_vnow(self, which=VNOW_POS): # queries the voltage output 547 """Queries the feedback voltage of the HVPS. 548 :param which: which output voltage to read {VNOW_POS (default), VNOW_NEG, VNOW_OUT}. Voltage at the output of the 549 positive DCDC converter, negative DCDC converter, or output of the HVPS 550 :return: voltage value in volts 551 """ 552 553 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 554 which = VNOW_POS 555 z = self._send_4bytes_receive_nbytes(SERIAL_QVNOW, param1=which, fstring='<2h') 556 z_out = z[1] 557 z_main = z[0] 558 if DEBUG: 559 y = "q_vnow -> " + str(z_main) + " / " + str(z_out) 560 print(y) 561 return z_main 562 563 def q_vnow_raw(self, which=VNOW_POS): # queries a raw value of the voltage output 564 """Queries the current feedback voltage of the HVPS. The returned value is a raw 12bit ADC value. This avoids 565 running slow floating point calculations on the 566 microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to 567 execute than q_vnow(). Useful for streaming voltage values 568 :param which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the 569 positive DCDC converter, negative DCDC converter, or output of the HVPS. 570 :return: a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT 571 (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is 572 a 12 bit value that can be converted to voltage using the unit's calibration values (see q_vnow_fast() for a 573 way to do this automatically""" 574 575 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 576 which = VNOW_POS 577 z = self._send_receive_4bytes(SERIAL_QVNOWRAW, param1=which, typ='uint') 578 z_out = z & 0xFFFF 579 z_main = (z & 0xFFFF0000) >> 16 580 if DEBUG: 581 y = "q_vnow_raw -> " + str(z_main) + " , " + str(z_out) 582 print(y) 583 return z_main, z_out 584 585 def q_vnow_fast(self, which=VNOW_POS): # queries the voltage output 586 """Queries the current feedback voltage of the HVPS in a raw format and convert it to a calibrated voltage 587 This avoids running slow floating point calculations on the 588 microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to 589 execute than q_vnow(). Useful for streaming voltage values. This method is similar to q_vnow(), except that 590 the conversion from a raw value to a calibrated value is done 591 on the host rather than on the microcontroller. It can take up to 300us to convert a value on the MUC 592 (it depends on the method used (linear, quadratic, lookup table) 593 :param which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the 594 positive DCDC converter, negative DCDC converter, or output of the HVPS. 595 :return: a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT 596 (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is 597 a 12 bit value that can be converted to voltage using the unit's calibration values The returned values 598 are calibrated voltages in Volt""" 599 600 z_main, z_out = self.q_vnow_raw(which=which) 601 v_main = self._convert_raw_to_voltage(z_main, which=which) 602 v_out = self._convert_raw_to_voltage(z_out, which=VNOW_OUT) 603 604 return v_main, v_out 605 606 def s_f(self, x): # sets the frequency 607 """Sets the frequency of the signal when the HVPS is in switching or waveform mode (SWMODE_SW / SWMODE_SW).\n 608 The value returned is the new frequency, taking quantification into account. 609 :param x: frequency in Hz between 0.001 and 1000. In waveform mode, 0.16 Hz <= f <=100 Hz 610 :return: frequency accepted by hvps-x in Hz 611 """ 612 x = constrain(x, 0.001, 1000.0) 613 614 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 615 # Ex: 10.0: 0x41 0x20 0x00 0x00. 616 # 0 10000010 01000000000000000000000 617 # S=0 Positive 618 # Exp=130-127 (offset)=2^3=8 619 # Fraction=1+2^-2=1.25 620 # number=+1.25*8=10 621 f_byte = struct.pack('<f', x) 622 z = self._send_nbytes_receive_4bytes(SERIAL_SF, f_byte, typ='float') 623 624 if DEBUG: 625 y = "s_f(" + str(x) + ") -> " + str(z) 626 print(y) 627 return z 628 629 def q_f(self): # queries the frequency 630 """Queries the frequency. The returned value is in Hz.""" 631 z = self._send_receive_4bytes(SERIAL_QF, param1=0, param2=0, typ='float') 632 633 if DEBUG: 634 y = "q_f -> " + str(z) 635 print(y) 636 637 return z 638 639 def s_current(self, xnorm, xhigh): # sets the current limit setting 640 """Sets the current output in both current settings (normal and high). This value is used to calculate the PID 641 coefficients for the waveform. Values can be obtained by measuring the charging rate when charging a capacitor 642 of known value. This is a configuration parameter that must be set according to the capabilities of the hvps-x. 643 It is not a user-settable output current. 644 :param xnorm: current in A between 50E-6 and 500E-6 in normal output current config. 645 :param xhigh: current in A between 50E-6 and 500E-6 in high output current config. 646 :return: SERIAL_OK or SERIAL_ERROR 647 """ 648 xnorm = constrain(xnorm, 50E-6, 500E-6) 649 xhigh= constrain(xhigh, 50E-6, 500E-6) 650 f_byte = struct.pack('<2f', xnorm, xhigh) 651 z = self._send_nbytes_receive_4bytes(SERIAL_SCURR, f_byte, typ='uint') 652 653 if DEBUG: 654 y = "s_current({},{}) -> {}".format(xnorm, xhigh, z) 655 print(y) 656 return z 657 658 def q_current(self): # queries the current limit setting 659 """Queries the current limit setting. The returned value is in A. 660 661 :return: A tuple of two current values corresponding to the output current settings in normal and high current 662 modes.""" 663 664 z = self._send_4bytes_receive_nbytes(SERIAL_QCURR, param1=0, param2=0, fstring='<2f') 665 666 if DEBUG: 667 y = "q_curr() -> " + str(z) 668 print(y) 669 return z 670 671 def s_current_selection(self, x): 672 """Sets the position of the current selection jumper 673 :param x: Position of the output current selection jumper. Either J2_3V3 (normal) or J2_5V depending on the 674 jumper position. 3V3 and 5V are written on the silkscreen and respectively correspond to normal and high current 675 modes. After changing the position of the jumper, this command should be run to reflect the change. The new 676 setting will be kept until the unit is power-cycled. To make the selection permanent, use the save() command 677 after the s_current_selection(x) command. 678 :return: Accepted current selection jumper position (J2_3V3 or J2_5V) 679 """ 680 x = int(np.abs(x)) 681 if x > J2_5V: 682 x = J2_5V 683 z = self._send_receive_4bytes(SERIAL_SCURRSEL, param1=x, param2=0, typ='uint') 684 685 if DEBUG: 686 print("s_current_selection({}) --> {}".format(x, z)) 687 return z 688 689 def q_current_selection(self): 690 """Queries the position of the current selection jumper 691 :return: Current selection (J2_3V3 or J2_5V) 692 """ 693 z = self._send_receive_4bytes(SERIAL_QCURRSEL, param1=0, param2=0, typ='uint') 694 695 if DEBUG: 696 print("q_current_selection() --> {}".format(z)) 697 return z 698 699 def s_duty(self, x): # sets the duty cycle of the switching signal 700 """Sets the duty cycle of the switching signal 701 :param x: the duty cycle (float in the range 0-1) 702 :return: the current duty cycle (float between 0 and 1) 703 """ 704 duty = int(x * 1000) # hvps-x is coding duty cycle on a 0-1000 scale 705 706 z = self._send_receive_4bytes(SERIAL_SDUTY, param1=0, param2=duty, typ='uint') 707 z = z / 1000 708 if DEBUG: 709 y = "s_duty(" + str(x) + ") -> " + str(z) 710 print(y) 711 return z 712 713 def q_duty(self): # queries the duty cycle of the switching signal 714 """queries the duty cycle of the switching signal 715 :return: the current duty cycle (float between 0 and 1) 716 """ 717 718 z = self._send_receive_4bytes(SERIAL_QDUTY, param1=0, param2=0, typ='uint') 719 z = float(z) / 1000.0 720 if DEBUG: 721 y = "q_duty() -> " + str(z) 722 print(y) 723 return z 724 725 # ===Commands to change the voltage control and switching behaviour (mode and source)==== 726 def s_sw_mode(self, x): # sets the switching mode 727 """Sets the switching mode of the hvps-x. \n 728 SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. 729 This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV 730 DC/DC converter. 731 SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and 732 SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with 733 the specified duty cycle. 734 SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory 735 The program tab "Waveform" can be used to set the parameters of the waveform. 736 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 737 save() command enables to save this parameter in memory 738 :param x: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM 739 :return: Switching mode set by the HVPS 740 """ 741 742 if x > SWMODE_LOW: 743 x = SWMODE_OFF 744 745 z = self._send_receive_4bytes(SERIAL_SSWMODE, param1=x, param2=0, typ='uint') 746 if DEBUG: 747 y = "s_sw_mode(" + str(x) + ") -> " + str(z) 748 print(y) 749 return z 750 751 def q_sw_mode(self): # queries the switching mode 752 """queries the switching mode of the hvps-x. \n 753 SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. 754 This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV 755 DC/DC converter. 756 SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and 757 SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with 758 the specified duty cycle. 759 SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory 760 The program tab "Waveform" can be used to set the parameters of the waveform. 761 :return: Switching mode set by the HVPS: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM 762 """ 763 z = self._send_receive_4bytes(SERIAL_QSWMODE, param1=0, param2=0, typ='uint') 764 if DEBUG: 765 y = "q_sw_mode -> " + str(z) 766 print(y) 767 return z 768 769 def s_sw_src(self, x): # sets the switching source 770 """Sets the source of the switching signal. 771 772 Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard 773 switching (from internal clock of the board), or SWSRC_BTTN for the push button.\n 774 Using the save() command enables to save this parameter in memory 775 :param x: SWSRC_TMR, or SWSRC_BTTN 776 :return: SWSRC_TMR, or SWSRC_BTTN 777 """ 778 if x > SWSRC_BTTN: 779 x = SWSRC_TMR 780 z = self._send_receive_4bytes(SERIAL_SSWSRC, param1=x, param2=0, typ='uint') 781 if DEBUG: 782 y = "s_sw_src(" + str(x) + ") -> " + str(z) 783 print(y) 784 return z 785 786 def q_sw_src(self): # queries the switching source 787 """queries the source of the switching signal. 788 789 queries the source of the switching signal. Output values are: SWSRC_TMR for onboard 790 switching (from internal clock of the board), or SWSRC_BTTN for the push button.\n 791 Using the save() command enables to save this parameter in memory 792 :return: SWSRC_TMR, or SWSRC_BTTN 793 """ 794 795 z = self._send_receive_4bytes(SERIAL_QSWSRC, param1=0, param2=0, typ='uint') 796 797 if DEBUG: 798 y = "q_sw_src -> " + str(z) 799 print(y) 800 return z 801 802 def s_bttn_cfg(self, x): # sets the configuration of the push button 803 """Defines the behaviour of the push button 804 805 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 806 save() command enables to save this parameter in memory 807 :param x: 2-bit value (0x0 to 0x3). bit 0 - Defines the behaviour of the push button, when the switching source 808 of the HVPS is set to the push button 809 (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour 810 (i.e. the high voltage is turned on as long as the button is pressed), 811 and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time 812 to turn it off).\n 813 bit 1 - State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW 814 :return: the 2-bit button config value 815 """ 816 if x > 3: 817 x = 3 818 z = self._send_receive_4bytes(SERIAL_SBTTNCFG, param1=x, param2=0, typ='uint') 819 if DEBUG: 820 y = "s_bttn_cfg(" + str(x) + ") -> " + str(z) 821 print(y) 822 823 return z 824 825 def q_bttn_cfg(self): # queries the latch mode of the push button 826 """Queries the behaviour of the push button 827 828 :return: the 2-bit button config value. bit 0: Defines the behaviour of the push button, when the switching 829 source of the HVPS is set to the push button (SWSRC_BTTN, c.f. s_sw_src() command above). 830 Values are 0 for a push button behaviour (i.e. the high voltage is turned on as long as the button is pressed), 831 and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time 832 to turn it off).\n 833 bit 1: State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW 834 """ 835 z = self._send_receive_4bytes(SERIAL_QBTTNCFG, param1=0, param2=0, typ='uint') 836 if DEBUG: 837 y = "q_bttn_cfg -> " + str(z) 838 print(y) 839 840 return z 841 842 def q_kill(self): # queries the state of the kill button. Kill=1 means HV is disabled 843 """Queries whether HV is disabled (Kill=1) or enabled (kill=0). When kill = 1 there will not be a HV present at 844 the hvps-x output, irrespective of any software setting. 845 :return: 1 if the Switch S1 on the board is set to 0 (HV output is killed), 0 otherwise. 846 """ 847 z = self._send_receive_4bytes(SERIAL_QKILL, param1=0, param2=0, typ='uint') 848 849 if DEBUG: 850 y = "q_kill -> " + str(z) 851 print(y) 852 return z 853 854 def s_v_mode(self, x): # sets the voltage control mode 855 """Sets the voltage control mode 856 857 Sets the voltage control mode (i.e. how is the value of the output voltage controlled):\n 858 VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).\n 859 VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).\n 860 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 861 save() command enables to save this parameter in memory\n 862 VMODE_O has an internal safeguard that will decrease the setting if the sensing circuit saturates. However, 863 the voltage can still be slightly higher than the DCDC converter upper limit. User must check that the output 864 voltage remains within the allowed range 865 :param x: VMODE_R, VMODE_O 866 :return: VMODE_R, VMODE_O 867 """ 868 869 if x > VMODE_O: 870 x = VMODE_R 871 872 z = self._send_receive_4bytes(SERIAL_SVMODE, param1=x, param2=0, typ='uint') 873 if DEBUG: 874 y = "s_v_mode(" + str(x) + ") -> " + str(z) 875 print(y) 876 return z 877 878 def q_v_mode(self): # queries the switching source 879 """Queries the voltage control mode 880 881 :return: VMODE_R internal voltage regulator, VMODE_O internal 882 open loop control (on-board regulator disconnected). 883 """ 884 885 z = self._send_receive_4bytes(SERIAL_QVMODE, param1=0, param2=0, typ='uint') 886 887 if DEBUG: 888 y = "q_v_mode -> " + str(z) 889 print(y) 890 return z 891 892 # ====Functions to set configuration parameters==== 893 def s_pid(self, x, pid=PID_KPP): 894 """Sets the gains of the PIDs 895 Use save() command to save the new values to memory 896 :param x: the value (float) of the gain. 897 :param pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, 898 for positive voltage PID, output voltage PID (used for the waveform mode). 899 :return: the gain value 900 """ 901 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 902 # Ex: 10.0: 0x41 0x20 0x00 0x00. 903 # 0 10000010 01000000000000000000000 904 # S=0 Positive 905 # Exp=130-127 (offset)=2^3=8 906 # Fraction=1+2^-2=1.25 907 # number=+1.25*8=10 908 pid_byte = struct.pack('<Bf', pid, x) 909 z = self._send_nbytes_receive_4bytes(SERIAL_SPID, pid_byte, typ='float') 910 911 if DEBUG: 912 y = "s_pid(" + str(x) + "," + str(pid) + ") -> " + str(z) 913 print(y) 914 return z 915 916 def q_pid(self, pid=PID_KPP): # queries the frequency 917 """returns the gains of the PIDs 918 919 :param pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, 920 for positive voltage PID, output voltage PID (used for the waveform mode). 921 :return: the gain value of the chosen parameter 922 """ 923 924 z = self._send_receive_4bytes(SERIAL_QPID, param1=pid, param2=0, typ='float') 925 926 if DEBUG: 927 y = "q_pid(" + str(pid) + ") -> " + str(z) 928 print(y) 929 930 return z 931 932 def s_cal(self, x, cal=CAL_C1P): 933 """Sets the calibration constants of the analogue input signals (conversion to calibrated voltage values) 934 Use save() to commit the setting to memory 935 :param x: the value of the calibration coefficient (float) 936 :param cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive 937 voltage measurement, output voltage measurement 938 :return: the calibration value 939 """ 940 941 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 942 # Ex: 10.0: 0x41 0x20 0x00 0x00. 943 # 0 10000010 01000000000000000000000 944 # S=0 Positive 945 # Exp=130-127 (offset)=2^3=8 946 # Fraction=1+2^-2=1.25 947 # number=+1.25*8=10 948 cal_byte = struct.pack('<Bf', cal, x) 949 z = self._send_nbytes_receive_4bytes(SERIAL_SCAL, cal_byte, typ='float') 950 951 if DEBUG: 952 y = "s_cal(" + str(x) + "," + str(cal) + ") -> " + str(z) 953 print(y) 954 return z 955 956 def q_cal(self, cal=CAL_C1P): # queries the frequency 957 """queries the calibration constants of the analogue input signals (conversion to calibrated voltage values) 958 959 :param cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive 960 voltage measurement, output voltage measurement 961 :return: the calibration value 962 """ 963 964 z = self._send_receive_4bytes(SERIAL_QCAL, param1=cal, param2=0, typ='float') 965 966 if DEBUG: 967 y = "q_cal(" + str(cal) + ") -> " + str(z) 968 print(y) 969 return z 970 971 def s_calibration_method(self, which=VNOW_POS, calmeth=CALMETH_POLYNOMIAL): 972 if not (calmeth == CALMETH_POLYNOMIAL or calmeth == CALMETH_LOOKUP): 973 calmeth = CALMETH_POLYNOMIAL 974 if not (which == VNOW_POS or which == VNOW_OUT or which == VNOW_NEG): 975 which = VNOW_POS 976 z = self._send_receive_4bytes(SERIAL_SCALMETH, param1=which, param2=calmeth, typ='uint') 977 978 if DEBUG: 979 y = "s_calibration_method({0}, {1}) -> {2}".format(which, calmeth, z) 980 print(y) 981 if which == VNOW_POS: 982 self.calmeth_p = z 983 elif which == VNOW_OUT: 984 self.calmeth_o = z 985 else: 986 self.calmeth_n = z 987 988 def q_calibration_method(self, which=VNOW_POS): 989 if not (which == VNOW_POS or which == VNOW_OUT or which == VNOW_NEG): 990 which = VNOW_POS 991 z = self._send_receive_4bytes(SERIAL_QCALMETH, param1=which, param2=0, typ='uint') 992 993 if DEBUG: 994 y = "q_calibration_method({0}) -> {1}".format(which, z) 995 print(y) 996 if which == VNOW_POS: 997 self.calmeth_p = z 998 elif which == VNOW_NEG: 999 self.calmeth_n = z 1000 else: 1001 self.calmeth_o = z 1002 1003 def s_lookup(self, x, n, table=LOOKUP_ADC_P): 1004 if n > 20: 1005 n = 20 1006 n = n << 2 # shift left 2 bits 1007 if table == LOOKUP_ADC_P: 1008 n = n | 0b01 1009 elif table == LOOKUP_ADC_O: 1010 n = n | 0b10 1011 x = int(x) 1012 z = self._send_receive_4bytes(SERIAL_SLKUP, param1=n, param2=x, typ='int16') 1013 if DEBUG: 1014 y = "s_lookup({0},{1},{2}) - > {3}".format(x, n, table, z) 1015 print(y) 1016 1017 def q_lookup(self, which): # which=0: vout, which=1: ADC_p, which=2: ADC_o 1018 format_string = f'<21h' 1019 z = self._send_4bytes_receive_nbytes(SERIAL_QLKUP, param1=which, param2=0, fstring=format_string) 1020 lookup_list = list(z) 1021 if DEBUG: 1022 y = "q_lookup({0}) - > {1}".format(which, lookup_list) 1023 print(y) 1024 return lookup_list 1025 1026 def s_saturation_limit(self, limit): # limit: 0-> reset to default 2% above Vmax. Otherwise:1-4095 1027 if limit < 0: 1028 limit = 0 1029 elif limit > 4095: 1030 limit = 4095 1031 z = self._send_receive_4bytes(SERIAL_SLKUP, param1=0, param2=limit, typ='int16') 1032 if DEBUG: 1033 y = "s_saturation_limit({0}) - > {1}".format(limit, z) 1034 print(y) 1035 return z 1036 1037 # ====User Waveform Functions==== 1038 def s_wfm_pts(self, n=1, m=500, pts=None): 1039 """Sets the data points of the waveform 1040 Use save() to commit the setting to memory 1041 :param n: The loop counter. The frequency of the signal is f = 1 / (n * m * 0.5E-3) 0 < n < 256. If you don't 1042 want to bother with n, set n = 1, and use s_f() after transferring the points to get the desired wfm frequency. 1043 :param m: The number of points in the waveform 0 <m <= 500 1044 :param pts: a list of integer voltage points. The list must be 500 elements long. If m<500, then the end of the list 1045 must be padded with zeros. 1046 :return: SERIAL_OK or SERIAL_ERROR 1047 """ 1048 # parameters are 1) n (byte B) the counter divider, 2) the number of points m, an unsigned short, and 3) m unsigned short pts 1049 if pts is None: 1050 z = SERIAL_ERROR 1051 if DEBUG: 1052 print("s_wfm_pts(): ERROR: no points given") 1053 elif len(pts) == 500 and all(isinstance(item, int) for item in pts): 1054 n=int(n) 1055 if n > 255: 1056 n=255 1057 m=int(m) 1058 format_string = f'<B{WFM_MAX_PTS+1}H' 1059 buffer = struct.pack(format_string, n, m, *pts) # array pts always has WFM_MAX_PTS points, but with zeros as 1060 # padding at the end. m indicated the effective number of data points. This is to always have the same command 1061 # length 1062 z = self._send_nbytes_receive_4bytes(SERIAL_SWFMPTS, buffer) 1063 if DEBUG: 1064 y = "s_wfm_pts({0}, {1}, {2}) - > {3}".format(n, m, buffer, z) 1065 print(y) 1066 else: 1067 z = SERIAL_ERROR 1068 if DEBUG: 1069 print("s_wfm_pts(): ERROR: number of points not equal to 500, or not all integer values") 1070 return z 1071 1072 def q_wfm_pts(self): 1073 """queries the data points of the waveform 1074 :return : n, z with n: the number of points in the waveform and z: the list of waveform points in Volts 1075 """ 1076 format_string = f'<H500H' # 1 16-bit unsigned value for the number of points and 500 unsigned 16-bit values. Not all points are effective points (some may be padding zeroes) hence why n is required. 1077 z = self._send_4bytes_receive_nbytes(SERIAL_QWFMPTS, param1=0, param2=0, fstring=format_string) 1078 z = list(z) 1079 n = z.pop(0) # the first element is the number of points 1080 del z[n:] #remove all elements of the list after the number of points to keep 1081 if DEBUG: 1082 y = "q_wfm_pts() - > {}/{}".format(n, z) 1083 print(y) 1084 return n, z 1085 1086 1087 # Miscellaneous functions 1088 def save(self): # save current HVPS parameters into the memory 1089 """save active HVPS parameters into the memory 1090 1091 This command saves the active parameters as \'current\' settings. Current setting are the settings that are 1092 loaded when power is applied to the hvps-x 1093 1094 :return: SERIAL_OK or SERIAL_ERROR 1095 """ 1096 1097 z = self._send_receive_4bytes(SERIAL_SAVE) 1098 1099 if DEBUG: 1100 y = "save -> " + str(z) 1101 print(y) 1102 return z 1103 1104 def save_memory_to_file(self, settings=SETTINGS_CURRENT): 1105 """Dumps the content of the memory into a JSON file. 1106 1107 Dumps the content of the memory into a file. This is useful to keep a backup of the parameters on file. 1108 Files will be created in the interface folder and have the following format: Name_settings_type_date_time.json\n 1109 json files with settings can be transferred back to the hvps-x with the transfer_settings() method of the HVPS 1110 class, or the higher-level function transfer_file_to_memory() 1111 :param settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_Backup 1112 :return: the name of the created file (it follows a standard naming scheme with the date, the name of the 1113 hvps-x and the setting type. 1114 """ 1115 1116 memory = self.q_mem(settings) 1117 now = datetime.now() # current date and time 1118 date_time = now.strftime("%Y_%m_%d") 1119 if settings == SETTINGS_CURRENT: 1120 st_string = '_cur_st_' 1121 else: 1122 st_string = '_bk_st_' 1123 file_name = self.name.decode("utf-8") + st_string + date_time + '.json' 1124 with open(file_name, "w") as write_file: 1125 json.dump(memory, write_file, indent=4) 1126 1127 return file_name 1128 1129 def s_settings(self, 1130 settings=SETTINGS_CURRENT): # sets the active setting to a particular type (useful before saving) 1131 if not (settings == SETTINGS_CURRENT or settings == SETTINGS_FACTORY or settings == SETTINGS_BACKUP): 1132 settings = SETTINGS_CURRENT 1133 x = self._send_receive_4bytes(SERIAL_SST, param1=settings) 1134 if DEBUG: 1135 print('s_settings({0}) -> {1}'.format(settings, x)) 1136 return x 1137 1138 def load_settings(self, settings=SETTINGS_CURRENT): # Load a setting set from memory to the active settings 1139 """This function loads one of the two set of settings (current or backup) as active settings used by the hvps-x 1140 1141 :param settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_BACKUP 1142 :return: 1143 """ 1144 1145 if not (settings == SETTINGS_CURRENT or settings == SETTINGS_BACKUP): 1146 settings = SETTINGS_CURRENT 1147 x = self._send_receive_4bytes(SERIAL_LST, param1=settings) 1148 if DEBUG: 1149 print('load_settings({0}) -> {1}'.format(settings, x)) 1150 1151 self._initialise_hvpsx() # need to reread all parameters 1152 1153 return x 1154 1155 def transfer_settings(self, dict_settings): 1156 """ transfer a setting dictionary from the computer to the hvps-x memory 1157 :param dict_settings: a dictionary containing the settings values. 1158 :return: SERIAL_OK or SERIAL_ERROR 1159 The dictionary of settings should be read from a file dumped using the function save_memory_to_file(). Together 1160 these two functions make it possible to backup the settings (this includes calibration and PID settings in a 1161 file, and gives the opportunity to restore the settings""" 1162 1163 error = False 1164 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1165 siz = struct.calcsize(memory_string[fw_string]) 1166 buffer = ctypes.create_string_buffer(siz) 1167 memmap = memorymap[fw_string] 1168 counter = 0 1169 for x in memmap: 1170 n = x[0] 1171 key = x[1] 1172 data_format = x[2] 1173 if data_format == 'B' or data_format == 's': 1174 bytelength = 1 1175 elif data_format == 'h' or data_format == 'H': 1176 bytelength = 2 1177 elif data_format == 'f': 1178 bytelength = 4 1179 else: 1180 bytelength = 1 1181 data = dict_settings[key] 1182 if key == 'Name': 1183 data = bytes(data, 'utf-8') 1184 if len(data) > 12: 1185 error = True 1186 if DEBUG: 1187 print("Error: name should not exceed 12 characters") 1188 else: 1189 format_str = '<{0}{1}'.format(n, data_format) 1190 struct.pack_into(format_str, buffer, counter, data) 1191 else: 1192 format_str = '<{0}'.format(data_format) 1193 if n == 1: 1194 struct.pack_into(format_str, buffer, counter, data) 1195 else: 1196 for i in range(n): 1197 try: 1198 struct.pack_into(format_str, buffer, counter + i * bytelength, data[i]) 1199 except IndexError: 1200 error = True 1201 if DEBUG: 1202 print('setting dictionary does not fit the expected format') 1203 1204 counter = counter + n * bytelength 1205 data_fw = 'Fw-{0}.{1}'.format(dict_settings['fw_major'], dict_settings['fw_minor']) 1206 if data_fw != fw_string: 1207 error = True 1208 if DEBUG: 1209 print('Error: JSON file firmware version does not match firmware currently on hvps-x. Exiting') 1210 exit(0) 1211 if not error: 1212 buffer = b'xxx' + buffer # Adds 3 random bytes. CMD + 3 random bytes means that when mapping the transfer 1213 # buffer to a structure, it will on an aligned memory address 1214 x = self._send_nbytes_receive_4bytes(SERIAL_XFERST, buffer) 1215 else: 1216 x = SERIAL_ERROR 1217 1218 if DEBUG: 1219 print("transfer_settings(...) -> {0}".format(x)) 1220 return x 1221 1222 def copy_settings(self, src, dst): 1223 """Copies one set of settings to another location:\n 1224 Copying BACKUP_SETTINGS to CURRENT_SETTINGS is useful to restore the backup settings as current settings 1225 (for example if some temporary settings were saved as current settings, for example to experiment with new PID 1226 gain values)\ 1227 Copying CURRENT_SETTINGS to BACKUP_SETTINGS is useful after a new calibration of the HVPS to save the new 1228 calibration as a set of back-up settings 1229 :param src: the settings to copy (CURRENT_SETTINGS or BACKUP_SETTINGS) 1230 :param dst: the destination settings (CURRENT_SETTINGS or BACKUP_SETTINGS) (destination will be overwritten by 1231 the source 1232 :return: SERIAL_OK or SERIAL_ERROR 1233 """ 1234 if (src == SETTINGS_CURRENT or src == SETTINGS_BACKUP) and (dst == SETTINGS_CURRENT or dst == SETTINGS_BACKUP): 1235 x = self._send_receive_4bytes(SERIAL_CPYST, param1=src, param2=dst) 1236 else: 1237 x = SERIAL_ERROR 1238 1239 if DEBUG: 1240 print('copy_settings({0},{1}) -> {2}'.format(src, dst, x)) 1241 1242 return x 1243 1244 def q_mem(self, settings=SETTINGS_CURRENT): # queries the content of the memory 1245 """ 1246 :param settings: which of the two sets of setting sets to query. Either SETTINGS_CURRENT or SETTINGS_Backup 1247 :return: A dictionary with the content of the memory. This is similar to the function save_memory_to_file() 1248 except that it doesn't save the content to a file""" 1249 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1250 memmap = memorymap[fw_string] 1251 dict_mem = {} 1252 1253 z = self._send_4bytes_receive_nbytes(SERIAL_QMEM, settings, param2=0, fstring=memory_string[fw_string]) 1254 bytecount = 0 1255 for i in range(len(memmap)): 1256 length = memmap[i][0] 1257 field = memmap[i][1] 1258 if field == 'Name': 1259 length = 1 1260 y = z[bytecount:bytecount + length] 1261 # if field != 'Padding': 1262 if length == 1: 1263 dict_mem[field] = y[0] 1264 else: 1265 dict_mem[field] = y 1266 bytecount = bytecount + length 1267 1268 dict_mem['Name'] = dict_mem['Name'].decode('UTF-8') 1269 dict_mem['Name'] = dict_mem['Name'].split("\0")[0] 1270 1271 if DEBUG: 1272 print(dict_mem) 1273 return dict_mem 1274 1275 def q_ver(self): # queries the firmware version 1276 """returns the current version of the firmware / hardware running on the board.""" 1277 z = self._send_receive_4bytes(SERIAL_QVER) 1278 Firm_minor = z & 0xFF 1279 Firm_major = (z >> 8) & 0xFF 1280 Hard_minor = (z >> 16) & 0xFF 1281 Hard_major = (z >> 24) 1282 self.firmware = Firm_major + Firm_minor / 10 1283 self.hardware = Hard_major + Hard_minor / 10 1284 if DEBUG: 1285 y = "q_ver -> {0} / {1}".format(self.firmware, self.hardware) 1286 print(y) 1287 return self.firmware, self.hardware 1288 1289 # Configuration functions 1290 def q_conf(self): # queries the configuration 1291 """returns the configuration of the board. 1292 :return: the configuration of the board in the high 2 bytes (CONF_UNCONF, CONF_UNIPOLAR, or CONF_BIPOLAR) 1293 and the PID (product ID) in the low 2 bytes. This is 0x0632 for a hvps-x unit. 1294 """ 1295 1296 z = self._send_receive_4bytes(SERIAL_QCONF) 1297 z_hvps = z & 0xFFFF 1298 z_conf = (z & 0xFFFF0000) >> 16 1299 if DEBUG: 1300 y = "q_conf -> " + hex(z_conf) + " / " + hex(z_hvps) 1301 print(y) 1302 self.conf = z_conf 1303 return z 1304 1305 def s_conf(self, bipolar): 1306 """Sets the configuration of the board 1307 :param bipolar: boolean. Is board bipolar (True), or unipolar (false) 1308 :return: bipolar: boolean 1309 """ 1310 if bipolar: 1311 param = CONF_BIPOLAR 1312 else: 1313 param = CONF_UNIPOLAR 1314 z = self._send_receive_4bytes(SERIAL_SCONF, param1=param) 1315 if DEBUG: 1316 y = "s_conf -> " + hex(z) 1317 print(y) 1318 if z == SERIAL_ERROR: 1319 print("Error: this configuration is not recognised") 1320 self.conf = z 1321 return z 1322 1323 def s_vmax(self, x): # sets the maximum voltage rating of the board 1324 """ sets the voltage rating of the hvps-x. Must match the EMCO DC/DC converter rating. 1325 :param x: Voltage rating of hvps-x in Volt 1326 :return: Voltage rating of hvps-x in Volt 1327 """ 1328 1329 x = constrain(x, 0, 6000) 1330 z = self._send_receive_4bytes(SERIAL_SVMAX, param2=x) 1331 if DEBUG: 1332 y = "s_vmax(" + str(x) + ") -> " + str(z) 1333 print(y) 1334 self.vmax = z 1335 return z 1336 1337 def q_vmax(self): # queries the voltage rating of the board 1338 """ Queries the maximal voltage of the board. The returned value is in volts. 1339 :return: board maximal voltage (V) 1340 """ 1341 z = self._send_receive_4bytes(SERIAL_QVMAX) 1342 if DEBUG: 1343 y = "q_vmax -> " + str(z) 1344 print(y) 1345 self.vmax = z 1346 return z 1347 1348 def s_name(self, x): # set the name of the HVPS 1349 """ Sets the name of the HVPS. 1350 :param x: Name of the hvps-x. 11 characters maximum 1351 :return: name accepted by hvps-x 1352 """ 1353 ll = len(x) 1354 if ll <= 12: 1355 x = bytearray(x, 'utf-8') 1356 for i in range(12 - ll): # pad the string with 0s 1357 x = x + b'\0' 1358 self._send_nbytes_receive_4bytes(SERIAL_SNAME, x, typ='uint') 1359 z = self.q_name() 1360 else: 1361 z = 'too long' 1362 if DEBUG: 1363 y = "s_name(" + str(x) + ") -> " + str(z) 1364 print(y) 1365 return z 1366 1367 def q_name(self): # queries the name of the board 1368 """queries the name of the board 1369 :return: Name of the board 1370 """ 1371 1372 # x = self._send_receive_4bytes(SERIAL_QNAME, param1=0) 1373 # x = x + (self._send_receive_4bytes(SERIAL_QNAME, param1=4) << 32) 1374 # x = x + (self._send_receive_4bytes(SERIAL_QNAME, param1=8) << 64) 1375 # x = x.to_bytes(12, 'little') 1376 1377 z = self._send_4bytes_receive_nbytes(SERIAL_QNAME, param1=0, param2=0, fstring='<12s') 1378 z = z[0].split(b'\x00', 1)[0] # take first (only) element of the tuple and remove the 0s at the end of string 1379 1380 self.name = z 1381 if DEBUG: 1382 y = "q_name -> " + str(z) 1383 print(y) 1384 return z 1385 1386 def set_hardware_version(self, hw_major, hw_minor): 1387 param = (hw_major << 8) + hw_minor 1388 self._send_receive_4bytes(SERIAL_SHW, param2=param) 1389 1390 def q_load_parameters(self): 1391 z = self._send_4bytes_receive_nbytes(SERIAL_QLOAD, param1=0, param2=0, fstring='<4H') 1392 if np.isin(0, np.array(z)): 1393 r = 0 1394 c = 0 1395 else: 1396 dv = self._convert_raw_to_voltage(z[0], VNOW_OUT) 1397 pwm_dv = z[1] 1398 ssv = self._convert_raw_to_voltage(z[2], VNOW_OUT) 1399 pwm_ss = z[3] 1400 current_selection = self.q_current_selection() 1401 imax = self.q_current()[current_selection] 1402 i_step = imax * pwm_dv / 4095 1403 i_ss = imax * pwm_ss / 4095 1404 dv_dt = dv / 5E-3 # dv is measured over 20ms 1405 c = i_step / dv_dt * 1E9 # C in nF 1406 r = ssv / i_ss / 1E6 # R in MOhm 1407 if DEBUG: 1408 print('q_load_parameters() --> {} nF, {} MOhm'.format(c, r)) 1409 return r, c 1410 1411 def check_version_compatibility(self): 1412 self.q_ver() 1413 lib_string = 'Lib-' + LIB_VER 1414 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1415 hw_string = 'Hw-{0:.1f}'.format(self.hardware) 1416 1417 list_compatible_fw = compatibility_dict[lib_string] 1418 if fw_string in list_compatible_fw: 1419 list_compatible_hw = compatibility_dict[fw_string] 1420 if hw_string not in list_compatible_hw: 1421 self.err |= ERR_FIRM 1422 else: 1423 self.err |= ERR_FIRM 1424 1425 def diagnostic_ADC(self, which_vnow=VNOW_POS): 1426 x = self._send_4bytes_receive_nbytes(SERIAL_DADC, which_vnow, 0, '<32H') 1427 return x 1428 1429 def q_trace(self, which_vnow=VNOW_POS): 1430 x = self._send_4bytes_receive_nbytes(SERIAL_QTRACE, which_vnow, 0, '<100H') 1431 x = np.array(x) 1432 if DEBUG: 1433 y = "trace({0}) -> {1}".format(which_vnow, x) 1434 print(y) 1435 return x 1436 1437 def s_trace_rate(self, rate=5): 1438 """Sets the rate at which trace points are stored in memory 1439 :param rate: time in ms between point acquisition. This is limited by the time at which the hvps-x is 1440 calculating the voltages; currently evey 5ms. 1441 :return: the rate accepted by the hvps-x""" 1442 1443 rate = int(rate) 1444 if rate < 2: 1445 rate = 2 1446 x = self._send_receive_4bytes(SERIAL_STRRATE, param1=0, param2=rate, typ='uint') 1447 x = x / 10 # the function returns the number of ticks of the counter, which counts at 10kHz. 1448 if DEBUG: 1449 y = "s_trace_rate({0}) -> {1}".format(rate, x) 1450 print(y) 1451 return x 1452 1453 def q_trace_rate(self): 1454 """queries the rate at which trace points are stored in memory 1455 :param 1456 :return the rate at which trace points are stored in memory (in ms)""" 1457 1458 x = self._send_receive_4bytes(SERIAL_QTRRATE, param1=0, param2=0, typ='uint') 1459 x = x / 10 # the function returns the number of ticks of the counter, which counts at 10kHz. 1460 if DEBUG: 1461 y = "q_trace_rate() -> {0}".format(x) 1462 print(y) 1463 return x 1464 1465 def s_waveform_pwm(self, value): 1466 1467 value = constrain(value, -4095, 4095) 1468 value = int(value) 1469 x = self._send_receive_4bytes(SERIAL_WFMPWM, param1=0, param2=value, typ='uint') 1470 if DEBUG: 1471 y = "s_waveform_pwm({0}) -> {1}".format(value, x) 1472 print(y) 1473 return x
Class to control a petapicovoltron hvps-x
The class implements the low-level functions to interface the hvps-x firmware for easy communication with the hvps-x.
252 def __init__(self, port, init=True): 253 """ Initialises a HVPS object: dev = HVPS('/path/to/serial/port'). 254 255 :param port: COM port to which the HVPS is connected. 256 :param init: if True (default) initialises the hvps-x. Setting to false can be useful if the intended use is 257 to check whether the device connected to the com port is a hvps-x""" 258 self.name = '' 259 self.vmax = 0 260 self.err = 0 261 self.ser = serial.Serial() # the serial connection to the HVPS 262 self.lookup_adc_p = [] # lookup tables for linear interpolation 263 self.lookup_adc_o = [] 264 self.lookup_v_out = [] 265 self.cp = [] # calibration coefficients. 266 self.co = [] 267 self.calmeth_p = -1 268 self.calmeth_n = -1 269 self.calmeth_o = -1 270 271 self.firmware = 0 272 self.hardware = 0 273 self.conf = 0 # configuration of the hvps-x 274 self.is_hvpsx = 0 275 276 try: 277 self.ser = serial.Serial(port, 115200, timeout=20) 278 except serial.SerialException: 279 self.err = self.err | ERR_PORT 280 if DEBUG: 281 print("Cannot connect to serial port. Probably busy.") 282 else: 283 self.ser.reset_input_buffer() 284 if DEBUG: 285 text = "connecting to " + port 286 print(text) 287 print("Serial port open") 288 z = self._send_receive_4bytes(SERIAL_QCONF) 289 if z == 0: # no response: the device connected doesn't talk using this protocol 290 self.err = self.err | ERR_COM 291 if DEBUG: 292 print("Communication error: the connected device doesn't reply to commands") 293 z_hvps = z & 0xFFFF 294 z_conf = (z & 0xFFFF0000) >> 16 295 if z_hvps == HVPSX_PID: 296 self.is_hvpsx = 1 297 if z_conf != CONF_UNCONF: 298 self.conf = z_conf 299 else: 300 self.err = self.err | ERR_CONF # the hvps-x is not configured 301 if DEBUG: 302 print("The hvps is not configured") 303 z = self._send_4bytes_receive_nbytes(SERIAL_QNAME, param1=0, param2=0, fstring='<12s') 304 z = z[0].split(b'\x00', 1)[0] 305 self.name = z 306 else: 307 self.err = self.err | ERR_TYPE # The connected device replied with Err! but is not a hvps-x 308 if DEBUG: 309 print("Type error: the device replies to commands, but is not a hvps-x.") 310 if init and not self.err: 311 self.check_version_compatibility() 312 self.q_vmax()
Initialises a HVPS object: dev = HVPS('/path/to/serial/port').
Parameters
- port: COM port to which the HVPS is connected.
- init: if True (default) initialises the hvps-x. Setting to false can be useful if the intended use is to check whether the device connected to the com port is a hvps-x
314 def close(self, zero=True): # closes connection with the HVPS 315 """Closes the connection to the HVPS. Sets voltage to 0 if board was connected 316 317 Input (optional): zero. If True, will set the voltage to 0 and the optocouplers to off before closing the 318 communication. If False, the hvps-x is left in its current state""" 319 320 if self.ser.is_open: 321 if not (self.err & ERR_COM): # if connected board is not running the firmware, do not send command 322 if zero: 323 self.s_sw_mode(SWMODE_OFF) # for hvps-x, this will disable both optocouplers. 324 self.s_vset(0, DCDC_BOTH) # set the voltage to 0 as a safety measure 325 self.ser.close() 326 327 if DEBUG: 328 print("Serial port closed")
Closes the connection to the HVPS. Sets voltage to 0 if board was connected
Input (optional): zero. If True, will set the voltage to 0 and the optocouplers to off before closing the communication. If False, the hvps-x is left in its current state
456 def is_bipolar(self): # return true if connected device is hvpsx and bipolar unit 457 """ Returns true if the hvps-x configuration is bipolar.""" 458 if self.conf & 1: 459 return True 460 else: 461 return False
Returns true if the hvps-x configuration is bipolar.
464 def s_vset(self, x, polarity=DCDC_POS): # sets the output voltage 465 """Sets the output voltage of the HVPS. The new parameter remains valid until a new call to this command, or 466 when the HVPS is powered off. Using the 467 save() command enables to save this parameter in memory\n 468 Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is 469 regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the 470 output to the set point; see s_vmode() command. 471 :param x: voltage set point in volt (int) 472 :param polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). 473 This parameter can be ignored given that only unipolar configuration is currently supported. 474 :return: voltage set point accepted by hvps-x 475 """ 476 x = constrain(abs(x), 0, self.vmax) 477 x = int(x) 478 if polarity != DCDC_POS and polarity != DCDC_NEG and polarity != DCDC_BOTH: 479 polarity = DCDC_POS 480 z = self._send_receive_4bytes(SERIAL_SVSET, param1=polarity, param2=x) 481 if x == 0: 482 z = 0 # avoids calibration issue near 0 that would lead to a negative number which is saturated to 0 in 483 # the STM32 (as the stored raw value is uint), leading to a possible large offset error returned value of a 484 # few 10s of volts when back calculated in volts 485 if DEBUG: 486 y = "s_vset(" + str(x) + ") -> " + str(z) 487 print(y) 488 return z
Sets the output voltage of the HVPS. The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory
Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the output to the set point; see s_vmode() command.
Parameters
- x: voltage set point in volt (int)
- polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). This parameter can be ignored given that only unipolar configuration is currently supported.
Returns
voltage set point accepted by hvps-x
490 def q_vset(self, polarity=DCDC_POS): # queries the voltage setpoint 491 """Queries the voltage set point. The returned value is in volts. 492 :param polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for 493 unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported. 494 :return: Voltage set point in volts 495 """ 496 if polarity != DCDC_POS and polarity != DCDC_NEG: 497 polarity = DCDC_POS 498 z = self._send_receive_4bytes(SERIAL_QVSET, param1=polarity) 499 if DEBUG: 500 y = "q_vset -> " + str(z) 501 print(y) 502 503 return z
Queries the voltage set point. The returned value is in volts.
Parameters
- polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported.
Returns
Voltage set point in volts
505 def s_vset_raw(self, x, polarity=DCDC_POS): # sets the output voltage 506 """Sets the raw output voltage of the HVPS (0-4095). The new parameter remains valid until a new call to this 507 command, or when the HVPS is powered off. Using the 508 save() command enables to save this parameter in memory\n 509 Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is 510 regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the 511 output to the set point; see s_vmode() command. 512 :param x: raw voltage set point (0-4095) 513 :param polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). 514 This parameter can be ignored given that only unipolar configuration is currently supported. 515 :return: voltage set point accepted by hvps-x 516 """ 517 x = constrain(x, 0, 4095) 518 x = int(x) 519 if polarity != DCDC_POS and polarity != DCDC_NEG and polarity != DCDC_BOTH: 520 polarity = DCDC_POS 521 z = self._send_receive_4bytes(SERIAL_SVSETRAW, param1=polarity, param2=x) 522 if x == 0: 523 z = 0 # avoids calibration issue near 0 that would lead to a negative number which is saturated to 0 in 524 # the STM32 (as the stored raw value is uint), leading to a possible large offset error returned value of a 525 # few 10s of volts when back calculated in volts 526 if DEBUG: 527 y = "s_vset_raw(" + str(x) + ") -> " + str(z) 528 print(y) 529 return z
Sets the raw output voltage of the HVPS (0-4095). The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory
Although this command can be used at any time, it is mainly useful when the HVPS voltage control mode is regulated (i.e. closed loop) (VMODE_R). In this case the hvps-x will adjust the control voltage to keep the output to the set point; see s_vmode() command.
Parameters
- x: raw voltage set point (0-4095)
- polarity: which DC/DC converter to address (DCDC_POS (default), DCDC_NEG (bipolar config), DCDC_BOTH). This parameter can be ignored given that only unipolar configuration is currently supported.
Returns
voltage set point accepted by hvps-x
531 def q_vset_raw(self, polarity=DCDC_POS): # queries the voltage setpoint 532 """Queries the raw voltage set point. The returned value is between 0 and 4095. 533 534 :param polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for 535 unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported. 536 :return: Voltage raw set point (0-4095)) 537 """ 538 if polarity != DCDC_POS and polarity != DCDC_NEG: 539 polarity = DCDC_POS 540 z = self._send_receive_4bytes(SERIAL_QVSETRAW, param1=polarity) 541 if DEBUG: 542 y = "q_vset_raw -> " + str(z) 543 print(y) 544 return z
Queries the raw voltage set point. The returned value is between 0 and 4095.
Parameters
- polarity: DCDC converter to be queried (DCDC_POS (default) or DCDC_NEG). DCDC_NEG will return 0 for unipolar configs. This parameter can be ignored given that only unipolar configuration is currently supported.
Returns
Voltage raw set point (0-4095))
546 def q_vnow(self, which=VNOW_POS): # queries the voltage output 547 """Queries the feedback voltage of the HVPS. 548 :param which: which output voltage to read {VNOW_POS (default), VNOW_NEG, VNOW_OUT}. Voltage at the output of the 549 positive DCDC converter, negative DCDC converter, or output of the HVPS 550 :return: voltage value in volts 551 """ 552 553 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 554 which = VNOW_POS 555 z = self._send_4bytes_receive_nbytes(SERIAL_QVNOW, param1=which, fstring='<2h') 556 z_out = z[1] 557 z_main = z[0] 558 if DEBUG: 559 y = "q_vnow -> " + str(z_main) + " / " + str(z_out) 560 print(y) 561 return z_main
Queries the feedback voltage of the HVPS.
Parameters
- which: which output voltage to read {VNOW_POS (default), VNOW_NEG, VNOW_OUT}. Voltage at the output of the positive DCDC converter, negative DCDC converter, or output of the HVPS
Returns
voltage value in volts
563 def q_vnow_raw(self, which=VNOW_POS): # queries a raw value of the voltage output 564 """Queries the current feedback voltage of the HVPS. The returned value is a raw 12bit ADC value. This avoids 565 running slow floating point calculations on the 566 microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to 567 execute than q_vnow(). Useful for streaming voltage values 568 :param which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the 569 positive DCDC converter, negative DCDC converter, or output of the HVPS. 570 :return: a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT 571 (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is 572 a 12 bit value that can be converted to voltage using the unit's calibration values (see q_vnow_fast() for a 573 way to do this automatically""" 574 575 if which != VNOW_POS and which != VNOW_NEG and which != VNOW_OUT: 576 which = VNOW_POS 577 z = self._send_receive_4bytes(SERIAL_QVNOWRAW, param1=which, typ='uint') 578 z_out = z & 0xFFFF 579 z_main = (z & 0xFFFF0000) >> 16 580 if DEBUG: 581 y = "q_vnow_raw -> " + str(z_main) + " , " + str(z_out) 582 print(y) 583 return z_main, z_out
Queries the current feedback voltage of the HVPS. The returned value is a raw 12bit ADC value. This avoids running slow floating point calculations on the microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to execute than q_vnow(). Useful for streaming voltage values
Parameters
- which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the positive DCDC converter, negative DCDC converter, or output of the HVPS.
Returns
a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is a 12 bit value that can be converted to voltage using the unit's calibration values (see q_vnow_fast() for a way to do this automatically
585 def q_vnow_fast(self, which=VNOW_POS): # queries the voltage output 586 """Queries the current feedback voltage of the HVPS in a raw format and convert it to a calibrated voltage 587 This avoids running slow floating point calculations on the 588 microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to 589 execute than q_vnow(). Useful for streaming voltage values. This method is similar to q_vnow(), except that 590 the conversion from a raw value to a calibrated value is done 591 on the host rather than on the microcontroller. It can take up to 300us to convert a value on the MUC 592 (it depends on the method used (linear, quadratic, lookup table) 593 :param which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the 594 positive DCDC converter, negative DCDC converter, or output of the HVPS. 595 :return: a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT 596 (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is 597 a 12 bit value that can be converted to voltage using the unit's calibration values The returned values 598 are calibrated voltages in Volt""" 599 600 z_main, z_out = self.q_vnow_raw(which=which) 601 v_main = self._convert_raw_to_voltage(z_main, which=which) 602 v_out = self._convert_raw_to_voltage(z_out, which=VNOW_OUT) 603 604 return v_main, v_out
Queries the current feedback voltage of the HVPS in a raw format and convert it to a calibrated voltage This avoids running slow floating point calculations on the microcontroller to convert ADC readings into a calibrated voltage, and this command is therefore faster to execute than q_vnow(). Useful for streaming voltage values. This method is similar to q_vnow(), except that the conversion from a raw value to a calibrated value is done on the host rather than on the microcontroller. It can take up to 300us to convert a value on the MUC (it depends on the method used (linear, quadratic, lookup table)
Parameters
- which: which output voltage to read {VNOW_POS, VNOW_NEG, VNOW_OUT}. Voltage at the output of the positive DCDC converter, negative DCDC converter, or output of the HVPS.
Returns
a tuple with two values: the first element is the requested value, and the second is always VNOW_OUT (i.e. you get VNOW_OUT for free when reading the voltage of one of the DCDC converters.). The returned value is a 12 bit value that can be converted to voltage using the unit's calibration values The returned values are calibrated voltages in Volt
606 def s_f(self, x): # sets the frequency 607 """Sets the frequency of the signal when the HVPS is in switching or waveform mode (SWMODE_SW / SWMODE_SW).\n 608 The value returned is the new frequency, taking quantification into account. 609 :param x: frequency in Hz between 0.001 and 1000. In waveform mode, 0.16 Hz <= f <=100 Hz 610 :return: frequency accepted by hvps-x in Hz 611 """ 612 x = constrain(x, 0.001, 1000.0) 613 614 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 615 # Ex: 10.0: 0x41 0x20 0x00 0x00. 616 # 0 10000010 01000000000000000000000 617 # S=0 Positive 618 # Exp=130-127 (offset)=2^3=8 619 # Fraction=1+2^-2=1.25 620 # number=+1.25*8=10 621 f_byte = struct.pack('<f', x) 622 z = self._send_nbytes_receive_4bytes(SERIAL_SF, f_byte, typ='float') 623 624 if DEBUG: 625 y = "s_f(" + str(x) + ") -> " + str(z) 626 print(y) 627 return z
Sets the frequency of the signal when the HVPS is in switching or waveform mode (SWMODE_SW / SWMODE_SW).
The value returned is the new frequency, taking quantification into account.
Parameters
- x: frequency in Hz between 0.001 and 1000. In waveform mode, 0.16 Hz <= f <=100 Hz
Returns
frequency accepted by hvps-x in Hz
629 def q_f(self): # queries the frequency 630 """Queries the frequency. The returned value is in Hz.""" 631 z = self._send_receive_4bytes(SERIAL_QF, param1=0, param2=0, typ='float') 632 633 if DEBUG: 634 y = "q_f -> " + str(z) 635 print(y) 636 637 return z
Queries the frequency. The returned value is in Hz.
639 def s_current(self, xnorm, xhigh): # sets the current limit setting 640 """Sets the current output in both current settings (normal and high). This value is used to calculate the PID 641 coefficients for the waveform. Values can be obtained by measuring the charging rate when charging a capacitor 642 of known value. This is a configuration parameter that must be set according to the capabilities of the hvps-x. 643 It is not a user-settable output current. 644 :param xnorm: current in A between 50E-6 and 500E-6 in normal output current config. 645 :param xhigh: current in A between 50E-6 and 500E-6 in high output current config. 646 :return: SERIAL_OK or SERIAL_ERROR 647 """ 648 xnorm = constrain(xnorm, 50E-6, 500E-6) 649 xhigh= constrain(xhigh, 50E-6, 500E-6) 650 f_byte = struct.pack('<2f', xnorm, xhigh) 651 z = self._send_nbytes_receive_4bytes(SERIAL_SCURR, f_byte, typ='uint') 652 653 if DEBUG: 654 y = "s_current({},{}) -> {}".format(xnorm, xhigh, z) 655 print(y) 656 return z
Sets the current output in both current settings (normal and high). This value is used to calculate the PID coefficients for the waveform. Values can be obtained by measuring the charging rate when charging a capacitor of known value. This is a configuration parameter that must be set according to the capabilities of the hvps-x. It is not a user-settable output current.
Parameters
- xnorm: current in A between 50E-6 and 500E-6 in normal output current config.
- xhigh: current in A between 50E-6 and 500E-6 in high output current config.
Returns
SERIAL_OK or SERIAL_ERROR
658 def q_current(self): # queries the current limit setting 659 """Queries the current limit setting. The returned value is in A. 660 661 :return: A tuple of two current values corresponding to the output current settings in normal and high current 662 modes.""" 663 664 z = self._send_4bytes_receive_nbytes(SERIAL_QCURR, param1=0, param2=0, fstring='<2f') 665 666 if DEBUG: 667 y = "q_curr() -> " + str(z) 668 print(y) 669 return z
Queries the current limit setting. The returned value is in A.
Returns
A tuple of two current values corresponding to the output current settings in normal and high current modes.
671 def s_current_selection(self, x): 672 """Sets the position of the current selection jumper 673 :param x: Position of the output current selection jumper. Either J2_3V3 (normal) or J2_5V depending on the 674 jumper position. 3V3 and 5V are written on the silkscreen and respectively correspond to normal and high current 675 modes. After changing the position of the jumper, this command should be run to reflect the change. The new 676 setting will be kept until the unit is power-cycled. To make the selection permanent, use the save() command 677 after the s_current_selection(x) command. 678 :return: Accepted current selection jumper position (J2_3V3 or J2_5V) 679 """ 680 x = int(np.abs(x)) 681 if x > J2_5V: 682 x = J2_5V 683 z = self._send_receive_4bytes(SERIAL_SCURRSEL, param1=x, param2=0, typ='uint') 684 685 if DEBUG: 686 print("s_current_selection({}) --> {}".format(x, z)) 687 return z
Sets the position of the current selection jumper
Parameters
- x: Position of the output current selection jumper. Either J2_3V3 (normal) or J2_5V depending on the jumper position. 3V3 and 5V are written on the silkscreen and respectively correspond to normal and high current modes. After changing the position of the jumper, this command should be run to reflect the change. The new setting will be kept until the unit is power-cycled. To make the selection permanent, use the save() command after the s_current_selection(x) command.
Returns
Accepted current selection jumper position (J2_3V3 or J2_5V)
689 def q_current_selection(self): 690 """Queries the position of the current selection jumper 691 :return: Current selection (J2_3V3 or J2_5V) 692 """ 693 z = self._send_receive_4bytes(SERIAL_QCURRSEL, param1=0, param2=0, typ='uint') 694 695 if DEBUG: 696 print("q_current_selection() --> {}".format(z)) 697 return z
Queries the position of the current selection jumper
Returns
Current selection (J2_3V3 or J2_5V)
699 def s_duty(self, x): # sets the duty cycle of the switching signal 700 """Sets the duty cycle of the switching signal 701 :param x: the duty cycle (float in the range 0-1) 702 :return: the current duty cycle (float between 0 and 1) 703 """ 704 duty = int(x * 1000) # hvps-x is coding duty cycle on a 0-1000 scale 705 706 z = self._send_receive_4bytes(SERIAL_SDUTY, param1=0, param2=duty, typ='uint') 707 z = z / 1000 708 if DEBUG: 709 y = "s_duty(" + str(x) + ") -> " + str(z) 710 print(y) 711 return z
Sets the duty cycle of the switching signal
Parameters
- x: the duty cycle (float in the range 0-1)
Returns
the current duty cycle (float between 0 and 1)
713 def q_duty(self): # queries the duty cycle of the switching signal 714 """queries the duty cycle of the switching signal 715 :return: the current duty cycle (float between 0 and 1) 716 """ 717 718 z = self._send_receive_4bytes(SERIAL_QDUTY, param1=0, param2=0, typ='uint') 719 z = float(z) / 1000.0 720 if DEBUG: 721 y = "q_duty() -> " + str(z) 722 print(y) 723 return z
queries the duty cycle of the switching signal
Returns
the current duty cycle (float between 0 and 1)
726 def s_sw_mode(self, x): # sets the switching mode 727 """Sets the switching mode of the hvps-x. \n 728 SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. 729 This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV 730 DC/DC converter. 731 SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and 732 SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with 733 the specified duty cycle. 734 SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory 735 The program tab "Waveform" can be used to set the parameters of the waveform. 736 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 737 save() command enables to save this parameter in memory 738 :param x: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM 739 :return: Switching mode set by the HVPS 740 """ 741 742 if x > SWMODE_LOW: 743 x = SWMODE_OFF 744 745 z = self._send_receive_4bytes(SERIAL_SSWMODE, param1=x, param2=0, typ='uint') 746 if DEBUG: 747 y = "s_sw_mode(" + str(x) + ") -> " + str(z) 748 print(y) 749 return z
Sets the switching mode of the hvps-x.
SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV DC/DC converter. SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with the specified duty cycle. SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory The program tab "Waveform" can be used to set the parameters of the waveform. The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory
Parameters
- x: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM
Returns
Switching mode set by the HVPS
751 def q_sw_mode(self): # queries the switching mode 752 """queries the switching mode of the hvps-x. \n 753 SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. 754 This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV 755 DC/DC converter. 756 SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and 757 SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with 758 the specified duty cycle. 759 SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory 760 The program tab "Waveform" can be used to set the parameters of the waveform. 761 :return: Switching mode set by the HVPS: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM 762 """ 763 z = self._send_receive_4bytes(SERIAL_QSWMODE, param1=0, param2=0, typ='uint') 764 if DEBUG: 765 y = "q_sw_mode -> " + str(z) 766 print(y) 767 return z
queries the switching mode of the hvps-x.
SWMODE_OFF Both optocouplers turned off. SWMODE_HIGH: Optocoupler 1 is on and Optocoupler 2 is off. This effectively connects the red output (J4) of the hvps-x to the high voltage produced by the main board HV DC/DC converter. SWMODE_LOW: Optocoupler 2 is on and Optocoupler 1 is off. This connects J4 to ground and SWMODE_SW: the optocouplers are switching between SWMODE_HIGH and SWMODE_LOW at the specified frequency and with the specified duty cycle. SWMODE_WFM: The optocouplers are controlled with a PWM signal to reproduce the user-defined waveform in memory The program tab "Waveform" can be used to set the parameters of the waveform.
Returns
Switching mode set by the HVPS: SWMODE_OFF, SWMODE_HIGH, SWMODE_LOW, SWMODE_SW, SWMODE_WFM
769 def s_sw_src(self, x): # sets the switching source 770 """Sets the source of the switching signal. 771 772 Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard 773 switching (from internal clock of the board), or SWSRC_BTTN for the push button.\n 774 Using the save() command enables to save this parameter in memory 775 :param x: SWSRC_TMR, or SWSRC_BTTN 776 :return: SWSRC_TMR, or SWSRC_BTTN 777 """ 778 if x > SWSRC_BTTN: 779 x = SWSRC_TMR 780 z = self._send_receive_4bytes(SERIAL_SSWSRC, param1=x, param2=0, typ='uint') 781 if DEBUG: 782 y = "s_sw_src(" + str(x) + ") -> " + str(z) 783 print(y) 784 return z
Sets the source of the switching signal.
Sets the source of the switching signal. Accepted values are: SWSRC_TMR for onboard switching (from internal clock of the board), or SWSRC_BTTN for the push button.
Using the save() command enables to save this parameter in memory
Parameters
- x: SWSRC_TMR, or SWSRC_BTTN
Returns
SWSRC_TMR, or SWSRC_BTTN
786 def q_sw_src(self): # queries the switching source 787 """queries the source of the switching signal. 788 789 queries the source of the switching signal. Output values are: SWSRC_TMR for onboard 790 switching (from internal clock of the board), or SWSRC_BTTN for the push button.\n 791 Using the save() command enables to save this parameter in memory 792 :return: SWSRC_TMR, or SWSRC_BTTN 793 """ 794 795 z = self._send_receive_4bytes(SERIAL_QSWSRC, param1=0, param2=0, typ='uint') 796 797 if DEBUG: 798 y = "q_sw_src -> " + str(z) 799 print(y) 800 return z
queries the source of the switching signal.
queries the source of the switching signal. Output values are: SWSRC_TMR for onboard switching (from internal clock of the board), or SWSRC_BTTN for the push button.
Using the save() command enables to save this parameter in memory
Returns
SWSRC_TMR, or SWSRC_BTTN
802 def s_bttn_cfg(self, x): # sets the configuration of the push button 803 """Defines the behaviour of the push button 804 805 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 806 save() command enables to save this parameter in memory 807 :param x: 2-bit value (0x0 to 0x3). bit 0 - Defines the behaviour of the push button, when the switching source 808 of the HVPS is set to the push button 809 (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour 810 (i.e. the high voltage is turned on as long as the button is pressed), 811 and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time 812 to turn it off).\n 813 bit 1 - State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW 814 :return: the 2-bit button config value 815 """ 816 if x > 3: 817 x = 3 818 z = self._send_receive_4bytes(SERIAL_SBTTNCFG, param1=x, param2=0, typ='uint') 819 if DEBUG: 820 y = "s_bttn_cfg(" + str(x) + ") -> " + str(z) 821 print(y) 822 823 return z
Defines the behaviour of the push button
The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory
Parameters
- x: 2-bit value (0x0 to 0x3). bit 0 - Defines the behaviour of the push button, when the switching source of the HVPS is set to the push button (SWSRC_BTTN, c.f. s_sw_src() command above). Accepted values are 0 and 1: 0 for a push button behaviour (i.e. the high voltage is turned on as long as the button is pressed), and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time to turn it off).
bit 1 - State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW
Returns
the 2-bit button config value
825 def q_bttn_cfg(self): # queries the latch mode of the push button 826 """Queries the behaviour of the push button 827 828 :return: the 2-bit button config value. bit 0: Defines the behaviour of the push button, when the switching 829 source of the HVPS is set to the push button (SWSRC_BTTN, c.f. s_sw_src() command above). 830 Values are 0 for a push button behaviour (i.e. the high voltage is turned on as long as the button is pressed), 831 and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time 832 to turn it off).\n 833 bit 1: State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW 834 """ 835 z = self._send_receive_4bytes(SERIAL_QBTTNCFG, param1=0, param2=0, typ='uint') 836 if DEBUG: 837 y = "q_bttn_cfg -> " + str(z) 838 print(y) 839 840 return z
Queries the behaviour of the push button
Returns
the 2-bit button config value. bit 0: Defines the behaviour of the push button, when the switching source of the HVPS is set to the push button (SWSRC_BTTN, c.f. s_sw_src() command above). Values are 0 for a push button behaviour (i.e. the high voltage is turned on as long as the button is pressed), and 1 for a latching switch behaviour (i.e. press once to turn the high voltage on, and press a second time to turn it off).
bit 1: State of the button when it is not activated: 0: SWMODE_OFF, 1: SWMODE_LOW
842 def q_kill(self): # queries the state of the kill button. Kill=1 means HV is disabled 843 """Queries whether HV is disabled (Kill=1) or enabled (kill=0). When kill = 1 there will not be a HV present at 844 the hvps-x output, irrespective of any software setting. 845 :return: 1 if the Switch S1 on the board is set to 0 (HV output is killed), 0 otherwise. 846 """ 847 z = self._send_receive_4bytes(SERIAL_QKILL, param1=0, param2=0, typ='uint') 848 849 if DEBUG: 850 y = "q_kill -> " + str(z) 851 print(y) 852 return z
Queries whether HV is disabled (Kill=1) or enabled (kill=0). When kill = 1 there will not be a HV present at the hvps-x output, irrespective of any software setting.
Returns
1 if the Switch S1 on the board is set to 0 (HV output is killed), 0 otherwise.
854 def s_v_mode(self, x): # sets the voltage control mode 855 """Sets the voltage control mode 856 857 Sets the voltage control mode (i.e. how is the value of the output voltage controlled):\n 858 VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).\n 859 VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).\n 860 The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the 861 save() command enables to save this parameter in memory\n 862 VMODE_O has an internal safeguard that will decrease the setting if the sensing circuit saturates. However, 863 the voltage can still be slightly higher than the DCDC converter upper limit. User must check that the output 864 voltage remains within the allowed range 865 :param x: VMODE_R, VMODE_O 866 :return: VMODE_R, VMODE_O 867 """ 868 869 if x > VMODE_O: 870 x = VMODE_R 871 872 z = self._send_receive_4bytes(SERIAL_SVMODE, param1=x, param2=0, typ='uint') 873 if DEBUG: 874 y = "s_v_mode(" + str(x) + ") -> " + str(z) 875 print(y) 876 return z
Sets the voltage control mode
Sets the voltage control mode (i.e. how is the value of the output voltage controlled):
VMODE_R for internal voltage regulator (regulates the voltage to the value defined with the Vset command).
VMODE_O (that's an O like in open) internal open loop control (on-board regulator disconnected).
The new parameter remains valid until a new call to this command, or when the HVPS is powered off. Using the save() command enables to save this parameter in memory
VMODE_O has an internal safeguard that will decrease the setting if the sensing circuit saturates. However, the voltage can still be slightly higher than the DCDC converter upper limit. User must check that the output voltage remains within the allowed range
Parameters
- x: VMODE_R, VMODE_O
Returns
VMODE_R, VMODE_O
878 def q_v_mode(self): # queries the switching source 879 """Queries the voltage control mode 880 881 :return: VMODE_R internal voltage regulator, VMODE_O internal 882 open loop control (on-board regulator disconnected). 883 """ 884 885 z = self._send_receive_4bytes(SERIAL_QVMODE, param1=0, param2=0, typ='uint') 886 887 if DEBUG: 888 y = "q_v_mode -> " + str(z) 889 print(y) 890 return z
Queries the voltage control mode
Returns
VMODE_R internal voltage regulator, VMODE_O internal open loop control (on-board regulator disconnected).
893 def s_pid(self, x, pid=PID_KPP): 894 """Sets the gains of the PIDs 895 Use save() command to save the new values to memory 896 :param x: the value (float) of the gain. 897 :param pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, 898 for positive voltage PID, output voltage PID (used for the waveform mode). 899 :return: the gain value 900 """ 901 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 902 # Ex: 10.0: 0x41 0x20 0x00 0x00. 903 # 0 10000010 01000000000000000000000 904 # S=0 Positive 905 # Exp=130-127 (offset)=2^3=8 906 # Fraction=1+2^-2=1.25 907 # number=+1.25*8=10 908 pid_byte = struct.pack('<Bf', pid, x) 909 z = self._send_nbytes_receive_4bytes(SERIAL_SPID, pid_byte, typ='float') 910 911 if DEBUG: 912 y = "s_pid(" + str(x) + "," + str(pid) + ") -> " + str(z) 913 print(y) 914 return z
Sets the gains of the PIDs Use save() command to save the new values to memory
Parameters
- x: the value (float) of the gain.
- pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, for positive voltage PID, output voltage PID (used for the waveform mode).
Returns
the gain value
916 def q_pid(self, pid=PID_KPP): # queries the frequency 917 """returns the gains of the PIDs 918 919 :param pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, 920 for positive voltage PID, output voltage PID (used for the waveform mode). 921 :return: the gain value of the chosen parameter 922 """ 923 924 z = self._send_receive_4bytes(SERIAL_QPID, param1=pid, param2=0, typ='float') 925 926 if DEBUG: 927 y = "q_pid(" + str(pid) + ") -> " + str(z) 928 print(y) 929 930 return z
returns the gains of the PIDs
Parameters
- pid: PID_KXY, with X={P,I,D} for the gains Kp, Ki, and Kd, and Y={P,O}, for positive voltage PID, output voltage PID (used for the waveform mode).
Returns
the gain value of the chosen parameter
932 def s_cal(self, x, cal=CAL_C1P): 933 """Sets the calibration constants of the analogue input signals (conversion to calibrated voltage values) 934 Use save() to commit the setting to memory 935 :param x: the value of the calibration coefficient (float) 936 :param cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive 937 voltage measurement, output voltage measurement 938 :return: the calibration value 939 """ 940 941 # 4-bytes floats: sign-exponent-fraction. Sign is bit 31. must add 1 to fraction 942 # Ex: 10.0: 0x41 0x20 0x00 0x00. 943 # 0 10000010 01000000000000000000000 944 # S=0 Positive 945 # Exp=130-127 (offset)=2^3=8 946 # Fraction=1+2^-2=1.25 947 # number=+1.25*8=10 948 cal_byte = struct.pack('<Bf', cal, x) 949 z = self._send_nbytes_receive_4bytes(SERIAL_SCAL, cal_byte, typ='float') 950 951 if DEBUG: 952 y = "s_cal(" + str(x) + "," + str(cal) + ") -> " + str(z) 953 print(y) 954 return z
Sets the calibration constants of the analogue input signals (conversion to calibrated voltage values) Use save() to commit the setting to memory
Parameters
- x: the value of the calibration coefficient (float)
- cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive voltage measurement, output voltage measurement
Returns
the calibration value
956 def q_cal(self, cal=CAL_C1P): # queries the frequency 957 """queries the calibration constants of the analogue input signals (conversion to calibrated voltage values) 958 959 :param cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive 960 voltage measurement, output voltage measurement 961 :return: the calibration value 962 """ 963 964 z = self._send_receive_4bytes(SERIAL_QCAL, param1=cal, param2=0, typ='float') 965 966 if DEBUG: 967 y = "q_cal(" + str(cal) + ") -> " + str(z) 968 print(y) 969 return z
queries the calibration constants of the analogue input signals (conversion to calibrated voltage values)
Parameters
- cal: CAL_CXY, with X={0,1,2} for the calibration coefficient of order X, and Y={P,O}, for positive voltage measurement, output voltage measurement
Returns
the calibration value
971 def s_calibration_method(self, which=VNOW_POS, calmeth=CALMETH_POLYNOMIAL): 972 if not (calmeth == CALMETH_POLYNOMIAL or calmeth == CALMETH_LOOKUP): 973 calmeth = CALMETH_POLYNOMIAL 974 if not (which == VNOW_POS or which == VNOW_OUT or which == VNOW_NEG): 975 which = VNOW_POS 976 z = self._send_receive_4bytes(SERIAL_SCALMETH, param1=which, param2=calmeth, typ='uint') 977 978 if DEBUG: 979 y = "s_calibration_method({0}, {1}) -> {2}".format(which, calmeth, z) 980 print(y) 981 if which == VNOW_POS: 982 self.calmeth_p = z 983 elif which == VNOW_OUT: 984 self.calmeth_o = z 985 else: 986 self.calmeth_n = z
988 def q_calibration_method(self, which=VNOW_POS): 989 if not (which == VNOW_POS or which == VNOW_OUT or which == VNOW_NEG): 990 which = VNOW_POS 991 z = self._send_receive_4bytes(SERIAL_QCALMETH, param1=which, param2=0, typ='uint') 992 993 if DEBUG: 994 y = "q_calibration_method({0}) -> {1}".format(which, z) 995 print(y) 996 if which == VNOW_POS: 997 self.calmeth_p = z 998 elif which == VNOW_NEG: 999 self.calmeth_n = z 1000 else: 1001 self.calmeth_o = z
1003 def s_lookup(self, x, n, table=LOOKUP_ADC_P): 1004 if n > 20: 1005 n = 20 1006 n = n << 2 # shift left 2 bits 1007 if table == LOOKUP_ADC_P: 1008 n = n | 0b01 1009 elif table == LOOKUP_ADC_O: 1010 n = n | 0b10 1011 x = int(x) 1012 z = self._send_receive_4bytes(SERIAL_SLKUP, param1=n, param2=x, typ='int16') 1013 if DEBUG: 1014 y = "s_lookup({0},{1},{2}) - > {3}".format(x, n, table, z) 1015 print(y)
1017 def q_lookup(self, which): # which=0: vout, which=1: ADC_p, which=2: ADC_o 1018 format_string = f'<21h' 1019 z = self._send_4bytes_receive_nbytes(SERIAL_QLKUP, param1=which, param2=0, fstring=format_string) 1020 lookup_list = list(z) 1021 if DEBUG: 1022 y = "q_lookup({0}) - > {1}".format(which, lookup_list) 1023 print(y) 1024 return lookup_list
1026 def s_saturation_limit(self, limit): # limit: 0-> reset to default 2% above Vmax. Otherwise:1-4095 1027 if limit < 0: 1028 limit = 0 1029 elif limit > 4095: 1030 limit = 4095 1031 z = self._send_receive_4bytes(SERIAL_SLKUP, param1=0, param2=limit, typ='int16') 1032 if DEBUG: 1033 y = "s_saturation_limit({0}) - > {1}".format(limit, z) 1034 print(y) 1035 return z
1038 def s_wfm_pts(self, n=1, m=500, pts=None): 1039 """Sets the data points of the waveform 1040 Use save() to commit the setting to memory 1041 :param n: The loop counter. The frequency of the signal is f = 1 / (n * m * 0.5E-3) 0 < n < 256. If you don't 1042 want to bother with n, set n = 1, and use s_f() after transferring the points to get the desired wfm frequency. 1043 :param m: The number of points in the waveform 0 <m <= 500 1044 :param pts: a list of integer voltage points. The list must be 500 elements long. If m<500, then the end of the list 1045 must be padded with zeros. 1046 :return: SERIAL_OK or SERIAL_ERROR 1047 """ 1048 # parameters are 1) n (byte B) the counter divider, 2) the number of points m, an unsigned short, and 3) m unsigned short pts 1049 if pts is None: 1050 z = SERIAL_ERROR 1051 if DEBUG: 1052 print("s_wfm_pts(): ERROR: no points given") 1053 elif len(pts) == 500 and all(isinstance(item, int) for item in pts): 1054 n=int(n) 1055 if n > 255: 1056 n=255 1057 m=int(m) 1058 format_string = f'<B{WFM_MAX_PTS+1}H' 1059 buffer = struct.pack(format_string, n, m, *pts) # array pts always has WFM_MAX_PTS points, but with zeros as 1060 # padding at the end. m indicated the effective number of data points. This is to always have the same command 1061 # length 1062 z = self._send_nbytes_receive_4bytes(SERIAL_SWFMPTS, buffer) 1063 if DEBUG: 1064 y = "s_wfm_pts({0}, {1}, {2}) - > {3}".format(n, m, buffer, z) 1065 print(y) 1066 else: 1067 z = SERIAL_ERROR 1068 if DEBUG: 1069 print("s_wfm_pts(): ERROR: number of points not equal to 500, or not all integer values") 1070 return z
Sets the data points of the waveform Use save() to commit the setting to memory
Parameters
- n: The loop counter. The frequency of the signal is f = 1 / (n * m * 0.5E-3) 0 < n < 256. If you don't want to bother with n, set n = 1, and use s_f() after transferring the points to get the desired wfm frequency.
- m: The number of points in the waveform 0
- pts: a list of integer voltage points. The list must be 500 elements long. If m<500, then the end of the list must be padded with zeros.
Returns
SERIAL_OK or SERIAL_ERROR
1072 def q_wfm_pts(self): 1073 """queries the data points of the waveform 1074 :return : n, z with n: the number of points in the waveform and z: the list of waveform points in Volts 1075 """ 1076 format_string = f'<H500H' # 1 16-bit unsigned value for the number of points and 500 unsigned 16-bit values. Not all points are effective points (some may be padding zeroes) hence why n is required. 1077 z = self._send_4bytes_receive_nbytes(SERIAL_QWFMPTS, param1=0, param2=0, fstring=format_string) 1078 z = list(z) 1079 n = z.pop(0) # the first element is the number of points 1080 del z[n:] #remove all elements of the list after the number of points to keep 1081 if DEBUG: 1082 y = "q_wfm_pts() - > {}/{}".format(n, z) 1083 print(y) 1084 return n, z
queries the data points of the waveform
Returns
the list of waveform points in Volts
1088 def save(self): # save current HVPS parameters into the memory 1089 """save active HVPS parameters into the memory 1090 1091 This command saves the active parameters as \'current\' settings. Current setting are the settings that are 1092 loaded when power is applied to the hvps-x 1093 1094 :return: SERIAL_OK or SERIAL_ERROR 1095 """ 1096 1097 z = self._send_receive_4bytes(SERIAL_SAVE) 1098 1099 if DEBUG: 1100 y = "save -> " + str(z) 1101 print(y) 1102 return z
save active HVPS parameters into the memory
This command saves the active parameters as 'current' settings. Current setting are the settings that are loaded when power is applied to the hvps-x
Returns
SERIAL_OK or SERIAL_ERROR
1104 def save_memory_to_file(self, settings=SETTINGS_CURRENT): 1105 """Dumps the content of the memory into a JSON file. 1106 1107 Dumps the content of the memory into a file. This is useful to keep a backup of the parameters on file. 1108 Files will be created in the interface folder and have the following format: Name_settings_type_date_time.json\n 1109 json files with settings can be transferred back to the hvps-x with the transfer_settings() method of the HVPS 1110 class, or the higher-level function transfer_file_to_memory() 1111 :param settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_Backup 1112 :return: the name of the created file (it follows a standard naming scheme with the date, the name of the 1113 hvps-x and the setting type. 1114 """ 1115 1116 memory = self.q_mem(settings) 1117 now = datetime.now() # current date and time 1118 date_time = now.strftime("%Y_%m_%d") 1119 if settings == SETTINGS_CURRENT: 1120 st_string = '_cur_st_' 1121 else: 1122 st_string = '_bk_st_' 1123 file_name = self.name.decode("utf-8") + st_string + date_time + '.json' 1124 with open(file_name, "w") as write_file: 1125 json.dump(memory, write_file, indent=4) 1126 1127 return file_name
Dumps the content of the memory into a JSON file.
Dumps the content of the memory into a file. This is useful to keep a backup of the parameters on file. Files will be created in the interface folder and have the following format: Name_settings_type_date_time.json
json files with settings can be transferred back to the hvps-x with the transfer_settings() method of the HVPS class, or the higher-level function transfer_file_to_memory()
Parameters
- settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_Backup
Returns
the name of the created file (it follows a standard naming scheme with the date, the name of the hvps-x and the setting type.
1129 def s_settings(self, 1130 settings=SETTINGS_CURRENT): # sets the active setting to a particular type (useful before saving) 1131 if not (settings == SETTINGS_CURRENT or settings == SETTINGS_FACTORY or settings == SETTINGS_BACKUP): 1132 settings = SETTINGS_CURRENT 1133 x = self._send_receive_4bytes(SERIAL_SST, param1=settings) 1134 if DEBUG: 1135 print('s_settings({0}) -> {1}'.format(settings, x)) 1136 return x
1138 def load_settings(self, settings=SETTINGS_CURRENT): # Load a setting set from memory to the active settings 1139 """This function loads one of the two set of settings (current or backup) as active settings used by the hvps-x 1140 1141 :param settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_BACKUP 1142 :return: 1143 """ 1144 1145 if not (settings == SETTINGS_CURRENT or settings == SETTINGS_BACKUP): 1146 settings = SETTINGS_CURRENT 1147 x = self._send_receive_4bytes(SERIAL_LST, param1=settings) 1148 if DEBUG: 1149 print('load_settings({0}) -> {1}'.format(settings, x)) 1150 1151 self._initialise_hvpsx() # need to reread all parameters 1152 1153 return x
This function loads one of the two set of settings (current or backup) as active settings used by the hvps-x
Parameters
- settings: which of the two sets of settings to save. Either SETTINGS_CURRENT or SETTINGS_BACKUP
Returns
1155 def transfer_settings(self, dict_settings): 1156 """ transfer a setting dictionary from the computer to the hvps-x memory 1157 :param dict_settings: a dictionary containing the settings values. 1158 :return: SERIAL_OK or SERIAL_ERROR 1159 The dictionary of settings should be read from a file dumped using the function save_memory_to_file(). Together 1160 these two functions make it possible to backup the settings (this includes calibration and PID settings in a 1161 file, and gives the opportunity to restore the settings""" 1162 1163 error = False 1164 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1165 siz = struct.calcsize(memory_string[fw_string]) 1166 buffer = ctypes.create_string_buffer(siz) 1167 memmap = memorymap[fw_string] 1168 counter = 0 1169 for x in memmap: 1170 n = x[0] 1171 key = x[1] 1172 data_format = x[2] 1173 if data_format == 'B' or data_format == 's': 1174 bytelength = 1 1175 elif data_format == 'h' or data_format == 'H': 1176 bytelength = 2 1177 elif data_format == 'f': 1178 bytelength = 4 1179 else: 1180 bytelength = 1 1181 data = dict_settings[key] 1182 if key == 'Name': 1183 data = bytes(data, 'utf-8') 1184 if len(data) > 12: 1185 error = True 1186 if DEBUG: 1187 print("Error: name should not exceed 12 characters") 1188 else: 1189 format_str = '<{0}{1}'.format(n, data_format) 1190 struct.pack_into(format_str, buffer, counter, data) 1191 else: 1192 format_str = '<{0}'.format(data_format) 1193 if n == 1: 1194 struct.pack_into(format_str, buffer, counter, data) 1195 else: 1196 for i in range(n): 1197 try: 1198 struct.pack_into(format_str, buffer, counter + i * bytelength, data[i]) 1199 except IndexError: 1200 error = True 1201 if DEBUG: 1202 print('setting dictionary does not fit the expected format') 1203 1204 counter = counter + n * bytelength 1205 data_fw = 'Fw-{0}.{1}'.format(dict_settings['fw_major'], dict_settings['fw_minor']) 1206 if data_fw != fw_string: 1207 error = True 1208 if DEBUG: 1209 print('Error: JSON file firmware version does not match firmware currently on hvps-x. Exiting') 1210 exit(0) 1211 if not error: 1212 buffer = b'xxx' + buffer # Adds 3 random bytes. CMD + 3 random bytes means that when mapping the transfer 1213 # buffer to a structure, it will on an aligned memory address 1214 x = self._send_nbytes_receive_4bytes(SERIAL_XFERST, buffer) 1215 else: 1216 x = SERIAL_ERROR 1217 1218 if DEBUG: 1219 print("transfer_settings(...) -> {0}".format(x)) 1220 return x
transfer a setting dictionary from the computer to the hvps-x memory
Parameters
- dict_settings: a dictionary containing the settings values.
Returns
SERIAL_OK or SERIAL_ERROR The dictionary of settings should be read from a file dumped using the function save_memory_to_file(). Together these two functions make it possible to backup the settings (this includes calibration and PID settings in a file, and gives the opportunity to restore the settings
1222 def copy_settings(self, src, dst): 1223 """Copies one set of settings to another location:\n 1224 Copying BACKUP_SETTINGS to CURRENT_SETTINGS is useful to restore the backup settings as current settings 1225 (for example if some temporary settings were saved as current settings, for example to experiment with new PID 1226 gain values)\ 1227 Copying CURRENT_SETTINGS to BACKUP_SETTINGS is useful after a new calibration of the HVPS to save the new 1228 calibration as a set of back-up settings 1229 :param src: the settings to copy (CURRENT_SETTINGS or BACKUP_SETTINGS) 1230 :param dst: the destination settings (CURRENT_SETTINGS or BACKUP_SETTINGS) (destination will be overwritten by 1231 the source 1232 :return: SERIAL_OK or SERIAL_ERROR 1233 """ 1234 if (src == SETTINGS_CURRENT or src == SETTINGS_BACKUP) and (dst == SETTINGS_CURRENT or dst == SETTINGS_BACKUP): 1235 x = self._send_receive_4bytes(SERIAL_CPYST, param1=src, param2=dst) 1236 else: 1237 x = SERIAL_ERROR 1238 1239 if DEBUG: 1240 print('copy_settings({0},{1}) -> {2}'.format(src, dst, x)) 1241 1242 return x
Copies one set of settings to another location:
Copying BACKUP_SETTINGS to CURRENT_SETTINGS is useful to restore the backup settings as current settings (for example if some temporary settings were saved as current settings, for example to experiment with new PID gain values) Copying CURRENT_SETTINGS to BACKUP_SETTINGS is useful after a new calibration of the HVPS to save the new calibration as a set of back-up settings
Parameters
- src: the settings to copy (CURRENT_SETTINGS or BACKUP_SETTINGS)
- dst: the destination settings (CURRENT_SETTINGS or BACKUP_SETTINGS) (destination will be overwritten by the source
Returns
SERIAL_OK or SERIAL_ERROR
1244 def q_mem(self, settings=SETTINGS_CURRENT): # queries the content of the memory 1245 """ 1246 :param settings: which of the two sets of setting sets to query. Either SETTINGS_CURRENT or SETTINGS_Backup 1247 :return: A dictionary with the content of the memory. This is similar to the function save_memory_to_file() 1248 except that it doesn't save the content to a file""" 1249 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1250 memmap = memorymap[fw_string] 1251 dict_mem = {} 1252 1253 z = self._send_4bytes_receive_nbytes(SERIAL_QMEM, settings, param2=0, fstring=memory_string[fw_string]) 1254 bytecount = 0 1255 for i in range(len(memmap)): 1256 length = memmap[i][0] 1257 field = memmap[i][1] 1258 if field == 'Name': 1259 length = 1 1260 y = z[bytecount:bytecount + length] 1261 # if field != 'Padding': 1262 if length == 1: 1263 dict_mem[field] = y[0] 1264 else: 1265 dict_mem[field] = y 1266 bytecount = bytecount + length 1267 1268 dict_mem['Name'] = dict_mem['Name'].decode('UTF-8') 1269 dict_mem['Name'] = dict_mem['Name'].split("\0")[0] 1270 1271 if DEBUG: 1272 print(dict_mem) 1273 return dict_mem
Parameters
- settings: which of the two sets of setting sets to query. Either SETTINGS_CURRENT or SETTINGS_Backup
Returns
A dictionary with the content of the memory. This is similar to the function save_memory_to_file() except that it doesn't save the content to a file
1275 def q_ver(self): # queries the firmware version 1276 """returns the current version of the firmware / hardware running on the board.""" 1277 z = self._send_receive_4bytes(SERIAL_QVER) 1278 Firm_minor = z & 0xFF 1279 Firm_major = (z >> 8) & 0xFF 1280 Hard_minor = (z >> 16) & 0xFF 1281 Hard_major = (z >> 24) 1282 self.firmware = Firm_major + Firm_minor / 10 1283 self.hardware = Hard_major + Hard_minor / 10 1284 if DEBUG: 1285 y = "q_ver -> {0} / {1}".format(self.firmware, self.hardware) 1286 print(y) 1287 return self.firmware, self.hardware
returns the current version of the firmware / hardware running on the board.
1290 def q_conf(self): # queries the configuration 1291 """returns the configuration of the board. 1292 :return: the configuration of the board in the high 2 bytes (CONF_UNCONF, CONF_UNIPOLAR, or CONF_BIPOLAR) 1293 and the PID (product ID) in the low 2 bytes. This is 0x0632 for a hvps-x unit. 1294 """ 1295 1296 z = self._send_receive_4bytes(SERIAL_QCONF) 1297 z_hvps = z & 0xFFFF 1298 z_conf = (z & 0xFFFF0000) >> 16 1299 if DEBUG: 1300 y = "q_conf -> " + hex(z_conf) + " / " + hex(z_hvps) 1301 print(y) 1302 self.conf = z_conf 1303 return z
returns the configuration of the board.
Returns
the configuration of the board in the high 2 bytes (CONF_UNCONF, CONF_UNIPOLAR, or CONF_BIPOLAR) and the PID (product ID) in the low 2 bytes. This is 0x0632 for a hvps-x unit.
1305 def s_conf(self, bipolar): 1306 """Sets the configuration of the board 1307 :param bipolar: boolean. Is board bipolar (True), or unipolar (false) 1308 :return: bipolar: boolean 1309 """ 1310 if bipolar: 1311 param = CONF_BIPOLAR 1312 else: 1313 param = CONF_UNIPOLAR 1314 z = self._send_receive_4bytes(SERIAL_SCONF, param1=param) 1315 if DEBUG: 1316 y = "s_conf -> " + hex(z) 1317 print(y) 1318 if z == SERIAL_ERROR: 1319 print("Error: this configuration is not recognised") 1320 self.conf = z 1321 return z
Sets the configuration of the board
Parameters
- bipolar: boolean. Is board bipolar (True), or unipolar (false)
Returns
bipolar: boolean
1323 def s_vmax(self, x): # sets the maximum voltage rating of the board 1324 """ sets the voltage rating of the hvps-x. Must match the EMCO DC/DC converter rating. 1325 :param x: Voltage rating of hvps-x in Volt 1326 :return: Voltage rating of hvps-x in Volt 1327 """ 1328 1329 x = constrain(x, 0, 6000) 1330 z = self._send_receive_4bytes(SERIAL_SVMAX, param2=x) 1331 if DEBUG: 1332 y = "s_vmax(" + str(x) + ") -> " + str(z) 1333 print(y) 1334 self.vmax = z 1335 return z
sets the voltage rating of the hvps-x. Must match the EMCO DC/DC converter rating.
Parameters
- x: Voltage rating of hvps-x in Volt
Returns
Voltage rating of hvps-x in Volt
1337 def q_vmax(self): # queries the voltage rating of the board 1338 """ Queries the maximal voltage of the board. The returned value is in volts. 1339 :return: board maximal voltage (V) 1340 """ 1341 z = self._send_receive_4bytes(SERIAL_QVMAX) 1342 if DEBUG: 1343 y = "q_vmax -> " + str(z) 1344 print(y) 1345 self.vmax = z 1346 return z
Queries the maximal voltage of the board. The returned value is in volts.
Returns
board maximal voltage (V)
1348 def s_name(self, x): # set the name of the HVPS 1349 """ Sets the name of the HVPS. 1350 :param x: Name of the hvps-x. 11 characters maximum 1351 :return: name accepted by hvps-x 1352 """ 1353 ll = len(x) 1354 if ll <= 12: 1355 x = bytearray(x, 'utf-8') 1356 for i in range(12 - ll): # pad the string with 0s 1357 x = x + b'\0' 1358 self._send_nbytes_receive_4bytes(SERIAL_SNAME, x, typ='uint') 1359 z = self.q_name() 1360 else: 1361 z = 'too long' 1362 if DEBUG: 1363 y = "s_name(" + str(x) + ") -> " + str(z) 1364 print(y) 1365 return z
Sets the name of the HVPS.
Parameters
- x: Name of the hvps-x. 11 characters maximum
Returns
name accepted by hvps-x
1367 def q_name(self): # queries the name of the board 1368 """queries the name of the board 1369 :return: Name of the board 1370 """ 1371 1372 # x = self._send_receive_4bytes(SERIAL_QNAME, param1=0) 1373 # x = x + (self._send_receive_4bytes(SERIAL_QNAME, param1=4) << 32) 1374 # x = x + (self._send_receive_4bytes(SERIAL_QNAME, param1=8) << 64) 1375 # x = x.to_bytes(12, 'little') 1376 1377 z = self._send_4bytes_receive_nbytes(SERIAL_QNAME, param1=0, param2=0, fstring='<12s') 1378 z = z[0].split(b'\x00', 1)[0] # take first (only) element of the tuple and remove the 0s at the end of string 1379 1380 self.name = z 1381 if DEBUG: 1382 y = "q_name -> " + str(z) 1383 print(y) 1384 return z
queries the name of the board
Returns
Name of the board
1390 def q_load_parameters(self): 1391 z = self._send_4bytes_receive_nbytes(SERIAL_QLOAD, param1=0, param2=0, fstring='<4H') 1392 if np.isin(0, np.array(z)): 1393 r = 0 1394 c = 0 1395 else: 1396 dv = self._convert_raw_to_voltage(z[0], VNOW_OUT) 1397 pwm_dv = z[1] 1398 ssv = self._convert_raw_to_voltage(z[2], VNOW_OUT) 1399 pwm_ss = z[3] 1400 current_selection = self.q_current_selection() 1401 imax = self.q_current()[current_selection] 1402 i_step = imax * pwm_dv / 4095 1403 i_ss = imax * pwm_ss / 4095 1404 dv_dt = dv / 5E-3 # dv is measured over 20ms 1405 c = i_step / dv_dt * 1E9 # C in nF 1406 r = ssv / i_ss / 1E6 # R in MOhm 1407 if DEBUG: 1408 print('q_load_parameters() --> {} nF, {} MOhm'.format(c, r)) 1409 return r, c
1411 def check_version_compatibility(self): 1412 self.q_ver() 1413 lib_string = 'Lib-' + LIB_VER 1414 fw_string = 'Fw-{0:.1f}'.format(self.firmware) 1415 hw_string = 'Hw-{0:.1f}'.format(self.hardware) 1416 1417 list_compatible_fw = compatibility_dict[lib_string] 1418 if fw_string in list_compatible_fw: 1419 list_compatible_hw = compatibility_dict[fw_string] 1420 if hw_string not in list_compatible_hw: 1421 self.err |= ERR_FIRM 1422 else: 1423 self.err |= ERR_FIRM
1437 def s_trace_rate(self, rate=5): 1438 """Sets the rate at which trace points are stored in memory 1439 :param rate: time in ms between point acquisition. This is limited by the time at which the hvps-x is 1440 calculating the voltages; currently evey 5ms. 1441 :return: the rate accepted by the hvps-x""" 1442 1443 rate = int(rate) 1444 if rate < 2: 1445 rate = 2 1446 x = self._send_receive_4bytes(SERIAL_STRRATE, param1=0, param2=rate, typ='uint') 1447 x = x / 10 # the function returns the number of ticks of the counter, which counts at 10kHz. 1448 if DEBUG: 1449 y = "s_trace_rate({0}) -> {1}".format(rate, x) 1450 print(y) 1451 return x
Sets the rate at which trace points are stored in memory
Parameters
- rate: time in ms between point acquisition. This is limited by the time at which the hvps-x is calculating the voltages; currently evey 5ms.
Returns
the rate accepted by the hvps-x
1453 def q_trace_rate(self): 1454 """queries the rate at which trace points are stored in memory 1455 :param 1456 :return the rate at which trace points are stored in memory (in ms)""" 1457 1458 x = self._send_receive_4bytes(SERIAL_QTRRATE, param1=0, param2=0, typ='uint') 1459 x = x / 10 # the function returns the number of ticks of the counter, which counts at 10kHz. 1460 if DEBUG: 1461 y = "q_trace_rate() -> {0}".format(x) 1462 print(y) 1463 return x
queries the rate at which trace points are stored in memory :param :return the rate at which trace points are stored in memory (in ms)
1476def list_hvps(list_all=False): 1477 """lists the hvps-x connected to PC in a dictionary.The key is the name of the HVPS, and the parameter is 1478 the associated serial port 1479 :param list_all: if True list all connected HVPS. 1480 if list_all is false, it will only list device that are configured. Using True enable to list unconfigured 1481 devices and give the opportunity to configure them 1482 :return: dictionary of connected hvps-x""" 1483 1484 hvpsx_ports = [ # creates a list with all STM32 VCP devices connected 1485 p.device 1486 for p in serial.tools.list_ports.comports() 1487 if (p.pid == 0x5740 and p.vid == 0x483) 1488 ] 1489 dict_hvps = {} 1490 for port in hvpsx_ports: 1491 dev = HVPS(port, init=False) # Attempts to connect to HVPS but without initialisation 1492 if dev.is_hvpsx: 1493 if dev.conf != CONF_UNCONF or list_all: 1494 dict_hvps[dev.name.decode()] = port # add an entry in the dictionary with the name of the 1495 # HVPS and the port 1496 dev.close(zero=False) 1497 1498 return dict_hvps
lists the hvps-x connected to PC in a dictionary.The key is the name of the HVPS, and the parameter is the associated serial port
Parameters
- list_all: if True list all connected HVPS. if list_all is false, it will only list device that are configured. Using True enable to list unconfigured devices and give the opportunity to configure them
Returns
dictionary of connected hvps-x
1501def connect_to_hvps(unconf=False): 1502 """Scan for connected hvps-x and connects to it if a single hvps-x is found. User can choose to which hvps-x to 1503 connect if more than one is detected. 1504 :param unconf: True/False. If true will connect to an unconfigured hvps-x. If False (default) only connects to a 1505 configured device 1506 :return: a HVPS object (or None if could not connect to a valid hvps-x 1507 If more than 1 hvps-x is connected to the computed, a list of names is displayed and the user must choose which one 1508 they want to connect to""" 1509 cont = False # flag to decide whether to continue the program or close the COM port 1510 dev = None 1511 1512 ports = list_hvps(list_all=unconf) 1513 keys = list(ports.keys()) 1514 if len(ports) == 0: 1515 print("No HVPS connected to the device. Terminating program") 1516 exit() 1517 elif len(ports) == 1: 1518 dev = HVPS(ports[keys[0]]) # connects to the only available HVPS 1519 else: 1520 print("List of connected HVPS:") 1521 for i in range(len(ports)): 1522 print(str(i) + ": " + keys[i]) 1523 print("Enter the number of the board you want to connect to") 1524 connect_to = 0 1525 try: 1526 connect_to = int(input()) 1527 except ValueError: 1528 print("Invalid entry. Terminating program") 1529 exit() 1530 if connect_to >= len(ports): 1531 print("Invalid entry. Terminating program") 1532 exit() 1533 else: 1534 dev = HVPS(ports[keys[connect_to]]) 1535 error = dev.err 1536 if error & ERR_PORT: 1537 print("Cannot open COM port. Probably busy with another process. Terminating program") 1538 if error & ERR_COM: 1539 print("Cannot communicate with hvps-x. Has firmware been flashed?. Terminating program") 1540 if error & ERR_TYPE: 1541 print("Device connected this port is not recognised. Terminating program") 1542 if error & ERR_CONF: 1543 print("This hvps-x is not configured. Please configure unit before using it. " 1544 "Terminating program.") 1545 cont = True 1546 elif error & ERR_FIRM: 1547 print("Warning: your hvps-x library is not optimal for the firmware or the firmware is not optimal for your " 1548 "hardware. Refer to the website for a compatibility table") 1549 cont = True 1550 if error == 0: 1551 cont = True 1552 if cont: 1553 return dev 1554 else: 1555 return None
Scan for connected hvps-x and connects to it if a single hvps-x is found. User can choose to which hvps-x to connect if more than one is detected.
Parameters
- unconf: True/False. If true will connect to an unconfigured hvps-x. If False (default) only connects to a configured device
Returns
a HVPS object (or None if could not connect to a valid hvps-x If more than 1 hvps-x is connected to the computed, a list of names is displayed and the user must choose which one they want to connect to
1558def configure(dev): 1559 """Script to use to for the initial configuration (or re-configuration) of an hvps-x. Follow prompt on the console. 1560 :param dev: an HVPS object 1561 :return: nothing 1562 """ 1563 if dev != -1: 1564 config = configparser.RawConfigParser() 1565 config.read('config.ini') 1566 1567 if dev.conf != CONF_UNCONF: 1568 answer = input("It appears this hvps-x is already configured. (C)ontinue with configuration or (A)bort?: ") 1569 if answer == 'a' or answer == 'A': 1570 print("exiting configuration") 1571 dev.close() 1572 exit(-1) 1573 dev.s_conf(bipolar=False) # This is now a constant as there is no bipolar configuration 1574 answer = input("Enter the version of your hvps-x PCB (hardware version), as printed on the PCB " 1575 "(format: X.Y; e.g. 1.2): ") 1576 hw_list = answer.split('.') 1577 hw = int(hw_list[0]) + int(hw_list[1]) / 10 1578 if len(hw_list) != 2: 1579 print("Error, the format of the hardware string must be X.Y, e.g. 1.2. Exiting") 1580 exit(0) 1581 dev.set_hardware_version(int(hw_list[0]), int(hw_list[1])) 1582 print("The current name of the hvps-x is " + dev.q_name().decode("utf-8")) 1583 answer = input("Do you want to set/change the name of this hvps-x? Y/N?: ") 1584 if answer == 'y' or answer == 'Y': 1585 name = input("Enter hvps-x name (12 char max): ") 1586 if len(name) > 12: 1587 print("Name too long. Ignoring name change") 1588 else: 1589 dev.s_name(name) 1590 else: 1591 print("Leaving name unchanged") 1592 print("\nThe current maximal voltage rating of this HVPS is " + str(dev.q_vmax()) + " V") 1593 answer = input("Do you want to set the maximal voltage of the HVPS? Y/N: ") 1594 if answer == 'y' or answer == 'Y': 1595 answer = input("Enter the maximal voltage of the hvps-x in Volt. It must match the voltage rating of the " 1596 "Emco DC/DC converter: ") 1597 print("Setting Vmax to " + answer + "V") 1598 dev.s_vmax(int(answer)) 1599 else: 1600 print("Leaving Vmax unchanged") 1601 vmax = dev.q_vmax() 1602 conf_section = 'hvps-x-' + str(vmax) 1603 if config.has_section(conf_section): 1604 print("Default values for this voltage found in configuration file\n") 1605 answer = input("Do you want to replace the voltage calibration values stored in the hvps-x by the one from the config file " 1606 "Y/N (choose Y if configuring a new hvps-x): ") 1607 if answer == 'Y' or answer == 'y': 1608 c0p = config.getfloat(conf_section, 'C0P') 1609 c1p = config.getfloat(conf_section, 'C1P') 1610 c2p = config.getfloat(conf_section, 'C2P') 1611 c0o = config.getfloat(conf_section, 'C0O') 1612 c1o = config.getfloat(conf_section, 'C1O') 1613 c2o = config.getfloat(conf_section, 'C2O') 1614 1615 dev.s_cal(c0p, CAL_C0P) 1616 dev.s_cal(c1p, CAL_C1P) 1617 dev.s_cal(c2p, CAL_C2P) 1618 dev.s_cal(c0o, CAL_C0O) 1619 dev.s_cal(c1o, CAL_C1O) 1620 dev.s_cal(c2o, CAL_C2O) 1621 print("Voltage calibration values set...\n") 1622 answer = input( 1623 "Reset PID values to their default values? Y/N (choose Y when configuring a board for the first " 1624 "time): ") 1625 if answer == 'Y' or answer == 'y': 1626 kpp = config.getfloat(conf_section, 'KPP') 1627 kip = config.getfloat(conf_section, 'KIP') 1628 kdp = config.getfloat(conf_section, 'KDP') 1629 1630 dev.s_pid(kpp, PID_KPP) 1631 dev.s_pid(kip, PID_KIP) 1632 dev.s_pid(kdp, PID_KDP) 1633 print("PID values set...\n") 1634 1635 if hw >= 1.4: 1636 answer = input("Do you want to set the output current values? Y/N (if No, then default values will " 1637 "be used): ") 1638 if answer == 'Y' or answer == 'y': 1639 inorm = input("Normal current mode: Enter the output current in uA (e.g. 120): ") 1640 ihigh = input("High current mode: Enter the output current in uA (e.g. 120): ") 1641 inorm = 1e-6 * float(inorm) 1642 ihigh = 1e-6 * float(ihigh) 1643 else: 1644 inorm = config.getfloat(conf_section, 'INORM') 1645 ihigh = config.getfloat(conf_section, 'IHIGH') 1646 dev.s_current(inorm, ihigh) 1647 print("Current values set...\n") 1648 curr_sel = input("Position of the output current selection jumper:\n1: 3V3 (Norm)\n2: 5V (High)?\n" 1649 "Choose 1/2: ") 1650 if curr_sel == '2': 1651 curr_sel = 1 1652 else: 1653 curr_sel = 0 1654 dev.s_current_selection(curr_sel) 1655 print("Current selection jumper position set...\n") 1656 1657 print("hvps-x configured. It is recommended to perform a calibration of the voltage readout circuit!") 1658 print("Saving information to hvps-x memory. Backup-settings and default settings") 1659 dev.s_settings(SETTINGS_BACKUP) 1660 dev.save() 1661 dev.s_settings(SETTINGS_CURRENT) 1662 dev.save() 1663 else: 1664 print("Cannot find a section in the config file for a {0} V hvps-x. You need to enter values for voltage " 1665 "calibration and PID manually ".format(vmax)) 1666 dev.save() # saves previous parameters anyway
Script to use to for the initial configuration (or re-configuration) of an hvps-x. Follow prompt on the console.
Parameters
- dev: an HVPS object
Returns
nothing
1669def check_functionality(dev): 1670 """This script can be used after configuration to test the hvps-x functionality. Follow prompt on the console 1671 1672 :param dev: an HVPS object 1673 :return: nothing 1674 """ 1675 1676 print("Important:\n" 1677 "This function is experimental and the threshold values are based on a limited number of assembled boards." 1678 "In particular:\n" 1679 "- If the test fails with a value very close to the threshold value, then your board is most likely fine.\n" 1680 "- This version of the script must be used with the bill of material revision 1. If you assembled your" 1681 "board with the original bill of material, this script will not return the correct output. You can use the" 1682 "script included with the library v1.0. Otherwise the website describe how the high-voltage functionality " 1683 "can be manually tested.\n" 1684 "- If unsure, contact Peta-pico-Voltron with test results (be sure to specify hardware version of PCB).\n\n") 1685 if not dev.q_kill(): 1686 input("Place HV safety switch (S1) on position 0 and press any key.") 1687 if not dev.q_kill(): 1688 input("Switch S1 appears not to be working. Check Switch S1 functionality and restart the test") 1689 dev.close() 1690 exit() 1691 print("During the course of this test, a moderate (~20% of full scale) voltage will be applied to the output of\n " 1692 "the hvps-x. Make sure that it nothing is connected to the output and that you are not touching the\n " 1693 "instrument\n") 1694 print("Voltage set point 0V. Output off") 1695 dev.s_vset(0, DCDC_BOTH) 1696 dev.s_sw_mode(SWMODE_OFF) 1697 dev.s_v_mode(VMODE_O) 1698 dev.s_sw_src(SWSRC_TMR) 1699 1700 print("---Testing HV enable switch (S1) ---") 1701 input("Place HV enable switch (S1) on position 1 and press any key") 1702 if dev.q_kill(): 1703 print("S1 switch still reads off state. Check functionality of switch S1.\n Test FAILED. Exiting script...") 1704 dev.close() 1705 exit() 1706 else: 1707 print("***PASS\n") 1708 1709 print("---Testing Voltage monitoring at set point 0 ---") 1710 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1711 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1712 if vnow_p < 25: 1713 print("This is within the tolerance range V < 25. Continuing...") 1714 1715 else: 1716 print("This is outside of the tolerance range V < 25. Something appears to be wrong with the positive DCDC " 1717 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1718 dev.close() 1719 exit() 1720 1721 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1722 if vnow_o < 25: 1723 print("This is within the tolerance range V < 25. Continuing...") 1724 else: 1725 print("This is outside of the tolerance range V < 25. Something appears to be wrong with the output " 1726 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1727 dev.close() 1728 exit() 1729 print("***PASS\n") 1730 1731 print("---Testing Voltage monitoring at set point 20% FS Output off ---") 1732 print("Applying 20% PWM value") 1733 dev.s_vset(int(0.2 * dev.vmax), DCDC_POS) 1734 sleep(0.2) 1735 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1736 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1737 if vnow_p > 900: 1738 print("This is within the tolerance range V > 900. Continuing...") 1739 else: 1740 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the positive DCDC " 1741 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1742 dev.close() 1743 exit() 1744 1745 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1746 if vnow_o < 100: 1747 print("This is within the tolerance range V < 100. Continuing...") 1748 else: 1749 print("This is outside of the tolerance range V < 100. Something appears to be wrong with the output " 1750 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1751 dev.close() 1752 exit() 1753 print("***PASS\n") 1754 1755 print("---Testing Voltage monitoring at set point 20% FS Output LOW ---") 1756 print("Output LOW") 1757 dev.s_sw_mode(SWMODE_LOW) 1758 sleep(0.2) 1759 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1760 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1761 if vnow_p > 900: 1762 print("This is within the tolerance range V > 900. Continuing...") 1763 else: 1764 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the positive DCDC " 1765 "voltage monitoring circuit.\n Test Failed. Exiting script...") 1766 dev.close() 1767 exit() 1768 1769 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1770 if vnow_o < 25: 1771 print("This is within the tolerance range V < 25. Continuing...") 1772 else: 1773 print("This is outside of the tolerance range V < 25. Something appears to be wrong with the output " 1774 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1775 dev.close() 1776 exit() 1777 print("***PASS\n") 1778 1779 print("---Testing Voltage monitoring at set point 20% FS Output HIGH ---") 1780 print("Output HIGH") 1781 dev.s_sw_mode(SWMODE_HIGH) 1782 sleep(0.2) 1783 vnow_p, vnow_o = dev.q_vnow_raw(VNOW_POS) 1784 print("Raw reading at the output of the converter is {0} ".format(vnow_p)) 1785 if vnow_p > 900: 1786 print("This is within the tolerance range V > 900. Continuing...") 1787 else: 1788 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the positive DCDC " 1789 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1790 dev.close() 1791 exit() 1792 1793 print("Raw reading at the output of optocouplers {0} ".format(vnow_o)) 1794 if vnow_o > 900: 1795 print("This is within the tolerance range V > 900. Continuing...") 1796 else: 1797 print("This is outside of the tolerance range V > 900. Something appears to be wrong with the output " 1798 "voltage monitoring circuit.\n Test FAILED. Exiting script...") 1799 dev.close() 1800 exit() 1801 print("***PASS\n") 1802 print("***hvps-x passes all tests***") 1803 1804 dev.s_vset(0, DCDC_BOTH) 1805 dev.close()
This script can be used after configuration to test the hvps-x functionality. Follow prompt on the console
Parameters
- dev: an HVPS object
Returns
nothing
1808def main(): 1809 dev = connect_to_hvps(unconf=True) 1810 if dev: 1811 print("Name: {0}".format(dev.q_name())) 1812 print("Vmax: {0} V".format(dev.vmax)) 1813 print("\n Your choices:") 1814 print("[1] Initial configuration (to be performed after assembling the low-voltage components)") 1815 print("[2] Basic functionality test (to be performed after assembling the high-voltage components)") 1816 print("[q] quit\n") 1817 answer = input("Your choice: ") 1818 if answer == '1': 1819 configure(dev) 1820 elif answer == '2': 1821 check_functionality(dev) 1822 dev.close(zero=False)
1824def constrain(val, min_val, max_val): 1825 """A simple implementation to constrain a value between two boundaries""" 1826 return min(max_val, max(min_val, val))
A simple implementation to constrain a value between two boundaries