diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index c0d75282e89e6118752b8d55b42960472a857d35..52d59ce1347b2ad0cc2b591d3d2e58e3905f7b9d 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -16,6 +16,11 @@ add_executable(fluke_example) target_sources(fluke_example PRIVATE fluke_example.cpp) target_link_libraries(fluke_example PRIVATE Utils Meter Com) +# multimeter +add_executable(multimeter_example) +target_sources(multimeter_example PRIVATE multimeter_example.cpp) +target_link_libraries(multimeter_example PRIVATE Utils Meter Com) + # scope if ( ${libScope_FOUND} ) add_executable(scope_example) diff --git a/src/examples/multimeter_example.cpp b/src/examples/multimeter_example.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c8d31a1be88c828cf0fa0d117879c920b7296aab --- /dev/null +++ b/src/examples/multimeter_example.cpp @@ -0,0 +1,40 @@ +#include <iostream> +#include <iomanip> + +#include "Logger.h" +#include "DMM6500.h" +#include "CharDeviceCom.h" + +int main(int argc, char*argv[]) { + + DMM6500 meter(argv[1]); + + std::shared_ptr<CharDeviceCom> com = std::make_shared<CharDeviceCom>(argv[1]); + meter.setCom(com); + + meter.reset(); + + /* ping multimeter */ + meter.ping(0); + + /* ping scanner card */ + meter.ping(1); + + + std::cout<<"=======================================scan result========================================="<<std::endl; + std::cout<<std::setw(13)<<" Ch1 [Ohm],"<<std::setw(13)<<" Ch2 [Ohm],"<<std::setw(13)<<" Ch3 [Ohm]," + <<std::setw(13)<<" Ch4 [pF],"<<std::setw(13)<<" Ch5 [pF],"<<std::setw(13)<<" Ch9 [pF],"<<std::setw(13)<<" Ch10 [pF],"<<std::endl; + std::cout<<std::setw(12)<<meter.measureRES(1, true)<<","; + std::cout<<std::setw(12)<<meter.measureRES(2, true)<<","; + std::cout<<std::setw(12)<<meter.measureRES(3, true)<<","; + std::cout<<std::setw(12)<<meter.measureCAP(4)*1e12<<","; + std::cout<<std::setw(12)<<meter.measureCAP(5)*1e12<<","; + std::cout<<std::setw(12)<<meter.measureCAP(9)*1e12<<","; + std::cout<<std::setw(12)<<meter.measureCAP(10)*1e12<<","; + std::cout<<std::endl; + std::cout<<"==========================================================================================="<<std::endl; + + + return 0; +} + diff --git a/src/libMeter/CMakeLists.txt b/src/libMeter/CMakeLists.txt index 56f8ae1a18d587ac44e3d5e9678891efe80e6031..2c471e9b0eedcf9d8085a6c24ab14ddc1ed677ed 100644 --- a/src/libMeter/CMakeLists.txt +++ b/src/libMeter/CMakeLists.txt @@ -1,10 +1,12 @@ add_library(Meter SHARED) target_sources(Meter PRIVATE + IMeter.cpp Keithley2000.cpp Fluke8842.cpp HP3478A.cpp PM6680.cpp + DMM6500.cpp ) target_link_libraries(Meter PRIVATE Com Utils) target_include_directories(Meter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/libMeter/DMM6500.cpp b/src/libMeter/DMM6500.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a403719bdbe8e850eeb41c15ed135f9ee2835845 --- /dev/null +++ b/src/libMeter/DMM6500.cpp @@ -0,0 +1,172 @@ +#include "DMM6500.h" +#include "Logger.h" + +#include "IMeter.h" +#include "ScopeLock.h" + +DMM6500::DMM6500(const std::string& name) : IMeter(name, {"DMM6500"}){ } + +bool DMM6500::ping(unsigned dev) +{ + std::string result = ""; + if(dev == 0){ + logger(logDEBUG) <<"ping the multimeter....."; + result = m_com->sendreceive("*IDN?"); + } + if(dev == 1){ + logger(logDEBUG)<<"ping scanner card....."; + result = m_com->sendreceive(":SYSTem:CARD1:IDN?"); + } + result.erase(result.size()-1); + if(result.empty()) + throw std::runtime_error("Failed communication with the device"); + else + logger(logDEBUG)<<result; + + return !result.empty(); +} + +std::string DMM6500::identify() +{ + std::string idn=m_com->sendreceive("*IDN?"); + return idn; +} + +void DMM6500::autowait() { + m_com->send("*OPC"); + int ESRValue = 0; + while ( (ESRValue & 1) == 0) + { + ESRValue = std::stoi(m_com->sendreceive("*ESR?")); + std::this_thread::sleep_for(std::chrono::milliseconds(m_wait)); + } +} + +void DMM6500::send(const std::string& cmd) { + logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Sending: " << cmd; + m_com->send("*CLS"); + m_com->send(cmd); + this->autowait(); +} + +std::string DMM6500::sendreceive(const std::string& cmd) { + m_com->send("*CLS"); + std::string buf=m_com->sendreceive(cmd); + this->autowait(); + logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Received: " << buf; + buf.erase(buf.size()-1); + return buf; +} + +void DMM6500::reset() { + logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Initialising: "; + this->send("*RST"); +} + +void DMM6500::sendScanCommand(std::string channel){ + this->send(":ROUT:SCAN:CRE (@"+channel+")"); + this->send(":ROUT:SCAN:COUNT:SCAN 1"); + this->send(":TRAC:CLE"); + this->send("INIT"); + this->send("*WAI"); +} + +//measure DC voltage with high precision +//take average of 10 repeatings +double DMM6500::measureDCV(unsigned channel){ + std::string s_ch = ""; + if(channel > 0)//use scanner card + s_ch = ", (@"+std::to_string(channel)+")"; + + ScopeLock lock(m_com); + this->send("*RST"); + this->send(":SENS:FUNC \"VOLT:DC\""+s_ch); + this->send(":SENS:VOLT:RANG:AUTO ON"+s_ch); + this->send(":SENS:VOLT:INP AUTO"+s_ch); + this->send(":SENS:VOLT:NPLC 1"+s_ch); + this->send(":SENS:VOLT:AZER ON"+s_ch); + this->send(":SENS:VOLT:AVER:TCON REP"+s_ch); + this->send(":SENS:VOLT:AVER:COUN 10"+s_ch); + this->send(":SENS:VOLT:AVER ON"+s_ch); + + if(channel > 0)//use scanner card + this->sendScanCommand(std::to_string(channel)); + + return std::stod(this->sendreceive(":READ?")); +} + +//measure resistance (2W or 4W) +//take average of 10 repeatings +double DMM6500::measureRES(unsigned channel, bool use4w){ + std::string n_func = "RES"; + if(use4w) + n_func = "FRES"; + + std::string s_ch = ""; + if(channel > 0)//use scanner card + s_ch = ", (@"+std::to_string(channel)+")"; + + ScopeLock lock(m_com); + this->send("*RST"); + this->send(":SENS:FUNC \""+n_func+"\""+s_ch); + this->send(":SENS:"+n_func+":RANG:AUTO ON"+s_ch); + if(use4w) + this->send(":SENS:"+n_func+":OCOM ON"+s_ch); + this->send(":SENS:"+n_func+":AZER ON"+s_ch); + this->send(":SENS:"+n_func+":NPLC 1"+s_ch); + this->send(":SENS:"+n_func+":AVER:TCON REP"+s_ch); + this->send(":SENS:"+n_func+":AVER:COUN 10"+s_ch); + this->send(":SENS:"+n_func+":AVER ON"+s_ch); + + if(channel > 0)//use scanner card + this->sendScanCommand(std::to_string(channel)); + + return std::stod(this->sendreceive(":READ?")); +} + +//measure capacitance +//take average of 10 repeatings +double DMM6500::measureCAP(unsigned channel){ + std::string s_ch = ""; + if(channel > 0)//use scanner card + s_ch = ", (@"+std::to_string(channel)+")"; + + ScopeLock lock(m_com); + this->send("*RST"); + this->send(":SENS:FUNC \"CAP\""+s_ch); + this->send(":SENS:CAP:RANG:AUTO ON"+s_ch); + this->send(":SENS:CAP:AVER:TCON REP"+s_ch); + this->send(":SENS:CAP:AVER:COUN 10"+s_ch); + this->send(":SENS:CAP:AVER ON"+s_ch); + + if(channel > 0)//use scanner card + this->sendScanCommand(std::to_string(channel)); + + return std::stod(this->sendreceive(":READ?")); +} + + +//measure DC current with high precision +//take average of 10 repeatings +double DMM6500::measureDCI(unsigned channel){ + std::string s_ch = ""; + if(channel > 0)//use scanner card + s_ch = ", (@"+std::to_string(channel)+")"; + + ScopeLock lock(m_com); + this->send("*RST"); + this->send(":SENS:FUNC \"CURR:DC\""+s_ch); + this->send(":SENS:CURR:RANG:AUTO ON"+s_ch); + this->send(":SENS:CURR:NPLC 1"+s_ch); + this->send(":SENS:CURR:AZER ON"+s_ch); + this->send(":SENS:CURR:AVER:TCON REP"+s_ch); + this->send(":SENS:CURR:AVER:COUN 10"+s_ch); + this->send(":SENS:CURR:AVER ON"+s_ch); + + if(channel > 0)//use scanner card + this->sendScanCommand(std::to_string(channel)); + + return std::stod(this->sendreceive(":READ?")); +} + + diff --git a/src/libMeter/DMM6500.h b/src/libMeter/DMM6500.h new file mode 100644 index 0000000000000000000000000000000000000000..5995193dc2a1e5c9e434938d746960c6cc85c41b --- /dev/null +++ b/src/libMeter/DMM6500.h @@ -0,0 +1,72 @@ +#ifndef DMM6500_H +#define DMM6500_H +#include <iostream> +#include <string> +#include <thread> +#include <chrono> +#include "IMeter.h" + +/* + DMM6500 multimeter + Author: Zhicai Zhang + Date: Feb 2021 + Reference 1: https://www.tek.com/tektronix-and-keithley-digital-multimeter/dmm6500-manual/model-dmm6500-6-1-2-digit-multimeter-3 + Reference 2: https://www.tek.com/manual/model-dmm6500-6-1-2-digit-multimeter-user-manual + 2000-Scan card note: https://www.tek.com/default-accessory-series-manual/model-2000-scan-scanner-card +*/ +class DMM6500 : public IMeter { + public: + DMM6500(const std::string& name); + + /** ping the device + * @param dev: index of the device to ping (if there are multiple parts connected) + * dev = 0 is for the main meter + * dev > 0 is for other parts that's connected to the meter + */ + virtual bool ping(unsigned dev = 0); + + virtual std::string identify(); + + virtual void reset(); + + /** measure DC voltage (unit: V) + * @param channel: channel ID to perform the measurement + * channel = 0 means without scanner card + * channel > 0 means measure with specific channel on scanner card + */ + virtual double measureDCV(unsigned channel = 0); + + /** measure DC current (unit: A) + * @param channel: channel ID to perform the measurement + * channel = 0 means without scanner card + * channel > 0 means measure with specific channel on scanner card + */ + virtual double measureDCI(unsigned channel = 0); + + /* + measure resistance (unit: Ohm) + * @param channel: channel ID to perform the measurement + * channel = 0 means without scanner card + * channel > 0 means measure with specific channel on scanner card + * @param use4w: whether or not use 4-wire method + */ + virtual double measureRES(unsigned channel = 0, bool use4w = false); + + /* + measure capacitance (unit: F) + * @param channel: channel ID to perform the measurement + * channel = 0 means without scanner card + * channel > 0 means measure with specific channel on scanner card + */ + virtual double measureCAP(unsigned channel = 0); + + private: + void send(const std::string& cmd); + void sendScanCommand(std::string channel="1"); + std::string sendreceive(const std::string& cmd); + void autowait(); + + std::chrono::milliseconds m_wait{10}; +}; + +#endif diff --git a/src/libMeter/IMeter.cpp b/src/libMeter/IMeter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3d0c84ff80b4e855fc36f90dda4ab791cb2cffa9 --- /dev/null +++ b/src/libMeter/IMeter.cpp @@ -0,0 +1,75 @@ +#include "IMeter.h" +#include "Logger.h" + +IMeter::IMeter(const std::string& name, std::vector<std::string> models) { + m_name = name; + m_models = models; +} + +void IMeter::setCom(std::shared_ptr<ICom> com) +{ + if(!com->is_open()) + com->init(); + + m_com = com; + if(!ping(0)) + throw std::runtime_error("Failed communication with the multimeter"); +} + +bool IMeter::ping(unsigned dev) +{ + logger(logWARNING) << "ping() not implemented for this multimeter."; + return false; +} + +void IMeter::checkCompatibilityList() +{ + // get model connected to the meter + std::string idn = identify(); + + // get list of models + std::vector<std::string> models = IMeter::getListOfModels(); + + if (models.empty()){ + logger(logINFO) << "No model identifier implemented for this meter. No check is performed."; + return; + } + + for(const std::string& model : models) + { + if(idn.find(model) != std::string::npos) + return; + } + + logger(logERROR)<< "Unknown meter: " << idn; + throw std::runtime_error("Unknown meter: " + idn); + +} + +std::vector<std::string> IMeter::getListOfModels() +{ + return m_models; +} + +double IMeter::measureDCV(unsigned channel){ + logger(logWARNING) << "measureDCV() not implemented for this multimeter."; + return 0; +} + +double IMeter::measureRES(unsigned channel, bool use4w){ + logger(logWARNING) << "measureRES() not implemented for this multimeter."; + return 0; +} + +double IMeter::measureCAP(unsigned channel){ + logger(logWARNING) << "measureCAP() not implemented for this multimeter."; + return 0; +} + + +double IMeter::measureDCI(unsigned channel){ + logger(logWARNING) << "measureDCI() not implemented for this multimeter."; + return 0; +} + + diff --git a/src/libMeter/IMeter.h b/src/libMeter/IMeter.h new file mode 100644 index 0000000000000000000000000000000000000000..a26d98c89b241d7a4b82746eecf8646547c900bc --- /dev/null +++ b/src/libMeter/IMeter.h @@ -0,0 +1,85 @@ +#ifndef IMETER_H +#define IMETER_H + +#include <iostream> +#include <string> +#include "ICom.h" + + +/* Generic multimeter interface + a typical workflow: + IMeter meter(name); + meter.setCom(com); + meter.reset(); + meter.ping(0); + meter.measureDCV(); + //.... +*/ +class IMeter { + public: + + /** Constructor. + * @param name: name of the meter + * @param models: list of tested models (empty means no check is performed) + */ + IMeter(const std::string& name, std::vector<std::string> models={}); + + void setCom(std::shared_ptr<ICom> com); + + /** ping the device + * @param dev: index of the device to ping (if there are multiple parts connected) + * dev = 0 is for the main meter + * dev > 0 is for other parts that's connected to the meter + */ + virtual bool ping(unsigned dev = 0); + + virtual void reset() = 0; + + /** Check that the device model is supported by checking that + * model identifier of the connected meter contains + * one of the supported models for that meter + * Throws exception if compatibility is not found + * If the list of model identifiers is empty, no check is performed + */ + virtual void checkCompatibilityList(); + + /* Return list of supported models. */ + virtual std::vector<std::string> getListOfModels(); + + /** Returns the model of the meter connected. + * Meter classes should return an empty string if this functionality does not exist + */ + virtual std::string identify() = 0; + + /** measure DC voltage (unit: V) + * @param channel: channel ID to perform the measurement + */ + virtual double measureDCV(unsigned channel = 0); + + /** measure DC current (unit: A) + * @param channel: channel ID to perform the measurement + */ + virtual double measureDCI(unsigned channel = 0); + + /** measure resistance (unit: Ohm) + * @param channel: channel ID to perform the measurement + * @param use4w: whether or not use 4-wire method + */ + virtual double measureRES(unsigned channel = 0, bool use4w = false); + + /** measure capacitance (unit: F) + * @param channel: channel ID to perform the measurement + */ + virtual double measureCAP(unsigned channel = 0); + + protected: + + std::shared_ptr<ICom> m_com = nullptr; + + /** Store device configuration name */ + std::string m_name; + + std::vector<std::string> m_models; +}; + +#endif