mirror of
git://projects.qi-hardware.com/ben-wpan.git
synced 2024-11-04 23:23:43 +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:
parent
4d49921b9b
commit
c7303e4ac1
22
TODO
22
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.
|
||||
|
66
cntr/README
Normal file
66
cntr/README
Normal 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
|
@ -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:
|
||||
|
62
cntr/fw/include/cntr/ep0.h
Normal file
62
cntr/fw/include/cntr/ep0.h
Normal 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 */
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <usb.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
@ -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 */
|
||||
error_exp = "%";
|
||||
error *= 100.0;
|
||||
if (error < 0.1) {
|
||||
error_exp = " ppm";
|
||||
error *= 10000.0;
|
||||
if (error < 1.0) {
|
||||
error_exp = 'm'; /* ppm */
|
||||
error_exp = " ppb";
|
||||
error *= 1000.0;
|
||||
}
|
||||
if (error < 1.0) {
|
||||
error_exp = 'b'; /* 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"
|
||||
"%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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user