Root/tools/atrf-path/atrf-path.c

Source at commit ac710d0de437e2292a94498c38339d8d978adbee created 6 years 7 months ago.
By Werner Almesberger, web/index.html: update shop links; status table; link to ATUSB schematics
1/*
2 * atrf-path/atrf-path.c - Measure path characteristics
3 *
4 * Written 2011 by Werner Almesberger
5 * Copyright 2011 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#include <stdlib.h>
15#include <stdio.h>
16#include <unistd.h>
17#include <string.h>
18#include <ctype.h>
19
20#include "at86rf230.h"
21
22#include "misctxrx.h"
23#include "cwtest.h"
24#include "atrf.h"
25
26#include "gui.h"
27#include "sweep.h"
28
29
30#define DEFAULT_TRIM 8
31#define DEFAULT_POWER 15
32
33
34static void set_channel(struct atrf_dsc *dsc, int chan)
35{
36    atrf_reg_write(dsc, REG_PHY_CC_CCA, (1 << CCA_MODE_SHIFT) | chan);
37}
38
39
40static void init_common(struct atrf_dsc *dsc, int trim)
41{
42    atrf_reg_write(dsc, REG_TRX_STATE, TRX_CMD_TRX_OFF);
43    atrf_reg_write(dsc, REG_XOSC_CTRL,
44        (XTAL_MODE_INT << XTAL_MODE_SHIFT) | trim);
45    atrf_set_clkm(dsc, 0);
46}
47
48
49static void init_tx(struct atrf_dsc *dsc, int trim, int power)
50{
51    init_common(dsc, trim);
52    set_power_step(dsc, power, 0);
53}
54
55
56static void init_rx(struct atrf_dsc *dsc, int trim)
57{
58    init_common(dsc, trim);
59    atrf_reg_write(dsc, REG_TRX_STATE, TRX_CMD_RX_ON);
60}
61
62
63static double rssi_to_dBm(double rssi)
64{
65    return -91+3*(rssi-1);
66}
67
68
69static void sample(const struct sweep *sweep, int cont_tx,
70    struct sample *res, int first)
71{
72    static int need_init = 1;
73    int i, rssi;
74    int sum = 0, min = -1, max = -1;
75    double offset = tx_power_step2dBm(sweep->tx, sweep->power);
76
77    if (cw_test_needs_reset(sweep->tx)) {
78        init_tx(sweep->tx, sweep->trim_tx, sweep->power);
79        need_init = 1;
80    }
81    usleep(155); /* table 7-2, tTR19 */
82    if (first || need_init) {
83        cw_test_begin(sweep->tx, cont_tx);
84        need_init = 0;
85    } else {
86        cw_test_resume(sweep->tx);
87    }
88    /* table 7-1, tTR10, doubling since it's a "typical" value */
89    usleep(2*16);
90
91    for (i = 0; i != sweep->samples; i++) {
92        /* according to 8.3.2, PHY_RSSI is updated every 2 us */
93        usleep(2);
94
95        rssi = atrf_reg_read(sweep->rx, REG_PHY_RSSI) & RSSI_MASK;
96        sum += rssi;
97        if (min == -1 || rssi < min)
98            min = rssi;
99        if (rssi > max)
100            max = rssi;
101    }
102
103    cw_test_end(sweep->tx);
104
105    res->avg = rssi_to_dBm((double) sum/sweep->samples)-offset;
106    res->min = rssi_to_dBm(min)-offset;
107    res->max = rssi_to_dBm(max)-offset;
108}
109
110
111static int do_half_sweep(const struct sweep *sweep, int cont_tx,
112    struct sample *res)
113{
114    int i, chan;
115    int fail = 0;
116
117    if (sweep->cont_tx && sweep->cont_tx != cont_tx)
118        return 0;
119    chan = 11;
120    for (i = 0; i != N_CHAN; i++) {
121        set_channel(sweep->rx, chan);
122        set_channel(sweep->tx, chan);
123
124        sample(sweep, cont_tx, res, chan == 11 && !sweep->cont_tx);
125        if (res->avg > sweep->max[i])
126            fail = 1;
127        if (!fail && res->avg < sweep->min[i])
128            fail = -1;
129
130        res += 2;
131        chan++;
132    }
133    return fail;
134}
135
136
137int do_sweep(const struct sweep *sweep, struct sample *res)
138{
139    int fail1, fail2;
140
141    fail1 = do_half_sweep(sweep, CONT_TX_M500K, res);
142    fail2 = do_half_sweep(sweep, CONT_TX_P500K, res+1);
143    if (fail1 > 0 || fail2 > 0)
144        return 1;
145    if (fail1 < 0 || fail2 < 0)
146        return -1;
147    return 0;
148}
149
150
151void print_sweep(const struct sweep *sweep, const struct sample *res)
152{
153    int chan;
154
155    for (chan = 11; chan <= 26; chan++) {
156        if (sweep->cont_tx != CONT_TX_P500K)
157            printf("%.1f %.2f %.0f %.0f\n",
158                2350+5*chan-0.5, res->avg, res->min, res->max);
159        res++;
160        if (sweep->cont_tx != CONT_TX_M500K)
161            printf("%.1f %.2f %.0f %.0f\n",
162                2350+5*chan+0.5, res->avg, res->min, res->max);
163        res++;
164    }
165}
166
167
168static int do_sweeps(const struct sweep *sweep, int sweeps)
169{
170    struct sample res[N_CHAN*2]; /* 2 offsets per channel */
171    int decision = 0, fail, pass;
172    int i;
173
174    /*
175     * The pass/fail logic here goes as follows:
176     *
177     * Pass if and only if all sweeps pass.
178     * Fail if and only if all sweeps are below the minimum.
179     * Make no decision if any sweeps were above the maximum or if there
180     * was a mixture of pass and fail.
181     */
182
183    for (i = 0; i != sweeps; i++) {
184        if (i)
185            putchar('\n');
186        fail = do_sweep(sweep, res);
187        print_sweep(sweep, res);
188        pass = fail < 0 ? -1 : fail > 0 ? 0 : 1;
189        if (!i)
190            decision = pass;
191        else {
192            if (pass != decision)
193                decision = 0;
194        }
195    }
196    return decision;
197}
198
199
200static int do_read_profile(const char *name, struct sweep *sweep)
201{
202    FILE *file;
203    char buf[300];
204    int got;
205    char *p;
206    double min = MIN_DIFF, max = MAX_DIFF;
207    int n = 0;
208
209    file = fopen(name, "r");
210    if (!file) {
211        perror(name);
212        exit(1);
213    }
214    while (fgets(buf, sizeof(buf), file)) {
215        p = strchr(buf, '\n');
216        if (p)
217            *p = 0;
218        p = strchr(buf, '#');
219        if (p)
220            *p = 0;
221        for (p = buf; *p && isspace(*p); p++);
222        if (!*p)
223            continue;
224        got = sscanf(buf, "%lf %lf", &min, &max);
225        switch (got) {
226        case 0:
227            fprintf(stderr, "can't parse \"%s\"\n", buf);
228            exit(1);
229        case 1:
230            max = MAX_DIFF;
231            /* fall through */
232        case 2:
233            if (min < MIN_DIFF) {
234                fprintf(stderr, "minimum is %g dBm\n",
235                    MIN_DIFF);
236                exit(1);
237            }
238            if (max > MAX_DIFF) {
239                fprintf(stderr, "maximum is %g dBm\n",
240                    MAX_DIFF);
241                exit(1);
242            }
243            if (min > max) {
244                fprintf(stderr, "lower bound > upper bound\n");
245                exit(1);
246            }
247            if (n == N_CHAN) {
248                fprintf(stderr, "too many channels\n");
249                exit(1);
250            }
251            sweep->min[n] = min;
252            sweep->max[n] = max;
253            n++;
254            break;
255        default:
256            abort();
257        }
258    }
259    fclose(file);
260    return n;
261}
262
263
264static void read_profile(const char *name, struct sweep *sweep)
265{
266    int n = 0;
267
268    if (name)
269        n = do_read_profile(name, sweep);
270
271    while (n != N_CHAN) {
272        sweep->min[n] = MIN_DIFF;
273        sweep->max[n] = MAX_DIFF;
274        n++;
275    }
276}
277
278
279static void usage(const char *name)
280{
281    fprintf(stderr,
282"usage: %s common_args [[sweeps] samples]\n"
283
284#ifdef HAVE_GFX
285"%6s %s -g common_args [[sweeps] samples]\n"
286#endif
287    "\n"
288" common args: [-p power] [-P profile] [-t trim_tx [-t trim_rx]]\n"
289" [-T offset] driver_tx[:arg] driver_rx[:arg]\n\n"
290
291#ifdef HAVE_GFX
292" -g display results graphically\n"
293#endif
294" -p power transmit power, 0 to 15 (default %d)\n"
295" -P profile load profile for pass/fail decisions\n"
296" -t trim trim capacitor, 0 to 15 (default %d)\n"
297" -T offset constant wave offset in MHz, -0.5 or +0.5 (default: scan both)\n"
298
299    , name,
300#ifdef HAVE_GFX
301    "", name,
302#endif
303    DEFAULT_POWER, DEFAULT_TRIM);
304
305    exit(1);
306}
307
308    
309int main(int argc, char **argv)
310{
311    const char *tx_drv, *rx_drv;
312    struct sweep sweep = {
313        .trim_tx = -1,
314        .trim_rx = DEFAULT_TRIM,
315        .cont_tx = 0,
316        .samples = 1,
317    };
318    int graphical = 0;
319    int power = DEFAULT_POWER;
320    const char *profile = NULL;
321    int sweeps = 1;
322    unsigned long tmp;
323    char *end;
324    int c, decision;
325
326    while ((c = getopt(argc, argv, "gp:P:t:T:")) != EOF)
327        switch (c) {
328        case'g':
329            graphical = 1;
330            sweeps = 0;
331            break;
332        case 'p':
333            tmp = strtoul(optarg, &end, 0);
334            if (*end || tmp > 15)
335                usage(*argv);
336            power = tmp;
337            break;
338        case 'P':
339            profile = optarg;
340            break;
341        case 't':
342            tmp = strtoul(optarg, &end, 0);
343            if (*end || tmp > 15)
344                usage(*argv);
345            if (sweep.trim_tx == -1)
346                sweep.trim_tx = tmp;
347            else
348                sweep.trim_rx = tmp;
349            break;
350        case 'T':
351            if (!strcmp(optarg, "-0.5"))
352                sweep.cont_tx = CONT_TX_M500K;
353            else if (!strcmp(optarg, "+0.5"))
354                sweep.cont_tx = CONT_TX_P500K;
355            else
356                usage(*argv);
357            break;
358        default:
359            usage(*argv);
360        }
361
362    if (sweep.trim_tx == -1)
363        sweep.trim_tx = DEFAULT_TRIM;
364
365    switch (argc-optind) {
366    case 4:
367        sweep.samples = strtoul(argv[optind+3], &end, 0);
368        if (*end)
369            usage(*argv);
370        /* fall through */
371    case 3:
372        sweeps = strtoul(argv[optind+2], &end, 0);
373        if (*end)
374            usage(*argv);
375        if (argc-optind == 3) {
376            sweep.samples = sweeps;
377            sweeps = graphical ? 0 : 1;
378        }
379        /* fall through */
380    case 2:
381        tx_drv = argv[optind];
382        rx_drv = argv[optind+1];
383        break;
384    default:
385        usage(*argv);
386    }
387
388    read_profile(profile, &sweep);
389
390    sweep.tx = atrf_open(tx_drv);
391    if (!sweep.tx)
392        return 1;
393    sweep.rx = atrf_open(rx_drv);
394    if (!sweep.rx)
395        return 1;
396
397    sweep.power = 15-power;
398    init_rx(sweep.rx, sweep.trim_rx);
399    init_tx(sweep.tx, sweep.trim_tx, sweep.power);
400    if (graphical)
401        decision = gui(&sweep, sweeps);
402    else
403        decision = do_sweeps(&sweep, sweeps);
404
405    switch (decision) {
406    case -1:
407        printf("#FAIL\n");
408        break;
409    case 0:
410        break;
411    case 1:
412        printf("#PASS\n");
413        break;
414    default:
415        abort();
416    }
417
418    atrf_reg_write(sweep.tx, REG_TRX_STATE, TRX_CMD_TRX_OFF);
419    atrf_reg_write(sweep.rx, REG_TRX_STATE, TRX_CMD_TRX_OFF);
420
421    atrf_close(sweep.tx);
422    atrf_close(sweep.rx);
423
424    return 0;
425}
426

Archive Download this file



interactive