1
0
mirror of git://projects.qi-hardware.com/ben-wpan.git synced 2024-11-25 15:45:56 +02:00

Made communication with CNTR board more robust. Added documentation.

- cntr/README: description of the counter board and its application
- cntr/fw/common/crc32.c: variant of CRC32-IEEE802.3 shared by firmware and
  measurement application
- cntr/fw/cntr/ep0.c (my_setup), cntr/tools/cntr/cntr.c (get_sample):
  protect the counter value with a CRC and an one's complement copy
- cntr/fw/include/cntr/ep0.h: oops, wasn't checked into repository
- cntr/tools/cntr/cntr.c: added section titles
- cntr/tools/cntr/cntr.c (measure): show communication statistics at the end
- cntr/tools/cntr/cntr.c (measure, usage, main): new option -d to enable
  reporting of communication errors
- cntr/tools/cntr/cntr.c (set_stop, measure): let user stop measurement with
  SIGINT
- cntr/tools/cntr/cntr.c (measure): get multiple "first samples" and keep
  the one with the shortest round-trip time
- cntr/tools/cntr/cntr.c (measure): changed unit "ppk" (1/1000) to percent
  (1/100)
- cntr/tools/cntr/cntr.c (usage, main): command-line argument is now the
  accuracy goal, while the system clock deviation is set with the new
  option -c
- TODO: some more things to do
This commit is contained in:
Werner Almesberger 2010-08-25 18:47:45 -03:00
parent 4d49921b9b
commit c7303e4ac1
5 changed files with 295 additions and 31 deletions

22
TODO
View File

@ -105,10 +105,30 @@ Things not done yet
- measure duty cycle - measure duty cycle
- display activity on clock input and duty cycle - use the LED to display activity on clock input and duty cycle
- consider using a comparator and a DAC to allow for programmable logic levels - consider using a comparator and a DAC to allow for programmable logic levels
- evaluate termination resistance - evaluate termination resistance
- document circuit design - document circuit design
- record beats between 16 bit counter polls and use them for the estimate
of lost cycles (2*1 is way too optimistic)
- include system clock resolution in accuracy calculation
- consider running shorter sliding windows to estimate drift
- consider detecting unusual half-periods
- consider using a reversed USB connector, to avoid having to cross D+/D- and,
worse, VBUS and GND
- test input performance by counting a source that emits a known number of
cycles
- consider using historical margins to sanity-check the current margin (if any
old.max < curr.min or old.min > curr.max, we have a problem) and to further
narrow the effective margin, thus achieving faster convergence. We would have
to consider temperature drift of the frequency source in this case.

66
cntr/README Normal file
View File

@ -0,0 +1,66 @@
Arbitrary-precision counter
===========================
Theory of operation
-------------------
The arbitrary-precision counter counts clock cycles of a frequency
source that is assumed to be free from drift. It compares the count
with the host's system clock. If the system clock is synchronized with
an accurate NTP reference, measurements with arbitrarily high accuracy
can be obtained.
In practice, this is limited by the the frequency source's drift and
the time one is willing to wait. If NTP maintains the system time
with an accuracy of +/- 100 ms, obtaining measurements with an
accuracy of +/- 1 ppm would take about 28 hours.
Additional error sources, such as the round-trip time when requesting
a sample from the microcontroller, are also considered in the accuracy
calculation.
The counter consists of a board based on a C8051F320 microcontroller
and the control software on the host. The microcontroller counts
events in a free-running 16 bit counter that is regularly read and
extended to 32 bits. The 32 bit counter is periodically queried by
the host.
The microcontroller's counter can count at a frequency of up to 3 MHz.
(SYSCLK/4)
In order to protect against transmission errors not detected by USB's
CRC, which are occur relatively often, each packet is protected by a
CRC-32 and an inverted copy of the payload. Corrupted packets are
rejected by the host.
The 32 bit counter wraps around at most once very 21.8 ms. The 32 bit
counter wraps around at most every 1431 s. The host extends the 32 bit
counter to 64 bits, and calculates frequency and accuracy from the
count and the run time of the measurement application.
Performing a measurement
------------------------
To perform a measurement, connect the CNTR board's probe input to the
clock source and then run the "cntr" application on the host. An
accuracy goal (in ppm) can be specified on the command line (see
below).
The host polls the microcontroller every 100 ms and displays the run
time (in seconds), the measured frequency, and the accuracy achieved
so far.
Measurements can be stopped by pressing ^C or by specifying an
accuracy goal. At the end, the total number of events counted and
communication statistics are displayed.
Updating the firmware
---------------------
The protocol revision and the build date of the firmware of the CNTR
board can be queried with "cntr -i".
To update the firmware, run
cntr -r && sleep 1 && dfu-util -d 0x20b7:0xcb72 -D cntr.bin

View File

@ -22,7 +22,6 @@
#include "cntr/ep0.h" #include "cntr/ep0.h"
#include "version.h" #include "version.h"
#define debug(...) #define debug(...)
#define error(...) #define error(...)
@ -61,9 +60,13 @@ static __xdata uint8_t buf[128];
#define BUILD_OFFSET 7 /* '#' plus "65535" plus ' ' */ #define BUILD_OFFSET 7 /* '#' plus "65535" plus ' ' */
/* crc32() */
#include "cntr/crc32.c"
static __bit my_setup(struct setup_request *setup) __reentrant static __bit my_setup(struct setup_request *setup) __reentrant
{ {
unsigned tmp; uint32_t tmp;
uint8_t size, i; uint8_t size, i;
switch (setup->bmRequestType | setup->bRequest << 8) { switch (setup->bmRequestType | setup->bRequest << 8) {
@ -102,7 +105,18 @@ static __bit my_setup(struct setup_request *setup) __reentrant
buf[1] = cntr[1]; buf[1] = cntr[1];
buf[2] = cntr[2]; buf[2] = cntr[2];
buf[3] = cntr[3]; buf[3] = cntr[3];
usb_send(&ep0, buf, 4, NULL, NULL); tmp = (uint32_t) buf[0] | ((uint32_t) buf[1] << 8) |
((uint32_t) buf[2] << 16) | ((uint32_t) buf[3] << 24);
tmp = crc32(tmp, 0xffffffff);
buf[4] = tmp;
buf[5] = tmp >> 8;
buf[6] = tmp >> 16;
buf[7] = tmp >> 24;
buf[8] = ~cntr[0];
buf[9] = ~cntr[1];
buf[10] = ~cntr[2];
buf[11] = ~cntr[3];
usb_send(&ep0, buf, 12, NULL, NULL);
return 1; return 1;
default: default:

View File

@ -0,0 +1,62 @@
/*
* include/cntr/ep0.h - EP0 extension protocol
*
* Written 2008-2010 by Werner Almesberger
* Copyright 2008-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.
*/
#ifndef EP0_H
#define EP0_H
/*
* Direction bRequest wValue wIndex wLength
*
* ->host CNTR_ID - - 3
* ->host CNTR_BUILD - - #bytes
* host-> CNTR_RESET - - 0
*
* ->host CNTR_READ - 0 12
*/
/*
* EP0 protocol:
*
* 0.0 initial release
*/
#define EP0CNTR_MAJOR 0 /* EP0 protocol, major revision */
#define EP0CNTR_MINOR 0 /* EP0 protocol, minor revision */
/*
* bmRequestType:
*
* D7 D6..5 D4...0
* | | |
* direction (0 = host->dev)
* type (2 = vendor)
* recipient (0 = device)
*/
#define CNTR_TO_DEV(req) (0x40 | (req) << 8)
#define CNTR_FROM_DEV(req) (0xc0 | (req) << 8)
enum cntr_requests {
CNTR_ID = 0x00,
CNTR_BUILD,
CNTR_RESET,
CNTR_READ = 0x10,
};
void ep0_init(void);
#endif /* !EP0_H */

View File

@ -13,6 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <signal.h>
#include <usb.h> #include <usb.h>
#include <sys/time.h> #include <sys/time.h>
@ -28,6 +29,20 @@
#define BUF_SIZE 256 #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) static void reset_cntr(usb_dev_handle *dev)
{ {
@ -41,6 +56,9 @@ static void reset_cntr(usb_dev_handle *dev)
} }
/* ----- identify ---------------------------------------------------------- */
static void identify_cntr(usb_dev_handle *dev) static void identify_cntr(usb_dev_handle *dev)
{ {
const struct usb_device *device = usb_device(dev); const struct usb_device *device = usb_device(dev);
@ -71,19 +89,32 @@ static void identify_cntr(usb_dev_handle *dev)
} }
/* ----- measurements ------------------------------------------------------ */
struct sample { struct sample {
double t0, t1; double t0, t1;
uint64_t cntr; uint64_t cntr;
}; };
static void get_sample(usb_dev_handle *dev, struct sample *s) static unsigned packets = 0, crc_errors = 0, inv_errors = 0;
static volatile int stop = 0;
static void set_stop(int sig)
{
stop = 1;
}
static int get_sample(usb_dev_handle *dev, struct sample *s)
{ {
static uint32_t last = 0, high = 0; static uint32_t last = 0, high = 0;
struct timeval t0, t1; struct timeval t0, t1;
int res; int res, bad;
uint8_t buf[4]; uint8_t buf[12];
uint32_t cntr; uint32_t cntr, inv, crc, expect;
gettimeofday(&t0, NULL); gettimeofday(&t0, NULL);
res = usb_control_msg(dev, FROM_DEV, CNTR_READ, 0, 0, res = usb_control_msg(dev, FROM_DEV, CNTR_READ, 0, 0,
@ -93,27 +124,73 @@ static void get_sample(usb_dev_handle *dev, struct sample *s)
fprintf(stderr, "CNTR_READ: %s\n", usb_strerror()); fprintf(stderr, "CNTR_READ: %s\n", usb_strerror());
exit(1); exit(1);
} }
packets++;
cntr = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); 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) if (last > cntr)
high++; high++;
last = cntr; last = cntr;
s->t0 = t0.tv_sec+t0.tv_usec/1000000.0; s->t0 = t0.tv_sec+t0.tv_usec/1000000.0;
s->t1 = t1.tv_sec+t1.tv_usec/1000000.0; s->t1 = t1.tv_sec+t1.tv_usec/1000000.0;
s->cntr = (uint64_t) high << 32 | cntr; 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;
} }
static void measure(usb_dev_handle *dev, double clock_dev_s) static void measure(usb_dev_handle *dev, double clock_dev_s, double error_goal)
{ {
struct sample start, now; struct sample start, now;
uint64_t dc; uint64_t dc;
double dt, f, error; double dt, f, error;
char *f_exp, error_exp; char *f_exp, *error_exp;
int i;
get_sample(dev, &start); signal(SIGINT, set_stop);
while (1) {
/*
* 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); usleep(100000);
get_sample(dev, &now); while (!get_sample(dev, &now));
dc = now.cntr-start.cntr; dc = now.cntr-start.cntr;
dt = now.t0-start.t0; dt = now.t0-start.t0;
f = dc/dt; f = dc/dt;
@ -133,40 +210,54 @@ static void measure(usb_dev_handle *dev, double clock_dev_s)
error += (start.t1-start.t0)/dt;/* start sample read */ error += (start.t1-start.t0)/dt;/* start sample read */
error += (now.t1-now.t0)/dt; /* last sample read */ error += (now.t1-now.t0)/dt; /* last sample read */
error += clock_dev_s/dt; /* system clock deviation */ error += clock_dev_s/dt; /* system clock deviation */
if (error > 1) { if (error >= 1) {
printf("\r(wait) "); printf("\r(wait) ");
fflush(stdout); fflush(stdout);
continue; continue;
} }
if (dc && error <= error_goal)
stop = 1;
error_exp = 'k'; error_exp = "%";
error *= 1000.0; /* ppm */ error *= 100.0;
if (error < 1.0) { if (error < 0.1) {
error_exp = 'm'; /* ppm */ error_exp = " ppm";
error *= 1000.0; error *= 10000.0;
} if (error < 1.0) {
if (error < 1.0) { error_exp = " ppb";
error_exp = 'b'; /* ppb */ error *= 1000.0;
error *= 1000.0; }
} }
printf("\r%6.1f %1.9f %sHz %3.3f pp%c ", printf("\r%6.1f %1.9f %sHz %3.3f%s ",
dt, f, f_exp, error, error_exp); dt, f, f_exp, error, error_exp);
fflush(stdout); 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) static void usage(const char *name)
{ {
fprintf(stderr, fprintf(stderr,
"usage: %s [clock_dev_s]\n" "usage: %s [-c clock_dev] [-d] [-v] [accuracy_ppm]\n"
"%6s %s -i\n" "%6s %s -i\n"
"%6s %s r\n\n" "%6s %s -r\n\n"
" clock_dev_s is the maximum deviation of the system clock, in seconds\n" " accuracy_ppm stop when specified accuracy is reached (default: never\n"
" (default: %g s)\n" " stop)\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" " -i identify the CNTR board\n"
" -r reset the CNTR board\n" " -r reset the CNTR board\n"
" -v verbose reporting of communication errors\n"
, name, "", name, "", name, DEFAULT_CLOCK_DEV_S); , name, "", name, "", name, DEFAULT_CLOCK_DEV_S);
exit(1); exit(1);
} }
@ -177,10 +268,19 @@ int main(int argc, char *const *argv)
usb_dev_handle *dev; usb_dev_handle *dev;
int c, identify = 0, reset = 0; int c, identify = 0, reset = 0;
double clock_dev_s = DEFAULT_CLOCK_DEV_S; double clock_dev_s = DEFAULT_CLOCK_DEV_S;
double error_goal = 0;
char *end; char *end;
while ((c = getopt(argc, argv, "ir")) != EOF) while ((c = getopt(argc, argv, "c:dir")) != EOF)
switch (c) { switch (c) {
case 'c':
clock_dev_s = strtod(argv[optind], &end);
if (*end)
usage(*argv);
break;
case 'd':
debug = 1;
break;
case 'i': case 'i':
identify = 1; identify = 1;
break; break;
@ -190,12 +290,14 @@ int main(int argc, char *const *argv)
default: default:
usage(*argv); usage(*argv);
} }
if (identify && reset)
usage(*argv);
switch (argc-optind) { switch (argc-optind) {
case 0: case 0:
break; break;
case 1: case 1:
clock_dev_s = strtod(argv[optind], &end); error_goal = strtod(argv[optind], &end)/1000000.0;
if (*end) if (*end)
usage(*argv); usage(*argv);
break; break;
@ -219,7 +321,7 @@ int main(int argc, char *const *argv)
return 0; return 0;
} }
measure(dev, clock_dev_s); measure(dev, clock_dev_s, error_goal);
return 0; return 0;
} }