# spaces.
# Note: If this tag is empty the current directory is searched.
-INPUT = src include $(BUILDDIR)include/libsigrok
+INPUT = $(SRCDIR)src $(SRCDIR)include $(BUILDDIR)include/libsigrok
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
# *.qsf, *.as and *.js.
-FILE_PATTERNS =
+# BEWARE! DON'T set the variable to an empty value. Don't set the variable
+# at all instead. See https://github.com/doxygen/doxygen/issues/7190 and
+# https://sigrok.org/bugzilla/show_bug.cgi?id=1422 (can get reverted when
+# the Doxygen version which causes the issue no longer is used in the wild).
+## FILE_PATTERNS =
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
(using the Tondaj SL-814 device as example). It's basically what the
'new-driver' script (see above) does for you:
- - Makefile.am: Add HW_TONDAJ_SL_814 and add to libsigrok_la_SOURCES.
- - configure.ac: Add a DRIVER() and DRIVER2() call.
+ - Makefile.am: Add to src_libdrivers_la_SOURCES under the HW_TONDAJ_SL_814
+ condition.
+ - configure.ac: Add an SR_DRIVER() call.
- src/drivers.c: Add a tondaj_sl_814_driver_info entry in two places.
- src/hardware/tondaj-sl-814/ directory: Add api.c, protocol.c, protocol.h.
src/output/ols.c \
src/output/srzip.c \
src/output/vcd.c \
+ src/output/wavedrom.c \
src/output/null.c
# Transform modules
src/scpi/vxi_xdr.c \
src/scpi/vxi.h
endif
+# if HAVE_BLUETOOTH
+libsigrok_la_SOURCES += \
+ src/bt/bt_bluez.c
+# endif
if NEED_SERIAL
libsigrok_la_SOURCES += \
src/serial.c \
+ src/serial_bt.c \
+ src/serial_hid.c \
+ src/serial_hid.h \
+ src/serial_hid_bu86x.c \
+ src/serial_hid_ch9325.c \
+ src/serial_hid_cp2110.c \
+ src/serial_hid_victor.c \
+ src/serial_libsp.c \
src/scpi/scpi_serial.c
endif
if NEED_USB
libsigrok_la_SOURCES += \
src/dmm/asycii.c \
src/dmm/bm25x.c \
+ src/dmm/bm86x.c \
src/dmm/dtm0660.c \
src/dmm/eev121gw.c \
src/dmm/es519xx.c \
src/dmm/fs9922.c \
src/dmm/m2110.c \
src/dmm/metex14.c \
+ src/dmm/ms2115b.c \
src/dmm/ms8250d.c \
src/dmm/rs9lcd.c \
src/dmm/ut372.c \
# Hardware (LCR chip parsers)
if NEED_SERIAL
libsigrok_la_SOURCES += \
- src/lcr/es51919.c
+ src/lcr/es51919.c \
+ src/lcr/vc4080.c
endif
# Hardware (Scale protocol parsers)
src/hardware/beaglelogic/beaglelogic_native.c \
src/hardware/beaglelogic/beaglelogic_tcp.c
endif
-if HW_BRYMEN_BM86X
-src_libdrivers_la_SOURCES += \
- src/hardware/brymen-bm86x/protocol.h \
- src/hardware/brymen-bm86x/protocol.c \
- src/hardware/brymen-bm86x/api.c
-endif
if HW_BRYMEN_DMM
src_libdrivers_la_SOURCES += \
src/hardware/brymen-dmm/parser.c \
src/hardware/manson-hcs-3xxx/protocol.c \
src/hardware/manson-hcs-3xxx/api.c
endif
+if HW_MASTECH_MS6514
+src_libdrivers_la_SOURCES += \
+ src/hardware/mastech-ms6514/protocol.h \
+ src/hardware/mastech-ms6514/protocol.c \
+ src/hardware/mastech-ms6514/api.c
+endif
if HW_MAYNUO_M97
src_libdrivers_la_SOURCES += \
src/hardware/maynuo-m97/protocol.h \
src/hardware/maynuo-m97/protocol.c \
src/hardware/maynuo-m97/api.c
endif
+if HW_MICROCHIP_PICKIT2
+src_libdrivers_la_SOURCES += \
+ src/hardware/microchip-pickit2/protocol.h \
+ src/hardware/microchip-pickit2/protocol.c \
+ src/hardware/microchip-pickit2/api.c
+endif
if HW_MIC_985XX
src_libdrivers_la_SOURCES += \
src/hardware/mic-985xx/protocol.h \
src/hardware/mic-985xx/protocol.c \
src/hardware/mic-985xx/api.c
endif
+if HW_MOOSHIMETER_DMM
+src_libdrivers_la_SOURCES += \
+ src/hardware/mooshimeter-dmm/protocol.h \
+ src/hardware/mooshimeter-dmm/protocol.c \
+ src/hardware/mooshimeter-dmm/api.c
+endif
if HW_MOTECH_LPS_30X
src_libdrivers_la_SOURCES += \
src/hardware/motech-lps-30x/protocol.h \
src/hardware/saleae-logic-pro/protocol.c \
src/hardware/saleae-logic-pro/api.c
endif
+if HW_SCPI_DMM
+src_libdrivers_la_SOURCES += \
+ src/hardware/scpi-dmm/protocol.h \
+ src/hardware/scpi-dmm/protocol.c \
+ src/hardware/scpi-dmm/api.c
+endif
if HW_SCPI_PPS
src_libdrivers_la_SOURCES += \
src/hardware/scpi-pps/protocol.h \
endif
if HW_SERIAL_LCR
src_libdrivers_la_SOURCES += \
+ src/hardware/serial-lcr/protocol.h \
+ src/hardware/serial-lcr/protocol.c \
src/hardware/serial-lcr/api.c
endif
if HW_SIGLENT_SDS
src/hardware/sysclk-lwla/protocol.c \
src/hardware/sysclk-lwla/api.c
endif
+if HW_SYSCLK_SLA5032
+src_libdrivers_la_SOURCES += \
+ src/hardware/sysclk-sla5032/protocol.h \
+ src/hardware/sysclk-sla5032/protocol.c \
+ src/hardware/sysclk-sla5032/api.c
+endif
if HW_TELEINFO
src_libdrivers_la_SOURCES += \
src/hardware/teleinfo/protocol.h \
src/hardware/uni-t-ut32x/protocol.c \
src/hardware/uni-t-ut32x/api.c
endif
-if HW_VICTOR_DMM
-src_libdrivers_la_SOURCES += \
- src/hardware/victor-dmm/protocol.h \
- src/hardware/victor-dmm/protocol.c \
- src/hardware/victor-dmm/api.c
-endif
if HW_YOKOGAWA_DLM
src_libdrivers_la_SOURCES += \
src/hardware/yokogawa-dlm/protocol.h \
pkgconfig_DATA += bindings/cxx/libsigrokcxx.pc
doxy/xml/index.xml: include/libsigrok/libsigrok.h
- $(AM_V_GEN)cd $(srcdir) && BUILDDIR=$(abs_builddir)/ doxygen Doxyfile 2>/dev/null
+ $(AM_V_GEN)cd $(srcdir) && SRCDIR=$(abs_srcdir)/ BUILDDIR=$(abs_builddir)/ doxygen Doxyfile 2>/dev/null
bindings/swig/enums.i: bindings/cxx/enums.timestamp
bindings/cxx/enums.cpp: bindings/cxx/enums.timestamp
$(CPPXMLDOC): bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp \
bindings/cxx/enums.timestamp
- $(AM_V_GEN)cd $(srcdir)/bindings/cxx && BUILDDIR=$(abs_builddir)/bindings/cxx/ doxygen Doxyfile 2>/dev/null
+ $(AM_V_GEN)cd $(srcdir)/bindings/cxx && SRCDIR=$(abs_srcdir)/bindings/cxx/ BUILDDIR=$(abs_builddir)/bindings/cxx/ doxygen Doxyfile 2>/dev/null
# Macro definitions to be used by the SWIG parser.
swig_defs = -Dnoexcept= -Dprivate=protected -DG_GNUC_BEGIN_IGNORE_DEPRECATIONS= -DG_GNUC_END_IGNORE_DEPRECATIONS=
-$(AM_V_at)$(setup_py) clean --all 2>/dev/null
python-doc:
- $(AM_V_at)cd $(srcdir)/$(PDIR) && BUILDDIR="$(abs_builddir)/$(PDIR)/" doxygen Doxyfile 2>/dev/null
+ $(AM_V_at)cd $(srcdir)/$(PDIR) && SRCDIR="$(abs_srcdir)/$(PDIR)/" BUILDDIR="$(abs_builddir)/$(PDIR)/" doxygen Doxyfile 2>/dev/null
BUILD_EXTRA += python-build
INSTALL_EXTRA += python-install
-$(AM_V_at)rm -fr $(JDIR)/doxy
java-doc:
- $(AM_V_at)cd $(srcdir)/$(JDIR) && BUILDDIR="$(abs_builddir)/$(JDIR)/" doxygen Doxyfile
+ $(AM_V_at)cd $(srcdir)/$(JDIR) && SRCDIR="$(abs_srcdir)/$(JDIR)/" BUILDDIR="$(abs_builddir)/$(JDIR)/" doxygen Doxyfile
BUILD_EXTRA += java-build
INSTALL_EXTRA += java-install
- LeCroy LogicStudio
- mcupro Logic16 clone
- Pipistrello OLS
- - SysClk LWLA1016
+ - Sysclk LWLA1016
- Oscilloscopes:
- Rigol/Agilent DS1000Z series
- Yokogawa DLM2000 series
- libserialport >= 0.1.1 (optional, used by some drivers)
- librevisa >= 0.0.20130412 (optional, used by some drivers)
- libusb-1.0 >= 1.0.16 (optional, used by some drivers)
+ - hidapi >= 0.8.0 (optional, used for some HID based "serial cables")
+ - bluez/libbluetooth >= 4.0 (optional, used for Bluetooth/BLE communication)
- libftdi1 >= 1.0 (optional, used by some drivers)
- libgpib (optional, used by some drivers)
- libieee1284 (optional, used by some drivers)
+ - libgio >= 2.32.0 (optional, used by some drivers)
- check >= 0.9.4 (optional, only needed to run unit tests)
- doxygen (optional, only needed for the C API docs)
- graphviz (optional, only needed for the C API docs)
'sigrok-firmware' repository/project under a license which allows us
to redistribute them.
+ - dreamsourcelab-dslogic: The DreamSourceLab DSLogic/DSCope device series
+ requires various firmware files and FPGA bitstream files.
+ These can be extracted/downloaded from the vendor's GitHub repo using a
+ tool from our 'sigrok-util' repository/project.
+
- fx2lafw: Logic analyzers based on the Cypress FX2(LP) chip need the
firmware files from the 'sigrok-firmware-fx2lafw' repository/project.
The firmware is written from scratch and licensed under the GNU GPLv2+.
These can be extracted from the vendor's Linux application using a tool
from our 'sigrok-util' repository/project.
+ - saleae-logic-pro: The Saleae Logic Pro 16 needs a firmware file for the
+ Cypress FX3 chip in the device, as well as an FPGA bitstream file.
+ These can be extracted from the vendor's Linux application using a tool
+ from our 'sigrok-util' repository/project.
+
- sysclk-lwla:
- The Sysclk LWLA1034 requires various bitstream files.
These can be extracted from the vendor's Windows drivers using a tool
from our 'sigrok-util' repository/project.
+ - sysclk-sla5032: The Sysclk SLA5032 needs an FPGA bitstream file.
+ This file can be copied (and renamed) from the Windows vendor software
+ installation directory. Details:
+
+ https://sigrok.org/wiki/Sysclk_SLA5032#Firmware
+
The following drivers/devices do not need any firmware upload:
- agilent-dmm
- atten-pps3xxx
- baylibre-acme
- beaglelogic
- - brymen-bm86x
- brymen-dmm
- cem-dt-885x
- center-3xx (including all subdrivers)
- colead-slm
- conrad-digi-35-cpu
- demo
+ - fluke-45
- fluke-dmm
- ftdi-la
- gmc-mh-1x-2x (including all subdrivers)
- gwinstek-gds-800
+ - gwinstek-gpd
- hameg-hmo
+ - hantek-4032l
- hp-3457a
+ - hp-3478a
- hung-chang-dso-2100
- ikalogic-scanalogic2
- ikalogic-scanaplus
+ - ipdbg-la
- kecheng-kc-330b
- kern-scale
+ - korad-kaxxxxp
- lascar-el-usb
+ - lecroy-xstream
- link-mso19
- manson-hcs-3xxx
- maynuo-m97
- mic-985xx (including all subdrivers)
+ - microchip-pickit2
+ - mooshimeter-dmm
- motech-lps-30x
- norma-dmm
- openbench-logic-sniffer
- pce-322a
- pipistrello-ols
+ - rdtech-dps
- rigol-ds
+ - rohde-schwarz-sme-0x
+ - scpi-dmm
- scpi-pps
- serial-dmm (including all subdrivers)
- serial-lcr (including all subdrivers)
- tondaj-sl-814
- uni-t-dmm (including all subdrivers)
- uni-t-ut32x
- - victor-dmm
- yokogawa-dlm
- zeroplus-logic-cube
+ - zketech-ebd-usb
Specifying serial ports
-----------------------
Many devices supported by libsigrok use serial port based cables (real RS232
-or USB-to-serial ones) to connect to a PC.
+or USB-to-serial ones, CDC class) to connect to a PC. These serial cables are
+supported by the libserialport library. Some vendors prefer to use HID chips
+instead of CDC chips in their serial cables. These cables can get supported
+by means of the hidapi library. Note that each chip type requires specific
+support in the libsigrok library. Bluetooth connected devices may be supported
+as well when they communicate by means of RFCOMM channels, or one of the
+implemented BLE notification/indication approaches, and one of the Bluetooth
+supporting platforms is used.
For all these devices, you need to specify the serial port they are connected
to (e.g. using the 'conn' option in sigrok-cli). It is not possible to scan
Example:
$ sigrok-cli --driver <somedriver>:conn=/dev/ttyUSB0 ...
-
-The following drivers/devices require a serial port specification. Some of
-the drivers implement a default for the connection.
-
- - agilent-dmm
- - appa-55ii
- - atten-pps3xxx
- - brymen-dmm
- - cem-dt-885x
- - center-3xx (including all subdrivers)
- - colead-slm
- - conrad-digi-35-cpu
- - fluke-dmm
- - gmc-mh-1x-2x (including all subdrivers)
- - hameg-hmo
- - link-mso19
- - mic-985xx (including all subdrivers)
- - norma-dmm
- - openbench-logic-sniffer
- - rigol-ds (for RS232; not required for USBTMC or TCP)
- - serial-dmm (including all subdrivers)
- - serial-lcr (including all subdrivers)
- - teleinfo
- - tondaj-sl-814
-
-The following drivers/devices do not require a serial port specification:
-
- - asix-sigma
- - brymen-bm86x
- - chronovu-la
- - demo
- - fx2lafw
- - hantek-dso
- - ikalogic-scanalogic2
- - ikalogic-scanaplus
- - kecheng-kc-330b
- - lascar-el-usb
- - pipistrello-ols
- - rigol-ds (USBTMC or TCP)
- - saleae-logic16
- - sysclk-lwla
- - uni-t-dmm (including all subdrivers)
- - uni-t-ut32x
- - victor-dmm
- - yokogawa-dlm (USBTMC or TCP)
- - zeroplus-logic-cube
-
-Beyond strict serial communication over COM ports (e.g. /dev/ttyUSB0), the
+ $ sigrok-cli --driver <somedriver>:conn=hid/cp2110 ...
+ $ sigrok-cli --driver <somedriver>:conn=bt/rfcomm/01-23-45-67-89-ab ...
+
+Formal syntax for serial communication:
+
+ - COM ports (RS232, USB CDC):
+ conn=<com-port>
+ - USB HID cables:
+ conn=hid[/<chip>]
+ conn=hid[/<chip>]/usb=<bus>.<dev>[.<if>]
+ conn=hid[/<chip>]/raw=<path>
+ conn=hid[/<chip>]/sn=<serno>
+ chip can be: bu86x, ch9325, cp2110, victor
+ path may contain slashes
+ path and serno are "greedy" (span to the end of the spec)
+ - Bluetooth Classic and Bluetooth Low Energy (BLE):
+ conn=bt/<conn>/<addr>
+ conn can be: rfcomm, ble122, nrf51, cc254x
+ addr can be "dense" or separated, bt/cc254x/0123456789ab or
+ bt/rfcomm/11-22-33-44-55-66 or bt/ble122/88:6b:12:34:56:78
+ (note that colons may not be available when the conn= spec is taken
+ from a string that separates fields by colon, e.g. in the "--driver
+ <name>:conn=<spec>" example, that is why the dense form and the use
+ of dashes for separation are supported)
+
+Some of the drivers implement a default for the connection. Some of the
+drivers can auto-detect USB connected devices.
+
+Beyond strict serial communication over COM ports (discussed above), the
conn= property can also address specific USB devices, as well as specify TCP
or VXI communication parameters. See these examples:
$ sigrok-cli --driver <somedriver>:conn=<vid>.<pid> ...
$ sigrok-cli --driver <somedriver>:conn=tcp-raw/<ipaddr>/<port> ...
$ sigrok-cli --driver <somedriver>:conn=vxi/<ipaddr> ...
-
-The following drivers/devices accept network communication parameters:
-
- - hameg-hmo
- - rigol-ds
- - siglent-sds
- - yokogawa-dlm
+ $ sigrok-cli --driver <somedriver>:conn=usbtmc/<bus>.<addr> ...
Specifying serial port parameters
UNI-T multimeters (and rebranded devices, e.g. some Voltcraft models) can
ship with different PC connectivity cables:
+ - UT-D02 (RS232 cable)
- UT-D04 (USB/HID cable with Hoitek HE2325U chip, USB VID/PID 04fa:2490)
- UT-D04 (USB/HID cable with WCH CH9325 chip, USB VID/PID 1a86:e008)
- - UT-D02 (RS232 cable)
+ - UT-D07 (Bluetooth adapter, ISSC BL79 BLETR chip)
+ - UT-D09 (USB/HID cable with SiL CP2110 chip, USB VID/PID 10c4:ea80)
The above cables are all physically compatible (same IR connector shape)
with all/most currently known UNI-T multimeters. For example, you can
- BBC Goertz Metrawatt M2110: Briefly press the "Start/Reset" button on the
interface panel on top.
+ - Brymen BM257s: Press HOLD during power-on.
- Digitek DT4000ZC: Briefly press the "RS232" button.
+ - EEVBlog 121GW: Hold "1ms PEAK" until the "BT" indicator is shown.
+ - ES51919 based LCR meters (DER EE DE-5000, PeakTech 2170, UNI-T UT612):
+ Press the button with the "RS232" or "USB" or "PC link" label (usually
+ the "up" cursor button).
- Gossen Metrawatt Metrahit 1x/2x devices, driver gmc-mh-1x-2x-rs232:
- Power on the device with the "DATA" button pressed.
- Metrahit 2x devices must be configured for the respective interface type.
'SI232 online' (28-29S) or 'SI232 store' (22-26x). The interface must
be configured to the same baud rate as the host (default 9600).
Multimeter and interface must be configured to the same address.
+ - MASTECH MS6514: Press the "Setup/PC-Link" button for roughly 3 seconds.
- Metrix MX56C: Press the PRINT button to have the meter send acquisition
data via IR. Hold the PRINT button to adjust the meter's transmission
interval.
$ sigrok-cli --driver ols:conn=/dev/ttyACM0 ...
+
+Mooshimeter
+-----------
+
+The Mooshim Engineering Mooshimeter is controlled via Bluetooth Low Energy
+(sometimes called Bluetooth 4.0), as such it requires a supported Bluetooth
+interface available. The 'conn' option is required and must contain the
+Bluetooth MAC address of the meter.
+
+Example:
+
+ $ sigrok-cli --driver mooshimeter-dmm:conn=12-34-56-78-9A-BC ...
+
+Since the Mooshimeter has no physical interface on the meter itself, the
+channel configuration is set with the 'channel_config' option. The format
+of this option is 'CH1,CH2' where each channel configuration has the form
+'MODE:RANGE:ANALYSIS', with later parts being optional. In addition for
+CLI compatibility, the ',' in the channels can also be a '/' and the ':' in
+the individual configuration can be a ';'.
+
+Available channel 1 modes:
+
+ - Current, A: Current in amps
+ - Temperature, T, K: Internal meter temperature in Kelvin
+ - Resistance, Ohm, W: Resistance in ohms
+ - Diode, D: Diode voltage
+ - Aux, LV: Auxiliary (W input) low voltage sensor (1.2V max)
+
+Available channel 2 modes:
+
+ - Voltage, V: Voltage
+ - Temperature, T, K: Internal meter temperature in Kelvin
+ - Resistance, Ohm, W: Resistance in ohms
+ - Diode, D: Diode voltage
+ - Aux, LV: Auxiliary (W input) low voltage sensor (1.2V max)
+
+Only one channel can use the shared inputs at a time (e.g. if CH1 is measuring
+resistance, CH2 cannot measure low voltage). Temperature is excepted from
+this, so the meter can measure internal temperature and low voltage at the
+same time.
+
+Additionally, the meter can calculate the real power of both channels. This
+generally only makes sense when CH1 is set to current and CH2 is set to a
+voltage and so it is disabled by default. It must be enabled by enabling the
+'P' channel (the third channel).
+
+The range of the channel specification sets the maximum input for that channel
+and is rounded up to the next value the meter itself supports. For example,
+specifying 50 for the voltage will result in the actual maximum of 60.
+Specifying 61 would result in 600. If omitted, sigrok will perform
+auto-ranging of the channel by selecting the next greater value than the
+latest maximum.
+
+The analysis option sets how the meter reports its internal sampling buffer
+to sigrok:
+
+ - Mean, DC: The default is a simple arithmetic mean of the sample buffer
+ - RMS, AC: The root mean square of the sample buffer
+ - Buf, Buffer, Samples: Report the entire sample buffer to sigrok. This
+ results in packets that contain all the samples in the buffer instead
+ of a single output value.
+
+The size of the sample buffer is set with the 'avg_samples' option, while
+the sampling rate is set with the 'samplerate' option. So the update rate
+is avg_samples/samplerate. Both are rounded up to the next supported value
+by the meter.
+
+Example:
+
+ $ sigrok-cli -c channel_config="Aux;0.1/T" --driver mooshimeter-dmm...
+ $ sigrok-cli -c channel_config="A;;AC/V;;AC" --driver mooshimeter-dmm...
return DataType::get(info->datatype);
}
-string ConfigKey::identifier() const
+std::string ConfigKey::identifier() const
{
const struct sr_key_info *info = sr_key_info_get(SR_KEY_CONFIG, id());
if (!info)
return valid_string(info->id);
}
-string ConfigKey::description() const
+std::string ConfigKey::description() const
{
const struct sr_key_info *info = sr_key_info_get(SR_KEY_CONFIG, id());
if (!info)
return valid_string(info->name);
}
-const ConfigKey *ConfigKey::get_by_identifier(string identifier)
+const ConfigKey *ConfigKey::get_by_identifier(std::string identifier)
{
const struct sr_key_info *info = sr_key_info_name_get(SR_KEY_CONFIG, identifier.c_str());
if (!info)
}
#endif
-Glib::VariantBase ConfigKey::parse_string(string value, enum sr_datatype dt)
+Glib::VariantBase ConfigKey::parse_string(std::string value, enum sr_datatype dt)
{
GVariant *variant;
uint64_t p, q;
return Glib::VariantBase(variant, false);
}
-Glib::VariantBase ConfigKey::parse_string(string value) const
+Glib::VariantBase ConfigKey::parse_string(std::string value) const
{
enum sr_datatype dt = (enum sr_datatype)(data_type()->id());
return parse_string(value, dt);
/** Data type used for this configuration key. */
const DataType *data_type() const;
/** String identifier for this configuration key, suitable for CLI use. */
- string identifier() const;
+ std::string identifier() const;
/** Description of this configuration key. */
- string description() const;
+ std::string description() const;
/** Get configuration key by string identifier. */
- static const ConfigKey *get_by_identifier(string identifier);
+ static const ConfigKey *get_by_identifier(std::string identifier);
/** Parse a string argument into the appropriate type for this key. */
- static Glib::VariantBase parse_string(string value, enum sr_datatype dt);
- Glib::VariantBase parse_string(string value) const;
+ static Glib::VariantBase parse_string(std::string value, enum sr_datatype dt);
+ Glib::VariantBase parse_string(std::string value) const;
# spaces.
# Note: If this tag is empty the current directory is searched.
-INPUT = include/libsigrokcxx/libsigrokcxx.hpp \
+INPUT = $(SRCDIR)include/libsigrokcxx/libsigrokcxx.hpp \
$(BUILDDIR)include/libsigrokcxx/enums.hpp
# This tag can be used to specify the character encoding of the source files
-vector<const QuantityFlag *>
+std::vector<const QuantityFlag *>
QuantityFlag::flags_from_mask(unsigned int mask)
{
- auto result = vector<const QuantityFlag *>();
+ auto result = std::vector<const QuantityFlag *>();
while (mask)
{
unsigned int new_mask = mask & (mask - 1);
return result;
}
-unsigned int QuantityFlag::mask_from_flags(vector<const QuantityFlag *> flags)
+unsigned int QuantityFlag::mask_from_flags(std::vector<const QuantityFlag *> flags)
{
unsigned int result = 0;
for (auto flag : flags)
/** Get flags corresponding to a bitmask. */
- static vector<const QuantityFlag *>
+ static std::vector<const QuantityFlag *>
flags_from_mask(unsigned int mask);
/** Get bitmask corresponding to a set of flags. */
static unsigned int mask_from_flags(
- vector<const QuantityFlag *> flags);
+ std::vector<const QuantityFlag *> flags);
namespace sigrok
{
+using namespace std;
+
/** Helper function to translate C errors to C++ exceptions. */
static void check(int result)
{
return shared_ptr<Packet>{new Packet{nullptr, packet}, default_delete<Packet>{}};
}
+shared_ptr<Packet> Context::create_end_packet()
+{
+ auto packet = g_new(struct sr_datafeed_packet, 1);
+ packet->type = SR_DF_END;
+ return shared_ptr<Packet>{new Packet{nullptr, packet},
+ default_delete<Packet>{}};
+}
+
shared_ptr<Session> Context::load_session(string filename)
{
return shared_ptr<Session>{
_owned_devices.emplace(sdi, move(device));
}
_context->_session = this;
+ g_slist_free(dev_list);
}
Session::~Session()
auto *const sdi = static_cast<struct sr_dev_inst *>(dev->data);
result.push_back(get_device(sdi));
}
+ g_slist_free(dev_list);
return result;
}
check(sr_output_free(_structure));
}
+shared_ptr<OutputFormat> Output::format()
+{
+ return _format;
+}
+
string Output::receive(shared_ptr<Packet> packet)
{
GString *out;
namespace sigrok
{
-using namespace std;
-
/* Forward declarations */
class SR_API Error;
class SR_API Context;
class SR_API UserDevice;
/** Exception thrown when an error code is returned by any libsigrok call. */
-class SR_API Error: public exception
+class SR_API Error: public std::exception
{
public:
explicit Error(int result);
{
private:
/* Weak pointer for shared_from_this() implementation. */
- weak_ptr<Class> _weak_this;
+ std::weak_ptr<Class> _weak_this;
static void reset_parent(Class *object)
{
This strategy ensures that the destructors for both the child and
the parent are called at the correct time, i.e. only when all
references to both the parent and all its children are gone. */
- shared_ptr<Parent> _parent;
+ std::shared_ptr<Parent> _parent;
ParentOwned() {}
/* Note, this implementation will create a new smart_ptr if none exists. */
- shared_ptr<Class> shared_from_this()
+ std::shared_ptr<Class> shared_from_this()
{
- shared_ptr<Class> shared = _weak_this.lock();
+ std::shared_ptr<Class> shared = _weak_this.lock();
if (!shared)
{
return shared;
}
- shared_ptr<Class> share_owned_by(shared_ptr<Parent> parent)
+ std::shared_ptr<Class> share_owned_by(std::shared_ptr<Parent> parent)
{
if (!parent)
throw Error(SR_ERR_BUG);
public:
/* Get parent object that owns this object. */
- shared_ptr<Parent> parent()
+ std::shared_ptr<Parent> parent()
{
return _parent;
}
/* Base template for classes whose resources are owned by the user. */
template <class Class>
-class SR_API UserOwned : public enable_shared_from_this<Class>
+class SR_API UserOwned : public std::enable_shared_from_this<Class>
{
protected:
UserOwned() {}
- shared_ptr<Class> shared_from_this()
+ std::shared_ptr<Class> shared_from_this()
{
- auto shared = enable_shared_from_this<Class>::shared_from_this();
+ auto shared = std::enable_shared_from_this<Class>::shared_from_this();
if (!shared)
throw Error(SR_ERR_BUG);
return shared;
};
/** Type of log callback */
-typedef function<void(const LogLevel *, string message)> LogCallbackFunction;
+typedef std::function<void(const LogLevel *, std::string message)> LogCallbackFunction;
/** Resource reader delegate. */
class SR_API ResourceReader
virtual ~ResourceReader();
private:
/** Resource open hook. */
- virtual void open(struct sr_resource *res, string name) = 0;
+ virtual void open(struct sr_resource *res, std::string name) = 0;
/** Resource close hook. */
virtual void close(struct sr_resource *res) = 0;
/** Resource read hook. */
{
public:
/** Create new context */
- static shared_ptr<Context> create();
+ static std::shared_ptr<Context> create();
/** libsigrok package version. */
- static string package_version();
+ static std::string package_version();
/** libsigrok library version. */
- static string lib_version();
+ static std::string lib_version();
/** Available hardware drivers, indexed by name. */
- map<string, shared_ptr<Driver> > drivers();
+ std::map<std::string, std::shared_ptr<Driver> > drivers();
/** Available input formats, indexed by name. */
- map<string, shared_ptr<InputFormat> > input_formats();
+ std::map<std::string, std::shared_ptr<InputFormat> > input_formats();
/** Lookup the responsible input module for an input file. */
- shared_ptr<InputFormat> input_format_match(string filename);
+ std::shared_ptr<InputFormat> input_format_match(std::string filename);
/** Available output formats, indexed by name. */
- map<string, shared_ptr<OutputFormat> > output_formats();
+ std::map<std::string, std::shared_ptr<OutputFormat> > output_formats();
/** Current log level. */
const LogLevel *log_level() const;
/** Set the log level.
* @param reader The resource reader delegate, or nullptr to unset. */
void set_resource_reader(ResourceReader *reader);
/** Create a new session. */
- shared_ptr<Session> create_session();
+ std::shared_ptr<Session> create_session();
/** Create a new user device. */
- shared_ptr<UserDevice> create_user_device(
- string vendor, string model, string version);
+ std::shared_ptr<UserDevice> create_user_device(
+ std::string vendor, std::string model, std::string version);
/** Create a header packet. */
- shared_ptr<Packet> create_header_packet(Glib::TimeVal start_time);
+ std::shared_ptr<Packet> create_header_packet(Glib::TimeVal start_time);
/** Create a meta packet. */
- shared_ptr<Packet> create_meta_packet(
- map<const ConfigKey *, Glib::VariantBase> config);
+ std::shared_ptr<Packet> create_meta_packet(
+ std::map<const ConfigKey *, Glib::VariantBase> config);
/** Create a logic packet. */
- shared_ptr<Packet> create_logic_packet(
+ std::shared_ptr<Packet> create_logic_packet(
void *data_pointer, size_t data_length, unsigned int unit_size);
/** Create an analog packet. */
- shared_ptr<Packet> create_analog_packet(
- vector<shared_ptr<Channel> > channels,
+ std::shared_ptr<Packet> create_analog_packet(
+ std::vector<std::shared_ptr<Channel> > channels,
float *data_pointer, unsigned int num_samples, const Quantity *mq,
- const Unit *unit, vector<const QuantityFlag *> mqflags);
+ const Unit *unit, std::vector<const QuantityFlag *> mqflags);
+ /** Create an end packet. */
+ std::shared_ptr<Packet> create_end_packet();
/** Load a saved session.
* @param filename File name string. */
- shared_ptr<Session> load_session(string filename);
+ std::shared_ptr<Session> load_session(std::string filename);
/** Create a new trigger.
* @param name Name string for new trigger. */
- shared_ptr<Trigger> create_trigger(string name);
+ std::shared_ptr<Trigger> create_trigger(std::string name);
/** Open an input file.
* @param filename File name string. */
- shared_ptr<Input> open_file(string filename);
+ std::shared_ptr<Input> open_file(std::string filename);
/** Open an input stream based on header data.
* @param header Initial data from stream. */
- shared_ptr<Input> open_stream(string header);
- map<string, string> serials(shared_ptr<Driver> driver) const;
+ std::shared_ptr<Input> open_stream(std::string header);
+ std::map<std::string, std::string> serials(std::shared_ptr<Driver> driver) const;
private:
struct sr_context *_structure;
- map<string, unique_ptr<Driver> > _drivers;
- map<string, unique_ptr<InputFormat> > _input_formats;
- map<string, unique_ptr<OutputFormat> > _output_formats;
+ std::map<std::string, std::unique_ptr<Driver> > _drivers;
+ std::map<std::string, std::unique_ptr<InputFormat> > _input_formats;
+ std::map<std::string, std::unique_ptr<OutputFormat> > _output_formats;
Session *_session;
LogCallbackFunction _log_callback;
Context();
{
public:
/** Supported configuration keys. */
- set<const ConfigKey *> config_keys() const;
+ std::set<const ConfigKey *> config_keys() const;
/** Read configuration for the given key.
* @param key ConfigKey to read. */
Glib::VariantBase config_get(const ConfigKey *key) const;
Glib::VariantContainerBase config_list(const ConfigKey *key) const;
/** Enumerate configuration capabilities for the given configuration key.
* @param key ConfigKey to enumerate capabilities for. */
- set<const Capability *> config_capabilities(const ConfigKey *key) const;
+ std::set<const Capability *> config_capabilities(const ConfigKey *key) const;
/** Check whether a configuration capability is supported for a given key.
* @param key ConfigKey to check.
* @param capability Capability to check for. */
{
public:
/** Name of this driver. */
- string name() const;
+ std::string name() const;
/** Long name for this driver. */
- string long_name() const;
+ std::string long_name() const;
/** Scan options supported by this driver. */
- set<const ConfigKey *> scan_options() const;
+ std::set<const ConfigKey *> scan_options() const;
/** Scan for devices and return a list of devices found.
* @param options Mapping of (ConfigKey, value) pairs. */
- vector<shared_ptr<HardwareDevice> > scan(map<const ConfigKey *, Glib::VariantBase>
- options = map<const ConfigKey *, Glib::VariantBase>());
+ std::vector<std::shared_ptr<HardwareDevice> > scan(std::map<const ConfigKey *, Glib::VariantBase>
+ options = std::map<const ConfigKey *, Glib::VariantBase>());
private:
struct sr_dev_driver *_structure;
bool _initialized;
- vector<HardwareDevice *> _devices;
+ std::vector<HardwareDevice *> _devices;
explicit Driver(struct sr_dev_driver *structure);
~Driver();
friend class Context;
{
public:
/** Vendor name for this device. */
- string vendor() const;
+ std::string vendor() const;
/** Model name for this device. */
- string model() const;
+ std::string model() const;
/** Version string for this device. */
- string version() const;
+ std::string version() const;
/** Serial number for this device. */
- string serial_number() const;
+ std::string serial_number() const;
/** Connection ID for this device. */
- string connection_id() const;
+ std::string connection_id() const;
/** List of the channels available on this device. */
- vector<shared_ptr<Channel> > channels();
+ std::vector<std::shared_ptr<Channel> > channels();
/** Channel groups available on this device, indexed by name. */
- map<string, shared_ptr<ChannelGroup> > channel_groups();
+ std::map<std::string, std::shared_ptr<ChannelGroup> > channel_groups();
/** Open device. */
void open();
/** Close device. */
protected:
explicit Device(struct sr_dev_inst *structure);
~Device();
- virtual shared_ptr<Device> get_shared_from_this() = 0;
- shared_ptr<Channel> get_channel(struct sr_channel *ptr);
+ virtual std::shared_ptr<Device> get_shared_from_this() = 0;
+ std::shared_ptr<Channel> get_channel(struct sr_channel *ptr);
struct sr_dev_inst *_structure;
- map<struct sr_channel *, unique_ptr<Channel> > _channels;
+ std::map<struct sr_channel *, std::unique_ptr<Channel> > _channels;
private:
- map<string, unique_ptr<ChannelGroup> > _channel_groups;
+ std::map<std::string, std::unique_ptr<ChannelGroup> > _channel_groups;
friend class Session;
friend class Channel;
{
public:
/** Driver providing this device. */
- shared_ptr<Driver> driver();
+ std::shared_ptr<Driver> driver();
private:
- HardwareDevice(shared_ptr<Driver> driver, struct sr_dev_inst *structure);
+ HardwareDevice(std::shared_ptr<Driver> driver, struct sr_dev_inst *structure);
~HardwareDevice();
- shared_ptr<Device> get_shared_from_this();
- shared_ptr<Driver> _driver;
+ std::shared_ptr<Device> get_shared_from_this();
+ std::shared_ptr<Driver> _driver;
friend class Driver;
friend class ChannelGroup;
{
public:
/** Add a new channel to this device. */
- shared_ptr<Channel> add_channel(unsigned int index, const ChannelType *type, string name);
+ std::shared_ptr<Channel> add_channel(unsigned int index, const ChannelType *type, std::string name);
private:
- UserDevice(string vendor, string model, string version);
+ UserDevice(std::string vendor, std::string model, std::string version);
~UserDevice();
- shared_ptr<Device> get_shared_from_this();
+ std::shared_ptr<Device> get_shared_from_this();
friend class Context;
friend struct std::default_delete<UserDevice>;
{
public:
/** Current name of this channel. */
- string name() const;
+ std::string name() const;
/** Set the name of this channel. *
* @param name Name string to set. */
- void set_name(string name);
+ void set_name(std::string name);
/** Type of this channel. */
const ChannelType *type() const;
/** Enabled status of this channel. */
{
public:
/** Name of this channel group. */
- string name() const;
+ std::string name() const;
/** List of the channels in this group. */
- vector<shared_ptr<Channel> > channels();
+ std::vector<std::shared_ptr<Channel> > channels();
private:
ChannelGroup(const Device *device, struct sr_channel_group *structure);
~ChannelGroup();
- vector<Channel *> _channels;
+ std::vector<Channel *> _channels;
friend class Device;
friend struct std::default_delete<ChannelGroup>;
};
{
public:
/** Name of this trigger configuration. */
- string name() const;
+ std::string name() const;
/** List of the stages in this trigger. */
- vector<shared_ptr<TriggerStage> > stages();
+ std::vector<std::shared_ptr<TriggerStage> > stages();
/** Add a new stage to this trigger. */
- shared_ptr<TriggerStage> add_stage();
+ std::shared_ptr<TriggerStage> add_stage();
private:
- Trigger(shared_ptr<Context> context, string name);
+ Trigger(std::shared_ptr<Context> context, std::string name);
~Trigger();
struct sr_trigger *_structure;
- shared_ptr<Context> _context;
- vector<unique_ptr<TriggerStage> > _stages;
+ std::shared_ptr<Context> _context;
+ std::vector<std::unique_ptr<TriggerStage> > _stages;
friend class Context;
friend class Session;
friend struct std::default_delete<Trigger>;
/** Index number of this stage. */
int number() const;
/** List of match conditions on this stage. */
- vector<shared_ptr<TriggerMatch> > matches();
+ std::vector<std::shared_ptr<TriggerMatch> > matches();
/** Add a new match condition to this stage.
* @param channel Channel to match on.
* @param type TriggerMatchType to apply. */
- void add_match(shared_ptr<Channel> channel, const TriggerMatchType *type);
+ void add_match(std::shared_ptr<Channel> channel, const TriggerMatchType *type);
/** Add a new match condition to this stage.
* @param channel Channel to match on.
* @param type TriggerMatchType to apply.
* @param value Threshold value. */
- void add_match(shared_ptr<Channel> channel, const TriggerMatchType *type, float value);
+ void add_match(std::shared_ptr<Channel> channel, const TriggerMatchType *type, float value);
private:
struct sr_trigger_stage *_structure;
- vector<unique_ptr<TriggerMatch> > _matches;
+ std::vector<std::unique_ptr<TriggerMatch> > _matches;
explicit TriggerStage(struct sr_trigger_stage *structure);
~TriggerStage();
friend class Trigger;
{
public:
/** Channel this condition matches on. */
- shared_ptr<Channel> channel();
+ std::shared_ptr<Channel> channel();
/** Type of match. */
const TriggerMatchType *type() const;
/** Threshold value. */
float value() const;
private:
- TriggerMatch(struct sr_trigger_match *structure, shared_ptr<Channel> channel);
+ TriggerMatch(struct sr_trigger_match *structure, std::shared_ptr<Channel> channel);
~TriggerMatch();
struct sr_trigger_match *_structure;
- shared_ptr<Channel> _channel;
+ std::shared_ptr<Channel> _channel;
friend class TriggerStage;
friend struct std::default_delete<TriggerMatch>;
};
/** Type of session stopped callback */
-typedef function<void()> SessionStoppedCallback;
+typedef std::function<void()> SessionStoppedCallback;
/** Type of datafeed callback */
-typedef function<void(shared_ptr<Device>, shared_ptr<Packet>)>
+typedef std::function<void(std::shared_ptr<Device>, std::shared_ptr<Packet>)>
DatafeedCallbackFunction;
/* Data required for C callback function to call a C++ datafeed callback */
private:
explicit SessionDevice(struct sr_dev_inst *sdi);
~SessionDevice();
- shared_ptr<Device> get_shared_from_this();
+ std::shared_ptr<Device> get_shared_from_this();
friend class Session;
friend struct std::default_delete<SessionDevice>;
public:
/** Add a device to this session.
* @param device Device to add. */
- void add_device(shared_ptr<Device> device);
+ void add_device(std::shared_ptr<Device> device);
/** List devices attached to this session. */
- vector<shared_ptr<Device> > devices();
+ std::vector<std::shared_ptr<Device> > devices();
/** Remove all devices from this session. */
void remove_devices();
/** Add a datafeed callback to this session.
/** Set callback to be invoked on session stop. */
void set_stopped_callback(SessionStoppedCallback callback);
/** Get current trigger setting. */
- shared_ptr<Trigger> trigger();
+ std::shared_ptr<Trigger> trigger();
/** Get the context. */
- shared_ptr<Context> context();
+ std::shared_ptr<Context> context();
/** Set trigger setting.
* @param trigger Trigger object to use. */
- void set_trigger(shared_ptr<Trigger> trigger);
+ void set_trigger(std::shared_ptr<Trigger> trigger);
/** Get filename this session was loaded from. */
- string filename() const;
+ std::string filename() const;
private:
- explicit Session(shared_ptr<Context> context);
- Session(shared_ptr<Context> context, string filename);
+ explicit Session(std::shared_ptr<Context> context);
+ Session(std::shared_ptr<Context> context, std::string filename);
~Session();
- shared_ptr<Device> get_device(const struct sr_dev_inst *sdi);
+ std::shared_ptr<Device> get_device(const struct sr_dev_inst *sdi);
struct sr_session *_structure;
- const shared_ptr<Context> _context;
- map<const struct sr_dev_inst *, unique_ptr<SessionDevice> > _owned_devices;
- map<const struct sr_dev_inst *, shared_ptr<Device> > _other_devices;
- vector<unique_ptr<DatafeedCallbackData> > _datafeed_callbacks;
+ const std::shared_ptr<Context> _context;
+ std::map<const struct sr_dev_inst *, std::unique_ptr<SessionDevice> > _owned_devices;
+ std::map<const struct sr_dev_inst *, std::shared_ptr<Device> > _other_devices;
+ std::vector<std::unique_ptr<DatafeedCallbackData> > _datafeed_callbacks;
SessionStoppedCallback _stopped_callback;
- string _filename;
- shared_ptr<Trigger> _trigger;
+ std::string _filename;
+ std::shared_ptr<Trigger> _trigger;
friend class Context;
friend class DatafeedCallbackData;
/** Type of this packet. */
const PacketType *type() const;
/** Payload of this packet. */
- shared_ptr<PacketPayload> payload();
+ std::shared_ptr<PacketPayload> payload();
private:
- Packet(shared_ptr<Device> device,
+ Packet(std::shared_ptr<Device> device,
const struct sr_datafeed_packet *structure);
~Packet();
const struct sr_datafeed_packet *_structure;
- shared_ptr<Device> _device;
- unique_ptr<PacketPayload> _payload;
+ std::shared_ptr<Device> _device;
+ std::unique_ptr<PacketPayload> _payload;
friend class Session;
friend class Output;
PacketPayload();
virtual ~PacketPayload() = 0;
private:
- virtual shared_ptr<PacketPayload> share_owned_by(shared_ptr<Packet> parent) = 0;
+ virtual std::shared_ptr<PacketPayload> share_owned_by(std::shared_ptr<Packet> parent) = 0;
friend class Packet;
friend class Output;
private:
explicit Header(const struct sr_datafeed_header *structure);
~Header();
- shared_ptr<PacketPayload> share_owned_by(shared_ptr<Packet> parent);
+ std::shared_ptr<PacketPayload> share_owned_by(std::shared_ptr<Packet> parent);
const struct sr_datafeed_header *_structure;
{
public:
/* Mapping of (ConfigKey, value) pairs. */
- map<const ConfigKey *, Glib::VariantBase> config() const;
+ std::map<const ConfigKey *, Glib::VariantBase> config() const;
private:
explicit Meta(const struct sr_datafeed_meta *structure);
~Meta();
- shared_ptr<PacketPayload> share_owned_by(shared_ptr<Packet> parent);
+ std::shared_ptr<PacketPayload> share_owned_by(std::shared_ptr<Packet> parent);
const struct sr_datafeed_meta *_structure;
- map<const ConfigKey *, Glib::VariantBase> _config;
+ std::map<const ConfigKey *, Glib::VariantBase> _config;
friend class Packet;
};
private:
explicit Logic(const struct sr_datafeed_logic *structure);
~Logic();
- shared_ptr<PacketPayload> share_owned_by(shared_ptr<Packet> parent);
+ std::shared_ptr<PacketPayload> share_owned_by(std::shared_ptr<Packet> parent);
const struct sr_datafeed_logic *_structure;
/** Number of samples in this packet. */
unsigned int num_samples() const;
/** Channels for which this packet contains data. */
- vector<shared_ptr<Channel> > channels();
+ std::vector<std::shared_ptr<Channel> > channels();
/** Size of a single sample in bytes. */
unsigned int unitsize() const;
/** Samples use a signed data type. */
/** TBD */
bool is_digits_decimal() const;
/** TBD */
- shared_ptr<Rational> scale();
+ std::shared_ptr<Rational> scale();
/** TBD */
- shared_ptr<Rational> offset();
+ std::shared_ptr<Rational> offset();
/** Measured quantity of the samples in this packet. */
const Quantity *mq() const;
/** Unit of the samples in this packet. */
const Unit *unit() const;
/** Measurement flags associated with the samples in this packet. */
- vector<const QuantityFlag *> mq_flags() const;
+ std::vector<const QuantityFlag *> mq_flags() const;
/**
* Provides a Logic packet that contains a conversion of the analog
* data using a simple threshold.
* logic->data_pointer() will be allocated and must
* be freed by the caller.
*/
- shared_ptr<Logic> get_logic_via_threshold(float threshold,
+ std::shared_ptr<Logic> get_logic_via_threshold(float threshold,
uint8_t *data_ptr=nullptr) const;
/**
* Provides a Logic packet that contains a conversion of the analog
* logic->data_pointer() will be allocated and must be
* freed by the caller.
*/
- shared_ptr<Logic> get_logic_via_schmitt_trigger(float lo_thr,
+ std::shared_ptr<Logic> get_logic_via_schmitt_trigger(float lo_thr,
float hi_thr, uint8_t *state, uint8_t *data_ptr=nullptr) const;
private:
explicit Analog(const struct sr_datafeed_analog *structure);
~Analog();
- shared_ptr<PacketPayload> share_owned_by(shared_ptr<Packet> parent);
+ std::shared_ptr<PacketPayload> share_owned_by(std::shared_ptr<Packet> parent);
const struct sr_datafeed_analog *_structure;
private:
explicit Rational(const struct sr_rational *structure);
~Rational();
- shared_ptr<Rational> share_owned_by(shared_ptr<Analog> parent);
+ std::shared_ptr<Rational> share_owned_by(std::shared_ptr<Analog> parent);
const struct sr_rational *_structure;
{
public:
/** Name of this input format. */
- string name() const;
+ std::string name() const;
/** Description of this input format. */
- string description() const;
+ std::string description() const;
/** A list of preferred file name extensions for this file format.
* @note This list is a recommendation only. */
- vector<string> extensions() const;
+ std::vector<std::string> extensions() const;
/** Options supported by this input format. */
- map<string, shared_ptr<Option> > options();
+ std::map<std::string, std::shared_ptr<Option> > options();
/** Create an input using this input format.
* @param options Mapping of (option name, value) pairs. */
- shared_ptr<Input> create_input(map<string, Glib::VariantBase>
- options = map<string, Glib::VariantBase>());
+ std::shared_ptr<Input> create_input(std::map<std::string, Glib::VariantBase>
+ options = std::map<std::string, Glib::VariantBase>());
private:
explicit InputFormat(const struct sr_input_module *structure);
~InputFormat();
{
public:
/** Virtual device associated with this input. */
- shared_ptr<InputDevice> device();
+ std::shared_ptr<InputDevice> device();
/** Send next stream data.
* @param data Next stream data.
* @param length Length of data. */
void end();
void reset();
private:
- Input(shared_ptr<Context> context, const struct sr_input *structure);
+ Input(std::shared_ptr<Context> context, const struct sr_input *structure);
~Input();
const struct sr_input *_structure;
- shared_ptr<Context> _context;
- unique_ptr<InputDevice> _device;
+ std::shared_ptr<Context> _context;
+ std::unique_ptr<InputDevice> _device;
friend class Context;
friend class InputFormat;
public Device
{
private:
- InputDevice(shared_ptr<Input> input, struct sr_dev_inst *sdi);
+ InputDevice(std::shared_ptr<Input> input, struct sr_dev_inst *sdi);
~InputDevice();
- shared_ptr<Device> get_shared_from_this();
- shared_ptr<Input> _input;
+ std::shared_ptr<Device> get_shared_from_this();
+ std::shared_ptr<Input> _input;
friend class Input;
friend struct std::default_delete<InputDevice>;
};
{
public:
/** Short name of this option suitable for command line usage. */
- string id() const;
+ std::string id() const;
/** Short name of this option suitable for GUI usage. */
- string name() const;
+ std::string name() const;
/** Description of this option in a sentence. */
- string description() const;
+ std::string description() const;
/** Default value for this option. */
Glib::VariantBase default_value() const;
/** Possible values for this option, if a limited set. */
- vector<Glib::VariantBase> values() const;
+ std::vector<Glib::VariantBase> values() const;
/** Parse a string argument into the appropriate type for this option. */
- Glib::VariantBase parse_string(string value);
+ Glib::VariantBase parse_string(std::string value);
private:
Option(const struct sr_option *structure,
- shared_ptr<const struct sr_option *> structure_array);
+ std::shared_ptr<const struct sr_option *> structure_array);
~Option();
const struct sr_option *_structure;
- shared_ptr<const struct sr_option *> _structure_array;
+ std::shared_ptr<const struct sr_option *> _structure_array;
friend class InputFormat;
friend class OutputFormat;
{
public:
/** Name of this output format. */
- string name() const;
+ std::string name() const;
/** Description of this output format. */
- string description() const;
+ std::string description() const;
/** A list of preferred file name extensions for this file format.
* @note This list is a recommendation only. */
- vector<string> extensions() const;
+ std::vector<std::string> extensions() const;
/** Options supported by this output format. */
- map<string, shared_ptr<Option> > options();
+ std::map<std::string, std::shared_ptr<Option> > options();
/** Create an output using this format.
* @param device Device to output for.
* @param options Mapping of (option name, value) pairs. */
- shared_ptr<Output> create_output(shared_ptr<Device> device,
- map<string, Glib::VariantBase> options = map<string, Glib::VariantBase>());
+ std::shared_ptr<Output> create_output(std::shared_ptr<Device> device,
+ std::map<std::string, Glib::VariantBase> options = std::map<std::string, Glib::VariantBase>());
/** Create an output using this format.
* @param filename Name of destination file.
* @param device Device to output for.
* @param options Mapping of (option name, value) pairs. */
- shared_ptr<Output> create_output(string filename,
- shared_ptr<Device> device,
- map<string, Glib::VariantBase> options = map<string, Glib::VariantBase>());
+ std::shared_ptr<Output> create_output(std::string filename,
+ std::shared_ptr<Device> device,
+ std::map<std::string, Glib::VariantBase> options = std::map<std::string, Glib::VariantBase>());
/**
* Checks whether a given flag is set.
* @param flag Flag to check
public:
/** Update output with data from the given packet.
* @param packet Packet to handle. */
- string receive(shared_ptr<Packet> packet);
+ std::string receive(std::shared_ptr<Packet> packet);
+ /** Output format in use for this output */
+ std::shared_ptr<OutputFormat> format();
private:
- Output(shared_ptr<OutputFormat> format, shared_ptr<Device> device);
- Output(shared_ptr<OutputFormat> format,
- shared_ptr<Device> device, map<string, Glib::VariantBase> options);
- Output(string filename, shared_ptr<OutputFormat> format,
- shared_ptr<Device> device, map<string, Glib::VariantBase> options);
+ Output(std::shared_ptr<OutputFormat> format, std::shared_ptr<Device> device);
+ Output(std::shared_ptr<OutputFormat> format,
+ std::shared_ptr<Device> device, std::map<std::string, Glib::VariantBase> options);
+ Output(std::string filename, std::shared_ptr<OutputFormat> format,
+ std::shared_ptr<Device> device, std::map<std::string, Glib::VariantBase> options);
~Output();
const struct sr_output *_structure;
- const shared_ptr<OutputFormat> _format;
- const shared_ptr<Device> _device;
- const map<string, Glib::VariantBase> _options;
+ const std::shared_ptr<OutputFormat> _format;
+ const std::shared_ptr<Device> _device;
+ const std::map<std::string, Glib::VariantBase> _options;
friend class OutputFormat;
friend struct std::default_delete<Output>;
return static_cast<int>(_id);
}
/** The name associated with this value. */
- string name() const
+ std::string name() const
{
return _name;
}
private:
static const std::map<const Enum, const Class * const> _values;
const Enum _id;
- const string _name;
+ const std::string _name;
};
}
# spaces.
# Note: If this tag is empty the current directory is searched.
-INPUT = org/sigrok/core $(BUILDDIR)org/sigrok/core
+INPUT = $(SRCDIR)org/sigrok/core $(BUILDDIR)org/sigrok/core
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
/* Ignore these methods, we will override them below. */
%ignore sigrok::Analog::data;
+%ignore sigrok::Logic::data;
%ignore sigrok::Driver::scan;
%ignore sigrok::InputFormat::create_input;
%ignore sigrok::OutputFormat::create_output;
}
}
+/* Return NumPy array from Logic::data(). */
+%extend sigrok::Logic
+{
+ PyObject * _data()
+ {
+ npy_intp dims[2];
+ dims[0] = $self->data_length() / $self->unit_size();
+ dims[1] = $self->unit_size();
+ int typenum = NPY_UINT8;
+ void *data = $self->data_pointer();
+ return PyArray_SimpleNewFromData(2, dims, typenum, data);
+ }
+
+%pythoncode
+{
+ data = property(_data)
+}
+}
+
+/* Create logic packet from Python buffer. */
+%extend sigrok::Context
+{
+ std::shared_ptr<Packet> _create_logic_packet_buf(PyObject *buf, unsigned int unit_size)
+ {
+ Py_buffer view;
+ PyObject_GetBuffer(buf, &view, PyBUF_SIMPLE);
+ return $self->create_logic_packet(view.buf, view.len, unit_size);
+ }
+}
+
+%pythoncode
+{
+ def _Context_create_logic_packet(self, buf, unit_size):
+ return self._create_logic_packet_buf(buf, unit_size)
+
+ Context.create_logic_packet = _Context_create_logic_packet
+}
+
%include "doc_end.i"
SR_EXTRA_LIBS=
SR_EXTRA_CXX_LIBS=
-SR_ARG_OPT_PKG([libserialport], [LIBSERIALPORT], [NEED_SERIAL],
+SR_ARG_OPT_PKG([libserialport], [LIBSERIALPORT], ,
[libserialport >= 0.1.1])
SR_ARG_OPT_PKG([libftdi], [LIBFTDI], , [libftdi1 >= 1.0])
+# pkg-config file names: MinGW/MacOSX: hidapi; Linux: hidapi-hidraw/-libusb
+SR_ARG_OPT_PKG([libhidapi], [LIBHIDAPI], ,
+ [hidapi >= 0.8.0], [hidapi-hidraw >= 0.8.0], [hidapi-libusb >= 0.8.0])
+
+SR_ARG_OPT_PKG([libbluez], [LIBBLUEZ], , [bluez >= 4.0])
+
# FreeBSD comes with an "integrated" libusb-1.0-style USB API.
# This means libusb-1.0 is always available; no need to check for it.
# On Windows, require the latest version we can get our hands on,
AS_IF([test "x$sr_have_libieee1284" = xyes],
[SR_PREPEND([SR_EXTRA_LIBS], [-lieee1284])])
+SR_ARG_OPT_PKG([libgio], [LIBGIO], , [gio-2.0 >= 2.24.0])
+
+# See if any of the (potentially platform specific) libs are available
+# which provide some means of Bluetooth communication.
+AS_IF([test "x$sr_have_libbluez" = xyes],
+ sr_have_bluetooth=yes, sr_have_bluetooth=no)
+AS_IF([test "x$sr_have_bluetooth" = xyes],
+ [AC_DEFINE([HAVE_BLUETOOTH], [1], [Specifies whether Bluetooth communication is supported.])])
+AS_IF([test "x$sr_have_bluetooth" = xyes],
+ [SR_APPEND([sr_deps_avail], [bluetooth_comm])])
+
+AS_IF([test "x$sr_have_libserialport" = xyes -o "x$sr_have_libhidapi" = xyes -o "x$sr_have_bluetooth" = xyes],
+ sr_have_serial_comm=yes, sr_have_serial_comm=no)
+AS_IF([test "x$sr_have_serial_comm" = xyes],
+ [AC_DEFINE([HAVE_SERIAL_COMM], [1], [Specifies whether serial communication is supported.])])
+AS_IF([test "x$sr_have_serial_comm" = xyes],
+ [SR_APPEND([sr_deps_avail], [serial_comm])])
+AM_CONDITIONAL([NEED_SERIAL], [test "x$sr_have_serial_comm" = xyes])
+
######################
## Feature checks ##
######################
# Check for compiler support of 128 bit integers
AC_CHECK_TYPES([__int128_t, __uint128_t], [], [], [])
+# Availability of bt_put_le16() depends on the bluez library version.
+AC_CACHE_CHECK([for bt_put_le16], [sr_cv_have_btputle16],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM(
+ [[#include <bluetooth/bluetooth.h>]],
+ [[bt_put_le16(0, (void *)0);]])],
+ [sr_cv_have_btputle16=yes], [sr_cv_have_btputle16=no])])
+AS_IF([test "x$sr_cv_have_btputle16" = xyes],
+ [AC_DEFINE([HAVE_BT_PUT_LE16], [1], [Specifies whether we have bt_put_le16().])])
+
########################
## Hardware drivers ##
########################
m4_define([SR_DRIVER],
[_SR_DRIVER([$1], [$2], m4_expand([AS_TR_CPP([HW_$2])]), [$3])])
-SR_DRIVER([Agilent DMM], [agilent-dmm], [libserialport])
-SR_DRIVER([Appa 55II], [appa-55ii], [libserialport])
-SR_DRIVER([Arachnid Labs Re:load Pro], [arachnid-labs-re-load-pro], [libserialport])
+SR_DRIVER([Agilent DMM], [agilent-dmm], [serial_comm])
+SR_DRIVER([Appa 55II], [appa-55ii], [serial_comm])
+SR_DRIVER([Arachnid Labs Re:load Pro], [arachnid-labs-re-load-pro], [serial_comm])
SR_DRIVER([ASIX SIGMA/SIGMA2], [asix-sigma], [libftdi])
-SR_DRIVER([Atten PPS3xxx], [atten-pps3xxx], [libserialport])
+SR_DRIVER([Atten PPS3xxx], [atten-pps3xxx], [serial_comm])
SR_DRIVER([BayLibre ACME], [baylibre-acme], [sys_timerfd_h])
SR_DRIVER([BeagleLogic], [beaglelogic], [sys_mman_h sys_ioctl_h])
-SR_DRIVER([Brymen BM86x], [brymen-bm86x], [libusb])
-SR_DRIVER([Brymen DMM], [brymen-dmm], [libserialport])
-SR_DRIVER([CEM DT-885x], [cem-dt-885x], [libserialport])
-SR_DRIVER([Center 3xx], [center-3xx], [libserialport])
+SR_DRIVER([Brymen DMM], [brymen-dmm], [serial_comm])
+SR_DRIVER([CEM DT-885x], [cem-dt-885x], [serial_comm])
+SR_DRIVER([Center 3xx], [center-3xx], [serial_comm])
SR_DRIVER([ChronoVu LA], [chronovu-la], [libusb libftdi])
-SR_DRIVER([Colead SLM], [colead-slm], [libserialport])
-SR_DRIVER([Conrad DIGI 35 CPU], [conrad-digi-35-cpu], [libserialport])
+SR_DRIVER([Colead SLM], [colead-slm], [serial_comm])
+SR_DRIVER([Conrad DIGI 35 CPU], [conrad-digi-35-cpu], [serial_comm])
SR_DRIVER([demo], [demo])
SR_DRIVER([DreamSourceLab DSLogic], [dreamsourcelab-dslogic], [libusb])
SR_DRIVER([Fluke 45], [fluke-45])
-SR_DRIVER([Fluke DMM], [fluke-dmm], [libserialport])
+SR_DRIVER([Fluke DMM], [fluke-dmm], [serial_comm])
SR_DRIVER([FTDI LA], [ftdi-la], [libusb libftdi])
SR_DRIVER([fx2lafw], [fx2lafw], [libusb])
-SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [libserialport])
-SR_DRIVER([GW Instek GDS-800], [gwinstek-gds-800], [libserialport])
-SR_DRIVER([GW Instek GPD], [gwinstek-gpd], [libserialport])
-SR_DRIVER([Hameg HMO], [hameg-hmo], [libserialport])
+SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [serial_comm])
+SR_DRIVER([GW Instek GDS-800], [gwinstek-gds-800], [serial_comm])
+SR_DRIVER([GW Instek GPD], [gwinstek-gpd], [serial_comm])
+SR_DRIVER([Hameg HMO], [hameg-hmo], [serial_comm])
SR_DRIVER([Hantek 4032L], [hantek-4032l], [libusb])
SR_DRIVER([Hantek 6xxx], [hantek-6xxx], [libusb])
SR_DRIVER([Hantek DSO], [hantek-dso], [libusb])
SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi])
SR_DRIVER([IPDBG LA], [ipdbg-la])
SR_DRIVER([Kecheng KC-330B], [kecheng-kc-330b], [libusb])
-SR_DRIVER([KERN scale], [kern-scale], [libserialport])
-SR_DRIVER([Korad KAxxxxP], [korad-kaxxxxp], [libserialport])
+SR_DRIVER([KERN scale], [kern-scale], [serial_comm])
+SR_DRIVER([Korad KAxxxxP], [korad-kaxxxxp], [serial_comm])
SR_DRIVER([Lascar EL-USB], [lascar-el-usb], [libusb])
SR_DRIVER([LeCroy LogicStudio], [lecroy-logicstudio], [libusb])
SR_DRIVER([LeCroy X-Stream], [lecroy-xstream])
-SR_DRIVER([Manson HCS-3xxx], [manson-hcs-3xxx], [libserialport])
+SR_DRIVER([Manson HCS-3xxx], [manson-hcs-3xxx], [serial_comm])
+SR_DRIVER([Mastech MS6514], [mastech-ms6514], [serial_comm])
SR_DRIVER([maynuo-m97], [maynuo-m97])
-SR_DRIVER([MIC 985xx], [mic-985xx], [libserialport])
-SR_DRIVER([Motech LPS 30x], [motech-lps-30x], [libserialport])
-SR_DRIVER([Norma DMM], [norma-dmm], [libserialport])
-SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [libserialport])
-SR_DRIVER([PCE PCE-322A], [pce-322a], [libserialport])
+SR_DRIVER([MIC 985xx], [mic-985xx], [serial_comm])
+SR_DRIVER([Microchip PICkit2], [microchip-pickit2], [libusb])
+SR_DRIVER([Mooshimeter DMM], [mooshimeter-dmm], [bluetooth_comm libgio])
+SR_DRIVER([Motech LPS 30x], [motech-lps-30x], [serial_comm])
+SR_DRIVER([Norma DMM], [norma-dmm], [serial_comm])
+SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [serial_comm])
+SR_DRIVER([PCE PCE-322A], [pce-322a], [serial_comm])
SR_DRIVER([Pipistrello-OLS], [pipistrello-ols], [libftdi])
-SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [libserialport])
+SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [serial_comm])
SR_DRIVER([Rigol DS], [rigol-ds])
-SR_DRIVER([Rohde&Schwarz SME-0x], [rohde-schwarz-sme-0x], [libserialport])
+SR_DRIVER([Rohde&Schwarz SME-0x], [rohde-schwarz-sme-0x], [serial_comm])
SR_DRIVER([Saleae Logic16], [saleae-logic16], [libusb])
SR_DRIVER([Saleae Logic Pro], [saleae-logic-pro], [libusb])
+SR_DRIVER([SCPI DMM], [scpi-dmm])
SR_DRIVER([SCPI PPS], [scpi-pps])
-SR_DRIVER([serial DMM], [serial-dmm], [libserialport])
-SR_DRIVER([serial LCR], [serial-lcr], [libserialport])
+SR_DRIVER([serial DMM], [serial-dmm], [serial_comm])
+SR_DRIVER([serial LCR], [serial-lcr], [serial_comm])
SR_DRIVER([Siglent SDS], [siglent-sds])
SR_DRIVER([Sysclk LWLA], [sysclk-lwla], [libusb])
-SR_DRIVER([Teleinfo], [teleinfo], [libserialport])
+SR_DRIVER([Sysclk SLA5032], [sysclk-sla5032], [libusb])
+SR_DRIVER([Teleinfo], [teleinfo], [serial_comm])
SR_DRIVER([Testo], [testo], [libusb])
-SR_DRIVER([Tondaj SL-814], [tondaj-sl-814], [libserialport])
+SR_DRIVER([Tondaj SL-814], [tondaj-sl-814], [serial_comm])
SR_DRIVER([UNI-T DMM], [uni-t-dmm], [libusb])
-SR_DRIVER([UNI-T UT32x], [uni-t-ut32x], [libusb])
-SR_DRIVER([Victor DMM], [victor-dmm], [libusb])
+SR_DRIVER([UNI-T UT32x], [uni-t-ut32x], [serial_comm])
SR_DRIVER([Yokogawa DL/DLM], [yokogawa-dlm])
SR_DRIVER([ZEROPLUS Logic Cube], [zeroplus-logic-cube], [libusb])
-SR_DRIVER([ZKETECH EBD-USB], [zketech-ebd-usb], [libserialport])
+SR_DRIVER([ZKETECH EBD-USB], [zketech-ebd-usb], [serial_comm])
###############################
## Language bindings setup ##
$sr_pkglibs_summary
Enabled hardware drivers:
$sr_driver_summary
+Enabled serial communication transports:
+ - serial comm ................... $sr_have_serial_comm
+ - libserialport ................. $sr_have_libserialport
+ - hidapi ........................ $sr_have_libhidapi
+ - bluetooth ..................... $sr_have_bluetooth
+ - bluez ......................... $sr_have_libbluez
+
Enabled SCPI backends:
- TCP............................. yes
- RPC............................. $sr_cv_have_rpc
- - serial.......................... $sr_have_libserialport
+ - serial.......................... $sr_have_serial_comm
- VISA............................ $sr_have_librevisa
- GPIB............................ $sr_have_libgpib
- USBTMC.......................... $sr_have_libusb
#
ACTION!="add|change", GOTO="libsigrok_rules_end"
-SUBSYSTEM!="usb|usbmisc|usb_device", GOTO="libsigrok_rules_end"
+SUBSYSTEM!="usb|usbmisc|usb_device|hidraw", GOTO="libsigrok_rules_end"
# Agilent USBTMC-connected devices
# 34405A
# Brymen BU-86X adapter (e.g. for Brymen BM867/BM869 and possibly others).
ATTRS{idVendor}=="0820", ATTRS{idProduct}=="0001", ENV{ID_SIGROK}="1"
-# CEM DT-8852
-ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ENV{ID_SIGROK}="1"
-
# ChronoVu LA8 (new VID/PID)
# ChronoVu LA16 (new VID/PID)
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8867", ENV{ID_SIGROK}="1"
ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="4123", ENV{ID_SIGROK}="1"
# IKALOGIC ScanaPLUS
+# ftdi-la
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", ENV{ID_SIGROK}="1"
# Kecheng KC-330B
ATTRS{idVendor}=="05ff", ATTRS{idProduct}=="a001", ENV{ID_SIGROK}="1"
ATTRS{idVendor}=="05ff", ATTRS{idProduct}=="a002", ENV{ID_SIGROK}="1"
+# LeCroy WaveRunner
+# 05ff:1023: 625Zi
+ATTRS{idVendor}=="05ff", ATTRS{idProduct}=="1023", ENV{ID_SIGROK}="1"
+
# Link Instruments MSO-19
ATTRS{idVendor}=="3195", ATTRS{idProduct}=="f190", ENV{ID_SIGROK}="1"
# Rigol DP800 series
ATTRS{idVendor}=="1ab1", ATTRS{idProduct}=="0e11", ENV{ID_SIGROK}="1"
-# Rohde&Schwarz HMO1002 Series VCP/USBTMC mode
+# Rigol MSO5000 series
+ATTRS{idVendor}=="1ab1", ATTRS{idProduct}=="0515", ENV{ID_SIGROK}="1"
+
+# Rohde&Schwarz HMO series mixed-signal oscilloscope (previously branded Hameg) VCP/USBTMC mode
+ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0117", ENV{ID_SIGROK}="1"
ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0118", ENV{ID_SIGROK}="1"
ATTRS{idVendor}=="0aad", ATTRS{idProduct}=="0119", ENV{ID_SIGROK}="1"
# Siglent USBTMC devices.
# f4ec:ee3a: E.g. SDS1052DL+ scope
+# f4ec:ee38: E.g. SDS1104X-E scope
# f4ed:ee3a: E.g. SDS1202X-E scope or SDG1010 waveform generator
ATTRS{idVendor}=="f4ec", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1"
ATTRS{idVendor}=="f4ed", ATTRS{idProduct}=="ee3a", ENV{ID_SIGROK}="1"
# sigrok usb-c-grok
ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="608f", ENV{ID_SIGROK}="1"
-# SysClk LWLA1016
+# SiLabs CP210x (USB CDC) UART bridge, used (among others) in:
+# CEM DT-8852
+# Manson HCS-3202
+# MASTECH MS2115B
+# MASTECH MS5308
+# MASTECH MS8250D
+# PeakTech 3330
+# Voltcraft PPS-11815
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ENV{ID_SIGROK}="1"
+
+# SiLabs CP2110 (USB HID) UART bridge, used (among others) in:
+# UNI-T UT612
+# UNI-T UT-D09 multimeter cable (for various UNI-T and rebranded DMMs)
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea80", ENV{ID_SIGROK}="1"
+
+# Sysclk LWLA1016
ATTRS{idVendor}=="2961", ATTRS{idProduct}=="6688", ENV{ID_SIGROK}="1"
-# SysClk LWLA1034
+# Sysclk LWLA1034
ATTRS{idVendor}=="2961", ATTRS{idProduct}=="6689", ENV{ID_SIGROK}="1"
+# Sysclk SLA5032 ("32CH 500M" mode)
+ATTRS{idVendor}=="2961", ATTRS{idProduct}=="66b0", ENV{ID_SIGROK}="1"
+
# Testo 435
ATTRS{idVendor}=="128d", ATTRS{idProduct}=="0003", ENV{ID_SIGROK}="1"
# UNI-T UT-D04 multimeter cable (for various UNI-T and rebranded DMMs)
-# http://sigrok.org/wiki/Device_cables#UNI-T_UT-D04
# UNI-T UT325
ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="e008", ENV{ID_SIGROK}="1"
# V&A VA4000 multimeter cable (for various V&A DMMs)
-# http://sigrok.org/wiki/Device_cables#V.26A_VA4000
ATTRS{idVendor}=="04fc", ATTRS{idProduct}=="0201", ENV{ID_SIGROK}="1"
# Victor 70C
# Victor 86C
ATTRS{idVendor}=="1244", ATTRS{idProduct}=="d237", ENV{ID_SIGROK}="1"
+# YiXingDianZi MDSO
+ATTRS{idVendor}=="d4a2", ATTRS{idProduct}=="5660", ENV{ID_SIGROK}="1"
+
# ZEROPLUS Logic Cube LAP-C series
# There are various devices in the ZEROPLUS Logic Cube series:
# 0c12:7002: LAP-16128U
* Channel regulation
* get: "CV", "CC" or "UR", denoting constant voltage, constant current
* or unregulated.
+ * "CC-" denotes a power supply in current sink mode (e.g. HP 66xxB).
+ * "" is used when there is no regulation, e.g. the output is disabled.
*/
SR_CONF_REGULATION,
*/
SR_CONF_EXTERNAL_CLOCK_SOURCE,
+ /** Offset of a source without strictly-defined MQ. */
+ SR_CONF_OFFSET,
+
+ /** The device supports setting a pattern for the logic trigger. */
+ SR_CONF_TRIGGER_PATTERN,
+
+ /** High resolution mode. */
+ SR_CONF_HIGH_RESOLUTION,
+
+ /** Peak detection. */
+ SR_CONF_PEAK_DETECTION,
+
+ /** Logic threshold: predefined levels (TTL, ECL, CMOS, etc). */
+ SR_CONF_LOGIC_THRESHOLD,
+
+ /** Logic threshold: custom numerical value. */
+ SR_CONF_LOGIC_THRESHOLD_CUSTOM,
+
+ /** The measurement range of a DMM or the output range of a power supply. */
+ SR_CONF_RANGE,
+
+ /** The number of digits (e.g. for a DMM). */
+ SR_CONF_DIGITS,
+
/* Update sr_key_info_config[] (hwdriver.c) upon changes! */
/*--- Special stuff -------------------------------------------------*/
includedir=@includedir@
Name: libsigrok
-Description: Backend library of the sigrok logic analyzer software
+Description: Backend library for the sigrok signal analysis software suite
URL: http://www.sigrok.org
Requires: glib-2.0
Requires.private: @SR_PKGLIBS@
#endif
l = g_slist_append(l, m);
#endif
+#ifdef HAVE_LIBHIDAPI
+ m = g_slist_append(NULL, g_strdup("hidapi"));
+ m = g_slist_append(m, g_strdup_printf("%s", CONF_LIBHIDAPI_VERSION));
+ l = g_slist_append(l, m);
+#endif
+#ifdef HAVE_LIBBLUEZ
+ m = g_slist_append(NULL, g_strdup("bluez"));
+ m = g_slist_append(m, g_strdup_printf("%s", CONF_LIBBLUEZ_VERSION));
+ l = g_slist_append(l, m);
+#endif
#ifdef HAVE_LIBFTDI
m = g_slist_append(NULL, g_strdup("libftdi"));
m = g_slist_append(m, g_strdup_printf("%s", CONF_LIBFTDI_VERSION));
#if HAVE_RPC
g_string_append_printf(s, "RPC, ");
#endif
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
g_string_append_printf(s, "serial, ");
#endif
#ifdef HAVE_LIBREVISA
char *str;
const char *lib, *version;
- sr_dbg("libsigrok %s/%s (rt: %s/%s).",
- SR_PACKAGE_VERSION_STRING, SR_LIB_VERSION_STRING,
+ sr_dbg("libsigrok %s/%s.",
sr_package_version_string_get(), sr_lib_version_string_get());
s = g_string_sized_new(200);
ret = SR_ERR;
goto done;
}
+#endif
+#ifdef HAVE_LIBHIDAPI
+ /*
+ * According to <hidapi.h>, the hid_init() routine just returns
+ * zero or non-zero, and hid_error() appears to relate to calls
+ * for a specific device after hid_open(). Which means that there
+ * is no more detailled information available beyond success/fail
+ * at this point in time.
+ */
+ if (hid_init() != 0) {
+ sr_err("HIDAPI hid_init() failed.");
+ ret = SR_ERR;
+ goto done;
+ }
#endif
sr_resource_set_hooks(context, NULL, NULL, NULL, NULL);
WSACleanup();
#endif
+#ifdef HAVE_LIBHIDAPI
+ hid_exit();
+#endif
#ifdef HAVE_LIBUSB_1_0
libusb_exit(ctx->libusb_ctx);
#endif
--- /dev/null
+/*
+ * This file is part of the sigrok project.
+ *
+ * Copyright (C) 2018-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/>.
+ */
+
+/*
+ * Scan support for Bluetooth LE devices is modelled after the MIT licensed
+ * https://github.com/carsonmcdonald/bluez-experiments experiments/scantest.c
+ * example source code which is:
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 Carson McDonald
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * This file implements an internal platform agnostic API of libsigrok
+ * for Bluetooth communication, as well as the first implementation on a
+ * specific platform which is based on the BlueZ library and got tested
+ * on Linux.
+ *
+ * TODO
+ * - Separate the "common" from the "bluez specific" parts. The current
+ * implementation uses the fact that HAVE_BLUETOOTH exclusively depends
+ * on HAVE_LIBBLUEZ, and thus both are identical.
+ * - Add missing features to the Linux platform support: Scan without
+ * root privileges, UUID to handle translation.
+ * - Add support for other platforms.
+ */
+
+#include "config.h"
+
+/* Unconditionally compile the source, optionally end up empty. */
+#ifdef HAVE_BLUETOOTH
+
+#ifdef HAVE_LIBBLUEZ
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/rfcomm.h>
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <glib.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "bt-bluez"
+/** @endcond */
+
+#define CONNECT_BLE_TIMEOUT 20 /* Connect timeout in seconds. */
+#define STORE_MAC_REVERSE 1
+#define ACCEPT_NONSEP_MAC 1
+
+/* Silence warning about (currently) unused routine. */
+#define WITH_WRITE_TYPE_HANDLE 0
+
+/* {{{ compat decls */
+/*
+ * The availability of conversion helpers in <bluetooth/bluetooth.h>
+ * appears to be version dependent. Let's provide the helper here if
+ * the header doesn't.
+ */
+
+#if !defined HAVE_BT_PUT_LE16
+static inline void bt_put_le16(uint16_t v, uint8_t *p)
+{
+ p[0] = (v >> 0) & 0xff;
+ p[1] = (v >> 8) & 0xff;
+}
+#endif
+
+/* }}} compat decls */
+/* {{{ Linux socket specific decls */
+
+#define BLE_ATT_ERROR_RESP 0x01
+#define BLE_ATT_EXCHANGE_MTU_REQ 0x02
+#define BLE_ATT_EXCHANGE_MTU_RESP 0x03
+#define BLE_ATT_FIND_INFORMATION_REQ 0x04
+#define BLE_ATT_FIND_INFORMATION_RESP 0x05
+#define BLE_ATT_FIND_BY_TYPE_REQ 0x06
+#define BLE_ATT_FIND_BY_TYPE_RESP 0x07
+#define BLE_ATT_READ_BY_TYPE_REQ 0x08
+#define BLE_ATT_READ_BY_TYPE_RESP 0x09
+#define BLE_ATT_READ_REQ 0x0a
+#define BLE_ATT_READ_RESP 0x0b
+#define BLE_ATT_READ_BLOB_REQ 0x0c
+#define BLE_ATT_READ_BLOB_RESP 0x0d
+#define BLE_ATT_READ_MULTIPLE_REQ 0x0e
+#define BLE_ATT_READ_MULTIPLE_RESP 0x0f
+#define BLE_ATT_READ_BY_GROUP_REQ 0x10
+#define BLE_ATT_READ_BY_GROUP_RESP 0x11
+#define BLE_ATT_WRITE_REQ 0x12
+#define BLE_ATT_WRITE_RESP 0x13
+#define BLE_ATT_WRITE_CMD 0x16
+#define BLE_ATT_HANDLE_NOTIFICATION 0x1b
+#define BLE_ATT_HANDLE_INDICATION 0x1d
+#define BLE_ATT_HANDLE_CONFIRMATION 0x1e
+#define BLE_ATT_SIGNED_WRITE_CMD 0x52
+
+/* }}} Linux socket specific decls */
+/* {{{ conversion */
+
+/*
+ * Convert textual MAC presentation to array of bytes. In contrast to
+ * BlueZ conversion, accept colon or dash separated input as well as a
+ * dense format without separators (001122334455). We expect to use the
+ * library in an environment where colons are not always available as a
+ * separator in user provided specs, while users do want to use some
+ * separator for readability.
+ *
+ * TODO Instead of doing the actual conversion here (and dealing with
+ * BlueZ' internal byte order for device address bytes), we might as
+ * well just transform the input string to an output string, and always
+ * use the officially provided str2ba() conversion routine.
+ */
+static int sr_bt_mac_text_to_bytes(const char *text, uint8_t *buf)
+{
+ size_t len;
+ long v;
+ char *endp;
+ char numbuf[3];
+
+ len = 6;
+ if (STORE_MAC_REVERSE)
+ buf += len;
+ endp = (char *)text;
+ while (len && endp && *endp) {
+ text = endp;
+ if (ACCEPT_NONSEP_MAC) {
+ numbuf[0] = endp[0];
+ numbuf[1] = endp[0] ? endp[1] : '\0';
+ numbuf[2] = '\0';
+ }
+ endp = NULL;
+ v = strtol(ACCEPT_NONSEP_MAC ? numbuf : text, &endp, 16);
+ if (!endp)
+ break;
+ if (*endp != ':' && *endp != '-' && *endp != '\0')
+ break;
+ if (v < 0 || v > 255)
+ break;
+ if (STORE_MAC_REVERSE)
+ *(--buf) = v;
+ else
+ *buf++ = v;
+ len--;
+ if (ACCEPT_NONSEP_MAC)
+ endp = (char *)text + (endp - numbuf);
+ if (*endp == ':' || *endp == '-')
+ endp++;
+ }
+
+ if (len) {
+ sr_err("Failed to parse MAC, too few bytes in '%s'", text);
+ return -1;
+ }
+ while (isspace(*endp))
+ endp++;
+ if (*endp) {
+ sr_err("Failed to parse MAC, excess data in '%s'", text);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* }}} conversion */
+/* {{{ helpers */
+
+SR_PRIV const char *sr_bt_adapter_get_address(size_t idx)
+{
+ int rc;
+ struct hci_dev_info info;
+ char addr[20];
+
+ rc = hci_devinfo(idx, &info);
+ sr_spew("DIAG: hci_devinfo(%zu) => rc %d", idx, rc);
+ if (rc < 0)
+ return NULL;
+
+ rc = ba2str(&info.bdaddr, addr);
+ sr_spew("DIAG: ba2str() => rc %d", rc);
+ if (rc < 0)
+ return NULL;
+
+ return g_strdup(addr);
+}
+
+/* }}} helpers */
+/* {{{ descriptor */
+
+struct sr_bt_desc {
+ /* User servicable options. */
+ sr_bt_scan_cb scan_cb;
+ void *scan_cb_data;
+ sr_bt_data_cb data_cb;
+ void *data_cb_data;
+ char local_addr[20];
+ char remote_addr[20];
+ size_t rfcomm_channel;
+ uint16_t read_handle;
+ uint16_t write_handle;
+ uint16_t cccd_handle;
+ uint16_t cccd_value;
+ /* Internal state. */
+ int devid;
+ int fd;
+ struct hci_filter orig_filter;
+};
+
+static int sr_bt_desc_open(struct sr_bt_desc *desc, int *id_ref);
+static void sr_bt_desc_close(struct sr_bt_desc *desc);
+static int sr_bt_check_socket_usable(struct sr_bt_desc *desc);
+static ssize_t sr_bt_write_type(struct sr_bt_desc *desc, uint8_t type);
+#if WITH_WRITE_TYPE_HANDLE
+static ssize_t sr_bt_write_type_handle(struct sr_bt_desc *desc,
+ uint8_t type, uint16_t handle);
+#endif
+static ssize_t sr_bt_write_type_handle_bytes(struct sr_bt_desc *desc,
+ uint8_t type, uint16_t handle, const uint8_t *data, size_t len);
+static ssize_t sr_bt_char_write_req(struct sr_bt_desc *desc,
+ uint16_t handle, const void *data, size_t len);
+
+SR_PRIV struct sr_bt_desc *sr_bt_desc_new(void)
+{
+ struct sr_bt_desc *desc;
+
+ desc = g_malloc0(sizeof(*desc));
+ if (!desc)
+ return NULL;
+
+ desc->devid = -1;
+ desc->fd = -1;
+
+ return desc;
+}
+
+SR_PRIV void sr_bt_desc_free(struct sr_bt_desc *desc)
+{
+ if (!desc)
+ return;
+
+ sr_bt_desc_close(desc);
+ g_free(desc);
+}
+
+SR_PRIV int sr_bt_config_cb_scan(struct sr_bt_desc *desc,
+ sr_bt_scan_cb cb, void *cb_data)
+{
+ if (!desc)
+ return -1;
+
+ desc->scan_cb = cb;
+ desc->scan_cb_data = cb_data;
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_config_cb_data(struct sr_bt_desc *desc,
+ sr_bt_data_cb cb, void *cb_data)
+{
+ if (!desc)
+ return -1;
+
+ desc->data_cb = cb;
+ desc->data_cb_data = cb_data;
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_config_addr_local(struct sr_bt_desc *desc, const char *addr)
+{
+ bdaddr_t mac_bytes;
+ int rc;
+
+ if (!desc)
+ return -1;
+
+ if (!addr || !addr[0]) {
+ desc->local_addr[0] = '\0';
+ return 0;
+ }
+
+ rc = sr_bt_mac_text_to_bytes(addr, &mac_bytes.b[0]);
+ if (rc < 0)
+ return -1;
+
+ rc = ba2str(&mac_bytes, desc->local_addr);
+ if (rc < 0)
+ return -1;
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_config_addr_remote(struct sr_bt_desc *desc, const char *addr)
+{
+ bdaddr_t mac_bytes;
+ int rc;
+
+ if (!desc)
+ return -1;
+
+ if (!addr || !addr[0]) {
+ desc->remote_addr[0] = '\0';
+ return 0;
+ }
+
+ rc = sr_bt_mac_text_to_bytes(addr, &mac_bytes.b[0]);
+ if (rc < 0)
+ return -1;
+
+ rc = ba2str(&mac_bytes, desc->remote_addr);
+ if (rc < 0)
+ return -1;
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel)
+{
+ if (!desc)
+ return -1;
+
+ desc->rfcomm_channel = channel;
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc,
+ uint16_t read_handle, uint16_t write_handle,
+ uint16_t cccd_handle, uint16_t cccd_value)
+{
+
+ if (!desc)
+ return -1;
+
+ desc->read_handle = read_handle;
+ desc->write_handle = write_handle;
+ desc->cccd_handle = cccd_handle;
+ desc->cccd_value = cccd_value;
+
+ return 0;
+}
+
+static int sr_bt_desc_open(struct sr_bt_desc *desc, int *id_ref)
+{
+ int id, sock;
+ bdaddr_t mac;
+
+ if (!desc)
+ return -1;
+ sr_dbg("BLE open");
+
+ if (desc->local_addr[0]) {
+ id = hci_devid(desc->local_addr);
+ } else if (desc->remote_addr[0]) {
+ str2ba(desc->remote_addr, &mac);
+ id = hci_get_route(&mac);
+ } else {
+ id = hci_get_route(NULL);
+ }
+ if (id < 0) {
+ sr_err("devid failed");
+ return -1;
+ }
+ desc->devid = id;
+ if (id_ref)
+ *id_ref = id;
+
+ sock = hci_open_dev(id);
+ if (sock < 0) {
+ perror("open HCI socket");
+ return -1;
+ }
+ desc->fd = sock;
+
+ return sock;
+}
+
+static void sr_bt_desc_close(struct sr_bt_desc *desc)
+{
+ if (!desc)
+ return;
+
+ sr_dbg("BLE close");
+ if (desc->fd >= 0) {
+ hci_close_dev(desc->fd);
+ desc->fd = -1;
+ }
+ desc->devid = -1;
+}
+
+/* }}} descriptor */
+/* {{{ scan */
+
+#define EIR_NAME_COMPLETE 9
+
+static int sr_bt_scan_prep(struct sr_bt_desc *desc)
+{
+ int rc;
+ uint8_t type, owntype, filter;
+ uint16_t ival, window;
+ int timeout;
+ uint8_t enable, dup;
+ socklen_t slen;
+ struct hci_filter scan_filter;
+
+ if (!desc)
+ return -1;
+
+ /* TODO Replace magic values with symbolic identifiers. */
+ type = 0x01; /* LE public? */
+ ival = htobs(0x0010);
+ window = htobs(0x0010);
+ owntype = 0x00; /* any? */
+ filter = 0x00;
+ timeout = 1000;
+ rc = hci_le_set_scan_parameters(desc->fd,
+ type, ival, window, owntype, filter, timeout);
+ if (rc < 0) {
+ perror("set LE scan params");
+ return -1;
+ }
+
+ enable = 1;
+ dup = 1;
+ timeout = 1000;
+ rc = hci_le_set_scan_enable(desc->fd, enable, dup, timeout);
+ if (rc < 0) {
+ perror("set LE scan enable");
+ return -1;
+ }
+
+ /* Save the current filter. For later restoration. */
+ slen = sizeof(desc->orig_filter);
+ rc = getsockopt(desc->fd, SOL_HCI, HCI_FILTER,
+ &desc->orig_filter, &slen);
+ if (rc < 0) {
+ perror("getsockopt(HCI_FILTER)");
+ return -1;
+ }
+
+ hci_filter_clear(&scan_filter);
+ hci_filter_set_ptype(HCI_EVENT_PKT, &scan_filter);
+ hci_filter_set_event(EVT_LE_META_EVENT, &scan_filter);
+ rc = setsockopt(desc->fd, SOL_HCI, HCI_FILTER,
+ &scan_filter, sizeof(scan_filter));
+ if (rc < 0) {
+ perror("setsockopt(HCI_FILTER)");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sr_bt_scan_post(struct sr_bt_desc *desc)
+{
+ int rc;
+ uint8_t enable, dup;
+ int timeout;
+
+ if (!desc)
+ return -1;
+
+ /* Restore previous HCI filter. */
+ rc = setsockopt(desc->fd, SOL_HCI, HCI_FILTER,
+ &desc->orig_filter, sizeof(desc->orig_filter));
+ if (rc < 0) {
+ perror("setsockopt(HCI_FILTER)");
+ return -1;
+ }
+
+ enable = 0;
+ dup = 1;
+ timeout = 1000;
+ rc = hci_le_set_scan_enable(desc->fd, enable, dup, timeout);
+ if (rc < 0)
+ return -1;
+
+ return 0;
+}
+
+static int sr_bt_scan_proc(struct sr_bt_desc *desc,
+ sr_bt_scan_cb scan_cb, void *cb_data,
+ uint8_t *data, size_t dlen, le_advertising_info *info)
+{
+ uint8_t type;
+ char addr[20];
+ const char *name;
+
+ (void)desc;
+
+ type = data[0];
+ if (type == EIR_NAME_COMPLETE) {
+ ba2str(&info->bdaddr, addr);
+ name = g_strndup((const char *)&data[1], dlen - 1);
+ if (scan_cb)
+ scan_cb(cb_data, addr, name);
+ free((void *)name);
+ return 0;
+ }
+
+ /* Unknown or unsupported type, ignore silently. */
+ return 0;
+}
+
+SR_PRIV int sr_bt_scan_le(struct sr_bt_desc *desc, int duration)
+{
+ int rc;
+ time_t deadline;
+ uint8_t buf[HCI_MAX_EVENT_SIZE];
+ ssize_t rdlen, rdpos;
+ evt_le_meta_event *meta;
+ le_advertising_info *info;
+ uint8_t *dataptr;
+ size_t datalen;
+
+ if (!desc)
+ return -1;
+ sr_dbg("BLE scan (LE)");
+
+ rc = sr_bt_desc_open(desc, NULL);
+ if (rc < 0)
+ return -1;
+
+ rc = sr_bt_scan_prep(desc);
+ if (rc < 0)
+ return -1;
+
+ deadline = time(NULL);
+ deadline += duration;
+ while (time(NULL) <= deadline) {
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ break;
+ rdlen = sr_bt_read(desc, buf, sizeof(buf));
+ if (rdlen < 0)
+ break;
+ if (!rdlen) {
+ g_usleep(50000);
+ continue;
+ }
+ if (rdlen < 1 + HCI_EVENT_HDR_SIZE)
+ continue;
+ meta = (void *)&buf[1 + HCI_EVENT_HDR_SIZE];
+ rdlen -= 1 + HCI_EVENT_HDR_SIZE;
+ if (meta->subevent != EVT_LE_ADVERTISING_REPORT)
+ continue;
+ info = (void *)&meta->data[1];
+ sr_spew("evt: type %d, len %d", info->evt_type, info->length);
+ if (!info->length)
+ continue;
+
+ rdpos = 0;
+ while (rdpos < rdlen) {
+ datalen = info->data[rdpos];
+ dataptr = &info->data[1 + rdpos];
+ if (rdpos + 1 + datalen > info->length)
+ break;
+ rdpos += 1 + datalen;
+ rc = sr_bt_scan_proc(desc,
+ desc->scan_cb, desc->scan_cb_data,
+ dataptr, datalen, info);
+ if (rc < 0)
+ break;
+ }
+ }
+
+ rc = sr_bt_scan_post(desc);
+ if (rc < 0)
+ return -1;
+
+ sr_bt_desc_close(desc);
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_scan_bt(struct sr_bt_desc *desc, int duration)
+{
+ int dev_id, sock, rsp_max;
+ long flags;
+ inquiry_info *info;
+ int inq_rc;
+ size_t rsp_count, idx;
+ char addr[20];
+ char name[256];
+
+ if (!desc)
+ return -1;
+ sr_dbg("BLE scan (BT)");
+
+ sock = sr_bt_desc_open(desc, &dev_id);
+ if (sock < 0)
+ return -1;
+
+ rsp_max = 255;
+ info = g_malloc0(rsp_max * sizeof(*info));
+ flags = 0 /* | IREQ_CACHE_FLUSH */;
+ inq_rc = hci_inquiry(dev_id, duration, rsp_max, NULL, &info, flags);
+ if (inq_rc < 0)
+ perror("hci_inquiry");
+ rsp_count = inq_rc;
+
+ for (idx = 0; idx < rsp_count; idx++) {
+ memset(addr, 0, sizeof(addr));
+ ba2str(&info[idx].bdaddr, addr);
+ memset(name, 0, sizeof(name));
+ if (hci_read_remote_name(sock, &info[idx].bdaddr, sizeof(name), name, 0) < 0)
+ snprintf(name, sizeof(name), "[unknown]");
+ if (desc->scan_cb)
+ desc->scan_cb(desc->scan_cb_data, addr, name);
+ }
+ g_free(info);
+
+ sr_bt_desc_close(desc);
+
+ return 0;
+}
+
+/* }}} scan */
+/* {{{ connect/disconnect */
+
+SR_PRIV int sr_bt_connect_ble(struct sr_bt_desc *desc)
+{
+ struct sockaddr_l2 sl2;
+ bdaddr_t mac;
+ int s, ret;
+ gint64 deadline;
+
+ if (!desc)
+ return -1;
+ if (!desc->remote_addr[0])
+ return -1;
+ sr_dbg("BLE connect, remote addr %s", desc->remote_addr);
+
+ s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, 0);
+ if (s < 0) {
+ perror("socket create");
+ return s;
+ }
+ desc->fd = s;
+
+ memset(&sl2, 0, sizeof(sl2));
+ sl2.l2_family = AF_BLUETOOTH;
+ sl2.l2_psm = 0;
+ if (desc->local_addr[0])
+ str2ba(desc->local_addr, &mac);
+ else
+ mac = *BDADDR_ANY;
+ memcpy(&sl2.l2_bdaddr, &mac, sizeof(sl2.l2_bdaddr));
+ sl2.l2_cid = L2CAP_FC_CONNLESS;
+ sl2.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+ ret = bind(s, (void *)&sl2, sizeof(sl2));
+ if (ret < 0) {
+ perror("bind");
+ return ret;
+ }
+
+ if (0) {
+ struct bt_security buf = {
+ .level = BT_SECURITY_LOW,
+ .key_size = 0,
+ };
+ ret = setsockopt(s, SOL_BLUETOOTH, BT_SECURITY, &buf, sizeof(buf));
+ if (ret < 0) {
+ perror("setsockopt");
+ return ret;
+ }
+ }
+
+ deadline = g_get_monotonic_time();
+ deadline += CONNECT_BLE_TIMEOUT * 1000 * 1000;
+ str2ba(desc->remote_addr, &mac);
+ memcpy(&sl2.l2_bdaddr, &mac, sizeof(sl2.l2_bdaddr));
+ sl2.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+ ret = connect(s, (void *)&sl2, sizeof(sl2));
+ /*
+ * Cope with "in progress" condition. Keep polling the status
+ * until connect() completes, then get the error by means of
+ * getsockopt(). See the connect(2) manpage for details.
+ */
+ if (ret < 0 && errno == EINPROGRESS) {
+ struct pollfd fds[1];
+ uint32_t soerror;
+ socklen_t solen;
+
+ /* TODO
+ * We seem to get here ("connect in progress") even when
+ * the specified peer is not around at all. Which results
+ * in extended periods of time where nothing happens, and
+ * an application timeout seems to be required.
+ */
+ sr_spew("in progress ...");
+
+ do {
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = s;
+ fds[0].events = POLLOUT;
+ ret = poll(fds, ARRAY_SIZE(fds), -1);
+ if (ret < 0) {
+ perror("poll(OUT)");
+ return ret;
+ }
+ if (!ret)
+ continue;
+ if (!(fds[0].revents & POLLOUT))
+ continue;
+ if (g_get_monotonic_time() >= deadline) {
+ sr_warn("Connect attempt timed out");
+ return SR_ERR_IO;
+ }
+ } while (1);
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = s;
+ fds[0].events = POLLNVAL;
+ ret = poll(fds, 1, 0);
+ if (ret < 0) {
+ perror("poll(INVAL)");
+ return ret;
+ }
+ if (ret) {
+ /* socket fd is invalid(?) */
+ desc->fd = -1;
+ close(s);
+ return -1;
+ }
+ solen = sizeof(soerror);
+ ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &solen);
+ if (ret < 0) {
+ perror("getsockopt(SO_ERROR)");
+ return ret;
+ }
+ if (soerror) {
+ /* connect(2) failed, SO_ERROR has the error code. */
+ errno = soerror;
+ perror("connect(PROGRESS)");
+ return soerror;
+ }
+
+ /*
+ * TODO Get the receive MTU here?
+ * getsockopt(SOL_BLUETOOTH, BT_RCVMTU, u16);
+ */
+ }
+ if (ret < 0) {
+ perror("connect");
+ return ret;
+ }
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_connect_rfcomm(struct sr_bt_desc *desc)
+{
+ struct sockaddr_rc addr;
+ int fd, rc;
+
+ if (!desc)
+ return -1;
+ if (!desc->remote_addr[0])
+ return -1;
+ sr_dbg("RFCOMM connect, remote addr %s, channel %zu",
+ desc->remote_addr, desc->rfcomm_channel);
+
+ if (!desc->rfcomm_channel)
+ desc->rfcomm_channel = 1;
+
+ fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+ if (fd < 0) {
+ perror("socket");
+ return -1;
+ }
+ desc->fd = fd;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.rc_family = AF_BLUETOOTH;
+ str2ba(desc->remote_addr, &addr.rc_bdaddr);
+ addr.rc_channel = desc->rfcomm_channel;
+ rc = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc < 0) {
+ perror("connect");
+ return -2;
+ }
+ sr_spew("connected");
+
+ return 0;
+}
+
+SR_PRIV void sr_bt_disconnect(struct sr_bt_desc *desc)
+{
+ sr_dbg("BLE disconnect");
+
+ if (!desc)
+ return;
+ sr_bt_desc_close(desc);
+}
+
+static int sr_bt_check_socket_usable(struct sr_bt_desc *desc)
+{
+ struct pollfd fds[1];
+ int ret;
+
+ if (!desc)
+ return -1;
+ if (desc->fd < 0)
+ return -1;
+
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = desc->fd;
+ fds[0].events = POLLERR | POLLHUP;
+ ret = poll(fds, ARRAY_SIZE(fds), 0);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return 0;
+ if (fds[0].revents & POLLHUP)
+ return -1;
+ if (fds[0].revents & POLLERR)
+ return -2;
+ if (fds[0].revents & POLLNVAL)
+ return -3;
+
+ return 0;
+}
+
+/* }}} connect/disconnect */
+/* {{{ indication/notification */
+
+SR_PRIV int sr_bt_start_notify(struct sr_bt_desc *desc)
+{
+ uint8_t buf[sizeof(desc->cccd_value)];
+ ssize_t wrlen;
+
+ if (!desc)
+ return -1;
+ sr_dbg("BLE start notify");
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ return -2;
+
+ bt_put_le16(desc->cccd_value, buf);
+ wrlen = sr_bt_char_write_req(desc, desc->cccd_handle, buf, sizeof(buf));
+ if (wrlen != sizeof(buf))
+ return -2;
+
+ return 0;
+}
+
+SR_PRIV int sr_bt_check_notify(struct sr_bt_desc *desc)
+{
+ uint8_t buf[1024];
+ ssize_t rdlen;
+ uint8_t packet_type;
+ uint16_t packet_handle;
+ uint8_t *packet_data;
+ size_t packet_dlen;
+
+ if (!desc)
+ return -1;
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ return -2;
+
+ /* Get another message from the Bluetooth socket. */
+ rdlen = sr_bt_read(desc, buf, sizeof(buf));
+ if (rdlen < 0)
+ return -2;
+ if (!rdlen)
+ return 0;
+
+ /* Get header fields and references to the payload data. */
+ packet_type = 0x00;
+ packet_handle = 0x0000;
+ packet_data = NULL;
+ packet_dlen = 0;
+ if (rdlen >= 1)
+ packet_type = buf[0];
+ if (rdlen >= 3) {
+ packet_handle = bt_get_le16(&buf[1]);
+ packet_data = &buf[3];
+ packet_dlen = rdlen - 3;
+ }
+
+ /* Dispatch according to the message type. */
+ switch (packet_type) {
+ case BLE_ATT_ERROR_RESP:
+ sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "error response");
+ /* EMPTY */
+ break;
+ case BLE_ATT_WRITE_RESP:
+ sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "write response");
+ /* EMPTY */
+ break;
+ case BLE_ATT_HANDLE_INDICATION:
+ sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "handle indication");
+ sr_bt_write_type(desc, BLE_ATT_HANDLE_CONFIRMATION);
+ if (packet_handle != desc->read_handle)
+ return -4;
+ if (!packet_data)
+ return -4;
+ if (!desc->data_cb)
+ return 0;
+ return desc->data_cb(desc->data_cb_data, packet_data, packet_dlen);
+ case BLE_ATT_HANDLE_NOTIFICATION:
+ sr_spew("read() len %zd, type 0x%02x (%s)", rdlen, buf[0], "handle notification");
+ if (packet_handle != desc->read_handle)
+ return -4;
+ if (!packet_data)
+ return -4;
+ if (!desc->data_cb)
+ return 0;
+ return desc->data_cb(desc->data_cb_data, packet_data, packet_dlen);
+ default:
+ sr_spew("unsupported type 0x%02x", packet_type);
+ return -3;
+ }
+
+ return 0;
+}
+
+/* }}} indication/notification */
+/* {{{ read/write */
+
+SR_PRIV ssize_t sr_bt_write(struct sr_bt_desc *desc,
+ const void *data, size_t len)
+{
+ if (!desc)
+ return -1;
+ if (desc->fd < 0)
+ return -1;
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ return -2;
+
+ /* Send TX data to the writable characteristics for BLE UART services. */
+ if (desc->write_handle)
+ return sr_bt_char_write_req(desc, desc->write_handle, data, len);
+
+ /* Send raw TX data to the RFCOMM socket for BT Classic channels. */
+ return write(desc->fd, data, len);
+}
+
+static ssize_t sr_bt_write_type(struct sr_bt_desc *desc, uint8_t type)
+{
+ ssize_t wrlen;
+
+ if (!desc)
+ return -1;
+ if (desc->fd < 0)
+ return -1;
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ return -2;
+
+ wrlen = write(desc->fd, &type, sizeof(type));
+ if (wrlen < 0)
+ return wrlen;
+ if (wrlen < (ssize_t)sizeof(type))
+ return -1;
+
+ return 0;
+}
+
+#if WITH_WRITE_TYPE_HANDLE
+static ssize_t sr_bt_write_type_handle(struct sr_bt_desc *desc,
+ uint8_t type, uint16_t handle)
+{
+ return sr_bt_write_type_handle_bytes(desc, type, handle, NULL, 0);
+}
+#endif
+
+static ssize_t sr_bt_write_type_handle_bytes(struct sr_bt_desc *desc,
+ uint8_t type, uint16_t handle, const uint8_t *data, size_t len)
+{
+ uint8_t header[sizeof(uint8_t) + sizeof(uint16_t)];
+ struct iovec iov[2] = {
+ { .iov_base = header, .iov_len = sizeof(header), },
+ { .iov_base = (void *)data, .iov_len = len, },
+ };
+ ssize_t wrlen;
+
+ if (!desc)
+ return -1;
+ if (desc->fd < 0)
+ return -1;
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ return -2;
+
+ header[0] = type;
+ bt_put_le16(handle, &header[1]);
+
+ if (data && len)
+ wrlen = writev(desc->fd, iov, ARRAY_SIZE(iov));
+ else
+ wrlen = write(desc->fd, header, sizeof(header));
+
+ if (wrlen < 0)
+ return wrlen;
+ if (wrlen < (ssize_t)sizeof(header))
+ return -1;
+ wrlen -= sizeof(header);
+
+ return wrlen;
+}
+
+/* Returns negative upon error, or returns the number of _payload_ bytes written. */
+static ssize_t sr_bt_char_write_req(struct sr_bt_desc *desc,
+ uint16_t handle, const void *data, size_t len)
+{
+ return sr_bt_write_type_handle_bytes(desc, BLE_ATT_WRITE_REQ,
+ handle, data, len);
+}
+
+SR_PRIV ssize_t sr_bt_read(struct sr_bt_desc *desc, void *data, size_t len)
+{
+ struct pollfd fds[1];
+ int ret;
+ ssize_t rdlen;
+
+ if (!desc)
+ return -1;
+ if (desc->fd < 0)
+ return -1;
+
+ if (sr_bt_check_socket_usable(desc) < 0)
+ return -2;
+
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = desc->fd;
+ fds[0].events = POLLIN;
+ ret = poll(fds, ARRAY_SIZE(fds), 0);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return 0;
+ if (!(fds[0].revents & POLLIN))
+ return 0;
+
+ rdlen = read(desc->fd, data, len);
+
+ return rdlen;
+}
+
+/* }}} indication/notification */
+
+#endif
#include <string.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
+#include "scpi.h"
/** @cond PRIVATE */
#define LOG_PREFIX "device"
{
struct sr_channel *ch;
- ch = g_malloc0(sizeof(struct sr_channel));
+ ch = g_malloc0(sizeof(*ch));
ch->sdi = sdi;
ch->index = index;
ch->type = type;
{
struct sr_dev_inst *sdi;
- sdi = g_malloc0(sizeof(struct sr_dev_inst));
+ sdi = g_malloc0(sizeof(*sdi));
sdi->vendor = g_strdup(vendor);
sdi->model = g_strdup(model);
{
struct sr_usb_dev_inst *udi;
- udi = g_malloc0(sizeof(struct sr_usb_dev_inst));
+ udi = g_malloc0(sizeof(*udi));
udi->bus = bus;
udi->address = address;
udi->devhdl = hdl;
#endif
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
/**
* Allocate and init a struct for a serial device instance.
{
struct sr_serial_dev_inst *serial;
- serial = g_malloc0(sizeof(struct sr_serial_dev_inst));
+ serial = g_malloc0(sizeof(*serial));
serial->port = g_strdup(port);
if (serialcomm)
serial->serialcomm = g_strdup(serialcomm);
{
struct sr_usbtmc_dev_inst *usbtmc;
- usbtmc = g_malloc0(sizeof(struct sr_usbtmc_dev_inst));
+ usbtmc = g_malloc0(sizeof(*usbtmc));
usbtmc->device = g_strdup(device);
usbtmc->fd = -1;
#ifdef HAVE_LIBUSB_1_0
struct drv_context *drvc;
int cnt, i, a, b;
- char connection_id[64];
+ char conn_id_usb[64];
struct sr_usb_dev_inst *usb;
struct libusb_device **devlist;
#endif
+#ifdef HAVE_SERIAL_COMM
+ struct sr_serial_dev_inst *serial;
+#endif
+
+ struct sr_scpi_dev_inst *scpi;
+ char *conn_id_scpi;
+
if (!sdi)
return NULL;
-#ifdef HAVE_LIBSERIALPORT
- struct sr_serial_dev_inst *serial;
-
+#ifdef HAVE_SERIAL_COMM
if ((!sdi->connection_id) && (sdi->inst_type == SR_INST_SERIAL)) {
- /* connection_id isn't populated, let's do that here. */
+ /* connection_id isn't populated, let's do that for serial devices. */
serial = sdi->conn;
((struct sr_dev_inst *)sdi)->connection_id = g_strdup(serial->port);
#ifdef HAVE_LIBUSB_1_0
if ((!sdi->connection_id) && (sdi->inst_type == SR_INST_USB)) {
- /* connection_id isn't populated, let's do that here. */
+ /* connection_id isn't populated, let's do that for USB devices. */
drvc = sdi->driver->context;
usb = sdi->conn;
if (b != usb->bus || a != usb->address)
continue;
- if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0)
+ if (usb_get_port_path(devlist[i], conn_id_usb, sizeof(conn_id_usb)) < 0)
continue;
- ((struct sr_dev_inst *)sdi)->connection_id = g_strdup(connection_id);
+ ((struct sr_dev_inst *)sdi)->connection_id = g_strdup(conn_id_usb);
break;
}
}
#endif
+ if ((!sdi->connection_id) && (sdi->inst_type == SR_INST_SCPI)) {
+ /* connection_id isn't populated, let's do that for SCPI devices. */
+
+ scpi = sdi->conn;
+ sr_scpi_connection_id(scpi, &conn_id_scpi);
+ ((struct sr_dev_inst *)sdi)->connection_id = g_strdup(conn_id_scpi);
+ g_free(conn_id_scpi);
+ }
+
return sdi->connection_id;
}
return TRUE;
}
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
/**
* Arrange for the reception of another measurement from the DMM.
*
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
+ * 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/>.
+ */
+
+/**
+ * @file
+ *
+ * Brymen BM86x serial protocol parser. The USB protocol (for the cable)
+ * and the packet description (for the meter) were retrieved from:
+ * http://brymen.com/product-html/Download2.html
+ * http://brymen.com/product-html/PD02BM860s_protocolDL.html
+ * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM860-BM860s_List/BM860-BM860s-500000-count-dual-display-DMMs-protocol.pdf
+ */
+
+#include <config.h>
+#include <math.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include <string.h>
+
+#define LOG_PREFIX "brymen-bm86x"
+
+#ifdef HAVE_SERIAL_COMM
+SR_PRIV int sr_brymen_bm86x_packet_request(struct sr_serial_dev_inst *serial)
+{
+ static const uint8_t request[] = { 0x00, 0x00, 0x86, 0x66, };
+
+ serial_write_nonblocking(serial, request, sizeof(request));
+
+ return SR_OK;
+}
+#endif
+
+SR_PRIV gboolean sr_brymen_bm86x_packet_valid(const uint8_t *buf)
+{
+ /*
+ * "Model ID3" (3rd HID report, byte 3) is the only documented
+ * fixed value, and must be 0x86. All other positions either depend
+ * on the meter's function, or the measurement's value, or are not
+ * documented by the vendor (are marked as "don't care", no fixed
+ * values are listed). There is nothing else we can check reliably.
+ */
+ if (buf[19] != 0x86)
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Data bytes in the DMM packet encode LCD segments in an unusual order
+ * (bgcdafe) and in an unusual position (bits 7:1 within the byte). The
+ * decimal point (bit 0) for one digit resides in the _next_ digit's byte.
+ *
+ * These routines convert LCD segments to characters, and a section of the
+ * DMM packet (which corresponds to the primary or secondary display) to
+ * the text representation of the measurement's value, before regular text
+ * to number conversion is applied. The first byte of the passed in block
+ * contains indicators, the value's digits start at the second byte.
+ */
+
+static char brymen_bm86x_parse_digit(uint8_t b)
+{
+ switch (b >> 1) {
+ /* Sign. */
+ case 0x20: return '-';
+ /* Decimal digits. */
+ case 0x5f: return '0';
+ case 0x50: return '1';
+ case 0x6d: return '2';
+ case 0x7c: return '3';
+ case 0x72: return '4';
+ case 0x3e: return '5';
+ case 0x3f: return '6';
+ case 0x54: return '7';
+ case 0x7f: return '8';
+ case 0x7e: return '9';
+ /* Temperature units. */
+ case 0x0f: return 'C';
+ case 0x27: return 'F';
+ /* OL condition, and diode mode. */
+ case 0x0b: return 'L';
+ case 0x79: return 'd';
+ case 0x10: return 'i';
+ case 0x39: return 'o';
+ /* Blank digit. */
+ case 0x00: return '\0';
+ /* Invalid or unknown segment combination. */
+ default:
+ sr_warn("Unknown encoding for digit: 0x%02x.", b);
+ return '\0';
+ }
+}
+
+static int brymen_bm86x_parse_digits(const uint8_t *pkt, size_t pktlen,
+ char *txtbuf, float *value, char *temp_unit, int *digits, int signflag)
+{
+ uint8_t byte;
+ char *txtptr, txtchar;
+ size_t pos;
+ int ret;
+
+ txtptr = txtbuf;
+ if (digits)
+ *digits = INT_MIN;
+
+ if (pkt[0] & signflag)
+ *txtptr++ = '-';
+ for (pos = 0; pos < pktlen; pos++) {
+ byte = pkt[1 + pos];
+ if (pos && pos < 5 && (byte & 0x01)) {
+ *txtptr++ = '.';
+ if (digits)
+ *digits = 0;
+ }
+ txtchar = brymen_bm86x_parse_digit(byte);
+ if (pos == 5 && (txtchar == 'C' || txtchar == 'F')) {
+ if (temp_unit)
+ *temp_unit = txtchar;
+ } else if (txtchar) {
+ *txtptr++ = txtchar;
+ if (digits)
+ (*digits)++;
+ }
+ }
+ *txtptr = '\0';
+
+ if (digits && *digits < 0)
+ *digits = 0;
+
+ ret = value ? sr_atof_ascii(txtbuf, value) : SR_OK;
+ if (ret != SR_OK) {
+ sr_dbg("invalid float string: '%s'", txtbuf);
+ return ret;
+ }
+
+ return SR_OK;
+}
+
+/*
+ * Extract the measurement value and its properties for one of the
+ * meter's displays from the DMM packet.
+ */
+static void brymen_bm86x_parse(const uint8_t *buf, float *floatval,
+ struct sr_datafeed_analog *analog, size_t ch_idx)
+{
+ char txtbuf[16], temp_unit;
+ int ret, digits, is_diode, over_limit, scale;
+ uint8_t ind1, ind15;
+
+ temp_unit = '\0';
+ if (ch_idx == 0) {
+ /*
+ * Main display. Note that _some_ of the second display's
+ * indicators are involved in the inspection of the _first_
+ * display's measurement value. So we have to get the
+ * second display's text buffer here, too.
+ */
+ (void)brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
+ NULL, NULL, NULL, 0);
+ is_diode = strcmp(txtbuf, "diod") == 0;
+ ret = brymen_bm86x_parse_digits(&buf[2], 6, txtbuf,
+ floatval, &temp_unit, &digits, 0x80);
+ over_limit = strstr(txtbuf, "0L") || strstr(txtbuf, "0.L");
+ if (ret != SR_OK && !over_limit)
+ return;
+
+ /* SI unit. */
+ if (buf[8] & 0x01) {
+ analog->meaning->mq = SR_MQ_VOLTAGE;
+ analog->meaning->unit = SR_UNIT_VOLT;
+ if (is_diode) {
+ analog->meaning->mqflags |= SR_MQFLAG_DIODE;
+ analog->meaning->mqflags |= SR_MQFLAG_DC;
+ }
+ } else if (buf[14] & 0x80) {
+ analog->meaning->mq = SR_MQ_CURRENT;
+ analog->meaning->unit = SR_UNIT_AMPERE;
+ } else if (buf[14] & 0x20) {
+ analog->meaning->mq = SR_MQ_CAPACITANCE;
+ analog->meaning->unit = SR_UNIT_FARAD;
+ } else if (buf[14] & 0x10) {
+ analog->meaning->mq = SR_MQ_CONDUCTANCE;
+ analog->meaning->unit = SR_UNIT_SIEMENS;
+ } else if (buf[15] & 0x01) {
+ analog->meaning->mq = SR_MQ_FREQUENCY;
+ analog->meaning->unit = SR_UNIT_HERTZ;
+ } else if (buf[10] & 0x01) {
+ analog->meaning->mq = SR_MQ_CONTINUITY;
+ analog->meaning->unit = SR_UNIT_OHM;
+ } else if (buf[15] & 0x10) {
+ analog->meaning->mq = SR_MQ_RESISTANCE;
+ analog->meaning->unit = SR_UNIT_OHM;
+ } else if (buf[15] & 0x02) {
+ analog->meaning->mq = SR_MQ_POWER;
+ analog->meaning->unit = SR_UNIT_DECIBEL_MW;
+ } else if (buf[15] & 0x80) {
+ analog->meaning->mq = SR_MQ_DUTY_CYCLE;
+ analog->meaning->unit = SR_UNIT_PERCENTAGE;
+ } else if ((buf[2] & 0x0a) && temp_unit) {
+ analog->meaning->mq = SR_MQ_TEMPERATURE;
+ if (temp_unit == 'F')
+ analog->meaning->unit = SR_UNIT_FAHRENHEIT;
+ else
+ analog->meaning->unit = SR_UNIT_CELSIUS;
+ }
+
+ /*
+ * Remove the MIN/MAX/AVG indicators when all of them
+ * are shown at the same time.
+ */
+ ind1 = buf[1];
+ if ((ind1 & 0xe0) == 0xe0)
+ ind1 &= ~0xe0;
+
+ /* AC/DC/Auto flags. */
+ if (buf[1] & 0x10)
+ analog->meaning->mqflags |= SR_MQFLAG_DC;
+ if (buf[2] & 0x01)
+ analog->meaning->mqflags |= SR_MQFLAG_AC;
+ if (buf[1] & 0x01)
+ analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
+ if (buf[1] & 0x08)
+ analog->meaning->mqflags |= SR_MQFLAG_HOLD;
+ if (ind1 & 0x20)
+ analog->meaning->mqflags |= SR_MQFLAG_MAX;
+ if (ind1 & 0x40)
+ analog->meaning->mqflags |= SR_MQFLAG_MIN;
+ if (ind1 & 0x80)
+ analog->meaning->mqflags |= SR_MQFLAG_AVG;
+ if (buf[3] & 0x01)
+ analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
+
+ /*
+ * Remove the "dBm" indication's "m" indicator before the
+ * SI unit's prefixes get inspected. To avoid an interaction
+ * with the "milli" prefix.
+ */
+ ind15 = buf[15];
+ if (ind15 & 0x02)
+ ind15 &= ~0x04;
+
+ /* SI prefix. */
+ scale = 0;
+ if (buf[14] & 0x40) /* n */
+ scale = -9;
+ if (buf[15] & 0x08) /* u */
+ scale = -6;
+ if (ind15 & 0x04) /* m */
+ scale = -3;
+ if (buf[15] & 0x40) /* k */
+ scale = +3;
+ if (buf[15] & 0x20) /* M */
+ scale = +6;
+ if (scale) {
+ *floatval *= pow(10, scale);
+ digits += -scale;
+ }
+
+ if (over_limit)
+ *floatval = INFINITY;
+
+ analog->encoding->digits = digits;
+ analog->spec->spec_digits = digits;
+ } else if (ch_idx == 1) {
+ /*
+ * Secondary display. Also inspect _some_ primary display
+ * data, to determine the secondary display's validity.
+ */
+ (void)brymen_bm86x_parse_digits(&buf[2], 6, txtbuf,
+ NULL, &temp_unit, NULL, 0x80);
+ ret = brymen_bm86x_parse_digits(&buf[9], 4, txtbuf,
+ floatval, NULL, &digits, 0x10);
+
+ /* SI unit. */
+ if (buf[14] & 0x08) {
+ analog->meaning->mq = SR_MQ_VOLTAGE;
+ analog->meaning->unit = SR_UNIT_VOLT;
+ } else if (buf[9] & 0x04) {
+ analog->meaning->mq = SR_MQ_CURRENT;
+ analog->meaning->unit = SR_UNIT_AMPERE;
+ } else if (buf[9] & 0x08) {
+ analog->meaning->mq = SR_MQ_CURRENT;
+ analog->meaning->unit = SR_UNIT_PERCENTAGE;
+ } else if (buf[14] & 0x04) {
+ analog->meaning->mq = SR_MQ_FREQUENCY;
+ analog->meaning->unit = SR_UNIT_HERTZ;
+ } else if ((buf[9] & 0x40) && temp_unit) {
+ analog->meaning->mq = SR_MQ_TEMPERATURE;
+ if (temp_unit == 'F')
+ analog->meaning->unit = SR_UNIT_FAHRENHEIT;
+ else
+ analog->meaning->unit = SR_UNIT_CELSIUS;
+ }
+
+ /* AC flag. */
+ if (buf[9] & 0x20)
+ analog->meaning->mqflags |= SR_MQFLAG_AC;
+
+ /* SI prefix. */
+ scale = 0;
+ if (buf[ 9] & 0x01) /* u */
+ scale = -6;
+ if (buf[ 9] & 0x02) /* m */
+ scale = -3;
+ if (buf[14] & 0x02) /* k */
+ scale = +3;
+ if (buf[14] & 0x01) /* M */
+ scale = +6;
+ if (scale) {
+ *floatval *= pow(10, scale);
+ digits += -scale;
+ }
+
+ analog->encoding->digits = digits;
+ analog->spec->spec_digits = digits;
+ }
+
+ if (buf[9] & 0x80)
+ sr_warn("Battery is low.");
+}
+
+SR_PRIV int sr_brymen_bm86x_parse(const uint8_t *buf, float *val,
+ struct sr_datafeed_analog *analog, void *info)
+{
+ struct brymen_bm86x_info *info_local;
+ size_t ch_idx;
+
+ /*
+ * Scan a portion of the received DMM packet which corresponds
+ * to the caller's specified display. Then prepare to scan a
+ * different portion of the packet for another display. This
+ * routine gets called multiple times for one received packet.
+ */
+ info_local = info;
+ ch_idx = info_local->ch_idx;
+ brymen_bm86x_parse(buf, val, analog, ch_idx);
+ info_local->ch_idx = ch_idx + 1;
+
+ return SR_OK;
+}
static const struct mode_range_items mode_ranges_temp_sub = {
.range_count = 2,
.ranges = {
- [1] = { .desc = "sub 100.0C", .digits = 1, .factor = 1, },
+ [1] = { .desc = "100.0C", .digits = 1, .factor = 1, },
},
};
static const struct mode_range_items mode_ranges_freq_sub = {
.range_count = 4,
.ranges = {
- [1] = { .desc = "999.9Hz", .digits = 1, .factor = 1, },
- [2] = { .desc = "99.99Hz", .digits = 2, .factor = 2, },
- [3] = { .desc = "9.999kHz", .digits = 3, .factor = 3, },
+ [1] = { .desc = "999.9Hz", .digits = 1, .factor = 1, },
+ [2] = { .desc = "99.99Hz", .digits = 2, .factor = 2, },
+ [3] = { .desc = "9.999kHz", .digits = 3, .factor = 3, },
},
};
static const struct mode_range_items mode_ranges_batt_sub = {
.range_count = 2,
.ranges = {
- [1] = { .desc = "sub 10.0V", .digits = 1, .factor = 1, },
+ [1] = { .desc = "10.0V", .digits = 1, .factor = 1, },
},
};
static const struct mode_range_items mode_ranges_gain_sub = {
.range_count = 4,
.ranges = {
- [1] = { .desc = "dbm 5000.0dBm", .digits = 1, .factor = 1, },
- [2] = { .desc = "dbm 500.00dBm", .digits = 2, .factor = 2, },
- [3] = { .desc = "dbm 50.000dBm", .digits = 3, .factor = 3, },
+ [1] = { .desc = "5000.0dBm", .digits = 1, .factor = 1, },
+ [2] = { .desc = "500.00dBm", .digits = 2, .factor = 2, },
+ [3] = { .desc = "50.000dBm", .digits = 3, .factor = 3, },
},
};
static const struct mode_range_items mode_ranges_diode_sub = {
.range_count = 1,
.ranges = {
- [0] = { .desc = "diode 15.0V", .digits = 0, .factor = 0, },
+ [0] = { .desc = "15.0V", .digits = 0, .factor = 0, },
},
};
-/* TODO: Complete the list of ranges. Only tested with low voltages so far. */
static const struct mode_range_items mode_ranges_volts_sub = {
.range_count = 5,
.ranges = {
+ [3] = { .desc = "50.000V", .digits = 3, .factor = 3, },
[4] = { .desc = "5.0000V", .digits = 4, .factor = 4, },
},
};
-/* TODO: Complete the list of ranges. Only tested with low voltages so far. */
static const struct mode_range_items mode_ranges_mamps_sub = {
- .range_count = 3,
+ .range_count = 5,
.ranges = {
[2] = { .desc = "500.00mA", .digits = 5, .factor = 5, },
+ [3] = { .desc = "50.000mA", .digits = 6, .factor = 6, },
+ [4] = { .desc = "5.0000mA", .digits = 7, .factor = 7, },
+ },
+};
+
+static const struct mode_range_items mode_ranges_uamps_sub = {
+ .range_count = 5,
+ .ranges = {
+ [4] = { .desc = "5.0000mA", .digits = 7, .factor = 7, },
},
};
[MODE_AC_V] = &mode_ranges_volts_sub,
[MODE_DC_A] = &mode_ranges_mamps_sub,
[MODE_AC_A] = &mode_ranges_mamps_sub,
+ [MODE_DC_MA] = &mode_ranges_mamps_sub,
+ [MODE_AC_MA] = &mode_ranges_mamps_sub,
+ [MODE_DC_UA] = &mode_ranges_uamps_sub,
+ [MODE_AC_UA] = &mode_ranges_uamps_sub,
[MODE_FREQ] = &mode_ranges_freq_sub,
[MODE_DIODE] = &mode_ranges_diode_sub,
[MODE_SUB_TEMPC] = &mode_ranges_temp_sub,
* @return SR_OK upon success, SR_ERR upon failure. Upon errors, the
* 'analog' variable contents are undefined and should not be used.
*/
-SR_PRIV int sr_eev121gw_parse(const uint8_t *buf, float *floatval,
+static int sr_eev121gw_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info)
{
struct eev121gw_info *info_local;
/*
* Get those fields which correspond to the secondary
* display. The value's mantissa has 16 bits. The sign
- * is separate is only applies to some of the modes.
+ * is separate and only applies to some of the modes.
* Scaling and precision also depend on the mode. The
* interpretation of the secondary display is different
* from the main display: The 'range' is not an index
}
is_k = FIELD_NB(raw_sub_range, SUB_RANGE_K);
- /*
- * TODO: Re-check the power mode display as more data becomes
- * available.
- *
- * The interpretation of the secondary display in power (VA)
- * modes is uncertain. The mode suggests A or uA units but the
- * value is supposed to be mA without a reliable condition
- * for us to check...
- *
- * f2 17 84 21 21 18 02 00 00 01 04 00 0b 00 00 0a 40 00 3f
- * f2 17 84 21 21 18 02 00 00 15 03 00 00 00 00 0a 40 00 27
- * DC VA DC V / DC A
- * 25.000VA dot 4 / dot 3
- *
- * f2 17 84 21 21 18 00 00 26 01 04 4c 57 00 00 0e 40 00 0f
- * f2 17 84 21 21 18 00 00 26 15 02 00 c7 00 00 0e 40 00 c1
- * 3.8mVA DC 1.9543V
- * 1.98mA (!) DC A + dot 2 -> milli(!) amps?
- *
- * f2 17 84 21 21 17 00 07 85 01 04 4c 5a 00 00 0e 40 00 a9
- * f2 17 84 21 21 17 00 07 85 13 04 26 7b 00 00 0e 40 00 f0
- * 1.925mVA DC 1.9546V
- * 0.9852mA
- *
- * f2 17 84 21 21 16 02 11 e0 01 04 26 39 00 02 0e 40 00 d2
- * f2 17 84 21 21 16 02 11 e0 11 04 12 44 00 02 0e 40 00 8b
- * 457.6uVA DC 0.9785V
- * 0.4676mA (!) DC uA + dot 4 -> milli(!) amps?
- */
-
switch (sub_mode) {
case MODE_DC_V:
info_local->is_voltage = TRUE;
return TRUE;
}
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
SR_PRIV int sr_metex14_packet_request(struct sr_serial_dev_inst *serial)
{
const uint8_t wbuf = 'D';
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Vitaliy Vorobyov
+ *
+ * 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 2 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/>.
+ */
+
+ /*
+ * MASTECH MS2115B protocol parser.
+ *
+ * Sends 9 bytes.
+ * D0 D1 D2 D3 D4 D5 D6 D7 D8 D9
+ *
+ * D0 = 0x55 - sync byte
+ *
+ * D1 - mode:
+ * bits:
+ * B7..B4 ??
+ * B3 - func
+ * B2..B0:
+ * 0 - A 600/1000 (func=0 AC, func=1 DC), signed
+ * 1 - A 60 (func=0 AC, func=1 DC), signed
+ * 2 - V (func=0 AC, func=1 DC), signed
+ * 3 - diode/beep (func=0 buz, func=1 diode)
+ * 4 - resistance
+ * 5 - capacitance
+ * 6 - hz
+ *
+ * D2 - range
+ *
+ * D3 - frq range
+ *
+ * D4 main value LSB
+ * D5 main value MSB
+ *
+ * (secondary value, hz, min/max, rel)
+ * D6 secondary value LSB
+ * D7 secondary value MSB
+ *
+ * D8 - flags
+ * bits:
+ * B7..B1:??
+ * B0 - 0 - auto, 1 - manual
+ *
+ * resistance:
+ * 55 04 00 00 9B 18 00 00 01 (0.L, manual) 600.0 Ohm (x 0.1)
+ * 55 04 01 00 9B 18 00 00 01 (0.L, manual) 6.000 kOhm (x 0.001)
+ * 55 04 02 00 9B 18 00 00 01 (0.L, manual) 60.00 kOhm (x 0.01)
+ * 55 04 03 00 9B 18 00 00 01 (0.L, manual) 600.0 kOhm (x 0.1)
+ * 55 04 04 00 9B 18 00 00 01 (0.L, manual) 6.000 MOhm (x 0.001)
+ * 55 04 05 00 9B 18 00 00 00 (0.L, auto) 60.00 MOhm (x 0.01)
+ *
+ * capacitance:
+ * 55 05 00 00 04 00 00 00 00 (4nF, auto)
+ * 55 05 00 00 05 00 00 00 01 (5nF, manual) 6.000 nF (x 0.001)
+ * 55 05 01 00 03 19 00 00 01 (0.L nF, manual) 60.00 nF (x 0.01)
+ * 55 05 02 00 D4 03 00 00 01 (980.0 nF, manual) 600.0 nF (x 0.1)
+ * 55 05 03 00 63 00 00 00 01 (0.099 uF, manual) 6.000 uF (x 0.001)
+ * 55 05 04 00 40 18 00 00 01 (0.L uF, manual)
+ * 55 05 04 00 F0 00 00 00 01 (2.40 uF, manual) 60.00 uF (x 0.01)
+ * 55 05 05 00 17 00 00 00 01 (2.3 uF, manual) 600.0 uF (x 0.1)
+ * 55 05 06 00 02 00 00 00 01 (0.002 mF, manual) 6.000 mF (x 0.001)
+ * 55 05 07 00 2F 00 00 00 01 (0.47 mF, manual) 60.00 mF (x 0.01)
+ *
+ * voltage:
+ * 55 02 00 00 00 00 00 00 01 (0.0mV, manual) 600.0 mV (x 0.1)
+ * 55 02 01 00 00 00 00 00 00 (0.000V, auto)
+ * 55 02 01 00 00 00 00 00 01 (0.000V, manual) 6.000 V (x 0.001)
+ * 55 02 02 00 00 00 00 00 01 (0.00V, manual) 60.00 V (x 0.01)
+ * 55 02 03 00 00 00 00 00 01 (0.0V, manual) 600.0 V (x 0.1)
+ * 55 02 04 00 00 00 00 00 01 (0V, manual) 1000 V (x 1)
+ *
+ * - Communication parameters: Unidirectional, 1200/8n1
+ * - CP2102 USB to UART bridge controller
+ */
+
+#include <config.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "ms2115b"
+
+static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
+ const struct ms2115b_info *info)
+{
+ /* Measurement modes */
+ if (info->is_volt) {
+ analog->meaning->mq = SR_MQ_VOLTAGE;
+ analog->meaning->unit = SR_UNIT_VOLT;
+ }
+ if (info->is_ampere) {
+ analog->meaning->mq = SR_MQ_CURRENT;
+ analog->meaning->unit = SR_UNIT_AMPERE;
+ }
+ if (info->is_ohm) {
+ analog->meaning->mq = SR_MQ_RESISTANCE;
+ analog->meaning->unit = SR_UNIT_OHM;
+ }
+ if (info->is_hz) {
+ analog->meaning->mq = SR_MQ_FREQUENCY;
+ analog->meaning->unit = SR_UNIT_HERTZ;
+ }
+ if (info->is_farad) {
+ analog->meaning->mq = SR_MQ_CAPACITANCE;
+ analog->meaning->unit = SR_UNIT_FARAD;
+ }
+ if (info->is_beep) {
+ analog->meaning->mq = SR_MQ_CONTINUITY;
+ analog->meaning->unit = SR_UNIT_BOOLEAN;
+ *floatval = (*floatval == INFINITY) ? 0.0 : 1.0;
+ }
+ if (info->is_diode) {
+ analog->meaning->mq = SR_MQ_VOLTAGE;
+ analog->meaning->unit = SR_UNIT_VOLT;
+ }
+
+ if (info->is_duty_cycle)
+ analog->meaning->mq = SR_MQ_DUTY_CYCLE;
+
+ if (info->is_percent)
+ analog->meaning->unit = SR_UNIT_PERCENTAGE;
+
+ /* Measurement related flags */
+ if (info->is_ac)
+ analog->meaning->mqflags |= SR_MQFLAG_AC;
+ if (info->is_dc)
+ analog->meaning->mqflags |= SR_MQFLAG_DC;
+ if (info->is_auto)
+ analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
+ if (info->is_diode)
+ analog->meaning->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC;
+}
+
+SR_PRIV gboolean sr_ms2115b_packet_valid(const uint8_t *buf)
+{
+ sr_dbg("DMM packet: %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6],
+ buf[7], buf[8]);
+
+ if (buf[0] == 0x55)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Mode values equal to received data */
+enum {
+ MODE_A600_1000 = 0,
+ MODE_A60 = 1,
+ MODE_V = 2,
+ MODE_DIODE_BEEP = 3,
+ MODE_OHM = 4,
+ MODE_CAP = 5,
+ MODE_HZ = 6,
+};
+
+static const int res_exp[] = {
+ -1, /* 600.0 Ohm (x 0.1) */
+ -3 + 3, /* 6.000 kOhm (x 0.001) */
+ -2 + 3, /* 60.00 kOhm (x 0.01) */
+ -1 + 3, /* 600.0 kOhm (x 0.1) */
+ -3 + 6, /* 6.000 MOhm (x 0.001) */
+ -2 + 6, /* 60.00 MOhm (x 0.01) */
+};
+
+static const int cap_exp[] = {
+ -3 - 9, /* 6.000 nF (x 0.001) */
+ -2 - 9, /* 60.00 nF (x 0.01) */
+ -1 - 9, /* 600.0 nF (x 0.1) */
+ -3 - 6, /* 6.000 uF (x 0.001) */
+ -2 - 6, /* 60.00 uF (x 0.01) */
+ -1 - 6, /* 600.0 uF (x 0.1) */
+ -3 - 3, /* 6.000 mF (x 0.001) */
+ -2 - 3, /* 60.00 mF (x 0.01) */
+};
+
+static const int hz_exp[] = {
+ -2, /* 60.00 Hz (x 0.01) */
+ -1, /* 600.0 Hz (x 0.1) */
+ -3 + 3, /* 6.000 kHz (x 0.001) */
+ -2 + 3, /* 60.00 kHz (x 0.01) */
+ -1 + 3, /* 600.0 kHz (x 0.1) */
+ -3 + 6, /* 6.000 MHz (x 0.001) */
+ -2 + 6, /* 60.00 MHz (x 0.01) */
+};
+
+static const int v_exp[] = {
+ -1 - 3, /* 600.0 mV (x 0.1) */
+ -3, /* 6.000 V (x 0.001) */
+ -2, /* 60.00 V (x 0.01) */
+ -1, /* 600.0 V (x 0.1) */
+ 0, /* 1000 V (x 1) */
+};
+
+SR_PRIV const char *ms2115b_channel_formats[MS2115B_DISPLAY_COUNT] = {
+ "main", "sub",
+};
+
+static int ms2115b_parse(const uint8_t *buf, float *floatval,
+ struct sr_datafeed_analog *analog, void *info)
+{
+ int exponent = 0;
+ float up_limit = 6000.0;
+ gboolean sign = FALSE;
+
+ uint32_t mode = (buf[1] & 7);
+ gboolean func = (buf[1] & 8) ? TRUE : FALSE;
+ uint32_t range = (buf[2] & 7);
+
+ struct ms2115b_info *info_local = info;
+
+ enum eev121gw_display display = info_local->ch_idx;
+ memset(info_local, 0, sizeof(*info_local));
+ info_local->ch_idx = display;
+
+ switch (display) {
+ case MS2115B_DISPLAY_MAIN:
+ switch (mode) {
+ case MODE_A600_1000:
+ exponent = -1;
+ sign = TRUE;
+ info_local->is_ampere = TRUE;
+ if (func)
+ info_local->is_dc = TRUE;
+ else
+ info_local->is_ac = TRUE;
+ break;
+ case MODE_A60:
+ exponent = -2;
+ sign = TRUE;
+ info_local->is_ampere = TRUE;
+ if (func)
+ info_local->is_dc = TRUE;
+ else
+ info_local->is_ac = TRUE;
+ break;
+ case MODE_V:
+ if (range >= ARRAY_SIZE(v_exp))
+ return SR_ERR;
+ exponent = v_exp[range];
+ sign = TRUE;
+ info_local->is_volt = TRUE;
+ if (func)
+ info_local->is_dc = TRUE;
+ else
+ info_local->is_ac = TRUE;
+ break;
+ case MODE_DIODE_BEEP:
+ if (func) {
+ exponent = -3;
+ up_limit = 2500.0;
+ info_local->is_diode = TRUE;
+ } else {
+ info_local->is_beep = TRUE;
+ }
+ break;
+ case MODE_OHM:
+ if (range >= ARRAY_SIZE(res_exp))
+ return SR_ERR;
+ exponent = res_exp[range];
+ info_local->is_ohm = TRUE;
+ break;
+ case MODE_CAP:
+ if (range >= ARRAY_SIZE(cap_exp))
+ return SR_ERR;
+ exponent = cap_exp[range];
+ info_local->is_farad = TRUE;
+ break;
+ case MODE_HZ:
+ range = (buf[3] & 7);
+ if (range >= ARRAY_SIZE(hz_exp))
+ return SR_ERR;
+ exponent = hz_exp[range];
+ info_local->is_hz = TRUE;
+ break;
+ default:
+ return SR_ERR;
+ }
+
+ if (sign) {
+ *floatval = RL16S(buf + 4); /* signed 16bit value */
+ } else {
+ *floatval = RL16(buf + 4); /* unsigned 16bit value */
+ }
+
+ info_local->is_auto = (buf[8] & 1) ? FALSE : TRUE;
+ break;
+ case MS2115B_DISPLAY_SUB:
+ switch (mode) {
+ case MODE_A600_1000:
+ case MODE_A60:
+ case MODE_V:
+ if (func) /* DC */
+ return SR_ERR_NA;
+
+ /* AC */
+ info_local->is_hz = TRUE;
+ exponent = -2;
+ break;
+ case MODE_HZ:
+ info_local->is_duty_cycle = TRUE;
+ info_local->is_percent = TRUE;
+ exponent = -1;
+ break;
+ default:
+ return SR_ERR_NA;
+ }
+
+ *floatval = RL16(buf + 6); /* unsigned 16bit value */
+ break;
+ default:
+ return SR_ERR;
+ }
+
+ if (fabsf(*floatval) > up_limit) {
+ sr_spew("Over limit.");
+ *floatval = INFINITY;
+ return SR_OK;
+ }
+
+ *floatval *= powf(10, exponent);
+
+ handle_flags(analog, floatval, info_local);
+
+ analog->encoding->digits = -exponent;
+ analog->spec->spec_digits = -exponent;
+
+ return SR_OK;
+}
+
+/**
+ * Parse a protocol packet.
+ *
+ * @param buf Buffer containing the 9-byte protocol packet. Must not be NULL.
+ * @param floatval Pointer to a float variable. That variable will contain the
+ * result value upon parsing success. Must not be NULL.
+ * @param analog Pointer to a struct sr_datafeed_analog. The struct will be
+ * filled with data according to the protocol packet.
+ * Must not be NULL.
+ * @param info Pointer to a struct ms2115b_info. The struct will be filled
+ * with data according to the protocol packet. Must not be NULL.
+ *
+ * @return SR_OK upon success, SR_ERR upon failure. Upon errors, the
+ * 'analog' variable contents are undefined and should not be used.
+ */
+SR_PRIV int sr_ms2115b_parse(const uint8_t *buf, float *floatval,
+ struct sr_datafeed_analog *analog, void *info)
+{
+ int ret;
+ int ch_idx;
+ struct ms2115b_info *info_local = info;
+
+ ch_idx = info_local->ch_idx;
+ ret = ms2115b_parse(buf, floatval, analog, info);
+ info_local->ch_idx = ch_idx + 1;
+
+ return ret;
+}
#include <config.h>
#include <glib.h>
#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
-#ifndef HAVE_LIBSERIALPORT
+#ifndef HAVE_SERIAL_COMM
SR_API GSList *sr_serial_list(const struct sr_dev_driver *driver)
{
extern const struct agdmm_recv agdmm_recvs_u124x[];
extern const struct agdmm_recv agdmm_recvs_u124xc[];
extern const struct agdmm_recv agdmm_recvs_u125x[];
+extern const struct agdmm_recv agdmm_recvs_u127x[];
extern const struct agdmm_recv agdmm_recvs_u128x[];
/* This works on all the Agilent U12xxA series, although the
{ AGILENT_U1252, "U1252B", 3, agdmm_jobs_live, NULL, agdmm_recvs_u125x },
{ AGILENT_U1253, "U1253B", 3, agdmm_jobs_live, NULL, agdmm_recvs_u125x },
+ { AGILENT_U1271, "U1271A", 3, agdmm_jobs_live, NULL, agdmm_recvs_u127x },
+ { AGILENT_U1272, "U1272A", 3, agdmm_jobs_live, NULL, agdmm_recvs_u127x },
+ { AGILENT_U1273, "U1273A", 3, agdmm_jobs_live, NULL, agdmm_recvs_u127x },
+
{ KEYSIGHT_U1281, "U1281A", 3, agdmm_jobs_live, agdmm_jobs_log, agdmm_recvs_u128x },
{ KEYSIGHT_U1282, "U1282A", 3, agdmm_jobs_live, agdmm_jobs_log, agdmm_recvs_u128x },
ALL_ZERO
devc->cur_mqflags[i] = 0;
devc->cur_exponent[i] = 0;
devc->cur_digits[i] = 3 - resolution;
+ } else if (!strcmp(mstr, "MA")) {
+ devc->cur_mq[i] = SR_MQ_CURRENT;
+ devc->cur_unit[i] = SR_UNIT_AMPERE;
+ devc->cur_mqflags[i] = 0;
+ devc->cur_exponent[i] = -3;
+ devc->cur_digits[i] = 8 - resolution;
} else if (!strcmp(mstr, "UA")) {
devc->cur_mq[i] = SR_MQ_CURRENT;
devc->cur_unit[i] = SR_UNIT_AMPERE;
devc->cur_mqflags[i] = SR_MQFLAG_DIODE | SR_MQFLAG_DC;
devc->cur_exponent[i] = 0;
devc->cur_digits[i] = 3;
+ } else if (!strcmp(mstr, "TEMP")) {
+ devc->cur_mq[i] = SR_MQ_TEMPERATURE;
+ devc->cur_unit[i] = SR_UNIT_CELSIUS;
+ devc->cur_mqflags[i] = 0;
+ devc->cur_exponent[i] = 0;
+ devc->cur_digits[i] = 1;
} else if (!strcmp(mstr, "CAP")) {
devc->cur_mq[i] = SR_MQ_CAPACITANCE;
devc->cur_unit[i] = SR_UNIT_FARAD;
devc->cur_mqflags[i] |= SR_MQFLAG_RMS;
} else if (!strcmp(mstr, "DC")) {
devc->cur_mqflags[i] |= SR_MQFLAG_DC;
+ } else if (!strcmp(mstr, "ACDC")) {
+ devc->cur_mqflags[i] |= SR_MQFLAG_AC | SR_MQFLAG_DC | SR_MQFLAG_RMS;
} else {
sr_dbg("Unknown first argument '%s'.", mstr);
}
} else
devc->cur_mqflags[i] &= ~(SR_MQFLAG_AC | SR_MQFLAG_DC);
- return JOB_CONF;
+ struct sr_channel *prev_conf = devc->cur_conf;
+ devc->cur_conf = sr_next_enabled_channel(sdi, devc->cur_conf);
+ if (devc->cur_conf->index >= MIN(devc->profile->nb_channels, 2))
+ devc->cur_conf = sr_next_enabled_channel(sdi, devc->cur_conf);
+ if (devc->cur_conf->index > prev_conf->index)
+ return JOB_AGAIN;
+ else
+ return JOB_CONF;
}
static int recv_conf_u124x_5x(const struct sr_dev_inst *sdi, GMatchInfo *match)
{ "^\"(\\d\\d.{18}\\d)\"$", recv_stat_u125x },
{ "^\\*([0-9])$", recv_switch },
{ "^([-+][0-9]\\.[0-9]{8}E[-+][0-9]{2})$", recv_fetc },
- { "^\"(VOLT|CURR|RES|CAP|FREQ) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
- { "^\"(VOLT:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
- { "^\"(CURR:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
- { "^\"(CPER:[40]-20mA) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{8}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+ { "^\"(VOLT|CURR|RES|CONT|COND|CAP|FREQ) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+ { "^\"(VOLT:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+ { "^\"(CURR:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+ { "^\"(CPER:[40]-20mA) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+ { "^\"(PULS:PWID|PULS:PWID:[ACD]+) ([-+][0-9\\.E\\-+]+),([-+][0-9]\\.[0-9]{6}E([-+][0-9]{2}))\"$", recv_conf_u124x_5x },
+ { "^\"(TEMP:[A-Z]+) ([A-Z]+)\"$", recv_conf_u124x_5x },
{ "^\"(T[0-9]:[A-Z]+) ([A-Z]+)\"$", recv_conf_u124x_5x },
- { "^\"(DIOD)\"$", recv_conf_u124x_5x },
+ { "^\"(DIOD|PULS:[PN]DUT)\"$", recv_conf_u124x_5x },
+ ALL_ZERO
+};
+
+SR_PRIV const struct agdmm_recv agdmm_recvs_u127x[] = {
+ { "^\"(\\d\\d.{18}\\d)\"$", recv_stat_u123x },
+ { "^\\*([0-9])$", recv_switch },
+ { "^([-+][0-9]\\.[0-9]{8}E[-+][0-9]{2})$", recv_fetc },
+ { "^\"(V|MV|A|MA|UA|FREQ),(\\d),(AC|DC|ACDC)\"$", recv_conf_u123x },
+ { "^\"(RES|CAP),(\\d)\"$", recv_conf_u123x},
+ { "^\"(DIOD|TEMP)\"$", recv_conf_u123x },
ALL_ZERO
};
AGILENT_U1252,
AGILENT_U1253,
+ AGILENT_U1271,
+ AGILENT_U1272,
+ AGILENT_U1273,
+
KEYSIGHT_U1281,
KEYSIGHT_U1282,
};
/* Let's get a bit of data and see if we can find a packet. */
if (serial_stream_detect(serial, buf, &len, 25,
- appa_55ii_packet_valid, 500, 9600) != SR_OK)
+ appa_55ii_packet_valid, 500) != SR_OK)
goto scan_cleanup;
sr_info("Found device on port %s.", conn);
return SR_OK;
}
-static int send_config_update_key(const struct sr_dev_inst *sdi,
- uint32_t key, GVariant *var)
-{
- struct sr_config *cfg;
- struct sr_datafeed_packet packet;
- struct sr_datafeed_meta meta;
- int ret;
-
- cfg = sr_config_new(key, var);
- if (!cfg)
- return SR_ERR;
-
- memset(&meta, 0, sizeof(meta));
-
- packet.type = SR_DF_META;
- packet.payload = &meta;
-
- meta.config = g_slist_append(meta.config, cfg);
-
- ret = sr_session_send(sdi, &packet);
- sr_config_free(cfg);
-
- return ret;
-}
-
static void handle_packet(const struct sr_dev_inst *sdi)
{
struct sr_datafeed_packet packet;
if (g_str_has_prefix((const char *)devc->buf, "overtemp")) {
sr_warn("Overtemperature condition!");
devc->otp_active = TRUE;
- send_config_update_key(sdi, SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE,
+ sr_session_send_meta(sdi, SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE,
g_variant_new_boolean(TRUE));
return;
}
if (g_str_has_prefix((const char *)devc->buf, "undervolt")) {
sr_warn("Undervoltage condition!");
devc->uvc_active = TRUE;
- send_config_update_key(sdi, SR_CONF_UNDER_VOLTAGE_CONDITION_ACTIVE,
+ sr_session_send_meta(sdi, SR_CONF_UNDER_VOLTAGE_CONDITION_ACTIVE,
g_variant_new_boolean(TRUE));
return;
}
devc->current_limit = g_ascii_strtod(tokens[1], NULL) / 1000;
g_strfreev(tokens);
g_cond_signal(&devc->current_limit_cond);
- send_config_update_key(sdi, SR_CONF_CURRENT_LIMIT,
+ sr_session_send_meta(sdi, SR_CONF_CURRENT_LIMIT,
g_variant_new_double(devc->current_limit));
return;
}
g_strfreev(tokens);
g_cond_signal(&devc->uvc_threshold_cond);
if (devc->uvc_threshold == .0) {
- send_config_update_key(sdi, SR_CONF_UNDER_VOLTAGE_CONDITION,
+ sr_session_send_meta(sdi, SR_CONF_UNDER_VOLTAGE_CONDITION,
g_variant_new_boolean(FALSE));
} else {
- send_config_update_key(sdi, SR_CONF_UNDER_VOLTAGE_CONDITION,
+ sr_session_send_meta(sdi, SR_CONF_UNDER_VOLTAGE_CONDITION,
g_variant_new_boolean(TRUE));
- send_config_update_key(sdi,
+ sr_session_send_meta(sdi,
SR_CONF_UNDER_VOLTAGE_CONDITION_THRESHOLD,
g_variant_new_double(devc->uvc_threshold));
}
*triggerpos = result[0] | (result[1] << 8) | (result[2] << 16);
*stoppos = result[3] | (result[4] << 8) | (result[5] << 16);
- /* Not really sure why this must be done, but according to spec. */
+ /*
+ * These "position" values point to after the event (end of
+ * capture data, trigger condition matched). This is why they
+ * get decremented here. Sample memory consists of 512-byte
+ * chunks with meta data in the upper 64 bytes. Thus when the
+ * decrements takes us into this upper part of the chunk, then
+ * further move backwards to the end of the chunk's data part.
+ */
if ((--*stoppos & 0x1ff) == 0x1ff)
*stoppos -= 64;
-
- if ((*--triggerpos & 0x1ff) == 0x1ff)
+ if ((--*triggerpos & 0x1ff) == 0x1ff)
*triggerpos -= 64;
return 1;
devc = sdi->priv;
dl_events_in_line = 64 * 7;
- trg_line = ~0;
- trg_event = ~0;
dram_line = g_try_malloc0(chunks_per_read * sizeof(*dram_line));
if (!dram_line)
/* Check if trigger has fired. */
modestatus = sigma_get_register(READ_MODE, devc);
+ trg_line = ~0;
+ trg_event = ~0;
if (modestatus & RMR_TRIGGERED) {
trg_line = triggerpos >> 9;
trg_event = triggerpos & 0x1ff;
int ret;
char *resp;
- beaglelogic_tcp_send_cmd(devc, "memalloc %lu", devc->buffersize);
+ beaglelogic_tcp_send_cmd(devc, "memalloc %" PRIu32, devc->buffersize);
ret = beaglelogic_tcp_get_string(devc, NULL, &resp);
if (ret == SR_OK && !g_ascii_strncasecmp(resp, "ok", 2))
ret = SR_OK;
int ret;
char *resp;
- beaglelogic_tcp_send_cmd(devc, "samplerate %lu",
+ beaglelogic_tcp_send_cmd(devc, "samplerate %" PRIu32,
(uint32_t)devc->cur_samplerate);
ret = beaglelogic_tcp_get_string(devc, NULL, &resp);
if (ret == SR_OK && !g_ascii_strncasecmp(resp, "ok", 2))
int ret;
char *resp;
- beaglelogic_tcp_send_cmd(devc, "sampleunit %lu", devc->sampleunit);
+ beaglelogic_tcp_send_cmd(devc, "sampleunit %" PRIu32, devc->sampleunit);
ret = beaglelogic_tcp_get_string(devc, NULL, &resp);
if (ret == SR_OK && !g_ascii_strncasecmp(resp, "ok", 2))
ret = SR_OK;
int ret;
char *resp;
- beaglelogic_tcp_send_cmd(devc, "triggerflags %lu", devc->triggerflags);
+ beaglelogic_tcp_send_cmd(devc, "triggerflags %" PRIu32, devc->triggerflags);
ret = beaglelogic_tcp_get_string(devc, NULL, &resp);
if (ret == SR_OK && !g_ascii_strncasecmp(resp, "ok", 2))
ret = SR_OK;
int ret;
char *resp;
- beaglelogic_tcp_send_cmd(devc, "bufunitsize %ld", devc->bufunitsize);
+ beaglelogic_tcp_send_cmd(devc, "bufunitsize %" PRIu32, devc->bufunitsize);
ret = beaglelogic_tcp_get_string(devc, NULL, &resp);
if (ret == SR_OK && !g_ascii_strncasecmp(resp, "ok", 2))
ret = SR_OK;
+++ /dev/null
-/*
- * This file is part of the libsigrok project.
- *
- * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
- *
- * 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 "protocol.h"
-
-#define BRYMEN_BC86X "0820.0001"
-
-static const uint32_t scanopts[] = {
- SR_CONF_CONN,
-};
-
-static const uint32_t drvopts[] = {
- SR_CONF_MULTIMETER,
-};
-
-static const uint32_t devopts[] = {
- SR_CONF_CONTINUOUS,
- SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
- SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
-};
-
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
-{
- GSList *usb_devices, *devices, *l;
- struct drv_context *drvc;
- struct dev_context *devc;
- struct sr_dev_inst *sdi;
- struct sr_usb_dev_inst *usb;
- struct sr_config *src;
- const char *conn;
-
- drvc = di->context;
-
- conn = BRYMEN_BC86X;
- for (l = options; l; l = l->next) {
- src = l->data;
- switch (src->key) {
- case SR_CONF_CONN:
- conn = g_variant_get_string(src->data, NULL);
- break;
- }
- }
-
- devices = NULL;
- if (!(usb_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn))) {
- g_slist_free_full(usb_devices, g_free);
- return NULL;
- }
-
- for (l = usb_devices; l; l = l->next) {
- usb = l->data;
-
- sdi = g_malloc0(sizeof(struct sr_dev_inst));
- sdi->status = SR_ST_INACTIVE;
- sdi->vendor = g_strdup("Brymen");
- sdi->model = g_strdup("BM869");
- devc = g_malloc0(sizeof(struct dev_context));
- sdi->priv = devc;
- sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "P1");
- sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "P2");
-
- sdi->inst_type = SR_INST_USB;
- sdi->conn = usb;
-
- sr_sw_limits_init(&devc->sw_limits);
-
- devices = g_slist_append(devices, sdi);
- }
-
- return std_scan_complete(di, devices);
-}
-
-static int dev_open(struct sr_dev_inst *sdi)
-{
- struct sr_dev_driver *di = sdi->driver;
- struct drv_context *drvc = di->context;
- struct sr_usb_dev_inst *usb;
- struct dev_context *devc;
- int ret;
-
- usb = sdi->conn;
- devc = sdi->priv;
-
- if ((ret = sr_usb_open(drvc->sr_ctx->libusb_ctx, usb)) < 0)
- return SR_ERR;
-
- if (libusb_kernel_driver_active(usb->devhdl, 0) == 1) {
- ret = libusb_detach_kernel_driver(usb->devhdl, 0);
- if (ret < 0) {
- sr_err("Failed to detach kernel driver: %s.",
- libusb_error_name(ret));
- return SR_ERR;
- }
- devc->detached_kernel_driver = 1;
- }
-
- if ((ret = libusb_claim_interface(usb->devhdl, 0)) < 0) {
- sr_err("Failed to claim interface 0: %s.",
- libusb_error_name(ret));
- return SR_ERR;
- }
-
- return SR_OK;
-}
-
-static int dev_close(struct sr_dev_inst *sdi)
-{
- struct sr_usb_dev_inst *usb;
- struct dev_context *devc;
- int ret;
-
- usb = sdi->conn;
- devc = sdi->priv;
-
- if (!usb->devhdl)
- return SR_OK;
-
- if ((ret = libusb_release_interface(usb->devhdl, 0)))
- sr_err("Failed to release interface 0: %s.\n", libusb_error_name(ret));
-
- if (!ret && devc->detached_kernel_driver) {
- if ((ret = libusb_attach_kernel_driver(usb->devhdl, 0)))
- sr_err("Failed to attach kernel driver: %s.\n",
- libusb_error_name(ret));
- else
- devc->detached_kernel_driver = 0;
- }
-
- libusb_close(usb->devhdl);
-
- return (ret == 0) ? SR_OK : SR_ERR;
-}
-
-static int config_get(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
-{
- struct dev_context *devc = sdi->priv;
-
- (void)cg;
-
- return sr_sw_limits_config_get(&devc->sw_limits, key, data);
-}
-
-static int config_set(uint32_t key, GVariant *data,
- const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
-{
- struct dev_context *devc;
-
- (void)cg;
-
- devc = sdi->priv;
-
- return sr_sw_limits_config_set(&devc->sw_limits, key, data);
-}
-
-static int config_list(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
-{
- return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
-}
-
-static int dev_acquisition_start(const struct sr_dev_inst *sdi)
-{
- struct dev_context *devc;
-
- devc = sdi->priv;
-
- sr_sw_limits_acquisition_start(&devc->sw_limits);
-
- std_session_send_df_header(sdi);
-
- sr_session_source_add(sdi->session, -1, 0, 10,
- brymen_bm86x_receive_data, (void *)sdi);
-
- return SR_OK;
-}
-
-static int dev_acquisition_stop(struct sr_dev_inst *sdi)
-{
- std_session_send_df_end(sdi);
-
- sr_session_source_remove(sdi->session, -1);
-
- return SR_OK;
-}
-
-static struct sr_dev_driver brymen_bm86x_driver_info = {
- .name = "brymen-bm86x",
- .longname = "Brymen BM86X",
- .api_version = 1,
- .init = std_init,
- .cleanup = std_cleanup,
- .scan = scan,
- .dev_list = std_dev_list,
- .dev_clear = std_dev_clear,
- .config_get = config_get,
- .config_set = config_set,
- .config_list = config_list,
- .dev_open = dev_open,
- .dev_close = dev_close,
- .dev_acquisition_start = dev_acquisition_start,
- .dev_acquisition_stop = dev_acquisition_stop,
- .context = NULL,
-};
-SR_REGISTER_DEV_DRIVER(brymen_bm86x_driver_info);
+++ /dev/null
-/*
- * This file is part of the libsigrok project.
- *
- * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
- *
- * 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 <string.h>
-#include <math.h>
-#include "protocol.h"
-
-#define USB_TIMEOUT 500
-
-static const char char_map[128] = {
- [0x20] = '-',
- [0x5F] = '0',
- [0x50] = '1',
- [0x6D] = '2',
- [0x7C] = '3',
- [0x72] = '4',
- [0x3E] = '5',
- [0x3F] = '6',
- [0x54] = '7',
- [0x7F] = '8',
- [0x7E] = '9',
- [0x0F] = 'C',
- [0x27] = 'F',
- [0x0B] = 'L',
- [0x79] = 'd',
- [0x10] = 'i',
- [0x39] = 'o',
-};
-
-static int brymen_bm86x_parse_digits(const unsigned char *buf, int length,
- char *str, float *floatval,
- char *temp_unit, int *digits, int flag)
-{
- char c, *p = str;
- int i, ret;
-
- *digits = INT_MIN;
-
- if (buf[0] & flag)
- *p++ = '-';
- for (i = 0; i < length; i++) {
- if (i && i < 5 && buf[i+1] & 0x01) {
- *p++ = '.';
- *digits = 0;
- }
- c = char_map[buf[i+1] >> 1];
- if (i == 5 && (c == 'C' || c == 'F'))
- *temp_unit = c;
- else if (c) {
- *p++ = c;
- (*digits)++;
- }
- }
- *p = 0;
-
- if (*digits < 0)
- *digits = 0;
-
- if ((ret = sr_atof_ascii(str, floatval))) {
- sr_dbg("invalid float string: '%s'", str);
- return ret;
- }
-
- return SR_OK;
-}
-
-static void brymen_bm86x_parse(unsigned char *buf, float *floatval,
- struct sr_datafeed_analog *analog)
-{
- char str[16], temp_unit;
- int ret1, ret2, digits[2], over_limit;
-
- ret1 = brymen_bm86x_parse_digits(buf+2, 6, str, &floatval[0],
- &temp_unit, &digits[0], 0x80);
- over_limit = strstr(str, "0L") || strstr(str, "0.L");
- ret2 = brymen_bm86x_parse_digits(buf+9, 4, str, &floatval[1],
- &temp_unit, &digits[1], 0x10);
-
- /* main display */
- if (ret1 == SR_OK || over_limit) {
- /* SI unit */
- if (buf[8] & 0x01) {
- analog[0].meaning->mq = SR_MQ_VOLTAGE;
- analog[0].meaning->unit = SR_UNIT_VOLT;
- if (!strcmp(str, "diod")) {
- analog[0].meaning->mqflags |= SR_MQFLAG_DIODE;
- analog[0].meaning->mqflags |= SR_MQFLAG_DC;
- }
- } else if (buf[14] & 0x80) {
- analog[0].meaning->mq = SR_MQ_CURRENT;
- analog[0].meaning->unit = SR_UNIT_AMPERE;
- } else if (buf[14] & 0x20) {
- analog[0].meaning->mq = SR_MQ_CAPACITANCE;
- analog[0].meaning->unit = SR_UNIT_FARAD;
- } else if (buf[14] & 0x10) {
- analog[0].meaning->mq = SR_MQ_CONDUCTANCE;
- analog[0].meaning->unit = SR_UNIT_SIEMENS;
- } else if (buf[15] & 0x01) {
- analog[0].meaning->mq = SR_MQ_FREQUENCY;
- analog[0].meaning->unit = SR_UNIT_HERTZ;
- } else if (buf[10] & 0x01) {
- analog[0].meaning->mq = SR_MQ_CONTINUITY;
- analog[0].meaning->unit = SR_UNIT_OHM;
- } else if (buf[15] & 0x10) {
- analog[0].meaning->mq = SR_MQ_RESISTANCE;
- analog[0].meaning->unit = SR_UNIT_OHM;
- } else if (buf[15] & 0x02) {
- analog[0].meaning->mq = SR_MQ_POWER;
- analog[0].meaning->unit = SR_UNIT_DECIBEL_MW;
- } else if (buf[15] & 0x80) {
- analog[0].meaning->mq = SR_MQ_DUTY_CYCLE;
- analog[0].meaning->unit = SR_UNIT_PERCENTAGE;
- } else if (buf[ 2] & 0x0A) {
- analog[0].meaning->mq = SR_MQ_TEMPERATURE;
- if (temp_unit == 'F')
- analog[0].meaning->unit = SR_UNIT_FAHRENHEIT;
- else
- analog[0].meaning->unit = SR_UNIT_CELSIUS;
- }
-
- /* when MIN MAX and AVG are displayed at the same time, remove them */
- if ((buf[1] & 0xE0) == 0xE0)
- buf[1] &= ~0xE0;
-
- /* AC/DC/Auto flags */
- if (buf[1] & 0x10) analog[0].meaning->mqflags |= SR_MQFLAG_DC;
- if (buf[2] & 0x01) analog[0].meaning->mqflags |= SR_MQFLAG_AC;
- if (buf[1] & 0x01) analog[0].meaning->mqflags |= SR_MQFLAG_AUTORANGE;
- if (buf[1] & 0x08) analog[0].meaning->mqflags |= SR_MQFLAG_HOLD;
- if (buf[1] & 0x20) analog[0].meaning->mqflags |= SR_MQFLAG_MAX;
- if (buf[1] & 0x40) analog[0].meaning->mqflags |= SR_MQFLAG_MIN;
- if (buf[1] & 0x80) analog[0].meaning->mqflags |= SR_MQFLAG_AVG;
- if (buf[3] & 0x01) analog[0].meaning->mqflags |= SR_MQFLAG_RELATIVE;
-
- /* when dBm is displayed, remove the m suffix so that it is
- not considered as the 10e-3 SI prefix */
- if (buf[15] & 0x02)
- buf[15] &= ~0x04;
-
- /* SI prefix */
- if (buf[14] & 0x40) { floatval[0] *= 1e-9; digits[0] += 9; } /* n */
- if (buf[15] & 0x08) { floatval[0] *= 1e-6; digits[0] += 6; } /* µ */
- if (buf[15] & 0x04) { floatval[0] *= 1e-3; digits[0] += 3; } /* m */
- if (buf[15] & 0x40) { floatval[0] *= 1e3; digits[0] -= 3; } /* k */
- if (buf[15] & 0x20) { floatval[0] *= 1e6; digits[0] -= 6; } /* M */
-
- if (over_limit) floatval[0] = INFINITY;
-
- analog[0].encoding->digits = digits[0];
- analog[0].spec->spec_digits = digits[0];
- }
-
- /* secondary display */
- if (ret2 == SR_OK) {
- /* SI unit */
- if (buf[14] & 0x08) {
- analog[1].meaning->mq = SR_MQ_VOLTAGE;
- analog[1].meaning->unit = SR_UNIT_VOLT;
- } else if (buf[9] & 0x04) {
- analog[1].meaning->mq = SR_MQ_CURRENT;
- analog[1].meaning->unit = SR_UNIT_AMPERE;
- } else if (buf[9] & 0x08) {
- analog[1].meaning->mq = SR_MQ_CURRENT;
- analog[1].meaning->unit = SR_UNIT_PERCENTAGE;
- } else if (buf[14] & 0x04) {
- analog[1].meaning->mq = SR_MQ_FREQUENCY;
- analog[1].meaning->unit = SR_UNIT_HERTZ;
- } else if (buf[9] & 0x40) {
- analog[1].meaning->mq = SR_MQ_TEMPERATURE;
- if (temp_unit == 'F')
- analog[1].meaning->unit = SR_UNIT_FAHRENHEIT;
- else
- analog[1].meaning->unit = SR_UNIT_CELSIUS;
- }
-
- /* AC flag */
- if (buf[9] & 0x20) analog[1].meaning->mqflags |= SR_MQFLAG_AC;
-
- /* SI prefix */
- if (buf[ 9] & 0x01) { floatval[1] *= 1e-6; digits[1] += 6; } /* µ */
- if (buf[ 9] & 0x02) { floatval[1] *= 1e-3; digits[1] += 3; } /* m */
- if (buf[14] & 0x02) { floatval[1] *= 1e3; digits[1] -= 3; } /* k */
- if (buf[14] & 0x01) { floatval[1] *= 1e6; digits[1] -= 6; } /* M */
-
- analog[1].encoding->digits = digits[1];
- analog[1].spec->spec_digits = digits[1];
- }
-
- if (buf[9] & 0x80)
- sr_spew("Battery is low.");
-}
-
-static void brymen_bm86x_handle_packet(const struct sr_dev_inst *sdi,
- unsigned char *buf)
-{
- struct dev_context *devc;
- struct sr_datafeed_packet packet;
- struct sr_datafeed_analog analog[2];
- struct sr_analog_encoding encoding[2];
- struct sr_analog_meaning meaning[2];
- struct sr_analog_spec spec[2];
- struct sr_channel *channel;
- int sent_ch1, sent_ch2;
- float floatval[2];
-
- devc = sdi->priv;
-
- /* Note: digits/spec_digits will be overridden later. */
- sr_analog_init(&analog[0], &encoding[0], &meaning[0], &spec[0], 0);
- sr_analog_init(&analog[1], &encoding[1], &meaning[1], &spec[1], 0);
-
- brymen_bm86x_parse(buf, floatval, analog);
- sent_ch1 = sent_ch2 = 0;
-
- channel = sdi->channels->data;
- if (analog[0].meaning->mq != 0 && channel->enabled) {
- /* Got a measurement. */
- sent_ch1 = 1;
- analog[0].num_samples = 1;
- analog[0].data = &floatval[0];
- analog[0].meaning->channels = g_slist_append(NULL, channel);
- packet.type = SR_DF_ANALOG;
- packet.payload = &analog[0];
- sr_session_send(sdi, &packet);
- g_slist_free(analog[0].meaning->channels);
- }
-
- channel = sdi->channels->next->data;
- if (analog[1].meaning->mq != 0 && channel->enabled) {
- /* Got a measurement. */
- sent_ch2 = 1;
- analog[1].num_samples = 1;
- analog[1].data = &floatval[1];
- analog[1].meaning->channels = g_slist_append(NULL, channel);
- packet.type = SR_DF_ANALOG;
- packet.payload = &analog[1];
- sr_session_send(sdi, &packet);
- g_slist_free(analog[1].meaning->channels);
- }
-
- if (sent_ch1 || sent_ch2)
- sr_sw_limits_update_samples_read(&devc->sw_limits, 1);
-}
-
-static int brymen_bm86x_send_command(const struct sr_dev_inst *sdi)
-{
- struct sr_usb_dev_inst *usb;
- unsigned char buf[] = { 0x00, 0x86, 0x66 };
- int ret;
-
- usb = sdi->conn;
-
- sr_dbg("Sending HID set report.");
- ret = libusb_control_transfer(usb->devhdl,
- LIBUSB_REQUEST_TYPE_CLASS |
- LIBUSB_RECIPIENT_INTERFACE |
- LIBUSB_ENDPOINT_OUT,
- 9, /* bRequest: HID set_report */
- 0x300, /* wValue: HID feature, report num 0 */
- 0, /* wIndex: interface 0 */
- buf, sizeof(buf), USB_TIMEOUT);
-
- if (ret < 0) {
- sr_err("HID feature report error: %s.", libusb_error_name(ret));
- return SR_ERR;
- }
-
- if (ret != sizeof(buf)) {
- sr_err("Short packet: sent %d/%zu bytes.", ret, sizeof(buf));
- return SR_ERR;
- }
-
- return SR_OK;
-}
-
-static int brymen_bm86x_read_interrupt(const struct sr_dev_inst *sdi)
-{
- struct dev_context *devc;
- struct sr_usb_dev_inst *usb;
- unsigned char buf[24];
- int ret, transferred;
-
- devc = sdi->priv;
- usb = sdi->conn;
-
- sr_dbg("Reading HID interrupt report.");
- /* Get data from EP1 using an interrupt transfer. */
- ret = libusb_interrupt_transfer(usb->devhdl,
- LIBUSB_ENDPOINT_IN | 1, /* EP1, IN */
- buf, sizeof(buf),
- &transferred, USB_TIMEOUT);
-
- if (ret == LIBUSB_ERROR_TIMEOUT) {
- if (++devc->interrupt_pending > 3)
- devc->interrupt_pending = 0;
- return SR_OK;
- }
-
- if (ret < 0) {
- sr_err("USB receive error: %s.", libusb_error_name(ret));
- return SR_ERR;
- }
-
- if (transferred != sizeof(buf)) {
- sr_err("Short packet: received %d/%zu bytes.", transferred, sizeof(buf));
- return SR_ERR;
- }
-
- devc->interrupt_pending = 0;
- brymen_bm86x_handle_packet(sdi, buf);
-
- return SR_OK;
-}
-
-SR_PRIV int brymen_bm86x_receive_data(int fd, int revents, void *cb_data)
-{
- struct sr_dev_inst *sdi;
- struct dev_context *devc;
-
- (void)fd;
- (void)revents;
-
- if (!(sdi = cb_data))
- return TRUE;
-
- if (!(devc = sdi->priv))
- return TRUE;
-
- if (!devc->interrupt_pending) {
- if (brymen_bm86x_send_command(sdi))
- return FALSE;
- devc->interrupt_pending = 1;
- }
-
- if (brymen_bm86x_read_interrupt(sdi))
- return FALSE;
-
- if (sr_sw_limits_check(&devc->sw_limits))
- sr_dev_acquisition_stop(sdi);
-
- return TRUE;
-}
+++ /dev/null
-/*
- * This file is part of the libsigrok project.
- *
- * Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
- *
- * 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/>.
- */
-
-#ifndef LIBSIGROK_HARDWARE_BRYMEN_BM86X_PROTOCOL_H
-#define LIBSIGROK_HARDWARE_BRYMEN_BM86X_PROTOCOL_H
-
-#include <stdint.h>
-#include <glib.h>
-#include <libsigrok/libsigrok.h>
-#include "libsigrok-internal.h"
-
-#define LOG_PREFIX "brymen-bm86x"
-
-struct dev_context {
- struct sr_sw_limits sw_limits;
- int detached_kernel_driver; /**< Whether kernel driver was detached or not */
- int interrupt_pending;
-};
-
-SR_PRIV int brymen_bm86x_receive_data(int fd, int revents, void *cb_data);
-
-#endif
g_variant_new_uint64(devc->buf[7] * 1000));
meta.config = g_slist_append(NULL, src);
sr_session_send(sdi, &packet);
- g_free(src);
+ g_slist_free(meta.config);
+ sr_config_free(src);
devc->buf_len = 0;
}
} else if (devc->state == ST_GET_LOG_RECORD_DATA) {
|| match->match == SR_TRIGGER_RISING)
devc->trigger_pattern |= channel_bit;
+ /* LA8 and LA16 support state triggering. */
+ if (match->match == SR_TRIGGER_ONE
+ || match->match == SR_TRIGGER_ZERO)
+ devc->trigger_mask |= channel_bit;
+
/* LA16 (but not LA8) supports edge triggering. */
if ((devc->prof->model == CHRONOVU_LA16)) {
if (match->match == SR_TRIGGER_RISING
* Copyright (C) 2011 Olivier Fauchon <olivier@aixmarseille.com>
* Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com>
* Copyright (C) 2015 Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
#include "libsigrok-internal.h"
#include "protocol.h"
-#define DEFAULT_NUM_LOGIC_CHANNELS 8
-#define DEFAULT_LOGIC_PATTERN PATTERN_SIGROK
+#define DEFAULT_NUM_LOGIC_CHANNELS 8
+#define DEFAULT_LOGIC_PATTERN PATTERN_SIGROK
-#define DEFAULT_NUM_ANALOG_CHANNELS 4
-#define DEFAULT_ANALOG_AMPLITUDE 10
+#define DEFAULT_NUM_ANALOG_CHANNELS 5
/* Note: No spaces allowed because of sigrok-cli. */
static const char *logic_pattern_str[] = {
static const uint32_t devopts_cg_analog_group[] = {
SR_CONF_AMPLITUDE | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_OFFSET | SR_CONF_GET | SR_CONF_SET,
};
static const uint32_t devopts_cg_analog_channel[] = {
+ SR_CONF_MEASURED_QUANTITY | SR_CONF_GET | SR_CONF_SET,
SR_CONF_PATTERN_MODE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_AMPLITUDE | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_OFFSET | SR_CONF_GET | SR_CONF_SET,
};
static const int32_t trigger_matches[] = {
/* Analog channels, channel groups and pattern generators. */
devc->ch_ag = g_hash_table_new(g_direct_hash, g_direct_equal);
if (num_analog_channels > 0) {
+ /*
+ * Have the waveform for analog patterns pre-generated. It's
+ * supposed to be periodic, so the generator just needs to
+ * access the prepared sample data (DDS style).
+ */
+ demo_generate_analog_pattern(devc);
+
pattern = 0;
/* An "Analog" channel group with all analog channels in it. */
acg = g_malloc0(sizeof(struct sr_channel_group));
/* Every channel gets a generator struct. */
ag = g_malloc(sizeof(struct analog_gen));
ag->ch = ch;
+ ag->mq = SR_MQ_VOLTAGE;
+ ag->mq_flags = SR_MQFLAG_DC;
+ ag->unit = SR_UNIT_VOLT;
ag->amplitude = DEFAULT_ANALOG_AMPLITUDE;
+ ag->offset = DEFAULT_ANALOG_OFFSET;
sr_analog_init(&ag->packet, &ag->encoding, &ag->meaning, &ag->spec, 2);
ag->packet.meaning->channels = cg->channels;
- ag->packet.meaning->mq = 0;
- ag->packet.meaning->mqflags = 0;
- ag->packet.meaning->unit = SR_UNIT_VOLT;
- ag->packet.data = ag->pattern_data;
+ ag->packet.meaning->mq = ag->mq;
+ ag->packet.meaning->mqflags = ag->mq_flags;
+ ag->packet.meaning->unit = ag->unit;
+ ag->packet.encoding->digits = DEFAULT_ANALOG_ENCODING_DIGITS;
+ ag->packet.spec->spec_digits = DEFAULT_ANALOG_SPEC_DIGITS;
+ ag->packet.data = devc->analog_patterns[pattern];
ag->pattern = pattern;
ag->avg_val = 0.0f;
ag->num_avgs = 0;
GHashTableIter iter;
void *value;
+ demo_free_analog_pattern(devc);
+
/* Analog generators. */
g_hash_table_iter_init(&iter, devc->ch_ag);
while (g_hash_table_iter_next(&iter, NULL, &value))
struct dev_context *devc;
struct sr_channel *ch;
struct analog_gen *ag;
+ GVariant *mq_arr[2];
int pattern;
if (!sdi)
case SR_CONF_AVG_SAMPLES:
*data = g_variant_new_uint64(devc->avg_samples);
break;
+ case SR_CONF_MEASURED_QUANTITY:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ /* Any channel in the group will do. */
+ ch = cg->channels->data;
+ if (ch->type != SR_CHANNEL_ANALOG)
+ return SR_ERR_ARG;
+ ag = g_hash_table_lookup(devc->ch_ag, ch);
+ mq_arr[0] = g_variant_new_uint32(ag->mq);
+ mq_arr[1] = g_variant_new_uint64(ag->mq_flags);
+ *data = g_variant_new_tuple(mq_arr, 2);
+ break;
case SR_CONF_PATTERN_MODE:
if (!cg)
return SR_ERR_CHANNEL_GROUP;
ag = g_hash_table_lookup(devc->ch_ag, ch);
*data = g_variant_new_double(ag->amplitude);
break;
+ case SR_CONF_OFFSET:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ /* Any channel in the group will do. */
+ ch = cg->channels->data;
+ if (ch->type != SR_CHANNEL_ANALOG)
+ return SR_ERR_ARG;
+ ag = g_hash_table_lookup(devc->ch_ag, ch);
+ *data = g_variant_new_double(ag->offset);
+ break;
case SR_CONF_CAPTURE_RATIO:
*data = g_variant_new_uint64(devc->capture_ratio);
break;
struct dev_context *devc;
struct analog_gen *ag;
struct sr_channel *ch;
+ GVariant *mq_tuple_child;
GSList *l;
int logic_pattern, analog_pattern;
devc->avg_samples = g_variant_get_uint64(data);
sr_dbg("Setting averaging rate to %" PRIu64, devc->avg_samples);
break;
+ case SR_CONF_MEASURED_QUANTITY:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ for (l = cg->channels; l; l = l->next) {
+ ch = l->data;
+ if (ch->type != SR_CHANNEL_ANALOG)
+ return SR_ERR_ARG;
+ ag = g_hash_table_lookup(devc->ch_ag, ch);
+ mq_tuple_child = g_variant_get_child_value(data, 0);
+ ag->mq = g_variant_get_uint32(mq_tuple_child);
+ mq_tuple_child = g_variant_get_child_value(data, 1);
+ ag->mq_flags = g_variant_get_uint64(mq_tuple_child);
+ g_variant_unref(mq_tuple_child);
+ }
+ break;
case SR_CONF_PATTERN_MODE:
if (!cg)
return SR_ERR_CHANNEL_GROUP;
ag->amplitude = g_variant_get_double(data);
}
break;
+ case SR_CONF_OFFSET:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ for (l = cg->channels; l; l = l->next) {
+ ch = l->data;
+ if (ch->type != SR_CHANNEL_ANALOG)
+ return SR_ERR_ARG;
+ ag = g_hash_table_lookup(devc->ch_ag, ch);
+ ag->offset = g_variant_get_double(data);
+ }
+ break;
case SR_CONF_CAPTURE_RATIO:
devc->capture_ratio = g_variant_get_uint64(data);
break;
struct sr_channel *ch;
int bitpos;
uint8_t mask;
- GHashTableIter iter;
- void *value;
struct sr_trigger *trigger;
devc = sdi->priv;
devc->first_partial_logic_index,
devc->first_partial_logic_mask);
- /*
- * Have the waveform for analog patterns pre-generated. It's
- * supposed to be periodic, so the generator just needs to
- * access the prepared sample data (DDS style).
- */
- g_hash_table_iter_init(&iter, devc->ch_ag);
- while (g_hash_table_iter_next(&iter, NULL, &value))
- demo_generate_analog_pattern(value, devc->cur_samplerate);
-
sr_session_source_add(sdi->session, -1, 0, 100,
demo_prepare_data, (struct sr_dev_inst *)sdi);
* Copyright (C) 2011 Olivier Fauchon <olivier@aixmarseille.com>
* Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com>
* Copyright (C) 2015 Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
};
-SR_PRIV void demo_generate_analog_pattern(struct analog_gen *ag, uint64_t sample_rate)
+SR_PRIV void demo_generate_analog_pattern(struct dev_context *devc)
{
double t, frequency;
- float value;
+ float amplitude, offset;
+ struct analog_pattern *pattern;
unsigned int num_samples, i;
+ float value;
int last_end;
- sr_dbg("Generating %s pattern.", analog_pattern_str[ag->pattern]);
-
num_samples = ANALOG_BUFSIZE / sizeof(float);
+ frequency = (double) devc->cur_samplerate / ANALOG_SAMPLES_PER_PERIOD;
+ amplitude = DEFAULT_ANALOG_AMPLITUDE;
+ offset = DEFAULT_ANALOG_OFFSET;
+
+ /*
+ * FIXME: We actually need only one period. A ringbuffer would be
+ * useful here.
+ * Make sure the number of samples we put out is an integer
+ * multiple of our period size.
+ */
- switch (ag->pattern) {
- case PATTERN_SQUARE:
- value = ag->amplitude;
- last_end = 0;
- for (i = 0; i < num_samples; i++) {
- if (i % 5 == 0)
- value = -value;
- if (i % 10 == 0)
- last_end = i;
- ag->pattern_data[i] = value;
- }
- ag->num_samples = last_end;
- break;
- case PATTERN_SINE:
- frequency = (double) sample_rate / ANALOG_SAMPLES_PER_PERIOD;
-
- /* Make sure the number of samples we put out is an integer
- * multiple of our period size */
- /* FIXME we actually need only one period. A ringbuffer would be
- * useful here. */
- while (num_samples % ANALOG_SAMPLES_PER_PERIOD != 0)
- num_samples--;
-
- for (i = 0; i < num_samples; i++) {
- t = (double) i / (double) sample_rate;
- ag->pattern_data[i] = ag->amplitude *
- sin(2 * G_PI * frequency * t);
- }
-
- ag->num_samples = num_samples;
- break;
- case PATTERN_TRIANGLE:
- frequency = (double) sample_rate / ANALOG_SAMPLES_PER_PERIOD;
-
- while (num_samples % ANALOG_SAMPLES_PER_PERIOD != 0)
- num_samples--;
-
- for (i = 0; i < num_samples; i++) {
- t = (double) i / (double) sample_rate;
- ag->pattern_data[i] = (2 * ag->amplitude / G_PI) *
- asin(sin(2 * G_PI * frequency * t));
- }
-
- ag->num_samples = num_samples;
- break;
- case PATTERN_SAWTOOTH:
- frequency = (double) sample_rate / ANALOG_SAMPLES_PER_PERIOD;
-
- while (num_samples % ANALOG_SAMPLES_PER_PERIOD != 0)
- num_samples--;
-
- for (i = 0; i < num_samples; i++) {
- t = (double) i / (double) sample_rate;
- ag->pattern_data[i] = 2 * ag->amplitude *
- ((t * frequency) - floor(0.5f + t * frequency));
- }
-
- ag->num_samples = num_samples;
- break;
+ /* PATTERN_SQUARE: */
+ sr_dbg("Generating %s pattern.", analog_pattern_str[PATTERN_SQUARE]);
+ pattern = g_malloc(sizeof(struct analog_pattern));
+ value = amplitude;
+ last_end = 0;
+ for (i = 0; i < num_samples; i++) {
+ if (i % 5 == 0)
+ value = -value;
+ if (i % 10 == 0)
+ last_end = i;
+ pattern->data[i] = value + offset;
+ }
+ pattern->num_samples = last_end;
+ devc->analog_patterns[PATTERN_SQUARE] = pattern;
+
+ /* Readjusting num_samples for all other patterns. */
+ while (num_samples % ANALOG_SAMPLES_PER_PERIOD != 0)
+ num_samples--;
+
+ /* PATTERN_SINE: */
+ sr_dbg("Generating %s pattern.", analog_pattern_str[PATTERN_SINE]);
+ pattern = g_malloc(sizeof(struct analog_pattern));
+ for (i = 0; i < num_samples; i++) {
+ t = (double) i / (double) devc->cur_samplerate;
+ pattern->data[i] = sin(2 * G_PI * frequency * t) * amplitude + offset;
+ }
+ pattern->num_samples = last_end;
+ devc->analog_patterns[PATTERN_SINE] = pattern;
+
+ /* PATTERN_TRIANGLE: */
+ sr_dbg("Generating %s pattern.", analog_pattern_str[PATTERN_TRIANGLE]);
+ pattern = g_malloc(sizeof(struct analog_pattern));
+ for (i = 0; i < num_samples; i++) {
+ t = (double) i / (double) devc->cur_samplerate;
+ pattern->data[i] = (2 / G_PI) * asin(sin(2 * G_PI * frequency * t)) *
+ amplitude + offset;
+ }
+ pattern->num_samples = last_end;
+ devc->analog_patterns[PATTERN_TRIANGLE] = pattern;
+
+ /* PATTERN_SAWTOOTH: */
+ sr_dbg("Generating %s pattern.", analog_pattern_str[PATTERN_SAWTOOTH]);
+ pattern = g_malloc(sizeof(struct analog_pattern));
+ for (i = 0; i < num_samples; i++) {
+ t = (double) i / (double) devc->cur_samplerate;
+ pattern->data[i] = 2 * ((t * frequency) - floor(0.5f + t * frequency)) *
+ amplitude + offset;
}
+ pattern->num_samples = last_end;
+ devc->analog_patterns[PATTERN_SAWTOOTH] = pattern;
+
+ /* PATTERN_ANALOG_RANDOM */
+ /* Data not filled here, will be generated in send_analog_packet(). */
+ pattern = g_malloc(sizeof(struct analog_pattern));
+ pattern->num_samples = last_end;
+ devc->analog_patterns[PATTERN_ANALOG_RANDOM] = pattern;
+}
+
+SR_PRIV void demo_free_analog_pattern(struct dev_context *devc)
+{
+ g_free(devc->analog_patterns[PATTERN_SQUARE]);
+ g_free(devc->analog_patterns[PATTERN_SINE]);
+ g_free(devc->analog_patterns[PATTERN_TRIANGLE]);
+ g_free(devc->analog_patterns[PATTERN_SAWTOOTH]);
+ g_free(devc->analog_patterns[PATTERN_ANALOG_RANDOM]);
}
static uint64_t encode_number_to_gray(uint64_t nr)
{
struct sr_datafeed_packet packet;
struct dev_context *devc;
+ struct analog_pattern *pattern;
uint64_t sending_now, to_avg;
int ag_pattern_pos;
unsigned int i;
+ float amplitude, offset, value;
+ float *data;
if (!ag->ch || !ag->ch->enabled)
return;
packet.type = SR_DF_ANALOG;
packet.payload = &ag->packet;
+ pattern = devc->analog_patterns[ag->pattern];
+
+ ag->packet.meaning->channels = g_slist_append(NULL, ag->ch);
+ ag->packet.meaning->mq = ag->mq;
+ ag->packet.meaning->mqflags = ag->mq_flags;
+
+ /* Set a unit for the given quantity. */
+ if (ag->mq == SR_MQ_VOLTAGE)
+ ag->packet.meaning->unit = SR_UNIT_VOLT;
+ else if (ag->mq == SR_MQ_CURRENT)
+ ag->packet.meaning->unit = SR_UNIT_AMPERE;
+ else if (ag->mq == SR_MQ_RESISTANCE)
+ ag->packet.meaning->unit = SR_UNIT_OHM;
+ else if (ag->mq == SR_MQ_CAPACITANCE)
+ ag->packet.meaning->unit = SR_UNIT_FARAD;
+ else if (ag->mq == SR_MQ_TEMPERATURE)
+ ag->packet.meaning->unit = SR_UNIT_CELSIUS;
+ else if (ag->mq == SR_MQ_FREQUENCY)
+ ag->packet.meaning->unit = SR_UNIT_HERTZ;
+ else if (ag->mq == SR_MQ_DUTY_CYCLE)
+ ag->packet.meaning->unit = SR_UNIT_PERCENTAGE;
+ else if (ag->mq == SR_MQ_CONTINUITY)
+ ag->packet.meaning->unit = SR_UNIT_OHM;
+ else if (ag->mq == SR_MQ_PULSE_WIDTH)
+ ag->packet.meaning->unit = SR_UNIT_PERCENTAGE;
+ else if (ag->mq == SR_MQ_CONDUCTANCE)
+ ag->packet.meaning->unit = SR_UNIT_SIEMENS;
+ else if (ag->mq == SR_MQ_POWER)
+ ag->packet.meaning->unit = SR_UNIT_WATT;
+ else if (ag->mq == SR_MQ_GAIN)
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+ else if (ag->mq == SR_MQ_SOUND_PRESSURE_LEVEL)
+ ag->packet.meaning->unit = SR_UNIT_DECIBEL_SPL;
+ else if (ag->mq == SR_MQ_CARBON_MONOXIDE)
+ ag->packet.meaning->unit = SR_UNIT_CONCENTRATION;
+ else if (ag->mq == SR_MQ_RELATIVE_HUMIDITY)
+ ag->packet.meaning->unit = SR_UNIT_HUMIDITY_293K;
+ else if (ag->mq == SR_MQ_TIME)
+ ag->packet.meaning->unit = SR_UNIT_SECOND;
+ else if (ag->mq == SR_MQ_WIND_SPEED)
+ ag->packet.meaning->unit = SR_UNIT_METER_SECOND;
+ else if (ag->mq == SR_MQ_PRESSURE)
+ ag->packet.meaning->unit = SR_UNIT_HECTOPASCAL;
+ else if (ag->mq == SR_MQ_PARALLEL_INDUCTANCE)
+ ag->packet.meaning->unit = SR_UNIT_HENRY;
+ else if (ag->mq == SR_MQ_PARALLEL_CAPACITANCE)
+ ag->packet.meaning->unit = SR_UNIT_FARAD;
+ else if (ag->mq == SR_MQ_PARALLEL_RESISTANCE)
+ ag->packet.meaning->unit = SR_UNIT_OHM;
+ else if (ag->mq == SR_MQ_SERIES_INDUCTANCE)
+ ag->packet.meaning->unit = SR_UNIT_HENRY;
+ else if (ag->mq == SR_MQ_SERIES_CAPACITANCE)
+ ag->packet.meaning->unit = SR_UNIT_FARAD;
+ else if (ag->mq == SR_MQ_SERIES_RESISTANCE)
+ ag->packet.meaning->unit = SR_UNIT_OHM;
+ else if (ag->mq == SR_MQ_DISSIPATION_FACTOR)
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+ else if (ag->mq == SR_MQ_QUALITY_FACTOR)
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+ else if (ag->mq == SR_MQ_PHASE_ANGLE)
+ ag->packet.meaning->unit = SR_UNIT_DEGREE;
+ else if (ag->mq == SR_MQ_DIFFERENCE)
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+ else if (ag->mq == SR_MQ_COUNT)
+ ag->packet.meaning->unit = SR_UNIT_PIECE;
+ else if (ag->mq == SR_MQ_POWER_FACTOR)
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+ else if (ag->mq == SR_MQ_APPARENT_POWER)
+ ag->packet.meaning->unit = SR_UNIT_VOLT_AMPERE;
+ else if (ag->mq == SR_MQ_MASS)
+ ag->packet.meaning->unit = SR_UNIT_GRAM;
+ else if (ag->mq == SR_MQ_HARMONIC_RATIO)
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+ else
+ ag->packet.meaning->unit = SR_UNIT_UNITLESS;
+
if (!devc->avg) {
- ag_pattern_pos = analog_pos % ag->num_samples;
- sending_now = MIN(analog_todo, ag->num_samples - ag_pattern_pos);
- ag->packet.data = ag->pattern_data + ag_pattern_pos;
+ ag_pattern_pos = analog_pos % pattern->num_samples;
+ sending_now = MIN(analog_todo, pattern->num_samples - ag_pattern_pos);
+ if (ag->amplitude != DEFAULT_ANALOG_AMPLITUDE ||
+ ag->offset != DEFAULT_ANALOG_OFFSET ||
+ ag->pattern == PATTERN_ANALOG_RANDOM) {
+ /*
+ * Amplitude or offset changed (or we are generating
+ * random data), modify each sample.
+ */
+ if (ag->pattern == PATTERN_ANALOG_RANDOM) {
+ amplitude = ag->amplitude / 500.0;
+ offset = ag->offset - DEFAULT_ANALOG_OFFSET - ag->amplitude;
+ } else {
+ amplitude = ag->amplitude / DEFAULT_ANALOG_AMPLITUDE;
+ offset = ag->offset - DEFAULT_ANALOG_OFFSET;
+ }
+ data = ag->packet.data;
+ for (i = 0; i < sending_now; i++) {
+ if (ag->pattern == PATTERN_ANALOG_RANDOM)
+ data[i] = (rand() % 1000) * amplitude + offset;
+ else
+ data[i] = pattern->data[ag_pattern_pos + i] * amplitude + offset;
+ }
+ } else {
+ /* Amplitude and offset unchanged, use the fast way. */
+ ag->packet.data = pattern->data + ag_pattern_pos;
+ }
ag->packet.num_samples = sending_now;
sr_session_send(sdi, &packet);
/* Whichever channel group gets there first. */
*analog_sent = MAX(*analog_sent, sending_now);
} else {
- ag_pattern_pos = analog_pos % ag->num_samples;
- to_avg = MIN(analog_todo, ag->num_samples - ag_pattern_pos);
+ ag_pattern_pos = analog_pos % pattern->num_samples;
+ to_avg = MIN(analog_todo, pattern->num_samples - ag_pattern_pos);
+ if (ag->pattern == PATTERN_ANALOG_RANDOM) {
+ amplitude = ag->amplitude / 500.0;
+ offset = ag->offset - DEFAULT_ANALOG_OFFSET - ag->amplitude;
+ } else {
+ amplitude = ag->amplitude / DEFAULT_ANALOG_AMPLITUDE;
+ offset = ag->offset - DEFAULT_ANALOG_OFFSET;
+ }
for (i = 0; i < to_avg; i++) {
- ag->avg_val = (ag->avg_val +
- *(ag->pattern_data +
- ag_pattern_pos + i)) / 2;
+ if (ag->pattern == PATTERN_ANALOG_RANDOM)
+ value = (rand() % 1000) * amplitude + offset;
+ else
+ value = *(pattern->data + ag_pattern_pos + i) * amplitude + offset;
+ ag->avg_val = (ag->avg_val + value) / 2;
ag->num_avgs++;
/* Time to send averaged data? */
if ((devc->avg_samples > 0) && (ag->num_avgs >= devc->avg_samples))
}
if (devc->avg_samples == 0) {
- /* We're averaging all the samples, so wait with
+ /*
+ * We're averaging all the samples, so wait with
* sending until the very end.
*/
*analog_sent = ag->num_avgs;
* Copyright (C) 2011 Olivier Fauchon <olivier@aixmarseille.com>
* Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com>
* Copyright (C) 2015 Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
#define SAMPLES_PER_FRAME 1000UL
#define DEFAULT_LIMIT_FRAMES 0
+#define DEFAULT_ANALOG_ENCODING_DIGITS 4
+#define DEFAULT_ANALOG_SPEC_DIGITS 4
+#define DEFAULT_ANALOG_AMPLITUDE 10
+#define DEFAULT_ANALOG_OFFSET 0.
+
/* Logic patterns we can generate. */
enum logic_pattern_type {
/**
PATTERN_SINE,
PATTERN_TRIANGLE,
PATTERN_SAWTOOTH,
+ PATTERN_ANALOG_RANDOM,
+};
+
+static const char *analog_pattern_str[] = {
+ "square",
+ "sine",
+ "triangle",
+ "sawtooth",
+ "random",
+};
+
+struct analog_pattern {
+ float data[ANALOG_BUFSIZE];
+ unsigned int num_samples;
};
struct dev_context {
enum logic_pattern_type logic_pattern;
uint8_t logic_data[LOGIC_BUFSIZE];
/* Analog */
+ struct analog_pattern *analog_patterns[ARRAY_SIZE(analog_pattern_str)];
int32_t num_analog_channels;
GHashTable *ch_ag;
gboolean avg; /* True if averaging is enabled */
struct soft_trigger_logic *stl;
};
-static const char *analog_pattern_str[] = {
- "square",
- "sine",
- "triangle",
- "sawtooth",
-};
-
struct analog_gen {
struct sr_channel *ch;
+ enum sr_mq mq;
+ enum sr_mqflag mq_flags;
+ enum sr_unit unit;
enum analog_pattern_type pattern;
float amplitude;
- float pattern_data[ANALOG_BUFSIZE];
- unsigned int num_samples;
+ float offset;
struct sr_datafeed_analog packet;
struct sr_analog_encoding encoding;
struct sr_analog_meaning meaning;
unsigned int num_avgs; /* Number of samples averaged */
};
-SR_PRIV void demo_generate_analog_pattern(struct analog_gen *ag, uint64_t sample_rate);
+SR_PRIV void demo_generate_analog_pattern(struct dev_context *devc);
+SR_PRIV void demo_free_analog_pattern(struct dev_context *devc);
SR_PRIV int demo_prepare_data(int fd, int revents, void *cb_data);
#endif
#include "scpi.h"
#include "protocol.h"
+/*
+ * This test violates the SCPI protocol, and confuses other devices.
+ * Disable it for now, until a better location was found.
+ */
+#define ECHO_TEST 0
+
static const uint32_t scanopts[] = {
SR_CONF_CONN,
SR_CONF_SERIALCOMM,
unsigned int i;
const struct fluke_scpi_dmm_model *model = NULL;
gchar *channel_name;
+#if ECHO_TEST
char *response;
+#endif
- sdi = g_malloc0(sizeof(struct sr_dev_inst));
- sdi->conn = scpi;
-
+#if ECHO_TEST
/* Test for serial port ECHO enabled. */
+ response = NULL;
sr_scpi_get_string(scpi, "ECHO-TEST", &response);
- if (strcmp(response, "ECHO-TEST") == 0) {
+ if (response && strcmp(response, "ECHO-TEST") == 0) {
sr_err("Serial port ECHO is ON. Please turn it OFF!");
return NULL;
}
+#endif
/* Get device IDN. */
if (sr_scpi_get_hw_id(scpi, &hw_info) != SR_OK) {
}
/* Set up device parameters. */
+ sdi = g_malloc0(sizeof(struct sr_dev_inst));
sdi->vendor = g_strdup(model->vendor);
sdi->model = g_strdup(model->model);
sdi->version = g_strdup(hw_info->firmware_version);
sdi->conn = scpi;
sdi->driver = &fluke_45_driver_info;
sdi->inst_type = SR_INST_SCPI;
+ sr_scpi_hw_info_free(hw_info);
devc = g_malloc0(sizeof(struct dev_context));
devc->num_channels = model->num_channels;
devc->cmdset = cmdset;
+ sdi->priv = devc;
/* Create channels. */
for (i = 0; i < devc->num_channels; i++) {
sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, channel_name);
}
- sdi->priv = devc;
-
return sdi;
}
#include "libsigrok-internal.h"
#include "protocol.h"
-static struct sr_datafeed_analog *handle_qm_18x(const struct sr_dev_inst *sdi,
- char **tokens)
+static void handle_qm_18x(const struct sr_dev_inst *sdi, char **tokens)
{
- struct sr_datafeed_analog *analog;
+ struct dev_context *devc;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
struct sr_analog_encoding encoding;
struct sr_analog_meaning meaning;
struct sr_analog_spec spec;
char *e, *u;
gboolean is_oor;
+ devc = sdi->priv;
+
if (strcmp(tokens[0], "QM") || !tokens[1])
- return NULL;
+ return;
if ((e = strstr(tokens[1], "Out of range"))) {
is_oor = TRUE;
if (sr_atof_ascii(tokens[1], &fvalue) != SR_OK || fvalue == 0.0) {
/* Happens all the time, when switching modes. */
sr_dbg("Invalid float.");
- return NULL;
+ return;
}
}
while (*e && *e == ' ')
e++;
- analog = g_malloc0(sizeof(struct sr_datafeed_analog));
/* TODO: Use proper 'digits' value for this device (and its modes). */
- sr_analog_init(analog, &encoding, &meaning, &spec, 2);
- analog->data = g_malloc(sizeof(float));
- analog->meaning->channels = sdi->channels;
- analog->num_samples = 1;
+ sr_analog_init(&analog, &encoding, &meaning, &spec, 2);
+ analog.data = &fvalue;
+ analog.meaning->channels = sdi->channels;
+ analog.num_samples = 1;
if (is_oor)
- *((float *)analog->data) = NAN;
- else
- *((float *)analog->data) = fvalue;
- analog->meaning->mq = 0;
+ fvalue = NAN;
+ analog.meaning->mq = 0;
if ((u = strstr(e, "V DC")) || (u = strstr(e, "V AC"))) {
- analog->meaning->mq = SR_MQ_VOLTAGE;
- analog->meaning->unit = SR_UNIT_VOLT;
+ analog.meaning->mq = SR_MQ_VOLTAGE;
+ analog.meaning->unit = SR_UNIT_VOLT;
if (!is_oor && e[0] == 'm')
- *((float *)analog->data) /= 1000;
+ fvalue /= 1000;
/* This catches "V AC", "V DC" and "V AC+DC". */
if (strstr(u, "AC"))
- analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+ analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
if (strstr(u, "DC"))
- analog->meaning->mqflags |= SR_MQFLAG_DC;
+ analog.meaning->mqflags |= SR_MQFLAG_DC;
} else if ((u = strstr(e, "dBV")) || (u = strstr(e, "dBm"))) {
- analog->meaning->mq = SR_MQ_VOLTAGE;
+ analog.meaning->mq = SR_MQ_VOLTAGE;
if (u[2] == 'm')
- analog->meaning->unit = SR_UNIT_DECIBEL_MW;
+ analog.meaning->unit = SR_UNIT_DECIBEL_MW;
else
- analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
- analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+ analog.meaning->unit = SR_UNIT_DECIBEL_VOLT;
+ analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
} else if ((u = strstr(e, "Ohms"))) {
- analog->meaning->mq = SR_MQ_RESISTANCE;
- analog->meaning->unit = SR_UNIT_OHM;
+ analog.meaning->mq = SR_MQ_RESISTANCE;
+ analog.meaning->unit = SR_UNIT_OHM;
if (is_oor)
- *((float *)analog->data) = INFINITY;
+ fvalue = INFINITY;
else if (e[0] == 'k')
- *((float *)analog->data) *= 1000;
+ fvalue *= 1000;
else if (e[0] == 'M')
- *((float *)analog->data) *= 1000000;
+ fvalue *= 1000000;
} else if (!strcmp(e, "nS")) {
- analog->meaning->mq = SR_MQ_CONDUCTANCE;
- analog->meaning->unit = SR_UNIT_SIEMENS;
- *((float *)analog->data) /= 1e+9;
+ analog.meaning->mq = SR_MQ_CONDUCTANCE;
+ analog.meaning->unit = SR_UNIT_SIEMENS;
+ *((float *)analog.data) /= 1e+9;
} else if ((u = strstr(e, "Farads"))) {
- analog->meaning->mq = SR_MQ_CAPACITANCE;
- analog->meaning->unit = SR_UNIT_FARAD;
+ analog.meaning->mq = SR_MQ_CAPACITANCE;
+ analog.meaning->unit = SR_UNIT_FARAD;
if (!is_oor) {
if (e[0] == 'm')
- *((float *)analog->data) /= 1e+3;
+ fvalue /= 1e+3;
else if (e[0] == 'u')
- *((float *)analog->data) /= 1e+6;
+ fvalue /= 1e+6;
else if (e[0] == 'n')
- *((float *)analog->data) /= 1e+9;
+ fvalue /= 1e+9;
}
} else if ((u = strstr(e, "Deg C")) || (u = strstr(e, "Deg F"))) {
- analog->meaning->mq = SR_MQ_TEMPERATURE;
+ analog.meaning->mq = SR_MQ_TEMPERATURE;
if (u[4] == 'C')
- analog->meaning->unit = SR_UNIT_CELSIUS;
+ analog.meaning->unit = SR_UNIT_CELSIUS;
else
- analog->meaning->unit = SR_UNIT_FAHRENHEIT;
+ analog.meaning->unit = SR_UNIT_FAHRENHEIT;
} else if ((u = strstr(e, "A AC")) || (u = strstr(e, "A DC"))) {
- analog->meaning->mq = SR_MQ_CURRENT;
- analog->meaning->unit = SR_UNIT_AMPERE;
+ analog.meaning->mq = SR_MQ_CURRENT;
+ analog.meaning->unit = SR_UNIT_AMPERE;
/* This catches "A AC", "A DC" and "A AC+DC". */
if (strstr(u, "AC"))
- analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+ analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
if (strstr(u, "DC"))
- analog->meaning->mqflags |= SR_MQFLAG_DC;
+ analog.meaning->mqflags |= SR_MQFLAG_DC;
if (!is_oor) {
if (e[0] == 'm')
- *((float *)analog->data) /= 1e+3;
+ fvalue /= 1e+3;
else if (e[0] == 'u')
- *((float *)analog->data) /= 1e+6;
+ fvalue /= 1e+6;
}
} else if ((u = strstr(e, "Hz"))) {
- analog->meaning->mq = SR_MQ_FREQUENCY;
- analog->meaning->unit = SR_UNIT_HERTZ;
+ analog.meaning->mq = SR_MQ_FREQUENCY;
+ analog.meaning->unit = SR_UNIT_HERTZ;
if (e[0] == 'k')
- *((float *)analog->data) *= 1e+3;
+ fvalue *= 1e+3;
} else if (!strcmp(e, "%")) {
- analog->meaning->mq = SR_MQ_DUTY_CYCLE;
- analog->meaning->unit = SR_UNIT_PERCENTAGE;
+ analog.meaning->mq = SR_MQ_DUTY_CYCLE;
+ analog.meaning->unit = SR_UNIT_PERCENTAGE;
} else if ((u = strstr(e, "ms"))) {
- analog->meaning->mq = SR_MQ_PULSE_WIDTH;
- analog->meaning->unit = SR_UNIT_SECOND;
- *((float *)analog->data) /= 1e+3;
+ analog.meaning->mq = SR_MQ_PULSE_WIDTH;
+ analog.meaning->unit = SR_UNIT_SECOND;
+ fvalue /= 1e+3;
}
- if (analog->meaning->mq == 0) {
- /* Not a valid measurement. */
- g_free(analog->data);
- g_free(analog);
- analog = NULL;
+ if (analog.meaning->mq != 0) {
+ /* Got a measurement. */
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ sr_session_send(sdi, &packet);
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
}
-
- return analog;
}
-static struct sr_datafeed_analog *handle_qm_28x(const struct sr_dev_inst *sdi,
- char **tokens)
+static void handle_qm_28x(const struct sr_dev_inst *sdi, char **tokens)
{
- struct sr_datafeed_analog *analog;
+ struct dev_context *devc;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
struct sr_analog_encoding encoding;
struct sr_analog_meaning meaning;
struct sr_analog_spec spec;
float fvalue;
+ devc = sdi->priv;
+
if (!tokens[1])
- return NULL;
+ return;
if (sr_atof_ascii(tokens[0], &fvalue) != SR_OK || fvalue == 0.0) {
sr_err("Invalid float '%s'.", tokens[0]);
- return NULL;
+ return;
}
- analog = g_malloc0(sizeof(struct sr_datafeed_analog));
/* TODO: Use proper 'digits' value for this device (and its modes). */
- sr_analog_init(analog, &encoding, &meaning, &spec, 2);
- analog->data = g_malloc(sizeof(float));
- analog->meaning->channels = sdi->channels;
- analog->num_samples = 1;
- *((float *)analog->data) = fvalue;
- analog->meaning->mq = 0;
+ sr_analog_init(&analog, &encoding, &meaning, &spec, 2);
+ analog.data = &fvalue;
+ analog.meaning->channels = sdi->channels;
+ analog.num_samples = 1;
+ analog.meaning->mq = 0;
if (!strcmp(tokens[1], "VAC") || !strcmp(tokens[1], "VDC")) {
- analog->meaning->mq = SR_MQ_VOLTAGE;
- analog->meaning->unit = SR_UNIT_VOLT;
+ analog.meaning->mq = SR_MQ_VOLTAGE;
+ analog.meaning->unit = SR_UNIT_VOLT;
if (!strcmp(tokens[2], "NORMAL")) {
if (tokens[1][1] == 'A') {
- analog->meaning->mqflags |= SR_MQFLAG_AC;
- analog->meaning->mqflags |= SR_MQFLAG_RMS;
+ analog.meaning->mqflags |= SR_MQFLAG_AC;
+ analog.meaning->mqflags |= SR_MQFLAG_RMS;
} else
- analog->meaning->mqflags |= SR_MQFLAG_DC;
+ analog.meaning->mqflags |= SR_MQFLAG_DC;
} else if (!strcmp(tokens[2], "OL") || !strcmp(tokens[2], "OL_MINUS")) {
- *((float *)analog->data) = NAN;
+ fvalue = NAN;
} else
- analog->meaning->mq = 0;
+ analog.meaning->mq = 0;
} else if (!strcmp(tokens[1], "dBV") || !strcmp(tokens[1], "dBm")) {
- analog->meaning->mq = SR_MQ_VOLTAGE;
+ analog.meaning->mq = SR_MQ_VOLTAGE;
if (tokens[1][2] == 'm')
- analog->meaning->unit = SR_UNIT_DECIBEL_MW;
+ analog.meaning->unit = SR_UNIT_DECIBEL_MW;
else
- analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
- analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+ analog.meaning->unit = SR_UNIT_DECIBEL_VOLT;
+ analog.meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
} else if (!strcmp(tokens[1], "CEL") || !strcmp(tokens[1], "FAR")) {
if (!strcmp(tokens[2], "NORMAL")) {
- analog->meaning->mq = SR_MQ_TEMPERATURE;
+ analog.meaning->mq = SR_MQ_TEMPERATURE;
if (tokens[1][0] == 'C')
- analog->meaning->unit = SR_UNIT_CELSIUS;
+ analog.meaning->unit = SR_UNIT_CELSIUS;
else
- analog->meaning->unit = SR_UNIT_FAHRENHEIT;
+ analog.meaning->unit = SR_UNIT_FAHRENHEIT;
}
} else if (!strcmp(tokens[1], "OHM")) {
if (!strcmp(tokens[3], "NONE")) {
- analog->meaning->mq = SR_MQ_RESISTANCE;
- analog->meaning->unit = SR_UNIT_OHM;
+ analog.meaning->mq = SR_MQ_RESISTANCE;
+ analog.meaning->unit = SR_UNIT_OHM;
if (!strcmp(tokens[2], "OL") || !strcmp(tokens[2], "OL_MINUS")) {
- *((float *)analog->data) = INFINITY;
+ fvalue = INFINITY;
} else if (strcmp(tokens[2], "NORMAL"))
- analog->meaning->mq = 0;
+ analog.meaning->mq = 0;
} else if (!strcmp(tokens[3], "OPEN_CIRCUIT")) {
- analog->meaning->mq = SR_MQ_CONTINUITY;
- analog->meaning->unit = SR_UNIT_BOOLEAN;
- *((float *)analog->data) = 0.0;
+ analog.meaning->mq = SR_MQ_CONTINUITY;
+ analog.meaning->unit = SR_UNIT_BOOLEAN;
+ fvalue = 0.0;
} else if (!strcmp(tokens[3], "SHORT_CIRCUIT")) {
- analog->meaning->mq = SR_MQ_CONTINUITY;
- analog->meaning->unit = SR_UNIT_BOOLEAN;
- *((float *)analog->data) = 1.0;
+ analog.meaning->mq = SR_MQ_CONTINUITY;
+ analog.meaning->unit = SR_UNIT_BOOLEAN;
+ fvalue = 1.0;
}
} else if (!strcmp(tokens[1], "F")
&& !strcmp(tokens[2], "NORMAL")
&& !strcmp(tokens[3], "NONE")) {
- analog->meaning->mq = SR_MQ_CAPACITANCE;
- analog->meaning->unit = SR_UNIT_FARAD;
+ analog.meaning->mq = SR_MQ_CAPACITANCE;
+ analog.meaning->unit = SR_UNIT_FARAD;
} else if (!strcmp(tokens[1], "AAC") || !strcmp(tokens[1], "ADC")) {
- analog->meaning->mq = SR_MQ_CURRENT;
- analog->meaning->unit = SR_UNIT_AMPERE;
+ analog.meaning->mq = SR_MQ_CURRENT;
+ analog.meaning->unit = SR_UNIT_AMPERE;
if (!strcmp(tokens[2], "NORMAL")) {
if (tokens[1][1] == 'A') {
- analog->meaning->mqflags |= SR_MQFLAG_AC;
- analog->meaning->mqflags |= SR_MQFLAG_RMS;
+ analog.meaning->mqflags |= SR_MQFLAG_AC;
+ analog.meaning->mqflags |= SR_MQFLAG_RMS;
} else
- analog->meaning->mqflags |= SR_MQFLAG_DC;
+ analog.meaning->mqflags |= SR_MQFLAG_DC;
} else if (!strcmp(tokens[2], "OL") || !strcmp(tokens[2], "OL_MINUS")) {
- *((float *)analog->data) = NAN;
+ fvalue = NAN;
} else
- analog->meaning->mq = 0;
- } if (!strcmp(tokens[1], "Hz") && !strcmp(tokens[2], "NORMAL")) {
- analog->meaning->mq = SR_MQ_FREQUENCY;
- analog->meaning->unit = SR_UNIT_HERTZ;
+ analog.meaning->mq = 0;
+ } else if (!strcmp(tokens[1], "Hz") && !strcmp(tokens[2], "NORMAL")) {
+ analog.meaning->mq = SR_MQ_FREQUENCY;
+ analog.meaning->unit = SR_UNIT_HERTZ;
} else if (!strcmp(tokens[1], "PCT") && !strcmp(tokens[2], "NORMAL")) {
- analog->meaning->mq = SR_MQ_DUTY_CYCLE;
- analog->meaning->unit = SR_UNIT_PERCENTAGE;
+ analog.meaning->mq = SR_MQ_DUTY_CYCLE;
+ analog.meaning->unit = SR_UNIT_PERCENTAGE;
} else if (!strcmp(tokens[1], "S") && !strcmp(tokens[2], "NORMAL")) {
- analog->meaning->mq = SR_MQ_PULSE_WIDTH;
- analog->meaning->unit = SR_UNIT_SECOND;
+ analog.meaning->mq = SR_MQ_PULSE_WIDTH;
+ analog.meaning->unit = SR_UNIT_SECOND;
} else if (!strcmp(tokens[1], "SIE") && !strcmp(tokens[2], "NORMAL")) {
- analog->meaning->mq = SR_MQ_CONDUCTANCE;
- analog->meaning->unit = SR_UNIT_SIEMENS;
+ analog.meaning->mq = SR_MQ_CONDUCTANCE;
+ analog.meaning->unit = SR_UNIT_SIEMENS;
}
- if (analog->meaning->mq == 0) {
- /* Not a valid measurement. */
- g_free(analog->data);
- g_free(analog);
- analog = NULL;
+ if (analog.meaning->mq != 0) {
+ /* Got a measurement. */
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ sr_session_send(sdi, &packet);
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
}
-
- return analog;
}
static void handle_qm_19x_meta(const struct sr_dev_inst *sdi, char **tokens)
{
struct dev_context *devc;
struct sr_serial_dev_inst *serial;
- struct sr_datafeed_packet packet;
- struct sr_datafeed_analog *analog;
int num_tokens, n, i;
char cmd[16], **tokens;
return;
}
- analog = NULL;
tokens = g_strsplit(devc->buf, ",", 0);
if (tokens[0]) {
if (devc->profile->model == FLUKE_187 || devc->profile->model == FLUKE_189) {
devc->expect_response = FALSE;
- analog = handle_qm_18x(sdi, tokens);
+ handle_qm_18x(sdi, tokens);
} else if (devc->profile->model == FLUKE_287 || devc->profile->model == FLUKE_289) {
devc->expect_response = FALSE;
- analog = handle_qm_28x(sdi, tokens);
+ handle_qm_28x(sdi, tokens);
} else if (devc->profile->model == FLUKE_190) {
devc->expect_response = FALSE;
for (num_tokens = 0; tokens[num_tokens]; num_tokens++);
}
g_strfreev(tokens);
devc->buflen = 0;
-
- if (analog) {
- /* Got a measurement. */
- packet.type = SR_DF_ANALOG;
- packet.payload = analog;
- sr_session_send(sdi, &packet);
- sr_sw_limits_update_samples_read(&devc->limits, 1);
- g_free(analog->data);
- g_free(analog);
- }
-
}
SR_PRIV int fluke_receive_data(int fd, int revents, void *cb_data)
}
};
+static const struct ftdi_chip_desc ft232h_desc = {
+ .vendor = 0x0403,
+ .product = 0x6014,
+ .samplerate_div = 30,
+ .channel_names = {
+ "ADBUS0", "ADBUS1", "ADBUS2", "ADBUS3", "ADBUS4", "ADBUS5", "ADBUS6", "ADBUS7",
+ NULL
+ }
+};
+
static const struct ftdi_chip_desc *chip_descs[] = {
&ft2232h_desc,
&ft232r_desc,
+ &ft232h_desc,
+ NULL,
};
static void scan_device(struct ftdi_context *ftdic,
desc = NULL;
for (unsigned long i = 0; i < ARRAY_SIZE(chip_descs); i++) {
desc = chip_descs[i];
+ if (!desc)
+ break;
if (desc->vendor == usb_desc.idVendor &&
desc->product == usb_desc.idProduct)
break;
}
if (!desc) {
- sr_spew("Unsupported FTDI device 0x%4x:0x%4x.",
+ sr_spew("Unsupported FTDI device 0x%04x:0x%04x.",
usb_desc.idVendor, usb_desc.idProduct);
return;
}
SR_MHZ(12),
SR_MHZ(16),
SR_MHZ(24),
+ SR_MHZ(48),
};
static gboolean is_plausible(const struct libusb_device_descriptor *des)
sr_dbg("GPIF delay = %d, clocksource = %sMHz.", delay,
(cmd.flags & CMD_START_FLAGS_CLK_48MHZ) ? "48" : "30");
- if (delay <= 0 || delay > MAX_SAMPLE_DELAY) {
+ if (delay < 0 || delay > MAX_SAMPLE_DELAY) {
sr_err("Unable to sample at %" PRIu64 "Hz.", samplerate);
return SR_ERR;
}
gint64 start, remaining;
const int timeout_ms = 100;
- if (!serial || (lines <= 0) || !buf || (buflen <= 0))
+ if (!serial || !buf || (buflen <= 0))
return SR_ERR_ARG;
start = g_get_monotonic_time();
* This file is part of the libsigrok project.
*
* Copyright (C) 2013 poljar (Damir Jelić) <poljarinho@gmail.com>
+ * Copyright (C) 2018 Guido Trentalancia <guido@trentalancia.com>
*
* 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
#include "scpi.h"
#include "protocol.h"
-#define SERIALCOMM "115200/8n1/flow=1"
-
static struct sr_dev_driver hameg_hmo_driver_info;
static const char *manufacturers[] = {
static const uint32_t drvopts[] = {
SR_CONF_OSCILLOSCOPE,
+ SR_CONF_LOGIC_ANALYZER,
};
enum {
static int config_get(uint32_t key, GVariant **data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
- int cg_type, idx;
+ int cg_type, idx, i;
struct dev_context *devc;
const struct scope_config *model;
struct scope_state *state;
case SR_CONF_TRIGGER_SLOPE:
*data = g_variant_new_string((*model->trigger_slopes)[state->trigger_slope]);
break;
+ case SR_CONF_TRIGGER_PATTERN:
+ *data = g_variant_new_string(state->trigger_pattern);
+ break;
+ case SR_CONF_HIGH_RESOLUTION:
+ *data = g_variant_new_boolean(state->high_resolution);
+ break;
+ case SR_CONF_PEAK_DETECTION:
+ *data = g_variant_new_boolean(state->peak_detection);
+ break;
case SR_CONF_HORIZ_TRIGGERPOS:
*data = g_variant_new_double(state->horiz_triggerpos);
break;
case SR_CONF_SAMPLERATE:
*data = g_variant_new_uint64(state->sample_rate);
break;
+ case SR_CONF_LOGIC_THRESHOLD:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ if (cg_type != CG_DIGITAL)
+ return SR_ERR_NA;
+ if (!model)
+ return SR_ERR_ARG;
+ if ((idx = std_cg_idx(cg, devc->digital_groups, model->digital_pods)) < 0)
+ return SR_ERR_ARG;
+ *data = g_variant_new_string((*model->logic_threshold)[state->digital_pods[idx].threshold]);
+ break;
+ case SR_CONF_LOGIC_THRESHOLD_CUSTOM:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ if (cg_type != CG_DIGITAL)
+ return SR_ERR_NA;
+ if (!model)
+ return SR_ERR_ARG;
+ if ((idx = std_cg_idx(cg, devc->digital_groups, model->digital_pods)) < 0)
+ return SR_ERR_ARG;
+ /* Check if the oscilloscope is currently in custom threshold mode. */
+ for (i = 0; i < model->num_logic_threshold; i++) {
+ if (!strcmp("USER2", (*model->logic_threshold)[i]))
+ if (strcmp("USER2", (*model->logic_threshold)[state->digital_pods[idx].threshold]))
+ return SR_ERR_NA;
+ if (!strcmp("USER", (*model->logic_threshold)[i]))
+ if (strcmp("USER", (*model->logic_threshold)[state->digital_pods[idx].threshold]))
+ return SR_ERR_NA;
+ if (!strcmp("MAN", (*model->logic_threshold)[i]))
+ if (strcmp("MAN", (*model->logic_threshold)[state->digital_pods[idx].threshold]))
+ return SR_ERR_NA;
+ }
+ *data = g_variant_new_double(state->digital_pods[idx].user_threshold);
+ break;
default:
return SR_ERR_NA;
}
static int config_set(uint32_t key, GVariant *data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
- int ret, cg_type, idx, j;
- char command[MAX_COMMAND_SIZE], float_str[30];
+ int ret, cg_type, idx, i, j;
+ char command[MAX_COMMAND_SIZE], command2[MAX_COMMAND_SIZE];
+ char float_str[30], *tmp_str;
struct dev_context *devc;
const struct scope_config *model;
struct scope_state *state;
- double tmp_d;
- gboolean update_sample_rate;
+ double tmp_d, tmp_d2;
+ gboolean update_sample_rate, tmp_bool;
if (!sdi)
return SR_ERR_ARG;
update_sample_rate = FALSE;
switch (key) {
+ case SR_CONF_LIMIT_SAMPLES:
+ devc->samples_limit = g_variant_get_uint64(data);
+ ret = SR_OK;
+ break;
case SR_CONF_LIMIT_FRAMES:
devc->frame_limit = g_variant_get_uint64(data);
ret = SR_OK;
break;
- case SR_CONF_TRIGGER_SOURCE:
- if ((idx = std_str_idx(data, *model->trigger_sources, model->num_trigger_sources)) < 0)
- return SR_ERR_ARG;
- state->trigger_source = idx;
- g_snprintf(command, sizeof(command),
- (*model->scpi_dialect)[SCPI_CMD_SET_TRIGGER_SOURCE],
- (*model->trigger_sources)[idx]);
- ret = sr_scpi_send(sdi->conn, command);
- break;
case SR_CONF_VDIV:
if (!cg)
return SR_ERR_CHANNEL_GROUP;
return SR_ERR_ARG;
if ((j = std_cg_idx(cg, devc->analog_groups, model->analog_channels)) < 0)
return SR_ERR_ARG;
- state->analog_channels[j].vdiv = idx;
g_ascii_formatd(float_str, sizeof(float_str), "%E",
(float) (*model->vdivs)[idx][0] / (*model->vdivs)[idx][1]);
g_snprintf(command, sizeof(command),
- (*model->scpi_dialect)[SCPI_CMD_SET_VERTICAL_DIV],
+ (*model->scpi_dialect)[SCPI_CMD_SET_VERTICAL_SCALE],
j + 1, float_str);
if (sr_scpi_send(sdi->conn, command) != SR_OK ||
sr_scpi_get_opc(sdi->conn) != SR_OK)
return SR_ERR;
+ state->analog_channels[j].vdiv = idx;
ret = SR_OK;
break;
case SR_CONF_TIMEBASE:
if ((idx = std_u64_tuple_idx(data, *model->timebases, model->num_timebases)) < 0)
return SR_ERR_ARG;
- state->timebase = idx;
g_ascii_formatd(float_str, sizeof(float_str), "%E",
(float) (*model->timebases)[idx][0] / (*model->timebases)[idx][1]);
g_snprintf(command, sizeof(command),
(*model->scpi_dialect)[SCPI_CMD_SET_TIMEBASE],
float_str);
- ret = sr_scpi_send(sdi->conn, command);
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->timebase = idx;
+ ret = SR_OK;
update_sample_rate = TRUE;
break;
case SR_CONF_HORIZ_TRIGGERPOS:
tmp_d = g_variant_get_double(data);
if (tmp_d < 0.0 || tmp_d > 1.0)
return SR_ERR;
- state->horiz_triggerpos = tmp_d;
- tmp_d = -(tmp_d - 0.5) *
+ tmp_d2 = -(tmp_d - 0.5) *
((double) (*model->timebases)[state->timebase][0] /
(*model->timebases)[state->timebase][1])
* model->num_xdivs;
- g_ascii_formatd(float_str, sizeof(float_str), "%E", tmp_d);
+ g_ascii_formatd(float_str, sizeof(float_str), "%E", tmp_d2);
g_snprintf(command, sizeof(command),
(*model->scpi_dialect)[SCPI_CMD_SET_HORIZ_TRIGGERPOS],
float_str);
- ret = sr_scpi_send(sdi->conn, command);
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->horiz_triggerpos = tmp_d;
+ ret = SR_OK;
+ break;
+ case SR_CONF_TRIGGER_SOURCE:
+ if ((idx = std_str_idx(data, *model->trigger_sources, model->num_trigger_sources)) < 0)
+ return SR_ERR_ARG;
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_TRIGGER_SOURCE],
+ (*model->trigger_sources)[idx]);
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->trigger_source = idx;
+ ret = SR_OK;
break;
case SR_CONF_TRIGGER_SLOPE:
if ((idx = std_str_idx(data, *model->trigger_slopes, model->num_trigger_slopes)) < 0)
return SR_ERR_ARG;
- state->trigger_slope = idx;
g_snprintf(command, sizeof(command),
(*model->scpi_dialect)[SCPI_CMD_SET_TRIGGER_SLOPE],
(*model->trigger_slopes)[idx]);
- ret = sr_scpi_send(sdi->conn, command);
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->trigger_slope = idx;
+ ret = SR_OK;
+ break;
+ case SR_CONF_TRIGGER_PATTERN:
+ tmp_str = (char *)g_variant_get_string(data, 0);
+ idx = strlen(tmp_str);
+ if (idx == 0 || idx > model->analog_channels + model->digital_channels)
+ return SR_ERR_ARG;
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_TRIGGER_PATTERN],
+ tmp_str);
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ strncpy(state->trigger_pattern,
+ tmp_str,
+ MAX_ANALOG_CHANNEL_COUNT + MAX_DIGITAL_CHANNEL_COUNT);
+ ret = SR_OK;
+ break;
+ case SR_CONF_HIGH_RESOLUTION:
+ tmp_bool = g_variant_get_boolean(data);
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_HIGH_RESOLUTION],
+ tmp_bool ? "AUTO" : "OFF");
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ /* High Resolution mode automatically switches off Peak Detection. */
+ if (tmp_bool) {
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_PEAK_DETECTION],
+ "OFF");
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->peak_detection = FALSE;
+ }
+ state->high_resolution = tmp_bool;
+ ret = SR_OK;
+ break;
+ case SR_CONF_PEAK_DETECTION:
+ tmp_bool = g_variant_get_boolean(data);
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_PEAK_DETECTION],
+ tmp_bool ? "AUTO" : "OFF");
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ /* Peak Detection automatically switches off High Resolution mode. */
+ if (tmp_bool) {
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_HIGH_RESOLUTION],
+ "OFF");
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->high_resolution = FALSE;
+ }
+ state->peak_detection = tmp_bool;
+ ret = SR_OK;
break;
case SR_CONF_COUPLING:
if (!cg)
return SR_ERR_ARG;
if ((j = std_cg_idx(cg, devc->analog_groups, model->analog_channels)) < 0)
return SR_ERR_ARG;
- state->analog_channels[j].coupling = idx;
g_snprintf(command, sizeof(command),
(*model->scpi_dialect)[SCPI_CMD_SET_COUPLING],
j + 1, (*model->coupling_options)[idx]);
if (sr_scpi_send(sdi->conn, command) != SR_OK ||
sr_scpi_get_opc(sdi->conn) != SR_OK)
return SR_ERR;
+ state->analog_channels[j].coupling = idx;
+ ret = SR_OK;
+ break;
+ case SR_CONF_LOGIC_THRESHOLD:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ if (cg_type != CG_DIGITAL)
+ return SR_ERR_NA;
+ if (!model)
+ return SR_ERR_ARG;
+ if ((idx = std_str_idx(data, *model->logic_threshold, model->num_logic_threshold)) < 0)
+ return SR_ERR_ARG;
+ if ((j = std_cg_idx(cg, devc->digital_groups, model->digital_pods)) < 0)
+ return SR_ERR_ARG;
+ /* Check if the threshold command is based on the POD or digital channel index. */
+ if (model->logic_threshold_for_pod)
+ i = j + 1;
+ else
+ i = j * DIGITAL_CHANNELS_PER_POD;
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_THRESHOLD],
+ i, (*model->logic_threshold)[idx]);
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->digital_pods[j].threshold = idx;
+ ret = SR_OK;
+ break;
+ case SR_CONF_LOGIC_THRESHOLD_CUSTOM:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ if (cg_type != CG_DIGITAL)
+ return SR_ERR_NA;
+ if (!model)
+ return SR_ERR_ARG;
+ if ((j = std_cg_idx(cg, devc->digital_groups, model->digital_pods)) < 0)
+ return SR_ERR_ARG;
+ tmp_d = g_variant_get_double(data);
+ if (tmp_d < -2.0 || tmp_d > 8.0)
+ return SR_ERR;
+ g_ascii_formatd(float_str, sizeof(float_str), "%E", tmp_d);
+ /* Check if the threshold command is based on the POD or digital channel index. */
+ if (model->logic_threshold_for_pod)
+ idx = j + 1;
+ else
+ idx = j * DIGITAL_CHANNELS_PER_POD;
+ /* Try to support different dialects exhaustively. */
+ for (i = 0; i < model->num_logic_threshold; i++) {
+ if (!strcmp("USER2", (*model->logic_threshold)[i])) {
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_USER_THRESHOLD],
+ idx, 2, float_str); /* USER2 */
+ g_snprintf(command2, sizeof(command2),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_THRESHOLD],
+ idx, "USER2");
+ break;
+ }
+ if (!strcmp("USER", (*model->logic_threshold)[i])) {
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_USER_THRESHOLD],
+ idx, float_str);
+ g_snprintf(command2, sizeof(command2),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_THRESHOLD],
+ idx, "USER");
+ break;
+ }
+ if (!strcmp("MAN", (*model->logic_threshold)[i])) {
+ g_snprintf(command, sizeof(command),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_USER_THRESHOLD],
+ idx, float_str);
+ g_snprintf(command2, sizeof(command2),
+ (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_THRESHOLD],
+ idx, "MAN");
+ break;
+ }
+ }
+ if (sr_scpi_send(sdi->conn, command) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ if (sr_scpi_send(sdi->conn, command2) != SR_OK ||
+ sr_scpi_get_opc(sdi->conn) != SR_OK)
+ return SR_ERR;
+ state->digital_pods[j].user_threshold = tmp_d;
ret = SR_OK;
break;
default:
break;
}
- if (ret == SR_OK)
- ret = sr_scpi_get_opc(sdi->conn);
-
if (ret == SR_OK && update_sample_rate)
ret = hmo_update_sample_rate(sdi);
*data = std_gvar_array_u32(ARRAY_AND_SIZE(drvopts));
} else if (cg_type == CG_ANALOG) {
*data = std_gvar_array_u32(*model->devopts_cg_analog, model->num_devopts_cg_analog);
+ } else if (cg_type == CG_DIGITAL) {
+ *data = std_gvar_array_u32(*model->devopts_cg_digital, model->num_devopts_cg_digital);
} else {
*data = std_gvar_array_u32(NULL, 0);
}
return SR_ERR_ARG;
*data = std_gvar_tuple_array(*model->vdivs, model->num_vdivs);
break;
+ case SR_CONF_LOGIC_THRESHOLD:
+ if (!cg)
+ return SR_ERR_CHANNEL_GROUP;
+ if (!model)
+ return SR_ERR_ARG;
+ *data = g_variant_new_strv(*model->logic_threshold, model->num_logic_threshold);
+ break;
default:
return SR_ERR_NA;
}
case SR_CHANNEL_LOGIC:
g_snprintf(command, sizeof(command),
(*model->scpi_dialect)[SCPI_CMD_GET_DIG_DATA],
- ch->index < 8 ? 1 : 2);
+ ch->index / DIGITAL_CHANNELS_PER_POD + 1);
break;
default:
sr_err("Invalid channel type.");
enabled_chan[idx] = TRUE;
break;
case SR_CHANNEL_LOGIC:
- idx = ch->index / 8;
+ idx = ch->index / DIGITAL_CHANNELS_PER_POD;
if (idx < ARRAY_SIZE(enabled_pod))
enabled_pod[idx] = TRUE;
break;
case SR_CHANNEL_LOGIC:
/*
* A digital POD needs to be enabled for every group of
- * 8 channels.
+ * DIGITAL_CHANNELS_PER_POD channels.
*/
if (ch->enabled)
- pod_enabled[ch->index < 8 ? 0 : 1] = TRUE;
+ pod_enabled[ch->index / DIGITAL_CHANNELS_PER_POD] = TRUE;
if (ch->enabled == state->digital_channels[ch->index])
break;
ret = SR_OK;
for (i = 0; i < model->digital_pods; i++) {
- if (state->digital_pods[i] == pod_enabled[i])
+ if (state->digital_pods[i].state == pod_enabled[i])
continue;
g_snprintf(command, sizeof(command),
(*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_STATE],
ret = SR_ERR;
break;
}
- state->digital_pods[i] = pod_enabled[i];
+ state->digital_pods[i].state = pod_enabled[i];
setup_changed = TRUE;
}
g_free(pod_enabled);
scpi = sdi->conn;
devc = sdi->priv;
+ devc->num_samples = 0;
+ devc->num_frames = 0;
+
/* Preset empty results. */
for (group = 0; group < ARRAY_SIZE(digital_added); group++)
digital_added[group] = FALSE;
if (!ch->enabled)
continue;
/* Only add a single digital channel per group (pod). */
- group = ch->index / 8;
+ group = ch->index / DIGITAL_CHANNELS_PER_POD;
if (ch->type != SR_CHANNEL_LOGIC || !digital_added[group]) {
devc->enabled_channels = g_slist_append(
devc->enabled_channels, ch);
devc = sdi->priv;
+ devc->num_samples = 0;
devc->num_frames = 0;
g_slist_free(devc->enabled_channels);
devc->enabled_channels = NULL;
* This file is part of the libsigrok project.
*
* Copyright (C) 2013 poljar (Damir Jelić) <poljarinho@gmail.com>
+ * Copyright (C) 2018 Guido Trentalancia <guido@trentalancia.com>
*
* 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
SR_PRIV void hmo_cleanup_logic_data(struct dev_context *devc);
static const char *hameg_scpi_dialect[] = {
- [SCPI_CMD_GET_DIG_DATA] = ":FORM UINT,8;:POD%d:DATA?",
- [SCPI_CMD_GET_TIMEBASE] = ":TIM:SCAL?",
- [SCPI_CMD_SET_TIMEBASE] = ":TIM:SCAL %s",
- [SCPI_CMD_GET_COUPLING] = ":CHAN%d:COUP?",
- [SCPI_CMD_SET_COUPLING] = ":CHAN%d:COUP %s",
- [SCPI_CMD_GET_SAMPLE_RATE] = ":ACQ:SRAT?",
- [SCPI_CMD_GET_SAMPLE_RATE_LIVE] = ":%s:DATA:POINTS?",
- [SCPI_CMD_GET_ANALOG_DATA] = ":FORM:BORD %s;" \
- ":FORM REAL,32;:CHAN%d:DATA?",
- [SCPI_CMD_GET_VERTICAL_DIV] = ":CHAN%d:SCAL?",
- [SCPI_CMD_SET_VERTICAL_DIV] = ":CHAN%d:SCAL %s",
- [SCPI_CMD_GET_DIG_POD_STATE] = ":POD%d:STAT?",
- [SCPI_CMD_SET_DIG_POD_STATE] = ":POD%d:STAT %d",
- [SCPI_CMD_GET_TRIGGER_SLOPE] = ":TRIG:A:EDGE:SLOP?",
- [SCPI_CMD_SET_TRIGGER_SLOPE] = ":TRIG:A:EDGE:SLOP %s",
- [SCPI_CMD_GET_TRIGGER_SOURCE] = ":TRIG:A:SOUR?",
- [SCPI_CMD_SET_TRIGGER_SOURCE] = ":TRIG:A:SOUR %s",
- [SCPI_CMD_GET_DIG_CHAN_STATE] = ":LOG%d:STAT?",
- [SCPI_CMD_SET_DIG_CHAN_STATE] = ":LOG%d:STAT %d",
- [SCPI_CMD_GET_VERTICAL_OFFSET] = ":CHAN%d:POS?",
- [SCPI_CMD_GET_HORIZ_TRIGGERPOS] = ":TIM:POS?",
- [SCPI_CMD_SET_HORIZ_TRIGGERPOS] = ":TIM:POS %s",
- [SCPI_CMD_GET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT?",
- [SCPI_CMD_SET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT %d",
- [SCPI_CMD_GET_PROBE_UNIT] = ":PROB%d:SET:ATT:UNIT?",
+ [SCPI_CMD_GET_DIG_DATA] = ":FORM UINT,8;:POD%d:DATA?",
+ [SCPI_CMD_GET_TIMEBASE] = ":TIM:SCAL?",
+ [SCPI_CMD_SET_TIMEBASE] = ":TIM:SCAL %s",
+ [SCPI_CMD_GET_HORIZONTAL_DIV] = ":TIM:DIV?",
+ [SCPI_CMD_GET_COUPLING] = ":CHAN%d:COUP?",
+ [SCPI_CMD_SET_COUPLING] = ":CHAN%d:COUP %s",
+ [SCPI_CMD_GET_SAMPLE_RATE] = ":ACQ:SRAT?",
+ [SCPI_CMD_GET_ANALOG_DATA] = ":FORM:BORD %s;" \
+ ":FORM REAL,32;:CHAN%d:DATA?",
+ [SCPI_CMD_GET_VERTICAL_SCALE] = ":CHAN%d:SCAL?",
+ [SCPI_CMD_SET_VERTICAL_SCALE] = ":CHAN%d:SCAL %s",
+ [SCPI_CMD_GET_DIG_POD_STATE] = ":POD%d:STAT?",
+ [SCPI_CMD_SET_DIG_POD_STATE] = ":POD%d:STAT %d",
+ [SCPI_CMD_GET_TRIGGER_SOURCE] = ":TRIG:A:SOUR?",
+ [SCPI_CMD_SET_TRIGGER_SOURCE] = ":TRIG:A:SOUR %s",
+ [SCPI_CMD_GET_TRIGGER_SLOPE] = ":TRIG:A:EDGE:SLOP?",
+ [SCPI_CMD_SET_TRIGGER_SLOPE] = ":TRIG:A:TYPE EDGE;:TRIG:A:EDGE:SLOP %s",
+ [SCPI_CMD_GET_TRIGGER_PATTERN] = ":TRIG:A:PATT:SOUR?",
+ [SCPI_CMD_SET_TRIGGER_PATTERN] = ":TRIG:A:TYPE LOGIC;" \
+ ":TRIG:A:PATT:FUNC AND;" \
+ ":TRIG:A:PATT:COND \"TRUE\";" \
+ ":TRIG:A:PATT:MODE OFF;" \
+ ":TRIG:A:PATT:SOUR \"%s\"",
+ [SCPI_CMD_GET_HIGH_RESOLUTION] = ":ACQ:HRES?",
+ [SCPI_CMD_SET_HIGH_RESOLUTION] = ":ACQ:HRES %s",
+ [SCPI_CMD_GET_PEAK_DETECTION] = ":ACQ:PEAK?",
+ [SCPI_CMD_SET_PEAK_DETECTION] = ":ACQ:PEAK %s",
+ [SCPI_CMD_GET_DIG_CHAN_STATE] = ":LOG%d:STAT?",
+ [SCPI_CMD_SET_DIG_CHAN_STATE] = ":LOG%d:STAT %d",
+ [SCPI_CMD_GET_VERTICAL_OFFSET] = ":CHAN%d:POS?",
+ [SCPI_CMD_GET_HORIZ_TRIGGERPOS] = ":TIM:POS?",
+ [SCPI_CMD_SET_HORIZ_TRIGGERPOS] = ":TIM:POS %s",
+ [SCPI_CMD_GET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT?",
+ [SCPI_CMD_SET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT %d",
+ [SCPI_CMD_GET_PROBE_UNIT] = ":PROB%d:SET:ATT:UNIT?",
+ [SCPI_CMD_GET_DIG_POD_THRESHOLD] = ":POD%d:THR?",
+ [SCPI_CMD_SET_DIG_POD_THRESHOLD] = ":POD%d:THR %s",
+ [SCPI_CMD_GET_DIG_POD_USER_THRESHOLD] = ":POD%d:THR:UDL%d?",
+ [SCPI_CMD_SET_DIG_POD_USER_THRESHOLD] = ":POD%d:THR:UDL%d %s",
+};
+
+static const char *rohde_schwarz_log_not_pod_scpi_dialect[] = {
+ [SCPI_CMD_GET_DIG_DATA] = ":FORM UINT,8;:LOG%d:DATA?",
+ [SCPI_CMD_GET_TIMEBASE] = ":TIM:SCAL?",
+ [SCPI_CMD_SET_TIMEBASE] = ":TIM:SCAL %s",
+ [SCPI_CMD_GET_HORIZONTAL_DIV] = ":TIM:DIV?",
+ [SCPI_CMD_GET_COUPLING] = ":CHAN%d:COUP?",
+ [SCPI_CMD_SET_COUPLING] = ":CHAN%d:COUP %s",
+ [SCPI_CMD_GET_SAMPLE_RATE] = ":ACQ:SRAT?",
+ [SCPI_CMD_GET_ANALOG_DATA] = ":FORM:BORD %s;" \
+ ":FORM REAL,32;:CHAN%d:DATA?",
+ [SCPI_CMD_GET_VERTICAL_SCALE] = ":CHAN%d:SCAL?",
+ [SCPI_CMD_SET_VERTICAL_SCALE] = ":CHAN%d:SCAL %s",
+ [SCPI_CMD_GET_DIG_POD_STATE] = ":LOG%d:STAT?",
+ [SCPI_CMD_SET_DIG_POD_STATE] = ":LOG%d:STAT %d",
+ [SCPI_CMD_GET_TRIGGER_SOURCE] = ":TRIG:A:SOUR?",
+ [SCPI_CMD_SET_TRIGGER_SOURCE] = ":TRIG:A:SOUR %s",
+ [SCPI_CMD_GET_TRIGGER_SLOPE] = ":TRIG:A:EDGE:SLOP?",
+ [SCPI_CMD_SET_TRIGGER_SLOPE] = ":TRIG:A:TYPE EDGE;:TRIG:A:EDGE:SLOP %s",
+ [SCPI_CMD_GET_TRIGGER_PATTERN] = ":TRIG:A:PATT:SOUR?",
+ [SCPI_CMD_SET_TRIGGER_PATTERN] = ":TRIG:A:TYPE LOGIC;" \
+ ":TRIG:A:PATT:FUNC AND;" \
+ ":TRIG:A:PATT:COND \"TRUE\";" \
+ ":TRIG:A:PATT:MODE OFF;" \
+ ":TRIG:A:PATT:SOUR \"%s\"",
+ [SCPI_CMD_GET_HIGH_RESOLUTION] = ":ACQ:HRES?",
+ [SCPI_CMD_SET_HIGH_RESOLUTION] = ":ACQ:HRES %s",
+ [SCPI_CMD_GET_PEAK_DETECTION] = ":ACQ:PEAK?",
+ [SCPI_CMD_SET_PEAK_DETECTION] = ":ACQ:PEAK %s",
+ [SCPI_CMD_GET_DIG_CHAN_STATE] = ":LOG%d:STAT?",
+ [SCPI_CMD_SET_DIG_CHAN_STATE] = ":LOG%d:STAT %d",
+ [SCPI_CMD_GET_VERTICAL_OFFSET] = ":CHAN%d:POS?", /* Might not be supported on RTB200x... */
+ [SCPI_CMD_GET_HORIZ_TRIGGERPOS] = ":TIM:POS?",
+ [SCPI_CMD_SET_HORIZ_TRIGGERPOS] = ":TIM:POS %s",
+ [SCPI_CMD_GET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT?",
+ [SCPI_CMD_SET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT %d",
+ [SCPI_CMD_GET_PROBE_UNIT] = ":PROB%d:SET:ATT:UNIT?",
+ [SCPI_CMD_GET_DIG_POD_THRESHOLD] = ":DIG%d:TECH?",
+ [SCPI_CMD_SET_DIG_POD_THRESHOLD] = ":DIG%d:TECH %s",
+ [SCPI_CMD_GET_DIG_POD_USER_THRESHOLD] = ":DIG%d:THR?",
+ [SCPI_CMD_SET_DIG_POD_USER_THRESHOLD] = ":DIG%d:THR %s",
};
static const uint32_t devopts[] = {
SR_CONF_OSCILLOSCOPE,
- SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_SET,
+ SR_CONF_LIMIT_FRAMES | SR_CONF_SET,
SR_CONF_SAMPLERATE | SR_CONF_GET,
SR_CONF_TIMEBASE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_NUM_HDIV | SR_CONF_GET,
SR_CONF_HORIZ_TRIGGERPOS | SR_CONF_GET | SR_CONF_SET,
SR_CONF_TRIGGER_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_TRIGGER_SLOPE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_TRIGGER_PATTERN | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_HIGH_RESOLUTION | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_PEAK_DETECTION | SR_CONF_GET | SR_CONF_SET,
};
static const uint32_t devopts_cg_analog[] = {
SR_CONF_COUPLING | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};
+static const uint32_t devopts_cg_digital[] = {
+ SR_CONF_LOGIC_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_LOGIC_THRESHOLD_CUSTOM | SR_CONF_GET | SR_CONF_SET,
+};
+
static const char *coupling_options[] = {
"AC", // AC with 50 Ohm termination (152x, 202x, 30xx, 1202)
"ACL", // AC with 1 MOhm termination
"GND",
};
+static const char *coupling_options_rtb200x[] = {
+ "ACL", // AC with 1 MOhm termination
+ "DCL", // DC with 1 MOhm termination
+ "GND",
+};
+
+static const char *coupling_options_rtm300x[] = {
+ "ACL", // AC with 1 MOhm termination
+ "DC", // DC with 50 Ohm termination
+ "DCL", // DC with 1 MOhm termination
+ "GND",
+};
+
static const char *scope_trigger_slopes[] = {
"POS",
"NEG",
"EITH",
};
-static const char *compact2_trigger_sources[] = {
+/* Predefined logic thresholds. */
+static const char *logic_threshold[] = {
+ "TTL",
+ "ECL",
+ "CMOS",
+ "USER1",
+ "USER2", // overwritten by logic_threshold_custom, use USER1 for permanent setting
+};
+
+static const char *logic_threshold_rtb200x_rtm300x[] = {
+ "TTL",
+ "ECL",
+ "CMOS",
+ "MAN", // overwritten by logic_threshold_custom
+};
+
+/* This might need updates whenever logic_threshold* above change. */
+#define MAX_NUM_LOGIC_THRESHOLD_ENTRIES ARRAY_SIZE(logic_threshold)
+
+/* RTC1002, HMO Compact2 and HMO1002/HMO1202 */
+static const char *an2_dig8_trigger_sources[] = {
+ "CH1", "CH2",
+ "LINE", "EXT", "PATT", "BUS1", "BUS2",
+ "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+};
+
+/* HMO3xx2 */
+static const char *an2_dig16_trigger_sources[] = {
"CH1", "CH2",
"LINE", "EXT", "PATT", "BUS1", "BUS2",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+ "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
};
-static const char *compact4_trigger_sources[] = {
+/* RTB2002 and RTM3002 */
+static const char *an2_dig16_sbus_trigger_sources[] = {
+ "CH1", "CH2",
+ "LINE", "EXT", "PATT", "SBUS1", "SBUS2",
+ "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+ "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
+};
+
+/* HMO Compact4 */
+static const char *an4_dig8_trigger_sources[] = {
"CH1", "CH2", "CH3", "CH4",
"LINE", "EXT", "PATT", "BUS1", "BUS2",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
};
-static const char *compact4_dig16_trigger_sources[] = {
+/* HMO3xx4 and HMO2524 */
+static const char *an4_dig16_trigger_sources[] = {
"CH1", "CH2", "CH3", "CH4",
"LINE", "EXT", "PATT", "BUS1", "BUS2",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
"D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
};
+/* RTB2004, RTM3004 and RTA4004 */
+static const char *an4_dig16_sbus_trigger_sources[] = {
+ "CH1", "CH2", "CH3", "CH4",
+ "LINE", "EXT", "PATT", "SBUS1", "SBUS2",
+ "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+ "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
+};
+
static const uint64_t timebases[][2] = {
+ /* nanoseconds */
+ { 1, 1000000000 },
+ { 2, 1000000000 },
+ { 5, 1000000000 },
+ { 10, 1000000000 },
+ { 20, 1000000000 },
+ { 50, 1000000000 },
+ { 100, 1000000000 },
+ { 200, 1000000000 },
+ { 500, 1000000000 },
+ /* microseconds */
+ { 1, 1000000 },
+ { 2, 1000000 },
+ { 5, 1000000 },
+ { 10, 1000000 },
+ { 20, 1000000 },
+ { 50, 1000000 },
+ { 100, 1000000 },
+ { 200, 1000000 },
+ { 500, 1000000 },
+ /* milliseconds */
+ { 1, 1000 },
+ { 2, 1000 },
+ { 5, 1000 },
+ { 10, 1000 },
+ { 20, 1000 },
+ { 50, 1000 },
+ { 100, 1000 },
+ { 200, 1000 },
+ { 500, 1000 },
+ /* seconds */
+ { 1, 1 },
+ { 2, 1 },
+ { 5, 1 },
+ { 10, 1 },
+ { 20, 1 },
+ { 50, 1 },
+};
+
+/* HMO Compact series (HMO722/724/1022/1024/1522/1524/2022/2024) do
+ * not support 1 ns timebase setting.
+ */
+static const uint64_t timebases_hmo_compact[][2] = {
/* nanoseconds */
{ 2, 1000000000 },
{ 5, 1000000000 },
{ 2, 1 },
{ 5, 1 },
{ 10, 1 },
- { 20, 1 },
- { 50, 1 },
};
static const char *scope_analog_channel_names[] = {
"D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
};
-static const struct scope_config scope_models[] = {
+static struct scope_config scope_models[] = {
+ {
+ /* HMO Compact2: HMO722/1022/1522/2022 support only 8 digital channels. */
+ .name = {"HMO722", "HMO1022", "HMO1522", "HMO2022", NULL},
+ .analog_channels = 2,
+ .digital_channels = 8,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options,
+ .num_coupling_options = ARRAY_SIZE(coupling_options),
+
+ .logic_threshold = &logic_threshold,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+ .logic_threshold_for_pod = TRUE,
+
+ .trigger_sources = &an2_dig8_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an2_dig8_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases_hmo_compact,
+ .num_timebases = ARRAY_SIZE(timebases_hmo_compact),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &hameg_scpi_dialect,
+ },
{
- /* HMO2522/3032/3042/3052 support 16 digital channels but they're not supported yet. */
- .name = {"HMO1002", "HMO722", "HMO1022", "HMO1522", "HMO2022", "HMO2522",
- "HMO3032", "HMO3042", "HMO3052", NULL},
+ /* RTC1002 and HMO1002/HMO1202 support only 8 digital channels. */
+ .name = {"RTC1002", "HMO1002", "HMO1202", NULL},
.analog_channels = 2,
.digital_channels = 8,
- .digital_pods = 1,
.analog_names = &scope_analog_channel_names,
.digital_names = &scope_digital_channel_names,
.devopts_cg_analog = &devopts_cg_analog,
.num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
.coupling_options = &coupling_options,
.num_coupling_options = ARRAY_SIZE(coupling_options),
- .trigger_sources = &compact2_trigger_sources,
- .num_trigger_sources = ARRAY_SIZE(compact2_trigger_sources),
+ .logic_threshold = &logic_threshold,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+ .logic_threshold_for_pod = TRUE,
+
+ .trigger_sources = &an2_dig8_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an2_dig8_trigger_sources),
.trigger_slopes = &scope_trigger_slopes,
.num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
.vdivs = &vdivs,
.num_vdivs = ARRAY_SIZE(vdivs),
- .num_xdivs = 12,
.num_ydivs = 8,
.scpi_dialect = &hameg_scpi_dialect,
},
{
+ /* HMO3032/3042/3052/3522 support 16 digital channels. */
+ .name = {"HMO3032", "HMO3042", "HMO3052", "HMO3522", NULL},
+ .analog_channels = 2,
+ .digital_channels = 16,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options,
+ .num_coupling_options = ARRAY_SIZE(coupling_options),
+
+ .logic_threshold = &logic_threshold,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+ .logic_threshold_for_pod = TRUE,
+
+ .trigger_sources = &an2_dig16_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an2_dig16_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases,
+ .num_timebases = ARRAY_SIZE(timebases),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &hameg_scpi_dialect,
+ },
+ {
+ /* HMO Compact4: HMO724/1024/1524/2024 support only 8 digital channels. */
.name = {"HMO724", "HMO1024", "HMO1524", "HMO2024", NULL},
.analog_channels = 4,
.digital_channels = 8,
- .digital_pods = 1,
.analog_names = &scope_analog_channel_names,
.digital_names = &scope_digital_channel_names,
.devopts_cg_analog = &devopts_cg_analog,
.num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
.coupling_options = &coupling_options,
.num_coupling_options = ARRAY_SIZE(coupling_options),
- .trigger_sources = &compact4_trigger_sources,
- .num_trigger_sources = ARRAY_SIZE(compact4_trigger_sources),
+ .logic_threshold = &logic_threshold,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+ .logic_threshold_for_pod = TRUE,
+
+ .trigger_sources = &an4_dig8_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an4_dig8_trigger_sources),
.trigger_slopes = &scope_trigger_slopes,
.num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
- .timebases = &timebases,
- .num_timebases = ARRAY_SIZE(timebases),
+ .timebases = &timebases_hmo_compact,
+ .num_timebases = ARRAY_SIZE(timebases_hmo_compact),
.vdivs = &vdivs,
.num_vdivs = ARRAY_SIZE(vdivs),
- .num_xdivs = 12,
.num_ydivs = 8,
.scpi_dialect = &hameg_scpi_dialect,
.name = {"HMO2524", "HMO3034", "HMO3044", "HMO3054", "HMO3524", NULL},
.analog_channels = 4,
.digital_channels = 16,
- .digital_pods = 2,
.analog_names = &scope_analog_channel_names,
.digital_names = &scope_digital_channel_names,
.devopts_cg_analog = &devopts_cg_analog,
.num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
.coupling_options = &coupling_options,
.num_coupling_options = ARRAY_SIZE(coupling_options),
- .trigger_sources = &compact4_dig16_trigger_sources,
- .num_trigger_sources = ARRAY_SIZE(compact4_dig16_trigger_sources),
+ .logic_threshold = &logic_threshold,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold),
+ .logic_threshold_for_pod = TRUE,
+
+ .trigger_sources = &an4_dig16_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an4_dig16_trigger_sources),
.trigger_slopes = &scope_trigger_slopes,
.num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
.vdivs = &vdivs,
.num_vdivs = ARRAY_SIZE(vdivs),
- .num_xdivs = 12,
.num_ydivs = 8,
.scpi_dialect = &hameg_scpi_dialect,
},
+ {
+ .name = {"RTB2002", NULL},
+ .analog_channels = 2,
+ .digital_channels = 16,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options_rtb200x,
+ .num_coupling_options = ARRAY_SIZE(coupling_options_rtb200x),
+
+ .logic_threshold = &logic_threshold_rtb200x_rtm300x,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold_rtb200x_rtm300x),
+ .logic_threshold_for_pod = FALSE,
+
+ .trigger_sources = &an2_dig16_sbus_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an2_dig16_sbus_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases,
+ .num_timebases = ARRAY_SIZE(timebases),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+ },
+ {
+ .name = {"RTB2004", NULL},
+ .analog_channels = 4,
+ .digital_channels = 16,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options_rtb200x,
+ .num_coupling_options = ARRAY_SIZE(coupling_options_rtb200x),
+
+ .logic_threshold = &logic_threshold_rtb200x_rtm300x,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold_rtb200x_rtm300x),
+ .logic_threshold_for_pod = FALSE,
+
+ .trigger_sources = &an4_dig16_sbus_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an4_dig16_sbus_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases,
+ .num_timebases = ARRAY_SIZE(timebases),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+ },
+ {
+ .name = {"RTM3002", NULL},
+ .analog_channels = 2,
+ .digital_channels = 16,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options_rtm300x,
+ .num_coupling_options = ARRAY_SIZE(coupling_options_rtm300x),
+
+ .logic_threshold = &logic_threshold_rtb200x_rtm300x,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold_rtb200x_rtm300x),
+ .logic_threshold_for_pod = FALSE,
+
+ .trigger_sources = &an2_dig16_sbus_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an2_dig16_sbus_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases,
+ .num_timebases = ARRAY_SIZE(timebases),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+ },
+ {
+ .name = {"RTM3004", NULL},
+ .analog_channels = 4,
+ .digital_channels = 16,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options_rtm300x,
+ .num_coupling_options = ARRAY_SIZE(coupling_options_rtm300x),
+
+ .logic_threshold = &logic_threshold_rtb200x_rtm300x,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold_rtb200x_rtm300x),
+ .logic_threshold_for_pod = FALSE,
+
+ .trigger_sources = &an4_dig16_sbus_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an4_dig16_sbus_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases,
+ .num_timebases = ARRAY_SIZE(timebases),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+ },
+ {
+ .name = {"RTA4004", NULL},
+ .analog_channels = 4,
+ .digital_channels = 16,
+
+ .analog_names = &scope_analog_channel_names,
+ .digital_names = &scope_digital_channel_names,
+
+ .devopts = &devopts,
+ .num_devopts = ARRAY_SIZE(devopts),
+
+ .devopts_cg_analog = &devopts_cg_analog,
+ .num_devopts_cg_analog = ARRAY_SIZE(devopts_cg_analog),
+
+ .devopts_cg_digital = &devopts_cg_digital,
+ .num_devopts_cg_digital = ARRAY_SIZE(devopts_cg_digital),
+
+ .coupling_options = &coupling_options_rtm300x,
+ .num_coupling_options = ARRAY_SIZE(coupling_options_rtm300x),
+
+ .logic_threshold = &logic_threshold_rtb200x_rtm300x,
+ .num_logic_threshold = ARRAY_SIZE(logic_threshold_rtb200x_rtm300x),
+ .logic_threshold_for_pod = FALSE,
+
+ .trigger_sources = &an4_dig16_sbus_trigger_sources,
+ .num_trigger_sources = ARRAY_SIZE(an4_dig16_sbus_trigger_sources),
+
+ .trigger_slopes = &scope_trigger_slopes,
+ .num_trigger_slopes = ARRAY_SIZE(scope_trigger_slopes),
+
+ .timebases = &timebases,
+ .num_timebases = ARRAY_SIZE(timebases),
+
+ .vdivs = &vdivs,
+ .num_vdivs = ARRAY_SIZE(vdivs),
+
+ .num_ydivs = 8,
+
+ .scpi_dialect = &rohde_schwarz_log_not_pod_scpi_dialect,
+ },
};
static void scope_state_dump(const struct scope_config *config,
}
for (i = 0; i < config->digital_pods; i++) {
- sr_info("State of digital POD %d -> %s", i,
- state->digital_pods[i] ? "On" : "Off");
+ if (!strncmp("USER", (*config->logic_threshold)[state->digital_pods[i].threshold], 4) ||
+ !strcmp("MAN", (*config->logic_threshold)[state->digital_pods[i].threshold]))
+ sr_info("State of digital POD %d -> %s : %E (threshold)", i + 1,
+ state->digital_pods[i].state ? "On" : "Off",
+ state->digital_pods[i].user_threshold);
+ else
+ sr_info("State of digital POD %d -> %s : %s (threshold)", i + 1,
+ state->digital_pods[i].state ? "On" : "Off",
+ (*config->logic_threshold)[state->digital_pods[i].threshold]);
}
tmp = sr_period_string((*config->timebases)[state->timebase][0],
sr_info("Current samplerate: %s", tmp);
g_free(tmp);
- sr_info("Current trigger: %s (source), %s (slope) %.2f (offset)",
- (*config->trigger_sources)[state->trigger_source],
- (*config->trigger_slopes)[state->trigger_slope],
- state->horiz_triggerpos);
+ if (!strcmp("PATT", (*config->trigger_sources)[state->trigger_source]))
+ sr_info("Current trigger: %s (pattern), %.2f (offset)",
+ state->trigger_pattern,
+ state->horiz_triggerpos);
+ else // Edge (slope) trigger
+ sr_info("Current trigger: %s (source), %s (slope) %.2f (offset)",
+ (*config->trigger_sources)[state->trigger_source],
+ (*config->trigger_slopes)[state->trigger_slope],
+ state->horiz_triggerpos);
}
static int scope_state_get_array_option(struct sr_scpi_dev_inst *scpi,
char *tmp;
int idx;
- if (sr_scpi_get_string(scpi, command, &tmp) != SR_OK) {
- g_free(tmp);
+ if (sr_scpi_get_string(scpi, command, &tmp) != SR_OK)
return SR_ERR;
- }
if ((idx = std_str_idx_s(tmp, *array, n)) < 0) {
g_free(tmp);
ch->enabled = state->analog_channels[i].state;
g_snprintf(command, sizeof(command),
- (*config->scpi_dialect)[SCPI_CMD_GET_VERTICAL_DIV],
+ (*config->scpi_dialect)[SCPI_CMD_GET_VERTICAL_SCALE],
i + 1);
if (sr_scpi_get_string(scpi, command, &tmp_str) != SR_OK)
const struct scope_config *config,
struct scope_state *state)
{
- unsigned int i;
+ unsigned int i, idx;
+ int result = SR_ERR;
+ char *logic_threshold_short[MAX_NUM_LOGIC_THRESHOLD_ENTRIES];
char command[MAX_COMMAND_SIZE];
struct sr_channel *ch;
struct sr_scpi_dev_inst *scpi = sdi->conn;
ch->enabled = state->digital_channels[i];
}
+ /* According to the SCPI standard, on models that support multiple
+ * user-defined logic threshold settings the response to the command
+ * SCPI_CMD_GET_DIG_POD_THRESHOLD might return "USER" instead of
+ * "USER1".
+ *
+ * This makes more difficult to validate the response when the logic
+ * threshold is set to "USER1" and therefore we need to prevent device
+ * opening failures in such configuration case...
+ */
+ for (i = 0; i < config->num_logic_threshold; i++) {
+ logic_threshold_short[i] = g_strdup((*config->logic_threshold)[i]);
+ if (!strcmp("USER1", (*config->logic_threshold)[i]))
+ g_strlcpy(logic_threshold_short[i],
+ (*config->logic_threshold)[i], strlen((*config->logic_threshold)[i]));
+ }
+
for (i = 0; i < config->digital_pods; i++) {
g_snprintf(command, sizeof(command),
(*config->scpi_dialect)[SCPI_CMD_GET_DIG_POD_STATE],
i + 1);
if (sr_scpi_get_bool(scpi, command,
- &state->digital_pods[i]) != SR_OK)
- return SR_ERR;
+ &state->digital_pods[i].state) != SR_OK)
+ goto exit;
+
+ /* Check if the threshold command is based on the POD or digital channel index. */
+ if (config->logic_threshold_for_pod)
+ idx = i + 1;
+ else
+ idx = i * DIGITAL_CHANNELS_PER_POD;
+
+ g_snprintf(command, sizeof(command),
+ (*config->scpi_dialect)[SCPI_CMD_GET_DIG_POD_THRESHOLD],
+ idx);
+
+ /* Check for both standard and shortened responses. */
+ if (scope_state_get_array_option(scpi, command, config->logic_threshold,
+ config->num_logic_threshold,
+ &state->digital_pods[i].threshold) != SR_OK)
+ if (scope_state_get_array_option(scpi, command, (const char * (*)[]) &logic_threshold_short,
+ config->num_logic_threshold,
+ &state->digital_pods[i].threshold) != SR_OK)
+ goto exit;
+
+ /* If used-defined or custom threshold is active, get the level. */
+ if (!strcmp("USER1", (*config->logic_threshold)[state->digital_pods[i].threshold]))
+ g_snprintf(command, sizeof(command),
+ (*config->scpi_dialect)[SCPI_CMD_GET_DIG_POD_USER_THRESHOLD],
+ idx, 1); /* USER1 logic threshold setting. */
+ else if (!strcmp("USER2", (*config->logic_threshold)[state->digital_pods[i].threshold]))
+ g_snprintf(command, sizeof(command),
+ (*config->scpi_dialect)[SCPI_CMD_GET_DIG_POD_USER_THRESHOLD],
+ idx, 2); /* USER2 for custom logic_threshold setting. */
+ else if (!strcmp("USER", (*config->logic_threshold)[state->digital_pods[i].threshold]) ||
+ !strcmp("MAN", (*config->logic_threshold)[state->digital_pods[i].threshold]))
+ g_snprintf(command, sizeof(command),
+ (*config->scpi_dialect)[SCPI_CMD_GET_DIG_POD_USER_THRESHOLD],
+ idx); /* USER or MAN for custom logic_threshold setting. */
+ if (!strcmp("USER1", (*config->logic_threshold)[state->digital_pods[i].threshold]) ||
+ !strcmp("USER2", (*config->logic_threshold)[state->digital_pods[i].threshold]) ||
+ !strcmp("USER", (*config->logic_threshold)[state->digital_pods[i].threshold]) ||
+ !strcmp("MAN", (*config->logic_threshold)[state->digital_pods[i].threshold]))
+ if (sr_scpi_get_float(scpi, command,
+ &state->digital_pods[i].user_threshold) != SR_OK)
+ goto exit;
}
- return SR_OK;
+ result = SR_OK;
+
+exit:
+ for (i = 0; i < config->num_logic_threshold; i++)
+ g_free(logic_threshold_short[i]);
+
+ return result;
}
SR_PRIV int hmo_update_sample_rate(const struct sr_dev_inst *sdi)
struct dev_context *devc;
struct scope_state *state;
const struct scope_config *config;
- int tmp;
- unsigned int i;
float tmp_float;
- gboolean channel_found;
- char tmp_str[MAX_COMMAND_SIZE];
- char chan_name[20];
devc = sdi->priv;
config = devc->model_config;
state = devc->model_state;
- channel_found = FALSE;
-
- for (i = 0; i < config->analog_channels; i++) {
- if (!state->analog_channels[i].state)
- continue;
- g_snprintf(chan_name, sizeof(chan_name), "CHAN%d", i + 1);
- g_snprintf(tmp_str, sizeof(tmp_str),
- (*config->scpi_dialect)[SCPI_CMD_GET_SAMPLE_RATE_LIVE],
- chan_name);
- channel_found = TRUE;
- break;
- }
-
- if (!channel_found) {
- for (i = 0; i < config->digital_pods; i++) {
- if (!state->digital_pods[i])
- continue;
- g_snprintf(chan_name, sizeof(chan_name), "POD%d", i);
- g_snprintf(tmp_str, sizeof(tmp_str),
- (*config->scpi_dialect)[SCPI_CMD_GET_SAMPLE_RATE_LIVE],
- chan_name);
- channel_found = TRUE;
- break;
- }
- }
- /* No channel is active, ask the instrument for the sample rate
- * in single shot mode */
- if (!channel_found) {
- if (sr_scpi_get_float(sdi->conn,
- (*config->scpi_dialect)[SCPI_CMD_GET_SAMPLE_RATE],
- &tmp_float) != SR_OK)
- return SR_ERR;
+ if (sr_scpi_get_float(sdi->conn,
+ (*config->scpi_dialect)[SCPI_CMD_GET_SAMPLE_RATE],
+ &tmp_float) != SR_OK)
+ return SR_ERR;
- state->sample_rate = tmp_float;
- } else {
- if (sr_scpi_get_int(sdi->conn, tmp_str, &tmp) != SR_OK)
- return SR_ERR;
- state->sample_rate = tmp / (((float) (*config->timebases)[state->timebase][0] /
- (*config->timebases)[state->timebase][1]) *
- config->num_xdivs);
- }
+ state->sample_rate = tmp_float;
return SR_OK;
}
if (digital_channel_state_get(sdi, config, state) != SR_OK)
return SR_ERR;
- if (sr_scpi_get_float(sdi->conn,
- (*config->scpi_dialect)[SCPI_CMD_GET_TIMEBASE],
- &tmp_float) != SR_OK)
- return SR_ERR;
-
if (sr_scpi_get_string(sdi->conn,
(*config->scpi_dialect)[SCPI_CMD_GET_TIMEBASE],
&tmp_str) != SR_OK)
state->timebase = i;
+ /* Determine the number of horizontal (x) divisions. */
+ if (sr_scpi_get_int(sdi->conn,
+ (*config->scpi_dialect)[SCPI_CMD_GET_HORIZONTAL_DIV],
+ (int *)&config->num_xdivs) != SR_OK)
+ return SR_ERR;
+
if (sr_scpi_get_float(sdi->conn,
(*config->scpi_dialect)[SCPI_CMD_GET_HORIZ_TRIGGERPOS],
&tmp_float) != SR_OK)
&state->trigger_slope) != SR_OK)
return SR_ERR;
+ if (sr_scpi_get_string(sdi->conn,
+ (*config->scpi_dialect)[SCPI_CMD_GET_TRIGGER_PATTERN],
+ &tmp_str) != SR_OK)
+ return SR_ERR;
+ strncpy(state->trigger_pattern,
+ sr_scpi_unquote_string(tmp_str),
+ MAX_ANALOG_CHANNEL_COUNT + MAX_DIGITAL_CHANNEL_COUNT);
+ g_free(tmp_str);
+
+ if (sr_scpi_get_string(sdi->conn,
+ (*config->scpi_dialect)[SCPI_CMD_GET_HIGH_RESOLUTION],
+ &tmp_str) != SR_OK)
+ return SR_ERR;
+ if (!strcmp("OFF", tmp_str))
+ state->high_resolution = FALSE;
+ else
+ state->high_resolution = TRUE;
+ g_free(tmp_str);
+
+ if (sr_scpi_get_string(sdi->conn,
+ (*config->scpi_dialect)[SCPI_CMD_GET_PEAK_DETECTION],
+ &tmp_str) != SR_OK)
+ return SR_ERR;
+ if (!strcmp("OFF", tmp_str))
+ state->peak_detection = FALSE;
+ else
+ state->peak_detection = TRUE;
+ g_free(tmp_str);
+
if (hmo_update_sample_rate(sdi) != SR_OK)
return SR_ERR;
state->digital_channels = g_malloc0_n(
config->digital_channels, sizeof(gboolean));
state->digital_pods = g_malloc0_n(config->digital_pods,
- sizeof(gboolean));
+ sizeof(struct digital_pod_state));
return state;
}
}
if (model_index == -1) {
- sr_dbg("Unsupported HMO device.");
+ sr_dbg("Unsupported device.");
return SR_ERR_NA;
}
+ /* Configure the number of PODs given the number of digital channels. */
+ scope_models[model_index].digital_pods = scope_models[model_index].digital_channels / DIGITAL_CHANNELS_PER_POD;
+
devc->analog_groups = g_malloc0(sizeof(struct sr_channel_group*) *
scope_models[model_index].analog_channels);
devc->digital_groups = g_malloc0(sizeof(struct sr_channel_group*) *
ret = SR_ERR_MALLOC;
break;
}
- devc->digital_groups[i]->name = g_strdup_printf("POD%d", i);
+ devc->digital_groups[i]->name = g_strdup_printf("POD%d", i + 1);
sdi->channel_groups = g_slist_append(sdi->channel_groups,
devc->digital_groups[i]);
}
ch = sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE,
(*scope_models[model_index].digital_names)[i]);
- group = i / 8;
+ group = i / DIGITAL_CHANNELS_PER_POD;
devc->digital_groups[group]->channels = g_slist_append(
devc->digital_groups[group]->channels, ch);
}
devc->model_config = &scope_models[model_index];
+ devc->samples_limit = 0;
devc->frame_limit = 0;
if (!(devc->model_state = scope_state_new(devc->model_config)))
} else {
store = devc->logic_data;
size = store->len / devc->pod_count;
- if (size != pod_data->len)
- return;
if (group >= devc->pod_count)
return;
}
*logic_data = pod_data->data[idx];
logic_data += logic_step;
}
+
+ /* Truncate acquisition if a smaller number of samples has been requested. */
+ if (devc->samples_limit > 0 && devc->logic_data->len > devc->samples_limit * devc->pod_count)
+ devc->logic_data->len = devc->samples_limit * devc->pod_count;
}
/* Submit data for all channels, after the individual groups got collected. */
if (sr_scpi_get_block(sdi->conn, NULL, &data) != SR_OK) {
if (data)
g_byte_array_free(data, TRUE);
-
return TRUE;
}
analog.data = data->data;
analog.num_samples = data->len / sizeof(float);
+ /* Truncate acquisition if a smaller number of samples has been requested. */
+ if (devc->samples_limit > 0 && analog.num_samples > devc->samples_limit)
+ analog.num_samples = devc->samples_limit;
analog.encoding = &encoding;
analog.meaning = &meaning;
analog.spec = &spec;
spec.spec_digits = 2;
packet.payload = &analog;
sr_session_send(sdi, &packet);
+ devc->num_samples = data->len / sizeof(float);
g_slist_free(meaning.channels);
g_byte_array_free(data, TRUE);
data = NULL;
break;
case SR_CHANNEL_LOGIC:
if (sr_scpi_get_block(sdi->conn, NULL, &data) != SR_OK) {
- g_free(data);
+ if (data)
+ g_byte_array_free(data, TRUE);
return TRUE;
}
packet.type = SR_DF_LOGIC;
logic.data = data->data;
logic.length = data->len;
+ /* Truncate acquisition if a smaller number of samples has been requested. */
+ if (devc->samples_limit > 0 && logic.length > devc->samples_limit)
+ logic.length = devc->samples_limit;
logic.unitsize = 1;
packet.payload = &logic;
sr_session_send(sdi, &packet);
} else {
- group = ch->index / 8;
+ group = ch->index / DIGITAL_CHANNELS_PER_POD;
hmo_queue_logic_data(devc, group, data);
}
+ devc->num_samples = data->len / devc->pod_count;
g_byte_array_free(data, TRUE);
data = NULL;
break;
/*
* End of frame was reached. Stop acquisition after the specified
- * number of frames, or continue reception by starting over at
- * the first enabled channel.
+ * number of frames or after the specified number of samples, or
+ * continue reception by starting over at the first enabled channel.
*/
- if (++devc->num_frames == devc->frame_limit) {
+ if (++devc->num_frames >= devc->frame_limit || devc->num_samples >= devc->samples_limit) {
sr_dev_acquisition_stop(sdi);
hmo_cleanup_logic_data(devc);
} else {
#define LOG_PREFIX "hameg-hmo"
-#define MAX_INSTRUMENT_VERSIONS 10
-#define MAX_COMMAND_SIZE 48
-#define MAX_ANALOG_CHANNEL_COUNT 4
-#define MAX_DIGITAL_CHANNEL_COUNT 16
-#define MAX_DIGITAL_GROUP_COUNT 2
+#define DIGITAL_CHANNELS_PER_POD 8
+
+#define MAX_INSTRUMENT_VERSIONS 10
+#define MAX_COMMAND_SIZE 128
+#define MAX_ANALOG_CHANNEL_COUNT 4
+#define MAX_DIGITAL_CHANNEL_COUNT 16
+#define MAX_DIGITAL_GROUP_COUNT 2
struct scope_config {
const char *name[MAX_INSTRUMENT_VERSIONS];
const uint8_t analog_channels;
const uint8_t digital_channels;
- const uint8_t digital_pods;
+ uint8_t digital_pods;
const char *(*analog_names)[];
const char *(*digital_names)[];
const uint32_t (*devopts_cg_analog)[];
const uint8_t num_devopts_cg_analog;
+ const uint32_t (*devopts_cg_digital)[];
+ const uint8_t num_devopts_cg_digital;
+
const char *(*coupling_options)[];
const uint8_t num_coupling_options;
+ const char *(*logic_threshold)[];
+ const uint8_t num_logic_threshold;
+ const gboolean logic_threshold_for_pod;
+
const char *(*trigger_sources)[];
const uint8_t num_trigger_sources;
const uint64_t (*vdivs)[][2];
const uint8_t num_vdivs;
- const uint8_t num_xdivs;
- const uint8_t num_ydivs;
+ unsigned int num_xdivs;
+ const unsigned int num_ydivs;
const char *(*scpi_dialect)[];
};
char probe_unit;
};
+struct digital_pod_state {
+ gboolean state;
+
+ int threshold;
+ float user_threshold;
+};
+
struct scope_state {
struct analog_channel_state *analog_channels;
gboolean *digital_channels;
- gboolean *digital_pods;
+ struct digital_pod_state *digital_pods;
int timebase;
float horiz_triggerpos;
int trigger_source;
int trigger_slope;
+ char trigger_pattern[MAX_ANALOG_CHANNEL_COUNT + MAX_DIGITAL_CHANNEL_COUNT + 1];
+
+ gboolean high_resolution;
+ gboolean peak_detection;
+
uint64_t sample_rate;
};
GSList *enabled_channels;
GSList *current_channel;
+ uint64_t num_samples;
uint64_t num_frames;
+ uint64_t samples_limit;
uint64_t frame_limit;
size_t pod_count;
SR_MHZ(320),
};
-SR_PRIV struct sr_dev_driver hantek_4032l_driver_info;
+static struct sr_dev_driver hantek_4032l_driver_info;
static GSList *scan(struct sr_dev_driver *di, GSList *options)
{
else
cmd_pkt->sample_rate = devc->sample_rate;
- /* Set pwm channel values. */
+ /* Set PWM channel values. */
devc->cmd_pkt.pwm_a = h4032l_voltage2pwm(devc->cur_threshold[0]);
devc->cmd_pkt.pwm_b = h4032l_voltage2pwm(devc->cur_threshold[1]);
channel = channel->next;
}
- /* Compress range mask value and apply range settings. */
- if (range_mask) {
- cmd_pkt->trigger[0].flags.data_range_enabled = 1;
- cmd_pkt->trigger[0].data_range_mask |= (range_mask);
-
- uint32_t new_range_value = 0;
- uint32_t bit_mask = 1;
- while (range_mask) {
- if ((range_mask & 1) != 0) {
- new_range_value <<= 1;
- if ((range_value & 1) != 0)
- new_range_value |= bit_mask;
- bit_mask <<= 1;
- }
- range_mask >>= 1;
- range_value >>= 1;
- }
- cmd_pkt->trigger[0].data_range_max |= range_value;
- }
+ cmd_pkt->trigger[0].flags.data_range_enabled = 1;
+ cmd_pkt->trigger[0].data_range_mask |= range_mask;
+ cmd_pkt->trigger[0].data_range_max = range_value;
}
usb_source_add(sdi->session, drvc->sr_ctx, 1000,
return h4032l_stop(sdi);
}
-SR_PRIV struct sr_dev_driver hantek_4032l_driver_info = {
+static struct sr_dev_driver hantek_4032l_driver_info = {
.name = "hantek-4032l",
.longname = "Hantek 4032L",
.api_version = 1,
sr_session_send(sdi, &trig);
/* Send rest of data. */
- logic.length = (sample_count-trigger_offset) * sizeof(uint32_t);
+ logic.length = (sample_count - trigger_offset) * sizeof(uint32_t);
logic.data = data + trigger_offset;
if (logic.length)
sr_session_send(sdi, &packet);
"Hantek", "6022BL", "fx2lafw-hantek-6022bl.fw",
ARRAY_AND_SIZE(dc_coupling), FALSE,
},
+ {
+ 0xd4a2, 0x5660, 0x1d50, 0x608e, 0x0004,
+ "YiXingDianZi", "MDSO", "fx2lafw-yixingdianzi-mdso.fw",
+ ARRAY_AND_SIZE(dc_coupling), FALSE,
+ },
ALL_ZERO
};
SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
SR_CONF_MEASURED_QUANTITY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_RANGE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_DIGITS | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};
static const struct {
enum sr_mqflag mqflag;
} mqopts[] = {
{SR_MQ_VOLTAGE, SR_MQFLAG_DC},
- {SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_AUTORANGE},
- {SR_MQ_VOLTAGE, SR_MQFLAG_AC | SR_MQFLAG_RMS},
- {SR_MQ_VOLTAGE, SR_MQFLAG_AC | SR_MQFLAG_RMS | SR_MQFLAG_AUTORANGE},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_AC},
{SR_MQ_CURRENT, SR_MQFLAG_DC},
- {SR_MQ_CURRENT, SR_MQFLAG_DC | SR_MQFLAG_AUTORANGE},
- {SR_MQ_CURRENT, SR_MQFLAG_AC | SR_MQFLAG_RMS},
- {SR_MQ_CURRENT, SR_MQFLAG_AC | SR_MQFLAG_RMS | SR_MQFLAG_AUTORANGE},
+ {SR_MQ_CURRENT, SR_MQFLAG_AC},
{SR_MQ_RESISTANCE, 0},
- {SR_MQ_RESISTANCE, 0 | SR_MQFLAG_AUTORANGE},
{SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE},
- {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE | SR_MQFLAG_AUTORANGE},
};
-SR_PRIV struct sr_dev_driver hp_3478a_driver_info;
+static const struct {
+ enum sr_mq mq;
+ enum sr_mqflag mqflag;
+ int range_exp;
+ const char *range_str;
+} rangeopts[] = {
+ /* -99 is a dummy exponent for auto ranging. */
+ {SR_MQ_VOLTAGE, SR_MQFLAG_DC, -99, "Auto"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_DC, -2, "30mV"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_DC, -1, "300mV"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_DC, 0, "3V"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_DC, 1, "30V"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_DC, 2, "300V"},
+ /* -99 is a dummy exponent for auto ranging. */
+ {SR_MQ_VOLTAGE, SR_MQFLAG_AC, -99, "Auto"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_AC, -1, "300mV"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_AC, 0, "3V"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_AC, 1, "30V"},
+ {SR_MQ_VOLTAGE, SR_MQFLAG_AC, 2, "300V"},
+ /* -99 is a dummy exponent for auto ranging. */
+ {SR_MQ_CURRENT, SR_MQFLAG_DC, -99, "Auto"},
+ {SR_MQ_CURRENT, SR_MQFLAG_DC, -1, "300mV"},
+ {SR_MQ_CURRENT, SR_MQFLAG_DC, 0, "3V"},
+ /* -99 is a dummy exponent for auto ranging. */
+ {SR_MQ_CURRENT, SR_MQFLAG_AC, -99, "Auto"},
+ {SR_MQ_CURRENT, SR_MQFLAG_AC, -1, "300mV"},
+ {SR_MQ_CURRENT, SR_MQFLAG_AC, 0, "3V"},
+ /* -99 is a dummy exponent for auto ranging. */
+ {SR_MQ_RESISTANCE, 0, -99, "Auto"},
+ {SR_MQ_RESISTANCE, 0, 1, "30"},
+ {SR_MQ_RESISTANCE, 0, 2, "300"},
+ {SR_MQ_RESISTANCE, 0, 3, "3k"},
+ {SR_MQ_RESISTANCE, 0, 4, "30k"},
+ {SR_MQ_RESISTANCE, 0, 5, "300k"},
+ {SR_MQ_RESISTANCE, 0, 6, "3M"},
+ {SR_MQ_RESISTANCE, 0, 7, "30M"},
+ /* -99 is a dummy exponent for auto ranging. */
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, -99, "Auto"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 1, "30R"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 2, "300R"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 3, "3kR"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 4, "30kR"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 5, "300kR"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 6, "3MR"},
+ {SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, 7, "30MR"},
+};
+
+/** Available digits as strings. */
+static const char *digits[] = {
+ "3.5", "4.5", "5.5",
+};
+
+/** Mapping between devc->spec_digits and digits string. */
+static const char *digits_map[] = {
+ "", "", "", "", "3.5", "4.5", "5.5",
+};
+
+static struct sr_dev_driver hp_3478a_driver_info;
static int create_front_channel(struct sr_dev_inst *sdi, int chan_idx)
{
struct dev_context *devc;
int ret;
GVariant *arr[2];
+ unsigned int i;
+ const char *range_str;
(void)cg;
arr[1] = g_variant_new_uint64(devc->measurement_mq_flags);
*data = g_variant_new_tuple(arr, 2);
break;
+ case SR_CONF_RANGE:
+ ret = hp_3478a_get_status_bytes(sdi);
+ if (ret != SR_OK)
+ return ret;
+ range_str = "Auto";
+ for (i = 0; i < ARRAY_SIZE(rangeopts); i++) {
+ if (rangeopts[i].mq == devc->measurement_mq &&
+ rangeopts[i].mqflag == devc->measurement_mq_flags &&
+ rangeopts[i].range_exp == devc->range_exp) {
+ range_str = rangeopts[i].range_str;
+ break;
+ }
+ }
+ *data = g_variant_new_string(range_str);
+ break;
+ case SR_CONF_DIGITS:
+ ret = hp_3478a_get_status_bytes(sdi);
+ if (ret != SR_OK)
+ return ret;
+ *data = g_variant_new_string(digits_map[devc->spec_digits]);
+ break;
default:
return SR_ERR_NA;
}
enum sr_mq mq;
enum sr_mqflag mq_flags;
GVariant *tuple_child;
+ unsigned int i;
+ const char *range_str;
+ const char *digits_str;
(void)cg;
mq_flags = g_variant_get_uint64(tuple_child);
g_variant_unref(tuple_child);
return hp_3478a_set_mq(sdi, mq, mq_flags);
+ case SR_CONF_RANGE:
+ range_str = g_variant_get_string(data, NULL);
+ for (i = 0; i < ARRAY_SIZE(rangeopts); i++) {
+ if (rangeopts[i].mq == devc->measurement_mq &&
+ rangeopts[i].mqflag == devc->measurement_mq_flags &&
+ g_strcmp0(rangeopts[i].range_str, range_str) == 0) {
+ return hp_3478a_set_range(sdi, rangeopts[i].range_exp);
+ }
+ }
+ return SR_ERR_NA;
+ case SR_CONF_DIGITS:
+ digits_str = g_variant_get_string(data, NULL);
+ for (i = 0; i < ARRAY_SIZE(rangeopts); i++) {
+ if (g_strcmp0(digits_map[i], digits_str) == 0)
+ return hp_3478a_set_digits(sdi, i);
+ }
+ return SR_ERR_NA;
default:
return SR_ERR_NA;
}
static int config_list(uint32_t key, GVariant **data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
+ struct dev_context *devc;
+ int ret;
unsigned int i;
GVariant *gvar, *arr[2];
GVariantBuilder gvb;
+ devc = sdi->priv;
+
switch (key) {
case SR_CONF_SCAN_OPTIONS:
case SR_CONF_DEVICE_OPTIONS:
}
*data = g_variant_builder_end(&gvb);
break;
+ case SR_CONF_RANGE:
+ ret = hp_3478a_get_status_bytes(sdi);
+ if (ret != SR_OK)
+ return ret;
+ g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+ for (i = 0; i < ARRAY_SIZE(rangeopts); i++) {
+ if (rangeopts[i].mq == devc->measurement_mq &&
+ rangeopts[i].mqflag == devc->measurement_mq_flags) {
+ g_variant_builder_add(&gvb, "s", rangeopts[i].range_str);
+ }
+ }
+ *data = g_variant_builder_end(&gvb);
+ break;
+ case SR_CONF_DIGITS:
+ *data = g_variant_new_strv(ARRAY_AND_SIZE(digits));
+ break;
default:
return SR_ERR_NA;
}
* NOTE: For faster readings, there are some things one can do:
* - Turn off the display: sr_scpi_send(scpi, "D3SIGROK").
* - Set the line frequency to 60Hz via switch (back of the unit).
- * - Set to 3.5 digits measurement (add config key SR_CONF_DIGITS).
+ * - Set to 3.5 digits measurement.
*/
/* Set to internal trigger. */
return SR_OK;
}
-SR_PRIV struct sr_dev_driver hp_3478a_driver_info = {
+static struct sr_dev_driver hp_3478a_driver_info = {
.name = "hp-3478a",
.longname = "HP 3478A",
.api_version = 1,
.dev_acquisition_stop = dev_acquisition_stop,
.context = NULL,
};
-
SR_REGISTER_DEV_DRIVER(hp_3478a_driver_info);
return SR_ERR_NA;
}
+SR_PRIV int hp_3478a_set_range(const struct sr_dev_inst *sdi, int range_exp)
+{
+ int ret;
+ struct sr_scpi_dev_inst *scpi = sdi->conn;
+ struct dev_context *devc = sdi->priv;
+
+ /* No need to send command if we're not changing the range. */
+ if (devc->range_exp == range_exp)
+ return SR_OK;
+
+ /* -99 is a dummy exponent for auto ranging. */
+ if (range_exp == -99)
+ ret = sr_scpi_send(scpi, "RA");
+ else
+ ret = sr_scpi_send(scpi, "R%i", range_exp);
+ if (ret != SR_OK)
+ return ret;
+
+ return hp_3478a_get_status_bytes(sdi);
+}
+
+SR_PRIV int hp_3478a_set_digits(const struct sr_dev_inst *sdi, uint8_t digits)
+{
+ int ret;
+ struct sr_scpi_dev_inst *scpi = sdi->conn;
+ struct dev_context *devc = sdi->priv;
+
+ /* No need to send command if we're not changing the range. */
+ if (devc->spec_digits == digits)
+ return SR_OK;
+
+ /* digits are based on devc->spec_digits, so we have to substract 1 */
+ ret = sr_scpi_send(scpi, "N%i", digits-1);
+ if (ret != SR_OK)
+ return ret;
+
+ return hp_3478a_get_status_bytes(sdi);
+}
+
static int parse_range_vdc(struct dev_context *devc, uint8_t range_byte)
{
- if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30MV)
+ if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30MV) {
+ devc->range_exp = -2;
devc->enc_digits = devc->spec_digits - 2;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300MV)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300MV) {
+ devc->range_exp = -1;
devc->enc_digits = devc->spec_digits - 3;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_3V)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_3V) {
+ devc->range_exp = 0;
devc->enc_digits = devc->spec_digits - 1;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30V)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_30V) {
+ devc->range_exp = 1;
devc->enc_digits = devc->spec_digits - 2;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300V)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VDC_300V) {
+ devc->range_exp = 2;
devc->enc_digits = devc->spec_digits - 3;
- else
+ } else
return SR_ERR_DATA;
return SR_OK;
static int parse_range_vac(struct dev_context *devc, uint8_t range_byte)
{
- if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300MV)
+ if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300MV) {
+ devc->range_exp = -1;
devc->enc_digits = devc->spec_digits - 3;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_3V)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_3V) {
+ devc->range_exp = 0;
devc->enc_digits = devc->spec_digits - 1;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_30V)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_30V) {
+ devc->range_exp = 1;
devc->enc_digits = devc->spec_digits - 2;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300V)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_VAC_300V) {
+ devc->range_exp = 2;
devc->enc_digits = devc->spec_digits - 3;
- else
+ } else
return SR_ERR_DATA;
return SR_OK;
static int parse_range_a(struct dev_context *devc, uint8_t range_byte)
{
- if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_300MA)
+ if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_300MA) {
+ devc->range_exp = -1;
devc->enc_digits = devc->spec_digits - 3;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_3A)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_A_3A) {
+ devc->range_exp = 0;
devc->enc_digits = devc->spec_digits - 1;
- else
+ } else
return SR_ERR_DATA;
return SR_OK;
static int parse_range_ohm(struct dev_context *devc, uint8_t range_byte)
{
- if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30R)
+ if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30R) {
+ devc->range_exp = 1;
devc->enc_digits = devc->spec_digits - 2;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300R)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300R) {
+ devc->range_exp = 2;
devc->enc_digits = devc->spec_digits - 3;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3KR)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3KR) {
+ devc->range_exp = 3;
devc->enc_digits = devc->spec_digits - 1;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30KR)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30KR) {
+ devc->range_exp = 4;
devc->enc_digits = devc->spec_digits - 2;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300KR)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_300KR) {
+ devc->range_exp = 5;
devc->enc_digits = devc->spec_digits - 3;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3MR)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_3MR) {
+ devc->range_exp = 6;
devc->enc_digits = devc->spec_digits - 1;
- else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30MR)
+ } else if ((range_byte & SB1_RANGE_BLOCK) == RANGE_OHM_30MR) {
+ devc->range_exp = 7;
devc->enc_digits = devc->spec_digits - 2;
- else
+ } else
return SR_ERR_DATA;
return SR_OK;
/* Function + Range */
devc->measurement_mq_flags = 0;
+ devc->acquisition_mq_flags = 0;
if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_VDC) {
devc->measurement_mq = SR_MQ_VOLTAGE;
devc->measurement_mq_flags |= SR_MQFLAG_DC;
+ devc->acquisition_mq_flags |= SR_MQFLAG_DC;
devc->measurement_unit = SR_UNIT_VOLT;
parse_range_vdc(devc, function_byte);
} else if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_VAC) {
devc->measurement_mq = SR_MQ_VOLTAGE;
- devc->measurement_mq_flags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+ devc->measurement_mq_flags |= SR_MQFLAG_AC;
+ devc->acquisition_mq_flags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
devc->measurement_unit = SR_UNIT_VOLT;
parse_range_vac(devc, function_byte);
} else if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_2WR) {
} else if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_4WR) {
devc->measurement_mq = SR_MQ_RESISTANCE;
devc->measurement_mq_flags |= SR_MQFLAG_FOUR_WIRE;
+ devc->acquisition_mq_flags |= SR_MQFLAG_FOUR_WIRE;
devc->measurement_unit = SR_UNIT_OHM;
parse_range_ohm(devc, function_byte);
} else if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_ADC) {
devc->measurement_mq = SR_MQ_CURRENT;
devc->measurement_mq_flags |= SR_MQFLAG_DC;
+ devc->acquisition_mq_flags |= SR_MQFLAG_DC;
devc->measurement_unit = SR_UNIT_AMPERE;
parse_range_a(devc, function_byte);
} else if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_AAC) {
devc->measurement_mq = SR_MQ_CURRENT;
- devc->measurement_mq_flags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+ devc->measurement_mq_flags |= SR_MQFLAG_AC;
+ devc->acquisition_mq_flags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
devc->measurement_unit = SR_UNIT_AMPERE;
parse_range_a(devc, function_byte);
} else if ((function_byte & SB1_FUNCTION_BLOCK) == FUNCTION_EXR) {
devc->auto_zero = FALSE;
/* Auto-Range */
- if ((status_byte & STATUS_AUTO_RANGE) == STATUS_AUTO_RANGE)
- devc->measurement_mq_flags |= SR_MQFLAG_AUTORANGE;
- else
- devc->measurement_mq_flags &= ~SR_MQFLAG_AUTORANGE;
+ if ((status_byte & STATUS_AUTO_RANGE) == STATUS_AUTO_RANGE) {
+ devc->acquisition_mq_flags |= SR_MQFLAG_AUTORANGE;
+ devc->range_exp = -99;
+ } else
+ devc->acquisition_mq_flags &= ~SR_MQFLAG_AUTORANGE;
/* Internal trigger */
if ((status_byte & STATUS_INT_TRIGGER) == STATUS_INT_TRIGGER)
encoding.digits = devc->enc_digits;
meaning.mq = devc->measurement_mq;
- meaning.mqflags = devc->measurement_mq_flags;
+ meaning.mqflags = devc->acquisition_mq_flags;
meaning.unit = devc->measurement_unit;
meaning.channels = sdi->channels;
struct sr_scpi_dev_inst *scpi;
struct sr_dev_inst *sdi;
struct dev_context *devc;
+ char status_register;
(void)fd;
(void)revents;
scpi = sdi->conn;
/*
- * This is necessary to get the actual range for the encoding digits.
- * When SPoll is implemmented, this can be done via SPoll.
+ * TODO: Wait for SRQ from the DMM when a new measurement is available.
+ * For now, we don't wait for a SRQ, but just do a SPoll and
+ * check the Data Ready bit (0x01).
+ * This is necessary, because (1) reading a value will block the
+ * bus until a measurement is available and (2) when switching
+ * ranges, there could be a timeout.
*/
- if (hp_3478a_get_status_bytes(sdi) != SR_OK)
+ if (sr_scpi_gpib_spoll(scpi, &status_register) != SR_OK)
+ return FALSE;
+ if (!(((uint8_t)status_register) & 0x01))
+ return TRUE;
+
+ /* Get a reading from the DMM. */
+ if (sr_scpi_get_double(scpi, NULL, &devc->measurement) != SR_OK)
return FALSE;
+ /* Check for overflow. */
+ if (devc->measurement >= 9.998e+9)
+ devc->measurement = INFINITY;
+
/*
- * TODO: Implement GPIB-SPoll, to get notified by a SRQ when a new
- * measurement is available. This is necessary, because when
- * switching ranges, there could be a timeout.
+ * This is necessary to get the actual range for the encoding digits.
+ * Must be called after reading the value, because it resets the
+ * status register!
*/
- if (sr_scpi_get_double(scpi, NULL, &devc->measurement) != SR_OK)
+ if (hp_3478a_get_status_bytes(sdi) != SR_OK)
return FALSE;
acq_send_measurement(sdi);
double measurement;
enum sr_mq measurement_mq;
+ /** The measurement mq flags only contain flags for AC, DC and 4-wire. */
enum sr_mqflag measurement_mq_flags;
+ /** The acquisition mq flags also contain flags for autoranging and RMS. */
+ enum sr_mqflag acquisition_mq_flags;
enum sr_unit measurement_unit;
+ int range_exp;
uint8_t enc_digits;
uint8_t spec_digits;
SR_PRIV int hp_3478a_set_mq(const struct sr_dev_inst *sdi, enum sr_mq mq,
enum sr_mqflag mq_flags);
+SR_PRIV int hp_3478a_set_range(const struct sr_dev_inst *sdi, int range_exp);
+SR_PRIV int hp_3478a_set_digits(const struct sr_dev_inst *sdi, uint8_t digits);
SR_PRIV int hp_3478a_get_status_bytes(const struct sr_dev_inst *sdi);
SR_PRIV int hp_3478a_receive_data(int fd, int revents, void *cb_data);
SR_TRIGGER_EDGE,
};
-SR_PRIV struct sr_dev_driver ipdbg_la_driver_info;
-
static void ipdbg_la_split_addr_port(const char *conn, char **addr,
char **port)
{
devc->capture_ratio = g_variant_get_uint64(data);
break;
case SR_CONF_LIMIT_SAMPLES:
- devc->limit_samples = g_variant_get_uint64(data);
+ {
+ uint64_t limit_samples = g_variant_get_uint64(data);
+ if (limit_samples <= devc->limit_samples_max)
+ devc->limit_samples = limit_samples;
+ }
break;
default:
return SR_ERR_NA;
struct ipdbg_la_tcp *tcp = sdi->conn;
struct dev_context *devc = sdi->priv;
- uint8_t byte;
+ const size_t bufsize = 1024;
+ uint8_t buffer[bufsize];
if (devc->num_transfers > 0) {
while (devc->num_transfers <
(devc->limit_samples_max * devc->data_width_bytes)) {
- ipdbg_la_tcp_receive(tcp, &byte);
- devc->num_transfers++;
+ int recd = ipdbg_la_tcp_receive(tcp, buffer, bufsize);
+ if (recd > 0)
+ devc->num_transfers += recd;
}
}
return SR_OK;
}
-SR_PRIV struct sr_dev_driver ipdbg_la_driver_info = {
+static struct sr_dev_driver ipdbg_la_driver_info = {
.name = "ipdbg-la",
.longname = "IPDBG LA",
.api_version = 1,
.dev_acquisition_stop = dev_acquisition_stop,
.context = NULL,
};
-
SR_REGISTER_DEV_DRIVER(ipdbg_la_driver_info);
{
#ifdef _WIN32
u_long bytes_available;
- ioctlsocket(tcp->socket, FIONREAD, &bytes_available);
- return (bytes_available > 0);
+ if (ioctlsocket(tcp->socket, FIONREAD, &bytes_available) != 0) {
#else
- int status;
-
- if (ioctl(tcp->socket, FIONREAD, &status) < 0) { // TIOCMGET
+ int bytes_available;
+ if (ioctl(tcp->socket, FIONREAD, &bytes_available) < 0) { /* TIOCMGET */
+#endif
sr_err("FIONREAD failed: %s\n", g_strerror(errno));
return FALSE;
}
-
- return (status < 1) ? FALSE : TRUE;
-#endif
+ return (bytes_available > 0);
}
SR_PRIV struct ipdbg_la_tcp *ipdbg_la_tcp_new(void)
{
int ret = SR_OK;
+#ifdef _WIN32
+ if (shutdown(tcp->socket, SD_SEND) != SOCKET_ERROR) {
+ char recvbuf[16];
+ int recvbuflen = 16;
+ /* Receive until the peer closes the connection. */
+ while (recv(tcp->socket, recvbuf, recvbuflen, 0) > 0);
+ }
+#endif
if (close(tcp->socket) < 0)
ret = SR_ERR;
int received = 0;
int error_count = 0;
- /* Timeout after 500ms of not receiving data */
- while ((received < bufsize) && (error_count < 500)) {
- if (ipdbg_la_tcp_receive(tcp, buf) > 0) {
- buf++;
- received++;
+ /* Timeout after 2s of not receiving data. */
+ /* Increase timeout in case lab is not just beside the office. */
+ while ((received < bufsize) && (error_count < 2000)) {
+ int recd = ipdbg_la_tcp_receive(tcp, buf, bufsize - received);
+ if (recd > 0) {
+ buf += recd;
+ received += recd;
} else {
error_count++;
- g_usleep(1000); /* Sleep for 1ms */
+ g_usleep(1000);
}
}
}
SR_PRIV int ipdbg_la_tcp_receive(struct ipdbg_la_tcp *tcp,
- uint8_t *buf)
+ uint8_t *buf, size_t bufsize)
{
int received = 0;
-
- if (data_available(tcp)) {
- while (received < 1) {
- int len = recv(tcp->socket, (char *)buf, 1, 0);
-
- if (len < 0) {
- sr_err("Receive error: %s", g_strerror(errno));
- return SR_ERR;
- } else
- received += len;
- }
-
- return received;
- } else
+ if (data_available(tcp))
+ received = recv(tcp->socket, (char *)buf, bufsize, 0);
+ if (received < 0) {
+ sr_err("Receive error: %s", g_strerror(errno));
return -1;
+ } else
+ return received;
}
SR_PRIV int ipdbg_la_convert_trigger(const struct sr_dev_inst *sdi)
if (devc->num_transfers <
(devc->limit_samples_max * devc->data_width_bytes)) {
- uint8_t byte;
-
- if (ipdbg_la_tcp_receive(tcp, &byte) == 1) {
- if (devc->num_transfers <
- (devc->limit_samples * devc->data_width_bytes))
- devc->raw_sample_buf[devc->num_transfers] = byte;
-
- devc->num_transfers++;
+ const size_t bufsize = 1024;
+ uint8_t buffer[bufsize];
+
+ const int recd = ipdbg_la_tcp_receive(tcp, buffer, bufsize);
+ if ( recd > 0) {
+ int num_move = (((devc->num_transfers + recd) <=
+ (devc->limit_samples * devc->data_width_bytes))
+ ?
+ recd
+ :
+ (int)((devc->limit_samples * devc->data_width_bytes) -
+ devc->num_transfers));
+ if ( num_move > 0 )
+ memcpy(&(devc->raw_sample_buf[devc->num_transfers]),
+ buffer, num_move);
+ devc->num_transfers += recd;
}
} else {
if (devc->delay_value > 0) {
SR_PRIV int ipdbg_la_send_delay(struct dev_context *devc,
struct ipdbg_la_tcp *tcp)
{
- devc->delay_value = (devc->limit_samples / 100.0) * devc->capture_ratio;
+ devc->delay_value = ((devc->limit_samples - 1) / 100.0) * devc->capture_ratio;
uint8_t buf;
buf = CMD_CFG_LA;
SR_PRIV void ipdbg_la_tcp_free(struct ipdbg_la_tcp *tcp);
SR_PRIV int ipdbg_la_tcp_open(struct ipdbg_la_tcp *tcp);
SR_PRIV int ipdbg_la_tcp_close(struct ipdbg_la_tcp *tcp);
-SR_PRIV int ipdbg_la_tcp_receive(struct ipdbg_la_tcp *tcp, uint8_t *buf);
+SR_PRIV int ipdbg_la_tcp_receive(struct ipdbg_la_tcp *tcp,
+ uint8_t *buf, size_t bufsize);
SR_PRIV int ipdbg_la_convert_trigger(const struct sr_dev_inst *sdi);
packet.payload = &meta;
meta.config = g_slist_append(NULL, src);
sr_session_send(sdi, &packet);
- g_free(src);
+ g_slist_free(meta.config);
+ sr_config_free(src);
}
if (!(devc->xfer = libusb_alloc_transfer(0)))
/* Let's get a bit of data and see if we can find a packet. */
len = sizeof(buf);
ret = serial_stream_detect(serial, buf, &len, scale->packet_size,
- scale->packet_valid, 3000, scale->baudrate);
+ scale->packet_valid, 3000);
if (ret != SR_OK)
goto scan_cleanup;
return SR_OK;
}
-#define SCALE(ID, CHIPSET, VENDOR, MODEL, CONN, BAUDRATE, PACKETSIZE, \
+#define SCALE(ID, CHIPSET, VENDOR, MODEL, CONN, PACKETSIZE, \
VALID, PARSE) \
&((struct scale_info) { \
{ \
.dev_acquisition_stop = std_serial_dev_acquisition_stop, \
.context = NULL, \
}, \
- VENDOR, MODEL, CONN, BAUDRATE, PACKETSIZE, \
+ VENDOR, MODEL, CONN, PACKETSIZE, \
VALID, PARSE, sizeof(struct CHIPSET##_info) \
}).di
SR_REGISTER_DEV_DRIVER_LIST(kern_scale_drivers,
SCALE(
"kern-ew-6200-2nm", kern,
- "KERN", "EW 6200-2NM", "1200/8n2", 1200,
+ "KERN", "EW 6200-2NM", "1200/8n2",
15 /* (or 14) */, sr_kern_packet_valid, sr_kern_parse
)
);
const char *device;
/** serialconn string. */
const char *conn;
- /** Baud rate. */
- uint32_t baudrate;
/** Packet size in bytes. */
int packet_size;
/** Packet validation function. */
* This file is part of the libsigrok project.
*
* Copyright (C) 2015 Hannu Vuolasaho <vuokkosetae@gmail.com>
- * Copyright (C) 2018 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2018-2019 Frank Stettner <frank-stettner@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
/* Sometimes the KA3005P has an extra 0x01 after the ID. */
{KORAD_KA3005P_0X01, "Korad", "KA3005P",
"KORADKA3005PV2.0\x01", 1, {0, 31, 0.01}, {0, 5, 0.001}},
+ /* Sometimes the KA3005P has an extra 0xBC after the ID. */
+ {KORAD_KA3005P_0XBC, "Korad", "KA3005P",
+ "KORADKA3005PV2.0\xBC", 1, {0, 31, 0.01}, {0, 5, 0.001}},
{KORAD_KD3005P, "Korad", "KD3005P",
"KORAD KD3005P V2.0", 1, {0, 31, 0.01}, {0, 5, 0.001}},
{KORAD_KD3005P_V20_NOSP, "Korad", "KD3005P",
"KORADKD3005PV2.0", 1, {0, 31, 0.01}, {0, 5, 0.001}},
+ {RND_320_KD3005P, "RND", "KD3005P",
+ "RND 320-KD3005P V4.2", 1, {0, 31, 0.01}, {0, 5, 0.001}},
{RND_320K30PV, "RND", "KA3005P",
"RND 320-KA3005P V2.0", 1, {0, 31, 0.01}, {0, 5, 0.001}},
{TENMA_72_2540_V20, "Tenma", "72-2540",
"TENMA72-2540V2.0", 1, {0, 31, 0.01}, {0, 5, 0.001}},
{TENMA_72_2540_V21, "Tenma", "72-2540",
"TENMA 72-2540 V2.1", 1, {0, 31, 0.01}, {0, 5, 0.001}},
+ {TENMA_72_2535_V21, "Tenma", "72-2535",
+ "TENMA 72-2535 V2.1", 1, {0, 31, 0.01}, {0, 3, 0.001}},
+ {STAMOS_SLS31_V20, "Stamos Soldering", "S-LS-31",
+ "S-LS-31 V2.0", 1, {0, 31, 0.01}, {0, 5.1, 0.001}},
+ {KORAD_KD6005P, "Korad", "KD6005P",
+ "KORAD KD6005P V2.2", 1, {0, 61, 0.01}, {0, 5, 0.001}},
ALL_ZERO
};
if (strlen(models[i].id) > len)
len = strlen(models[i].id);
}
+
+ /*
+ * Some models also include the serial number:
+ * RND 320-KD3005P V4.2 SN:59834414
+ */
+ len += 12;
+
memset(&reply, 0, sizeof(reply));
sr_dbg("Want max %d bytes.", len);
if ((korad_kaxxxxp_send_cmd(serial, "*IDN?") < 0))
return NULL;
sr_dbg("Received: %d, %s", i, reply);
model_id = -1;
+
+ /* Truncate before serial number. */
+ char *sn = g_strrstr(reply, " SN:");
+ if (sn)
+ *sn = '\0';
+
for (i = 0; models[i].id; i++) {
- if (!strcmp(models[i].id, reply))
+ if (!g_strcmp0(models[i].id, reply))
model_id = i;
}
if (model_id < 0) {
g_mutex_init(&devc->rw_mutex);
devc->model = &models[model_id];
devc->req_sent_at = 0;
+ devc->cc_mode_1_changed = FALSE;
+ devc->cc_mode_2_changed = FALSE;
+ devc->output_enabled_changed = FALSE;
+ devc->ocp_enabled_changed = FALSE;
+ devc->ovp_enabled_changed = FALSE;
sdi->priv = devc;
/* Get current status of device. */
*data = g_variant_new_double(devc->voltage);
break;
case SR_CONF_VOLTAGE_TARGET:
- korad_kaxxxxp_get_value(sdi->conn, KAXXXXP_VOLTAGE_MAX, devc);
- *data = g_variant_new_double(devc->voltage_max);
+ korad_kaxxxxp_get_value(sdi->conn, KAXXXXP_VOLTAGE_TARGET, devc);
+ *data = g_variant_new_double(devc->voltage_target);
break;
case SR_CONF_CURRENT:
korad_kaxxxxp_get_value(sdi->conn, KAXXXXP_CURRENT, devc);
*data = g_variant_new_double(devc->current);
break;
case SR_CONF_CURRENT_LIMIT:
- korad_kaxxxxp_get_value(sdi->conn, KAXXXXP_CURRENT_MAX, devc);
- *data = g_variant_new_double(devc->current_max);
+ korad_kaxxxxp_get_value(sdi->conn, KAXXXXP_CURRENT_LIMIT, devc);
+ *data = g_variant_new_double(devc->current_limit);
break;
case SR_CONF_ENABLED:
korad_kaxxxxp_get_value(sdi->conn, KAXXXXP_OUTPUT, devc);
dval = g_variant_get_double(data);
if (dval < devc->model->voltage[0] || dval > devc->model->voltage[1])
return SR_ERR_ARG;
- devc->voltage_max = dval;
- if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_VOLTAGE_MAX, devc) < 0)
+ devc->set_voltage_target = dval;
+ if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_VOLTAGE_TARGET, devc) < 0)
return SR_ERR;
break;
case SR_CONF_CURRENT_LIMIT:
dval = g_variant_get_double(data);
if (dval < devc->model->current[0] || dval > devc->model->current[1])
return SR_ERR_ARG;
- devc->current_max = dval;
- if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_CURRENT_MAX, devc) < 0)
+ devc->set_current_limit = dval;
+ if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_CURRENT_LIMIT, devc) < 0)
return SR_ERR;
break;
case SR_CONF_ENABLED:
bval = g_variant_get_boolean(data);
/* Set always so it is possible turn off with sigrok-cli. */
- devc->output_enabled = bval;
+ devc->set_output_enabled = bval;
if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_OUTPUT, devc) < 0)
return SR_ERR;
break;
case SR_CONF_OVER_CURRENT_PROTECTION_ENABLED:
bval = g_variant_get_boolean(data);
- devc->ocp_enabled = bval;
+ devc->set_ocp_enabled = bval;
if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_OCP, devc) < 0)
return SR_ERR;
break;
case SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED:
bval = g_variant_get_boolean(data);
- devc->ovp_enabled = bval;
+ devc->set_ovp_enabled = bval;
if (korad_kaxxxxp_set_value(sdi->conn, KAXXXXP_OVP, devc) < 0)
return SR_ERR;
break;
* This file is part of the libsigrok project.
*
* Copyright (C) 2015 Hannu Vuolasaho <vuokkosetae@gmail.com>
- * Copyright (C) 2018 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2018-2019 Frank Stettner <frank-stettner@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
sr_err("Can't set measurable parameter %d.", target);
g_mutex_unlock(&devc->rw_mutex);
return SR_ERR;
- case KAXXXXP_CURRENT_MAX:
+ case KAXXXXP_CURRENT_LIMIT:
cmd = "ISET1:%05.3f";
- value = devc->current_max;
+ value = devc->set_current_limit;
break;
- case KAXXXXP_VOLTAGE_MAX:
+ case KAXXXXP_VOLTAGE_TARGET:
cmd = "VSET1:%05.2f";
- value = devc->voltage_max;
+ value = devc->set_voltage_target;
break;
case KAXXXXP_OUTPUT:
cmd = "OUT%01.0f";
- value = (devc->output_enabled) ? 1 : 0;
+ value = (devc->set_output_enabled) ? 1 : 0;
+ /* Set value back to recognize changes */
+ devc->output_enabled = devc->set_output_enabled;
break;
case KAXXXXP_BEEP:
cmd = "BEEP%01.0f";
- value = (devc->beep_enabled) ? 1 : 0;
+ value = (devc->set_beep_enabled) ? 1 : 0;
break;
case KAXXXXP_OCP:
cmd = "OCP%01.0f";
- value = (devc->ocp_enabled) ? 1 : 0;
+ value = (devc->set_ocp_enabled) ? 1 : 0;
+ /* Set value back to recognize changes */
+ devc->ocp_enabled = devc->set_ocp_enabled;
break;
case KAXXXXP_OVP:
cmd = "OVP%01.0f";
- value = (devc->ovp_enabled) ? 1 : 0;
+ value = (devc->set_ovp_enabled) ? 1 : 0;
+ /* Set value back to recognize changes */
+ devc->ovp_enabled = devc->set_ovp_enabled;
break;
case KAXXXXP_SAVE:
cmd = "SAV%01.0f";
char reply[6];
float *value;
char status_byte;
+ gboolean prev_status;
g_mutex_lock(&devc->rw_mutex);
give_device_time_to_process(devc);
ret = korad_kaxxxxp_send_cmd(serial, "IOUT1?");
value = &(devc->current);
break;
- case KAXXXXP_CURRENT_MAX:
+ case KAXXXXP_CURRENT_LIMIT:
/* Read set current from device. */
ret = korad_kaxxxxp_send_cmd(serial, "ISET1?");
- value = &(devc->current_max);
+ value = &(devc->current_limit);
break;
case KAXXXXP_VOLTAGE:
/* Read voltage from device. */
ret = korad_kaxxxxp_send_cmd(serial, "VOUT1?");
value = &(devc->voltage);
break;
- case KAXXXXP_VOLTAGE_MAX:
+ case KAXXXXP_VOLTAGE_TARGET:
/* Read set voltage from device. */
ret = korad_kaxxxxp_send_cmd(serial, "VSET1?");
- value = &(devc->voltage_max);
+ value = &(devc->voltage_target);
break;
case KAXXXXP_STATUS:
case KAXXXXP_OUTPUT:
} else {
/* We have status reply. */
status_byte = reply[0];
- /* Constant current */
- devc->cc_mode[0] = !(status_byte & (1 << 0)); /* Channel one */
- devc->cc_mode[1] = !(status_byte & (1 << 1)); /* Channel two */
+
+ /* Constant current channel one. */
+ prev_status = devc->cc_mode[0];
+ devc->cc_mode[0] = !(status_byte & (1 << 0));
+ devc->cc_mode_1_changed = devc->cc_mode[0] != prev_status;
+ /* Constant current channel two. */
+ prev_status = devc->cc_mode[1];
+ devc->cc_mode[1] = !(status_byte & (1 << 1));
+ devc->cc_mode_2_changed = devc->cc_mode[1] != prev_status;
+
/*
- * Tracking
+ * Tracking:
* status_byte & ((1 << 2) | (1 << 3))
* 00 independent 01 series 11 parallel
*/
- devc->beep_enabled = (1 << 4);
- devc->ocp_enabled = (status_byte & (1 << 5));
- devc->output_enabled = (status_byte & (1 << 6));
- /* Velleman LABPS3005 quirk */
- if (devc->output_enabled)
- devc->ovp_enabled = (status_byte & (1 << 7));
+ devc->beep_enabled = status_byte & (1 << 4);
+
+ /* OCP enabled. */
+ prev_status = devc->ocp_enabled;
+ devc->ocp_enabled = status_byte & (1 << 5);
+ devc->ocp_enabled_changed = devc->ocp_enabled != prev_status;
+
+ /* Output status. */
+ prev_status = devc->output_enabled;
+ devc->output_enabled = status_byte & (1 << 6);
+ devc->output_enabled_changed = devc->output_enabled != prev_status;
+
+ /* OVP enabled, special handling for Velleman LABPS3005 quirk. */
+ if ((devc->model->model_id == VELLEMAN_LABPS3005D && devc->output_enabled) ||
+ devc->model->model_id != VELLEMAN_LABPS3005D) {
+
+ prev_status = devc->ovp_enabled;
+ devc->ovp_enabled = status_byte & (1 << 7);
+ devc->ovp_enabled_changed = devc->ovp_enabled != prev_status;
+ }
+
sr_dbg("Status: 0x%02x", status_byte);
sr_spew("Status: CH1: constant %s CH2: constant %s. "
- "Tracking would be %s. Device is "
- "%s and %s. Buttons are %s. Output is %s "
- "and extra byte is %s.",
+ "Tracking would be %s and %s. Output is %s. "
+ "OCP is %s, OVP is %s. Device is %s.",
(status_byte & (1 << 0)) ? "voltage" : "current",
(status_byte & (1 << 1)) ? "voltage" : "current",
(status_byte & (1 << 2)) ? "parallel" : "series",
(status_byte & (1 << 3)) ? "tracking" : "independent",
- (status_byte & (1 << 4)) ? "beeping" : "silent",
- (status_byte & (1 << 5)) ? "locked" : "unlocked",
(status_byte & (1 << 6)) ? "enabled" : "disabled",
- (status_byte & (1 << 7)) ? "true" : "false");
+ (status_byte & (1 << 5)) ? "enabled" : "disabled",
+ (status_byte & (1 << 7)) ? "enabled" : "disabled",
+ (status_byte & (1 << 4)) ? "beeping" : "silent");
}
/* Read the sixth byte from ISET? BUG workaround. */
- if (target == KAXXXXP_CURRENT_MAX)
+ if (target == KAXXXXP_CURRENT_LIMIT)
serial_read_blocking(serial, &status_byte, 1, 10);
g_mutex_unlock(&devc->rw_mutex);
analog.meaning->channels = l;
analog.meaning->mq = SR_MQ_CURRENT;
analog.meaning->unit = SR_UNIT_AMPERE;
- analog.meaning->mqflags = 0;
+ analog.meaning->mqflags = SR_MQFLAG_DC;
analog.encoding->digits = 3;
analog.spec->spec_digits = 3;
analog.data = &devc->current;
analog.data = &devc->voltage;
sr_session_send(sdi, &packet);
sr_sw_limits_update_samples_read(&devc->limits, 1);
+ } else if (devc->acquisition_target == KAXXXXP_STATUS) {
+ if (devc->cc_mode_1_changed) {
+ sr_session_send_meta(sdi, SR_CONF_REGULATION,
+ g_variant_new_string((devc->cc_mode[0]) ? "CC" : "CV"));
+ devc->cc_mode_1_changed = FALSE;
+ }
+ if (devc->cc_mode_2_changed) {
+ sr_session_send_meta(sdi, SR_CONF_REGULATION,
+ g_variant_new_string((devc->cc_mode[1]) ? "CC" : "CV"));
+ devc->cc_mode_2_changed = FALSE;
+ }
+ if (devc->output_enabled_changed) {
+ sr_session_send_meta(sdi, SR_CONF_ENABLED,
+ g_variant_new_boolean(devc->output_enabled));
+ devc->output_enabled_changed = FALSE;
+ }
+ if (devc->ocp_enabled_changed) {
+ sr_session_send_meta(sdi, SR_CONF_OVER_CURRENT_PROTECTION_ENABLED,
+ g_variant_new_boolean(devc->ocp_enabled));
+ devc->ocp_enabled_changed = FALSE;
+ }
+ if (devc->ovp_enabled_changed) {
+ sr_session_send_meta(sdi, SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED,
+ g_variant_new_boolean(devc->ovp_enabled));
+ devc->ovp_enabled_changed = FALSE;
+ }
}
next_measurement(devc);
* This file is part of the libsigrok project.
*
* Copyright (C) 2015 Hannu Vuolasaho <vuokkosetae@gmail.com>
- * Copyright (C) 2018 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2018-2019 Frank Stettner <frank-stettner@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
VELLEMAN_LABPS3005D,
KORAD_KA3005P,
KORAD_KA3005P_0X01,
+ KORAD_KA3005P_0XBC,
KORAD_KD3005P,
KORAD_KD3005P_V20_NOSP,
+ RND_320_KD3005P,
RND_320K30PV,
TENMA_72_2540_V20,
TENMA_72_2540_V21,
+ TENMA_72_2535_V21,
+ STAMOS_SLS31_V20,
+ KORAD_KD6005P,
/* Support for future devices with this protocol. */
};
/* Reply targets */
enum {
KAXXXXP_CURRENT,
- KAXXXXP_CURRENT_MAX,
+ KAXXXXP_CURRENT_LIMIT,
KAXXXXP_VOLTAGE,
- KAXXXXP_VOLTAGE_MAX,
+ KAXXXXP_VOLTAGE_TARGET,
KAXXXXP_STATUS,
KAXXXXP_OUTPUT,
KAXXXXP_BEEP,
GMutex rw_mutex;
float current; /**< Last current value [A] read from device. */
- float current_max; /**< Output current set. */
+ float current_limit; /**< Output current set. */
float voltage; /**< Last voltage value [V] read from device. */
- float voltage_max; /**< Output voltage set. */
+ float voltage_target; /**< Output voltage set. */
gboolean cc_mode[2]; /**< Device is in CC mode (otherwise CV). */
gboolean output_enabled; /**< Is the output enabled? */
gboolean ocp_enabled; /**< Output current protection enabled. */
gboolean ovp_enabled; /**< Output voltage protection enabled. */
+ gboolean cc_mode_1_changed; /**< CC mode of channel 1 has changed. */
+ gboolean cc_mode_2_changed; /**< CC mode of channel 2 has changed. */
+ gboolean output_enabled_changed; /**< Output enabled state has changed. */
+ gboolean ocp_enabled_changed; /**< OCP enabled state has changed. */
+ gboolean ovp_enabled_changed; /**< OVP enabled state has changed. */
+
int acquisition_target; /**< What reply to expect. */
int program; /**< Program to store or recall. */
+
+ float set_current_limit; /**< New output current to set. */
+ float set_voltage_target; /**< New output voltage to set. */
+ gboolean set_output_enabled; /**< New output enabled to set. */
+ gboolean set_beep_enabled; /**< New enable beeper to set. */
+ gboolean set_ocp_enabled; /**< New OCP enabled to set. */
+ gboolean set_ovp_enabled; /**< New OVP enabled to set. */
};
SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
src = sr_config_new(SR_CONF_SAMPLE_INTERVAL, g_variant_new_uint64(interval));
meta.config = g_slist_append(NULL, src);
sr_session_send(sdi, &packet);
- g_free(src);
+ g_slist_free(meta.config);
+ sr_config_free(src);
if (devc->logged_samples == 0) {
/* This ensures the frontend knows the session is done. */
struct sr_analog_encoding encoding;
struct sr_analog_meaning meaning;
struct sr_analog_spec spec;
- char buf[8];
(void)fd;
(void)revents;
if (ch->type != SR_CHANNEL_ANALOG)
return SR_ERR;
- /* Pass on the received data of the channel(s). */
- if (sr_scpi_read_data(sdi->conn, buf, 4) != 4) {
- sr_err("Reading header failed, scope probably didn't send any data.");
- return TRUE;
- }
-
if (sr_scpi_get_block(sdi->conn, NULL, &data) != SR_OK) {
if (data)
g_byte_array_free(data, TRUE);
/* Note: All models have one power supply output only. */
static const struct hcs_model models[] = {
- { MANSON_HCS_3100, "HCS-3100", "3100", { 1, 18, 0.1 }, { 0, 10, 0.10 } },
- { MANSON_HCS_3102, "HCS-3102", "3102", { 1, 36, 0.1 }, { 0, 5, 0.01 } },
- { MANSON_HCS_3104, "HCS-3104", "3104", { 1, 60, 0.1 }, { 0, 2.5, 0.01 } },
- { MANSON_HCS_3150, "HCS-3150", "3150", { 1, 18, 0.1 }, { 0, 15, 0.10 } },
- { MANSON_HCS_3200, "HCS-3200", "3200", { 1, 18, 0.1 }, { 0, 20, 0.10 } },
- { MANSON_HCS_3202, "HCS-3202", "3202", { 1, 36, 0.1 }, { 0, 10, 0.10 } },
- { MANSON_HCS_3204, "HCS-3204", "3204", { 1, 60, 0.1 }, { 0, 5, 0.01 } },
- { MANSON_HCS_3300, "HCS-3300-USB", "3300", { 1, 16, 0.1 }, { 0, 30, 0.10 } },
- { MANSON_HCS_3302, "HCS-3302-USB", "3302", { 1, 32, 0.1 }, { 0, 15, 0.10 } },
- { MANSON_HCS_3304, "HCS-3304-USB", "3304", { 1, 60, 0.1 }, { 0, 8, 0.10 } },
+ { MANSON_HCS_3100, "HCS-3100", "3100", { 1, 18, 0.1 }, { 0, 10, 0.10 } },
+ { MANSON_HCS_3102, "HCS-3102", "3102", { 1, 36, 0.1 }, { 0, 5, 0.01 } },
+ { MANSON_HCS_3104, "HCS-3104", "3104", { 1, 60, 0.1 }, { 0, 2.5, 0.01 } },
+ { MANSON_HCS_3150, "HCS-3150", "3150", { 1, 18, 0.1 }, { 0, 15, 0.10 } },
+ { MANSON_HCS_3200, "HCS-3200", "3200", { 1, 18, 0.1 }, { 0, 20, 0.10 } },
+ { MANSON_HCS_3200, "HCS-3200", "HCS-3200", { 1, 18, 0.1 }, { 0, 20, 0.10 } },
+ { MANSON_HCS_3202, "HCS-3202", "3202", { 1, 36, 0.1 }, { 0, 10, 0.10 } },
+ { MANSON_HCS_3202, "HCS-3202", "HCS-3202", { 1, 36, 0.1 }, { 0, 10, 0.10 } },
+ { MANSON_HCS_3204, "HCS-3204", "3204", { 1, 60, 0.1 }, { 0, 5, 0.01 } },
+ { MANSON_HCS_3300, "HCS-3300-USB", "3300", { 1, 16, 0.1 }, { 0, 30, 0.10 } },
+ { MANSON_HCS_3300, "HCS-3300-USB", "HCS-3300", { 1, 16, 0.1 }, { 0, 30, 0.10 } },
+ { MANSON_HCS_3302, "HCS-3302-USB", "3302", { 1, 32, 0.1 }, { 0, 15, 0.10 } },
+ { MANSON_HCS_3302, "HCS-3302-USB", "HCS-3302", { 1, 32, 0.1 }, { 0, 15, 0.10 } },
+ { MANSON_HCS_3304, "HCS-3304-USB", "3304", { 1, 60, 0.1 }, { 0, 8, 0.10 } },
{ MANSON_HCS_3304, "HCS-3304-USB", "HCS-3304", { 1, 60, 0.1 }, { 0, 8, 0.10 } },
- { MANSON_HCS_3400, "HCS-3400-USB", "3400", { 1, 16, 0.1 }, { 0, 40, 0.10 } },
- { MANSON_HCS_3402, "HCS-3402-USB", "3402", { 1, 32, 0.1 }, { 0, 20, 0.10 } },
- { MANSON_HCS_3404, "HCS-3404-USB", "3404", { 1, 60, 0.1 }, { 0, 10, 0.10 } },
- { MANSON_HCS_3600, "HCS-3600-USB", "3600", { 1, 16, 0.1 }, { 0, 60, 0.10 } },
- { MANSON_HCS_3602, "HCS-3602-USB", "3602", { 1, 32, 0.1 }, { 0, 30, 0.10 } },
- { MANSON_HCS_3604, "HCS-3604-USB", "3604", { 1, 60, 0.1 }, { 0, 15, 0.10 } },
+ { MANSON_HCS_3400, "HCS-3400-USB", "3400", { 1, 16, 0.1 }, { 0, 40, 0.10 } },
+ { MANSON_HCS_3402, "HCS-3402-USB", "3402", { 1, 32, 0.1 }, { 0, 20, 0.10 } },
+ { MANSON_HCS_3404, "HCS-3404-USB", "3404", { 1, 60, 0.1 }, { 0, 10, 0.10 } },
+ { MANSON_HCS_3600, "HCS-3600-USB", "3600", { 1, 16, 0.1 }, { 0, 60, 0.10 } },
+ { MANSON_HCS_3602, "HCS-3602-USB", "3602", { 1, 32, 0.1 }, { 0, 30, 0.10 } },
+ { MANSON_HCS_3604, "HCS-3604-USB", "3604", { 1, 60, 0.1 }, { 0, 15, 0.10 } },
ALL_ZERO
};
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Dave Buechi <db@pflutsch.ch>
+ *
+ * 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 "protocol.h"
+
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+ SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_THERMOMETER,
+};
+
+static const uint32_t devopts[] = {
+ SR_CONF_CONTINUOUS,
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_DATA_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static const char *channel_names[] = {
+ "T1", "T2", "T1-T2",
+};
+
+static const char *data_sources[] = {
+ "Live", "Memory",
+};
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ struct sr_dev_inst *sdi;
+ struct sr_config *src;
+ GSList *devices, *l;
+ const char *conn, *serialcomm;
+ uint8_t buf[2 * MASTECH_MS6514_FRAME_SIZE];
+ size_t len, i;
+
+ len = sizeof(buf);
+ devices = NULL;
+ conn = serialcomm = NULL;
+ for (l = options; l; l = l->next) {
+ src = l->data;
+ switch (src->key) {
+ case SR_CONF_CONN:
+ conn = g_variant_get_string(src->data, NULL);
+ break;
+ case SR_CONF_SERIALCOMM:
+ serialcomm = g_variant_get_string(src->data, NULL);
+ break;
+ }
+ }
+ if (!conn)
+ return NULL;
+ if (!serialcomm)
+ serialcomm = "9600/8n1";
+
+ serial = sr_serial_dev_inst_new(conn, serialcomm);
+
+ if (serial_open(serial, SERIAL_RDONLY) != SR_OK)
+ return NULL;
+
+ sr_info("Probing serial port %s.", conn);
+
+ serial_flush(serial);
+
+ /* Let's get a bit of data and see if we can find a packet. */
+ if (serial_stream_detect(serial, buf, &len, (2 * MASTECH_MS6514_FRAME_SIZE),
+ mastech_ms6514_packet_valid, 500) != SR_OK)
+ goto scan_cleanup;
+
+ sr_info("Found device on port %s.", conn);
+
+ sdi = g_malloc0(sizeof(struct sr_dev_inst));
+ sdi->status = SR_ST_INACTIVE;
+ sdi->vendor = g_strdup("MASTECH");
+ sdi->model = g_strdup("MS6514");
+ devc = g_malloc0(sizeof(struct dev_context));
+ devc->data_source = DEFAULT_DATA_SOURCE;
+ sdi->inst_type = SR_INST_SERIAL;
+ sdi->conn = serial;
+ sdi->priv = devc;
+
+ for (i = 0; i < ARRAY_SIZE(channel_names); i++)
+ sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_names[i]);
+
+ devices = g_slist_append(devices, sdi);
+
+scan_cleanup:
+ serial_close(serial);
+
+ return std_scan_complete(di, devices);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc = sdi->priv;
+
+ (void)cg;
+
+ switch (key) {
+ case SR_CONF_LIMIT_SAMPLES:
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_get(&devc->limits, key, data);
+ case SR_CONF_DATA_SOURCE:
+ *data = g_variant_new_string(data_sources[devc->data_source]);
+ break;
+ default:
+ return SR_ERR_NA;
+ }
+
+ return SR_OK;
+}
+
+static int config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ int idx;
+
+ (void)cg;
+
+ devc = sdi->priv;
+
+ switch (key) {
+ case SR_CONF_LIMIT_SAMPLES:
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_set(&devc->limits, key, data);
+ case SR_CONF_DATA_SOURCE:
+ if ((idx = std_str_idx(data, ARRAY_AND_SIZE(data_sources))) < 0)
+ return SR_ERR_ARG;
+ devc->data_source = idx;
+ break;
+ default:
+ return SR_ERR_NA;
+ }
+
+ return SR_OK;
+}
+
+static int config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ switch (key) {
+ case SR_CONF_SCAN_OPTIONS:
+ case SR_CONF_DEVICE_OPTIONS:
+ return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+ case SR_CONF_DATA_SOURCE:
+ *data = g_variant_new_strv(ARRAY_AND_SIZE(data_sources));
+ break;
+ default:
+ return SR_ERR_NA;
+ }
+
+ return SR_OK;
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+ struct sr_serial_dev_inst *serial;
+ struct dev_context *devc;
+ uint8_t command;
+
+ serial = sdi->conn;
+ devc = sdi->priv;
+
+ sr_sw_limits_acquisition_start(&devc->limits);
+ std_session_send_df_header(sdi);
+
+ if (devc->data_source == DATA_SOURCE_MEMORY) {
+ command = CMD_GET_STORED;
+ serial_write_blocking(serial, &command, sizeof(command), 0);
+ }
+
+ serial_source_add(sdi->session, serial, G_IO_IN, MASTECH_MS6514_BUF_SIZE,
+ mastech_ms6514_receive_data, (void *)sdi);
+
+ return SR_OK;
+}
+
+static struct sr_dev_driver mastech_ms6514_driver_info = {
+ .name = "mastech-ms6514",
+ .longname = "MASTECH MS6514",
+ .api_version = 1,
+ .init = std_init,
+ .cleanup = std_cleanup,
+ .scan = scan,
+ .dev_list = std_dev_list,
+ .dev_clear = std_dev_clear,
+ .config_get = config_get,
+ .config_set = config_set,
+ .config_list = config_list,
+ .dev_open = std_serial_dev_open,
+ .dev_close = std_serial_dev_close,
+ .dev_acquisition_start = dev_acquisition_start,
+ .dev_acquisition_stop = std_serial_dev_acquisition_stop,
+ .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(mastech_ms6514_driver_info);
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Dave Buechi <db@pflutsch.ch>
+ *
+ * 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 <string.h>
+#include <math.h>
+#include "protocol.h"
+
+static const uint8_t channel_assignment[16][2] = {
+ /* MAIN AUX */
+ {0, 1}, /* T1 T2 */
+ {1, 0}, /* T2 T1 */
+ {2, 0}, /* T1-T2 T1 */
+ {2, 1}, /* T1-T2 T2 */
+ {0, 0}, /* T1 T1 MAX */
+ {1, 1}, /* T2 T2 MAX */
+ {2, 2}, /* T1-T2 T1-T2 MAX */
+ {2, 2}, /* T1-T2 T1-T2 MAX */
+ {0, 0}, /* T1 T1 MIN */
+ {1, 1}, /* T2 T2 MIN */
+ {2, 2}, /* T1-T2 T1-T2 MIN */
+ {2, 2}, /* T1-T2 T1-T2 MIN */
+ {0, 0}, /* T1 T1 AVG */
+ {1, 1}, /* T2 T2 AVG */
+ {2, 2}, /* T1-T2 T1-T2 AVG */
+ {2, 2}, /* T1-T2 T1-T2 AVG */
+};
+
+SR_PRIV gboolean mastech_ms6514_packet_valid(const uint8_t *buf)
+{
+ if ((buf[0] == 0x65) && (buf[1] == 0x14) &&
+ (buf[16] == 0x0D) && (buf[17] == 0x0A))
+ return TRUE;
+
+ return FALSE;
+}
+
+static uint64_t mastech_ms6514_flags(const uint8_t *buf, const uint8_t channel_index)
+{
+ uint64_t flags;
+
+ flags = 0;
+ if ((buf[10] & 0x40) == 0x40)
+ flags |= SR_MQFLAG_HOLD;
+
+ if (channel_index == 0) {
+ if ((buf[11] & 0x03) > 0x01)
+ flags |= SR_MQFLAG_RELATIVE;
+ }
+
+ if (channel_index == 1) {
+ switch (buf[12] & 0x03) {
+ case 0x01:
+ flags |= SR_MQFLAG_MAX;
+ break;
+ case 0x02:
+ flags |= SR_MQFLAG_MIN;
+ break;
+ case 0x03:
+ flags |= SR_MQFLAG_AVG;
+ break;
+ }
+ }
+
+ return flags;
+}
+
+static enum sr_unit mastech_ms6514_unit(const uint8_t *buf)
+{
+ enum sr_unit unit;
+
+ switch (buf[10] & 0x03) {
+ case 0x01:
+ unit = SR_UNIT_CELSIUS;
+ break;
+ case 0x02:
+ unit = SR_UNIT_FAHRENHEIT;
+ break;
+ case 0x03:
+ unit = SR_UNIT_KELVIN;
+ break;
+ default:
+ unit = SR_UNIT_UNITLESS;
+ break;
+ }
+
+ return unit;
+}
+
+static uint8_t mastech_ms6514_channel_assignment(const uint8_t *buf, const uint8_t index)
+{
+ return channel_assignment[((buf[12] & 0x03) << 2) + (buf[11] & 0x03)][index];
+}
+
+static uint8_t mastech_ms6514_data_source(const uint8_t *buf)
+{
+ if ((buf[2] & 0x01) == 0x01)
+ return DATA_SOURCE_MEMORY;
+ else
+ return DATA_SOURCE_LIVE;
+}
+
+static float mastech_ms6514_temperature(const uint8_t *buf, const uint8_t channel_index, int *digits)
+{
+ float value;
+ uint8_t modifiers;
+
+ *digits = 0;
+ value = (buf[5 + channel_index * 2] << 8) + buf[6 + channel_index * 2];
+ modifiers = buf[11 + channel_index];
+
+ if ((modifiers & 0x80) == 0x80)
+ value = -value;
+
+ if ((modifiers & 0x08) == 0x08) {
+ value /= 10.0;
+ *digits = 1;
+ }
+
+ if ((modifiers & 0x40) == 0x40)
+ value = INFINITY;
+
+ return value;
+}
+
+static void mastech_ms6514_data(struct sr_dev_inst *sdi, const uint8_t *buf)
+{
+ struct dev_context *devc;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
+ struct sr_analog_encoding encoding;
+ struct sr_analog_meaning meaning;
+ struct sr_analog_spec spec;
+ struct sr_channel *ch;
+ float value;
+ int i, digits;
+
+ devc = sdi->priv;
+
+ if ((devc->data_source == DATA_SOURCE_MEMORY) && \
+ (mastech_ms6514_data_source(buf) == DATA_SOURCE_LIVE)) {
+ sr_dev_acquisition_stop(sdi);
+ return;
+ }
+
+ for (i = 0; i < MASTECH_MS6514_NUM_CHANNELS; i++) {
+ ch = g_slist_nth_data(sdi->channels, i);
+ if (!ch->enabled)
+ continue;
+
+ value = mastech_ms6514_temperature(buf, i, &digits);
+ sr_analog_init(&analog, &encoding, &meaning, &spec, digits);
+ analog.num_samples = 1;
+ analog.data = &value;
+ analog.meaning->mq = SR_MQ_TEMPERATURE;
+ analog.meaning->unit = mastech_ms6514_unit(buf);
+ analog.meaning->mqflags = mastech_ms6514_flags(buf, i);
+
+ analog.meaning->channels = g_slist_append(NULL,
+ g_slist_nth_data(sdi->channels,
+ mastech_ms6514_channel_assignment(buf, i)));
+
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ sr_session_send(sdi, &packet);
+ g_slist_free(analog.meaning->channels);
+ }
+
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
+}
+
+static const uint8_t *mastech_ms6514_parse_data(struct sr_dev_inst *sdi,
+ const uint8_t *buf, int len)
+{
+ if (len < MASTECH_MS6514_FRAME_SIZE)
+ return NULL; /* Not enough data for a full packet. */
+
+ if (buf[0] != 0x65 || buf[1] != 0x14)
+ return buf + 1; /* Try to re-synchronize on a packet start. */
+
+ if (buf[16] != 0x0D || buf[17] != 0x0A)
+ return buf + MASTECH_MS6514_FRAME_SIZE; /* Valid start but no valid end -> skip. */
+
+ mastech_ms6514_data(sdi, buf);
+
+ return buf + MASTECH_MS6514_FRAME_SIZE;
+}
+
+SR_PRIV int mastech_ms6514_receive_data(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ const uint8_t *ptr, *next_ptr, *end_ptr;
+ int len;
+
+ (void)fd;
+
+ if (!(sdi = cb_data) || !(devc = sdi->priv) || revents != G_IO_IN)
+ return TRUE;
+ serial = sdi->conn;
+
+ /* Try to get as much data as the buffer can hold. */
+ len = sizeof(devc->buf) - devc->buf_len;
+ len = serial_read_nonblocking(serial, devc->buf + devc->buf_len, len);
+ if (len < 1) {
+ sr_err("Serial port read error: %d.", len);
+ return FALSE;
+ }
+ devc->buf_len += len;
+
+ /* Now look for packets in that data. */
+ ptr = devc->buf;
+ end_ptr = ptr + devc->buf_len;
+ while ((next_ptr = mastech_ms6514_parse_data(sdi, ptr, end_ptr - ptr)))
+ ptr = next_ptr;
+
+ /* If we have any data left, move it to the beginning of our buffer. */
+ memmove(devc->buf, ptr, end_ptr - ptr);
+ devc->buf_len -= ptr - devc->buf;
+
+ /* If buffer is full and no valid packet was found, wipe buffer. */
+ if (devc->buf_len >= sizeof(devc->buf)) {
+ devc->buf_len = 0;
+ return FALSE;
+ }
+
+ if (sr_sw_limits_check(&devc->limits)) {
+ sr_dev_acquisition_stop(sdi);
+ return TRUE;
+ }
+
+ return TRUE;
+}
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Dave Buechi <db@pflutsch.ch>
+ *
+ * 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/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_MASTECH_MS6514_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_MASTECH_MS6514_PROTOCOL_H
+
+#include <stdint.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "mastech-ms6514"
+
+#define MASTECH_MS6514_NUM_CHANNELS 2
+#define MASTECH_MS6514_BUF_SIZE (3 * 18)
+#define MASTECH_MS6514_FRAME_SIZE 18
+#define DEFAULT_DATA_SOURCE DATA_SOURCE_LIVE
+
+enum mastech_ms6614_data_source {
+ DATA_SOURCE_LIVE,
+ DATA_SOURCE_MEMORY,
+};
+
+enum mastech_ms6614_command {
+ CMD_GET_STORED = 0xA1
+};
+
+struct dev_context {
+ struct sr_sw_limits limits;
+ enum mastech_ms6614_data_source data_source;
+ unsigned int buf_len;
+ uint8_t buf[MASTECH_MS6514_BUF_SIZE];
+ unsigned int log_buf_len;
+};
+
+SR_PRIV int mastech_ms6514_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV gboolean mastech_ms6514_packet_valid(const uint8_t *buf);
+
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018 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/>.
+ */
+
+/*
+ * TODO
+ * - Data acquisition works, but triggers either seem to not take effect,
+ * or the trigger position is not in the expected spot according to the
+ * user provided acquisition parameters. More research is required. The
+ * bitmasks for enable/level/edge as well as the magic 16bit values for
+ * position may need adjustment.
+ * - The trigger position logic assumes that capture ratio specs are in
+ * the range of 0-6%, which gets mapped to none/10%/50%/90%/+1W/+2W/+3W
+ * choices. This avoids issues with applications which lack support for
+ * non-contiguous discrete supported values, and values outside of the
+ * 0-100% range. This is considered acceptable, to avoid the necessity
+ * to extend common infrastructure to an unusual feature of a single
+ * device of limited popularity. Just needs to get communicated to users.
+ * - When a formula for the trigger position values in the SETUP packet
+ * is found, the driver may accept arbitrary values between 0-100%, but
+ * still could not express the "plus N windows" settings. Though that'd
+ * be a rather useful feature considering the very short memory depth.
+ * - The current implementation assumes externally provided Vdd, without
+ * which input levels won't get detected. A future implementation could
+ * optionally power Vdd from the PICkit2 itself, according to a user
+ * provided configuration value.
+ * - The current implementation silently accepts sample count limits beyond
+ * 1024, just won't provide more than 1024 samples to the session. A
+ * future implementation could cap the settings upon reception. Apps
+ * like PulseView may not be able to specify 1024, and pass 1000 or
+ * 2000 instead (the latter results in 1024 getting used).
+ * - The manual suggests that users can assign names to devices. The
+ * current implementation supports conn= specs with USB VID:PID pairs
+ * or bus/address numbers. A future implementation could scan for user
+ * assigned names as well (when the opcode to query the name was found).
+ * - The "attach kernel driver" support code probably should move to a
+ * common location, instead of getting repeated across several drivers.
+ * - Diagnostics may benefit from cleanup.
+ */
+
+#include <config.h>
+#include <libusb.h>
+#include <string.h>
+#include "protocol.h"
+
+#define PICKIT2_VENDOR_NAME "Microchip"
+#define PICKIT2_PRODUCT_NAME "PICkit2"
+
+#define PICKIT2_DEFAULT_ADDRESS "04d8.0033"
+#define PICKIT2_USB_INTERFACE 0
+
+static struct sr_dev_driver microchip_pickit2_driver_info;
+
+static const char *channel_names[] = {
+ "pin4", "pin5", "pin6",
+};
+
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_LOGIC_ANALYZER,
+};
+
+static const uint32_t devopts[] = {
+ SR_CONF_CONN | SR_CONF_GET,
+ SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_TRIGGER_MATCH | SR_CONF_LIST,
+ SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static const int32_t trigger_matches[] = {
+ SR_TRIGGER_ZERO,
+ SR_TRIGGER_ONE,
+ SR_TRIGGER_RISING,
+ SR_TRIGGER_FALLING,
+};
+
+/*
+ * Note that a list of 0, 10, 50, 90, 91, 92, 93, would have been nicer
+ * from a user's perspective, but applications may not support a set of
+ * discrete supported values, and 91+ is as much of a hack to work around
+ * the "0-100%" limitation. So let's map those 0-6 "percent" to the vendor
+ * app's 10/50/90/1W/2W/3W locations.
+ */
+static const uint64_t captureratios[] = {
+ 0, 1, 2, 3, 4, 5, 6,
+};
+
+static const uint64_t samplerates[] = {
+ SR_KHZ(5),
+ SR_KHZ(10),
+ SR_KHZ(25),
+ SR_KHZ(50),
+ SR_KHZ(100),
+ SR_KHZ(250),
+ SR_KHZ(500),
+ SR_MHZ(1),
+};
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+ struct drv_context *drvc;
+ const char *conn;
+ GSList *l, *devices, *usb_devices;
+ struct sr_config *cfg;
+ struct sr_usb_dev_inst *usb;
+ struct sr_dev_inst *sdi;
+ struct sr_channel_group *cg;
+ size_t ch_count, ch_idx;
+ struct sr_channel *ch;
+ struct dev_context *devc;
+
+ drvc = di->context;
+
+ conn = PICKIT2_DEFAULT_ADDRESS;
+ for (l = options; l; l = l->next) {
+ cfg = l->data;
+ switch (cfg->key) {
+ case SR_CONF_CONN:
+ conn = g_variant_get_string(cfg->data, NULL);
+ break;
+ }
+ }
+
+ devices = NULL;
+ usb_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn);
+ if (!usb_devices)
+ return NULL;
+
+ for (l = usb_devices; l; l = l->next) {
+ usb = l->data;
+
+ /* Create the device instance. */
+ sdi = g_malloc0(sizeof(*sdi));
+ devices = g_slist_append(devices, sdi);
+ sdi->status = SR_ST_INACTIVE;
+ sdi->vendor = g_strdup(PICKIT2_VENDOR_NAME);
+ sdi->model = g_strdup(PICKIT2_PRODUCT_NAME);
+ sdi->inst_type = SR_INST_USB;
+ sdi->conn = usb;
+ sdi->connection_id = g_strdup(conn);
+
+ /* Create the logic channels group. */
+ cg = g_malloc0(sizeof(*cg));
+ sdi->channel_groups = g_slist_append(NULL, cg);
+ cg->name = g_strdup("Logic");
+ ch_count = ARRAY_SIZE(channel_names);
+ for (ch_idx = 0; ch_idx < ch_count; ch_idx++) {
+ ch = sr_channel_new(sdi, ch_idx, SR_CHANNEL_LOGIC,
+ TRUE, channel_names[ch_idx]);
+ cg->channels = g_slist_append(cg->channels, ch);
+ }
+
+ /*
+ * Create the device context. Pre-select the highest
+ * samplerate and the deepest sample count available.
+ */
+ devc = g_malloc0(sizeof(*devc));
+ sdi->priv = devc;
+ devc->samplerates = samplerates;
+ devc->num_samplerates = ARRAY_SIZE(samplerates);
+ devc->curr_samplerate_idx = devc->num_samplerates - 1;
+ devc->captureratios = captureratios;
+ devc->num_captureratios = ARRAY_SIZE(captureratios);
+ devc->curr_captureratio_idx = 0;
+ devc->sw_limits.limit_samples = PICKIT2_SAMPLE_COUNT;
+ }
+
+ return std_scan_complete(di, devices);
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+ struct sr_usb_dev_inst *usb;
+ struct dev_context *devc;
+ struct sr_dev_driver *di;
+ struct drv_context *drvc;
+ int ret;
+
+ usb = sdi->conn;
+ devc = sdi->priv;
+ di = sdi->driver;
+ drvc = di->context;
+
+ ret = sr_usb_open(drvc->sr_ctx->libusb_ctx, usb);
+ if (ret < 0)
+ return SR_ERR;
+
+ if (libusb_kernel_driver_active(usb->devhdl, PICKIT2_USB_INTERFACE) == 1) {
+ ret = libusb_detach_kernel_driver(usb->devhdl, PICKIT2_USB_INTERFACE);
+ if (ret < 0) {
+ sr_err("Canot detach kernel driver: %s.",
+ libusb_error_name(ret));
+ return SR_ERR;
+ }
+ devc->detached_kernel_driver = TRUE;
+ }
+
+ ret = libusb_claim_interface(usb->devhdl, PICKIT2_USB_INTERFACE);
+ if (ret < 0) {
+ sr_err("Cannot claim interface: %s.", libusb_error_name(ret));
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+static int dev_close(struct sr_dev_inst *sdi)
+{
+ struct sr_usb_dev_inst *usb;
+ struct dev_context *devc;
+ int ret;
+
+ usb = sdi->conn;
+ devc = sdi->priv;
+
+ if (!usb)
+ return SR_OK;
+ if (!usb->devhdl)
+ return SR_OK;
+
+ ret = libusb_release_interface(usb->devhdl, PICKIT2_USB_INTERFACE);
+ if (ret) {
+ sr_err("Cannot release interface: %s.", libusb_error_name(ret));
+ return SR_ERR;
+ }
+
+ if (devc->detached_kernel_driver) {
+ ret = libusb_attach_kernel_driver(usb->devhdl, PICKIT2_USB_INTERFACE);
+ if (ret) {
+ sr_err("Cannot attach kernel driver: %s.",
+ libusb_error_name(ret));
+ return SR_ERR;
+ }
+ devc->detached_kernel_driver = FALSE;
+ }
+
+ libusb_close(usb->devhdl);
+ sdi->conn = NULL;
+
+ return SR_OK;
+}
+
+static int config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ struct sr_usb_dev_inst *usb;
+ uint64_t rate, ratio;
+
+ (void)cg;
+
+ devc = sdi ? sdi->priv : NULL;
+
+ switch (key) {
+ case SR_CONF_CONN:
+ if (!sdi->conn)
+ return SR_ERR_ARG;
+ usb = sdi->conn;
+ *data = g_variant_new_printf("%d.%d", usb->bus, usb->address);
+ return SR_OK;
+ case SR_CONF_SAMPLERATE:
+ rate = devc->samplerates[devc->curr_samplerate_idx];
+ *data = g_variant_new_uint64(rate);
+ return SR_OK;
+ case SR_CONF_LIMIT_SAMPLES:
+ return sr_sw_limits_config_get(&devc->sw_limits, key, data);
+ case SR_CONF_CAPTURE_RATIO:
+ ratio = devc->captureratios[devc->curr_captureratio_idx];
+ *data = g_variant_new_uint64(ratio);
+ return SR_OK;
+ default:
+ return SR_ERR_NA;
+ }
+}
+
+static int config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ int idx;
+
+ (void)cg;
+
+ devc = sdi ? sdi->priv : NULL;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ if (!devc)
+ return SR_ERR_ARG;
+ idx = std_u64_idx(data, devc->samplerates, devc->num_samplerates);
+ if (idx < 0)
+ return SR_ERR_ARG;
+ devc->curr_samplerate_idx = idx;
+ return SR_OK;
+ case SR_CONF_CAPTURE_RATIO:
+ if (!devc)
+ return SR_ERR_ARG;
+ idx = std_u64_idx(data, devc->captureratios, devc->num_captureratios);
+ if (idx >= 0)
+ devc->curr_captureratio_idx = idx;
+ return SR_OK;
+ case SR_CONF_LIMIT_SAMPLES:
+ return sr_sw_limits_config_set(&devc->sw_limits, key, data);
+ default:
+ return SR_ERR_NA;
+ }
+}
+
+static int config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+
+ devc = sdi ? sdi->priv : NULL;
+
+ switch (key) {
+ case SR_CONF_SCAN_OPTIONS:
+ case SR_CONF_DEVICE_OPTIONS:
+ return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+ case SR_CONF_SAMPLERATE:
+ if (!devc)
+ return SR_ERR_NA;
+ *data = std_gvar_samplerates(devc->samplerates, devc->num_samplerates);
+ return SR_OK;
+ case SR_CONF_TRIGGER_MATCH:
+ *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches));
+ return SR_OK;
+ case SR_CONF_CAPTURE_RATIO:
+ *data = std_gvar_array_u64(ARRAY_AND_SIZE(captureratios));
+ return SR_OK;
+ default:
+ return SR_ERR_NA;
+ }
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct sr_trigger *trigger;
+ struct sr_trigger_stage *stage;
+ struct sr_trigger_match *match;
+ GSList *l;
+ size_t idx;
+ int ret;
+
+ devc = sdi->priv;
+
+ /*
+ * Query triggers, translate the more complex caller spec to
+ * "flat" internal variables, to simplify the construction of
+ * the SETUP packet elsewhere. This driver supports a single
+ * stage, with match conditions for one or multiple channels.
+ */
+ memset(&devc->triggers, 0, sizeof(devc->triggers));
+ trigger = sr_session_trigger_get(sdi->session);
+ if (trigger) {
+ if (g_slist_length(trigger->stages) > 1)
+ return SR_ERR_NA;
+ stage = g_slist_nth_data(trigger->stages, 0);
+ if (!stage)
+ return SR_ERR_ARG;
+ for (l = stage->matches; l; l = l->next) {
+ match = l->data;
+ if (!match->match)
+ continue;
+ if (!match->channel->enabled)
+ continue;
+ idx = match->channel->index;
+ devc->triggers[idx] = match->match;
+ }
+ sr_dbg("acq start: trigger specs: %x/%x/%x",
+ devc->triggers[0], devc->triggers[1],
+ devc->triggers[2]);
+ }
+ devc->trigpos = trigger ? devc->curr_captureratio_idx : 0;
+
+ /* Have the SETUP packet sent, then poll for the status. */
+ devc->state = STATE_CONF;
+ ret = microchip_pickit2_setup_trigger(sdi);
+ if (ret) {
+ devc->state = STATE_IDLE;
+ return ret;
+ }
+ devc->state = STATE_WAIT;
+
+ std_session_send_df_header(sdi);
+ sr_session_source_add(sdi->session, -1, 0, 20,
+ microchip_pickit2_receive_data, (void *)sdi);
+
+ return SR_OK;
+}
+
+static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+
+ devc = sdi->priv;
+ if (devc->state < STATE_CONF)
+ return SR_OK;
+
+ /*
+ * Keep up the acquisition until either data becomes available
+ * (according to the previously configured trigger condition),
+ * or until the user cancels the acquisition by pressing the
+ * device's button. This is a firmware limitation which the
+ * vendor software "suffers from" as well.
+ */
+ if (devc->state == STATE_WAIT) {
+ sr_err("Cannot terminate by software, need either data trigger or cancel button.");
+ return SR_OK;
+ }
+
+ if (devc->state > STATE_CONF) {
+ std_session_send_df_end(sdi);
+ }
+ sr_session_source_remove(sdi->session, -1);
+ devc->state = STATE_IDLE;
+
+ return SR_OK;
+}
+
+static struct sr_dev_driver microchip_pickit2_driver_info = {
+ .name = "microchip-pickit2",
+ .longname = PICKIT2_VENDOR_NAME " " PICKIT2_PRODUCT_NAME,
+ .api_version = 1,
+ .init = std_init,
+ .cleanup = std_cleanup,
+ .scan = scan,
+ .dev_list = std_dev_list,
+ .dev_clear = std_dev_clear,
+ .config_get = config_get,
+ .config_set = config_set,
+ .config_list = config_list,
+ .dev_open = dev_open,
+ .dev_close = dev_close,
+ .dev_acquisition_start = dev_acquisition_start,
+ .dev_acquisition_stop = dev_acquisition_stop,
+ .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(microchip_pickit2_driver_info);
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018 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 <string.h>
+#include "protocol.h"
+
+#define PICKIT2_PACKET_LENGTH 64
+#define PICKIT2_USB_ENDPOINT 1
+#define PICKIT2_USB_TIMEOUT 250
+
+#define PICKIT2_CMD_CHKSTAT 0xa2
+#define PICKIT2_CMD_CHKVOLT 0xa3
+#define PICKIT2_CMD_READ 0xac
+#define PICKIT2_CMD_PADCHAR 0xad
+#define PICKIT2_CMD_SETUP 0xb8
+#define PICKIT2_CMD_SETPOS 0xb9
+
+#define PICKIT2_SEL_BANK0 0x06
+#define PICKIT2_SEL_BANK1 0x07
+
+struct pickit2_cmd {
+ size_t length;
+ uint8_t raw[PICKIT2_PACKET_LENGTH];
+};
+
+static void pickit2_cmd_clear(struct pickit2_cmd *cmd)
+{
+ if (!cmd)
+ return;
+ memset(&cmd->raw[0], PICKIT2_CMD_PADCHAR, PICKIT2_PACKET_LENGTH);
+ cmd->length = 0;
+}
+
+static void pickit2_cmd_append(struct pickit2_cmd *cmd, uint8_t b)
+{
+ if (!cmd)
+ return;
+ if (cmd->length == PICKIT2_PACKET_LENGTH)
+ return;
+ cmd->raw[cmd->length++] = b;
+}
+
+static int pickit2_usb_send(const struct sr_dev_inst *sdi, struct pickit2_cmd *cmd)
+{
+ struct sr_usb_dev_inst *usb;
+ int ret, sent;
+ GString *text;
+
+ if (!cmd)
+ return SR_OK;
+ usb = sdi->conn;
+ if (!usb)
+ return SR_ERR_ARG;
+
+ text = sr_hexdump_new(&cmd->raw[0], cmd->length);
+ sr_dbg("USB sent: %s", text->str);
+ sr_hexdump_free(text);
+
+ ret = libusb_interrupt_transfer(usb->devhdl,
+ LIBUSB_ENDPOINT_OUT | PICKIT2_USB_ENDPOINT,
+ &cmd->raw[0], PICKIT2_PACKET_LENGTH,
+ &sent, PICKIT2_USB_TIMEOUT);
+ if (ret < 0) {
+ sr_err("USB transmit error: %s.", libusb_error_name(ret));
+ return SR_ERR_IO;
+ }
+ if (sent != PICKIT2_PACKET_LENGTH) {
+ sr_err("USB short send: %d/%d bytes.",
+ sent, PICKIT2_PACKET_LENGTH);
+ return SR_ERR_IO;
+ }
+
+ return SR_OK;
+}
+
+static int pickit2_usb_recv(const struct sr_dev_inst *sdi, struct pickit2_cmd *cmd)
+{
+ struct sr_usb_dev_inst *usb;
+ int ret, rcvd;
+ GString *text;
+
+ if (!cmd)
+ return SR_ERR_ARG;
+ usb = sdi->conn;
+ if (!usb)
+ return SR_ERR_ARG;
+
+ ret = libusb_interrupt_transfer(usb->devhdl,
+ LIBUSB_ENDPOINT_IN | PICKIT2_USB_ENDPOINT,
+ &cmd->raw[0], PICKIT2_PACKET_LENGTH,
+ &rcvd, PICKIT2_USB_TIMEOUT);
+ if (ret < 0) {
+ if (ret == LIBUSB_ERROR_TIMEOUT)
+ sr_dbg("USB receive error: %s.", libusb_error_name(ret));
+ else
+ sr_err("USB receive error: %s.", libusb_error_name(ret));
+ return SR_ERR_IO;
+ }
+
+ text = sr_hexdump_new(&cmd->raw[0], rcvd);
+ sr_dbg("USB recv: %s", text->str);
+ sr_hexdump_free(text);
+
+ cmd->length = rcvd;
+ if (rcvd != PICKIT2_PACKET_LENGTH) {
+ sr_err("USB short recv: %d/%d bytes.",
+ rcvd, PICKIT2_PACKET_LENGTH);
+ return SR_ERR_IO;
+ }
+
+ return SR_OK;
+}
+
+/* Send a request, (optionally) keep reading until response became available. */
+static int pickit2_usb_send_recv(const struct sr_dev_inst *sdi,
+ struct pickit2_cmd *send_cmd, struct pickit2_cmd *recv_cmd, int do_wait)
+{
+ int ret;
+
+ /* Send the command when one got specified. Ignore errors. */
+ if (send_cmd)
+ (void)pickit2_usb_send(sdi, send_cmd);
+
+ /*
+ * Try receiving data, always ignore errors. When requested by
+ * the caller then keep receiving until response data became
+ * available.
+ */
+ if (!recv_cmd)
+ return SR_OK;
+ do {
+ ret = pickit2_usb_recv(sdi, recv_cmd);
+ if (ret == SR_OK)
+ return SR_OK;
+ if (!do_wait)
+ return ret;
+ } while (1);
+ /* UNREACH */
+}
+
+SR_PRIV int microchip_pickit2_setup_trigger(const struct sr_dev_inst *sdi)
+{
+ static const uint8_t trigger_channel_masks[PICKIT2_CHANNEL_COUNT] = {
+ /* Bit positions for channels in trigger registers. */
+ 0x04, 0x08, 0x10,
+ };
+ static const uint16_t captureratio_magics[] = {
+ /* TODO
+ * How to exactly calculate these magic 16bit values?
+ * They seem to neither match a percentage value nor a
+ * sample count (assuming 1 window holds 1K samples).
+ * As long as the formula is unknown, we are stuck with
+ * looking up magic values from a table of few pre-sets.
+ */
+ 0x0000, /* unspecified ratio value */
+ 0x03cc, 0x000a, 0x0248, /* 10%/50%/90% in the first window */
+ 0x07b4, 0x0b9c, 0x0f84, /* 10% "plus 1/2/3 window widths" */
+ };
+
+ struct dev_context *devc;
+ uint8_t trig_en, trig_lvl, trig_edge, trig_rep, trig_div;
+ uint16_t trig_pos;
+ uint64_t rate;
+ size_t trig_pos_idx, ch_idx;
+ uint8_t ch_mask, ch_cond;
+ struct pickit2_cmd cmd;
+
+ devc = sdi->priv;
+
+ /* Translate user specs to internal setup values. */
+ trig_en = trig_lvl = trig_edge = 0;
+ for (ch_idx = 0; ch_idx < PICKIT2_CHANNEL_COUNT; ch_idx++) {
+ if (!devc->triggers[ch_idx])
+ continue;
+ ch_mask = trigger_channel_masks[ch_idx];
+ ch_cond = devc->triggers[ch_idx];
+ trig_en |= ch_mask;
+ switch (ch_cond) {
+ case SR_TRIGGER_ONE:
+ case SR_TRIGGER_RISING:
+ trig_lvl |= ch_mask;
+ break;
+ }
+ switch (ch_cond) {
+ case SR_TRIGGER_FALLING:
+ case SR_TRIGGER_RISING:
+ trig_edge |= ch_mask;
+ break;
+ }
+ }
+ trig_rep = 1;
+ trig_rep = MIN(trig_rep, 255);
+ trig_rep = MAX(trig_rep, 1);
+ if (!trig_en)
+ trig_rep = 0;
+ rate = devc->samplerates[devc->curr_samplerate_idx];
+ rate = SR_MHZ(1) / rate - 1;
+ trig_div = rate & 0xff;
+ trig_pos_idx = devc->trigpos;
+ if (trig_pos_idx >= ARRAY_SIZE(captureratio_magics))
+ trig_pos_idx = 0;
+ trig_pos = captureratio_magics[trig_pos_idx];
+
+ /* Construct the SETUP packet. */
+ pickit2_cmd_clear(&cmd);
+ pickit2_cmd_append(&cmd, PICKIT2_CMD_SETUP);
+ pickit2_cmd_append(&cmd, 0x01);
+ pickit2_cmd_append(&cmd, trig_en);
+ pickit2_cmd_append(&cmd, trig_lvl);
+ pickit2_cmd_append(&cmd, trig_edge);
+ pickit2_cmd_append(&cmd, trig_rep);
+ pickit2_cmd_append(&cmd, trig_pos % 256);
+ pickit2_cmd_append(&cmd, trig_pos / 256);
+ pickit2_cmd_append(&cmd, trig_div);
+
+ /*
+ * Transmit the SETUP packet. Only send it out, poll for the
+ * response later. When a trigger is involved, the response may
+ * take considerable amounts of time to arrive. We want apps
+ * to remain responsive during that period of time.
+ */
+ (void)pickit2_usb_send_recv(sdi, &cmd, NULL, FALSE);
+
+ return SR_OK;
+}
+
+/* Read specified bank data at given offset into caller provided buffer. */
+static int pickit2_retrieve_bank(struct sr_dev_inst *sdi,
+ size_t bank_idx, size_t offset, uint8_t **buf, size_t *len)
+{
+ struct pickit2_cmd send_cmd, recv_cmd;
+ int ret;
+ size_t copy_iter, copy_len;
+
+ /* Construct and send the SETPOS packet. No response expected. */
+ pickit2_cmd_clear(&send_cmd);
+ pickit2_cmd_append(&send_cmd, PICKIT2_CMD_SETPOS);
+ pickit2_cmd_append(&send_cmd, offset & 0xff);
+ pickit2_cmd_append(&send_cmd, PICKIT2_SEL_BANK0 + bank_idx);
+ ret = pickit2_usb_send_recv(sdi, &send_cmd, NULL, FALSE);
+ if (ret != SR_OK)
+ return ret;
+ sr_dbg("read bank: pos set");
+
+ /* Run two READ cycles, get 2x 64 bytes => 128 bytes raw data. */
+ pickit2_cmd_clear(&send_cmd);
+ pickit2_cmd_append(&send_cmd, PICKIT2_CMD_READ);
+ copy_iter = 2;
+ while (copy_iter-- > 0) {
+ ret = pickit2_usb_send_recv(sdi, &send_cmd, &recv_cmd, TRUE);
+ if (ret != SR_OK)
+ return ret;
+ copy_len = MIN(PICKIT2_PACKET_LENGTH, *len);
+ memcpy(*buf, &recv_cmd.raw[0], copy_len);
+ *buf += copy_len;
+ *len -= copy_len;
+ }
+
+ return SR_OK;
+}
+
+/* Read all of the (banked, raw) sample data after acquisition completed. */
+static int pickit2_retrieve_sample_data(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ uint8_t *rdpos;
+ size_t rdlen;
+ int ret;
+
+ devc = sdi->priv;
+ rdpos = &devc->samples_raw[0];
+ rdlen = sizeof(devc->samples_raw);
+
+ ret = pickit2_retrieve_bank(sdi, 0, 0x00, &rdpos, &rdlen);
+ if (ret)
+ return ret;
+ ret = pickit2_retrieve_bank(sdi, 0, 0x80, &rdpos, &rdlen);
+ if (ret)
+ return ret;
+ ret = pickit2_retrieve_bank(sdi, 1, 0x00, &rdpos, &rdlen);
+ if (ret)
+ return ret;
+ ret = pickit2_retrieve_bank(sdi, 1, 0x80, &rdpos, &rdlen);
+ if (ret)
+ return ret;
+
+ return SR_OK;
+}
+
+/* Send converted sample data to the sigrok session. */
+static int pickit2_submit_logic_data(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct {
+ uint8_t raw_mask, conv_mask;
+ } ch_map[PICKIT2_CHANNEL_COUNT] = {
+ { 0x04, 0x01, },
+ { 0x08, 0x02, },
+ { 0x01, 0x04, },
+ };
+ uint8_t *raw_buf, raw_byte, *conv_buf;
+ size_t raw_len, conv_len;
+ uint64_t limit;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_logic logic;
+
+ devc = sdi->priv;
+
+ /*
+ * TODO Manipulate (or create) the above channel mapping table.
+ * Remove disabled channels, create dense output format.
+ * Could:
+ * - Loop over the index, check the corresponding channel's
+ * state, clear out the conv_mask part and shift down all
+ * subsequent conv_mask parts.
+ */
+
+ /*
+ * Convert raw dump (two samples per byte, at odd positions) to
+ * internal sigrok format (one sample per byte, at increasing
+ * offsets which start at 0).
+ */
+#define handle_nibble(n) do { \
+ uint8_t conv_byte; \
+ size_t ch_idx; \
+ conv_byte = 0x00; \
+ for (ch_idx = 0; ch_idx < PICKIT2_CHANNEL_COUNT; ch_idx++) { \
+ if ((n) & ch_map[ch_idx].raw_mask) \
+ conv_byte |= ch_map[ch_idx].conv_mask; \
+ } \
+ *conv_buf++ = conv_byte; \
+ conv_len++; \
+} while (0)
+
+ raw_len = sizeof(devc->samples_raw);
+ raw_buf = &devc->samples_raw[raw_len];
+ conv_buf = &devc->samples_conv[0];
+ conv_len = 0;
+ while (raw_len-- > 0) {
+ raw_byte = *(--raw_buf);
+ handle_nibble((raw_byte >> 0) & 0x0f);
+ handle_nibble((raw_byte >> 4) & 0x0f);
+ }
+
+ /* Submit a logic packet to the sigrok session. */
+ packet.type = SR_DF_LOGIC;
+ packet.payload = &logic;
+ logic.unitsize = sizeof(uint8_t);
+ logic.data = &devc->samples_conv[0];
+ logic.length = conv_len;
+ limit = devc->sw_limits.limit_samples;
+ if (limit && limit < logic.length)
+ logic.length = limit;
+ sr_session_send(sdi, &packet);
+
+ return SR_OK;
+}
+
+static gboolean pickit2_status_is_cancel(uint16_t status)
+{
+ /* "Button press" and "transfer timeout" translate to "cancelled". */
+ static const uint16_t status_cancel_mask = 0x4004;
+
+ sr_dbg("recv: status 0x%04x", status);
+ if ((status & status_cancel_mask) == status_cancel_mask)
+ return TRUE;
+ return FALSE;
+}
+
+/* Periodically invoked poll routine, checking for incoming receive data. */
+SR_PRIV int microchip_pickit2_receive_data(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ struct pickit2_cmd cmd;
+ int ret;
+ uint16_t status;
+
+ (void)fd;
+ (void)revents;
+
+ sdi = cb_data;
+ if (!sdi)
+ return TRUE;
+ devc = sdi->priv;
+ if (!devc)
+ return TRUE;
+
+ /* Waiting for the trigger condition? */
+ if (devc->state == STATE_WAIT) {
+ /* Keep waiting until status becomes available. */
+ ret = pickit2_usb_send_recv(sdi, NULL, &cmd, FALSE);
+ if (ret != SR_OK)
+ return TRUE;
+ /* Check status flags for cancel requests. */
+ devc->state = STATE_DATA;
+ status = RL16(&cmd.raw[0]);
+ if (pickit2_status_is_cancel(status)) {
+ sr_info("User cancelled acquisition.");
+ sr_dev_acquisition_stop(sdi);
+ return TRUE;
+ }
+ sr_dbg("recv: Data has become available.");
+ /* FALLTHROUGH */
+ }
+
+ /*
+ * Retrieve acquired sample data (blocking, acquisition has
+ * completed and samples are few), and stop acquisition (have
+ * the poll routine unregistered).
+ */
+ ret = pickit2_retrieve_sample_data(sdi);
+ if (ret != SR_OK)
+ return ret;
+ ret = pickit2_submit_logic_data(sdi);
+ if (ret != SR_OK)
+ return ret;
+ sr_dev_acquisition_stop(sdi);
+ return TRUE;
+}
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018 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/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_MICROCHIP_PICKIT2_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_MICROCHIP_PICKIT2_PROTOCOL_H
+
+#include <glib.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "microchip-pickit2"
+
+#define PICKIT2_CHANNEL_COUNT 3
+#define PICKIT2_SAMPLE_COUNT 1024
+#define PICKIT2_SAMPLE_RAWLEN (4 * 128)
+
+enum pickit_state {
+ STATE_IDLE,
+ STATE_CONF,
+ STATE_WAIT,
+ STATE_DATA,
+};
+
+struct dev_context {
+ enum pickit_state state;
+ const uint64_t *samplerates;
+ size_t num_samplerates;
+ size_t curr_samplerate_idx;
+ const uint64_t *captureratios;
+ size_t num_captureratios;
+ size_t curr_captureratio_idx;
+ struct sr_sw_limits sw_limits;
+ gboolean detached_kernel_driver;
+ int32_t triggers[PICKIT2_CHANNEL_COUNT]; /**@< see @ref SR_TRIGGER_ZERO et al */
+ size_t trigpos;
+ uint8_t samples_raw[PICKIT2_SAMPLE_RAWLEN];
+ uint8_t samples_conv[PICKIT2_SAMPLE_COUNT];
+};
+
+SR_PRIV int microchip_pickit2_setup_trigger(const struct sr_dev_inst *sdi);
+SR_PRIV int microchip_pickit2_receive_data(int fd, int revents, void *cb_data);
+
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Derek Hageman <hageman@inthat.cloud>
+ *
+ * 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 "protocol.h"
+
+static struct sr_dev_driver mooshimeter_dmm_driver_info;
+
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_MULTIMETER,
+};
+
+static const uint32_t devopts[] = {
+ SR_CONF_CONTINUOUS,
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_LIMIT_MSEC | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_AVG_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_CHANNEL_CONFIG | SR_CONF_SET,
+};
+
+static void init_dev(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct sr_channel *chan;
+
+ devc = g_new0(struct dev_context, 1);
+ sdi->priv = devc;
+ sdi->status = SR_ST_INITIALIZING;
+ sdi->vendor = g_strdup("Mooshim Engineering");
+ sdi->model = g_strdup("Mooshimeter");
+
+ sr_sw_limits_init(&devc->limits);
+
+ chan = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "CH1");
+ devc->channel_meaning[0].mq = SR_MQ_CURRENT;
+ devc->channel_meaning[0].unit = SR_UNIT_AMPERE;
+ devc->channel_meaning[0].mqflags = SR_MQFLAG_DC;
+ devc->channel_meaning[0].channels = g_slist_prepend(NULL, chan);
+
+ chan = sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "CH2");
+ devc->channel_meaning[1].mq = SR_MQ_VOLTAGE;
+ devc->channel_meaning[1].unit = SR_UNIT_VOLT;
+ devc->channel_meaning[1].mqflags = SR_MQFLAG_DC;
+ devc->channel_meaning[1].channels = g_slist_prepend(NULL, chan);
+
+ chan = sr_channel_new(sdi, 2, SR_CHANNEL_ANALOG, FALSE, "P");
+ devc->channel_meaning[2].mq = SR_MQ_POWER;
+ devc->channel_meaning[2].unit = SR_UNIT_WATT;
+ devc->channel_meaning[2].mqflags = SR_MQFLAG_RMS;
+ devc->channel_meaning[2].channels = g_slist_prepend(NULL, chan);
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+ struct sr_bt_desc *desc;
+ const char *conn;
+ struct sr_config *src;
+ GSList *l;
+ int ret;
+
+ conn = NULL;
+ for (l = options; l; l = l->next) {
+ src = l->data;
+ switch (src->key) {
+ case SR_CONF_CONN:
+ conn = g_variant_get_string(src->data, NULL);
+ break;
+ }
+ }
+
+ if (!conn)
+ return NULL;
+
+ desc = sr_bt_desc_new();
+ if (!desc)
+ return NULL;
+
+ ret = sr_bt_config_addr_remote(desc, conn);
+ if (ret < 0)
+ goto err;
+
+ /*
+ * These handles where queried with btgatt-client, since the
+ * documentation specifies them in terms of UUIDs.
+ *
+ * service - start: 0x0010, end: 0xffff, type: primary, uuid: 1bc5ffa0-0200-62ab-e411-f254e005dbd4
+ * charac - start: 0x0011, value: 0x0012, props: 0x08, ext_props: 0x0000, uuid: 1bc5ffa1-0200-62ab-e411-f254e005dbd4
+ * descr - handle: 0x0013, uuid: 00002901-0000-1000-8000-00805f9b34fb
+ * charac - start: 0x0014, value: 0x0015, props: 0x10, ext_props: 0x0000, uuid: 1bc5ffa2-0200-62ab-e411-f254e005dbd4
+ * descr - handle: 0x0016, uuid: 00002902-0000-1000-8000-00805f9b34fb
+ * descr - handle: 0x0017, uuid: 00002901-0000-1000-8000-00805f9b34fb
+ */
+ ret = sr_bt_config_notify(desc, 0x0015, 0x0012, 0x0016, 0x0001);
+ if (ret < 0)
+ goto err;
+
+ ret = sr_bt_connect_ble(desc);
+ if (ret < 0)
+ goto err;
+ sr_bt_disconnect(desc);
+
+ struct sr_dev_inst *sdi = g_malloc0(sizeof(struct sr_dev_inst));
+ struct dev_context *devc = g_malloc0(sizeof(struct dev_context));
+
+ sdi->priv = devc;
+ sdi->inst_type = SR_INST_USER;
+ sdi->connection_id = g_strdup(conn);
+ sdi->conn = desc;
+
+ init_dev(sdi);
+
+ return std_scan_complete(di, g_slist_prepend(NULL, sdi));
+
+err:
+ sr_bt_desc_free(desc);
+ return NULL;
+}
+
+static int dev_clear(const struct sr_dev_driver *di)
+{
+ struct drv_context *drvc = di->context;
+ struct sr_dev_inst *sdi;
+ GSList *l;
+
+ if (drvc) {
+ for (l = drvc->instances; l; l = l->next) {
+ sdi = l->data;
+ struct sr_bt_desc *desc = sdi->conn;
+ if (desc)
+ sr_bt_desc_free(desc);
+ sdi->conn = NULL;
+ }
+ }
+
+ return std_dev_clear(di);
+}
+
+static int set_channel1_mean(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_RMS;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_DC;
+
+ return mooshimeter_dmm_set_chooser(sdi, "CH1:ANALYSIS",
+ "CH1:ANALYSIS:MEAN");
+}
+
+static int set_channel1_rms(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_DC;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_RMS;
+
+ return mooshimeter_dmm_set_chooser(sdi, "CH1:ANALYSIS",
+ "CH1:ANALYSIS:RMS");
+}
+
+static int set_channel1_buffer(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ devc->channel_meaning[0].mqflags &= ~(SR_MQFLAG_DC | SR_MQFLAG_RMS);
+
+ return mooshimeter_dmm_set_chooser(sdi, "CH1:ANALYSIS",
+ "CH1:ANALYSIS:BUFFER");
+}
+
+static int set_channel2_mean(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_RMS;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_DC;
+
+ return mooshimeter_dmm_set_chooser(sdi, "CH2:ANALYSIS",
+ "CH2:ANALYSIS:MEAN");
+}
+
+static int set_channel2_rms(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_DC;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_RMS;
+
+ return mooshimeter_dmm_set_chooser(sdi, "CH2:ANALYSIS",
+ "CH2:ANALYSIS:RMS");
+}
+
+static int set_channel2_buffer(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+
+ devc->channel_meaning[1].mqflags &= ~(SR_MQFLAG_DC | SR_MQFLAG_RMS);
+
+ return mooshimeter_dmm_set_chooser(sdi, "CH2:ANALYSIS",
+ "CH2:ANALYSIS:BUFFER");
+}
+
+static void autorange_channel1_current(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH1:RANGE_I",
+ "CH1:MAPPING:CURRENT", value);
+}
+
+static int configure_channel1_current(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH1:MAPPING",
+ "CH1:MAPPING:CURRENT");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH1:RANGE_I",
+ "CH1:MAPPING:CURRENT", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[0] = autorange_channel1_current;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[0] = NULL;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[0].mq = SR_MQ_CURRENT;
+ devc->channel_meaning[0].unit = SR_UNIT_AMPERE;
+
+ return SR_OK;
+}
+
+static void autorange_channel1_temperature(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH1:RANGE_I",
+ "CH1:MAPPING:TEMP", value);
+}
+
+static int configure_channel1_temperature(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH1:MAPPING",
+ "CH1:MAPPING:TEMP");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH1:RANGE_I",
+ "CH1:MAPPING:TEMP", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[0] = autorange_channel1_temperature;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[0] = NULL;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[0].mq = SR_MQ_TEMPERATURE;
+ devc->channel_meaning[0].unit = SR_UNIT_KELVIN;
+
+ return SR_OK;
+}
+
+static void autorange_channel1_auxv(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH1:RANGE_I",
+ "SHARED:AUX_V", value);
+}
+
+static int configure_channel1_auxv(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SHARED", "SHARED:AUX_V");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH1:MAPPING",
+ "CH1:MAPPING:SHARED");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH1:RANGE_I",
+ "SHARED:AUX_V", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[0] = autorange_channel1_auxv;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[0] = NULL;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[0].mq = SR_MQ_VOLTAGE;
+ devc->channel_meaning[0].unit = SR_UNIT_VOLT;
+
+ return SR_OK;
+}
+
+static void autorange_channel1_resistance(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH1:RANGE_I",
+ "SHARED:RESISTANCE", value);
+}
+
+static int configure_channel1_resistance(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SHARED", "SHARED:RESISTANCE");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH1:MAPPING",
+ "CH1:MAPPING:SHARED");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH1:RANGE_I",
+ "SHARED:RESISTANCE", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[0] = autorange_channel1_resistance;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[0] = NULL;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[0].mq = SR_MQ_RESISTANCE;
+ devc->channel_meaning[0].unit = SR_UNIT_OHM;
+
+ return SR_OK;
+}
+
+static void autorange_channel1_diode(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH1:RANGE_I",
+ "SHARED:DIODE", value);
+}
+
+static int configure_channel1_diode(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SHARED", "SHARED:DIODE");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH1:MAPPING",
+ "CH1:MAPPING:SHARED");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH1:RANGE_I",
+ "SHARED:DIODE", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[0] = autorange_channel1_diode;
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[0] = NULL;
+ devc->channel_meaning[0].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[0].mqflags |= SR_MQFLAG_DIODE;
+ devc->channel_meaning[0].mq = SR_MQ_VOLTAGE;
+ devc->channel_meaning[0].unit = SR_UNIT_VOLT;
+
+ return SR_OK;
+}
+
+static void autorange_channel2_voltage(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH2:RANGE_I",
+ "CH2:MAPPING:VOLTAGE", value);
+}
+
+static int configure_channel2_voltage(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH2:MAPPING",
+ "CH2:MAPPING:VOLTAGE");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH2:RANGE_I",
+ "CH2:MAPPING:VOLTAGE", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[1] = autorange_channel2_voltage;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[1] = NULL;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[1].mq = SR_MQ_VOLTAGE;
+ devc->channel_meaning[1].unit = SR_UNIT_VOLT;
+
+ return SR_OK;
+}
+
+static void autorange_channel2_temperature(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH2:RANGE_I",
+ "CH2:MAPPING:TEMP", value);
+}
+
+static int configure_channel2_temperature(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH2:MAPPING",
+ "CH2:MAPPING:TEMP");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH2:RANGE_I",
+ "CH2:MAPPING:TEMP", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[1] = autorange_channel2_temperature;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[1] = NULL;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[1].mq = SR_MQ_TEMPERATURE;
+ devc->channel_meaning[1].unit = SR_UNIT_CELSIUS;
+
+ return SR_OK;
+}
+
+static void autorange_channel2_auxv(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH2:RANGE_I",
+ "SHARED:AUX_V", value);
+}
+
+static int configure_channel2_auxv(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SHARED", "SHARED:AUX_V");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH2:MAPPING",
+ "CH2:MAPPING:SHARED");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH2:RANGE_I",
+ "SHARED:AUX_V", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[1] = autorange_channel2_auxv;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[1] = NULL;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[1].mq = SR_MQ_VOLTAGE;
+ devc->channel_meaning[1].unit = SR_UNIT_VOLT;
+
+ return SR_OK;
+}
+
+static void autorange_channel2_resistance(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH2:RANGE_I",
+ "SHARED:RESISTANCE", value);
+}
+
+static int configure_channel2_resistance(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SHARED", "SHARED:RESISTANCE");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH2:MAPPING",
+ "CH2:MAPPING:SHARED");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH2:RANGE_I",
+ "SHARED:RESISTANCE", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[1] = autorange_channel2_resistance;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[1] = NULL;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_DIODE;
+ devc->channel_meaning[1].mq = SR_MQ_RESISTANCE;
+ devc->channel_meaning[1].unit = SR_UNIT_OHM;
+
+ return SR_OK;
+}
+
+static void autorange_channel2_diode(const struct sr_dev_inst *sdi,
+ float value)
+{
+ mooshimeter_dmm_set_autorange(sdi, "CH2:RANGE_I",
+ "SHARED:DIODE", value);
+}
+
+static int configure_channel2_diode(const struct sr_dev_inst *sdi,
+ float range)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SHARED", "SHARED:DIODE");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "CH2:MAPPING",
+ "CH2:MAPPING:SHARED");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "CH2:RANGE_I",
+ "SHARED:DIODE", range);
+ if (ret != SR_OK)
+ return ret;
+
+ if (range <= 0) {
+ devc->channel_autorange[1] = autorange_channel2_diode;
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_AUTORANGE;
+ } else {
+ devc->channel_autorange[1] = NULL;
+ devc->channel_meaning[1].mqflags &= ~SR_MQFLAG_AUTORANGE;
+ }
+
+ devc->channel_meaning[1].mqflags |= SR_MQFLAG_DIODE;
+ devc->channel_meaning[1].mq = SR_MQ_VOLTAGE;
+ devc->channel_meaning[1].unit = SR_UNIT_VOLT;
+
+ return SR_OK;
+}
+
+/*
+ * Full string: CH1,CH2
+ * Each channel: MODE[:RANGE[:ANALYSIS]]
+ * Channel 1 mode:
+ * Current, A
+ * Temperature, T, K
+ * Resistance, Ohm, W
+ * Diode, D
+ * Aux, LV
+ * Channel 2 mode:
+ * Voltage, V
+ * Temperature, T, K
+ * Resistance, Ohm, W
+ * Diode, D
+ * Aux, LV
+ * Range is the upper bound of the range (e.g. 60 for 0-60 V or 600 for 0-600),
+ * zero or absent for autoranging
+ * Analysis:
+ * Mean, DC
+ * RMS, AC
+ * Buffer, Samples
+ */
+static int apply_channel_config(const struct sr_dev_inst *sdi,
+ const char *config)
+{
+ gchar **channel_config;
+ gchar **parameters;
+ const gchar *param;
+ int ret = SR_ERR;
+ float range;
+ gboolean shared_in_use = FALSE;
+
+ channel_config = g_strsplit_set(config, ",/", -1);
+ if (!channel_config[0])
+ goto err_free_channel_config;
+
+ parameters = g_strsplit_set(channel_config[0], ":;", -1);
+ if (parameters[0] && parameters[0][0]) {
+ range = 0;
+ if (parameters[1])
+ range = g_ascii_strtod(parameters[1], NULL);
+
+ param = parameters[0];
+ if (!g_ascii_strncasecmp(param, "Resistance", 10) ||
+ !g_ascii_strncasecmp(param, "Ohm", 3) ||
+ !g_ascii_strncasecmp(param, "W", 1) ||
+ !g_ascii_strncasecmp(param, "R", 1)) {
+ ret = configure_channel1_resistance(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ shared_in_use = TRUE;
+ } else if (!g_ascii_strncasecmp(param, "Diode", 5) ||
+ !g_ascii_strncasecmp(param, "D", 1)) {
+ ret = configure_channel1_diode(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ shared_in_use = TRUE;
+ } else if (!g_ascii_strncasecmp(param, "Aux", 3) ||
+ !g_ascii_strncasecmp(param, "LV", 2)) {
+ ret = configure_channel1_auxv(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ shared_in_use = TRUE;
+ } else if (!g_ascii_strncasecmp(param, "T", 1) ||
+ !g_ascii_strncasecmp(param, "K", 1)) {
+ ret = configure_channel1_temperature(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strncasecmp(param, "Current", 7) ||
+ !g_ascii_strncasecmp(param, "A", 1) ||
+ *parameters[0]) {
+ ret = configure_channel1_current(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else {
+ sr_info("Unrecognized mode for CH1: %s.", param);
+ ret = configure_channel1_current(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ }
+
+ if (parameters[1] && parameters[2]) {
+ param = parameters[2];
+ if (!g_ascii_strcasecmp(param, "RMS") ||
+ !g_ascii_strcasecmp(param, "AC")) {
+ ret = set_channel1_rms(sdi);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strcasecmp(param, "Buffer") ||
+ !g_ascii_strcasecmp(param, "Samples")) {
+ ret = set_channel1_buffer(sdi);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else {
+ ret = set_channel1_mean(sdi);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ }
+ }
+ }
+ g_strfreev(parameters);
+
+ if (!channel_config[1]) {
+ g_strfreev(channel_config);
+ return SR_OK;
+ }
+
+ parameters = g_strsplit_set(channel_config[1], ":;", -1);
+ if (parameters[0] && parameters[0][0]) {
+ range = 0;
+ if (parameters[1])
+ range = g_ascii_strtod(parameters[1], NULL);
+
+ param = parameters[0];
+ if (!g_ascii_strncasecmp(param, "Resistance", 10) ||
+ !g_ascii_strncasecmp(param, "Ohm", 3) ||
+ !g_ascii_strncasecmp(param, "W", 1) ||
+ !g_ascii_strncasecmp(param, "R", 1)) {
+ if (shared_in_use) {
+ ret = SR_ERR;
+ goto err_free_parameters;
+ }
+ ret = configure_channel2_resistance(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strncasecmp(param, "Diode", 5) ||
+ !g_ascii_strncasecmp(param, "D", 1)) {
+ if (shared_in_use) {
+ ret = SR_ERR;
+ goto err_free_parameters;
+ }
+ ret = configure_channel2_diode(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strncasecmp(param, "Aux", 3) ||
+ !g_ascii_strncasecmp(param, "LV", 2)) {
+ if (shared_in_use) {
+ ret = SR_ERR;
+ goto err_free_parameters;
+ }
+ ret = configure_channel2_auxv(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strncasecmp(param, "T", 1) ||
+ !g_ascii_strncasecmp(param, "K", 1)) {
+ ret = configure_channel2_temperature(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strncasecmp(param, "V", 1) ||
+ !param[0]) {
+ ret = configure_channel2_voltage(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else {
+ sr_info("Unrecognized mode for CH2: %s.", param);
+ ret = configure_channel2_voltage(sdi, range);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ }
+
+ if (parameters[1] && parameters[2]) {
+ param = parameters[2];
+ if (!g_ascii_strcasecmp(param, "RMS") ||
+ !g_ascii_strcasecmp(param, "AC")) {
+ ret = set_channel2_rms(sdi);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else if (!g_ascii_strcasecmp(param, "Buffer") ||
+ !g_ascii_strcasecmp(param, "Samples")) {
+ ret = set_channel2_buffer(sdi);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ } else {
+ ret = set_channel2_mean(sdi);
+ if (ret != SR_OK)
+ goto err_free_parameters;
+ }
+ }
+ }
+ g_strfreev(parameters);
+
+ g_strfreev(channel_config);
+ return SR_OK;
+
+err_free_parameters:
+ g_strfreev(parameters);
+err_free_channel_config:
+ g_strfreev(channel_config);
+ return ret;
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+ int ret;
+
+ ret = mooshimeter_dmm_open(sdi);
+ if (ret != SR_OK)
+ return ret;
+
+ sdi->status = SR_ST_INACTIVE;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SAMPLING:TRIGGER",
+ "SAMPLING:TRIGGER:OFF");
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "SAMPLING:RATE",
+ "SAMPLING:RATE", 125);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_larger_number(sdi, "SAMPLING:DEPTH",
+ "SAMPLING:DEPTH", 64);
+ if (ret != SR_OK)
+ return ret;
+
+ /* Looks like these sometimes get set to 8, somehow? */
+ ret = mooshimeter_dmm_set_integer(sdi, "CH1:BUF_BPS", 24);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = mooshimeter_dmm_set_integer(sdi, "CH2:BUF_BPS", 24);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = configure_channel1_current(sdi, 0);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = set_channel1_mean(sdi);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = configure_channel2_voltage(sdi, 0);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = set_channel2_mean(sdi);
+ if (ret != SR_OK)
+ return ret;
+
+ sdi->status = SR_ST_ACTIVE;
+
+ return SR_OK;
+}
+
+static int dev_close(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+
+ sdi->status = SR_ST_INACTIVE;
+
+ g_slist_free(devc->channel_meaning[0].channels);
+ devc->channel_meaning[0].channels = NULL;
+
+ g_slist_free(devc->channel_meaning[1].channels);
+ devc->channel_meaning[1].channels = NULL;
+
+ g_slist_free(devc->channel_meaning[2].channels);
+ devc->channel_meaning[2].channels = NULL;
+
+ return mooshimeter_dmm_close(sdi);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+ float value;
+
+ (void)cg;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ ret = mooshimeter_dmm_get_chosen_number(sdi, "SAMPLING:RATE",
+ "SAMPLING:RATE", &value);
+ if (ret != SR_OK)
+ return ret;
+ *data = g_variant_new_uint64((guint64)value);
+ return SR_OK;
+ case SR_CONF_AVG_SAMPLES:
+ ret = mooshimeter_dmm_get_chosen_number(sdi, "SAMPLING:DEPTH",
+ "SAMPLING:DEPTH", &value);
+ if (ret != SR_OK)
+ return ret;
+ *data = g_variant_new_uint64((guint64)value);
+ return SR_OK;
+ case SR_CONF_CHANNEL_CONFIG:
+ return SR_ERR_NA;
+ default:
+ break;
+ }
+
+ return sr_sw_limits_config_get(&devc->limits, key, data);
+}
+
+static int config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg)
+{
+ struct dev_context *devc = sdi->priv;
+
+ (void)cg;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ return mooshimeter_dmm_set_larger_number(sdi, "SAMPLING:RATE",
+ "SAMPLING:RATE", g_variant_get_uint64(data));
+ case SR_CONF_AVG_SAMPLES:
+ return mooshimeter_dmm_set_larger_number(sdi, "SAMPLING:DEPTH",
+ "SAMPLING:DEPTH", g_variant_get_uint64(data));
+ case SR_CONF_CHANNEL_CONFIG:
+ return apply_channel_config(sdi, g_variant_get_string(data, NULL));
+ default:
+ break;
+ }
+
+ return sr_sw_limits_config_set(&devc->limits, key, data);
+}
+
+static int config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg)
+{
+ int ret;
+ float *values;
+ size_t count;
+ uint64_t *integers;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ ret = mooshimeter_dmm_get_available_number_choices(sdi,
+ "SAMPLING:RATE", &values, &count);
+ if (ret != SR_OK)
+ return ret;
+ integers = g_malloc(sizeof(uint64_t) * count);
+ for (size_t i = 0; i < count; i++)
+ integers[i] = (uint64_t)values[i];
+ g_free(values);
+ *data = std_gvar_samplerates(integers, count);
+ g_free(integers);
+ return SR_OK;
+ case SR_CONF_AVG_SAMPLES:
+ ret = mooshimeter_dmm_get_available_number_choices(sdi,
+ "SAMPLING:DEPTH", &values, &count);
+ if (ret != SR_OK)
+ return ret;
+ integers = g_malloc(sizeof(uint64_t) * count);
+ for (size_t i = 0; i < count; i++)
+ integers[i] = (uint64_t)values[i];
+ g_free(values);
+ *data = std_gvar_array_u64(integers, count);
+ g_free(integers);
+ return SR_OK;
+ case SR_CONF_CHANNEL_CONFIG:
+ return SR_ERR_NA;
+ default:
+ break;
+ }
+
+ return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ int ret;
+
+ ret = mooshimeter_dmm_set_chooser(sdi, "SAMPLING:TRIGGER",
+ "SAMPLING:TRIGGER:CONTINUOUS");
+ if (ret)
+ return ret;
+
+ sr_sw_limits_acquisition_start(&devc->limits);
+ std_session_send_df_header(sdi);
+
+ sr_session_source_add(sdi->session, -1, 0, 10000,
+ mooshimeter_dmm_heartbeat, (void *)sdi);
+
+ /* The Bluetooth socket isn't exposed, so just poll for data. */
+ sr_session_source_add(sdi->session, -2, 0, 50,
+ mooshimeter_dmm_poll, (void *)sdi);
+
+ devc->enable_value_stream = TRUE;
+
+ return SR_OK;
+}
+
+static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+
+ sr_session_source_remove(sdi->session, -1);
+ sr_session_source_remove(sdi->session, -2);
+ devc->enable_value_stream = FALSE;
+
+ mooshimeter_dmm_set_chooser(sdi, "SAMPLING:TRIGGER",
+ "SAMPLING:TRIGGER:OFF");
+
+ return SR_OK;
+}
+
+static struct sr_dev_driver mooshimeter_dmm_driver_info = {
+ .name = "mooshimeter-dmm",
+ .longname = "Mooshimeter DMM",
+ .api_version = 1,
+ .init = std_init,
+ .cleanup = std_cleanup,
+ .scan = scan,
+ .dev_list = std_dev_list,
+ .dev_clear = dev_clear,
+ .config_get = config_get,
+ .config_set = config_set,
+ .config_list = config_list,
+ .dev_open = dev_open,
+ .dev_close = dev_close,
+ .dev_acquisition_start = dev_acquisition_start,
+ .dev_acquisition_stop = dev_acquisition_stop,
+ .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(mooshimeter_dmm_driver_info);
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Derek Hageman <hageman@inthat.cloud>
+ *
+ * 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 <gio/gio.h>
+#include <math.h>
+#include <string.h>
+#include "protocol.h"
+
+/*
+ * The Mooshimeter protocol is broken down into several layers in a
+ * communication stack.
+ *
+ * The lowest layer is the BLE GATT stack, which provides two characteristics:
+ * one to write packets to the meter and one to receive them from it. The
+ * MTU for a packet in either direction is 20 bytes. This is implemented
+ * in the GATT abstraction, so we can talk to it via simple write commands
+ * and a read callback.
+ *
+ *
+ * The next layer is the serial stream: each BLE packet in either direction
+ * has a 1-byte header of a sequence number. Despite what the documentation
+ * says, this is present in both directions (not just meter output) and is
+ * NOT reset on the meter output on BLE connection. So the implementation
+ * here needs to provide an output sequence number and incoming reassembly
+ * for out of order packets (I haven't actually observed this, but
+ * supposedly it happens, which is why the sequence number is present).
+ * So the structure of packets received looks like:
+ *
+ * | 1 byte | 1-19 bytes |
+ * |--------|-------------|
+ * | SeqNum | Serial Data |
+ *
+ *
+ * On top of the serial layer is the "config tree" layer. This is how
+ * the meter actually exposes data and configuration. The tree itself
+ * is composed of nodes, each with a string name, data type, and a list
+ * of children (zero or more). For value containing (non-informational)
+ * nodes, they also contain a 7-bit unique identifier. Access to the
+ * config tree is provided by packets on the serial stream, each packet
+ * has a 1-byte header, where the uppermost bit (0x80) is set when writing
+ * (i.e. never by the meter) and the remaining 7 bits are the node identifier.
+ * The length of the packets varies based on the datatype of the tree node.
+ * This means that any lost/dropped packets can make the stream unrecoverable
+ * (i.e. there's no defined sync method other than reconnection). Packets
+ * are emitted by the meter in response to a read or write command (write
+ * commands simply back the value) and at unsolicited times by the meter
+ * (e.g. continuous sampling and periodic battery voltage). A read packet
+ * send to the meter looks like:
+ *
+ * | 1 bit | 7 bits |
+ * |-------|--------|
+ * | 0 | NodeID |
+ *
+ * In response to the read, the meter will send:
+ *
+ * | 1 bit | 7 bits | 1-N bytes |
+ * |-------|--------|-----------|
+ * | 0 | NodeID | NodeValue |
+ *
+ * A write packet sent to the meter:
+ *
+ * | 1 bit | 7 bits | 1-N bytes |
+ * |-------|--------|-----------|
+ * | 1 | NodeID | NodeValue |
+ *
+ * In response to the write, the meter will send a read response:
+ *
+ * | 1 bit | 7 bits | 1-N bytes |
+ * |-------|--------|-----------|
+ * | 0 | NodeID | NodeValue |
+ *
+ *
+ * For the data in the tree, all values are little endian (least significant
+ * bytes first). The supported type codes are:
+ *
+ * | Code | Description | Wire Format |
+ * |------|-------------|----------------------------------------|
+ * | 0 | Plain | |
+ * | 1 | Link | |
+ * | 2 | Chooser | uint8_t |
+ * | 3 | U8 | uint8_t |
+ * | 4 | U16 | uint16_t |
+ * | 5 | U32 | uint32_t |
+ * | 6 | S8 | int8_t |
+ * | 7 | S16 | int16_t |
+ * | 8 | S32 | int32_t |
+ * | 9 | String | uint16_t length; char value[length] |
+ * | 10 | Binary | uint16_t length; uint8_t value[length] |
+ * | 11 | Float | float |
+ *
+ * Plain and Link nodes are present to provide information and/or choices
+ * but do not provide commands codes for direct access (see serialization
+ * below). Chooser nodes are written with indices described by their Plain
+ * type children (e.g. to select a choice identified by the second child
+ * of a chooser, write 1 to the chooser node itself).
+ *
+ * On initial connection only three nodes at fixed identifiers are available:
+ *
+ * | Node | ID | Type |
+ * |------------------|----|--------|
+ * | ADMIN:CRC32 | 0 | U32 |
+ * | ADMIN:TREE | 1 | Binary |
+ * | ADMIN:DIAGNOSTIC | 2 | String |
+ *
+ *
+ * The handshake sequence is to read the contents of ADMIN:TREE, which contains
+ * the zlib compressed tree serialization, then write the CRC of the compressed
+ * data back to ADMIN:CRC32 (which the meter will echo back). Only after
+ * that is done will the meter accept access to the rest of the tree.
+ *
+ * After zlib decompression the tree serialization is as follows:
+ *
+ * | Type | Description |
+ * |--------------|-------------------------------------|
+ * | uint8_t | The node data type code from above |
+ * | uint8_t | Name length |
+ * | char[length] | Node name (e.g. "ADMIN" or "CRC32") |
+ * | uint8_t | Number of children |
+ * | Node[count] | Child serialization (length varies) |
+ *
+ * Once the tree has been deserialized, each node needs its identifier
+ * assigned. This is a depth first tree walk, assigning sequential identifiers
+ * first the the current node (if it needs one), then repeating recursively
+ * for each of its children. Plain and Link nodes are skipped in assignment
+ * but not the walk (so the recursion still happens, but the identifier
+ * is not incremented).
+ *
+ *
+ * So, for example a write to the ADMIN:CRC32 as part of the handshake would
+ * be a write by us (the host):
+ *
+ * | SerSeq | NodeID | U32 (CRC) |
+ * | 1 byte | 1 byte | 4 bytes |
+ * ---------|--------|------------|
+ * | 0x01 | 0x80 | 0xDEADBEEF |
+ *
+ * The meter will respond with a packet like:
+ *
+ * | SerSeq | NodeID | U32 (CRC) |
+ * | 1 byte | 1 byte | 4 bytes |
+ * ---------|--------|------------|
+ * | 0x42 | 0x00 | 0xDEADBEEF |
+ *
+ * A spontaneous error from the meter (e.g. in response to a bad packet)
+ * can be emitted like:
+ *
+ * | SerSeq | NodeID | U16 (len) | String |
+ * | 1 byte | 1 byte | 2 bytes | len (=8) bytes |
+ * ---------|--------|------------|------------------|
+ * | 0xAB | 0x20 | 0x0008 | BAD\x20DATA |
+ *
+ *
+ * The config tree at the time of writing looks like:
+ *
+ * <ROOT> (PLAIN)
+ * ADMIN (PLAIN)
+ * CRC32 (U32) = 0
+ * TREE (BIN) = 1
+ * DIAGNOSTIC (STR) = 2
+ * PCB_VERSION (U8) = 3
+ * NAME (STR) = 4
+ * TIME_UTC (U32) = 5
+ * TIME_UTC_MS (U16) = 6
+ * BAT_V (FLT) = 7
+ * REBOOT (CHOOSER) = 8
+ * NORMAL (PLAIN)
+ * SHIPMODE (PLAIN)
+ * SAMPLING (PLAIN)
+ * RATE (CHOOSER) = 9
+ * 125 (PLAIN)
+ * 250 (PLAIN)
+ * 500 (PLAIN)
+ * 1000 (PLAIN)
+ * 2000 (PLAIN)
+ * 4000 (PLAIN)
+ * 8000 (PLAIN)
+ * DEPTH (CHOOSER) = 10
+ * 32 (PLAIN)
+ * 64 (PLAIN)
+ * 128 (PLAIN)
+ * 256 (PLAIN)
+ * TRIGGER (CHOOSER) = 11
+ * OFF (PLAIN)
+ * SINGLE (PLAIN)
+ * CONTINUOUS (PLAIN)
+ * LOG (PLAIN)
+ * ON (U8) = 12
+ * INTERVAL (U16) = 13
+ * STATUS (U8) = 14
+ * POLLDIR (U8) = 15
+ * INFO (PLAIN)
+ * INDEX (U16) = 16
+ * END_TIME (U32) = 17
+ * N_BYTES (U32) = 18
+ * STREAM (PLAIN)
+ * INDEX (U16) = 19
+ * OFFSET (U32) = 20
+ * DATA (BIN) = 21
+ * CH1 (PLAIN)
+ * MAPPING (CHOOSER) = 22
+ * CURRENT (PLAIN)
+ * 10 (PLAIN)
+ * TEMP (PLAIN)
+ * 350 (PLAIN)
+ * SHARED (LINK)
+ * RANGE_I (U8) = 23
+ * ANALYSIS (CHOOSER) = 24
+ * MEAN (PLAIN)
+ * RMS (PLAIN)
+ * BUFFER (PLAIN)
+ * VALUE (FLT) = 25
+ * OFFSET (FLT) = 26
+ * BUF (BIN) = 27
+ * BUF_BPS (U8) = 28
+ * BUF_LSB2NATIVE (FLT) = 29
+ * CH2 (PLAIN)
+ * MAPPING (CHOOSER) = 30
+ * VOLTAGE (PLAIN)
+ * 60 (PLAIN)
+ * 600 (PLAIN)
+ * TEMP (PLAIN)
+ * 350 (PLAIN)
+ * SHARED (LINK)
+ * RANGE_I (U8) = 31
+ * ANALYSIS (CHOOSER) = 32
+ * MEAN (PLAIN)
+ * RMS (PLAIN)
+ * BUFFER (PLAIN)
+ * VALUE (FLT) = 33
+ * OFFSET (FLT) = 34
+ * BUF (BIN) = 35
+ * BUF_BPS (U8) = 36
+ * BUF_LSB2NATIVE (FLT) = 37
+ * SHARED (CHOOSER) = 38
+ * AUX_V (PLAIN)
+ * 0.1 (PLAIN)
+ * 0.3 (PLAIN)
+ * 1.2 (PLAIN)
+ * RESISTANCE (PLAIN)
+ * 1000.0 (PLAIN)
+ * 10000.0 (PLAIN)
+ * 100000.0 (PLAIN)
+ * 1000000.0 (PLAIN)
+ * 10000000.0 (PLAIN)
+ * DIODE (PLAIN)
+ * 1.2 (PLAIN)
+ * REAL_PWR (FLT) = 39
+ */
+
+static struct config_tree_node *lookup_tree_path(struct dev_context *devc,
+ const char *path)
+{
+ struct config_tree_node *current = &devc->tree_root;
+ struct config_tree_node *next;
+ const char *end;
+ size_t length;
+
+ for (;;) {
+ end = strchr(path, ':');
+ if (!end)
+ length = strlen(path);
+ else
+ length = end - path;
+
+ next = NULL;
+ for (size_t i = 0; i < current->count_children; i++) {
+ if (!current->children[i].name)
+ continue;
+ if (strlen(current->children[i].name) != length)
+ continue;
+ if (g_ascii_strncasecmp(path,
+ current->children[i].name,
+ length)) {
+ continue;
+ }
+
+ next = ¤t->children[i];
+ }
+ if (!next)
+ return NULL;
+ if (!end)
+ return next;
+
+ path = end + 1;
+ current = next;
+ }
+}
+
+static int lookup_chooser_index(struct dev_context *devc, const char *path)
+{
+ struct config_tree_node *node;
+
+ node = lookup_tree_path(devc, path);
+ if (!node)
+ return -1;
+
+ return (int)node->index_in_parent;
+}
+
+static gboolean update_tree_data(struct config_tree_node *node,
+ GByteArray *contents)
+{
+ guint len;
+ switch (node->type) {
+ case TREE_NODE_DATATYPE_PLAIN:
+ case TREE_NODE_DATATYPE_LINK:
+ sr_err("Update for dataless node.");
+ g_byte_array_remove_range(contents, 0, 2);
+ return TRUE;
+ case TREE_NODE_DATATYPE_CHOOSER:
+ case TREE_NODE_DATATYPE_U8:
+ node->value.i = R8(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 2);
+ break;
+ case TREE_NODE_DATATYPE_U16:
+ if (contents->len < 3)
+ return FALSE;
+ node->value.i = RL16(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 3);
+ break;
+ case TREE_NODE_DATATYPE_U32:
+ if (contents->len < 5)
+ return FALSE;
+ node->value.i = RL32(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 5);
+ break;
+ case TREE_NODE_DATATYPE_S8:
+ node->value.i = (int8_t)R8(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 2);
+ break;
+ case TREE_NODE_DATATYPE_S16:
+ if (contents->len < 3)
+ return FALSE;
+ node->value.i = RL16S(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 3);
+ break;
+ case TREE_NODE_DATATYPE_S32:
+ if (contents->len < 5)
+ return FALSE;
+ node->value.i = RL32S(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 5);
+ break;
+ case TREE_NODE_DATATYPE_STRING:
+ case TREE_NODE_DATATYPE_BINARY:
+ if (contents->len < 3)
+ return FALSE;
+ len = RL16(contents->data + 1);
+ if (contents->len < 3 + len)
+ return FALSE;
+ g_byte_array_set_size(node->value.b, len);
+ memcpy(node->value.b->data, contents->data + 3, len);
+ g_byte_array_remove_range(contents, 0, 3 + len);
+ break;
+ case TREE_NODE_DATATYPE_FLOAT:
+ if (contents->len < 5)
+ return FALSE;
+ node->value.f = RLFL(contents->data + 1);
+ g_byte_array_remove_range(contents, 0, 5);
+ break;
+ }
+
+ node->update_number++;
+
+ if (node->on_update)
+ (*node->on_update)(node, node->on_update_param);
+
+ return TRUE;
+}
+
+static gboolean incoming_frame(struct packet_rx *rx,
+ const void *data, guint count)
+{
+ const guint8 *bytes = data;
+ int seq, ahead;
+ GByteArray *ba;
+ GSList *target = NULL;
+
+ if (!count)
+ return FALSE;
+
+ seq = bytes[0];
+ if (rx->sequence_number < 0) {
+ rx->sequence_number = (seq + 1) & 0xFF;
+ g_byte_array_append(rx->contents, bytes + 1, count - 1);
+ return TRUE;
+ } else if (rx->sequence_number == seq) {
+ rx->sequence_number = (seq + 1) & 0xFF;
+ g_byte_array_append(rx->contents, data + 1, count - 1);
+
+ while (rx->reorder_buffer && rx->reorder_buffer->data) {
+ rx->sequence_number = (rx->sequence_number + 1) & 0xFF;
+
+ ba = rx->reorder_buffer->data;
+ g_byte_array_append(rx->contents, ba->data, ba->len);
+ g_byte_array_free(ba, TRUE);
+ target = rx->reorder_buffer;
+ rx->reorder_buffer = rx->reorder_buffer->next;
+ g_slist_free_1(target);
+ }
+ return TRUE;
+ } else {
+ ahead = seq - rx->sequence_number;
+ if (ahead < 0)
+ ahead += 256;
+ if (!rx->reorder_buffer)
+ rx->reorder_buffer = g_slist_alloc();
+ target = rx->reorder_buffer;
+ for (--ahead; ahead > 0; --ahead) {
+ if (!target->next)
+ target->next = g_slist_alloc();
+ target = target->next;
+ }
+ if (target->data)
+ g_byte_array_free(target->data, TRUE);
+ target->data = g_byte_array_sized_new(count);
+ g_byte_array_append(target->data, data + 1, count - 1);
+ return TRUE;
+ }
+}
+
+static void consume_packets(struct dev_context *devc)
+{
+ uint8_t id;
+ struct config_tree_node *target;
+
+ if (devc->rx.contents->len < 2)
+ return;
+
+ id = devc->rx.contents->data[0];
+ id &= 0x7F;
+ target = devc->tree_id_lookup[id];
+
+ if (!target) {
+ sr_err("Command %hhu code does not map to a known node.", id);
+ g_byte_array_remove_index(devc->rx.contents, 0);
+ return consume_packets(devc);
+ }
+
+ if (!update_tree_data(target, devc->rx.contents))
+ return;
+
+ return consume_packets(devc);
+}
+
+static int notify_cb(void *cb_data, uint8_t *data, size_t dlen)
+{
+ const struct sr_dev_inst *sdi = cb_data;
+ struct dev_context *devc = sdi->priv;
+
+ if (!incoming_frame(&devc->rx, data, (guint)dlen))
+ return -1;
+
+ consume_packets(devc);
+
+ return 0;
+}
+
+static int write_frame(const struct sr_dev_inst *sdi,
+ const void *frame, size_t length)
+{
+ struct sr_bt_desc *desc = sdi->conn;
+
+ if (sr_bt_write(desc, frame, length) != (ssize_t)length)
+ return SR_ERR;
+
+ return SR_OK;
+}
+
+static int poll_tree_value(const struct sr_dev_inst *sdi,
+ struct config_tree_node *node)
+{
+ struct dev_context *devc = sdi->priv;
+
+ uint8_t frame[2];
+ W8(&frame[0], devc->tx.sequence_number);
+ W8(&frame[1], node->id);
+
+ devc->tx.sequence_number = (devc->tx.sequence_number + 1) & 0xFF;
+
+ return write_frame(sdi, frame, 2);
+}
+
+static void set_tree_integer(const struct sr_dev_inst *sdi,
+ struct config_tree_node *node, int32_t value)
+{
+ struct dev_context *devc = sdi->priv;
+ uint8_t frame[20];
+ size_t length;
+
+ W8(&frame[0], devc->tx.sequence_number);
+ W8(&frame[1], 0x80 | node->id);
+
+ length = 2;
+
+ switch (node->type) {
+ case TREE_NODE_DATATYPE_PLAIN:
+ case TREE_NODE_DATATYPE_LINK:
+ sr_err("Set attempted for dataless node.");
+ return;
+ case TREE_NODE_DATATYPE_CHOOSER:
+ case TREE_NODE_DATATYPE_U8:
+ node->value.i = value;
+ W8(&frame[length], value);
+ length += 1;
+ break;
+ case TREE_NODE_DATATYPE_U16:
+ node->value.i = value;
+ WL16(&frame[length], value);
+ length += 2;
+ break;
+ case TREE_NODE_DATATYPE_U32:
+ node->value.i = value;
+ WL32(&frame[length], value);
+ length += 4;
+ break;
+ case TREE_NODE_DATATYPE_S8:
+ node->value.i = value;
+ W8(&frame[length], value);
+ length += 1;
+ break;
+ case TREE_NODE_DATATYPE_S16:
+ node->value.i = value;
+ WL16(&frame[length], value);
+ length += 2;
+ break;
+ case TREE_NODE_DATATYPE_S32:
+ node->value.i = value;
+ WL32(&frame[length], value);
+ length += 4;
+ break;
+ case TREE_NODE_DATATYPE_STRING:
+ case TREE_NODE_DATATYPE_BINARY:
+ case TREE_NODE_DATATYPE_FLOAT:
+ return;
+ }
+
+ devc->tx.sequence_number = (devc->tx.sequence_number + 1) & 0xFF;
+ write_frame(sdi, frame, length);
+}
+
+static int32_t get_tree_integer(struct config_tree_node *node)
+{
+ switch (node->type) {
+ case TREE_NODE_DATATYPE_PLAIN:
+ case TREE_NODE_DATATYPE_LINK:
+ sr_err("Read attempted for dataless node.");
+ return 0;
+ case TREE_NODE_DATATYPE_CHOOSER:
+ case TREE_NODE_DATATYPE_U8:
+ case TREE_NODE_DATATYPE_U16:
+ case TREE_NODE_DATATYPE_U32:
+ case TREE_NODE_DATATYPE_S8:
+ case TREE_NODE_DATATYPE_S16:
+ case TREE_NODE_DATATYPE_S32:
+ return node->value.i;
+ case TREE_NODE_DATATYPE_FLOAT:
+ return (int)node->value.f;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void tree_diagnostic_updated(struct config_tree_node *node, void *param)
+{
+ (void)param;
+
+ if (!node->value.b->len) {
+ sr_warn("Mooshimeter error with no information.");
+ return;
+ }
+
+ if (node->value.b->data[node->value.b->len]) {
+ g_byte_array_set_size(node->value.b, node->value.b->len + 1);
+ node->value.b->data[node->value.b->len - 1] = 0;
+ }
+
+ sr_warn("Mooshimeter error: %s.", node->value.b->data);
+}
+
+static void chX_value_update(struct config_tree_node *node,
+ struct sr_dev_inst *sdi, int channel)
+{
+ struct dev_context *devc = sdi->priv;
+ float value;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
+ struct sr_analog_encoding encoding;
+ struct sr_analog_meaning meaning;
+ struct sr_analog_spec spec;
+
+ if (!devc->enable_value_stream)
+ return;
+
+ if (!((struct sr_channel *)devc->channel_meaning[channel].
+ channels->data)->enabled) {
+ return;
+ }
+
+ if (node->type != TREE_NODE_DATATYPE_FLOAT)
+ return;
+ value = node->value.f;
+
+ sr_spew("Received value for channel %d = %g.", channel, value);
+
+ /*
+ * Could do significant digit calculations based on the
+ * effective number of effective bits (sample rate, buffer size, etc),
+ * but does it matter?
+ * (see https://github.com/mooshim/Mooshimeter-AndroidApp/blob/94a20a2d42f6af9975ad48591caa6a17130ca53b/app/src/main/java/com/mooshim/mooshimeter/devices/MooshimeterDevice.java#L691 )
+ */
+ sr_analog_init(&analog, &encoding, &meaning, &spec, 2);
+
+ memcpy(analog.meaning, &devc->channel_meaning[channel],
+ sizeof(struct sr_analog_meaning));
+ analog.num_samples = 1;
+ analog.data = &value;
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ sr_session_send(sdi, &packet);
+
+ if (devc->channel_autorange[channel])
+ (*devc->channel_autorange[channel])(sdi, value);
+
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
+ if (sr_sw_limits_check(&devc->limits))
+ sr_dev_acquisition_stop(sdi);
+}
+
+static void chX_buffer_update(struct config_tree_node *node,
+ struct sr_dev_inst *sdi, int channel)
+{
+ struct dev_context *devc = sdi->priv;
+ uint32_t bits_per_sample = devc->buffer_bps[channel];
+ float output_scalar = devc->buffer_lsb2native[channel];
+ uint32_t bytes_per_sample;
+ const uint8_t *raw;
+ size_t size;
+ size_t number_of_samples;
+ int32_t unscaled = 0;
+ int32_t sign_bit;
+ int32_t sign_mask;
+ float converted_value = 0;
+ float maximum_value = 0;
+ float *values;
+ float *output_value;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
+ struct sr_analog_encoding encoding;
+ struct sr_analog_meaning meaning;
+ struct sr_analog_spec spec;
+
+ if (!devc->enable_value_stream)
+ return;
+
+ if (!((struct sr_channel *)devc->channel_meaning[channel].
+ channels->data)->enabled) {
+ return;
+ }
+
+ if (!bits_per_sample)
+ return;
+ if (node->type != TREE_NODE_DATATYPE_BINARY)
+ return;
+ raw = node->value.b->data;
+ size = node->value.b->len;
+ if (!size)
+ return;
+
+ bytes_per_sample = bits_per_sample / 8;
+ if (bits_per_sample % 8 != 0)
+ bytes_per_sample++;
+ if (bytes_per_sample > 4)
+ return;
+ number_of_samples = size / bytes_per_sample;
+ if (!number_of_samples)
+ return;
+
+ sr_analog_init(&analog, &encoding, &meaning, &spec, 0);
+
+ values = g_new0(float, number_of_samples);
+ output_value = values;
+
+ memcpy(analog.meaning, &devc->channel_meaning[channel],
+ sizeof(struct sr_analog_meaning));
+ analog.num_samples = number_of_samples;
+ analog.data = output_value;
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+
+ sr_spew("Received buffer for channel %d with %u bytes (%u samples).",
+ channel, (unsigned int)size, (unsigned int)number_of_samples);
+
+ sign_bit = 1 << (bits_per_sample - 1);
+ sign_mask = sign_bit - 1;
+ for (; size >= bytes_per_sample; size -= bytes_per_sample,
+ raw += bytes_per_sample, output_value++) {
+ switch (bytes_per_sample) {
+ case 1:
+ unscaled = R8(raw);
+ break;
+ case 2:
+ unscaled = RL16(raw);
+ break;
+ case 3:
+ unscaled = ((uint32_t)raw[0]) |
+ (((uint32_t)raw[1]) << 8) |
+ (((uint32_t)raw[2]) << 16);
+ break;
+ case 4:
+ unscaled = RL32(raw);
+ break;
+ default:
+ break;
+ }
+
+ unscaled = (unscaled & sign_mask) - (unscaled & sign_bit);
+ converted_value = (float)unscaled * output_scalar;
+ *output_value = converted_value;
+ if (fabsf(converted_value) > maximum_value)
+ maximum_value = fabsf(maximum_value);
+ }
+
+ sr_session_send(sdi, &packet);
+
+ g_free(values);
+
+ if (devc->channel_autorange[channel])
+ (*devc->channel_autorange[channel])(sdi, maximum_value);
+
+ sr_sw_limits_update_samples_read(&devc->limits, number_of_samples);
+ if (sr_sw_limits_check(&devc->limits))
+ sr_dev_acquisition_stop(sdi);
+}
+
+static void ch1_value_update(struct config_tree_node *node, void *param)
+{
+ chX_value_update(node, param, 0);
+}
+
+static void ch2_value_update(struct config_tree_node *node, void *param)
+{
+ chX_value_update(node, param, 1);
+}
+
+static void power_value_update(struct config_tree_node *node, void *param)
+{
+ chX_value_update(node, param, 2);
+}
+
+static void ch1_buffer_update(struct config_tree_node *node, void *param)
+{
+ chX_buffer_update(node, param, 0);
+}
+
+static void ch2_buffer_update(struct config_tree_node *node, void *param)
+{
+ chX_buffer_update(node, param, 1);
+}
+
+static void ch1_buffer_bps_update(struct config_tree_node *node, void *param)
+{
+ const struct sr_dev_inst *sdi = param;
+ struct dev_context *devc = sdi->priv;
+ devc->buffer_bps[0] = (uint32_t)get_tree_integer(node);
+}
+
+static void ch2_buffer_bps_update(struct config_tree_node *node, void *param)
+{
+ const struct sr_dev_inst *sdi = param;
+ struct dev_context *devc = sdi->priv;
+ devc->buffer_bps[1] = (uint32_t)get_tree_integer(node);
+}
+
+static void ch1_buffer_lsb2native_update(struct config_tree_node *node,
+ void *param)
+{
+ const struct sr_dev_inst *sdi = param;
+ struct dev_context *devc = sdi->priv;
+ if (node->type != TREE_NODE_DATATYPE_BINARY)
+ return;
+ devc->buffer_lsb2native[0] = node->value.f;
+}
+
+static void ch2_buffer_lsb2native_update(struct config_tree_node *node,
+ void *param)
+{
+ const struct sr_dev_inst *sdi = param;
+ struct dev_context *devc = sdi->priv;
+ if (node->type != TREE_NODE_DATATYPE_BINARY)
+ return;
+ devc->buffer_lsb2native[1] = node->value.f;
+}
+
+static void release_tree_node(struct config_tree_node *node)
+{
+ g_free(node->name);
+
+ switch (node->type) {
+ case TREE_NODE_DATATYPE_STRING:
+ case TREE_NODE_DATATYPE_BINARY:
+ g_byte_array_free(node->value.b, TRUE);
+ break;
+ default:
+ break;
+ }
+
+ for (size_t i = 0; i < node->count_children; i++)
+ release_tree_node(node->children + i);
+ g_free(node->children);
+}
+
+static void allocate_startup_tree(struct dev_context *devc)
+{
+ struct config_tree_node *node;
+
+ node = &devc->tree_root;
+ node->name = g_strdup("ADMIN");
+ node->type = TREE_NODE_DATATYPE_PLAIN;
+ node->count_children = 3;
+ node->children = g_new0(struct config_tree_node, node->count_children);
+
+ node = &devc->tree_root.children[0];
+ node->name = g_strdup("CRC");
+ node->type = TREE_NODE_DATATYPE_U32;
+ node->id = 0;
+ devc->tree_id_lookup[node->id] = node;
+
+ node = &devc->tree_root.children[1];
+ node->name = g_strdup("TREE");
+ node->type = TREE_NODE_DATATYPE_BINARY;
+ node->value.b = g_byte_array_new();
+ node->id = 1;
+ devc->tree_id_lookup[node->id] = node;
+
+ node = &devc->tree_root.children[2];
+ node->name = g_strdup("DIAGNOSTIC");
+ node->type = TREE_NODE_DATATYPE_STRING;
+ node->value.b = g_byte_array_new();
+ node->id = 2;
+ devc->tree_id_lookup[node->id] = node;
+}
+
+static gboolean tree_node_has_id(struct config_tree_node *node)
+{
+ switch (node->type) {
+ case TREE_NODE_DATATYPE_PLAIN:
+ case TREE_NODE_DATATYPE_LINK:
+ return FALSE;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static int deserialize_tree(struct dev_context *devc,
+ struct config_tree_node *node,
+ int *id, const uint8_t **data, size_t *size)
+{
+ size_t n;
+ int res;
+
+ if (*size < 2)
+ return SR_ERR_DATA;
+
+ n = R8(*data);
+ *data += 1;
+ *size -= 1;
+ if (n > TREE_NODE_DATATYPE_FLOAT)
+ return SR_ERR_DATA;
+ node->type = n;
+
+ switch (node->type) {
+ case TREE_NODE_DATATYPE_STRING:
+ case TREE_NODE_DATATYPE_BINARY:
+ node->value.b = g_byte_array_new();
+ break;
+ default:
+ break;
+ }
+
+ n = R8(*data);
+ *data += 1;
+ *size -= 1;
+ if (n > *size)
+ return SR_ERR_DATA;
+ node->name = g_strndup((const char *)(*data), n);
+ *data += n;
+ *size -= n;
+
+ if (!(*size))
+ return SR_ERR_DATA;
+
+ if (tree_node_has_id(node)) {
+ node->id = *id;
+ (*id)++;
+ devc->tree_id_lookup[node->id] = node;
+ }
+
+ n = R8(*data);
+ *data += 1;
+ *size -= 1;
+
+ if (n) {
+ node->count_children = n;
+ node->children = g_new0(struct config_tree_node, n);
+
+ for (size_t i = 0; i < n; i++) {
+ if ((res = deserialize_tree(devc,
+ node->children + i, id,
+ data, size)) != SR_OK) {
+ return res;
+ }
+ node->children[i].index_in_parent = i;
+ }
+ }
+
+ return SR_OK;
+}
+
+static int wait_for_update(const struct sr_dev_inst *sdi,
+ struct config_tree_node *node,
+ uint32_t original_update_number)
+{
+ struct sr_bt_desc *desc = sdi->conn;
+ int ret;
+ gint64 start_time;
+
+ start_time = g_get_monotonic_time();
+ for (;;) {
+ ret = sr_bt_check_notify(desc);
+ if (ret < 0)
+ return SR_ERR;
+
+ if (node->update_number != original_update_number)
+ return SR_OK;
+
+ if (g_get_monotonic_time() - start_time > 5 * 1000 * 1000)
+ break;
+
+ if (ret > 0)
+ continue;
+
+ /* Nothing pollable, so just sleep a bit and try again. */
+ g_usleep(50 * 1000);
+ }
+
+ return SR_ERR_TIMEOUT;
+}
+
+static void install_update_handlers(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *target;
+
+ target = lookup_tree_path(devc, "CH1:VALUE");
+ if (target) {
+ target->on_update = ch1_value_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 1 values.");
+ }
+
+ target = lookup_tree_path(devc, "CH1:BUF");
+ if (target) {
+ target->on_update = ch1_buffer_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 1 buffer.");
+ }
+
+ target = lookup_tree_path(devc, "CH1:BUF_BPS");
+ if (target) {
+ target->on_update = ch1_buffer_bps_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 1 buffer BPS.");
+ }
+
+ target = lookup_tree_path(devc, "CH1:BUF_LSB2NATIVE");
+ if (target) {
+ target->on_update = ch1_buffer_lsb2native_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 1 buffer conversion factor.");
+ }
+
+ target = lookup_tree_path(devc, "CH2:VALUE");
+ if (target) {
+ target->on_update = ch2_value_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 2 values.");
+ }
+
+ target = lookup_tree_path(devc, "CH2:BUF");
+ if (target) {
+ target->on_update = ch2_buffer_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 2 buffer.");
+ }
+
+ target = lookup_tree_path(devc, "CH2:BUF_BPS");
+ if (target) {
+ target->on_update = ch2_buffer_bps_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 2 buffer BPS.");
+ }
+
+ target = lookup_tree_path(devc, "CH2:BUF_LSB2NATIVE");
+ if (target) {
+ target->on_update = ch2_buffer_lsb2native_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for channel 2 buffer conversion factor.");
+ }
+
+ target = lookup_tree_path(devc, "REAL_PWR");
+ if (target) {
+ target->on_update = power_value_update;
+ target->on_update_param = sdi;
+ } else {
+ sr_warn("No tree path for real power.");
+ }
+}
+
+struct startup_context {
+ struct sr_dev_inst *sdi;
+ uint32_t crc;
+ int result;
+ gboolean running;
+};
+
+static void startup_failed(struct startup_context *ctx, int err)
+{
+ sr_dbg("Startup handshake failed: %s.", sr_strerror(err));
+
+ ctx->result = err;
+ ctx->running = FALSE;
+}
+
+static void startup_complete(struct startup_context *ctx)
+{
+ sr_dbg("Startup handshake completed.");
+
+ install_update_handlers(ctx->sdi);
+
+ ctx->running = FALSE;
+}
+
+static int startup_run(struct startup_context *ctx)
+{
+ struct sr_bt_desc *desc = ctx->sdi->conn;
+ int ret;
+ gint64 start_time;
+
+ ctx->result = SR_OK;
+ ctx->running = TRUE;
+
+ start_time = g_get_monotonic_time();
+ for (;;) {
+ ret = sr_bt_check_notify(desc);
+ if (ret < 0)
+ return SR_ERR;
+
+ if (!ctx->running)
+ return ctx->result;
+
+ if (g_get_monotonic_time() - start_time > 30 * 1000 * 1000)
+ break;
+
+ if (ret > 0)
+ continue;
+
+ /* Nothing pollable, so just sleep a bit and try again. */
+ g_usleep(50 * 1000);
+ }
+
+ return SR_ERR_TIMEOUT;
+}
+
+static void startup_tree_crc_updated(struct config_tree_node *node, void *param)
+{
+ struct startup_context *ctx = param;
+ uint32_t result;
+
+ node->on_update = NULL;
+
+ result = (uint32_t)get_tree_integer(node);
+ if (result != ctx->crc) {
+ sr_err("Tree CRC mismatch, expected %08X but received %08X.",
+ ctx->crc, result);
+ startup_failed(ctx, SR_ERR_DATA);
+ return;
+ }
+
+ startup_complete(ctx);
+}
+
+static void startup_send_tree_crc(struct startup_context *ctx)
+{
+ struct dev_context *devc = ctx->sdi->priv;
+ struct config_tree_node *target;
+
+ if (!(target = lookup_tree_path(devc, "ADMIN:CRC32"))) {
+ sr_err("ADMIN:CRC32 node not found in received startup tree.");
+ startup_failed(ctx, SR_ERR_DATA);
+ return;
+ }
+
+ target->on_update = startup_tree_crc_updated;
+ target->on_update_param = ctx;
+
+ set_tree_integer(ctx->sdi, target, ctx->crc);
+}
+
+static uint32_t crc32(const uint8_t *ptr, size_t size)
+{
+ uint32_t result = 0xFFFFFFFF;
+ uint32_t t;
+ for (; size; size--, ptr++) {
+ result ^= *ptr;
+ for (int i = 0; i < 8; i++) {
+ t = result & 1;
+ result >>= 1;
+ if (t)
+ result ^= 0xEDB88320;
+ }
+ }
+
+ return ~result;
+}
+
+static void startup_tree_updated(struct config_tree_node *node, void *param)
+{
+ struct startup_context *ctx = param;
+ struct dev_context *devc = ctx->sdi->priv;
+
+ GConverter *decompressor;
+ GConverterResult decompress_result;
+ GByteArray *tree_data;
+ gsize input_read;
+ gsize output_size;
+ GError *err = NULL;
+ int res;
+ int id;
+ const uint8_t *data;
+ size_t size;
+ struct config_tree_node *target;
+
+ ctx->crc = crc32(node->value.b->data, node->value.b->len);
+
+ tree_data = g_byte_array_new();
+ g_byte_array_set_size(tree_data, 4096);
+ decompressor = (GConverter *)g_zlib_decompressor_new(
+ G_ZLIB_COMPRESSOR_FORMAT_ZLIB);
+ for (;;) {
+ g_converter_reset(decompressor);
+ decompress_result = g_converter_convert(decompressor,
+ node->value.b->data,
+ node->value.b->len,
+ tree_data->data,
+ tree_data->len,
+ G_CONVERTER_INPUT_AT_END,
+ &input_read,
+ &output_size,
+ &err);
+ if (decompress_result == G_CONVERTER_FINISHED)
+ break;
+ if (decompress_result == G_CONVERTER_ERROR) {
+ if (err->code == G_IO_ERROR_NO_SPACE &&
+ tree_data->len < 1024 * 1024) {
+ g_byte_array_set_size(tree_data,
+ tree_data->len * 2);
+ continue;
+ }
+ sr_err("Tree decompression failed: %s.", err->message);
+ } else {
+ sr_err("Tree decompression error %d.",
+ (int)decompress_result);
+ }
+ startup_failed(ctx, SR_ERR_DATA);
+ return;
+ }
+ g_object_unref(decompressor);
+
+ sr_dbg("Config tree received (%d -> %d bytes) with CRC %08X.",
+ node->value.b->len, (int)output_size,
+ ctx->crc);
+
+ release_tree_node(&devc->tree_root);
+ memset(&devc->tree_root, 0, sizeof(struct config_tree_node));
+ memset(devc->tree_id_lookup, 0, sizeof(devc->tree_id_lookup));
+
+ id = 0;
+ data = tree_data->data;
+ size = output_size;
+ res = deserialize_tree(devc, &devc->tree_root, &id, &data, &size);
+ g_byte_array_free(tree_data, TRUE);
+
+ if (res != SR_OK) {
+ sr_err("Tree deserialization failed.");
+ startup_failed(ctx, res);
+ return;
+ }
+
+ if ((target = lookup_tree_path(devc, "ADMIN:DIAGNOSTIC"))) {
+ target->on_update = tree_diagnostic_updated;
+ target->on_update_param = ctx->sdi;
+ }
+
+ startup_send_tree_crc(ctx);
+}
+
+static void release_rx_buffer(void *data)
+{
+ GByteArray *ba = data;
+ if (!ba)
+ return;
+ g_byte_array_free(ba, TRUE);
+}
+
+SR_PRIV int mooshimeter_dmm_open(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ struct sr_bt_desc *desc = sdi->conn;
+ struct startup_context ctx;
+ int ret;
+
+ release_tree_node(&devc->tree_root);
+ memset(&devc->tree_root, 0, sizeof(struct config_tree_node));
+ memset(devc->tree_id_lookup, 0, sizeof(devc->tree_id_lookup));
+
+ g_slist_free_full(devc->rx.reorder_buffer, release_rx_buffer);
+ devc->rx.reorder_buffer = NULL;
+ if (devc->rx.contents)
+ devc->rx.contents->len = 0;
+ else
+ devc->rx.contents = g_byte_array_new();
+ devc->rx.sequence_number = -1;
+ devc->tx.sequence_number = 0;
+
+ ret = sr_bt_config_cb_data(desc, notify_cb, (void *)sdi);
+ if (ret < 0)
+ return SR_ERR;
+
+ ret = sr_bt_connect_ble(desc);
+ if (ret < 0)
+ return SR_ERR;
+
+ ret = sr_bt_start_notify(desc);
+ if (ret < 0)
+ return SR_ERR;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.sdi = (struct sr_dev_inst *)sdi;
+
+ allocate_startup_tree(devc);
+ devc->tree_id_lookup[1]->on_update = startup_tree_updated;
+ devc->tree_id_lookup[1]->on_update_param = &ctx;
+ devc->tree_id_lookup[2]->on_update = tree_diagnostic_updated;
+ devc->tree_id_lookup[2]->on_update_param = (struct sr_dev_inst *)sdi;
+
+ sr_spew("Initiating startup handshake.");
+
+ ret = poll_tree_value(sdi, devc->tree_id_lookup[1]);
+ if (ret != SR_OK)
+ return ret;
+
+ return startup_run(&ctx);
+}
+
+SR_PRIV int mooshimeter_dmm_close(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc = sdi->priv;
+ struct sr_bt_desc *desc = sdi->conn;
+
+ sr_bt_disconnect(desc);
+
+ release_tree_node(&devc->tree_root);
+ memset(&devc->tree_root, 0, sizeof(struct config_tree_node));
+ memset(devc->tree_id_lookup, 0, sizeof(devc->tree_id_lookup));
+
+ g_slist_free_full(devc->rx.reorder_buffer, release_rx_buffer);
+ devc->rx.reorder_buffer = NULL;
+ if (devc->rx.contents)
+ g_byte_array_free(devc->rx.contents, TRUE);
+ devc->rx.contents = NULL;
+
+ return SR_OK;
+}
+
+SR_PRIV int mooshimeter_dmm_set_chooser(const struct sr_dev_inst *sdi,
+ const char *path, const char *choice)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *target;
+ int value;
+ uint32_t original_update_number;
+
+ value = lookup_chooser_index(devc, choice);
+ if (value == -1) {
+ sr_err("Value %s not found for chooser %s.", choice, path);
+ return SR_ERR_DATA;
+ }
+
+ target = lookup_tree_path(devc, path);
+ if (!target) {
+ sr_err("Tree path %s not found.", path);
+ return SR_ERR_DATA;
+ }
+
+ sr_spew("Setting chooser %s to %s (%d).", path, choice, value);
+
+ original_update_number = target->update_number;
+ set_tree_integer(sdi, target, value);
+ return wait_for_update(sdi, target, original_update_number);
+}
+
+SR_PRIV int mooshimeter_dmm_set_integer(const struct sr_dev_inst *sdi,
+ const char *path, int value)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *target;
+ uint32_t original_update_number;
+
+ target = lookup_tree_path(devc, path);
+ if (!target) {
+ sr_err("Tree path %s not found.", path);
+ return SR_ERR_DATA;
+ }
+
+ sr_spew("Setting integer %s to %d.", path, value);
+
+ original_update_number = target->update_number;
+ set_tree_integer(sdi, target, value);
+ return wait_for_update(sdi, target, original_update_number);
+}
+
+static struct config_tree_node *select_next_largest_in_tree(
+ struct dev_context *devc,
+ const char *parent, float number)
+{
+ float node_value;
+ float distance;
+ float best_distance = 0;
+ struct config_tree_node *choice_parent;
+ struct config_tree_node *selected_choice = NULL;
+
+ choice_parent = lookup_tree_path(devc, parent);
+ if (!choice_parent) {
+ sr_err("Tree path %s not found.", parent);
+ return NULL;
+ }
+ if (!choice_parent->count_children) {
+ sr_err("Tree path %s has no children.", parent);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < choice_parent->count_children; i++) {
+ node_value = strtof(choice_parent->children[i].name, NULL);
+ if (node_value <= 0)
+ continue;
+ distance = node_value - number;
+ if (!selected_choice) {
+ selected_choice = &choice_parent->children[i];
+ best_distance = distance;
+ continue;
+ }
+ /* Select the one that's the least below it, if all
+ * are below the target */
+ if (distance < 0) {
+ if (best_distance > 0)
+ continue;
+ if (distance > best_distance) {
+ selected_choice = &choice_parent->children[i];
+ best_distance = distance;
+ }
+ continue;
+ }
+ if (best_distance < 0 || distance < best_distance) {
+ selected_choice = &choice_parent->children[i];
+ best_distance = distance;
+ }
+ }
+
+ return selected_choice;
+}
+
+SR_PRIV int mooshimeter_dmm_set_larger_number(const struct sr_dev_inst *sdi,
+ const char *path, const char *parent, float number)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *selected_choice;
+ struct config_tree_node *target;
+ uint32_t original_update_number;
+
+ selected_choice = select_next_largest_in_tree(devc, parent, number);
+ if (!selected_choice) {
+ sr_err("No choice available for %f at %s.", number, parent);
+ return SR_ERR_NA;
+ }
+
+ target = lookup_tree_path(devc, path);
+ if (!target) {
+ sr_err("Tree path %s not found.", path);
+ return SR_ERR_DATA;
+ }
+
+ sr_spew("Setting number choice %s to index %d for requested %g.", path,
+ (int)selected_choice->index_in_parent, number);
+
+ original_update_number = target->update_number;
+ set_tree_integer(sdi, target, selected_choice->index_in_parent);
+ return wait_for_update(sdi, target, original_update_number);
+}
+
+SR_PRIV gboolean mooshimeter_dmm_set_autorange(const struct sr_dev_inst *sdi,
+ const char *path, const char *parent, float latest)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *selected_choice;
+ struct config_tree_node *target;
+
+ selected_choice = select_next_largest_in_tree(devc, parent,
+ fabsf(latest));
+ if (!selected_choice) {
+ sr_err("No choice available for %f at %s.", latest, parent);
+ return FALSE;
+ }
+
+ target = lookup_tree_path(devc, path);
+ if (!target) {
+ sr_err("Tree path %s not found.", path);
+ return FALSE;
+ }
+
+ if (get_tree_integer(target) == (int)selected_choice->index_in_parent)
+ return FALSE;
+
+ sr_spew("Changing autorange %s to index %d for %g.", path,
+ (int)selected_choice->index_in_parent, latest);
+
+ set_tree_integer(sdi, target, selected_choice->index_in_parent);
+
+ return TRUE;
+}
+
+SR_PRIV int mooshimeter_dmm_get_chosen_number(const struct sr_dev_inst *sdi,
+ const char *path, const char *parent, float *number)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *value_node;
+ struct config_tree_node *available;
+ int32_t selected;
+
+ value_node = lookup_tree_path(devc, path);
+ if (!value_node) {
+ sr_err("Tree path %s not found.", path);
+ return SR_ERR_DATA;
+ }
+
+ available = lookup_tree_path(devc, parent);
+ if (!available) {
+ sr_err("Tree path %s not found.", path);
+ return SR_ERR_DATA;
+ }
+
+ selected = get_tree_integer(value_node);
+ if (selected < 0 || selected >= (int32_t)available->count_children)
+ return SR_ERR_DATA;
+
+ *number = g_ascii_strtod(available->children[selected].name, NULL);
+
+ return SR_OK;
+}
+
+SR_PRIV int mooshimeter_dmm_get_available_number_choices(
+ const struct sr_dev_inst *sdi, const char *path,
+ float **numbers, size_t *count)
+{
+ struct dev_context *devc = sdi->priv;
+ struct config_tree_node *available;
+
+ available = lookup_tree_path(devc, path);
+ if (!available) {
+ sr_err("Tree path %s not found.", path);
+ return SR_ERR_NA;
+ }
+
+ *numbers = g_malloc(sizeof(float) * available->count_children);
+ *count = available->count_children;
+
+ for (size_t i = 0; i < available->count_children; i++) {
+ (*numbers)[i] = g_ascii_strtod(available->children[i].name,
+ NULL);
+ }
+
+ return SR_OK;
+}
+
+SR_PRIV int mooshimeter_dmm_poll(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct sr_bt_desc *desc;
+
+ (void)fd;
+ (void)revents;
+
+ if (!(sdi = cb_data))
+ return TRUE;
+
+ desc = sdi->conn;
+
+ while (sr_bt_check_notify(desc) > 0);
+
+ return TRUE;
+}
+
+/*
+ * The meter will disconnect if it doesn't receive a host command for 30 (?)
+ * seconds, so periodically poll a trivial value to keep it alive.
+ */
+SR_PRIV int mooshimeter_dmm_heartbeat(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ struct config_tree_node *target;
+
+ (void)fd;
+ (void)revents;
+
+ if (!(sdi = cb_data))
+ return TRUE;
+
+ if (!(devc = sdi->priv))
+ return TRUE;
+
+ target = lookup_tree_path(devc, "PCB_VERSION");
+ if (!target) {
+ sr_err("Tree for PCB_VERSION not found.");
+ return FALSE;
+ }
+
+ sr_spew("Sending heartbeat request.");
+ poll_tree_value(sdi, target);
+
+ return TRUE;
+}
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Derek Hageman <hageman@inthat.cloud>
+ *
+ * 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/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_MOOSHIMETER_DMM_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_MOOSHIMETER_DMM_PROTOCOL_H
+
+#include <stdint.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "mooshimeter-dmm"
+
+struct packet_rx {
+ int sequence_number;
+ GSList *reorder_buffer;
+ GByteArray *contents;
+};
+
+struct packet_tx {
+ int sequence_number;
+};
+
+enum tree_node_datatype {
+ TREE_NODE_DATATYPE_PLAIN = 0,
+ TREE_NODE_DATATYPE_LINK,
+ TREE_NODE_DATATYPE_CHOOSER,
+ TREE_NODE_DATATYPE_U8,
+ TREE_NODE_DATATYPE_U16,
+ TREE_NODE_DATATYPE_U32,
+ TREE_NODE_DATATYPE_S8,
+ TREE_NODE_DATATYPE_S16,
+ TREE_NODE_DATATYPE_S32,
+ TREE_NODE_DATATYPE_STRING,
+ TREE_NODE_DATATYPE_BINARY,
+ TREE_NODE_DATATYPE_FLOAT,
+};
+
+union tree_value {
+ int32_t i;
+ float f;
+ GByteArray *b;
+};
+
+struct config_tree_node {
+ char *name;
+ int id;
+ size_t index_in_parent;
+
+ enum tree_node_datatype type;
+ union tree_value value;
+
+ size_t count_children;
+ struct config_tree_node *children;
+
+ uint32_t update_number;
+ void (*on_update)(struct config_tree_node *node, void *param);
+ void *on_update_param;
+};
+
+struct dev_context {
+ struct packet_rx rx;
+ struct packet_tx tx;
+ struct config_tree_node tree_root;
+ struct config_tree_node *tree_id_lookup[0x7F];
+ uint32_t buffer_bps[2];
+ float buffer_lsb2native[2];
+
+ void (*channel_autorange[3])(const struct sr_dev_inst *sdi, float value);
+
+ struct sr_sw_limits limits;
+ struct sr_analog_meaning channel_meaning[3];
+
+ gboolean enable_value_stream;
+};
+
+SR_PRIV int mooshimeter_dmm_open(const struct sr_dev_inst *sdi);
+SR_PRIV int mooshimeter_dmm_close(const struct sr_dev_inst *sdi);
+SR_PRIV int mooshimeter_dmm_set_chooser(const struct sr_dev_inst *sdi, const char *path, const char *choice);
+SR_PRIV int mooshimeter_dmm_set_integer(const struct sr_dev_inst *sdi, const char *path, int value);
+SR_PRIV int mooshimeter_dmm_set_larger_number(const struct sr_dev_inst *sdi, const char *path, const char *parent, float number);
+SR_PRIV gboolean mooshimeter_dmm_set_autorange(const struct sr_dev_inst *sdi, const char *path, const char *parent, float latest);
+SR_PRIV int mooshimeter_dmm_get_chosen_number(const struct sr_dev_inst *sdi, const char *path, const char *parent, float *number);
+SR_PRIV int mooshimeter_dmm_get_available_number_choices(const struct sr_dev_inst *sdi, const char *path, float **numbers, size_t *count);
+SR_PRIV int mooshimeter_dmm_poll(int fd, int revents, void *cb_data);
+SR_PRIV int mooshimeter_dmm_heartbeat(int fd, int revents, void *cb_data);
+
+#endif
SR_HZ(1),
};
-#define RESPONSE_DELAY_US (10 * 1000)
+#define RESPONSE_DELAY_US (20 * 1000)
static GSList *scan(struct sr_dev_driver *di, GSList *options)
{
g_usleep(RESPONSE_DELAY_US);
- if (sp_input_waiting(serial->data) == 0) {
+ if (serial_has_receive_data(serial) == 0) {
sr_dbg("Didn't get any reply.");
return NULL;
}
g_usleep(RESPONSE_DELAY_US);
- if (sp_input_waiting(serial->data) != 0) {
+ if (serial_has_receive_data(serial) != 0) {
/* Got metadata. */
sdi = get_metadata(serial);
} else {
{
struct dev_context *devc;
struct sr_serial_dev_inst *serial;
- uint16_t samplecount, readcount, delaycount;
+ uint32_t samplecount, readcount, delaycount;
uint8_t ols_changrp_mask, arg[4];
int num_ols_changrp;
int ret, i;
/*
* Limit readcount to prevent reading past the end of the hardware
- * buffer.
+ * buffer. Rather read too many samples than too few.
*/
samplecount = MIN(devc->max_samples / num_ols_changrp, devc->limit_samples);
- readcount = samplecount / 4;
-
- /* Rather read too many samples than too few. */
- if (samplecount % 4 != 0)
- readcount++;
+ readcount = (samplecount + 3) / 4;
/* Basic triggers. */
if (ols_convert_trigger(sdi) != SR_OK) {
/* Send sample limit and pre/post-trigger capture ratio. */
sr_dbg("Setting sample limit %d, trigger point at %d",
(readcount - 1) * 4, (delaycount - 1) * 4);
- arg[0] = ((readcount - 1) & 0xff);
- arg[1] = ((readcount - 1) & 0xff00) >> 8;
- arg[2] = ((delaycount - 1) & 0xff);
- arg[3] = ((delaycount - 1) & 0xff00) >> 8;
- if (send_longcommand(serial, CMD_CAPTURE_SIZE, arg) != SR_OK)
- return SR_ERR;
+
+ if (devc->max_samples > 256 * 1024) {
+ arg[0] = ((readcount - 1) & 0xff);
+ arg[1] = ((readcount - 1) & 0xff00) >> 8;
+ arg[2] = ((readcount - 1) & 0xff0000) >> 16;
+ arg[3] = ((readcount - 1) & 0xff000000) >> 24;
+ if (send_longcommand(serial, CMD_CAPTURE_READCOUNT, arg) != SR_OK)
+ return SR_ERR;
+ arg[0] = ((delaycount - 1) & 0xff);
+ arg[1] = ((delaycount - 1) & 0xff00) >> 8;
+ arg[2] = ((delaycount - 1) & 0xff0000) >> 16;
+ arg[3] = ((delaycount - 1) & 0xff000000) >> 24;
+ if (send_longcommand(serial, CMD_CAPTURE_DELAYCOUNT, arg) != SR_OK)
+ return SR_ERR;
+ } else {
+ arg[0] = ((readcount - 1) & 0xff);
+ arg[1] = ((readcount - 1) & 0xff00) >> 8;
+ arg[2] = ((delaycount - 1) & 0xff);
+ arg[3] = ((delaycount - 1) & 0xff00) >> 8;
+ if (send_longcommand(serial, CMD_CAPTURE_SIZE, arg) != SR_OK)
+ return SR_ERR;
+ }
/* Flag register. */
sr_dbg("Setting intpat %s, extpat %s, RLE %s, noise_filter %s, demux %s",
static struct sr_dev_driver ols_driver_info = {
.name = "ols",
- .longname = "Openbench Logic Sniffer",
+ .longname = "Openbench Logic Sniffer & SUMP compatibles",
.api_version = 1,
.init = std_init,
.cleanup = std_cleanup,
/* Acquisition settings */
devc->limit_samples = devc->capture_ratio = 0;
devc->trigger_at = -1;
- devc->channel_mask = 0xffffffff;
devc->flag_reg = 0;
return devc;
}
+static void ols_channel_new(struct sr_dev_inst *sdi, int num_chan)
+{
+ struct dev_context *devc = sdi->priv;
+ int i;
+
+ for (i = 0; i < num_chan; i++)
+ sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE,
+ ols_channel_names[i]);
+
+ devc->max_channels = num_chan;
+}
+
+static void metadata_quirks(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ gboolean is_shrimp;
+
+ if (!sdi)
+ return;
+ devc = sdi->priv;
+ if (!devc)
+ return;
+
+ is_shrimp = sdi->model && strcmp(sdi->model, "Shrimp1.0") == 0;
+ if (is_shrimp) {
+ if (!devc->max_channels)
+ ols_channel_new(sdi, 4);
+ if (!devc->max_samples)
+ devc->max_samples = 256 * 1024;
+ if (!devc->max_samplerate)
+ devc->max_samplerate = SR_MHZ(20);
+ }
+}
+
SR_PRIV struct sr_dev_inst *get_metadata(struct sr_serial_dev_inst *serial)
{
struct sr_dev_inst *sdi;
struct dev_context *devc;
- uint32_t tmp_int, ui;
+ uint32_t tmp_int;
uint8_t key, type, token;
int delay_ms;
GString *tmp_str, *devname, *version;
switch (token) {
case 0x00:
/* Number of usable channels */
- for (ui = 0; ui < tmp_int; ui++)
- sr_channel_new(sdi, ui, SR_CHANNEL_LOGIC, TRUE,
- ols_channel_names[ui]);
+ ols_channel_new(sdi, tmp_int);
break;
case 0x01:
/* Amount of sample memory available (bytes) */
switch (token) {
case 0x00:
/* Number of usable channels */
- for (ui = 0; ui < tmp_c; ui++)
- sr_channel_new(sdi, ui, SR_CHANNEL_LOGIC, TRUE,
- ols_channel_names[ui]);
+ ols_channel_new(sdi, tmp_c);
break;
case 0x01:
/* protocol version */
g_string_free(devname, FALSE);
g_string_free(version, FALSE);
+ /* Optionally amend received metadata, model specific quirks. */
+ metadata_quirks(sdi);
+
return sdi;
}
sr_info("Enabling demux mode.");
devc->flag_reg |= FLAG_DEMUX;
devc->flag_reg &= ~FLAG_FILTER;
- devc->max_channels = NUM_CHANNELS / 2;
devc->cur_samplerate_divider = (CLOCK_RATE * 2 / samplerate) - 1;
} else {
sr_info("Disabling demux mode.");
devc->flag_reg &= ~FLAG_DEMUX;
devc->flag_reg |= FLAG_FILTER;
- devc->max_channels = NUM_CHANNELS;
devc->cur_samplerate_divider = (CLOCK_RATE / samplerate) - 1;
}
}
num_ols_changrp = 0;
- for (i = NUM_CHANNELS; i > 0x02; i /= 2) {
+ for (i = 0x20; i > 0x02; i >>= 1) {
if ((devc->flag_reg & i) == 0) {
num_ols_changrp++;
}
#define LOG_PREFIX "openbench-logic-sniffer"
-#define NUM_CHANNELS 32
#define NUM_TRIGGER_STAGES 4
-#define SERIAL_SPEED B115200
#define CLOCK_RATE SR_MHZ(100)
#define MIN_NUM_SAMPLES 4
#define DEFAULT_SAMPLERATE SR_KHZ(200)
/* Command opcodes */
#define CMD_RESET 0x00
#define CMD_RUN 0x01
-#define CMD_TESTMODE 0x03
#define CMD_ID 0x02
+#define CMD_TESTMODE 0x03
#define CMD_METADATA 0x04
-#define CMD_SET_FLAGS 0x82
#define CMD_SET_DIVIDER 0x80
#define CMD_CAPTURE_SIZE 0x81
+#define CMD_SET_FLAGS 0x82
+#define CMD_CAPTURE_DELAYCOUNT 0x83 /* extension for Pepino */
+#define CMD_CAPTURE_READCOUNT 0x84 /* extension for Pepino */
#define CMD_SET_TRIGGER_MASK 0xc0
#define CMD_SET_TRIGGER_VALUE 0xc1
#define CMD_SET_TRIGGER_CONFIG 0xc2
arg[1] = ((readcount - 1) & 0xff00) >> 8;
arg[2] = ((readcount - 1) & 0xff0000) >> 16;
arg[3] = ((readcount - 1) & 0xff000000) >> 24;
- if (write_longcommand(devc, CMD_CAPTURE_DELAY, arg) != SR_OK)
+ if (write_longcommand(devc, CMD_CAPTURE_READCOUNT, arg) != SR_OK)
return SR_ERR;
arg[0] = ((delaycount - 1) & 0xff);
arg[1] = ((delaycount - 1) & 0xff00) >> 8;
arg[2] = ((delaycount - 1) & 0xff0000) >> 16;
arg[3] = ((delaycount - 1) & 0xff000000) >> 24;
- if (write_longcommand(devc, CMD_CAPTURE_COUNT, arg) != SR_OK)
+ if (write_longcommand(devc, CMD_CAPTURE_DELAYCOUNT, arg) != SR_OK)
return SR_ERR;
/* Flag register. */
return SR_OK;
}
-SR_PRIV struct sr_dev_driver p_ols_driver_info = {
+static struct sr_dev_driver p_ols_driver_info = {
.name = "p-ols",
.longname = "Pipistrello OLS",
.api_version = 1,
#define CMD_METADATA 0x04
#define CMD_SET_DIVIDER 0x80
#define CMD_SET_FLAGS 0x82
-#define CMD_CAPTURE_COUNT 0x83
-#define CMD_CAPTURE_DELAY 0x84
+#define CMD_CAPTURE_DELAYCOUNT 0x83
+#define CMD_CAPTURE_READCOUNT 0x84
#define CMD_SET_TRIGGER_MASK 0xc0
#define CMD_SET_TRIGGER_VALUE 0xc1
#define CMD_SET_TRIGGER_CONFIG 0xc2
* This file is part of the libsigrok project.
*
* Copyright (C) 2018 James Churchill <pelrun@gmail.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
*/
#include <config.h>
+#include <math.h>
#include "protocol.h"
static const uint32_t scanopts[] = {
/* Model ID, model name, max current, max voltage, max power */
static const struct rdtech_dps_model supported_models[] = {
- { 3005, "DPS3005", 3, 50, 160 },
- { 5005, "DPS5005", 5, 50, 250 },
- { 5205, "DPH5005", 5, 50, 250 },
- { 5015, "DPS5015", 15, 50, 750 },
- { 5020, "DPS5020", 20, 50, 1000 },
- { 8005, "DPS8005", 5, 80, 408 },
+ { 3005, "DPS3005", 5, 30, 160, 3, 2 },
+ { 5005, "DPS5005", 5, 50, 250, 3, 2 },
+ { 5205, "DPH5005", 5, 50, 250, 3, 2 },
+ { 5015, "DPS5015", 15, 50, 750, 2, 2 },
+ { 5020, "DPS5020", 20, 50, 1000, 2, 2 },
+ { 8005, "DPS8005", 5, 80, 408, 3, 2 },
};
static struct sr_dev_driver rdtech_dps_driver_info;
devc = g_malloc0(sizeof(struct dev_context));
sr_sw_limits_init(&devc->limits);
devc->model = model;
- devc->expecting_registers = 0;
+ devc->current_multiplier = pow(10.0, model->current_digits);
+ devc->voltage_multiplier = pow(10.0, model->voltage_digits);
sdi->priv = devc;
if (sr_modbus_open(modbus) < 0)
return SR_ERR;
- rdtech_dps_set_reg(modbus, REG_LOCK, 1);
+ rdtech_dps_set_reg(sdi, REG_LOCK, 1);
return SR_OK;
}
static int dev_close(struct sr_dev_inst *sdi)
{
- struct dev_context *devc;
struct sr_modbus_dev_inst *modbus;
modbus = sdi->conn;
-
if (!modbus)
return SR_ERR_BUG;
- devc = sdi->priv;
-
- if (devc->expecting_registers) {
- /* Wait for the last data that was requested from the device. */
- uint16_t registers[devc->expecting_registers];
- sr_modbus_read_holding_registers(modbus, -1,
- devc->expecting_registers, registers);
- }
-
- rdtech_dps_set_reg(modbus, REG_LOCK, 0);
+ rdtech_dps_set_reg(sdi, REG_LOCK, 0);
return sr_modbus_close(modbus);
}
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
struct dev_context *devc;
- struct sr_modbus_dev_inst *modbus;
int ret;
uint16_t ivalue;
(void)cg;
- modbus = sdi->conn;
devc = sdi->priv;
ret = SR_OK;
ret = sr_sw_limits_config_get(&devc->limits, key, data);
break;
case SR_CONF_ENABLED:
- if ((ret = rdtech_dps_get_reg(modbus, REG_ENABLE, &ivalue)) == SR_OK)
+ if ((ret = rdtech_dps_get_reg(sdi, REG_ENABLE, &ivalue)) == SR_OK)
*data = g_variant_new_boolean(ivalue);
break;
case SR_CONF_REGULATION:
- if ((ret = rdtech_dps_get_reg(modbus, REG_CV_CC, &ivalue)) != SR_OK)
+ if ((ret = rdtech_dps_get_reg(sdi, REG_CV_CC, &ivalue)) != SR_OK)
break;
*data = g_variant_new_string((ivalue == MODE_CC) ? "CC" : "CV");
break;
case SR_CONF_VOLTAGE:
- if ((ret = rdtech_dps_get_reg(modbus, REG_UOUT, &ivalue)) == SR_OK)
- *data = g_variant_new_double((float)ivalue / 100.0f);
+ if ((ret = rdtech_dps_get_reg(sdi, REG_UOUT, &ivalue)) == SR_OK)
+ *data = g_variant_new_double((float)ivalue / devc->voltage_multiplier);
break;
case SR_CONF_VOLTAGE_TARGET:
- if ((ret = rdtech_dps_get_reg(modbus, REG_USET, &ivalue)) == SR_OK)
- *data = g_variant_new_double((float)ivalue / 100.0f);
+ if ((ret = rdtech_dps_get_reg(sdi, REG_USET, &ivalue)) == SR_OK)
+ *data = g_variant_new_double((float)ivalue / devc->voltage_multiplier);
break;
case SR_CONF_CURRENT:
- if ((ret = rdtech_dps_get_reg(modbus, REG_IOUT, &ivalue)) == SR_OK)
- *data = g_variant_new_double((float)ivalue / 100.0f);
+ if ((ret = rdtech_dps_get_reg(sdi, REG_IOUT, &ivalue)) == SR_OK)
+ *data = g_variant_new_double((float)ivalue / devc->current_multiplier);
break;
case SR_CONF_CURRENT_LIMIT:
- if ((ret = rdtech_dps_get_reg(modbus, REG_ISET, &ivalue)) == SR_OK)
- *data = g_variant_new_double((float)ivalue / 1000.0f);
+ if ((ret = rdtech_dps_get_reg(sdi, REG_ISET, &ivalue)) == SR_OK)
+ *data = g_variant_new_double((float)ivalue / devc->current_multiplier);
break;
case SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED:
*data = g_variant_new_boolean(TRUE);
break;
case SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE:
- if ((ret = rdtech_dps_get_reg(modbus, REG_PROTECT, &ivalue)) == SR_OK)
+ if ((ret = rdtech_dps_get_reg(sdi, REG_PROTECT, &ivalue)) == SR_OK)
*data = g_variant_new_boolean(ivalue == STATE_OVP);
break;
case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD:
- if ((ret = rdtech_dps_get_reg(modbus, PRE_OVPSET, &ivalue)) == SR_OK)
- *data = g_variant_new_double((float)ivalue / 100.0f);
+ if ((ret = rdtech_dps_get_reg(sdi, PRE_OVPSET, &ivalue)) == SR_OK)
+ *data = g_variant_new_double((float)ivalue / devc->voltage_multiplier);
break;
case SR_CONF_OVER_CURRENT_PROTECTION_ENABLED:
*data = g_variant_new_boolean(TRUE);
break;
case SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE:
- if ((ret = rdtech_dps_get_reg(modbus, REG_PROTECT, &ivalue)) == SR_OK)
+ if ((ret = rdtech_dps_get_reg(sdi, REG_PROTECT, &ivalue)) == SR_OK)
*data = g_variant_new_boolean(ivalue == STATE_OCP);
break;
case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD:
- if ((ret = rdtech_dps_get_reg(modbus, PRE_OCPSET, &ivalue)) == SR_OK)
- *data = g_variant_new_double((float)ivalue / 1000.0f);
+ if ((ret = rdtech_dps_get_reg(sdi, PRE_OCPSET, &ivalue)) == SR_OK)
+ *data = g_variant_new_double((float)ivalue / devc->current_multiplier);
break;
default:
return SR_ERR_NA;
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
struct dev_context *devc;
- struct sr_modbus_dev_inst *modbus;
(void)cg;
- modbus = sdi->conn;
devc = sdi->priv;
switch (key) {
case SR_CONF_LIMIT_MSEC:
return sr_sw_limits_config_set(&devc->limits, key, data);
case SR_CONF_ENABLED:
- return rdtech_dps_set_reg(modbus, REG_ENABLE, g_variant_get_boolean(data));
+ return rdtech_dps_set_reg(sdi, REG_ENABLE, g_variant_get_boolean(data));
case SR_CONF_VOLTAGE_TARGET:
- return rdtech_dps_set_reg(modbus, REG_USET, g_variant_get_double(data) * 100);
+ return rdtech_dps_set_reg(sdi, REG_USET,
+ g_variant_get_double(data) * devc->voltage_multiplier);
case SR_CONF_CURRENT_LIMIT:
- return rdtech_dps_set_reg(modbus, REG_ISET, g_variant_get_double(data) * 1000);
+ return rdtech_dps_set_reg(sdi, REG_ISET,
+ g_variant_get_double(data) * devc->current_multiplier);
case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD:
- return rdtech_dps_set_reg(modbus, PRE_OVPSET, g_variant_get_double(data) * 100);
+ return rdtech_dps_set_reg(sdi, PRE_OVPSET,
+ g_variant_get_double(data) * devc->voltage_multiplier);
case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD:
- return rdtech_dps_set_reg(modbus, PRE_OCPSET, g_variant_get_double(data) * 1000);
+ return rdtech_dps_set_reg(sdi, PRE_OCPSET,
+ g_variant_get_double(data) * devc->current_multiplier);
default:
return SR_ERR_NA;
}
case SR_CONF_DEVICE_OPTIONS:
return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
case SR_CONF_VOLTAGE_TARGET:
- *data = std_gvar_min_max_step(0.0, devc->model->max_voltage, 0.001);
+ *data = std_gvar_min_max_step(0.0, devc->model->max_voltage,
+ 1 / devc->voltage_multiplier);
break;
case SR_CONF_CURRENT_LIMIT:
- *data = std_gvar_min_max_step(0.0, devc->model->max_current, 0.0001);
+ *data = std_gvar_min_max_step(0.0, devc->model->max_current,
+ 1 / devc->current_multiplier);
break;
default:
return SR_ERR_NA;
{
struct dev_context *devc;
struct sr_modbus_dev_inst *modbus;
+ uint16_t registers[3];
int ret;
modbus = sdi->conn;
devc = sdi->priv;
+ /* Prefill actual states */
+ ret = rdtech_dps_read_holding_registers(modbus, REG_PROTECT, 3, registers);
+ if (ret != SR_OK)
+ return ret;
+ devc->actual_ovp_state = RB16(registers + 0) == STATE_OVP;
+ devc->actual_ocp_state = RB16(registers + 0) == STATE_OCP;
+ devc->actual_regulation_state = RB16(registers + 1);
+ devc->actual_output_state = RB16(registers + 2);
+
if ((ret = sr_modbus_source_add(sdi->session, modbus, G_IO_IN, 10,
rdtech_dps_receive_data, (void *)sdi)) != SR_OK)
return ret;
sr_sw_limits_acquisition_start(&devc->limits);
std_session_send_df_header(sdi);
- return rdtech_dps_capture_start(sdi);
+ return SR_OK;
}
static int dev_acquisition_stop(struct sr_dev_inst *sdi)
* This file is part of the libsigrok project.
*
* Copyright (C) 2018 James Churchill <pelrun@gmail.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
#include <config.h>
#include "protocol.h"
-SR_PRIV int rdtech_dps_get_reg(struct sr_modbus_dev_inst *modbus,
+SR_PRIV int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus,
+ int address, int nb_registers, uint16_t *registers)
+{
+ int i, ret;
+
+ i = 0;
+ do {
+ ret = sr_modbus_read_holding_registers(modbus,
+ address, nb_registers, registers);
+ ++i;
+ } while (ret != SR_OK && i < 3);
+
+ return ret;
+}
+
+SR_PRIV int rdtech_dps_get_reg(const struct sr_dev_inst *sdi,
uint16_t address, uint16_t *value)
{
+ struct dev_context *devc;
+ struct sr_modbus_dev_inst *modbus;
uint16_t registers[1];
- int ret = sr_modbus_read_holding_registers(modbus, address, 1, registers);
+ int ret;
+
+ devc = sdi->priv;
+ modbus = sdi->conn;
+
+ g_mutex_lock(&devc->rw_mutex);
+ ret = rdtech_dps_read_holding_registers(modbus, address, 1, registers);
+ g_mutex_unlock(&devc->rw_mutex);
*value = RB16(registers + 0);
return ret;
}
-SR_PRIV int rdtech_dps_set_reg(struct sr_modbus_dev_inst *modbus,
+SR_PRIV int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
uint16_t address, uint16_t value)
{
+ struct dev_context *devc;
+ struct sr_modbus_dev_inst *modbus;
uint16_t registers[1];
+ int ret;
+
+ devc = sdi->priv;
+ modbus = sdi->conn;
+
WB16(registers, value);
- return sr_modbus_write_multiple_registers(modbus, address, 1, registers);
+ g_mutex_lock(&devc->rw_mutex);
+ ret = sr_modbus_write_multiple_registers(modbus, address, 1, registers);
+ g_mutex_unlock(&devc->rw_mutex);
+ return ret;
}
SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
{
uint16_t registers[2];
int ret;
- ret = sr_modbus_read_holding_registers(modbus, REG_MODEL, 2, registers);
+
+ /*
+ * No mutex here, because there is no sr_dev_inst when this function
+ * is called.
+ */
+ ret = rdtech_dps_read_holding_registers(modbus, REG_MODEL, 2, registers);
if (ret == SR_OK) {
*model = RB16(registers + 0);
*version = RB16(registers + 1);
}
static void send_value(const struct sr_dev_inst *sdi, struct sr_channel *ch,
- float value, enum sr_mq mq, enum sr_unit unit, int digits)
+ float value, enum sr_mq mq, enum sr_mqflag mqflags,
+ enum sr_unit unit, int digits)
{
struct sr_datafeed_packet packet;
struct sr_datafeed_analog analog;
analog.num_samples = 1;
analog.data = &value;
analog.meaning->mq = mq;
+ analog.meaning->mqflags = mqflags;
analog.meaning->unit = unit;
- analog.meaning->mqflags = SR_MQFLAG_DC;
packet.type = SR_DF_ANALOG;
packet.payload = &analog;
g_slist_free(analog.meaning->channels);
}
-SR_PRIV int rdtech_dps_capture_start(const struct sr_dev_inst *sdi)
-{
- struct dev_context *devc;
- struct sr_modbus_dev_inst *modbus;
- int ret;
-
- modbus = sdi->conn;
- devc = sdi->priv;
-
- if ((ret = sr_modbus_read_holding_registers(modbus, REG_UOUT, 3, NULL)) == SR_OK)
- devc->expecting_registers = 2;
- return ret;
-}
-
SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data)
{
struct sr_dev_inst *sdi;
struct dev_context *devc;
struct sr_modbus_dev_inst *modbus;
struct sr_datafeed_packet packet;
- uint16_t registers[3];
+ uint16_t registers[8];
+ int ret;
(void)fd;
(void)revents;
modbus = sdi->conn;
devc = sdi->priv;
- devc->expecting_registers = 0;
- if (sr_modbus_read_holding_registers(modbus, -1, 3, registers) == SR_OK) {
+ g_mutex_lock(&devc->rw_mutex);
+ /*
+ * Using the libsigrok function here, because it doesn't matter if the
+ * reading fails. It will be done again in the next acquision cycle anyways.
+ */
+ ret = sr_modbus_read_holding_registers(modbus, REG_UOUT, 8, registers);
+ g_mutex_unlock(&devc->rw_mutex);
+
+ if (ret == SR_OK) {
+ /* Send channel values */
packet.type = SR_DF_FRAME_BEGIN;
sr_session_send(sdi, &packet);
send_value(sdi, sdi->channels->data,
- RB16(registers + 0) / 100.0f,
- SR_MQ_VOLTAGE, SR_UNIT_VOLT, 3);
+ RB16(registers + 0) / devc->voltage_multiplier,
+ SR_MQ_VOLTAGE, SR_MQFLAG_DC, SR_UNIT_VOLT,
+ devc->model->voltage_digits);
send_value(sdi, sdi->channels->next->data,
- RB16(registers + 1) / 1000.0f,
- SR_MQ_CURRENT, SR_UNIT_AMPERE, 4);
+ RB16(registers + 1) / devc->current_multiplier,
+ SR_MQ_CURRENT, SR_MQFLAG_DC, SR_UNIT_AMPERE,
+ devc->model->current_digits);
send_value(sdi, sdi->channels->next->next->data,
RB16(registers + 2) / 100.0f,
- SR_MQ_POWER, SR_UNIT_WATT, 3);
+ SR_MQ_POWER, 0, SR_UNIT_WATT, 2);
packet.type = SR_DF_FRAME_END;
sr_session_send(sdi, &packet);
+
+ /* Check for state changes */
+ if (devc->actual_ovp_state != (RB16(registers + 5) == STATE_OVP)) {
+ devc->actual_ovp_state = RB16(registers + 5) == STATE_OVP;
+ sr_session_send_meta(sdi, SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE,
+ g_variant_new_boolean(devc->actual_ovp_state));
+ }
+ if (devc->actual_ocp_state != (RB16(registers + 5) == STATE_OCP)) {
+ devc->actual_ocp_state = RB16(registers + 5) == STATE_OCP;
+ sr_session_send_meta(sdi, SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE,
+ g_variant_new_boolean(devc->actual_ocp_state));
+ }
+ if (devc->actual_regulation_state != RB16(registers + 6)) {
+ devc->actual_regulation_state = RB16(registers + 6);
+ sr_session_send_meta(sdi, SR_CONF_REGULATION,
+ g_variant_new_string(
+ devc->actual_regulation_state == MODE_CC ? "CC" : "CV"));
+ }
+ if (devc->actual_output_state != RB16(registers + 7)) {
+ devc->actual_output_state = RB16(registers + 7);
+ sr_session_send_meta(sdi, SR_CONF_ENABLED,
+ g_variant_new_boolean(devc->actual_output_state));
+ }
+
sr_sw_limits_update_samples_read(&devc->limits, 1);
}
return TRUE;
}
- rdtech_dps_capture_start(sdi);
return TRUE;
}
* This file is part of the libsigrok project.
*
* Copyright (C) 2018 James Churchill <pelrun@gmail.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
unsigned int max_current;
unsigned int max_voltage;
unsigned int max_power;
+ unsigned int current_digits;
+ unsigned int voltage_digits;
};
struct dev_context {
const struct rdtech_dps_model *model;
struct sr_sw_limits limits;
- int expecting_registers;
+ GMutex rw_mutex;
+ double current_multiplier;
+ double voltage_multiplier;
+ gboolean actual_ovp_state;
+ gboolean actual_ocp_state;
+ uint16_t actual_regulation_state;
+ uint16_t actual_output_state;
};
enum rdtech_dps_register {
MODE_CC = 1,
};
-SR_PRIV int rdtech_dps_get_reg(struct sr_modbus_dev_inst *modbus, uint16_t address, uint16_t *value);
-SR_PRIV int rdtech_dps_set_reg(struct sr_modbus_dev_inst *modbus, uint16_t address, uint16_t value);
+SR_PRIV int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus,
+ int address, int nb_registers, uint16_t *registers);
+
+SR_PRIV int rdtech_dps_get_reg(const struct sr_dev_inst *sdi, uint16_t address, uint16_t *value);
+SR_PRIV int rdtech_dps_set_reg(const struct sr_dev_inst *sdi, uint16_t address, uint16_t value);
SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
uint16_t *model, uint16_t *version);
-SR_PRIV int rdtech_dps_capture_start(const struct sr_dev_inst *sdi);
SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data);
#endif
DS2000,
DS2000A,
DSO1000,
+ DSO1000B,
DS1000Z,
DS4000,
+ MSO5000,
MSO7000A,
};
{1000, 1}, {500, 1000000}, 14, 1400, 14000},
[DSO1000] = {VENDOR(AGILENT), "DSO1000", PROTOCOL_V3, FORMAT_IEEE488_2,
{50, 1}, {2, 1000}, 12, 600, 20480},
+ [DSO1000B] = {VENDOR(AGILENT), "DSO1000", PROTOCOL_V3, FORMAT_IEEE488_2,
+ {50, 1}, {2, 1000}, 12, 600, 20480},
[DS1000Z] = {VENDOR(RIGOL), "DS1000Z", PROTOCOL_V4, FORMAT_IEEE488_2,
{50, 1}, {1, 1000}, 12, 1200, 12000000},
[DS4000] = {VENDOR(RIGOL), "DS4000", PROTOCOL_V4, FORMAT_IEEE488_2,
- {1000, 1}, {1, 1000}, 14, 1400, 14000},
+ {1000, 1}, {1, 1000}, 14, 1400, 0},
+ [MSO5000] = {VENDOR(RIGOL), "MSO5000", PROTOCOL_V5, FORMAT_IEEE488_2,
+ {1000, 1}, {500, 1000000}, 10, 1000, 0},
[MSO7000A] = {VENDOR(AGILENT), "MSO7000A", PROTOCOL_V4, FORMAT_IEEE488_2,
{50, 1}, {2, 1000}, 10, 1000, 8000000},
};
{SERIES(DSO1000), "DSO1014A", {2, 1000000000}, CH_INFO(4, false), std_cmd},
{SERIES(DSO1000), "DSO1022A", {2, 1000000000}, CH_INFO(2, false), std_cmd},
{SERIES(DSO1000), "DSO1024A", {2, 1000000000}, CH_INFO(4, false), std_cmd},
+ {SERIES(DSO1000B), "DSO1052B", {2, 1000000000}, CH_INFO(2, false), std_cmd},
+ {SERIES(DSO1000B), "DSO1072B", {2, 1000000000}, CH_INFO(2, false), std_cmd},
+ {SERIES(DSO1000B), "DSO1102B", {2, 1000000000}, CH_INFO(2, false), std_cmd},
+ {SERIES(DSO1000B), "DSO1152B", {2, 1000000000}, CH_INFO(2, false), std_cmd},
{SERIES(DS1000Z), "DS1054Z", {5, 1000000000}, CH_INFO(4, false), std_cmd},
{SERIES(DS1000Z), "DS1074Z", {5, 1000000000}, CH_INFO(4, false), std_cmd},
{SERIES(DS1000Z), "DS1104Z", {5, 1000000000}, CH_INFO(4, false), std_cmd},
{SERIES(DS1000Z), "MSO1074Z-S", {5, 1000000000}, CH_INFO(4, true), std_cmd},
{SERIES(DS1000Z), "MSO1104Z-S", {5, 1000000000}, CH_INFO(4, true), std_cmd},
{SERIES(DS4000), "DS4024", {1, 1000000000}, CH_INFO(4, false), std_cmd},
+ {SERIES(MSO5000), "MSO5072", {1, 1000000000}, CH_INFO(2, true), std_cmd},
+ {SERIES(MSO5000), "MSO5074", {1, 1000000000}, CH_INFO(4, true), std_cmd},
+ {SERIES(MSO5000), "MSO5102", {1, 1000000000}, CH_INFO(2, true), std_cmd},
+ {SERIES(MSO5000), "MSO5104", {1, 1000000000}, CH_INFO(4, true), std_cmd},
+ {SERIES(MSO5000), "MSO5204", {1, 1000000000}, CH_INFO(4, true), std_cmd},
+ {SERIES(MSO5000), "MSO5354", {1, 1000000000}, CH_INFO(4, true), std_cmd},
/* TODO: Digital channels are not yet supported on MSO7000A. */
{SERIES(MSO7000A), "MSO7034A", {2, 1000000000}, CH_INFO(4, false), mso7000a_cmd},
};
struct sr_datafeed_packet packet;
gboolean some_digital;
GSList *l;
+ char *cmd;
scpi = sdi->conn;
devc = sdi->priv;
}
if (ch->enabled != devc->digital_channels[ch->index]) {
/* Enabled channel is currently disabled, or vice versa. */
- if (rigol_ds_config_set(sdi,
- devc->model->series->protocol >= PROTOCOL_V3 ?
- ":LA:DIG%d:DISP %s" : ":DIG%d:TURN %s", ch->index,
+ if (devc->model->series->protocol >= PROTOCOL_V5)
+ cmd = ":LA:DISP D%d,%s";
+ else if (devc->model->series->protocol >= PROTOCOL_V3)
+ cmd = ":LA:DIG%d:DISP %s";
+ else
+ cmd = ":DIG%d:TURN %s";
+
+ if (rigol_ds_config_set(sdi, cmd, ch->index,
ch->enabled ? "ON" : "OFF") != SR_OK)
return SR_ERR;
devc->digital_channels[ch->index] = ch->enabled;
struct dev_context *devc;
gchar *trig_mode;
unsigned int num_channels, i, j;
+ int buffer_samples;
if (!(devc = sdi->priv))
return SR_ERR;
break;
case PROTOCOL_V3:
case PROTOCOL_V4:
+ case PROTOCOL_V5:
if (rigol_ds_config_set(sdi, ":WAV:FORM BYTE") != SR_OK)
return SR_ERR;
if (devc->data_source == DATA_SOURCE_LIVE) {
if (devc->model->series->protocol == PROTOCOL_V3) {
if (rigol_ds_config_set(sdi, ":WAV:MODE RAW") != SR_OK)
return SR_ERR;
- } else if (devc->model->series->protocol == PROTOCOL_V4) {
+ } else if (devc->model->series->protocol >= PROTOCOL_V4) {
num_channels = 0;
/* Channels 3 and 4 are multiplexed with D0-7 and D8-15 */
}
}
- devc->analog_frame_size = devc->digital_frame_size =
- num_channels == 1 ?
- devc->model->series->buffer_samples :
- num_channels == 2 ?
- devc->model->series->buffer_samples / 2 :
- devc->model->series->buffer_samples / 4;
+ buffer_samples = devc->model->series->buffer_samples;
+ if (buffer_samples == 0)
+ {
+ /* The DS4000 series does not have a fixed memory depth, it
+ * can be chosen from the menu and also varies with number
+ * of active channels. Retrieve the actual number with the
+ * ACQ:MDEP command. */
+ sr_scpi_get_int(sdi->conn, "ACQ:MDEP?", &buffer_samples);
+ devc->analog_frame_size = devc->digital_frame_size =
+ buffer_samples;
+ }
+ else
+ {
+ /* The DS1000Z series has a fixed memory depth which we
+ * need to divide correctly according to the number of
+ * active channels. */
+ devc->analog_frame_size = devc->digital_frame_size =
+ num_channels == 1 ?
+ buffer_samples :
+ num_channels == 2 ?
+ buffer_samples / 2 :
+ buffer_samples / 4;
+ }
}
if (rigol_ds_config_set(sdi, ":SING") != SR_OK)
}
break;
case PROTOCOL_V4:
+ case PROTOCOL_V5:
if (ch->type == SR_CHANNEL_ANALOG) {
if (rigol_ds_config_set(sdi, ":WAV:SOUR CHAN%d",
ch->index + 1) != SR_OK)
// TODO: For the MSO1000Z series, we need a way to express that
// this data is in fact just for a single channel, with the valid
// data for that channel in the LSB of each byte.
- logic.unitsize = devc->model->series->protocol == PROTOCOL_V4 ? 1 : 2;
+ logic.unitsize = devc->model->series->protocol >= PROTOCOL_V4 ? 1 : 2;
logic.data = devc->buffer;
packet.type = SR_DF_LOGIC;
packet.payload = &logic;
sr_dbg("Logic analyzer %s, current digital channel state:",
devc->la_enabled ? "enabled" : "disabled");
for (i = 0; i < ARRAY_SIZE(devc->digital_channels); i++) {
- cmd = g_strdup_printf(
- devc->model->series->protocol >= PROTOCOL_V3 ?
- ":LA:DIG%d:DISP?" : ":DIG%d:TURN?", i);
+ if (devc->model->series->protocol >= PROTOCOL_V5)
+ cmd = g_strdup_printf(":LA:DISP? D%d", i);
+ else if (devc->model->series->protocol >= PROTOCOL_V3)
+ cmd = g_strdup_printf(":LA:DIG%d:DISP?", i);
+ else
+ cmd = g_strdup_printf(":DIG%d:TURN?", i);
res = sr_scpi_get_bool(sdi->conn, cmd, &devc->digital_channels[i]);
g_free(cmd);
if (res != SR_OK)
PROTOCOL_V2, /* DS1000 */
PROTOCOL_V3, /* DS2000, DSO1000 */
PROTOCOL_V4, /* DS1000Z */
+ PROTOCOL_V5, /* MSO5000 */
};
enum data_format {
*/
#include <config.h>
-#include <libserialport.h>
#include <scpi.h>
#include <string.h>
#include "protocol.h"
-SR_PRIV struct sr_dev_driver rohde_schwarz_sme_0x_driver_info;
+static struct sr_dev_driver rohde_schwarz_sme_0x_driver_info;
static const char *manufacturer = "Rohde&Schwarz";
return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
}
-SR_PRIV struct sr_dev_driver rohde_schwarz_sme_0x_driver_info = {
+static struct sr_dev_driver rohde_schwarz_sme_0x_driver_info = {
.name = "rohde-schwarz-sme-0x",
.longname = "Rohde&Schwarz SME-0x",
.api_version = 1,
.dev_acquisition_stop = std_serial_dev_acquisition_stop,
.context = NULL,
};
-
SR_REGISTER_DEV_DRIVER(rohde_schwarz_sme_0x_driver_info);
#define BUF_SIZE (16 * 1024)
#define BUF_TIMEOUT 1000
-SR_PRIV struct sr_dev_driver saleae_logic_pro_driver_info;
-
static const uint32_t scanopts[] = {
SR_CONF_CONN,
};
return SR_OK;
}
-SR_PRIV struct sr_dev_driver saleae_logic_pro_driver_info = {
+static struct sr_dev_driver saleae_logic_pro_driver_info = {
.name = "saleae-logic-pro",
.longname = "Saleae Logic Pro",
.api_version = 1,
.dev_acquisition_stop = dev_acquisition_stop,
.context = NULL,
};
-
SR_REGISTER_DEV_DRIVER(saleae_logic_pro_driver_info);
return ret;
if (version != 0x10 && version != 0x13 && version != 0x40 && version != 0x41) {
- sr_err("Unsupported FPGA version: 0x%02x.", version);
- return SR_ERR;
+ sr_warn("Unsupported FPGA version: 0x%02x.", version);
}
return SR_OK;
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018 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 <string.h>
+#include "protocol.h"
+
+static struct sr_dev_driver scpi_dmm_driver_info;
+
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+ SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_MULTIMETER,
+};
+
+static const uint32_t devopts_generic[] = {
+ SR_CONF_CONTINUOUS,
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_MEASURED_QUANTITY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static const struct scpi_command cmdset_agilent[] = {
+ { DMM_CMD_SETUP_REMOTE, "\n", },
+ { DMM_CMD_SETUP_FUNC, "CONF:%s", },
+ { DMM_CMD_QUERY_FUNC, "CONF?", },
+ { DMM_CMD_START_ACQ, "MEAS", },
+ { DMM_CMD_STOP_ACQ, "ABORT", },
+ { DMM_CMD_QUERY_VALUE, "READ?", },
+ { DMM_CMD_QUERY_PREC, "CONF?", },
+ ALL_ZERO,
+};
+
+/*
+ * cmdset_hp is used for the 34401A, which was added to this code after the
+ * 34405A and 34465A. It differs in starting the measurement with INIT: using
+ * MEAS without a trailing '?' (as used for the 34405A) is not valid for the
+ * 34401A and gives an error.
+ * I'm surprised the same instruction sequence doesn't work and INIT may
+ * work for both, but I don't have the others to re-test.
+ *
+ * On the 34401A,
+ * MEAS <optional parameters> ? configures, arms, triggers and waits
+ * for a reading
+ * CONF <parameters> configures
+ * INIT prepares for triggering (trigger mode is not set, assumed
+ * internal - external might time out)
+ * *OPC waits for completion, and
+ * READ? retrieves the result
+ */
+static const struct scpi_command cmdset_hp[] = {
+ { DMM_CMD_SETUP_REMOTE, "\n", },
+ { DMM_CMD_SETUP_FUNC, "CONF:%s", },
+ { DMM_CMD_QUERY_FUNC, "CONF?", },
+ { DMM_CMD_START_ACQ, "INIT", },
+ { DMM_CMD_STOP_ACQ, "ABORT", },
+ { DMM_CMD_QUERY_VALUE, "READ?", },
+ { DMM_CMD_QUERY_PREC, "CONF?", },
+ ALL_ZERO,
+};
+
+static const struct mqopt_item mqopts_agilent_34405a[] = {
+ { SR_MQ_VOLTAGE, SR_MQFLAG_DC, "VOLT:DC", "VOLT ", NO_DFLT_PREC, },
+ { SR_MQ_VOLTAGE, SR_MQFLAG_AC, "VOLT:AC", "VOLT:AC ", NO_DFLT_PREC, },
+ { SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR ", NO_DFLT_PREC, },
+ { SR_MQ_CURRENT, SR_MQFLAG_AC, "CURR:AC", "CURR:AC ", NO_DFLT_PREC, },
+ { SR_MQ_RESISTANCE, 0, "RES", "RES ", NO_DFLT_PREC, },
+ { SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, },
+ { SR_MQ_CAPACITANCE, 0, "CAP", "CAP ", NO_DFLT_PREC, },
+ { SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, },
+ { SR_MQ_TEMPERATURE, 0, "TEMP", "TEMP ", NO_DFLT_PREC, },
+ { SR_MQ_FREQUENCY, 0, "FREQ", "FREQ ", NO_DFLT_PREC, },
+};
+
+static const struct mqopt_item mqopts_agilent_34401a[] = {
+ { SR_MQ_VOLTAGE, SR_MQFLAG_DC, "VOLT:DC", "VOLT ", NO_DFLT_PREC, },
+ { SR_MQ_VOLTAGE, SR_MQFLAG_AC, "VOLT:AC", "VOLT:AC ", NO_DFLT_PREC, },
+ { SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR ", NO_DFLT_PREC, },
+ { SR_MQ_CURRENT, SR_MQFLAG_AC, "CURR:AC", "CURR:AC ", NO_DFLT_PREC, },
+ { SR_MQ_RESISTANCE, 0, "RES", "RES ", NO_DFLT_PREC, },
+ { SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, "FRES", "FRES ", NO_DFLT_PREC, },
+ { SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, },
+ { SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, },
+ { SR_MQ_FREQUENCY, 0, "FREQ", "FREQ ", NO_DFLT_PREC, },
+ { SR_MQ_TIME, 0, "PER", "PER ", NO_DFLT_PREC, },
+};
+
+SR_PRIV const struct scpi_dmm_model models[] = {
+ {
+ "Agilent", "34405A",
+ 1, 5, cmdset_agilent, ARRAY_AND_SIZE(mqopts_agilent_34405a),
+ scpi_dmm_get_meas_agilent,
+ ARRAY_AND_SIZE(devopts_generic),
+ 0,
+ },
+ {
+ "Keysight", "34465A",
+ 1, 5, cmdset_agilent, ARRAY_AND_SIZE(mqopts_agilent_34405a),
+ scpi_dmm_get_meas_agilent,
+ ARRAY_AND_SIZE(devopts_generic),
+ 0,
+ },
+ {
+ "HP", "34401A",
+ 1, 6, cmdset_hp, ARRAY_AND_SIZE(mqopts_agilent_34401a),
+ scpi_dmm_get_meas_agilent,
+ ARRAY_AND_SIZE(devopts_generic),
+ /* 34401A: typ. 1020ms for AC readings (default is 1000ms). */
+ 1000 * 1500,
+ },
+};
+
+static const struct scpi_dmm_model *is_compatible(const char *vendor, const char *model)
+{
+ size_t i;
+ const struct scpi_dmm_model *entry;
+
+ for (i = 0; i < ARRAY_SIZE(models); i++) {
+ entry = &models[i];
+ if (!entry->vendor || !entry->model)
+ continue;
+ if (strcmp(vendor, entry->vendor) != 0)
+ continue;
+ if (strcmp(model, entry->model) != 0)
+ continue;
+ return entry;
+ }
+
+ return NULL;
+}
+
+static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
+{
+ struct sr_scpi_hw_info *hw_info;
+ int ret;
+ const char *vendor;
+ const struct scpi_dmm_model *model;
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ size_t i;
+ gchar *channel_name;
+
+ scpi_dmm_cmd_delay(scpi);
+ ret = sr_scpi_get_hw_id(scpi, &hw_info);
+ if (ret != SR_OK) {
+ sr_info("Could not get IDN response.");
+ return NULL;
+ }
+ vendor = sr_vendor_alias(hw_info->manufacturer);
+ model = is_compatible(vendor, hw_info->model);
+ if (!model) {
+ sr_scpi_hw_info_free(hw_info);
+ return NULL;
+ }
+
+ sdi = g_malloc0(sizeof(*sdi));
+ sdi->vendor = g_strdup(hw_info->manufacturer);
+ sdi->model = g_strdup(hw_info->model);
+ sdi->version = g_strdup(hw_info->firmware_version);
+ sdi->serial_num = g_strdup(hw_info->serial_number);
+ sdi->conn = scpi;
+ sdi->driver = &scpi_dmm_driver_info;
+ sdi->inst_type = SR_INST_SCPI;
+ sr_scpi_hw_info_free(hw_info);
+ if (model->read_timeout_us) /* non-default read timeout */
+ scpi->read_timeout_us = model->read_timeout_us;
+ devc = g_malloc0(sizeof(*devc));
+ sdi->priv = devc;
+ devc->num_channels = model->num_channels;
+ devc->cmdset = model->cmdset;
+ devc->model = model;
+
+ for (i = 0; i < devc->num_channels; i++) {
+ channel_name = g_strdup_printf("P%zu", i + 1);
+ sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, channel_name);
+ }
+
+ return sdi;
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+ return sr_scpi_scan(di->context, options, probe_device);
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+ int ret;
+
+ scpi = sdi->conn;
+ ret = sr_scpi_open(scpi);
+ if (ret < 0) {
+ sr_err("Failed to open SCPI device: %s.", sr_strerror(ret));
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+static int dev_close(struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+
+ scpi = sdi->conn;
+ if (!scpi)
+ return SR_ERR_BUG;
+
+ sr_dbg("DIAG: sdi->status %d.", sdi->status - SR_ST_NOT_FOUND);
+ if (sdi->status <= SR_ST_INACTIVE)
+ return SR_OK;
+
+ return sr_scpi_close(scpi);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ enum sr_mq mq;
+ enum sr_mqflag mqflag;
+ GVariant *arr[2];
+ int ret;
+
+ (void)cg;
+
+ devc = sdi->priv;
+
+ switch (key) {
+ case SR_CONF_LIMIT_SAMPLES:
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_get(&devc->limits, key, data);
+ case SR_CONF_MEASURED_QUANTITY:
+ ret = scpi_dmm_get_mq(sdi, &mq, &mqflag, NULL, NULL);
+ if (ret != SR_OK)
+ return ret;
+ arr[0] = g_variant_new_uint32(mq);
+ arr[1] = g_variant_new_uint64(mqflag);
+ *data = g_variant_new_tuple(arr, ARRAY_SIZE(arr));
+ return SR_OK;
+ default:
+ return SR_ERR_NA;
+ }
+}
+
+static int config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ enum sr_mq mq;
+ enum sr_mqflag mqflag;
+ GVariant *tuple_child;
+
+ (void)cg;
+
+ devc = sdi->priv;
+
+ switch (key) {
+ case SR_CONF_LIMIT_SAMPLES:
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_set(&devc->limits, key, data);
+ case SR_CONF_MEASURED_QUANTITY:
+ tuple_child = g_variant_get_child_value(data, 0);
+ mq = g_variant_get_uint32(tuple_child);
+ tuple_child = g_variant_get_child_value(data, 1);
+ mqflag = g_variant_get_uint64(tuple_child);
+ g_variant_unref(tuple_child);
+ return scpi_dmm_set_mq(sdi, mq, mqflag);
+ default:
+ return SR_ERR_NA;
+ }
+}
+
+static int config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ GVariant *gvar, *arr[2];
+ GVariantBuilder gvb;
+ size_t i;
+
+ (void)cg;
+
+ devc = sdi ? sdi->priv : NULL;
+
+ switch (key) {
+ case SR_CONF_SCAN_OPTIONS:
+ case SR_CONF_DEVICE_OPTIONS:
+ if (!devc)
+ return STD_CONFIG_LIST(key, data, sdi, cg,
+ scanopts, drvopts, devopts_generic);
+ return std_opts_config_list(key, data, sdi, cg,
+ ARRAY_AND_SIZE(scanopts), ARRAY_AND_SIZE(drvopts),
+ devc->model->devopts, devc->model->devopts_size);
+ case SR_CONF_MEASURED_QUANTITY:
+ /* TODO Use std_gvar_measured_quantities() when available. */
+ if (!devc)
+ return SR_ERR_ARG;
+ g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+ for (i = 0; i < devc->model->mqopt_size; i++) {
+ arr[0] = g_variant_new_uint32(devc->model->mqopts[i].mq);
+ arr[1] = g_variant_new_uint64(devc->model->mqopts[i].mqflag);
+ gvar = g_variant_new_tuple(arr, ARRAY_SIZE(arr));
+ g_variant_builder_add_value(&gvb, gvar);
+ }
+ *data = g_variant_builder_end(&gvb);
+ return SR_OK;
+ default:
+ (void)devc;
+ return SR_ERR_NA;
+ }
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+ struct dev_context *devc;
+ int ret;
+ const struct mqopt_item *item;
+ const char *command;
+
+ scpi = sdi->conn;
+ devc = sdi->priv;
+
+ ret = scpi_dmm_get_mq(sdi, &devc->start_acq_mq.curr_mq,
+ &devc->start_acq_mq.curr_mqflag, NULL, &item);
+ if (ret != SR_OK)
+ return ret;
+
+ command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_START_ACQ);
+ if (command && *command) {
+ scpi_dmm_cmd_delay(scpi);
+ ret = sr_scpi_send(scpi, command);
+ if (ret != SR_OK)
+ return ret;
+ }
+
+ sr_sw_limits_acquisition_start(&devc->limits);
+ ret = std_session_send_df_header(sdi);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = sr_scpi_source_add(sdi->session, scpi, G_IO_IN, 10,
+ scpi_dmm_receive_data, (void *)sdi);
+ if (ret != SR_OK)
+ return ret;
+
+ return SR_OK;
+}
+
+static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+ struct dev_context *devc;
+ const char *command;
+
+ scpi = sdi->conn;
+ devc = sdi->priv;
+
+ command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_STOP_ACQ);
+ if (command && *command) {
+ scpi_dmm_cmd_delay(scpi);
+ (void)sr_scpi_send(scpi, command);
+ }
+ sr_scpi_source_remove(sdi->session, scpi);
+
+ std_session_send_df_end(sdi);
+
+ return SR_OK;
+}
+
+static struct sr_dev_driver scpi_dmm_driver_info = {
+ .name = "scpi-dmm",
+ .longname = "SCPI DMM",
+ .api_version = 1,
+ .init = std_init,
+ .cleanup = std_cleanup,
+ .scan = scan,
+ .dev_list = std_dev_list,
+ .dev_clear = std_dev_clear,
+ .config_get = config_get,
+ .config_set = config_set,
+ .config_list = config_list,
+ .dev_open = dev_open,
+ .dev_close = dev_close,
+ .dev_acquisition_start = dev_acquisition_start,
+ .dev_acquisition_stop = dev_acquisition_stop,
+ .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(scpi_dmm_driver_info);
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018 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 <math.h>
+#include <string.h>
+#include "protocol.h"
+
+#define WITH_CMD_DELAY 0 /* TODO See which devices need delays. */
+
+SR_PRIV void scpi_dmm_cmd_delay(struct sr_scpi_dev_inst *scpi)
+{
+ if (WITH_CMD_DELAY)
+ g_usleep(WITH_CMD_DELAY * 1000);
+ sr_scpi_get_opc(scpi);
+}
+
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_number(
+ const struct sr_dev_inst *sdi, enum sr_mq mq, enum sr_mqflag flag)
+{
+ struct dev_context *devc;
+ size_t i;
+ const struct mqopt_item *item;
+
+ devc = sdi->priv;
+ for (i = 0; i < devc->model->mqopt_size; i++) {
+ item = &devc->model->mqopts[i];
+ if (item->mq != mq || item->mqflag != flag)
+ continue;
+ return item;
+ }
+
+ return NULL;
+}
+
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_text(
+ const struct sr_dev_inst *sdi, const char *text)
+{
+ struct dev_context *devc;
+ size_t i;
+ const struct mqopt_item *item;
+
+ devc = sdi->priv;
+ for (i = 0; i < devc->model->mqopt_size; i++) {
+ item = &devc->model->mqopts[i];
+ if (!item->scpi_func_query || !item->scpi_func_query[0])
+ continue;
+ if (!g_str_has_prefix(text, item->scpi_func_query))
+ continue;
+ return item;
+ }
+
+ return NULL;
+}
+
+SR_PRIV int scpi_dmm_get_mq(const struct sr_dev_inst *sdi,
+ enum sr_mq *mq, enum sr_mqflag *flag, char **rsp,
+ const struct mqopt_item **mqitem)
+{
+ struct dev_context *devc;
+ const char *command;
+ char *response;
+ const char *have;
+ int ret;
+ const struct mqopt_item *item;
+
+ devc = sdi->priv;
+ if (mq)
+ *mq = 0;
+ if (flag)
+ *flag = 0;
+ if (rsp)
+ *rsp = NULL;
+ if (mqitem)
+ *mqitem = NULL;
+
+ scpi_dmm_cmd_delay(sdi->conn);
+ command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_QUERY_FUNC);
+ if (!command || !*command)
+ return SR_ERR_NA;
+ response = NULL;
+ ret = sr_scpi_get_string(sdi->conn, command, &response);
+ if (ret != SR_OK)
+ return ret;
+ if (!response || !*response)
+ return SR_ERR_NA;
+ have = response;
+ if (*have == '"')
+ have++;
+
+ ret = SR_ERR_NA;
+ item = scpi_dmm_lookup_mq_text(sdi, have);
+ if (item) {
+ if (mq)
+ *mq = item->mq;
+ if (flag)
+ *flag = item->mqflag;
+ if (mqitem)
+ *mqitem = item;
+ ret = SR_OK;
+ }
+
+ if (rsp) {
+ *rsp = response;
+ response = NULL;
+ }
+ g_free(response);
+
+ return ret;
+}
+
+SR_PRIV int scpi_dmm_set_mq(const struct sr_dev_inst *sdi,
+ enum sr_mq mq, enum sr_mqflag flag)
+{
+ struct dev_context *devc;
+ const struct mqopt_item *item;
+ const char *mode, *command;
+ int ret;
+
+ devc = sdi->priv;
+ item = scpi_dmm_lookup_mq_number(sdi, mq, flag);
+ if (!item)
+ return SR_ERR_NA;
+
+ mode = item->scpi_func_setup;
+ command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_SETUP_FUNC);
+ scpi_dmm_cmd_delay(sdi->conn);
+ ret = sr_scpi_send(sdi->conn, command, mode);
+ if (ret != SR_OK)
+ return ret;
+
+ return SR_OK;
+}
+
+SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch)
+{
+ struct sr_scpi_dev_inst *scpi;
+ struct dev_context *devc;
+ struct scpi_dmm_acq_info *info;
+ struct sr_datafeed_analog *analog;
+ int ret;
+ enum sr_mq mq;
+ enum sr_mqflag mqflag;
+ char *mode_response;
+ const char *p;
+ char **fields;
+ size_t count;
+ char prec_text[20];
+ const struct mqopt_item *item;
+ int prec_exp;
+ const char *command;
+ char *response;
+ gboolean use_double;
+ int sig_digits, val_exp;
+ int digits;
+ enum sr_unit unit;
+
+ scpi = sdi->conn;
+ devc = sdi->priv;
+ info = &devc->run_acq_info;
+ analog = &info->analog[ch];
+
+ /*
+ * Get the meter's current mode, keep the response around.
+ * Skip the measurement if the mode is uncertain.
+ */
+ ret = scpi_dmm_get_mq(sdi, &mq, &mqflag, &mode_response, &item);
+ if (ret != SR_OK) {
+ g_free(mode_response);
+ return ret;
+ }
+ if (!mode_response)
+ return SR_ERR;
+ if (!mq) {
+ g_free(mode_response);
+ return +1;
+ }
+
+ /*
+ * Get the last comma separated field of the function query
+ * response, or fallback to the model's default precision for
+ * the current function. This copes with either of these cases:
+ * VOLT +1.00000E-01,+1.00000E-06
+ * DIOD
+ * TEMP THER,5000,+1.00000E+00,+1.00000E-01
+ */
+ p = sr_scpi_unquote_string(mode_response);
+ fields = g_strsplit(p, ",", 0);
+ count = g_strv_length(fields);
+ if (count >= 2) {
+ snprintf(prec_text, sizeof(prec_text),
+ "%s", fields[count - 1]);
+ p = prec_text;
+ } else if (!item) {
+ p = NULL;
+ } else if (item->default_precision == NO_DFLT_PREC) {
+ p = NULL;
+ } else {
+ snprintf(prec_text, sizeof(prec_text),
+ "1e%d", item->default_precision);
+ p = prec_text;
+ }
+ g_strfreev(fields);
+
+ /*
+ * Need to extract the exponent value ourselves, since a strtod()
+ * call will "eat" the exponent, too. Strip space, strip sign,
+ * strip float number (without! exponent), check for exponent
+ * and get exponent value. Accept absence of Esnn suffixes.
+ */
+ while (p && *p && g_ascii_isspace(*p))
+ p++;
+ if (p && *p && (*p == '+' || *p == '-'))
+ p++;
+ while (p && *p && g_ascii_isdigit(*p))
+ p++;
+ if (p && *p && *p == '.')
+ p++;
+ while (p && *p && g_ascii_isdigit(*p))
+ p++;
+ ret = SR_OK;
+ if (!p || !*p)
+ prec_exp = 0;
+ else if (*p != 'e' && *p != 'E')
+ ret = SR_ERR_DATA;
+ else
+ ret = sr_atoi(++p, &prec_exp);
+ g_free(mode_response);
+ if (ret != SR_OK)
+ return ret;
+
+ /*
+ * Get the measurement value. Make sure to strip trailing space
+ * or else number conversion may fail in fatal ways. Detect OL
+ * conditions. Determine the measurement's precision: Count the
+ * number of significant digits before the period, and get the
+ * exponent's value.
+ *
+ * The text presentation of values is like this:
+ * +1.09450000E-01
+ * Skip space/sign, count digits before the period, skip to the
+ * exponent, get exponent value.
+ *
+ * TODO Can sr_parse_rational() return the exponent for us? In
+ * addition to providing a precise rational value instead of a
+ * float that's an approximation of the received value? Can the
+ * 'analog' struct that we fill in carry rationals?
+ *
+ * Use double precision FP here during conversion. Optionally
+ * downgrade to single precision later to reduce the amount of
+ * logged information.
+ */
+ command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_QUERY_VALUE);
+ if (!command || !*command)
+ return SR_ERR_NA;
+ scpi_dmm_cmd_delay(scpi);
+ ret = sr_scpi_get_string(scpi, command, &response);
+ if (ret != SR_OK)
+ return ret;
+ g_strstrip(response);
+ use_double = devc->model->digits > 6;
+ ret = sr_atod_ascii(response, &info->d_value);
+ if (ret != SR_OK) {
+ g_free(response);
+ return ret;
+ }
+ if (!response)
+ return SR_ERR;
+ if (info->d_value > +9e37) {
+ info->d_value = +INFINITY;
+ } else if (info->d_value < -9e37) {
+ info->d_value = -INFINITY;
+ } else {
+ p = response;
+ while (p && *p && g_ascii_isspace(*p))
+ p++;
+ if (p && *p && (*p == '-' || *p == '+'))
+ p++;
+ sig_digits = 0;
+ while (p && *p && g_ascii_isdigit(*p)) {
+ sig_digits++;
+ p++;
+ }
+ if (p && *p && *p == '.')
+ p++;
+ while (p && *p && g_ascii_isdigit(*p))
+ p++;
+ ret = SR_OK;
+ if (!p || !*p)
+ val_exp = 0;
+ else if (*p != 'e' && *p != 'E')
+ ret = SR_ERR_DATA;
+ else
+ ret = sr_atoi(++p, &val_exp);
+ }
+ g_free(response);
+ if (ret != SR_OK)
+ return ret;
+ /*
+ * TODO Come up with the most appropriate 'digits' calculation.
+ * This implementation assumes that either the device provides
+ * the resolution with the query for the meter's function, or
+ * the driver uses a fallback text pretending the device had
+ * provided it. This works with supported Agilent devices.
+ *
+ * An alternative may be to assume a given digits count which
+ * depends on the device, and adjust that count based on the
+ * value's significant digits and exponent. But this approach
+ * fails if devices change their digits count depending on
+ * modes or user requests, and also fails when e.g. devices
+ * with "100000 counts" can provide values between 100000 and
+ * 120000 in either 4 or 5 digits modes, depending on the most
+ * recent trend of the values. This less robust approach should
+ * only be taken if the mode inquiry won't yield the resolution
+ * (as e.g. DIOD does on 34405A, though we happen to know the
+ * fixed resolution for this very mode on this very model).
+ *
+ * For now, let's keep the prepared code path for the second
+ * approach in place, should some Agilent devices need it yet
+ * benefit from re-using most of the remaining acquisition
+ * routine.
+ */
+#if 1
+ digits = -prec_exp;
+#else
+ digits = devc->model->digits;
+ digits -= sig_digits;
+ digits -= val_exp;
+#endif
+
+ /*
+ * Fill in the 'analog' description: value, encoding, meaning.
+ * Callers will fill in the sample count, and channel name,
+ * and will send out the packet.
+ */
+ if (use_double) {
+ analog->data = &info->d_value;
+ analog->encoding->unitsize = sizeof(info->d_value);
+ } else {
+ info->f_value = info->d_value;
+ analog->data = &info->f_value;
+ analog->encoding->unitsize = sizeof(info->f_value);
+ }
+ analog->encoding->is_float = TRUE;
+#ifdef WORDS_BIGENDIAN
+ analog->encoding->is_bigendian = TRUE;
+#else
+ analog->encoding->is_bigendian = FALSE;
+#endif
+ analog->encoding->digits = digits;
+ analog->meaning->mq = mq;
+ analog->meaning->mqflags = mqflag;
+ switch (mq) {
+ case SR_MQ_VOLTAGE:
+ unit = SR_UNIT_VOLT;
+ break;
+ case SR_MQ_CURRENT:
+ unit = SR_UNIT_AMPERE;
+ break;
+ case SR_MQ_RESISTANCE:
+ case SR_MQ_CONTINUITY:
+ unit = SR_UNIT_OHM;
+ break;
+ case SR_MQ_CAPACITANCE:
+ unit = SR_UNIT_FARAD;
+ break;
+ case SR_MQ_TEMPERATURE:
+ unit = SR_UNIT_CELSIUS;
+ break;
+ case SR_MQ_FREQUENCY:
+ unit = SR_UNIT_HERTZ;
+ break;
+ case SR_MQ_TIME:
+ unit = SR_UNIT_SECOND;
+ break;
+ default:
+ return SR_ERR_NA;
+ }
+ analog->meaning->unit = unit;
+ analog->spec->spec_digits = digits;
+
+ return SR_OK;
+}
+
+/* Strictly speaking this is a timer controlled poll routine. */
+SR_PRIV int scpi_dmm_receive_data(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct sr_scpi_dev_inst *scpi;
+ struct dev_context *devc;
+ struct scpi_dmm_acq_info *info;
+ gboolean sent_sample;
+ size_t ch;
+ struct sr_channel *channel;
+ int ret;
+
+ (void)fd;
+ (void)revents;
+
+ sdi = cb_data;
+ if (!sdi)
+ return TRUE;
+ scpi = sdi->conn;
+ devc = sdi->priv;
+ if (!scpi || !devc)
+ return TRUE;
+ info = &devc->run_acq_info;
+
+ sent_sample = FALSE;
+ ret = SR_OK;
+ for (ch = 0; ch < devc->num_channels; ch++) {
+ /* Check the channel's enabled status. */
+ channel = g_slist_nth_data(sdi->channels, ch);
+ if (!channel->enabled)
+ continue;
+
+ /*
+ * Prepare an analog measurement value. Note that digits
+ * will get updated later.
+ */
+ info->packet.type = SR_DF_ANALOG;
+ info->packet.payload = &info->analog[ch];
+ sr_analog_init(&info->analog[ch], &info->encoding[ch],
+ &info->meaning[ch], &info->spec[ch], 0);
+
+ /* Just check OPC before sending another request. */
+ scpi_dmm_cmd_delay(sdi->conn);
+
+ /*
+ * Have the model take and interpret a measurement. Lack
+ * of support is pointless, failed retrieval/conversion
+ * is considered fatal. The routine will fill in the
+ * 'analog' details, except for channel name and sample
+ * count (assume one value per channel).
+ *
+ * Note that non-zero non-negative return codes signal
+ * that the channel's data shell get skipped in this
+ * iteration over the channels. This copes with devices
+ * or modes where channels may provide data at different
+ * rates.
+ */
+ if (!devc->model->get_measurement) {
+ ret = SR_ERR_NA;
+ break;
+ }
+ ret = devc->model->get_measurement(sdi, ch);
+ if (ret > 0)
+ continue;
+ if (ret != SR_OK)
+ break;
+
+ /* Send the packet that was filled in by the model's routine. */
+ info->analog[ch].num_samples = 1;
+ info->analog[ch].meaning->channels = g_slist_append(NULL, channel);
+ sr_session_send(sdi, &info->packet);
+ g_slist_free(info->analog[ch].meaning->channels);
+ sent_sample = TRUE;
+ }
+ if (sent_sample)
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
+ if (ret != SR_OK) {
+ /* Stop acquisition upon communication or data errors. */
+ sr_dev_acquisition_stop(sdi);
+ return TRUE;
+ }
+ if (sr_sw_limits_check(&devc->limits))
+ sr_dev_acquisition_stop(sdi);
+
+ return TRUE;
+}
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018 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/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_SCPI_DMM_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_SCPI_DMM_PROTOCOL_H
+
+#include <config.h>
+#include <glib.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "scpi.h"
+
+#define LOG_PREFIX "scpi-dmm"
+
+#define SCPI_DMM_MAX_CHANNELS 1
+
+enum scpi_dmm_cmdcode {
+ DMM_CMD_SETUP_REMOTE,
+ DMM_CMD_SETUP_FUNC,
+ DMM_CMD_QUERY_FUNC,
+ DMM_CMD_START_ACQ,
+ DMM_CMD_STOP_ACQ,
+ DMM_CMD_QUERY_VALUE,
+ DMM_CMD_QUERY_PREC,
+};
+
+struct mqopt_item {
+ enum sr_mq mq;
+ enum sr_mqflag mqflag;
+ const char *scpi_func_setup;
+ const char *scpi_func_query;
+ int default_precision;
+};
+#define NO_DFLT_PREC -99
+
+struct scpi_dmm_model {
+ const char *vendor;
+ const char *model;
+ size_t num_channels;
+ ssize_t digits;
+ const struct scpi_command *cmdset;
+ const struct mqopt_item *mqopts;
+ size_t mqopt_size;
+ int (*get_measurement)(const struct sr_dev_inst *sdi, size_t ch);
+ const uint32_t *devopts;
+ size_t devopts_size;
+ unsigned int read_timeout_us; /* If zero, use default from src/scpi/scpi.c. */
+};
+
+struct dev_context {
+ size_t num_channels;
+ const struct scpi_command *cmdset;
+ const struct scpi_dmm_model *model;
+ struct sr_sw_limits limits;
+ struct {
+ enum sr_mq curr_mq;
+ enum sr_mqflag curr_mqflag;
+ } start_acq_mq;
+ struct scpi_dmm_acq_info {
+ float f_value;
+ double d_value;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog[SCPI_DMM_MAX_CHANNELS];
+ struct sr_analog_encoding encoding[SCPI_DMM_MAX_CHANNELS];
+ struct sr_analog_meaning meaning[SCPI_DMM_MAX_CHANNELS];
+ struct sr_analog_spec spec[SCPI_DMM_MAX_CHANNELS];
+ } run_acq_info;
+};
+
+SR_PRIV void scpi_dmm_cmd_delay(struct sr_scpi_dev_inst *scpi);
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_number(
+ const struct sr_dev_inst *sdi, enum sr_mq mq, enum sr_mqflag flag);
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_text(
+ const struct sr_dev_inst *sdi, const char *text);
+SR_PRIV int scpi_dmm_get_mq(const struct sr_dev_inst *sdi,
+ enum sr_mq *mq, enum sr_mqflag *flag, char **rsp,
+ const struct mqopt_item **mqitem);
+SR_PRIV int scpi_dmm_set_mq(const struct sr_dev_inst *sdi,
+ enum sr_mq mq, enum sr_mqflag flag);
+SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch);
+SR_PRIV int scpi_dmm_receive_data(int fd, int revents, void *cb_data);
+
+#endif
* This file is part of the libsigrok project.
*
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
- * Copyright (C) 2017 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2017,2019 Frank Stettner <frank-stettner@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
for (l = sdi->channels; l; l = l->next) {
ch = l->data;
pch = ch->priv;
+ /* Add mqflags from channel_group_spec only to voltage
+ * and current channels.
+ */
+ if (pch->mq == SR_MQ_VOLTAGE || pch->mq == SR_MQ_CURRENT)
+ pch->mqflags = cgs->mqflags;
+ else
+ pch->mqflags = 0;
if (pch->hw_output_idx == j)
cg->channels = g_slist_append(cg->channels, ch);
}
sr_scpi_hw_info_free(hw_info);
hw_info = NULL;
- sr_scpi_cmd(sdi, devc->device->commands, 0, NULL, SCPI_CMD_LOCAL);
+ /* Don't send SCPI_CMD_LOCAL for HP 66xxB using SCPI over GPIB. */
+ if (!(devc->device->dialect == SCPI_DIALECT_HP_66XXB &&
+ scpi->transport == SCPI_TRANSPORT_LIBGPIB))
+ sr_scpi_cmd(sdi, devc->device->commands, 0, NULL, SCPI_CMD_LOCAL);
return sdi;
}
return SR_ERR;
devc = sdi->priv;
- sr_scpi_cmd(sdi, devc->device->commands, 0, NULL, SCPI_CMD_REMOTE);
+
+ /* Don't send SCPI_CMD_REMOTE for HP 66xxB using SCPI over GPIB. */
+ if (!(devc->device->dialect == SCPI_DIALECT_HP_66XXB &&
+ scpi->transport == SCPI_TRANSPORT_LIBGPIB))
+ sr_scpi_cmd(sdi, devc->device->commands, 0, NULL, SCPI_CMD_REMOTE);
+
devc->beeper_was_set = FALSE;
if (sr_scpi_cmd_resp(sdi, devc->device->commands, 0, NULL,
&beeper, G_VARIANT_TYPE_BOOLEAN, SCPI_CMD_BEEPER) == SR_OK) {
if (devc->beeper_was_set)
sr_scpi_cmd(sdi, devc->device->commands,
0, NULL, SCPI_CMD_BEEPER_ENABLE);
- sr_scpi_cmd(sdi, devc->device->commands, 0, NULL, SCPI_CMD_LOCAL);
+
+ /* Don't send SCPI_CMD_LOCAL for HP 66xxB using SCPI over GPIB. */
+ if (!(devc->device->dialect == SCPI_DIALECT_HP_66XXB &&
+ scpi->transport == SCPI_TRANSPORT_LIBGPIB))
+ sr_scpi_cmd(sdi, devc->device->commands, 0, NULL, SCPI_CMD_LOCAL);
return sr_scpi_close(scpi);
}
char *channel_group_name;
int cmd, ret;
const char *s;
+ int reg;
if (!sdi)
return SR_ERR_ARG;
cmd = SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ENABLED;
break;
case SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE:
- gvtype = G_VARIANT_TYPE_BOOLEAN;
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB ||
+ devc->device->dialect == SCPI_DIALECT_HP_COMP)
+ gvtype = G_VARIANT_TYPE_STRING;
+ else
+ gvtype = G_VARIANT_TYPE_BOOLEAN;
cmd = SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ACTIVE;
break;
case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD:
cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ENABLED;
break;
case SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE:
- gvtype = G_VARIANT_TYPE_BOOLEAN;
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB ||
+ devc->device->dialect == SCPI_DIALECT_HP_COMP)
+ gvtype = G_VARIANT_TYPE_STRING;
+ else
+ gvtype = G_VARIANT_TYPE_BOOLEAN;
cmd = SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE;
break;
case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD:
gvtype = G_VARIANT_TYPE_BOOLEAN;
cmd = SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION;
break;
+ case SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE:
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB ||
+ devc->device->dialect == SCPI_DIALECT_HP_COMP)
+ gvtype = G_VARIANT_TYPE_STRING;
+ else
+ gvtype = G_VARIANT_TYPE_BOOLEAN;
+ cmd = SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION_ACTIVE;
+ break;
case SR_CONF_REGULATION:
gvtype = G_VARIANT_TYPE_STRING;
cmd = SCPI_CMD_GET_OUTPUT_REGULATION;
channel_group_cmd, channel_group_name, data, gvtype, cmd);
g_free(channel_group_name);
+ /*
+ * Handle special cases
+ */
+
if (cmd == SCPI_CMD_GET_OUTPUT_REGULATION) {
- /*
- * The Rigol DP800 series return CV/CC/UR, Philips PM2800
- * return VOLT/CURR. We always return a GVariant string in
- * the Rigol notation.
- */
- s = g_variant_get_string(*data, NULL);
- if (!strcmp(s, "VOLT")) {
+ if (devc->device->dialect == SCPI_DIALECT_PHILIPS) {
+ /*
+ * The Philips PM2800 series returns VOLT/CURR. We always return
+ * a GVariant string in the Rigol notation (CV/CC/UR).
+ */
+ s = g_variant_get_string(*data, NULL);
+ if (!g_strcmp0(s, "VOLT")) {
+ g_variant_unref(*data);
+ *data = g_variant_new_string("CV");
+ } else if (!g_strcmp0(s, "CURR")) {
+ g_variant_unref(*data);
+ *data = g_variant_new_string("CC");
+ }
+ }
+ if (devc->device->dialect == SCPI_DIALECT_HP_COMP) {
+ /* Evaluate Status Register from a HP 66xx in COMP mode. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
g_variant_unref(*data);
- *data = g_variant_new_string("CV");
- } else if (!strcmp(s, "CURR")) {
+ if (reg & (1 << 0))
+ *data = g_variant_new_string("CV");
+ else if (reg & (1 << 1))
+ *data = g_variant_new_string("CC");
+ else if (reg & (1 << 2))
+ *data = g_variant_new_string("UR");
+ else if (reg & (1 << 9))
+ *data = g_variant_new_string("CC-");
+ else
+ *data = g_variant_new_string("");
+ }
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB) {
+ /* Evaluate Operational Status Register from a HP 66xxB. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
g_variant_unref(*data);
- *data = g_variant_new_string("CC");
+ if (reg & (1 << 8))
+ *data = g_variant_new_string("CV");
+ else if (reg & (1 << 10))
+ *data = g_variant_new_string("CC");
+ else if (reg & (1 << 11))
+ *data = g_variant_new_string("CC-");
+ else
+ *data = g_variant_new_string("UR");
}
s = g_variant_get_string(*data, NULL);
- if (strcmp(s, "CV") && strcmp(s, "CC") && strcmp(s, "UR")) {
- sr_dbg("Unknown response to SCPI_CMD_GET_OUTPUT_REGULATION: %s", s);
+ if (g_strcmp0(s, "CV") && g_strcmp0(s, "CC") && g_strcmp0(s, "CC-") &&
+ g_strcmp0(s, "UR") && g_strcmp0(s, "")) {
+
+ sr_err("Unknown response to SCPI_CMD_GET_OUTPUT_REGULATION: %s", s);
ret = SR_ERR_DATA;
}
}
+ if (cmd == SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ACTIVE) {
+ if (devc->device->dialect == SCPI_DIALECT_HP_COMP) {
+ /* Evaluate Status Register from a HP 66xx in COMP mode. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
+ g_variant_unref(*data);
+ *data = g_variant_new_boolean(reg & (1 << 3));
+ }
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB) {
+ /* Evaluate Questionable Status Register bit 0 from a HP 66xxB. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
+ g_variant_unref(*data);
+ *data = g_variant_new_boolean(reg & (1 << 0));
+ }
+ }
+
+ if (cmd == SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE) {
+ if (devc->device->dialect == SCPI_DIALECT_HP_COMP) {
+ /* Evaluate Status Register from a HP 66xx in COMP mode. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
+ g_variant_unref(*data);
+ *data = g_variant_new_boolean(reg & (1 << 6));
+ }
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB) {
+ /* Evaluate Questionable Status Register bit 1 from a HP 66xxB. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
+ g_variant_unref(*data);
+ *data = g_variant_new_boolean(reg & (1 << 1));
+ }
+ }
+
+ if (cmd == SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION_ACTIVE) {
+ if (devc->device->dialect == SCPI_DIALECT_HP_COMP) {
+ /* Evaluate Status Register from a HP 66xx in COMP mode. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
+ g_variant_unref(*data);
+ *data = g_variant_new_boolean(reg & (1 << 4));
+ }
+ if (devc->device->dialect == SCPI_DIALECT_HP_66XXB) {
+ /* Evaluate Questionable Status Register bit 4 from a HP 66xxB. */
+ s = g_variant_get_string(*data, NULL);
+ sr_atoi(s, ®);
+ g_variant_unref(*data);
+ *data = g_variant_new_boolean(reg & (1 << 4));
+ }
+ }
+
return ret;
}
/* Prime the pipe with the first channel. */
devc->cur_acquisition_channel = sr_next_enabled_channel(sdi, NULL);
+ /* Device specific initialization before acquisition starts. */
+ if (devc->device->init_acquisition)
+ devc->device->init_acquisition(sdi);
+
if ((ret = sr_scpi_source_add(sdi->session, scpi, G_IO_IN, 10,
scpi_pps_receive_data, (void *)sdi)) != SR_OK)
return ret;
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
* Copyright (C) 2015 Google, Inc.
* (Written by Alexandru Gagniuc <mrnuke@google.com> for Google, Inc.)
- * Copyright (C) 2017 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2017,2019 Frank Stettner <frank-stettner@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
};
static const struct channel_group_spec agilent_n5700a_cg[] = {
- { "1", CH_IDX(0), PPS_OVP | PPS_OCP },
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
};
static const struct channel_spec agilent_n5767a_ch[] = {
ALL_ZERO
};
+/* BK Precision 9130 series */
+static const uint32_t bk_9130_devopts[] = {
+ SR_CONF_CONTINUOUS,
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t bk_9130_devopts_cg[] = {
+ SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_VOLTAGE | SR_CONF_GET,
+ SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_CURRENT | SR_CONF_GET,
+ SR_CONF_CURRENT_LIMIT | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const struct channel_spec bk_9130_ch[] = {
+ { "1", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+ { "2", { 0, 30, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 90, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+ { "3", { 0, 5, 0.001, 3, 3 }, { 0, 3, 0.001, 3, 3 }, { 0, 15, 0, 3, 3 }, FREQ_DC_ONLY, NO_OVP_LIMITS, NO_OCP_LIMITS },
+};
+
+static const struct channel_group_spec bk_9130_cg[] = {
+ { "1", CH_IDX(0), PPS_OVP, SR_MQFLAG_DC },
+ { "2", CH_IDX(1), PPS_OVP, SR_MQFLAG_DC },
+ { "3", CH_IDX(2), PPS_OVP, SR_MQFLAG_DC },
+};
+
+static const struct scpi_command bk_9130_cmd[] = {
+ { SCPI_CMD_REMOTE, "SYST:REMOTE" },
+ { SCPI_CMD_LOCAL, "SYST:LOCAL" },
+ { SCPI_CMD_SELECT_CHANNEL, ":INST:NSEL %s" },
+ { SCPI_CMD_GET_MEAS_VOLTAGE, ":MEAS:VOLT?" },
+ { SCPI_CMD_GET_MEAS_CURRENT, ":MEAS:CURR?" },
+ { SCPI_CMD_GET_MEAS_POWER, ":MEAS:POWER?" },
+ { SCPI_CMD_GET_VOLTAGE_TARGET, ":SOUR:VOLT?" },
+ { SCPI_CMD_SET_VOLTAGE_TARGET, ":SOUR:VOLT %.6f" },
+ { SCPI_CMD_GET_CURRENT_LIMIT, ":SOUR:CURR?" },
+ { SCPI_CMD_SET_CURRENT_LIMIT, ":SOUR:CURR %.6f" },
+ { SCPI_CMD_GET_OUTPUT_ENABLED, ":OUTP?" },
+ { SCPI_CMD_SET_OUTPUT_ENABLE, ":OUTP 1" },
+ { SCPI_CMD_SET_OUTPUT_DISABLE, ":OUTP 0" },
+ { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_THRESHOLD, ":SOUR:VOLT:PROT?" },
+ { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_THRESHOLD, ":SOUR:VOLT:PROT %.6f" },
+ ALL_ZERO
+};
+
/* Chroma 61600 series AC source */
static const uint32_t chroma_61604_devopts[] = {
SR_CONF_CONTINUOUS,
};
static const struct channel_group_spec chroma_61604_cg[] = {
- { "1", CH_IDX(0), PPS_OVP | PPS_OCP },
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_AC },
};
static const struct scpi_command chroma_61604_cmd[] = {
};
static const struct channel_group_spec chroma_62000_cg[] = {
- { "1", CH_IDX(0), PPS_OVP | PPS_OCP },
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
};
static const struct scpi_command chroma_62000_cmd[] = {
};
static const struct channel_group_spec rigol_dp700_cg[] = {
- { "1", CH_IDX(0), PPS_OVP | PPS_OCP },
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
};
/* Same as the DP800 series, except for the missing :SYST:OTP* commands. */
};
static const struct channel_group_spec rigol_dp820_cg[] = {
- { "1", CH_IDX(0), PPS_OVP | PPS_OCP },
- { "2", CH_IDX(1), PPS_OVP | PPS_OCP },
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
+ { "2", CH_IDX(1), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
};
static const struct channel_group_spec rigol_dp830_cg[] = {
- { "1", CH_IDX(0), PPS_OVP | PPS_OCP },
- { "2", CH_IDX(1), PPS_OVP | PPS_OCP },
- { "3", CH_IDX(2), PPS_OVP | PPS_OCP },
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
+ { "2", CH_IDX(1), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
+ { "3", CH_IDX(2), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
};
static const struct scpi_command rigol_dp800_cmd[] = {
ALL_ZERO
};
-/* HP 663xx series */
+/* HP 663xA series */
static const uint32_t hp_6630a_devopts[] = {
SR_CONF_CONTINUOUS,
SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
SR_CONF_CURRENT | SR_CONF_GET,
SR_CONF_VOLTAGE_TARGET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_CURRENT_LIMIT | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET,
SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_OVER_CURRENT_PROTECTION_ENABLED | SR_CONF_SET,
+ SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET,
+ SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE | SR_CONF_GET,
+ SR_CONF_REGULATION | SR_CONF_GET,
};
+static const struct channel_spec hp_6632a_ch[] = {
+ { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
+};
+
+static const struct channel_spec hp_6633a_ch[] = {
+ { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS },
+};
+
+static const struct channel_spec hp_6634a_ch[] = {
+ { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS },
+};
+
+static const struct channel_group_spec hp_6630a_cg[] = {
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
+};
+
+static const struct scpi_command hp_6630a_cmd[] = {
+ { SCPI_CMD_SET_OUTPUT_ENABLE, "OUT 1" },
+ { SCPI_CMD_SET_OUTPUT_DISABLE, "OUT 0" },
+ { SCPI_CMD_GET_MEAS_VOLTAGE, "VOUT?" },
+ { SCPI_CMD_GET_MEAS_CURRENT, "IOUT?" },
+ { SCPI_CMD_SET_VOLTAGE_TARGET, "VSET %.4f" },
+ { SCPI_CMD_SET_CURRENT_LIMIT, "ISET %.4f" },
+ { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ACTIVE, "STS?" },
+ { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_THRESHOLD, "OVSET %.4f" },
+ { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_ENABLE, "OCP 1" },
+ { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DISABLE, "OCP 0" },
+ { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE, "STS?" },
+ { SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION_ACTIVE, "STS?" },
+ { SCPI_CMD_GET_OUTPUT_REGULATION, "STS?" },
+ ALL_ZERO
+};
+
+static int hp_6630a_init_acquisition(const struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+
+ scpi = sdi->conn;
+
+ /*
+ * Monitor CV (1), CC+ (2), UR (4), OVP (8), OTP (16), OCP (64) and
+ * CC- (256) bits of the Status Register for the FAULT? query.
+ */
+ return sr_scpi_send(scpi, "UNMASK 607");
+}
+
+static int hp_6630a_update_status(const struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+ int ret;
+ int fault;
+ gboolean cv, cc_pos, unreg, cc_neg;
+ gboolean regulation_changed;
+ char *regulation;
+
+ scpi = sdi->conn;
+
+ /*
+ * Use the FAULT register (only 0->1 transitions), this way multiple set
+ * regulation bits in the STS/ASTS registers are ignored. In rare cases
+ * we will miss some changes (1->0 transitions, e.g. no regulation at all),
+ * but SPS/ASPS doesn't work either, unless all states are stored and
+ * compared to the states in STS/ASTS.
+ * TODO: Use SPoll or SRQ when SCPI over GPIB is used.
+ */
+ ret = sr_scpi_get_int(scpi, "FAULT?", &fault);
+ if (ret != SR_OK)
+ return ret;
+
+ /* OVP */
+ if (fault & (1 << 3))
+ sr_session_send_meta(sdi, SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE,
+ g_variant_new_boolean(fault & (1 << 3)));
+
+ /* OCP */
+ if (fault & (1 << 6))
+ sr_session_send_meta(sdi, SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE,
+ g_variant_new_boolean(fault & (1 << 6)));
+
+ /* OTP */
+ if (fault & (1 << 4))
+ sr_session_send_meta(sdi, SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE,
+ g_variant_new_boolean(fault & (1 << 4)));
+
+ /* CV */
+ cv = (fault & (1 << 0));
+ regulation_changed = (fault & (1 << 0));
+ /* CC+ */
+ cc_pos = (fault & (1 << 1));
+ regulation_changed = (fault & (1 << 1)) | regulation_changed;
+ /* UNREG */
+ unreg = (fault & (1 << 2));
+ regulation_changed = (fault & (1 << 2)) | regulation_changed;
+ /* CC- */
+ cc_neg = (fault & (1 << 9));
+ regulation_changed = (fault & (1 << 9)) | regulation_changed;
+
+ if (regulation_changed) {
+ if (cv && !cc_pos && !cc_neg && !unreg)
+ regulation = "CV";
+ else if (cc_pos && !cv && !cc_neg && !unreg)
+ regulation = "CC";
+ else if (cc_neg && !cv && !cc_pos && !unreg)
+ regulation = "CC-";
+ else if (unreg && !cv && !cc_pos && !cc_neg)
+ regulation = "UR";
+ else if (!cv && !cc_pos && !cc_neg && !unreg)
+ regulation = "";
+ else {
+ sr_dbg("Undefined regulation for HP 66xxA "
+ "(CV=%i, CC+=%i, CC-=%i, UR=%i).",
+ cv, cc_pos, cc_neg, unreg);
+ return FALSE;
+ }
+ sr_session_send_meta(sdi, SR_CONF_REGULATION,
+ g_variant_new_string(regulation));
+ }
+
+ return SR_OK;
+}
+
+/* HP 663xB series */
static const uint32_t hp_6630b_devopts[] = {
SR_CONF_CONTINUOUS,
SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
SR_CONF_CURRENT | SR_CONF_GET,
SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_CURRENT_LIMIT | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET,
SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_OVER_CURRENT_PROTECTION_ENABLED | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET,
+ SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE | SR_CONF_GET,
+ SR_CONF_REGULATION | SR_CONF_GET,
};
-static const struct channel_spec hp_6633a_ch[] = {
- { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS },
+static const struct channel_spec hp_6611c_ch[] = {
+ { "1", { 0, 8.19, 0.002, 3, 4 }, { 0, 5.1188, 0.00125, 4, 5 }, { 0, 41.92297 }, FREQ_DC_ONLY, { 0, 12, 0.06 }, NO_OCP_LIMITS },
+};
+
+static const struct channel_spec hp_6612c_ch[] = {
+ { "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 2.0475, 0.0005, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
+};
+
+static const struct channel_spec hp_6613c_ch[] = {
+ { "1", { 0, 51.188, 0.0125, 3, 4 }, { 0, 1.0238, 0.00025, 4, 5 }, { 0, 52.40627 }, FREQ_DC_ONLY, { 0, 55, 0.25 }, NO_OCP_LIMITS },
+};
+
+static const struct channel_spec hp_6614c_ch[] = {
+ { "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 0.5118, 0.000125, 4, 5 }, { 0, 52.39808 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS },
};
static const struct channel_spec hp_6631b_ch[] = {
{ "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
};
+static const struct channel_spec hp_66312a_ch[] = {
+ { "1", { 0, 20.475, 0.0001, 4, 5 }, { 0, 2.0475, 0.0001, 4, 5 }, { 0, 41.92256 }, FREQ_DC_ONLY, { 0, 22, 0.01 }, NO_OCP_LIMITS },
+};
+
static const struct channel_spec hp_66332a_ch[] = {
{ "1", { 0, 20.475, 0.005, 3, 4 }, { 0, 5.1188, 0.00132, 4, 5 }, { 0, 104.80743 }, FREQ_DC_ONLY, { 0, 22, 0.1 }, NO_OCP_LIMITS },
};
{ "1", { 0, 102.38, 0.025, 3, 4 }, { 0, 1.0238, 0.000263, 4, 5 }, { 0, 104.81664 }, FREQ_DC_ONLY, { 0, 110, 0.5 }, NO_OCP_LIMITS },
};
-static const struct channel_group_spec hp_663xx_cg[] = {
- { "1", CH_IDX(0), 0 },
-};
-
-static const struct scpi_command hp_6630a_cmd[] = {
- { SCPI_CMD_SET_OUTPUT_ENABLE, "OUT 1" },
- { SCPI_CMD_SET_OUTPUT_DISABLE, "OUT 0" },
- { SCPI_CMD_GET_MEAS_VOLTAGE, "VOUT?" },
- { SCPI_CMD_GET_MEAS_CURRENT, "IOUT?" },
- { SCPI_CMD_SET_VOLTAGE_TARGET, "VSET %.4f" },
- { SCPI_CMD_SET_CURRENT_LIMIT, "ISET %.4f" },
- { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_ENABLE, "OCP 1" },
- { SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DISABLE, "OCP 0" },
- { SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_THRESHOLD, "OVSET %.4f" },
- ALL_ZERO
+static const struct channel_group_spec hp_6630b_cg[] = {
+ { "1", CH_IDX(0), PPS_OVP | PPS_OCP, SR_MQFLAG_DC },
};
static const struct scpi_command hp_6630b_cmd[] = {
+ /*
+ * SCPI_CMD_REMOTE and SCPI_CMD_LOCAL are not used when GPIB is used,
+ * otherwise the device will report (non critical) error 602.
+ */
{ SCPI_CMD_REMOTE, "SYST:REM" },
{ SCPI_CMD_LOCAL, "SYST:LOC" },
{ SCPI_CMD_GET_OUTPUT_ENABLED, "OUTP:STAT?" },
{ SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ENABLED, ":CURR:PROT:STAT?" },
{ SCPI_CMD_SET_OVER_CURRENT_PROTECTION_ENABLE, ":CURR:PROT:STAT 1" },
{ SCPI_CMD_SET_OVER_CURRENT_PROTECTION_DISABLE, ":CURR:PROT:STAT 0" },
+ { SCPI_CMD_GET_OVER_CURRENT_PROTECTION_ACTIVE, "STAT:QUES:COND?" },
+ { SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ACTIVE, "STAT:QUES:COND?" },
{ SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_THRESHOLD, ":VOLT:PROT?" },
{ SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_THRESHOLD, ":VOLT:PROT %.6f" },
+ { SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION_ACTIVE, "STAT:QUES:COND?" },
+ { SCPI_CMD_GET_OUTPUT_REGULATION, "STAT:OPER:COND?" },
ALL_ZERO
};
+static int hp_6630b_init_acquisition(const struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+ int ret;
+
+ scpi = sdi->conn;
+
+ /*
+ * Monitor CV (256), CC+ (1024) and CC- (2048) bits of the
+ * Operational Status Register.
+ * Use both positive and negative transitions of the status bits.
+ */
+ ret = sr_scpi_send(scpi, "STAT:OPER:PTR 3328;NTR 3328;ENAB 3328");
+ if (ret != SR_OK)
+ return ret;
+
+ /*
+ * Monitor OVP (1), OCP (2), OTP (16) and Unreg (1024) bits of the
+ * Questionable Status Register.
+ * Use both positive and negative transitions of the status bits.
+ */
+ ret = sr_scpi_send(scpi, "STAT:QUES:PTR 1043;NTR 1043;ENAB 1043");
+ if (ret != SR_OK)
+ return ret;
+
+ /*
+ * Service Request Enable Register set for Operational Status Register
+ * bits (128) and Questionable Status Register bits (8).
+ * This masks the Status Register generating a SRQ/RQS. Not implemented yet!
+ */
+ /*
+ ret = sr_scpi_send(scpi, "*SRE 136");
+ if (ret != SR_OK)
+ return ret;
+ */
+
+ return SR_OK;
+}
+
+static int hp_6630b_update_status(const struct sr_dev_inst *sdi)
+{
+ struct sr_scpi_dev_inst *scpi;
+ int ret;
+ int stb;
+ int ques_even, ques_cond;
+ int oper_even, oper_cond;
+ gboolean output_enabled;
+ gboolean unreg, cv, cc_pos, cc_neg;
+ gboolean regulation_changed;
+ char *regulation;
+
+ scpi = sdi->conn;
+
+ unreg = FALSE;
+ cv = FALSE;
+ cc_pos = FALSE;
+ cc_neg = FALSE;
+ regulation_changed = FALSE;
+
+ /*
+ * Use SPoll when SCPI uses GPIB as transport layer.
+ * SPoll is approx. twice as fast as a normal GPIB write + read would be!
+ */
+#ifdef HAVE_LIBGPIB
+ char spoll_buf;
+
+ if (scpi->transport == SCPI_TRANSPORT_LIBGPIB) {
+ ret = sr_scpi_gpib_spoll(scpi, &spoll_buf);
+ if (ret != SR_OK)
+ return ret;
+ stb = (uint8_t)spoll_buf;
+ }
+ else {
+#endif
+ ret = sr_scpi_get_int(scpi, "*STB?", &stb);
+ if (ret != SR_OK)
+ return ret;
+#ifdef HAVE_LIBGPIB
+ }
+#endif
+
+ /* Questionable status summary bit */
+ if (stb & (1 << 3)) {
+ /* Read the event register to clear it! */
+ ret = sr_scpi_get_int(scpi, "STAT:QUES:EVEN?", &ques_even);
+ if (ret != SR_OK)
+ return ret;
+ /* Now get the values. */
+ ret = sr_scpi_get_int(scpi, "STAT:QUES:COND?", &ques_cond);
+ if (ret != SR_OK)
+ return ret;
+
+ /* OVP */
+ if (ques_even & (1 << 0))
+ sr_session_send_meta(sdi, SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE,
+ g_variant_new_boolean(ques_cond & (1 << 0)));
+
+ /* OCP */
+ if (ques_even & (1 << 1))
+ sr_session_send_meta(sdi, SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE,
+ g_variant_new_boolean(ques_cond & (1 << 1)));
+
+ /* OTP */
+ if (ques_even & (1 << 4))
+ sr_session_send_meta(sdi, SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE,
+ g_variant_new_boolean(ques_cond & (1 << 4)));
+
+ /* UNREG */
+ unreg = (ques_cond & (1 << 10));
+ regulation_changed = (ques_even & (1 << 10)) | regulation_changed;
+
+ /*
+ * Check if output state has changed, due to one of the
+ * questionable states changed.
+ * NOTE: The output state is sent even if it hasn't changed,
+ * but that only happens rarely.
+ */
+ ret = sr_scpi_get_bool(scpi, "OUTP:STAT?", &output_enabled);
+ if (ret != SR_OK)
+ return ret;
+ sr_session_send_meta(sdi, SR_CONF_ENABLED,
+ g_variant_new_boolean(output_enabled));
+ }
+
+ /* Operation status summary bit */
+ if (stb & (1 << 7)) {
+ /* Read the event register to clear it! */
+ ret = sr_scpi_get_int(scpi, "STAT:OPER:EVEN?", &oper_even);
+ if (ret != SR_OK)
+ return ret;
+ /* Now get the values. */
+ ret = sr_scpi_get_int(scpi, "STAT:OPER:COND?", &oper_cond);
+ if (ret != SR_OK)
+ return ret;
+
+ /* CV */
+ cv = (oper_cond & (1 << 8));
+ regulation_changed = (oper_even & (1 << 8)) | regulation_changed;
+ /* CC+ */
+ cc_pos = (oper_cond & (1 << 10));
+ regulation_changed = (oper_even & (1 << 10)) | regulation_changed;
+ /* CC- */
+ cc_neg = (oper_cond & (1 << 11));
+ regulation_changed = (oper_even & (1 << 11)) | regulation_changed;
+ }
+
+ if (regulation_changed) {
+ if (cv && !cc_pos && !cc_neg && !unreg)
+ regulation = "CV";
+ else if (cc_pos && !cv && !cc_neg && !unreg)
+ regulation = "CC";
+ else if (cc_neg && !cv && !cc_pos && !unreg)
+ regulation = "CC-";
+ else if (unreg && !cv && !cc_pos && !cc_neg)
+ regulation = "UR";
+ else if (!cv && !cc_pos && !cc_neg && !unreg)
+ /* This happens in case of OCP active. */
+ regulation = "";
+ else {
+ /* This happens from time to time (CV and CC+ active). */
+ sr_dbg("Undefined regulation for HP 66xxB "
+ "(CV=%i, CC+=%i, CC-=%i, UR=%i).",
+ cv, cc_pos, cc_neg, unreg);
+ return FALSE;
+ }
+ sr_session_send_meta(sdi, SR_CONF_REGULATION,
+ g_variant_new_string(regulation));
+ }
+
+ return SR_OK;
+}
+
/* Philips/Fluke PM2800 series */
static const uint32_t philips_pm2800_devopts[] = {
SR_CONF_CONTINUOUS,
(*channel_groups)[i].name = (char *)philips_pm2800_names[i];
(*channel_groups)[i].channel_index_mask = 1 << i;
(*channel_groups)[i].features = PPS_OTP | PPS_OVP | PPS_OCP;
+ (*channel_groups)[i].mqflags = SR_MQFLAG_DC;
}
*num_channels = *num_channel_groups = num_modules;
};
static const struct channel_group_spec rs_hmc8043_cg[] = {
- { "1", CH_IDX(0), PPS_OVP },
- { "2", CH_IDX(1), PPS_OVP },
- { "3", CH_IDX(2), PPS_OVP },
+ { "1", CH_IDX(0), PPS_OVP, SR_MQFLAG_DC },
+ { "2", CH_IDX(1), PPS_OVP, SR_MQFLAG_DC },
+ { "3", CH_IDX(2), PPS_OVP, SR_MQFLAG_DC },
};
static const struct scpi_command rs_hmc8043_cmd[] = {
SR_PRIV const struct scpi_pps pps_profiles[] = {
/* Agilent N5763A */
- { "Agilent", "N5763A", 0,
+ { "Agilent", "N5763A", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(agilent_n5700a_devopts),
ARRAY_AND_SIZE(agilent_n5700a_devopts_cg),
ARRAY_AND_SIZE(agilent_n5763a_ch),
ARRAY_AND_SIZE(agilent_n5700a_cg),
agilent_n5700a_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
/* Agilent N5767A */
- { "Agilent", "N5767A", 0,
+ { "Agilent", "N5767A", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(agilent_n5700a_devopts),
ARRAY_AND_SIZE(agilent_n5700a_devopts_cg),
ARRAY_AND_SIZE(agilent_n5767a_ch),
ARRAY_AND_SIZE(agilent_n5700a_cg),
agilent_n5700a_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
+ },
+
+ /* BK Precision 9310 */
+ { "BK", "^9130$", SCPI_DIALECT_UNKNOWN, 0,
+ ARRAY_AND_SIZE(bk_9130_devopts),
+ ARRAY_AND_SIZE(bk_9130_devopts_cg),
+ ARRAY_AND_SIZE(bk_9130_ch),
+ ARRAY_AND_SIZE(bk_9130_cg),
+ bk_9130_cmd,
+ .probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
/* Chroma 61604 */
- { "Chroma", "61604", 0,
+ { "Chroma", "61604", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(chroma_61604_devopts),
ARRAY_AND_SIZE(chroma_61604_devopts_cg),
ARRAY_AND_SIZE(chroma_61604_ch),
ARRAY_AND_SIZE(chroma_61604_cg),
chroma_61604_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
/* Chroma 62000 series */
- { "Chroma", "620[0-9]{2}P-[0-9]{2,3}-[0-9]{1,3}", 0,
+ { "Chroma", "620[0-9]{2}P-[0-9]{2,3}-[0-9]{1,3}", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(chroma_62000_devopts),
ARRAY_AND_SIZE(chroma_62000_devopts_cg),
NULL, 0,
NULL, 0,
chroma_62000_cmd,
.probe_channels = chroma_62000p_probe_channels,
+ .init_acquisition = NULL,
+ .update_status = NULL,
+ },
+
+ /*
+ * This entry is for testing the HP COMP language with a HP 6632B power
+ * supply switched to the COMP language ("SYST:LANG COMP"). When used,
+ * disable the entry for the HP 6632B below!
+ */
+ /*
+ { "HP", "6632B", SCPI_DIALECT_HP_COMP, 0,
+ ARRAY_AND_SIZE(hp_6630a_devopts),
+ ARRAY_AND_SIZE(hp_6630a_devopts_cg),
+ ARRAY_AND_SIZE(hp_6632a_ch),
+ ARRAY_AND_SIZE(hp_6630a_cg),
+ hp_6630a_cmd,
+ .probe_channels = NULL,
+ hp_6630a_init_acquisition,
+ hp_6630a_update_status,
+ },
+ */
+
+ /* HP 6632A */
+ { "HP", "6632A", SCPI_DIALECT_HP_COMP, 0,
+ ARRAY_AND_SIZE(hp_6630a_devopts),
+ ARRAY_AND_SIZE(hp_6630a_devopts_cg),
+ ARRAY_AND_SIZE(hp_6632a_ch),
+ ARRAY_AND_SIZE(hp_6630a_cg),
+ hp_6630a_cmd,
+ .probe_channels = NULL,
+ hp_6630a_init_acquisition,
+ hp_6630a_update_status,
},
/* HP 6633A */
- { "HP", "6633A", 0,
+ { "HP", "6633A", SCPI_DIALECT_HP_COMP, 0,
ARRAY_AND_SIZE(hp_6630a_devopts),
ARRAY_AND_SIZE(hp_6630a_devopts_cg),
ARRAY_AND_SIZE(hp_6633a_ch),
- ARRAY_AND_SIZE(hp_663xx_cg),
+ ARRAY_AND_SIZE(hp_6630a_cg),
hp_6630a_cmd,
.probe_channels = NULL,
+ hp_6630a_init_acquisition,
+ hp_6630a_update_status,
+ },
+
+ /* HP 6634A */
+ { "HP", "6634A", SCPI_DIALECT_HP_COMP, 0,
+ ARRAY_AND_SIZE(hp_6630a_devopts),
+ ARRAY_AND_SIZE(hp_6630a_devopts_cg),
+ ARRAY_AND_SIZE(hp_6634a_ch),
+ ARRAY_AND_SIZE(hp_6630a_cg),
+ hp_6630a_cmd,
+ .probe_channels = NULL,
+ hp_6630a_init_acquisition,
+ hp_6630a_update_status,
+ },
+
+ /* HP 6611C */
+ { "HP", "6611C", SCPI_DIALECT_HP_66XXB, PPS_OTP,
+ ARRAY_AND_SIZE(hp_6630b_devopts),
+ ARRAY_AND_SIZE(hp_6630b_devopts_cg),
+ ARRAY_AND_SIZE(hp_6611c_ch),
+ ARRAY_AND_SIZE(hp_6630b_cg),
+ hp_6630b_cmd,
+ .probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
+ },
+
+ /* HP 6612C */
+ { "HP", "6612C", SCPI_DIALECT_HP_66XXB, PPS_OTP,
+ ARRAY_AND_SIZE(hp_6630b_devopts),
+ ARRAY_AND_SIZE(hp_6630b_devopts_cg),
+ ARRAY_AND_SIZE(hp_6612c_ch),
+ ARRAY_AND_SIZE(hp_6630b_cg),
+ hp_6630b_cmd,
+ .probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
+ },
+
+ /* HP 6613C */
+ { "HP", "6613C", SCPI_DIALECT_HP_66XXB, PPS_OTP,
+ ARRAY_AND_SIZE(hp_6630b_devopts),
+ ARRAY_AND_SIZE(hp_6630b_devopts_cg),
+ ARRAY_AND_SIZE(hp_6613c_ch),
+ ARRAY_AND_SIZE(hp_6630b_cg),
+ hp_6630b_cmd,
+ .probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
+ },
+
+ /* HP 6614C */
+ { "HP", "6614C", SCPI_DIALECT_HP_66XXB, PPS_OTP,
+ ARRAY_AND_SIZE(hp_6630b_devopts),
+ ARRAY_AND_SIZE(hp_6630b_devopts_cg),
+ ARRAY_AND_SIZE(hp_6614c_ch),
+ ARRAY_AND_SIZE(hp_6630b_cg),
+ hp_6630b_cmd,
+ .probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
},
/* HP 6631B */
- { "HP", "6631B", PPS_OVP | PPS_OCP | PPS_OTP,
+ { "HP", "6631B", SCPI_DIALECT_HP_66XXB, PPS_OTP,
ARRAY_AND_SIZE(hp_6630b_devopts),
ARRAY_AND_SIZE(hp_6630b_devopts_cg),
ARRAY_AND_SIZE(hp_6631b_ch),
- ARRAY_AND_SIZE(hp_663xx_cg),
+ ARRAY_AND_SIZE(hp_6630b_cg),
hp_6630b_cmd,
.probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
},
/* HP 6632B */
- { "HP", "6632B", PPS_OVP | PPS_OCP | PPS_OTP,
+ { "HP", "6632B", SCPI_DIALECT_HP_66XXB, PPS_OTP,
ARRAY_AND_SIZE(hp_6630b_devopts),
ARRAY_AND_SIZE(hp_6630b_devopts_cg),
ARRAY_AND_SIZE(hp_6632b_ch),
- ARRAY_AND_SIZE(hp_663xx_cg),
+ ARRAY_AND_SIZE(hp_6630b_cg),
+ hp_6630b_cmd,
+ .probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
+ },
+
+ /* HP 66312A */
+ { "HP", "66312A", SCPI_DIALECT_HP_66XXB, PPS_OTP,
+ ARRAY_AND_SIZE(hp_6630b_devopts),
+ ARRAY_AND_SIZE(hp_6630b_devopts_cg),
+ ARRAY_AND_SIZE(hp_66312a_ch),
+ ARRAY_AND_SIZE(hp_6630b_cg),
hp_6630b_cmd,
.probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
},
/* HP 66332A */
- { "HP", "66332A", PPS_OVP | PPS_OCP | PPS_OTP,
+ { "HP", "66332A", SCPI_DIALECT_HP_66XXB, PPS_OTP,
ARRAY_AND_SIZE(hp_6630b_devopts),
ARRAY_AND_SIZE(hp_6630b_devopts_cg),
ARRAY_AND_SIZE(hp_66332a_ch),
- ARRAY_AND_SIZE(hp_663xx_cg),
+ ARRAY_AND_SIZE(hp_6630b_cg),
hp_6630b_cmd,
.probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
},
/* HP 6633B */
- { "HP", "6633B", PPS_OVP | PPS_OCP | PPS_OTP,
+ { "HP", "6633B", SCPI_DIALECT_HP_66XXB, PPS_OTP,
ARRAY_AND_SIZE(hp_6630b_devopts),
ARRAY_AND_SIZE(hp_6630b_devopts_cg),
ARRAY_AND_SIZE(hp_6633b_ch),
- ARRAY_AND_SIZE(hp_663xx_cg),
+ ARRAY_AND_SIZE(hp_6630b_cg),
hp_6630b_cmd,
.probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
},
/* HP 6634B */
- { "HP", "6634B", PPS_OVP | PPS_OCP | PPS_OTP,
+ { "HP", "6634B", SCPI_DIALECT_HP_66XXB, PPS_OTP,
ARRAY_AND_SIZE(hp_6630b_devopts),
ARRAY_AND_SIZE(hp_6630b_devopts_cg),
ARRAY_AND_SIZE(hp_6634b_ch),
- ARRAY_AND_SIZE(hp_663xx_cg),
+ ARRAY_AND_SIZE(hp_6630b_cg),
hp_6630b_cmd,
.probe_channels = NULL,
+ hp_6630b_init_acquisition,
+ hp_6630b_update_status,
},
/* Rigol DP700 series */
- { "Rigol", "^DP711$", 0,
+ { "Rigol", "^DP711$", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(rigol_dp700_devopts),
ARRAY_AND_SIZE(rigol_dp700_devopts_cg),
ARRAY_AND_SIZE(rigol_dp711_ch),
ARRAY_AND_SIZE(rigol_dp700_cg),
rigol_dp700_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
- { "Rigol", "^DP712$", 0,
+ { "Rigol", "^DP712$", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(rigol_dp700_devopts),
ARRAY_AND_SIZE(rigol_dp700_devopts_cg),
ARRAY_AND_SIZE(rigol_dp712_ch),
ARRAY_AND_SIZE(rigol_dp700_cg),
rigol_dp700_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
/* Rigol DP800 series */
- { "Rigol", "^DP821A$", PPS_OTP,
+ { "Rigol", "^DP821A$", SCPI_DIALECT_UNKNOWN, PPS_OTP,
ARRAY_AND_SIZE(rigol_dp800_devopts),
ARRAY_AND_SIZE(rigol_dp800_devopts_cg),
ARRAY_AND_SIZE(rigol_dp821a_ch),
ARRAY_AND_SIZE(rigol_dp820_cg),
rigol_dp800_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
- { "Rigol", "^DP831A$", PPS_OTP,
+ { "Rigol", "^DP831A$", SCPI_DIALECT_UNKNOWN, PPS_OTP,
ARRAY_AND_SIZE(rigol_dp800_devopts),
ARRAY_AND_SIZE(rigol_dp800_devopts_cg),
ARRAY_AND_SIZE(rigol_dp831_ch),
ARRAY_AND_SIZE(rigol_dp830_cg),
rigol_dp800_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
- { "Rigol", "^(DP832|DP832A)$", PPS_OTP,
+ { "Rigol", "^(DP832|DP832A)$", SCPI_DIALECT_UNKNOWN, PPS_OTP,
ARRAY_AND_SIZE(rigol_dp800_devopts),
ARRAY_AND_SIZE(rigol_dp800_devopts_cg),
ARRAY_AND_SIZE(rigol_dp832_ch),
ARRAY_AND_SIZE(rigol_dp830_cg),
rigol_dp800_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
/* Philips/Fluke PM2800 series */
- { "Philips", "^PM28[13][123]/[01234]{1,2}$", 0,
+ { "Philips", "^PM28[13][123]/[01234]{1,2}$", SCPI_DIALECT_PHILIPS, 0,
ARRAY_AND_SIZE(philips_pm2800_devopts),
ARRAY_AND_SIZE(philips_pm2800_devopts_cg),
NULL, 0,
NULL, 0,
philips_pm2800_cmd,
philips_pm2800_probe_channels,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
/* Rohde & Schwarz HMC8043 */
- { "Rohde&Schwarz", "HMC8043", 0,
+ { "Rohde&Schwarz", "HMC8043", SCPI_DIALECT_UNKNOWN, 0,
ARRAY_AND_SIZE(rs_hmc8043_devopts),
ARRAY_AND_SIZE(rs_hmc8043_devopts_cg),
ARRAY_AND_SIZE(rs_hmc8043_ch),
ARRAY_AND_SIZE(rs_hmc8043_cg),
rs_hmc8043_cmd,
.probe_channels = NULL,
+ .init_acquisition = NULL,
+ .update_status = NULL,
},
};
* This file is part of the libsigrok project.
*
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
+ * Copyright (C) 2019 Frank Stettner <frank-stettner@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
SR_PRIV int scpi_pps_receive_data(int fd, int revents, void *cb_data)
{
struct dev_context *devc;
+ const struct scpi_pps *device;
struct sr_datafeed_packet packet;
struct sr_datafeed_analog analog;
struct sr_analog_encoding encoding;
if (!(devc = sdi->priv))
return TRUE;
+ if (!(device = devc->device))
+ return TRUE;
+
pch = devc->cur_acquisition_channel->priv;
channel_group_cmd = 0;
channel_group_name = pch->hwname;
}
+ /*
+ * When the current channel is the first in the array, perform the device
+ * specific status update first.
+ */
+ if (devc->cur_acquisition_channel == sr_next_enabled_channel(sdi, NULL) &&
+ device->update_status) {
+ device->update_status(sdi);
+ }
+
if (pch->mq == SR_MQ_VOLTAGE) {
gvtype = G_VARIANT_TYPE_DOUBLE;
cmd = SCPI_CMD_GET_MEAS_VOLTAGE;
analog.meaning->channels = g_slist_append(NULL, devc->cur_acquisition_channel);
analog.num_samples = 1;
analog.meaning->mq = pch->mq;
+ analog.meaning->mqflags = pch->mqflags;
if (pch->mq == SR_MQ_VOLTAGE) {
analog.meaning->unit = SR_UNIT_VOLT;
analog.encoding->digits = ch_spec->voltage[4];
analog.meaning->unit = SR_UNIT_WATT;
analog.encoding->digits = ch_spec->power[4];
analog.spec->spec_digits = ch_spec->power[3];
+ } else if (pch->mq == SR_MQ_FREQUENCY) {
+ analog.meaning->unit = SR_UNIT_HERTZ;
+ analog.encoding->digits = ch_spec->frequency[4];
+ analog.spec->spec_digits = ch_spec->frequency[3];
}
- analog.meaning->mqflags = SR_MQFLAG_DC;
f = (float)g_variant_get_double(gvdata);
g_variant_unref(gvdata);
analog.data = &f;
* This file is part of the libsigrok project.
*
* Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
- * Copyright (C) 2017 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2017,2019 Frank Stettner <frank-stettner@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
SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION,
SCPI_CMD_SET_OVER_TEMPERATURE_PROTECTION_ENABLE,
SCPI_CMD_SET_OVER_TEMPERATURE_PROTECTION_DISABLE,
+ SCPI_CMD_GET_OVER_TEMPERATURE_PROTECTION_ACTIVE,
SCPI_CMD_GET_OVER_VOLTAGE_PROTECTION_ENABLED,
SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_ENABLE,
SCPI_CMD_SET_OVER_VOLTAGE_PROTECTION_DISABLE,
SCPI_CMD_SET_OVER_CURRENT_PROTECTION_THRESHOLD,
};
+/* Defines the SCPI dialect */
+enum pps_scpi_dialect {
+ SCPI_DIALECT_UNKNOWN = 1,
+ SCPI_DIALECT_HP_COMP,
+ SCPI_DIALECT_HP_66XXB,
+ SCPI_DIALECT_PHILIPS,
+};
+
/*
* These are bit values denoting features a device can have either globally,
* in scpi_pps.features, or on a per-channel-group basis in
struct scpi_pps {
const char *vendor;
const char *model;
+ const enum pps_scpi_dialect dialect;
uint64_t features;
const uint32_t *devopts;
unsigned int num_devopts;
int (*probe_channels) (struct sr_dev_inst *sdi, struct sr_scpi_hw_info *hwinfo,
struct channel_spec **channels, unsigned int *num_channels,
struct channel_group_spec **channel_groups, unsigned int *num_channel_groups);
+ int (*init_acquisition) (const struct sr_dev_inst *sdi);
+ int (*update_status) (const struct sr_dev_inst *sdi);
};
struct channel_spec {
const char *name;
uint64_t channel_index_mask;
uint64_t features;
+ /* The mqflags will only be applied to voltage and current channels! */
+ enum sr_mqflag mqflags;
};
struct pps_channel {
enum sr_mq mq;
+ enum sr_mqflag mqflags;
unsigned int hw_output_idx;
const char *hwname;
int digits;
dmm = (struct dmm_info *)di;
- conn = serialcomm = NULL;
+ conn = dmm->conn;
+ serialcomm = dmm->serialcomm;
for (l = options; l; l = l->next) {
src = l->data;
switch (src->key) {
if (!conn)
return NULL;
- if (!serialcomm)
- serialcomm = dmm->conn;
-
serial = sr_serial_dev_inst_new(conn, serialcomm);
if (serial_open(serial, SERIAL_RDWR) != SR_OK)
/* Let's get a bit of data and see if we can find a packet. */
len = sizeof(buf);
ret = serial_stream_detect(serial, buf, &len, dmm->packet_size,
- dmm->packet_valid, 3000,
- dmm->baudrate);
+ dmm->packet_valid, 3000);
if (ret != SR_OK)
goto scan_cleanup;
sdi->conn = serial;
sdi->priv = devc;
dmm->channel_count = 1;
- if (dmm->packet_parse == sr_metex14_4packets_parse)
- dmm->channel_count = 4;
+ if (dmm->packet_parse == sr_brymen_bm86x_parse)
+ dmm->channel_count = BRYMEN_BM86X_DISPLAY_COUNT;
if (dmm->packet_parse == sr_eev121gw_3displays_parse) {
dmm->channel_count = EEV121GW_DISPLAY_COUNT;
dmm->channel_formats = eev121gw_channel_formats;
}
+ if (dmm->packet_parse == sr_metex14_4packets_parse)
+ dmm->channel_count = 4;
+ if (dmm->packet_parse == sr_ms2115b_parse) {
+ dmm->channel_count = MS2115B_DISPLAY_COUNT;
+ dmm->channel_formats = ms2115b_channel_formats;
+ }
for (ch_idx = 0; ch_idx < dmm->channel_count; ch_idx++) {
size_t ch_num;
const char *fmt;
return SR_OK;
}
-#define DMM(ID, CHIPSET, VENDOR, MODEL, CONN, BAUDRATE, PACKETSIZE, TIMEOUT, \
- DELAY, REQUEST, VALID, PARSE, DETAILS) \
+#define DMM_CONN(ID, CHIPSET, VENDOR, MODEL, \
+ CONN, SERIALCOMM, PACKETSIZE, TIMEOUT, DELAY, \
+ REQUEST, VALID, PARSE, DETAILS) \
&((struct dmm_info) { \
{ \
.name = ID, \
.dev_acquisition_stop = std_serial_dev_acquisition_stop, \
.context = NULL, \
}, \
- VENDOR, MODEL, CONN, BAUDRATE, PACKETSIZE, TIMEOUT, DELAY, \
+ VENDOR, MODEL, CONN, SERIALCOMM, PACKETSIZE, TIMEOUT, DELAY, \
REQUEST, 1, NULL, VALID, PARSE, DETAILS, sizeof(struct CHIPSET##_info) \
}).di
+#define DMM(ID, CHIPSET, VENDOR, MODEL, SERIALCOMM, PACKETSIZE, TIMEOUT, \
+ DELAY, REQUEST, VALID, PARSE, DETAILS) \
+ DMM_CONN(ID, CHIPSET, VENDOR, MODEL, NULL, SERIALCOMM, PACKETSIZE, \
+ TIMEOUT, DELAY, REQUEST, VALID, PARSE, DETAILS)
+
SR_REGISTER_DEV_DRIVER_LIST(serial_dmm_drivers,
/*
* The items are sorted by chipset first and then model name.
/* asycii based meters {{{ */
DMM(
"metrix-mx56c", asycii, "Metrix", "MX56C",
- "2400/8n1", 2400, ASYCII_PACKET_SIZE, 0, 0, NULL,
+ "2400/8n1", ASYCII_PACKET_SIZE, 0, 0, NULL,
sr_asycii_packet_valid, sr_asycii_parse, NULL
),
/* }}} */
DMM(
"brymen-bm25x", bm25x,
"Brymen", "BM25x", "9600/8n1/rts=1/dtr=1",
- 9600, BRYMEN_BM25X_PACKET_SIZE, 0, 0, NULL,
+ BRYMEN_BM25X_PACKET_SIZE, 0, 0, NULL,
sr_brymen_bm25x_packet_valid, sr_brymen_bm25x_parse,
NULL
),
/* }}} */
+ /* bm86x based meters {{{ */
+ DMM_CONN(
+ "brymen-bm86x", brymen_bm86x, "Brymen", "BM86x",
+ "hid/bu86x", NULL, BRYMEN_BM86X_PACKET_SIZE, 500, 100,
+ sr_brymen_bm86x_packet_request,
+ sr_brymen_bm86x_packet_valid, sr_brymen_bm86x_parse,
+ NULL
+ ),
+ /* }}} */
/* dtm0660 based meters {{{ */
DMM(
"peaktech-3415", dtm0660,
"PeakTech", "3415", "2400/8n1/rts=0/dtr=1",
- 2400, DTM0660_PACKET_SIZE, 0, 0, NULL,
+ DTM0660_PACKET_SIZE, 0, 0, NULL,
sr_dtm0660_packet_valid, sr_dtm0660_parse, NULL
),
DMM(
"velleman-dvm4100", dtm0660,
"Velleman", "DVM4100", "2400/8n1/rts=0/dtr=1",
- 2400, DTM0660_PACKET_SIZE, 0, 0, NULL,
+ DTM0660_PACKET_SIZE, 0, 0, NULL,
sr_dtm0660_packet_valid, sr_dtm0660_parse, NULL
),
/* }}} */
/* eev121gw based meters {{{ */
DMM(
"eevblog-121gw", eev121gw, "EEVblog", "121GW",
- "115200/8n1", 115200, EEV121GW_PACKET_SIZE, 0, 0, NULL,
+ "115200/8n1", EEV121GW_PACKET_SIZE, 0, 0, NULL,
sr_eev121gw_packet_valid, sr_eev121gw_3displays_parse, NULL
),
/* }}} */
DMM(
"iso-tech-idm103n", es519xx,
"ISO-TECH", "IDM103N", "2400/7o1/rts=0/dtr=1",
- 2400, ES519XX_11B_PACKET_SIZE, 0, 0, NULL,
+ ES519XX_11B_PACKET_SIZE, 0, 0, NULL,
sr_es519xx_2400_11b_packet_valid, sr_es519xx_2400_11b_parse,
NULL
),
DMM(
"tenma-72-7750-ser", es519xx,
"Tenma", "72-7750 (UT-D02 cable)", "19200/7o1/rts=0/dtr=1",
- 19200, ES519XX_11B_PACKET_SIZE, 0, 0, NULL,
+ ES519XX_11B_PACKET_SIZE, 0, 0, NULL,
sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse,
NULL
),
DMM(
"uni-t-ut60g-ser", es519xx,
"UNI-T", "UT60G (UT-D02 cable)", "19200/7o1/rts=0/dtr=1",
- 19200, ES519XX_11B_PACKET_SIZE, 0, 0, NULL,
+ ES519XX_11B_PACKET_SIZE, 0, 0, NULL,
sr_es519xx_19200_11b_packet_valid, sr_es519xx_19200_11b_parse,
NULL
),
DMM(
"uni-t-ut61e-ser", es519xx,
"UNI-T", "UT61E (UT-D02 cable)", "19200/7o1/rts=0/dtr=1",
- 19200, ES519XX_14B_PACKET_SIZE, 0, 0, NULL,
+ ES519XX_14B_PACKET_SIZE, 0, 0, NULL,
sr_es519xx_19200_14b_packet_valid, sr_es519xx_19200_14b_parse,
NULL
),
/* fs9721 based meters {{{ */
DMM(
"digitek-dt4000zc", fs9721,
- "Digitek", "DT4000ZC", "2400/8n1/dtr=1", 2400,
+ "Digitek", "DT4000ZC", "2400/8n1/dtr=1",
FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_10_temp_c
DMM(
"mastech-ms8250b", fs9721,
"MASTECH", "MS8250B", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
NULL
),
DMM(
"pce-pce-dm32", fs9721,
- "PCE", "PCE-DM32", "2400/8n1", 2400,
+ "PCE", "PCE-DM32", "2400/8n1",
FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_01_10_temp_f_c
),
DMM(
"peaktech-3330", fs9721,
- "PeakTech", "3330", "2400/8n1/dtr=1", 2400,
+ "PeakTech", "3330", "2400/8n1/dtr=1",
FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_01_10_temp_f_c
DMM(
"tecpel-dmm-8061-ser", fs9721,
"Tecpel", "DMM-8061 (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_00_temp_c
),
DMM(
"tekpower-tp4000ZC", fs9721,
- "TekPower", "TP4000ZC", "2400/8n1/dtr=1", 2400,
+ "TekPower", "TP4000ZC", "2400/8n1/dtr=1",
FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_10_temp_c
DMM(
"tenma-72-7745-ser", fs9721,
"Tenma", "72-7745 (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_00_temp_c
),
DMM(
"uni-t-ut60a-ser", fs9721,
"UNI-T", "UT60A (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
NULL
),
DMM(
"uni-t-ut60e-ser", fs9721,
"UNI-T", "UT60E (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_00_temp_c
),
DMM(
"va-va18b", fs9721,
- "V&A", "VA18B", "2400/8n1", 2400,
+ "V&A", "VA18B", "2400/8n1",
FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_01_temp_c
),
DMM(
"va-va40b", fs9721,
- "V&A", "VA40B", "2400/8n1", 2400,
+ "V&A", "VA40B", "2400/8n1",
FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_max_c_min
DMM(
"voltcraft-vc820-ser", fs9721,
"Voltcraft", "VC-820 (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
NULL
),
DMM(
"voltcraft-vc840-ser", fs9721,
"Voltcraft", "VC-840 (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9721_PACKET_SIZE, 0, 0, NULL,
+ FS9721_PACKET_SIZE, 0, 0, NULL,
sr_fs9721_packet_valid, sr_fs9721_parse,
sr_fs9721_00_temp_c
),
DMM(
"sparkfun-70c", fs9922,
"SparkFun", "70C", "2400/8n1/rts=0/dtr=1",
- 2400, FS9922_PACKET_SIZE, 0, 0, NULL,
+ FS9922_PACKET_SIZE, 0, 0, NULL,
sr_fs9922_packet_valid, sr_fs9922_parse, NULL
),
DMM(
"uni-t-ut61b-ser", fs9922,
"UNI-T", "UT61B (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9922_PACKET_SIZE, 0, 0, NULL,
+ FS9922_PACKET_SIZE, 0, 0, NULL,
sr_fs9922_packet_valid, sr_fs9922_parse, NULL
),
DMM(
"uni-t-ut61c-ser", fs9922,
"UNI-T", "UT61C (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9922_PACKET_SIZE, 0, 0, NULL,
+ FS9922_PACKET_SIZE, 0, 0, NULL,
sr_fs9922_packet_valid, sr_fs9922_parse, NULL
),
DMM(
"uni-t-ut61d-ser", fs9922,
"UNI-T", "UT61D (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9922_PACKET_SIZE, 0, 0, NULL,
+ FS9922_PACKET_SIZE, 0, 0, NULL,
sr_fs9922_packet_valid, sr_fs9922_parse, NULL
),
- DMM(
- "victor-dmm-ser", fs9922,
- "Victor", "Victor DMMs (Mini-USB cable)", "2400/8n1",
- 2400, FS9922_PACKET_SIZE, 0, 0, NULL,
+ DMM_CONN(
+ "victor-dmm", fs9922, "Victor", "Victor DMMs",
+ "hid/victor", "2400/8n1", FS9922_PACKET_SIZE, 0, 0, NULL,
sr_fs9922_packet_valid, sr_fs9922_parse, NULL
),
DMM(
*/
"voltcraft-vc830-ser", fs9922,
"Voltcraft", "VC-830 (UT-D02 cable)", "2400/8n1/rts=0/dtr=1",
- 2400, FS9922_PACKET_SIZE, 0, 0, NULL,
+ FS9922_PACKET_SIZE, 0, 0, NULL,
sr_fs9922_packet_valid, sr_fs9922_parse,
&sr_fs9922_z1_diode
),
/* m2110 based meters {{{ */
DMM(
"bbcgm-2010", m2110,
- "BBC Goertz Metrawatt", "M2110", "1200/7n2", 1200,
+ "BBC Goertz Metrawatt", "M2110", "1200/7n2",
BBCGM_M2110_PACKET_SIZE, 0, 0, NULL,
sr_m2110_packet_valid, sr_m2110_parse,
NULL
),
/* }}} */
+ /* ms2115b based meters {{{ */
+ DMM(
+ "mastech-ms2115b", ms2115b,
+ "MASTECH", "MS2115B", "1200/8n1",
+ MS2115B_PACKET_SIZE, 0, 0, NULL,
+ sr_ms2115b_packet_valid, sr_ms2115b_parse,
+ NULL
+ ),
+ /* }}} */
/* ms8250d based meters {{{ */
DMM(
"mastech-ms8250d", ms8250d,
"MASTECH", "MS8250D", "2400/8n1/rts=0/dtr=1",
- 2400, MS8250D_PACKET_SIZE, 0, 0, NULL,
+ MS8250D_PACKET_SIZE, 0, 0, NULL,
sr_ms8250d_packet_valid, sr_ms8250d_parse,
NULL
),
/* metex14 based meters {{{ */
DMM(
"mastech-mas345", metex14,
- "MASTECH", "MAS345", "600/7n2/rts=0/dtr=1", 600,
+ "MASTECH", "MAS345", "600/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"metex-m3640d", metex14,
- "Metex", "M-3640D", "1200/7n2/rts=0/dtr=1", 1200,
+ "Metex", "M-3640D", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"metex-m3860m", metex14,
- "Metex", "M-3860M", "9600/7n2/rts=0/dtr=1", 9600,
+ "Metex", "M-3860M", "9600/7n2/rts=0/dtr=1",
4 * METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_4packets_valid, sr_metex14_4packets_parse,
NULL
),
DMM(
"metex-m4650cr", metex14,
- "Metex", "M-4650CR", "1200/7n2/rts=0/dtr=1", 1200,
+ "Metex", "M-4650CR", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"metex-me31", metex14,
- "Metex", "ME-31", "600/7n2/rts=0/dtr=1", 600,
+ "Metex", "ME-31", "600/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"peaktech-3410", metex14,
- "PeakTech", "3410", "600/7n2/rts=0/dtr=1", 600,
+ "PeakTech", "3410", "600/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"peaktech-4370", metex14,
- "PeakTech", "4370", "1200/7n2/rts=0/dtr=1", 1200,
+ "PeakTech", "4370", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"peaktech-4390a", metex14,
- "PeakTech", "4390A", "9600/7n2/rts=0/dtr=1", 9600,
+ "PeakTech", "4390A", "9600/7n2/rts=0/dtr=1",
4 * METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_4packets_valid, sr_metex14_4packets_parse,
NULL
),
DMM(
"radioshack-22-168", metex14,
- "RadioShack", "22-168", "1200/7n2/rts=0/dtr=1", 1200,
+ "RadioShack", "22-168", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"radioshack-22-805", metex14,
- "RadioShack", "22-805", "600/7n2/rts=0/dtr=1", 600,
+ "RadioShack", "22-805", "600/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"voltcraft-m3650cr", metex14,
- "Voltcraft", "M-3650CR", "1200/7n2/rts=0/dtr=1", 1200,
+ "Voltcraft", "M-3650CR", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 150, 20, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"voltcraft-m3650d", metex14,
- "Voltcraft", "M-3650D", "1200/7n2/rts=0/dtr=1", 1200,
+ "Voltcraft", "M-3650D", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"voltcraft-m4650cr", metex14,
- "Voltcraft", "M-4650CR", "1200/7n2/rts=0/dtr=1", 1200,
+ "Voltcraft", "M-4650CR", "1200/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 0, 0, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
),
DMM(
"voltcraft-me42", metex14,
- "Voltcraft", "ME-42", "600/7n2/rts=0/dtr=1", 600,
+ "Voltcraft", "ME-42", "600/7n2/rts=0/dtr=1",
METEX14_PACKET_SIZE, 250, 60, sr_metex14_packet_request,
sr_metex14_packet_valid, sr_metex14_parse,
NULL
/* rs9lcd based meters {{{ */
DMM(
"radioshack-22-812", rs9lcd,
- "RadioShack", "22-812", "4800/8n1/rts=0/dtr=1", 4800,
+ "RadioShack", "22-812", "4800/8n1/rts=0/dtr=1",
RS9LCD_PACKET_SIZE, 0, 0, NULL,
sr_rs9lcd_packet_valid, sr_rs9lcd_parse,
NULL
DMM(
"tenma-72-7730-ser", ut71x,
"Tenma", "72-7730 (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"tenma-72-7732-ser", ut71x,
"Tenma", "72-7732 (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"tenma-72-9380a-ser", ut71x,
"Tenma", "72-9380A (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"uni-t-ut71a-ser", ut71x,
"UNI-T", "UT71A (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"uni-t-ut71b-ser", ut71x,
"UNI-T", "UT71B (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"uni-t-ut71c-ser", ut71x,
"UNI-T", "UT71C (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"uni-t-ut71d-ser", ut71x,
"UNI-T", "UT71D (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"uni-t-ut71e-ser", ut71x,
"UNI-T", "UT71E (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"voltcraft-vc920-ser", ut71x,
"Voltcraft", "VC-920 (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"voltcraft-vc940-ser", ut71x,
"Voltcraft", "VC-940 (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
DMM(
"voltcraft-vc960-ser", ut71x,
"Voltcraft", "VC-960 (UT-D02 cable)", "2400/7o1/rts=0/dtr=1",
- 2400, UT71X_PACKET_SIZE, 0, 0, NULL,
+ UT71X_PACKET_SIZE, 0, 0, NULL,
sr_ut71x_packet_valid, sr_ut71x_parse, NULL
),
/* }}} */
DMM(
"voltcraft-vc870-ser", vc870,
"Voltcraft", "VC-870 (UT-D02 cable)", "9600/8n1/rts=0/dtr=1",
- 9600, VC870_PACKET_SIZE, 0, 0, NULL,
+ VC870_PACKET_SIZE, 0, 0, NULL,
sr_vc870_packet_valid, sr_vc870_parse, NULL
),
/* }}} */
/* vc96 based meters {{{ */
DMM(
"voltcraft-vc96", vc96,
- "Voltcraft", "VC-96", "1200/8n2", 1200,
+ "Voltcraft", "VC-96", "1200/8n2",
VC96_PACKET_SIZE, 0, 0, NULL,
sr_vc96_packet_valid, sr_vc96_parse,
NULL
const char *vendor;
/** Model. */
const char *device;
- /** serialconn string. */
+ /** conn string. */
const char *conn;
- /** Baud rate. */
- uint32_t baudrate;
+ /** serialcomm string. */
+ const char *serialcomm;
/** Packet size in bytes. */
int packet_size;
/**
* 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
*/
#include <config.h>
-#include <stdint.h>
-#include <string.h>
-#include <math.h>
#include <glib.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
+#include <math.h>
+#include "protocol.h"
+#include <stdint.h>
+#include <string.h>
-#define LOG_PREFIX "serial-lcr-es51919"
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+ SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_LCRMETER,
+};
-struct lcr_es51919_info {
- struct sr_dev_driver di;
- const char *vendor;
- const char *model;
+static const uint32_t devopts[] = {
+ SR_CONF_CONTINUOUS,
+ SR_CONF_LIMIT_FRAMES | SR_CONF_SET,
+ SR_CONF_LIMIT_MSEC | SR_CONF_SET,
+ SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_LIST,
+ SR_CONF_EQUIV_CIRCUIT_MODEL | SR_CONF_GET | SR_CONF_LIST,
};
-static int dev_clear(const struct sr_dev_driver *di)
+static struct sr_dev_inst *scan_packet_check_devinst;
+
+static void scan_packet_check_setup(struct sr_dev_inst *sdi)
+{
+ scan_packet_check_devinst = sdi;
+}
+
+static gboolean scan_packet_check_func(const uint8_t *buf)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ const struct lcr_info *lcr;
+ struct lcr_parse_info *info;
+
+ /* Get a reference to the LCR model that is getting checked. */
+ sdi = scan_packet_check_devinst;
+ if (!sdi)
+ return FALSE;
+ devc = sdi->priv;
+ if (!devc)
+ return FALSE;
+ lcr = devc->lcr_info;
+ if (!lcr)
+ return FALSE;
+
+ /* Synchronize to the stream of LCR packets. */
+ if (!lcr->packet_valid(buf))
+ return FALSE;
+
+ /* Have LCR packets _processed_, gather current configuration. */
+ info = &devc->parse_info;
+ memset(info, 0, sizeof(*info));
+ if (lcr->packet_parse(buf, NULL, NULL, info) == SR_OK) {
+ devc->output_freq = info->output_freq;
+ if (info->circuit_model)
+ devc->circuit_model = info->circuit_model;
+ }
+
+ return TRUE;
+}
+
+static int scan_lcr_port(const struct lcr_info *lcr,
+ const char *conn, struct sr_serial_dev_inst *serial)
{
- return std_dev_clear_with_callback(di, es51919_serial_clean);
+ size_t len;
+ uint8_t buf[128];
+ int ret;
+ size_t dropped;
+
+ if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+ return SR_ERR_IO;
+ sr_info("Probing serial port %s.", conn);
+
+ /*
+ * See if we can detect a device of specified type.
+ *
+ * No supported device provides a means to "identify" yet. No
+ * supported device requires "packet request" yet. They all just
+ * send data periodically. So we check if the packets match the
+ * probed device's expected format.
+ */
+ serial_flush(serial);
+ if (lcr->packet_request) {
+ ret = lcr->packet_request(serial);
+ if (ret < 0) {
+ sr_err("Failed to request packet: %d.", ret);
+ goto scan_port_cleanup;
+ }
+ }
+ len = sizeof(buf);
+ ret = serial_stream_detect(serial, buf, &len,
+ lcr->packet_size, lcr->packet_valid, 3000);
+ if (ret != SR_OK)
+ goto scan_port_cleanup;
+
+ /*
+ * If the packets were found to match after more than two packets
+ * got dropped, something is wrong. This is worth warning about,
+ * but isn't fatal. The dropped bytes might be due to nonstandard
+ * cables that ship with some devices.
+ */
+ dropped = len - lcr->packet_size;
+ if (dropped > 2 * lcr->packet_size)
+ sr_warn("Had to drop unexpected amounts of data.");
+
+ /* Create a device instance for the found device. */
+ sr_info("Found %s %s device on port %s.", lcr->vendor, lcr->model, conn);
+
+scan_port_cleanup:
+ /* Keep serial port open if probe succeeded. */
+ if (ret != SR_OK)
+ serial_close(serial);
+
+ return ret;
+}
+
+static struct sr_dev_inst *create_lcr_sdi(struct lcr_info *lcr,
+ struct sr_serial_dev_inst *serial)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ size_t ch_idx;
+ const char **ch_fmts;
+ const char *fmt;
+ char ch_name[8];
+
+ sdi = g_malloc0(sizeof(*sdi));
+ sdi->status = SR_ST_INACTIVE;
+ sdi->vendor = g_strdup(lcr->vendor);
+ sdi->model = g_strdup(lcr->model);
+ sdi->inst_type = SR_INST_SERIAL;
+ sdi->conn = serial;
+ devc = g_malloc0(sizeof(*devc));
+ sdi->priv = devc;
+ devc->lcr_info = lcr;
+ sr_sw_limits_init(&devc->limits);
+ ch_fmts = lcr->channel_formats;
+ for (ch_idx = 0; ch_idx < lcr->channel_count; ch_idx++) {
+ fmt = (ch_fmts && ch_fmts[ch_idx]) ? ch_fmts[ch_idx] : "P%zu";
+ snprintf(ch_name, sizeof(ch_name), fmt, ch_idx + 1);
+ sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, ch_name);
+ }
+
+ return sdi;
+}
+
+static int read_lcr_port(struct sr_dev_inst *sdi,
+ const struct lcr_info *lcr, struct sr_serial_dev_inst *serial)
+{
+ size_t len;
+ uint8_t buf[128];
+ int ret;
+
+ serial_flush(serial);
+ if (lcr->packet_request) {
+ ret = lcr->packet_request(serial);
+ if (ret < 0) {
+ sr_err("Failed to request packet: %d.", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Receive a few more packets (and process them!) to have the
+ * current output frequency and circuit model parameter values
+ * detected. The above "stream detect" phase only synchronized
+ * to the packets by checking their validity, but it cannot
+ * provide details. This phase here runs a modified "checker"
+ * routine which also extracts details from LCR packets after
+ * the device got detected and parameter storage was prepared.
+ */
+ sr_info("Retrieving current acquisition parameters.");
+ len = sizeof(buf);
+ scan_packet_check_setup(sdi);
+ ret = serial_stream_detect(serial, buf, &len,
+ lcr->packet_size, scan_packet_check_func, 1500);
+ scan_packet_check_setup(NULL);
+
+ return ret;
}
static GSList *scan(struct sr_dev_driver *di, GSList *options)
{
- struct lcr_es51919_info *lcr;
+ struct lcr_info *lcr;
+ struct sr_config *src;
+ GSList *l, *devices;
+ const char *conn, *serialcomm;
+ struct sr_serial_dev_inst *serial;
+ int ret;
struct sr_dev_inst *sdi;
- lcr = (struct lcr_es51919_info *)di;
+ lcr = (struct lcr_info *)di;
- if (!(sdi = es51919_serial_scan(options, lcr->vendor, lcr->model)))
+ /* Get serial port name and communication parameters. */
+ conn = NULL;
+ serialcomm = lcr->comm;
+ for (l = options; l; l = l->next) {
+ src = l->data;
+ switch (src->key) {
+ case SR_CONF_CONN:
+ conn = g_variant_get_string(src->data, NULL);
+ break;
+ case SR_CONF_SERIALCOMM:
+ serialcomm = g_variant_get_string(src->data, NULL);
+ break;
+ }
+ }
+ if (!conn)
return NULL;
- return std_scan_complete(di, g_slist_append(NULL, sdi));
+ devices = NULL;
+ /* TODO Handle ambiguous conn= specs, see serial-dmm. */
+
+ /* Open the serial port, check data packets. */
+ serial = sr_serial_dev_inst_new(conn, serialcomm);
+ ret = scan_lcr_port(lcr, conn, serial);
+ if (ret != SR_OK) {
+ /* Probe failed, release 'serial'. */
+ sr_serial_dev_inst_free(serial);
+ } else {
+ /* Create and return device instance, keep 'serial' alive. */
+ sdi = create_lcr_sdi(lcr, serial);
+ devices = g_slist_append(devices, sdi);
+ (void)read_lcr_port(sdi, lcr, serial);
+ serial_close(serial);
+ }
+
+ return std_scan_complete(di, devices);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ const struct lcr_info *lcr;
+
+ if (!sdi)
+ return SR_ERR_ARG;
+ devc = sdi->priv;
+
+ switch (key) {
+ case SR_CONF_LIMIT_FRAMES:
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_get(&devc->limits, key, data);
+ case SR_CONF_OUTPUT_FREQUENCY:
+ *data = g_variant_new_double(devc->output_freq);
+ return SR_OK;
+ case SR_CONF_EQUIV_CIRCUIT_MODEL:
+ if (!devc->circuit_model)
+ return SR_ERR_NA;
+ *data = g_variant_new_string(devc->circuit_model);
+ return SR_OK;
+ default:
+ lcr = devc->lcr_info;
+ if (!lcr)
+ return SR_ERR_NA;
+ if (!lcr->config_get)
+ return SR_ERR_NA;
+ return lcr->config_get(key, data, sdi, cg);
+ }
+ /* UNREACH */
+}
+
+static int config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ const struct lcr_info *lcr;
+
+ if (!sdi)
+ return SR_ERR_ARG;
+ devc = sdi->priv;
+
+ switch (key) {
+ case SR_CONF_LIMIT_FRAMES:
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_set(&devc->limits, key, data);
+ default:
+ lcr = devc->lcr_info;
+ if (!lcr)
+ return SR_ERR_NA;
+ if (!lcr->config_set)
+ return SR_ERR_NA;
+ return lcr->config_set(key, data, sdi, cg);
+ }
+ /* UNREACH */
+}
+
+static int config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+ const struct lcr_info *lcr;
+
+ switch (key) {
+ case SR_CONF_SCAN_OPTIONS:
+ case SR_CONF_DEVICE_OPTIONS:
+ return STD_CONFIG_LIST(key, data, sdi, cg,
+ scanopts, drvopts, devopts);
+ default:
+ break;
+ }
+
+ if (!sdi)
+ return SR_ERR_ARG;
+ devc = sdi->priv;
+ switch (key) {
+ default:
+ lcr = devc->lcr_info;
+ if (!lcr || !lcr->config_list)
+ return SR_ERR_NA;
+ return lcr->config_list(key, data, sdi, cg);
+ }
+ /* UNREACH */
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+
+ devc = sdi->priv;
+
+ /*
+ * Clear values that were gathered during scan or in a previous
+ * acquisition, so that this acquisition's data feed immediately
+ * starts with meta packets before first measurement values, and
+ * also communicates subsequent parameter changes.
+ */
+ devc->output_freq = 0;
+ devc->circuit_model = NULL;
+
+ sr_sw_limits_acquisition_start(&devc->limits);
+ std_session_send_df_header(sdi);
+
+ serial = sdi->conn;
+ serial_source_add(sdi->session, serial, G_IO_IN, 50,
+ lcr_receive_data, (void *)sdi);
+
+ return SR_OK;
}
#define LCR_ES51919(id, vendor, model) \
- &((struct lcr_es51919_info) { \
+ &((struct lcr_info) { \
{ \
.name = id, \
.longname = vendor " " model, \
.cleanup = std_cleanup, \
.scan = scan, \
.dev_list = std_dev_list, \
- .dev_clear = dev_clear, \
- .config_get = es51919_serial_config_get, \
- .config_set = es51919_serial_config_set, \
- .config_list = es51919_serial_config_list, \
+ .dev_clear = std_dev_clear, \
+ .config_get = config_get, \
+ .config_set = config_set, \
+ .config_list = config_list, \
.dev_open = std_serial_dev_open, \
.dev_close = std_serial_dev_close, \
- .dev_acquisition_start = es51919_serial_acquisition_start, \
+ .dev_acquisition_start = dev_acquisition_start, \
.dev_acquisition_stop = std_serial_dev_acquisition_stop, \
.context = NULL, \
}, \
- vendor, model, \
+ vendor, model, ES51919_CHANNEL_COUNT, NULL, \
+ ES51919_COMM_PARAM, ES51919_PACKET_SIZE, \
+ 0, NULL, \
+ es51919_packet_valid, es51919_packet_parse, \
+ NULL, NULL, es51919_config_list, \
}).di
SR_REGISTER_DEV_DRIVER_LIST(lcr_es51919_drivers,
LCR_ES51919("deree-de5000", "DER EE", "DE-5000"),
+ LCR_ES51919("mastech-ms5308", "MASTECH", "MS5308"),
LCR_ES51919("peaktech-2170", "PeakTech", "2170"),
+ LCR_ES51919("uni-t-ut612", "UNI-T", "UT612"),
+);
+
+#define LCR_VC4080(id, vendor, model) \
+ &((struct lcr_info) { \
+ { \
+ .name = id, \
+ .longname = vendor " " model, \
+ .api_version = 1, \
+ .init = std_init, \
+ .cleanup = std_cleanup, \
+ .scan = scan, \
+ .dev_list = std_dev_list, \
+ .dev_clear = std_dev_clear, \
+ .config_get = config_get, \
+ .config_set = config_set, \
+ .config_list = config_list, \
+ .dev_open = std_serial_dev_open, \
+ .dev_close = std_serial_dev_close, \
+ .dev_acquisition_start = dev_acquisition_start, \
+ .dev_acquisition_stop = std_serial_dev_acquisition_stop, \
+ .context = NULL, \
+ }, \
+ vendor, model, \
+ VC4080_CHANNEL_COUNT, vc4080_channel_formats, \
+ VC4080_COMM_PARAM, VC4080_PACKET_SIZE, \
+ 500, vc4080_packet_request, \
+ vc4080_packet_valid, vc4080_packet_parse, \
+ NULL, NULL, vc4080_config_list, \
+ }).di
+
+SR_REGISTER_DEV_DRIVER_LIST(lcr_vc4080_drivers,
+ LCR_VC4080("peaktech-2165", "PeakTech", "2165"),
+ LCR_VC4080("voltcraft-4080", "Voltcraft", "4080"),
);
--- /dev/null
+/*
+ * 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 <libsigrok/libsigrok.h>
+#include <libsigrok-internal.h>
+#include "protocol.h"
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void send_frame_start(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct lcr_parse_info *info;
+ uint64_t freq;
+ const char *model;
+ struct sr_datafeed_packet packet;
+
+ devc = sdi->priv;
+ info = &devc->parse_info;
+
+ /* Communicate changes of frequency or model before data values. */
+ freq = info->output_freq;
+ if (freq != devc->output_freq) {
+ devc->output_freq = freq;
+ sr_session_send_meta(sdi, SR_CONF_OUTPUT_FREQUENCY,
+ g_variant_new_double(freq));
+ }
+ model = info->circuit_model;
+ if (model && model != devc->circuit_model) {
+ devc->circuit_model = model;
+ sr_session_send_meta(sdi, SR_CONF_EQUIV_CIRCUIT_MODEL,
+ g_variant_new_string(model));
+ }
+
+ /* Data is about to get sent. Start a new frame. */
+ packet.type = SR_DF_FRAME_BEGIN;
+ sr_session_send(sdi, &packet);
+}
+
+static int handle_packet(struct sr_dev_inst *sdi, const uint8_t *pkt)
+{
+ struct dev_context *devc;
+ struct lcr_parse_info *info;
+ const struct lcr_info *lcr;
+ size_t ch_idx;
+ int rc;
+ float value;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
+ struct sr_analog_encoding encoding;
+ struct sr_analog_meaning meaning;
+ struct sr_analog_spec spec;
+ gboolean frame;
+ struct sr_channel *channel;
+
+ devc = sdi->priv;
+ info = &devc->parse_info;
+ lcr = devc->lcr_info;
+
+ /* Note: digits/spec_digits will be overridden later. */
+ sr_analog_init(&analog, &encoding, &meaning, &spec, 0);
+ analog.num_samples = 1;
+ analog.data = &value;
+
+ frame = FALSE;
+ for (ch_idx = 0; ch_idx < lcr->channel_count; ch_idx++) {
+ channel = g_slist_nth_data(sdi->channels, ch_idx);
+ analog.meaning->channels = g_slist_append(NULL, channel);
+ info->ch_idx = ch_idx;
+ rc = lcr->packet_parse(pkt, &value, &analog, info);
+ if (sdi->session && rc == SR_OK && analog.meaning->mq && channel->enabled) {
+ if (!frame) {
+ send_frame_start(sdi);
+ frame = TRUE;
+ }
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ sr_session_send(sdi, &packet);
+ }
+ g_slist_free(analog.meaning->channels);
+ }
+ if (frame) {
+ std_session_send_frame_end(sdi);
+ sr_sw_limits_update_frames_read(&devc->limits, 1);
+ }
+
+ return SR_OK;
+}
+
+static int handle_new_data(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ ssize_t rdsize;
+ const struct lcr_info *lcr;
+ uint8_t *pkt;
+ size_t copy_len;
+
+ devc = sdi->priv;
+ serial = sdi->conn;
+
+ /* Read another chunk of data into the buffer. */
+ rdsize = sizeof(devc->buf) - devc->buf_rxpos;
+ rdsize = serial_read_nonblocking(serial, &devc->buf[devc->buf_rxpos], rdsize);
+ if (rdsize < 0)
+ return SR_ERR_IO;
+ devc->buf_rxpos += rdsize;
+
+ /*
+ * Process as many packets as the buffer might contain. Assume
+ * that the stream is synchronized in the typical case. Re-sync
+ * in case of mismatch (skip individual bytes until data matches
+ * the expected packet layout again).
+ */
+ lcr = devc->lcr_info;
+ while (devc->buf_rxpos >= lcr->packet_size) {
+ pkt = &devc->buf[0];
+ if (!lcr->packet_valid(pkt)) {
+ copy_len = devc->buf_rxpos - 1;
+ memmove(&devc->buf[0], &devc->buf[1], copy_len);
+ devc->buf_rxpos--;
+ continue;
+ }
+ (void)handle_packet(sdi, pkt);
+ copy_len = devc->buf_rxpos - lcr->packet_size;
+ memmove(&devc->buf[0], &devc->buf[lcr->packet_size], copy_len);
+ devc->buf_rxpos -= lcr->packet_size;
+ }
+
+ return SR_OK;
+}
+
+static int handle_timeout(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ const struct lcr_info *lcr;
+ struct sr_serial_dev_inst *serial;
+ int64_t now;
+ int ret;
+
+ devc = sdi->priv;
+ lcr = devc->lcr_info;
+
+ if (!lcr->packet_request)
+ return SR_OK;
+
+ now = g_get_monotonic_time();
+ if (devc->req_next_at && now < devc->req_next_at)
+ return SR_OK;
+
+ serial = sdi->conn;
+ ret = lcr->packet_request(serial);
+ if (ret < 0) {
+ sr_err("Failed to request packet: %d.", ret);
+ return ret;
+ }
+
+ if (lcr->req_timeout_ms)
+ devc->req_next_at = now + lcr->req_timeout_ms * 1000;
+
+ return SR_OK;
+}
+
+SR_PRIV int lcr_receive_data(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ int ret;
+
+ (void)fd;
+
+ if (!(sdi = cb_data))
+ return TRUE;
+ if (!(devc = sdi->priv))
+ return TRUE;
+
+ if (revents == G_IO_IN)
+ ret = handle_new_data(sdi);
+ else
+ ret = handle_timeout(sdi);
+ if (sr_sw_limits_check(&devc->limits))
+ sr_dev_acquisition_stop(sdi);
+ if (ret != SR_OK)
+ return FALSE;
+
+ return TRUE;
+}
--- /dev/null
+/*
+ * 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/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_SERIAL_LCR_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_SERIAL_LCR_PROTOCOL_H
+
+#define LOG_PREFIX "serial-lcr"
+
+#include <libsigrok/libsigrok.h>
+#include <libsigrok-internal.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+struct lcr_info {
+ struct sr_dev_driver di;
+ const char *vendor;
+ const char *model;
+ size_t channel_count;
+ const char **channel_formats;
+ const char *comm;
+ size_t packet_size;
+ int64_t req_timeout_ms;
+ int (*packet_request)(struct sr_serial_dev_inst *serial);
+ gboolean (*packet_valid)(const uint8_t *pkt);
+ int (*packet_parse)(const uint8_t *pkt, float *value,
+ struct sr_datafeed_analog *analog, void *info);
+ int (*config_get)(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg);
+ int (*config_set)(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg);
+ int (*config_list)(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg);
+};
+
+#define LCR_BUFSIZE 128
+
+struct dev_context {
+ const struct lcr_info *lcr_info;
+ struct sr_sw_limits limits;
+ uint8_t buf[LCR_BUFSIZE];
+ size_t buf_rxpos, buf_rdpos;
+ struct lcr_parse_info parse_info;
+ uint64_t output_freq;
+ const char *circuit_model;
+ int64_t req_next_at;
+};
+
+SR_PRIV int lcr_receive_data(int fd, int revents, void *cb_data);
+
+#endif
#define SERIES(x) &supported_series[x]
/* series, model, min timebase, analog channels, digital */
static const struct siglent_sds_model supported_models[] = {
- { SERIES(SDS1000CML), "SDS1152CML", { 20, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000CML), "SDS1102CML", { 10, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000CML), "SDS1072CML", { 5, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000CNL), "SDS1202CNL", { 20, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000CNL), "SDS1102CNL", { 10, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000CNL), "SDS1072CNL", { 5, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000DL), "SDS1202DL", { 20, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000DL), "SDS1102DL", { 10, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000DL), "SDS1022DL", { 5, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000DL), "SDS1052DL", { 5, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000DL), "SDS1052DL+", { 5, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000X), "SDS1102X", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000XP), "SDS1102X+", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000X), "SDS1202X", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000XP), "SDS1202X+", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000XE), "SDS1202X-E", { 1, 1000000000 }, 2, false, 0 },
- { SERIES(SDS1000XE), "SDS1104X-E", { 1, 1000000000 }, 4, true, 16 },
- { SERIES(SDS1000XE), "SDS1204X-E", { 1, 1000000000 }, 4, true, 16 },
- { SERIES(SDS2000X), "SDS2072X", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS2000X), "SDS2074X", { 2, 1000000000 }, 4, false, 0 },
- { SERIES(SDS2000X), "SDS2102X", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS2000X), "SDS2104X", { 2, 1000000000 }, 4, false, 0 },
- { SERIES(SDS2000X), "SDS2202X", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS2000X), "SDS2204X", { 2, 1000000000 }, 4, false, 0 },
- { SERIES(SDS2000X), "SDS2302X", { 2, 1000000000 }, 2, false, 0 },
- { SERIES(SDS2000X), "SDS2304X", { 2, 1000000000 }, 4, false, 0 },
+ { SERIES(SDS1000CML), "SDS1152CML", { 20, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000CML), "SDS1102CML", { 10, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000CML), "SDS1072CML", { 5, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000CNL), "SDS1202CNL", { 20, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000CNL), "SDS1102CNL", { 10, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000CNL), "SDS1072CNL", { 5, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000DL), "SDS1202DL", { 20, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000DL), "SDS1102DL", { 10, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000DL), "SDS1022DL", { 5, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000DL), "SDS1052DL", { 5, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000DL), "SDS1052DL+", { 5, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000X), "SDS1102X", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000XP), "SDS1102X+", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000X), "SDS1202X", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000XP), "SDS1202X+", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000XE), "SDS1202X-E", { 1, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS1000XE), "SDS1104X-E", { 1, 1000000000 }, 4, TRUE, 16 },
+ { SERIES(SDS1000XE), "SDS1204X-E", { 1, 1000000000 }, 4, TRUE, 16 },
+ { SERIES(SDS2000X), "SDS2072X", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2074X", { 2, 1000000000 }, 4, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2102X", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2104X", { 2, 1000000000 }, 4, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2202X", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2204X", { 2, 1000000000 }, 4, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2302X", { 2, 1000000000 }, 2, FALSE, 0 },
+ { SERIES(SDS2000X), "SDS2304X", { 2, 1000000000 }, 4, FALSE, 0 },
};
-SR_PRIV struct sr_dev_driver siglent_sds_driver_info;
+static struct sr_dev_driver siglent_sds_driver_info;
static void clear_helper(void *priv)
{
return SR_OK;
}
-SR_PRIV struct sr_dev_driver siglent_sds_driver_info = {
+static struct sr_dev_driver siglent_sds_driver_info = {
.name = "siglent-sds",
.longname = "Siglent SDS1000/SDS2000",
.api_version = 1,
.dev_acquisition_stop = dev_acquisition_stop,
.context = NULL,
};
-
SR_REGISTER_DEV_DRIVER(siglent_sds_driver_info);
struct sr_channel *ch;
int len, i;
float wait;
- gboolean read_complete = false;
+ gboolean read_complete = FALSE;
(void)fd;
}
do {
- read_complete = false;
+ read_complete = FALSE;
if (devc->num_block_bytes > devc->num_samples) {
/* We received all data as one block. */
/* Offset the data block buffer past the IEEE header and description header. */
sr_dbg("Transfer has been completed.");
devc->num_header_bytes = 0;
devc->num_block_bytes = 0;
- read_complete = true;
+ read_complete = TRUE;
if (!sr_scpi_read_complete(scpi)) {
sr_err("Read should have been completed.");
packet.type = SR_DF_FRAME_END;
#ifndef LIBSIGROK_HARDWARE_SIGLENT_SDS_PROTOCOL_H
#define LIBSIGROK_HARDWARE_SIGLENT_SDS_PROTOCOL_H
-#include <stdbool.h>
#include <stdint.h>
#include <glib.h>
#include <libsigrok/libsigrok.h>
const char *name;
uint64_t min_timebase[2];
unsigned int analog_channels;
- bool has_digital;
+ gboolean has_digital;
unsigned int digital_channels;
};
sdi = g_malloc0(sizeof(struct sr_dev_inst));
sdi->status = SR_ST_INACTIVE;
- sdi->vendor = g_strdup("SysClk");
+ sdi->vendor = g_strdup("Sysclk");
sdi->model = g_strdup(model->name);
sdi->priv = devc;
return sdi;
}
-/* Create a new device instance for a libusb device if it is a SysClk LWLA
+/* Create a new device instance for a libusb device if it is a Sysclk LWLA
* device and also matches the connection specification.
*/
static struct sr_dev_inst *dev_inst_new_matching(GSList *conn_matches,
} else {
if (conn_matches)
sr_warn("USB device %d.%d (%04x:%04x) is not a"
- " SysClk LWLA.", bus, address, vid, pid);
+ " Sysclk LWLA.", bus, address, vid, pid);
return NULL;
}
sdi = dev_inst_new(model);
static struct sr_dev_driver sysclk_lwla_driver_info = {
.name = "sysclk-lwla",
- .longname = "SysClk LWLA series",
+ .longname = "Sysclk LWLA series",
.api_version = 1,
.init = std_init,
.cleanup = std_cleanup,
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
+ * Copyright (C) 2019 Vitaliy Vorobyov
+ *
+ * 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 <libusb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libsigrok/libsigrok.h>
+#include <libsigrok-internal.h>
+#include "protocol.h"
+
+ /* Number of logic channels. */
+#define NUM_CHANNELS 32
+
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_LOGIC_ANALYZER,
+};
+
+static const int32_t trigger_matches[] = {
+ SR_TRIGGER_ZERO,
+ SR_TRIGGER_ONE,
+ SR_TRIGGER_RISING,
+ SR_TRIGGER_FALLING,
+};
+
+static const uint64_t capture_ratios[] = {
+ 0, 10, 20, 30, 50, 70, 90, 100,
+};
+
+static const uint32_t devopts[] = {
+ SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+ SR_CONF_TRIGGER_MATCH | SR_CONF_LIST,
+ SR_CONF_RLE | SR_CONF_GET,
+};
+
+static const uint64_t samplerates[] = {
+ SR_MHZ(500), SR_MHZ(400), SR_MHZ(250), SR_MHZ(200), SR_MHZ(100),
+ SR_MHZ(50), SR_MHZ(25), SR_MHZ(20), SR_MHZ(10), SR_MHZ(5), SR_MHZ(2),
+ SR_MHZ(1), SR_KHZ(500), SR_KHZ(200), SR_KHZ(100), SR_KHZ(50),
+ SR_KHZ(20), SR_KHZ(10), SR_KHZ(5), SR_KHZ(2),
+};
+
+static struct sr_dev_inst *dev_inst_new(void)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ int i;
+ char name[8];
+
+ devc = g_malloc0(sizeof(struct dev_context));
+ devc->active_fpga_config = FPGA_NOCONF;
+ devc->samplerate = samplerates[0];
+ devc->limit_samples = MAX_LIMIT_SAMPLES;
+ devc->capture_ratio = capture_ratios[4];
+ devc->channel_mask = (UINT64_C(1) << NUM_CHANNELS) - 1;
+
+ sdi = g_malloc0(sizeof(struct sr_dev_inst));
+ sdi->status = SR_ST_INACTIVE;
+ sdi->vendor = g_strdup("Sysclk");
+ sdi->model = g_strdup("SLA5032");
+ sdi->priv = devc;
+
+ for (i = 0; i < NUM_CHANNELS; i++) {
+ g_snprintf(name, sizeof(name), "CH%d", i);
+ sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE, name);
+ }
+
+ return sdi;
+}
+
+/* Create a new device instance for a libusb device if it is a Sysclk SLA5032
+ * device and also matches the connection specification.
+ */
+static struct sr_dev_inst *dev_inst_new_matching(GSList *conn_matches,
+ libusb_device *dev)
+{
+ GSList *node;
+ struct sr_usb_dev_inst *usb;
+ struct sr_dev_inst *sdi;
+ struct libusb_device_descriptor des;
+ int bus, address, ret;
+ unsigned int vid, pid;
+
+ bus = libusb_get_bus_number(dev);
+ address = libusb_get_device_address(dev);
+
+ for (node = conn_matches; node != NULL; node = node->next) {
+ usb = node->data;
+ if (usb && usb->bus == bus && usb->address == address)
+ break; /* found */
+ }
+ if (conn_matches && !node)
+ return NULL; /* no match */
+
+ ret = libusb_get_device_descriptor(dev, &des);
+ if (ret != 0) {
+ sr_err("Failed to get USB device descriptor: %s.",
+ libusb_error_name(ret));
+ return NULL;
+ }
+ vid = des.idVendor;
+ pid = des.idProduct;
+
+ /* Create sigrok device instance. */
+ if (vid == USB_VID_SYSCLK && pid == USB_PID_SLA5032) {
+ } else {
+ if (conn_matches)
+ sr_warn("USB device %d.%d (%04x:%04x) is not a"
+ " Sysclk SLA5032.", bus, address, vid, pid);
+ return NULL;
+ }
+ sdi = dev_inst_new();
+
+ sdi->inst_type = SR_INST_USB;
+ sdi->conn = sr_usb_dev_inst_new(bus, address, NULL);
+
+ return sdi;
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+ GSList *conn_devices, *devices, *node;
+ struct drv_context *drvc;
+ struct sr_dev_inst *sdi;
+ struct sr_config *src;
+ const char *conn;
+ libusb_device **devlist;
+ ssize_t num_devs, i;
+
+ drvc = di->context;
+ conn = NULL;
+ conn_devices = NULL;
+ devices = NULL;
+
+ for (node = options; node != NULL; node = node->next) {
+ src = node->data;
+ if (src->key == SR_CONF_CONN) {
+ conn = g_variant_get_string(src->data, NULL);
+ break;
+ }
+ }
+ if (conn) {
+ /* Find devices matching the connection specification. */
+ conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn);
+ }
+
+ /* List all libusb devices. */
+ num_devs = libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist);
+ if (num_devs < 0) {
+ sr_err("Failed to list USB devices: %s.",
+ libusb_error_name(num_devs));
+ g_slist_free_full(conn_devices,
+ (GDestroyNotify)&sr_usb_dev_inst_free);
+ return NULL;
+ }
+
+ /* Scan the USB device list for matching devices. */
+ for (i = 0; i < num_devs; i++) {
+ sdi = dev_inst_new_matching(conn_devices, devlist[i]);
+ if (!sdi)
+ continue; /* no match */
+
+ /* Register device instance with driver. */
+ devices = g_slist_append(devices, sdi);
+ }
+
+ libusb_free_device_list(devlist, 1);
+ g_slist_free_full(conn_devices, (GDestroyNotify)&sr_usb_dev_inst_free);
+
+ return std_scan_complete(di, devices);
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+ struct drv_context *drvc;
+ struct dev_context *devc;
+ struct sr_usb_dev_inst *usb;
+ int ret;
+
+ drvc = sdi->driver->context;
+ devc = sdi->priv;
+ usb = sdi->conn;
+
+ ret = sr_usb_open(drvc->sr_ctx->libusb_ctx, usb);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = libusb_set_configuration(usb->devhdl, USB_CONFIG);
+ if (ret != LIBUSB_SUCCESS) {
+ sr_err("Failed to set USB configuration: %s.",
+ libusb_error_name(ret));
+ sr_usb_close(usb);
+ return SR_ERR;
+ }
+
+ ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE);
+ if (ret != LIBUSB_SUCCESS) {
+ sr_err("Failed to claim interface: %s.",
+ libusb_error_name(ret));
+ sr_usb_close(usb);
+ return SR_ERR;
+ }
+
+ sdi->status = SR_ST_ACTIVE;
+
+ devc->active_fpga_config = FPGA_NOCONF;
+ devc->state = STATE_IDLE;
+
+ return sla5032_apply_fpga_config(sdi);
+}
+
+static int dev_close(struct sr_dev_inst *sdi)
+{
+ struct sr_usb_dev_inst *usb;
+
+ usb = sdi->conn;
+
+ if (usb->devhdl)
+ libusb_release_interface(usb->devhdl, USB_INTERFACE);
+
+ sr_usb_close(usb);
+
+ return SR_OK;
+}
+
+/* Check whether the device options contain a specific key.
+ * Also match against get/set/list bits if specified.
+ */
+static int has_devopt(uint32_t key)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(devopts); i++) {
+ if ((devopts[i] & (SR_CONF_MASK | key)) == key)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+
+ (void)cg;
+
+ if (!sdi)
+ return SR_ERR_ARG;
+
+ devc = sdi->priv;
+
+ if (!has_devopt(key | SR_CONF_GET))
+ return SR_ERR_NA;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ *data = g_variant_new_uint64(devc->samplerate);
+ break;
+ case SR_CONF_LIMIT_SAMPLES:
+ *data = g_variant_new_uint64(devc->limit_samples);
+ break;
+ case SR_CONF_CAPTURE_RATIO:
+ *data = g_variant_new_uint64(devc->capture_ratio);
+ break;
+ case SR_CONF_RLE:
+ *data = g_variant_new_boolean(TRUE);
+ break;
+ default:
+ /* Must not happen for a key listed in devopts. */
+ return SR_ERR_BUG;
+ }
+
+ return SR_OK;
+}
+
+static int config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ uint64_t value;
+ struct dev_context *devc;
+ int idx;
+
+ (void)cg;
+
+ if (!sdi)
+ return SR_ERR_ARG;
+
+ devc = sdi->priv;
+
+ if (!has_devopt(key | SR_CONF_SET))
+ return SR_ERR_NA;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ value = g_variant_get_uint64(data);
+ if ((idx = std_u64_idx(data, ARRAY_AND_SIZE(samplerates))) < 0)
+ return SR_ERR_ARG;
+ devc->samplerate = samplerates[idx];
+ break;
+ case SR_CONF_LIMIT_SAMPLES:
+ value = g_variant_get_uint64(data);
+ if (value > MAX_LIMIT_SAMPLES || value < MIN_LIMIT_SAMPLES)
+ return SR_ERR_ARG;
+ devc->limit_samples = value;
+ break;
+ case SR_CONF_CAPTURE_RATIO:
+ devc->capture_ratio = g_variant_get_uint64(data);
+ break;
+ default:
+ /* Must not happen for a key listed in devopts. */
+ return SR_ERR_BUG;
+ }
+
+ return SR_OK;
+}
+
+static int config_channel_set(const struct sr_dev_inst *sdi,
+ struct sr_channel *ch, unsigned int changes)
+{
+ uint64_t channel_bit;
+ struct dev_context *devc;
+
+ if (!sdi)
+ return SR_ERR_ARG;
+
+ devc = sdi->priv;
+
+ if (ch->index < 0 || ch->index >= NUM_CHANNELS) {
+ sr_err("Channel index %d out of range.", ch->index);
+ return SR_ERR_BUG;
+ }
+
+ if ((changes & SR_CHANNEL_SET_ENABLED) != 0) {
+ channel_bit = UINT64_C(1) << ch->index;
+
+ /* Enable or disable logic input for this channel. */
+ if (ch->enabled)
+ devc->channel_mask |= channel_bit;
+ else
+ devc->channel_mask &= ~channel_bit;
+ }
+
+ return SR_OK;
+}
+
+/* Derive trigger masks from the session's trigger configuration. */
+static int prepare_trigger_masks(const struct sr_dev_inst *sdi)
+{
+ uint32_t trigger_mask, trigger_values, trigger_edge_mask;
+ uint32_t level_bit, type_bit;
+ struct dev_context *devc;
+ struct sr_trigger *trigger;
+ struct sr_trigger_stage *stage;
+ struct sr_trigger_match *match;
+ const GSList *node;
+ int idx;
+ enum sr_trigger_matches trg;
+
+ devc = sdi->priv;
+
+ trigger_mask = 0;
+ trigger_values = 0;
+ trigger_edge_mask = 0;
+
+ trigger = sr_session_trigger_get(sdi->session);
+ if (!trigger || !trigger->stages) {
+ goto no_triggers;
+ }
+
+ if (trigger->stages->next) {
+ sr_err("This device only supports 1 trigger stage.");
+ return SR_ERR_ARG;
+ }
+ stage = trigger->stages->data;
+
+ for (node = stage->matches; node; node = node->next) {
+ match = node->data;
+
+ if (!match->channel->enabled)
+ continue; /* Ignore disabled channel. */
+
+ idx = match->channel->index;
+ trg = match->match;
+
+ if (idx < 0 || idx >= NUM_CHANNELS) {
+ sr_err("Channel index %d out of range.", idx);
+ return SR_ERR_BUG; /* Should not happen. */
+ }
+ if (trg != SR_TRIGGER_ZERO
+ && trg != SR_TRIGGER_ONE
+ && trg != SR_TRIGGER_RISING
+ && trg != SR_TRIGGER_FALLING) {
+ sr_err("Unsupported trigger match for CH%d.", idx);
+ return SR_ERR_ARG;
+ }
+ level_bit = (trg == SR_TRIGGER_ONE
+ || trg == SR_TRIGGER_RISING) ? 1 : 0;
+ type_bit = (trg == SR_TRIGGER_RISING
+ || trg == SR_TRIGGER_FALLING) ? 1 : 0; /* 1 if edge triggered, 0 if level triggered */
+
+ trigger_mask |= UINT32_C(1) << idx;
+ trigger_values |= level_bit << idx;
+ trigger_edge_mask |= type_bit << idx;
+ }
+
+no_triggers:
+ devc->trigger_mask = trigger_mask;
+ devc->trigger_values = trigger_values;
+ devc->trigger_edge_mask = trigger_edge_mask;
+
+ return SR_OK;
+}
+
+static int config_commit(const struct sr_dev_inst *sdi)
+{
+ int ret;
+
+ ret = prepare_trigger_masks(sdi);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = sla5032_apply_fpga_config(sdi);
+ if (ret != SR_OK) {
+ sr_err("Failed to apply FPGA configuration.");
+ return ret;
+ }
+
+ return SR_OK;
+}
+
+static int config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+ struct dev_context *devc;
+
+ devc = (sdi) ? sdi->priv : NULL;
+
+ switch (key) {
+ case SR_CONF_SCAN_OPTIONS:
+ case SR_CONF_DEVICE_OPTIONS:
+ return std_opts_config_list(key, data, sdi, cg,
+ ARRAY_AND_SIZE(scanopts), ARRAY_AND_SIZE(drvopts),
+ (devc) ? devopts : NULL,
+ (devc) ? ARRAY_SIZE(devopts) : 0);
+ }
+
+ if (!devc)
+ return SR_ERR_ARG;
+ if (!has_devopt(key | SR_CONF_LIST))
+ return SR_ERR_NA;
+
+ switch (key) {
+ case SR_CONF_SAMPLERATE:
+ *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates));
+ break;
+ case SR_CONF_LIMIT_SAMPLES:
+ *data = std_gvar_tuple_u64(MIN_LIMIT_SAMPLES, MAX_LIMIT_SAMPLES);
+ break;
+ case SR_CONF_CAPTURE_RATIO:
+ *data = std_gvar_array_u64(ARRAY_AND_SIZE(capture_ratios));
+ break;
+ case SR_CONF_TRIGGER_MATCH:
+ *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches));
+ break;
+ default:
+ /* Must not happen for a key listed in devopts. */
+ return SR_ERR_BUG;
+ }
+
+ return SR_OK;
+}
+
+/* Set up the device hardware to begin capturing samples as soon as the
+ * configured trigger conditions are met, or immediately if no triggers
+ * are configured.
+ */
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+ return sla5032_start_acquisition(sdi);
+}
+
+static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+
+ devc = sdi->priv;
+
+ sr_session_source_remove(sdi->session, -1);
+
+ std_session_send_df_end(sdi);
+
+ devc->state = STATE_IDLE;
+
+ return SR_OK;
+}
+
+static struct sr_dev_driver sysclk_sla5032_driver_info = {
+ .name = "sysclk-sla5032",
+ .longname = "Sysclk SLA5032",
+ .api_version = 1,
+ .init = std_init,
+ .cleanup = std_cleanup,
+ .scan = scan,
+ .dev_list = std_dev_list,
+ .dev_clear = std_dev_clear,
+ .config_get = config_get,
+ .config_set = config_set,
+ .config_channel_set = config_channel_set,
+ .config_commit = config_commit,
+ .config_list = config_list,
+ .dev_open = dev_open,
+ .dev_close = dev_close,
+ .dev_acquisition_start = dev_acquisition_start,
+ .dev_acquisition_stop = dev_acquisition_stop,
+ .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(sysclk_sla5032_driver_info);
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Vitaliy Vorobyov
+ *
+ * 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 <string.h>
+#include "protocol.h"
+
+/*
+ * Register description (all registers are 32bit):
+ *
+ * Rx - means register with index x (register address is x*4)
+ *
+ * R0(wr): trigger sel0 (low/high)
+ * R0(rd): n*256 samples (post trigger) captured
+ *
+ * R1(wr): trigger sel1 (level/edge)
+ * R1(rd): current sampled value
+ *
+ * R2(wr): trigger enable mask
+ *
+ * R2(rd): (status register)
+ * b0: 1 - keys entered
+ * b1: 1 - triggered
+ * b3: 1 - capture done
+ *
+ * not configured: B6FF9C97, 12FF9C97, 92FF9C97, 16FF9C97, ...
+ * configured: A5A5A5A0, after enter keys A5A5A5A1
+ *
+ * sel1 (one bit per channel):
+ * 0 - level triggered
+ * 1 - edge triggered
+ *
+ * sel0 (one bit per channel):
+ * 0 - (low level trigger, sel1=0), (falling edge, sel1=1)
+ * 1 - (high level trigger, sel1=0), (raising edge, sel1=1)
+ *
+ * mask (one bit per channel):
+ * 0 - disable trigger on channel n
+ * 1 - enable trigger on channel n
+ *
+ * R3: upload base address or num samples (0x300000)
+ *
+ * R4: pll divisor - 1
+ * 0 - div 1 (no division)
+ * 1 - div 2
+ * 2 - div 3
+ * ...
+ * n-1 - div n
+ *
+ * R5(rd/wr):
+ * b0: 1 - enable pll mul 2, 0 - disable pll mul 2
+ * b1: ??
+ * b2: ??
+ * b3: ??
+ * b4:
+ * b5: ->0->1 upload next data chunk (to pc)
+ * b6: ??
+ * b7: 0 - enable pll mul 1.25, 1 - disable pll mul 1.25
+ * b8: ??
+ *
+ * R6: post trigger depth, value x means (x+1)*256 (samples), min value is 1
+ * R7: pre trigger depth, value y means (y+1)*256 (samples), min value is 1
+ * (x+1)*256 + (y+1)*256 <= 64M
+ *
+ * R9: PWM1 HI (1 width-1)
+ * R10: PWM1 LO (0 width-1)
+ *
+ * R11: PWM2 HI (1 width-1)
+ * R12: PWM2 LO (0 width-1)
+ *
+ * R14:
+ * 1 - start sample?
+ * 0 - upload done?
+ *
+ * R16: rom key 0
+ * R17: rom key 1
+ *
+ * key0 is F6 81 13 64
+ * key1 is 00 00 00 00
+ *
+ * start sample:
+ * r5 <= b2 <= 0
+ * r5 <= b3 <= 0
+ * r5 <= b5 <= 0
+ *
+ * r5 <= b6 <= 1
+ * r5 <= b1 <= 1
+ * r5 <= b1 <= 0
+ *
+ * r5 <= b8 <= 1
+ * r5 <= b8 <= 0
+ *
+ * r5 <= b6 <= 1
+ * r5 <= b2 <= 1
+ *
+ * read back:
+ * r5 <= 0x08 (b3)
+ * r5 <= 0x28 (b5,b3)
+ */
+
+#define BITSTREAM_NAME "sysclk-sla5032.bit"
+#define BITSTREAM_MAX_SIZE (512 * 1024) /* Bitstream size limit for safety */
+#define BITSTREAM_HEADER_SIZE 0x69
+#define FW_CHUNK_SIZE 250
+#define XILINX_SYNC_WORD 0xAA995566
+
+static int la_write_cmd_buf(const struct sr_usb_dev_inst *usb, uint8_t cmd,
+ unsigned int addr, unsigned int len, const void *data)
+{
+ uint8_t *cmd_pkt;
+ int ret, xfer_len;
+ int cmd_len;
+
+ cmd_pkt = g_try_malloc(len + 10);
+ if (!cmd_pkt) {
+ ret = SR_ERR_MALLOC;
+ goto exit;
+ }
+
+ cmd_pkt[0] = cmd;
+ cmd_len = 1;
+ xfer_len = 0;
+
+ switch(cmd) {
+ case CMD_INIT_FW_UPLOAD: /* init firmware upload */
+ break;
+ case CMD_UPLOAD_FW_CHUNK:
+ cmd_pkt[1] = len;
+ cmd_len += 1 + len;
+ memcpy(&cmd_pkt[2], data, len);
+ break;
+ case CMD_READ_REG: /* read register */
+ cmd_pkt[1] = addr;
+ cmd_pkt[2] = len;
+ cmd_len += 2;
+ break;
+ case CMD_WRITE_REG: /* write register */
+ cmd_pkt[1] = addr;
+ cmd_pkt[2] = len;
+ cmd_len += 2 + len;
+ memcpy(&cmd_pkt[3], data, len);
+ break;
+ case CMD_READ_MEM: /* read mem */
+ cmd_pkt[1] = (addr >> 8) & 0xFF;
+ cmd_pkt[2] = addr & 0xFF;
+ cmd_pkt[3] = len;
+ cmd_len += 3;
+ break;
+ case CMD_READ_DATA: /* read samples */
+ cmd_pkt[1] = addr;
+ cmd_len += 1;
+ break;
+ }
+
+ ret = libusb_bulk_transfer(usb->devhdl, EP_COMMAND, cmd_pkt, cmd_len,
+ &xfer_len, USB_CMD_TIMEOUT_MS);
+ if (ret != 0) {
+ sr_dbg("Failed to send command %d: %s.",
+ cmd, libusb_error_name(ret));
+ return SR_ERR;
+ }
+
+ if (xfer_len != cmd_len) {
+ sr_dbg("Invalid send command response of length %d.", xfer_len);
+ return SR_ERR;
+ }
+
+exit:
+ g_free(cmd_pkt);
+ return ret;
+}
+
+static int la_read_reg(const struct sr_usb_dev_inst *usb, unsigned int reg, uint32_t *val)
+{
+ int ret, xfer_len;
+ uint32_t reply;
+
+ ret = la_write_cmd_buf(usb, CMD_READ_REG, reg * sizeof(uint32_t),
+ sizeof(reply), NULL); /* rd reg */
+ if (ret != SR_OK)
+ return ret;
+
+ ret = libusb_bulk_transfer(usb->devhdl, EP_REPLY, (uint8_t *)&reply,
+ sizeof(reply), &xfer_len, USB_REPLY_TIMEOUT_MS);
+ if (ret != SR_OK)
+ return ret;
+
+ if (xfer_len != sizeof(uint32_t)) {
+ sr_dbg("Invalid register read response of length %d.", xfer_len);
+ return SR_ERR;
+ }
+
+ *val = GUINT32_FROM_BE(reply);
+
+ return ret;
+}
+
+static int la_write_reg(const struct sr_usb_dev_inst *usb, unsigned int reg, uint32_t val)
+{
+ uint32_t val_be;
+
+ val_be = GUINT32_TO_BE(val);
+
+ return la_write_cmd_buf(usb, CMD_WRITE_REG, reg * sizeof(uint32_t),
+ sizeof(val_be), &val_be); /* wr reg */
+}
+
+static int la_read_mem(const struct sr_usb_dev_inst *usb, unsigned int addr, unsigned int len, void *data)
+{
+ int ret, xfer_len;
+
+ ret = la_write_cmd_buf(usb, CMD_READ_MEM, addr, len, NULL); /* rd mem */
+ if (ret != SR_OK)
+ return ret;
+
+ xfer_len = 0;
+ ret = libusb_bulk_transfer(usb->devhdl, EP_REPLY, (uint8_t *)data,
+ len, &xfer_len, USB_REPLY_TIMEOUT_MS);
+ if (xfer_len != (int)len) {
+ sr_dbg("Invalid memory read response of length %d.", xfer_len);
+ return SR_ERR;
+ }
+
+ return ret;
+}
+
+static int la_read_samples(const struct sr_usb_dev_inst *usb, unsigned int addr)
+{
+ return la_write_cmd_buf(usb, CMD_READ_DATA, addr, 0, NULL); /* rd samples */
+}
+
+static int sla5032_set_depth(const struct sr_usb_dev_inst *usb, uint32_t pre, uint32_t post)
+{
+ int ret;
+
+ /* (pre + 1)*256 + (post + 1)*256 <= 64*1024*1024 */
+ ret = la_write_reg(usb, 7, pre);
+ if (ret != SR_OK)
+ return ret;
+
+ return la_write_reg(usb, 6, post);
+}
+
+static int sla5032_set_triggers(const struct sr_usb_dev_inst *usb,
+ uint32_t trg_value, uint32_t trg_edge_mask, uint32_t trg_mask)
+{
+ int ret;
+
+ sr_dbg("set trigger: val: %08X, e_mask: %08X, mask: %08X.", trg_value,
+ trg_edge_mask, trg_mask);
+
+ ret = la_write_reg(usb, 0, trg_value);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = la_write_reg(usb, 1, trg_edge_mask);
+ if (ret != SR_OK)
+ return ret;
+
+ return la_write_reg(usb, 2, trg_mask);
+}
+
+static int la_set_res_reg_bit(const struct sr_usb_dev_inst *usb,
+ unsigned int reg, unsigned int bit, unsigned int set_bit)
+{
+ int ret;
+ uint32_t v;
+
+ v = 0;
+ ret = la_read_reg(usb, reg, &v);
+ if (ret != SR_OK)
+ return ret;
+
+ if (set_bit)
+ v |= (1u << bit);
+ else
+ v &= ~(1u << bit);
+
+ return la_write_reg(usb, reg, v);
+}
+
+struct pll_tbl_entry_t
+{
+ unsigned int sr;
+ uint32_t pll_div_minus_1;
+ unsigned int pll_mul_flags;
+};
+
+enum {
+ PLL_MUL2 = 1, /* x2 */
+ PLL_MUL1_25 = 2, /* x1.25 */
+};
+
+static const struct pll_tbl_entry_t pll_tbl[] = {
+ { 500000000, 0, PLL_MUL2 | PLL_MUL1_25 }, /* 500M = f*2*1.25/1 */
+ { 400000000, 0, PLL_MUL2 }, /* 400M = f*2/1 */
+ { 250000000, 0, PLL_MUL1_25 }, /* 250M = f*1.25/1 */
+ { 200000000, 0, 0 }, /* 200M = f/1 */
+ { 100000000, 1, 0 }, /* 100M = f/2 */
+ { 50000000, 3, 0 }, /* 50M = f/4 */
+ { 25000000, 7, 0 }, /* 25M = f/8 */
+ { 20000000, 9, 0 }, /* 20M = f/10 */
+ { 10000000, 19, 0 }, /* 10M = f/20 */
+ { 5000000, 39, 0 }, /* 5M = f/40 */
+ { 2000000, 99, 0 }, /* 2M = f/100 */
+ { 1000000, 199, 0 }, /* 1M = f/200 */
+ { 500000, 399, 0 }, /* 500k = f/400 */
+ { 200000, 999, 0 }, /* 200k = f/1000 */
+ { 100000, 1999, 0 }, /* 100k = f/2000 */
+ { 50000, 3999, 0 }, /* 50k = f/4000 */
+ { 20000, 9999, 0 }, /* 20k = f/10000 */
+ { 10000, 19999, 0 }, /* 10k = f/20000 */
+ { 5000, 39999, 0 }, /* 5k = f/40000 */
+ { 2000, 99999, 0 }, /* 2k = f/100000 */
+};
+
+static int sla5032_set_samplerate(const struct sr_usb_dev_inst *usb, unsigned int sr)
+{
+ int i, ret;
+ const struct pll_tbl_entry_t *e;
+
+ e = NULL;
+ for (i = 0; i < (int)ARRAY_SIZE(pll_tbl); i++) {
+ if (sr == pll_tbl[i].sr) {
+ e = &pll_tbl[i];
+ break;
+ }
+ }
+
+ if (!e)
+ return SR_ERR_SAMPLERATE;
+
+ sr_dbg("set sample rate: %u.", e->sr);
+
+ ret = la_write_reg(usb, 4, e->pll_div_minus_1);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = la_set_res_reg_bit(usb, 5, 0,
+ (e->pll_mul_flags & PLL_MUL2) ? 1 : 0); /* bit0 (1=en_mul2) */
+ if (ret != SR_OK)
+ return ret;
+
+ return la_set_res_reg_bit(usb, 5, 7,
+ (e->pll_mul_flags & PLL_MUL1_25) ? 0 : 1); /* bit7 (0=en_mul_1.25) */
+}
+
+static int sla5032_start_sample(const struct sr_usb_dev_inst *usb)
+{
+ int ret;
+ const unsigned int bits[10][2] = {
+ {2, 0}, {3, 0}, {5, 0}, {6, 1}, {1, 1},
+ {1, 0}, {8, 1}, {8, 0}, {6, 1}, {2, 1},
+ };
+
+ ret = la_write_reg(usb, 14, 1);
+ if (ret != SR_OK)
+ return ret;
+
+ for (size_t i = 0; i < ARRAY_SIZE(bits); i++) {
+ ret = la_set_res_reg_bit(usb, 5, bits[i][0], bits[i][1]);
+ if (ret != SR_OK)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int sla5032_get_status(const struct sr_usb_dev_inst *usb, uint32_t status[3])
+{
+ int ret;
+ uint32_t v;
+
+ ret = la_read_reg(usb, 1, &status[0]);
+ if (ret != SR_OK)
+ return ret;
+
+ status[1] = 1; /* wait trigger */
+
+ ret = la_read_reg(usb, 0, &status[2]);
+ if (ret != SR_OK)
+ return ret;
+
+ v = 0;
+ ret = la_read_reg(usb, 2, &v);
+ if (ret != SR_OK)
+ return ret;
+
+ if (v & 8) {
+ status[1] = 3; /* sample done */
+ sr_dbg("get status, reg2: %08X.", v);
+ } else if (v & 2) {
+ status[1] = 2; /* triggered */
+ }
+
+ return ret;
+}
+
+static int la_read_samples_data(const struct sr_usb_dev_inst *usb, void *buf,
+ unsigned int len, int *xfer_len)
+{
+ return libusb_bulk_transfer(usb->devhdl, EP_DATA, (uint8_t *)buf, len,
+ xfer_len, USB_DATA_TIMEOUT_MS);
+}
+
+static int sla5032_read_data_chunk(const struct sr_usb_dev_inst *usb,
+ void *buf, unsigned int len, int *xfer_len)
+{
+ int ret;
+
+ ret = la_read_samples(usb, 3);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = la_write_reg(usb, 3, 0x300000);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = la_set_res_reg_bit(usb, 5, 4, 0);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = la_set_res_reg_bit(usb, 5, 4, 1);
+ if (ret != SR_OK)
+ return ret;
+
+ return la_read_samples_data(usb, buf, len, xfer_len);
+}
+
+static int sla5032_set_read_back(const struct sr_usb_dev_inst *usb)
+{
+ int ret;
+
+ ret = la_write_reg(usb, 5, 0x08);
+ if (ret != SR_OK)
+ return ret;
+
+ return la_write_reg(usb, 5, 0x28);
+}
+
+static int sla5032_set_pwm1(const struct sr_usb_dev_inst *usb, uint32_t hi, uint32_t lo)
+{
+ int ret;
+
+ ret = la_write_reg(usb, 9, hi);
+ if (ret != SR_OK)
+ return ret;
+
+ return la_write_reg(usb, 10, lo);
+}
+
+static int sla5032_set_pwm2(const struct sr_usb_dev_inst *usb, uint32_t hi, uint32_t lo)
+{
+ int ret;
+
+ ret = la_write_reg(usb, 11, hi);
+ if (ret != SR_OK)
+ return ret;
+
+ return la_write_reg(usb, 12, lo);
+}
+
+static int sla5032_write_reg14_zero(const struct sr_usb_dev_inst *usb)
+{
+ return la_write_reg(usb, 14, 0);
+}
+
+static int la_cfg_fpga_done(const struct sr_usb_dev_inst *usb, unsigned int addr)
+{
+ uint8_t done_key[8];
+ uint32_t k0, k1;
+ unsigned int reg2;
+ int ret;
+
+ memset(done_key, 0, sizeof(done_key));
+
+ ret = la_read_mem(usb, addr, sizeof(done_key), done_key); /* read key from eeprom */
+ if (ret != SR_OK)
+ return ret;
+
+ k0 = RL32(done_key); /* 0x641381F6 */
+ k1 = RL32(done_key + 4); /* 0x00000000 */
+
+ sr_dbg("cfg fpga done, k0: %08X, k1: %08X.", k0, k1);
+
+ ret = la_write_reg(usb, 16, k0);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = la_write_reg(usb, 17, k1);
+ if (ret != SR_OK)
+ return ret;
+
+ reg2 = 0;
+ ret = la_read_reg(usb, 2, ®2);
+
+ sr_dbg("cfg fpga done, reg2: %08X.", reg2);
+
+ return ret;
+}
+
+/*
+ * Load a bitstream file into memory. Returns a newly allocated array
+ * consisting of a 32-bit length field followed by the bitstream data.
+ */
+static unsigned char *load_bitstream(struct sr_context *ctx,
+ const char *name, int *length_p)
+{
+ struct sr_resource fw;
+ unsigned char *stream, *fw_data;
+ ssize_t length, count;
+
+ if (sr_resource_open(ctx, &fw, SR_RESOURCE_FIRMWARE, name) != SR_OK)
+ return NULL;
+
+ if (fw.size <= BITSTREAM_HEADER_SIZE || fw.size > BITSTREAM_MAX_SIZE) {
+ sr_err("Refusing to load bitstream of unreasonable size "
+ "(%" PRIu64 " bytes).", fw.size);
+ sr_resource_close(ctx, &fw);
+ return NULL;
+ }
+
+ stream = g_try_malloc(fw.size);
+ if (!stream) {
+ sr_err("Failed to allocate bitstream buffer.");
+ sr_resource_close(ctx, &fw);
+ return NULL;
+ }
+
+ count = sr_resource_read(ctx, &fw, stream, fw.size);
+ sr_resource_close(ctx, &fw);
+
+ if (count != (ssize_t)fw.size) {
+ sr_err("Failed to read bitstream '%s'.", name);
+ g_free(stream);
+ return NULL;
+ }
+
+ if (RB32(stream + BITSTREAM_HEADER_SIZE) != XILINX_SYNC_WORD) {
+ sr_err("Invalid bitstream signature.");
+ g_free(stream);
+ return NULL;
+ }
+
+ length = fw.size - BITSTREAM_HEADER_SIZE + 0x100;
+ fw_data = g_try_malloc(length);
+ if (!fw_data) {
+ sr_err("Failed to allocate bitstream aligned buffer.");
+ return NULL;
+ }
+
+ memset(fw_data, 0xFF, 0x100);
+ memcpy(fw_data + 0x100, stream + BITSTREAM_HEADER_SIZE,
+ fw.size - BITSTREAM_HEADER_SIZE);
+ g_free(stream);
+
+ *length_p = length;
+
+ return fw_data;
+}
+
+static int sla5032_is_configured(const struct sr_usb_dev_inst *usb, gboolean *is_configured)
+{
+ int ret;
+ uint32_t reg2;
+
+ reg2 = 0;
+ ret = la_read_reg(usb, 2, ®2);
+ if (ret == SR_OK)
+ *is_configured = (reg2 & 0xFFFFFFF1) == 0xA5A5A5A1 ? TRUE : FALSE;
+
+ return ret;
+}
+
+/* Load a Binary File from the firmware directory, transfer it to the device. */
+static int sla5032_send_bitstream(struct sr_context *ctx,
+ const struct sr_usb_dev_inst *usb, const char *name)
+{
+ unsigned char *stream;
+ int ret, length, i, n, m;
+ uint32_t reg2;
+
+ if (!ctx || !usb || !name)
+ return SR_ERR_BUG;
+
+ stream = load_bitstream(ctx, name, &length);
+ if (!stream)
+ return SR_ERR;
+
+ sr_dbg("Downloading FPGA bitstream '%s'.", name);
+
+ reg2 = 0;
+ ret = la_read_reg(usb, 2, ®2);
+ sr_dbg("send bitstream, reg2: %08X.", reg2);
+
+ /* Transfer the entire bitstream in one URB. */
+ ret = la_write_cmd_buf(usb, CMD_INIT_FW_UPLOAD, 0, 0, NULL); /* init firmware upload */
+ if (ret != SR_OK) {
+ g_free(stream);
+ return ret;
+ }
+
+ n = length / FW_CHUNK_SIZE;
+ m = length % FW_CHUNK_SIZE;
+
+ for (i = 0; i < n; i++) {
+ /* upload firmware chunk */
+ ret = la_write_cmd_buf(usb, CMD_UPLOAD_FW_CHUNK, 0,
+ FW_CHUNK_SIZE, &stream[i * FW_CHUNK_SIZE]);
+
+ if (ret != SR_OK) {
+ g_free(stream);
+ return ret;
+ }
+ }
+
+ if (m != 0) {
+ /* upload firmware last chunk */
+ ret = la_write_cmd_buf(usb, CMD_UPLOAD_FW_CHUNK, 0, m,
+ &stream[n * FW_CHUNK_SIZE]);
+
+ if (ret != SR_OK) {
+ g_free(stream);
+ return ret;
+ }
+ }
+
+ g_free(stream);
+
+ la_cfg_fpga_done(usb, 4000);
+
+ sla5032_write_reg14_zero(usb);
+
+ sr_dbg("FPGA bitstream download of %d bytes done.", length);
+
+ return SR_OK;
+}
+
+/* Select and transfer FPGA bitstream for the current configuration. */
+SR_PRIV int sla5032_apply_fpga_config(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct drv_context *drvc;
+ int ret;
+ gboolean is_configured;
+
+ devc = sdi->priv;
+ drvc = sdi->driver->context;
+
+ if (FPGA_NOCONF != devc->active_fpga_config)
+ return SR_OK; /* No change. */
+
+ is_configured = FALSE;
+ ret = sla5032_is_configured(sdi->conn, &is_configured);
+ if (ret != SR_OK)
+ return ret;
+
+ if (is_configured) {
+ devc->active_fpga_config = FPGA_CONF;
+ return ret;
+ }
+
+ sr_dbg("FPGA not configured, send bitstream.");
+ ret = sla5032_send_bitstream(drvc->sr_ctx, sdi->conn, BITSTREAM_NAME);
+ devc->active_fpga_config = (ret == SR_OK) ? FPGA_CONF : FPGA_NOCONF;
+
+ return ret;
+}
+
+/* Callback handling data */
+static int la_prepare_data(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ struct sr_usb_dev_inst *usb;
+ int i, j, ret, xfer_len;
+ uint8_t *rle_buf, *samples;
+ const uint8_t *p, *q;
+ uint16_t rle_count;
+ int samples_count, rle_samples_count;
+ uint32_t status[3];
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_logic logic;
+ uint32_t value;
+ int trigger_offset;
+
+ enum {
+ RLE_SAMPLE_SIZE = sizeof(uint32_t) + sizeof(uint16_t),
+ RLE_SAMPLES_COUNT = 0x100000,
+ RLE_BUF_SIZE = RLE_SAMPLES_COUNT * RLE_SAMPLE_SIZE,
+ RLE_END_MARKER = 0xFFFF,
+ };
+
+ (void)fd;
+ (void)revents;
+
+ sdi = cb_data;
+ devc = sdi->priv;
+ usb = sdi->conn;
+
+ memset(status, 0, sizeof(status));
+ ret = sla5032_get_status(usb, status);
+ if (ret != SR_OK) {
+ sla5032_write_reg14_zero(usb);
+ sr_dev_acquisition_stop(sdi);
+ return G_SOURCE_CONTINUE;
+ }
+
+ /* data not ready (acquision in progress) */
+ if (status[1] != 3)
+ return G_SOURCE_CONTINUE;
+
+ sr_dbg("acquision done, status: %u.", (unsigned int)status[2]);
+
+ /* data ready (download, decode and send to sigrok) */
+ ret = sla5032_set_read_back(usb);
+ if (ret != SR_OK) {
+ sla5032_write_reg14_zero(usb);
+ sr_dev_acquisition_stop(sdi);
+ return G_SOURCE_CONTINUE;
+ }
+
+ rle_buf = g_try_malloc(RLE_BUF_SIZE);
+ if (rle_buf == NULL) {
+ sla5032_write_reg14_zero(usb);
+ sr_dev_acquisition_stop(sdi);
+ return G_SOURCE_CONTINUE;
+ }
+
+ do {
+ xfer_len = 0;
+ ret = sla5032_read_data_chunk(usb, rle_buf, RLE_BUF_SIZE, &xfer_len);
+ if (ret != SR_OK) {
+ sla5032_write_reg14_zero(usb);
+ g_free(rle_buf);
+ sr_dev_acquisition_stop(sdi);
+
+ sr_dbg("acquision done, ret: %d.", ret);
+ return G_SOURCE_CONTINUE;
+ }
+
+ sr_dbg("acquision done, xfer_len: %d.", xfer_len);
+
+ if (xfer_len == 0) {
+ sla5032_write_reg14_zero(usb);
+ g_free(rle_buf);
+ sr_dev_acquisition_stop(sdi);
+ return G_SOURCE_CONTINUE;
+ }
+
+ p = rle_buf;
+ samples_count = 0;
+ rle_samples_count = xfer_len / RLE_SAMPLE_SIZE;
+
+ sr_dbg("acquision done, rle_samples_count: %d.", rle_samples_count);
+
+ for (i = 0; i < rle_samples_count; i++) {
+ p += sizeof(uint32_t); /* skip sample value */
+
+ rle_count = RL16(p); /* read RLE counter */
+ p += sizeof(uint16_t);
+ if (rle_count == RLE_END_MARKER) {
+ rle_samples_count = i;
+ break;
+ }
+ samples_count += rle_count + 1;
+ }
+ sr_dbg("acquision done, samples_count: %d.", samples_count);
+
+ if (samples_count == 0) {
+ sr_dbg("acquision done, no samples.");
+ sla5032_write_reg14_zero(usb);
+ g_free(rle_buf);
+ sr_dev_acquisition_stop(sdi);
+ return G_SOURCE_CONTINUE;
+ }
+
+ /* Decode RLE */
+ samples = g_try_malloc(samples_count * sizeof(uint32_t));
+ if (!samples) {
+ sr_dbg("memory allocation error.");
+ sla5032_write_reg14_zero(usb);
+ g_free(rle_buf);
+ sr_dev_acquisition_stop(sdi);
+ return G_SOURCE_CONTINUE;
+ }
+
+ p = rle_buf;
+ q = samples;
+ for (i = 0; i < rle_samples_count; i++) {
+ value = RL32(p);
+ p += sizeof(uint32_t); /* read sample value */
+
+ rle_count = RL16(p); /* read RLE counter */
+ p += sizeof(uint16_t);
+
+ if (rle_count == RLE_END_MARKER) {
+ sr_dbg("RLE end marker found.");
+ break;
+ }
+
+ for (j = 0; j <= rle_count; j++) {
+ WL32(q, value);
+ q += sizeof(uint32_t);
+ }
+ }
+
+ if (devc->trigger_fired) {
+ /* Send the incoming transfer to the session bus. */
+ packet.type = SR_DF_LOGIC;
+ packet.payload = &logic;
+
+ logic.length = samples_count * sizeof(uint32_t);
+ logic.unitsize = sizeof(uint32_t);
+ logic.data = samples;
+ sr_session_send(sdi, &packet);
+ } else {
+ trigger_offset = soft_trigger_logic_check(devc->stl,
+ samples, samples_count * sizeof(uint32_t), NULL);
+ if (trigger_offset > -1) {
+ packet.type = SR_DF_LOGIC;
+ packet.payload = &logic;
+ int num_samples = samples_count - trigger_offset;
+
+ logic.length = num_samples * sizeof(uint32_t);
+ logic.unitsize = sizeof(uint32_t);
+ logic.data = samples + trigger_offset * sizeof(uint32_t);
+ sr_session_send(sdi, &packet);
+
+ devc->trigger_fired = TRUE;
+ }
+ }
+
+ g_free(samples);
+ } while (rle_samples_count == RLE_SAMPLES_COUNT);
+
+ sr_dbg("acquision stop, rle_samples_count < RLE_SAMPLES_COUNT.");
+
+ sla5032_write_reg14_zero(usb);
+
+ sr_dev_acquisition_stop(sdi); /* if all data transfered */
+
+ g_free(rle_buf);
+
+ if (devc->stl) {
+ soft_trigger_logic_free(devc->stl);
+ devc->stl = NULL;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+SR_PRIV int sla5032_start_acquisition(const struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct sr_usb_dev_inst *usb;
+ struct sr_trigger *trigger;
+ int ret;
+ enum { poll_interval_ms = 100 };
+ uint64_t pre, post;
+
+ devc = sdi->priv;
+ usb = sdi->conn;
+
+ if (devc->state != STATE_IDLE) {
+ sr_err("Not in idle state, cannot start acquisition.");
+ return SR_ERR;
+ }
+
+ pre = (devc->limit_samples * devc->capture_ratio) / 100;
+ post = devc->limit_samples - pre;
+
+ if ((trigger = sr_session_trigger_get(sdi->session))) {
+ devc->stl = soft_trigger_logic_new(sdi, trigger, pre);
+ if (!devc->stl) {
+ sr_err("stl alloc error.");
+ return SR_ERR_MALLOC;
+ }
+ devc->trigger_fired = FALSE;
+ }
+ else
+ devc->trigger_fired = TRUE;
+
+ sr_dbg("start acquision, smp lim: %" PRIu64 ", cap ratio: %" PRIu64
+ ".", devc->limit_samples, devc->capture_ratio);
+
+ sr_dbg("start acquision, pre: %" PRIu64 ", post: %" PRIu64 ".", pre, post);
+ pre /= 256;
+ pre = MAX(pre, 2);
+ pre--;
+
+ post /= 256;
+ post = MAX(post, 2);
+ post--;
+
+ sr_dbg("start acquision, pre: %" PRIx64 ", post: %" PRIx64 ".", pre, post);
+
+ /* (x + 1) * 256 (samples) pre, post */
+ ret = sla5032_set_depth(usb, pre, post);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = sla5032_set_triggers(usb, devc->trigger_values, devc->trigger_edge_mask, devc->trigger_mask);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = sla5032_set_samplerate(usb, devc->samplerate);
+ if (ret != SR_OK)
+ return ret;
+
+ /* TODO: make PWM generator as separate configurable subdevice */
+ enum {
+ pwm1_hi = 20000000 - 1,
+ pwm1_lo = 200000 - 1,
+ pwm2_hi = 15 - 1,
+ pwm2_lo = 5 - 1,
+ };
+
+ ret = sla5032_set_pwm1(usb, pwm1_hi, pwm1_lo);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = sla5032_set_pwm2(usb, pwm2_hi, pwm2_lo);
+ if (ret != SR_OK)
+ return ret;
+
+ ret = sla5032_start_sample(usb);
+ if (ret != SR_OK)
+ return ret;
+
+ sr_session_source_add(sdi->session, -1, 0, poll_interval_ms,
+ la_prepare_data, (struct sr_dev_inst *)sdi);
+
+ std_session_send_df_header(sdi);
+
+ return ret;
+}
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Vitaliy Vorobyov
+ *
+ * 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/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_SYSCLK_SLA5032_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_SYSCLK_SLA5032_PROTOCOL_H
+
+#include <stdint.h>
+#include <libusb.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include <libsigrok-internal.h>
+
+#define LOG_PREFIX "sysclk-sla5032"
+
+/* Maximum configurable sample count limit. */
+#define MAX_LIMIT_SAMPLES (64 * 1024 * 1024)
+#define MIN_LIMIT_SAMPLES 512
+
+/* USB vendor and product IDs. */
+enum {
+ USB_VID_SYSCLK = 0x2961,
+ USB_PID_SLA5032 = 0x66B0,
+};
+
+/* USB device characteristics. */
+enum {
+ USB_CONFIG = 1,
+ USB_INTERFACE = 0,
+ USB_CMD_TIMEOUT_MS = 5000,
+ USB_REPLY_TIMEOUT_MS = 500000,
+ USB_DATA_TIMEOUT_MS = 2000,
+};
+
+/* USB device end points. */
+enum usb_endpoint {
+ EP_COMMAND = 4 | LIBUSB_ENDPOINT_OUT,
+ EP_REPLY = 8 | LIBUSB_ENDPOINT_IN,
+ EP_DATA = 6 | LIBUSB_ENDPOINT_IN,
+};
+
+
+/* Common indicator for no or unknown FPGA config. */
+enum {
+ FPGA_NOCONF = -1,
+ FPGA_CONF,
+};
+
+/** Acquisition protocol states. */
+enum protocol_state {
+ /* idle states */
+ STATE_IDLE = 0,
+ STATE_STATUS_WAIT,
+ /* device command states */
+ STATE_START_CAPTURE,
+ STATE_STOP_CAPTURE,
+ STATE_READ_PREPARE,
+ STATE_READ_FINISH,
+ /* command followed by response */
+ STATE_EXPECT_RESPONSE = 1 << 3,
+ STATE_STATUS_REQUEST = STATE_EXPECT_RESPONSE,
+ STATE_LENGTH_REQUEST,
+ STATE_READ_REQUEST,
+};
+
+/** SLA5032 protocol command ID codes. */
+enum command_id {
+ CMD_INIT_FW_UPLOAD = 1,
+ CMD_UPLOAD_FW_CHUNK = 2,
+ CMD_READ_REG = 3,
+ CMD_WRITE_REG = 4,
+ CMD_READ_MEM = 5,
+ CMD_READ_DATA = 7,
+};
+
+struct dev_context {
+ uint64_t samplerate; /* requested samplerate */
+ uint64_t limit_samples; /* requested capture length (samples) */
+ uint64_t capture_ratio;
+
+ uint64_t channel_mask; /* bit mask of enabled channels */
+ uint64_t trigger_mask; /* trigger enable mask */
+ uint64_t trigger_edge_mask; /* trigger type mask */
+ uint64_t trigger_values; /* trigger level/slope bits */
+
+ struct soft_trigger_logic *stl;
+ gboolean trigger_fired;
+
+ int active_fpga_config; /* FPGA configuration index */
+
+ enum protocol_state state; /* async protocol state */
+};
+
+SR_PRIV int sla5032_start_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV int sla5032_apply_fpga_config(const struct sr_dev_inst *sdi);
+
+#endif
/* Let's get a bit of data and see if we can find a packet. */
if (serial_stream_detect(serial, buf, &len, len,
- teleinfo_packet_valid, 3000, 1200) != SR_OK)
+ teleinfo_packet_valid, 3000) != SR_OK)
goto scan_cleanup;
sr_info("Found device on port %s.", conn);
static const uint32_t scanopts[] = {
SR_CONF_CONN,
+ SR_CONF_SERIALCOMM,
};
static const uint32_t drvopts[] = {
static const uint32_t devopts[] = {
SR_CONF_CONTINUOUS,
SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+ SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
SR_CONF_DATA_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};
+/*
+ * BEWARE! "T1-T2" looks like a range, and is probably not a good
+ * channel name. Using it in sigrok-cli -C specs is troublesome. Use
+ * "delta" instead? -- But OTOH channels are not selected by the
+ * software. Instead received packets just reflect the one channel
+ * that manually was selected by the user via the device's buttons.
+ * So the name is not a blocker, and it matches the labels on the
+ * device and in the manual. So we can get away with it.
+ */
static const char *channel_names[] = {
"T1", "T2", "T1-T2",
};
static GSList *scan(struct sr_dev_driver *di, GSList *options)
{
- struct drv_context *drvc;
- struct dev_context *devc;
- struct sr_dev_inst *sdi;
+ const char *conn, *serialcomm;
struct sr_config *src;
- GSList *usb_devices, *devices, *l;
- unsigned int i;
- const char *conn;
-
- drvc = di->context;
-
+ GSList *l, *devices;
+ struct sr_serial_dev_inst *serial;
+ int rc;
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ size_t i;
+
+ /*
+ * Implementor's note: Do _not_ add a default conn value here,
+ * always expect users to specify the connection. Otherwise the
+ * UT32x driver's scan routine results in false positives, will
+ * match _any_ UT-D04 cable which uses the same USB HID chip.
+ */
conn = NULL;
+ serialcomm = "2400/8n1";
for (l = options; l; l = l->next) {
src = l->data;
switch (src->key) {
case SR_CONF_CONN:
conn = g_variant_get_string(src->data, NULL);
break;
+ case SR_CONF_SERIALCOMM:
+ serialcomm = g_variant_get_string(src->data, NULL);
+ break;
}
}
if (!conn)
return NULL;
devices = NULL;
- if ((usb_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn))) {
- /* We have a list of sr_usb_dev_inst matching the connection
- * string. Wrap them in sr_dev_inst and we're done. */
- for (l = usb_devices; l; l = l->next) {
- sdi = g_malloc0(sizeof(struct sr_dev_inst));
- sdi->status = SR_ST_INACTIVE;
- sdi->vendor = g_strdup("UNI-T");
- sdi->model = g_strdup("UT32x");
- sdi->inst_type = SR_INST_USB;
- sdi->conn = l->data;
- for (i = 0; i < ARRAY_SIZE(channel_names); i++)
- sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE,
- channel_names[i]);
- devc = g_malloc0(sizeof(struct dev_context));
- sdi->priv = devc;
- devc->limit_samples = 0;
- devc->data_source = DEFAULT_DATA_SOURCE;
- devices = g_slist_append(devices, sdi);
- }
- g_slist_free(usb_devices);
- } else
- g_slist_free_full(usb_devices, g_free);
-
- return std_scan_complete(di, devices);
-}
-
-static int dev_open(struct sr_dev_inst *sdi)
-{
- struct sr_dev_driver *di = sdi->driver;
- struct drv_context *drvc = di->context;
- struct sr_usb_dev_inst *usb;
- int ret;
-
- usb = sdi->conn;
-
- if (sr_usb_open(drvc->sr_ctx->libusb_ctx, usb) != SR_OK)
- return SR_ERR;
-
-/*
- * The libusb 1.0.9 Darwin backend is broken: it can report a kernel
- * driver being active, but detaching it always returns an error.
- */
-#if !defined(__APPLE__)
- if (libusb_kernel_driver_active(usb->devhdl, USB_INTERFACE) == 1) {
- if ((ret = libusb_detach_kernel_driver(usb->devhdl, USB_INTERFACE)) < 0) {
- sr_err("failed to detach kernel driver: %s",
- libusb_error_name(ret));
- return SR_ERR;
- }
+ serial = sr_serial_dev_inst_new(conn, serialcomm);
+ rc = serial_open(serial, SERIAL_RDWR);
+ serial_flush(serial);
+ /* Cannot query/identify the device. Successful open shall suffice. */
+ serial_close(serial);
+ if (rc != SR_OK) {
+ sr_serial_dev_inst_free(serial);
+ return devices;
}
-#endif
- if ((ret = libusb_set_configuration(usb->devhdl, USB_CONFIGURATION))) {
- sr_err("Failed to set configuration: %s.", libusb_error_name(ret));
- return SR_ERR;
+ sdi = g_malloc0(sizeof(*sdi));
+ sdi->status = SR_ST_INACTIVE;
+ sdi->vendor = g_strdup("UNI-T");
+ sdi->model = g_strdup("UT32x");
+ sdi->inst_type = SR_INST_SERIAL;
+ sdi->conn = serial;
+ devc = g_malloc0(sizeof(*devc));
+ sdi->priv = devc;
+ sr_sw_limits_init(&devc->limits);
+ devc->data_source = DEFAULT_DATA_SOURCE;
+ for (i = 0; i < ARRAY_SIZE(channel_names); i++) {
+ sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE,
+ channel_names[i]);
}
+ devices = g_slist_append(devices, sdi);
- if ((ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE))) {
- sr_err("Failed to claim interface: %s.", libusb_error_name(ret));
- return SR_ERR;
- }
-
- return SR_OK;
-}
-
-static int dev_close(struct sr_dev_inst *sdi)
-{
- struct sr_usb_dev_inst *usb;
+ serial_close(serial);
+ if (!devices)
+ sr_serial_dev_inst_free(serial);
- usb = sdi->conn;
-
- if (!usb->devhdl)
- return SR_ERR_BUG;
-
- libusb_release_interface(usb->devhdl, USB_INTERFACE);
- libusb_close(usb->devhdl);
- usb->devhdl = NULL;
-
- return SR_OK;
+ return std_scan_complete(di, devices);
}
static int config_get(uint32_t key, GVariant **data,
devc = sdi->priv;
switch (key) {
case SR_CONF_LIMIT_SAMPLES:
- *data = g_variant_new_uint64(devc->limit_samples);
- break;
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_get(&devc->limits, key, data);
case SR_CONF_DATA_SOURCE:
- if (devc->data_source == DATA_SOURCE_LIVE)
- *data = g_variant_new_string("Live");
- else
- *data = g_variant_new_string("Memory");
+ *data = g_variant_new_string(data_sources[devc->data_source]);
break;
default:
return SR_ERR_NA;
switch (key) {
case SR_CONF_LIMIT_SAMPLES:
- devc->limit_samples = g_variant_get_uint64(data);
- break;
+ case SR_CONF_LIMIT_MSEC:
+ return sr_sw_limits_config_set(&devc->limits, key, data);
case SR_CONF_DATA_SOURCE:
if ((idx = std_str_idx(data, ARRAY_AND_SIZE(data_sources))) < 0)
return SR_ERR_ARG;
static int dev_acquisition_start(const struct sr_dev_inst *sdi)
{
- struct sr_dev_driver *di = sdi->driver;
- struct drv_context *drvc;
struct dev_context *devc;
- struct sr_usb_dev_inst *usb;
- int len, ret;
- unsigned char cmd[2];
+ struct sr_serial_dev_inst *serial;
+ uint8_t cmd;
- drvc = di->context;
devc = sdi->priv;
- usb = sdi->conn;
+ serial = sdi->conn;
- devc->num_samples = 0;
+ sr_sw_limits_acquisition_start(&devc->limits);
devc->packet_len = 0;
-
- /* Configure serial port parameters on USB-UART interface
- * chip inside the device (just baudrate 2400 actually). */
- cmd[0] = 0x09;
- cmd[1] = 0x60;
- ret = libusb_control_transfer(usb->devhdl, 0x21, 0x09, 0x0300, 0x00,
- cmd, 2, 5);
- if (ret != 2) {
- sr_dbg("Failed to configure CH9325: %s", libusb_error_name(ret));
- return SR_ERR;
- }
-
std_session_send_df_header(sdi);
- if (!(devc->xfer = libusb_alloc_transfer(0)))
- return SR_ERR;
-
- /* Length of payload to follow. */
- cmd[0] = 0x01;
if (devc->data_source == DATA_SOURCE_LIVE)
- cmd[1] = CMD_GET_LIVE;
+ cmd = CMD_GET_LIVE;
else
- cmd[1] = CMD_GET_STORED;
-
- ret = libusb_bulk_transfer(usb->devhdl, EP_OUT, cmd, 2, &len, 5);
- if (ret != 0 || len != 2) {
- sr_dbg("Failed to start acquisition: %s", libusb_error_name(ret));
- libusb_free_transfer(devc->xfer);
- return SR_ERR;
- }
-
- libusb_fill_bulk_transfer(devc->xfer, usb->devhdl, EP_IN, devc->buf,
- 8, uni_t_ut32x_receive_transfer, (void *)sdi, 15);
- if (libusb_submit_transfer(devc->xfer) != 0) {
- libusb_free_transfer(devc->xfer);
- return SR_ERR;
- }
+ cmd = CMD_GET_STORED;
+ serial_write_blocking(serial, &cmd, sizeof(cmd), 0);
- usb_source_add(sdi->session, drvc->sr_ctx, 10,
- uni_t_ut32x_handle_events, (void *)sdi);
+ serial_source_add(sdi->session, serial, G_IO_IN, 10,
+ ut32x_handle_events, (void *)sdi);
return SR_OK;
}
static int dev_acquisition_stop(struct sr_dev_inst *sdi)
{
- /* Signal USB transfer handler to clean up and stop. */
+ /* Have the reception routine stop the acquisition. */
sdi->status = SR_ST_STOPPING;
return SR_OK;
.config_get = config_get,
.config_set = config_set,
.config_list = config_list,
- .dev_open = dev_open,
- .dev_close = dev_close,
+ .dev_open = std_serial_dev_open,
+ .dev_close = std_serial_dev_close,
.dev_acquisition_start = dev_acquisition_start,
.dev_acquisition_stop = dev_acquisition_stop,
.context = NULL,
#include <math.h>
#include "protocol.h"
+#define SEP "\r\n"
+#define BLANK ':'
+#define NEG ';'
+
+/*
+ * Get a temperature value from a four-character buffer. The value is
+ * encoded in ASCII and the unit is deci-degrees (tenths of degrees).
+ */
static float parse_temperature(unsigned char *buf)
{
float temp;
negative = FALSE;
temp = 0.0;
for (i = 0; i < 4; i++) {
- if (buf[i] == 0x3a)
+ if (buf[i] == BLANK)
continue;
- if (buf[i] == 0x3b) {
+ if (buf[i] == NEG) {
if (negative) {
sr_dbg("Double negative sign!");
return NAN;
- } else {
- negative = TRUE;
- continue;
}
+ negative = TRUE;
+ continue;
}
- if (buf[i] < 0x30 || buf[i] > 0x39) {
+ if (buf[i] < '0' || buf[i] > '9') {
sr_dbg("Invalid digit '%.2x'!", buf[i]);
return NAN;
}
temp *= 10;
- temp += (buf[i] - 0x30);
+ temp += buf[i] - '0';
}
temp /= 10;
if (negative)
return temp;
}
-static void process_packet(struct sr_dev_inst *sdi)
+static void process_packet(struct sr_dev_inst *sdi, uint8_t *pkt, size_t len)
{
struct dev_context *devc;
struct sr_datafeed_packet packet;
struct sr_analog_spec spec;
GString *spew;
float temp;
- int i;
gboolean is_valid;
- devc = sdi->priv;
- sr_dbg("Received full 19-byte packet.");
if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
- spew = g_string_sized_new(60);
- for (i = 0; i < devc->packet_len; i++)
- g_string_append_printf(spew, "%.2x ", devc->packet[i]);
- sr_spew("%s", spew->str);
- g_string_free(spew, TRUE);
+ spew = sr_hexdump_new(pkt, len);
+ sr_spew("Got a packet, len %zu, bytes%s", len, spew->str);
+ sr_hexdump_free(spew);
}
+ if (len != PACKET_SIZE)
+ return;
+ if (pkt[17] != SEP[0] || pkt[18] != SEP[1])
+ return;
+ if (pkt[8] != '0' || pkt[16] != '1')
+ return;
+ sr_dbg("Processing 19-byte packet.");
is_valid = TRUE;
- if (devc->packet[1] == 0x3b && devc->packet[2] == 0x3b
- && devc->packet[3] == 0x3b && devc->packet[4] == 0x3b)
+ if (pkt[1] == NEG && pkt[2] == NEG && pkt[3] == NEG && pkt[4] == NEG)
/* No measurement: missing channel, empty storage location, ... */
is_valid = FALSE;
- temp = parse_temperature(devc->packet + 1);
+ temp = parse_temperature(&pkt[1]);
if (isnan(temp))
is_valid = FALSE;
if (is_valid) {
+ memset(&packet, 0, sizeof(packet));
sr_analog_init(&analog, &encoding, &meaning, &spec, 1);
analog.meaning->mq = SR_MQ_TEMPERATURE;
analog.meaning->mqflags = 0;
- switch (devc->packet[5] - 0x30) {
+ switch (pkt[5] - '0') {
case 1:
analog.meaning->unit = SR_UNIT_CELSIUS;
break;
break;
default:
/* We can still pass on the measurement, whatever it is. */
- sr_dbg("Unknown unit 0x%.2x.", devc->packet[5]);
+ sr_dbg("Unknown unit 0x%.2x.", pkt[5]);
}
- switch (devc->packet[13] - 0x30) {
+ switch (pkt[13] - '0') {
case 0:
/* Channel T1. */
analog.meaning->channels = g_slist_append(NULL, g_slist_nth_data(sdi->channels, 0));
analog.meaning->mqflags |= SR_MQFLAG_RELATIVE;
break;
default:
- sr_err("Unknown channel 0x%.2x.", devc->packet[13]);
+ sr_err("Unknown channel 0x%.2x.", pkt[13]);
is_valid = FALSE;
}
if (is_valid) {
}
}
- /* We count packets even if the temperature was invalid. This way
- * a sample limit on "Memory" data source still works: unused
- * memory slots come through as "----" measurements. */
- devc->num_samples++;
- if (devc->limit_samples && devc->num_samples >= devc->limit_samples)
+ /*
+ * We count packets even if the measurement was invalid. This way
+ * a sample limit on "Memory" data source still works: Unused
+ * memory slots come through as "----" measurements.
+ */
+ devc = sdi->priv;
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
+ if (sr_sw_limits_check(&devc->limits))
sr_dev_acquisition_stop(sdi);
}
-SR_PRIV void LIBUSB_CALL uni_t_ut32x_receive_transfer(struct libusb_transfer *transfer)
+static int process_buffer(struct sr_dev_inst *sdi)
{
struct dev_context *devc;
- struct sr_dev_inst *sdi;
- int hid_payload_len, ret;
+ uint8_t *pkt;
+ size_t remain, idx;
- sdi = transfer->user_data;
+ /*
+ * Specifically do not insist on finding the packet boundary at
+ * the end of the most recently received data chunk. Serial
+ * ports might involve hardware buffers (FIFO). We want to sync
+ * as fast as possible.
+ *
+ * Handle the synchronized situation first. Process complete
+ * packets that reside at the start of the buffer. Then fallback
+ * to incomplete or unaligned packets if the receive buffer
+ * still contains data bytes. (Depending on the bitrate and the
+ * poll interval, we may always end up in the manual search. But
+ * considering the update rate - two or three packets per second
+ * - this is not an issue.)
+ */
devc = sdi->priv;
- if (transfer->actual_length == 8) {
- /* CH9325 encodes length in low nibble of first byte, with
- * bytes 1-7 being the (padded) payload. */
- hid_payload_len = transfer->buffer[0] & 0x0f;
- memcpy(devc->packet + devc->packet_len, transfer->buffer + 1,
- hid_payload_len);
- devc->packet_len += hid_payload_len;
- if (devc->packet_len >= 2
- && devc->packet[devc->packet_len - 2] == 0x0d
- && devc->packet[devc->packet_len - 1] == 0x0a) {
- /* Got end of packet, but do we have a complete packet? */
- if (devc->packet_len == 19)
- process_packet(sdi);
- /* Either way, done with it. */
- devc->packet_len = 0;
- } else if (devc->packet_len > 19) {
- /* Guard against garbage from the device overrunning
- * our packet buffer. */
- sr_dbg("Buffer overrun!");
- devc->packet_len = 0;
- }
+ pkt = &devc->packet[0];
+ while (devc->packet_len >= PACKET_SIZE &&
+ pkt[PACKET_SIZE - 2] == SEP[0] &&
+ pkt[PACKET_SIZE - 1] == SEP[1]) {
+ process_packet(sdi, &pkt[0], PACKET_SIZE);
+ remain = devc->packet_len - PACKET_SIZE;
+ if (remain)
+ memmove(&pkt[0], &pkt[PACKET_SIZE], remain);
+ devc->packet_len -= PACKET_SIZE;
}
- /* Get the next transfer (unless we're shutting down). */
- if (sdi->status != SR_ST_STOPPING) {
- if ((ret = libusb_submit_transfer(devc->xfer)) != 0) {
- sr_dbg("Failed to resubmit transfer: %s", libusb_error_name(ret));
- sdi->status = SR_ST_STOPPING;
- libusb_free_transfer(devc->xfer);
- }
- } else
- libusb_free_transfer(devc->xfer);
+ /*
+ * The 'for' loop and the increment upon re-iteration after
+ * setting the loop var to zero is not an issue. The marker has
+ * two bytes, so effectively starting the search at offset 1 is
+ * fine for the specific packet layout.
+ */
+ for (idx = 0; idx < devc->packet_len; idx++) {
+ if (idx < 1)
+ continue;
+ if (pkt[idx - 1] != SEP[0] || pkt[idx] != SEP[1])
+ continue;
+ /* Found a packet that spans up to and including 'idx'. */
+ idx++;
+ process_packet(sdi, &pkt[0], idx);
+ remain = devc->packet_len - idx;
+ if (remain)
+ memmove(&pkt[0], &pkt[idx], remain);
+ devc->packet_len -= idx;
+ idx = 0;
+ }
+ return 0;
}
-SR_PRIV int uni_t_ut32x_handle_events(int fd, int revents, void *cb_data)
+/* Gets invoked when RX data is available. */
+static int ut32x_receive_data(struct sr_dev_inst *sdi)
{
- struct drv_context *drvc;
struct dev_context *devc;
- struct sr_dev_driver *di;
+ struct sr_serial_dev_inst *serial;
+ size_t len;
+
+ devc = sdi->priv;
+ serial = sdi->conn;
+
+ /*
+ * Discard receive data when the buffer is exhausted. This shall
+ * allow to (re-)synchronize to the data stream when we find it
+ * in an arbitrary state. Drain more data from the serial port,
+ * and check the receive buffer for packets.
+ */
+ if (devc->packet_len == sizeof(devc->packet)) {
+ process_packet(sdi, &devc->packet[0], devc->packet_len);
+ devc->packet_len = 0;
+ }
+ len = sizeof(devc->packet) - devc->packet_len;
+ len = serial_read_nonblocking(serial,
+ &devc->packet[devc->packet_len], len);
+ if (!len)
+ return 0;
+
+ devc->packet_len += len;
+ process_buffer(sdi);
+
+ return 0;
+}
+
+/* Gets periodically invoked by the glib main loop. */
+SR_PRIV int ut32x_handle_events(int fd, int revents, void *cb_data)
+{
struct sr_dev_inst *sdi;
- struct sr_usb_dev_inst *usb;
- struct timeval tv;
- int len, ret;
- unsigned char cmd[2];
+ struct sr_serial_dev_inst *serial;
+ uint8_t cmd;
(void)fd;
- (void)revents;
- if (!(sdi = cb_data))
+ sdi = cb_data;
+ if (!sdi)
return TRUE;
-
- di = sdi->driver;
- drvc = di->context;
-
- if (!(devc = sdi->priv))
+ serial = sdi->conn;
+ if (!serial)
return TRUE;
- memset(&tv, 0, sizeof(struct timeval));
- libusb_handle_events_timeout_completed(drvc->sr_ctx->libusb_ctx, &tv,
- NULL);
+ if (revents & G_IO_IN)
+ ut32x_receive_data(sdi);
if (sdi->status == SR_ST_STOPPING) {
- usb_source_remove(sdi->session, drvc->sr_ctx);
+ serial_source_remove(sdi->session, serial);
std_session_send_df_end(sdi);
-
- /* Tell the device to stop sending USB packets. */
- usb = sdi->conn;
- cmd[0] = 0x01;
- cmd[1] = CMD_STOP;
- ret = libusb_bulk_transfer(usb->devhdl, EP_OUT, cmd, 2, &len, 5);
- if (ret != 0 || len != 2) {
- /* Warning only, doesn't matter. */
- sr_dbg("Failed to send stop command: %s", libusb_error_name(ret));
- }
-
sdi->status = SR_ST_ACTIVE;
- return TRUE;
+
+ /* Tell the device to stop sending data. */
+ cmd = CMD_STOP;
+ serial_write_blocking(serial, &cmd, sizeof(cmd), 0);
}
return TRUE;
#define LOG_PREFIX "uni-t-ut32x"
#define DEFAULT_DATA_SOURCE DATA_SOURCE_LIVE
-#define USB_CONN "1a86.e008"
-#define USB_INTERFACE 0
-#define USB_CONFIGURATION 1
-#define EP_IN (0x80 | 2)
-#define EP_OUT 2
+#define PACKET_SIZE 19
-enum {
+enum ut32x_data_source {
DATA_SOURCE_LIVE,
DATA_SOURCE_MEMORY,
};
-enum {
+enum ut32x_cmd_code {
CMD_GET_LIVE = 1,
CMD_STOP = 2,
CMD_GET_STORED = 7,
};
struct dev_context {
- uint64_t limit_samples;
- gboolean data_source;
-
- uint64_t num_samples;
- unsigned char buf[8];
- struct libusb_transfer *xfer;
-
- unsigned char packet[32];
- int packet_len;
+ struct sr_sw_limits limits;
+ enum ut32x_data_source data_source;
+ uint8_t packet[PACKET_SIZE];
+ size_t packet_len;
};
-SR_PRIV int uni_t_ut32x_handle_events(int fd, int revents, void *cb_data);
-SR_PRIV void LIBUSB_CALL uni_t_ut32x_receive_transfer(struct libusb_transfer *transfer);
+SR_PRIV int ut32x_handle_events(int fd, int revents, void *cb_data);
#endif
+++ /dev/null
-/*
- * This file is part of the libsigrok project.
- *
- * Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
- *
- * 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 <libusb.h>
-#include <stdlib.h>
-#include <string.h>
-#include <libsigrok/libsigrok.h>
-#include "libsigrok-internal.h"
-#include "protocol.h"
-
-#define VICTOR_VID 0x1244
-#define VICTOR_PID 0xd237
-#define VICTOR_INTERFACE 0
-#define VICTOR_ENDPOINT (LIBUSB_ENDPOINT_IN | 1)
-
-static const uint32_t scanopts[] = {
- SR_CONF_CONN,
-};
-
-static const uint32_t drvopts[] = {
- SR_CONF_MULTIMETER,
-};
-
-static const uint32_t devopts[] = {
- SR_CONF_CONTINUOUS,
- SR_CONF_LIMIT_SAMPLES | SR_CONF_SET,
- SR_CONF_LIMIT_MSEC | SR_CONF_SET,
- SR_CONF_CONN | SR_CONF_GET,
-};
-
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
-{
- struct drv_context *drvc;
- struct dev_context *devc;
- struct sr_dev_inst *sdi;
- struct libusb_device_descriptor des;
- libusb_device **devlist;
- GSList *devices;
- int i;
- char connection_id[64];
-
- (void)options;
-
- drvc = di->context;
-
- devices = NULL;
- libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist);
- for (i = 0; devlist[i]; i++) {
- libusb_get_device_descriptor(devlist[i], &des);
-
- if (des.idVendor != VICTOR_VID || des.idProduct != VICTOR_PID)
- continue;
-
- if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0)
- continue;
-
- sdi = g_malloc0(sizeof(struct sr_dev_inst));
- sdi->status = SR_ST_INACTIVE;
- sdi->vendor = g_strdup("Victor");
- sdi->connection_id = g_strdup(connection_id);
- devc = g_malloc0(sizeof(struct dev_context));
- sr_sw_limits_init(&devc->limits);
- sdi->priv = devc;
-
- sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "P1");
- sdi->conn = sr_usb_dev_inst_new(libusb_get_bus_number(devlist[i]),
- libusb_get_device_address(devlist[i]), NULL);
- sdi->inst_type = SR_INST_USB;
-
- devices = g_slist_append(devices, sdi);
- }
- libusb_free_device_list(devlist, 1);
-
- return std_scan_complete(di, devices);
-}
-
-static int dev_open(struct sr_dev_inst *sdi)
-{
- struct sr_dev_driver *di = sdi->driver;
- struct drv_context *drvc = di->context;
- struct sr_usb_dev_inst *usb;
- int ret;
-
- usb = sdi->conn;
-
- ret = sr_usb_open(drvc->sr_ctx->libusb_ctx, usb);
- if (ret != SR_OK)
- return ret;
-
- if (libusb_kernel_driver_active(usb->devhdl, 0) == 1) {
- if ((ret = libusb_detach_kernel_driver(usb->devhdl, 0)) < 0) {
- sr_err("Failed to detach kernel driver: %s.",
- libusb_error_name(ret));
- return SR_ERR;
- }
- }
-
- if ((ret = libusb_claim_interface(usb->devhdl,
- VICTOR_INTERFACE))) {
- sr_err("Failed to claim interface: %s.", libusb_error_name(ret));
- return SR_ERR;
- }
-
- return SR_OK;
-}
-
-static int dev_close(struct sr_dev_inst *sdi)
-{
- struct sr_usb_dev_inst *usb;
-
- usb = sdi->conn;
-
- if (!usb->devhdl)
- return SR_ERR_BUG;
-
- libusb_release_interface(usb->devhdl, VICTOR_INTERFACE);
- libusb_close(usb->devhdl);
- usb->devhdl = NULL;
-
- return SR_OK;
-}
-
-static int config_get(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
-{
- struct dev_context *devc = sdi->priv;
- struct sr_usb_dev_inst *usb;
-
- (void)cg;
-
- switch (key) {
- case SR_CONF_CONN:
- if (!sdi || !sdi->conn)
- return SR_ERR_ARG;
- usb = sdi->conn;
- *data = g_variant_new_printf("%d.%d", usb->bus, usb->address);
- break;
- case SR_CONF_LIMIT_SAMPLES:
- case SR_CONF_LIMIT_MSEC:
- return sr_sw_limits_config_get(&devc->limits, key, data);
- default:
- return SR_ERR_NA;
- }
-
- return SR_OK;
-}
-
-static int config_set(uint32_t key, GVariant *data,
- const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
-{
- struct dev_context *devc;
-
- (void)cg;
-
- devc = sdi->priv;
-
- return sr_sw_limits_config_set(&devc->limits, key, data);
-}
-
-static int config_list(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
-{
- return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
-}
-
-static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer)
-{
- struct dev_context *devc;
- struct sr_dev_inst *sdi;
- int ret;
-
- sdi = transfer->user_data;
- devc = sdi->priv;
- if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) {
- /* USB device was unplugged. */
- sr_dev_acquisition_stop(sdi);
- } else if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
- sr_dbg("Got %d-byte packet.", transfer->actual_length);
- if (transfer->actual_length == DMM_DATA_SIZE) {
- victor_dmm_receive_data(sdi, transfer->buffer);
- if (sr_sw_limits_check(&devc->limits))
- sr_dev_acquisition_stop(sdi);
- }
- }
- /* Anything else is either an error or a timeout, which is fine:
- * we were just going to send another transfer request anyway. */
-
- if (sdi->status == SR_ST_ACTIVE) {
- /* Send the same request again. */
- if ((ret = libusb_submit_transfer(transfer) != 0)) {
- sr_err("Unable to resubmit transfer: %s.",
- libusb_error_name(ret));
- g_free(transfer->buffer);
- libusb_free_transfer(transfer);
- sr_dev_acquisition_stop(sdi);
- }
- } else {
- /* This was the last transfer we're going to receive, so
- * clean up now. */
- g_free(transfer->buffer);
- libusb_free_transfer(transfer);
- }
-}
-
-static int handle_events(int fd, int revents, void *cb_data)
-{
- struct dev_context *devc;
- struct drv_context *drvc;
- struct sr_dev_inst *sdi;
- struct sr_dev_driver *di;
- struct timeval tv;
-
- (void)fd;
- (void)revents;
-
- sdi = cb_data;
- devc = sdi->priv;
- di = sdi->driver;
- drvc = di->context;
-
- if (sr_sw_limits_check(&devc->limits))
- sr_dev_acquisition_stop(sdi);
-
- if (sdi->status == SR_ST_STOPPING) {
- usb_source_remove(sdi->session, drvc->sr_ctx);
- dev_close(sdi);
- std_session_send_df_end(sdi);
- }
-
- memset(&tv, 0, sizeof(struct timeval));
- libusb_handle_events_timeout_completed(drvc->sr_ctx->libusb_ctx, &tv,
- NULL);
-
- return TRUE;
-}
-
-static int dev_acquisition_start(const struct sr_dev_inst *sdi)
-{
- struct sr_dev_driver *di = sdi->driver;
- struct drv_context *drvc = di->context;
- struct sr_usb_dev_inst *usb;
- struct libusb_transfer *transfer;
- int ret;
- unsigned char *buf;
-
- usb = sdi->conn;
-
- std_session_send_df_header(sdi);
-
- usb_source_add(sdi->session, drvc->sr_ctx, 100,
- handle_events, (void *)sdi);
-
- buf = g_malloc(DMM_DATA_SIZE);
- transfer = libusb_alloc_transfer(0);
- /* Each transfer request gets 100ms to arrive before it's restarted.
- * The device only sends 1 transfer/second no matter how many
- * times you ask, but we want to keep step with the USB events
- * handling above. */
- libusb_fill_interrupt_transfer(transfer, usb->devhdl,
- VICTOR_ENDPOINT, buf, DMM_DATA_SIZE, receive_transfer,
- (struct sr_dev_inst *)sdi, 100);
- if ((ret = libusb_submit_transfer(transfer) != 0)) {
- sr_err("Unable to submit transfer: %s.", libusb_error_name(ret));
- libusb_free_transfer(transfer);
- g_free(buf);
- return SR_ERR;
- }
-
- return SR_OK;
-}
-
-static int dev_acquisition_stop(struct sr_dev_inst *sdi)
-{
- sdi->status = SR_ST_STOPPING;
-
- return SR_OK;
-}
-
-static struct sr_dev_driver victor_dmm_driver_info = {
- .name = "victor-dmm",
- .longname = "Victor DMMs",
- .api_version = 1,
- .init = std_init,
- .cleanup = std_cleanup,
- .scan = scan,
- .dev_list = std_dev_list,
- .dev_clear = std_dev_clear,
- .config_get = config_get,
- .config_set = config_set,
- .config_list = config_list,
- .dev_open = dev_open,
- .dev_close = dev_close,
- .dev_acquisition_start = dev_acquisition_start,
- .dev_acquisition_stop = dev_acquisition_stop,
- .context = NULL,
-};
-SR_REGISTER_DEV_DRIVER(victor_dmm_driver_info);
+++ /dev/null
-/*
- * This file is part of the libsigrok project.
- *
- * Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
- *
- * 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 <string.h>
-#include <math.h>
-#include <libsigrok/libsigrok.h>
-#include "libsigrok-internal.h"
-#include "protocol.h"
-
-/* Reverse the high nibble into the low nibble */
-static uint8_t decode_digit(uint8_t in)
-{
- uint8_t out, i;
-
- out = 0;
- in >>= 4;
- for (i = 0x08; i; i >>= 1) {
- out >>= 1;
- if (in & i)
- out |= 0x08;
- }
-
- return out;
-}
-
-static void decode_buf(struct sr_dev_inst *sdi, unsigned char *data)
-{
- struct sr_datafeed_packet packet;
- struct sr_datafeed_analog analog;
- struct sr_analog_encoding encoding;
- struct sr_analog_meaning meaning;
- struct sr_analog_spec spec;
- struct dev_context *devc;
- long factor, ivalue;
- uint8_t digits[4];
- gboolean is_duty, is_continuity, is_diode, is_ac, is_dc, is_auto;
- gboolean is_hold, is_max, is_min, is_relative, minus;
- float fvalue;
-
- devc = sdi->priv;
-
- digits[0] = decode_digit(data[12]);
- digits[1] = decode_digit(data[11]);
- digits[2] = decode_digit(data[10]);
- digits[3] = decode_digit(data[9]);
-
- if (digits[0] == 0x0f && digits[1] == 0x00 && digits[2] == 0x0a &&
- digits[3] == 0x0f)
- /* The "over limit" (OL) display comes through like this */
- ivalue = -1;
- else if (digits[0] > 9 || digits[1] > 9 || digits[2] > 9 || digits[3] > 9)
- /* An invalid digit in any position denotes no value. */
- ivalue = -2;
- else {
- ivalue = digits[0] * 1000;
- ivalue += digits[1] * 100;
- ivalue += digits[2] * 10;
- ivalue += digits[3];
- }
-
- /* Decimal point position */
- factor = 0;
- switch (data[7] >> 4) {
- case 0x00:
- factor = 0;
- break;
- case 0x02:
- factor = 1;
- break;
- case 0x04:
- factor = 2;
- break;
- case 0x08:
- factor = 3;
- break;
- default:
- sr_err("Unknown decimal point byte: 0x%.2x.", data[7]);
- break;
- }
-
- /* Minus flag */
- minus = data[2] & 0x01;
-
- /* Mode detail symbols on the right side of the digits */
- is_duty = is_continuity = is_diode = FALSE;
- switch (data[4]) {
- case 0x00:
- /* None. */
- break;
- case 0x01:
- /* Micro */
- factor += 6;
- break;
- case 0x02:
- /* Milli */
- factor += 3;
- break;
- case 0x04:
- /* Kilo */
- ivalue *= 1000;
- break;
- case 0x08:
- /* Mega */
- ivalue *= 1000000;
- break;
- case 0x10:
- /* Continuity shows up as Ohm + this bit */
- is_continuity = TRUE;
- break;
- case 0x20:
- /* Diode tester is Volt + this bit */
- is_diode = TRUE;
- break;
- case 0x40:
- is_duty = TRUE;
- break;
- case 0x80:
- /* Never seen */
- sr_dbg("Unknown mode right detail: 0x%.2x.", data[4]);
- break;
- default:
- sr_dbg("Unknown/invalid mode right detail: 0x%.2x.", data[4]);
- break;
- }
-
- /* Scale flags on the right, continued */
- is_max = is_min = FALSE;
- if (data[5] & 0x04)
- is_max = TRUE;
- if (data[5] & 0x08)
- is_min = TRUE;
- if (data[5] & 0x40)
- /* Nano */
- factor += 9;
-
- /* Mode detail symbols on the left side of the digits */
- is_auto = is_dc = is_ac = is_hold = is_relative = FALSE;
- if (data[6] & 0x04)
- is_auto = TRUE;
- if (data[6] & 0x08)
- is_dc = TRUE;
- if (data[6] & 0x10)
- is_ac = TRUE;
- if (data[6] & 0x20)
- is_relative = TRUE;
- if (data[6] & 0x40)
- is_hold = TRUE;
-
- fvalue = (float)ivalue / pow(10, factor);
- if (minus)
- fvalue = -fvalue;
-
- sr_analog_init(&analog, &encoding, &meaning, &spec, 4);
-
- /* Measurement mode */
- meaning.channels = sdi->channels;
- meaning.mq = 0;
- switch (data[3]) {
- case 0x00:
- if (is_duty) {
- meaning.mq = SR_MQ_DUTY_CYCLE;
- meaning.unit = SR_UNIT_PERCENTAGE;
- } else
- sr_dbg("Unknown measurement mode: %.2x.", data[3]);
- break;
- case 0x01:
- if (is_diode) {
- meaning.mq = SR_MQ_VOLTAGE;
- meaning.unit = SR_UNIT_VOLT;
- meaning.mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC;
- if (ivalue < 0)
- fvalue = NAN;
- } else {
- if (ivalue < 0)
- break;
- meaning.mq = SR_MQ_VOLTAGE;
- meaning.unit = SR_UNIT_VOLT;
- if (is_ac)
- meaning.mqflags |= SR_MQFLAG_AC;
- if (is_dc)
- meaning.mqflags |= SR_MQFLAG_DC;
- }
- break;
- case 0x02:
- meaning.mq = SR_MQ_CURRENT;
- meaning.unit = SR_UNIT_AMPERE;
- if (is_ac)
- meaning.mqflags |= SR_MQFLAG_AC;
- if (is_dc)
- meaning.mqflags |= SR_MQFLAG_DC;
- break;
- case 0x04:
- if (is_continuity) {
- meaning.mq = SR_MQ_CONTINUITY;
- meaning.unit = SR_UNIT_BOOLEAN;
- fvalue = ivalue < 0 ? 0.0 : 1.0;
- } else {
- meaning.mq = SR_MQ_RESISTANCE;
- meaning.unit = SR_UNIT_OHM;
- if (ivalue < 0)
- fvalue = INFINITY;
- }
- break;
- case 0x08:
- /* Never seen */
- sr_dbg("Unknown measurement mode: 0x%.2x.", data[3]);
- break;
- case 0x10:
- meaning.mq = SR_MQ_FREQUENCY;
- meaning.unit = SR_UNIT_HERTZ;
- break;
- case 0x20:
- meaning.mq = SR_MQ_CAPACITANCE;
- meaning.unit = SR_UNIT_FARAD;
- break;
- case 0x40:
- meaning.mq = SR_MQ_TEMPERATURE;
- meaning.unit = SR_UNIT_CELSIUS;
- break;
- case 0x80:
- meaning.mq = SR_MQ_TEMPERATURE;
- meaning.unit = SR_UNIT_FAHRENHEIT;
- break;
- default:
- sr_dbg("Unknown/invalid measurement mode: 0x%.2x.", data[3]);
- break;
- }
- if (meaning.mq == 0)
- return;
-
- if (is_auto)
- meaning.mqflags |= SR_MQFLAG_AUTORANGE;
- if (is_hold)
- meaning.mqflags |= SR_MQFLAG_HOLD;
- if (is_max)
- meaning.mqflags |= SR_MQFLAG_MAX;
- if (is_min)
- meaning.mqflags |= SR_MQFLAG_MIN;
- if (is_relative)
- meaning.mqflags |= SR_MQFLAG_RELATIVE;
-
- analog.data = &fvalue;
- analog.num_samples = 1;
-
- packet.type = SR_DF_ANALOG;
- packet.payload = &analog;
- sr_session_send(sdi, &packet);
-
- sr_sw_limits_update_samples_read(&devc->limits, 1);
-}
-
-SR_PRIV int victor_dmm_receive_data(struct sr_dev_inst *sdi, unsigned char *buf)
-{
- static const unsigned char obfuscation[DMM_DATA_SIZE] = "jodenxunickxia";
- static const unsigned char shuffle[DMM_DATA_SIZE] = {
- 6, 13, 5, 11, 2, 7, 9, 8, 3, 10, 12, 0, 4, 1
- };
- GString *dbg;
- int i;
- unsigned char data[DMM_DATA_SIZE];
-
- for (i = 0; i < DMM_DATA_SIZE && buf[i] == 0; i++);
- if (i == DMM_DATA_SIZE) {
- /* This DMM outputs all zeroes from time to time, just ignore it. */
- sr_dbg("Received all zeroes.");
- return SR_OK;
- }
-
- /* Deobfuscate and reorder data. */
- for (i = 0; i < DMM_DATA_SIZE; i++)
- data[shuffle[i]] = (buf[i] - obfuscation[i]) & 0xff;
-
- if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
- dbg = g_string_sized_new(128);
- g_string_printf(dbg, "Deobfuscated.");
- for (i = 0; i < DMM_DATA_SIZE; i++)
- g_string_append_printf(dbg, " %.2x", data[i]);
- sr_spew("%s", dbg->str);
- g_string_free(dbg, TRUE);
- }
-
- decode_buf(sdi, data);
-
- return SR_OK;
-}
+++ /dev/null
-/*
- * This file is part of the libsigrok project.
- *
- * Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
- *
- * 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/>.
- */
-
-#ifndef LIBSIGROK_HARDWARE_VICTOR_DMM_PROTOCOL_H
-#define LIBSIGROK_HARDWARE_VICTOR_DMM_PROTOCOL_H
-
-#include <stdint.h>
-#include <libsigrok/libsigrok.h>
-#include "libsigrok-internal.h"
-
-#define LOG_PREFIX "victor-dmm"
-
-#define DMM_DATA_SIZE 14
-
-struct dev_context {
- struct sr_sw_limits limits;
-};
-
-SR_PRIV int victor_dmm_receive_data(struct sr_dev_inst *sdi, unsigned char *buf);
-
-#endif
return std_serial_dev_acquisition_stop(sdi);
}
-SR_PRIV struct sr_dev_driver zketech_ebd_usb_driver_info = {
+static struct sr_dev_driver zketech_ebd_usb_driver_info = {
.name = "zketech-ebd-usb",
.longname = "ZKETECH EBD-USB",
.api_version = 1,
"Trigger level", NULL},
{SR_CONF_EXTERNAL_CLOCK_SOURCE, SR_T_STRING, "external_clock_source",
"External clock source", NULL},
+ {SR_CONF_OFFSET, SR_T_FLOAT, "offset",
+ "Offset", NULL},
+ {SR_CONF_TRIGGER_PATTERN, SR_T_STRING, "triggerpattern",
+ "Trigger pattern", NULL},
+ {SR_CONF_HIGH_RESOLUTION, SR_T_BOOL, "highresolution",
+ "High resolution", NULL},
+ {SR_CONF_PEAK_DETECTION, SR_T_BOOL, "peakdetection",
+ "Peak detection", NULL},
+ {SR_CONF_LOGIC_THRESHOLD, SR_T_STRING, "logic_threshold",
+ "Logic threshold (predefined)", NULL},
+ {SR_CONF_LOGIC_THRESHOLD_CUSTOM, SR_T_FLOAT, "logic_threshold_custom",
+ "Logic threshold (custom)", NULL},
+ {SR_CONF_RANGE, SR_T_STRING, "range",
+ "Range", NULL},
+ {SR_CONF_DIGITS, SR_T_STRING, "digits",
+ "Digits", NULL},
/* Special stuff */
{SR_CONF_SESSIONFILE, SR_T_STRING, "sessionfile",
*
* A floating reference can be passed in for data.
*
+ * @param key The config key to use.
+ * @param data The GVariant data to use.
+ *
+ * @return The newly allocated struct sr_config. This function is assumed
+ * to never fail.
+ *
* @private
*/
SR_PRIV struct sr_config *sr_config_new(uint32_t key, GVariant *data)
* This file is part of the libsigrok project.
*
* Copyright (C) 2013 Marc Schink <sigrok-dev@marcschink.de>
+ * 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <config.h>
+#include "config.h"
+
+#include <ctype.h>
+#include <glib.h>
#include <stdlib.h>
#include <string.h>
-#include <glib.h>
+#include <strings.h>
+
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
+#include "scpi.h" /* String un-quote for channel name from header line. */
#define LOG_PREFIX "input/csv"
/*
* The CSV input module has the following options:
*
- * single-column: Specifies the column number which stores the sample data for
- * single column mode and enables single column mode. Multi
- * column mode is used if this parameter is omitted.
+ * column_formats: Specifies the data formats and channel counts for the
+ * input file's text columns. Accepts a comma separated list of tuples
+ * with: an optional column repeat count ('*' as a wildcard meaning
+ * "all remaining columns", only applicable to the last field), a format
+ * specifying character ('x' hexadecimal, 'o' octal, 'b' binary, 'l'
+ * single-bit logic), and an optional bit count (translating to: logic
+ * channels communicated in that column). The 'a' format marks analog
+ * data, an optionally following number is the digits count (resolution).
+ * The 't' format marks timestamp values, which could help in automatic
+ * determination of the input stream's samplerate. This "column_formats"
+ * option is most versatile, other forms of specifying the column layout
+ * only exist for backwards compatibility, and are rather limited. They
+ * exclusively support logic input data in strictly adjacent columns,
+ * with further constraints on column layout for multi-bit data.
+ *
+ * single_column: Specifies the column number which contains the logic data
+ * for single-column mode. All logic data is taken from several bits
+ * which all are kept within that one column. Only exists for backwards
+ * compatibility, see "column_formats" for more flexibility.
+ *
+ * first_column: Specifies the number of the first column with logic data
+ * in simple multi-column mode. Only exists for backwards compatibility,
+ * see "column_formats" for more flexibility.
+ *
+ * logic_channels: Specifies the number of logic channels. Is required in
+ * simple single-column mode. Is optional in simple multi-column mode
+ * (and defaults to all remaining columns). Only exists for backwards
+ * compatibility, see "column_formats" for more flexibility.
*
- * numchannels: Specifies the number of channels to use. In multi column mode
- * the number of channels are the number of columns and in single
- * column mode the number of bits (LSB first) beginning at
- * 'first-channel'.
+ * single_format: Specifies the format of the input text in simple single-
+ * column mode. Available formats are: 'bin' (default), 'hex' and 'oct'.
+ * Simple multi-column mode always uses single-bit data per column.
+ * Only exists for backwards compatibility, see "column_formats" for
+ * more flexibility.
*
- * delimiter: Specifies the delimiter for columns. Must be at least one
- * character. Comma is used as default delimiter.
+ * start_line: Specifies at which line to start processing the input file.
+ * Allows to skip leading lines which neither are header nor data lines.
+ * By default all of the input file gets processed.
*
- * format: Specifies the format of the sample data in single column mode.
- * Available formats are: 'bin', 'hex' and 'oct'. The binary
- * format is used by default. This option has no effect in multi
- * column mode.
+ * header: Boolean option, controls whether the first processed line is used
+ * to determine channel names. Off by default. Generic channel names are
+ * used in the absence of header line content.
*
- * comment: Specifies the prefix character(s) for comments. No prefix
- * characters are used by default which disables removing of
- * comments.
+ * samplerate: Specifies the samplerate of the input data. Defaults to 0.
+ * User specs take precedence over data which optionally gets derived
+ * from input data.
*
- * samplerate: Samplerate which the sample data was captured with. Default
- * value is 0.
+ * column_separator: Specifies the sequence which separates the text file
+ * columns. Cannot be empty. Defaults to comma.
*
- * first-channel: Column number of the first channel in multi column mode and
- * position of the bit for the first channel in single column mode.
- * Default value is 0.
+ * comment_leader: Specifies the sequence which starts comments that run
+ * up to the end of the current text line. Can be empty to disable
+ * comment support. Defaults to semicolon.
*
- * header: Determines if the first line should be treated as header
- * and used for channel names in multi column mode. Empty header
- * names will be replaced by the channel number. If enabled in
- * single column mode the first line will be skipped. Usage of
- * header is disabled by default.
+ * Typical examples of using these options:
+ * - ... -I csv:column_formats=*l ...
+ * All columns are single-bit logic data. Identical to the previous
+ * multi-column mode (the default when no options were given at all).
+ * - ... -I csv:column_formats=3-,*l ...
+ * Ignore the first three columns, get single-bit logic data from all
+ * remaining lines (multi-column mode with first-column above 1).
+ * - ... -I csv:column_formats=3-,4l,x8 ...
+ * Ignore the first three columns, get single-bit logic data from the
+ * next four columns, then eight-bit data in hex format from the next
+ * column. More columns may follow in the input text but won't get
+ * processed. (Mix of previous multi-column as well as single-column
+ * modes.)
+ * - ... -I csv:column_formats=4x8,b16,5l ...
+ * Get eight-bit data in hex format from the first four columns, then
+ * sixteen-bit data in binary format, then five times single-bit data.
+ * - ... -I csv:single_column=2:single_format=bin:logic_channels=8 ...
+ * Get eight logic bits in binary format from column 2. (Simple
+ * single-column mode, corresponds to the "-,b8" format.)
+ * - ... -I csv:first_column=6:logic_channels=4 ...
+ * Get four single-bit logic channels from columns 6 to 9 respectively.
+ * (Simple multi-column mode, corresponds to the "5-,4b" format.)
+ * - ... -I csv:start_line=20:header=yes:...
+ * Skip the first 19 text lines. Use line 20 to derive channel names.
+ * Data starts at line 21.
+ * - ... -I csv:column_formats=*a6 ...
+ * Each column contains an analog value with six significant digits
+ * after the decimal period.
+ * - ... -I csv:column_formats=t,2a ...
+ * The first column contains timestamps, the next two columns contain
+ * analog values. The capture's samplerate could get determined from
+ * the timestamp values if not provided by the user by means of the
+ * 'samplerate' option. This assumes a mere number in units of seconds,
+ * and equidistant rows, there is no fancy support for textual unit
+ * suffixes nor gaps in the stream of samples nor other non-linearity,
+ * just '-' ignore the column if the format is not supported).
*
- * startline: Line number to start processing sample data. Must be greater
- * than 0. The default line number to start processing is 1.
+ * IMPORTANT! Make sure the .format_match() logic matches the default
+ * values for the input module's options. Ideally the format match test
+ * shall pass for all input data that is supported by default.
*/
/*
* TODO
*
- * - Determine how the text line handling can get improved, regarding
- * all of robustness and flexibility and correctness.
- * - The current implementation splits on "any run of CR and LF". Which
- * translates to: Line numbers are wrong in the presence of empty
- * lines in the input stream. See below for an (expensive) fix.
- * - Dropping support for CR style end-of-line markers could improve
- * the situation a lot. Code could search for and split on LF, and
- * trim optional trailing CR. This would result in proper support
- * for CRLF (Windows) as well as LF (Unix), and allow for correct
- * line number counts.
- * - When support for CR-only line termination cannot get dropped,
- * then the current implementation is inappropriate. Currently the
- * input stream is scanned for the first occurance of either of the
- * supported termination styles (which is good). For the remaining
- * session a consistent encoding of the text lines is assumed (which
- * is acceptable).
- * - When line numbers need to be correct and reliable, _and_ the full
- * set of previously supported line termination sequences are required,
- * and potentially more are to get added for improved compatibility
- * with more platforms or generators, then the current approach of
- * splitting on runs of termination characters needs to get replaced,
- * by the more expensive approach to scan for and count the initially
- * determined termination sequence.
- *
- * - Add support for analog input data? (optional)
- * - Needs a syntax first for user specs which channels (columns) are
- * logic and which are analog. May need heuristics(?) to guess from
- * input data in the absence of user provided specs.
+ * - Unbreak analog data when submitted in the 'double' data type. This
+ * was observed with sigrok-cli screen output. Is analog.encoding->unitsize
+ * not handled appropriately? Is it a sigrok-cli or libsigrok issue?
+ * - Add a test suite for input modules in general, and CSV in specific?
+ * Becomes more important with the multitude of options and their
+ * interaction. Could cover edge cases (BOM presence, line termination
+ * absence, etc) and auto-stuff as well (channel names, channel counts,
+ * samplerates, etc).
*/
+typedef float csv_analog_t; /* 'double' currently is flawed. */
+
/* Single column formats. */
-enum {
- FORMAT_BIN,
- FORMAT_HEX,
- FORMAT_OCT
+enum single_col_format {
+ FORMAT_NONE, /* Ignore this column. */
+ FORMAT_BIN, /* Bin digits for a set of bits (or just one bit). */
+ FORMAT_HEX, /* Hex digits for a set of bits. */
+ FORMAT_OCT, /* Oct digits for a set of bits. */
+ FORMAT_ANALOG, /* Floating point number for an analog channel. */
+ FORMAT_TIME, /* Timestamps. */
+};
+
+static const char *col_format_text[] = {
+ [FORMAT_NONE] = "unknown",
+ [FORMAT_BIN] = "binary",
+ [FORMAT_HEX] = "hexadecimal",
+ [FORMAT_OCT] = "octal",
+ [FORMAT_ANALOG] = "analog",
+ [FORMAT_TIME] = "timestamp",
+};
+
+static const char col_format_char[] = {
+ [FORMAT_NONE] = '?',
+ [FORMAT_BIN] = 'b',
+ [FORMAT_HEX] = 'x',
+ [FORMAT_OCT] = 'o',
+ [FORMAT_ANALOG] = 'a',
+ [FORMAT_TIME] = 't',
+};
+
+static gboolean format_is_ignore(enum single_col_format fmt)
+{
+ return fmt == FORMAT_NONE;
+}
+
+static gboolean format_is_logic(enum single_col_format fmt)
+{
+ return fmt >= FORMAT_BIN && fmt <= FORMAT_OCT;
+}
+
+static gboolean format_is_analog(enum single_col_format fmt)
+{
+ return fmt == FORMAT_ANALOG;
+}
+
+static gboolean format_is_timestamp(enum single_col_format fmt)
+{
+ return fmt == FORMAT_TIME;
+}
+
+struct column_details {
+ size_t col_nr;
+ enum single_col_format text_format;
+ size_t channel_offset;
+ size_t channel_count;
+ int analog_digits;
+ GString **channel_names;
};
struct context {
gboolean started;
- /* Current selected samplerate. */
+ /* Current samplerate, optionally determined from input data. */
uint64_t samplerate;
+ uint64_t calc_samplerate;
+ double prev_timestamp;
+ gboolean samplerate_sent;
/* Number of channels. */
- unsigned int num_channels;
+ size_t logic_channels;
+ size_t analog_channels;
- /* Column delimiter character(s). */
+ /* Column delimiter (actually separator), comment leader, EOL sequence. */
GString *delimiter;
-
- /* Comment prefix character(s). */
GString *comment;
-
- /* Termination character(s) used in current stream. */
char *termination;
- /* Determines if sample data is stored in multiple columns. */
- gboolean multi_column_mode;
-
- /* Column number of the sample data in single column mode. */
- unsigned int single_column;
-
- /*
- * Number of the first column to parse. Equivalent to the number of the
- * first channel in multi column mode and the single column number in
- * single column mode.
- */
- unsigned int first_column;
-
- /*
- * Column number of the first channel in multi column mode and position of
- * the bit for the first channel in single column mode.
- */
- unsigned int first_channel;
+ /* Format specs for input columns, and processing state. */
+ size_t column_seen_count;
+ const char *column_formats;
+ size_t column_want_count;
+ struct column_details *column_details;
/* Line number to start processing. */
size_t start_line;
* Determines if the first line should be treated as header and used for
* channel names in multi column mode.
*/
- gboolean header;
-
- /* Format sample data is stored in single column mode. */
- int format;
+ gboolean use_header;
+ gboolean header_seen;
size_t sample_unit_size; /**!< Byte count for a single sample. */
uint8_t *sample_buffer; /**!< Buffer for a single sample. */
+ csv_analog_t *analog_sample_buffer; /**!< Buffer for one set of analog values. */
uint8_t *datafeed_buffer; /**!< Queue for datafeed submission. */
size_t datafeed_buf_size;
size_t datafeed_buf_fill;
+ /* "Striped" layout, M samples for N channels each. */
+ csv_analog_t *analog_datafeed_buffer; /**!< Queue for analog datafeed. */
+ size_t analog_datafeed_buf_size;
+ size_t analog_datafeed_buf_fill;
+ int *analog_datafeed_digits;
+ GSList **analog_datafeed_channels;
/* Current line number. */
size_t line_number;
+
+ /* List of previously created sigrok channels. */
+ GSList *prev_sr_channels;
+ GSList **prev_df_channels;
};
-static void strip_comment(char *buf, const GString *prefix)
+/*
+ * Primitive operations to handle sample sets:
+ * - Keep a buffer for datafeed submission, capable of holding many
+ * samples (reduces call overhead, improves throughput).
+ * - Have a "current sample set" pointer reference one position in that
+ * large samples buffer.
+ * - Clear the current sample set before text line inspection, then set
+ * the bits which are found active in the current line of text input.
+ * Phrase the API such that call sites can be kept simple. Advance to
+ * the next sample set between lines, flush the larger buffer as needed
+ * (when it is full, or upon EOF).
+ */
+
+static int flush_samplerate(const struct sr_input *in)
{
- char *ptr;
+ struct context *inc;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_meta meta;
+ struct sr_config *src;
- if (!prefix->len)
+ inc = in->priv;
+ if (!inc->calc_samplerate && inc->samplerate)
+ inc->calc_samplerate = inc->samplerate;
+ if (inc->calc_samplerate && !inc->samplerate_sent) {
+ packet.type = SR_DF_META;
+ packet.payload = &meta;
+ src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(inc->calc_samplerate));
+ meta.config = g_slist_append(NULL, src);
+ sr_session_send(in->sdi, &packet);
+ g_slist_free(meta.config);
+ sr_config_free(src);
+ inc->samplerate_sent = TRUE;
+ }
+
+ return SR_OK;
+}
+
+static void clear_logic_samples(struct context *inc)
+{
+ if (!inc->logic_channels)
return;
+ inc->sample_buffer = &inc->datafeed_buffer[inc->datafeed_buf_fill];
+ memset(inc->sample_buffer, 0, inc->sample_unit_size);
+}
- if ((ptr = strstr(buf, prefix->str)))
- *ptr = '\0';
+static void set_logic_level(struct context *inc, size_t ch_idx, int on)
+{
+ size_t byte_idx, bit_idx;
+ uint8_t bit_mask;
+
+ if (ch_idx >= inc->logic_channels)
+ return;
+ if (!on)
+ return;
+
+ byte_idx = ch_idx / 8;
+ bit_idx = ch_idx % 8;
+ bit_mask = 1 << bit_idx;
+ inc->sample_buffer[byte_idx] |= bit_mask;
}
-static int parse_binstr(const char *str, struct context *inc)
+static int flush_logic_samples(const struct sr_input *in)
{
- gsize i, j, length;
+ struct context *inc;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_logic logic;
+ int rc;
- length = strlen(str);
+ inc = in->priv;
+ if (!inc->datafeed_buf_fill)
+ return SR_OK;
- if (!length) {
- sr_err("Column %u in line %zu is empty.", inc->single_column,
- inc->line_number);
- return SR_ERR;
- }
+ rc = flush_samplerate(in);
+ if (rc != SR_OK)
+ return rc;
- /* Clear buffer in order to set bits only. */
- memset(inc->sample_buffer, 0, inc->sample_unit_size);
+ memset(&packet, 0, sizeof(packet));
+ memset(&logic, 0, sizeof(logic));
+ packet.type = SR_DF_LOGIC;
+ packet.payload = &logic;
+ logic.unitsize = inc->sample_unit_size;
+ logic.length = inc->datafeed_buf_fill;
+ logic.data = inc->datafeed_buffer;
- i = inc->first_channel;
+ rc = sr_session_send(in->sdi, &packet);
+ if (rc != SR_OK)
+ return rc;
- for (j = 0; i < length && j < inc->num_channels; i++, j++) {
- if (str[length - i - 1] == '1') {
- inc->sample_buffer[j / 8] |= (1 << (j % 8));
- } else if (str[length - i - 1] != '0') {
- sr_err("Invalid value '%s' in column %u in line %zu.",
- str, inc->single_column, inc->line_number);
- return SR_ERR;
- }
- }
+ inc->datafeed_buf_fill = 0;
return SR_OK;
}
-static int parse_hexstr(const char *str, struct context *inc)
+static int queue_logic_samples(const struct sr_input *in)
{
- gsize i, j, k, length;
- uint8_t value;
- char c;
+ struct context *inc;
+ int rc;
- length = strlen(str);
+ inc = in->priv;
+ if (!inc->logic_channels)
+ return SR_OK;
- if (!length) {
- sr_err("Column %u in line %zu is empty.", inc->single_column,
- inc->line_number);
- return SR_ERR;
+ inc->datafeed_buf_fill += inc->sample_unit_size;
+ if (inc->datafeed_buf_fill == inc->datafeed_buf_size) {
+ rc = flush_logic_samples(in);
+ if (rc != SR_OK)
+ return rc;
}
- /* Clear buffer in order to set bits only. */
- memset(inc->sample_buffer, 0, inc->sample_unit_size);
+ return SR_OK;
+}
- /* Calculate the position of the first hexadecimal digit. */
- i = inc->first_channel / 4;
+static void set_analog_value(struct context *inc, size_t ch_idx, csv_analog_t value);
- for (j = 0; i < length && j < inc->num_channels; i++) {
- c = str[length - i - 1];
+static void clear_analog_samples(struct context *inc)
+{
+ size_t idx;
- if (!g_ascii_isxdigit(c)) {
- sr_err("Invalid value '%s' in column %u in line %zu.",
- str, inc->single_column, inc->line_number);
- return SR_ERR;
- }
+ if (!inc->analog_channels)
+ return;
+ inc->analog_sample_buffer = &inc->analog_datafeed_buffer[inc->analog_datafeed_buf_fill];
+ for (idx = 0; idx < inc->analog_channels; idx++)
+ set_analog_value(inc, idx, 0.0);
+}
- value = g_ascii_xdigit_value(c);
+static void set_analog_value(struct context *inc, size_t ch_idx, csv_analog_t value)
+{
+ if (ch_idx >= inc->analog_channels)
+ return;
+ if (!value)
+ return;
+ inc->analog_sample_buffer[ch_idx * inc->analog_datafeed_buf_size] = value;
+}
- k = (inc->first_channel + j) % 4;
+static int flush_analog_samples(const struct sr_input *in)
+{
+ struct context *inc;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
+ struct sr_analog_encoding encoding;
+ struct sr_analog_meaning meaning;
+ struct sr_analog_spec spec;
+ csv_analog_t *samples;
+ size_t ch_idx;
+ int digits;
+ int rc;
- for (; j < inc->num_channels && k < 4; k++) {
- if (value & (1 << k))
- inc->sample_buffer[j / 8] |= (1 << (j % 8));
+ inc = in->priv;
+ if (!inc->analog_datafeed_buf_fill)
+ return SR_OK;
- j++;
- }
+ rc = flush_samplerate(in);
+ if (rc != SR_OK)
+ return rc;
+
+ samples = inc->analog_datafeed_buffer;
+ for (ch_idx = 0; ch_idx < inc->analog_channels; ch_idx++) {
+ digits = inc->analog_datafeed_digits[ch_idx];
+ sr_analog_init(&analog, &encoding, &meaning, &spec, digits);
+ memset(&packet, 0, sizeof(packet));
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ analog.num_samples = inc->analog_datafeed_buf_fill;
+ analog.data = samples;
+ analog.meaning->channels = inc->analog_datafeed_channels[ch_idx];
+ analog.meaning->mq = 0;
+ analog.meaning->mqflags = 0;
+ analog.meaning->unit = 0;
+ analog.encoding->unitsize = sizeof(samples[0]);
+ analog.encoding->is_signed = TRUE;
+ analog.encoding->is_float = TRUE;
+#ifdef WORDS_BIGENDIAN
+ analog.encoding->is_bigendian = TRUE;
+#else
+ analog.encoding->is_bigendian = FALSE;
+#endif
+ analog.encoding->digits = spec.spec_digits;
+ rc = sr_session_send(in->sdi, &packet);
+ if (rc != SR_OK)
+ return rc;
+ samples += inc->analog_datafeed_buf_size;
}
+ inc->analog_datafeed_buf_fill = 0;
+
return SR_OK;
}
-static int parse_octstr(const char *str, struct context *inc)
+static int queue_analog_samples(const struct sr_input *in)
{
- gsize i, j, k, length;
- uint8_t value;
- char c;
+ struct context *inc;
+ int rc;
- length = strlen(str);
+ inc = in->priv;
+ if (!inc->analog_channels)
+ return SR_OK;
- if (!length) {
- sr_err("Column %u in line %zu is empty.", inc->single_column,
- inc->line_number);
- return SR_ERR;
+ inc->analog_datafeed_buf_fill++;
+ if (inc->analog_datafeed_buf_fill == inc->analog_datafeed_buf_size) {
+ rc = flush_analog_samples(in);
+ if (rc != SR_OK)
+ return rc;
}
- /* Clear buffer in order to set bits only. */
- memset(inc->sample_buffer, 0, inc->sample_unit_size);
-
- /* Calculate the position of the first octal digit. */
- i = inc->first_channel / 3;
-
- for (j = 0; i < length && j < inc->num_channels; i++) {
- c = str[length - i - 1];
-
- if (c < '0' || c > '7') {
- sr_err("Invalid value '%s' in column %u in line %zu.",
- str, inc->single_column, inc->line_number);
- return SR_ERR;
- }
+ return SR_OK;
+}
- value = g_ascii_xdigit_value(c);
+/* Helpers for "column processing". */
- k = (inc->first_channel + j) % 3;
+static int split_column_format(const char *spec,
+ size_t *column_count, enum single_col_format *format, size_t *bit_count)
+{
+ size_t count;
+ char *endp, format_char;
+ enum single_col_format format_code;
- for (; j < inc->num_channels && k < 3; k++) {
- if (value & (1 << k))
- inc->sample_buffer[j / 8] |= (1 << (j % 8));
+ if (!spec || !*spec)
+ return SR_ERR_ARG;
- j++;
- }
+ /* Get the (optional, decimal, default 1) column count. Accept '*'. */
+ endp = NULL;
+ if (*spec == '*') {
+ /* Workaround, strtoul("*") won't always yield expected endp. */
+ count = 0;
+ endp = (char *)&spec[1];
+ } else {
+ count = strtoul(spec, &endp, 10);
+ }
+ if (!endp)
+ return SR_ERR_ARG;
+ if (endp == spec)
+ count = 1;
+ if (column_count)
+ *column_count = count;
+ spec = endp;
+
+ /* Get the (mandatory, single letter) type spec (-/xob/l). */
+ format_char = *spec++;
+ switch (format_char) {
+ case '-':
+ case '/':
+ format_char = '-';
+ format_code = FORMAT_NONE;
+ break;
+ case 'x':
+ format_code = FORMAT_HEX;
+ break;
+ case 'o':
+ format_code = FORMAT_OCT;
+ break;
+ case 'b':
+ case 'l':
+ format_code = FORMAT_BIN;
+ break;
+ case 'a':
+ format_code = FORMAT_ANALOG;
+ break;
+ case 't':
+ format_code = FORMAT_TIME;
+ break;
+ default: /* includes NUL */
+ return SR_ERR_ARG;
}
+ if (format)
+ *format = format_code;
+
+ /* Get the (optional, decimal, default 1) bit count. */
+ endp = NULL;
+ count = strtoul(spec, &endp, 10);
+ if (!endp)
+ return SR_ERR_ARG;
+ if (endp == spec)
+ count = format_is_analog(format_code) ? 3 : 1;
+ if (format_is_ignore(format_code))
+ count = 0;
+ if (format_char == 'l')
+ count = 1;
+ if (bit_count)
+ *bit_count = count;
+ spec = endp;
+
+ /* Input spec must have been exhausted. */
+ if (*spec)
+ return SR_ERR_ARG;
return SR_OK;
}
-static char **parse_line(char *buf, struct context *inc, int max_columns)
+static int make_column_details_from_format(const struct sr_input *in,
+ const char *column_format, char **column_texts)
{
- const char *str, *remainder;
- GSList *list, *l;
- char **columns;
+ struct context *inc;
+ char **formats, *format;
+ size_t format_count, column_count, logic_count, analog_count;
+ size_t auto_column_count;
+ size_t format_idx, c, b, column_idx, channel_idx, analog_idx;
+ enum single_col_format f;
+ struct column_details *detail;
+ GString *channel_name;
+ size_t create_idx;
char *column;
- gsize n, k;
-
- n = 0;
- k = 0;
- list = NULL;
-
- remainder = buf;
- str = strstr(remainder, inc->delimiter->str);
+ const char *caption;
+ int channel_type, channel_sdi_nr;
+ void *channel;
+ int ret;
- while (str && max_columns) {
- if (n >= inc->first_column) {
- column = g_strndup(remainder, str - remainder);
- list = g_slist_prepend(list, g_strstrip(column));
+ inc = in->priv;
+ inc->column_seen_count = g_strv_length(column_texts);
- max_columns--;
- k++;
+ /* Split the input spec, count involved columns and channels. */
+ formats = g_strsplit(column_format, ",", 0);
+ if (!formats) {
+ sr_err("Cannot parse columns format %s (comma split).", column_format);
+ return SR_ERR_ARG;
+ }
+ format_count = g_strv_length(formats);
+ if (!format_count) {
+ sr_err("Cannot parse columns format %s (field count).", column_format);
+ g_strfreev(formats);
+ return SR_ERR_ARG;
+ }
+ column_count = logic_count = analog_count = 0;
+ auto_column_count = 0;
+ for (format_idx = 0; format_idx < format_count; format_idx++) {
+ format = formats[format_idx];
+ ret = split_column_format(format, &c, &f, &b);
+ sr_dbg("fmt %s -> %zu cols, %s fmt, %zu bits, rc %d", format, c, col_format_text[f], b, ret);
+ if (ret != SR_OK) {
+ sr_err("Cannot parse columns format %s (field split, %s).", column_format, format);
+ g_strfreev(formats);
+ return SR_ERR_ARG;
+ }
+ if (f && !c) {
+ /* User requested "auto-count", must be last format. */
+ if (formats[format_idx + 1]) {
+ sr_err("Auto column count must be last format field.");
+ g_strfreev(formats);
+ return SR_ERR_ARG;
+ }
+ auto_column_count = inc->column_seen_count - column_count;
+ c = auto_column_count;
+ }
+ column_count += c;
+ if (format_is_analog(f))
+ analog_count += c;
+ else if (format_is_logic(f))
+ logic_count += c * b;
+ }
+ sr_dbg("Column format %s -> %zu columns, %zu logic, %zu analog channels.",
+ column_format, column_count, logic_count, analog_count);
+
+ /* Allocate and fill in "column processing" details. */
+ inc->column_want_count = column_count;
+ if (inc->column_seen_count < inc->column_want_count) {
+ sr_err("Insufficient input text width for desired data amount, got %zu but want %zu columns.",
+ inc->column_seen_count, inc->column_want_count);
+ g_strfreev(formats);
+ return SR_ERR_ARG;
+ }
+ inc->logic_channels = logic_count;
+ inc->analog_channels = analog_count;
+ inc->analog_datafeed_digits = g_malloc0(inc->analog_channels * sizeof(inc->analog_datafeed_digits[0]));
+ inc->analog_datafeed_channels = g_malloc0(inc->analog_channels * sizeof(inc->analog_datafeed_channels[0]));
+ inc->column_details = g_malloc0_n(column_count, sizeof(inc->column_details[0]));
+ column_idx = channel_idx = analog_idx = 0;
+ channel_name = g_string_sized_new(64);
+ for (format_idx = 0; format_idx < format_count; format_idx++) {
+ /* Process a format field, which can span multiple columns. */
+ format = formats[format_idx];
+ (void)split_column_format(format, &c, &f, &b);
+ if (f && !c)
+ c = auto_column_count;
+ while (c-- > 0) {
+ /* Fill in a column's processing details. */
+ detail = &inc->column_details[column_idx++];
+ detail->col_nr = column_idx;
+ detail->text_format = f;
+ if (format_is_analog(detail->text_format)) {
+ detail->channel_offset = analog_idx;
+ detail->channel_count = 1;
+ detail->analog_digits = b;
+ analog_idx += detail->channel_count;
+ } else if (format_is_logic(detail->text_format)) {
+ detail->channel_offset = channel_idx;
+ detail->channel_count = b;
+ channel_idx += detail->channel_count;
+ } else if (format_is_ignore(detail->text_format)) {
+ /* EMPTY */
+ continue;
+ } else {
+ /*
+ * Neither logic nor analog data, nor ignore.
+ * Format was noted. No channel creation involved.
+ */
+ continue;
+ }
+ /*
+ * Pick most appropriate channel names. Optionally
+ * use text from a header line (when requested by the
+ * user). In the absence of header text, channels are
+ * assigned rather generic names.
+ *
+ * Manipulation of the column's caption (when a header
+ * line is seen) is acceptable, because this header
+ * line won't get processed another time.
+ */
+ column = column_texts[detail->col_nr - 1];
+ if (inc->use_header && column && *column)
+ caption = sr_scpi_unquote_string(column);
+ else
+ caption = NULL;
+ if (!caption || !*caption)
+ caption = NULL;
+ /*
+ * Collect channel creation details here, but defer
+ * actual creation of the channels such that all
+ * logic channels can get created first and analog
+ * channels only get created afterwards.
+ */
+ detail->channel_names = g_malloc0(detail->channel_count * sizeof(detail->channel_names[0]));
+ for (create_idx = 0; create_idx < detail->channel_count; create_idx++) {
+ if (caption && detail->channel_count == 1) {
+ g_string_assign(channel_name, caption);
+ } else if (caption) {
+ g_string_printf(channel_name, "%s[%zu]",
+ caption, create_idx);
+ } else {
+ g_string_printf(channel_name, "%zu",
+ detail->channel_offset + create_idx);
+ }
+ detail->channel_names[create_idx] = g_string_new_len(channel_name->str, channel_name->len);
+ }
}
-
- remainder = str + inc->delimiter->len;
- str = strstr(remainder, inc->delimiter->str);
- n++;
}
+ g_string_free(channel_name, TRUE);
+ g_strfreev(formats);
- if (buf[0] && max_columns && n >= inc->first_column) {
- column = g_strdup(remainder);
- list = g_slist_prepend(list, g_strstrip(column));
- k++;
+ /* Create channels in strict logic to analog order. */
+ channel_type = SR_CHANNEL_LOGIC;
+ for (column_idx = 0; column_idx < inc->column_want_count; column_idx++) {
+ detail = &inc->column_details[column_idx];
+ if (!format_is_logic(detail->text_format))
+ continue;
+ for (create_idx = 0; create_idx < detail->channel_count; create_idx++) {
+ caption = detail->channel_names[create_idx]->str;
+ channel_sdi_nr = g_slist_length(in->sdi->channels);
+ sr_channel_new(in->sdi, channel_sdi_nr, channel_type, TRUE, caption);
+ }
+ }
+ channel_type = SR_CHANNEL_ANALOG;
+ for (column_idx = 0; column_idx < inc->column_want_count; column_idx++) {
+ detail = &inc->column_details[column_idx];
+ if (!format_is_analog(detail->text_format))
+ continue;
+ caption = detail->channel_names[0]->str;
+ channel_sdi_nr = g_slist_length(in->sdi->channels);
+ channel = sr_channel_new(in->sdi, channel_sdi_nr, channel_type, TRUE, caption);
+ channel_idx = channel_sdi_nr - inc->logic_channels;
+ inc->analog_datafeed_digits[channel_idx] = detail->analog_digits;
+ inc->analog_datafeed_channels[channel_idx] = g_slist_append(NULL, channel);
}
- if (!(columns = g_try_new(char *, k + 1)))
+ return SR_OK;
+}
+
+static const struct column_details *lookup_column_details(struct context *inc, size_t nr)
+{
+ if (!inc || !inc->column_details)
+ return NULL;
+ if (!nr || nr > inc->column_want_count)
return NULL;
- columns[k--] = NULL;
+ return &inc->column_details[nr - 1];
+}
- for (l = list; l; l = l->next)
- columns[k--] = l->data;
+/*
+ * Primitive operations for text input: Strip comments off text lines.
+ * Split text lines into columns. Process input text for individual
+ * columns.
+ */
- g_slist_free(list);
+static void strip_comment(char *buf, const GString *prefix)
+{
+ char *ptr;
+
+ if (!prefix->len)
+ return;
- return columns;
+ if ((ptr = strstr(buf, prefix->str))) {
+ *ptr = '\0';
+ g_strstrip(buf);
+ }
}
-static int parse_multi_columns(char **columns, struct context *inc)
+/**
+ * Splits a text line into a set of columns.
+ *
+ * @param[in] buf The input text line to split.
+ * @param[in] inc The input module's context.
+ *
+ * @returns An array of strings, representing the columns' text.
+ *
+ * This routine splits a text line on previously determined separators.
+ */
+static char **split_line(char *buf, struct context *inc)
{
- gsize i;
- char *column;
+ return g_strsplit(buf, inc->delimiter->str, 0);
+}
- /* Clear buffer in order to set bits only. */
- memset(inc->sample_buffer, 0, inc->sample_unit_size);
+/**
+ * Parse a multi-bit field into several logic channels.
+ *
+ * @param[in] column The input text, a run of bin/hex/oct digits.
+ * @param[in] inc The input module's context.
+ * @param[in] details The column processing details.
+ *
+ * @retval SR_OK Success.
+ * @retval SR_ERR Invalid input data (empty, or format error).
+ *
+ * This routine modifies the logic levels in the current sample set,
+ * based on the text input and a user provided format spec.
+ */
+static int parse_logic(const char *column, struct context *inc,
+ const struct column_details *details)
+{
+ size_t length, ch_rem, ch_idx, ch_inc;
+ const char *rdptr;
+ char c;
+ gboolean valid;
+ const char *type_text;
+ uint8_t bits;
+
+ /*
+ * Prepare to read the digits from the text end towards the start.
+ * A digit corresponds to a variable number of channels (depending
+ * on the value's radix). Prepare the mapping of text digits to
+ * (a number of) logic channels.
+ */
+ length = strlen(column);
+ if (!length) {
+ sr_err("Column %zu in line %zu is empty.", details->col_nr,
+ inc->line_number);
+ return SR_ERR;
+ }
+ rdptr = &column[length];
+ ch_idx = details->channel_offset;
+ ch_rem = details->channel_count;
- for (i = 0; i < inc->num_channels; i++) {
- column = columns[i];
- if (column[0] == '1') {
- inc->sample_buffer[i / 8] |= (1 << (i % 8));
- } else if (!strlen(column)) {
- sr_err("Column %zu in line %zu is empty.",
- inc->first_channel + i, inc->line_number);
+ /*
+ * Get another digit and derive up to four logic channels' state from
+ * it. Make sure to not process more bits than the column has channels
+ * associated with it.
+ */
+ while (rdptr > column && ch_rem) {
+ /* Check for valid digits according to the input radix. */
+ c = *(--rdptr);
+ switch (details->text_format) {
+ case FORMAT_BIN:
+ valid = g_ascii_isxdigit(c) && c < '2';
+ ch_inc = 1;
+ break;
+ case FORMAT_OCT:
+ valid = g_ascii_isxdigit(c) && c < '8';
+ ch_inc = 3;
+ break;
+ case FORMAT_HEX:
+ valid = g_ascii_isxdigit(c);
+ ch_inc = 4;
+ break;
+ default:
+ valid = FALSE;
+ break;
+ }
+ if (!valid) {
+ type_text = col_format_text[details->text_format];
+ sr_err("Invalid text '%s' in %s type column %zu in line %zu.",
+ column, type_text, details->col_nr, inc->line_number);
return SR_ERR;
- } else if (column[0] != '0') {
- sr_err("Invalid value '%s' in column %zu in line %zu.",
- column, inc->first_channel + i,
- inc->line_number);
+ }
+ /* Use the digit's bits for logic channels' data. */
+ bits = g_ascii_xdigit_value(c);
+ switch (details->text_format) {
+ case FORMAT_HEX:
+ if (ch_rem >= 4) {
+ ch_rem--;
+ set_logic_level(inc, ch_idx + 3, bits & (1 << 3));
+ }
+ /* FALLTHROUGH */
+ case FORMAT_OCT:
+ if (ch_rem >= 3) {
+ ch_rem--;
+ set_logic_level(inc, ch_idx + 2, bits & (1 << 2));
+ }
+ if (ch_rem >= 2) {
+ ch_rem--;
+ set_logic_level(inc, ch_idx + 1, bits & (1 << 1));
+ }
+ /* FALLTHROUGH */
+ case FORMAT_BIN:
+ ch_rem--;
+ set_logic_level(inc, ch_idx + 0, bits & (1 << 0));
+ break;
+ default:
+ /* ShouldNotHappen(TM), but silences compiler warning. */
return SR_ERR;
}
+ ch_idx += ch_inc;
}
+ /*
+ * TODO Determine whether the availability of extra input data
+ * for unhandled logic channels is worth warning here. In this
+ * implementation users are in control, and can have the more
+ * significant bits ignored (which can be considered a feature
+ * and not really a limitation).
+ */
return SR_OK;
}
-static int parse_single_column(const char *column, struct context *inc)
+/**
+ * Parse a floating point text into an analog value.
+ *
+ * @param[in] column The input text, a floating point number.
+ * @param[in] inc The input module's context.
+ * @param[in] details The column processing details.
+ *
+ * @retval SR_OK Success.
+ * @retval SR_ERR Invalid input data (empty, or format error).
+ *
+ * This routine modifies the analog values in the current sample set,
+ * based on the text input and a user provided format spec.
+ */
+static int parse_analog(const char *column, struct context *inc,
+ const struct column_details *details)
{
- int res;
+ size_t length;
+ double dvalue; float fvalue;
+ csv_analog_t value;
+ int ret;
- res = SR_ERR;
+ if (!format_is_analog(details->text_format))
+ return SR_ERR_BUG;
- switch (inc->format) {
- case FORMAT_BIN:
- res = parse_binstr(column, inc);
- break;
- case FORMAT_HEX:
- res = parse_hexstr(column, inc);
- break;
- case FORMAT_OCT:
- res = parse_octstr(column, inc);
- break;
+ length = strlen(column);
+ if (!length) {
+ sr_err("Column %zu in line %zu is empty.", details->col_nr,
+ inc->line_number);
+ return SR_ERR;
}
+ if (sizeof(value) == sizeof(double)) {
+ ret = sr_atod_ascii(column, &dvalue);
+ value = dvalue;
+ } else if (sizeof(value) == sizeof(float)) {
+ ret = sr_atof_ascii(column, &fvalue);
+ value = fvalue;
+ } else {
+ ret = SR_ERR_BUG;
+ }
+ if (ret != SR_OK) {
+ sr_err("Cannot parse analog text %s in column %zu in line %zu.",
+ column, details->col_nr, inc->line_number);
+ return SR_ERR_DATA;
+ }
+ set_analog_value(inc, details->channel_offset, value);
- return res;
+ return SR_OK;
}
-static int flush_samples(const struct sr_input *in)
+/**
+ * Parse a timestamp text, auto-determine samplerate.
+ *
+ * @param[in] column The input text, a floating point number.
+ * @param[in] inc The input module's context.
+ * @param[in] details The column processing details.
+ *
+ * @retval SR_OK Success.
+ * @retval SR_ERR Invalid input data (empty, or format error).
+ *
+ * This routine attempts to automatically determine the input data's
+ * samplerate from text rows' timestamp values. Only simple formats are
+ * supported, user provided values always take precedence.
+ */
+static int parse_timestamp(const char *column, struct context *inc,
+ const struct column_details *details)
{
- struct context *inc;
- struct sr_datafeed_packet packet;
- struct sr_datafeed_logic logic;
- int rc;
+ double ts, rate;
+ int ret;
- inc = in->priv;
- if (!inc->datafeed_buf_fill)
+ if (!format_is_timestamp(details->text_format))
+ return SR_ERR_BUG;
+
+ /*
+ * Implementor's notes on timestamp interpretation. Use a simple
+ * approach for improved maintainability which covers most cases
+ * of input data. There is not much gain in adding complexity,
+ * users can easily provide the rate when auto-detection fails.
+ * - Bail out if samplerate is known already.
+ * - Try to interpret the timestamp (simple float conversion).
+ * If conversion fails then clear all previous knowledge and
+ * bail out (non-fatal, perhaps warn). Silently ignore values
+ * of zero since those could be silent fails -- assume that
+ * genuine data contains at least two adjacent rows with useful
+ * timestamps for the feature to work reliably. Annoying users
+ * with "failed to detect" messages is acceptable here, since
+ * users expecting the feature to work should provide useful
+ * data, and there are easy ways to disable the detection or
+ * ignore the column.
+ * - If there is no previous timestamp, keep the current value
+ * for later reference and bail out.
+ * - If a previous timestamp was seen, determine the difference
+ * between them, and derive the samplerate. Update internal
+ * state (the value automatically gets sent to the datafeed),
+ * and clear previous knowledge. Subsequent calls will ignore
+ * following input data (see above, rate is known).
+ *
+ * TODO Potential future improvements:
+ * - Prefer rationals over floats for improved precision and
+ * reduced rounding errors which result in odd rates.
+ * - Support other formats ("2 ms" or similar)?
+ */
+ if (inc->calc_samplerate)
+ return SR_OK;
+ ret = sr_atod_ascii(column, &ts);
+ if (ret != SR_OK)
+ ts = 0.0;
+ if (!ts) {
+ sr_info("Cannot convert timestamp text %s in line %zu (or zero value).",
+ column, inc->line_number);
+ inc->prev_timestamp = 0.0;
return SR_OK;
+ }
+ if (!inc->prev_timestamp) {
+ sr_dbg("First timestamp value %g in line %zu.",
+ ts, inc->line_number);
+ inc->prev_timestamp = ts;
+ return SR_OK;
+ }
+ sr_dbg("Second timestamp value %g in line %zu.", ts, inc->line_number);
+ ts -= inc->prev_timestamp;
+ sr_dbg("Timestamp difference %g in line %zu.",
+ ts, inc->line_number);
+ if (!ts) {
+ sr_warn("Zero timestamp difference in line %zu.",
+ inc->line_number);
+ inc->prev_timestamp = ts;
+ return SR_OK;
+ }
+ rate = 1.0 / ts;
+ rate += 0.5;
+ rate = (uint64_t)rate;
+ sr_dbg("Rate from timestamp %g in line %zu.", rate, inc->line_number);
+ inc->calc_samplerate = rate;
+ inc->prev_timestamp = 0.0;
- memset(&packet, 0, sizeof(packet));
- memset(&logic, 0, sizeof(logic));
- packet.type = SR_DF_LOGIC;
- packet.payload = &logic;
- logic.unitsize = inc->sample_unit_size;
- logic.length = inc->datafeed_buf_fill;
- logic.data = inc->datafeed_buffer;
+ return SR_OK;
+}
- rc = sr_session_send(in->sdi, &packet);
- if (rc != SR_OK)
- return rc;
+/**
+ * Parse routine which ignores the input text.
+ *
+ * This routine exists to unify dispatch code paths, mapping input file
+ * columns' data types to their respective parse routines.
+ */
+static int parse_ignore(const char *column, struct context *inc,
+ const struct column_details *details)
+{
+ (void)column;
+ (void)inc;
+ (void)details;
- inc->datafeed_buf_fill = 0;
return SR_OK;
}
-static int queue_samples(const struct sr_input *in)
-{
- struct context *inc;
- int rc;
+typedef int (*col_parse_cb)(const char *column, struct context *inc,
+ const struct column_details *details);
- inc = in->priv;
+static const col_parse_cb col_parse_funcs[] = {
+ [FORMAT_NONE] = parse_ignore,
+ [FORMAT_BIN] = parse_logic,
+ [FORMAT_OCT] = parse_logic,
+ [FORMAT_HEX] = parse_logic,
+ [FORMAT_ANALOG] = parse_analog,
+ [FORMAT_TIME] = parse_timestamp,
+};
- inc->datafeed_buf_fill += inc->sample_unit_size;
- if (inc->datafeed_buf_fill == inc->datafeed_buf_size) {
- rc = flush_samples(in);
- if (rc != SR_OK)
- return rc;
+/*
+ * BEWARE! Implementor's notes. Sync with feature set and default option
+ * values required during maintenance of the input module implementation.
+ *
+ * When applications invoke .format_match() routines, trying automatic
+ * determination of an input file's format handler, then no options are
+ * in effect. Because specifying options requires selection of an input
+ * module to pass the options to, which obsoletes the format-match check.
+ *
+ * Which means that we only need to deal with the default format here,
+ * which happens to be the simple multi-column format without header
+ * lines or leading garbage. Which means that the check can be rather
+ * strict, resulting in high levels of confidence upon match, never
+ * "accidently" winning for unreadable or unsupported-by-default formats.
+ *
+ * This .format_match() logic only needs to become more involved when
+ * default option values change, or when automatic detection of column
+ * data types improves. Then the supported-by-default types of input
+ * data must be considered acceptable here in the format-match check
+ * as well.
+ *
+ * Notice that the format check cannot re-use regular processing logic
+ * when their implementation assumes proper input data and wll generate
+ * diagnostics for unexpected input data. Failure to match the format is
+ * non-fatal here, mismatch must remain silent. It's up to applications
+ * how large a chunk of data gets passed here (start of the file's
+ * content). But inspection of the first few hundred bytes will usually
+ * be GoodEnough(TM) for the format-match purpose. Notice that filenames
+ * need not necessarily be available to the format-match routine.
+ *
+ * This implementation errs on the safe side. Users can always select
+ * the CSV input module when automatic format detection fails.
+ */
+static int format_match(GHashTable *metadata, unsigned int *confidence)
+{
+ const int match_confidence = 100;
+ const char *default_extension = ".csv";
+ const char *line_termination = "\n";
+ const char *comment_leader = ";";
+ const char *column_separator = ",";
+ const char *binary_charset = "01";
+
+ const char *fn;
+ GString *buf;
+ size_t fn_len;
+ GString *tmpbuf;
+ gboolean status;
+ size_t line_idx, col_idx;
+ char *rdptr, **lines, *line;
+ char **cols, *col;
+
+ /* Get the application provided input data properties. */
+ fn = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_FILENAME));
+ buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
+
+ /* Filenames are a strong hint. Use then when available. */
+ if (fn && *fn && (fn_len = strlen(fn)) >= strlen(default_extension)) {
+ if (strcasecmp(&fn[fn_len - strlen(default_extension)], default_extension) == 0) {
+ *confidence = 10;
+ return SR_OK;
+ }
}
- inc->sample_buffer = &inc->datafeed_buffer[inc->datafeed_buf_fill];
+
+ /*
+ * Check file content for compatibility with the input module's
+ * default format. Which translates to:
+ * - Must be at least one text line worth of input data. Ignore
+ * incomplete lines at the end of the available buffer.
+ * - Must be LF terminated text lines, optional CR-LF sequence.
+ * (Drop CR-only for simplicity since that's rare and users
+ * can override the automatic detection.)
+ * - Strip comments and skip empty lines.
+ * - Data lines must be binary input (potentially multiple bits
+ * per column which then get ignored). Presence of comma is
+ * optional but then must be followed by another data column.
+ * - No other content is acceptable, there neither are ignored
+ * columns nor analog data nor timestamps in the default layout.
+ * (See the above "sync format match with default options"
+ * comment though during maintenance!)
+ * Run the check on a copy to not affect the caller's buffer.
+ */
+ if (!buf || !buf->len || !buf->str || !*buf->str)
+ return SR_ERR;
+ rdptr = g_strstr_len(buf->str, buf->len, line_termination);
+ if (!rdptr)
+ return SR_ERR;
+ tmpbuf = g_string_new_len(buf->str, rdptr + 1 - buf->str);
+ tmpbuf->str[tmpbuf->len - 1] = '\0';
+ status = TRUE;
+ *confidence = match_confidence;
+ lines = g_strsplit(tmpbuf->str, line_termination, 0);
+ for (line_idx = 0; status && (line = lines[line_idx]); line_idx++) {
+ rdptr = strstr(line, comment_leader);
+ if (rdptr)
+ *rdptr = '\0';
+ line = g_strstrip(line);
+ if (!line || !*line)
+ continue;
+ cols = g_strsplit(line, column_separator, 0);
+ if (!cols) {
+ status = FALSE;
+ break;
+ }
+ for (col_idx = 0; status && (col = cols[col_idx]); col_idx++) {
+ if (strspn(col, binary_charset) != strlen(col)) {
+ status = FALSE;
+ break;
+ }
+ }
+ g_strfreev(cols);
+ }
+ g_strfreev(lines);
+ g_string_free(tmpbuf, TRUE);
+
+ if (!status)
+ return SR_ERR;
+
return SR_OK;
}
static int init(struct sr_input *in, GHashTable *options)
{
struct context *inc;
+ size_t single_column, first_column, logic_channels;
const char *s;
+ enum single_col_format format;
+ char format_char;
- in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
- in->priv = inc = g_malloc0(sizeof(struct context));
-
- inc->single_column = g_variant_get_int32(g_hash_table_lookup(options, "single-column"));
- inc->multi_column_mode = inc->single_column == 0;
-
- inc->num_channels = g_variant_get_int32(g_hash_table_lookup(options, "numchannels"));
+ in->sdi = g_malloc0(sizeof(*in->sdi));
+ in->priv = inc = g_malloc0(sizeof(*inc));
+ single_column = g_variant_get_uint32(g_hash_table_lookup(options, "single_column"));
+ logic_channels = g_variant_get_uint32(g_hash_table_lookup(options, "logic_channels"));
inc->delimiter = g_string_new(g_variant_get_string(
- g_hash_table_lookup(options, "delimiter"), NULL));
- if (inc->delimiter->len == 0) {
- sr_err("Delimiter must be at least one character.");
+ g_hash_table_lookup(options, "column_separator"), NULL));
+ if (!inc->delimiter->len) {
+ sr_err("Column separator cannot be empty.");
return SR_ERR_ARG;
}
-
- s = g_variant_get_string(g_hash_table_lookup(options, "format"), NULL);
- if (!g_ascii_strncasecmp(s, "bin", 3)) {
- inc->format = FORMAT_BIN;
- } else if (!g_ascii_strncasecmp(s, "hex", 3)) {
- inc->format = FORMAT_HEX;
- } else if (!g_ascii_strncasecmp(s, "oct", 3)) {
- inc->format = FORMAT_OCT;
+ s = g_variant_get_string(g_hash_table_lookup(options, "single_format"), NULL);
+ if (g_ascii_strncasecmp(s, "bin", 3) == 0) {
+ format = FORMAT_BIN;
+ } else if (g_ascii_strncasecmp(s, "hex", 3) == 0) {
+ format = FORMAT_HEX;
+ } else if (g_ascii_strncasecmp(s, "oct", 3) == 0) {
+ format = FORMAT_OCT;
} else {
- sr_err("Invalid format: '%s'", s);
+ sr_err("Invalid single-column format: '%s'", s);
return SR_ERR_ARG;
}
-
inc->comment = g_string_new(g_variant_get_string(
- g_hash_table_lookup(options, "comment"), NULL));
+ g_hash_table_lookup(options, "comment_leader"), NULL));
if (g_string_equal(inc->comment, inc->delimiter)) {
- /* That's never going to work. Likely the result of the user
- * setting the delimiter to ; -- the default comment. Clearing
- * the comment setting will work in that case. */
+ /*
+ * Using the same sequence as comment leader and column
+ * separator won't work. The user probably specified ';'
+ * as the column separator but did not adjust the comment
+ * leader. Try DWIM, drop comment strippin support here.
+ */
+ sr_warn("Comment leader and column separator conflict, disabling comment support.");
g_string_truncate(inc->comment, 0);
}
-
inc->samplerate = g_variant_get_uint64(g_hash_table_lookup(options, "samplerate"));
-
- inc->first_channel = g_variant_get_int32(g_hash_table_lookup(options, "first-channel"));
-
- inc->header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
-
- inc->start_line = g_variant_get_int32(g_hash_table_lookup(options, "startline"));
+ first_column = g_variant_get_uint32(g_hash_table_lookup(options, "first_column"));
+ inc->use_header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
+ inc->start_line = g_variant_get_uint32(g_hash_table_lookup(options, "start_line"));
if (inc->start_line < 1) {
sr_err("Invalid start line %zu.", inc->start_line);
return SR_ERR_ARG;
}
- if (inc->multi_column_mode)
- inc->first_column = inc->first_channel;
- else
- inc->first_column = inc->single_column;
-
- if (!inc->multi_column_mode && !inc->num_channels) {
- sr_err("Number of channels needs to be specified in single column mode.");
- return SR_ERR_ARG;
+ /*
+ * Scan flexible, to get prefered format specs which describe
+ * the input file's data formats. As well as some simple specs
+ * for backwards compatibility and user convenience.
+ *
+ * This logic ends up with a copy of the format string, either
+ * user provided or internally derived. Actual creation of the
+ * column processing details gets deferred until the first line
+ * of input data was seen. To support automatic determination of
+ * e.g. channel counts from column counts.
+ */
+ s = g_variant_get_string(g_hash_table_lookup(options, "column_formats"), NULL);
+ if (s && *s) {
+ inc->column_formats = g_strdup(s);
+ sr_dbg("User specified column_formats: %s.", s);
+ } else if (single_column && logic_channels) {
+ format_char = col_format_char[format];
+ if (single_column == 1) {
+ inc->column_formats = g_strdup_printf("%c%zu",
+ format_char, logic_channels);
+ } else {
+ inc->column_formats = g_strdup_printf("%zu-,%c%zu",
+ single_column - 1,
+ format_char, logic_channels);
+ }
+ sr_dbg("Backwards compat single_column, col %zu, fmt %s, bits %zu -> %s.",
+ single_column, col_format_text[format], logic_channels,
+ inc->column_formats);
+ } else if (!single_column) {
+ if (first_column > 1) {
+ inc->column_formats = g_strdup_printf("%zu-,%zul",
+ first_column - 1, logic_channels);
+ } else {
+ inc->column_formats = g_strdup_printf("%zul",
+ logic_channels);
+ }
+ sr_dbg("Backwards compat multi-column, col %zu, chans %zu -> %s.",
+ first_column, logic_channels,
+ inc->column_formats);
+ } else {
+ sr_warn("Unknown or unsupported columns layout spec, assuming simple multi-column mode.");
+ inc->column_formats = g_strdup("*l");
}
return SR_OK;
}
+/*
+ * Check the channel list for consistency across file re-import. See
+ * the VCD input module for more details and motivation.
+ */
+static void release_df_channels(struct context *inc, GSList **l)
+{
+ size_t idx;
+
+ if (!inc->analog_channels || !l)
+ return;
+ for (idx = 0; idx < inc->analog_channels; idx++)
+ g_slist_free(l[idx]);
+ g_free(l);
+}
+
+static void keep_header_for_reread(const struct sr_input *in)
+{
+ struct context *inc;
+
+ inc = in->priv;
+
+ g_slist_free_full(inc->prev_sr_channels, sr_channel_free_cb);
+ inc->prev_sr_channels = in->sdi->channels;
+ in->sdi->channels = NULL;
+
+ release_df_channels(inc, inc->prev_df_channels);
+ inc->prev_df_channels = inc->analog_datafeed_channels;
+ inc->analog_datafeed_channels = NULL;
+}
+
+static int check_header_in_reread(const struct sr_input *in)
+{
+ struct context *inc;
+
+ if (!in)
+ return FALSE;
+ inc = in->priv;
+ if (!inc)
+ return FALSE;
+ if (!inc->prev_sr_channels)
+ return TRUE;
+
+ if (sr_channel_lists_differ(inc->prev_sr_channels, in->sdi->channels)) {
+ sr_err("Channel list change not supported for file re-read.");
+ return FALSE;
+ }
+
+ g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
+ in->sdi->channels = inc->prev_sr_channels;
+ inc->prev_sr_channels = NULL;
+
+ release_df_channels(inc, inc->analog_datafeed_channels);
+ inc->analog_datafeed_channels = inc->prev_df_channels;
+ inc->prev_df_channels = NULL;
+
+ return TRUE;
+}
+
static const char *delim_set = "\r\n";
static const char *get_line_termination(GString *buf)
static int initial_parse(const struct sr_input *in, GString *buf)
{
struct context *inc;
- GString *channel_name;
- unsigned int num_columns, i;
- size_t line_number, l;
+ size_t num_columns;
+ size_t line_number, line_idx;
int ret;
- char **lines, *line, **columns, *column;
+ char **lines, *line, **columns;
ret = SR_OK;
inc = in->priv;
columns = NULL;
+ /* Search for the first line to process (header or data). */
line_number = 0;
- lines = g_strsplit_set(buf->str, delim_set, 0);
- for (l = 0; lines[l]; l++) {
+ if (inc->termination)
+ lines = g_strsplit(buf->str, inc->termination, 0);
+ else
+ lines = g_strsplit_set(buf->str, delim_set, 0);
+ for (line_idx = 0; (line = lines[line_idx]); line_idx++) {
line_number++;
- line = lines[l];
if (inc->start_line > line_number) {
- sr_spew("Line %zu skipped.", line_number);
+ sr_spew("Line %zu skipped (before start).", line_number);
continue;
}
if (line[0] == '\0') {
/* Reached first proper line. */
break;
}
- if (!lines[l]) {
+ if (!line) {
/* Not enough data for a proper line yet. */
ret = SR_ERR_NA;
goto out;
}
- /*
- * In order to determine the number of columns parse the current line
- * without limiting the number of columns.
- */
- columns = parse_line(line, inc, -1);
+ /* Get the number of columns in the line. */
+ columns = split_line(line, inc);
if (!columns) {
sr_err("Error while parsing line %zu.", line_number);
ret = SR_ERR;
goto out;
}
num_columns = g_strv_length(columns);
-
- /* Ensure that the first column is not out of bounds. */
if (!num_columns) {
- sr_err("Column %u in line %zu is out of bounds.",
- inc->first_column, line_number);
+ sr_err("Error while parsing line %zu.", line_number);
ret = SR_ERR;
goto out;
}
+ sr_dbg("Got %zu columns in text line: %s.", num_columns, line);
- if (inc->multi_column_mode) {
- /*
- * Detect the number of channels in multi column mode
- * automatically if not specified.
- */
- if (!inc->num_channels) {
- inc->num_channels = num_columns;
- sr_dbg("Number of auto-detected channels: %u.",
- inc->num_channels);
- }
-
- /*
- * Ensure that the number of channels does not exceed the number
- * of columns in multi column mode.
- */
- if (num_columns < inc->num_channels) {
- sr_err("Not enough columns for desired number of channels in line %zu.",
- line_number);
- ret = SR_ERR;
- goto out;
- }
+ /*
+ * Interpret the user provided column format specs. This might
+ * involve inspection of the now received input text, to support
+ * e.g. automatic detection of channel counts in the absence of
+ * user provided specs. Optionally a header line is used to get
+ * channels' names.
+ *
+ * Check the then created channels for consistency across .reset
+ * and .receive sequences (file re-load).
+ */
+ ret = make_column_details_from_format(in, inc->column_formats, columns);
+ if (ret != SR_OK) {
+ sr_err("Cannot parse columns format using line %zu.", line_number);
+ goto out;
}
-
- channel_name = g_string_sized_new(64);
- for (i = 0; i < inc->num_channels; i++) {
- column = columns[i];
- if (inc->header && inc->multi_column_mode && column[0] != '\0')
- g_string_assign(channel_name, column);
- else
- g_string_printf(channel_name, "%u", i);
- sr_channel_new(in->sdi, i, SR_CHANNEL_LOGIC, TRUE, channel_name->str);
+ if (!check_header_in_reread(in)) {
+ ret = SR_ERR_DATA;
+ goto out;
}
- g_string_free(channel_name, TRUE);
/*
+ * Allocate buffer memory for datafeed submission of sample data.
* Calculate the minimum buffer size to store the set of samples
* of all channels (unit size). Determine a larger buffer size
* for datafeed submission that is a multiple of the unit size.
- * Allocate the larger buffer, and have the "sample buffer" point
- * to a location within that large buffer.
+ * Allocate the larger buffer, the "sample buffer" will point
+ * to a location within that large buffer later.
+ *
+ * TODO Move channel creation here, and just store required
+ * parameters in the format parser above? Could simplify the
+ * arrangement that logic and analog channels get created in
+ * strict sequence in their respective groups.
*/
- inc->sample_unit_size = (inc->num_channels + 7) / 8;
- inc->datafeed_buf_size = CHUNK_SIZE;
- inc->datafeed_buf_size *= inc->sample_unit_size;
- inc->datafeed_buffer = g_malloc(inc->datafeed_buf_size);
- inc->datafeed_buf_fill = 0;
- inc->sample_buffer = &inc->datafeed_buffer[inc->datafeed_buf_fill];
+ if (inc->logic_channels) {
+ inc->sample_unit_size = (inc->logic_channels + 7) / 8;
+ inc->datafeed_buf_size = CHUNK_SIZE;
+ inc->datafeed_buf_size *= inc->sample_unit_size;
+ inc->datafeed_buffer = g_malloc(inc->datafeed_buf_size);
+ if (!inc->datafeed_buffer) {
+ sr_err("Cannot allocate datafeed send buffer (logic).");
+ ret = SR_ERR_MALLOC;
+ goto out;
+ }
+ inc->datafeed_buf_fill = 0;
+ }
+
+ if (inc->analog_channels) {
+ size_t sample_size, sample_count;
+ sample_size = sizeof(inc->analog_datafeed_buffer[0]);
+ inc->analog_datafeed_buf_size = CHUNK_SIZE;
+ inc->analog_datafeed_buf_size /= sample_size;
+ inc->analog_datafeed_buf_size /= inc->analog_channels;
+ sample_count = inc->analog_channels * inc->analog_datafeed_buf_size;
+ inc->analog_datafeed_buffer = g_malloc0(sample_count * sample_size);
+ if (!inc->analog_datafeed_buffer) {
+ sr_err("Cannot allocate datafeed send buffer (analog).");
+ ret = SR_ERR_MALLOC;
+ goto out;
+ }
+ inc->analog_datafeed_buf_fill = 0;
+ }
out:
if (columns)
static int process_buffer(struct sr_input *in, gboolean is_eof)
{
- struct sr_datafeed_packet packet;
- struct sr_datafeed_meta meta;
- struct sr_config *src;
struct context *inc;
gsize num_columns;
- uint64_t samplerate;
- int max_columns, ret, l;
- char *p, **lines, *line, **columns;
+ size_t line_idx, col_idx, col_nr;
+ const struct column_details *details;
+ col_parse_cb parse_func;
+ int ret;
+ char *processed_up_to;
+ char **lines, *line, **columns, *column;
inc = in->priv;
if (!inc->started) {
std_session_send_df_header(in->sdi);
-
- if (inc->samplerate) {
- packet.type = SR_DF_META;
- packet.payload = &meta;
- samplerate = inc->samplerate;
- src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(samplerate));
- meta.config = g_slist_append(NULL, src);
- sr_session_send(in->sdi, &packet);
- g_slist_free(meta.config);
- sr_config_free(src);
- }
-
inc->started = TRUE;
}
- /* Limit the number of columns to parse. */
- if (inc->multi_column_mode)
- max_columns = inc->num_channels;
- else
- max_columns = 1;
-
/*
* Consider empty input non-fatal. Keep accumulating input until
* at least one full text line has become available. Grab the
if (!in->buf->len)
return SR_OK;
if (is_eof) {
- p = in->buf->str + in->buf->len;
+ processed_up_to = in->buf->str + in->buf->len;
} else {
- p = g_strrstr_len(in->buf->str, in->buf->len, inc->termination);
- if (!p)
- return SR_ERR;
- *p = '\0';
- p += strlen(inc->termination);
+ processed_up_to = g_strrstr_len(in->buf->str, in->buf->len,
+ inc->termination);
+ if (!processed_up_to)
+ return SR_OK;
+ *processed_up_to = '\0';
+ processed_up_to += strlen(inc->termination);
}
- g_strstrip(in->buf->str);
+ /* Split input text lines and process their columns. */
ret = SR_OK;
- lines = g_strsplit_set(in->buf->str, delim_set, 0);
- for (l = 0; lines[l]; l++) {
+ lines = g_strsplit(in->buf->str, inc->termination, 0);
+ for (line_idx = 0; (line = lines[line_idx]); line_idx++) {
inc->line_number++;
- line = lines[l];
+ if (inc->line_number < inc->start_line) {
+ sr_spew("Line %zu skipped (before start).", inc->line_number);
+ continue;
+ }
if (line[0] == '\0') {
sr_spew("Blank line %zu skipped.", inc->line_number);
continue;
}
/* Skip the header line, its content was used as the channel names. */
- if (inc->header) {
+ if (inc->use_header && !inc->header_seen) {
sr_spew("Header line %zu skipped.", inc->line_number);
- inc->header = FALSE;
+ inc->header_seen = TRUE;
continue;
}
- columns = parse_line(line, inc, max_columns);
+ /* Split the line into columns, check for minimum length. */
+ columns = split_line(line, inc);
if (!columns) {
sr_err("Error while parsing line %zu.", inc->line_number);
g_strfreev(lines);
return SR_ERR;
}
num_columns = g_strv_length(columns);
- if (!num_columns) {
- sr_err("Column %u in line %zu is out of bounds.",
- inc->first_column, inc->line_number);
- g_strfreev(columns);
- g_strfreev(lines);
- return SR_ERR;
- }
- /*
- * Ensure that the number of channels does not exceed the number
- * of columns in multi column mode.
- */
- if (inc->multi_column_mode && num_columns < inc->num_channels) {
- sr_err("Not enough columns for desired number of channels in line %zu.",
- inc->line_number);
+ if (num_columns < inc->column_want_count) {
+ sr_err("Insufficient column count %zu in line %zu.",
+ num_columns, inc->line_number);
g_strfreev(columns);
g_strfreev(lines);
return SR_ERR;
}
- if (inc->multi_column_mode)
- ret = parse_multi_columns(columns, inc);
- else
- ret = parse_single_column(columns[0], inc);
- if (ret != SR_OK) {
- g_strfreev(columns);
- g_strfreev(lines);
- return SR_ERR;
+ /* Have the columns of the current text line processed. */
+ clear_logic_samples(inc);
+ clear_analog_samples(inc);
+ for (col_idx = 0; col_idx < inc->column_want_count; col_idx++) {
+ column = columns[col_idx];
+ col_nr = col_idx + 1;
+ details = lookup_column_details(inc, col_nr);
+ if (!details || !details->text_format)
+ continue;
+ parse_func = col_parse_funcs[details->text_format];
+ if (!parse_func)
+ continue;
+ ret = parse_func(column, inc, details);
+ if (ret != SR_OK) {
+ g_strfreev(columns);
+ g_strfreev(lines);
+ return SR_ERR;
+ }
}
- /* Send sample data to the session bus. */
- ret = queue_samples(in);
+ /* Send sample data to the session bus (buffered). */
+ ret = queue_logic_samples(in);
+ ret += queue_analog_samples(in);
if (ret != SR_OK) {
sr_err("Sending samples failed.");
g_strfreev(columns);
g_strfreev(columns);
}
g_strfreev(lines);
- g_string_erase(in->buf, 0, p - in->buf->str);
+ g_string_erase(in->buf, 0, processed_up_to - in->buf->str);
return ret;
}
g_string_append_len(in->buf, buf->str, buf->len);
inc = in->priv;
- if (!inc->termination) {
+ if (!inc->column_seen_count) {
ret = initial_receive(in);
if (ret == SR_ERR_NA)
/* Not enough data yet. */
if (ret != SR_OK)
return ret;
- ret = flush_samples(in);
+ ret = flush_logic_samples(in);
+ ret += flush_analog_samples(in);
if (ret != SR_OK)
return ret;
static void cleanup(struct sr_input *in)
{
- struct context *inc;
+ struct context *inc, save_ctx;
- inc = in->priv;
+ /* Keep channel references between file re-imports. */
+ keep_header_for_reread(in);
- if (inc->delimiter)
- g_string_free(inc->delimiter, TRUE);
-
- if (inc->comment)
- g_string_free(inc->comment, TRUE);
+ /* Release dynamically allocated resources. */
+ inc = in->priv;
g_free(inc->termination);
+ inc->termination = NULL;
g_free(inc->datafeed_buffer);
+ inc->datafeed_buffer = NULL;
+ g_free(inc->analog_datafeed_buffer);
+ inc->analog_datafeed_buffer = NULL;
+ g_free(inc->analog_datafeed_digits);
+ inc->analog_datafeed_digits = NULL;
+ /* analog_datafeed_channels was released in keep_header_for_reread() */
+ /* TODO Release channel names (before releasing details). */
+ g_free(inc->column_details);
+ inc->column_details = NULL;
+
+ /* Clear internal state, but keep what .init() has provided. */
+ save_ctx = *inc;
+ memset(inc, 0, sizeof(*inc));
+ inc->samplerate = save_ctx.samplerate;
+ inc->delimiter = save_ctx.delimiter;
+ inc->comment = save_ctx.comment;
+ inc->column_formats = save_ctx.column_formats;
+ inc->start_line = save_ctx.start_line;
+ inc->use_header = save_ctx.use_header;
+ inc->prev_sr_channels = save_ctx.prev_sr_channels;
+ inc->prev_df_channels = save_ctx.prev_df_channels;
}
static int reset(struct sr_input *in)
{
- struct context *inc = in->priv;
+ struct context *inc;
+ inc = in->priv;
cleanup(in);
inc->started = FALSE;
g_string_truncate(in->buf, 0);
return SR_OK;
}
+enum option_index {
+ OPT_COL_FMTS,
+ OPT_SINGLE_COL,
+ OPT_FIRST_COL,
+ OPT_NUM_LOGIC,
+ OPT_SINGLE_FMT,
+ OPT_START_LINE,
+ OPT_HEADER,
+ OPT_SAMPLERATE,
+ OPT_COL_SEP,
+ OPT_COMMENT,
+ OPT_MAX,
+};
+
static struct sr_option options[] = {
- { "single-column", "Single column", "Enable single-column mode, using the specified column (>= 1); 0: multi-col. mode", NULL, NULL },
- { "numchannels", "Number of logic channels", "The number of (logic) channels (single-col. mode: number of bits beginning at 'first channel', LSB-first)", NULL, NULL },
- { "delimiter", "Column delimiter", "The column delimiter (>= 1 characters)", NULL, NULL },
- { "format", "Data format (single-col. mode)", "The numeric format of the data (single-col. mode): bin, hex, oct", NULL, NULL },
- { "comment", "Comment character(s)", "The comment prefix character(s)", NULL, NULL },
- { "samplerate", "Samplerate (Hz)", "The sample rate (used during capture) in Hz", NULL, NULL },
- { "first-channel", "First channel", "The column number of the first channel (multi-col. mode); bit position for the first channel (single-col. mode)", NULL, NULL },
- { "header", "Interpret first line as header (multi-col. mode)", "Treat the first line as header with channel names (multi-col. mode)", NULL, NULL },
- { "startline", "Start line", "The line number at which to start processing samples (>= 1)", NULL, NULL },
- ALL_ZERO
+ [OPT_COL_FMTS] = {
+ "column_formats", "Column format specs",
+ "Text columns data types. A comma separated list of [<cols>]<fmt>[<bits>] items. * for all remaining columns. - ignores columns, x/o/b/l logic data, a (and digits) analog data, t timestamps.",
+ NULL, NULL,
+ },
+ [OPT_SINGLE_COL] = {
+ "single_column", "Single column",
+ "Simple single-column mode, exclusively use text from the specified column (number starting at 1). Obsoleted by 'column_formats=4-,x16'.",
+ NULL, NULL,
+ },
+ [OPT_FIRST_COL] = {
+ "first_column", "First column",
+ "First column with logic data in simple multi-column mode (number starting at 1, default 1). Obsoleted by 'column_formats=4-,*l'.",
+ NULL, NULL,
+ },
+ [OPT_NUM_LOGIC] = {
+ "logic_channels", "Number of logic channels",
+ "Logic channel count, required in simple single-column mode, defaults to \"all remaining columns\" in simple multi-column mode. Obsoleted by 'column_formats=8l'.",
+ NULL, NULL,
+ },
+ [OPT_SINGLE_FMT] = {
+ "single_format", "Data format for simple single-column mode.",
+ "The input text number format of simple single-column mode: bin, hex, oct. Obsoleted by 'column_formats=x8'.",
+ NULL, NULL,
+ },
+ [OPT_START_LINE] = {
+ "start_line", "Start line",
+ "The line number at which to start processing input text (default: 1).",
+ NULL, NULL,
+ },
+ [OPT_HEADER] = {
+ "header", "Get channel names from first line.",
+ "Use the first processed line's column captions (when available) as channel names. Enabled by default.",
+ NULL, NULL,
+ },
+ [OPT_SAMPLERATE] = {
+ "samplerate", "Samplerate (Hz)",
+ "The input data's sample rate in Hz. No default value.",
+ NULL, NULL,
+ },
+ [OPT_COL_SEP] = {
+ "column_separator", "Column separator",
+ "The sequence which separates text columns. Non-empty text, comma by default.",
+ NULL, NULL,
+ },
+ [OPT_COMMENT] = {
+ "comment_leader", "Comment leader character",
+ "The text which starts comments at the end of text lines, semicolon by default.",
+ NULL, NULL,
+ },
+ [OPT_MAX] = ALL_ZERO,
};
static const struct sr_option *get_options(void)
GSList *l;
if (!options[0].def) {
- options[0].def = g_variant_ref_sink(g_variant_new_int32(0));
- options[1].def = g_variant_ref_sink(g_variant_new_int32(0));
- options[2].def = g_variant_ref_sink(g_variant_new_string(","));
- options[3].def = g_variant_ref_sink(g_variant_new_string("bin"));
+ options[OPT_COL_FMTS].def = g_variant_ref_sink(g_variant_new_string(""));
+ options[OPT_SINGLE_COL].def = g_variant_ref_sink(g_variant_new_uint32(0));
+ options[OPT_FIRST_COL].def = g_variant_ref_sink(g_variant_new_uint32(1));
+ options[OPT_NUM_LOGIC].def = g_variant_ref_sink(g_variant_new_uint32(0));
+ options[OPT_SINGLE_FMT].def = g_variant_ref_sink(g_variant_new_string("bin"));
l = NULL;
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("bin")));
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("hex")));
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("oct")));
- options[3].values = l;
- options[4].def = g_variant_ref_sink(g_variant_new_string(";"));
- options[5].def = g_variant_ref_sink(g_variant_new_uint64(0));
- options[6].def = g_variant_ref_sink(g_variant_new_int32(0));
- options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
- options[8].def = g_variant_ref_sink(g_variant_new_int32(1));
+ options[OPT_SINGLE_FMT].values = l;
+ options[OPT_START_LINE].def = g_variant_ref_sink(g_variant_new_uint32(1));
+ options[OPT_HEADER].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
+ options[OPT_SAMPLERATE].def = g_variant_ref_sink(g_variant_new_uint64(0));
+ options[OPT_COL_SEP].def = g_variant_ref_sink(g_variant_new_string(","));
+ options[OPT_COMMENT].def = g_variant_ref_sink(g_variant_new_string(";"));
}
return options;
.name = "CSV",
.desc = "Comma-separated values",
.exts = (const char*[]){"csv", NULL},
+ .metadata = { SR_INPUT_META_FILENAME, SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
.options = get_options,
+ .format_match = format_match,
.init = init,
.receive = receive,
.end = end,
#define CHUNK_SIZE (4 * 1024 * 1024)
#define MAX_POD_COUNT 12
-#define HEADER_SIZE 80
#define SPACE ' '
#define CTRLZ '\x1a'
enum ad_format {
AD_FORMAT_UNKNOWN,
- AD_FORMAT_BINHDR, /* Binary header, binary data, textual setup info */
+ AD_FORMAT_BINHDR1, /* Binary header, binary data, textual setup info, v1 */
+ AD_FORMAT_BINHDR2, /* Binary header, binary data, textual setup info, v2 */
AD_FORMAT_TXTHDR, /* Textual header, binary data */
};
char pod_status[MAX_POD_COUNT];
struct sr_channel *channels[MAX_POD_COUNT][17]; /* 16 + CLK */
uint64_t trigger_timestamp;
- uint32_t record_size, record_count, cur_record;
+ uint32_t header_size, record_size, record_count, cur_record;
int32_t last_record;
uint64_t samplerate;
double timestamp_scale;
enum ad_format format;
/*
- * 00-31 (0x00-1F) file format name
- * 32-39 (0x20-27) trigger timestamp u64
- * 40-47 (0x28-2F) unused
- * 48 (0x30) compression
- * 49-53 (0x31-35) ??
- * 50 (0x32) 0x00 (PI), 0x01 (iprobe)
- * 54 (0x36) 0x08 (PI 250/500), 0x0A (iprobe 250)
- * 55 (0x37) 0x00 (250), 0x01 (500)
- * 56 (0x38) record size u8
- * 57-59 (0x39-3B) const 0x00
- * 60-63 (0x3C-3F) number of records u32
- * 64-67 (0x40-43) id of last record s32
- * 68-77 (0x44-4D) ??
- * 71 (0x47) const 0x80=128
- * 72 (0x48) const 0x01
- * 78-79 (0x4E-4F) ??
+ * First-level file header:
+ * 0x00-1F file format name
+ * 0x20 u64 trigger timestamp
+ * 0x28-2F unused
+ * 0x30 u8 compression
+ * 0x31-35 ??
+ * 0x32 u8 0x00 (PI), 0x01 (iprobe)
+ * 0x36 u8 device id: 0x08 (PI 250/500), 0x0A (iprobe 250)
+ */
+
+ /*
+ * Second-level file header, version 1:
+ * 0x37 u8 capture speed: 0x00 (250), 0x01 (500)
+ * 0x38 u8 record size
+ * 0x39-3B const 0x00
+ * 0x3C u32 number of records
+ * 0x40 s32 id of last record
+ * 0x44-4D ??
+ * 0x47 u8 const 0x80=128
+ * 0x48 u8 const 0x01
+ * 0x4E-4F ??
+ */
+
+ /*
+ * Second-level file header, version 2:
+ * 0x37 u8 ??
+ * 0x38 u64 ??
+ * 0x40 u64 ??
+ * 0x48 u8 record size
+ * 0x49-4F ??
+ * 0x50 u64 ??
+ * 0x58 u64 number of records
+ * 0x60 u64 ??
+ * 0x68 u64 ??
+ * 0x70 u64 ??
+ * 0x78 u64 ??
+ * 0x80 u64 ??
+ * 0x88 u64 ?? (timestamp of some kind?)
+ * 0x90 u64 ??
+ * 0x98-9E ??
+ * 0x9F u8 capture speed: 0x00 (250), 0x01 (500)
+ * 0xA0 u64 ??
+ * 0xA8 u64 ??
+ * 0xB0 u64 ??
+ * 0xB8-CF version string? (e.g. '93173--96069', same for all tested .ad files)
+ * 0xC8 u16 ??
*/
/*
format = AD_FORMAT_UNKNOWN;
if (has_trace32) {
/* Literal "trace32" leader, binary header follows. */
- format = AD_FORMAT_BINHDR;
+ format = AD_FORMAT_BINHDR1;
} else if (g_ascii_isdigit(format_name[0]) && (format_name[1] == SPACE)) {
/* Digit and SPACE leader, currently unsupported text header. */
format = AD_FORMAT_TXTHDR;
if (!format)
return SR_ERR;
+ /* If the device id is 0x00, we have a v2 format file. */
+ if (R8(buf->str + 0x36) == 0x00)
+ format = AD_FORMAT_BINHDR2;
+
p = printable_name(format_name);
if (inc)
sr_dbg("File says it's \"%s\" -> format type %u.", p, format);
g_free(p);
- record_size = R8(buf->str + 56);
+ record_size = (format == AD_FORMAT_BINHDR1) ?
+ R8(buf->str + 0x38) : R8(buf->str + 0x48);
device_id = 0;
if (g_strcmp0(format_name, "trace32 power integrator data") == 0) {
inc->format = format;
inc->device = device_id;
- inc->trigger_timestamp = RL64(buf->str + 32);
- inc->compression = R8(buf->str + 48); /* Maps to the enum. */
- inc->record_mode = R8(buf->str + 55); /* Maps to the enum. */
+ inc->trigger_timestamp = RL64(buf->str + 0x20);
+ inc->compression = R8(buf->str + 0x30); /* Maps to the enum. */
+ inc->header_size = (format == AD_FORMAT_BINHDR1) ? 0x50 : 0xCA;
inc->record_size = record_size;
- inc->record_count = RL32(buf->str + 60);
- inc->last_record = RL32S(buf->str + 64);
+
+ if (format == AD_FORMAT_BINHDR1) {
+ inc->record_mode = R8(buf->str + 0x37); /* Maps to the enum. */
+ inc->record_count = RL32(buf->str + 0x3C);
+ inc->last_record = RL32S(buf->str + 0x40);
+ } else {
+ inc->record_mode = R8(buf->str + 0x9F); /* Maps to the enum. */
+ inc->record_count = RL32(buf->str + 0x58);
+ inc->last_record = inc->record_count;
+ }
sr_dbg("Trigger occured at %lf s.",
inc->trigger_timestamp * TIMESTAMP_RESOLUTION);
buf = in->buf;
/*
- * 00-07 timestamp
- * 08-09 A15..0
- * 10-11 B15..0
- * 12-13 C15..0
- * 14-15 D15..0
- * 16-17 E15..0
- * 18-19 F15..0
- * 20-23 ??
- * 24-25 J15..0 Not present in 500MHz mode
- * 26-27 K15..0 Not present in 500MHz mode
- * 28-29 L15..0 Not present in 500MHz mode
- * 30-31 M15..0 Not present in 500MHz mode
- * 32-33 N15..0 Not present in 500MHz mode
- * 34-35 O15..0 Not present in 500MHz mode
- * 36-39 ?? Not present in 500MHz mode
- * 40/24 CLKF..A (32=CLKF, .., 1=CLKA)
- * 41 CLKO..J (32=CLKO, .., 1=CLKJ) Not present in 500MHz mode
- * 42/25 ??
- * 43/26 ??
- * 44/27 ??
+ * 0x00 u8 timestamp
+ * 0x08 u16 A15..0
+ * 0x0A u16 B15..0
+ * 0x0C u16 C15..0
+ * 0x0E u16 D15..0
+ * 0x10 u16 E15..0
+ * 0x12 u16 F15..0
+ * 0x14 u32 ??
+ * 0x18 u16 J15..0 Not present in 500MHz mode
+ * 0x1A u16 K15..0 Not present in 500MHz mode
+ * 0x1C u16 L15..0 Not present in 500MHz mode
+ * 0x1E u16 M15..0 Not present in 500MHz mode
+ * 0x20 u16 N15..0 Not present in 500MHz mode
+ * 0x22 u16 O15..0 Not present in 500MHz mode
+ * 0x24 u32 ?? Not present in 500MHz mode
+ * 0x28/18 u8 CLKF..A (32=CLKF, .., 1=CLKA)
+ * 0x29/1A u8 CLKO..J (32=CLKO, .., 1=CLKJ) Not present in 500MHz mode
+ * 0x2A/19 u8 ??
+ * 0x2B/1A u8 ??
+ * 0x2C/1B u8 ??
*/
timestamp = RL64(buf->str + start);
if (inc->record_mode == AD_MODE_500MHZ) {
pod_count = 6;
- clk_offset = 24;
+ clk_offset = 0x18;
} else {
pod_count = 12;
- clk_offset = 40;
+ clk_offset = 0x28;
}
payload_bit = 0;
switch (pod) {
case 0: /* A */
- pod_data = RL16(buf->str + start + 8);
+ pod_data = RL16(buf->str + start + 0x08);
pod_data |= (RL16(buf->str + start + clk_offset) & 1) << 16;
break;
case 1: /* B */
- pod_data = RL16(buf->str + start + 10);
+ pod_data = RL16(buf->str + start + 0x0A);
pod_data |= (RL16(buf->str + start + clk_offset) & 2) << 15;
break;
case 2: /* C */
- pod_data = RL16(buf->str + start + 12);
+ pod_data = RL16(buf->str + start + 0x0C);
pod_data |= (RL16(buf->str + start + clk_offset) & 4) << 14;
break;
case 3: /* D */
- pod_data = RL16(buf->str + start + 14);
+ pod_data = RL16(buf->str + start + 0x0E);
pod_data |= (RL16(buf->str + start + clk_offset) & 8) << 13;
break;
case 4: /* E */
- pod_data = RL16(buf->str + start + 16);
+ pod_data = RL16(buf->str + start + 0x10);
pod_data |= (RL16(buf->str + start + clk_offset) & 16) << 12;
break;
case 5: /* F */
- pod_data = RL16(buf->str + start + 18);
+ pod_data = RL16(buf->str + start + 0x12);
pod_data |= (RL16(buf->str + start + clk_offset) & 32) << 11;
break;
case 6: /* J */
- pod_data = RL16(buf->str + start + 24);
- pod_data |= (RL16(buf->str + start + 41) & 1) << 16;
+ pod_data = RL16(buf->str + start + 0x18);
+ pod_data |= (RL16(buf->str + start + 0x29) & 1) << 16;
break;
case 7: /* K */
- pod_data = RL16(buf->str + start + 26);
- pod_data |= (RL16(buf->str + start + 41) & 2) << 15;
+ pod_data = RL16(buf->str + start + 0x1A);
+ pod_data |= (RL16(buf->str + start + 0x29) & 2) << 15;
break;
case 8: /* L */
- pod_data = RL16(buf->str + start + 28);
- pod_data |= (RL16(buf->str + start + 41) & 4) << 14;
+ pod_data = RL16(buf->str + start + 0x1C);
+ pod_data |= (RL16(buf->str + start + 0x29) & 4) << 14;
break;
case 9: /* M */
- pod_data = RL16(buf->str + start + 30);
- pod_data |= (RL16(buf->str + start + 41) & 8) << 13;
+ pod_data = RL16(buf->str + start + 0x1E);
+ pod_data |= (RL16(buf->str + start + 0x29) & 8) << 13;
break;
case 10: /* N */
- pod_data = RL16(buf->str + start + 32);
- pod_data |= (RL16(buf->str + start + 41) & 16) << 12;
+ pod_data = RL16(buf->str + start + 0x20);
+ pod_data |= (RL16(buf->str + start + 0x29) & 16) << 12;
break;
case 11: /* O */
- pod_data = RL16(buf->str + start + 34);
- pod_data |= (RL16(buf->str + start + 41) & 32) << 11;
+ pod_data = RL16(buf->str + start + 0x22);
+ pod_data |= (RL16(buf->str + start + 0x29) & 32) << 11;
break;
default:
sr_err("Don't know how to obtain data for pod %d.", pod);
inc = in->priv;
/*
- * 00-07 timestamp
- * 08-09 IP15..0
- * 10 CLK
+ * 0x00 u64 timestamp
+ * 0x08 u16 IP15..0
+ * 0x0A u8 CLK
*/
timestamp = RL64(in->buf->str + start);
- single_payload[0] = R8(in->buf->str + start + 8);
- single_payload[1] = R8(in->buf->str + start + 9);
- single_payload[2] = R8(in->buf->str + start + 10) & 1;
+ single_payload[0] = R8(in->buf->str + start + 0x08);
+ single_payload[1] = R8(in->buf->str + start + 0x09);
+ single_payload[2] = R8(in->buf->str + start + 0x0A) & 1;
payload_len = 3;
if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) {
if (!inc->header_read) {
res = process_header(in->buf, inc);
- g_string_erase(in->buf, 0, HEADER_SIZE);
+ g_string_erase(in->buf, 0, inc->header_size);
if (res != SR_OK)
return res;
}
* 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
*/
#include <config.h>
-#include <stdint.h>
-#include <string.h>
-#include <math.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"
-struct dev_buffer {
- /** Total size of the buffer. */
- size_t size;
- /** Amount of data currently in the buffer. */
- size_t len;
- /** Offset where the data starts in the buffer. */
- size_t offset;
- /** Space for the data. */
- uint8_t data[];
-};
-
-static struct dev_buffer *dev_buffer_new(size_t size)
-{
- struct dev_buffer *dbuf;
-
- dbuf = g_malloc0(sizeof(struct dev_buffer) + size);
- dbuf->size = size;
- dbuf->len = 0;
- dbuf->offset = 0;
-
- return dbuf;
-}
-
-static void dev_buffer_destroy(struct dev_buffer *dbuf)
-{
- g_free(dbuf);
-}
-
-static int dev_buffer_fill_serial(struct dev_buffer *dbuf,
- struct sr_dev_inst *sdi)
-{
- struct sr_serial_dev_inst *serial;
- int len;
-
- serial = sdi->conn;
-
- /* If we already have data, move it to the beginning of the buffer. */
- if (dbuf->len > 0 && dbuf->offset > 0)
- memmove(dbuf->data, dbuf->data + dbuf->offset, dbuf->len);
-
- dbuf->offset = 0;
-
- len = dbuf->size - dbuf->len;
- len = serial_read_nonblocking(serial, dbuf->data + dbuf->len, len);
- if (len < 0) {
- sr_err("Serial port read error: %d.", len);
- return len;
- }
-
- dbuf->len += len;
-
- return SR_OK;
-}
-
-static uint8_t *dev_buffer_packet_find(struct dev_buffer *dbuf,
- gboolean (*packet_valid)(const uint8_t *),
- size_t packet_size)
-{
- size_t offset;
-
- while (dbuf->len >= packet_size) {
- if (packet_valid(dbuf->data + dbuf->offset)) {
- offset = dbuf->offset;
- dbuf->offset += packet_size;
- dbuf->len -= packet_size;
- return dbuf->data + offset;
- }
- dbuf->offset++;
- dbuf->len--;
- }
-
- return NULL;
-}
-
-struct dev_limit_counter {
- /** The current number of received samples/frames/etc. */
- uint64_t count;
- /** The limit (in number of samples/frames/etc.). */
- uint64_t limit;
-};
-
-static void dev_limit_counter_start(struct dev_limit_counter *cnt)
-{
- cnt->count = 0;
-}
-
-static void dev_limit_counter_inc(struct dev_limit_counter *cnt)
-{
- cnt->count++;
-}
-
-static void dev_limit_counter_limit_set(struct dev_limit_counter *cnt,
- uint64_t limit)
-{
- cnt->limit = limit;
-}
-
-static gboolean dev_limit_counter_limit_reached(struct dev_limit_counter *cnt)
-{
- if (cnt->limit && cnt->count >= cnt->limit) {
- sr_info("Requested counter limit reached.");
- return TRUE;
- }
-
- return FALSE;
-}
-
-struct dev_time_counter {
- /** The starting time of current sampling run. */
- int64_t starttime;
- /** The time limit (in milliseconds). */
- uint64_t limit;
-};
-
-static void dev_time_counter_start(struct dev_time_counter *cnt)
-{
- cnt->starttime = g_get_monotonic_time();
-}
-
-static void dev_time_limit_set(struct dev_time_counter *cnt, uint64_t limit)
-{
- cnt->limit = limit;
-}
-
-static gboolean dev_time_limit_reached(struct dev_time_counter *cnt)
-{
- int64_t time;
-
- if (cnt->limit) {
- time = (g_get_monotonic_time() - cnt->starttime) / 1000;
- if (time > (int64_t)cnt->limit) {
- sr_info("Requested time limit reached.");
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-static void serial_conf_get(GSList *options, const char *def_serialcomm,
- const char **conn, const char **serialcomm)
-{
- struct sr_config *src;
- GSList *l;
-
- *conn = *serialcomm = NULL;
- for (l = options; l; l = l->next) {
- src = l->data;
- switch (src->key) {
- case SR_CONF_CONN:
- *conn = g_variant_get_string(src->data, NULL);
- break;
- case SR_CONF_SERIALCOMM:
- *serialcomm = g_variant_get_string(src->data, NULL);
- break;
- }
- }
-
- if (*serialcomm == NULL)
- *serialcomm = def_serialcomm;
-}
-
-static struct sr_serial_dev_inst *serial_dev_new(GSList *options,
- const char *def_serialcomm)
-
-{
- const char *conn, *serialcomm;
-
- serial_conf_get(options, def_serialcomm, &conn, &serialcomm);
-
- if (!conn)
- return NULL;
-
- return sr_serial_dev_inst_new(conn, serialcomm);
-}
-
-static int serial_stream_check_buf(struct sr_serial_dev_inst *serial,
- uint8_t *buf, size_t buflen,
- size_t packet_size,
- packet_valid_callback is_valid,
- uint64_t timeout_ms, int baudrate)
-{
- size_t len, dropped;
- int ret;
-
- if ((ret = serial_open(serial, SERIAL_RDWR)) != SR_OK)
- return ret;
-
- serial_flush(serial);
-
- len = buflen;
- ret = serial_stream_detect(serial, buf, &len, packet_size,
- is_valid, timeout_ms, baudrate);
-
- serial_close(serial);
-
- if (ret != SR_OK)
- return ret;
-
- /*
- * If we dropped more than two packets worth of data, something is
- * wrong. We shouldn't quit however, since the dropped bytes might be
- * just zeroes at the beginning of the stream. Those can occur as a
- * combination of the nonstandard cable that ships with some devices
- * and the serial port or USB to serial adapter.
- */
- dropped = len - packet_size;
- if (dropped > 2 * packet_size)
- sr_warn("Had to drop too much data.");
-
- return SR_OK;
-}
-
-static int serial_stream_check(struct sr_serial_dev_inst *serial,
- size_t packet_size,
- packet_valid_callback is_valid,
- uint64_t timeout_ms, int baudrate)
-{
- uint8_t buf[128];
-
- return serial_stream_check_buf(serial, buf, sizeof(buf), packet_size,
- is_valid, timeout_ms, baudrate);
-}
-
-static int send_config_update(struct sr_dev_inst *sdi, struct sr_config *cfg)
-{
- struct sr_datafeed_packet packet;
- struct sr_datafeed_meta meta;
-
- memset(&meta, 0, sizeof(meta));
-
- packet.type = SR_DF_META;
- packet.payload = &meta;
-
- meta.config = g_slist_append(meta.config, cfg);
-
- return sr_session_send(sdi, &packet);
-}
-
-static int send_config_update_key(struct sr_dev_inst *sdi, uint32_t key,
- GVariant *var)
-{
- struct sr_config *cfg;
- int ret;
-
- cfg = sr_config_new(key, var);
- if (!cfg)
- return SR_ERR;
-
- ret = send_config_update(sdi, cfg);
- sr_config_free(cfg);
-
- return ret;
-}
+#ifdef HAVE_SERIAL_COMM
/*
* Cyrustek ES51919 LCR chipset host protocol.
* 0x10: footer2 (0x0a) ?
*/
-#define PACKET_SIZE 17
-
static const double frequencies[] = {
- 100, 120, 1000, 10000, 100000, 0,
+ SR_HZ(0), SR_HZ(100), SR_HZ(120),
+ SR_KHZ(1), SR_KHZ(10), SR_KHZ(100),
};
-enum { MODEL_NONE, MODEL_PAR, MODEL_SER, MODEL_AUTO, };
-
-static const char *const models[] = {
- "NONE", "PARALLEL", "SERIES", "AUTO",
+static const size_t freq_code_map[] = {
+ 1, 2, 3, 4, 5, 0,
};
-struct dev_context {
- struct dev_limit_counter frame_count;
+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];
+ }
- struct dev_time_counter time_count;
+ code = freq_code_map[code];
+ freq = frequencies[code];
- struct dev_buffer *buf;
+ return freq;
+}
- /** The frequency of the test signal (index to frequencies[]). */
- unsigned int freq;
+enum { MODEL_NONE, MODEL_PAR, MODEL_SER, MODEL_AUTO, };
- /** Equivalent circuit model (index to models[]). */
- unsigned int model;
+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 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];
- return (float)val * powf(10, exponent);
+ 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)
+ struct sr_datafeed_analog *analog, int is_secondary)
{
static const struct {
int unit;
{ SR_UNIT_PERCENTAGE, 0 }, /* % */
{ SR_UNIT_DEGREE, 0 }, /* degree */
};
+
const uint8_t *buf;
int digits, exponent;
int state;
analog->spec->spec_digits = digits - exponent;
}
-static unsigned int parse_freq(const uint8_t *pkt)
+static uint64_t parse_freq(const uint8_t *pkt)
{
- unsigned int freq;
-
- freq = pkt[3] >> 5;
-
- if (freq >= ARRAY_SIZE(frequencies)) {
- sr_err("Unknown frequency %u.", freq);
- freq = ARRAY_SIZE(frequencies) - 1;
- }
-
- return freq;
+ return get_frequency(pkt[3] >> 5);
}
-static unsigned int parse_model(const uint8_t *pkt)
+static const char *parse_model(const uint8_t *pkt)
{
+ size_t code;
+
if (pkt[2] & 0x40)
- return MODEL_AUTO;
+ code = MODEL_AUTO;
else if (parse_mq(pkt, 0, 0) == SR_MQ_RESISTANCE)
- return MODEL_NONE;
- else if (pkt[2] & 0x80)
- return MODEL_PAR;
+ code = MODEL_NONE;
else
- return MODEL_SER;
-}
-
-static gboolean packet_valid(const uint8_t *pkt)
-{
- /*
- * If the first two bytes of the packet are indeed a constant
- * header, they should be checked too. Since we don't know it
- * for sure, we'll just check the last two for now since they
- * seem to be constant just like in the other Cyrustek chipset
- * protocols.
- */
- if (pkt[15] == 0xd && pkt[16] == 0xa)
- return TRUE;
-
- return FALSE;
-}
-
-static int do_config_update(struct sr_dev_inst *sdi, uint32_t key,
- GVariant *var)
-{
- return send_config_update_key(sdi, key, var);
-}
-
-static int send_freq_update(struct sr_dev_inst *sdi, unsigned int freq)
-{
- return do_config_update(sdi, SR_CONF_OUTPUT_FREQUENCY,
- g_variant_new_double(frequencies[freq]));
-}
-
-static int send_model_update(struct sr_dev_inst *sdi, unsigned int model)
-{
- return do_config_update(sdi, SR_CONF_EQUIV_CIRCUIT_MODEL,
- g_variant_new_string(models[model]));
-}
-
-static void handle_packet(struct sr_dev_inst *sdi, const uint8_t *pkt)
-{
- struct sr_datafeed_packet packet;
- struct sr_datafeed_analog analog;
- struct sr_analog_encoding encoding;
- struct sr_analog_meaning meaning;
- struct sr_analog_spec spec;
- struct dev_context *devc;
- unsigned int val;
- float floatval;
- gboolean frame;
- struct sr_channel *channel;
-
- devc = sdi->priv;
-
- val = parse_freq(pkt);
- if (val != devc->freq) {
- if (send_freq_update(sdi, val) == SR_OK)
- devc->freq = val;
- else
- return;
- }
-
- val = parse_model(pkt);
- if (val != devc->model) {
- if (send_model_update(sdi, val) == SR_OK)
- devc->model = val;
- else
- return;
- }
-
- frame = FALSE;
-
- /* Note: digits/spec_digits will be overridden later. */
- sr_analog_init(&analog, &encoding, &meaning, &spec, 0);
-
- analog.num_samples = 1;
- analog.data = &floatval;
-
- channel = sdi->channels->data;
- analog.meaning->channels = g_slist_append(NULL, channel);
-
- parse_measurement(pkt, &floatval, &analog, 0);
- if (analog.meaning->mq != 0 && channel->enabled) {
- if (!frame) {
- packet.type = SR_DF_FRAME_BEGIN;
- sr_session_send(sdi, &packet);
- frame = TRUE;
- }
-
- packet.type = SR_DF_ANALOG;
- packet.payload = &analog;
-
- sr_session_send(sdi, &packet);
- }
-
- g_slist_free(analog.meaning->channels);
-
- channel = sdi->channels->next->data;
- analog.meaning->channels = g_slist_append(NULL, channel);
-
- parse_measurement(pkt, &floatval, &analog, 1);
- if (analog.meaning->mq != 0 && channel->enabled) {
- if (!frame) {
- packet.type = SR_DF_FRAME_BEGIN;
- sr_session_send(sdi, &packet);
- frame = TRUE;
- }
+ code = (pkt[2] & 0x80) ? MODEL_PAR : MODEL_SER;
- packet.type = SR_DF_ANALOG;
- packet.payload = &analog;
-
- sr_session_send(sdi, &packet);
- }
-
- g_slist_free(analog.meaning->channels);
-
- if (frame) {
- packet.type = SR_DF_FRAME_END;
- sr_session_send(sdi, &packet);
- dev_limit_counter_inc(&devc->frame_count);
- }
-}
-
-static int handle_new_data(struct sr_dev_inst *sdi)
-{
- struct dev_context *devc;
- uint8_t *pkt;
- int ret;
-
- devc = sdi->priv;
-
- ret = dev_buffer_fill_serial(devc->buf, sdi);
- if (ret < 0)
- return ret;
-
- while ((pkt = dev_buffer_packet_find(devc->buf, packet_valid,
- PACKET_SIZE)))
- handle_packet(sdi, pkt);
-
- return SR_OK;
+ return get_equiv_model(code);
}
-static int receive_data(int fd, int revents, void *cb_data)
+SR_PRIV gboolean es51919_packet_valid(const uint8_t *pkt)
{
- struct sr_dev_inst *sdi;
- struct dev_context *devc;
-
- (void)fd;
- if (!(sdi = cb_data))
- return TRUE;
+ /* Check for fixed 0x00 0x0d prefix. */
+ if (pkt[0] != 0x00 || pkt[1] != 0x0d)
+ return FALSE;
- if (!(devc = sdi->priv))
- return TRUE;
-
- if (revents == G_IO_IN) {
- /* Serial data arrived. */
- handle_new_data(sdi);
- }
-
- if (dev_limit_counter_limit_reached(&devc->frame_count) ||
- dev_time_limit_reached(&devc->time_count))
- sr_dev_acquisition_stop(sdi);
+ /* Check for fixed 0x0d 0x0a suffix. */
+ if (pkt[15] != 0x0d || pkt[16] != 0x0a)
+ return FALSE;
+ /* Packet appears to be valid. */
return TRUE;
}
-static const char *const channel_names[] = { "P1", "P2" };
-
-static int setup_channels(struct sr_dev_inst *sdi)
-{
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(channel_names); i++)
- sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_names[i]);
-
- return SR_OK;
-}
-
-SR_PRIV void es51919_serial_clean(void *priv)
-{
- struct dev_context *devc;
-
- if (!(devc = priv))
- return;
-
- dev_buffer_destroy(devc->buf);
-}
-
-SR_PRIV struct sr_dev_inst *es51919_serial_scan(GSList *options,
- const char *vendor,
- const char *model)
+SR_PRIV int es51919_packet_parse(const uint8_t *pkt, float *val,
+ struct sr_datafeed_analog *analog, void *info)
{
- struct sr_serial_dev_inst *serial;
- struct sr_dev_inst *sdi;
- struct dev_context *devc;
- int ret;
-
- serial = NULL;
- sdi = NULL;
- devc = NULL;
-
- if (!(serial = serial_dev_new(options, "9600/8n1/rts=1/dtr=1")))
- goto scan_cleanup;
-
- ret = serial_stream_check(serial, PACKET_SIZE, packet_valid,
- 3000, 9600);
- if (ret != SR_OK)
- goto scan_cleanup;
-
- sr_info("Found device on port %s.", serial->port);
-
- sdi = g_malloc0(sizeof(struct sr_dev_inst));
- sdi->status = SR_ST_INACTIVE;
- sdi->vendor = g_strdup(vendor);
- sdi->model = g_strdup(model);
- devc = g_malloc0(sizeof(struct dev_context));
- devc->buf = dev_buffer_new(PACKET_SIZE * 8);
- sdi->inst_type = SR_INST_SERIAL;
- sdi->conn = serial;
- sdi->priv = devc;
-
- if (setup_channels(sdi) != SR_OK)
- goto scan_cleanup;
-
- return sdi;
-
-scan_cleanup:
- es51919_serial_clean(devc);
- sr_dev_inst_free(sdi);
- sr_serial_dev_inst_free(serial);
-
- return NULL;
-}
-
-SR_PRIV int es51919_serial_config_get(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi,
- const struct sr_channel_group *cg)
-{
- struct dev_context *devc;
-
- (void)cg;
+ struct lcr_parse_info *parse_info;
- devc = sdi->priv;
-
- switch (key) {
- case SR_CONF_OUTPUT_FREQUENCY:
- *data = g_variant_new_double(frequencies[devc->freq]);
- break;
- case SR_CONF_EQUIV_CIRCUIT_MODEL:
- *data = g_variant_new_string(models[devc->model]);
- break;
- default:
- return SR_ERR_NA;
+ 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;
}
-SR_PRIV int es51919_serial_config_set(uint32_t key, GVariant *data,
- const struct sr_dev_inst *sdi,
- const struct sr_channel_group *cg)
+/*
+ * 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)
{
- struct dev_context *devc;
- uint64_t val;
+ (void)sdi;
(void)cg;
- if (!(devc = sdi->priv))
- return SR_ERR_BUG;
-
- switch (key) {
- case SR_CONF_LIMIT_MSEC:
- val = g_variant_get_uint64(data);
- dev_time_limit_set(&devc->time_count, val);
- sr_dbg("Setting time limit to %" PRIu64 ".", val);
- break;
- case SR_CONF_LIMIT_FRAMES:
- val = g_variant_get_uint64(data);
- dev_limit_counter_limit_set(&devc->frame_count, val);
- sr_dbg("Setting frame limit to %" PRIu64 ".", val);
- break;
- default:
- sr_spew("%s: Unsupported key %u", __func__, key);
- return SR_ERR_NA;
- }
-
- return SR_OK;
-}
-
-static const uint32_t scanopts[] = {
- SR_CONF_CONN,
- SR_CONF_SERIALCOMM,
-};
-
-static const uint32_t drvopts[] = {
- SR_CONF_LCRMETER,
-};
-
-static const uint32_t devopts[] = {
- SR_CONF_CONTINUOUS,
- SR_CONF_LIMIT_FRAMES | SR_CONF_SET,
- SR_CONF_LIMIT_MSEC | SR_CONF_SET,
- SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_LIST,
- SR_CONF_EQUIV_CIRCUIT_MODEL | SR_CONF_GET | SR_CONF_LIST,
-};
-
-SR_PRIV int es51919_serial_config_list(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi,
- const struct sr_channel_group *cg)
-{
switch (key) {
- case SR_CONF_SCAN_OPTIONS:
- case SR_CONF_DEVICE_OPTIONS:
- return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
case SR_CONF_OUTPUT_FREQUENCY:
*data = g_variant_new_fixed_array(G_VARIANT_TYPE_DOUBLE,
- ARRAY_AND_SIZE(frequencies), sizeof(double));
- break;
+ ARRAY_AND_SIZE(frequencies), sizeof(frequencies[0]));
+ return SR_OK;
case SR_CONF_EQUIV_CIRCUIT_MODEL:
- *data = g_variant_new_strv(ARRAY_AND_SIZE(models));
- break;
+ *data = g_variant_new_strv(ARRAY_AND_SIZE(circuit_models));
+ return SR_OK;
default:
return SR_ERR_NA;
}
-
- return SR_OK;
+ /* UNREACH */
}
-SR_PRIV int es51919_serial_acquisition_start(const struct sr_dev_inst *sdi)
-{
- struct dev_context *devc;
- struct sr_serial_dev_inst *serial;
-
- if (!(devc = sdi->priv))
- return SR_ERR_BUG;
-
- dev_limit_counter_start(&devc->frame_count);
- dev_time_counter_start(&devc->time_count);
-
- std_session_send_df_header(sdi);
-
- serial = sdi->conn;
- serial_source_add(sdi->session, serial, G_IO_IN, 50,
- receive_data, (void *)sdi);
-
- return SR_OK;
-}
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * 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 "vc4080"
+
+#ifdef HAVE_SERIAL_COMM
+
+/**
+ * @file Packet parser for Voltcraft 4080 LCR meters.
+ */
+
+/*
+ * Developer notes on the protocol and the implementation:
+ *
+ * The LCR meter is connected to a serial port (1200/7e1). The protocol
+ * is text based (printables plus some line termination), is accessible
+ * to interactive exploration in a terminal. Requests differ in length
+ * (single character, or sequence of seven characters in brackets).
+ * Responses either have 14 (setup) or 39 (measurement) characters.
+ * Thus the protocol lends itself to integration with the serial-lcr
+ * driver. Setup is handled outside of the acquisition loop, and all
+ * measurement results are of equal length and end in a termination
+ * that we can synchronize to. Requesting packets from the meter is
+ * similar to serial-dmm operation.
+ *
+ * Quick notes for our parser's purposes:
+ *
+ * pkt[0] 'L'/'C'/'R'
+ * pkt[1] 'Q'/'D'/'R'
+ * pkt[2] 'A'/'B' output frequency
+ * pkt[3] 'P'/'S' circuit model
+ * pkt[4] 'A'/'M' auto/manual
+ *
+ * pkt[5:9] main display value in text format, '8' switching range, '9' OL
+ * pkt[10] main display range, '0'-'6', depends on RLC and freq and ser/par
+ *
+ * pkt[11:14] secondary display value in text format, '9' OL
+ * pkt[15] secondary display range, '1'-'5', depends on QDR and Rs value
+ *
+ * pkt[16] packet sequence counter, cycling through '0'-'9'
+ *
+ * pkt[17:20] D value in text form, '9' OL
+ * pkt[21] D range
+ *
+ * pkt[22:25] Q value in text form, '9' OL
+ * pkt[26] Q range
+ *
+ * pkt[27] 'S'/'_', SETup(?)
+ * pkt[28] 'F'/'_', FUSE
+ * pkt[29] 'H'/'_', HOLD
+ * pkt[30] 'R' (present value), 'M' (max), 'I' (min), 'A' (avg),
+ * 'X' (max - min), '_' (normal)
+ * pkt[31] 'R' (REL), 'S' (REL SET), '_' (normal)
+ * pkt[32] 'L' (LIMITS), '_' (normal)
+ * pkt[33] 'T' (TOL), 'S' (TOL SET), '_' (normal)
+ * pkt[34] 'B' (backlight), '_' (normal)
+ * pkt[35] 'A' (adapter inserted(?)), '_' (normal)
+ * pkt[36] 'B' (low battery), '_' (normal)
+ *
+ * pkt[37] always CR (\r)
+ * pkt[38] always LF (\n)
+ *
+ * Example packet, PeakTech 2165, 1200/8n1 and parity bit stripped:
+ *
+ * L Q A P A 9 0 0 0 0 6 1 4 0 6 2 1 0 7 1 1 4 1 4 0 6 2 _ _ _ _ _ _ _ _ _ _ CR LF
+ * 0 5 10 15 20 25 30 35 38
+ *
+ * Another example, resistance mode, 1k probed:
+ *
+ * 52 5f 42 5f 41 30 39 39 33 30 32 30 30 30 30 39 33 37 34 35 36 31 30 30 31 33 34 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 0d 0a
+ * R _ B _ A 09930 2 00009 3 7456 1 0013 4 __________ CR/LF
+ *
+ * Another example, C mode:
+ *
+ * 43 51 42 53 4d 30 39 38 39 31 35 30 30 31 33 34 31 37 35 38 33 31 30 30 31 33 34 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 0d 0a
+ * C Q B S M 09891 5 00134 1 7583 1 0013 4 ____...
+ * C, Q, 120, ser, man, 09891 @2000uF -> C = 989.1uF, 00134 -> Q = 13.4
+ *
+ * 43 51 42 53 4d 30 39 38 38 30 35 30 30 31 33 34 34 37 35 37 34 31 30 30 31 33 34 5f 5f 5f 5f 5f 5f 5f 42 5f 5f 0d 0a
+ * 900uF (main)
+ *
+ * For more details see Conrad's summary document and PeakTech's manual:
+ * http://www.produktinfo.conrad.com/datenblaetter/100000-124999/121064-da-01-en-Schnittstellenbeschr_LCR_4080_Handmessg.pdf
+ * http://peaktech.de/productdetail/kategorie/lcr-messer/produkt/p-2165.html?file=tl_files/downloads/2001%20-%203000/PeakTech_2165_USB.pdf
+ *
+ * TODO
+ * - Check response lengths. Are line terminators involved during setup?
+ * - Check parity. Does FT232R not handle parity correctly? Neither 7e1 (as
+ * documented) nor 7o1 (for fun) worked. 8n1 provided data but contained
+ * garbage (LCR driver needs to strip off the parity bit?).
+ * - Determine whether the D and Q channels are required. It seems that
+ * every LCR packet has space to provide these values, but we may as well
+ * get away with just two channels, since users can select D and Q to be
+ * shown in the secondary display. It's yet uncertain whether the D and Q
+ * values in the packets are meaningful when the meter is not in the D/Q
+ * measurement mode.
+ */
+
+/*
+ * Supported output frequencies and equivalent circuit models. A helper
+ * for the packet parser (accepting a "code" for the property, regardless
+ * of its position in the LCR packet), and a list for capability queries.
+ * Concentrated in a single spot to remain aware duing maintenance.
+ */
+
+static const double frequencies[] = {
+ SR_HZ(120), SR_KHZ(1),
+};
+
+static uint64_t get_frequency(char code)
+{
+ switch (code) {
+ case 'A': return SR_KHZ(1);
+ case 'B': return SR_HZ(120);
+ default: return 0;
+ }
+}
+
+enum equiv_model { MODEL_PAR, MODEL_SER, MODEL_NONE, };
+
+static const char *const circuit_models[] = {
+ "PARALLEL", "SERIES", "NONE",
+};
+
+static enum equiv_model get_equiv_model(char lcr_code, char model_code)
+{
+ switch (lcr_code) {
+ case 'L': /* EMPTY */ break;
+ case 'C': /* EMPTY */ break;
+ case 'R': return MODEL_NONE;
+ default: return MODEL_NONE;
+ }
+ switch (model_code) {
+ case 'P': return MODEL_PAR;
+ case 'S': return MODEL_SER;
+ default: return MODEL_NONE;
+ }
+}
+
+static const char *get_equiv_model_text(enum equiv_model model)
+{
+ return circuit_models[model];
+}
+
+/*
+ * Packet parse routine and its helpers. Depending on the specific layout
+ * of the meter's packet which communicates measurement results. Some of
+ * them are also used outside of strict packet parsing for value extraction.
+ */
+
+static uint64_t parse_freq(const uint8_t *pkt)
+{
+ return get_frequency(pkt[2]);
+}
+
+static const char *parse_model(const uint8_t *pkt)
+{
+ return get_equiv_model_text(get_equiv_model(pkt[0], pkt[3]));
+}
+
+static float parse_number(const uint8_t *digits, size_t length)
+{
+ char value_text[8];
+ float number;
+ int ret;
+
+ memcpy(value_text, digits, length);
+ value_text[length] = '\0';
+ ret = sr_atof_ascii(value_text, &number);
+
+ return (ret == SR_OK) ? number : 0;
+}
+
+/*
+ * Conrad's protocol description suggests that:
+ * - The main display's LCR selection, output frequency, and range
+ * result in an Rs value in the 100R to 100k range, in addition to
+ * the main display's scale for the value.
+ * - The secondary display's DQR selection, the above determined Rs
+ * value, and range result in the value's scale.
+ * - The D and Q values' range seems to follow the secondary display's
+ * logic.
+ */
+
+enum lcr_kind { LCR_NONE, LCR_IS_L, LCR_IS_C, LCR_IS_R, };
+enum dqr_kind { DQR_NONE, DQR_IS_D, DQR_IS_Q, DQR_IS_R, };
+
+static int get_main_scale_rs(int *digits, int *rs,
+ uint8_t range, enum lcr_kind lcr, uint64_t freq)
+{
+ /*
+ * Scaling factors for values. Digits count for 20000 full scale.
+ * Full scale values for different modes are:
+ * R: 20R, 200R, 2k, 20k, 200k, 2M, 10M
+ * L 1kHz: 2mH, 20mH, 200mH, 2H, 20H, 200H, 1000H
+ * L 120Hz: 20mH, 200mH, 2H, 20H, 200H, 2kH, 10kH
+ * C 1kHz: 2nF, 20nF, 200nF, 2uF, 20uF, 200uF, 2mF
+ * C 120Hz: 20nF, 200nF, 2unF, 20uF, 200uF, 2muF, 20mF
+ */
+ static const int dig_r[] = { -3, -2, -1, +0, +1, +2, +3, };
+ static const int dig_l_1k[] = { -7, -6, -5, -4, -3, -2, -1, };
+ static const int dig_l_120[] = { -6, -5, -4, -3, -2, -1, 0, };
+ static const int dig_c_1k[] = { -13, -12, -11, -10, -9, -8, -7, };
+ static const int dig_c_120[] = { -12, -11, -10, -9, -8, -7, -6, };
+ /*
+ * Rs values for the scale, depending on LCR mode.
+ * Values for R/L: 100R, 100R, 100R, 1k, 10k, 100k, 100k
+ * Values for C: 100k, 100k, 10k, 1k, 100R, 100R, 100R
+ */
+ static const int rs_r_l[] = {
+ 100, 100, 100, 1000, 10000, 100000, 100000,
+ };
+ static const int rs_c[] = {
+ 100000, 100000, 10000, 1000, 100, 100, 100,
+ };
+
+ const int *digits_table, *rs_table;
+
+ /* The 'range' input value is only valid between 0..6. */
+ if (range > 6)
+ return SR_ERR_DATA;
+
+ if (lcr == LCR_IS_R) {
+ digits_table = dig_r;
+ rs_table = rs_r_l;
+ } else if (lcr == LCR_IS_L && freq == SR_KHZ(1)) {
+ digits_table = dig_l_1k;
+ rs_table = rs_r_l;
+ } else if (lcr == LCR_IS_L && freq == SR_HZ(120)) {
+ digits_table = dig_l_120;
+ rs_table = rs_r_l;
+ } else if (lcr == LCR_IS_C && freq == SR_KHZ(1)) {
+ digits_table = dig_c_1k;
+ rs_table = rs_c;
+ } else if (lcr == LCR_IS_C && freq == SR_HZ(120)) {
+ digits_table = dig_c_120;
+ rs_table = rs_c;
+ } else {
+ return SR_ERR_DATA;
+ }
+
+ if (digits)
+ *digits = digits_table[range];
+ if (rs)
+ *rs = rs_table[range];
+
+ return SR_OK;
+}
+
+static int get_sec_scale(int *digits, uint8_t range, enum dqr_kind dqr, int rs)
+{
+ static const int dig_d_q[] = { 0, -1, -2, -3, -4, 0, };
+ static const int dig_r_100[] = { 0, -2, -1, +0, +1, 0, };
+ static const int dig_r_1k_10k[] = { 0, -2, -1, +0, +1, +2, };
+ static const int dig_r_100k[] = { 0, 0, -1, +0, +1, +2, };
+
+ const int *digits_table;
+
+ /*
+ * Absolute 'range' limits are 1..5, some modes have additional
+ * invalid positions (these get checked below).
+ */
+ if (range < 1 || range > 5)
+ return SR_ERR_DATA;
+
+ if (dqr == DQR_IS_D || dqr == DQR_IS_Q) {
+ if (range > 4)
+ return SR_ERR_DATA;
+ digits_table = dig_d_q;
+ } else if (dqr == DQR_IS_R && rs == 100) {
+ if (range > 4)
+ return SR_ERR_DATA;
+ digits_table = dig_r_100;
+ } else if (dqr == DQR_IS_R && (rs == 1000 || rs == 10000)) {
+ digits_table = dig_r_1k_10k;
+ } else if (dqr == DQR_IS_R && rs == 100000) {
+ if (range < 2)
+ return SR_ERR_DATA;
+ digits_table = dig_r_100k;
+ } else {
+ return SR_ERR_DATA;
+ }
+
+ if (digits)
+ *digits = digits_table[range];
+
+ return SR_OK;
+}
+
+static void parse_measurement(const uint8_t *pkt, float *floatval,
+ struct sr_datafeed_analog *analog, size_t disp_idx)
+{
+ enum lcr_kind lcr;
+ enum dqr_kind dqr;
+ uint64_t freq;
+ enum equiv_model model;
+ gboolean is_auto, main_ranging, main_ol, sec_ol, d_ol, q_ol;
+ float main_value, sec_value, d_value, q_value;
+ char main_range, sec_range, d_range, q_range;
+ gboolean is_hold, is_relative, has_adapter, is_lowbatt;
+ enum minmax_kind {
+ MINMAX_MAX, MINMAX_MIN, MINMAX_SPAN,
+ MINMAX_AVG, MINMAX_CURR, MINMAX_NONE,
+ } minmax;
+ gboolean is_parallel;
+ int mq, mqflags, unit;
+ float value;
+ int digits, exponent;
+ gboolean ol, invalid;
+ int ret, rs, main_digits, sec_digits, d_digits, q_digits;
+ int main_invalid, sec_invalid, d_invalid, q_invalid;
+
+ /* Prepare void return values for error paths. */
+ analog->meaning->mq = 0;
+ analog->meaning->mqflags = 0;
+ if (disp_idx >= VC4080_CHANNEL_COUNT)
+ return;
+
+ /*
+ * The interpretation of secondary displays may depend not only
+ * on the meter's status (indicator flags), but also on the main
+ * display's current value (ranges, scaling). Unconditionally
+ * inspect most of the packet's content, regardless of which
+ * display we are supposed to extract the value for in this
+ * invocation.
+ *
+ * While we are converting the input text, check a few "fatal"
+ * conditions early, cease further packet inspection when the
+ * value is unstable or not yet available, or when the meter's
+ * current mode/function is not supported by this LCR parser.
+ */
+ switch (pkt[0]) {
+ case 'L': lcr = LCR_IS_L; break;
+ case 'R': lcr = LCR_IS_R; break;
+ case 'C': lcr = LCR_IS_C; break;
+ default: return;
+ }
+ switch (pkt[1]) {
+ case 'D': dqr = DQR_IS_D; break;
+ case 'Q': dqr = DQR_IS_Q; break;
+ case 'R': dqr = DQR_IS_R; break;
+ case '_': dqr = DQR_NONE; break; /* Can be valid, like in R mode. */
+ default: return;
+ }
+ freq = get_frequency(pkt[2]);
+ model = get_equiv_model(pkt[0], pkt[3]);
+ is_auto = pkt[4] == 'A';
+ main_ranging = pkt[5] == '8';
+ if (main_ranging) /* Switching ranges. */
+ return;
+ main_ol = pkt[5] == '9';
+ main_value = parse_number(&pkt[5], 5);
+ main_range = pkt[10];
+ if (main_range < '0' || main_range > '6')
+ main_range = '9';
+ main_range -= '0';
+ /*
+ * Contrary to the documentation, there have been valid four-digit
+ * values in the secondary display which start with '9'. Let's not
+ * consider these as overflown. Out-of-range 'range' specs for the
+ * secondary display will also invalidate these values.
+ */
+ sec_ol = 0 && pkt[11] == '9';
+ sec_value = parse_number(&pkt[11], 4);
+ sec_range = pkt[15];
+ if (sec_range < '0' || sec_range > '6')
+ sec_range = '9';
+ sec_range -= '0';
+ d_ol = pkt[17] == '9';
+ d_value = parse_number(&pkt[17], 4);
+ d_range = pkt[21];
+ if (d_range < '0' || d_range > '6')
+ d_range = '9';
+ d_range -= '0';
+ q_ol = pkt[22] == '9';
+ q_value = parse_number(&pkt[22], 4);
+ q_range = pkt[26];
+ if (q_range < '0' || q_range > '6')
+ q_range = '9';
+ d_range -= '0';
+ switch (pkt[27]) {
+ case 'S': return; /* Setup mode. Not supported. */
+ case '_': /* EMPTY */ break;
+ default: return; /* Unknown. */
+ }
+ is_hold = pkt[29] == 'H';
+ switch (pkt[30]) { /* Min/max modes. */
+ case 'R': minmax = MINMAX_CURR; break; /* Live reading. */
+ case 'M': minmax = MINMAX_MAX; break;
+ case 'I': minmax = MINMAX_MIN; break;
+ case 'X': minmax = MINMAX_SPAN; break; /* "Max - min" difference. */
+ case 'A': minmax = MINMAX_AVG; break;
+ case '_': minmax = MINMAX_NONE; break;
+ default: return; /* Unknown. */
+ }
+ if (minmax == MINMAX_SPAN) /* Not supported. */
+ return;
+ if (minmax == MINMAX_CURR) /* Normalize. */
+ minmax = MINMAX_NONE;
+ switch (pkt[31]) {
+ case 'R': is_relative = TRUE; break;
+ case 'S': return; /* Relative setup. Not supported. */
+ /* TODO Is this SR_MQFLAG_REFERENCE? */
+ case '_': is_relative = FALSE; break;
+ default: return; /* Unknown. */
+ }
+ if (pkt[32] != '_') /* Limits. Not supported. */
+ return;
+ if (pkt[33] != '_') /* Tolerance. Not supported. */
+ return;
+ has_adapter = pkt[35] == 'A';
+ is_lowbatt = pkt[36] == 'B';
+
+ /*
+ * Always need to inspect the main display's properties, to
+ * determine how to interpret the secondary displays.
+ */
+ rs = main_digits = sec_digits = d_digits = q_digits = 0;
+ main_invalid = sec_invalid = d_invalid = q_invalid = 0;
+ ret = get_main_scale_rs(&main_digits, &rs, main_range, lcr, freq);
+ if (ret != SR_OK)
+ main_invalid = 1;
+ ret = get_sec_scale(&sec_digits, sec_range, dqr, rs);
+ if (ret != SR_OK)
+ sec_invalid = 1;
+ ret = get_sec_scale(&d_digits, d_range, dqr, rs);
+ if (ret != SR_OK)
+ d_invalid = 1;
+ ret = get_sec_scale(&q_digits, q_range, dqr, rs);
+ if (ret != SR_OK)
+ q_invalid = 1;
+
+ /* Determine the measurement value and its units. Apply scaling. */
+ is_parallel = model == MODEL_PAR;
+ mq = 0;
+ mqflags = 0;
+ unit = 0;
+ switch (disp_idx) {
+ case VC4080_DISPLAY_PRIMARY:
+ invalid = main_invalid;
+ if (invalid)
+ break;
+ if (lcr == LCR_IS_L) {
+ mq = is_parallel
+ ? SR_MQ_PARALLEL_INDUCTANCE
+ : SR_MQ_SERIES_INDUCTANCE;
+ unit = SR_UNIT_HENRY;
+ } else if (lcr == LCR_IS_C) {
+ mq = is_parallel
+ ? SR_MQ_PARALLEL_CAPACITANCE
+ : SR_MQ_SERIES_CAPACITANCE;
+ unit = SR_UNIT_FARAD;
+ } else if (lcr == LCR_IS_R) {
+ mq = is_parallel
+ ? SR_MQ_PARALLEL_RESISTANCE
+ : SR_MQ_SERIES_RESISTANCE;
+ unit = SR_UNIT_OHM;
+ }
+ value = main_value;
+ ol = main_ol;
+ digits = 0;
+ exponent = main_digits;
+ break;
+ case VC4080_DISPLAY_SECONDARY:
+ invalid = sec_invalid;
+ if (invalid)
+ break;
+ if (dqr == DQR_IS_D) {
+ mq = SR_MQ_DISSIPATION_FACTOR;
+ unit = SR_UNIT_UNITLESS;
+ } else if (dqr == DQR_IS_Q) {
+ mq = SR_MQ_QUALITY_FACTOR;
+ unit = SR_UNIT_UNITLESS;
+ } else if (dqr == DQR_IS_R) {
+ mq = SR_MQ_RESISTANCE;
+ unit = SR_UNIT_OHM;
+ }
+ value = sec_value;
+ ol = sec_ol;
+ digits = 0;
+ exponent = sec_digits;
+ break;
+#if VC4080_WITH_DQ_CHANS
+ case VC4080_DISPLAY_D_VALUE:
+ invalid = d_invalid;
+ if (invalid)
+ break;
+ mq = SR_MQ_DISSIPATION_FACTOR;
+ unit = SR_UNIT_UNITLESS;
+ value = d_value;
+ ol = d_ol;
+ digits = 4;
+ exponent = d_digits;
+ break;
+ case VC4080_DISPLAY_Q_VALUE:
+ invalid = q_invalid;
+ if (invalid)
+ break;
+ mq = SR_MQ_QUALITY_FACTOR;
+ unit = SR_UNIT_UNITLESS;
+ value = q_value;
+ ol = q_ol;
+ digits = 4;
+ exponent = q_digits;
+ break;
+#else
+ (void)d_invalid;
+ (void)d_value;
+ (void)d_ol;
+ (void)d_digits;
+ (void)q_invalid;
+ (void)q_value;
+ (void)q_ol;
+ (void)q_digits;
+#endif
+ default:
+ /* ShouldNotHappen(TM). Won't harm either. Silences warnings. */
+ return;
+ }
+ if (invalid)
+ return;
+ if (is_auto)
+ mqflags |= SR_MQFLAG_AUTORANGE;
+ if (is_hold)
+ mqflags |= SR_MQFLAG_HOLD;
+ if (is_relative)
+ mqflags |= SR_MQFLAG_RELATIVE;
+ if (has_adapter)
+ mqflags |= SR_MQFLAG_FOUR_WIRE;
+ switch (minmax) {
+ case MINMAX_MAX:
+ mqflags |= SR_MQFLAG_MAX;
+ break;
+ case MINMAX_MIN:
+ mqflags |= SR_MQFLAG_MIN;
+ break;
+ case MINMAX_SPAN:
+ mqflags |= SR_MQFLAG_MAX | SR_MQFLAG_RELATIVE;
+ break;
+ case MINMAX_AVG:
+ mqflags |= SR_MQFLAG_AVG;
+ break;
+ case MINMAX_CURR:
+ case MINMAX_NONE:
+ default:
+ /* EMPTY */
+ break;
+ }
+
+ /* "Commit" the resulting value. */
+ if (ol) {
+ value = INFINITY;
+ } else {
+ value *= powf(10, exponent);
+ digits -= exponent;
+ }
+ *floatval = value;
+ analog->meaning->mq = mq;
+ analog->meaning->mqflags = mqflags;
+ analog->meaning->unit = unit;
+ analog->encoding->digits = digits;
+ analog->spec->spec_digits = digits;
+
+ /* Low battery is rather severe, the measurement could be invalid. */
+ if (is_lowbatt)
+ sr_warn("Low battery.");
+}
+
+/*
+ * Workaround for cables' improper(?) parity handling.
+ * TODO Should this move to serial-lcr or even common libsigrok code?
+ *
+ * Implementor's note: Serial communication is documented to be 1200/7e1.
+ * But practial setups with the shipped FT232R cable received no response
+ * at all with these settings. The 8n1 configuration resulted in responses
+ * while the LCR meter's packet parser then needs to strip the parity bits.
+ *
+ * Let's run this slightly modified setup for now, until more cables and
+ * compatible devices got observed and the proper solution gets determined.
+ * This cheat lets us receive measurement data right now. Stripping the
+ * parity bits off the packet bytes here in the parser is an idempotent
+ * operation that happens to work during stream detect as well as in the
+ * acquisition loop. It helps in the 8n1 configuration, and keeps working
+ * transparently in the 7e1 configuration, too. No harm is done, and the
+ * initial device support is achieved.
+ *
+ * By coincidence, the 'N' command which requests the next measurement
+ * value happens to conform with the 7e1 frame format (0b_0100_1110
+ * byte value). When the SETUP commands are supposed to work with this
+ * LCR meter as well, then the serial-lcr driver's TX data and RX data
+ * probably needs to pass LCR chip specific transformation routines,
+ * if the above mentioned parity support in serial cables issue has not
+ * yet been resolved.
+ */
+
+static void strip_parity_bit(uint8_t *p, size_t l)
+{
+ while (l--)
+ *p++ &= ~0x80;
+}
+
+/* LCR packet parser's public API. */
+
+SR_PRIV const char *vc4080_channel_formats[VC4080_CHANNEL_COUNT] = {
+ "P1", "P2",
+#if VC4080_WITH_DQ_CHANS
+ "D", "Q",
+#endif
+};
+
+SR_PRIV int vc4080_packet_request(struct sr_serial_dev_inst *serial)
+{
+ static const char *command = "N";
+
+ serial_write_blocking(serial, command, strlen(command), 0);
+
+ return SR_OK;
+}
+
+SR_PRIV gboolean vc4080_packet_valid(const uint8_t *pkt)
+{
+ /* Workaround for funny serial cables. */
+ strip_parity_bit((void *)pkt, VC4080_PACKET_SIZE);
+
+ /* Fixed CR/LF terminator. */
+ if (pkt[37] != '\r' || pkt[38] != '\n')
+ return FALSE;
+
+ return TRUE;
+}
+
+SR_PRIV int vc4080_packet_parse(const uint8_t *pkt, float *val,
+ struct sr_datafeed_analog *analog, void *info)
+{
+ struct lcr_parse_info *parse_info;
+
+ /* Workaround for funny serial cables. */
+ strip_parity_bit((void *)pkt, VC4080_PACKET_SIZE);
+
+ 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);
+
+ 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 vc4080_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
#ifndef LIBSIGROK_LIBSIGROK_INTERNAL_H
#define LIBSIGROK_LIBSIGROK_INTERNAL_H
-#include <stdarg.h>
-#include <stdio.h>
+#include "config.h"
+
#include <glib.h>
-#ifdef HAVE_LIBUSB_1_0
-#include <libusb.h>
+#ifdef HAVE_LIBHIDAPI
+#include <hidapi.h>
#endif
#ifdef HAVE_LIBSERIALPORT
#include <libserialport.h>
#endif
+#ifdef HAVE_LIBUSB_1_0
+#include <libusb.h>
+#endif
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
struct zip;
struct zip_stat;
#ifdef __APPLE__
#define SR_DRIVER_LIST_SECTION "__DATA,__sr_driver_list"
#else
-#define SR_DRIVER_LIST_SECTION "sr_driver_list"
+#define SR_DRIVER_LIST_SECTION "__sr_driver_list"
#endif
/**
};
#endif
-#ifdef HAVE_LIBSERIALPORT
+struct sr_serial_dev_inst;
+#ifdef HAVE_SERIAL_COMM
+struct ser_lib_functions;
+struct ser_hid_chip_functions;
+struct sr_bt_desc;
+typedef void (*serial_rx_chunk_callback)(struct sr_serial_dev_inst *serial,
+ void *cb_data, const void *buf, size_t count);
struct sr_serial_dev_inst {
/** Port name, e.g. '/dev/tty42'. */
char *port;
/** Comm params for serial_set_paramstr(). */
char *serialcomm;
+ struct ser_lib_functions *lib_funcs;
+ struct {
+ int bit_rate;
+ int data_bits;
+ int parity_bits;
+ int stop_bits;
+ } comm_params;
+ GString *rcv_buffer;
+ serial_rx_chunk_callback rx_chunk_cb_func;
+ void *rx_chunk_cb_data;
+#ifdef HAVE_LIBSERIALPORT
/** libserialport port handle */
- struct sp_port *data;
+ struct sp_port *sp_data;
+#endif
+#ifdef HAVE_LIBHIDAPI
+ enum ser_hid_chip_t {
+ SER_HID_CHIP_UNKNOWN, /**!< place holder */
+ SER_HID_CHIP_BTC_BU86X, /**!< Brymen BU86x */
+ SER_HID_CHIP_SIL_CP2110, /**!< SiLabs CP2110 */
+ SER_HID_CHIP_VICTOR_DMM, /**!< Victor 70/86 DMM cable */
+ SER_HID_CHIP_WCH_CH9325, /**!< WCH CH9325 */
+ SER_HID_CHIP_LAST, /**!< sentinel */
+ } hid_chip;
+ struct ser_hid_chip_functions *hid_chip_funcs;
+ char *usb_path;
+ char *usb_serno;
+ const char *hid_path;
+ hid_device *hid_dev;
+ GSList *hid_source_args;
+#endif
+#ifdef HAVE_BLUETOOTH
+ enum ser_bt_conn_t {
+ SER_BT_CONN_UNKNOWN, /**!< place holder */
+ SER_BT_CONN_RFCOMM, /**!< BT classic, RFCOMM channel */
+ SER_BT_CONN_BLE122, /**!< BLE, BLE122 module, indications */
+ SER_BT_CONN_NRF51, /**!< BLE, Nordic nRF51, notifications */
+ SER_BT_CONN_CC254x, /**!< BLE, TI CC254x, notifications */
+ SER_BT_CONN_MAX, /**!< sentinel */
+ } bt_conn_type;
+ char *bt_addr_local;
+ char *bt_addr_remote;
+ size_t bt_rfcomm_channel;
+ uint16_t bt_notify_handle_read;
+ uint16_t bt_notify_handle_write;
+ uint16_t bt_notify_handle_cccd;
+ uint16_t bt_notify_value_cccd;
+ struct sr_bt_desc *bt_desc;
+ GSList *bt_source_args;
+#endif
};
#endif
SR_PRIV void sr_usb_dev_inst_free(struct sr_usb_dev_inst *usb);
#endif
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
+#ifndef HAVE_LIBSERIALPORT
+/*
+ * Some identifiers which initially got provided by libserialport are
+ * used internally within the libsigrok serial layer's implementation,
+ * while libserialport no longer is the exclusive provider of serial
+ * communication support. Declare the identifiers here so they remain
+ * available across all build configurations.
+ */
+enum libsp_parity {
+ SP_PARITY_NONE = 0,
+ SP_PARITY_ODD = 1,
+ SP_PARITY_EVEN = 2,
+ SP_PARITY_MARK = 3,
+ SP_PARITY_SPACE = 4,
+};
+
+enum libsp_flowcontrol {
+ SP_FLOWCONTROL_NONE = 0,
+ SP_FLOWCONTROL_XONXOFF = 1,
+ SP_FLOWCONTROL_RTSCTS = 2,
+ SP_FLOWCONTROL_DTRDSR = 3,
+};
+#endif
+
/* Serial-specific instances */
SR_PRIV struct sr_serial_dev_inst *sr_serial_dev_inst_new(const char *port,
const char *serialcomm);
SR_PRIV int sr_session_source_remove_channel(struct sr_session *session,
GIOChannel *channel);
+SR_PRIV int sr_session_send_meta(const struct sr_dev_inst *sdi,
+ uint32_t key, GVariant *var);
SR_PRIV int sr_session_send(const struct sr_dev_inst *sdi,
const struct sr_datafeed_packet *packet);
SR_PRIV int sr_sessionfile_check(const char *filename);
SR_PRIV int std_dummy_dev_close(struct sr_dev_inst *sdi);
SR_PRIV int std_dummy_dev_acquisition_start(const struct sr_dev_inst *sdi);
SR_PRIV int std_dummy_dev_acquisition_stop(struct sr_dev_inst *sdi);
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
SR_PRIV int std_serial_dev_open(struct sr_dev_inst *sdi);
SR_PRIV int std_serial_dev_acquisition_stop(struct sr_dev_inst *sdi);
#endif
SR_PRIV int std_cg_idx(const struct sr_channel_group *cg, struct sr_channel_group *a[], unsigned int n);
+SR_PRIV int std_dummy_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr);
+
/*--- resource.c ------------------------------------------------------------*/
SR_PRIV int64_t sr_file_get_size(FILE *file);
SR_PRIV int soft_trigger_logic_check(struct soft_trigger_logic *st, uint8_t *buf,
int len, int *pre_trigger_samples);
-/*--- hardware/serial.c -----------------------------------------------------*/
+/*--- serial.c --------------------------------------------------------------*/
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
enum {
SERIAL_RDWR = 1,
SERIAL_RDONLY = 2,
typedef gboolean (*packet_valid_callback)(const uint8_t *buf);
+typedef GSList *(*sr_ser_list_append_t)(GSList *devs, const char *name,
+ const char *desc);
+typedef GSList *(*sr_ser_find_append_t)(GSList *devs, const char *name);
+
SR_PRIV int serial_open(struct sr_serial_dev_inst *serial, int flags);
SR_PRIV int serial_close(struct sr_serial_dev_inst *serial);
SR_PRIV int serial_flush(struct sr_serial_dev_inst *serial);
SR_PRIV int serial_drain(struct sr_serial_dev_inst *serial);
+SR_PRIV size_t serial_has_receive_data(struct sr_serial_dev_inst *serial);
SR_PRIV int serial_write_blocking(struct sr_serial_dev_inst *serial,
const void *buf, size_t count, unsigned int timeout_ms);
SR_PRIV int serial_write_nonblocking(struct sr_serial_dev_inst *serial,
size_t count, unsigned int timeout_ms);
SR_PRIV int serial_read_nonblocking(struct sr_serial_dev_inst *serial, void *buf,
size_t count);
+SR_PRIV int serial_set_read_chunk_cb(struct sr_serial_dev_inst *serial,
+ serial_rx_chunk_callback cb, void *cb_data);
SR_PRIV int serial_set_params(struct sr_serial_dev_inst *serial, int baudrate,
int bits, int parity, int stopbits, int flowcontrol, int rts, int dtr);
SR_PRIV int serial_set_paramstr(struct sr_serial_dev_inst *serial,
uint8_t *buf, size_t *buflen,
size_t packet_size,
packet_valid_callback is_valid,
- uint64_t timeout_ms, int baudrate);
+ uint64_t timeout_ms);
SR_PRIV int sr_serial_extract_options(GSList *options, const char **serial_device,
const char **serial_options);
SR_PRIV int serial_source_add(struct sr_session *session,
struct sr_serial_dev_inst *serial);
SR_PRIV GSList *sr_serial_find_usb(uint16_t vendor_id, uint16_t product_id);
SR_PRIV int serial_timeout(struct sr_serial_dev_inst *port, int num_bytes);
+
+SR_PRIV void sr_ser_discard_queued_data(struct sr_serial_dev_inst *serial);
+SR_PRIV size_t sr_ser_has_queued_data(struct sr_serial_dev_inst *serial);
+SR_PRIV void sr_ser_queue_rx_data(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, size_t len);
+SR_PRIV size_t sr_ser_unqueue_rx_data(struct sr_serial_dev_inst *serial,
+ uint8_t *data, size_t len);
+
+struct ser_lib_functions {
+ int (*open)(struct sr_serial_dev_inst *serial, int flags);
+ int (*close)(struct sr_serial_dev_inst *serial);
+ int (*flush)(struct sr_serial_dev_inst *serial);
+ int (*drain)(struct sr_serial_dev_inst *serial);
+ int (*write)(struct sr_serial_dev_inst *serial,
+ const void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms);
+ int (*read)(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms);
+ int (*set_params)(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr);
+ int (*setup_source_add)(struct sr_session *session,
+ struct sr_serial_dev_inst *serial,
+ int events, int timeout,
+ sr_receive_data_callback cb, void *cb_data);
+ int (*setup_source_remove)(struct sr_session *session,
+ struct sr_serial_dev_inst *serial);
+ GSList *(*list)(GSList *list, sr_ser_list_append_t append);
+ GSList *(*find_usb)(GSList *list, sr_ser_find_append_t append,
+ uint16_t vendor_id, uint16_t product_id);
+ int (*get_frame_format)(struct sr_serial_dev_inst *serial,
+ int *baud, int *bits);
+ size_t (*get_rx_avail)(struct sr_serial_dev_inst *serial);
+};
+extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_libsp;
+SR_PRIV int ser_name_is_hid(struct sr_serial_dev_inst *serial);
+extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_hid;
+SR_PRIV int ser_name_is_bt(struct sr_serial_dev_inst *serial);
+extern SR_PRIV struct ser_lib_functions *ser_lib_funcs_bt;
+
+#ifdef HAVE_LIBHIDAPI
+struct vid_pid_item {
+ uint16_t vid, pid;
+};
+
+struct ser_hid_chip_functions {
+ const char *chipname;
+ const char *chipdesc;
+ const struct vid_pid_item *vid_pid_items;
+ const int max_bytes_per_request;
+ int (*set_params)(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr);
+ int (*read_bytes)(struct sr_serial_dev_inst *serial,
+ uint8_t *data, int space, unsigned int timeout);
+ int (*write_bytes)(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, int space);
+ int (*flush)(struct sr_serial_dev_inst *serial);
+ int (*drain)(struct sr_serial_dev_inst *serial);
+};
+extern SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_bu86x;
+extern SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_ch9325;
+extern SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_cp2110;
+extern SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_victor;
+SR_PRIV const char *ser_hid_chip_find_name_vid_pid(uint16_t vid, uint16_t pid);
+#endif
+#endif
+
+/*--- bt/ API ---------------------------------------------------------------*/
+
+#ifdef HAVE_BLUETOOTH
+SR_PRIV const char *sr_bt_adapter_get_address(size_t idx);
+
+struct sr_bt_desc;
+typedef void (*sr_bt_scan_cb)(void *cb_data, const char *addr, const char *name);
+typedef int (*sr_bt_data_cb)(void *cb_data, uint8_t *data, size_t dlen);
+
+SR_PRIV struct sr_bt_desc *sr_bt_desc_new(void);
+SR_PRIV void sr_bt_desc_free(struct sr_bt_desc *desc);
+
+SR_PRIV int sr_bt_config_cb_scan(struct sr_bt_desc *desc,
+ sr_bt_scan_cb cb, void *cb_data);
+SR_PRIV int sr_bt_config_cb_data(struct sr_bt_desc *desc,
+ sr_bt_data_cb cb, void *cb_data);
+SR_PRIV int sr_bt_config_addr_local(struct sr_bt_desc *desc, const char *addr);
+SR_PRIV int sr_bt_config_addr_remote(struct sr_bt_desc *desc, const char *addr);
+SR_PRIV int sr_bt_config_rfcomm(struct sr_bt_desc *desc, size_t channel);
+SR_PRIV int sr_bt_config_notify(struct sr_bt_desc *desc,
+ uint16_t read_handle, uint16_t write_handle,
+ uint16_t cccd_handle, uint16_t cccd_value);
+
+SR_PRIV int sr_bt_scan_le(struct sr_bt_desc *desc, int duration);
+SR_PRIV int sr_bt_scan_bt(struct sr_bt_desc *desc, int duration);
+
+SR_PRIV int sr_bt_connect_ble(struct sr_bt_desc *desc);
+SR_PRIV int sr_bt_connect_rfcomm(struct sr_bt_desc *desc);
+SR_PRIV void sr_bt_disconnect(struct sr_bt_desc *desc);
+
+SR_PRIV ssize_t sr_bt_read(struct sr_bt_desc *desc,
+ void *data, size_t len);
+SR_PRIV ssize_t sr_bt_write(struct sr_bt_desc *desc,
+ const void *data, size_t len);
+
+SR_PRIV int sr_bt_start_notify(struct sr_bt_desc *desc);
+SR_PRIV int sr_bt_check_notify(struct sr_bt_desc *desc);
#endif
-/*--- hardware/ezusb.c ------------------------------------------------------*/
+/*--- ezusb.c ---------------------------------------------------------------*/
#ifdef HAVE_LIBUSB_1_0
SR_PRIV int ezusb_reset(struct libusb_device_handle *hdl, int set_clear);
int configuration, const char *name);
#endif
-/*--- hardware/usb.c --------------------------------------------------------*/
+/*--- usb.c -----------------------------------------------------------------*/
#ifdef HAVE_LIBUSB_1_0
SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn);
SR_PRIV int sr_modbus_close(struct sr_modbus_dev_inst *modbus);
SR_PRIV void sr_modbus_free(struct sr_modbus_dev_inst *modbus);
-/*--- hardware/dmm/es519xx.c ------------------------------------------------*/
+/*--- dmm/es519xx.c ---------------------------------------------------------*/
/**
* All 11-byte es519xx chips repeat each block twice for each conversion cycle
SR_PRIV int sr_es519xx_19200_14b_sel_lpf_parse(const uint8_t *buf,
float *floatval, struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/fs9922.c -------------------------------------------------*/
+/*--- dmm/fs9922.c ----------------------------------------------------------*/
#define FS9922_PACKET_SIZE 14
struct sr_datafeed_analog *analog, void *info);
SR_PRIV void sr_fs9922_z1_diode(struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/fs9721.c -------------------------------------------------*/
+/*--- dmm/fs9721.c ----------------------------------------------------------*/
#define FS9721_PACKET_SIZE 14
SR_PRIV void sr_fs9721_01_10_temp_f_c(struct sr_datafeed_analog *analog, void *info);
SR_PRIV void sr_fs9721_max_c_min(struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/ms8250d.c ------------------------------------------------*/
+/*--- dmm/ms2115b.c ---------------------------------------------------------*/
+
+#define MS2115B_PACKET_SIZE 9
+
+enum ms2115b_display {
+ MS2115B_DISPLAY_MAIN,
+ MS2115B_DISPLAY_SUB,
+ MS2115B_DISPLAY_COUNT,
+};
+
+struct ms2115b_info {
+ /* Selected channel. */
+ size_t ch_idx;
+ gboolean is_ac, is_dc, is_auto;
+ gboolean is_diode, is_beep, is_farad;
+ gboolean is_ohm, is_ampere, is_volt, is_hz;
+ gboolean is_duty_cycle, is_percent;
+};
+
+extern SR_PRIV const char *ms2115b_channel_formats[];
+SR_PRIV gboolean sr_ms2115b_packet_valid(const uint8_t *buf);
+SR_PRIV int sr_ms2115b_parse(const uint8_t *buf, float *floatval,
+ struct sr_datafeed_analog *analog, void *info);
+
+/*--- dmm/ms8250d.c ---------------------------------------------------------*/
#define MS8250D_PACKET_SIZE 18
SR_PRIV int sr_ms8250d_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/dtm0660.c ------------------------------------------------*/
+/*--- dmm/dtm0660.c ---------------------------------------------------------*/
#define DTM0660_PACKET_SIZE 15
SR_PRIV int sr_dtm0660_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/m2110.c --------------------------------------------------*/
+/*--- dmm/m2110.c -----------------------------------------------------------*/
#define BBCGM_M2110_PACKET_SIZE 9
SR_PRIV int sr_m2110_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/metex14.c ------------------------------------------------*/
+/*--- dmm/metex14.c ---------------------------------------------------------*/
#define METEX14_PACKET_SIZE 14
gboolean is_hfe, is_unitless, is_logic, is_min, is_max, is_avg;
};
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
SR_PRIV int sr_metex14_packet_request(struct sr_serial_dev_inst *serial);
#endif
SR_PRIV gboolean sr_metex14_packet_valid(const uint8_t *buf);
SR_PRIV int sr_metex14_4packets_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/rs9lcd.c -------------------------------------------------*/
+/*--- dmm/rs9lcd.c ----------------------------------------------------------*/
#define RS9LCD_PACKET_SIZE 9
SR_PRIV int sr_rs9lcd_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/bm25x.c --------------------------------------------------*/
+/*--- dmm/bm25x.c -----------------------------------------------------------*/
#define BRYMEN_BM25X_PACKET_SIZE 15
SR_PRIV int sr_brymen_bm25x_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/ut71x.c --------------------------------------------------*/
+/*--- dmm/bm86x.c -----------------------------------------------------------*/
+
+#define BRYMEN_BM86X_PACKET_SIZE 24
+#define BRYMEN_BM86X_DISPLAY_COUNT 2
+
+struct brymen_bm86x_info { size_t ch_idx; };
+
+#ifdef HAVE_SERIAL_COMM
+SR_PRIV int sr_brymen_bm86x_packet_request(struct sr_serial_dev_inst *serial);
+#endif
+SR_PRIV gboolean sr_brymen_bm86x_packet_valid(const uint8_t *buf);
+SR_PRIV int sr_brymen_bm86x_parse(const uint8_t *buf, float *floatval,
+ struct sr_datafeed_analog *analog, void *info);
+
+/*--- dmm/ut71x.c -----------------------------------------------------------*/
#define UT71X_PACKET_SIZE 11
SR_PRIV int sr_ut71x_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/vc870.c --------------------------------------------------*/
+/*--- dmm/vc870.c -----------------------------------------------------------*/
#define VC870_PACKET_SIZE 23
SR_PRIV int sr_vc870_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/vc96.c ---------------------------------------------------*/
+/*--- dmm/vc96.c ------------------------------------------------------------*/
#define VC96_PACKET_SIZE 13
SR_PRIV int sr_vc96_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/lcr/es51919.c ------------------------------------------------*/
-
-SR_PRIV void es51919_serial_clean(void *priv);
-SR_PRIV struct sr_dev_inst *es51919_serial_scan(GSList *options,
- const char *vendor,
- const char *model);
-SR_PRIV int es51919_serial_config_get(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi,
- const struct sr_channel_group *cg);
-SR_PRIV int es51919_serial_config_set(uint32_t key, GVariant *data,
- const struct sr_dev_inst *sdi,
- const struct sr_channel_group *cg);
-SR_PRIV int es51919_serial_config_list(uint32_t key, GVariant **data,
- const struct sr_dev_inst *sdi,
- const struct sr_channel_group *cg);
-SR_PRIV int es51919_serial_acquisition_start(const struct sr_dev_inst *sdi);
-SR_PRIV int es51919_serial_acquisition_stop(struct sr_dev_inst *sdi);
-
-/*--- hardware/dmm/ut372.c --------------------------------------------------*/
+/*--- lcr/es51919.c ---------------------------------------------------------*/
+
+/* Acquisition details which apply to all supported serial-lcr devices. */
+struct lcr_parse_info {
+ size_t ch_idx;
+ uint64_t output_freq;
+ const char *circuit_model;
+};
+
+#define ES51919_PACKET_SIZE 17
+#define ES51919_CHANNEL_COUNT 2
+#define ES51919_COMM_PARAM "9600/8n1/rts=1/dtr=1"
+
+SR_PRIV int es51919_config_get(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
+SR_PRIV int es51919_config_set(uint32_t key, GVariant *data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
+SR_PRIV int es51919_config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
+SR_PRIV gboolean es51919_packet_valid(const uint8_t *pkt);
+SR_PRIV int es51919_packet_parse(const uint8_t *pkt, float *floatval,
+ struct sr_datafeed_analog *analog, void *info);
+
+/*--- lcr/vc4080.c ----------------------------------------------------------*/
+
+/* Note: Also uses 'struct lcr_parse_info' from es51919 above. */
+
+#define VC4080_PACKET_SIZE 39
+#define VC4080_COMM_PARAM "1200/8n1"
+#define VC4080_WITH_DQ_CHANS 0 /* Enable separate D/Q channels? */
+
+enum vc4080_display {
+ VC4080_DISPLAY_PRIMARY,
+ VC4080_DISPLAY_SECONDARY,
+#if VC4080_WITH_DQ_CHANS
+ VC4080_DISPLAY_D_VALUE,
+ VC4080_DISPLAY_Q_VALUE,
+#endif
+ VC4080_CHANNEL_COUNT,
+};
+
+extern SR_PRIV const char *vc4080_channel_formats[VC4080_CHANNEL_COUNT];
+
+SR_PRIV int vc4080_config_list(uint32_t key, GVariant **data,
+ const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
+SR_PRIV int vc4080_packet_request(struct sr_serial_dev_inst *serial);
+SR_PRIV gboolean vc4080_packet_valid(const uint8_t *pkt);
+SR_PRIV int vc4080_packet_parse(const uint8_t *pkt, float *floatval,
+ struct sr_datafeed_analog *analog, void *info);
+
+/*--- dmm/ut372.c -----------------------------------------------------------*/
#define UT372_PACKET_SIZE 27
SR_PRIV int sr_ut372_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/dmm/asycii.c -------------------------------------------------*/
+/*--- dmm/asycii.c ----------------------------------------------------------*/
#define ASYCII_PACKET_SIZE 16
gboolean is_invalid;
};
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
SR_PRIV int sr_asycii_packet_request(struct sr_serial_dev_inst *serial);
#endif
SR_PRIV gboolean sr_asycii_packet_valid(const uint8_t *buf);
SR_PRIV int sr_asycii_parse(const uint8_t *buf, float *floatval,
struct sr_datafeed_analog *analog, void *info);
-/*--- src/dmm/eev121gw.c ----------------------------------------------------*/
+/*--- dmm/eev121gw.c --------------------------------------------------------*/
#define EEV121GW_PACKET_SIZE 19
extern SR_PRIV const char *eev121gw_channel_formats[];
SR_PRIV gboolean sr_eev121gw_packet_valid(const uint8_t *buf);
-SR_PRIV int sr_eev121gw_parse(const uint8_t *buf, float *floatval,
- struct sr_datafeed_analog *analog, void *info);
SR_PRIV int sr_eev121gw_3displays_parse(const uint8_t *buf, float *floatval,
- struct sr_datafeed_analog *analog, void *info);
+ struct sr_datafeed_analog *analog, void *info);
-/*--- hardware/scale/kern.c -------------------------------------------------*/
+/*--- scale/kern.c ----------------------------------------------------------*/
struct kern_info {
gboolean is_gram, is_carat, is_ounce, is_pound, is_troy_ounce;
struct sr_sw_limits {
uint64_t limit_samples;
+ uint64_t limit_frames;
uint64_t limit_msec;
uint64_t samples_read;
+ uint64_t frames_read;
uint64_t start_time;
};
SR_PRIV gboolean sr_sw_limits_check(struct sr_sw_limits *limits);
SR_PRIV void sr_sw_limits_update_samples_read(struct sr_sw_limits *limits,
uint64_t samples_read);
+SR_PRIV void sr_sw_limits_update_frames_read(struct sr_sw_limits *limits,
+ uint64_t frames_read);
SR_PRIV void sr_sw_limits_init(struct sr_sw_limits *limits);
#endif
SR_PRIV extern const struct sr_modbus_dev_inst modbus_serial_rtu_dev;
static const struct sr_modbus_dev_inst *modbus_devs[] = {
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
&modbus_serial_rtu_dev, /* Must be last as it matches any resource. */
#endif
};
#define LOG_PREFIX "modbus_serial"
+#ifdef HAVE_SERIAL_COMM
+
#define BUFFER_SIZE 1024
struct modbus_serial_rtu {
.close = modbus_serial_rtu_close,
.free = modbus_serial_rtu_free,
};
+
+#endif
g_string_append_printf(*out, "%"
G_GUINT64_FORMAT,
g_variant_get_uint64(src->data));
+ } else if (srci->datatype == SR_T_STRING) {
+ g_string_append_printf(*out, "%s",
+ g_variant_get_string(src->data, NULL));
}
g_string_append(*out, "\n");
}
}
header = g_string_sized_new(512);
- g_string_printf(header, "%s %s\n", PACKAGE_NAME, SR_PACKAGE_VERSION_STRING);
+ g_string_printf(header, "%s %s\n", PACKAGE_NAME, sr_package_version_string_get());
num_channels = g_slist_length(o->sdi->channels);
g_string_append_printf(header, "Acquisition with %d/%d channels",
ctx->num_enabled_channels, num_channels);
}
header = g_string_sized_new(512);
- g_string_printf(header, "%s %s\n", PACKAGE_NAME, SR_PACKAGE_VERSION_STRING);
+ g_string_printf(header, "%s %s\n", PACKAGE_NAME, sr_package_version_string_get());
num_channels = g_slist_length(o->sdi->channels);
g_string_append_printf(header, "Acquisition with %d/%d channels",
ctx->num_enabled_channels, num_channels);
* Values are "channel", "units", "off". Defaults to "units".
*
* time: Whether or not the first column should include the time the sample
- * was taken. Defaults to TRUE.
+ * was taken. Defaults to FALSE.
*
* trigger: Whether or not to add a "trigger" column as the last column.
* Defaults to FALSE.
g_string_append_printf(header,
"%s CSV generated by %s %s\n%s from %s on %s",
ctx->comment, PACKAGE_NAME,
- SR_PACKAGE_VERSION_STRING, ctx->comment,
+ sr_package_version_string_get(), ctx->comment,
ctx->title, ctime(&hdr->starttime.tv_sec));
/* Columns / channels */
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("channel")));
l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("off")));
options[7].values = l;
- options[8].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
+ options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
}
}
header = g_string_sized_new(512);
- g_string_printf(header, "%s %s\n", PACKAGE_NAME, SR_PACKAGE_VERSION_STRING);
+ g_string_printf(header, "%s %s\n", PACKAGE_NAME, sr_package_version_string_get());
num_channels = g_slist_length(o->sdi->channels);
g_string_append_printf(header, "Acquisition with %d/%d channels",
ctx->num_enabled_channels, num_channels);
extern SR_PRIV struct sr_output_module output_analog;
extern SR_PRIV struct sr_output_module output_srzip;
extern SR_PRIV struct sr_output_module output_wav;
+extern SR_PRIV struct sr_output_module output_wavedrom;
extern SR_PRIV struct sr_output_module output_null;
/* @endcond */
&output_analog,
&output_srzip,
&output_wav,
+ &output_wavedrom,
&output_null,
NULL,
};
meta = g_key_file_new();
g_key_file_set_string(meta, "global", "sigrok version",
- SR_PACKAGE_VERSION_STRING);
+ sr_package_version_string_get());
devgroup = "device 1";
int num_enabled_channels;
uint8_t *prevsample;
gboolean header_done;
- int period;
+ uint64_t period;
int *channel_index;
uint64_t samplerate;
uint64_t samplecount;
return SR_OK;
}
+/*
+ * VCD can only handle 1/10/100 factors in the s to fs range. Find a
+ * suitable timescale which satisfies this resolution constraint, yet
+ * won't result in excessive overhead.
+ */
+static uint64_t get_timescale_freq(uint64_t samplerate)
+{
+ uint64_t timescale;
+ int max_up_scale;
+
+ /* Go to the next full decade. */
+ timescale = 1;
+ while (timescale < samplerate) {
+ timescale *= 10;
+ }
+
+ /*
+ * Avoid loss of precision, go up a few more decades when needed.
+ * For example switch to 10GHz timescale when samplerate is 400MHz.
+ * Stop after at most factor 100 to not loop endlessly for odd
+ * samplerates, yet provide good enough accuracy.
+ */
+ max_up_scale = 2;
+ while (max_up_scale--) {
+ if (timescale / samplerate * samplerate == timescale)
+ break;
+ timescale *= 10;
+ }
+
+ return timescale;
+}
+
static GString *gen_header(const struct sr_output *o)
{
struct context *ctx;
/* generator */
g_string_append_printf(header, "$version %s %s $end\n",
- PACKAGE_NAME, SR_PACKAGE_VERSION_STRING);
+ PACKAGE_NAME, sr_package_version_string_get());
g_string_append_printf(header, "$comment\n Acquisition with "
"%d/%d channels", ctx->num_enabled_channels, num_channels);
g_string_append_printf(header, "\n$end\n");
/* timescale */
- /* VCD can only handle 1/10/100 (s - fs), so scale up first */
- if (ctx->samplerate > SR_MHZ(1))
- ctx->period = SR_GHZ(1);
- else if (ctx->samplerate > SR_KHZ(1))
- ctx->period = SR_MHZ(1);
- else
- ctx->period = SR_KHZ(1);
+ ctx->period = get_timescale_freq(ctx->samplerate);
frequency_s = sr_period_string(1, ctx->period);
g_string_append_printf(header, "$timescale %s $end\n", frequency_s);
g_free(frequency_s);
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2019 Marc Jacobi <obiwanjacobi@hotmail.com>
+ *
+ * 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 <string.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "output/wavedrom"
+
+struct context {
+ uint32_t channel_count;
+ struct sr_channel **channels;
+ GString **channel_outputs; /* output strings */
+};
+
+/* Converts accumulated output data to a JSON string. */
+static GString *wavedrom_render(const struct context *ctx)
+{
+ GString *output;
+ size_t ch, i;
+ char last_char, curr_char;
+
+ output = g_string_new("{ \"signal\": [");
+ for (ch = 0; ch < ctx->channel_count; ch++) {
+ if (!ctx->channel_outputs[ch])
+ continue;
+
+ /* Channel strip. */
+ g_string_append_printf(output,
+ "{ \"name\": \"%s\", \"wave\": \"", ctx->channels[ch]->name);
+
+ last_char = 0;
+ for (i = 0; i < ctx->channel_outputs[ch]->len; i++) {
+ curr_char = ctx->channel_outputs[ch]->str[i];
+ /* Data point. */
+ if (curr_char == last_char) {
+ g_string_append_c(output, '.');
+ } else {
+ g_string_append_c(output, curr_char);
+ last_char = curr_char;
+ }
+ }
+ if (ch < ctx->channel_count - 1) {
+ g_string_append(output, "\" },");
+ } else {
+ /* Last channel, no comma. */
+ g_string_append(output, "\" }");
+ }
+ }
+ g_string_append(output, "], \"config\": { \"skin\": \"narrow\" }}");
+
+ return output;
+}
+
+static void process_logic(const struct context *ctx,
+ const struct sr_datafeed_logic *logic)
+{
+ size_t sample_count, ch, i;
+ uint8_t *sample, bit;
+ GString *accu;
+
+ if (!ctx->channel_count)
+ return;
+
+ /*
+ * Extract the logic bits for each channel and store them
+ * as wavedrom letters (1/0) in each channel's text string.
+ * This transforms the input which consists of sample sets
+ * that span multiple channels into output stripes per logic
+ * channel which consist of bits for that individual channel.
+ *
+ * TODO Reduce memory consumption during accumulation of
+ * output data.
+ *
+ * Ideally we'd accumulate binary chunks, and defer conversion
+ * to the text format. Analog data already won't get here, only
+ * logic data does. When the per-channel transformation also
+ * gets deferred until later, then the only overhead would be
+ * for disabled logic channels. Which may be acceptable or even
+ * negligable.
+ *
+ * An optional addition to the above mentioned accumulation of
+ * binary data is RLE compression. Mark both the position in the
+ * accumulated data as well as a repetition counter, instead of
+ * repeatedly storing the same sample set. The efficiency of
+ * this approach of course depends on the change rate of input
+ * data. But the approach perfectly matches the WaveDrom syntax
+ * for repeated bit patterns, and thus is easily handled in the
+ * text rendering stage of the output module.
+ */
+ sample_count = logic->length / logic->unitsize;
+ for (i = 0; i < sample_count; i++) {
+ sample = logic->data + i * logic->unitsize;
+ for (ch = 0; ch < ctx->channel_count; ch++) {
+ accu = ctx->channel_outputs[ch];
+ if (!accu)
+ continue;
+ bit = sample[ch / 8] & (1 << (ch % 8));
+ g_string_append_c(accu, bit ? '1' : '0');
+ }
+ }
+}
+
+static int receive(const struct sr_output *o,
+ const struct sr_datafeed_packet *packet, GString **out)
+{
+ struct context *ctx;
+
+ *out = NULL;
+
+ if (!o || !o->sdi || !o->priv)
+ return SR_ERR_ARG;
+
+ ctx = o->priv;
+
+ switch (packet->type) {
+ case SR_DF_LOGIC:
+ process_logic(ctx, packet->payload);
+ break;
+ case SR_DF_END:
+ *out = wavedrom_render(ctx);
+ break;
+ }
+
+ return SR_OK;
+}
+
+static int init(struct sr_output *o, GHashTable *options)
+{
+ struct context *ctx;
+ struct sr_channel *channel;
+ GSList *l;
+ size_t i;
+
+ (void)options;
+
+ if (!o || !o->sdi)
+ return SR_ERR_ARG;
+
+ o->priv = ctx = g_malloc0(sizeof(*ctx));
+
+ ctx->channel_count = g_slist_length(o->sdi->channels);
+ ctx->channels = g_malloc0(
+ sizeof(ctx->channels[0]) * ctx->channel_count);
+ ctx->channel_outputs = g_malloc0(
+ sizeof(ctx->channel_outputs[0]) * ctx->channel_count);
+
+ for (i = 0, l = o->sdi->channels; l; l = l->next, i++) {
+ channel = l->data;
+ if (channel->enabled && channel->type == SR_CHANNEL_LOGIC) {
+ ctx->channels[i] = channel;
+ ctx->channel_outputs[i] = g_string_new(NULL);
+ }
+ }
+
+ return SR_OK;
+}
+
+static int cleanup(struct sr_output *o)
+{
+ struct context *ctx;
+ GString *s;
+
+ if (!o)
+ return SR_ERR_ARG;
+
+ ctx = o->priv;
+ o->priv = NULL;
+
+ if (ctx) {
+ while (--ctx->channel_count) {
+ s = ctx->channel_outputs[ctx->channel_count];
+ if (s)
+ g_string_free(s, TRUE);
+ }
+ g_free(ctx->channel_outputs);
+ g_free(ctx->channels);
+ g_free(ctx);
+ }
+
+ return SR_OK;
+}
+
+SR_PRIV struct sr_output_module output_wavedrom = {
+ .id = "wavedrom",
+ .name = "WaveDrom",
+ .desc = "WaveDrom.com file format",
+ .exts = (const char *[]){"wavedrom", "json", NULL},
+ .flags = 0,
+ .options = NULL,
+ .init = init,
+ .receive = receive,
+ .cleanup = cleanup,
+};
#define SCPI_CMD_OPC "*OPC?"
enum {
- SCPI_CMD_SET_TRIGGER_SOURCE = 1,
+ SCPI_CMD_GET_TIMEBASE = 1,
SCPI_CMD_SET_TIMEBASE,
- SCPI_CMD_SET_VERTICAL_DIV,
+ SCPI_CMD_GET_HORIZONTAL_DIV,
+ SCPI_CMD_GET_VERTICAL_SCALE,
+ SCPI_CMD_SET_VERTICAL_SCALE,
+ SCPI_CMD_GET_TRIGGER_SOURCE,
+ SCPI_CMD_SET_TRIGGER_SOURCE,
+ SCPI_CMD_GET_TRIGGER_SLOPE,
SCPI_CMD_SET_TRIGGER_SLOPE,
+ SCPI_CMD_GET_TRIGGER_PATTERN,
+ SCPI_CMD_SET_TRIGGER_PATTERN,
+ SCPI_CMD_GET_HIGH_RESOLUTION,
+ SCPI_CMD_SET_HIGH_RESOLUTION,
+ SCPI_CMD_GET_PEAK_DETECTION,
+ SCPI_CMD_SET_PEAK_DETECTION,
+ SCPI_CMD_GET_COUPLING,
SCPI_CMD_SET_COUPLING,
+ SCPI_CMD_GET_HORIZ_TRIGGERPOS,
SCPI_CMD_SET_HORIZ_TRIGGERPOS,
SCPI_CMD_GET_ANALOG_CHAN_STATE,
- SCPI_CMD_GET_DIG_CHAN_STATE,
- SCPI_CMD_GET_TIMEBASE,
- SCPI_CMD_GET_VERTICAL_DIV,
- SCPI_CMD_GET_VERTICAL_OFFSET,
- SCPI_CMD_GET_TRIGGER_SOURCE,
- SCPI_CMD_GET_HORIZ_TRIGGERPOS,
- SCPI_CMD_GET_TRIGGER_SLOPE,
- SCPI_CMD_GET_COUPLING,
SCPI_CMD_SET_ANALOG_CHAN_STATE,
+ SCPI_CMD_GET_DIG_CHAN_STATE,
SCPI_CMD_SET_DIG_CHAN_STATE,
+ SCPI_CMD_GET_VERTICAL_OFFSET,
SCPI_CMD_GET_DIG_POD_STATE,
SCPI_CMD_SET_DIG_POD_STATE,
SCPI_CMD_GET_ANALOG_DATA,
SCPI_CMD_GET_DIG_DATA,
SCPI_CMD_GET_SAMPLE_RATE,
- SCPI_CMD_GET_SAMPLE_RATE_LIVE,
- SCPI_CMD_GET_DATA_FORMAT,
- SCPI_CMD_GET_PROBE_FACTOR,
- SCPI_CMD_SET_PROBE_FACTOR,
SCPI_CMD_GET_PROBE_UNIT,
- SCPI_CMD_SET_PROBE_UNIT,
- SCPI_CMD_GET_ANALOG_CHAN_NAME,
- SCPI_CMD_GET_DIG_CHAN_NAME,
+ SCPI_CMD_GET_DIG_POD_THRESHOLD,
+ SCPI_CMD_SET_DIG_POD_THRESHOLD,
+ SCPI_CMD_GET_DIG_POD_USER_THRESHOLD,
+ SCPI_CMD_SET_DIG_POD_USER_THRESHOLD,
+};
+
+enum scpi_transport_layer {
+ SCPI_TRANSPORT_LIBGPIB,
+ SCPI_TRANSPORT_SERIAL,
+ SCPI_TRANSPORT_RAW_TCP,
+ SCPI_TRANSPORT_RIGOL_TCP,
+ SCPI_TRANSPORT_USBTMC,
+ SCPI_TRANSPORT_VISA,
+ SCPI_TRANSPORT_VXI,
};
struct scpi_command {
struct sr_scpi_dev_inst {
const char *name;
const char *prefix;
+ enum scpi_transport_layer transport;
int priv_size;
GSList *(*scan)(struct drv_context *drvc);
int (*dev_inst_new)(void *priv, struct drv_context *drvc,
const char *resource, char **params, const char *serialcomm);
int (*open)(struct sr_scpi_dev_inst *scpi);
+ int (*connection_id)(struct sr_scpi_dev_inst *scpi, char **connection_id);
int (*source_add)(struct sr_session *session, void *priv, int events,
int timeout, sr_receive_data_callback cb, void *cb_data);
int (*source_remove)(struct sr_session *session, void *priv);
SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc,
const char *resource, const char *serialcomm);
SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi);
+SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id);
SR_PRIV int sr_scpi_source_add(struct sr_session *session,
struct sr_scpi_dev_inst *scpi, int events, int timeout,
sr_receive_data_callback cb, void *cb_data);
struct sr_scpi_hw_info **scpi_response);
SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info);
+SR_PRIV const char *sr_scpi_unquote_string(char *s);
+
SR_PRIV const char *sr_vendor_alias(const char *raw_vendor);
SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable,
int command);
int channel_command, const char *channel_name,
GVariant **gvar, const GVariantType *gvtype, int command, ...);
+/*--- GPIB only functions ---------------------------------------------------*/
+
+#ifdef HAVE_LIBGPIB
+SR_PRIV int sr_scpi_gpib_spoll(struct sr_scpi_dev_inst *scpi, char *buf);
+#endif
+
#endif
#define SCPI_READ_RETRY_TIMEOUT_US (10 * 1000)
static const char *scpi_vendors[][2] = {
- { "HEWLETT-PACKARD", "HP" },
{ "Agilent Technologies", "Agilent" },
- { "RIGOL TECHNOLOGIES", "Rigol" },
- { "PHILIPS", "Philips" },
{ "CHROMA", "Chroma" },
{ "Chroma ATE", "Chroma" },
+ { "HEWLETT-PACKARD", "HP" },
+ { "Keysight Technologies", "Keysight" },
+ { "PHILIPS", "Philips" },
+ { "RIGOL TECHNOLOGIES", "Rigol" },
};
/**
#ifdef HAVE_LIBGPIB
&scpi_libgpib_dev,
#endif
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
&scpi_serial_dev, /* Must be last as it matches any resource. */
#endif
};
return scpi->open(scpi);
}
+/**
+ * Get the connection ID of the SCPI device.
+ *
+ * @param scpi Previously initialized SCPI device structure.
+ * @param connection_id Pointer where to store the connection ID. The caller
+ * is responsible for g_free()ing the string when it is no longer needed.
+ *
+ * @return SR_OK on success, SR_ERR on failure.
+ */
+SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ return scpi->connection_id(scpi, connection_id);
+}
+
/**
* Add an event source for an SCPI device.
*
{
int ret;
GString* response;
+ gsize oldlen;
char buf[10];
long llen;
long datalen;
*scpi_response = NULL;
/* Get (the first chunk of) the response. */
- while (response->len < 2) {
+ do {
ret = scpi_read_response(scpi, response, timeout);
if (ret < 0) {
g_mutex_unlock(&scpi->scpi_mutex);
g_string_free(response, TRUE);
return ret;
}
- }
+ } while (response->len < 2);
/*
* SCPI protocol data blocks are preceeded with a length spec.
g_string_erase(response, 0, 2 + llen);
/*
- * If the initially assumed length does not cover the data block
- * length, then re-allocate the buffer size to the now known
- * length, and keep reading more chunks of response data.
+ * Re-allocate the buffer size to the now known length
+ * and keep reading more chunks of response data.
*/
- if (response->len < (unsigned long)(datalen)) {
- int oldlen = response->len;
- g_string_set_size(response, datalen);
- g_string_set_size(response, oldlen);
- }
-
- while (response->len < (unsigned long)(datalen)) {
- ret = scpi_read_response(scpi, response, timeout);
- if (ret < 0) {
- g_mutex_unlock(&scpi->scpi_mutex);
- g_string_free(response, TRUE);
- return ret;
- }
- if (ret > 0)
- timeout = g_get_monotonic_time() + scpi->read_timeout_us;
+ oldlen = response->len;
+ g_string_set_size(response, datalen);
+ g_string_set_size(response, oldlen);
+
+ if (oldlen < (unsigned long)(datalen)) {
+ do {
+ oldlen = response->len;
+ ret = scpi_read_response(scpi, response, timeout);
+
+ /* On timeout truncate the buffer and send the partial response
+ * instead of getting stuck on timeouts...
+ */
+ if (ret == SR_ERR_TIMEOUT) {
+ datalen = oldlen;
+ break;
+ }
+ if (ret < 0) {
+ g_mutex_unlock(&scpi->scpi_mutex);
+ g_string_free(response, TRUE);
+ return ret;
+ }
+ if (ret > 0)
+ timeout = g_get_monotonic_time() + scpi->read_timeout_us;
+ } while (response->len < (unsigned long)(datalen));
}
g_mutex_unlock(&scpi->scpi_mutex);
* model, serial number of the instrument and the firmware version.
*/
tokens = g_strsplit(response, ",", 0);
-
- for (num_tokens = 0; tokens[num_tokens] != NULL; num_tokens++);
-
+ num_tokens = g_strv_length(tokens);
if (num_tokens < 4) {
sr_dbg("IDN response not according to spec: %80.s.", response);
g_strfreev(tokens);
g_free(hw_info);
}
+/**
+ * Remove potentially enclosing pairs of quotes, un-escape content.
+ * This implementation modifies the caller's buffer when quotes are found
+ * and doubled quote characters need to get removed from the content.
+ *
+ * @param[in, out] s The SCPI string to check and un-quote.
+ *
+ * @return The start of the un-quoted string.
+ */
+SR_PRIV const char *sr_scpi_unquote_string(char *s)
+{
+ size_t s_len;
+ char quotes[3];
+ char *rdptr;
+
+ /* Immediately bail out on invalid or short input. */
+ if (!s || !*s)
+ return s;
+ s_len = strlen(s);
+ if (s_len < 2)
+ return s;
+
+ /* Check for matching quote characters front and back. */
+ if (s[0] != '\'' && s[0] != '"')
+ return s;
+ if (s[0] != s[s_len - 1])
+ return s;
+
+ /* Need to strip quotes, and un-double quote chars inside. */
+ quotes[0] = quotes[1] = *s;
+ quotes[2] = '\0';
+ s[s_len - 1] = '\0';
+ s++;
+ rdptr = s;
+ while ((rdptr = strstr(rdptr, quotes)) != NULL) {
+ memmove(rdptr, rdptr + 1, strlen(rdptr));
+ rdptr++;
+ }
+
+ return s;
+}
+
SR_PRIV const char *sr_vendor_alias(const char *raw_vendor)
{
unsigned int i;
return SR_OK;
}
+static int scpi_gpib_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ struct scpi_gpib *gscpi = scpi->priv;
+
+ *connection_id = g_strdup_printf("%s/%s", scpi->prefix, gscpi->name);
+
+ return SR_OK;
+}
+
static int scpi_gpib_source_add(struct sr_session *session, void *priv,
int events, int timeout, sr_receive_data_callback cb, void *cb_data)
{
g_free(gscpi->name);
}
+SR_PRIV int sr_scpi_gpib_spoll(struct sr_scpi_dev_inst *scpi, char *buf)
+{
+ struct scpi_gpib *gscpi = scpi->priv;
+
+ g_mutex_lock(&scpi->scpi_mutex);
+ ibrsp(gscpi->descriptor, buf);
+
+ if (ibsta & ERR) {
+ sr_err("Error while serial polling: iberr = %s.",
+ gpib_error_string(iberr));
+ g_mutex_unlock(&scpi->scpi_mutex);
+ return SR_ERR;
+ }
+ g_mutex_unlock(&scpi->scpi_mutex);
+ sr_spew("Successful serial poll: 0x%x", (uint8_t)buf[0]);
+
+ return SR_OK;
+}
+
SR_PRIV const struct sr_scpi_dev_inst scpi_libgpib_dev = {
- .name = "GPIB",
- .prefix = "libgpib",
- .priv_size = sizeof(struct scpi_gpib),
- .dev_inst_new = scpi_gpib_dev_inst_new,
- .open = scpi_gpib_open,
- .source_add = scpi_gpib_source_add,
+ .name = "GPIB",
+ .prefix = "libgpib",
+ .transport = SCPI_TRANSPORT_LIBGPIB,
+ .priv_size = sizeof(struct scpi_gpib),
+ .dev_inst_new = scpi_gpib_dev_inst_new,
+ .open = scpi_gpib_open,
+ .connection_id = scpi_gpib_connection_id,
+ .source_add = scpi_gpib_source_add,
.source_remove = scpi_gpib_source_remove,
- .send = scpi_gpib_send,
- .read_begin = scpi_gpib_read_begin,
- .read_data = scpi_gpib_read_data,
+ .send = scpi_gpib_send,
+ .read_begin = scpi_gpib_read_begin,
+ .read_data = scpi_gpib_read_data,
.read_complete = scpi_gpib_read_complete,
- .close = scpi_gpib_close,
- .free = scpi_gpib_free,
+ .close = scpi_gpib_close,
+ .free = scpi_gpib_free,
};
#define LOG_PREFIX "scpi_serial"
+#ifdef HAVE_SERIAL_COMM
+
struct scpi_serial {
struct sr_serial_dev_inst *serial;
gboolean got_newline;
};
+/* Default serial port options for some known USB devices */
static const struct {
uint16_t vendor_id;
uint16_t product_id;
} scpi_serial_usb_ids[] = {
{ 0x0403, 0xed72, "115200/8n1/flow=1" }, /* Hameg HO720 */
{ 0x0403, 0xed73, "115200/8n1/flow=1" }, /* Hameg HO730 */
- { 0x0aad, 0x0118, "115200/8n1" }, /* R&S HMO1002 */
+ { 0x0aad, 0x0117, "115200/8n1" }, /* R&S HMO series, previously branded as Hameg HMO */
+ { 0x0aad, 0x0118, "115200/8n1" }, /* R&S HMO series, previously branded as Hameg HMO */
+ { 0x0aad, 0x0119, "115200/8n1" }, /* R&S HMO series, previously branded as Hameg HMO */
};
static GSList *scpi_serial_scan(struct drv_context *drvc)
static int scpi_serial_dev_inst_new(void *priv, struct drv_context *drvc,
const char *resource, char **params, const char *serialcomm)
{
+ GSList *l, *r;
+ unsigned i;
struct scpi_serial *sscpi = priv;
(void)drvc;
(void)params;
+ /* If no serial port option is specified on the command-line using the
+ * "serialcomm" driver option, but the device is connected through USB
+ * and it requires a known default serial port option, then used it in
+ * order to avoid data corruption or even worse problems.
+ */
+ if (!serialcomm) {
+ for (i = 0; i < ARRAY_SIZE(scpi_serial_usb_ids); i++) {
+ if (!(l = sr_serial_find_usb(scpi_serial_usb_ids[i].vendor_id,
+ scpi_serial_usb_ids[i].product_id)))
+ continue;
+ for (r = l; r; r = r->next)
+ if (!strcmp(resource, r->data) && scpi_serial_usb_ids[i].serialcomm)
+ serialcomm = scpi_serial_usb_ids[i].serialcomm;
+ g_slist_free_full(l, g_free);
+ }
+ }
+
sscpi->serial = sr_serial_dev_inst_new(resource, serialcomm);
return SR_OK;
return SR_OK;
}
+static int scpi_serial_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ struct scpi_serial *sscpi = scpi->priv;
+ struct sr_serial_dev_inst *serial = sscpi->serial;
+
+ *connection_id = g_strdup(serial->port);
+
+ return SR_OK;
+}
+
static int scpi_serial_source_add(struct sr_session *session, void *priv,
int events, int timeout, sr_receive_data_callback cb, void *cb_data)
{
SR_PRIV const struct sr_scpi_dev_inst scpi_serial_dev = {
.name = "serial",
.prefix = "",
+ .transport = SCPI_TRANSPORT_SERIAL,
.priv_size = sizeof(struct scpi_serial),
.scan = scpi_serial_scan,
.dev_inst_new = scpi_serial_dev_inst_new,
.open = scpi_serial_open,
+ .connection_id = scpi_serial_connection_id,
.source_add = scpi_serial_source_add,
.source_remove = scpi_serial_source_remove,
.send = scpi_serial_send,
.close = scpi_serial_close,
.free = scpi_serial_free,
};
+
+#endif
return SR_OK;
}
+static int scpi_tcp_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ struct scpi_tcp *tcp = scpi->priv;
+
+ *connection_id = g_strdup_printf("%s/%s:%s",
+ scpi->prefix, tcp->address, tcp->port);
+
+ return SR_OK;
+}
+
static int scpi_tcp_source_add(struct sr_session *session, void *priv,
int events, int timeout, sr_receive_data_callback cb, void *cb_data)
{
SR_PRIV const struct sr_scpi_dev_inst scpi_tcp_raw_dev = {
.name = "RAW TCP",
.prefix = "tcp-raw",
+ .transport = SCPI_TRANSPORT_RAW_TCP,
.priv_size = sizeof(struct scpi_tcp),
.dev_inst_new = scpi_tcp_dev_inst_new,
.open = scpi_tcp_open,
+ .connection_id = scpi_tcp_connection_id,
.source_add = scpi_tcp_source_add,
.source_remove = scpi_tcp_source_remove,
.send = scpi_tcp_send,
SR_PRIV const struct sr_scpi_dev_inst scpi_tcp_rigol_dev = {
.name = "RIGOL TCP",
.prefix = "tcp-rigol",
+ .transport = SCPI_TRANSPORT_RIGOL_TCP,
.priv_size = sizeof(struct scpi_tcp),
.dev_inst_new = scpi_tcp_dev_inst_new,
.open = scpi_tcp_open,
+ .connection_id = scpi_tcp_connection_id,
.source_add = scpi_tcp_source_add,
.source_remove = scpi_tcp_source_remove,
.send = scpi_tcp_send,
*/
#include <config.h>
+#include <inttypes.h>
#include <string.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
{ 0x1ab1, 0x0588 }, /* Rigol DS1000 series */
{ 0x1ab1, 0x04b0 }, /* Rigol DS2000 series */
{ 0x1ab1, 0x04b1 }, /* Rigol DS4000 series */
+ { 0x1ab1, 0x0515 }, /* Rigol MSO5000 series */
{ 0x0957, 0x0588 }, /* Agilent DSO1000 series (rebadged Rigol DS1000) */
{ 0x0b21, 0xffff }, /* All Yokogawa devices */
{ 0xf4ec, 0xffff }, /* All Siglent SDS devices */
return SR_OK;
}
+static int scpi_usbtmc_libusb_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ struct scpi_usbtmc_libusb *uscpi = scpi->priv;
+ struct sr_usb_dev_inst *usb = uscpi->usb;
+
+ *connection_id = g_strdup_printf("%s/%" PRIu8 ".%" PRIu8 "",
+ scpi->prefix, usb->bus, usb->address);
+
+ return SR_OK;
+}
+
static int scpi_usbtmc_libusb_source_add(struct sr_session *session,
void *priv, int events, int timeout, sr_receive_data_callback cb,
void *cb_data)
SR_PRIV const struct sr_scpi_dev_inst scpi_usbtmc_libusb_dev = {
.name = "USBTMC",
.prefix = "usbtmc",
+ .transport = SCPI_TRANSPORT_USBTMC,
.priv_size = sizeof(struct scpi_usbtmc_libusb),
.scan = scpi_usbtmc_libusb_scan,
.dev_inst_new = scpi_usbtmc_libusb_dev_inst_new,
.open = scpi_usbtmc_libusb_open,
+ .connection_id = scpi_usbtmc_libusb_connection_id,
.source_add = scpi_usbtmc_libusb_source_add,
.source_remove = scpi_usbtmc_libusb_source_remove,
.send = scpi_usbtmc_libusb_send,
return SR_OK;
}
+static int scpi_visa_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ struct scpi_visa *vscpi = scpi->priv;
+
+ *connection_id = g_strdup_printf("%s/%s", scpi->prefix, vscpi->resource);
+
+ return SR_OK;
+}
+
static int scpi_visa_source_add(struct sr_session *session, void *priv,
int events, int timeout, sr_receive_data_callback cb, void *cb_data)
{
}
SR_PRIV const struct sr_scpi_dev_inst scpi_visa_dev = {
- .name = "VISA",
- .prefix = "visa",
- .priv_size = sizeof(struct scpi_visa),
- .dev_inst_new = scpi_visa_dev_inst_new,
- .open = scpi_visa_open,
- .source_add = scpi_visa_source_add,
+ .name = "VISA",
+ .prefix = "visa",
+ .transport = SCPI_TRANSPORT_VISA,
+ .priv_size = sizeof(struct scpi_visa),
+ .dev_inst_new = scpi_visa_dev_inst_new,
+ .open = scpi_visa_open,
+ .connection_id = scpi_visa_connection_id,
+ .source_add = scpi_visa_source_add,
.source_remove = scpi_visa_source_remove,
- .send = scpi_visa_send,
- .read_begin = scpi_visa_read_begin,
- .read_data = scpi_visa_read_data,
+ .send = scpi_visa_send,
+ .read_begin = scpi_visa_read_begin,
+ .read_data = scpi_visa_read_data,
.read_complete = scpi_visa_read_complete,
- .close = scpi_visa_close,
- .free = scpi_visa_free,
+ .close = scpi_visa_close,
+ .free = scpi_visa_free,
};
return SR_OK;
}
+static int scpi_vxi_connection_id(struct sr_scpi_dev_inst *scpi,
+ char **connection_id)
+{
+ struct scpi_vxi *vxi = scpi->priv;
+
+ *connection_id = g_strdup_printf("%s/%s", scpi->prefix, vxi->address);
+
+ return SR_OK;
+}
+
static int scpi_vxi_source_add(struct sr_session *session, void *priv,
int events, int timeout, sr_receive_data_callback cb, void *cb_data)
{
SR_PRIV const struct sr_scpi_dev_inst scpi_vxi_dev = {
.name = "VXI",
.prefix = "vxi",
+ .transport = SCPI_TRANSPORT_VXI,
.priv_size = sizeof(struct scpi_vxi),
.dev_inst_new = scpi_vxi_dev_inst_new,
.open = scpi_vxi_open,
+ .connection_id = scpi_vxi_connection_id,
.source_add = scpi_vxi_source_add,
.source_remove = scpi_vxi_source_remove,
.send = scpi_vxi_send,
* Copyright (C) 2010-2012 Uwe Hermann <uwe@hermann-uwe.de>
* Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com>
* Copyright (C) 2014 Uffe Jakobsen <uffe@uffe.org>
+ * Copyright (C) 2017-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
#include <stdlib.h>
#include <glib.h>
#include <glib/gstdio.h>
+#ifdef HAVE_LIBSERIALPORT
#include <libserialport.h>
+#endif
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
#ifdef _WIN32
* @{
*/
+#ifdef HAVE_SERIAL_COMM
+
+/* See if an (assumed opened) serial port is of any supported type. */
+static int dev_is_supported(struct sr_serial_dev_inst *serial)
+{
+ if (!serial || !serial->lib_funcs)
+ return 0;
+
+ return 1;
+}
+
/**
* Open the specified serial port.
*
SR_PRIV int serial_open(struct sr_serial_dev_inst *serial, int flags)
{
int ret;
- char *error;
- int sp_flags = 0;
if (!serial) {
sr_dbg("Invalid serial port.");
sr_spew("Opening serial port '%s' (flags %d).", serial->port, flags);
- sp_get_port_by_name(serial->port, &serial->data);
-
- if (flags & SERIAL_RDWR)
- sp_flags = (SP_MODE_READ | SP_MODE_WRITE);
- else if (flags & SERIAL_RDONLY)
- sp_flags = SP_MODE_READ;
+ /*
+ * Determine which serial transport library to use. Derive the
+ * variant from the serial port's name. Default to libserialport
+ * for backwards compatibility.
+ */
+ if (ser_name_is_hid(serial))
+ serial->lib_funcs = ser_lib_funcs_hid;
+ else if (ser_name_is_bt(serial))
+ serial->lib_funcs = ser_lib_funcs_bt;
+ else
+ serial->lib_funcs = ser_lib_funcs_libsp;
+ if (!serial->lib_funcs)
+ return SR_ERR_NA;
- ret = sp_open(serial->data, sp_flags);
+ /*
+ * Note that use of the 'rcv_buffer' is optional, and the buffer's
+ * size heavily depends on the specific transport. That's why the
+ * buffer's content gets accessed and the buffer is released here in
+ * common code, but the buffer gets allocated in libraries' open()
+ * routines.
+ */
- switch (ret) {
- case SP_ERR_ARG:
- sr_err("Attempt to open serial port with invalid parameters.");
- return SR_ERR_ARG;
- case SP_ERR_FAIL:
- error = sp_last_error_message();
- sr_err("Error opening port (%d): %s.",
- sp_last_error_code(), error);
- sp_free_error_message(error);
- return SR_ERR;
- }
+ /*
+ * Run the transport's open routine. Setup the bitrate and the
+ * UART frame format.
+ */
+ if (!serial->lib_funcs->open)
+ return SR_ERR_NA;
+ ret = serial->lib_funcs->open(serial, flags);
+ if (ret != SR_OK)
+ return ret;
if (serial->serialcomm)
return serial_set_paramstr(serial, serial->serialcomm);
*/
SR_PRIV int serial_close(struct sr_serial_dev_inst *serial)
{
- int ret;
- char *error;
+ int rc;
if (!serial) {
sr_dbg("Invalid serial port.");
return SR_ERR;
}
- if (!serial->data) {
- sr_dbg("Cannot close unopened serial port %s.", serial->port);
- return SR_ERR;
- }
-
sr_spew("Closing serial port %s.", serial->port);
- ret = sp_close(serial->data);
+ if (!serial->lib_funcs || !serial->lib_funcs->close)
+ return SR_ERR_NA;
- switch (ret) {
- case SP_ERR_ARG:
- sr_err("Attempt to close an invalid serial port.");
- return SR_ERR_ARG;
- case SP_ERR_FAIL:
- error = sp_last_error_message();
- sr_err("Error closing port (%d): %s.",
- sp_last_error_code(), error);
- sp_free_error_message(error);
- return SR_ERR;
+ rc = serial->lib_funcs->close(serial);
+ if (rc == SR_OK && serial->rcv_buffer) {
+ g_string_free(serial->rcv_buffer, TRUE);
+ serial->rcv_buffer = NULL;
}
- sp_free_port(serial->data);
- serial->data = NULL;
-
- return SR_OK;
+ return rc;
}
/**
- * Flush serial port buffers.
+ * Flush serial port buffers. Empty buffers, discard pending RX and TX data.
*
* @param serial Previously initialized serial port structure.
*
*/
SR_PRIV int serial_flush(struct sr_serial_dev_inst *serial)
{
- int ret;
- char *error;
-
if (!serial) {
sr_dbg("Invalid serial port.");
return SR_ERR;
}
- if (!serial->data) {
- sr_dbg("Cannot flush unopened serial port %s.", serial->port);
- return SR_ERR;
- }
-
sr_spew("Flushing serial port %s.", serial->port);
- ret = sp_flush(serial->data, SP_BUF_BOTH);
+ sr_ser_discard_queued_data(serial);
- switch (ret) {
- case SP_ERR_ARG:
- sr_err("Attempt to flush an invalid serial port.");
- return SR_ERR_ARG;
- case SP_ERR_FAIL:
- error = sp_last_error_message();
- sr_err("Error flushing port (%d): %s.",
- sp_last_error_code(), error);
- sp_free_error_message(error);
- return SR_ERR;
- }
+ if (!serial->lib_funcs || !serial->lib_funcs->flush)
+ return SR_ERR_NA;
- return SR_OK;
+ return serial->lib_funcs->flush(serial);
}
/**
- * Drain serial port buffers.
+ * Drain serial port buffers. Wait for pending TX data to be sent.
*
* @param serial Previously initialized serial port structure.
*
*/
SR_PRIV int serial_drain(struct sr_serial_dev_inst *serial)
{
- int ret;
- char *error;
-
if (!serial) {
sr_dbg("Invalid serial port.");
return SR_ERR;
}
- if (!serial->data) {
- sr_dbg("Cannot drain unopened serial port %s.", serial->port);
- return SR_ERR;
- }
-
sr_spew("Draining serial port %s.", serial->port);
- ret = sp_drain(serial->data);
+ if (!serial->lib_funcs || !serial->lib_funcs->drain)
+ return SR_ERR_NA;
- if (ret == SP_ERR_FAIL) {
- error = sp_last_error_message();
- sr_err("Error draining port (%d): %s.",
- sp_last_error_code(), error);
- sp_free_error_message(error);
- return SR_ERR;
- }
+ return serial->lib_funcs->drain(serial);
+}
+
+/*
+ * Provide an internal RX data buffer for the serial port. This is not
+ * supposed to be used directly by applications. Instead optional and
+ * alternative transports for serial communication can use this buffer
+ * if their progress is driven from background activity, and is not
+ * (directly) driven by external API calls.
+ *
+ * BEWARE! This implementation assumes that data which gets communicated
+ * via UART can get stored in a GString (which is a char array). Since
+ * the API hides this detail, we can address this issue later when needed.
+ * Callers use the API which communicates bytes.
+ *
+ * Applications optionally can register a "per RX chunk" callback, when
+ * they depend on the frame boundaries of the respective physical layer.
+ * Most callers just want the stream of RX data, and can use the buffer.
+ *
+ * The availability of RX chunks to callbacks, as well as the capability
+ * to pass on exact frames as chunks or potential re-assembly of chunks
+ * to a single data block, depend on each transport's implementation.
+ */
+
+/**
+ * Register application callback for RX data chunks.
+ *
+ * @param[in] serial Previously initialized serial port instance.
+ * @param[in] cb Routine to call as RX data becomes available.
+ * @param[in] cb_data User data to pass to the callback in addition to RX data.
+ *
+ * @retval SR_ERR_ARG Invalid parameters.
+ * @retval SR_OK Successful registration.
+ *
+ * Callbacks get unregistered by specifying #NULL for the 'cb' parameter.
+ */
+SR_PRIV int serial_set_read_chunk_cb(struct sr_serial_dev_inst *serial,
+ serial_rx_chunk_callback cb, void *cb_data)
+{
+ if (!serial)
+ return SR_ERR_ARG;
+
+ serial->rx_chunk_cb_func = cb;
+ serial->rx_chunk_cb_data = cb_data;
return SR_OK;
}
-static int _serial_write(struct sr_serial_dev_inst *serial,
- const void *buf, size_t count, int nonblocking, unsigned int timeout_ms)
+/**
+ * Discard previously queued RX data. Internal to the serial subsystem,
+ * coordination between common and transport specific support code.
+ *
+ * @param[in] serial Previously opened serial port instance.
+ *
+ * @internal
+ */
+SR_PRIV void sr_ser_discard_queued_data(struct sr_serial_dev_inst *serial)
{
- ssize_t ret;
- char *error;
+ if (!serial || !serial->rcv_buffer)
+ return;
- if (!serial) {
- sr_dbg("Invalid serial port.");
- return SR_ERR;
- }
+ g_string_truncate(serial->rcv_buffer, 0);
+}
- if (!serial->data) {
- sr_dbg("Cannot use unopened serial port %s.", serial->port);
- return SR_ERR;
+/**
+ * Get amount of queued RX data. Internal to the serial subsystem,
+ * coordination between common and transport specific support code.
+ *
+ * @param[in] serial Previously opened serial port instance.
+ *
+ * @internal
+ */
+SR_PRIV size_t sr_ser_has_queued_data(struct sr_serial_dev_inst *serial)
+{
+ if (!serial || !serial->rcv_buffer)
+ return 0;
+
+ return serial->rcv_buffer->len;
+}
+
+/**
+ * Queue received data. Internal to the serial subsystem, coordination
+ * between common and transport specific support code.
+ *
+ * @param[in] serial Previously opened serial port instance.
+ * @param[in] data Pointer to data bytes to queue.
+ * @param[in] len Number of data bytes to queue.
+ *
+ * @internal
+ */
+SR_PRIV void sr_ser_queue_rx_data(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, size_t len)
+{
+ if (!serial || !data || !len)
+ return;
+
+ if (serial->rx_chunk_cb_func)
+ serial->rx_chunk_cb_func(serial, serial->rx_chunk_cb_data, data, len);
+ else if (serial->rcv_buffer)
+ g_string_append_len(serial->rcv_buffer, (const gchar *)data, len);
+}
+
+/**
+ * Retrieve previously queued RX data. Internal to the serial subsystem,
+ * coordination between common and transport specific support code.
+ *
+ * @param[in] serial Previously opened serial port instance.
+ * @param[out] data Pointer to store retrieved data bytes into.
+ * @param[in] len Number of data bytes to retrieve.
+ *
+ * @internal
+ */
+SR_PRIV size_t sr_ser_unqueue_rx_data(struct sr_serial_dev_inst *serial,
+ uint8_t *data, size_t len)
+{
+ size_t qlen;
+ GString *buf;
+
+ if (!serial || !data || !len)
+ return 0;
+
+ qlen = sr_ser_has_queued_data(serial);
+ if (!qlen)
+ return 0;
+
+ buf = serial->rcv_buffer;
+ if (len > buf->len)
+ len = buf->len;
+ if (len) {
+ memcpy(data, buf->str, len);
+ g_string_erase(buf, 0, len);
}
- if (nonblocking)
- ret = sp_nonblocking_write(serial->data, buf, count);
- else
- ret = sp_blocking_write(serial->data, buf, count, timeout_ms);
+ return len;
+}
- switch (ret) {
- case SP_ERR_ARG:
- sr_err("Attempted serial port write with invalid arguments.");
- return SR_ERR_ARG;
- case SP_ERR_FAIL:
- error = sp_last_error_message();
- sr_err("Write error (%d): %s.", sp_last_error_code(), error);
- sp_free_error_message(error);
+/**
+ * Check for available receive data.
+ *
+ * @param[in] serial Previously opened serial port instance.
+ *
+ * @returns The number of (known) available RX data bytes.
+ *
+ * Returns 0 if no receive data is available, or if the amount of
+ * available receive data cannot get determined.
+ */
+SR_PRIV size_t serial_has_receive_data(struct sr_serial_dev_inst *serial)
+{
+ size_t lib_count, buf_count;
+
+ if (!serial)
+ return 0;
+
+ lib_count = 0;
+ if (serial->lib_funcs && serial->lib_funcs->get_rx_avail)
+ lib_count = serial->lib_funcs->get_rx_avail(serial);
+
+ buf_count = sr_ser_has_queued_data(serial);
+
+ return lib_count + buf_count;
+}
+
+static int _serial_write(struct sr_serial_dev_inst *serial,
+ const void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ ssize_t ret;
+
+ if (!serial) {
+ sr_dbg("Invalid serial port.");
return SR_ERR;
}
+ if (!serial->lib_funcs || !serial->lib_funcs->write)
+ return SR_ERR_NA;
+ ret = serial->lib_funcs->write(serial, buf, count,
+ nonblocking, timeout_ms);
sr_spew("Wrote %zd/%zu bytes.", ret, count);
return ret;
* @private
*/
SR_PRIV int serial_write_blocking(struct sr_serial_dev_inst *serial,
- const void *buf, size_t count, unsigned int timeout_ms)
+ const void *buf, size_t count, unsigned int timeout_ms)
{
return _serial_write(serial, buf, count, 0, timeout_ms);
}
* @private
*/
SR_PRIV int serial_write_nonblocking(struct sr_serial_dev_inst *serial,
- const void *buf, size_t count)
+ const void *buf, size_t count)
{
return _serial_write(serial, buf, count, 1, 0);
}
-static int _serial_read(struct sr_serial_dev_inst *serial, void *buf,
- size_t count, int nonblocking, unsigned int timeout_ms)
+static int _serial_read(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count, int nonblocking, unsigned int timeout_ms)
{
ssize_t ret;
- char *error;
if (!serial) {
sr_dbg("Invalid serial port.");
return SR_ERR;
}
- if (!serial->data) {
- sr_dbg("Cannot use unopened serial port %s.", serial->port);
- return SR_ERR;
- }
-
- if (nonblocking)
- ret = sp_nonblocking_read(serial->data, buf, count);
- else
- ret = sp_blocking_read(serial->data, buf, count, timeout_ms);
-
- switch (ret) {
- case SP_ERR_ARG:
- sr_err("Attempted serial port read with invalid arguments.");
- return SR_ERR_ARG;
- case SP_ERR_FAIL:
- error = sp_last_error_message();
- sr_err("Read error (%d): %s.", sp_last_error_code(), error);
- sp_free_error_message(error);
- return SR_ERR;
- }
-
+ if (!serial->lib_funcs || !serial->lib_funcs->read)
+ return SR_ERR_NA;
+ ret = serial->lib_funcs->read(serial, buf, count,
+ nonblocking, timeout_ms);
if (ret > 0)
sr_spew("Read %zd/%zu bytes.", ret, count);
*
* @private
*/
-SR_PRIV int serial_read_blocking(struct sr_serial_dev_inst *serial, void *buf,
- size_t count, unsigned int timeout_ms)
+SR_PRIV int serial_read_blocking(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count, unsigned int timeout_ms)
{
return _serial_read(serial, buf, count, 0, timeout_ms);
}
*
* @private
*/
-SR_PRIV int serial_read_nonblocking(struct sr_serial_dev_inst *serial, void *buf,
- size_t count)
+SR_PRIV int serial_read_nonblocking(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count)
{
return _serial_read(serial, buf, count, 1, 0);
}
*
* @private
*/
-SR_PRIV int serial_set_params(struct sr_serial_dev_inst *serial, int baudrate,
- int bits, int parity, int stopbits,
- int flowcontrol, int rts, int dtr)
+SR_PRIV int serial_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr)
{
int ret;
- char *error;
- struct sp_port_config *config;
if (!serial) {
sr_dbg("Invalid serial port.");
return SR_ERR;
}
- if (!serial->data) {
- sr_dbg("Cannot configure unopened serial port %s.", serial->port);
- return SR_ERR;
- }
-
sr_spew("Setting serial parameters on port %s.", serial->port);
- sp_new_config(&config);
- sp_set_config_baudrate(config, baudrate);
- sp_set_config_bits(config, bits);
- switch (parity) {
- case 0:
- sp_set_config_parity(config, SP_PARITY_NONE);
- break;
- case 1:
- sp_set_config_parity(config, SP_PARITY_EVEN);
- break;
- case 2:
- sp_set_config_parity(config, SP_PARITY_ODD);
- break;
- default:
- return SR_ERR_ARG;
- }
- sp_set_config_stopbits(config, stopbits);
- sp_set_config_rts(config, flowcontrol == 1 ? SP_RTS_FLOW_CONTROL : rts);
- sp_set_config_cts(config, flowcontrol == 1 ? SP_CTS_FLOW_CONTROL : SP_CTS_IGNORE);
- sp_set_config_dtr(config, dtr);
- sp_set_config_dsr(config, SP_DSR_IGNORE);
- sp_set_config_xon_xoff(config, flowcontrol == 2 ? SP_XONXOFF_INOUT : SP_XONXOFF_DISABLED);
-
- ret = sp_set_config(serial->data, config);
- sp_free_config(config);
-
- switch (ret) {
- case SP_ERR_ARG:
- sr_err("Invalid arguments for setting serial port parameters.");
- return SR_ERR_ARG;
- case SP_ERR_FAIL:
- error = sp_last_error_message();
- sr_err("Error setting serial port parameters (%d): %s.",
- sp_last_error_code(), error);
- sp_free_error_message(error);
- return SR_ERR;
+ if (!serial->lib_funcs || !serial->lib_funcs->set_params)
+ return SR_ERR_NA;
+ ret = serial->lib_funcs->set_params(serial,
+ baudrate, bits, parity, stopbits,
+ flowcontrol, rts, dtr);
+ if (ret == SR_OK) {
+ serial->comm_params.bit_rate = baudrate;
+ serial->comm_params.data_bits = bits;
+ serial->comm_params.parity_bits = parity ? 1 : 0;
+ serial->comm_params.stop_bits = stopbits;
+ sr_dbg("DBG: %s() rate %d, %d%s%d", __func__,
+ baudrate, bits,
+ (parity == 0) ? "n" : "x",
+ stopbits);
}
- return SR_OK;
+ return ret;
}
/**
* @private
*/
SR_PRIV int serial_set_paramstr(struct sr_serial_dev_inst *serial,
- const char *paramstr)
+ const char *paramstr)
{
/** @cond PRIVATE */
#define SERIAL_COMM_SPEC "^(\\d+)/([5678])([neo])([12])(.*)$"
/**
* Read a line from the specified serial port.
*
- * @param serial Previously initialized serial port structure.
- * @param buf Buffer where to store the bytes that are read.
- * @param buflen Size of the buffer.
+ * @param[in] serial Previously initialized serial port structure.
+ * @param[out] buf Buffer where to store the bytes that are read.
+ * @param[in] buflen Size of the buffer.
* @param[in] timeout_ms How long to wait for a line to come in.
*
- * Reading stops when CR of LR is found, which is stripped from the buffer.
+ * Reading stops when CR or LF is found, which is stripped from the buffer.
*
* @retval SR_OK Success.
* @retval SR_ERR Failure.
*
* @private
*/
-SR_PRIV int serial_readline(struct sr_serial_dev_inst *serial, char **buf,
- int *buflen, gint64 timeout_ms)
+SR_PRIV int serial_readline(struct sr_serial_dev_inst *serial,
+ char **buf, int *buflen, gint64 timeout_ms)
{
gint64 start, remaining;
int maxlen, len;
return SR_ERR;
}
- if (!serial->data) {
+ if (!dev_is_supported(serial)) {
sr_dbg("Cannot use unopened serial port %s.", serial->port);
return -1;
}
len = maxlen - *buflen - 1;
if (len < 1)
break;
- len = sp_blocking_read(serial->data, *buf + *buflen, 1, remaining);
+ len = serial_read_blocking(serial, *buf + *buflen, 1, remaining);
if (len > 0) {
*buflen += len;
*(*buf + *buflen) = '\0';
* @param is_valid Callback that assesses whether the packet is valid or not.
* @param[in] timeout_ms The timeout after which, if no packet is detected, to
* abort scanning.
- * @param[in] baudrate The baudrate of the serial port. This parameter is not
- * critical, but it helps fine tune the serial port polling
- * delay.
*
* @retval SR_OK Valid packet was found within the given timeout.
* @retval SR_ERR Failure.
* @private
*/
SR_PRIV int serial_stream_detect(struct sr_serial_dev_inst *serial,
- uint8_t *buf, size_t *buflen,
- size_t packet_size,
- packet_valid_callback is_valid,
- uint64_t timeout_ms, int baudrate)
+ uint8_t *buf, size_t *buflen,
+ size_t packet_size,
+ packet_valid_callback is_valid,
+ uint64_t timeout_ms)
{
uint64_t start, time, byte_delay_us;
size_t ibuf, i, maxlen;
maxlen = *buflen;
- sr_dbg("Detecting packets on %s (timeout = %" PRIu64
- "ms, baudrate = %d).", serial->port, timeout_ms, baudrate);
+ sr_dbg("Detecting packets on %s (timeout = %" PRIu64 "ms).",
+ serial->port, timeout_ms);
- if (maxlen < (packet_size / 2) ) {
+ if (maxlen < (packet_size * 2) ) {
sr_err("Buffer size must be at least twice the packet size.");
return SR_ERR;
}
/* Assume 8n1 transmission. That is 10 bits for every byte. */
- byte_delay_us = 10 * ((1000 * 1000) / baudrate);
+ byte_delay_us = serial_timeout(serial, 1) * 1000;
start = g_get_monotonic_time();
i = ibuf = len = 0;
*
* @private
*/
-SR_PRIV int sr_serial_extract_options(GSList *options, const char **serial_device,
- const char **serial_options)
+SR_PRIV int sr_serial_extract_options(GSList *options,
+ const char **serial_device, const char **serial_options)
{
GSList *l;
struct sr_config *src;
return SR_OK;
}
-/** @cond PRIVATE */
-#ifdef _WIN32
-typedef HANDLE event_handle;
-#else
-typedef int event_handle;
-#endif
-/** @endcond */
-
/** @private */
SR_PRIV int serial_source_add(struct sr_session *session,
- struct sr_serial_dev_inst *serial, int events, int timeout,
- sr_receive_data_callback cb, void *cb_data)
+ struct sr_serial_dev_inst *serial, int events, int timeout,
+ sr_receive_data_callback cb, void *cb_data)
{
- struct sp_event_set *event_set;
- gintptr poll_fd;
- unsigned int poll_events;
- enum sp_event mask = 0;
-
- if ((events & (G_IO_IN|G_IO_ERR)) && (events & G_IO_OUT)) {
+ if ((events & (G_IO_IN | G_IO_ERR)) && (events & G_IO_OUT)) {
sr_err("Cannot poll input/error and output simultaneously.");
return SR_ERR_ARG;
}
- if (sp_new_event_set(&event_set) != SP_OK)
- return SR_ERR;
-
- if (events & G_IO_IN)
- mask |= SP_EVENT_RX_READY;
- if (events & G_IO_OUT)
- mask |= SP_EVENT_TX_READY;
- if (events & G_IO_ERR)
- mask |= SP_EVENT_ERROR;
-
- if (sp_add_port_events(event_set, serial->data, mask) != SP_OK) {
- sp_free_event_set(event_set);
- return SR_ERR;
- }
- if (event_set->count != 1) {
- sr_err("Unexpected number (%u) of event handles to poll.",
- event_set->count);
- sp_free_event_set(event_set);
- return SR_ERR;
+ if (!dev_is_supported(serial)) {
+ sr_err("Invalid serial port.");
+ return SR_ERR_ARG;
}
- poll_fd = (gintptr) ((event_handle *)event_set->handles)[0];
- mask = event_set->masks[0];
-
- sp_free_event_set(event_set);
+ if (!serial->lib_funcs || !serial->lib_funcs->setup_source_add)
+ return SR_ERR_NA;
- poll_events = 0;
- if (mask & SP_EVENT_RX_READY)
- poll_events |= G_IO_IN;
- if (mask & SP_EVENT_TX_READY)
- poll_events |= G_IO_OUT;
- if (mask & SP_EVENT_ERROR)
- poll_events |= G_IO_ERR;
- /*
- * Using serial->data as the key for the event source is not quite
- * proper, as it makes it impossible to create another event source
- * for the same serial port. However, these fixed keys will soon be
- * removed from the API anyway, so this is OK for now.
- */
- return sr_session_fd_source_add(session, serial->data,
- poll_fd, poll_events, timeout, cb, cb_data);
+ return serial->lib_funcs->setup_source_add(session, serial,
+ events, timeout, cb, cb_data);
}
/** @private */
SR_PRIV int serial_source_remove(struct sr_session *session,
- struct sr_serial_dev_inst *serial)
+ struct sr_serial_dev_inst *serial)
{
- return sr_session_source_remove_internal(session, serial->data);
+ if (!dev_is_supported(serial)) {
+ sr_err("Invalid serial port.");
+ return SR_ERR_ARG;
+ }
+
+ if (!serial->lib_funcs || !serial->lib_funcs->setup_source_remove)
+ return SR_ERR_NA;
+
+ return serial->lib_funcs->setup_source_remove(session, serial);
}
/**
* @return The newly allocated sr_serial_port struct.
*/
static struct sr_serial_port *sr_serial_new(const char *name,
- const char *description)
+ const char *description)
{
struct sr_serial_port *serial;
if (!name)
return NULL;
- serial = g_malloc(sizeof(struct sr_serial_port));
+ serial = g_malloc0(sizeof(*serial));
serial->name = g_strdup(name);
serial->description = g_strdup(description ? description : "");
g_free(serial);
}
+static GSList *append_port_list(GSList *devs, const char *name, const char *desc)
+{
+ return g_slist_append(devs, sr_serial_new(name, desc));
+}
+
/**
* List available serial devices.
*
*/
SR_API GSList *sr_serial_list(const struct sr_dev_driver *driver)
{
- GSList *tty_devs = NULL;
- struct sp_port **ports;
- struct sr_serial_port *port;
- int i;
+ GSList *tty_devs;
+ GSList *(*list_func)(GSList *list, sr_ser_list_append_t append);
/* Currently unused, but will be used by some drivers later on. */
(void)driver;
- if (sp_list_ports(&ports) != SP_OK)
- return NULL;
-
- for (i = 0; ports[i]; i++) {
- port = sr_serial_new(sp_get_port_name(ports[i]),
- sp_get_port_description(ports[i]));
- tty_devs = g_slist_append(tty_devs, port);
+ tty_devs = NULL;
+ if (ser_lib_funcs_libsp && ser_lib_funcs_libsp->list) {
+ list_func = ser_lib_funcs_libsp->list;
+ tty_devs = list_func(tty_devs, append_port_list);
+ }
+ if (ser_lib_funcs_hid && ser_lib_funcs_hid->list) {
+ list_func = ser_lib_funcs_hid->list;
+ tty_devs = list_func(tty_devs, append_port_list);
+ }
+ if (ser_lib_funcs_bt && ser_lib_funcs_bt->list) {
+ list_func = ser_lib_funcs_bt->list;
+ tty_devs = list_func(tty_devs, append_port_list);
}
-
- sp_free_port_list(ports);
return tty_devs;
}
+static GSList *append_port_find(GSList *devs, const char *name)
+{
+ if (!name || !*name)
+ return devs;
+
+ return g_slist_append(devs, g_strdup(name));
+}
+
/**
* Find USB serial devices via the USB vendor ID and product ID.
*
*/
SR_PRIV GSList *sr_serial_find_usb(uint16_t vendor_id, uint16_t product_id)
{
- GSList *tty_devs = NULL;
- struct sp_port **ports;
- int i, vid, pid;
-
- if (sp_list_ports(&ports) != SP_OK)
- return NULL;
-
- for (i = 0; ports[i]; i++)
- if (sp_get_port_transport(ports[i]) == SP_TRANSPORT_USB &&
- sp_get_port_usb_vid_pid(ports[i], &vid, &pid) == SP_OK &&
- vid == vendor_id && pid == product_id) {
- tty_devs = g_slist_prepend(tty_devs,
- g_strdup(sp_get_port_name(ports[i])));
- }
-
- sp_free_port_list(ports);
+ GSList *tty_devs;
+ GSList *(*find_func)(GSList *list, sr_ser_find_append_t append,
+ uint16_t vid, uint16_t pid);
+
+ tty_devs = NULL;
+ if (ser_lib_funcs_libsp && ser_lib_funcs_libsp->find_usb) {
+ find_func = ser_lib_funcs_libsp->find_usb;
+ tty_devs = find_func(tty_devs, append_port_find,
+ vendor_id, product_id);
+ }
+ if (ser_lib_funcs_hid && ser_lib_funcs_hid->find_usb) {
+ find_func = ser_lib_funcs_hid->find_usb;
+ tty_devs = find_func(tty_devs, append_port_find,
+ vendor_id, product_id);
+ }
return tty_devs;
}
/** @private */
SR_PRIV int serial_timeout(struct sr_serial_dev_inst *port, int num_bytes)
{
- struct sp_port_config *config;
- int timeout_ms, bits, baud, tmp;
-
- /* Default to 1s. */
- timeout_ms = 1000;
-
- if (sp_new_config(&config) < 0)
- return timeout_ms;
+ int bits, baud, ret, timeout_ms;
+ /* Get the bitrate and frame length. */
bits = baud = 0;
- do {
- if (sp_get_config(port->data, config) < 0)
- break;
-
- /* Start bit. */
- bits = 1;
- if (sp_get_config_bits(config, &tmp) < 0)
- break;
- bits += tmp;
- if (sp_get_config_stopbits(config, &tmp) < 0)
- break;
- bits += tmp;
- if (sp_get_config_baudrate(config, &tmp) < 0)
- break;
- baud = tmp;
- } while (FALSE);
+ if (port->lib_funcs && port->lib_funcs->get_frame_format) {
+ ret = port->lib_funcs->get_frame_format(port, &baud, &bits);
+ if (ret != SR_OK)
+ bits = baud = 0;
+ } else {
+ baud = port->comm_params.bit_rate;
+ bits = 1 + port->comm_params.data_bits +
+ port->comm_params.parity_bits +
+ port->comm_params.stop_bits;
+ }
+ /* Derive the timeout. Default to 1s. */
+ timeout_ms = 1000;
if (bits && baud) {
/* Throw in 10ms for misc OS overhead. */
timeout_ms = 10;
timeout_ms += ((1000.0 / baud) * bits) * num_bytes;
}
- sp_free_config(config);
-
return timeout_ms;
}
+#else
+
+/* TODO Put fallback.c content here? */
+
+#endif
+
/** @} */
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018-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 <string.h>
+#include <memory.h>
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-bt"
+/** @endcond */
+
+#ifdef HAVE_SERIAL_COMM
+#ifdef HAVE_BLUETOOTH
+
+#define SER_BT_CONN_PREFIX "bt"
+#define SER_BT_CHUNK_SIZE 1200
+
+/**
+ * @file
+ *
+ * Serial port handling, wraps the external BT/BLE dependencies.
+ */
+
+/**
+ * @defgroup grp_serial_bt Serial port handling, BT/BLE group
+ *
+ * Make serial-over-BT communication appear like a regular serial port.
+ *
+ * @{
+ */
+
+/* {{{ support for serial-over-BT channels */
+
+static const struct scan_supported_item {
+ const char *name;
+ enum ser_bt_conn_t type;
+} scan_supported_items[] = {
+ /* Guess connection types from device names (useful for scans). */
+ { "121GW", SER_BT_CONN_BLE122, },
+ { "Adafruit Bluefruit LE 8134", SER_BT_CONN_NRF51, },
+ { "HC-05", SER_BT_CONN_RFCOMM, },
+ { NULL, SER_BT_CONN_UNKNOWN, },
+};
+
+static const char *ser_bt_conn_names[SER_BT_CONN_MAX] = {
+ [SER_BT_CONN_UNKNOWN] = "<type>",
+ [SER_BT_CONN_RFCOMM] = "rfcomm",
+ [SER_BT_CONN_BLE122] = "ble122",
+ [SER_BT_CONN_NRF51] = "nrf51",
+ [SER_BT_CONN_CC254x] = "cc254x",
+};
+
+static enum ser_bt_conn_t lookup_conn_name(const char *name)
+{
+ size_t idx;
+ const char *item;
+
+ if (!name || !*name)
+ return SER_BT_CONN_UNKNOWN;
+ idx = ARRAY_SIZE(ser_bt_conn_names);
+ while (idx-- > 0) {
+ item = ser_bt_conn_names[idx];
+ if (strcmp(item, name) == 0)
+ return idx;
+ }
+
+ return SER_BT_CONN_UNKNOWN;
+}
+
+static const char *conn_name_text(enum ser_bt_conn_t type)
+{
+ if (type >= ARRAY_SIZE(ser_bt_conn_names))
+ type = SER_BT_CONN_UNKNOWN;
+
+ return ser_bt_conn_names[type];
+}
+
+/**
+ * Parse conn= specs for serial over Bluetooth communication.
+ *
+ * @param[in] serial The serial port that is about to get opened.
+ * @param[in] spec The caller provided conn= specification.
+ * @param[out] conn_type The type of BT comm (BT RFCOMM, BLE notify).
+ * @param[out] remote_addr The remote device address.
+ * @param[out] rfcomm_channel The RFCOMM channel (if applicable).
+ * @param[out] read_hdl The BLE notify read handle (if applicable).
+ * @param[out] write_hdl The BLE notify write handle (if applicable).
+ * @param[out] cccd_hdl The BLE notify CCCD handle (if applicable).
+ * @param[out] cccd_val The BLE notify CCCD value (if applicable).
+ *
+ * @return 0 upon success, non-zero upon failure.
+ *
+ * @internal
+ *
+ * Summary of parsing rules as they are implemented:
+ * - Implementor's note: Automatic scan for available devices is not
+ * yet implemented. So strictly speaking some parts of the input
+ * spec are optional, but fallbacks may not take effect ATM.
+ * - Insist on the "bt" prefix. Accept "bt" alone without any other
+ * additional field.
+ * - The first field that follows is the connection type. Supported
+ * types are 'rfcomm', 'ble122', 'cc254x', and potentially others
+ * in a future implementation.
+ * - The next field is the remote device's address, either separated
+ * by colons or dashes or spaces, or not separated at all.
+ * - Other parameters (RFCOMM channel, notify handles and write values)
+ * get derived from the connection type. A future implementation may
+ * accept more fields, but the syntax is yet to get developed.
+ *
+ * Supported formats resulting from these rules:
+ * bt/<conn>/<addr>
+ *
+ * Examples:
+ * bt/rfcomm/11-22-33-44-55-66
+ * bt/ble122/88:6b:12:34:56:78
+ * bt/cc254x/0123456789ab
+ *
+ * It's assumed that users easily can create those conn= specs from
+ * available information, or that scan routines will create such specs
+ * that copy'n'paste results (or GUI choices from previous scan results)
+ * can get processed here.
+ */
+static int ser_bt_parse_conn_spec(
+ struct sr_serial_dev_inst *serial, const char *spec,
+ enum ser_bt_conn_t *conn_type, const char **remote_addr,
+ size_t *rfcomm_channel,
+ uint16_t *read_hdl, uint16_t *write_hdl,
+ uint16_t *cccd_hdl, uint16_t *cccd_val)
+{
+ enum ser_bt_conn_t type;
+ const char *addr;
+ char **fields, *field;
+
+ if (conn_type)
+ *conn_type = SER_BT_CONN_UNKNOWN;
+ if (remote_addr)
+ *remote_addr = NULL;
+ if (rfcomm_channel)
+ *rfcomm_channel = 0;
+ if (read_hdl)
+ *read_hdl = 0;
+ if (write_hdl)
+ *write_hdl = 0;
+ if (cccd_hdl)
+ *cccd_hdl = 0;
+ if (cccd_val)
+ *cccd_val = 0;
+
+ type = SER_BT_CONN_UNKNOWN;
+ addr = NULL;
+
+ if (!serial || !spec || !spec[0])
+ return SR_ERR_ARG;
+
+ /* Evaluate the mandatory first three fields. */
+ fields = g_strsplit_set(spec, "/", 0);
+ if (!fields)
+ return SR_ERR_ARG;
+ if (g_strv_length(fields) < 3) {
+ g_strfreev(fields);
+ return SR_ERR_ARG;
+ }
+ field = fields[0];
+ if (strcmp(field, SER_BT_CONN_PREFIX) != 0) {
+ g_strfreev(fields);
+ return SR_ERR_ARG;
+ }
+ field = fields[1];
+ type = lookup_conn_name(field);
+ if (!type) {
+ g_strfreev(fields);
+ return SR_ERR_ARG;
+ }
+ if (conn_type)
+ *conn_type = type;
+ field = fields[2];
+ if (!field || !*field) {
+ g_strfreev(fields);
+ return SR_ERR_ARG;
+ }
+ addr = g_strdup(field);
+ if (remote_addr)
+ *remote_addr = addr;
+
+ /* Derive default parameters that match the connection type. */
+ /* TODO Lookup defaults from a table? */
+ switch (type) {
+ case SER_BT_CONN_RFCOMM:
+ if (rfcomm_channel)
+ *rfcomm_channel = 1;
+ break;
+ case SER_BT_CONN_BLE122:
+ if (read_hdl)
+ *read_hdl = 8;
+ if (write_hdl)
+ *write_hdl = 0;
+ if (cccd_hdl)
+ *cccd_hdl = 9;
+ if (cccd_val)
+ *cccd_val = 0x0003;
+ break;
+ case SER_BT_CONN_NRF51:
+ /* TODO
+ * Are these values appropriate? Check the learn article at
+ * https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-uart-friend?view=all
+ */
+ if (read_hdl)
+ *read_hdl = 13;
+ if (write_hdl)
+ *write_hdl = 11;
+ if (cccd_hdl)
+ *cccd_hdl = 14;
+ if (cccd_val)
+ *cccd_val = 0x0001;
+ /* TODO 'random' type, sec-level=high */
+ break;
+ case SER_BT_CONN_CC254x:
+ /* TODO Are these values appropriate? Just guessing here. */
+ if (read_hdl)
+ *read_hdl = 20;
+ if (write_hdl)
+ *write_hdl = 0;
+ if (cccd_hdl)
+ *cccd_hdl = 21;
+ if (cccd_val)
+ *cccd_val = 0x0001;
+ break;
+ default:
+ return SR_ERR_ARG;
+ }
+
+ /* TODO Evaluate optionally trailing fields, override defaults? */
+
+ g_strfreev(fields);
+ return SR_OK;
+}
+
+static void ser_bt_mask_databits(struct sr_serial_dev_inst *serial,
+ uint8_t *data, size_t len)
+{
+ uint32_t mask32;
+ uint8_t mask;
+ size_t idx;
+
+ if ((serial->comm_params.data_bits % 8) == 0)
+ return;
+
+ mask32 = (1UL << serial->comm_params.data_bits) - 1;
+ mask = mask32 & 0xff;
+ for (idx = 0; idx < len; idx++)
+ data[idx] &= mask;
+}
+
+static int ser_bt_data_cb(void *cb_data, uint8_t *data, size_t dlen)
+{
+ struct sr_serial_dev_inst *serial;
+
+ serial = cb_data;
+ if (!serial)
+ return -1;
+
+ ser_bt_mask_databits(serial, data, dlen);
+ sr_ser_queue_rx_data(serial, data, dlen);
+
+ return 0;
+}
+
+/* }}} */
+/* {{{ wrap serial-over-BT operations in a common serial.c API */
+
+/* See if a serial port's name refers to a BT type. */
+SR_PRIV int ser_name_is_bt(struct sr_serial_dev_inst *serial)
+{
+ size_t off;
+ char sep;
+
+ if (!serial)
+ return 0;
+ if (!serial->port || !*serial->port)
+ return 0;
+
+ /* Accept either "bt" alone, or "bt/" as a prefix. */
+ if (!g_str_has_prefix(serial->port, SER_BT_CONN_PREFIX))
+ return 0;
+ off = strlen(SER_BT_CONN_PREFIX);
+ sep = serial->port[off];
+ if (sep != '\0' && sep != '/')
+ return 0;
+
+ return 1;
+}
+
+/* The open() wrapper for BT ports. */
+static int ser_bt_open(struct sr_serial_dev_inst *serial, int flags)
+{
+ enum ser_bt_conn_t conn_type;
+ const char *remote_addr;
+ size_t rfcomm_channel;
+ uint16_t read_hdl, write_hdl, cccd_hdl, cccd_val;
+ int rc;
+ struct sr_bt_desc *desc;
+
+ (void)flags;
+
+ /* Derive BT specific parameters from the port spec. */
+ rc = ser_bt_parse_conn_spec(serial, serial->port,
+ &conn_type, &remote_addr,
+ &rfcomm_channel,
+ &read_hdl, &write_hdl,
+ &cccd_hdl, &cccd_val);
+ if (rc != SR_OK)
+ return SR_ERR_ARG;
+
+ if (!conn_type || !remote_addr || !remote_addr[0]) {
+ /* TODO Auto-search for available connections? */
+ return SR_ERR_NA;
+ }
+
+ /* Create the connection. Only store params after successful use. */
+ desc = sr_bt_desc_new();
+ if (!desc)
+ return SR_ERR;
+ serial->bt_desc = desc;
+ rc = sr_bt_config_addr_remote(desc, remote_addr);
+ if (rc < 0)
+ return SR_ERR;
+ serial->bt_addr_remote = g_strdup(remote_addr);
+ switch (conn_type) {
+ case SER_BT_CONN_RFCOMM:
+ rc = sr_bt_config_rfcomm(desc, rfcomm_channel);
+ if (rc < 0)
+ return SR_ERR;
+ serial->bt_rfcomm_channel = rfcomm_channel;
+ break;
+ case SER_BT_CONN_BLE122:
+ case SER_BT_CONN_NRF51:
+ case SER_BT_CONN_CC254x:
+ rc = sr_bt_config_notify(desc,
+ read_hdl, write_hdl, cccd_hdl, cccd_val);
+ if (rc < 0)
+ return SR_ERR;
+ serial->bt_notify_handle_read = read_hdl;
+ serial->bt_notify_handle_write = write_hdl;
+ serial->bt_notify_handle_cccd = cccd_hdl;
+ serial->bt_notify_value_cccd = cccd_val;
+ break;
+ default:
+ /* Unsupported type, or incomplete implementation. */
+ return SR_ERR_ARG;
+ }
+ serial->bt_conn_type = conn_type;
+
+ /* Make sure the receive buffer can accept input data. */
+ if (!serial->rcv_buffer)
+ serial->rcv_buffer = g_string_sized_new(SER_BT_CHUNK_SIZE);
+ rc = sr_bt_config_cb_data(desc, ser_bt_data_cb, serial);
+ if (rc < 0)
+ return SR_ERR;
+
+ /* Open the connection. */
+ switch (conn_type) {
+ case SER_BT_CONN_RFCOMM:
+ rc = sr_bt_connect_rfcomm(desc);
+ if (rc < 0)
+ return SR_ERR;
+ break;
+ case SER_BT_CONN_BLE122:
+ case SER_BT_CONN_NRF51:
+ case SER_BT_CONN_CC254x:
+ rc = sr_bt_connect_ble(desc);
+ if (rc < 0)
+ return SR_ERR;
+ rc = sr_bt_start_notify(desc);
+ if (rc < 0)
+ return SR_ERR;
+ break;
+ default:
+ return SR_ERR_ARG;
+ }
+
+ return SR_OK;
+}
+
+static int ser_bt_close(struct sr_serial_dev_inst *serial)
+{
+ if (!serial)
+ return SR_ERR_ARG;
+
+ if (!serial->bt_desc)
+ return SR_OK;
+
+ sr_bt_disconnect(serial->bt_desc);
+ sr_bt_desc_free(serial->bt_desc);
+ serial->bt_desc = NULL;
+
+ g_free(serial->bt_addr_local);
+ serial->bt_addr_local = NULL;
+ g_free(serial->bt_addr_remote);
+ serial->bt_addr_remote = NULL;
+ g_slist_free_full(serial->bt_source_args, g_free);
+ serial->bt_source_args = NULL;
+
+ return SR_OK;
+}
+
+/* Flush, discards pending RX data, empties buffers. */
+static int ser_bt_flush(struct sr_serial_dev_inst *serial)
+{
+ (void)serial;
+ /* EMPTY */
+
+ return SR_OK;
+}
+
+/* Drain, waits for completion of pending TX data. */
+static int ser_bt_drain(struct sr_serial_dev_inst *serial)
+{
+ (void)serial;
+ /* EMPTY */ /* TODO? */
+
+ return SR_ERR_BUG;
+}
+
+static int ser_bt_write(struct sr_serial_dev_inst *serial,
+ const void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ ssize_t wrlen;
+
+ /*
+ * TODO Support chunked transmission when callers' requests
+ * exceed the BT channel's capacity? See ser_hid_write().
+ */
+
+ switch (serial->bt_conn_type) {
+ case SER_BT_CONN_RFCOMM:
+ (void)nonblocking;
+ (void)timeout_ms;
+ wrlen = sr_bt_write(serial->bt_desc, buf, count);
+ if (wrlen < 0)
+ return SR_ERR_IO;
+ return wrlen;
+ case SER_BT_CONN_BLE122:
+ case SER_BT_CONN_NRF51:
+ case SER_BT_CONN_CC254x:
+ /*
+ * Assume that when applications call the serial layer's
+ * write routine, then the BLE chip/module does support
+ * a TX handle. Just call the serial-BT library's write
+ * routine.
+ */
+ (void)nonblocking;
+ (void)timeout_ms;
+ wrlen = sr_bt_write(serial->bt_desc, buf, count);
+ if (wrlen < 0)
+ return SR_ERR_IO;
+ return wrlen;
+ default:
+ return SR_ERR_ARG;
+ }
+ /* UNREACH */
+}
+
+static int ser_bt_read(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ gint64 deadline_us, now_us;
+ uint8_t buffer[SER_BT_CHUNK_SIZE];
+ ssize_t rdlen;
+ int rc;
+ size_t dlen;
+
+ /*
+ * Immediately satisfy the caller's request from the RX buffer
+ * if the requested amount of data is available already.
+ */
+ if (sr_ser_has_queued_data(serial) >= count)
+ return sr_ser_unqueue_rx_data(serial, buf, count);
+
+ /*
+ * When a timeout was specified, then determine the deadline
+ * where to stop reception.
+ */
+ deadline_us = 0;
+ now_us = 0; /* Silence a (false) compiler warning. */
+ if (timeout_ms) {
+ now_us = g_get_monotonic_time();
+ deadline_us = now_us + timeout_ms * 1000;
+ }
+
+ /*
+ * Keep receiving from the port until the caller's requested
+ * amount of data has become available, or the timeout has
+ * expired. In the absence of a timeout, stop reading when an
+ * attempt no longer yields receive data.
+ */
+ while (TRUE) {
+ /* Run another attempt to receive data. */
+ switch (serial->bt_conn_type) {
+ case SER_BT_CONN_RFCOMM:
+ rdlen = sr_bt_read(serial->bt_desc, buffer, sizeof(buffer));
+ if (rdlen <= 0)
+ break;
+ rc = ser_bt_data_cb(serial, buffer, rdlen);
+ if (rc < 0)
+ rdlen = -1;
+ break;
+ case SER_BT_CONN_BLE122:
+ case SER_BT_CONN_NRF51:
+ case SER_BT_CONN_CC254x:
+ dlen = sr_ser_has_queued_data(serial);
+ rc = sr_bt_check_notify(serial->bt_desc);
+ if (rc < 0)
+ rdlen = -1;
+ else if (sr_ser_has_queued_data(serial) != dlen)
+ rdlen = +1;
+ else
+ rdlen = 0;
+ break;
+ default:
+ rdlen = -1;
+ break;
+ }
+
+ /*
+ * Stop upon receive errors, or timeout expiration. Only
+ * stop upon empty reception in the absence of a timeout.
+ */
+ if (rdlen < 0)
+ break;
+ if (nonblocking && !rdlen)
+ break;
+ if (deadline_us) {
+ now_us = g_get_monotonic_time();
+ if (now_us > deadline_us)
+ break;
+ }
+
+ /* Also stop when sufficient data has become available. */
+ if (sr_ser_has_queued_data(serial) >= count)
+ break;
+ }
+
+ /*
+ * Satisfy the caller's demand for receive data from previously
+ * queued incoming data.
+ */
+ dlen = sr_ser_has_queued_data(serial);
+ if (dlen > count)
+ dlen = count;
+ if (!dlen)
+ return 0;
+
+ return sr_ser_unqueue_rx_data(serial, buf, dlen);
+}
+
+struct bt_source_args_t {
+ /* The application callback. */
+ sr_receive_data_callback cb;
+ void *cb_data;
+ /* The serial device, to store RX data. */
+ struct sr_serial_dev_inst *serial;
+};
+
+/*
+ * Gets periodically invoked by the glib main loop. "Drives" (checks)
+ * progress of BT communication, and invokes the application's callback
+ * which processes RX data (when some has become available), as well as
+ * handles application level timeouts.
+ */
+static int bt_source_cb(int fd, int revents, void *cb_data)
+{
+ struct bt_source_args_t *args;
+ struct sr_serial_dev_inst *serial;
+ uint8_t rx_buf[SER_BT_CHUNK_SIZE];
+ ssize_t rdlen;
+ size_t dlen;
+ int rc;
+
+ args = cb_data;
+ if (!args)
+ return -1;
+ serial = args->serial;
+ if (!serial)
+ return -1;
+ if (!serial->bt_conn_type)
+ return -1;
+
+ /*
+ * Drain receive data which the channel might have pending.
+ * This is "a copy" of the "background part" of ser_bt_read(),
+ * without the timeout support code, and not knowing how much
+ * data the application is expecting.
+ */
+ do {
+ switch (serial->bt_conn_type) {
+ case SER_BT_CONN_RFCOMM:
+ rdlen = sr_bt_read(serial->bt_desc, rx_buf, sizeof(rx_buf));
+ if (rdlen <= 0)
+ break;
+ rc = ser_bt_data_cb(serial, rx_buf, rdlen);
+ if (rc < 0)
+ rdlen = -1;
+ break;
+ case SER_BT_CONN_BLE122:
+ case SER_BT_CONN_NRF51:
+ case SER_BT_CONN_CC254x:
+ dlen = sr_ser_has_queued_data(serial);
+ rc = sr_bt_check_notify(serial->bt_desc);
+ if (rc < 0)
+ rdlen = -1;
+ else if (sr_ser_has_queued_data(serial) != dlen)
+ rdlen = +1;
+ else
+ rdlen = 0;
+ break;
+ default:
+ rdlen = -1;
+ break;
+ }
+ } while (rdlen > 0);
+
+ /*
+ * When RX data became available (now or earlier), pass this
+ * condition to the application callback. Always periodically
+ * run the application callback, since it handles timeouts and
+ * might carry out other tasks as well like signalling progress.
+ */
+ if (sr_ser_has_queued_data(args->serial))
+ revents |= G_IO_IN;
+ rc = args->cb(fd, revents, args->cb_data);
+
+ return rc;
+}
+
+/* TODO Can we use the Bluetooth socket's file descriptor? Probably not portably. */
+#define WITH_MAXIMUM_TIMEOUT_VALUE 0
+static int ser_bt_setup_source_add(struct sr_session *session,
+ struct sr_serial_dev_inst *serial,
+ int events, int timeout,
+ sr_receive_data_callback cb, void *cb_data)
+{
+ struct bt_source_args_t *args;
+ int rc;
+
+ (void)events;
+
+ /* Optionally enforce a minimum poll period. */
+ if (WITH_MAXIMUM_TIMEOUT_VALUE && timeout > WITH_MAXIMUM_TIMEOUT_VALUE)
+ timeout = WITH_MAXIMUM_TIMEOUT_VALUE;
+
+ /* Allocate status container for background data reception. */
+ args = g_malloc0(sizeof(*args));
+ args->cb = cb;
+ args->cb_data = cb_data;
+ args->serial = serial;
+
+ /*
+ * Have a periodic timer installed. Register the allocated block
+ * with the serial device, since the GSource's finalizer won't
+ * free the memory, and we haven't bothered to create a custom
+ * BT specific GSource.
+ */
+ rc = sr_session_source_add(session, -1, events, timeout, bt_source_cb, args);
+ if (rc != SR_OK) {
+ g_free(args);
+ return rc;
+ }
+ serial->bt_source_args = g_slist_append(serial->bt_source_args, args);
+
+ return SR_OK;
+}
+
+static int ser_bt_setup_source_remove(struct sr_session *session,
+ struct sr_serial_dev_inst *serial)
+{
+ (void)serial;
+
+ (void)sr_session_source_remove(session, -1);
+ /* Release callback args here already? */
+
+ return SR_OK;
+}
+
+static enum ser_bt_conn_t scan_is_supported(const char *name)
+{
+ size_t idx;
+ const struct scan_supported_item *item;
+
+ for (idx = 0; idx < ARRAY_SIZE(scan_supported_items); idx++) {
+ item = &scan_supported_items[idx];
+ if (!item->name)
+ break;
+ if (strcmp(name, item->name) != 0)
+ continue;
+ return item->type;
+ }
+
+ return SER_BT_CONN_UNKNOWN;
+}
+
+struct bt_scan_args_t {
+ GSList *port_list;
+ sr_ser_list_append_t append;
+ GSList *addr_list;
+ const char *bt_type;
+};
+
+static void scan_cb(void *cb_args, const char *addr, const char *name)
+{
+ struct bt_scan_args_t *scan_args;
+ GSList *l;
+ char addr_text[20];
+ enum ser_bt_conn_t type;
+ char *port_name, *port_desc;
+ char *addr_copy;
+
+ scan_args = cb_args;
+ if (!scan_args)
+ return;
+ sr_info("BT scan, found: %s - %s\n", addr, name);
+
+ /* Check whether the device was seen before. */
+ for (l = scan_args->addr_list; l; l = l->next) {
+ if (strcmp(addr, l->data) == 0)
+ return;
+ }
+
+ /* Substitute colons in the address by dashes. */
+ if (!addr || !*addr)
+ return;
+ snprintf(addr_text, sizeof(addr_text), "%s", addr);
+ g_strcanon(addr_text, "0123456789abcdefABCDEF", '-');
+
+ /* Create a port name, and a description. */
+ type = scan_is_supported(name);
+ port_name = g_strdup_printf("%s/%s/%s",
+ SER_BT_CONN_PREFIX, conn_name_text(type), addr_text);
+ port_desc = g_strdup_printf("%s (%s)", name, scan_args->bt_type);
+
+ scan_args->port_list = scan_args->append(scan_args->port_list, port_name, port_desc);
+ g_free(port_name);
+ g_free(port_desc);
+
+ /* Keep track of the handled address. */
+ addr_copy = g_strdup(addr);
+ scan_args->addr_list = g_slist_append(scan_args->addr_list, addr_copy);
+}
+
+static GSList *ser_bt_list(GSList *list, sr_ser_list_append_t append)
+{
+ static const int scan_duration = 2;
+
+ struct bt_scan_args_t scan_args;
+ struct sr_bt_desc *desc;
+
+ /*
+ * Implementor's note: This "list" routine is best-effort. We
+ * assume that registering callbacks always succeeds. Silently
+ * ignore failure to scan for devices. Just return those which
+ * we happen to find.
+ */
+
+ desc = sr_bt_desc_new();
+ if (!desc)
+ return list;
+
+ memset(&scan_args, 0, sizeof(scan_args));
+ scan_args.port_list = list;
+ scan_args.append = append;
+
+ scan_args.addr_list = NULL;
+ scan_args.bt_type = "BT";
+ (void)sr_bt_config_cb_scan(desc, scan_cb, &scan_args);
+ (void)sr_bt_scan_bt(desc, scan_duration);
+ g_slist_free_full(scan_args.addr_list, g_free);
+
+ scan_args.addr_list = NULL;
+ scan_args.bt_type = "BLE";
+ (void)sr_bt_config_cb_scan(desc, scan_cb, &scan_args);
+ (void)sr_bt_scan_le(desc, scan_duration);
+ g_slist_free_full(scan_args.addr_list, g_free);
+
+ sr_bt_desc_free(desc);
+
+ return scan_args.port_list;
+}
+
+static struct ser_lib_functions serlib_bt = {
+ .open = ser_bt_open,
+ .close = ser_bt_close,
+ .flush = ser_bt_flush,
+ .drain = ser_bt_drain,
+ .write = ser_bt_write,
+ .read = ser_bt_read,
+ /*
+ * Bluetooth communication has no concept of bitrate, so ignore
+ * these arguments silently. Neither need we pass the frame format
+ * down to internal BT comm routines, nor need we keep the values
+ * here, since the caller will cache/register them already.
+ */
+ .set_params = std_dummy_set_params,
+ .setup_source_add = ser_bt_setup_source_add,
+ .setup_source_remove = ser_bt_setup_source_remove,
+ .list = ser_bt_list,
+ .get_frame_format = NULL,
+};
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_bt = &serlib_bt;
+
+/* }}} */
+#else
+
+SR_PRIV int ser_name_is_bt(struct sr_serial_dev_inst *serial)
+{
+ (void)serial;
+
+ return 0;
+}
+
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_bt = NULL;
+
+#endif
+#endif
+
+/** @} */
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2017-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>
+#ifdef HAVE_LIBHIDAPI
+#include <hidapi.h>
+#endif
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "serial_hid.h"
+#include <stdlib.h>
+#include <string.h>
+#ifdef G_OS_WIN32
+#include <windows.h> /* for HANDLE */
+#endif
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-hid"
+/** @endcond */
+
+#ifdef HAVE_SERIAL_COMM
+
+/**
+ * @file
+ *
+ * Serial port handling, HIDAPI library specific support code.
+ */
+
+/**
+ * @defgroup grp_serial_hid Serial port handling, HID group
+ *
+ * Make serial-over-HID communication appear like a regular serial port.
+ *
+ * @{
+ */
+
+#ifdef HAVE_LIBHIDAPI
+/* {{{ helper routines */
+
+/* Strip off parity bits for "odd" data bit counts like in 7e1 frames. */
+static void ser_hid_mask_databits(struct sr_serial_dev_inst *serial,
+ uint8_t *data, size_t len)
+{
+ uint32_t mask32;
+ uint8_t mask;
+ size_t idx;
+
+ if ((serial->comm_params.data_bits % 8) == 0)
+ return;
+
+ mask32 = (1UL << serial->comm_params.data_bits) - 1;
+ mask = mask32 & 0xff;
+ for (idx = 0; idx < len; idx++)
+ data[idx] &= mask;
+}
+
+/* }}} */
+/* {{{ open/close/list/find HIDAPI connection, exchange HID requests and data */
+
+/*
+ * Convert a HIDAPI path (which depends on the target platform, and may
+ * depend on one of several available API variants on that platform) to
+ * something that is usable as a "port name" in conn= specs.
+ *
+ * Since conn= is passed with -d where multiple options (among them conn=)
+ * are separated by colons, port names themselves cannot contain colons.
+ *
+ * Just replace colons by a period in the simple case (Linux platform,
+ * hidapi-libusb implementation, bus/address/interface). Prefix the
+ * HIDAPI path in the complex cases (Linux hidapi-hidraw, Windows, Mac).
+ * Paths with colons outside of libusb based implementations are unhandled
+ * here, but were not yet seen on any sigrok supported platform either.
+ * So just reject them.
+ */
+static char *get_hidapi_path_copy(const char *path)
+{
+ static const char *accept = "0123456789abcdefABCDEF:";
+ static const char *keep = "0123456789abcdefABCDEF";
+
+ int has_colon;
+ int is_hex_colon;
+ char *name;
+
+ has_colon = strchr(path, ':') != NULL;
+ is_hex_colon = strspn(path, accept) == strlen(path);
+ if (has_colon && !is_hex_colon) {
+ sr_err("Unsupported HIDAPI path format: %s", path);
+ return NULL;
+ }
+ if (is_hex_colon) {
+ name = g_strdup_printf("%s%s", SER_HID_USB_PREFIX, path);
+ g_strcanon(name + strlen(SER_HID_USB_PREFIX), keep, '.');
+ } else {
+ name = g_strdup_printf("%s%s", SER_HID_RAW_PREFIX, path);
+ }
+
+ return name;
+}
+
+/*
+ * Undo the port name construction that was done during scan. Extract
+ * the HIDAPI path from a conn= input spec (the part after the hid/
+ * prefix and chip type).
+ *
+ * Strip off the "raw" prefix, or undo colon substitution. See @ref
+ * get_hidapi_path_copy() for details.
+ */
+static const char *extract_hidapi_path(char *buffer)
+{
+ static const char *keep = "0123456789abcdefABCDEF:";
+
+ const char *p;
+
+ p = buffer;
+ if (!p || !*p)
+ return NULL;
+
+ if (strncmp(p, SER_HID_RAW_PREFIX, strlen(SER_HID_RAW_PREFIX)) == 0) {
+ p += strlen(SER_HID_RAW_PREFIX);
+ return p;
+ }
+ if (strncmp(p, SER_HID_USB_PREFIX, strlen(SER_HID_USB_PREFIX)) == 0) {
+ p += strlen(SER_HID_USB_PREFIX);
+ g_strcanon(buffer, keep, ':');
+ return p;
+ }
+
+ return NULL;
+}
+
+/*
+ * The HIDAPI specific list() callback, invoked by common serial.c code.
+ * Enumerate all devices (no VID:PID is involved).
+ * Invoke an 'append' callback with "path" and "name".
+ */
+static GSList *ser_hid_hidapi_list(GSList *list, sr_ser_list_append_t append)
+{
+ struct hid_device_info *devs, *curdev;
+ const char *chipname;
+ char *path, *name;
+ wchar_t *manuf, *prod, *serno;
+ uint16_t vid, pid;
+ GString *desc;
+
+ devs = hid_enumerate(0x0000, 0x0000);
+ for (curdev = devs; curdev; curdev = curdev->next) {
+ /*
+ * Determine the chip name from VID:PID (if it's one of
+ * the supported types with an ID known to us).
+ */
+ vid = curdev->vendor_id;
+ pid = curdev->product_id;
+ chipname = ser_hid_chip_find_name_vid_pid(vid, pid);
+ if (!chipname)
+ chipname = "<chip>";
+
+ /*
+ * Prefix port names such that open() calls with this
+ * conn= spec will end up here and contain all details
+ * that are essential for processing.
+ */
+ path = get_hidapi_path_copy(curdev->path);
+ if (!path)
+ continue;
+ name = g_strdup_printf("%s/%s/%s",
+ SER_HID_CONN_PREFIX, chipname, path);
+ g_free(path);
+
+ /*
+ * Print whatever information was available. Construct
+ * the description text from pieces. Absence of fields
+ * is not fatal, we have seen perfectly usable cables
+ * that only had a VID and PID (permissions were not an
+ * issue).
+ */
+ manuf = curdev->manufacturer_string;
+ prod = curdev->product_string;
+ serno = curdev->serial_number;
+ vid = curdev->vendor_id;
+ pid = curdev->product_id;
+ desc = g_string_sized_new(128);
+ g_string_append_printf(desc, "HID");
+ if (manuf && wcslen(manuf) != 0)
+ g_string_append_printf(desc, " %ls", manuf);
+ if (prod && wcslen(prod) != 0)
+ g_string_append_printf(desc, " %ls", prod);
+ if (serno && wcslen(serno) != 0)
+ g_string_append_printf(desc, " %ls", serno);
+ if (vid && pid)
+ g_string_append_printf(desc, " [%04hx.%04hx]", vid, pid);
+ list = append(list, name, desc->str);
+ g_string_free(desc, TRUE);
+ g_free(name);
+ }
+ hid_free_enumeration(devs);
+
+ return list;
+}
+
+/*
+ * The HIDAPI specific find_usb() callback, invoked by common serial.c code.
+ * Enumerate devices for the specified VID:PID pair.
+ * Invoke an "append" callback with 'path' for the device.
+ */
+static GSList *ser_hid_hidapi_find_usb(GSList *list, sr_ser_find_append_t append,
+ uint16_t vendor_id, uint16_t product_id)
+{
+ struct hid_device_info *devs, *curdev;
+ const char *name;
+
+ devs = hid_enumerate(vendor_id, product_id);
+ for (curdev = devs; curdev; curdev = curdev->next) {
+ name = curdev->path;
+ list = append(list, name);
+ }
+ hid_free_enumeration(devs);
+
+ return list;
+}
+
+/* Get the serial number of a device specified by path. */
+static int ser_hid_hidapi_get_serno(const char *path, char *buf, size_t blen)
+{
+ char *usbpath;
+ const char *hidpath;
+ hid_device *dev;
+ wchar_t *serno_wch;
+ int rc;
+
+ if (!path || !*path)
+ return SR_ERR_ARG;
+ usbpath = g_strdup(path);
+ hidpath = extract_hidapi_path(usbpath);
+ dev = hidpath ? hid_open_path(hidpath) : NULL;
+ g_free(usbpath);
+ if (!dev)
+ return SR_ERR_IO;
+
+ serno_wch = g_malloc0(blen * sizeof(*serno_wch));
+ rc = hid_get_serial_number_string(dev, serno_wch, blen - 1);
+ hid_close(dev);
+ if (rc != 0) {
+ g_free(serno_wch);
+ return SR_ERR_IO;
+ }
+
+ snprintf(buf, blen, "%ls", serno_wch);
+ g_free(serno_wch);
+
+ return SR_OK;
+}
+
+/* Get the VID and PID of a device specified by path. */
+static int ser_hid_hidapi_get_vid_pid(const char *path,
+ uint16_t *vid, uint16_t *pid)
+{
+#if 0
+ /*
+ * Bummer! It would have been most reliable to just open the
+ * device by the specified path, and grab its VID:PID. But
+ * there is no way to get these parameters, neither in the
+ * HIDAPI itself, nor when cheating and reaching behind the API
+ * and accessing the libusb handle in dirty ways. :(
+ */
+ hid_device *dev;
+
+ if (!path || !*path)
+ return SR_ERR_ARG;
+ dev = hid_open_path(path);
+ if (!dev)
+ return SR_ERR_IO;
+ if (vid)
+ *vid = dev->vendor_id;
+ if (pid)
+ *pid = dev->product_id;
+ hid_close(dev);
+
+ return SR_OK;
+#else
+ /*
+ * The fallback approach. Enumerate all devices, compare the
+ * enumerated USB path, and grab the VID:PID. Unfortunately the
+ * caller can provide path specs that differ from enumerated
+ * paths yet mean the same (address the same device). This needs
+ * more attention. Though the specific format of the path and
+ * its meaning are said to be OS specific, which is why we may
+ * not assume anything about it...
+ */
+ char *usbpath;
+ const char *hidpath;
+ struct hid_device_info *devs, *dev;
+ int found;
+
+ usbpath = g_strdup(path);
+ hidpath = extract_hidapi_path(usbpath);
+ if (!hidpath) {
+ g_free(usbpath);
+ return SR_ERR_NA;
+ }
+
+ devs = hid_enumerate(0x0000, 0x0000);
+ found = 0;
+ for (dev = devs; dev; dev = dev->next) {
+ if (strcmp(dev->path, hidpath) != 0)
+ continue;
+ if (vid)
+ *vid = dev->vendor_id;
+ if (pid)
+ *pid = dev->product_id;
+ found = 1;
+ break;
+ }
+ hid_free_enumeration(devs);
+ g_free(usbpath);
+
+ return found ? SR_OK : SR_ERR_NA;
+#endif
+}
+
+static int ser_hid_hidapi_open_dev(struct sr_serial_dev_inst *serial)
+{
+ hid_device *hid_dev;
+
+ if (!serial->usb_path || !*serial->usb_path)
+ return SR_ERR_ARG;
+
+ /*
+ * A path is available, assume that either a GUI or a
+ * user has copied what a previous listing has provided.
+ * Or a scan determined a matching device's USB path.
+ */
+ if (!serial->hid_path)
+ serial->hid_path = extract_hidapi_path(serial->usb_path);
+ hid_dev = hid_open_path(serial->hid_path);
+ if (!hid_dev) {
+ serial->hid_path = NULL;
+ return SR_ERR_IO;
+ }
+
+ serial->hid_dev = hid_dev;
+ hid_set_nonblocking(hid_dev, 1);
+
+ return SR_OK;
+}
+
+static void ser_hid_hidapi_close_dev(struct sr_serial_dev_inst *serial)
+{
+ if (serial->hid_dev) {
+ hid_close(serial->hid_dev);
+ serial->hid_dev = NULL;
+ serial->hid_path = NULL;
+ }
+ g_slist_free_full(serial->hid_source_args, g_free);
+ serial->hid_source_args = NULL;
+}
+
+struct hidapi_source_args_t {
+ /* Application callback. */
+ sr_receive_data_callback cb;
+ void *cb_data;
+ /* The serial device, to store RX data. */
+ struct sr_serial_dev_inst *serial;
+};
+
+/*
+ * Gets periodically invoked by the glib main loop. "Drives" (checks)
+ * progress of USB communication, and invokes the application's callback
+ * which processes RX data (when some has become available), as well as
+ * handles application level timeouts.
+ */
+static int hidapi_source_cb(int fd, int revents, void *cb_data)
+{
+ struct hidapi_source_args_t *args;
+ uint8_t rx_buf[SER_HID_CHUNK_SIZE];
+ int rc;
+
+ args = cb_data;
+
+ /*
+ * Drain receive data which the chip might have pending. This is
+ * "a copy" of the "background part" of ser_hid_read(), without
+ * the timeout support code, and not knowing how much data the
+ * application is expecting.
+ */
+ do {
+ rc = args->serial->hid_chip_funcs->read_bytes(args->serial,
+ rx_buf, sizeof(rx_buf), 0);
+ if (rc > 0) {
+ ser_hid_mask_databits(args->serial, rx_buf, rc);
+ sr_ser_queue_rx_data(args->serial, rx_buf, rc);
+ }
+ } while (rc > 0);
+
+ /*
+ * When RX data became available (now or earlier), pass this
+ * condition to the application callback. Always periodically
+ * run the application callback, since it handles timeouts and
+ * might carry out other tasks as well like signalling progress.
+ */
+ if (sr_ser_has_queued_data(args->serial))
+ revents |= G_IO_IN;
+ rc = args->cb(fd, revents, args->cb_data);
+
+ return rc;
+}
+
+#define WITH_MAXIMUM_TIMEOUT_VALUE 10
+static int ser_hid_hidapi_setup_source_add(struct sr_session *session,
+ struct sr_serial_dev_inst *serial, int events, int timeout,
+ sr_receive_data_callback cb, void *cb_data)
+{
+ struct hidapi_source_args_t *args;
+ int rc;
+
+ (void)events;
+
+ /* Optionally enforce a minimum poll period. */
+ if (WITH_MAXIMUM_TIMEOUT_VALUE && timeout > WITH_MAXIMUM_TIMEOUT_VALUE)
+ timeout = WITH_MAXIMUM_TIMEOUT_VALUE;
+
+ /* Allocate status container for background data reception. */
+ args = g_malloc0(sizeof(*args));
+ args->cb = cb;
+ args->cb_data = cb_data;
+ args->serial = serial;
+
+ /*
+ * Have a periodic timer installed. Register the allocated block
+ * with the serial device, since the GSource's finalizer won't
+ * free the memory, and we haven't bothered to create a custom
+ * HIDAPI specific GSource.
+ */
+ rc = sr_session_source_add(session, -1, events, timeout,
+ hidapi_source_cb, args);
+ if (rc != SR_OK) {
+ g_free(args);
+ return rc;
+ }
+ serial->hid_source_args = g_slist_append(serial->hid_source_args, args);
+
+ return SR_OK;
+}
+
+static int ser_hid_hidapi_setup_source_remove(struct sr_session *session,
+ struct sr_serial_dev_inst *serial)
+{
+ (void)serial;
+
+ (void)sr_session_source_remove(session, -1);
+ /*
+ * Release callback args here already? Can there be more than
+ * one source registered at any time, given that we pass fd -1
+ * which is used as the key for the session?
+ */
+
+ return SR_OK;
+}
+
+SR_PRIV int ser_hid_hidapi_get_report(struct sr_serial_dev_inst *serial,
+ uint8_t *data, size_t len)
+{
+ int rc;
+
+ rc = hid_get_feature_report(serial->hid_dev, data, len);
+ if (rc < 0)
+ return SR_ERR_IO;
+
+ return rc;
+}
+
+SR_PRIV int ser_hid_hidapi_set_report(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, size_t len)
+{
+ int rc;
+ const wchar_t *err_text;
+
+ rc = hid_send_feature_report(serial->hid_dev, data, len);
+ if (rc < 0) {
+ err_text = hid_error(serial->hid_dev);
+ sr_dbg("%s() hidapi error: %ls", __func__, err_text);
+ return SR_ERR_IO;
+ }
+
+ return rc;
+}
+
+SR_PRIV int ser_hid_hidapi_get_data(struct sr_serial_dev_inst *serial,
+ uint8_t ep, uint8_t *data, size_t len, int timeout)
+{
+ int rc;
+
+ (void)ep;
+
+ if (timeout)
+ rc = hid_read_timeout(serial->hid_dev, data, len, timeout);
+ else
+ rc = hid_read(serial->hid_dev, data, len);
+ if (rc < 0)
+ return SR_ERR_IO;
+ if (rc == 0)
+ return 0;
+
+ return rc;
+}
+
+SR_PRIV int ser_hid_hidapi_set_data(struct sr_serial_dev_inst *serial,
+ uint8_t ep, const uint8_t *data, size_t len, int timeout)
+{
+ int rc;
+
+ (void)ep;
+ (void)timeout;
+
+ rc = hid_write(serial->hid_dev, data, len);
+ if (rc < 0)
+ return SR_ERR_IO;
+
+ return rc;
+}
+
+/* }}} */
+/* {{{ support for serial-over-HID chips */
+
+static struct ser_hid_chip_functions **chips[SER_HID_CHIP_LAST] = {
+ [SER_HID_CHIP_UNKNOWN] = NULL,
+ [SER_HID_CHIP_BTC_BU86X] = &ser_hid_chip_funcs_bu86x,
+ [SER_HID_CHIP_SIL_CP2110] = &ser_hid_chip_funcs_cp2110,
+ [SER_HID_CHIP_VICTOR_DMM] = &ser_hid_chip_funcs_victor,
+ [SER_HID_CHIP_WCH_CH9325] = &ser_hid_chip_funcs_ch9325,
+};
+
+static struct ser_hid_chip_functions *get_hid_chip_funcs(enum ser_hid_chip_t chip)
+{
+ struct ser_hid_chip_functions *funcs;
+
+ if (chip >= ARRAY_SIZE(chips))
+ return NULL;
+ if (!chips[chip])
+ return NULL;
+ funcs = *chips[chip];
+ if (!funcs)
+ return NULL;
+
+ return funcs;
+}
+
+static int ser_hid_setup_funcs(struct sr_serial_dev_inst *serial)
+{
+
+ if (!serial)
+ return -1;
+
+ if (serial->hid_chip && !serial->hid_chip_funcs) {
+ serial->hid_chip_funcs = get_hid_chip_funcs(serial->hid_chip);
+ if (!serial->hid_chip_funcs)
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Takes a pointer to the chip spec with potentially trailing data,
+ * returns the chip index and advances the spec pointer upon match,
+ * returns SER_HID_CHIP_UNKNOWN upon mismatch.
+ */
+static enum ser_hid_chip_t ser_hid_chip_find_enum(const char **spec_p)
+{
+ const gchar *spec;
+ enum ser_hid_chip_t idx;
+ struct ser_hid_chip_functions *desc;
+
+ if (!spec_p || !*spec_p)
+ return SER_HID_CHIP_UNKNOWN;
+ spec = *spec_p;
+ if (!*spec)
+ return SER_HID_CHIP_UNKNOWN;
+ for (idx = 0; idx < SER_HID_CHIP_LAST; idx++) {
+ desc = get_hid_chip_funcs(idx);
+ if (!desc)
+ continue;
+ if (!desc->chipname)
+ continue;
+ if (!g_str_has_prefix(spec, desc->chipname))
+ continue;
+ spec += strlen(desc->chipname);
+ *spec_p = spec;
+ return idx;
+ }
+
+ return SER_HID_CHIP_UNKNOWN;
+}
+
+/* See if we can find a chip name for a VID:PID spec. */
+SR_PRIV const char *ser_hid_chip_find_name_vid_pid(uint16_t vid, uint16_t pid)
+{
+ size_t chip_idx;
+ struct ser_hid_chip_functions *desc;
+ const struct vid_pid_item *vid_pids;
+
+ for (chip_idx = 0; chip_idx < SER_HID_CHIP_LAST; chip_idx++) {
+ desc = get_hid_chip_funcs(chip_idx);
+ if (!desc)
+ continue;
+ if (!desc->chipname)
+ continue;
+ vid_pids = desc->vid_pid_items;
+ if (!vid_pids)
+ continue;
+ while (vid_pids->vid) {
+ if (vid_pids->vid == vid && vid_pids->pid == pid)
+ return desc->chipname;
+ vid_pids++;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * See if a text string is a valid USB path for a HID device.
+ * @param[in] serial The serial port that is about to get opened.
+ * @param[in] path The (assumed) USB path specification.
+ * @return SR_OK upon success, SR_ERR* upon failure.
+ */
+static int try_open_path(struct sr_serial_dev_inst *serial, const char *path)
+{
+ int rc;
+
+ serial->usb_path = g_strdup(path);
+ rc = ser_hid_hidapi_open_dev(serial);
+ ser_hid_hidapi_close_dev(serial);
+ g_free(serial->usb_path);
+ serial->usb_path = NULL;
+
+ return rc;
+}
+
+/**
+ * Parse conn= specs for serial over HID communication.
+ *
+ * @param[in] serial The serial port that is about to get opened.
+ * @param[in] spec The caller provided conn= specification.
+ * @param[out] chip_ref Pointer to a chip type (enum).
+ * @param[out] path_ref Pointer to a USB path (text string).
+ * @param[out] serno_ref Pointer to a serial number (text string).
+ *
+ * @return 0 upon success, non-zero upon failure. Fills the *_ref output
+ * values.
+ *
+ * @internal
+ *
+ * Summary of parsing rules as they are implemented:
+ * - Insist on the "hid" prefix. Accept "hid" alone without any other
+ * additional field.
+ * - The first field that follows can be a chip spec, yet is optional.
+ * - Any other field is assumed to be either a USB path or a serial
+ * number. There is no point in specifying both of these, as either
+ * of them uniquely identifies a device.
+ *
+ * Supported formats resulting from these rules:
+ * hid[/<chip>]
+ * hid[/<chip>]/usb=<bus>.<dev>[.<if>]
+ * hid[/<chip>]/raw=<path> (may contain slashes!)
+ * hid[/<chip>]/sn=serno
+ *
+ * This routine just parses the conn= spec, which either was provided by
+ * a user, or may reflect (cite) an item of a previously gathered listing
+ * (clipboard provided by CLI clients, or selected from a GUI form).
+ * Another routine will fill in the blanks, and do the cable selection
+ * when a filter was specified.
+ *
+ * Users will want to use short forms when they need to come up with the
+ * specs by themselves. The "verbose" or seemingly redundant forms (chip
+ * _and_ path/serno spec) are useful when the cable uses non-standard or
+ * not-yet-supported VID:PID items when automatic chip detection fails.
+ */
+static int ser_hid_parse_conn_spec(
+ struct sr_serial_dev_inst *serial, const char *spec,
+ enum ser_hid_chip_t *chip_ref, char **path_ref, char **serno_ref)
+{
+ const char *p;
+ enum ser_hid_chip_t chip;
+ char *path, *serno;
+ int rc;
+
+ if (chip_ref)
+ *chip_ref = SER_HID_CHIP_UNKNOWN;
+ if (path_ref)
+ *path_ref = NULL;
+ if (serno_ref)
+ *serno_ref = NULL;
+ chip = SER_HID_CHIP_UNKNOWN;
+ path = serno = NULL;
+
+ if (!serial || !spec || !*spec)
+ return SR_ERR_ARG;
+ p = spec;
+
+ /* The "hid" prefix is mandatory. */
+ if (!g_str_has_prefix(p, SER_HID_CONN_PREFIX))
+ return SR_ERR_ARG;
+ p += strlen(SER_HID_CONN_PREFIX);
+
+ /*
+ * Check for prefixed fields, assume chip type spec otherwise.
+ * Paths and serial numbers "are greedy" (span to the end of
+ * the input spec). Chip types are optional, and cannot repeat
+ * multiple times.
+ */
+ while (*p) {
+ if (*p == '/')
+ p++;
+ if (!*p)
+ break;
+ if (g_str_has_prefix(p, SER_HID_USB_PREFIX)) {
+ rc = try_open_path(serial, p);
+ if (rc != SR_OK)
+ return rc;
+ path = g_strdup(p);
+ p += strlen(p);
+ } else if (g_str_has_prefix(p, SER_HID_RAW_PREFIX)) {
+ rc = try_open_path(serial, p);
+ if (rc != SR_OK)
+ return rc;
+ path = g_strdup(p);
+ p += strlen(p);
+ } else if (g_str_has_prefix(p, SER_HID_SNR_PREFIX)) {
+ p += strlen(SER_HID_SNR_PREFIX);
+ serno = g_strdup(p);
+ p += strlen(p);
+ } else if (!chip) {
+ char *copy;
+ const char *endptr;
+ copy = g_strdup(p);
+ endptr = copy;
+ chip = ser_hid_chip_find_enum(&endptr);
+ if (!chip) {
+ g_free(copy);
+ return SR_ERR_ARG;
+ }
+ p += endptr - copy;
+ g_free(copy);
+ } else {
+ sr_err("unsupported conn= spec %s, error at %s", spec, p);
+ return SR_ERR_ARG;
+ }
+ if (*p == '/')
+ p++;
+ if (path || serno)
+ break;
+ }
+
+ if (chip_ref)
+ *chip_ref = chip;
+ if (path_ref && path)
+ *path_ref = path;
+ if (serno_ref && serno)
+ *serno_ref = serno;
+
+ return SR_OK;
+}
+
+/* Get and compare serial number. Boolean return value. */
+static int check_serno(const char *path, const char *serno_want)
+{
+ char *usb_path;
+ const char *hid_path;
+ char serno_got[128];
+ int rc;
+
+ usb_path = g_strdup(path);
+ hid_path = extract_hidapi_path(usb_path);
+ rc = ser_hid_hidapi_get_serno(hid_path, serno_got, sizeof(serno_got));
+ g_free(usb_path);
+ if (rc) {
+ sr_dbg("DBG: %s(), could not get serial number", __func__);
+ return 0;
+ }
+
+ return strcmp(serno_got, serno_want) == 0;
+}
+
+static GSList *append_find(GSList *devs, const char *path)
+{
+ char *copy;
+
+ if (!path || !*path)
+ return devs;
+
+ copy = g_strdup(path);
+ devs = g_slist_append(devs, copy);
+
+ return devs;
+}
+
+static GSList *list_paths_for_vids_pids(const struct vid_pid_item *vid_pids)
+{
+ GSList *list;
+ size_t idx;
+ uint16_t vid, pid;
+
+ list = NULL;
+ for (idx = 0; /* EMPTY */; idx++) {
+ if (!vid_pids) {
+ vid = pid = 0;
+ } else if (!vid_pids[idx].vid) {
+ break;
+ } else {
+ vid = vid_pids[idx].vid;
+ pid = vid_pids[idx].pid;
+ }
+ list = ser_hid_hidapi_find_usb(list, append_find, vid, pid);
+ if (!vid_pids)
+ break;
+ }
+
+ return list;
+}
+
+/**
+ * Search for a matching USB device for HID communication.
+ *
+ * @param[inout] chip The HID chip type (enum).
+ * @param[inout] usbpath The USB path for the device (string).
+ * @param[in] serno The serial number to search for.
+ *
+ * @retval SR_OK upon success
+ * @retval SR_ERR_* upon failure.
+ *
+ * @internal
+ *
+ * This routine fills in blanks which the conn= spec parser left open.
+ * When not specified yet, the HID chip type gets determined. When a
+ * serial number was specified, then search the corresponding device.
+ * Upon completion, the chip type and USB path for the device shall be
+ * known, as these are essential for subsequent operation.
+ */
+static int ser_hid_chip_search(enum ser_hid_chip_t *chip_ref,
+ char **path_ref, const char *serno)
+{
+ enum ser_hid_chip_t chip;
+ char *path;
+ int have_chip, have_path, have_serno;
+ struct ser_hid_chip_functions *chip_funcs;
+ int rc;
+ int serno_matched;
+ uint16_t vid, pid;
+ const char *name;
+ const struct vid_pid_item *vid_pids;
+ GSList *list, *matched, *matched2, *tmplist;
+
+ if (!chip_ref)
+ return SR_ERR_ARG;
+ chip = *chip_ref;
+ if (!path_ref)
+ return SR_ERR_ARG;
+ path = *path_ref;
+
+ /*
+ * Simplify the more complex conditions somewhat by assigning
+ * to local variables. Handle the easiest conditions first.
+ * - Either path or serial number can be specified, but not both
+ * at the same time.
+ * - When a USB path is given, immediately see which HID chip
+ * the device has, without the need for enumeration.
+ * - When a serial number is given, enumerate the devices and
+ * search for that number. Either enumerate all devices of the
+ * specified HID chip type (try the VID:PID pairs that we are
+ * aware of), or try all HID devices for unknown chip types.
+ * Not finding the serial number is fatal.
+ * - When no path was found yet, enumerate the devices and pick
+ * one of them. Try known VID:PID pairs for a HID chip, or all
+ * devices for unknown chips. Make sure to pick a device of a
+ * supported chip type if the chip was not specified.
+ * - Determine the chip type if not yet known. There should be
+ * a USB path by now, determined in one of the above blocks.
+ */
+ have_chip = (chip != SER_HID_CHIP_UNKNOWN) ? 1 : 0;
+ have_path = (path && *path) ? 1 : 0;
+ have_serno = (serno && *serno) ? 1 : 0;
+ if (have_path && have_serno) {
+ sr_err("Unsupported combination of USB path and serno");
+ return SR_ERR_ARG;
+ }
+ chip_funcs = have_chip ? get_hid_chip_funcs(chip) : NULL;
+ if (have_chip && !chip_funcs)
+ return SR_ERR_NA;
+ if (have_chip && !chip_funcs->vid_pid_items)
+ return SR_ERR_NA;
+ if (have_path && !have_chip) {
+ vid = pid = 0;
+ rc = ser_hid_hidapi_get_vid_pid(path, &vid, &pid);
+ if (rc != SR_OK)
+ return rc;
+ name = ser_hid_chip_find_name_vid_pid(vid, pid);
+ if (!name || !*name)
+ return SR_ERR_NA;
+ chip = ser_hid_chip_find_enum(&name);
+ if (chip == SER_HID_CHIP_UNKNOWN)
+ return SR_ERR_NA;
+ have_chip = 1;
+ }
+ if (have_serno) {
+ vid_pids = have_chip ? chip_funcs->vid_pid_items : NULL;
+ list = list_paths_for_vids_pids(vid_pids);
+ if (!list)
+ return SR_ERR_NA;
+ matched = NULL;
+ for (tmplist = list; tmplist; tmplist = tmplist->next) {
+ path = get_hidapi_path_copy(tmplist->data);
+ serno_matched = check_serno(path, serno);
+ g_free(path);
+ if (!serno_matched)
+ continue;
+ matched = tmplist;
+ break;
+ }
+ if (!matched)
+ return SR_ERR_NA;
+ path = g_strdup(matched->data);
+ have_path = 1;
+ g_slist_free_full(list, g_free);
+ }
+ if (!have_path) {
+ vid_pids = have_chip ? chip_funcs->vid_pid_items : NULL;
+ list = list_paths_for_vids_pids(vid_pids);
+ if (!list)
+ return SR_ERR_NA;
+ matched = matched2 = NULL;
+ if (have_chip) {
+ /* List already only contains specified chip. */
+ matched = list;
+ matched2 = list->next;
+ }
+ /* Works for lists with one or multiple chips. Saves indentation. */
+ for (tmplist = list; tmplist; tmplist = tmplist->next) {
+ if (have_chip)
+ break;
+ path = tmplist->data;
+ rc = ser_hid_hidapi_get_vid_pid(path, &vid, &pid);
+ if (rc || !ser_hid_chip_find_name_vid_pid(vid, pid))
+ continue;
+ if (!matched) {
+ matched = tmplist;
+ continue;
+ }
+ if (!matched2) {
+ matched2 = tmplist;
+ break;
+ }
+ }
+ if (!matched) {
+ g_slist_free_full(list, g_free);
+ return SR_ERR_NA;
+ }
+ /*
+ * TODO Optionally fail harder, expect users to provide
+ * unambiguous cable specs.
+ */
+ if (matched2)
+ sr_info("More than one cable matches, random pick.");
+ path = get_hidapi_path_copy(matched->data);
+ have_path = 1;
+ g_slist_free_full(list, g_free);
+ }
+ if (have_path && !have_chip) {
+ vid = pid = 0;
+ rc = ser_hid_hidapi_get_vid_pid(path, &vid, &pid);
+ if (rc != SR_OK)
+ return rc;
+ name = ser_hid_chip_find_name_vid_pid(vid, pid);
+ if (!name || !*name)
+ return SR_ERR_NA;
+ chip = ser_hid_chip_find_enum(&name);
+ if (chip == SER_HID_CHIP_UNKNOWN)
+ return SR_ERR_NA;
+ have_chip = 1;
+ }
+
+ if (chip_ref)
+ *chip_ref = chip;
+ if (path_ref)
+ *path_ref = path;
+
+ return SR_OK;
+}
+
+/* }}} */
+/* {{{ transport methods called by the common serial.c code */
+
+/* See if a serial port's name refers to an HID type. */
+SR_PRIV int ser_name_is_hid(struct sr_serial_dev_inst *serial)
+{
+ size_t off;
+ char sep;
+
+ if (!serial)
+ return 0;
+ if (!serial->port || !*serial->port)
+ return 0;
+
+ /* Accept either "hid" alone, or "hid/" as a prefix. */
+ if (!g_str_has_prefix(serial->port, SER_HID_CONN_PREFIX))
+ return 0;
+ off = strlen(SER_HID_CONN_PREFIX);
+ sep = serial->port[off];
+ if (sep != '\0' && sep != '/')
+ return 0;
+
+ return 1;
+}
+
+static int ser_hid_open(struct sr_serial_dev_inst *serial, int flags)
+{
+ enum ser_hid_chip_t chip;
+ char *usbpath, *serno;
+ int rc;
+
+ (void)flags;
+
+ if (ser_hid_setup_funcs(serial) != 0) {
+ sr_err("Cannot determine HID communication library.");
+ return SR_ERR_NA;
+ }
+
+ rc = ser_hid_parse_conn_spec(serial, serial->port,
+ &chip, &usbpath, &serno);
+ if (rc != SR_OK)
+ return SR_ERR_ARG;
+
+ /*
+ * When a serial number was specified, or when the chip type or
+ * the USB path were not specified, do a search to determine the
+ * device's USB path.
+ */
+ if (!chip || !usbpath || serno) {
+ rc = ser_hid_chip_search(&chip, &usbpath, serno);
+ if (rc != 0)
+ return SR_ERR_NA;
+ }
+
+ /*
+ * Open the HID device. Only store chip type and device handle
+ * when open completes successfully.
+ */
+ serial->hid_chip = chip;
+ if (ser_hid_setup_funcs(serial) != 0) {
+ sr_err("Cannot determine HID chip specific routines.");
+ return SR_ERR_NA;
+ }
+ if (usbpath && *usbpath)
+ serial->usb_path = usbpath;
+ if (serno && *serno)
+ serial->usb_serno = serno;
+
+ rc = ser_hid_hidapi_open_dev(serial);
+ if (rc) {
+ sr_err("Failed to open HID device.");
+ serial->hid_chip = 0;
+ g_free(serial->usb_path);
+ serial->usb_path = NULL;
+ g_free(serial->usb_serno);
+ serial->usb_serno = NULL;
+ return SR_ERR_IO;
+ }
+
+ if (!serial->rcv_buffer)
+ serial->rcv_buffer = g_string_sized_new(SER_HID_CHUNK_SIZE);
+
+ return SR_OK;
+}
+
+static int ser_hid_close(struct sr_serial_dev_inst *serial)
+{
+ ser_hid_hidapi_close_dev(serial);
+
+ return SR_OK;
+}
+
+static int ser_hid_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr)
+{
+ if (ser_hid_setup_funcs(serial) != 0)
+ return SR_ERR_NA;
+ if (!serial->hid_chip_funcs || !serial->hid_chip_funcs->set_params)
+ return SR_ERR_NA;
+
+ return serial->hid_chip_funcs->set_params(serial,
+ baudrate, bits, parity, stopbits,
+ flowcontrol, rts, dtr);
+}
+
+static int ser_hid_setup_source_add(struct sr_session *session,
+ struct sr_serial_dev_inst *serial, int events, int timeout,
+ sr_receive_data_callback cb, void *cb_data)
+{
+ return ser_hid_hidapi_setup_source_add(session, serial,
+ events, timeout, cb, cb_data);
+}
+
+static int ser_hid_setup_source_remove(struct sr_session *session,
+ struct sr_serial_dev_inst *serial)
+{
+ return ser_hid_hidapi_setup_source_remove(session, serial);
+}
+
+static GSList *ser_hid_list(GSList *list, sr_ser_list_append_t append)
+{
+ return ser_hid_hidapi_list(list, append);
+}
+
+static GSList *ser_hid_find_usb(GSList *list, sr_ser_find_append_t append,
+ uint16_t vendor_id, uint16_t product_id)
+{
+ return ser_hid_hidapi_find_usb(list, append, vendor_id, product_id);
+}
+
+static int ser_hid_flush(struct sr_serial_dev_inst *serial)
+{
+ if (!serial->hid_chip_funcs || !serial->hid_chip_funcs->flush)
+ return SR_ERR_NA;
+
+ return serial->hid_chip_funcs->flush(serial);
+}
+
+static int ser_hid_drain(struct sr_serial_dev_inst *serial)
+{
+ if (!serial->hid_chip_funcs || !serial->hid_chip_funcs->drain)
+ return SR_ERR_NA;
+
+ return serial->hid_chip_funcs->drain(serial);
+}
+
+static int ser_hid_write(struct sr_serial_dev_inst *serial,
+ const void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ int total, max_chunk, chunk_len;
+ int rc;
+
+ if (!serial->hid_chip_funcs || !serial->hid_chip_funcs->write_bytes)
+ return SR_ERR_NA;
+ if (!serial->hid_chip_funcs->max_bytes_per_request)
+ return SR_ERR_NA;
+
+ total = 0;
+ max_chunk = serial->hid_chip_funcs->max_bytes_per_request;
+ while (count > 0) {
+ chunk_len = count;
+ if (max_chunk && chunk_len > max_chunk)
+ chunk_len = max_chunk;
+ rc = serial->hid_chip_funcs->write_bytes(serial, buf, chunk_len);
+ if (rc < 0) {
+ sr_err("Error sending transmit data to HID device.");
+ return total;
+ }
+ if (rc != chunk_len) {
+ sr_warn("Short transmission to HID device (%d/%d bytes)?",
+ rc, chunk_len);
+ return total;
+ }
+ buf += chunk_len;
+ count -= chunk_len;
+ total += chunk_len;
+ /* TODO
+ * Need we wait here? For data to drain through the slow
+ * UART. Not all UART-over-HID chips will have FIFOs.
+ */
+ if (!nonblocking) {
+ (void)timeout_ms;
+ /* TODO */
+ }
+ }
+
+ return total;
+}
+
+static int ser_hid_read(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ gint64 deadline_us, now_us;
+ uint8_t buffer[SER_HID_CHUNK_SIZE];
+ int rc;
+ unsigned int got;
+
+ if (!serial->hid_chip_funcs || !serial->hid_chip_funcs->read_bytes)
+ return SR_ERR_NA;
+ if (!serial->hid_chip_funcs->max_bytes_per_request)
+ return SR_ERR_NA;
+
+ /*
+ * Immediately satisfy the caller's request from the RX buffer
+ * if the requested amount of data is available already.
+ */
+ if (sr_ser_has_queued_data(serial) >= count)
+ return sr_ser_unqueue_rx_data(serial, buf, count);
+
+ /*
+ * When a timeout was specified, then determine the deadline
+ * where to stop reception.
+ */
+ deadline_us = 0;
+ now_us = 0; /* Silence a (false) compiler warning. */
+ if (timeout_ms) {
+ now_us = g_get_monotonic_time();
+ deadline_us = now_us + timeout_ms * 1000;
+ }
+
+ /*
+ * Keep receiving from the port until the caller's requested
+ * amount of data has become available, or the timeout has
+ * expired. In the absence of a timeout, stop reading when an
+ * attempt no longer yields receive data.
+ *
+ * This implementation assumes that applications will call the
+ * read routine often enough, or that reception continues in
+ * background, such that data is not lost and hardware and
+ * software buffers won't overrun.
+ */
+ while (TRUE) {
+ /*
+ * Determine the timeout (in milliseconds) for this
+ * iteration. The 'now_us' timestamp was initially
+ * determined above, and gets updated at the bottom of
+ * the loop.
+ */
+ if (deadline_us) {
+ timeout_ms = (deadline_us - now_us) / 1000;
+ if (!timeout_ms)
+ timeout_ms = 1;
+ } else if (nonblocking) {
+ timeout_ms = 10;
+ } else {
+ timeout_ms = 0;
+ }
+
+ /*
+ * Check the HID transport for the availability of more
+ * receive data.
+ */
+ rc = serial->hid_chip_funcs->read_bytes(serial,
+ buffer, sizeof(buffer), timeout_ms);
+ if (rc < 0) {
+ sr_dbg("DBG: %s() read error %d.", __func__, rc);
+ return SR_ERR;
+ }
+ if (rc) {
+ ser_hid_mask_databits(serial, buffer, rc);
+ sr_ser_queue_rx_data(serial, buffer, rc);
+ }
+ got = sr_ser_has_queued_data(serial);
+
+ /*
+ * Stop reading when the requested amount is available,
+ * or when the timeout has expired.
+ *
+ * TODO Consider whether grabbing all RX data is more
+ * desirable. Implementing this approach requires a cheap
+ * check for the availability of more data on the USB level.
+ */
+ if (got >= count)
+ break;
+ if (nonblocking && !rc)
+ break;
+ if (deadline_us) {
+ now_us = g_get_monotonic_time();
+ if (now_us >= deadline_us) {
+ sr_dbg("DBG: %s() read loop timeout.", __func__);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Satisfy the caller's demand for receive data from previously
+ * queued incoming data.
+ */
+ if (got > count)
+ got = count;
+
+ return sr_ser_unqueue_rx_data(serial, buf, count);
+}
+
+static struct ser_lib_functions serlib_hid = {
+ .open = ser_hid_open,
+ .close = ser_hid_close,
+ .flush = ser_hid_flush,
+ .drain = ser_hid_drain,
+ .write = ser_hid_write,
+ .read = ser_hid_read,
+ .set_params = ser_hid_set_params,
+ .setup_source_add = ser_hid_setup_source_add,
+ .setup_source_remove = ser_hid_setup_source_remove,
+ .list = ser_hid_list,
+ .find_usb = ser_hid_find_usb,
+ .get_frame_format = NULL,
+};
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_hid = &serlib_hid;
+
+/* }}} */
+#else
+
+SR_PRIV int ser_name_is_hid(struct sr_serial_dev_inst *serial)
+{
+ (void)serial;
+
+ return 0;
+}
+
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_hid = NULL;
+
+#endif
+#endif
+/** @} */
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2017-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/>.
+ */
+
+#ifndef LIBSIGROK_SERIAL_HID_H
+#define LIBSIGROK_SERIAL_HID_H
+
+/* The prefix for port names which are HID based. */
+#define SER_HID_CONN_PREFIX "hid"
+#define SER_HID_USB_PREFIX "usb="
+#define SER_HID_RAW_PREFIX "raw="
+#define SER_HID_SNR_PREFIX "sn="
+
+/*
+ * The maximum number of bytes any supported HID chip can communicate
+ * within a single request.
+ *
+ * Brymen BU-86X: up to 8 bytes
+ * SiLabs CP2110: up to 63 bytes
+ * Victor DMM: up to 14 bytes
+ * WCH CH9325: up to 7 bytes
+ */
+#define SER_HID_CHUNK_SIZE 64
+
+/*
+ * Routines to get/set reports/data, provided by serial_hid.c and used
+ * in serial_hid_<chip>.c files.
+ */
+SR_PRIV int ser_hid_hidapi_get_report(struct sr_serial_dev_inst *serial,
+ uint8_t *data, size_t len);
+SR_PRIV int ser_hid_hidapi_set_report(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, size_t len);
+SR_PRIV int ser_hid_hidapi_get_data(struct sr_serial_dev_inst *serial,
+ uint8_t ep, uint8_t *data, size_t len, int timeout);
+SR_PRIV int ser_hid_hidapi_set_data(struct sr_serial_dev_inst *serial,
+ uint8_t ep, const uint8_t *data, size_t len, int timeout);
+
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * 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/>.
+ */
+
+/*
+ * This implements serial communication primitives for the Brymen BU-86X
+ * infrared adapter for handheld multimeters. The vendor's protocol spec
+ * http://brymen.com/product-html/images/DownloadList/ProtocolList/BM860-BM860s_List/BM860-BM860s-500000-count-dual-display-DMMs-protocol.pdf
+ * suggests that HID reports get communicated, but only report number 0
+ * is involved, which carries a mere byte stream in 8 byte chunks each.
+ * The frame format and bitrate are fixed, and need not get configured.
+ *
+ * The meter's packet consists of 24 bytes which get received in three
+ * HID reports. Packet reception gets initiated by sending a short HID
+ * report to the meter. It's uncertain which parts of this exchange are
+ * specific to the adapter and to the meter. Using the IR adapter with
+ * other devices, or using the meter with other cables/adapters may need
+ * a little more adjustment with respect to layering.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "serial_hid.h"
+#include <string.h>
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-bu86x"
+/** @endcond */
+
+#ifdef HAVE_SERIAL_COMM
+#ifdef HAVE_LIBHIDAPI
+
+/**
+ * @file
+ *
+ * Support serial-over-HID, specifically the Brymen BU-86X infrared adapter.
+ */
+
+#define BU86X_MAX_BYTES_PER_REQUEST 8
+
+static const struct vid_pid_item vid_pid_items_bu86x[] = {
+ { 0x0820, 0x0001, },
+ ALL_ZERO
+};
+
+static int bu86x_read_bytes(struct sr_serial_dev_inst *serial,
+ uint8_t *data, int space, unsigned int timeout)
+{
+ int rc;
+
+ if (space > BU86X_MAX_BYTES_PER_REQUEST)
+ space = BU86X_MAX_BYTES_PER_REQUEST;
+ rc = ser_hid_hidapi_get_data(serial, 0, data, space, timeout);
+ if (rc == SR_ERR_TIMEOUT)
+ return 0;
+ if (rc < 0)
+ return rc;
+ if (rc == 0)
+ return 0;
+ return rc;
+}
+
+static int bu86x_write_bytes(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, int size)
+{
+ return ser_hid_hidapi_set_data(serial, 0, data, size, 0);
+}
+
+static struct ser_hid_chip_functions chip_bu86x = {
+ .chipname = "bu86x",
+ .chipdesc = "Brymen BU-86X",
+ .vid_pid_items = vid_pid_items_bu86x,
+ .max_bytes_per_request = BU86X_MAX_BYTES_PER_REQUEST,
+ /*
+ * The IR adapter's communication parameters are fixed and need
+ * not get configured. Just silently ignore the caller's spec.
+ */
+ .set_params = std_dummy_set_params,
+ .read_bytes = bu86x_read_bytes,
+ .write_bytes = bu86x_write_bytes,
+};
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_bu86x = &chip_bu86x;
+
+#else
+
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_bu86x = NULL;
+
+#endif
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2017-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 "serial_hid.h"
+#include <string.h>
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-ch9325"
+/** @endcond */
+
+#ifdef HAVE_SERIAL_COMM
+#ifdef HAVE_LIBHIDAPI
+
+/**
+ * @file
+ *
+ * Support serial-over-HID, specifically the WCH CH9325 chip.
+ */
+
+#define CH9325_MAX_BYTES_PER_REQUEST 7
+
+static const struct vid_pid_item vid_pid_items_ch9325[] = {
+ { 0x1a86, 0xe008, }, /* CH9325 */
+ /*
+ * Strictly speaking Hoitek HE2325U is a different chip, but
+ * shares the programming model with WCH CH9325, and works
+ * with the same support code.
+ */
+ { 0x04fa, 0x2490, }, /* HE2325U */
+ ALL_ZERO
+};
+
+static int ch9325_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr)
+{
+ uint8_t report[6];
+ int replen;
+ int rc;
+ GString *text;
+
+ (void)parity;
+ (void)stopbits;
+ (void)flowcontrol;
+ (void)rts;
+ (void)dtr;
+
+ /*
+ * Setup bitrate and frame format. Report layout:
+ * (@-1, length 1, report number)
+ * @0, length 2, bitrate (little endian format)
+ * @2, length 1, unknown (parity? stop bits?)
+ * @3, length 1, unknown (parity? stop bits?)
+ * @4, length 1, data bits (0: 5, 1: 6, etc, 3: 8)
+ */
+ replen = 0;
+ report[replen++] = 0;
+ WL16(&report[replen], baudrate);
+ replen += sizeof(uint16_t);
+ report[replen++] = 0x00;
+ report[replen++] = 0x00;
+ report[replen++] = bits - 5;
+ rc = ser_hid_hidapi_set_report(serial, report, replen);
+ text = sr_hexdump_new(report, replen);
+ sr_dbg("DBG: %s() report %s => rc %d", __func__, text->str, rc);
+ sr_hexdump_free(text);
+ if (rc < 0)
+ return SR_ERR;
+ if (rc != replen)
+ return SR_ERR;
+
+ return SR_OK;
+}
+
+static int ch9325_read_bytes(struct sr_serial_dev_inst *serial,
+ uint8_t *data, int space, unsigned int timeout)
+{
+ uint8_t buffer[1 + CH9325_MAX_BYTES_PER_REQUEST];
+ int rc;
+ int count;
+
+ /*
+ * Check for available input data from the serial port.
+ * Packet layout:
+ * @0, length 1, number of bytes, OR-ed with 0xf0
+ * @1, length N, data bytes (up to 7 bytes)
+ */
+ rc = ser_hid_hidapi_get_data(serial, 2, buffer, sizeof(buffer), timeout);
+ if (rc < 0)
+ return SR_ERR;
+ if (rc == 0)
+ return 0;
+ sr_dbg("DBG: %s() got report len %d, 0x%02x.", __func__, rc, buffer[0]);
+
+ /* Check the length spec, get the byte count. */
+ count = buffer[0];
+ if ((count & 0xf0) != 0xf0)
+ return SR_ERR;
+ count &= 0x0f;
+ sr_dbg("DBG: %s(), got %d UART RX bytes.", __func__, count);
+ if (count > space)
+ return SR_ERR;
+
+ /* Pass received data bytes and their count to the caller. */
+ memcpy(data, &buffer[1], count);
+ return count;
+}
+
+static int ch9325_write_bytes(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, int size)
+{
+ uint8_t buffer[1 + CH9325_MAX_BYTES_PER_REQUEST];
+ int rc;
+
+ sr_dbg("DBG: %s() shall send UART TX data, len %d.", __func__, size);
+
+ if (size < 1)
+ return 0;
+ if (size > CH9325_MAX_BYTES_PER_REQUEST) {
+ size = CH9325_MAX_BYTES_PER_REQUEST;
+ sr_dbg("DBG: %s() capping size to %d.", __func__, size);
+ }
+
+ /*
+ * Packet layout to send serial data to the USB HID chip:
+ * (@-1, length 1, report number)
+ * @0, length 1, number of bytes, OR-ed with 0xf0
+ * @1, length N, data bytes (up to 7 bytes)
+ */
+ buffer[0] = size; /* YES! TX is *without* 0xf0! */
+ memcpy(&buffer[1], data, size);
+ rc = ser_hid_hidapi_set_data(serial, 2, buffer, sizeof(buffer), 0);
+ if (rc < 0)
+ return rc;
+ if (rc == 0)
+ return 0;
+ return size;
+}
+
+static struct ser_hid_chip_functions chip_ch9325 = {
+ .chipname = "ch9325",
+ .chipdesc = "WCH CH9325",
+ .vid_pid_items = vid_pid_items_ch9325,
+ .max_bytes_per_request = CH9325_MAX_BYTES_PER_REQUEST,
+ .set_params = ch9325_set_params,
+ .read_bytes = ch9325_read_bytes,
+ .write_bytes = ch9325_write_bytes,
+};
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_ch9325 = &chip_ch9325;
+
+#else
+
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_ch9325 = NULL;
+
+#endif
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2017 Carl-Fredrik Sundström <audio.cf@gmail.com>
+ * Copyright (C) 2017-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 "serial_hid.h"
+#include <string.h>
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-cp2110"
+/** @endcond */
+
+#ifdef HAVE_SERIAL_COMM
+#ifdef HAVE_LIBHIDAPI
+
+/**
+ * @file
+ *
+ * Support serial-over-HID, specifically the SiLabs CP2110 chip.
+ */
+
+#define CP2110_MAX_BYTES_PER_REQUEST 63
+
+static const struct vid_pid_item vid_pid_items_cp2110[] = {
+ { 0x10c4, 0xea80, },
+ ALL_ZERO
+};
+
+enum cp2110_report_id {
+ CP2110_UART_ENDIS = 0x41,
+ CP2110_UART_STATUS = 0x42,
+ CP2110_FIFO_PURGE = 0x43,
+ CP2110_UART_CONFIG = 0x50,
+};
+
+enum cp2110_uart_enable_param {
+ CP2110_UART_DISABLE = 0,
+ CP2110_UART_ENABLE = 1,
+};
+
+enum cp2110_fifo_purge_flag {
+ CP2110_FIFO_PURGE_TX = (1 << 0),
+ CP2110_FIFO_PURGE_RX = (1 << 1),
+};
+
+enum cp2110_uart_config_bitrate {
+ CP2110_BAUDRATE_MIN = 300,
+ CP2110_BAUDRATE_MAX = 1000000,
+};
+
+enum cp2110_uart_config_databits {
+ CP2110_DATABITS_MIN = 5,
+ CP2110_DATABITS_MAX = 8,
+};
+
+enum cp2110_uart_config_parity {
+ CP2110_PARITY_NONE = 0,
+ CP2110_PARITY_EVEN = 1,
+ CP2110_PARITY_ODD = 2,
+ CP2110_PARITY_MARK = 3,
+ CP2110_PARITY_SPACE = 4,
+};
+
+enum cp2110_uart_config_stopbits {
+ CP2110_STOPBITS_SHORT = 0,
+ CP2110_STOPBITS_LONG = 1,
+};
+
+/* Hardware flow control on CP2110 is RTS/CTS only. */
+enum cp2110_uart_config_flowctrl {
+ CP2110_FLOWCTRL_NONE = 0,
+ CP2110_FLOWCTRL_HARD = 1,
+};
+
+static int cp2110_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr)
+{
+ uint8_t report[9];
+ int replen;
+ int rc;
+
+ /* Map serial API specs to CP2110 register values. Check ranges. */
+ if (baudrate < CP2110_BAUDRATE_MIN || baudrate > CP2110_BAUDRATE_MAX) {
+ sr_err("CP2110: baudrate %d out of range", baudrate);
+ return SR_ERR_ARG;
+ }
+ if (bits < CP2110_DATABITS_MIN || bits > CP2110_DATABITS_MAX) {
+ sr_err("CP2110: %d databits out of range", bits);
+ return SR_ERR_ARG;
+ }
+ bits -= CP2110_DATABITS_MIN;
+ switch (parity) {
+ case SP_PARITY_NONE:
+ parity = CP2110_PARITY_NONE;
+ break;
+ case SP_PARITY_ODD:
+ parity = CP2110_PARITY_ODD;
+ break;
+ case SP_PARITY_EVEN:
+ parity = CP2110_PARITY_EVEN;
+ break;
+ case SP_PARITY_MARK:
+ parity = CP2110_PARITY_MARK;
+ break;
+ case SP_PARITY_SPACE:
+ parity = CP2110_PARITY_SPACE;
+ break;
+ default:
+ sr_err("CP2110: unknown parity spec %d", parity);
+ return SR_ERR_ARG;
+ }
+ switch (stopbits) {
+ case 1:
+ stopbits = CP2110_STOPBITS_SHORT;
+ break;
+ case 2:
+ stopbits = CP2110_STOPBITS_LONG;
+ break;
+ default:
+ sr_err("CP2110: unknown stop bits spec %d", stopbits);
+ return SR_ERR_ARG;
+ }
+ switch (flowcontrol) {
+ case SP_FLOWCONTROL_NONE:
+ flowcontrol = CP2110_FLOWCTRL_NONE;
+ break;
+ case SP_FLOWCONTROL_XONXOFF:
+ sr_err("CP2110: unsupported XON/XOFF flow control spec");
+ return SR_ERR_ARG;
+ case SP_FLOWCONTROL_RTSCTS:
+ flowcontrol = CP2110_FLOWCTRL_HARD;
+ break;
+ default:
+ sr_err("CP2110: unknown flow control spec %d", flowcontrol);
+ return SR_ERR_ARG;
+ }
+
+ /*
+ * Enable the UART. Report layout:
+ * @0, length 1, enabled state (0: disable, 1: enable)
+ */
+ replen = 0;
+ report[replen++] = CP2110_UART_ENDIS;
+ report[replen++] = CP2110_UART_ENABLE;
+ rc = ser_hid_hidapi_set_report(serial, report, replen);
+ if (rc < 0)
+ return SR_ERR;
+ if (rc != replen)
+ return SR_ERR;
+
+ /*
+ * Setup bitrate and frame format. Report layout:
+ * (@-1, length 1, report number)
+ * @0, length 4, bitrate (big endian format)
+ * @4, length 1, parity
+ * @5, length 1, flow control
+ * @6, length 1, data bits (0: 5, 1: 6, 2: 7, 3: 8)
+ * @7, length 1, stop bits
+ */
+ replen = 0;
+ report[replen++] = CP2110_UART_CONFIG;
+ WB32(&report[replen], baudrate);
+ replen += sizeof(uint32_t);
+ report[replen++] = parity;
+ report[replen++] = flowcontrol;
+ report[replen++] = bits;
+ report[replen++] = stopbits;
+ rc = ser_hid_hidapi_set_report(serial, report, replen);
+ if (rc < 0)
+ return SR_ERR;
+ if (rc != replen)
+ return SR_ERR;
+
+ /*
+ * Currently not implemented: Control RTS and DTR state.
+ * TODO are these controlled via GPIO requests?
+ * GPIO.1 == RTS, can't find DTR in AN433 table 4.3
+ */
+ (void)rts;
+ (void)dtr;
+
+ return SR_OK;
+}
+
+static int cp2110_read_bytes(struct sr_serial_dev_inst *serial,
+ uint8_t *data, int space, unsigned int timeout)
+{
+ uint8_t buffer[1 + CP2110_MAX_BYTES_PER_REQUEST];
+ int rc;
+ int count;
+
+ (void)timeout;
+
+ /*
+ * Check for available input data from the serial port.
+ * Packet layout:
+ * @0, length 1, number of bytes, range 0-63
+ * @1, length N, data bytes
+ */
+ memset(buffer, 0, sizeof(buffer));
+ rc = ser_hid_hidapi_get_data(serial, 0, buffer, sizeof(buffer), timeout);
+ if (rc == SR_ERR_TIMEOUT)
+ return 0;
+ if (rc < 0)
+ return SR_ERR;
+ if (rc == 0)
+ return 0;
+ sr_dbg("DBG: %s() got report len %d, 0x%02x.", __func__, rc, buffer[0]);
+
+ /* Check the length spec, get the byte count. */
+ count = buffer[0];
+ if (!count)
+ return 0;
+ if (count > CP2110_MAX_BYTES_PER_REQUEST)
+ return SR_ERR;
+ sr_dbg("DBG: %s(), got %d UART RX bytes.", __func__, count);
+ if (count > space)
+ return SR_ERR;
+
+ /* Pass received data bytes and their count to the caller. */
+ memcpy(data, &buffer[1], count);
+ return count;
+}
+
+static int cp2110_write_bytes(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, int size)
+{
+ uint8_t buffer[1 + CP2110_MAX_BYTES_PER_REQUEST];
+ int rc;
+
+ sr_dbg("DBG: %s() shall send UART TX data, len %d.", __func__, size);
+
+ if (size < 1)
+ return 0;
+ if (size > CP2110_MAX_BYTES_PER_REQUEST) {
+ size = CP2110_MAX_BYTES_PER_REQUEST;
+ sr_dbg("DBG: %s() capping size to %d.", __func__, size);
+ }
+
+ /*
+ * Packet layout to send serial data to the USB HID chip:
+ * @0, length 1, number of bytes, range 0-63
+ * @1, length N, data bytes
+ */
+ buffer[0] = size;
+ memcpy(&buffer[1], data, size);
+ rc = ser_hid_hidapi_set_data(serial, 0, buffer, sizeof(buffer), 0);
+ if (rc < 0)
+ return rc;
+ if (rc == 0)
+ return 0;
+ return size;
+}
+
+static int cp2110_flush(struct sr_serial_dev_inst *serial)
+{
+ uint8_t buffer[2];
+ int rc;
+
+ sr_dbg("DBG: %s() discarding RX and TX FIFO data.", __func__);
+
+ buffer[0] = CP2110_FIFO_PURGE;
+ buffer[1] = CP2110_FIFO_PURGE_TX | CP2110_FIFO_PURGE_RX;
+ rc = ser_hid_hidapi_set_data(serial, 0, buffer, sizeof(buffer), 0);
+ if (rc != 0)
+ return SR_ERR;
+ return SR_OK;
+}
+
+static int cp2110_drain(struct sr_serial_dev_inst *serial)
+{
+ uint8_t buffer[7];
+ int rc;
+ uint16_t tx_fill, rx_fill;
+
+ sr_dbg("DBG: %s() waiting for TX data to drain.", __func__);
+
+ /*
+ * Keep retrieving the UART status until the FIFO is found empty,
+ * or an error occured.
+ * Packet layout:
+ * @0, length 1, report ID
+ * @1, length 2, number of bytes in the TX FIFO (up to 480)
+ * @3, length 2, number of bytes in the RX FIFO (up to 480)
+ * @5, length 1, error status (parity and overrun error flags)
+ * @6, length 1, line break status
+ */
+ rx_fill = ~0;
+ do {
+ memset(buffer, 0, sizeof(buffer));
+ buffer[0] = CP2110_UART_STATUS;
+ rc = ser_hid_hidapi_get_data(serial, 0, buffer, sizeof(buffer), 0);
+ if (rc != sizeof(buffer)) {
+ rc = SR_ERR_DATA;
+ break;
+ }
+ if (buffer[0] != CP2110_UART_STATUS) {
+ rc = SR_ERR_DATA;
+ break;
+ }
+ rx_fill = RB16(&buffer[1]);
+ tx_fill = RB16(&buffer[3]);
+ if (!tx_fill) {
+ rc = SR_OK;
+ break;
+ }
+ g_usleep(2000);
+ } while (1);
+
+ sr_dbg("DBG: %s() TX drained, rc %d, RX fill %u, returning.",
+ __func__, rc, (unsigned int)rx_fill);
+ return rc;
+}
+
+static struct ser_hid_chip_functions chip_cp2110 = {
+ .chipname = "cp2110",
+ .chipdesc = "SiLabs CP2110",
+ .vid_pid_items = vid_pid_items_cp2110,
+ .max_bytes_per_request = CP2110_MAX_BYTES_PER_REQUEST,
+ .set_params = cp2110_set_params,
+ .read_bytes = cp2110_read_bytes,
+ .write_bytes = cp2110_write_bytes,
+ .flush = cp2110_flush,
+ .drain = cp2110_drain,
+};
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_cp2110 = &chip_cp2110;
+
+#else
+
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_cp2110 = NULL;
+
+#endif
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * 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/>.
+ */
+
+/*
+ * This implements serial transport primitives for Victor DMM cables,
+ * which forward normal DMM chips' protocols, but scramble the data in
+ * the process of forwarding. Just undoing the cable's scrambling at
+ * the serial communication level allows full re-use of existing DMM
+ * drivers, instead of creating Victor DMM specific support code.
+ *
+ * The cable's scrambling is somewhat complex:
+ * - The order of bits within the bytes gets reversed.
+ * - The order of bytes within the packet gets shuffled (randomly).
+ * - The byte values randomly get mapped to other values by adding a
+ * sequence of magic values to packet's byte values.
+ * None of this adds any value to the DMM chip vendor's protocol. It's
+ * mere obfuscation and extra complexity for the receiving application.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "serial_hid.h"
+#include <string.h>
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-victor"
+/** @endcond */
+
+#ifdef HAVE_SERIAL_COMM
+#ifdef HAVE_LIBHIDAPI
+
+/**
+ * @file
+ *
+ * Support serial-over-HID, specifically the Victor 70/86 DMM cables.
+ */
+
+#define VICTOR_DMM_PACKET_LENGTH 14
+
+static const struct vid_pid_item vid_pid_items_victor[] = {
+ { 0x1244, 0xd237, },
+ ALL_ZERO
+};
+
+/* Reverse bits within a byte. */
+static uint8_t bit_reverse(uint8_t b)
+{
+ static const uint8_t rev_nibble[] = {
+ 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e,
+ 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, 0x0f,
+ };
+
+ return (rev_nibble[(b >> 4) & 0xf]) |
+ (rev_nibble[b & 0xf] << 4);
+}
+
+/*
+ * The cable receives data by means of HID reports (simple data stream,
+ * HID report encapsulation was already trimmed). Assume that received
+ * data "is aligned", cope with zero or one 14-byte packets here, but
+ * don't try to even bother with odd-length reception units. Also drop
+ * the "all-zero" packets here which victor_dmm_receive_data() used to
+ * eliminate at the device driver level in the past.
+ */
+static int victor_unobfuscate(uint8_t *rx_buf, size_t rx_len,
+ uint8_t *ret_buf)
+{
+ static const uint8_t obfuscation[VICTOR_DMM_PACKET_LENGTH] = {
+ 'j', 'o', 'd', 'e', 'n', 'x', 'u', 'n', 'i', 'c', 'k', 'x', 'i', 'a',
+ };
+ static const uint8_t shuffle[VICTOR_DMM_PACKET_LENGTH] = {
+ 6, 13, 5, 11, 2, 7, 9, 8, 3, 10, 12, 0, 4, 1
+ };
+
+ GString *txt;
+ int is_zero;
+ size_t idx, to_idx;
+
+ if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+ txt = sr_hexdump_new(rx_buf, rx_len);
+ sr_spew("Received %zu bytes: %s.", rx_len, txt->str);
+ sr_hexdump_free(txt);
+ }
+
+ /* Pass unexpected data in verbatim form. */
+ if (rx_len != VICTOR_DMM_PACKET_LENGTH) {
+ memcpy(ret_buf, rx_buf, rx_len);
+ return rx_len;
+ }
+
+ /* Check for and discard all-zero packets. */
+ is_zero = 1;
+ for (idx = 0; is_zero && idx < VICTOR_DMM_PACKET_LENGTH; idx++) {
+ if (rx_buf[idx])
+ is_zero = 0;
+ }
+ if (is_zero) {
+ sr_dbg("Received all zeroes packet, discarding.");
+ return 0;
+ }
+
+ /*
+ * Unobfuscate data bytes by subtracting a magic pattern, shuffle
+ * the bits and bytes into the DMM chip's original order.
+ */
+ for (idx = 0; idx < VICTOR_DMM_PACKET_LENGTH; idx++) {
+ to_idx = VICTOR_DMM_PACKET_LENGTH - 1 - shuffle[idx];
+ ret_buf[to_idx] = bit_reverse(rx_buf[idx] - obfuscation[idx]);
+ }
+
+ if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+ txt = sr_hexdump_new(ret_buf, idx);
+ sr_spew("Deobfuscated: %s.", txt->str);
+ sr_hexdump_free(txt);
+ }
+
+ return rx_len;
+}
+
+/*
+ * Read into a local buffer, and unobfuscate into the caller's buffer.
+ * Always receive full DMM packets.
+ */
+static int victor_read_bytes(struct sr_serial_dev_inst *serial,
+ uint8_t *data, int space, unsigned int timeout)
+{
+ uint8_t buf[VICTOR_DMM_PACKET_LENGTH];
+ int rc;
+
+ if (space != sizeof(buf))
+ space = sizeof(buf);
+ rc = ser_hid_hidapi_get_data(serial, 0, buf, space, timeout);
+ if (rc == SR_ERR_TIMEOUT)
+ return 0;
+ if (rc < 0)
+ return rc;
+ if (rc == 0)
+ return 0;
+
+ return victor_unobfuscate(buf, rc, data);
+}
+
+/* Victor DMM cables are read-only. Just pretend successful transmission. */
+static int victor_write_bytes(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, int size)
+{
+ (void)serial;
+ (void)data;
+
+ return size;
+}
+
+static struct ser_hid_chip_functions chip_victor = {
+ .chipname = "victor",
+ .chipdesc = "Victor DMM scrambler",
+ .vid_pid_items = vid_pid_items_victor,
+ .max_bytes_per_request = VICTOR_DMM_PACKET_LENGTH,
+ /*
+ * The USB HID connection has no concept of UART bitrate or
+ * frame format. Silently ignore the parameters.
+ */
+ .set_params = std_dummy_set_params,
+ .read_bytes = victor_read_bytes,
+ .write_bytes = victor_write_bytes,
+};
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_victor = &chip_victor;
+
+#else
+
+SR_PRIV struct ser_hid_chip_functions *ser_hid_chip_funcs_victor = NULL;
+
+#endif
+#endif
--- /dev/null
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2010-2012 Bert Vermeulen <bert@biot.com>
+ * Copyright (C) 2010-2012 Uwe Hermann <uwe@hermann-uwe.de>
+ * Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com>
+ * Copyright (C) 2014 Uffe Jakobsen <uffe@uffe.org>
+ *
+ * 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"
+#ifdef HAVE_LIBSERIALPORT
+#include <libserialport.h>
+#endif
+#ifdef G_OS_WIN32
+#include <windows.h> /* for HANDLE */
+#endif
+
+/** @cond PRIVATE */
+#define LOG_PREFIX "serial-libsp"
+/** @endcond */
+
+/**
+ * @file
+ *
+ * Serial port handling, wraps the external libserialport dependency.
+ */
+
+#ifdef HAVE_LIBSERIALPORT
+
+/**
+ * @defgroup grp_serial_libsp Serial port handling, libserialport group
+ *
+ * Serial port handling functions, based on libserialport.
+ *
+ * @{
+ */
+
+static int sr_ser_libsp_open(struct sr_serial_dev_inst *serial, int flags)
+{
+ int ret;
+ char *error;
+ int sp_flags;
+
+ sp_get_port_by_name(serial->port, &serial->sp_data);
+
+ sp_flags = 0;
+ if (flags & SERIAL_RDWR)
+ sp_flags = (SP_MODE_READ | SP_MODE_WRITE);
+ else if (flags & SERIAL_RDONLY)
+ sp_flags = SP_MODE_READ;
+
+ ret = sp_open(serial->sp_data, sp_flags);
+
+ switch (ret) {
+ case SP_ERR_ARG:
+ sr_err("Attempt to open serial port with invalid parameters.");
+ return SR_ERR_ARG;
+ case SP_ERR_FAIL:
+ error = sp_last_error_message();
+ sr_err("Error opening port (%d): %s.",
+ sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+static int sr_ser_libsp_close(struct sr_serial_dev_inst *serial)
+{
+ int ret;
+ char *error;
+
+ if (!serial->sp_data) {
+ sr_dbg("Cannot close unopened serial port %s.", serial->port);
+ return SR_ERR;
+ }
+
+ ret = sp_close(serial->sp_data);
+
+ switch (ret) {
+ case SP_ERR_ARG:
+ sr_err("Attempt to close an invalid serial port.");
+ return SR_ERR_ARG;
+ case SP_ERR_FAIL:
+ error = sp_last_error_message();
+ sr_err("Error closing port (%d): %s.",
+ sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ sp_free_port(serial->sp_data);
+ serial->sp_data = NULL;
+
+ return SR_OK;
+}
+
+static int sr_ser_libsp_flush(struct sr_serial_dev_inst *serial)
+{
+ int ret;
+ char *error;
+
+ if (!serial->sp_data) {
+ sr_dbg("Cannot flush unopened serial port %s.", serial->port);
+ return SR_ERR;
+ }
+
+ ret = sp_flush(serial->sp_data, SP_BUF_BOTH);
+
+ switch (ret) {
+ case SP_ERR_ARG:
+ sr_err("Attempt to flush an invalid serial port.");
+ return SR_ERR_ARG;
+ case SP_ERR_FAIL:
+ error = sp_last_error_message();
+ sr_err("Error flushing port (%d): %s.",
+ sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+static int sr_ser_libsp_drain(struct sr_serial_dev_inst *serial)
+{
+ int ret;
+ char *error;
+
+ if (!serial->sp_data) {
+ sr_dbg("Cannot drain unopened serial port %s.", serial->port);
+ return SR_ERR;
+ }
+
+ ret = sp_drain(serial->sp_data);
+
+ if (ret == SP_ERR_FAIL) {
+ error = sp_last_error_message();
+ sr_err("Error draining port (%d): %s.",
+ sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+static int sr_ser_libsp_write(struct sr_serial_dev_inst *serial,
+ const void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ ssize_t ret;
+ char *error;
+
+ if (!serial->sp_data) {
+ sr_dbg("Cannot use unopened serial port %s.", serial->port);
+ return SR_ERR;
+ }
+
+ if (nonblocking)
+ ret = sp_nonblocking_write(serial->sp_data, buf, count);
+ else
+ ret = sp_blocking_write(serial->sp_data, buf, count, timeout_ms);
+
+ switch (ret) {
+ case SP_ERR_ARG:
+ sr_err("Attempted serial port write with invalid arguments.");
+ return SR_ERR_ARG;
+ case SP_ERR_FAIL:
+ error = sp_last_error_message();
+ sr_err("Write error (%d): %s.", sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ return ret;
+}
+
+static int sr_ser_libsp_read(struct sr_serial_dev_inst *serial,
+ void *buf, size_t count,
+ int nonblocking, unsigned int timeout_ms)
+{
+ ssize_t ret;
+ char *error;
+
+ if (!serial->sp_data) {
+ sr_dbg("Cannot use unopened serial port %s.", serial->port);
+ return SR_ERR;
+ }
+
+ if (nonblocking)
+ ret = sp_nonblocking_read(serial->sp_data, buf, count);
+ else
+ ret = sp_blocking_read(serial->sp_data, buf, count, timeout_ms);
+
+ switch (ret) {
+ case SP_ERR_ARG:
+ sr_err("Attempted serial port read with invalid arguments.");
+ return SR_ERR_ARG;
+ case SP_ERR_FAIL:
+ error = sp_last_error_message();
+ sr_err("Read error (%d): %s.", sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ return ret;
+}
+
+static int sr_ser_libsp_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr)
+{
+ int ret;
+ char *error;
+ struct sp_port_config *config;
+ int cts, xonoff;
+
+ if (!serial->sp_data) {
+ sr_dbg("Cannot configure unopened serial port %s.", serial->port);
+ return SR_ERR;
+ }
+
+ sp_new_config(&config);
+ sp_set_config_baudrate(config, baudrate);
+ sp_set_config_bits(config, bits);
+ switch (parity) {
+ case 0:
+ sp_set_config_parity(config, SP_PARITY_NONE);
+ break;
+ case 1:
+ sp_set_config_parity(config, SP_PARITY_EVEN);
+ break;
+ case 2:
+ sp_set_config_parity(config, SP_PARITY_ODD);
+ break;
+ default:
+ return SR_ERR_ARG;
+ }
+ sp_set_config_stopbits(config, stopbits);
+ rts = flowcontrol == 1 ? SP_RTS_FLOW_CONTROL : rts;
+ sp_set_config_rts(config, rts);
+ cts = flowcontrol == 1 ? SP_CTS_FLOW_CONTROL : SP_CTS_IGNORE;
+ sp_set_config_cts(config, cts);
+ sp_set_config_dtr(config, dtr);
+ sp_set_config_dsr(config, SP_DSR_IGNORE);
+ xonoff = flowcontrol == 2 ? SP_XONXOFF_INOUT : SP_XONXOFF_DISABLED;
+ sp_set_config_xon_xoff(config, xonoff);
+
+ ret = sp_set_config(serial->sp_data, config);
+ sp_free_config(config);
+
+ switch (ret) {
+ case SP_ERR_ARG:
+ sr_err("Invalid arguments for setting serial port parameters.");
+ return SR_ERR_ARG;
+ case SP_ERR_FAIL:
+ error = sp_last_error_message();
+ sr_err("Error setting serial port parameters (%d): %s.",
+ sp_last_error_code(), error);
+ sp_free_error_message(error);
+ return SR_ERR;
+ }
+
+ return SR_OK;
+}
+
+/** @cond PRIVATE */
+#ifdef G_OS_WIN32
+typedef HANDLE event_handle;
+#else
+typedef int event_handle;
+#endif
+/** @endcond */
+
+static int sr_ser_libsp_source_add_int(struct sr_serial_dev_inst *serial,
+ int events,
+ void **keyptr, gintptr *fdptr, unsigned int *pollevtptr)
+{
+ struct sp_event_set *event_set;
+ gintptr poll_fd;
+ unsigned int poll_events;
+ enum sp_event mask;
+
+ if ((events & (G_IO_IN | G_IO_ERR)) && (events & G_IO_OUT)) {
+ sr_err("Cannot poll input/error and output simultaneously.");
+ return SR_ERR_ARG;
+ }
+ if (!serial->sp_data) {
+ sr_err("Invalid serial port.");
+ return SR_ERR_ARG;
+ }
+
+ if (sp_new_event_set(&event_set) != SP_OK)
+ return SR_ERR;
+
+ mask = 0;
+ if (events & G_IO_IN)
+ mask |= SP_EVENT_RX_READY;
+ if (events & G_IO_OUT)
+ mask |= SP_EVENT_TX_READY;
+ if (events & G_IO_ERR)
+ mask |= SP_EVENT_ERROR;
+
+ if (sp_add_port_events(event_set, serial->sp_data, mask) != SP_OK) {
+ sp_free_event_set(event_set);
+ return SR_ERR;
+ }
+ if (event_set->count != 1) {
+ sr_err("Unexpected number (%u) of event handles to poll.",
+ event_set->count);
+ sp_free_event_set(event_set);
+ return SR_ERR;
+ }
+
+ poll_fd = (gintptr) ((event_handle *)event_set->handles)[0];
+ mask = event_set->masks[0];
+
+ sp_free_event_set(event_set);
+
+ poll_events = 0;
+ if (mask & SP_EVENT_RX_READY)
+ poll_events |= G_IO_IN;
+ if (mask & SP_EVENT_TX_READY)
+ poll_events |= G_IO_OUT;
+ if (mask & SP_EVENT_ERROR)
+ poll_events |= G_IO_ERR;
+
+ /*
+ * Using serial->sp_data as the key for the event source is not quite
+ * proper, as it makes it impossible to create another event source
+ * for the same serial port. However, these fixed keys will soon be
+ * removed from the API anyway, so this is OK for now.
+ */
+ *keyptr = serial->sp_data;
+ *fdptr = poll_fd;
+ *pollevtptr = poll_events;
+
+ return SR_OK;
+}
+
+static int sr_ser_libsp_source_add(struct sr_session *session,
+ struct sr_serial_dev_inst *serial, int events, int timeout,
+ sr_receive_data_callback cb, void *cb_data)
+{
+ int ret;
+ void *key;
+ gintptr poll_fd;
+ unsigned int poll_events;
+
+ ret = sr_ser_libsp_source_add_int(serial, events,
+ &key, &poll_fd, &poll_events);
+ if (ret != SR_OK)
+ return ret;
+
+ return sr_session_fd_source_add(session,
+ key, poll_fd, poll_events,
+ timeout, cb, cb_data);
+}
+
+static int sr_ser_libsp_source_remove(struct sr_session *session,
+ struct sr_serial_dev_inst *serial)
+{
+ void *key;
+
+ key = serial->sp_data;
+ return sr_session_source_remove_internal(session, key);
+}
+
+static GSList *sr_ser_libsp_list(GSList *list, sr_ser_list_append_t append)
+{
+ struct sp_port **ports;
+ size_t i;
+ const char *name;
+ const char *desc;
+
+ if (sp_list_ports(&ports) != SP_OK)
+ return list;
+
+ for (i = 0; ports[i]; i++) {
+ name = sp_get_port_name(ports[i]);
+ desc = sp_get_port_description(ports[i]);
+ list = append(list, name, desc);
+ }
+
+ sp_free_port_list(ports);
+
+ return list;
+}
+
+static GSList *sr_ser_libsp_find_usb(GSList *list, sr_ser_find_append_t append,
+ uint16_t vendor_id, uint16_t product_id)
+{
+ struct sp_port **ports;
+ int i, vid, pid;
+
+ if (sp_list_ports(&ports) != SP_OK)
+ return list;
+
+ for (i = 0; ports[i]; i++) {
+ if (sp_get_port_transport(ports[i]) != SP_TRANSPORT_USB)
+ continue;
+ if (sp_get_port_usb_vid_pid(ports[i], &vid, &pid) != SP_OK)
+ continue;
+ if (vendor_id && vid != vendor_id)
+ continue;
+ if (product_id && pid != product_id)
+ continue;
+ list = append(list, sp_get_port_name(ports[i]));
+ }
+
+ sp_free_port_list(ports);
+
+ return list;
+}
+
+static int sr_ser_libsp_get_frame_format(struct sr_serial_dev_inst *serial,
+ int *baud, int *bits)
+{
+ struct sp_port_config *config;
+ int ret, tmp;
+ enum sp_parity parity;
+
+ if (sp_new_config(&config) < 0)
+ return SR_ERR_MALLOC;
+ *baud = *bits = 0;
+ ret = SR_OK;
+ do {
+ if (sp_get_config(serial->sp_data, config) < 0) {
+ ret = SR_ERR_NA;
+ break;
+ }
+
+ if (sp_get_config_baudrate(config, &tmp) < 0) {
+ ret = SR_ERR_NA;
+ break;
+ }
+ *baud = tmp;
+
+ *bits += 1; /* Start bit. */
+ if (sp_get_config_bits(config, &tmp) < 0) {
+ ret = SR_ERR_NA;
+ break;
+ }
+ *bits += tmp;
+ if (sp_get_config_parity(config, &parity) < 0) {
+ ret = SR_ERR_NA;
+ break;
+ }
+ *bits += (parity != SP_PARITY_NONE) ? 1 : 0;
+ if (sp_get_config_stopbits(config, &tmp) < 0) {
+ ret = SR_ERR_NA;
+ break;
+ }
+ *bits += tmp;
+ } while (FALSE);
+ sp_free_config(config);
+
+ return ret;
+}
+
+static size_t sr_ser_libsp_get_rx_avail(struct sr_serial_dev_inst *serial)
+{
+ int rc;
+
+ if (!serial)
+ return 0;
+
+ rc = sp_input_waiting(serial->sp_data);
+ if (rc < 0)
+ return 0;
+
+ return rc;
+}
+
+static struct ser_lib_functions serlib_sp = {
+ .open = sr_ser_libsp_open,
+ .close = sr_ser_libsp_close,
+ .flush = sr_ser_libsp_flush,
+ .drain = sr_ser_libsp_drain,
+ .write = sr_ser_libsp_write,
+ .read = sr_ser_libsp_read,
+ .set_params = sr_ser_libsp_set_params,
+ .setup_source_add = sr_ser_libsp_source_add,
+ .setup_source_remove = sr_ser_libsp_source_remove,
+ .list = sr_ser_libsp_list,
+ .find_usb = sr_ser_libsp_find_usb,
+ .get_frame_format = sr_ser_libsp_get_frame_format,
+ .get_rx_avail = sr_ser_libsp_get_rx_avail,
+};
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_libsp = &serlib_sp;
+
+#else
+
+SR_PRIV struct ser_lib_functions *ser_lib_funcs_libsp = NULL;
+
+#endif
}
}
+/**
+ * Helper to send a meta datafeed package (SR_DF_META) to the session bus.
+ *
+ * @param sdi The device instance to send the package from. Must not be NULL.
+ * @param key The config key to send to the session bus.
+ * @param var The value to send to the session bus.
+ *
+ * @retval SR_OK Success.
+ * @retval SR_ERR_ARG Invalid argument.
+ *
+ * @private
+ */
+SR_PRIV int sr_session_send_meta(const struct sr_dev_inst *sdi,
+ uint32_t key, GVariant *var)
+{
+ struct sr_config *cfg;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_meta meta;
+ int ret;
+
+ cfg = sr_config_new(key, var);
+
+ memset(&meta, 0, sizeof(meta));
+
+ packet.type = SR_DF_META;
+ packet.payload = &meta;
+
+ meta.config = g_slist_append(NULL, cfg);
+
+ ret = sr_session_send(sdi, &packet);
+ g_slist_free(meta.config);
+ sr_config_free(cfg);
+
+ return ret;
+}
+
/**
* Send a packet to whatever is listening on the datafeed bus.
*
return SR_OK;
}
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
/**
* Standard serial driver dev_open() callback API helper.
return ret;
}
- if ((ret = sr_dev_close(sdi)) < 0) {
- sr_err("%s: Failed to close device: %d.", prefix, ret);
- return ret;
- }
-
return std_session_send_df_end(sdi);
}
driver->dev_close(sdi);
if (sdi->conn) {
-#ifdef HAVE_LIBSERIALPORT
+#ifdef HAVE_SERIAL_COMM
if (sdi->inst_type == SR_INST_SERIAL)
sr_serial_dev_inst_free(sdi->conn);
#endif
GVariant *rational[2];
GVariantBuilder gvb;
- g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_init(&gvb, G_VARIANT_TYPE_TUPLE);
for (i = 0; i < n; i++) {
rational[0] = g_variant_new_uint64(a[i][0]);
GVariant *rational[2];
GVariantBuilder gvb;
- g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_init(&gvb, G_VARIANT_TYPE_TUPLE);
for (i = 0; i < n; i++) {
rational[0] = g_variant_new_uint64(r[i].p);
return -1;
}
+
+SR_PRIV int std_dummy_set_params(struct sr_serial_dev_inst *serial,
+ int baudrate, int bits, int parity, int stopbits,
+ int flowcontrol, int rts, int dtr)
+{
+ (void)serial;
+ (void)baudrate;
+ (void)bits;
+ (void)parity;
+ (void)stopbits;
+ (void)flowcontrol;
+ (void)rts;
+ (void)dtr;
+
+ return SR_OK;
+}
+
SR_PRIV void sr_sw_limits_init(struct sr_sw_limits *limits)
{
limits->limit_samples = 0;
+ limits->limit_frames = 0;
limits->limit_msec = 0;
}
case SR_CONF_LIMIT_SAMPLES:
*data = g_variant_new_uint64(limits->limit_samples);
break;
+ case SR_CONF_LIMIT_FRAMES:
+ *data = g_variant_new_uint64(limits->limit_frames);
+ break;
case SR_CONF_LIMIT_MSEC:
*data = g_variant_new_uint64(limits->limit_msec / 1000);
break;
case SR_CONF_LIMIT_SAMPLES:
limits->limit_samples = g_variant_get_uint64(data);
break;
+ case SR_CONF_LIMIT_FRAMES:
+ limits->limit_frames = g_variant_get_uint64(data);
+ break;
case SR_CONF_LIMIT_MSEC:
limits->limit_msec = g_variant_get_uint64(data) * 1000;
break;
SR_PRIV void sr_sw_limits_acquisition_start(struct sr_sw_limits *limits)
{
limits->samples_read = 0;
+ limits->frames_read = 0;
limits->start_time = g_get_monotonic_time();
}
}
}
+ if (limits->limit_frames) {
+ if (limits->frames_read >= limits->limit_frames) {
+ sr_dbg("Requested number of frames (%" PRIu64
+ ") reached.", limits->limit_frames);
+ return TRUE;
+ }
+ }
+
if (limits->limit_msec) {
guint64 now;
now = g_get_monotonic_time();
}
/**
- * Update the amount samples that have been read
+ * Update the amount of samples that have been read
*
* Update the amount of samples that have been read in the current data
* acquisition run. For each invocation @p samples_read will be accumulated and
{
limits->samples_read += samples_read;
}
+
+/**
+ * Update the amount of frames that have been read
+ *
+ * Update the amount of frames that have been read in the current data
+ * acquisition run. For each invocation @p frames_read will be accumulated and
+ * once the configured frame limit has been reached sr_sw_limits_check() will
+ * return TRUE.
+ *
+ * @param limits software limits instance
+ * @param frames_read the amount of frames that have been read
+ */
+SR_PRIV void sr_sw_limits_update_frames_read(struct sr_sw_limits *limits,
+ uint64_t frames_read)
+{
+ limits->frames_read += frames_read;
+}