diff --git a/src/libPS/CMakeLists.txt b/src/libPS/CMakeLists.txt index 74e27b7313ca56b203a98669795f44d806f5e0a5..cf574c8dfa4c9e616cf02f4468d0b29ea7734417 100644 --- a/src/libPS/CMakeLists.txt +++ b/src/libPS/CMakeLists.txt @@ -12,6 +12,8 @@ target_sources(PS AgilentE3634APs.cpp AgilentE36300APs.cpp DT54xxPs.cpp + DT5471NPs.cpp + DT5472NPs.cpp SorensenPs.cpp RigolDP832.cpp Keithley24XX.cpp diff --git a/src/libPS/DT5471NPs.cpp b/src/libPS/DT5471NPs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ed171c009839bc104c6461707dcb1b1e16f21d1e --- /dev/null +++ b/src/libPS/DT5471NPs.cpp @@ -0,0 +1,16 @@ +#include "DT5471NPs.h" + +#include "TextSerialCom.h" + +#include <algorithm> +#include <thread> + +#include "Logger.h" + +//Register power supply +#include "PowerSupplyRegistry.h" +REGISTER_POWERSUPPLY(DT5471NPs) + +DT5471NPs::DT5471NPs(const std::string& name) +: DT54xxPs(name, {"DT5471"}, Polarity::Negative, 51e-6) +{ } diff --git a/src/libPS/DT5471NPs.h b/src/libPS/DT5471NPs.h new file mode 100644 index 0000000000000000000000000000000000000000..0b94cc42e4f041a8f1d9412b6793f28b25d8b430 --- /dev/null +++ b/src/libPS/DT5471NPs.h @@ -0,0 +1,20 @@ +#ifndef DT5471NPS_H +#define DT5471NPS_H + +#include "DT54xxPs.h" + +//! \brief Implementation for the CAEN DT5471N power supplies +/** + * [Reference](https://www.caen.it/products/dt5471/) + * + * Note: This is the negative output voltage variation. + */ +class DT5471NPs : public DT54xxPs +{ +public: + DT5471NPs(const std::string& name); + ~DT5471NPs() =default; +}; + +#endif // DT5471NPS_H + diff --git a/src/libPS/DT5472NPs.cpp b/src/libPS/DT5472NPs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a609acc79859cafe1ce7782db04dc88598640086 --- /dev/null +++ b/src/libPS/DT5472NPs.cpp @@ -0,0 +1,16 @@ +#include "DT5472NPs.h" + +#include "TextSerialCom.h" + +#include <algorithm> +#include <thread> + +#include "Logger.h" + +//Register power supply +#include "PowerSupplyRegistry.h" +REGISTER_POWERSUPPLY(DT5472NPs) + +DT5472NPs::DT5472NPs(const std::string& name) +: DT54xxPs(name, {"DT5472"}, Polarity::Negative, 105e-6) +{ } diff --git a/src/libPS/DT5472NPs.h b/src/libPS/DT5472NPs.h new file mode 100644 index 0000000000000000000000000000000000000000..9e48d9687a04d78a4a5f5398755121640bd0d2e6 --- /dev/null +++ b/src/libPS/DT5472NPs.h @@ -0,0 +1,20 @@ +#ifndef DT5472NPS_H +#define DT5472NPS_H + +#include "DT54xxPs.h" + +//! \brief Implementation for the CAEN DT5472N power supplies +/** + * [Reference](https://www.caen.it/products/dt5472/) + * + * Note: This is the negative output voltage variation. + */ +class DT5472NPs : public DT54xxPs +{ +public: + DT5472NPs(const std::string& name); + ~DT5472NPs() =default; +}; + +#endif // DT5472NPS_H + diff --git a/src/libPS/DT54xxPs.cpp b/src/libPS/DT54xxPs.cpp index b6825744bc1e2ff113eda40cfd8c8e6709e7c01a..a934cb404394fd40b8d16aabd0f19c2550a05227 100644 --- a/src/libPS/DT54xxPs.cpp +++ b/src/libPS/DT54xxPs.cpp @@ -11,8 +11,8 @@ #include "PowerSupplyRegistry.h" REGISTER_POWERSUPPLY(DT54xxPs) -DT54xxPs::DT54xxPs(const std::string& name) : -IPowerSupply(name, {"DT5472"}) +DT54xxPs::DT54xxPs(const std::string& name, const std::vector<std::string>& models, Polarity output, double imaxl) +: IPowerSupply(name, models), m_output(output), m_imaxl(imaxl) { } void DT54xxPs::reset() @@ -37,18 +37,21 @@ bool DT54xxPs::ping() return !result.empty(); } +void DT54xxPs::checkCompatibilityList() +{ + IPowerSupply::checkCompatibilityList(); + + Polarity pol=polarity(); + if(pol!=m_output) + throw std::runtime_error("Wrong polarity detected"); +} + void DT54xxPs::turnOn(unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); command("SET","ON"); - - while(status(channel)&Status::RampingUp) - { - double volt=measureVoltage(channel); - logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> ramping up: " << volt << "V"; - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + waitRamp(); } void DT54xxPs::turnOff(unsigned channel) @@ -56,79 +59,88 @@ void DT54xxPs::turnOff(unsigned channel) if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); command("SET","OFF"); - - while(status(channel)&Status::RampingDown) - { - double volt=measureVoltage(channel); - logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> ramping down: " << volt << "V"; - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + waitRamp(); } void DT54xxPs::setCurrentLevel(double cur, unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); + cur=checkPolarity(cur); + + // Set the optimal range for the requested maximum current + if(cur<m_imaxl) // set low range for small currents + setIMonRange(IMonRange::Low , channel); + else // use high range for large currents + setIMonRange(IMonRange::High, channel); + + // Set the maximum current command("SET", "ISET", std::to_string(cur*1e6)); } double DT54xxPs::getCurrentLevel(unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - return std::stod(command("MON","ISET"))/1e6; + + return convertPolarity(std::stod(command("MON","ISET"))/1e6); } void DT54xxPs::setCurrentProtect(double maxcur, unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - setCurrentLevel(maxcur, channel); + + setCurrentLevel(convertPolarity(maxcur), channel); } double DT54xxPs::getCurrentProtect(unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - return getCurrentLevel(channel); + return std::fabs(getCurrentLevel(channel)); } double DT54xxPs::measureCurrent(unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - return std::stod(command("MON","IMON"))/1e6; + return convertPolarity(std::stod(command("MON","IMON"))/1e6); } void DT54xxPs::setVoltageLevel(double volt, unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - command("SET", "VSET", std::to_string(volt)); + + command("SET", "VSET", std::to_string(checkPolarity(volt))); + waitRamp(); } double DT54xxPs::getVoltageLevel(unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - return std::stod(command("MON","VSET")); + return convertPolarity(std::stod(command("MON","VSET"))); } void DT54xxPs::setVoltageProtect(double maxvolt, unsigned channel) { + if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - setVoltageLevel(maxvolt, channel); - + setVoltageLevel(convertPolarity(maxvolt), channel); } double DT54xxPs::getVoltageProtect(unsigned channel) { + if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - return getVoltageLevel(channel); + return std::fabs(getVoltageLevel(channel)); } double DT54xxPs::measureVoltage(unsigned channel) { if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); - return std::stod(command("MON","VMON")); + + return convertPolarity(std::stod(command("MON","VMON"))); } uint16_t DT54xxPs::status(unsigned channel) @@ -138,6 +150,41 @@ uint16_t DT54xxPs::status(unsigned channel) return std::stoi(command("MON","STAT"))&0xFFFF; } +DT54xxPs::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; + else return Polarity::Positive; +} + +void DT54xxPs::setIMonRange(IMonRange range, unsigned channel) +{ + if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); + + command("SET", "IMRANGE", (range==IMonRange::Low)?"LOW":"HIGH"); +} + +DT54xxPs::IMonRange DT54xxPs::getIMonRange(unsigned channel) +{ + if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); + + return (command("MON", "IMRANGE")=="LOW") ? IMonRange::Low : IMonRange::High; +} + +void DT54xxPs::waitRamp(unsigned channel) +{ + if (channel != 1) throw std::runtime_error("Set the channel to 1 for single channel power-supply"); + + do + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> ramping: " << measureVoltage(channel) << "V"; + } + while( status(channel)&(Status::RampingUp|Status::RampingDown) ); +} + std::string DT54xxPs::command(const std::string& cmd, const std::string& par, const std::string& value) { // Build command @@ -173,11 +220,25 @@ std::string DT54xxPs::command(const std::string& cmd, const std::string& par, co } if(cmdvalue.empty()) - throw "DT54xx: No CMD in return statement :("; + throw std::runtime_error("DT54xx: No CMD in return statement :("); if(cmdvalue=="ERR") - throw "DT54xx:: CMD shows an error :("; + throw std::runtime_error("DT54xx: CMD shows an error :("); return retvalue; } +double DT54xxPs::checkPolarity(double input) +{ + if(m_output==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) + throw std::runtime_error("Specified negative output value for a power supply that only supports positive output."); + + return std::fabs(input); +} + +double DT54xxPs::convertPolarity(double value) +{ + return (m_output==Polarity::Negative)? -value : value ; +} diff --git a/src/libPS/DT54xxPs.h b/src/libPS/DT54xxPs.h index ed03f6b955bf5d915b8c7a912407d6bbf5acd3ee..406c7565d08bfd1b70905a215b6363f44777e903 100644 --- a/src/libPS/DT54xxPs.h +++ b/src/libPS/DT54xxPs.h @@ -8,12 +8,24 @@ #include "IPowerSupply.h" -/** \brief CAEN DT54xx - * CAEN DT54xx USB High Voltage Power Supplies +//! \brief Base implementation for the CAEN DT54xx power supplies +/** + * CAEN DT54xx USB High Voltage Power Supplies. * * [Programming Manual](https://www.caen.it/products/dt5472/) * - * Assumes direct USB connection. + * This class implements the command set from the programming manual. It + * contains a few configurable checks (ie: polarity) that are specific to + * a given variation of the DT54xx power supplies. There are sub-classes + * that correctly configure the checks. It is recommended that the user + * only uses those (ie: `DT54xxNPs` for the negative output variation). + * + * The implementation also performs auto-ranging for the current monitor. See + * the `setCurrentProtect` method for how this is achieved. + * + * *Warning:* The DT54xx power supplies are quite slow when updating the + * monitoring information. Tests show that monitoring information is updated + * roughly every 1s. */ class DT54xxPs : public IPowerSupply { @@ -39,7 +51,32 @@ public: CalError =(1<<13) // 1 : Calibration Error }; - DT54xxPs(const std::string& name); + /** + * Polarity of the power supply output + */ + enum Polarity + { + Positive, + Negative + }; + + /** + * Range of the current monitor + */ + enum IMonRange + { + High, + Low + }; + + //! \brief Constructor that configures checks for a specific collection of models + /** + * @param name `IPowerSupply` instance name + * @param models List of supported model names (see `IPowerSupply::checkCompatibilityList`) + * @param output The output voltage is negative + * @param imaxl Maximum current [A] for low IMon range + */ + DT54xxPs(const std::string& name, const std::vector<std::string>& models={}, Polarity output=Polarity::Positive, double imaxl=105e-6); ~DT54xxPs() =default; /** \name Communication @@ -50,6 +87,12 @@ public: virtual std::string identify(); + /** + * In addition to the standard model check from `IPowerSupply`, this also + * checks that the `output` setting is correct. + */ + virtual void checkCompatibilityList(); + /** @} */ /** \name Power Supply Control @@ -79,11 +122,31 @@ public: * @{ */ - 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); + /** + * Calls `setCurrentProtect` with `cur` as the maximum level. + */ + virtual void setCurrentLevel(double cur, unsigned channel = 1); + virtual double getCurrentLevel(unsigned channel = 1); + + //! \brief Set current protection + /** + * The current monitor range is also automatically set to match up + * with the maximum current (`maxcur`). If `maxcur` is below the maximum + * of the low IMon range (`imaxl`, see constructor), then low Imon range + * is used. Otherwise the high IMon range is used. + * + * @param maxcur maximum current (absolute value) [A] + * @param channel channel (if any) + */ + virtual void setCurrentProtect(double maxcur , unsigned channel = 1); + + //! \brief Get current protection + /** + * @param channel channel (if any) + * @return maximum current (absolute value) [A] + */ + virtual double getCurrentProtect(unsigned channel = 1); + virtual double measureCurrent(unsigned channel = 1); /** @} */ @@ -91,11 +154,33 @@ public: * @{ */ - 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); + //! \brief Set output voltage level. + /** + * For the N-type power supplies, the voltage level should be + * specified as negative. This function uses the `output` + * property to validate the input. + * + * @param volt voltage [V] + * @param channel channel (if any) + */ + virtual void setVoltageLevel(double volt, unsigned channel = 1); + virtual double getVoltageLevel(unsigned channel = 1); + + //! \brief Set voltage protection + /** + * @param maxvolt maximum voltage (absolute value) [V] + * @param channel channel (if any) + */ + virtual void setVoltageProtect(double maxvolt , unsigned channel = 1 ); + + //! \brief Get voltage protection + /** + * @param channel channel (if any) + * @return maximum voltage (absolute value) [V] + */ + virtual double getVoltageProtect(unsigned channel = 1); + + virtual double measureVoltage(unsigned channel = 1); /** @} */ @@ -111,25 +196,89 @@ public: * * @return status bits */ - uint16_t status(unsigned channel = 0); + uint16_t status(unsigned channel = 1); + + /** + * Return the polarity of the power supply. + * + * @param channel Channel to query + * + * @return polarity of the channel + */ + Polarity polarity(unsigned channel = 1); + + /** + * Set the current monitoring range + * + * @param range to set + */ + void setIMonRange(IMonRange range = Low, unsigned channel = 1); + + /** + * Get the current monitoring range + * + * @return currently set range + */ + IMonRange getIMonRange(unsigned channel = 1); + + //! \brief Wait for voltage ramp to complete + /** + * Monitors the status of the power supply every + * second until the ramp up and down bits are zero. + * + * The monitoring starts by waiting for 1 second for + * the status register to be updated. + */ + void waitRamp(unsigned channel = 1); /** @} */ private: + //! \brief Build a command string and parse the response /** - * Build a command string and parse the response - * * Throws an exception if any of the following errors are detected: * - no response * - returned CMD value is ERR * - * \param cmd CMD value - * \param par PAR value - * \param value VAL value (if empty, not appended) + * @param cmd CMD value + * @param par PAR value + * @param value VAL value (if empty, not appended) * - * \return The returned VAL value. + * @return The returned VAL value. */ std::string command(const std::string& cmd, const std::string& par, const std::string& value=""); + + //! \brief Check that the input (voltage or current) has the right sign and convert to absolute + /** + * A `std::runtime_error` is thrown if a wrong sign is supplied, as determine + * by the `m_output` setting. + * + * The conversion to absolute value is necessary for the power supplies + * command protocol. + * + * @param `input` Input value to check and convert. + * + * @return Absolute value of `input` + */ + double checkPolarity(double input); + + //! \brief Add correct sign to value measured by power supply + /** + * Adds the correct sign to a power supply measurement based on the polarity + * of the model. Necessary as the communication protocol does not include the + * sign. + * + * @param `value` Measured value as returned by the PS. + * + * @return `value` with correct sign based on polarity. + */ + double convertPolarity(double value); + + //! \brief Specify whether power supply outputs a negative voltage (N vs P model) + Polarity m_output=Polarity::Positive; + + //! \brief Specify maximum current [A] for low IMon range + double m_imaxl=105e-6; }; #endif // DT54XXPS_H diff --git a/src/libPS/python.cpp b/src/libPS/python.cpp index f13c1cd0a9067d58e9169ae7b5290487ac7b7e65..fcea21a7f9391764b18cb02e0726b7304dac8dea 100755 --- a/src/libPS/python.cpp +++ b/src/libPS/python.cpp @@ -13,6 +13,8 @@ #include "Bk16XXPs.h" #include "DT54xxPs.h" +#include "DT5471NPs.h" +#include "DT5472NPs.h" #include "Keithley22XX.h" #include "Keithley24XX.h" #include "PowerSupplyChannel.h" @@ -397,8 +399,11 @@ void register_ps(py::module& m){ py::class_<DT54xxPs, PyPS<DT54xxPs>, IPowerSupply, std::shared_ptr<DT54xxPs>> py_DT54xxPs(m, "DT54xxPs"); py_DT54xxPs - .def(py::init<const std::string &>()) - .def("status", &DT54xxPs::status); + .def(py::init<const std::string&, const std::vector<std::string>&, DT54xxPs::Polarity, double >()) + .def("status", &DT54xxPs::status) + .def("polarity", &DT54xxPs::polarity) + .def("setIMonRange", &DT54xxPs::setIMonRange) + .def("getIMonRange", &DT54xxPs::getIMonRange); py::enum_<DT54xxPs::Status>(py_DT54xxPs, "Status") .value("On", DT54xxPs::Status::On) @@ -415,6 +420,19 @@ void register_ps(py::module& m){ .value("Interlock", DT54xxPs::Status::Interlock) .value("CalError", DT54xxPs::Status::CalError); + py::enum_<DT54xxPs::Polarity>(py_DT54xxPs, "Polarity") + .value("Positive", DT54xxPs::Polarity::Positive) + .value("Negative", DT54xxPs::Polarity::Negative); + + py::enum_<DT54xxPs::IMonRange>(py_DT54xxPs, "IMonRange") + .value("High", DT54xxPs::IMonRange::High) + .value("Low" , DT54xxPs::IMonRange::Low ); + + py::class_<DT5471NPs, DT54xxPs, std::shared_ptr<DT5471NPs>>(m, "DT5471NPs") + .def(py::init<const std::string &>()); + + py::class_<DT5472NPs, DT54xxPs, std::shared_ptr<DT5472NPs>>(m, "DT5472NPs") + .def(py::init<const std::string &>()); py::class_<Keithley22XX, PyPS<Keithley22XX>, IPowerSupply, std::shared_ptr<Keithley22XX>>(m, "Keithley22XX") .def(py::init<const std::string &>());