Skip to content

Instantly share code, notes, and snippets.

@dstahlke
Created January 10, 2016 22:06
Show Gist options
  • Save dstahlke/d0621a061eb8839e2f5f to your computer and use it in GitHub Desktop.
Save dstahlke/d0621a061eb8839e2f5f to your computer and use it in GitHub Desktop.
Linux driver for B&K Precision 393 multimeter
// 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