1
0
mirror of git://projects.qi-hardware.com/ben-wpan.git synced 2024-11-27 01:54:58 +02:00
ben-wpan/tools/atrf-path/atrf-path.c

426 lines
8.3 KiB
C
Raw Normal View History

/*
* atrf-path/atrf-path.c - Measure path characteristics
*
* Written 2011 by Werner Almesberger
* Copyright 2011 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.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include "at86rf230.h"
#include "misctxrx.h"
#include "cwtest.h"
#include "atrf.h"
#include "gui.h"
#include "sweep.h"
#define DEFAULT_TRIM 8
#define DEFAULT_POWER 15
static void set_channel(struct atrf_dsc *dsc, int chan)
{
atrf_reg_write(dsc, REG_PHY_CC_CCA, (1 << CCA_MODE_SHIFT) | chan);
}
static void init_common(struct atrf_dsc *dsc, int trim)
{
atrf_reg_write(dsc, REG_TRX_STATE, TRX_CMD_TRX_OFF);
atrf_reg_write(dsc, REG_XOSC_CTRL,
(XTAL_MODE_INT << XTAL_MODE_SHIFT) | trim);
atrf_set_clkm(dsc, 0);
}
static void init_tx(struct atrf_dsc *dsc, int trim, int power)
{
init_common(dsc, trim);
set_power_step(dsc, power, 0);
}
static void init_rx(struct atrf_dsc *dsc, int trim)
{
init_common(dsc, trim);
atrf_reg_write(dsc, REG_TRX_STATE, TRX_CMD_RX_ON);
}
static double rssi_to_dBm(double rssi)
{
return -91+3*(rssi-1);
}
static void sample(const struct sweep *sweep, int cont_tx,
struct sample *res, int first)
{
static int need_init = 1;
int i, rssi;
int sum = 0, min = -1, max = -1;
double offset = tx_power_step2dBm(sweep->tx, sweep->power);
if (cw_test_needs_reset(sweep->tx)) {
init_tx(sweep->tx, sweep->trim_tx, sweep->power);
need_init = 1;
}
usleep(155); /* table 7-2, tTR19 */
if (first || need_init) {
cw_test_begin(sweep->tx, cont_tx);
need_init = 0;
} else {
cw_test_resume(sweep->tx);
}
/* table 7-1, tTR10, doubling since it's a "typical" value */
usleep(2*16);
for (i = 0; i != sweep->samples; i++) {
/* according to 8.3.2, PHY_RSSI is updated every 2 us */
usleep(2);
rssi = atrf_reg_read(sweep->rx, REG_PHY_RSSI) & RSSI_MASK;
sum += rssi;
if (min == -1 || rssi < min)
min = rssi;
if (rssi > max)
max = rssi;
}
cw_test_end(sweep->tx);
res->avg = rssi_to_dBm((double) sum/sweep->samples)-offset;
res->min = rssi_to_dBm(min)-offset;
res->max = rssi_to_dBm(max)-offset;
}
static int do_half_sweep(const struct sweep *sweep, int cont_tx,
struct sample *res)
{
int i, chan;
int fail = 0;
if (sweep->cont_tx && sweep->cont_tx != cont_tx)
return 0;
chan = 11;
for (i = 0; i != N_CHAN; i++) {
set_channel(sweep->rx, chan);
set_channel(sweep->tx, chan);
sample(sweep, cont_tx, res, chan == 11 && !sweep->cont_tx);
if (res->avg > sweep->max[i])
fail = 1;
if (!fail && res->avg < sweep->min[i])
fail = -1;
res += 2;
chan++;
}
return fail;
}
int do_sweep(const struct sweep *sweep, struct sample *res)
{
int fail1, fail2;
fail1 = do_half_sweep(sweep, CONT_TX_M500K, res);
fail2 = do_half_sweep(sweep, CONT_TX_P500K, res+1);
if (fail1 > 0 || fail2 > 0)
return 1;
if (fail1 < 0 || fail2 < 0)
return -1;
return 0;
}
void print_sweep(const struct sweep *sweep, const struct sample *res)
{
int chan;
for (chan = 11; chan <= 26; chan++) {
if (sweep->cont_tx != CONT_TX_P500K)
printf("%.1f %.2f %.0f %.0f\n",
2350+5*chan-0.5, res->avg, res->min, res->max);
res++;
if (sweep->cont_tx != CONT_TX_M500K)
printf("%.1f %.2f %.0f %.0f\n",
2350+5*chan+0.5, res->avg, res->min, res->max);
res++;
}
}
static int do_sweeps(const struct sweep *sweep, int sweeps)
{
struct sample res[N_CHAN*2]; /* 2 offsets per channel */
int decision = 0, fail, pass;
int i;
/*
* The pass/fail logic here goes as follows:
*
* Pass if and only if all sweeps pass.
* Fail if and only if all sweeps are below the minimum.
* Make no decision if any sweeps were above the maximum or if there
* was a mixture of pass and fail.
*/
for (i = 0; i != sweeps; i++) {
if (i)
putchar('\n');
fail = do_sweep(sweep, res);
print_sweep(sweep, res);
pass = fail < 0 ? -1 : fail > 0 ? 0 : 1;
if (!i)
decision = pass;
else {
if (pass != decision)
decision = 0;
}
}
return decision;
}
static int do_read_profile(const char *name, struct sweep *sweep)
{
FILE *file;
char buf[300];
int got;
char *p;
double min = MIN_DIFF, max = MAX_DIFF;
int n = 0;
file = fopen(name, "r");
if (!file) {
perror(name);
exit(1);
}
while (fgets(buf, sizeof(buf), file)) {
p = strchr(buf, '\n');
if (p)
*p = 0;
p = strchr(buf, '#');
if (p)
*p = 0;
for (p = buf; *p && isspace(*p); p++);
if (!*p)
continue;
got = sscanf(buf, "%lf %lf", &min, &max);
switch (got) {
case 0:
fprintf(stderr, "can't parse \"%s\"\n", buf);
exit(1);
case 1:
max = MAX_DIFF;
/* fall through */
case 2:
if (min < MIN_DIFF) {
fprintf(stderr, "minimum is %g dBm\n",
MIN_DIFF);
exit(1);
}
if (max > MAX_DIFF) {
fprintf(stderr, "maximum is %g dBm\n",
MAX_DIFF);
exit(1);
}
if (min > max) {
fprintf(stderr, "lower bound > upper bound\n");
exit(1);
}
if (n == N_CHAN) {
fprintf(stderr, "too many channels\n");
exit(1);
}
sweep->min[n] = min;
sweep->max[n] = max;
n++;
break;
default:
abort();
}
}
fclose(file);
return n;
}
static void read_profile(const char *name, struct sweep *sweep)
{
int n = 0;
if (name)
n = do_read_profile(name, sweep);
while (n != N_CHAN) {
sweep->min[n] = MIN_DIFF;
sweep->max[n] = MAX_DIFF;
n++;
}
}
static void usage(const char *name)
{
fprintf(stderr,
"usage: %s common_args [[sweeps] samples]\n"
#ifdef HAVE_GFX
"%6s %s -g common_args [[sweeps] samples]\n"
#endif
"\n"
" common args: [-p power] [-P profile] [-t trim_tx [-t trim_rx]]\n"
" [-T offset] driver_tx[:arg] driver_rx[:arg]\n\n"
#ifdef HAVE_GFX
" -g display results graphically\n"
#endif
" -p power transmit power, 0 to 15 (default %d)\n"
" -P profile load profile for pass/fail decisions\n"
" -t trim trim capacitor, 0 to 15 (default %d)\n"
" -T offset constant wave offset in MHz, -0.5 or +0.5 (default: scan both)\n"
, name,
#ifdef HAVE_GFX
"", name,
#endif
DEFAULT_POWER, DEFAULT_TRIM);
exit(1);
}
int main(int argc, char **argv)
{
const char *tx_drv, *rx_drv;
struct sweep sweep = {
.trim_tx = -1,
.trim_rx = DEFAULT_TRIM,
.cont_tx = 0,
.samples = 1,
};
int graphical = 0;
int power = DEFAULT_POWER;
const char *profile = NULL;
int sweeps = 1;
unsigned long tmp;
char *end;
int c, decision;
while ((c = getopt(argc, argv, "gp:P:t:T:")) != EOF)
switch (c) {
case'g':
graphical = 1;
sweeps = 0;
break;
case 'p':
tmp = strtoul(optarg, &end, 0);
if (*end || tmp > 15)
usage(*argv);
power = tmp;
break;
case 'P':
profile = optarg;
break;
case 't':
tmp = strtoul(optarg, &end, 0);
if (*end || tmp > 15)
usage(*argv);
if (sweep.trim_tx == -1)
sweep.trim_tx = tmp;
else
sweep.trim_rx = tmp;
break;
case 'T':
if (!strcmp(optarg, "-0.5"))
sweep.cont_tx = CONT_TX_M500K;
else if (!strcmp(optarg, "+0.5"))
sweep.cont_tx = CONT_TX_P500K;
else
usage(*argv);
break;
default:
usage(*argv);
}
if (sweep.trim_tx == -1)
sweep.trim_tx = DEFAULT_TRIM;
switch (argc-optind) {
case 4:
sweep.samples = strtoul(argv[optind+3], &end, 0);
if (*end)
usage(*argv);
/* fall through */
case 3:
sweeps = strtoul(argv[optind+2], &end, 0);
if (*end)
usage(*argv);
if (argc-optind == 3) {
sweep.samples = sweeps;
sweeps = graphical ? 0 : 1;
}
/* fall through */
case 2:
tx_drv = argv[optind];
rx_drv = argv[optind+1];
break;
default:
usage(*argv);
}
read_profile(profile, &sweep);
sweep.tx = atrf_open(tx_drv);
if (!sweep.tx)
return 1;
sweep.rx = atrf_open(rx_drv);
if (!sweep.rx)
return 1;
sweep.power = 15-power;
init_rx(sweep.rx, sweep.trim_rx);
init_tx(sweep.tx, sweep.trim_tx, sweep.power);
if (graphical)
decision = gui(&sweep, sweeps);
else
decision = do_sweeps(&sweep, sweeps);
switch (decision) {
case -1:
printf("#FAIL\n");
break;
case 0:
break;
case 1:
printf("#PASS\n");
break;
default:
abort();
}
atrf_reg_write(sweep.tx, REG_TRX_STATE, TRX_CMD_TRX_OFF);
atrf_reg_write(sweep.rx, REG_TRX_STATE, TRX_CMD_TRX_OFF);
atrf_close(sweep.tx);
atrf_close(sweep.rx);
return 0;
}