Created
January 10, 2016 22:06
-
-
Save dstahlke/d0621a061eb8839e2f5f to your computer and use it in GitHub Desktop.
Linux driver for B&K Precision 393 multimeter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Linux driver for B&K Precision 393 multimeter. | |
// g++ -Wall -Wextra -std=c++11 -o bk393 bk393.cc | |
// It currently points to /dev/ttyUSB0, but I'd recommend adding a udev rule to get a consistent device name: | |
// SUBSYSTEMS=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A60198LN", MODE="0664", GROUP="mygroup", SYMLINK+="bk393" | |
#include <boost/utility.hpp> | |
#include <array> | |
#include <iomanip> | |
#include <cstdio> | |
#include <cstdlib> | |
#include <cstdint> | |
#include <sstream> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <termios.h> | |
#include <fcntl.h> | |
#include <stdexcept> | |
#include <utility> | |
#include <string> | |
#include <system_error> | |
#include <sys/ioctl.h> | |
#include <chrono> | |
class BK393 : boost::noncopyable { | |
int fd; | |
struct termios oldtio; | |
public: | |
enum class Mode : uint8_t { | |
V_AC = 0x01, | |
V_DC = 0x02, | |
mV_AC = 0x03, | |
ohms = 0x04, | |
cap = 0x05, | |
Hz = 0x06, | |
uA_AC = 0x07, | |
A_AC = 0x08, | |
degC = 0x09, | |
V_DC_AC = 0x0b, | |
mV_DC = 0x0c, | |
mV_DC_AC = 0x0d, | |
cont = 0x0e, | |
diode = 0x0f, | |
uA_DC = 0x10, | |
mA_DC = 0x11, | |
A_DC = 0x12, | |
mA_AC = 0x13, | |
degF = 0x14, | |
duty = 0x16, | |
uA_DC_AC = 0x1b, | |
mA_DC_AC = 0x1c, | |
A_DC_AC = 0x1d, | |
ns = 0x1e, | |
}; | |
static const char *mode_name(Mode m) { | |
switch(m) { | |
case Mode::V_AC : return "V AC"; | |
case Mode::V_DC : return "V DC"; | |
case Mode::mV_AC : return "mV AC"; | |
case Mode::ohms : return "ohms"; | |
case Mode::cap : return "F"; | |
case Mode::Hz : return "Hz"; | |
case Mode::uA_AC : return "uA AC"; | |
case Mode::A_AC : return "A AC"; | |
case Mode::degC : return "degC"; | |
case Mode::V_DC_AC : return "V DC+AC"; | |
case Mode::mV_DC : return "mV DC"; | |
case Mode::mV_DC_AC : return "mV DC+AC"; | |
case Mode::cont : return "ohms (beeper)"; | |
case Mode::diode : return "V diode"; | |
case Mode::uA_DC : return "uA DC"; | |
case Mode::mA_DC : return "mA DC"; | |
case Mode::A_DC : return "A DC"; | |
case Mode::mA_AC : return "mA AC"; | |
case Mode::degF : return "degF"; | |
case Mode::duty : return "%"; | |
case Mode::uA_DC_AC : return "uA DC+AC"; | |
case Mode::mA_DC_AC : return "mA DC+AC"; | |
case Mode::A_DC_AC : return "A DC+AC"; | |
case Mode::ns : return "ns"; | |
default : return "???"; | |
} | |
} | |
struct Reading { | |
std::array<uint8_t, 10> data; | |
Reading() { data.fill(0); } | |
bool valid() const { | |
return data[0]==0xf0 && data[1]==0x11 && data[9]==0x05; | |
} | |
Mode mode() const { return Mode(data[2]); } | |
bool overload() const { return data[8] & 0x10; } | |
bool negative() const { return data[8] & 0x20; } | |
double value() const { | |
uint16_t v = 0; | |
for(int i=0; i<4; i++) { | |
v <<= 4; | |
uint8_t c = data[7-i]; | |
if(c >= '0' && c <= '9') { | |
v += c-'0'; | |
} else if(c >= 'A' && c <= 'F') { | |
v += c-'A'+0xa; | |
} else { | |
return std::numeric_limits<double>::quiet_NaN(); | |
} | |
} | |
double vf = v * scaling(); | |
if(negative()) vf *= -1; | |
for(int i=0; i<data[3]; i++) vf *= 10; | |
return vf; | |
} | |
std::string to_string() { | |
if(!valid()) return "invalid"; | |
std::stringstream ss; | |
ss << std::scientific; | |
ss << std::setprecision(4); | |
ss << value() << " " << mode_name(mode()); | |
if(overload()) ss << " (overload)"; | |
return ss.str(); | |
} | |
private: | |
double scaling() const { | |
switch(mode()) { | |
case Mode::V_AC : return 1e-4; | |
case Mode::V_DC : return 1e-4; | |
case Mode::mV_AC : return 1e-2; | |
case Mode::ohms : return 1e-2; | |
case Mode::cap : return 1e-12; | |
case Mode::Hz : return 1e-3; | |
case Mode::uA_AC : return 1e-2; | |
case Mode::A_AC : return 1e-4; | |
case Mode::degC : return 1e-2; | |
case Mode::V_DC_AC : return 1e-4; | |
case Mode::mV_DC : return 1e-2; | |
case Mode::mV_DC_AC : return 1e-2; | |
case Mode::cont : return 1e-2; | |
case Mode::diode : return 1e-4; | |
case Mode::uA_DC : return 1e-2; | |
case Mode::mA_DC : return 1e-3; | |
case Mode::A_DC : return 1e-4; | |
case Mode::mA_AC : return 1e-3; | |
case Mode::degF : return 1e-2; | |
case Mode::duty : return 1e-1; | |
case Mode::uA_DC_AC : return 1e-2; | |
case Mode::mA_DC_AC : return 1e-3; | |
case Mode::A_DC_AC : return 1e-4; | |
case Mode::ns : return 1e-2; | |
default : throw std::runtime_error("unrecognized mode"); | |
} | |
} | |
}; | |
BK393(const std::string &dev_fn) { | |
fd = open(dev_fn.c_str(), O_RDWR | O_NOCTTY); | |
if(fd < 0) throw std::system_error(errno, std::system_category()); | |
tcgetattr(fd, &oldtio); | |
struct termios newtio; | |
memset(&newtio, 0, sizeof(newtio)); | |
newtio.c_cflag = B9600 | CS8 | CLOCAL | CREAD; | |
newtio.c_iflag = IGNBRK; | |
newtio.c_oflag = 0; | |
newtio.c_lflag = 0; | |
newtio.c_cc[VTIME] = 20; | |
newtio.c_cc[VMIN] = 0; | |
tcsetattr(fd, TCSANOW, &newtio); | |
} | |
~BK393() { | |
tcsetattr(fd, TCSANOW, &oldtio); | |
close(fd); | |
} | |
Reading read() { | |
tcflush(fd, TCIFLUSH); | |
Reading ret; | |
for(;;) { | |
uint8_t x = 0; | |
if(::read(fd, &x, 1)) { | |
for(size_t i=0; i<ret.data.size()-1; ++i) { | |
ret.data[i] = ret.data[i+1]; | |
} | |
ret.data[ret.data.size()-1] = x; | |
if(ret.valid()) return ret; | |
} else { | |
return ret; | |
} | |
} | |
} | |
}; | |
int main() { | |
auto t0 = std::chrono::system_clock::now(); | |
BK393 bk("/dev/ttyUSB0"); | |
for(;;) { | |
auto rd = bk.read(); | |
auto t1 = std::chrono::system_clock::now(); | |
int64_t dt = (t1 - t0) / std::chrono::milliseconds(1); | |
std::printf("%.3f : ", dt/1000.0); | |
for(auto x : rd.data) std::printf("%02x ", x); | |
std::printf(": %s\n", rd.to_string().c_str()); | |
fflush(stdout); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment