IEEE 802.15.4 subsystem
Sign in or create your account | Project List | Help
IEEE 802.15.4 subsystem Commit Details
Date: | 2010-08-25 23:47:45 (13 years 7 months ago) |
---|---|
Author: | Werner Almesberger |
Commit: | c7303e4ac152c793db957c4ff453451b8098e5b7 |
Message: | 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 |
Files: |
TODO (1 diff) cntr/README (1 diff) cntr/fw/cntr/ep0.c (3 diffs) cntr/fw/include/cntr/ep0.h (1 diff) cntr/tools/cntr/cntr.c (9 diffs) |
Change Details
TODO | ||
---|---|---|
105 | 105 | |
106 | 106 | - measure duty cycle |
107 | 107 | |
108 | - display activity on clock input and duty cycle | |
108 | - use the LED to display activity on clock input and duty cycle | |
109 | 109 | |
110 | 110 | - consider using a comparator and a DAC to allow for programmable logic levels |
111 | 111 | |
112 | 112 | - evaluate termination resistance |
113 | 113 | |
114 | 114 | - document circuit design |
115 | ||
116 | - record beats between 16 bit counter polls and use them for the estimate | |
117 | of lost cycles (2*1 is way too optimistic) | |
118 | ||
119 | - include system clock resolution in accuracy calculation | |
120 | ||
121 | - consider running shorter sliding windows to estimate drift | |
122 | ||
123 | - consider detecting unusual half-periods | |
124 | ||
125 | - consider using a reversed USB connector, to avoid having to cross D+/D- and, | |
126 | worse, VBUS and GND | |
127 | ||
128 | - test input performance by counting a source that emits a known number of | |
129 | cycles | |
130 | ||
131 | - consider using historical margins to sanity-check the current margin (if any | |
132 | old.max < curr.min or old.min > curr.max, we have a problem) and to further | |
133 | narrow the effective margin, thus achieving faster convergence. We would have | |
134 | to consider temperature drift of the frequency source in this case. |
cntr/README | ||
---|---|---|
1 | Arbitrary-precision counter | |
2 | =========================== | |
3 | ||
4 | Theory of operation | |
5 | ------------------- | |
6 | ||
7 | The arbitrary-precision counter counts clock cycles of a frequency | |
8 | source that is assumed to be free from drift. It compares the count | |
9 | with the host's system clock. If the system clock is synchronized with | |
10 | an accurate NTP reference, measurements with arbitrarily high accuracy | |
11 | can be obtained. | |
12 | ||
13 | In practice, this is limited by the the frequency source's drift and | |
14 | the time one is willing to wait. If NTP maintains the system time | |
15 | with an accuracy of +/- 100 ms, obtaining measurements with an | |
16 | accuracy of +/- 1 ppm would take about 28 hours. | |
17 | ||
18 | Additional error sources, such as the round-trip time when requesting | |
19 | a sample from the microcontroller, are also considered in the accuracy | |
20 | calculation. | |
21 | ||
22 | The counter consists of a board based on a C8051F320 microcontroller | |
23 | and the control software on the host. The microcontroller counts | |
24 | events in a free-running 16 bit counter that is regularly read and | |
25 | extended to 32 bits. The 32 bit counter is periodically queried by | |
26 | the host. | |
27 | ||
28 | The microcontroller's counter can count at a frequency of up to 3 MHz. | |
29 | (SYSCLK/4) | |
30 | ||
31 | In order to protect against transmission errors not detected by USB's | |
32 | CRC, which are occur relatively often, each packet is protected by a | |
33 | CRC-32 and an inverted copy of the payload. Corrupted packets are | |
34 | rejected by the host. | |
35 | ||
36 | The 32 bit counter wraps around at most once very 21.8 ms. The 32 bit | |
37 | counter wraps around at most every 1431 s. The host extends the 32 bit | |
38 | counter to 64 bits, and calculates frequency and accuracy from the | |
39 | count and the run time of the measurement application. | |
40 | ||
41 | ||
42 | Performing a measurement | |
43 | ------------------------ | |
44 | ||
45 | To perform a measurement, connect the CNTR board's probe input to the | |
46 | clock source and then run the "cntr" application on the host. An | |
47 | accuracy goal (in ppm) can be specified on the command line (see | |
48 | below). | |
49 | ||
50 | The host polls the microcontroller every 100 ms and displays the run | |
51 | time (in seconds), the measured frequency, and the accuracy achieved | |
52 | so far. | |
53 | ||
54 | Measurements can be stopped by pressing ^C or by specifying an | |
55 | accuracy goal. At the end, the total number of events counted and | |
56 | communication statistics are displayed. | |
57 | ||
58 | ||
59 | Updating the firmware | |
60 | --------------------- | |
61 | ||
62 | The protocol revision and the build date of the firmware of the CNTR | |
63 | board can be queried with "cntr -i". | |
64 | ||
65 | To update the firmware, run | |
66 | cntr -r && sleep 1 && dfu-util -d 0x20b7:0xcb72 -D cntr.bin |
cntr/fw/cntr/ep0.c | ||
---|---|---|
22 | 22 | #include "cntr/ep0.h" |
23 | 23 | #include "version.h" |
24 | 24 | |
25 | ||
26 | 25 | #define debug(...) |
27 | 26 | #define error(...) |
28 | 27 | |
... | ... | |
61 | 60 | #define BUILD_OFFSET 7 /* '#' plus "65535" plus ' ' */ |
62 | 61 | |
63 | 62 | |
63 | /* crc32() */ | |
64 | #include "cntr/crc32.c" | |
65 | ||
66 | ||
64 | 67 | static __bit my_setup(struct setup_request *setup) __reentrant |
65 | 68 | { |
66 | unsigned tmp; | |
69 | uint32_t tmp; | |
67 | 70 | uint8_t size, i; |
68 | 71 | |
69 | 72 | switch (setup->bmRequestType | setup->bRequest << 8) { |
... | ... | |
102 | 105 | buf[1] = cntr[1]; |
103 | 106 | buf[2] = cntr[2]; |
104 | 107 | buf[3] = cntr[3]; |
105 | usb_send(&ep0, buf, 4, NULL, NULL); | |
108 | tmp = (uint32_t) buf[0] | ((uint32_t) buf[1] << 8) | | |
109 | ((uint32_t) buf[2] << 16) | ((uint32_t) buf[3] << 24); | |
110 | tmp = crc32(tmp, 0xffffffff); | |
111 | buf[4] = tmp; | |
112 | buf[5] = tmp >> 8; | |
113 | buf[6] = tmp >> 16; | |
114 | buf[7] = tmp >> 24; | |
115 | buf[8] = ~cntr[0]; | |
116 | buf[9] = ~cntr[1]; | |
117 | buf[10] = ~cntr[2]; | |
118 | buf[11] = ~cntr[3]; | |
119 | usb_send(&ep0, buf, 12, NULL, NULL); | |
106 | 120 | return 1; |
107 | 121 | |
108 | 122 | default: |
cntr/fw/include/cntr/ep0.h | ||
---|---|---|
1 | /* | |
2 | * include/cntr/ep0.h - EP0 extension protocol | |
3 | * | |
4 | * Written 2008-2010 by Werner Almesberger | |
5 | * Copyright 2008-2010 Werner Almesberger | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | */ | |
12 | ||
13 | ||
14 | #ifndef EP0_H | |
15 | #define EP0_H | |
16 | ||
17 | /* | |
18 | * Direction bRequest wValue wIndex wLength | |
19 | * | |
20 | * ->host CNTR_ID - - 3 | |
21 | * ->host CNTR_BUILD - - #bytes | |
22 | * host-> CNTR_RESET - - 0 | |
23 | * | |
24 | * ->host CNTR_READ - 0 12 | |
25 | */ | |
26 | ||
27 | /* | |
28 | * EP0 protocol: | |
29 | * | |
30 | * 0.0 initial release | |
31 | */ | |
32 | ||
33 | #define EP0CNTR_MAJOR 0 /* EP0 protocol, major revision */ | |
34 | #define EP0CNTR_MINOR 0 /* EP0 protocol, minor revision */ | |
35 | ||
36 | ||
37 | /* | |
38 | * bmRequestType: | |
39 | * | |
40 | * D7 D6..5 D4...0 | |
41 | * | | | | |
42 | * direction (0 = host->dev) | |
43 | * type (2 = vendor) | |
44 | * recipient (0 = device) | |
45 | */ | |
46 | ||
47 | ||
48 | #define CNTR_TO_DEV(req) (0x40 | (req) << 8) | |
49 | #define CNTR_FROM_DEV(req) (0xc0 | (req) << 8) | |
50 | ||
51 | ||
52 | enum cntr_requests { | |
53 | CNTR_ID = 0x00, | |
54 | CNTR_BUILD, | |
55 | CNTR_RESET, | |
56 | CNTR_READ = 0x10, | |
57 | }; | |
58 | ||
59 | ||
60 | void ep0_init(void); | |
61 | ||
62 | #endif /* !EP0_H */ |
cntr/tools/cntr/cntr.c | ||
---|---|---|
13 | 13 | |
14 | 14 | #include <stdlib.h> |
15 | 15 | #include <stdio.h> |
16 | #include <signal.h> | |
16 | 17 | #include <usb.h> |
17 | 18 | #include <sys/time.h> |
18 | 19 | |
... | ... | |
28 | 29 | #define BUF_SIZE 256 |
29 | 30 | |
30 | 31 | |
32 | static int debug = 0; | |
33 | static int verbose = 0; | |
34 | ||
35 | ||
36 | /* ----- CRC, shared with firmware ----------------------------------------- */ | |
37 | ||
38 | ||
39 | /* crc32() */ | |
40 | ||
41 | #include "cntr/crc32.c" | |
42 | ||
43 | ||
44 | /* ----- reset ------------------------------------------------------------- */ | |
45 | ||
31 | 46 | |
32 | 47 | static void reset_cntr(usb_dev_handle *dev) |
33 | 48 | { |
... | ... | |
41 | 56 | } |
42 | 57 | |
43 | 58 | |
59 | /* ----- identify ---------------------------------------------------------- */ | |
60 | ||
61 | ||
44 | 62 | static void identify_cntr(usb_dev_handle *dev) |
45 | 63 | { |
46 | 64 | const struct usb_device *device = usb_device(dev); |
... | ... | |
71 | 89 | } |
72 | 90 | |
73 | 91 | |
92 | /* ----- measurements ------------------------------------------------------ */ | |
93 | ||
94 | ||
74 | 95 | struct sample { |
75 | 96 | double t0, t1; |
76 | 97 | uint64_t cntr; |
77 | 98 | }; |
78 | 99 | |
79 | 100 | |
80 | static void get_sample(usb_dev_handle *dev, struct sample *s) | |
101 | static unsigned packets = 0, crc_errors = 0, inv_errors = 0; | |
102 | static volatile int stop = 0; | |
103 | ||
104 | ||
105 | static void set_stop(int sig) | |
106 | { | |
107 | stop = 1; | |
108 | } | |
109 | ||
110 | ||
111 | static int get_sample(usb_dev_handle *dev, struct sample *s) | |
81 | 112 | { |
82 | 113 | static uint32_t last = 0, high = 0; |
83 | 114 | struct timeval t0, t1; |
84 | int res; | |
85 | uint8_t buf[4]; | |
86 | uint32_t cntr; | |
115 | int res, bad; | |
116 | uint8_t buf[12]; | |
117 | uint32_t cntr, inv, crc, expect; | |
87 | 118 | |
88 | 119 | gettimeofday(&t0, NULL); |
89 | 120 | res = usb_control_msg(dev, FROM_DEV, CNTR_READ, 0, 0, |
... | ... | |
93 | 124 | fprintf(stderr, "CNTR_READ: %s\n", usb_strerror()); |
94 | 125 | exit(1); |
95 | 126 | } |
127 | packets++; | |
96 | 128 | cntr = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); |
129 | crc = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); | |
130 | inv = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24); | |
131 | expect = crc32(cntr, ~0); | |
132 | bad = 0; | |
133 | if (crc != expect) { | |
134 | if (verbose) | |
135 | fprintf(stderr, "\nCRC error (count 0x%08x->0x%08x " | |
136 | "CRC 0x%08x/0x%08x)\n", | |
137 | (unsigned) last, (unsigned) cntr, (unsigned) crc, | |
138 | (unsigned) expect); | |
139 | bad = 1; | |
140 | crc_errors++; | |
141 | } | |
142 | if (cntr != (inv ^ 0xffffffff)) { | |
143 | if (verbose) | |
144 | fprintf(stderr, | |
145 | "\ninverted counter error (0x%08x->0x%08x, " | |
146 | "inv 0x%08x)\n", | |
147 | (unsigned) last, (unsigned) cntr, (unsigned) inv); | |
148 | bad = 1; | |
149 | inv_errors++; | |
150 | } | |
151 | if (bad) | |
152 | return 0; | |
97 | 153 | if (last > cntr) |
98 | 154 | high++; |
99 | 155 | last = cntr; |
100 | 156 | s->t0 = t0.tv_sec+t0.tv_usec/1000000.0; |
101 | 157 | s->t1 = t1.tv_sec+t1.tv_usec/1000000.0; |
102 | 158 | s->cntr = (uint64_t) high << 32 | cntr; |
159 | if (debug) | |
160 | printf("0x%llx 0x%lx\n", | |
161 | (unsigned long long ) s->cntr, (unsigned long) cntr); | |
162 | return 1; | |
103 | 163 | } |
104 | 164 | |
105 | 165 | |
106 | static void measure(usb_dev_handle *dev, double clock_dev_s) | |
166 | static void measure(usb_dev_handle *dev, double clock_dev_s, double error_goal) | |
107 | 167 | { |
108 | 168 | struct sample start, now; |
109 | 169 | uint64_t dc; |
110 | 170 | double dt, f, error; |
111 | char *f_exp, error_exp; | |
112 | ||
113 | get_sample(dev, &start); | |
114 | while (1) { | |
171 | char *f_exp, *error_exp; | |
172 | int i; | |
173 | ||
174 | signal(SIGINT, set_stop); | |
175 | ||
176 | /* | |
177 | * The round-trip time for getting the first sample is one of the | |
178 | * error terms. The smaller we can make it, the better. Thus, we try a | |
179 | * few times to improve our first result. | |
180 | */ | |
181 | while (!get_sample(dev, &start)); | |
182 | for (i = 0; i != 10; i++) { | |
183 | while (!get_sample(dev, &now)); | |
184 | if (now.t1-now.t0 < start.t1-start.t0) { | |
185 | if (debug) | |
186 | fprintf(stderr, "improve %g -> %g\n", | |
187 | start.t1-start.t0, now.t1-now.t0); | |
188 | start = now; | |
189 | } | |
190 | } | |
191 | while (!stop) { | |
115 | 192 | usleep(100000); |
116 | get_sample(dev, &now); | |
193 | while (!get_sample(dev, &now)); | |
117 | 194 | dc = now.cntr-start.cntr; |
118 | 195 | dt = now.t0-start.t0; |
119 | 196 | f = dc/dt; |
... | ... | |
133 | 210 | error += (start.t1-start.t0)/dt;/* start sample read */ |
134 | 211 | error += (now.t1-now.t0)/dt; /* last sample read */ |
135 | 212 | error += clock_dev_s/dt; /* system clock deviation */ |
136 | if (error > 1) { | |
213 | if (error >= 1) { | |
137 | 214 | printf("\r(wait) "); |
138 | 215 | fflush(stdout); |
139 | 216 | continue; |
140 | 217 | } |
141 | ||
142 | error_exp = 'k'; | |
143 | error *= 1000.0; /* ppm */ | |
144 | if (error < 1.0) { | |
145 | error_exp = 'm'; /* ppm */ | |
146 | error *= 1000.0; | |
147 | } | |
148 | if (error < 1.0) { | |
149 | error_exp = 'b'; /* ppb */ | |
150 | error *= 1000.0; | |
218 | if (dc && error <= error_goal) | |
219 | stop = 1; | |
220 | ||
221 | error_exp = "%"; | |
222 | error *= 100.0; | |
223 | if (error < 0.1) { | |
224 | error_exp = " ppm"; | |
225 | error *= 10000.0; | |
226 | if (error < 1.0) { | |
227 | error_exp = " ppb"; | |
228 | error *= 1000.0; | |
229 | } | |
151 | 230 | } |
152 | 231 | |
153 | printf("\r%6.1f %1.9f %sHz %3.3f pp%c ", | |
232 | printf("\r%6.1f %1.9f %sHz %3.3f%s ", | |
154 | 233 | dt, f, f_exp, error, error_exp); |
155 | 234 | fflush(stdout); |
156 | 235 | } |
236 | printf( | |
237 | "\n%llu counts, %u packets, %u CRC error%s, %u invert error%s\n", | |
238 | (unsigned long long) (now.cntr-start.cntr), | |
239 | packets, crc_errors, crc_errors == 1 ? "" : "s", | |
240 | inv_errors, inv_errors == 1 ? "" : "s"); | |
157 | 241 | } |
158 | 242 | |
159 | 243 | |
244 | /* ----- command-line parsing ---------------------------------------------- */ | |
245 | ||
246 | ||
160 | 247 | static void usage(const char *name) |
161 | 248 | { |
162 | 249 | fprintf(stderr, |
163 | "usage: %s [clock_dev_s]\n" | |
250 | "usage: %s [-c clock_dev] [-d] [-v] [accuracy_ppm]\n" | |
164 | 251 | "%6s %s -i\n" |
165 | "%6s %s r\n\n" | |
166 | " clock_dev_s is the maximum deviation of the system clock, in seconds\n" | |
167 | " (default: %g s)\n" | |
252 | "%6s %s -r\n\n" | |
253 | " accuracy_ppm stop when specified accuracy is reached (default: never\n" | |
254 | " stop)\n" | |
255 | " -c clock_dev maximum deviation of the system clock, in seconds\n" | |
256 | " (default: %g s)\n" | |
257 | " -d debug mode. Print counter values.\n" | |
168 | 258 | " -i identify the CNTR board\n" |
169 | 259 | " -r reset the CNTR board\n" |
260 | " -v verbose reporting of communication errors\n" | |
170 | 261 | , name, "", name, "", name, DEFAULT_CLOCK_DEV_S); |
171 | 262 | exit(1); |
172 | 263 | } |
... | ... | |
177 | 268 | usb_dev_handle *dev; |
178 | 269 | int c, identify = 0, reset = 0; |
179 | 270 | double clock_dev_s = DEFAULT_CLOCK_DEV_S; |
271 | double error_goal = 0; | |
180 | 272 | char *end; |
181 | 273 | |
182 | while ((c = getopt(argc, argv, "ir")) != EOF) | |
274 | while ((c = getopt(argc, argv, "c:dir")) != EOF) | |
183 | 275 | switch (c) { |
276 | case 'c': | |
277 | clock_dev_s = strtod(argv[optind], &end); | |
278 | if (*end) | |
279 | usage(*argv); | |
280 | break; | |
281 | case 'd': | |
282 | debug = 1; | |
283 | break; | |
184 | 284 | case 'i': |
185 | 285 | identify = 1; |
186 | 286 | break; |
... | ... | |
190 | 290 | default: |
191 | 291 | usage(*argv); |
192 | 292 | } |
293 | if (identify && reset) | |
294 | usage(*argv); | |
193 | 295 | |
194 | 296 | switch (argc-optind) { |
195 | 297 | case 0: |
196 | 298 | break; |
197 | 299 | case 1: |
198 | clock_dev_s = strtod(argv[optind], &end); | |
300 | error_goal = strtod(argv[optind], &end)/1000000.0; | |
199 | 301 | if (*end) |
200 | 302 | usage(*argv); |
201 | 303 | break; |
... | ... | |
219 | 321 | return 0; |
220 | 322 | } |
221 | 323 | |
222 | measure(dev, clock_dev_s); | |
324 | measure(dev, clock_dev_s, error_goal); | |
223 | 325 | |
224 | 326 | return 0; |
225 | 327 | } |