X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=blobdiff_plain;f=hardware%2Fcem-dt-885x%2Fprotocol.c;h=19e083062c69331230589be10ab7e6ec34bab4e2;hp=3b1d291575a5a78cbce1141548b55c827bd27909;hb=ba7dd8bbb8168cba432a844259a3e239aa5f36d7;hpb=e1af0e85b9b48b309f3552bceeed81d356735a33 diff --git a/hardware/cem-dt-885x/protocol.c b/hardware/cem-dt-885x/protocol.c index 3b1d2915..19e08306 100644 --- a/hardware/cem-dt-885x/protocol.c +++ b/hardware/cem-dt-885x/protocol.c @@ -134,7 +134,7 @@ static void process_mset(const struct sr_dev_inst *sdi) analog.mq = SR_MQ_SOUND_PRESSURE_LEVEL; analog.mqflags = devc->cur_mqflags; analog.unit = SR_UNIT_DECIBEL_SPL; - analog.probes = sdi->probes; + analog.channels = sdi->channels; analog.num_samples = 1; analog.data = &devc->last_spl; packet.type = SR_DF_ANALOG; @@ -152,6 +152,12 @@ static void process_mset(const struct sr_dev_inst *sdi) case TOKEN_RECORDING_OFF: devc->recording = FALSE; break; + case TOKEN_MEAS_RANGE_30_80: + case TOKEN_MEAS_RANGE_30_130: + case TOKEN_MEAS_RANGE_50_100: + case TOKEN_MEAS_RANGE_80_130: + devc->cur_meas_range = devc->token; + break; case TOKEN_TIME: case TOKEN_STORE_OK: case TOKEN_STORE_FULL: @@ -160,20 +166,55 @@ static void process_mset(const struct sr_dev_inst *sdi) case TOKEN_MEAS_RANGE_OK: case TOKEN_MEAS_RANGE_OVER: case TOKEN_MEAS_RANGE_UNDER: - case TOKEN_MEAS_RANGE_30_80: - case TOKEN_MEAS_RANGE_30_130: - case TOKEN_MEAS_RANGE_50_100: - case TOKEN_MEAS_RANGE_80_130: /* Not useful, or not expressable in sigrok. */ break; } } +static void send_data(const struct sr_dev_inst *sdi, unsigned char *data, + uint64_t num_samples) +{ + struct dev_context *devc; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + float fbuf[SAMPLES_PER_PACKET]; + unsigned int i; + + devc = sdi->priv; + + for (i = 0; i < num_samples; i ++) { + fbuf[i] = ((data[i * 2] & 0xf0) >> 4) * 100; + fbuf[i] += (data[i * 2] & 0x0f) * 10; + fbuf[i] += ((data[i * 2 + 1] & 0xf0) >> 4); + fbuf[i] += (data[i * 2 + 1] & 0x0f) / 10.0; + } + memset(&analog, 0, sizeof(struct sr_datafeed_analog)); + analog.mq = SR_MQ_SOUND_PRESSURE_LEVEL; + analog.mqflags = devc->cur_mqflags; + analog.unit = SR_UNIT_DECIBEL_SPL; + analog.channels = sdi->channels; + analog.num_samples = num_samples; + analog.data = fbuf; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_session_send(devc->cb_data, &packet); + + devc->num_samples += analog.num_samples; + if (devc->limit_samples && devc->num_samples >= devc->limit_samples) + sdi->driver->dev_acquisition_stop((struct sr_dev_inst *)sdi, + devc->cb_data); + + return; +} + static void process_byte(const struct sr_dev_inst *sdi, const unsigned char c, int handle_packets) { struct dev_context *devc; + struct sr_datafeed_packet packet; + struct sr_datafeed_meta meta; + struct sr_config *src; gint64 cur_time; int len; @@ -214,7 +255,8 @@ static void process_byte(const struct sr_dev_inst *sdi, const unsigned char c, } else if (c == 0xbb) { devc->cmd = c; devc->buf_len = 0; - devc->state = ST_GET_LOG; + devc->state = ST_GET_LOG_HEADER; + sr_dbg("got command 0xbb"); } } else if (devc->state == ST_GET_TOKEN) { devc->token = c; @@ -256,7 +298,74 @@ static void process_byte(const struct sr_dev_inst *sdi, const unsigned char c, devc->state = ST_INIT; } } - } else if (devc->state == ST_GET_LOG) { + } else if (devc->state == ST_GET_LOG_HEADER) { + sr_dbg("log header: 0x%.2x", c); + if (devc->buf_len < 2) + devc->buf[devc->buf_len++] = c; + if (devc->buf_len == 2) { + sr_dbg("Device says it has %d bytes stored.", + ((devc->buf[0] << 8) + devc->buf[1]) - 100); + devc->buf_len = 0; + devc->state = ST_GET_LOG_RECORD_META; + } + } else if (devc->state == ST_GET_LOG_RECORD_META) { + sr_dbg("log meta: 0x%.2x", c); + if (c == RECORD_END) { + devc->state = ST_INIT; + /* Stop acquisition after transferring all stored + * records. Otherwise the frontend would have no + * way to tell where stored data ends and live + * measurements begin. */ + sdi->driver->dev_acquisition_stop((struct sr_dev_inst *)sdi, + devc->cb_data); + } else if (c == RECORD_DATA) { + devc->buf_len = 0; + devc->state = ST_GET_LOG_RECORD_DATA; + } else { + /* RECORD_DBA/RECORD_DBC + 7 bytes of metadata */ + devc->buf[devc->buf_len++] = c; + if (devc->buf_len < 8) + /* Keep filling up the record header. */ + return; + if (devc->buf[0] == RECORD_DBA) + devc->cur_mqflags = SR_MQFLAG_SPL_FREQ_WEIGHT_A; + else if (devc->buf[0] == RECORD_DBC) + devc->cur_mqflags = SR_MQFLAG_SPL_FREQ_WEIGHT_C; + else { + /* Shouldn't happen. */ + sr_dbg("Unknown record token 0x%.2x", c); + return; + } + packet.type = SR_DF_META; + packet.payload = &meta; + src = sr_config_new(SR_CONF_SAMPLE_INTERVAL, + g_variant_new_uint64(devc->buf[7] * 1000)); + meta.config = g_slist_append(NULL, src); + sr_session_send(devc->cb_data, &packet); + g_free(src); + devc->buf_len = 0; + } + } else if (devc->state == ST_GET_LOG_RECORD_DATA) { + sr_dbg("log data: 0x%.2x", c); + if (c == RECORD_DBA || c == RECORD_DBC || c == RECORD_DATA || c == RECORD_END) { + /* Work around off-by-one bug in device firmware. This + * happens only on the last record, i.e. before RECORD_END */ + if (devc->buf_len & 1) + devc->buf_len--; + /* Done with this set of samples */ + send_data(sdi, devc->buf, devc->buf_len / 2); + devc->buf_len = 0; + + /* Process this meta marker in the right state. */ + devc->state = ST_GET_LOG_RECORD_META; + process_byte(sdi, c, handle_packets); + } else { + devc->buf[devc->buf_len++] = c; + if (devc->buf_len == SAMPLES_PER_PACKET * 2) { + send_data(sdi, devc->buf, devc->buf_len / 2); + devc->buf_len = 0; + } + } } } @@ -264,26 +373,39 @@ static void process_byte(const struct sr_dev_inst *sdi, const unsigned char c, SR_PRIV int cem_dt_885x_receive_data(int fd, int revents, void *cb_data) { const struct sr_dev_inst *sdi; + struct dev_context *devc; struct sr_serial_dev_inst *serial; - unsigned char c; + unsigned char c, cmd; (void)fd; if (!(sdi = cb_data)) return TRUE; + devc = sdi->priv; serial = sdi->conn; if (revents == G_IO_IN) { if (serial_read(serial, &c, 1) != 1) return TRUE; process_byte(sdi, c, TRUE); + + if (devc->enable_data_source_memory) { + if (devc->state == ST_GET_LOG_HEADER) { + /* Memory transfer started. */ + devc->enable_data_source_memory = FALSE; + } else { + /* Tell device to start transferring from memory. */ + cmd = CMD_TRANSFER_MEMORY; + serial_write(serial, &cmd, 1); + } + } } return TRUE; } -static int wait_for_token(const struct sr_dev_inst *sdi, char *tokens, int timeout) +static int wait_for_token(const struct sr_dev_inst *sdi, int8_t *tokens, int timeout) { struct dev_context *devc; struct sr_serial_dev_inst *serial; @@ -318,7 +440,8 @@ static int wait_for_token(const struct sr_dev_inst *sdi, char *tokens, int timeo /* cmd is the command to send, tokens are the tokens that denote the state * which the command affects. The first token is the desired state. */ -SR_PRIV int cem_dt_885x_toggle(const struct sr_dev_inst *sdi, uint8_t cmd, char *tokens) +static int cem_dt_885x_toggle(const struct sr_dev_inst *sdi, uint8_t cmd, + int8_t *tokens, int timeout) { struct dev_context *devc; struct sr_serial_dev_inst *serial; @@ -332,8 +455,7 @@ SR_PRIV int cem_dt_885x_toggle(const struct sr_dev_inst *sdi, uint8_t cmd, char while (TRUE) { if (serial_write(serial, (const void *)&cmd, 1) != 1) return SR_ERR; - /* Notifications are sent at 2Hz minimum */ - if (wait_for_token(sdi, tokens, 510) == SR_ERR) + if (wait_for_token(sdi, tokens, timeout) == SR_ERR) return SR_ERR; if (devc->token == tokens[0]) /* It worked. */ @@ -343,35 +465,37 @@ SR_PRIV int cem_dt_885x_toggle(const struct sr_dev_inst *sdi, uint8_t cmd, char return SR_OK; } -SR_PRIV gboolean cem_dt_885x_recording_get(const struct sr_dev_inst *sdi) +SR_PRIV gboolean cem_dt_885x_recording_get(const struct sr_dev_inst *sdi, + int *state) { struct dev_context *devc; - char tokens[5]; + int8_t tokens[5]; devc = sdi->priv; - if (devc->recording == -1) { /* Didn't pick up device state yet. */ tokens[0] = TOKEN_RECORDING_ON; tokens[1] = TOKEN_RECORDING_OFF; tokens[2] = -1; - if (wait_for_token(sdi, tokens, 0) != SR_OK) + if (wait_for_token(sdi, tokens, 510) != SR_OK) return SR_ERR; } + *state = devc->token == TOKEN_RECORDING_ON; - return devc->token == TOKEN_RECORDING_ON; + return SR_OK; } -SR_PRIV int cem_dt_885x_recording_set(const struct sr_dev_inst *sdi, gboolean start) +SR_PRIV int cem_dt_885x_recording_set(const struct sr_dev_inst *sdi, + gboolean state) { struct dev_context *devc; int ret; - char tokens[5]; + int8_t tokens[5]; devc = sdi->priv; /* The toggle below needs the desired state in first position. */ - if (start) { + if (state) { tokens[0] = TOKEN_RECORDING_ON; tokens[1] = TOKEN_RECORDING_OFF; } else { @@ -387,11 +511,328 @@ SR_PRIV int cem_dt_885x_recording_set(const struct sr_dev_inst *sdi, gboolean st if (devc->token == tokens[0]) /* Nothing to do. */ return SR_OK; - } else if (devc->recording == start) + } else if (devc->recording == state) /* Nothing to do. */ return SR_OK; - ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_RECORDING, tokens); + /* Recording state notifications are sent at 2Hz, so allow just over + * that, 510ms, for the state to come in. */ + ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_RECORDING, tokens, 510); + + return ret; +} + +SR_PRIV int cem_dt_885x_weight_freq_get(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + int cur_setting; + int8_t tokens[5]; + + devc = sdi->priv; + + cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_FREQ_WEIGHT_A | SR_MQFLAG_SPL_FREQ_WEIGHT_C); + if (cur_setting == 0) { + /* Didn't pick up device state yet. */ + tokens[0] = TOKEN_WEIGHT_FREQ_A; + tokens[1] = TOKEN_WEIGHT_FREQ_C; + tokens[2] = -1; + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + if (devc->token == TOKEN_WEIGHT_FREQ_A) + return SR_MQFLAG_SPL_FREQ_WEIGHT_A; + else + return SR_MQFLAG_SPL_FREQ_WEIGHT_C; + } else + return cur_setting; +} + +SR_PRIV int cem_dt_885x_weight_freq_set(const struct sr_dev_inst *sdi, int freqw) +{ + struct dev_context *devc; + int cur_setting, ret; + int8_t tokens[5]; + + devc = sdi->priv; + + cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_FREQ_WEIGHT_A | SR_MQFLAG_SPL_FREQ_WEIGHT_C); + if (cur_setting == freqw) + /* Already set to this frequency weighting. */ + return SR_OK; + + /* The toggle below needs the desired state in first position. */ + if (freqw == SR_MQFLAG_SPL_FREQ_WEIGHT_A) { + tokens[0] = TOKEN_WEIGHT_FREQ_A; + tokens[1] = TOKEN_WEIGHT_FREQ_C; + } else { + tokens[0] = TOKEN_WEIGHT_FREQ_C; + tokens[1] = TOKEN_WEIGHT_FREQ_A; + } + tokens[2] = -1; + + if (cur_setting == 0) { + /* Didn't pick up device state yet. */ + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + if (devc->token == tokens[0]) + /* Nothing to do. */ + return SR_OK; + } + + /* 10ms timeout seems to work best for this. */ + ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_WEIGHT_FREQ, tokens, 10); return ret; } + +SR_PRIV int cem_dt_885x_weight_time_get(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + int cur_setting; + int8_t tokens[5]; + + devc = sdi->priv; + + cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_TIME_WEIGHT_F | SR_MQFLAG_SPL_TIME_WEIGHT_S); + if (cur_setting == 0) { + /* Didn't pick up device state yet. */ + tokens[0] = TOKEN_WEIGHT_TIME_FAST; + tokens[1] = TOKEN_WEIGHT_TIME_SLOW; + tokens[2] = -1; + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + if (devc->token == TOKEN_WEIGHT_TIME_FAST) + return SR_MQFLAG_SPL_TIME_WEIGHT_F; + else + return SR_MQFLAG_SPL_TIME_WEIGHT_S; + } else + return cur_setting; +} + +SR_PRIV int cem_dt_885x_weight_time_set(const struct sr_dev_inst *sdi, int timew) +{ + struct dev_context *devc; + int cur_setting, ret; + int8_t tokens[5]; + + devc = sdi->priv; + + cur_setting = devc->cur_mqflags & (SR_MQFLAG_SPL_TIME_WEIGHT_F | SR_MQFLAG_SPL_TIME_WEIGHT_S); + if (cur_setting == timew) + /* Already set to this time weighting. */ + return SR_OK; + + /* The toggle below needs the desired state in first position. */ + if (timew == SR_MQFLAG_SPL_TIME_WEIGHT_F) { + tokens[0] = TOKEN_WEIGHT_TIME_FAST; + tokens[1] = TOKEN_WEIGHT_TIME_SLOW; + } else { + tokens[0] = TOKEN_WEIGHT_TIME_SLOW; + tokens[1] = TOKEN_WEIGHT_TIME_FAST; + } + tokens[2] = -1; + + if (cur_setting == 0) { + /* Didn't pick up device state yet. */ + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + if (devc->token == tokens[0]) + /* Nothing to do. */ + return SR_OK; + } + + /* 51ms timeout seems to work best for this. */ + ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_WEIGHT_TIME, tokens, 51); + + return ret; +} + +SR_PRIV int cem_dt_885x_holdmode_get(const struct sr_dev_inst *sdi, + gboolean *holdmode) +{ + struct dev_context *devc; + int8_t tokens[5]; + + devc = sdi->priv; + + if (devc->cur_mqflags == 0) { + tokens[0] = TOKEN_HOLD_MAX; + tokens[1] = TOKEN_HOLD_MIN; + tokens[2] = TOKEN_HOLD_NONE; + tokens[3] = -1; + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + if (devc->token == TOKEN_HOLD_MAX) + devc->cur_mqflags = SR_MQFLAG_MAX; + else if (devc->token == TOKEN_HOLD_MIN) + devc->cur_mqflags = SR_MQFLAG_MIN; + } + *holdmode = devc->cur_mqflags & (SR_MQFLAG_MAX | SR_MQFLAG_MIN); + + return SR_OK; +} + +SR_PRIV int cem_dt_885x_holdmode_set(const struct sr_dev_inst *sdi, int holdmode) +{ + struct dev_context *devc; + int cur_setting, ret; + int8_t tokens[5]; + + devc = sdi->priv; + + /* The toggle below needs the desired state in first position. */ + if (holdmode == SR_MQFLAG_MAX) { + tokens[0] = TOKEN_HOLD_MAX; + tokens[1] = TOKEN_HOLD_MIN; + tokens[2] = TOKEN_HOLD_NONE; + } else if (holdmode == SR_MQFLAG_MIN) { + tokens[0] = TOKEN_HOLD_MIN; + tokens[1] = TOKEN_HOLD_MAX; + tokens[2] = TOKEN_HOLD_NONE; + } else { + tokens[0] = TOKEN_HOLD_NONE; + tokens[1] = TOKEN_HOLD_MAX; + tokens[2] = TOKEN_HOLD_MIN; + } + tokens[3] = -1; + + if (devc->cur_mqflags == 0) { + /* Didn't pick up device state yet. */ + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + if (devc->token == tokens[0]) + /* Nothing to do. */ + return SR_OK; + } else { + cur_setting = devc->cur_mqflags & (SR_MQFLAG_MAX | SR_MQFLAG_MIN); + if (cur_setting == holdmode) + /* Already set correctly. */ + return SR_OK; + } + + /* 51ms timeout seems to work best for this. */ + ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_HOLD_MAX_MIN, tokens, 51); + + return ret; +} + +SR_PRIV int cem_dt_885x_meas_range_get(const struct sr_dev_inst *sdi, + uint64_t *low, uint64_t *high) +{ + struct dev_context *devc; + int8_t tokens[5]; + + devc = sdi->priv; + if (devc->cur_meas_range == 0) { + tokens[0] = TOKEN_MEAS_RANGE_30_130; + tokens[1] = TOKEN_MEAS_RANGE_30_80; + tokens[2] = TOKEN_MEAS_RANGE_50_100; + tokens[3] = TOKEN_MEAS_RANGE_80_130; + tokens[4] = -1; + if (wait_for_token(sdi, tokens, 0) != SR_OK) + return SR_ERR; + devc->cur_meas_range = devc->token; + } + + switch (devc->cur_meas_range) { + case TOKEN_MEAS_RANGE_30_130: + *low = 30; + *high = 130; + break; + case TOKEN_MEAS_RANGE_30_80: + *low = 30; + *high = 80; + break; + case TOKEN_MEAS_RANGE_50_100: + *low = 50; + *high = 100; + break; + case TOKEN_MEAS_RANGE_80_130: + *low = 80; + *high = 130; + break; + default: + return SR_ERR; + } + + return SR_OK; +} + +SR_PRIV int cem_dt_885x_meas_range_set(const struct sr_dev_inst *sdi, + uint64_t low, uint64_t high) +{ + struct dev_context *devc; + int ret; + int8_t token, tokens[6]; + + devc = sdi->priv; + if (low == 30 && high == 130) + token = TOKEN_MEAS_RANGE_30_130; + else if (low == 30 && high == 80) + token = TOKEN_MEAS_RANGE_30_80; + else if (low == 50 && high == 100) + token = TOKEN_MEAS_RANGE_50_100; + else if (low == 80 && high == 130) + token = TOKEN_MEAS_RANGE_80_130; + else + return SR_ERR; + + sr_dbg("want 0x%.2x", token); + /* The toggle below needs the desired state in first position. */ + tokens[0] = token; + tokens[1] = TOKEN_MEAS_RANGE_30_130; + tokens[2] = TOKEN_MEAS_RANGE_30_80; + tokens[3] = TOKEN_MEAS_RANGE_50_100; + tokens[4] = TOKEN_MEAS_RANGE_80_130; + tokens[5] = -1; + + if (devc->cur_meas_range == 0) { + /* 110ms should be enough for two of these announcements */ + if (wait_for_token(sdi, tokens, 110) != SR_OK) + return SR_ERR; + devc->cur_meas_range = devc->token; + } + + if (devc->cur_meas_range == token) + /* Already set to this range. */ + return SR_OK; + + /* For measurement range, it works best to ignore announcements of the + * current setting and keep resending the toggle quickly. */ + tokens[1] = -1; + ret = cem_dt_885x_toggle(sdi, CMD_TOGGLE_MEAS_RANGE, tokens, 11); + + return ret; +} + +SR_PRIV int cem_dt_885x_power_off(const struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + char c, cmd; + + serial = sdi->conn; + + /* Reopen the port in non-blocking mode, so we can properly + * detect when the device stops communicating. */ + serial_close(serial); + if (serial_open(serial, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK) + return SR_ERR; + + cmd = CMD_TOGGLE_POWER_OFF; + while (TRUE) { + serial_flush(serial); + if (serial_write(serial, (const void *)&cmd, 1) != 1) + return SR_ERR; + /* It never takes more than 23ms for the next token to arrive. */ + g_usleep(25 * 1000); + if (serial_read(serial, &c, 1) != 1) + /* Device is no longer responding. Good! */ + break; + } + + /* In case the user manually turns on the device again, reset + * the port back to blocking. */ + serial_close(serial); + serial_open(serial, SERIAL_RDWR); + + return SR_OK; +}