From: Timo Kokkonen Date: Sat, 30 May 2020 23:03:26 +0000 (-0700) Subject: itech-it8500: ITECH IT8500 series DC electronic load driver. X-Git-Url: http://sigrok.org/gitweb/?p=libsigrok.git;a=commitdiff_plain;h=f21b6983e8e2d3b03eb518dbec3fe35cf0211d18 itech-it8500: ITECH IT8500 series DC electronic load driver. This driver supports ITECH IT8500 series electronic loads: IT8511+, IT8511A+, IT8512+, IT8512A+, IT8512B+, IT8512C+, IT8512H+, IT8513A+, IT8513B+, IT8513C+, IT8514C+, IT8514B+, IT8516C+ Additionally BK Precision 8500 series loads (models 8500, 8502, 8510, 8512, 8514, 8518, 8520, 8522, 8524 & 8526) should work as well. As ITECH is the OEM manufacturer for these BK Brecision models. --- diff --git a/configure.ac b/configure.ac index f52d7509..ee3481c4 100644 --- a/configure.ac +++ b/configure.ac @@ -277,7 +277,7 @@ SR_DRIVER([Hung-Chang DSO-2100], [hung-chang-dso-2100], [libieee1284]) 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]) +SR_DRIVER([ITECH IT8500], [itech-it8500], [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/itech-it8500/api.c b/src/hardware/itech-it8500/api.c index 3424366c..a9377262 100644 --- a/src/hardware/itech-it8500/api.c +++ b/src/hardware/itech-it8500/api.c @@ -18,130 +18,711 @@ */ #include +#include #include "protocol.h" -static struct sr_dev_driver itech_it8500_driver_info; +#define MIN_SAMPLE_RATE SR_HZ(1) +#define MAX_SAMPLE_RATE SR_HZ(60) +#define DEFAULT_SAMPLE_RATE SR_HZ(10) -static GSList *scan(struct sr_dev_driver *di, GSList *options) -{ - struct drv_context *drvc; - GSList *devices; - - (void)options; +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, +}; - devices = NULL; - drvc = di->context; - drvc->instances = NULL; +static const uint32_t drvopts[] = { + SR_CONF_ELECTRONIC_LOAD, +}; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ +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_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; - return devices; -} +static const uint32_t devopts_cg[] = { + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_REGULATION | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + 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_POWER | SR_CONF_GET, + SR_CONF_POWER_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_RESISTANCE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED | SR_CONF_GET, + SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OVER_CURRENT_PROTECTION_ENABLED | SR_CONF_GET, + SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_UNDER_VOLTAGE_CONDITION | SR_CONF_GET, + SR_CONF_UNDER_VOLTAGE_CONDITION_ACTIVE | SR_CONF_GET, + SR_CONF_UNDER_VOLTAGE_CONDITION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OVER_TEMPERATURE_PROTECTION | SR_CONF_GET, + SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE | SR_CONF_GET, +}; -static int dev_open(struct sr_dev_inst *sdi) -{ - (void)sdi; +static const uint64_t samplerates[] = { + SR_HZ(1), + SR_HZ(2), + SR_HZ(5), + SR_HZ(10), + SR_HZ(15), + SR_HZ(20), + SR_HZ(30), + SR_HZ(40), + SR_HZ(50), + SR_HZ(60), +}; - /* TODO: get handle from sdi->conn and open it. */ +static const char *default_serial_parameters[] = { + "9600/8n1", /* Factory default. */ + "38400/8n1", + "19200/8n1", + "4800/8n1", + NULL, +}; - return SR_OK; -} +static struct sr_dev_driver itech_it8500_driver_info; -static int dev_close(struct sr_dev_inst *sdi) +static GSList *scan(struct sr_dev_driver *di, GSList *options) { - (void)sdi; + struct sr_dev_inst *sdi; + struct sr_config *conf; + struct sr_serial_dev_inst *serial; + struct sr_channel_group *cg; + struct sr_channel *ch; + struct dev_context *devc; + const char *custom_serial_parameters[2]; + const char **serial_parameters; + const char *conn, *serialcomm; + GSList *l; + struct itech_it8500_cmd_packet *cmd, *response; + uint8_t fw_major, fw_minor; + const uint8_t *p; + char *unit_model, *unit_serial, *unit_barcode; + double max_i, max_v, min_v, max_p, max_r, min_r; + uint64_t max_samplerate; + + size_t u, i; + int ret; + + cmd = g_malloc0(sizeof(*cmd)); + devc = g_malloc0(sizeof(*devc)); + sdi = g_malloc0(sizeof(*sdi)); + if (!cmd || !devc || !sdi) + return NULL; + + serial = NULL; + response = NULL; + unit_model = NULL; + unit_serial = NULL; + + /* + * Use a list of typical parameters for serial communication by + * default. Prefer user specified parameters when available. + * Lack of a user specified serial port is fatal. + */ + conn = NULL; + serialcomm = NULL; + serial_parameters = default_serial_parameters; + for (l = options; l; l = l->next) { + conf = l->data; + switch (conf->key) { + case SR_CONF_CONN: + conn = g_variant_get_string(conf->data, NULL); + break; + case SR_CONF_SERIALCOMM: + serialcomm = g_variant_get_string(conf->data, NULL); + custom_serial_parameters[0] = serialcomm; + custom_serial_parameters[1] = NULL; + serial_parameters = custom_serial_parameters; + break; + } + } + if (!conn) + goto error; + + /* + * Try different serial parameters in the list + * until we get a response (or none at all). + */ + sr_info("Probing serial port: %s", conn); + for (i = 0; (serialcomm = serial_parameters[i]); i++) { + serial = sr_serial_dev_inst_new(conn, serialcomm); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) + goto error; + serial_flush(serial); + + cmd->address = 0xff; /* Use "broadcast" address. */ + cmd->command = CMD_GET_MODEL_INFO; + if (itech_it8500_send_cmd(serial, cmd, &response) == SR_OK) + break; + + serial_close(serial); + sr_serial_dev_inst_free(serial); + serial = NULL; + } + if (!serialcomm) + goto error; + + /* + * The "dense" response string consists of several fields. Grab + * integer data before putting terminators in their place to + * grab text strings afterwards. Order is important here. + */ + devc->address = response->address; + fw_major = response->data[6]; + fw_minor = response->data[5]; + response->data[5] = 0; + unit_model = g_strdup((const char *)&response->data[0]); + response->data[17] = 0; + unit_serial = g_strdup((const char *)&response->data[7]); + sr_info("Model name: %s (v%x.%02x)", unit_model, fw_major, fw_minor); + sr_info("Address: %d", devc->address); + sr_info("Serial number: %s", unit_serial); + + sdi->status = SR_ST_INACTIVE; + sdi->conn = serial; + sdi->inst_type = SR_INST_SERIAL; + sdi->driver = &itech_it8500_driver_info; + sdi->priv = devc; + g_mutex_init(&devc->mutex); + + /* + * Calculate maxium "safe" sample rate based on serial connection + * speed / bitrate. + */ + max_samplerate = serial->comm_params.bit_rate * 15 / 9600; + if (max_samplerate < 15) + max_samplerate = 10; + if (max_samplerate > MAX_SAMPLE_RATE) + max_samplerate = MAX_SAMPLE_RATE; + devc->max_sample_rate_idx = 0; + for (u = 0; u < ARRAY_SIZE(samplerates); u++) { + if (samplerates[u] > max_samplerate) + break; + devc->max_sample_rate_idx = u; + } + devc->sample_rate = DEFAULT_SAMPLE_RATE; + + /* + * Get full serial number (barcode). + */ + cmd->address = devc->address; + cmd->command = CMD_GET_BARCODE_INFO; + if (itech_it8500_send_cmd(serial, cmd, &response) == SR_OK) { + unit_barcode = g_malloc0(IT8500_DATA_LEN + 1); + memcpy(unit_barcode, response->data, IT8500_DATA_LEN); + sr_info("Barcode: %s", response->data); + g_free(unit_barcode); + } - /* TODO: get handle from sdi->conn and close it. */ + /* + * Query unit capabilities. + */ + cmd->command = CMD_GET_LOAD_LIMITS; + if (itech_it8500_send_cmd(serial, cmd, &response) != SR_OK) + goto error; + p = response->data; + max_i = read_u32le_inc(&p) / 10000.0; + max_v = read_u32le_inc(&p) / 1000.0; + min_v = read_u32le_inc(&p) / 1000.0; + max_p = read_u32le_inc(&p) / 1000.0; + max_r = read_u32le_inc(&p) / 1000.0; + min_r = read_u16le_inc(&p) / 1000.0; + sr_info("Max current: %.0f A", max_i); + sr_info("Max power: %.0f W", max_p); + sr_info("Voltage range: %.1f - %.1f V", min_v, max_v); + sr_info("Resistance range: %.2f - %.2f Ohm", min_r, max_r); + + /* + * Get current status of the unit. + */ + if ((ret = itech_it8500_get_status(sdi)) != SR_OK) { + sr_err("Failed to get unit status: %d", ret); + goto error; + } + sr_info("Mode: %s", itech_it8500_mode_to_string(devc->mode)); + sr_info("State: %s", devc->load_on ? "ON" : "OFF"); + sr_info("Default sample rate: %" PRIu64 " Hz", devc->sample_rate); + sr_info("Maximum sample rate: %" PRIu64 " Hz", + samplerates[devc->max_sample_rate_idx]); + + /* + * Populate data structures. + */ + + devc->fw_ver_major = fw_major; + devc->fw_ver_minor = fw_minor; + snprintf(devc->model, sizeof(devc->model), "%s", unit_model); + devc->max_current = max_i; + devc->min_voltage = min_v; + devc->max_voltage = max_v; + devc->max_power = max_p; + devc->min_resistance = min_r; + devc->max_resistance = max_r; + + sdi->vendor = g_strdup("ITECH"); + sdi->model = unit_model; + sdi->version = g_strdup_printf("%x.%02x", fw_major, fw_minor); + sdi->serial_num = unit_serial; + + cg = g_malloc0(sizeof(*cg)); + cg->name = g_strdup("1"); + sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + ch = sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V1"); + cg->channels = g_slist_append(cg->channels, ch); + ch = sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I1"); + cg->channels = g_slist_append(cg->channels, ch); + ch = sr_channel_new(sdi, 2, SR_CHANNEL_ANALOG, TRUE, "P1"); + cg->channels = g_slist_append(cg->channels, ch); + + g_free(cmd); + g_free(response); + serial_close(serial); + + return std_scan_complete(di, g_slist_append(NULL, sdi)); + +error: + g_free(cmd); + g_free(devc); + g_free(sdi); + g_free(response); + g_free(unit_model); + g_free(unit_serial); + if (serial) { + serial_close(serial); + sr_serial_dev_inst_free(serial); + } - return SR_OK; + return NULL; } static int config_get(uint32_t key, GVariant **data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; + const struct sr_key_info *kinfo; + const char *mode; + int ret, ival; + gboolean bval; - (void)sdi; - (void)data; (void)cg; + if (!data || !sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + kinfo = sr_key_info_get(SR_KEY_CONFIG, key); ret = SR_OK; + switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_MSEC: + ret = sr_sw_limits_config_get(&devc->limits, key, data); + break; + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(devc->sample_rate); + break; + case SR_CONF_ENABLED: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) + *data = g_variant_new_boolean(devc->load_on); + break; + case SR_CONF_REGULATION: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) { + mode = itech_it8500_mode_to_string(devc->mode); + *data = g_variant_new_string(mode); + } + break; + case SR_CONF_VOLTAGE: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) + *data = g_variant_new_double(devc->voltage); + break; + case SR_CONF_VOLTAGE_TARGET: + ret = itech_it8500_get_int(sdi, CMD_GET_CV_VOLTAGE, &ival); + if (ret == SR_OK) + *data = g_variant_new_double((double)ival / 1000.0); + break; + case SR_CONF_CURRENT: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) + *data = g_variant_new_double(devc->current); + break; + case SR_CONF_CURRENT_LIMIT: + ret = itech_it8500_get_int(sdi, CMD_GET_CC_CURRENT, &ival); + if (ret == SR_OK) + *data = g_variant_new_double((double)ival / 10000.0); + break; + case SR_CONF_POWER: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) + *data = g_variant_new_double(devc->power); + break; + case SR_CONF_POWER_TARGET: + ret = itech_it8500_get_int(sdi, CMD_GET_CW_POWER, &ival); + if (ret == SR_OK) + *data = g_variant_new_double((double)ival / 1000.0); + break; + case SR_CONF_RESISTANCE_TARGET: + ret = itech_it8500_get_int(sdi, CMD_GET_CR_RESISTANCE, &ival); + if (ret == SR_OK) + *data = g_variant_new_double((double)ival / 1000.0); + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED: + *data = g_variant_new_boolean(TRUE); + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) { + bval = devc->demand_state & DS_OV_FLAG; + *data = g_variant_new_boolean(bval); + } + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD: + ret = itech_it8500_get_int(sdi, CMD_GET_MAX_VOLTAGE, &ival); + if (ret == SR_OK) + *data = g_variant_new_double((double)ival / 1000.0); + break; + case SR_CONF_OVER_CURRENT_PROTECTION_ENABLED: + *data = g_variant_new_boolean(TRUE); + break; + case SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) { + bval = devc->demand_state & DS_OC_FLAG; + *data = g_variant_new_boolean(bval); + } + break; + case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD: + ret = itech_it8500_get_int(sdi, CMD_GET_MAX_CURRENT, &ival); + if (ret == SR_OK) + *data = g_variant_new_double((double)ival / 10000.0); + break; + case SR_CONF_OVER_TEMPERATURE_PROTECTION: + *data = g_variant_new_boolean(TRUE); + break; + case SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE: + ret = itech_it8500_get_status(sdi); + if (ret == SR_OK) { + bval = devc->demand_state & DS_OT_FLAG; + *data = g_variant_new_boolean(bval); + } + break; + /* Hardware doesn't support under voltage reporting. */ + case SR_CONF_UNDER_VOLTAGE_CONDITION: + case SR_CONF_UNDER_VOLTAGE_CONDITION_ACTIVE: + *data = g_variant_new_boolean(FALSE); + break; + case SR_CONF_UNDER_VOLTAGE_CONDITION_THRESHOLD: + *data = g_variant_new_double(0.0); + break; default: - return SR_ERR_NA; + sr_dbg("%s: Unsupported key: %u (%s)", __func__, key, + kinfo ? kinfo->name : "unknown"); + ret = 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) + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; + struct itech_it8500_cmd_packet *cmd, *response; + const struct sr_key_info *kinfo; + enum itech_it8500_modes mode; + int ret, ivalue; + uint64_t new_sr; + const char *s; - (void)sdi; - (void)data; (void)cg; + if (!data || !sdi) + return SR_ERR_ARG; + + cmd = g_malloc0(sizeof(*cmd)); + if (!cmd) + return SR_ERR_MALLOC; + + devc = sdi->priv; + response = NULL; ret = SR_OK; + + kinfo = sr_key_info_get(SR_KEY_CONFIG, key); + switch (key) { - /* TODO */ + case SR_CONF_LIMIT_MSEC: + case SR_CONF_LIMIT_SAMPLES: + ret = sr_sw_limits_config_set(&devc->limits, key, data); + goto done; + case SR_CONF_SAMPLERATE: + new_sr = g_variant_get_uint64(data); + if (new_sr < MIN_SAMPLE_RATE || + new_sr > samplerates[devc->max_sample_rate_idx]) { + ret = SR_ERR_SAMPLERATE; + goto done; + } + devc->sample_rate = new_sr; + goto done; + case SR_CONF_ENABLED: + cmd->command = CMD_LOAD_ON_OFF; + cmd->data[0] = g_variant_get_boolean(data); + break; + case SR_CONF_REGULATION: + cmd->command = CMD_SET_MODE; + s = g_variant_get_string(data, NULL); + if (itech_it8500_string_to_mode(s, &mode) != SR_OK) { + ret = SR_ERR_ARG; + goto done; + } + cmd->data[0] = mode; + break; + case SR_CONF_VOLTAGE_TARGET: + cmd->command = CMD_SET_CV_VOLTAGE; + ivalue = g_variant_get_double(data) * 1000.0; + WL32(&cmd->data[0], ivalue); + break; + case SR_CONF_CURRENT_LIMIT: + cmd->command = CMD_SET_CC_CURRENT; + ivalue = g_variant_get_double(data) * 10000.0; + WL32(&cmd->data[0], ivalue); + break; + case SR_CONF_POWER_TARGET: + cmd->command = CMD_SET_CW_POWER; + ivalue = g_variant_get_double(data) * 1000.0; + WL32(&cmd->data[0], ivalue); + break; + case SR_CONF_RESISTANCE_TARGET: + cmd->command = CMD_SET_CR_RESISTANCE; + ivalue = g_variant_get_double(data) * 1000.0; + WL32(&cmd->data[0], ivalue); + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD: + cmd->command = CMD_SET_MAX_VOLTAGE; + ivalue = g_variant_get_double(data) * 1000.0; + WL32(&cmd->data[0], ivalue); + break; + case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD: + cmd->command = CMD_SET_MAX_CURRENT; + ivalue = g_variant_get_double(data) * 10000.0; + WL32(&cmd->data[0], ivalue); + break; + default: + sr_dbg("%s: Unsupported key: %u (%s)", __func__, key, + kinfo ? kinfo->name : "unknown"); ret = SR_ERR_NA; + goto done; } + cmd->address = devc->address; + ret = itech_it8500_cmd(sdi, cmd, &response); + +done: + g_free(cmd); + g_free(response); + return ret; } static int config_list(uint32_t key, GVariant **data, - const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { - int ret; + const struct dev_context *devc; + const struct sr_key_info *kinfo; + GVariantBuilder *b; - (void)sdi; - (void)data; - (void)cg; + devc = sdi ? sdi->priv : NULL; + if (!data) + return SR_ERR_ARG; + + if (!cg) + return STD_CONFIG_LIST(key, data, sdi, cg, + scanopts, drvopts, devopts); + + kinfo = sr_key_info_get(SR_KEY_CONFIG, key); - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_DEVICE_OPTIONS: + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg)); + break; + case SR_CONF_SAMPLERATE: + *data = std_gvar_samplerates_steps(samplerates, + 1 + devc->max_sample_rate_idx); + break; + case SR_CONF_REGULATION: + b = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(b, "s", itech_it8500_mode_to_string(CC)); + g_variant_builder_add(b, "s", itech_it8500_mode_to_string(CV)); + g_variant_builder_add(b, "s", itech_it8500_mode_to_string(CW)); + g_variant_builder_add(b, "s", itech_it8500_mode_to_string(CR)); + *data = g_variant_new("as", b); + g_variant_builder_unref(b); + break; + case SR_CONF_VOLTAGE_TARGET: + if (!devc) + return SR_ERR_ARG; + *data = std_gvar_min_max_step(devc->min_voltage, + devc->max_voltage, 0.01); + break; + case SR_CONF_CURRENT_LIMIT: + if (!devc) + return SR_ERR_ARG; + *data = std_gvar_min_max_step(0.0, devc->max_current, 0.001); + break; + case SR_CONF_POWER_TARGET: + if (!devc) + return SR_ERR_ARG; + *data = std_gvar_min_max_step(0.0, devc->max_power, 0.01); + break; + case SR_CONF_RESISTANCE_TARGET: + if (!devc) + return SR_ERR_ARG; + *data = std_gvar_min_max_step(devc->min_resistance, + devc->max_resistance, 0.01); + break; + default: + sr_dbg("%s: Unsupported key: %u (%s)", __func__, key, + kinfo ? kinfo->name : "unknown"); return SR_ERR_NA; } - return ret; + return SR_OK; } static int dev_acquisition_start(const struct sr_dev_inst *sdi) { - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + int ret; - (void)sdi; + if (!sdi) + return SR_ERR_ARG; - return SR_OK; + devc = sdi->priv; + serial = sdi->conn; + + ret = serial_source_add(sdi->session, serial, G_IO_IN, + (1000.0 / devc->sample_rate), + itech_it8500_receive_data, (void *)sdi); + if (ret == SR_OK) { + sr_sw_limits_acquisition_start(&devc->limits); + std_session_send_df_header(sdi); + } + + return ret; } static int dev_acquisition_stop(struct sr_dev_inst *sdi) { - /* TODO: stop acquisition. */ + struct sr_serial_dev_inst *serial; + + if (!sdi) + return SR_ERR_ARG; - (void)sdi; + serial = sdi->conn; + + std_session_send_df_end(sdi); + serial_source_remove(sdi->session, serial); return SR_OK; } +static int dev_open(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct itech_it8500_cmd_packet *cmd, *response; + int ret, res; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + ret = std_serial_dev_open(sdi); + if (ret == SR_OK) { + /* Request the unit to enter remote control mode. */ + response = NULL; + cmd = g_malloc0(sizeof(*cmd)); + if (cmd) { + cmd->address = devc->address; + cmd->command = CMD_SET_REMOTE_MODE; + cmd->data[0] = 1; + res = itech_it8500_cmd(sdi, cmd, &response); + if (res != SR_OK) + sr_dbg("Failed to set unit to remote mode"); + g_free(cmd); + g_free(response); + } + } + + return ret; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct itech_it8500_cmd_packet *cmd, *response; + int ret; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + response = NULL; + cmd = g_malloc0(sizeof(*cmd)); + if (cmd) { + /* Request the unit to enter local control mode. */ + cmd->address = devc->address; + cmd->command = CMD_SET_REMOTE_MODE; + cmd->data[0] = 0; + ret = itech_it8500_cmd(sdi, cmd, &response); + if (ret != SR_OK) + sr_dbg("Failed to set unit back to local mode: %d", + ret); + } + + g_free(cmd); + g_free(response); + + return std_serial_dev_close(sdi); +} + +static void dev_clear_callback(void *priv) +{ + struct dev_context *devc; + + if (!priv) + return; + + devc = priv; + g_mutex_clear(&devc->mutex); +} + +static int dev_clear(const struct sr_dev_driver *di) +{ + return std_dev_clear_with_callback(di, dev_clear_callback); +} + static struct sr_dev_driver itech_it8500_driver_info = { .name = "itech-it8500", - .longname = "ITECH IT8500", + .longname = "ITECH IT8500 series", .api_version = 1, .init = std_init, .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, diff --git a/src/hardware/itech-it8500/protocol.c b/src/hardware/itech-it8500/protocol.c index 5b471bbe..825860a7 100644 --- a/src/hardware/itech-it8500/protocol.c +++ b/src/hardware/itech-it8500/protocol.c @@ -18,14 +18,363 @@ */ #include +#include #include "protocol.h" +SR_PRIV uint8_t itech_it8500_checksum(const uint8_t *packet) +{ + const uint8_t *p; + uint8_t checksum; + size_t i; + + if (!packet) + return 0xff; + + checksum = 0; + p = packet; + for (i = 0; i < IT8500_PACKET_LEN - 1; i++) + checksum += *p++; + + return checksum; +} + +SR_PRIV const char *itech_it8500_mode_to_string(enum itech_it8500_modes mode) +{ + switch (mode) { + case CC: + return "CC"; + case CV: + return "CV"; + case CW: + return "CW"; + case CR: + return "CR"; + default: + return "Unknown"; + } +} + +SR_PRIV int itech_it8500_string_to_mode(const char *modename, + enum itech_it8500_modes *mode) +{ + size_t i; + const char *s; + + for (i = 0; i < IT8500_MODES; i++) { + s = itech_it8500_mode_to_string(i); + if (strncmp(modename, s, strlen(s)) == 0) { + *mode = i; + return SR_OK; + } + } + + return SR_ERR; +} + +SR_PRIV int itech_it8500_send_cmd(struct sr_serial_dev_inst *serial, + struct itech_it8500_cmd_packet *cmd, + struct itech_it8500_cmd_packet **response) +{ + struct itech_it8500_cmd_packet *resp; + uint8_t *cmd_buf, *resp_buf, checksum; + int ret, read_len; + + if (!serial || !cmd || !response) + return SR_ERR_ARG; + + cmd_buf = g_malloc0(IT8500_PACKET_LEN); + resp_buf = g_malloc0(IT8500_PACKET_LEN); + resp = g_malloc0(sizeof(*resp)); + if (!cmd_buf || !resp_buf || !resp) + return SR_ERR_MALLOC; + + /* + * Construct request from: preamble, address, command, data, + * and checksum. + */ + cmd_buf[0] = IT8500_PREAMBLE; + cmd_buf[1] = cmd->address; + cmd_buf[2] = cmd->command; + memcpy(&cmd_buf[3], cmd->data, IT8500_DATA_LEN); + cmd_buf[IT8500_PACKET_LEN - 1] = itech_it8500_checksum(cmd_buf); + + sr_spew("%s: Sending command: %02x", __func__, cmd->command); + ret = serial_write_blocking(serial, cmd_buf, IT8500_PACKET_LEN, + serial_timeout(serial, IT8500_PACKET_LEN)); + if (ret < IT8500_PACKET_LEN) { + sr_dbg("%s: Error sending command 0x%02x: %d", __func__, + cmd->command, ret); + ret = SR_ERR; + goto error; + } + + ret = SR_ERR; + read_len = serial_read_blocking(serial, resp_buf, IT8500_PACKET_LEN, + 100); + if (read_len < IT8500_PACKET_LEN) { + sr_dbg("%s: Timeout waiting response to command: %d", + __func__, read_len); + goto error; + } + + if (resp_buf[0] != IT8500_PREAMBLE) { + sr_dbg("%s: Invalid packet received (first byte: %02x)", + __func__, resp_buf[0]); + goto error; + } + + checksum = itech_it8500_checksum(resp_buf); + if (resp_buf[IT8500_PACKET_LEN - 1] != checksum) { + sr_dbg("%s: Invalid packet received: checksum mismatch", + __func__); + goto error; + } + + resp->address = resp_buf[1]; + resp->command = resp_buf[2]; + memcpy(resp->data, &resp_buf[3], IT8500_DATA_LEN); + sr_spew("%s: Response packet received: cmd=%02x", __func__, + resp->command); + + if (resp->command == CMD_RESPONSE) { + if (resp->data[0] != IT8500_COMMAND_SUCCESSFUL) { + sr_dbg("%s: Command (%02x) failed: status=%02x", + __func__, cmd->command, resp->data[0]); + goto error; + } + } else { + if (resp->command != cmd->command) { + sr_dbg("%s: Invalid response received: %02x" + " (expected: %02x)", + __func__, resp->command, cmd->command); + goto error; + } + } + + if (*response) + g_free(*response); + *response = resp; + resp = NULL; + ret = SR_OK; + +error: + g_free(cmd_buf); + g_free(resp_buf); + g_free(resp); + + return ret; +} + +SR_PRIV int itech_it8500_cmd(const struct sr_dev_inst *sdi, + struct itech_it8500_cmd_packet *cmd, + struct itech_it8500_cmd_packet **response) +{ + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + int ret; + + if (!sdi) + return SR_ERR_ARG; + devc = sdi->priv; + serial = sdi->conn; + if (!devc || !serial) + return SR_ERR_NA; + + g_mutex_lock(&devc->mutex); + ret = itech_it8500_send_cmd(serial, cmd, response); + g_mutex_unlock(&devc->mutex); + + return ret; +} + +SR_PRIV void itech_it8500_status_change(const struct sr_dev_inst *sdi, + uint8_t old_os, uint8_t new_os, + uint16_t old_ds, uint16_t new_ds, + enum itech_it8500_modes old_m, enum itech_it8500_modes new_m) +{ + const char *mode; + gboolean old, new; + + /* Check it output status has changed. */ + old = old_os & OS_OUT_FLAG; + new = new_os & OS_OUT_FLAG; + if (old != new) + sr_session_send_meta(sdi, + SR_CONF_ENABLED, + g_variant_new_boolean(new)); + + /* Check if OVP status has changed. */ + old = old_ds & DS_OV_FLAG; + new = new_ds & DS_OV_FLAG; + if (old != new) + sr_session_send_meta(sdi, + SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE, + g_variant_new_boolean(new)); + + /* Check if OCP status has changed. */ + old = old_ds & DS_OC_FLAG; + new = new_ds & DS_OC_FLAG; + if (old != new) + sr_session_send_meta(sdi, + SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE, + g_variant_new_boolean(new)); + + /* Check if OTP status has changed. */ + old = old_ds & DS_OT_FLAG; + new = new_ds & DS_OT_FLAG; + if (old != new) + sr_session_send_meta(sdi, + SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE, + g_variant_new_boolean(new)); + + /* Check if operating mode has changed. */ + if (old_m != new_m) { + mode = itech_it8500_mode_to_string(new_m); + sr_session_send_meta(sdi, SR_CONF_REGULATION, + g_variant_new_string(mode)); + } +} + +SR_PRIV int itech_it8500_get_status(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct itech_it8500_cmd_packet *cmd; + struct itech_it8500_cmd_packet *resp; + double voltage, current, power; + uint8_t operation_state; + uint16_t demand_state; + enum itech_it8500_modes mode; + gboolean load_on; + const uint8_t *p; + int ret; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + if (!devc) + return SR_ERR_NA; + + cmd = g_malloc0(sizeof(*cmd)); + if (!cmd) + return SR_ERR_MALLOC; + cmd->address = devc->address; + cmd->command = CMD_GET_STATE; + resp = NULL; + + ret = itech_it8500_cmd(sdi, cmd, &resp); + if (ret == SR_OK) { + p = resp->data; + voltage = read_u32le_inc(&p) / 1000.0; + current = read_u32le_inc(&p) / 10000.0; + power = read_u32le_inc(&p) / 1000.0; + operation_state = read_u8_inc(&p); + demand_state = read_u16le_inc(&p); + + if (demand_state & DS_CC_MODE_FLAG) + mode = CC; + else if (demand_state & DS_CV_MODE_FLAG) + mode = CV; + else if (demand_state & DS_CW_MODE_FLAG) + mode = CW; + else if (demand_state & DS_CR_MODE_FLAG) + mode = CR; + else + mode = CC; + load_on = operation_state & OS_OUT_FLAG; + + sr_dbg("Load status: V=%.4f, I=%.4f, P=%.3f, State=%s, " + "Mode=%s (op=0x%02x, demand=0x%04x)", + voltage, current, power, (load_on ? "ON": "OFF"), + itech_it8500_mode_to_string(mode), + operation_state, demand_state); + + /* Check for status change only after scan() has completed. */ + if (sdi->model) { + itech_it8500_status_change(sdi, devc->operation_state, + operation_state, devc->demand_state, + demand_state, devc->mode, mode); + } + devc->voltage = voltage; + devc->current = current; + devc->power = power; + devc->operation_state = operation_state; + devc->demand_state = demand_state; + devc->mode = mode; + devc->load_on = load_on; + } + + g_free(cmd); + g_free(resp); + + return ret; +} + +SR_PRIV int itech_it8500_get_int(const struct sr_dev_inst *sdi, + enum itech_it8500_command command, int *result) +{ + struct dev_context *devc; + struct itech_it8500_cmd_packet *cmd; + struct itech_it8500_cmd_packet *resp; + int ret; + + if (!sdi || !result) + return SR_ERR_ARG; + + devc = sdi->priv; + cmd = g_malloc0(sizeof(*cmd)); + if (!cmd) + return SR_ERR_MALLOC; + cmd->address = devc->address; + cmd->command = command; + resp = NULL; + + ret = itech_it8500_cmd(sdi, cmd, &resp); + if (ret == SR_OK) + *result = RL32(&resp->data[0]); + + g_free(cmd); + g_free(resp); + + return ret; +} + +SR_PRIV void itech_it8500_channel_send_value(const struct sr_dev_inst *sdi, + struct sr_channel *ch, double value, enum sr_mq mq, + enum sr_unit unit, int digits) +{ + 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; + double val; + + val = value; + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); + analog.meaning->channels = g_slist_append(NULL, ch); + analog.num_samples = 1; + analog.data = &val; + analog.encoding->unitsize = sizeof(val); + analog.encoding->is_float = TRUE; + analog.meaning->mq = mq; + analog.meaning->unit = unit; + analog.meaning->mqflags = SR_MQFLAG_DC; + + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_session_send(sdi, &packet); + g_slist_free(analog.meaning->channels); +} + SR_PRIV int itech_it8500_receive_data(int fd, int revents, void *cb_data) { - const struct sr_dev_inst *sdi; + struct sr_dev_inst *sdi; struct dev_context *devc; + GSList *l; (void)fd; + (void)revents; if (!(sdi = cb_data)) return TRUE; @@ -33,9 +382,28 @@ SR_PRIV int itech_it8500_receive_data(int fd, int revents, void *cb_data) if (!(devc = sdi->priv)) return TRUE; - if (revents == G_IO_IN) { - /* TODO */ - } + if (itech_it8500_get_status(sdi) != SR_OK) + return TRUE; + + std_session_send_df_frame_begin(sdi); + + l = g_slist_nth(sdi->channels, 0); + itech_it8500_channel_send_value(sdi, l->data, devc->voltage, + SR_MQ_VOLTAGE, SR_UNIT_VOLT, 5); + + l = g_slist_nth(sdi->channels, 1); + itech_it8500_channel_send_value(sdi, l->data, devc->current, + SR_MQ_CURRENT, SR_UNIT_AMPERE, 5); + + l = g_slist_nth(sdi->channels, 2); + itech_it8500_channel_send_value(sdi, l->data, devc->power, + SR_MQ_POWER, SR_UNIT_WATT, 5); + + std_session_send_df_frame_end(sdi); + + sr_sw_limits_update_samples_read(&devc->limits, 1); + if (sr_sw_limits_check(&devc->limits)) + sr_dev_acquisition_stop(sdi); return TRUE; } diff --git a/src/hardware/itech-it8500/protocol.h b/src/hardware/itech-it8500/protocol.h index 3df079c4..b3b4ce3e 100644 --- a/src/hardware/itech-it8500/protocol.h +++ b/src/hardware/itech-it8500/protocol.h @@ -27,9 +27,185 @@ #define LOG_PREFIX "itech-it8500" +/* + * Unit uses 26 byte binary packets for communications. + * Packets have fixed format: + * + * Offset|Length|Description + * ------|------|------------------------------------- + * 0 | 1 | Preable (always set to 0xAA). + * 1 | 1 | Unit Address (0-254, 255=broadcast). + * 2 | 1 | Command number. + * 3 | 22 | Variable data. + * 25 | 1 | Parity code (checksum). + */ +#define IT8500_HEADER_LEN 3 +#define IT8500_DATA_LEN 22 +#define IT8500_PACKET_LEN (IT8500_HEADER_LEN + IT8500_DATA_LEN + 1) + +#define IT8500_PREAMBLE 0xaa +#define IT8500_MAX_MODEL_NAME_LEN 5 + +/* Status packet status byte values. */ +#define IT8500_COMMAND_SUCCESSFUL 0x80 +#define IT8500_INVALID_CHECKSUM 0x90 +#define IT8500_INVALID_PARAMETER 0xa0 +#define IT8500_UNKNOWN_COMMAND 0xb0 +#define IT8500_INVALID_COMMAND 0xc0 + +/* + * Operating modes. + * Note! These map directly to mode numbers used in CMD_SET_MODE + * and CMD_GET_MODE commands, so values are manually defined below. + */ +enum itech_it8500_modes { + CC = 0, + CV = 1, + CW = 2, + CR = 3, + IT8500_MODES, /* Total count, for internal use. */ +}; + +enum itech_it8500_command { + CMD_GET_LOAD_LIMITS = 0x01, + CMD_SET_HW_OPP_VALUE = 0x02, + CMD_GET_HW_OPP_VALUE = 0x03, + CMD_SET_VON_MODE = 0x0e, + CMD_GET_VON_MODE = 0x0f, + CMD_SET_VON_VALUE = 0x10, + CMD_GET_VON_VALUE = 0x11, + CMD_RESPONSE = 0x12, /* Response to commands not returning any data. */ + CMD_SET_REMOTE_MODE = 0x20, + CMD_LOAD_ON_OFF = 0x21, + CMD_SET_MAX_VOLTAGE = 0x22, + CMD_GET_MAX_VOLTAGE = 0x23, + CMD_SET_MAX_CURRENT = 0x24, + CMD_GET_MAX_CURRENT = 0x25, + CMD_SET_MAX_POWER = 0x26, + CMD_GET_MAX_POWER = 0x27, + CMD_SET_MODE = 0x28, + CMD_GET_MODE = 0x29, + CMD_SET_CC_CURRENT = 0x2a, + CMD_GET_CC_CURRENT = 0x2b, + CMD_SET_CV_VOLTAGE = 0x2c, + CMD_GET_CV_VOLTAGE = 0x2d, + CMD_SET_CW_POWER = 0x2e, + CMD_GET_CW_POWER = 0x2f, + CMD_SET_CR_RESISTANCE = 0x30, + CMD_GET_CR_RESISTANCE = 0x31, + CMD_SET_BATTERY_MIN_VOLTAGE = 0x4e, + CMD_GET_BATTERY_MIN_VOLTAGE = 0x4f, + CMD_SET_LOAD_ON_TIMER = 0x50, + CMD_GET_LOAD_ON_TIMER = 0x51, + CMD_LOAD_ON_TIMER = 0x52, + CMD_LOAD_ON_TIME_STATUS = 0x53, + CMD_SET_ADDRESS = 0x54, + CMD_LOCAL_CONTROL = 0x55, + CMD_REMOTE_SENSING = 0x56, + CMD_REMOTE_SENSING_STATUS = 0x57, + CMD_SET_TRIGGER_SOURCE = 0x58, + CMD_GET_TRIGGER_SOURCE = 0x59, + CMD_TRIGGER = 0x5a, + CMD_SAVE_SETTINGS = 0x5b, + CMD_LOAD_SETTINGS = 0x5c, + CMD_SET_FUNCTION = 0x5d, + CMD_GET_FUNCTION = 0x5e, + CMD_GET_STATE = 0x5f, + CMD_GET_MODEL_INFO = 0x6a, + CMD_GET_BARCODE_INFO = 0x6b, + CMD_SET_OCP_VALUE = 0x80, + CMD_GET_OCP_VALUE = 0x81, + CMD_SET_OCP_DELAY = 0x82, + CMD_GET_OCP_DELAY = 0x83, + CMD_ENABLE_OCP = 0x84, + CMD_DISABLE_OCP = 0x85, + CMD_SET_OPP_VALUE = 0x86, + CMD_GET_OPP_VALUE = 0x87, + CMD_SET_OPP_DELAY = 0x88, + CMD_GET_OPP_DELAY = 0x89, +}; + +/* + * Data structure to track commands and reponses. + */ +struct itech_it8500_cmd_packet { + uint8_t command; /* Command number. */ + uint8_t address; /* Unit address: 0..254 (255 = broadcast). */ + uint8_t data[IT8500_DATA_LEN]; /* Command/Response data. */ +}; + +/* + * "Operation state" register flags. + */ +#define OS_CAL_FLAG 0x01 +#define OS_WTG_FLAG 0x02 +#define OS_REM_FLAG 0x04 +#define OS_OUT_FLAG 0x08 +#define OS_LOCAL_FLAG 0x10 +#define OS_SENSE_FLAG 0x20 +#define OS_LOT_FLAG 0x40 + +/* + * "Demand state" register flags. + */ +#define DS_RV_FLAG 0x0001 +#define DS_OV_FLAG 0x0002 +#define DS_OC_FLAG 0x0004 +#define DS_OP_FLAG 0x0008 +#define DS_OT_FLAG 0x0010 +#define DS_SV_FLAG 0x0020 +#define DS_CC_MODE_FLAG 0x0040 +#define DS_CV_MODE_FLAG 0x0080 +#define DS_CW_MODE_FLAG 0x0100 +#define DS_CR_MODE_FLAG 0x0200 + struct dev_context { + char model[IT8500_MAX_MODEL_NAME_LEN + 1]; + uint8_t fw_ver_major; + uint8_t fw_ver_minor; + uint8_t address; + double max_current; + double min_voltage; + double max_voltage; + double max_power; + double min_resistance; + double max_resistance; + size_t max_sample_rate_idx; + + double voltage; + double current; + double power; + uint8_t operation_state; + uint16_t demand_state; + enum itech_it8500_modes mode; + gboolean load_on; + + uint64_t sample_rate; + struct sr_sw_limits limits; + + GMutex mutex; }; +SR_PRIV uint8_t itech_it8500_checksum(const uint8_t *packet); +SR_PRIV const char *itech_it8500_mode_to_string(enum itech_it8500_modes mode); +SR_PRIV int itech_it8500_string_to_mode(const char *modename, + enum itech_it8500_modes *mode); +SR_PRIV int itech_it8500_send_cmd(struct sr_serial_dev_inst *serial, + struct itech_it8500_cmd_packet *cmd, + struct itech_it8500_cmd_packet **response); +SR_PRIV int itech_it8500_cmd(const struct sr_dev_inst *sdi, + struct itech_it8500_cmd_packet *cmd, + struct itech_it8500_cmd_packet **response); +SR_PRIV void itech_it8500_status_change(const struct sr_dev_inst *sdi, + uint8_t old_op, uint8_t new_op, + uint16_t old_de, uint16_t new_de, + enum itech_it8500_modes old_m, enum itech_it8500_modes new_m); +SR_PRIV int itech_it8500_get_status(const struct sr_dev_inst *sdi); +SR_PRIV int itech_it8500_get_int(const struct sr_dev_inst *sdi, + enum itech_it8500_command command, int *result); +SR_PRIV void itech_it8500_channel_send_value(const struct sr_dev_inst *sdi, + struct sr_channel *ch, double value, enum sr_mq mq, + enum sr_unit unit, int digits); SR_PRIV int itech_it8500_receive_data(int fd, int revents, void *cb_data); #endif