Skip to content
Snippets Groups Projects
Commit ac0e9673 authored by Elisabetta Pianori's avatar Elisabetta Pianori
Browse files

Merge branch 'IsegSHR20xx' into 'main'

Implementation of iseg power supplies and in particular iseg SHRs

See merge request !321
parents 9dd93390 6b64bfa1
No related branches found
No related tags found
1 merge request!321Implementation of iseg power supplies and in particular iseg SHRs
Pipeline #7443091 failed
#ifndef CHECKSUMEXCEPTION_H
#define CHECKSUMEXCEPTION_H
#include <stdint.h>
#include <iostream>
#include "ComException.h"
......
#ifndef OUTOFRANGEEXCEPTION_H
#define OUTOFRANGEEXCEPTION_H
#include <stdint.h>
#include <iostream>
#include "ComException.h"
......
......@@ -16,6 +16,8 @@ target_sources(PS
DT5471NPs.cpp
DT5472NPs.cpp
DT8033NPs.cpp
IsegPs.cpp
IsegSHR20xxPs.cpp
SorensenPs.cpp
RigolDP832.cpp
Keithley24XX.cpp
......
......@@ -11,4 +11,4 @@
REGISTER_POWERSUPPLY(DT5471NPs)
DT5471NPs::DT5471NPs(const std::string& name)
: DT54xxPs(name, {"DT5471"}, Polarity::Negative, 51e-6) {}
: DT54xxPs(name, {"DT5471"}, IPowerSupply::Polarity::Negative, 51e-6) {}
......@@ -11,4 +11,4 @@
REGISTER_POWERSUPPLY(DT5472NPs)
DT5472NPs::DT5472NPs(const std::string& name)
: DT54xxPs(name, {"DT5472"}, Polarity::Negative, 105e-6) {}
: DT54xxPs(name, {"DT5472"}, IPowerSupply::Polarity::Negative, 105e-6) {}
......@@ -12,8 +12,8 @@
REGISTER_POWERSUPPLY(DT54xxPs)
DT54xxPs::DT54xxPs(const std::string& name,
const std::vector<std::string>& models, Polarity output,
double imaxl)
const std::vector<std::string>& models,
IPowerSupply::Polarity output, double imaxl)
: IPowerSupply(name, models), m_output(output), m_imaxl(imaxl) {}
void DT54xxPs::reset() {
......@@ -38,7 +38,7 @@ bool DT54xxPs::ping() {
void DT54xxPs::checkCompatibilityList() {
IPowerSupply::checkCompatibilityList();
Polarity pol = polarity();
IPowerSupply::Polarity pol = polarity();
if (pol != m_output) throw std::runtime_error("Wrong polarity detected");
}
......@@ -160,16 +160,16 @@ uint16_t DT54xxPs::status(unsigned channel) {
return std::stoi(command("MON", "STAT")) & 0xFFFF;
}
DT54xxPs::Polarity DT54xxPs::polarity(unsigned channel) {
IPowerSupply::Polarity DT54xxPs::polarity(unsigned channel) {
if (channel != 1)
throw std::runtime_error(
"Set the channel to 1 for single channel power-supply");
std::string polstr = command("MON", "POLARITY");
if (polstr == "-")
return Polarity::Negative;
return IPowerSupply::Polarity::Negative;
else
return Polarity::Positive;
return IPowerSupply::Polarity::Positive;
}
void DT54xxPs::setIMonRange(IMonRange range, unsigned channel) {
......@@ -239,11 +239,11 @@ std::string DT54xxPs::command(const std::string& cmd, const std::string& par,
}
double DT54xxPs::checkPolarity(double input) {
if (m_output == Polarity::Negative && input > 0)
if (m_output == IPowerSupply::Polarity::Negative && input > 0)
throw std::runtime_error(
"Specified positive output value for a power supply that only "
"supports negative output.");
if (m_output == Polarity::Positive && input < 0)
if (m_output == IPowerSupply::Polarity::Positive && input < 0)
throw std::runtime_error(
"Specified negative output value for a power supply that only "
"supports positive output.");
......@@ -252,5 +252,5 @@ double DT54xxPs::checkPolarity(double input) {
}
double DT54xxPs::convertPolarity(double value) {
return (m_output == Polarity::Negative) ? -value : value;
return (m_output == IPowerSupply::Polarity::Negative) ? -value : value;
}
......@@ -48,11 +48,6 @@ class DT54xxPs : public IPowerSupply {
CalError = (1 << 13) // 1 : Calibration Error
};
/**
* Polarity of the power supply output
*/
enum Polarity { Positive, Negative };
/**
* Range of the current monitor
*/
......@@ -69,7 +64,8 @@ class DT54xxPs : public IPowerSupply {
*/
DT54xxPs(const std::string& name,
const std::vector<std::string>& models = {},
Polarity output = Polarity::Positive, double imaxl = 105e-6);
IPowerSupply::Polarity output = IPowerSupply::Polarity::Positive,
double imaxl = 105e-6);
~DT54xxPs() = default;
/** \name Communication
......@@ -198,7 +194,7 @@ class DT54xxPs : public IPowerSupply {
*
* @return polarity of the channel
*/
Polarity polarity(unsigned channel = 1);
IPowerSupply::Polarity polarity(unsigned channel = 1);
/**
* Set the current monitoring range
......@@ -271,7 +267,7 @@ class DT54xxPs : public IPowerSupply {
//! \brief Specify whether power supply outputs a negative voltage (N vs P
//! model)
Polarity m_output = Polarity::Positive;
IPowerSupply::Polarity m_output = IPowerSupply::Polarity::Positive;
//! \brief Specify maximum current [A] for low IMon range
double m_imaxl = 105e-6;
......
......@@ -96,12 +96,12 @@ uint16_t DT8033NPs::status(unsigned channel) {
return std::stoi(command("MON", "STAT", channel)) & 0xFFFF;
}
DT8033NPs::Polarity DT8033NPs::polarity(unsigned channel) {
IPowerSupply::Polarity DT8033NPs::polarity(unsigned channel) {
std::string polstr = command("MON", "POLARITY", channel);
if (polstr == "-")
return Polarity::Negative;
return IPowerSupply::Polarity::Negative;
else
return Polarity::Positive;
return IPowerSupply::Polarity::Positive;
}
void DT8033NPs::setIMonRange(IMonRange range, unsigned channel) {
......@@ -162,11 +162,11 @@ std::string DT8033NPs::command(const std::string& cmd, const std::string& par,
}
double DT8033NPs::checkPolarity(double input) {
if (m_output == Polarity::Negative && input > 0)
if (m_output == IPowerSupply::Polarity::Negative && input > 0)
throw std::runtime_error(
"Specified positive output value for a power supply that only "
"supports negative output.");
if (m_output == Polarity::Positive && input < 0)
if (m_output == IPowerSupply::Polarity::Positive && input < 0)
throw std::runtime_error(
"Specified negative output value for a power supply that only "
"supports positive output.");
......
......@@ -47,11 +47,6 @@ class DT8033NPs : public IPowerSupply {
CalError = (1 << 13) // 1 : Calibration Error
};
/**
* Polarity of the power supply output
*/
enum Polarity { Positive, Negative };
/**
* Range of the current monitor
*/
......@@ -195,7 +190,7 @@ class DT8033NPs : public IPowerSupply {
*
* @return polarity of the channel
*/
Polarity polarity(unsigned channel = 1);
IPowerSupply::Polarity polarity(unsigned channel = 1);
/**
* Set the current monitoring range
......@@ -256,7 +251,7 @@ class DT8033NPs : public IPowerSupply {
//! \brief Specify whether power supply outputs a negative voltage (N vs P
//! model)
Polarity m_output = Polarity::Negative;
IPowerSupply::Polarity m_output = IPowerSupply::Polarity::Negative;
};
#endif // DT8033NPS_H
......@@ -2,6 +2,7 @@
#define IPOWERSUPPLY_H
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string>
#include "ICom.h"
......@@ -222,6 +223,21 @@ class IPowerSupply {
/** @} */
/** An exception to be thrown when an operation is executed which
* would trigger a polarity change while to power supply is turned on.
* Implemented in order for application to explicitly catch these types
* of errors and implement fault handling.
*/
struct polarity_error : public std::runtime_error {
polarity_error(std::string const& what = "")
: std::runtime_error(what) {}
};
/**
* Polarity of the power supply output
*/
enum class Polarity : int { Positive, Negative };
protected:
/** Communication */
std::shared_ptr<ICom> m_com = nullptr;
......
#include "IsegPs.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <thread>
#include "Logger.h"
#include "ScopeLock.h"
#include "StringUtils.h"
#include "TextSerialCom.h"
// Register power supply
#include "PowerSupplyRegistry.h"
REGISTER_POWERSUPPLY(IsegPs)
IsegPs::IsegPs(const std::string& name, std::vector<std::string> models,
unsigned maxChannels)
: IPowerSupply(name, models), m_maxChannels(maxChannels) {}
bool IsegPs::ping() {
std::string result = sendreceive("*IDN?");
return !result.empty();
}
void IsegPs::reset() {
send("*RST");
if (!ping()) throw std::runtime_error("No communication after reset.");
}
std::string IsegPs::identify() {
std::string idn = sendreceive("*IDN?");
return idn;
}
void IsegPs::turnOn(unsigned channel) { send(":VOLT ON,", channel); }
void IsegPs::turnOff(unsigned channel) { send(":VOLT OFF,", channel); }
void IsegPs::setCurrentLevel(double cur, unsigned channel) {
send(":CURR " + std::to_string(cur) + ",", channel);
}
double IsegPs::getCurrentLevel(unsigned channel) {
std::string const recv = sendreceive(":READ:CURR:NOM?", channel);
return std::stod(utils::trim_last_char(recv)); // remove the A from the end
}
void IsegPs::setCurrentProtect(double maxcur, unsigned channel) {
logger(logWARNING) << "setCurrentProtect() not implemented for this PS. "
"Manual adjustment at PS required.";
}
double IsegPs::getCurrentProtect(unsigned channel) {
std::string const recv = sendreceive(":READ:CURR:LIM?", channel);
return std::stod(utils::trim_last_char(recv));
}
double IsegPs::measureCurrent(unsigned channel) {
std::string const recv = sendreceive("MEAS:CURR?", channel);
return std::stod(utils::trim_last_char(recv)); // remove the A from the end
}
void IsegPs::setVoltageLevel(double volt, unsigned channel) {
auto channelOn = isOn(channel);
auto PSPolarity = getPolarity(channel); // the current polarity set
auto ReqPolarityPos =
!std::signbit(volt); // is requested volt not negative
if (!ReqPolarityPos && (PSPolarity == IPowerSupply::Polarity::Positive)) {
if (channelOn) {
throw IPowerSupply::polarity_error(
"Attempting to switch to negative from positive polarity while "
"channel is on");
} else {
setPolarity(IPowerSupply::Polarity::Negative, channel);
// need to wait before sending the new voltage, if no wait, the
// voltage will not be set correctly
std::this_thread::sleep_for(std::chrono::seconds(1));
}
} else if (ReqPolarityPos &&
(PSPolarity == IPowerSupply::Polarity::Negative)) {
if (channelOn) {
throw IPowerSupply::polarity_error(
"Attempting to switch to positive from negative polarity while "
"channel is on");
} else {
setPolarity(IPowerSupply::Polarity::Positive, channel);
// need to wait before sending the new voltage, if no wait, the
// voltage will not be set correctly
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
send(":VOLTAGE " + std::to_string(volt) + ",", channel);
}
double IsegPs::getVoltageLevel(unsigned channel) {
std::string const recv = sendreceive(":READ:VOLTAGE?", channel);
return std::stod(utils::trim_last_char(recv)); // remove the V from the end
}
void IsegPs::setVoltageProtect(double maxvolt, unsigned channel) {
logger(logWARNING) << "setVoltageProtect() not implemented for this PS. "
"Manual adjustment at PS required.";
}
double IsegPs::getVoltageProtect(unsigned channel) {
std::string const recv = sendreceive(":READ:VOLTAGE:LIM?", channel);
return std::stod(utils::trim_last_char(recv)); // remove the V from the end
}
double IsegPs::measureVoltage(unsigned channel) {
std::string const recv = sendreceive(":MEAS:VOLT?", channel);
return std::stod(utils::trim_last_char(recv)); // remove the V from the end
}
void IsegPs::send(const std::string& cmd) {
std::string opcreply = sendreceive(cmd + ";*OPC?");
utils::rtrim(opcreply);
if (opcreply != "1")
throw std::runtime_error("IsegPs::send: *OPC? check failed");
}
void IsegPs::send(const std::string& cmd, unsigned channel) {
if (m_maxChannels > 0) { // In-range channel check
if (channel > m_maxChannels)
throw std::runtime_error("Invalid channel: " +
std::to_string(channel));
}
std::string retCmd = cmd;
if (m_maxChannels != 1) retCmd += " (@" + std::to_string(channel) + ")";
send(retCmd);
}
IPowerSupply::Polarity IsegPs::getPolarity(unsigned channel) {
if (m_maxChannels > 0) { // In-range channel check
if (channel > m_maxChannels)
throw std::runtime_error("Invalid channel: " +
std::to_string(channel));
}
std::string cmd = ":CONF:OUTP:POL? (@" + std::to_string(channel) + ")";
std::string recv = sendreceive(cmd);
if (recv == "p")
return IPowerSupply::Polarity::Positive;
else if (recv == "n")
return IPowerSupply::Polarity::Negative;
else
throw std::runtime_error(
"IsegPs::getPolarityPos: could not determine polarity");
}
void IsegPs::setPolarity(IPowerSupply::Polarity polarity, unsigned channel) {
if (m_maxChannels > 0) { // In-range channel check
if (channel > m_maxChannels)
throw std::runtime_error("Invalid channel: " +
std::to_string(channel));
}
turnOff(channel);
std::string cmd;
if (polarity == IPowerSupply::Polarity::Positive)
cmd = ":CONF:OUTP:POL p, (@" + std::to_string(channel) + ")";
else if (polarity == IPowerSupply::Polarity::Negative)
cmd = ":CONF:OUTP:POL n, (@" + std::to_string(channel) + ")";
else
throw std::runtime_error("Invalid polarity");
send(cmd);
}
bool IsegPs::isOn(unsigned channel) {
if (m_maxChannels > 0) { // In-range channel check
if (channel > m_maxChannels)
throw std::runtime_error("Invalid channel: " +
std::to_string(channel));
}
std::string const cmd = ":READ:VOLT:ON? (@" + std::to_string(channel) + ")";
auto recv = sendreceive(cmd);
if (recv == "1")
return true;
else if (recv == "0")
return false;
else
throw std::runtime_error(
"IsegPs::isOn: could not determine if channel is on");
}
std::string IsegPs::sendreceive(const std::string& cmd) {
ScopeLock lock(m_com);
auto rec = m_com->sendreceive(cmd);
// In case the IsegSHR20xxPs uses a serial communication via USB/Serial its
// responses are echoed to validate communication integrity in these cases,
// the result looks like: <CMD><Term><RES><Term>, the ultimate termination
// is already stripped by the Com implementation, here we need to split the
// <CMD> and <RES> and validate that the read back <CMD> is the one we sent
auto text_serial_port = dynamic_cast<TextSerialCom*>(m_com.get());
if (text_serial_port) {
auto term_seq = text_serial_port->returnTermination();
auto rec_split = utils::split(rec, term_seq);
if (cmd != rec_split.at(0))
throw std::runtime_error(
"IsegPs::sendreceive: readback of command failed!");
if (rec_split.size() != 2)
throw std::runtime_error(
"IsegPs::sendreceive: received invalid reaback!");
return rec_split.at(1);
} else {
return rec;
}
}
std::string IsegPs::sendreceive(const std::string& cmd, unsigned channel) {
if (m_maxChannels > 0) { // In-range channel check
if (channel > m_maxChannels)
throw std::runtime_error("Invalid channel: " +
std::to_string(channel));
}
std::string retCmd = cmd;
if (m_maxChannels != 1) retCmd += " (@" + std::to_string(channel) + ")";
return sendreceive(retCmd);
}
#ifndef ISEGPS_H
#define ISEGPS_H
#include <chrono>
#include <memory>
#include <string>
#include "IPowerSupply.h"
/**
* Base implemetation for iseg power supplies that share
* a very similar command set.
*
* Options are possible to add additional for error checking on
* - channel checking (maximum number of channels, 0 means no check is
* performed)
*/
class IsegPs : public IPowerSupply {
public:
/**
* @param name Name of the power supply
* @param models List of supported models (empty means no check is
* performed)
* @param maxChannels Maximum number of channels in the power supply (0
* means no maximum)
*/
IsegPs(const std::string& name, std::vector<std::string> models = {},
unsigned maxChannels = 0);
~IsegPs() = default;
/** \name Communication
* @{
*/
virtual bool ping();
/** @} */
/** \name Power Supply Control
* @{
/**
* Returns the model identifier of the PS connected
*
*/
virtual std::string identify();
virtual void reset();
/** Turn on specific channel
*
* @param channel the channel to turn on
*/
virtual void turnOn(unsigned channel);
/** Turn off specific channel
*
* @param channel the channel to turn off
*/
virtual void turnOff(unsigned channel);
/**
* Returns true if the channel's output is on, and false otherwise
*
* @param channel the channel to query on state
*/
virtual bool isOn(unsigned channel);
/** @} */
/** \name Current Control and Measurement
* @{
*/
virtual void setCurrentLevel(double cur, unsigned channel = 0);
virtual double getCurrentLevel(unsigned channel = 0);
virtual void setCurrentProtect(double maxcur, unsigned channel = 0);
virtual double getCurrentProtect(unsigned channel = 0);
virtual double measureCurrent(unsigned channel = 0);
/** @} */
/** \name Voltage Control and Measurement
* @{
*/
/**
* Will set the voltage to the requested value for a given channel. Will
* throw an exception if the channel is powered on and a polarity switch is
* requested. Takes the signedness of the passed argument in order to
* determine the polarity, i.e. -0 and 0 produce a different polarity
*
* @param volt the voltage to set
* @param channel the channel to set the voltage
*/
virtual void setVoltageLevel(double volt, unsigned channel = 0);
virtual double getVoltageLevel(unsigned channel = 0);
virtual void setVoltageProtect(double maxvolt, unsigned channel = 0);
virtual double getVoltageProtect(unsigned channel = 0);
virtual double measureVoltage(unsigned channel = 0);
/** @} */
/** \name Polarity Control and Measurement
* @{
*/
virtual IPowerSupply::Polarity getPolarity(unsigned channel = 0);
virtual void setPolarity(IPowerSupply::Polarity polarity,
unsigned channel = 0);
/** @} */
protected:
//\brief Send power supply command and wait for operation complete.
/**
* Sends `cmd` to the power supply followed up `*OPC?`. Then blocks until
* a response is received. This prevents the program from terminating
* before the program is processed.
*
* An error is thrown if no response to `*OPC?` is seen.
*
* \param cmd Power supply command to transmit.
*/
void send(const std::string& cmd);
//! \brief Add channel set to power supply send
/**
* Adds `" (@"+channel+")"` to the end of the actual command. This is
* done without injecting `*OPC?`.
*
* The rest is achieved using `IsegPs::send(constd std::string& cmd)`.
*
* \param cmd Power supply command to transmit.
* \param channel Target channel
*/
void send(const std::string& cmd, unsigned channel);
//\brief Send power supply command and return response.
/**
* Wrapper around `ICom::sendreceive`.
*
* \param cmd Power supply command to transmit.
*
* \return Parameter response.
*/
std::string sendreceive(const std::string& cmd);
//! \brief Add channel set to power supply sendreceive
/**
* Adds `" (@"+channel+")"` to the end of the actual command. This is
* done without injecting `*OPC?`.
*
* The rest is achieved using `IsegPs::sendreceive(constd std::string&
* cmd)`.
*
* \param cmd Power supply command to transmit.
* \param channel Target channel
*
* \return Response for `cmd`
*/
std::string sendreceive(const std::string& cmd, unsigned channel);
private:
/** PS limitations @{ */
//! Maximum number of channels, 0 means unlimited
uint32_t m_maxChannels = 0;
};
#endif
#include "IsegSHR20xxPs.h"
#include "StringUtils.h"
#include "TextSerialCom.h"
// Register power supply
#include "PowerSupplyRegistry.h"
REGISTER_POWERSUPPLY(IsegSHR20xxPs)
IsegSHR20xxPs::IsegSHR20xxPs(const std::string& name)
: IsegPs(name, {"SR020020"}, 2) {}
\ No newline at end of file
#ifndef ISEGSHR20XXPS_H
#define ISEGSHR20XXPS_H
#include <string>
#include "IsegPs.h"
/**
* Implementation for the [ISEG SHR 20XX Dual Channel Output HV Power
* Supplies](https://iseg-hv.com/en/products/detail/SHR).
* The dual-channel ISEG power supplies appear to support the default [ISEG SHR
* programming
* model](https://iseg-hv.com/download/SOFTWARE/isegSCPI/SCPI_Programmers_Guide_en.pdf),
* but this has not been checked.
*/
class IsegSHR20xxPs : public IsegPs {
public:
IsegSHR20xxPs(const std::string& name);
~IsegSHR20xxPs() = default;
};
#endif // ISEGSHR20XXPS_H
......@@ -11,6 +11,8 @@
#include "DT54xxPs.h"
#include "DT8033NPs.h"
#include "IPowerSupply.h"
#include "IsegPs.h"
#include "IsegSHR20xxPs.h"
#include "Keithley22XX.h"
#include "Keithley24XX.h"
#include "PowerSupplyChannel.h"
......@@ -254,7 +256,7 @@ void register_ps(py::module &m) {
py_DT54xxPs
.def(py::init<const std::string &, const std::vector<std::string> &,
DT54xxPs::Polarity, double>())
IPowerSupply::Polarity, double>())
.def("status", &DT54xxPs::status)
.def("polarity", &DT54xxPs::polarity)
.def("setIMonRange", &DT54xxPs::setIMonRange)
......@@ -276,8 +278,8 @@ void register_ps(py::module &m) {
.value("CalError", DT54xxPs::Status::CalError);
py::enum_<DT54xxPs::Polarity>(py_DT54xxPs, "Polarity")
.value("Positive", DT54xxPs::Polarity::Positive)
.value("Negative", DT54xxPs::Polarity::Negative);
.value("Positive", IPowerSupply::Polarity::Positive)
.value("Negative", IPowerSupply::Polarity::Negative);
py::enum_<DT54xxPs::IMonRange>(py_DT54xxPs, "IMonRange")
.value("High", DT54xxPs::IMonRange::High)
......@@ -293,6 +295,17 @@ void register_ps(py::module &m) {
std::shared_ptr<DT8033NPs>>(m, "DT8033NPs")
.def(py::init<const std::string &>());
py::class_<IsegPs, PyPS<IsegPs>, IPowerSupply, std::shared_ptr<IsegPs>>(
m, "IsegPs")
.def(py::init<const std::string &, std::vector<std::string> &,
unsigned>(),
py::arg("name"), py::arg("models") = py::list(),
py::arg("maxChannels") = 0);
py::class_<IsegSHR20xxPs, IsegPs, std::shared_ptr<IsegSHR20xxPs>>(
m, "IsegSHR20xxPs")
.def(py::init<const std::string &>());
py::class_<Keithley22XX, PyPS<Keithley22XX>, IPowerSupply,
std::shared_ptr<Keithley22XX>>(m, "Keithley22XX")
.def(py::init<const std::string &>());
......
......@@ -3,6 +3,7 @@
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
......@@ -85,6 +86,11 @@ static inline std::string to_string_with_precision(const T a_value,
return out.str();
}
static inline std::string trim_last_char(std::string str) {
str.pop_back();
return str;
}
}; // namespace utils
#endif // STRINGUTILS_H
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment