1
0
mirror of git://projects.qi-hardware.com/ben-wpan.git synced 2025-01-10 00:50:14 +02:00
ben-wpan/cntr/tools/cntr/cntr.c

428 lines
9.1 KiB
C

/*
* cntr/cntr.c - CNTR control tool
*
* Written 2010 by Werner Almesberger
* Copyright 2010 Werner Almesberger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <usb.h>
#include <sys/time.h>
#include "f32xbase/usb.h"
#include "cntr/ep0.h"
#include "cntr/usb-ids.h"
#define FROM_DEV CNTR_FROM_DEV(0)
#define TO_DEV CNTR_TO_DEV(0)
#define DEFAULT_CLOCK_DEV_S 0.1 /* 100 ms, typ. NTP over WLAN dev. */
#define BUF_SIZE 256
static int debug = 0;
static int verbose = 0;
/* ----- CRC, shared with firmware ----------------------------------------- */
/* crc32() */
#include "cntr/crc32.c"
/* ----- reset ------------------------------------------------------------- */
static void reset_cntr(usb_dev_handle *dev)
{
int res;
res = usb_control_msg(dev, TO_DEV, CNTR_RESET, 0, 0, NULL, 0, 1000);
if (res < 0) {
fprintf(stderr, "CNTR_RESET: %d\n", res);
exit(1);
}
}
/* ----- identify ---------------------------------------------------------- */
static void identify_cntr(usb_dev_handle *dev)
{
const struct usb_device *device = usb_device(dev);
uint8_t ids[3];
char buf[BUF_SIZE+1]; /* +1 for terminating \0 */
int res;
printf("%04x:%04x ",
device->descriptor.idVendor, device->descriptor.idProduct);
res = usb_control_msg(dev, FROM_DEV, CNTR_ID, 0, 0,
(char *) ids, sizeof(ids), 1000);
if (res < 0) {
fprintf(stderr, "CNTR_ID: %s\n", usb_strerror());
exit(1);
}
printf("protocol %u.%u hw %u\n", ids[0], ids[1], ids[2]);
res = usb_control_msg(dev, FROM_DEV, CNTR_BUILD, 0, 0,
buf, sizeof(buf), 1000);
if (res < 0) {
fprintf(stderr, "CNTR_BUILD: %s\n", usb_strerror());
exit(1);
}
buf[res] = 0;
printf("%10s%s\n", "", buf);
}
/* ---- packet reception --------------------------------------------------- */
struct sample {
double t0, t1;
uint64_t cntr;
};
static unsigned packets = 0, crc_errors = 0, inv_errors = 0;
static int get_sample(usb_dev_handle *dev, struct sample *s)
{
static uint32_t last = 0, high = 0;
struct timeval t0, t1;
int res, bad;
uint8_t buf[12];
uint32_t cntr, inv, crc, expect;
gettimeofday(&t0, NULL);
res = usb_control_msg(dev, FROM_DEV, CNTR_READ, 0, 0,
(char *) buf, sizeof(buf), 1000);
gettimeofday(&t1, NULL);
if (res < 0) {
fprintf(stderr, "CNTR_READ: %s\n", usb_strerror());
exit(1);
}
packets++;
cntr = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
crc = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24);
inv = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24);
expect = crc32(cntr, ~0);
bad = 0;
if (crc != expect) {
if (verbose)
fprintf(stderr, "\nCRC error (count 0x%08x->0x%08x "
"CRC 0x%08x/0x%08x)\n",
(unsigned) last, (unsigned) cntr, (unsigned) crc,
(unsigned) expect);
bad = 1;
crc_errors++;
}
if (cntr != (inv ^ 0xffffffff)) {
if (verbose)
fprintf(stderr,
"\ninverted counter error (0x%08x->0x%08x, "
"inv 0x%08x)\n",
(unsigned) last, (unsigned) cntr, (unsigned) inv);
bad = 1;
inv_errors++;
}
if (bad)
return 0;
if (last > cntr)
high++;
last = cntr;
s->t0 = t0.tv_sec+t0.tv_usec/1000000.0;
s->t1 = t1.tv_sec+t1.tv_usec/1000000.0;
s->cntr = (uint64_t) high << 32 | cntr;
if (debug)
printf("0x%llx 0x%lx\n",
(unsigned long long ) s->cntr, (unsigned long) cntr);
return 1;
}
/* ---- SIGINT (Ctrl-C) ---------------------------------------------------- */
static volatile int stop = 0;
static void set_stop(int sig)
{
stop = 1;
}
static void arm_stop(void)
{
signal(SIGINT, set_stop);
}
/* ---- output ------------------------------------------------------------- */
static void print_f(double f, int digits)
{
char *f_exp;
if (f > 1000000.0) {
f /= 1000000.0;
f_exp = "M";
} else if (f > 1000.0) {
f /= 1000.0;
f_exp = "k";
} else {
f_exp = "";
}
printf("%1.*f %sHz", digits, f, f_exp);
}
/* ---- burst counter ------------------------------------------------------ */
/*
* Here is when the various samples are taken:
*
* Activity --------XXXXXXXXXXXXX-------------
* ^ ^ ^^
* | | last||
* start stable now
* |<-t(idle)->|
*
* "start" is the sample before counter activity
* "stable" is the first sample after counter activity
* "last" is the sample immediately preceding "now"
* "now" is the sample currently being processed
*
* The count is printed if t(idle) >= timeout.
*/
static void count_bursts(usb_dev_handle *dev, double timeout)
{
struct sample start, stable, now, last;
uint64_t dc, delta_n = 0;
double delta_sum = 0, dt;
arm_stop();
while (!get_sample(dev, &start));
stable = last = start;
while (!stop) {
while (!get_sample(dev, &now))
if (stop)
break;
delta_sum += now.t1-last.t1;
delta_n++;
last = now;
if (stable.cntr != now.cntr) {
stable = now;
continue;
}
if (now.t0-stable.t1 < timeout)
continue;
if (now.cntr != start.cntr) {
dc = now.cntr-start.cntr;
dt = stable.t1-start.t1-delta_sum/delta_n;
printf("%llu ~ ", (unsigned long long) dc);
if (dt > 0)
print_f(dc/dt, 3);
printf("\n");
fflush(stdout);
}
start = now;
}
}
/* ----- measurements ------------------------------------------------------ */
static void measure(usb_dev_handle *dev, double clock_dev_s, double error_goal)
{
struct sample start, now;
uint64_t dc;
double dt, f, error;
char *error_exp;
int i;
arm_stop();
/*
* The round-trip time for getting the first sample is one of the
* error terms. The smaller we can make it, the better. Thus, we try a
* few times to improve our first result.
*/
while (!get_sample(dev, &start));
for (i = 0; i != 10; i++) {
while (!get_sample(dev, &now));
if (now.t1-now.t0 < start.t1-start.t0) {
if (debug)
fprintf(stderr, "improve %g -> %g\n",
start.t1-start.t0, now.t1-now.t0);
start = now;
}
}
while (!stop) {
usleep(100000);
while (!get_sample(dev, &now))
if (stop)
break;
dc = now.cntr-start.cntr;
dt = (now.t0+now.t1)/2.0-(start.t0+start.t1)/2.0;
f = dc/dt;
if (dc)
error = 1.0/dc; /* one count */
else
error = 0;
error += (start.t1-start.t0)/dt/2.0; /* start sample read */
error += (now.t1-now.t0)/dt/2.0; /* last sample read */
error += clock_dev_s/dt; /* system clock dev. */
if (error >= 1) {
printf("\r(wait) ");
fflush(stdout);
continue;
}
if (dc && error <= error_goal)
stop = 1;
error_exp = "%";
error *= 100.0;
if (error < 0.1) {
error_exp = " ppm";
error *= 10000.0;
if (error < 1.0) {
error_exp = " ppb";
error *= 1000.0;
}
}
printf("\r%6.1f ", dt);
print_f(f, 9);
printf(" %3.3f%s ", error, error_exp);
fflush(stdout);
}
printf(
"\n%llu counts, %u packets, %u CRC error%s, %u invert error%s\n",
(unsigned long long) (now.cntr-start.cntr),
packets, crc_errors, crc_errors == 1 ? "" : "s",
inv_errors, inv_errors == 1 ? "" : "s");
}
/* ----- command-line parsing ---------------------------------------------- */
static void usage(const char *name)
{
fprintf(stderr,
"usage: %s [-c clock_dev] [-d] [-v] [accuracy_ppm]\n"
"%6s %s -b [-d] [-v] [timeout_s]\n"
"%6s %s -i\n"
"%6s %s -r\n\n"
" accuracy_ppm stop when specified accuracy is reached (default: never\n"
" stop)\n"
" timeout_s silence period between bursts, in seconds (default: 1s )\n"
" -b count bursts separated by silence periods\n"
" -c clock_dev maximum deviation of the system clock, in seconds\n"
" (default: %g s)\n"
" -d debug mode. Print counter values.\n"
" -i identify the CNTR board\n"
" -r reset the CNTR board\n"
" -v verbose reporting of communication errors\n"
, name, "", name, "", name, "", name, DEFAULT_CLOCK_DEV_S);
exit(1);
}
int main(int argc, char *const *argv)
{
usb_dev_handle *dev;
int c, burst = 0, identify = 0, reset = 0;
double clock_dev_s = DEFAULT_CLOCK_DEV_S;
double error_goal = 0;
char *end;
while ((c = getopt(argc, argv, "bc:dirv")) != EOF)
switch (c) {
case 'b':
burst = 1;
break;
case 'c':
clock_dev_s = strtod(argv[optind], &end);
if (*end)
usage(*argv);
break;
case 'd':
debug = 1;
break;
case 'i':
identify = 1;
break;
case 'r':
reset = 1;
break;
case 'v':
verbose = 1;
break;
default:
usage(*argv);
}
if (burst+identify+reset > 1)
usage(*argv);
switch (argc-optind) {
case 0:
break;
case 1:
if (identify || reset)
usage(*argv);
error_goal = strtod(argv[optind], &end)/1000000.0;
if (*end)
usage(*argv);
break;
default:
usage(*argv);
}
dev = open_usb(USB_VENDOR, USB_PRODUCT);
if (!dev) {
fprintf(stderr, ":-(\n");
return 1;
}
if (identify) {
identify_cntr(dev);
return 0;
}
if (reset) {
reset_cntr(dev);
return 0;
}
if (burst) {
count_bursts(dev, error_goal ? error_goal : 1);
return 0;
}
measure(dev, clock_dev_s, error_goal);
return 0;
}