* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <math.h>
#include <config.h>
#include "protocol.h"
static struct sr_dev_driver gwinstek_psp_driver_info;
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
+static const uint32_t scanopts[] = {
+ SR_CONF_CONN,
+ SR_CONF_SERIALCOMM,
+ SR_CONF_FORCE_DETECT,
+};
+
+static const uint32_t drvopts[] = {
+ SR_CONF_POWER_SUPPLY,
+};
+
+static const uint32_t devopts[] = {
+ SR_CONF_CONN | SR_CONF_GET,
+ 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_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_OVER_TEMPERATURE_PROTECTION_ACTIVE | SR_CONF_GET,
+ SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
+/* Voltage and current ranges. Values are: Min, max, step. */
+static const double volts_20[] = { 0, 20, 0.01 };
+static const double volts_40[] = { 0, 40, 0.01 };
+static const double volts_60[] = { 0, 60, 0.01 };
+static const double amps_3_5[] = { 0, 3.5, 0.01 };
+static const double amps_5[] = { 0, 5, 0.01 };
+static const double amps_10[] = { 0, 10, 0.01 };
+
+static const struct gwinstek_psp_model models[] = {
+ { "GW Instek", "PSP-603", volts_60, amps_3_5 },
+ { "GW Instek", "PSP-405", volts_40, amps_5 },
+ { "GW Instek", "PSP-2010", volts_20, amps_10 }
+};
+
+static const struct gwinstek_psp_model *model_lookup(const char *id_text)
{
- struct drv_context *drvc;
- GSList *devices;
+ size_t idx;
+ const struct gwinstek_psp_model *check;
- (void)options;
+ if (!id_text || !*id_text)
+ return NULL;
- devices = NULL;
- drvc = di->context;
- drvc->instances = NULL;
+ for (idx = 0; idx < ARRAY_SIZE(models); idx++) {
+ check = &models[idx];
+ if (!check->name || !check->name[0])
+ continue;
+ if (g_ascii_strncasecmp(id_text, check->name, strlen(check->name)) != 0)
+ continue;
- /* TODO: scan for devices, either based on a SR_CONF_CONN option
- * or on a USB scan. */
+ return check;
+ }
+ sr_dbg("Could not find a matching model for: [%s].", id_text);
- return devices;
+ return NULL;
}
-static int dev_open(struct sr_dev_inst *sdi)
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
{
- (void)sdi;
+ struct sr_config *src;
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ const char *conn, *serialcomm;
+ const char *force_detect;
+ const struct gwinstek_psp_model *model;
+ GSList *l;
+
+ conn = NULL;
+ serialcomm = NULL;
+ force_detect = NULL;
+
+ for (l = options; l; l = l->next) {
+ src = l->data;
+ switch (src->key) {
+ case SR_CONF_CONN:
+ conn = g_variant_get_string(src->data, NULL);
+ break;
+ case SR_CONF_SERIALCOMM:
+ serialcomm = g_variant_get_string(src->data, NULL);
+ break;
+ case SR_CONF_FORCE_DETECT:
+ force_detect = g_variant_get_string(src->data, NULL);
+ break;
+ default:
+ sr_err("Unknown option %d, skipping.", src->key);
+ break;
+ }
+ }
- /* TODO: get handle from sdi->conn and open it. */
+ if (!conn)
+ return NULL;
+ if (!serialcomm)
+ serialcomm = "2400/8n1";
+ if (!force_detect) {
+ sr_err("The gwinstek-psp driver requires the force_detect parameter");
+ return NULL;
+ }
- return SR_OK;
+ model = model_lookup(force_detect);
+
+ if (!model) {
+ sr_err("Unsupported model ID '%s', aborting.", force_detect);
+ return NULL;
+ }
+
+ serial = sr_serial_dev_inst_new(conn, serialcomm);
+ if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+ return NULL;
+
+ sdi = g_malloc0(sizeof(*sdi));
+ sdi->status = SR_ST_INACTIVE;
+ sdi->vendor = g_strdup(model->vendor);
+ sdi->model = g_strdup(model->name);
+ sdi->inst_type = SR_INST_SERIAL;
+ sdi->conn = serial;
+ sdi->connection_id = g_strdup(conn);
+
+ sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V");
+ sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I");
+
+ devc = g_malloc0(sizeof(*devc));
+ sr_sw_limits_init(&devc->limits);
+ g_mutex_init(&devc->rw_mutex);
+ devc->model = model;
+ devc->msg_terminator_len = 2;
+ sdi->priv = devc;
+
+ /* Get current status of device. */
+ if (gwinstek_psp_get_all_values(serial, devc) < 0)
+ goto exit_err;
+
+ if (gwinstek_psp_check_terminator(serial, devc) < 0)
+ goto exit_err;
+
+ if (gwinstek_psp_get_initial_voltage_target(devc) < 0)
+ goto exit_err;
+
+ serial_close(serial);
+
+ return std_scan_complete(di, g_slist_append(NULL, sdi));
+
+exit_err:
+ sr_dev_inst_free(sdi);
+ g_free(devc);
+ sr_dbg("Scan failed.");
+
+ return NULL;
}
static int dev_close(struct sr_dev_inst *sdi)
{
- (void)sdi;
+ struct dev_context *devc;
- /* TODO: get handle from sdi->conn and close it. */
+ devc = (sdi) ? sdi->priv : NULL;
+ if (devc) {
+ serial_flush(sdi->conn);
+ g_mutex_clear(&devc->rw_mutex);
+ }
- return SR_OK;
+ return std_serial_dev_close(sdi);
}
static int config_get(uint32_t key, GVariant **data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
- int ret;
+ struct dev_context *devc;
- (void)sdi;
- (void)data;
(void)cg;
- ret = SR_OK;
+ if (!sdi || !data)
+ return SR_ERR_ARG;
+
+ devc = sdi->priv;
+
+ if (key != SR_CONF_CONN && gwinstek_psp_get_all_values(sdi->conn, devc) < 0)
+ return SR_ERR;
+
switch (key) {
- /* TODO */
+ case SR_CONF_CONN:
+ *data = g_variant_new_string(sdi->connection_id);
+ break;
+ case SR_CONF_VOLTAGE:
+ *data = g_variant_new_double(devc->voltage_or_0);
+ break;
+ case SR_CONF_VOLTAGE_TARGET:
+ *data = g_variant_new_double(devc->voltage_target);
+ break;
+ case SR_CONF_CURRENT:
+ *data = g_variant_new_double(devc->current);
+ break;
+ case SR_CONF_CURRENT_LIMIT:
+ *data = g_variant_new_double(devc->current_limit);
+ break;
+ case SR_CONF_ENABLED:
+ *data = g_variant_new_boolean(devc->output_enabled);
+ break;
+ case SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE:
+ *data = g_variant_new_boolean(devc->otp_active);
+ break;
default:
return SR_ERR_NA;
}
- return ret;
+ return SR_OK;
}
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;
+ int voltage_limit;
+ double dval;
+ gboolean bval;
+ char msg[20];
- (void)sdi;
- (void)data;
(void)cg;
- ret = SR_OK;
+ devc = sdi->priv;
+
switch (key) {
- /* TODO */
+ case SR_CONF_LIMIT_MSEC:
+ case SR_CONF_LIMIT_SAMPLES:
+ return sr_sw_limits_config_set(&devc->limits, key, data);
+ case SR_CONF_VOLTAGE_TARGET:
+ dval = g_variant_get_double(data);
+ if (dval < devc->model->voltage[0] || dval > devc->model->voltage[1])
+ return SR_ERR_ARG;
+
+ /* Set voltage output limit to the next positive integer. No need
+ * to check against limits, device handles overflow correctly. */
+ voltage_limit = (int) ceil(dval);
+ if (devc->voltage_limit == voltage_limit) {
+ sr_dbg("Correct limit (%dV) already set", voltage_limit);
+ } else {
+ sr_snprintf_ascii(msg, sizeof(msg), "SU %d\r\n", voltage_limit);
+ if (gwinstek_psp_send_cmd(sdi->conn, devc, msg, TRUE) < 0)
+ return SR_ERR;
+ }
+
+ /* Set voltage output level */
+ sr_snprintf_ascii(msg, sizeof(msg), "SV %05.2f\r\n", dval);
+ if (gwinstek_psp_send_cmd(sdi->conn, devc, msg, TRUE) < 0)
+ return SR_ERR;
+ devc->set_voltage_target = dval;
+ devc->set_voltage_target_updated = g_get_monotonic_time();
+ break;
+ case SR_CONF_CURRENT_LIMIT:
+ dval = g_variant_get_double(data);
+ if (dval < devc->model->current[0] || dval > devc->model->current[1])
+ return SR_ERR_ARG;
+
+ sr_snprintf_ascii(msg, sizeof(msg), "SI %04.2f\r\n", dval);
+ if (gwinstek_psp_send_cmd(sdi->conn, devc, msg, TRUE) < 0)
+ return SR_ERR;
+ break;
+ case SR_CONF_ENABLED:
+ bval = g_variant_get_boolean(data);
+ /* Set always so it is possible turn off with sigrok-cli. */
+ if (gwinstek_psp_send_cmd(sdi->conn, devc,
+ bval ? "KOE\r\n" : "KOD\r\n", TRUE) < 0)
+ return SR_ERR;
+ break;
default:
- ret = SR_ERR_NA;
+ return SR_ERR_NA;
}
- return ret;
+ return SR_OK;
}
static int config_list(uint32_t key, GVariant **data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
- int ret;
+ struct dev_context *devc;
- (void)sdi;
- (void)data;
- (void)cg;
+ devc = (sdi) ? sdi->priv : NULL;
- ret = SR_OK;
switch (key) {
- /* TODO */
+ case SR_CONF_SCAN_OPTIONS:
+ case SR_CONF_DEVICE_OPTIONS:
+ return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+ case SR_CONF_VOLTAGE_TARGET:
+ if (!devc || !devc->model)
+ return SR_ERR_ARG;
+ *data = std_gvar_min_max_step_array(devc->model->voltage);
+ break;
+ case SR_CONF_CURRENT_LIMIT:
+ if (!devc || !devc->model)
+ return SR_ERR_ARG;
+ *data = std_gvar_min_max_step_array(devc->model->current);
+ break;
default:
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;
- (void)sdi;
+ devc = sdi->priv;
- return SR_OK;
-}
-
-static int dev_acquisition_stop(struct sr_dev_inst *sdi)
-{
- /* TODO: stop acquisition. */
+ sr_sw_limits_acquisition_start(&devc->limits);
+ std_session_send_df_header(sdi);
- (void)sdi;
+ devc->next_req_time = 0;
+ serial = sdi->conn;
+ serial_source_add(sdi->session, serial, G_IO_IN,
+ GWINSTEK_PSP_PROCESSING_TIME_MS,
+ gwinstek_psp_receive_data, (void *)sdi);
return SR_OK;
}
static struct sr_dev_driver gwinstek_psp_driver_info = {
.name = "gwinstek-psp",
- .longname = "GW Instek PSP",
+ .longname = "GW Instek PSP series",
.api_version = 1,
.init = std_init,
.cleanup = std_cleanup,
.config_get = config_get,
.config_set = config_set,
.config_list = config_list,
- .dev_open = dev_open,
+ .dev_open = std_serial_dev_open,
.dev_close = dev_close,
.dev_acquisition_start = dev_acquisition_start,
- .dev_acquisition_stop = dev_acquisition_stop,
+ .dev_acquisition_stop = std_serial_dev_acquisition_stop,
.context = NULL,
};
SR_REGISTER_DEV_DRIVER(gwinstek_psp_driver_info);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <config.h>
#include "protocol.h"
+#include <config.h>
+#include <math.h>
+
+#define CMD_ALL_QUERY "L\r\n"
+#define RESPONSE_ALL_QUERY_LEN 37
+
+static void give_device_time_to_process(struct dev_context *devc)
+{
+ int64_t sleeping_time;
+
+ if (!devc->next_req_time)
+ return;
+
+ sleeping_time = devc->next_req_time - g_get_monotonic_time();
+ if (sleeping_time > 0) {
+ g_usleep(sleeping_time);
+ sr_spew("Sleeping %" PRIi64 " us for processing", sleeping_time);
+ }
+}
+
+SR_PRIV int gwinstek_psp_send_cmd(struct sr_serial_dev_inst *serial,
+ struct dev_context *devc, const char *cmd, gboolean lock)
+{
+ int ret;
+
+ if (lock)
+ g_mutex_lock(&devc->rw_mutex);
+
+ give_device_time_to_process(devc);
+
+ sr_dbg("Sending '%s'.", cmd);
+ if ((ret = serial_write_blocking(serial, cmd, strlen(cmd), 0)) < 0) {
+ sr_err("Error sending command: %d.", ret);
+ }
+
+ devc->next_req_time =
+ g_get_monotonic_time() + GWINSTEK_PSP_PROCESSING_TIME_MS * 1000;
+
+ if (lock)
+ g_mutex_unlock(&devc->rw_mutex);
+
+ return ret;
+}
+
+/*
+ * Check for extra LF or CRLF (depends on whether device is in URPSP1 or URPSP2 mode.
+ * Must be called right after calling gwinstek_psp_get_all_values.
+ */
+SR_PRIV int gwinstek_psp_check_terminator(struct sr_serial_dev_inst *serial,
+ struct dev_context *devc)
+{
+ int bytes_left, ret;
+
+ g_mutex_lock(&devc->rw_mutex);
+ /* Sleep for a while to be extra sure the device has sent everything */
+ g_usleep(20 * 1000);
+
+ bytes_left = serial_has_receive_data(serial);
+ sr_dbg("%d bytes left in buffer", bytes_left);
+
+ if (bytes_left == 0) {
+ /* 2, must already be set if we got here */
+ sr_dbg("Device is in URPSP2 mode, terminator is CRLF");
+ } else if (bytes_left == 1) {
+ devc->msg_terminator_len = 3;
+ sr_dbg("Device is in URPSP1 mode, terminator is CRCRLF");
+ } else {
+ sr_err("Don't know how to deal with %d bytes left", bytes_left);
+ g_mutex_unlock(&devc->rw_mutex);
+ return SR_ERR;
+ }
+
+ ret = serial_flush(serial);
+
+ g_mutex_unlock(&devc->rw_mutex);
+ return ret;
+}
+
+/*
+ * Can we trust that the reported voltage is the same as the voltage target? If
+ * the output is off or the device is in CV mode, the answer is likely yes.
+ * Only run this once during the initialization, since naively detecting CV
+ * mode is not terribly reliable, especially when there is an ongoing
+ * transition from CV to CC or vice-versa.
+ */
+SR_PRIV int gwinstek_psp_get_initial_voltage_target(struct dev_context *devc)
+{
+ if (!devc->output_enabled || (fabs(devc->current - devc->current_limit) >= 0.01)) {
+ devc->voltage_target = devc->voltage;
+ sr_dbg("Set initial voltage target to %.2f", devc->voltage_target);
+ } else {
+ /* Would it be more correct to fail the scan here? */
+ sr_warn("Could not determine actual voltage target, falling back to 0");
+ devc->voltage_target = 0;
+ }
+
+ return SR_OK;
+}
+
+SR_PRIV int gwinstek_psp_get_all_values(struct sr_serial_dev_inst *serial,
+ struct dev_context *devc)
+{
+ int ret, bytes_to_read;
+ char buf[50], *b;
+ uint64_t now, delta;
+
+ g_mutex_lock(&devc->rw_mutex);
+
+ now = g_get_monotonic_time();
+ delta = now - devc->last_status_query_time;
+ if (delta <= GWINSTEK_PSP_STATUS_POLL_TIME_MS * 1000) {
+ sr_spew("Last status query was only %" PRIu64 "us ago, returning", delta);
+ g_mutex_unlock(&devc->rw_mutex);
+ return SR_OK;
+ }
+
+ ret = gwinstek_psp_send_cmd(serial, devc, CMD_ALL_QUERY, FALSE);
+ devc->last_status_query_time = now;
+
+ if (ret < 0) {
+ g_mutex_unlock(&devc->rw_mutex);
+ return ret;
+ }
+
+ b = buf;
+ memset(buf, 0, sizeof(buf));
+ bytes_to_read = RESPONSE_ALL_QUERY_LEN + devc->msg_terminator_len;
+ ret = serial_read_blocking(serial, b, bytes_to_read, 1000);
+
+ if (ret < 0) {
+ sr_err("Error %d reading from device.", ret);
+ g_mutex_unlock(&devc->rw_mutex);
+ return ret;
+ }
+
+ sr_dbg("Received: '%s'", buf);
+
+ if (ret != bytes_to_read || sscanf(buf, "V%fA%fW%fU%dI%f", &devc->voltage,
+ &devc->current, &devc->power,
+ &devc->voltage_limit, &devc->current_limit) != 5) {
+ sr_err("Parsing status payload failed");
+ while (serial_read_blocking(serial, b, 1, 1000) > 0);
+ g_mutex_unlock(&devc->rw_mutex);
+ return SR_ERR;
+ }
+
+ devc->output_enabled = (buf[31] == '1');
+ devc->otp_active = (buf[32] == '1');
+
+ if (devc->output_enabled) {
+ devc->voltage_or_0 = devc->voltage;
+ } else {
+ devc->voltage_target = devc->voltage;
+ devc->voltage_target_updated = g_get_monotonic_time();
+ devc->voltage_or_0 = 0;
+ }
+
+ sr_spew("Status: voltage_or_0=%.2f, voltage_target=%.2f, current=%.3f, power=%.1f, "
+ "voltage_limit=%d, current_limit=%.2f",
+ devc->voltage_or_0, devc->voltage_target, devc->current, devc->power,
+ devc->voltage_limit, devc->current_limit);
+
+ g_mutex_unlock(&devc->rw_mutex);
+ return SR_OK;
+}
SR_PRIV int gwinstek_psp_receive_data(int fd, int revents, void *cb_data)
{
- const struct sr_dev_inst *sdi;
+ struct sr_dev_inst *sdi;
struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ struct sr_datafeed_packet packet;
+ struct sr_datafeed_analog analog;
+ struct sr_analog_encoding encoding;
+ struct sr_analog_meaning meaning;
+ struct sr_analog_spec spec;
+ gboolean otp_active_prev;
+ gboolean output_enabled_prev;
+ GSList *l;
(void)fd;
+ (void)revents;
sdi = cb_data;
if (!sdi)
if (!devc)
return TRUE;
- if (revents == G_IO_IN) {
- /* TODO */
+ serial = sdi->conn;
+
+ otp_active_prev = devc->otp_active;
+ output_enabled_prev = devc->output_enabled;
+
+ gwinstek_psp_get_all_values(serial, devc);
+
+ if (otp_active_prev != devc->otp_active) {
+ sr_session_send_meta(sdi, SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE,
+ g_variant_new_boolean(devc->otp_active));
+ }
+
+ if (output_enabled_prev != devc->output_enabled) {
+ sr_session_send_meta(sdi, SR_CONF_ENABLED,
+ g_variant_new_boolean(devc->output_enabled));
}
+ if (devc->set_voltage_target != devc->voltage_target &&
+ (devc->set_voltage_target_updated + 1000 * 1000) <
+ devc->voltage_target_updated) {
+ /* The device reports a voltage target that is different from
+ * the one that was last set. Trust the device if the information
+ * is more recent. */
+ sr_dbg("Updating session voltage target to %.2f",
+ devc->voltage_target);
+ sr_session_send_meta(sdi, SR_CONF_VOLTAGE_TARGET,
+ g_variant_new_double(devc->voltage_target));
+ devc->set_voltage_target = devc->voltage_target;
+ devc->set_voltage_target_updated = g_get_monotonic_time();
+ }
+
+ /* Note: digits/spec_digits will be overridden later. */
+ sr_analog_init(&analog, &encoding, &meaning, &spec, 0);
+
+ packet.type = SR_DF_ANALOG;
+ packet.payload = &analog;
+ meaning.mqflags = SR_MQFLAG_DC;
+ analog.num_samples = 1;
+
+ /* Voltage */
+ l = g_slist_copy(sdi->channels);
+ l = g_slist_remove_link(l, g_slist_nth(l, 1));
+ meaning.channels = l;
+ meaning.mq = SR_MQ_VOLTAGE;
+ meaning.mqflags = SR_MQFLAG_DC;
+ meaning.unit = SR_UNIT_VOLT;
+ encoding.digits = 2;
+ spec.spec_digits = 2;
+ analog.data = &devc->voltage_or_0;
+ sr_session_send(sdi, &packet);
+ g_slist_free(l);
+
+ /* Current */
+ l = g_slist_copy(sdi->channels);
+ l = g_slist_remove_link(l, g_slist_nth(l, 0));
+ meaning.channels = l;
+ meaning.mq = SR_MQ_CURRENT;
+ meaning.unit = SR_UNIT_AMPERE;
+ encoding.digits = 3;
+ spec.spec_digits = 3;
+ analog.data = &devc->current;
+ sr_session_send(sdi, &packet);
+ g_slist_free(l);
+
+ sr_sw_limits_update_samples_read(&devc->limits, 1);
+
+ if (sr_sw_limits_check(&devc->limits))
+ sr_dev_acquisition_stop(sdi);
+
return TRUE;
}