From: Gerhard Sittig Date: Thu, 5 Oct 2023 15:21:07 +0000 (+0200) Subject: juntek-jds6600: implement device support, tested with Joy-IT JDS6600 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=18baeeed7bb99f5470f04caa60e1b624207728b7;p=libsigrok.git juntek-jds6600: implement device support, tested with Joy-IT JDS6600 Implement support for the essential signal generator part of the device, tested with a 60MHz Joy-IT JDS6600 model: Two channels, enable/disable, waveform, frequency, amplitude, offset, duty cycle selection, phase between channels. This version does not support external signals and frequency measurement nor counting. Upload/download of arbitrary waveforms could work from the serial communication's perspective, but sigrok lacks infrastructure/API for waveform exchange or communication of blobs. Tested with sigrok-cli and getters/setters. Quickly tested with SmuView. --- diff --git a/configure.ac b/configure.ac index 60e97a65..90a3c1c7 100644 --- a/configure.ac +++ b/configure.ac @@ -342,7 +342,7 @@ SR_DRIVER([Ikalogic Scanalogic-2], [ikalogic-scanalogic2], [libusb]) SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi]) SR_DRIVER([IPDBG LA], [ipdbg-la]) SR_DRIVER([ITECH IT8500], [itech-it8500], [serial_comm]) -SR_DRIVER([JUNTEK JDS6600], [juntek-jds6600]) +SR_DRIVER([JUNTEK JDS6600], [juntek-jds6600], [serial_comm]) SR_DRIVER([Kecheng KC-330B], [kecheng-kc-330b], [libusb]) SR_DRIVER([KERN scale], [kern-scale], [serial_comm]) SR_DRIVER([Kingst LA2016], [kingst-la2016], [libusb]) diff --git a/src/hardware/juntek-jds6600/api.c b/src/hardware/juntek-jds6600/api.c index 54cdfba4..ecc879a4 100644 --- a/src/hardware/juntek-jds6600/api.c +++ b/src/hardware/juntek-jds6600/api.c @@ -17,120 +17,383 @@ * along with this program. If not, see . */ -#include +#include "config.h" + #include "protocol.h" -static struct sr_dev_driver juntek_jds6600_driver_info; +#define DFLT_SERIALCOMM "115200/8n1" -static GSList *scan(struct sr_dev_driver *di, GSList *options) -{ - struct drv_context *drvc; - GSList *devices; +#define VENDOR_TEXT "Juntek" +#define MODEL_TEXT "JDS6600" - (void)options; +static const uint32_t scanopts[] = { + SR_CONF_CONN, +}; - devices = NULL; - drvc = di->context; - drvc->instances = NULL; +static const uint32_t drvopts[] = { + SR_CONF_SIGNAL_GENERATOR, +}; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ +static const uint32_t devopts[] = { + SR_CONF_CONN | SR_CONF_GET, + SR_CONF_ENABLED | SR_CONF_SET, + SR_CONF_PHASE | SR_CONF_GET | SR_CONF_SET, +}; - return devices; -} +static const uint32_t devopts_cg[] = { + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_PATTERN_MODE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_OUTPUT_FREQUENCY | 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, + SR_CONF_DUTY_CYCLE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; -static int dev_open(struct sr_dev_inst *sdi) +static GSList *scan(struct sr_dev_driver *di, GSList *options) { - (void)sdi; + GSList *devices; + const char *conn, *serialcomm; + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *ser; + int ret; + size_t ch_idx, idx, ch_nr; + char cg_name[8]; + struct sr_channel_group *cg; + struct sr_channel *ch; - /* TODO: get handle from sdi->conn and open it. */ + devices = NULL; - return SR_OK; -} + conn = NULL; + serialcomm = DFLT_SERIALCOMM; + (void)sr_serial_extract_options(options, &conn, &serialcomm); + if (!conn) + return devices; + + ser = sr_serial_dev_inst_new(conn, serialcomm); + if (!ser) + return devices; + ret = serial_open(ser, SERIAL_RDWR); + if (ret != SR_OK) + return devices; + + sdi = g_malloc0(sizeof(*sdi)); + sdi->status = SR_ST_INACTIVE; + sdi->inst_type = SR_INST_USB; + sdi->conn = ser; + sdi->connection_id = g_strdup(conn); + devc = g_malloc0(sizeof(*devc)); + sdi->priv = devc; + + ret = jds6600_identify(sdi); + if (ret != SR_OK) + goto fail; + ret = jds6600_setup_devc(sdi); + if (ret != SR_OK) + goto fail; + (void)serial_close(ser); + + sdi->vendor = g_strdup(VENDOR_TEXT); + sdi->model = g_strdup(MODEL_TEXT); + if (devc->device.serial_number) + sdi->serial_num = g_strdup(devc->device.serial_number); + + ch_idx = 0; + for (idx = 0; idx < MAX_GEN_CHANNELS; idx++) { + ch_nr = idx + 1; + snprintf(cg_name, sizeof(cg_name), "CH%zu", ch_nr); + cg = sr_channel_group_new(sdi, cg_name, NULL); + (void)cg; + ch = sr_channel_new(sdi, ch_idx, + SR_CHANNEL_ANALOG, FALSE, cg_name); + cg->channels = g_slist_append(cg->channels, ch); + ch_idx++; + } -static int dev_close(struct sr_dev_inst *sdi) -{ - (void)sdi; + devices = g_slist_append(devices, sdi); + return std_scan_complete(di, devices); - /* TODO: get handle from sdi->conn and close it. */ +fail: + (void)serial_close(ser); + sr_serial_dev_inst_free(ser); + if (devc) { + g_free(devc->device.serial_number); + g_free(devc->waveforms.fw_codes); + g_free(devc->waveforms.names); + } + g_free(devc); + sr_dev_inst_free(sdi); - return SR_OK; + return 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; int ret; + size_t cg_idx; + struct devc_wave *waves; + struct devc_chan *chan; + double dvalue; + const char *s; + + devc = sdi ? sdi->priv : NULL; + if (!cg) { + switch (key) { + case SR_CONF_CONN: + if (!sdi->connection_id) + return SR_ERR_NA; + *data = g_variant_new_string(sdi->connection_id); + return SR_OK; + case SR_CONF_PHASE: + if (!devc) + return SR_ERR_NA; + ret = jds6600_get_phase_chans(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = devc->channels_phase; + *data = g_variant_new_double(dvalue); + return SR_OK; + default: + return SR_ERR_NA; + } + } - (void)sdi; - (void)data; - (void)cg; + if (!devc) + return SR_ERR_NA; + ret = g_slist_index(sdi->channel_groups, cg); + if (ret < 0) + return SR_ERR_NA; + cg_idx = (size_t)ret; + if (cg_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_NA; + chan = &devc->channel_config[cg_idx]; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_ENABLED: + ret = jds6600_get_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + *data = g_variant_new_boolean(chan->enabled); + return SR_OK; + case SR_CONF_PATTERN_MODE: + ret = jds6600_get_waveform(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + waves = &devc->waveforms; + s = waves->names[chan->waveform_index]; + *data = g_variant_new_string(s); + return SR_OK; + case SR_CONF_OUTPUT_FREQUENCY: + ret = jds6600_get_frequency(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->output_frequency; + *data = g_variant_new_double(dvalue); + return SR_OK; + case SR_CONF_AMPLITUDE: + ret = jds6600_get_amplitude(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->amplitude; + *data = g_variant_new_double(dvalue); + return SR_OK; + case SR_CONF_OFFSET: + ret = jds6600_get_offset(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->offset; + *data = g_variant_new_double(dvalue); + return SR_OK; + case SR_CONF_DUTY_CYCLE: + ret = jds6600_get_dutycycle(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + dvalue = chan->dutycycle; + *data = g_variant_new_double(dvalue); + return SR_OK; default: return SR_ERR_NA; } - - return ret; } static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; + struct devc_wave *waves; + struct devc_chan *chan; + size_t cg_idx; + double dvalue; + gboolean on; + int ret, idx; + + devc = sdi ? sdi->priv : NULL; + + if (!cg) { + switch (key) { + case SR_CONF_ENABLED: + /* Enable/disable all channels at the same time. */ + on = g_variant_get_boolean(data); + if (!devc) + return SR_ERR_ARG; + cg_idx = devc->device.channel_count_gen; + while (cg_idx) { + chan = &devc->channel_config[--cg_idx]; + chan->enabled = on; + } + ret = jds6600_set_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_PHASE: + if (!devc) + return SR_ERR_ARG; + dvalue = g_variant_get_double(data); + devc->channels_phase = dvalue; + ret = jds6600_set_phase_chans(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + default: + return SR_ERR_NA; + } + } - (void)sdi; - (void)data; - (void)cg; + ret = g_slist_index(sdi->channel_groups, cg); + if (ret < 0) + return SR_ERR_NA; + cg_idx = (size_t)ret; + if (cg_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_NA; + chan = &devc->channel_config[cg_idx]; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_ENABLED: + on = g_variant_get_boolean(data); + chan->enabled = on; + ret = jds6600_set_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_PATTERN_MODE: + waves = &devc->waveforms; + idx = std_str_idx(data, waves->names, waves->names_count); + if (idx < 0) + return SR_ERR_NA; + if ((size_t)idx >= waves->names_count) + return SR_ERR_NA; + chan->waveform_index = idx; + chan->waveform_code = waves->fw_codes[chan->waveform_index]; + ret = jds6600_set_waveform(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_OUTPUT_FREQUENCY: + dvalue = g_variant_get_double(data); + chan->output_frequency = dvalue; + ret = jds6600_set_frequency(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_AMPLITUDE: + dvalue = g_variant_get_double(data); + chan->amplitude = dvalue; + ret = jds6600_set_amplitude(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_OFFSET: + dvalue = g_variant_get_double(data); + chan->offset = dvalue; + ret = jds6600_set_offset(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; + case SR_CONF_DUTY_CYCLE: + dvalue = g_variant_get_double(data); + chan->dutycycle = dvalue; + ret = jds6600_set_dutycycle(sdi, cg_idx); + if (ret != SR_OK) + return SR_ERR_NA; + return SR_OK; default: - ret = SR_ERR_NA; + return SR_ERR_NA; } - - return ret; } static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { - int ret; - - (void)sdi; - (void)data; - (void)cg; + struct dev_context *devc; + struct devc_wave *waves; + double fspec[3]; + + if (!cg) { + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + default: + return SR_ERR_NA; + } + } - ret = SR_OK; + if (!sdi) + return SR_ERR_NA; + devc = sdi->priv; + if (!devc) + return SR_ERR_NA; switch (key) { - /* TODO */ + case SR_CONF_DEVICE_OPTIONS: + *data =std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg)); + return SR_OK; + case SR_CONF_PATTERN_MODE: + waves = &devc->waveforms; + *data = std_gvar_array_str(waves->names, waves->names_count); + return SR_OK; + case SR_CONF_OUTPUT_FREQUENCY: + /* Announce range as tupe of min, max, step. */ + fspec[0] = 0.01; + fspec[1] = devc->device.max_output_frequency; + fspec[2] = 0.01; + *data = std_gvar_min_max_step_array(fspec); + return SR_OK; + case SR_CONF_DUTY_CYCLE: + /* Announce range as tupe of min, max, step. */ + fspec[0] = 0.0; + fspec[1] = 1.0; + fspec[2] = 0.001; + *data = std_gvar_min_max_step_array(fspec); + return SR_OK; default: return SR_ERR_NA; } - - return ret; } -static int dev_acquisition_start(const struct sr_dev_inst *sdi) +static void clear_helper(struct dev_context *devc) { - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ - - (void)sdi; - - return SR_OK; + struct devc_wave *waves; + + if (!devc) + return; + + g_free(devc->device.serial_number); + waves = &devc->waveforms; + while (waves->names_count) + g_free((char *)waves->names[--waves->names_count]); + g_free(waves->names); + g_free(waves->fw_codes); + if (devc->quick_req) + g_string_free(devc->quick_req, TRUE); } -static int dev_acquisition_stop(struct sr_dev_inst *sdi) +static int dev_clear(const struct sr_dev_driver *driver) { - /* TODO: stop acquisition. */ - - (void)sdi; - - return SR_OK; + return std_dev_clear_with_callback(driver, + (std_dev_clear_callback)clear_helper); } static struct sr_dev_driver juntek_jds6600_driver_info = { @@ -141,14 +404,14 @@ static struct sr_dev_driver juntek_jds6600_driver_info = { .cleanup = std_cleanup, .scan = scan, .dev_list = std_dev_list, - .dev_clear = std_dev_clear, + .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, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = std_dummy_dev_acquisition_start, + .dev_acquisition_stop = std_dummy_dev_acquisition_stop, .context = NULL, }; SR_REGISTER_DEV_DRIVER(juntek_jds6600_driver_info); diff --git a/src/hardware/juntek-jds6600/protocol.c b/src/hardware/juntek-jds6600/protocol.c index c388daed..c6a19f85 100644 --- a/src/hardware/juntek-jds6600/protocol.c +++ b/src/hardware/juntek-jds6600/protocol.c @@ -17,27 +17,1527 @@ * along with this program. If not, see . */ -#include +/* + * Juntek JDS6600 is a DDS signal generator. + * Often rebranded, goes by different names, among them Joy-IT JDS6600. + * + * This driver was built using Kristoff Bonne's knowledge as seen in his + * MIT licensed Python code for JDS6600 control. For details see the + * https://github.com/on1arf/jds6600_python repository. + * + * Supported features: + * - Model detection, which determines the upper output frequency limit + * (15..60MHz models exist). + * - Assumes exactly two channels. Other models were not seen out there. + * - Per channel configuration of: Waveform, output frequency, amplitude, + * offset, duty cycle. + * - Phase between channels is a global property and affects multiple + * channels at the same time (their relation to each other). + * + * TODO + * - Add support for the frequency measurement and/or the counter. This + * feature's availability may depend on or interact with the state of + * other generator channels. Needs consideration of constraints. + * - Add support for modulation (sweep, pulse, burst). Add support for + * other "modes"? + * - Add support for download/upload of arbitrary waveforms. This needs + * infrastructure in common libsigrok code as well as in applications. + * At the moment "blob transfer" (waveform upload/download) appears to + * not be supported. + * - Re-consider parameter value ranges. Frequency depends on the model. + * Amplitude range depends on the model and frequencies and interact + * with the offset offset configuration. Can be -20..+20, or -10..+10, + * or -5..+5 ranges. This implementation caps to the -20..+20 range. + * Many values are passed to the device and may result in capping or + * transformation there in the firwmare. + * + * Implementation details: + * - Communicates via USB CDC at 115200/8n1 (virtual COM port). + * - Requests are in text format. Start with a ':' colon, followed by a + * single letter instruction opcode, followed by a number which either + * addresses a parameter (think hardware register) or storage slot for + * an arbitrary waveform. Can be followed by an '=' equals sign and a + * value. Multiple values are comma separated. The line may end in a + * '.' period. Several end-of-line conventions are supported by the + * devices' firmware versions, LF and CR/LF are reported to work. + * - Responses also are in text format. Start with a ':' colon, followed + * by an instruction letter, followed by a number (a parameter index, + * or a waveform index), followed by '=' equal sign and one or more + * values. Optionally ending in a '.' period. And ending in the + * firmware's end-of-line. Read requests will have this format. + * Alternatively write requests may just respond with the ":ok" + * text phrase. + * - There are four instructions: 'r' to read and 'w' to write parameters + * (think hardware registers, optionaly multi-valued), 'a' to write and + * 'b' to read arbitrary waveform data (sequence of sample values). + * - Am not aware of a vendor's documentation for the protocol. Joy-IT + * provides the JT-JDS6600-Communication-protocol.pdf document which + * leaves a lot of questions. This sigrok driver implementation used + * a lot of https://github.com/on1arf/jds6600_python knowledge for + * the initial version (MIT licenced Python code by Kristoff Bonne). + * - The requests take effect when sent from application code. While + * the requests remain uneffective when typed in interactive terminal + * sessions. Though there are ":ok" responses, the action would not + * happen in the device. It's assumed to be a firmware implementation + * constraint that is essential to keep in mind. + * - The right hand side of write requests or read responses can carry + * any number of values, both numbers and text, integers and floats. + * Still some of the parameters (voltages, times, frequencies) come in + * interesting formats. A floating point "mantissa" and an integer code + * for scaling the value. Not an exponent, but some kind of index. In + * addition to an open coded "fixed point" style multiplier that is + * implied and essential, but doesn't show on the wire. Interpretation + * of responses and phrasing of values in requests is arbitrary, this + * "black magic" was found by local experimentation (reading back the + * values which were configured by local UI interaction). + */ + +#include "config.h" + +#include +#include +#include + #include "protocol.h" -SR_PRIV int juntek_jds6600_receive_data(int fd, int revents, void *cb_data) +#define WITH_ARBWAVE_DOWNLOAD 0 /* Development HACK */ + +/* + * The firmware's maximum response length. Seen when an arbitrary + * waveform gets retrieved. Carries 2048 samples in the 0..4095 range. + * Plus some decoration around that data. + * :b01=4095,4095,...,4095, + */ +#define MAX_RSP_LENGTH (8 + 2048 * 5) + +/* Times are in milliseconds. */ +#define DELAY_AFTER_WRITE 10 +#define DELAY_AFTER_FLASH 100 +#define TIMEOUT_READ_CHUNK 20 +#define TIMEOUT_IDENTIFY 200 + +/* Instruction codes. Read/write parameters/waveforms. */ +#define INSN_WRITE_PARA 'w' +#define INSN_READ_PARA 'r' +#define INSN_WRITE_WAVE 'a' +#define INSN_READ_WAVE 'b' + +/* Indices for "register access". */ +enum param_index { + IDX_DEVICE_TYPE = 0, + IDX_SERIAL_NUMBER = 1, + IDX_CHANNELS_ENABLE = 20, + IDX_WAVEFORM_CH1 = 21, + IDX_WAVEFORM_CH2 = 22, + IDX_FREQUENCY_CH1 = 23, + IDX_FREQUENCY_CH2 = 24, + IDX_AMPLITUDE_CH1 = 25, + IDX_AMPLITUDE_CH2 = 26, + IDX_OFFSET_CH1 = 27, + IDX_OFFSET_CH2 = 28, + IDX_DUTYCYCLE_CH1 = 29, + IDX_DUTYCYCLE_CH2 = 30, + IDX_PHASE_CHANNELS = 31, + IDX_ACTION = 32, + IDX_MODE = 33, + IDX_INPUT_COUPLING = 36, + IDX_MEASURE_GATE = 37, + IDX_MEASURE_MODE = 38, + IDX_COUNTER_RESET = 39, + IDX_SWEEP_STARTFREQ = 40, + IDX_SWEEP_ENDFREQ = 41, + IDX_SWEEP_TIME = 42, + IDX_SWEEP_DIRECTION = 43, + IDX_SWEEP_MODE = 44, + IDX_PULSE_WIDTH = 45, + IDX_PULSE_PERIOD = 46, + IDX_PULSE_OFFSET = 47, + IDX_PULSE_AMPLITUDE = 48, + IDX_BURST_COUNT = 49, + IDX_BURST_MODE = 50, + IDX_SYSTEM_SOUND = 51, + IDX_SYSTEM_BRIGHTNESS = 52, + IDX_SYSTEM_LANGUAGE = 53, + IDX_SYSTEM_SYNC = 54, /* "Tracking" channels? */ + IDX_SYSTEM_ARBMAX = 55, + IDX_PROFILE_SAVE = 70, + IDX_PROFILE_LOAD = 71, + IDX_PROFILE_CLEAR = 72, + IDX_COUNTER_VALUE = 80, + IDX_MEAS_VALUE_FREQLOW = 81, + IDX_MEAS_VALUE_FREQHI = 82, + IDX_MEAS_VALUE_WIDTHHI = 83, + IDX_MEAS_VALUE_WIDTHLOW = 84, + IDX_MEAS_VALUE_PERIOD = 85, + IDX_MEAS_VALUE_DUTYCYCLE = 86, + IDX_MEAS_VALUE_U1 = 87, + IDX_MEAS_VALUE_U2 = 88, + IDX_MEAS_VALUE_U3 = 89, +}; + +/* Firmware's codes for waveform selection. */ +enum waveform_index_t { + /* 17 pre-defined waveforms. */ + WAVE_SINE = 0, + WAVE_SQUARE = 1, + WAVE_PULSE = 2, + WAVE_TRIANGLE = 3, + WAVE_PARTIAL_SINE = 4, + WAVE_CMOS = 5, + WAVE_DC = 6, + WAVE_HALF_WAVE = 7, + WAVE_FULL_WAVE = 8, + WAVE_POS_LADDER = 9, + WAVE_NEG_LADDER = 10, + WAVE_NOISE = 11, + WAVE_EXP_RISE = 12, + WAVE_EXP_DECAY = 13, + WAVE_MULTI_TONE = 14, + WAVE_SINC = 15, + WAVE_LORENZ = 16, + WAVES_COUNT_BUILTIN, + /* Up to 60 arbitrary waveforms. */ + WAVES_ARB_BASE = 100, + WAVE_ARB01 = WAVES_ARB_BASE + 1, + /* ... */ + WAVE_ARB60 = WAVES_ARB_BASE + 60, + WAVES_PAST_LAST_ARB, +}; +#define WAVES_COUNT_ARBITRARY (WAVES_PAST_LAST_ARB - WAVE_ARB01) + +static const char *waveform_names[] = { + [WAVE_SINE] = "sine", + [WAVE_SQUARE] = "square", + [WAVE_PULSE] = "pulse", + [WAVE_TRIANGLE] = "triangle", + [WAVE_PARTIAL_SINE] = "partial-sine", + [WAVE_CMOS] = "cmos", + [WAVE_DC] = "dc", + [WAVE_HALF_WAVE] = "half-wave", + [WAVE_FULL_WAVE] = "full-wave", + [WAVE_POS_LADDER] = "pos-ladder", + [WAVE_NEG_LADDER] = "neg-ladder", + [WAVE_NOISE] = "noise", + [WAVE_EXP_RISE] = "exp-rise", + [WAVE_EXP_DECAY] = "exp-decay", + [WAVE_MULTI_TONE] = "multi-tone", + [WAVE_SINC] = "sinc", + [WAVE_LORENZ] = "lorenz", +}; +#define WAVEFORM_ARB_NAME_FMT "arb-%02zu" + +/* + * Writes a text line to the serial port. Normalizes end-of-line + * including trailing period. + */ +static int serial_send_textline(const struct sr_dev_inst *sdi, + GString *s, unsigned int delay_ms) +{ + struct sr_serial_dev_inst *conn; + const char *rdptr; + size_t padlen, rdlen, wrlen; + int ret; + + if (!sdi) + return SR_ERR_ARG; + conn = sdi->conn; + if (!conn) + return SR_ERR_ARG; + if (!s) + return SR_ERR_ARG; + + /* Trim surrounding whitespace. Normalize end-of-line. */ + padlen = 4; + while (padlen--) + g_string_append_c(s, '\0'); + rdptr = sr_text_trim_spaces(s->str); + rdlen = strlen(rdptr); + if (rdlen && rdptr[rdlen - 1] == '.') + rdlen--; + g_string_set_size(s, rdlen); + g_string_append_c(s, '.'); + sr_spew("serial TX data: --> %s", rdptr); + g_string_append_c(s, '\r'); + g_string_append_c(s, '\n'); + rdlen = strlen(rdptr); + + /* Handle chunked writes, check for transmission errors. */ + while (rdlen) { + ret = serial_write_blocking(conn, rdptr, rdlen, 0); + if (ret < 0) + return SR_ERR_IO; + wrlen = (size_t)ret; + if (wrlen > rdlen) + wrlen = rdlen; + rdptr += wrlen; + rdlen -= wrlen; + } + + if (delay_ms) + g_usleep(delay_ms * 1000); + + return SR_OK; +} + +/* + * Reads a text line from the serial port. Assumes that only a single + * response text line is in flight (does not handle the case of more + * receive data following after the first EOL). Transparently deals + * with trailing period and end-of-line, so callers need not bother. + * + * Checks plausibility when the caller specifies conditions to check. + * Optionally returns references (and lengths) to the response's RHS. + * That's fine because data resides in a caller provided buffer. + */ +static int serial_recv_textline(const struct sr_dev_inst *sdi, + GString *s, unsigned int delay_ms, unsigned int timeout_ms, + gboolean *is_ok, char wants_insn, size_t wants_index, + char **rhs_start, size_t *rhs_length) +{ + struct sr_serial_dev_inst *ser; + char *rdptr; + size_t rdlen, got; + int ret; + guint64 now_us, deadline_us; + gboolean has_timedout; + char *eol_pos, *endptr; + char got_insn; + unsigned long got_index; + + if (is_ok) + *is_ok = FALSE; + if (rhs_start) + *rhs_start = NULL; + if (rhs_length) + *rhs_length = 0; + + if (!sdi) + return SR_ERR_ARG; + ser = sdi->conn; + if (!ser) + return SR_ERR_ARG; + + g_string_set_size(s, MAX_RSP_LENGTH); + g_string_truncate(s, 0); + + /* Arrange for overall receive timeout when caller specified. */ + now_us = deadline_us = 0; + if (timeout_ms) { + now_us = g_get_monotonic_time(); + deadline_us = now_us; + deadline_us += timeout_ms * 1000; + } + + rdptr = s->str; + rdlen = s->allocated_len - 1 - s->len; + while (rdlen) { + /* Get another chunk of receive data. Check for EOL. */ + ret = serial_read_blocking(ser, rdptr, rdlen, delay_ms); + if (ret < 0) + return SR_ERR_IO; + got = (size_t)ret; + if (got > rdlen) + got = rdlen; + rdptr[got] = '\0'; + eol_pos = strchr(rdptr, '\n'); + rdptr += got; + rdlen -= got; + /* Check timeout expiration upon empty reception. */ + has_timedout = FALSE; + if (timeout_ms && !got) { + now_us = g_get_monotonic_time(); + if (now_us >= deadline_us) + has_timedout = TRUE; + } + if (!eol_pos) { + if (has_timedout) + break; + continue; + } + + /* Normalize the received text line. */ + *eol_pos++ = '\0'; + rdptr = s->str; + (void)sr_text_trim_spaces(rdptr); + rdlen = strlen(rdptr); + sr_spew("serial RX data: <-- %s", rdptr); + if (rdlen && rdptr[rdlen - 1] == '.') + rdptr[--rdlen] = '\0'; + + /* Check conditions as requested by the caller. */ + if (is_ok || wants_insn || rhs_start) { + if (*rdptr != ':') { + sr_dbg("serial read, colon missing"); + return SR_ERR_DATA; + } + rdptr++; + rdlen--; + } + /* + * The check for 'ok' is terminal. Does not combine with + * responses which carry payload data on their RHS. + */ + if (is_ok) { + *is_ok = strcmp(rdptr, "ok") == 0; + sr_dbg("serial read, 'ok' check %d", *is_ok); + return *is_ok ? SR_OK : SR_ERR_DATA; + } + /* + * Conditional strict checks for caller's expected fields. + * Unconditional weaker checks for general structure. + */ + if (wants_insn && *rdptr != wants_insn) { + sr_dbg("serial read, unexpected insn"); + return SR_ERR_DATA; + } + got_insn = *rdptr++; + switch (got_insn) { + case INSN_WRITE_PARA: + case INSN_READ_PARA: + case INSN_WRITE_WAVE: + case INSN_READ_WAVE: + /* EMPTY */ + break; + default: + sr_dbg("serial read, unknown insn %c", got_insn); + return SR_ERR_DATA; + } + endptr = NULL; + ret = sr_atoul_base(rdptr, &got_index, &endptr, 10); + if (ret != SR_OK || !endptr) + return SR_ERR_DATA; + if (wants_index && got_index != wants_index) { + sr_dbg("serial read, unexpected index %zu", got_index); + return SR_ERR_DATA; + } + rdptr = endptr; + if (rhs_start || rhs_length) { + if (*rdptr != '=') { + sr_dbg("serial read, equals sign missing"); + return SR_ERR_DATA; + } + } + if (*rdptr) + rdptr++; + + /* Response is considered plausible here. */ + if (rhs_start) + *rhs_start = rdptr; + if (rhs_length) + *rhs_length = strlen(rdptr); + return SR_OK; + } + + sr_dbg("serial read, no EOL seen"); + return SR_ERR_DATA; +} + +/* Formatting helpers for request construction. */ + +static void append_insn_read_para(GString *s, char insn, size_t idx) +{ + g_string_append_printf(s, ":%c%02zu=0", insn, idx); +} + +static void append_insn_write_para_va(GString *s, char insn, size_t idx, + const char *fmt, va_list args) ATTR_FMT_PRINTF(4, 0); +static void append_insn_write_para_va(GString *s, char insn, size_t idx, + const char *fmt, va_list args) +{ + g_string_append_printf(s, ":%c%02zu=", insn, idx); + g_string_append_vprintf(s, fmt, args); +} + +static void append_insn_write_para_dots(GString *s, char insn, size_t idx, + const char *fmt, ...) ATTR_FMT_PRINTF(4, 5); +static void append_insn_write_para_dots(GString *s, char insn, size_t idx, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + append_insn_write_para_va(s, insn, idx, fmt, args); + va_end(args); +} + +/* + * Turn comma separators into whitespace. Simplifies the interpretation + * of multi-value response payloads. Also replaces any trailing period + * in case callers kept one in the receive buffer. + */ +static void replace_separators(char *s) +{ + + while (s && *s) { + if (s[0] == ',') { + *s++ = ' '; + continue; + } + if (s[0] == '.' && s[1] == '\0') { + *s++ = ' '; + continue; + } + s++; + } +} + +/* + * Convenience to interpret responses' values. Also concentrates the + * involved magic and simplifies diagnostics. It's essential to apply + * implicit multipliers, and to properly combine multiple fields into + * the resulting parameter's value (think scaling and offsetting). + */ + +static const double scales_freq[] = { + 1, 1, 1, 1e-3, 1e-6, +}; + +static int parse_freq_text(char *s, double *value) +{ + char *word; + int ret; + double dvalue; + unsigned long scale; + + replace_separators(s); + + /* First word is a mantissa, in centi-Hertz. :-O */ + word = sr_text_next_word(s, &s); + ret = sr_atod(word, &dvalue); + if (ret != SR_OK) + return ret; + + /* Next word is an encoded scaling factor. */ + word = sr_text_next_word(s, &s); + ret = sr_atoul_base(word, &scale, NULL, 10); + if (ret != SR_OK) + return ret; + sr_spew("parse freq, mant %lf, scale %ld", dvalue, scale); + if (scale >= ARRAY_SIZE(scales_freq)) + return SR_ERR_DATA; + + /* Do scale the mantissa's value. */ + dvalue /= 100.0; + dvalue /= scales_freq[scale]; + sr_spew("parse freq, value %lf", dvalue); + + if (value) + *value = dvalue; + return SR_OK; +} + +static int parse_volt_text(char *s, double *value) +{ + int ret; + double dvalue; + + /* Single value, in units of mV. */ + ret = sr_atod(s, &dvalue); + if (ret != SR_OK) + return ret; + sr_spew("parse volt, mant %lf", dvalue); + dvalue /= 1000.0; + sr_spew("parse volt, value %lf", dvalue); + + if (value) + *value = dvalue; + return SR_OK; +} + +static int parse_bias_text(char *s, double *value) +{ + int ret; + double dvalue; + + /* + * Single value, in units of 10mV with a 10V offset. Capped to + * the +9.99V..-9.99V range. The Joy-IT PDF is a little weird + * suggesting that ":w27=9999." translates to 9.99 volts. + */ + ret = sr_atod(s, &dvalue); + if (ret != SR_OK) + return ret; + sr_spew("parse bias, mant %lf", dvalue); + dvalue /= 100.0; + dvalue -= 10.0; + if (dvalue >= 9.99) + dvalue = 9.99; + if (dvalue <= -9.99) + dvalue = -9.99; + sr_spew("parse bias, value %lf", dvalue); + + if (value) + *value = dvalue; + return SR_OK; +} + +static int parse_duty_text(char *s, double *value) +{ + int ret; + double dvalue; + + /* + * Single value, in units of 0.1% (permille). + * Scale to the 0.0..1.0 range. + */ + ret = sr_atod(s, &dvalue); + if (ret != SR_OK) + return ret; + sr_spew("parse duty, mant %lf", dvalue); + dvalue /= 1000.0; + sr_spew("parse duty, value %lf", dvalue); + + if (value) + *value = dvalue; + return SR_OK; +} + +static int parse_phase_text(char *s, double *value) +{ + int ret; + double dvalue; + + /* Single value, in units of deci-degrees. */ + ret = sr_atod(s, &dvalue); + if (ret != SR_OK) + return ret; + sr_spew("parse phase, mant %lf", dvalue); + dvalue /= 10.0; + sr_spew("parse phase, value %lf", dvalue); + + if (value) + *value = dvalue; + return SR_OK; +} + +/* + * Convenience to generate request presentations. Also concentrates the + * involved magic and simplifies diagnostics. It's essential to apply + * implicit multipliers, and to properly create all request fields that + * communicate a value to the device's firmware (think scale and offset). + */ + +static void write_freq_text(GString *s, double freq) +{ + unsigned long scale_idx; + const char *text_pos; + + sr_spew("write freq, value %lf", freq); + text_pos = &s->str[s->len]; + + /* + * First word is mantissa in centi-Hertz. Second word is a + * scaling factor code. Keep scaling simple, always scale + * by a factor of 1.0. + */ + scale_idx = 0; + freq *= scales_freq[scale_idx]; + freq *= 100.0; + + g_string_append_printf(s, "%.0lf,%lu", freq, scale_idx); + sr_spew("write freq, text %s", text_pos); +} + +static void write_volt_text(GString *s, double volt) +{ + const char *text_pos; + + sr_spew("write volt, value %lf", volt); + text_pos = &s->str[s->len]; + + /* + * Single value in units of 1mV. + * Limit input values to the 0..+20 range. This writer is only + * used by the amplitude setter. + */ + if (volt > 20.0) + volt = 20.0; + if (volt < 0.0) + volt = 0.0; + volt *= 1000.0; + g_string_append_printf(s, "%.0lf", volt); + sr_spew("write volt, text %s", text_pos); +} + +static void write_bias_text(GString *s, double volt) +{ + const char *text_pos; + + sr_spew("write bias, value %lf", volt); + text_pos = &s->str[s->len]; + + /* + * Single value in units of 10mV with a 10V offset. Capped to + * the +9.99..-9.99 range. + */ + if (volt > 9.99) + volt = 9.99; + if (volt < -9.99) + volt = -9.99; + volt += 10.0; + volt *= 100.0; + + g_string_append_printf(s, "%.0lf", volt); + sr_spew("write bias, text %s", text_pos); +} + +static void write_duty_text(GString *s, double duty) +{ + const char *text_pos; + + sr_spew("write duty, value %lf", duty); + text_pos = &s->str[s->len]; + + /* + * Single value in units of 0.1% (permille). Capped to the + * 0.0..1.0 range. + */ + if (duty < 0.0) + duty = 0.0; + if (duty > 1.0) + duty = 1.0; + duty *= 1000.0; + + g_string_append_printf(s, "%.0lf", duty); + sr_spew("write duty, text %s", text_pos); +} + +static void write_phase_text(GString *s, double phase) +{ + const char *text_pos; + + sr_spew("write phase, value %lf", phase); + text_pos = &s->str[s->len]; + + /* + * Single value in units of deci-degrees. + * Kept to the 0..360 range by means of a modulo operation. + */ + phase = fmod(phase, 360.0); + phase *= 10.0; + + g_string_append_printf(s, "%.0lf", phase); + sr_spew("write phase, text %s", text_pos); +} + +/* + * Convenience communication wrapper. Re-uses a buffer in devc, which + * simplifies resource handling in error paths. Sends a parameter-less + * read-request. Then receives a response which can carry values. + */ +static int quick_send_read_then_recv(const struct sr_dev_inst *sdi, + char insn, size_t idx, + unsigned int read_timeout_ms, + char **rhs_start, size_t *rhs_length) +{ + struct dev_context *devc; + GString *s; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (!devc->quick_req) + devc->quick_req = g_string_sized_new(MAX_RSP_LENGTH); + s = devc->quick_req; + + g_string_truncate(s, 0); + append_insn_read_para(s, insn, idx); + ret = serial_send_textline(sdi, s, DELAY_AFTER_WRITE); + if (ret != SR_OK) + return ret; + + ret = serial_recv_textline(sdi, s, + TIMEOUT_READ_CHUNK, read_timeout_ms, + NULL, insn, idx, rhs_start, rhs_length); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +/* + * Convenience communication wrapper, re-uses a buffer in devc. Sends a + * write-request with parameters. Then receives an "ok" style response. + * Had to put the request details after the response related parameters + * because of the va_list API. + */ +static int quick_send_write_then_recv_ok(const struct sr_dev_inst *sdi, + unsigned int read_timeout_ms, gboolean *is_ok, + char insn, size_t idx, const char *fmt, ...) ATTR_FMT_PRINTF(6, 7); +static int quick_send_write_then_recv_ok(const struct sr_dev_inst *sdi, + unsigned int read_timeout_ms, gboolean *is_ok, + char insn, size_t idx, const char *fmt, ...) +{ + struct dev_context *devc; + GString *s; + va_list args; + int ret; + gboolean ok; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (!devc->quick_req) + devc->quick_req = g_string_sized_new(MAX_RSP_LENGTH); + s = devc->quick_req; + + g_string_truncate(s, 0); + va_start(args, fmt); + append_insn_write_para_va(s, insn, idx, fmt, args); + va_end(args); + ret = serial_send_textline(sdi, s, DELAY_AFTER_WRITE); + if (ret != SR_OK) + return ret; + + ret = serial_recv_textline(sdi, s, + TIMEOUT_READ_CHUNK, read_timeout_ms, + &ok, '\0', 0, NULL, NULL); + if (is_ok) + *is_ok = ok; + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +/* + * High level getters/setters for device properties. + * To be used by the api.c config get/set infrastructure. + */ + +SR_PRIV int jds6600_get_chans_enable(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + int ret; + char *rdptr, *word, *endptr; + struct devc_dev *device; + struct devc_chan *chans; + size_t idx; + unsigned long on; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_CHANNELS_ENABLE, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get enabled, response text: %s", rdptr); + + /* Interpret the response (multiple values, boolean). */ + replace_separators(rdptr); + device = &devc->device; + chans = devc->channel_config; + for (idx = 0; idx < device->channel_count_gen; idx++) { + word = sr_text_next_word(rdptr, &rdptr); + if (!word || !*word) + return SR_ERR_DATA; + endptr = NULL; + ret = sr_atoul_base(word, &on, &endptr, 10); + if (ret != SR_OK || !endptr || *endptr) + return SR_ERR_DATA; + chans[idx].enabled = on; + } + + return SR_OK; +} + +SR_PRIV int jds6600_get_waveform(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + int ret; + char *rdptr, *endptr; + struct devc_wave *waves; + struct devc_chan *chan; + unsigned long code; + size_t idx; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + waves = &devc->waveforms; + if (ch_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_WAVEFORM_CH1 + ch_idx, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get waveform, response text: %s", rdptr); + + /* + * Interpret the response (integer value, waveform code). + * Lookup the firmware's code for that waveform in the + * list of user perceivable names for waveforms. + */ + endptr = NULL; + ret = sr_atoul_base(rdptr, &code, &endptr, 10); + if (ret != SR_OK) + return SR_ERR_DATA; + for (idx = 0; idx < waves->names_count; idx++) { + if (code != waves->fw_codes[idx]) + continue; + chan->waveform_code = code; + chan->waveform_index = idx; + sr_dbg("get waveform, code %lu, idx %zu, name %s", + code, idx, waves->names[idx]); + return SR_OK; + } + + return SR_ERR_DATA; +} + +#if WITH_ARBWAVE_DOWNLOAD +/* + * Development HACK. Get a waveform from the device. Uncertain where to + * dump it though. Have yet to identify a sigrok API for waveforms. + */ +static int jds6600_get_arb_waveform(const struct sr_dev_inst *sdi, size_t idx) +{ + struct dev_context *devc; + struct devc_wave *waves; + int ret; + char *rdptr, *word, *endptr; + size_t sample_count; + unsigned long value; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + waves = &devc->waveforms; + + if (idx >= waves->arbitrary_count) + return SR_ERR_ARG; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_WAVE, idx, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get arb wave, response text: %s", rdptr); + + /* Extract the sequence of samples for the waveform. */ + replace_separators(rdptr); + sample_count = 0; + while (rdptr && *rdptr) { + word = sr_text_next_word(rdptr, &rdptr); + if (!word) + break; + endptr = NULL; + ret = sr_atoul_base(word, &value, &endptr, 10); + if (ret != SR_OK || !endptr || *endptr) { + sr_dbg("get arb wave, conv error: %s", word); + return SR_ERR_DATA; + } + sample_count++; + } + sr_dbg("get arb wave, samples count: %zu", sample_count); + + return SR_OK; +} +#endif + +SR_PRIV int jds6600_get_frequency(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + int ret; + char *rdptr; + double freq; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_FREQUENCY_CH1 + ch_idx, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get frequency, response text: %s", rdptr); + + /* Interpret the response (value and scale, frequency). */ + ret = parse_freq_text(rdptr, &freq); + if (ret != SR_OK) + return SR_ERR_DATA; + sr_dbg("get frequency, value %lf", freq); + chan->output_frequency = freq; + return SR_OK; +} + +SR_PRIV int jds6600_get_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + int ret; + char *rdptr; + double amp; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_AMPLITUDE_CH1 + ch_idx, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get amplitude, response text: %s", rdptr); + + /* Interpret the response (single value, a voltage). */ + ret = parse_volt_text(rdptr, &); + if (ret != SR_OK) + return SR_ERR_DATA; + sr_dbg("get amplitude, value %lf", amp); + chan->amplitude = amp; + return SR_OK; +} + +SR_PRIV int jds6600_get_offset(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + int ret; + char *rdptr; + double off; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_OFFSET_CH1 + ch_idx, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get offset, response text: %s", rdptr); + + /* Interpret the response (single value, an offset). */ + ret = parse_bias_text(rdptr, &off); + if (ret != SR_OK) + return SR_ERR_DATA; + sr_dbg("get offset, value %lf", off); + chan->offset = off; + return SR_OK; +} + +SR_PRIV int jds6600_get_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx) { - const struct sr_dev_inst *sdi; struct dev_context *devc; + struct devc_chan *chan; + int ret; + char *rdptr; + double duty; - (void)fd; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= ARRAY_SIZE(devc->channel_config)) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_DUTYCYCLE_CH1 + ch_idx, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get duty cycle, response text: %s", rdptr); + + /* Interpret the response (single value, a percentage). */ + ret = parse_duty_text(rdptr, &duty); + if (ret != SR_OK) + return SR_ERR_DATA; + sr_dbg("get duty cycle, value %lf", duty); + chan->dutycycle = duty; + return SR_OK; +} + +SR_PRIV int jds6600_get_phase_chans(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + int ret; + char *rdptr; + double phase; + + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Transmit the request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_PHASE_CHANNELS, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("get phase, response text: %s", rdptr); + + /* Interpret the response (single value, an angle). */ + ret = parse_phase_text(rdptr, &phase); + if (ret != SR_OK) + return SR_ERR_DATA; + sr_dbg("get phase, value %lf", phase); + devc->channels_phase = phase; + return SR_OK; +} + +SR_PRIV int jds6600_set_chans_enable(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct devc_chan *chans; + GString *en_text; + size_t idx; + int ret; - sdi = cb_data; if (!sdi) - return TRUE; + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Transmit the request, receive an "ok" style response. */ + chans = devc->channel_config; + en_text = g_string_sized_new(20); + for (idx = 0; idx < devc->device.channel_count_gen; idx++) { + if (en_text->len) + g_string_append_c(en_text, ','); + g_string_append_c(en_text, chans[idx].enabled ? '1' : '0'); + } + sr_dbg("set enabled, request text: %s", en_text->str); + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_CHANNELS_ENABLE, "%s", en_text->str); + g_string_free(en_text, 20); + if (ret != SR_OK) + return ret; + + return SR_OK; +} +SR_PRIV int jds6600_set_waveform(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + int ret; + + if (!sdi) + return SR_ERR_ARG; devc = sdi->priv; if (!devc) - return TRUE; + return SR_ERR_ARG; + if (ch_idx >= devc->device.channel_count_gen) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive an "ok" style response. */ + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_WAVEFORM_CH1 + ch_idx, + "%" PRIu32, chan->waveform_code); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +#if WITH_ARBWAVE_DOWNLOAD +/* + * Development HACK. Send a waveform to the device. Uncertain where + * to get it from though. Just generate some stupid pattern that's + * seen on the LCD later. + * + * Local experiments suggest that writing another waveform after having + * written one earlier results in the next waveform to become mangled. + * It appears to start with an all-bits-set pattern for a remarkable + * number of samples, before the actually written pattern is seen. Some + * delay after reception of the ":ok" response may be required to avoid + * this corruption. + */ - if (revents == G_IO_IN) { - /* TODO */ +/* Stupid creation of one sample value. Gets waveform index and sample count. */ +static uint16_t make_sample(size_t wave, size_t curr, size_t total) +{ + uint16_t max_value, high_value, low_value; + size_t ival, high_width; + gboolean is_high; + + /* Get the waveform's amplitudes. */ + max_value = 4096; + high_value = max_value / (wave + 3); + high_value = max_value - high_value; + low_value = max_value - high_value; + + /* Get pulses' total interval, high and low half-periods. */ + ival = (total - 10) / wave; + high_width = ival / 2; + + /* Check location in the current period. */ + curr %= ival; + is_high = curr <= high_width; + return is_high ? high_value : low_value; +} + +/* Creation and download of the sequence of samples. */ +static int jds6600_set_arb_waveform(const struct sr_dev_inst *sdi, size_t idx) +{ + struct dev_context *devc; + struct devc_wave *waves; + GString *wave_text; + size_t samples_total, samples_curr; + uint16_t value; + gboolean ok; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + waves = &devc->waveforms; + + if (idx >= waves->arbitrary_count) + return SR_ERR_ARG; + + /* Construct a pattern that depends on the waveform index. */ + wave_text = g_string_sized_new(MAX_RSP_LENGTH); + samples_total = 2048; + samples_curr = 0; + for (samples_curr = 0; samples_curr < samples_total; samples_curr++) { + value = make_sample(idx, samples_curr, samples_total); + if (samples_curr) + g_string_append_c(wave_text, ','); + g_string_append_printf(wave_text, "%" PRIu16, value); } + sr_dbg("set arb wave, request text: %s", wave_text->str); + + /* Transmit the request, receive an "ok" style response. */ + ret = quick_send_write_then_recv_ok(sdi, 0, &ok, + INSN_WRITE_WAVE, idx, "%s", wave_text->str); + if (ret != SR_OK) + return ret; + sr_dbg("set arb wave, response ok: %d", ok); + + if (DELAY_AFTER_FLASH) + g_usleep(DELAY_AFTER_FLASH * 1000); + + return SR_OK; +} +#endif + +SR_PRIV int jds6600_set_frequency(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + double freq; + GString *freq_text; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= devc->device.channel_count_gen) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Limit input values to the range supported by the model. */ + freq = chan->output_frequency; + if (freq < 0.01) + freq = 0.01; + if (freq > devc->device.max_output_frequency) + freq = devc->device.max_output_frequency; + + /* Transmit the request, receive an "ok" style response. */ + freq_text = g_string_sized_new(32); + write_freq_text(freq_text, freq); + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_FREQUENCY_CH1 + ch_idx, + "%s", freq_text->str); + g_string_free(freq_text, TRUE); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +SR_PRIV int jds6600_set_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + GString *volt_text; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= devc->device.channel_count_gen) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive an "ok" style response. */ + volt_text = g_string_sized_new(32); + write_volt_text(volt_text, chan->amplitude); + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_AMPLITUDE_CH1 + ch_idx, + "%s", volt_text->str); + g_string_free(volt_text, TRUE); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +SR_PRIV int jds6600_set_offset(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + GString *volt_text; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= devc->device.channel_count_gen) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive an "ok" style response. */ + volt_text = g_string_sized_new(32); + write_bias_text(volt_text, chan->offset); + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_OFFSET_CH1 + ch_idx, + "%s", volt_text->str); + g_string_free(volt_text, TRUE); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +SR_PRIV int jds6600_set_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx) +{ + struct dev_context *devc; + struct devc_chan *chan; + GString *duty_text; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (ch_idx >= devc->device.channel_count_gen) + return SR_ERR_ARG; + chan = &devc->channel_config[ch_idx]; + + /* Transmit the request, receive an "ok" style response. */ + duty_text = g_string_sized_new(32); + write_duty_text(duty_text, chan->dutycycle); + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_DUTYCYCLE_CH1 + ch_idx, + "%s", duty_text->str); + g_string_free(duty_text, TRUE); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +SR_PRIV int jds6600_set_phase_chans(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + GString *phase_text; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Transmit the request, receive an "ok" style response. */ + phase_text = g_string_sized_new(32); + write_phase_text(phase_text, devc->channels_phase); + ret = quick_send_write_then_recv_ok(sdi, 0, NULL, + INSN_WRITE_PARA, IDX_PHASE_CHANNELS, + "%s", phase_text->str); + g_string_free(phase_text, TRUE); + if (ret != SR_OK) + return ret; + + return SR_OK; +} + +/* + * High level helpers for the scan/probe phase. Identify the attached + * device and synchronize to its current state and its capabilities. + */ + +SR_PRIV int jds6600_identify(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + int ret; + char *rdptr, *endptr; + unsigned long devtype; + + (void)append_insn_write_para_dots; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* Transmit "read device type" request, receive the response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_DEVICE_TYPE, + TIMEOUT_IDENTIFY, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("identify, device type '%s'", rdptr); + + /* Interpret the response (integer value, max freq). */ + endptr = NULL; + ret = sr_atoul_base(rdptr, &devtype, &endptr, 10); + if (ret != SR_OK || !endptr) + return SR_ERR_DATA; + devc->device.device_type = devtype; + + /* Transmit "read serial number" request. receive response. */ + ret = quick_send_read_then_recv(sdi, + INSN_READ_PARA, IDX_SERIAL_NUMBER, + 0, &rdptr, NULL); + if (ret != SR_OK) + return ret; + sr_dbg("identify, serial number '%s'", rdptr); + + /* Keep the response (in string format, some serial number). */ + devc->device.serial_number = g_strdup(rdptr); + + return SR_OK; +} + +SR_PRIV int jds6600_setup_devc(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + size_t alloc_count, assign_idx, idx; + struct devc_dev *device; + struct devc_wave *waves; + enum waveform_index_t code; + char *name; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + + /* + * Derive maximum output frequency from detected device type. + * Open coded generator channel count. + */ + device = &devc->device; + if (!device->device_type) + return SR_ERR_DATA; + device->max_output_frequency = device->device_type; + device->max_output_frequency *= SR_MHZ(1); + device->channel_count_gen = MAX_GEN_CHANNELS; + + /* Construct the list of waveform names and their codes. */ + waves = &devc->waveforms; + waves->builtin_count = WAVES_COUNT_BUILTIN; + waves->arbitrary_count = WAVES_COUNT_ARBITRARY; + alloc_count = waves->builtin_count; + alloc_count += waves->arbitrary_count; + waves->names_count = alloc_count; + waves->fw_codes = g_malloc0(alloc_count * sizeof(waves->fw_codes[0])); + alloc_count++; + waves->names = g_malloc0(alloc_count * sizeof(waves->names[0])); + if (!waves->names || !waves->fw_codes) + return SR_ERR_MALLOC; + assign_idx = 0; + for (idx = 0; idx < waves->builtin_count; idx++) { + code = idx; + name = g_strdup(waveform_names[idx]); + waves->fw_codes[assign_idx] = code; + waves->names[assign_idx] = name; + assign_idx++; + } + for (idx = 0; idx < waves->arbitrary_count; idx++) { + code = WAVE_ARB01 + idx; + name = g_strdup_printf(WAVEFORM_ARB_NAME_FMT, idx + 1); + waves->fw_codes[assign_idx] = code; + waves->names[assign_idx] = name; + assign_idx++; + } + waves->names[assign_idx] = NULL; + + /* + * Populate internal channel configuration details from the + * device's current state. Emit a series of queries which + * update internal knowledge. + */ + ret = SR_OK; + ret |= jds6600_get_chans_enable(sdi); + for (idx = 0; idx < device->channel_count_gen; idx++) { + ret |= jds6600_get_waveform(sdi, idx); + ret |= jds6600_get_frequency(sdi, idx); + ret |= jds6600_get_amplitude(sdi, idx); + ret |= jds6600_get_offset(sdi, idx); + ret |= jds6600_get_dutycycle(sdi, idx); + } + ret |= jds6600_get_phase_chans(sdi); + ret |= jds6600_get_chans_enable(sdi); + if (ret != SR_OK) + return SR_ERR_DATA; + +#if WITH_ARBWAVE_DOWNLOAD + /* + * Development HACK, to see how waveform upload works. + * How to forward the data to the application? Or the + * sigrok session actually? Provide these as acquisition + * results? + */ + ret |= jds6600_get_arb_waveform(sdi, 13); + if (ret != SR_OK) + return SR_ERR_DATA; + ret |= jds6600_set_arb_waveform(sdi, 12); + ret |= jds6600_set_arb_waveform(sdi, 13); + if (ret != SR_OK) + return SR_ERR_DATA; +#endif - return TRUE; + return SR_OK; } diff --git a/src/hardware/juntek-jds6600/protocol.h b/src/hardware/juntek-jds6600/protocol.h index ab5dff7d..1a7a1469 100644 --- a/src/hardware/juntek-jds6600/protocol.h +++ b/src/hardware/juntek-jds6600/protocol.h @@ -20,16 +20,59 @@ #ifndef LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H #define LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H -#include #include #include +#include + #include "libsigrok-internal.h" #define LOG_PREFIX "juntek-jds6600" +#define MAX_GEN_CHANNELS 2 + struct dev_context { + struct devc_dev { + unsigned int device_type; + char *serial_number; + uint64_t max_output_frequency; + size_t channel_count_gen; + } device; + struct devc_wave { + size_t builtin_count; + size_t arbitrary_count; + size_t names_count; + const char **names; + uint32_t *fw_codes; + } waveforms; + struct devc_chan { + gboolean enabled; + uint32_t waveform_code; + size_t waveform_index; + double output_frequency; + double amplitude, offset; + double dutycycle; + } channel_config[MAX_GEN_CHANNELS]; + double channels_phase; + GString *quick_req; }; -SR_PRIV int juntek_jds6600_receive_data(int fd, int revents, void *cb_data); +SR_PRIV int jds6600_identify(struct sr_dev_inst *sdi); +SR_PRIV int jds6600_setup_devc(struct sr_dev_inst *sdi); + +SR_PRIV int jds6600_get_chans_enable(const struct sr_dev_inst *sdi); +SR_PRIV int jds6600_get_waveform(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_get_frequency(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_get_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_get_offset(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_get_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_get_phase_chans(const struct sr_dev_inst *sdi); + +SR_PRIV int jds6600_set_chans_enable(const struct sr_dev_inst *sdi); +SR_PRIV int jds6600_set_waveform(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_set_frequency(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_set_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_set_offset(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_set_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx); +SR_PRIV int jds6600_set_phase_chans(const struct sr_dev_inst *sdi); #endif