370 lines
9.2 KiB
C
370 lines
9.2 KiB
C
/*
|
|
* This file is part of the libsigrok project.
|
|
*
|
|
* Copyright (C) 2014 Janne Huttunen <jahuttun@gmail.com>
|
|
* Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <glib.h>
|
|
#include <libsigrok/libsigrok.h>
|
|
#include "libsigrok-internal.h"
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#define LOG_PREFIX "es51919"
|
|
|
|
#ifdef HAVE_SERIAL_COMM
|
|
|
|
/*
|
|
* Cyrustek ES51919 LCR chipset host protocol.
|
|
*
|
|
* Public official documentation does not contain the protocol
|
|
* description, so this is all based on reverse engineering.
|
|
*
|
|
* Packet structure (17 bytes):
|
|
*
|
|
* 0x00: header1 ?? (0x00)
|
|
* 0x01: header2 ?? (0x0d)
|
|
*
|
|
* 0x02: flags
|
|
* bit 0 = hold enabled
|
|
* bit 1 = reference shown (in delta mode)
|
|
* bit 2 = delta mode
|
|
* bit 3 = calibration mode
|
|
* bit 4 = sorting mode
|
|
* bit 5 = LCR mode
|
|
* bit 6 = auto mode
|
|
* bit 7 = parallel measurement (vs. serial)
|
|
*
|
|
* 0x03: config
|
|
* bit 0-4 = ??? (0x10)
|
|
* bit 5-7 = test frequency
|
|
* 0 = 100 Hz
|
|
* 1 = 120 Hz
|
|
* 2 = 1 kHz
|
|
* 3 = 10 kHz
|
|
* 4 = 100 kHz
|
|
* 5 = 0 Hz (DC)
|
|
*
|
|
* 0x04: tolerance (sorting mode)
|
|
* 0 = not set
|
|
* 3 = +-0.25%
|
|
* 4 = +-0.5%
|
|
* 5 = +-1%
|
|
* 6 = +-2%
|
|
* 7 = +-5%
|
|
* 8 = +-10%
|
|
* 9 = +-20%
|
|
* 10 = -20+80%
|
|
*
|
|
* 0x05-0x09: primary measurement
|
|
* 0x05: measured quantity
|
|
* 1 = inductance
|
|
* 2 = capacitance
|
|
* 3 = resistance
|
|
* 4 = DC resistance
|
|
* 0x06: measurement MSB (0x4e20 = 20000 = outside limits)
|
|
* 0x07: measurement LSB
|
|
* 0x08: measurement info
|
|
* bit 0-2 = decimal point multiplier (10^-val)
|
|
* bit 3-7 = unit
|
|
* 0 = no unit
|
|
* 1 = Ohm
|
|
* 2 = kOhm
|
|
* 3 = MOhm
|
|
* 5 = uH
|
|
* 6 = mH
|
|
* 7 = H
|
|
* 8 = kH
|
|
* 9 = pF
|
|
* 10 = nF
|
|
* 11 = uF
|
|
* 12 = mF
|
|
* 13 = %
|
|
* 14 = degree
|
|
* 0x09: measurement status
|
|
* bit 0-3 = status
|
|
* 0 = normal (measurement shown)
|
|
* 1 = blank (nothing shown)
|
|
* 2 = lines ("----")
|
|
* 3 = outside limits ("OL")
|
|
* 7 = pass ("PASS")
|
|
* 8 = fail ("FAIL")
|
|
* 9 = open ("OPEn")
|
|
* 10 = shorted ("Srt")
|
|
* bit 4-6 = ??? (maybe part of same field with 0-3)
|
|
* bit 7 = ??? (some independent flag)
|
|
*
|
|
* 0x0a-0x0e: secondary measurement
|
|
* 0x0a: measured quantity
|
|
* 0 = none
|
|
* 1 = dissipation factor
|
|
* 2 = quality factor
|
|
* 3 = parallel AC resistance / ESR
|
|
* 4 = phase angle
|
|
* 0x0b-0x0e: like primary measurement
|
|
*
|
|
* 0x0f: footer1 (0x0d) ?
|
|
* 0x10: footer2 (0x0a) ?
|
|
*/
|
|
|
|
static const double frequencies[] = {
|
|
SR_HZ(0), SR_HZ(100), SR_HZ(120),
|
|
SR_KHZ(1), SR_KHZ(10), SR_KHZ(100),
|
|
};
|
|
|
|
static const size_t freq_code_map[] = {
|
|
1, 2, 3, 4, 5, 0,
|
|
};
|
|
|
|
static uint64_t get_frequency(size_t code)
|
|
{
|
|
uint64_t freq;
|
|
|
|
if (code >= ARRAY_SIZE(freq_code_map)) {
|
|
sr_err("Unknown output frequency code %zu.", code);
|
|
return frequencies[0];
|
|
}
|
|
|
|
code = freq_code_map[code];
|
|
freq = frequencies[code];
|
|
|
|
return freq;
|
|
}
|
|
|
|
enum { MODEL_NONE, MODEL_PAR, MODEL_SER, MODEL_AUTO, };
|
|
|
|
static const char *const circuit_models[] = {
|
|
"NONE", "PARALLEL", "SERIES", "AUTO",
|
|
};
|
|
|
|
static const char *get_equiv_model(size_t code)
|
|
{
|
|
if (code >= ARRAY_SIZE(circuit_models)) {
|
|
sr_err("Unknown equivalent circuit model code %zu.", code);
|
|
return "NONE";
|
|
}
|
|
|
|
return circuit_models[code];
|
|
}
|
|
|
|
static const uint8_t *pkt_to_buf(const uint8_t *pkt, int is_secondary)
|
|
{
|
|
return is_secondary ? pkt + 10 : pkt + 5;
|
|
}
|
|
|
|
static int parse_mq(const uint8_t *pkt, int is_secondary, int is_parallel)
|
|
{
|
|
const uint8_t *buf;
|
|
|
|
buf = pkt_to_buf(pkt, is_secondary);
|
|
|
|
switch (is_secondary << 8 | buf[0]) {
|
|
case 0x001:
|
|
return is_parallel ?
|
|
SR_MQ_PARALLEL_INDUCTANCE : SR_MQ_SERIES_INDUCTANCE;
|
|
case 0x002:
|
|
return is_parallel ?
|
|
SR_MQ_PARALLEL_CAPACITANCE : SR_MQ_SERIES_CAPACITANCE;
|
|
case 0x003:
|
|
case 0x103:
|
|
return is_parallel ?
|
|
SR_MQ_PARALLEL_RESISTANCE : SR_MQ_SERIES_RESISTANCE;
|
|
case 0x004:
|
|
return SR_MQ_RESISTANCE;
|
|
case 0x100:
|
|
return SR_MQ_DIFFERENCE;
|
|
case 0x101:
|
|
return SR_MQ_DISSIPATION_FACTOR;
|
|
case 0x102:
|
|
return SR_MQ_QUALITY_FACTOR;
|
|
case 0x104:
|
|
return SR_MQ_PHASE_ANGLE;
|
|
}
|
|
|
|
sr_err("Unknown quantity 0x%03x.", is_secondary << 8 | buf[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static float parse_value(const uint8_t *buf, int *digits)
|
|
{
|
|
static const int exponents[] = {0, -1, -2, -3, -4, -5, -6, -7};
|
|
|
|
int exponent;
|
|
int16_t val;
|
|
float fval;
|
|
|
|
exponent = exponents[buf[3] & 7];
|
|
*digits = -exponent;
|
|
val = (buf[1] << 8) | buf[2];
|
|
fval = (float)val;
|
|
fval *= powf(10, exponent);
|
|
|
|
return fval;
|
|
}
|
|
|
|
static void parse_measurement(const uint8_t *pkt, float *floatval,
|
|
struct sr_datafeed_analog *analog, int is_secondary)
|
|
{
|
|
static const struct {
|
|
int unit;
|
|
int exponent;
|
|
} units[] = {
|
|
{ SR_UNIT_UNITLESS, 0 }, /* no unit */
|
|
{ SR_UNIT_OHM, 0 }, /* Ohm */
|
|
{ SR_UNIT_OHM, 3 }, /* kOhm */
|
|
{ SR_UNIT_OHM, 6 }, /* MOhm */
|
|
{ -1, 0 }, /* ??? */
|
|
{ SR_UNIT_HENRY, -6 }, /* uH */
|
|
{ SR_UNIT_HENRY, -3 }, /* mH */
|
|
{ SR_UNIT_HENRY, 0 }, /* H */
|
|
{ SR_UNIT_HENRY, 3 }, /* kH */
|
|
{ SR_UNIT_FARAD, -12 }, /* pF */
|
|
{ SR_UNIT_FARAD, -9 }, /* nF */
|
|
{ SR_UNIT_FARAD, -6 }, /* uF */
|
|
{ SR_UNIT_FARAD, -3 }, /* mF */
|
|
{ SR_UNIT_PERCENTAGE, 0 }, /* % */
|
|
{ SR_UNIT_DEGREE, 0 }, /* degree */
|
|
};
|
|
|
|
const uint8_t *buf;
|
|
int digits, exponent;
|
|
int state;
|
|
|
|
buf = pkt_to_buf(pkt, is_secondary);
|
|
|
|
analog->meaning->mq = 0;
|
|
analog->meaning->mqflags = 0;
|
|
|
|
state = buf[4] & 0xf;
|
|
|
|
if (state != 0 && state != 3)
|
|
return;
|
|
|
|
if (pkt[2] & 0x18) {
|
|
/* Calibration and Sorting modes not supported. */
|
|
return;
|
|
}
|
|
|
|
if (!is_secondary) {
|
|
if (pkt[2] & 0x01)
|
|
analog->meaning->mqflags |= SR_MQFLAG_HOLD;
|
|
if (pkt[2] & 0x02)
|
|
analog->meaning->mqflags |= SR_MQFLAG_REFERENCE;
|
|
} else {
|
|
if (pkt[2] & 0x04)
|
|
analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
|
|
}
|
|
|
|
if ((analog->meaning->mq = parse_mq(pkt, is_secondary, pkt[2] & 0x80)) == 0)
|
|
return;
|
|
|
|
if ((buf[3] >> 3) >= ARRAY_SIZE(units)) {
|
|
sr_err("Unknown unit %u.", buf[3] >> 3);
|
|
analog->meaning->mq = 0;
|
|
return;
|
|
}
|
|
|
|
analog->meaning->unit = units[buf[3] >> 3].unit;
|
|
|
|
exponent = units[buf[3] >> 3].exponent;
|
|
*floatval = parse_value(buf, &digits);
|
|
*floatval *= (state == 0) ? powf(10, exponent) : INFINITY;
|
|
analog->encoding->digits = digits - exponent;
|
|
analog->spec->spec_digits = digits - exponent;
|
|
}
|
|
|
|
static uint64_t parse_freq(const uint8_t *pkt)
|
|
{
|
|
return get_frequency(pkt[3] >> 5);
|
|
}
|
|
|
|
static const char *parse_model(const uint8_t *pkt)
|
|
{
|
|
size_t code;
|
|
|
|
if (pkt[2] & 0x40)
|
|
code = MODEL_AUTO;
|
|
else if (parse_mq(pkt, 0, 0) == SR_MQ_RESISTANCE)
|
|
code = MODEL_NONE;
|
|
else
|
|
code = (pkt[2] & 0x80) ? MODEL_PAR : MODEL_SER;
|
|
|
|
return get_equiv_model(code);
|
|
}
|
|
|
|
SR_PRIV gboolean es51919_packet_valid(const uint8_t *pkt)
|
|
{
|
|
|
|
/* Check for fixed 0x00 0x0d prefix. */
|
|
if (pkt[0] != 0x00 || pkt[1] != 0x0d)
|
|
return FALSE;
|
|
|
|
/* Check for fixed 0x0d 0x0a suffix. */
|
|
if (pkt[15] != 0x0d || pkt[16] != 0x0a)
|
|
return FALSE;
|
|
|
|
/* Packet appears to be valid. */
|
|
return TRUE;
|
|
}
|
|
|
|
SR_PRIV int es51919_packet_parse(const uint8_t *pkt, float *val,
|
|
struct sr_datafeed_analog *analog, void *info)
|
|
{
|
|
struct lcr_parse_info *parse_info;
|
|
|
|
parse_info = info;
|
|
if (!parse_info->ch_idx) {
|
|
parse_info->output_freq = parse_freq(pkt);
|
|
parse_info->circuit_model = parse_model(pkt);
|
|
}
|
|
if (val && analog)
|
|
parse_measurement(pkt, val, analog, parse_info->ch_idx == 1);
|
|
|
|
return SR_OK;
|
|
}
|
|
|
|
/*
|
|
* These are the get/set/list routines for the _chip_ specific parameters,
|
|
* the _device_ driver resides in src/hardware/serial-lcr/ instead.
|
|
*/
|
|
|
|
SR_PRIV int es51919_config_list(uint32_t key, GVariant **data,
|
|
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
|
|
{
|
|
|
|
(void)sdi;
|
|
(void)cg;
|
|
|
|
switch (key) {
|
|
case SR_CONF_OUTPUT_FREQUENCY:
|
|
*data = g_variant_new_fixed_array(G_VARIANT_TYPE_DOUBLE,
|
|
ARRAY_AND_SIZE(frequencies), sizeof(frequencies[0]));
|
|
return SR_OK;
|
|
case SR_CONF_EQUIV_CIRCUIT_MODEL:
|
|
*data = g_variant_new_strv(ARRAY_AND_SIZE(circuit_models));
|
|
return SR_OK;
|
|
default:
|
|
return SR_ERR_NA;
|
|
}
|
|
/* UNREACH */
|
|
}
|
|
|
|
#endif
|