From: Mathieu Pilato Date: Sat, 4 Mar 2023 17:18:46 +0000 (+0100) Subject: atorch: Implement support for DC and USB Meters X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=a5800e90ab7154b5179c3d95ac988bda715ebbb2;p=libsigrok.git atorch: Implement support for DC and USB Meters Tested with ATORCH J7-c USB Meter and ATORCH DL24MP-150W Purple Electronic Load --- diff --git a/configure.ac b/configure.ac index 4e282c14..b482419c 100644 --- a/configure.ac +++ b/configure.ac @@ -295,7 +295,7 @@ SR_DRIVER([Appa 55II], [appa-55ii], [serial_comm]) SR_DRIVER([Arachnid Labs Re:load Pro], [arachnid-labs-re-load-pro], [serial_comm]) SR_DRIVER([ASIX SIGMA/SIGMA2], [asix-sigma], [libftdi]) SR_DRIVER([ASIX OMEGA RTM CLI], [asix-omega-rtm-cli]) -SR_DRIVER([Atorch], [atorch]) +SR_DRIVER([Atorch], [atorch], [serial_comm]) SR_DRIVER([Atten PPS3xxx], [atten-pps3xxx], [serial_comm]) SR_DRIVER([BayLibre ACME], [baylibre-acme], [sys_timerfd_h]) SR_DRIVER([BeagleLogic], [beaglelogic], [sys_mman_h sys_ioctl_h]) diff --git a/src/hardware/atorch/api.c b/src/hardware/atorch/api.c index 9cb615f2..69fe4e37 100644 --- a/src/hardware/atorch/api.c +++ b/src/hardware/atorch/api.c @@ -22,133 +22,206 @@ static struct sr_dev_driver atorch_driver_info; -static GSList *scan(struct sr_dev_driver *di, GSList *options) -{ - struct drv_context *drvc; - GSList *devices; +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, +}; - (void)options; +static const uint32_t drvopts[] = { + SR_CONF_ENERGYMETER, + SR_CONF_POWERMETER, + SR_CONF_ELECTRONIC_LOAD, +}; - devices = NULL; - drvc = di->context; - drvc->instances = NULL; +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, +}; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ +static int create_channels_feed_queues(struct sr_dev_inst *sdi, + struct dev_context *devc) +{ + size_t i; + struct sr_channel *sr_ch; + const struct atorch_channel_desc *at_ch; + struct feed_queue_analog *feed; + const struct atorch_device_profile *p; + + p = devc->profile; + devc->feeds = g_malloc0(p->channel_count * sizeof(devc->feeds[0])); + for (i = 0; i < p->channel_count; i++) { + at_ch = &p->channels[i]; + sr_ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, at_ch->name); + feed = feed_queue_analog_alloc(sdi, 1, at_ch->digits, sr_ch); + feed_queue_analog_mq_unit(feed, at_ch->mq, at_ch->flags, at_ch->unit); + feed_queue_analog_scale_offset(feed, &at_ch->scale, NULL); + devc->feeds[i] = feed; + } - return devices; + return SR_OK; } -static int dev_open(struct sr_dev_inst *sdi) +static GSList *atorch_scan(struct sr_dev_driver *di, + const char *conn, const char *serialcomm) { - (void)sdi; + struct sr_serial_dev_inst *serial; + GSList *devices; + struct dev_context *devc; + struct sr_dev_inst *sdi; - /* TODO: get handle from sdi->conn and open it. */ + serial = sr_serial_dev_inst_new(conn, serialcomm); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) + goto err_out; - return SR_OK; + devc = g_malloc0(sizeof(*devc)); + + if (atorch_probe(serial, devc) != SR_OK) { + sr_err("Failed to find a supported Atorch device."); + goto err_out_serial; + } + + sr_sw_limits_init(&devc->limits); + + sdi = g_malloc0(sizeof(*sdi)); + sdi->priv = devc; + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup("Atorch"); + sdi->model = g_strdup(devc->profile->device_name); + sdi->version = NULL; + sdi->inst_type = SR_INST_SERIAL; + sdi->conn = serial; + + create_channels_feed_queues(sdi, devc); + + serial_close(serial); + + devices = g_slist_append(NULL, sdi); + return std_scan_complete(di, devices); + +err_out_serial: + g_free(devc); + serial_close(serial); +err_out: + sr_serial_dev_inst_free(serial); + + return NULL; } -static int dev_close(struct sr_dev_inst *sdi) +static GSList *scan(struct sr_dev_driver *di, GSList *options) { - (void)sdi; + const char *serial_device, *serial_options; - /* TODO: get handle from sdi->conn and close it. */ + serial_device = NULL; + serial_options = "9600/8n1"; - return SR_OK; + (void)sr_serial_extract_options(options, &serial_device, &serial_options); + if (!serial_device || !*serial_device) + return NULL; + + return atorch_scan(di, serial_device, serial_options); } 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; + switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_FRAMES: + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_get(&devc->limits, key, data); 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; - (void)sdi; (void)data; (void)cg; - ret = SR_OK; + devc = sdi->priv; + switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_FRAMES: + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_set(&devc->limits, key, data); 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; - - ret = SR_OK; - switch (key) { - /* TODO */ - default: - return SR_ERR_NA; - } - - return ret; + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); } 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 sr_serial_dev_inst *serial; + struct dev_context *devc; + + serial = sdi->conn; + devc = sdi->priv; + + sr_sw_limits_acquisition_start(&devc->limits); + std_session_send_df_header(sdi); - (void)sdi; + serial_source_add(sdi->session, serial, G_IO_IN, 100, + atorch_receive_data_callback, (void *)sdi); return SR_OK; } -static int dev_acquisition_stop(struct sr_dev_inst *sdi) +static void clear_helper(struct dev_context *devc) { - /* TODO: stop acquisition. */ + size_t idx; - (void)sdi; + if (!devc) + return; - return SR_OK; + if (devc->feeds && devc->profile) { + for (idx = 0; idx < devc->profile->channel_count; idx++) + feed_queue_analog_free(devc->feeds[idx]); + g_free(devc->feeds); + } +} + +static int dev_clear(const struct sr_dev_driver *driver) +{ + return std_dev_clear_with_callback(driver, (std_dev_clear_callback)clear_helper); } static struct sr_dev_driver atorch_driver_info = { .name = "atorch", - .longname = "ATORCH", + .longname = "atorch meters and loads", .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, - .dev_open = dev_open, - .dev_close = dev_close, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, .dev_acquisition_start = dev_acquisition_start, - .dev_acquisition_stop = dev_acquisition_stop, + .dev_acquisition_stop = std_serial_dev_acquisition_stop, .context = NULL, }; SR_REGISTER_DEV_DRIVER(atorch_driver_info); diff --git a/src/hardware/atorch/protocol.c b/src/hardware/atorch/protocol.c index e9a15bef..53f03313 100644 --- a/src/hardware/atorch/protocol.c +++ b/src/hardware/atorch/protocol.c @@ -18,25 +18,228 @@ */ #include +#include #include "protocol.h" -SR_PRIV int atorch_receive_data(int fd, int revents, void *cb_data) +/* Duration of scan. */ +#define ATORCH_PROBE_TIMEOUT_MS 10000 + +/* + * Message layout: + * 2 magic header bytes + * 1 message type byte + * N payload bytes, determined by message type + */ + +/* Position of message type byte in a message. */ +#define HEADER_MSGTYPE_IDX 2 +#define PAYLOAD_START_IDX 3 + +/* Length of each message type. */ +#define MSGLEN_REPORT (4 + 32) +#define MSGLEN_REPLY (4 + 4) +#define MSGLEN_COMMAND (4 + 6) + +/* Minimal length of a valid message. */ +#define MSGLEN_MIN 4 + +static const uint8_t header_magic[] = { + 0xff, 0x55, +}; + +static const struct atorch_channel_desc atorch_dc_power_meter_channels[] = { + { "V", { 4, BVT_BE_UINT24, }, { 100, 1e3, }, 1, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "I", { 7, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, }, + { "C", { 10, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, }, + { "E", { 13, BVT_BE_UINT32, }, { 10, 1, }, -2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, }, + { "T", { 24, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, }, +}; + +static const struct atorch_channel_desc atorch_usb_power_meter_channels[] = { + { "V", { 4, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "I", { 7, BVT_BE_UINT24, }, { 10, 1e3, }, 2, SR_MQ_CURRENT, SR_UNIT_AMPERE, SR_MQFLAG_DC, }, + { "C", { 10, BVT_BE_UINT24, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_AMPERE_HOUR, 0, }, + { "E", { 13, BVT_BE_UINT32, }, { 10, 1e3, }, 2, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR, 0, }, + { "D-", { 17, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "D+", { 19, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT, SR_MQFLAG_DC, }, + { "T", { 21, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS, 0, }, +}; + +static const struct atorch_device_profile atorch_profiles[] = { + { 0x02, "DC Meter", ARRAY_AND_SIZE(atorch_dc_power_meter_channels), }, + { 0x03, "USB Meter", ARRAY_AND_SIZE(atorch_usb_power_meter_channels), }, +}; + +static size_t get_length_for_msg_type(uint8_t msg_type) +{ + switch (msg_type) { + case MSG_REPORT: + return MSGLEN_REPORT; + case MSG_REPLY: + return MSGLEN_REPLY; + case MSG_COMMAND: + return MSGLEN_COMMAND; + default: + return 0; + } +} + +static void log_atorch_msg(const uint8_t *buf, size_t len) +{ + GString *text; + + if (sr_log_loglevel_get() < SR_LOG_DBG) + return; + + text = sr_hexdump_new(buf, len); + sr_dbg("Atorch msg: %s", text->str); + sr_hexdump_free(text); +} + +static const uint8_t *locate_next_valid_msg(struct dev_context *devc) +{ + uint8_t *valid_msg_ptr; + size_t valid_msg_len; + uint8_t *msg_ptr; + + /* Enough byte to make a message? */ + while (devc->rd_idx + MSGLEN_MIN <= devc->wr_idx) { + /* Look for header magic. */ + msg_ptr = devc->buf + devc->rd_idx; + if (memcmp(msg_ptr, header_magic, sizeof(header_magic)) != 0) { + devc->rd_idx += 1; + continue; + } + + /* Determine msg type and length. */ + valid_msg_len = get_length_for_msg_type(msg_ptr[HEADER_MSGTYPE_IDX]); + if (!valid_msg_len) { + devc->rd_idx += 2; + continue; + } + + /* Do we have the complete message? */ + if (devc->rd_idx + valid_msg_len <= devc->wr_idx) { + valid_msg_ptr = msg_ptr; + devc->rd_idx += valid_msg_len; + log_atorch_msg(valid_msg_ptr, valid_msg_len); + return valid_msg_ptr; + } + + return NULL; + } + return NULL; +} + +static const uint8_t *receive_msg(struct sr_serial_dev_inst *serial, + struct dev_context *devc) +{ + size_t len; + const uint8_t *valid_msg_ptr; + + while (1) { + /* Remove bytes already processed. */ + if (devc->rd_idx > 0) { + len = devc->wr_idx - devc->rd_idx; + memmove(devc->buf, devc->buf + devc->rd_idx, len); + devc->wr_idx -= devc->rd_idx; + devc->rd_idx = 0; + } + + /* Read more bytes to process. */ + len = ATORCH_BUFSIZE - devc->wr_idx; + len = serial_read_nonblocking(serial, devc->buf + devc->wr_idx, len); + if (len <= 0) + return NULL; + devc->wr_idx += len; + + /* Locate next start of message. */ + valid_msg_ptr = locate_next_valid_msg(devc); + if (valid_msg_ptr) + return valid_msg_ptr; + } +} + +static const struct atorch_device_profile *find_profile_for_device_type(uint8_t dev_type) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(atorch_profiles); i++) { + if (atorch_profiles[i].device_type == dev_type) + return &atorch_profiles[i]; + } + return NULL; +} + +static void parse_report_msg(struct sr_dev_inst *sdi, const uint8_t *report_ptr) +{ + struct dev_context *devc; + float val; + size_t i; + + devc = sdi->priv; + + std_session_send_df_frame_begin(sdi); + + for (i = 0; i < devc->profile->channel_count; i++) { + bv_get_value(&val, &devc->profile->channels[i].spec, report_ptr); + feed_queue_analog_submit(devc->feeds[i], val, 1); + } + + std_session_send_df_frame_end(sdi); + + sr_sw_limits_update_frames_read(&devc->limits, 1); + if (sr_sw_limits_check(&devc->limits)) + sr_dev_acquisition_stop(sdi); +} + +SR_PRIV int atorch_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc) { - const struct sr_dev_inst *sdi; + int64_t deadline_us; + const struct atorch_device_profile *p; + const uint8_t *msg_ptr; + + devc->wr_idx = 0; + devc->rd_idx = 0; + + deadline_us = g_get_monotonic_time(); + deadline_us += ATORCH_PROBE_TIMEOUT_MS * 1000; + while (g_get_monotonic_time() <= deadline_us) { + msg_ptr = receive_msg(serial, devc); + if (msg_ptr && msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) { + p = find_profile_for_device_type(msg_ptr[PAYLOAD_START_IDX]); + if (p) { + devc->profile = p; + return SR_OK; + } + sr_err("Unrecognized device type (0x%.4" PRIx8 ").", + devc->buf[PAYLOAD_START_IDX]); + return SR_ERR; + } + g_usleep(100 * 1000); + } + return SR_ERR; +} + +SR_PRIV int atorch_receive_data_callback(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; struct dev_context *devc; + const uint8_t *msg_ptr; (void)fd; sdi = cb_data; - if (!sdi) - return TRUE; - devc = sdi->priv; - if (!devc) + + if (!sdi || !devc) return TRUE; - if (revents == G_IO_IN) { - /* TODO */ + if (revents & G_IO_IN) { + while ((msg_ptr = receive_msg(sdi->conn, devc))) { + if (msg_ptr[HEADER_MSGTYPE_IDX] == MSG_REPORT) + parse_report_msg(sdi, msg_ptr); + } } return TRUE; diff --git a/src/hardware/atorch/protocol.h b/src/hardware/atorch/protocol.h index 9312fae3..05cbe839 100644 --- a/src/hardware/atorch/protocol.h +++ b/src/hardware/atorch/protocol.h @@ -20,16 +20,49 @@ #ifndef LIBSIGROK_HARDWARE_ATORCH_PROTOCOL_H #define LIBSIGROK_HARDWARE_ATORCH_PROTOCOL_H -#include #include #include +#include + #include "libsigrok-internal.h" #define LOG_PREFIX "atorch" +#define ATORCH_BUFSIZE 128 + +struct atorch_device_profile { + uint8_t device_type; + const char *device_name; + const struct atorch_channel_desc *channels; + size_t channel_count; +}; + +struct atorch_channel_desc { + const char *name; + struct binary_value_spec spec; + struct sr_rational scale; + int digits; + enum sr_mq mq; + enum sr_unit unit; + enum sr_mqflag flags; +}; + +enum atorch_msg_type { + MSG_REPORT = 0x01, + MSG_REPLY = 0x02, + MSG_COMMAND = 0x11, +}; + struct dev_context { + const struct atorch_device_profile *profile; + struct sr_sw_limits limits; + struct feed_queue_analog **feeds; + uint8_t buf[ATORCH_BUFSIZE]; + size_t wr_idx; + size_t rd_idx; }; -SR_PRIV int atorch_receive_data(int fd, int revents, void *cb_data); +SR_PRIV int atorch_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc); +SR_PRIV int atorch_receive_data_callback(int fd, int revents, void *cb_data); #endif