mirror of
git://projects.qi-hardware.com/ben-wpan.git
synced 2025-01-10 00:50:14 +02:00
428 lines
9.1 KiB
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;
|
|
}
|