zoom/pc/dctest.c

385 lines
8.5 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include <stdint.h>
#include <string.h>
#include <syslog.h>
#include <err.h>
#include <linux/serial.h>
#include <sys/signal.h>
#include "serial-util.h"
#include "gpib.h"
#include "zoom.h"
#include "math.h"
#include <pthread.h>
#include <ctype.h>
#include "mt19937ar.h"
#define info(x...) fprintf(stderr,x)
static void dctest(int zoom, int gpib);
int g_quit = 0;
static void handle_sig(int sig) { g_quit = 1; }
int main(int argc, char *argv[])
{
char *zoomdev=strdup("/dev/serial/by-id/usb-FTDI_"
"FT232R_USB_UART_A6007wc5-if00-port0");
char *gpibdev=strdup("/dev/serial/by-id/usb-Prologix_"
"Prologix_GPIB-USB_Controller_PXQQY20G-if00-port0");
int rate=500000;
unsigned long seed = 1337;
int getopt_index;
int zoom, gpib;
static struct option long_opts[] = {
{ "zoom-device", required_argument, NULL, 'Z' },
{ "gpib-device", required_argument, NULL, 'G' },
{ "rate", required_argument, NULL, 'r' },
{ "seed", required_argument, NULL, 's' },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0}
};
int help=0;
char c;
while ((c = getopt_long(argc, argv, "Z:G:r:s:h?",
long_opts, &getopt_index)) != -1) {
switch(c)
{
case 'Z':
free(zoomdev);
zoomdev = strdup(optarg);
break;
case 'G':
free(gpibdev);
gpibdev = strdup(optarg);
break;
case 'r':
rate = atoi(optarg);
if(rate == 0)
errx(1, "invalid rate: %s", optarg);
break;
case 's':
seed = atol(optarg);
if(seed == 0)
errx(1, "invalid seed: %s", optarg);
break;
case 'h':
case '?':
default:
help = 1;
break;
}
}
if (help) {
fprintf(stderr, "Zoom Nilm DC Test Tool\n");
fprintf(stderr, "usage: %s [options]\n\n", *argv);
fprintf(stderr, " -Z, --zoom-device %-9s "
"zoom NILM serial port\n", "/dev/xxx");
fprintf(stderr, " -G, --gpib-device %-9s "
"GPIB serial port\n", "/dev/xxx");
fprintf(stderr, " -r, --rate %-16d baud rate\n", rate);
fprintf(stderr, " -s, --seed %-16ld random seed\n", seed);
fprintf(stderr, " -h, --help this help\n");
return 1;
}
signal(SIGINT, handle_sig);
info("Initializing twister with seed %ld\n", seed);
init_genrand(seed);
/* open devices */
info("Opening Zoom NILM device %s\n", zoomdev);
if ((zoom = serial_open(zoomdev, rate)) == -1)
err(1, "failed to open zoom device %s", zoomdev);
info("Opening GPIB device %s\n", gpibdev);
if ((gpib = serial_open(gpibdev, 9600)) == -1)
err(1, "failed to open gpib device %s", gpibdev);
/* do the dc test */
dctest(zoom, gpib);
close(zoom);
close(gpib);
return 0;
}
struct keithley_t {
double desired;
double actual;
int stable;
};
struct threadinfo_t {
int quit_flag;
int fd;
pthread_mutex_t mutex;
float calibration;
struct keithley_t k;
};
int process_adc_dac(const uint8_t *buf, struct threadinfo_t *ti)
{
uint16_t dac, tmp;
int16_t adc;
int overflow;
double idesired, iactual;
double calib;
int stable;
/* data OK? */
if ((buf[0] & 0xC0) != 0) return 0;
/* extract */
overflow = (buf[0] & 0x10) ? 1 : 0;
tmp = ((buf[0] & 0x0F) << 8) | buf[1];
/* sign-extend ADC value */
if (tmp & 0x0800)
tmp |= 0xF000;
else
tmp &= ~0xF000;
adc = (int16_t)tmp;
dac = (buf[2] << 8) | buf[3];
/* get locked data */
pthread_mutex_lock(&ti->mutex);
idesired = ti->k.desired;
iactual = ti->k.actual;
stable = ti->k.stable;
calib = ti->calibration;
pthread_mutex_unlock(&ti->mutex);
/* write it out */
printf("%d %.12f %.12f %.8f %5d %5d %d\n",
stable,
idesired,
iactual,
calib,
dac,
adc,
overflow);
return 1;
}
static int process_calibration(const uint8_t *buf, struct threadinfo_t *ti)
{
float f = *(float *)buf;
pthread_mutex_lock(&ti->mutex);
ti->calibration = f;
pthread_mutex_unlock(&ti->mutex);
info("New calibration value: %.8f\n", f);
return 1;
}
static int process(const uint8_t *buf, int len, struct threadinfo_t *ti)
{
int n = 0;
/* Process blocks */
retry:
for (; (n + 5) <= len; buf += 5, n += 5) {
int ok = 0;
switch (buf[0]) {
case 0xA0:
if (process_adc_dac(buf + 1, ti)) ok = 1;
break;
case 0xA1:
if (process_calibration(buf + 1, ti)) ok = 1;
break;
default:
break;
}
if (!ok) {
/* badly formed data; eat one byte and retry */
info("throwing away 0x%02x '%c'\n", buf[0],
isprint(buf[0]) ? buf[0] : '.');
buf++;
n++;
goto retry;
}
}
return n;
}
static void *read_data(void *arg)
{
struct threadinfo_t *ti = (struct threadinfo_t *)arg;
char buf[1024];
int len;
/* read data in a loop. Use saferead_timeout here so we can
notice quit_flag before too long. */
len = 0;
while (!ti->quit_flag) {
int processed, n;
n = saferead_timeout(ti->fd,
buf + len,
sizeof(buf) - len,
1000);
if (n < 0)
err(1, "read");
if (n == 0)
continue;
len += n;
processed = process((uint8_t *) buf, len, ti);
memmove(buf, buf + processed, len - processed);
len -= processed;
}
info("read thread quitting\n");
return NULL;
}
/* change keithley and update keithley_t structure with locking */
static int keithley_change(int gpib, double desired, struct threadinfo_t *ti)
{
double actual;
pthread_mutex_lock(&ti->mutex);
ti->k.stable = 0;
ti->k.desired = desired;
pthread_mutex_unlock(&ti->mutex);
if (keithley_current(gpib, desired) < 0)
return -1;
actual = keithley_read(gpib);
if (isnan(actual))
return -1;
pthread_mutex_lock(&ti->mutex);
ti->k.actual = actual;
ti->k.stable = 1;
pthread_mutex_unlock(&ti->mutex);
return 0;
}
static double genrand(double min, double max)
{
double x;
x = genrand_real1(); /* [0, 1] */
x *= (max - min); /* [0, max-min] */
x += min; /* [min, max] */
return x;
}
static void dctest(int zoom, int gpib)
{
pthread_t thread;
struct threadinfo_t ti;
double tmp;
int i;
/* Do a calibration with Keithley off */
info("Triggering calibration\n");
zoomrun_trigger_calibrate(zoom);
usleep(500000);
/* Init Keithley */
info("Initializing GPIB\n");
if (gpib_init(gpib) < 0) { info("failed\n"); goto out1; }
info("Initializing Keithley\n");
if (gpib_addr(gpib, 24) < 0) { info("failed\n"); goto out1; }
if (keithley_init(gpib) < 0) { info("failed\n"); goto out2; }
if (keithley_current(gpib, 0) < 0) { info("failed\n"); goto out2; }
if (isnan(keithley_read(gpib))) { info("failed\n"); goto out2; }
/* Start the thread that reads and dumps data */
info("Spawning thread\n");
if (pthread_mutex_init(&ti.mutex, NULL) != 0) {
info("failed\n");
goto out2;
}
ti.calibration = 0.0;
ti.k.desired = 0;
ti.k.actual = 0;
ti.k.stable = 0;
ti.quit_flag = 0;
ti.fd = zoom;
drain_timeout(zoom, 0);
if (pthread_create(&thread, NULL, read_data, &ti) != 0) {
info("failed\n");
goto out2;
}
/* Do another calibration now, and verify it works */
info("Triggering calibration\n");
pthread_mutex_lock(&ti.mutex);
ti.calibration = 0.0;
pthread_mutex_unlock(&ti.mutex);
if (keithley_change(gpib, 0.0, &ti) < 0) { info("failed\n"); goto out3; }
zoomrun_trigger_calibrate(zoom);
for (i = 0; i < 100; i++) {
usleep(50000);
pthread_mutex_lock(&ti.mutex);
tmp = ti.calibration;
pthread_mutex_unlock(&ti.mutex);
if (tmp != 0.0)
break;
}
if (tmp == 0.0) {
info("Invalid calibration data: is zoom NILM working?\n");
goto out3;
}
info("Running\n");
/* Change Keithley values */
while (!g_quit) {
#define DELAY 100000 /* after keithley settles, in us */
#define STEPS 10 /* how many small steps to take */
#define K_MIN -0.5 /* keithley min, amps */
#define K_MAX 0.5 /* keithley max, amps */
#define STEPSIZE 0.03 /* max size of small step, in amps */
/* Choose any value within the Keithley range */
double desired = genrand(K_MIN, K_MAX);
info("Big step: %.12f\n", desired);
if (keithley_change(gpib, desired, &ti) < 0) {
info("error setting keithley\n");
break;
}
usleep(DELAY);
/* Choose a few more values nearby */
for (i = 0; i < STEPS && !g_quit; i++) {
desired += genrand(-STEPSIZE/2, STEPSIZE/2);
if (desired < -1.0)
desired = -1.0;
if (desired > 1.0)
desired = 1.0;
if (keithley_change(gpib, desired, &ti) < 0) {
info("error setting keithley\n");
break;
}
usleep(DELAY);
}
/* one extra delay just to separate things a bit */
usleep(DELAY);
}
info("main thread quitting\n");
out3:
ti.quit_flag = 1;
pthread_join(thread, NULL);
out2:
keithley_off(gpib);
out1:
return;
}