From c7303e4ac152c793db957c4ff453451b8098e5b7 Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Wed, 25 Aug 2010 18:47:45 -0300 Subject: [PATCH] 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 --- TODO | 22 +++++- cntr/README | 66 ++++++++++++++++ cntr/fw/cntr/ep0.c | 20 ++++- cntr/fw/include/cntr/ep0.h | 62 +++++++++++++++ cntr/tools/cntr/cntr.c | 156 ++++++++++++++++++++++++++++++------- 5 files changed, 295 insertions(+), 31 deletions(-) create mode 100644 cntr/README create mode 100644 cntr/fw/include/cntr/ep0.h diff --git a/TODO b/TODO index 594a57a..2b8e016 100644 --- a/TODO +++ b/TODO @@ -105,10 +105,30 @@ Things not done yet - 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 - evaluate termination resistance - 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. diff --git a/cntr/README b/cntr/README new file mode 100644 index 0000000..c71af32 --- /dev/null +++ b/cntr/README @@ -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 diff --git a/cntr/fw/cntr/ep0.c b/cntr/fw/cntr/ep0.c index 098b305..fedb589 100644 --- a/cntr/fw/cntr/ep0.c +++ b/cntr/fw/cntr/ep0.c @@ -22,7 +22,6 @@ #include "cntr/ep0.h" #include "version.h" - #define debug(...) #define error(...) @@ -61,9 +60,13 @@ static __xdata uint8_t buf[128]; #define BUILD_OFFSET 7 /* '#' plus "65535" plus ' ' */ +/* crc32() */ +#include "cntr/crc32.c" + + static __bit my_setup(struct setup_request *setup) __reentrant { - unsigned tmp; + uint32_t tmp; uint8_t size, i; switch (setup->bmRequestType | setup->bRequest << 8) { @@ -102,7 +105,18 @@ static __bit my_setup(struct setup_request *setup) __reentrant buf[1] = cntr[1]; buf[2] = cntr[2]; 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; default: diff --git a/cntr/fw/include/cntr/ep0.h b/cntr/fw/include/cntr/ep0.h new file mode 100644 index 0000000..7dcaa7e --- /dev/null +++ b/cntr/fw/include/cntr/ep0.h @@ -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 */ diff --git a/cntr/tools/cntr/cntr.c b/cntr/tools/cntr/cntr.c index 9e526ce..013ac74 100644 --- a/cntr/tools/cntr/cntr.c +++ b/cntr/tools/cntr/cntr.c @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -28,6 +29,20 @@ #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) { @@ -41,6 +56,9 @@ static void reset_cntr(usb_dev_handle *dev) } +/* ----- identify ---------------------------------------------------------- */ + + static void identify_cntr(usb_dev_handle *dev) { const struct usb_device *device = usb_device(dev); @@ -71,19 +89,32 @@ static void identify_cntr(usb_dev_handle *dev) } +/* ----- measurements ------------------------------------------------------ */ + + struct sample { double t0, t1; 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; struct timeval t0, t1; - int res; - uint8_t buf[4]; - uint32_t cntr; + 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, @@ -93,27 +124,73 @@ static void get_sample(usb_dev_handle *dev, struct sample *s) 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; } -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; uint64_t dc; double dt, f, error; - char *f_exp, error_exp; + char *f_exp, *error_exp; + int i; - get_sample(dev, &start); - while (1) { + signal(SIGINT, set_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); - get_sample(dev, &now); + while (!get_sample(dev, &now)); dc = now.cntr-start.cntr; dt = now.t0-start.t0; 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 += (now.t1-now.t0)/dt; /* last sample read */ error += clock_dev_s/dt; /* system clock deviation */ - if (error > 1) { + if (error >= 1) { printf("\r(wait) "); fflush(stdout); continue; } + if (dc && error <= error_goal) + stop = 1; - error_exp = 'k'; - error *= 1000.0; /* ppm */ - if (error < 1.0) { - error_exp = 'm'; /* ppm */ - error *= 1000.0; - } - if (error < 1.0) { - error_exp = 'b'; /* ppb */ - error *= 1000.0; + 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 %1.9f %sHz %3.3f pp%c ", + printf("\r%6.1f %1.9f %sHz %3.3f%s ", dt, f, f_exp, 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 [clock_dev_s]\n" +"usage: %s [-c clock_dev] [-d] [-v] [accuracy_ppm]\n" "%6s %s -i\n" -"%6s %s r\n\n" -" clock_dev_s is the maximum deviation of the system clock, in seconds\n" -" (default: %g s)\n" +"%6s %s -r\n\n" +" accuracy_ppm stop when specified accuracy is reached (default: never\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" " -r reset the CNTR board\n" +" -v verbose reporting of communication errors\n" , name, "", name, "", name, DEFAULT_CLOCK_DEV_S); exit(1); } @@ -177,10 +268,19 @@ int main(int argc, char *const *argv) usb_dev_handle *dev; int c, identify = 0, reset = 0; double clock_dev_s = DEFAULT_CLOCK_DEV_S; + double error_goal = 0; char *end; - while ((c = getopt(argc, argv, "ir")) != EOF) + while ((c = getopt(argc, argv, "c:dir")) != EOF) switch (c) { + case 'c': + clock_dev_s = strtod(argv[optind], &end); + if (*end) + usage(*argv); + break; + case 'd': + debug = 1; + break; case 'i': identify = 1; break; @@ -190,12 +290,14 @@ int main(int argc, char *const *argv) default: usage(*argv); } + if (identify && reset) + usage(*argv); switch (argc-optind) { case 0: break; case 1: - clock_dev_s = strtod(argv[optind], &end); + error_goal = strtod(argv[optind], &end)/1000000.0; if (*end) usage(*argv); break; @@ -219,7 +321,7 @@ int main(int argc, char *const *argv) return 0; } - measure(dev, clock_dev_s); + measure(dev, clock_dev_s, error_goal); return 0; }