changeset 0:bd62be88259d

initial import of rfcal code and docs from freecalypso-tools repository
author Mychaela Falconia <falcon@freecalypso.org>
date Sat, 20 May 2017 18:49:35 +0000
parents
children 698602bbd120
files Makefile cmu200/Makefile cmu200/dispatch.c cmu200/init.c cmu200/main.c cmu200/openport.c cmu200/secaddr.h cmu200/sercmd.c cmu200/sertool.c cmu200/session.c cmu200/socket.c doc/Architecture doc/CMU200-notes doc/Test-system-interface doc/VCXO-notes tsid-test/Makefile tsid-test/fc-tsid-shell.c vcxo-manual/Makefile vcxo-manual/genparams.c vcxo-manual/linear.c vcxo-manual/meas.h vcxo-manual/readmeas.c
diffstat 22 files changed, 1250 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,15 @@
+SUBDIR=	cmu200 tsid-test vcxo-manual
+
+all:	${SUBDIR}
+
+${SUBDIR}: FRC
+	cd $@; ${MAKE} ${MFLAGS}
+
+clean: FRC
+	rm -f a.out core errs
+	for i in ${SUBDIR}; do (cd $$i; ${MAKE} ${MFLAGS} clean); done
+
+install: FRC
+	for i in ${SUBDIR}; do (cd $$i; ${MAKE} ${MFLAGS} install); done
+
+FRC:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/Makefile	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,22 @@
+CC=	gcc
+CFLAGS=	-O2
+PROGS=	fc-cmu200d fc-serscpi
+INSTBIN=/opt/freecalypso/bin
+
+CMU200D_OBJS=	dispatch.o init.o main.o openport.o sercmd.o session.o socket.o
+SERSCPI_OBJS=	openport.o sertool.o
+
+all:	${PROGS}
+
+fc-cmu200d:	${CMU200D_OBJS}
+	${CC} ${CFLAGS} -o $@ ${CMU200D_OBJS}
+
+fc-serscpi:	${SERSCPI_OBJS}
+	${CC} ${CFLAGS} -o $@ ${SERSCPI_OBJS}
+
+install:
+	mkdir -p ${INSTBIN}
+	install -c ${PROGS} ${INSTBIN}
+
+clean:
+	rm -f *.o *.out *errs ${PROGS}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/dispatch.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,38 @@
+/*
+ * This module contains the code that dispatches client commands.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+extern char *client_cmd_fields[];
+extern int client_cmd_nfields;
+
+cmd_ping()
+{
+	send_socket_response("+Pong\n");
+	return(0);
+}
+
+static struct cmdtab {
+	char	*cmd_kw;
+	int	(*handler)();
+} cmdtab[] = {
+	{"ping", cmd_ping},
+	{0, 0}
+};
+
+dispatch_client_command()
+{
+	struct cmdtab *tp;
+
+	for (tp = cmdtab; tp->cmd_kw; tp++)
+		if (!strcmp(client_cmd_fields[0], tp->cmd_kw))
+			break;
+	if (tp->handler)
+		return tp->handler();
+	send_socket_response("-Unknown or unimplemented command\n");
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/init.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,48 @@
+/*
+ * This module contains the code that fc-cmu200d runs at startup,
+ * to put the CMU200 into a known state at the global level.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "secaddr.h"
+
+extern char instrument_response[];
+
+static char id_string[] = "Rohde&Schwarz,CMU";
+
+static void
+assign_secondary_addr(addr, func)
+	char *func;
+{
+	char cmdbuf[80];
+
+	sprintf(cmdbuf, "SYST:REM:ADDR:SEC %d,\"%s\"\n", addr, func);
+	send_scpi_cmd(cmdbuf);
+}
+
+init_cmu200()
+{
+	/* Test if we are connected to a CMU */
+	send_scpi_cmd("*IDN?\n");
+	collect_instr_response();
+	if (strncasecmp(instrument_response, id_string, strlen(id_string))) {
+		fprintf(stderr, "error: not connected to a CMU200\n");
+		exit(1);
+	}
+	/* init commands */
+	send_scpi_cmd("*SEC 0\n");
+	send_scpi_cmd("*RST;*OPC?\n");
+	collect_staropc_response();
+	send_scpi_cmd("SYST:NONV:DIS\n");
+	assign_secondary_addr(SECADDR_RF_NSIG, "RF_NSig");
+	assign_secondary_addr(SECADDR_GSM900MS_NSIG, "GSM900MS_NSig");
+	assign_secondary_addr(SECADDR_GSM1800MS_NSIG, "GSM1800MS_NSig");
+	assign_secondary_addr(SECADDR_GSM1900MS_NSIG, "GSM1900MS_NSig");
+	assign_secondary_addr(SECADDR_GSM850MS_NSIG, "GSM850MS_NSig");
+	send_scpi_cmd("*OPC?\n");
+	collect_staropc_response();
+	printf("CMU200 init complete\n");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/main.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,35 @@
+/*
+ * This module contains the main() function for fc-cmu200d.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int target_fd;
+
+static char default_socket_pathname[] = "/tmp/fc_rftest_socket";
+
+char *bind_socket_pathname;
+
+main(argc, argv)
+	char **argv;
+{
+	if (argc < 3 || argc > 4) {
+		fprintf(stderr,
+			"usage: %s serial-port baud [socket-pathname]\n",
+			argv[0]);
+		exit(1);
+	}
+	open_target_serial(argv[1], argv[2]);
+	set_serial_nonblock(0);
+	init_cmu200();
+	if (argc > 3)
+		bind_socket_pathname = argv[3];
+	else
+		bind_socket_pathname = default_socket_pathname;
+	create_listener_socket();
+	for (;;) {
+		get_socket_connection();
+		handle_session();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/openport.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,71 @@
+/*
+ * Serial port opening code for talking to CMU200
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+extern int target_fd;
+
+static struct baudrate {
+	char	*name;
+	speed_t	termios_code;
+} baud_rate_table[] = {
+	{"1200",	B1200},
+	{"2400",	B2400},
+	{"4800",	B4800},
+	{"9600",	B9600},
+	{"19200",	B19200},
+	{"38400",	B38400},
+	{"57600",	B57600},
+	{"115200",	B115200},
+	/* table search terminator */
+	{NULL,		B0}
+};
+
+open_target_serial(ttydev, baudname)
+	char *ttydev, *baudname;
+{
+	struct termios target_termios;
+	struct baudrate *br;
+
+	for (br = baud_rate_table; br->name; br++)
+		if (!strcmp(br->name, baudname))
+			break;
+	if (!br->name) {
+		fprintf(stderr, "baud rate \"%s\" unknown/unsupported\n",
+			baudname);
+		exit(1);
+	}
+	target_fd = open(ttydev, O_RDWR|O_NONBLOCK);
+	if (target_fd < 0) {
+		perror(ttydev);
+		exit(1);
+	}
+	target_termios.c_iflag = IGNBRK;
+	target_termios.c_oflag = 0;
+	target_termios.c_cflag = CLOCAL|HUPCL|CREAD|CS8|CRTSCTS;
+	target_termios.c_lflag = 0;
+	target_termios.c_cc[VMIN] = 1;
+	target_termios.c_cc[VTIME] = 0;
+	cfsetispeed(&target_termios, br->termios_code);
+	cfsetospeed(&target_termios, br->termios_code);
+	if (tcsetattr(target_fd, TCSAFLUSH, &target_termios) < 0) {
+		perror("initial tcsetattr on target");
+		exit(1);
+	}
+	return 0;
+}
+
+set_serial_nonblock(state)
+	int state;
+{
+	ioctl(target_fd, FIONBIO, &state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/secaddr.h	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,14 @@
+/*
+ * The assignment of secondary addresses to CMU200 function groups is
+ * arbitrary, but we need *some* assignment in order to access the SCPI
+ * objects that correspond to useful functions.  We define our secondary
+ * address assignments in this header file, set them up on the CMU200
+ * at start-up, and then use them to access the functions we need
+ * when we need them.
+ */
+
+#define	SECADDR_RF_NSIG		1
+#define	SECADDR_GSM900MS_NSIG	2
+#define	SECADDR_GSM1800MS_NSIG	3
+#define	SECADDR_GSM1900MS_NSIG	4
+#define	SECADDR_GSM850MS_NSIG	5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/sercmd.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,58 @@
+/*
+ * This module contains the functions that send serial commands to the CMU200
+ * and collect the instrument's serial responses.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+
+extern int target_fd;
+
+char instrument_response[4096];
+
+send_scpi_cmd(cmd)
+	char *cmd;
+{
+	printf("Command to CMU: %s", cmd);
+	write(target_fd, cmd, strlen(cmd));
+}
+
+collect_instr_response()
+{
+	char buf[BUFSIZ];
+	int cc, pos;
+
+	for (pos = 0; ; ) {
+		cc = read(target_fd, buf, sizeof buf);
+		if (cc <= 0) {
+			perror("error reading from serial port");
+			exit(1);
+		}
+		if (pos + cc > sizeof instrument_response) {
+			fprintf(stderr,
+		"error: response from CMU200 exceeds our buffer size\n");
+			exit(1);
+		}
+		bcopy(buf, instrument_response + pos, cc);
+		pos += cc;
+		if (instrument_response[pos-1] == '\n')
+			break;
+	}
+	instrument_response[pos-1] = '\0';
+	printf("Instrument response: %s\n", instrument_response);
+}
+
+collect_staropc_response()
+{
+	collect_instr_response();
+	if (instrument_response[0] != '1' ||
+	    instrument_response[1] && !isspace(instrument_response[1])) {
+		fprintf(stderr, "error: unexpected response to *OPC?\n");
+		exit(1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/sertool.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,88 @@
+/*
+ * This module contains the main() function for fc-serscpi, a manual tool
+ * intended for learning how to control the CMU200 with SCPI commands
+ * over RS-232.
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <strings.h>
+
+extern int errno;
+
+int target_fd;
+
+static void
+safe_output(buf, cc)
+	u_char *buf;
+{
+	int i, c;
+
+	for (i = 0; i < cc; i++) {
+		c = buf[i];
+		if (c == '\r' || c == '\n' || c == '\t' || c == '\b') {
+			putchar(c);
+			continue;
+		}
+		if (c & 0x80) {
+			putchar('M');
+			putchar('-');
+			c &= 0x7F;
+		}
+		if (c < 0x20) {
+			putchar('^');
+			putchar(c + '@');
+		} else if (c == 0x7F) {
+			putchar('^');
+			putchar('?');
+		} else
+			putchar(c);
+	}
+	fflush(stdout);
+}
+
+main(argc, argv)
+	char **argv;
+{
+	char buf[BUFSIZ];
+	fd_set fds, fds1;
+	register int i, cc, max;
+
+	if (argc != 3) {
+		fprintf(stderr, "usage: %s ttyname baudrate\n", argv[0]);
+		exit(1);
+	}
+	open_target_serial(argv[1], argv[2]);
+	set_serial_nonblock(0);
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	FD_SET(target_fd, &fds);
+	max = target_fd + 1;
+	for (;;) {
+		bcopy(&fds, &fds1, sizeof(fd_set));
+		i = select(max, &fds1, NULL, NULL, NULL);
+		if (i < 0) {
+			if (errno == EINTR)
+				continue;
+			perror("select");
+			exit(1);
+		}
+		if (FD_ISSET(0, &fds1)) {
+			cc = read(0, buf, sizeof buf);
+			if (cc <= 0)
+				exit(0);
+			write(target_fd, buf, cc);
+		}
+		if (FD_ISSET(target_fd, &fds1)) {
+			cc = read(target_fd, buf, sizeof buf);
+			if (cc <= 0) {
+				fprintf(stderr, "EOF/error on target tty\n");
+				exit(1);
+			}
+			safe_output(buf, cc);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/session.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,86 @@
+/*
+ * This module contains the code that handles a single local socket
+ * connection session.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+extern int activesock;
+
+#define	MAX_FIELDS	10
+
+char client_cmd[256], *client_cmd_fields[MAX_FIELDS+1];
+int client_cmd_nfields;
+
+parse_cmd_into_fields()
+{
+	char *cp;
+
+	client_cmd_nfields = 0;
+	for (cp = client_cmd; ; ) {
+		while (isspace(*cp))
+			cp++;
+		if (*cp == '\0')
+			break;
+		if (client_cmd_nfields >= MAX_FIELDS) {
+			send_socket_response("-Command has too many fields\n");
+			return(1);
+		}
+		client_cmd_fields[client_cmd_nfields++] = cp;
+		while (*cp && !isspace(*cp))
+			cp++;
+		if (*cp)
+			*cp++ = '\0';
+	}
+	client_cmd_fields[client_cmd_nfields] = 0;
+	return(0);
+}
+
+handle_command()
+{
+	char readbuf[256];
+	int cc, pos, rc;
+
+	for (pos = 0; ; ) {
+		cc = read(activesock, readbuf, sizeof readbuf);
+		if (cc <= 0) {
+			printf("Client program closed connection\n");
+			return(1);
+		}
+		if (pos + cc > sizeof client_cmd) {
+			send_socket_response("-Command too long\n");
+			return(1);
+		}
+		bcopy(readbuf, client_cmd + pos, cc);
+		pos += cc;
+		if (client_cmd[pos-1] == '\n')
+			break;
+	}
+	client_cmd[pos-1] = '\0';
+	printf("Client command: %s\n", client_cmd);
+	rc = parse_cmd_into_fields();
+	if (rc)
+		return(0);
+	if (!client_cmd_nfields) {
+		send_socket_response("+Empty command OK\n");
+		return(0);
+	}
+	return dispatch_client_command();
+}
+
+handle_session()
+{
+	int rc;
+
+	send_socket_response("+CMU200 interface daemon ready\n");
+	for (;;) {
+		rc = handle_command();
+		if (rc)
+			break;
+	}
+	close(activesock);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmu200/socket.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,81 @@
+/*
+ * This module handles the local UNIX domain socket interface for fc-cmu200d.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int listener, activesock;
+
+extern char *bind_socket_pathname;
+
+create_listener_socket()
+{
+	/* local socket binding voodoo copied from osmocon */
+	struct sockaddr_un local;
+	unsigned int namelen;
+	int rc;
+
+	listener = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (listener < 0) {
+		perror("socket(AF_UNIX, SOCK_STREAM, 0)");
+		exit(1);
+	}
+
+	local.sun_family = AF_UNIX;
+	strncpy(local.sun_path, bind_socket_pathname, sizeof(local.sun_path));
+	local.sun_path[sizeof(local.sun_path) - 1] = '\0';
+	unlink(local.sun_path);
+
+	/* we use the same magic that X11 uses in Xtranssock.c for
+	 * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+	local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+	namelen = SUN_LEN(&local);
+#else
+	namelen = strlen(local.sun_path) +
+		  offsetof(struct sockaddr_un, sun_path) + 1;
+#endif
+
+	rc = bind(listener, (struct sockaddr *) &local, namelen);
+	if (rc != 0) {
+		perror("bind on local socket");
+		exit(1);
+	}
+	rc = listen(listener, 1);
+	if (rc != 0) {
+		perror("listen");
+		exit(1);
+	}
+	return(0);
+}
+
+get_socket_connection()
+{
+	struct sockaddr_un un_addr;
+	socklen_t len;
+
+	len = sizeof(un_addr);
+	activesock = accept(listener, (struct sockaddr *) &un_addr, &len);
+	if (activesock < 0) {
+		perror("socket accept");
+		exit(1);
+	}
+	printf("Accepted local socket connection\n");
+	return(0);
+}
+
+send_socket_response(str)
+	char *str;
+{
+	printf("Msg to client: %s", str);
+	write(activesock, str, strlen(str));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/Architecture	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,53 @@
+The RF calibration process fundamentally consists of 3 parts:
+
+1: The FreeCalypso GSM MS device under test (DUT) to be calibrated;
+
+2: An RF test station to which the DUT is connected via an RF coax cable,
+   performing the RF signal analyzer and signal generator functions required
+   for the calibration procedures;
+
+3: A program that communicates with both the DUT and the RF test station and
+   orchestrates all of the signal generation, measurement and computation steps
+   to arrive at the final calibration results to be stored in the flash file
+   system of the DUT.  The steps are too numerous, tedious and repetitive to be
+   performed manually, hence automation is required in order to make the process
+   practical.
+
+The goal of the FreeCalypso RF calibration subproject is to produce a set of
+tools for performing part 3 of the above breakdown.  The current vision is that
+our automated calibration software will be broken down into two interfacing
+components:
+
+1: There will be a Test System Interface Daemon (TSID) that encapsulates the
+   magic specific to a particular brand of RF test station, e.g., R&S CMU200.
+   The TSID will only talk to the CMU200 or other RF test station, but not to
+   the Calypso DUT, and the intent is that the TSID only needs to be started
+   once at the beginning of a calibration work shift and then stay running as a
+   hundred or more FreeCalypso GSM devices may be calibrated on the production
+   line.  The TSID will present a local socket interface (can be changed to
+   TCP/IP if operation over a network is required) to which the other component
+   below will connect as a client.
+
+2: There will be a set of 3 programs (fc-rfcal-vcxo, fc-rfcal-rxband and
+   fc-rfcal-txband) that perform the 3 required calibration groups for each
+   individual FreeCalypso device unit on the production line.  The production
+   automation script will need to run fc-rfcal-vcxo first, then fc-rfcal-rxband
+   for each of the hardware-supported bands (e.g., 900, 1800 and 1900 on
+   FCDEV3B-900), then fc-rfcal-txband for each of the same bands.  Each of these
+   programs will talk both to the DUT (via rvinterf) and to the RF test system
+   (via the TSID), i.e., will need to connect to an already-running rvinterf
+   process and to an already-running TSID via local sockets.
+
+The programs in the second group above will contain no knowledge specific to
+R&S CMU200 or any other particular brand of RF test station, instead this
+knowledge is to be encapsulated in the TSID.  The interface between the TSID
+and its clients will be of a command-response nature, and will be defined from
+the perspective of the needs of the FreeCalypso calibration process, rather than
+from the perspective of the capabilities of the CMU200 - in other words, the
+calibration automation program will command the TSID to the effect of "I need
+this", and it will be the responsibility of the TSID to figure out how to
+perform the required measurement or signal generation on the given type of test
+equipment.
+
+See the Test-system-interface document for the details of the TSID socket
+interface.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/CMU200-notes	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,52 @@
+R&S CMU200 is the RF tester used for production RF calibration of FreeCalypso
+GSM devices.  The CMU200 can be operated in three ways: manually via the front
+panel, programmatically via GPIB and programmatically via SCPI commands over
+RS-232.  GPIB is the industry standard, but for FreeCalypso the Mother has
+adopted the RS-232 control interface method instead in order to avoid the
+exotic hardware and equally exotic drivers and libraries required for GPIB:
+using the RS-232 interface requires absolutely no special hardware or drivers
+or libraries, just userspace C code without any dependencies talking to a
+serial port just like we do when communicating with Calypso target serial ports.
+
+Initialization commands
+=======================
+
+Our Test System Interface Daemon for the CMU200 will issue the following SCPI
+commands to the instrument on start-up:
+
+*SEC 0
+*RST;*OPC?
+SYST:NONV:DIS
+SYST:REM:ADDR:SEC 1,"RF_NSig"
+SYST:REM:ADDR:SEC 2,"GSM900MS_NSig"
+SYST:REM:ADDR:SEC 3,"GSM1800MS_NSig"
+SYST:REM:ADDR:SEC 4,"GSM1900MS_NSig"
+SYST:REM:ADDR:SEC 5,"GSM850MS_NSig"
+
+VCXO calibration
+================
+
+When commanded to prepare for VCXO calibration, our TSID will command the
+CMU200 as follows:
+
+*SEC 2
+RFAN:CHAN 40CH
+RFAN:TSEQ GSM5
+
+Command to read frequency offset:
+
+READ:MOD?
+
+Signal generator mode
+=====================
+
+Turn signal generator on:
+
+*SEC 1
+SOUR:RFG:LEV <level_in_dbm>
+SOUR:RFG:FREQ <freq_in_hz>
+INIT:RFG
+
+Turn signal generator off:
+
+ABORT:RFG
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/Test-system-interface	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,46 @@
+The Test System Interface Daemon (TSID) will provide a connection-oriented
+(SOCK_STREAM) UNIX domain local socket at /tmp/fc_rftest_socket, and will speak
+an ASCII line-based command-response protocol on this socket.  All command and
+response lines will end with \n only (no \r), and each TSID response including
+the initial greeting on connection establishment will begin with a '+' or '-'
+character to indicate OK or error, respectively, like in POP3.
+
+The following commands are currently defined:
+
+vcxo-cal-setup band arfcn
+
+This command is sent to the TSID to prepare the RF test system for the VCXO
+calibration procedure.  The first argument after the command keyword is the
+band to be used (850, 900, 1800 or 1900) and the second argument is the ARFCN
+in that band on which the DUT will be transmitting.
+
+freq-meas hint
+
+This command directs the RF test system to take a frequency offset measurement
+(vcxo-cal-setup must have been performed previously) and report it back to the
+client in the response line.  The hint argument after the freq-meas keyword will
+be sent by the VCXO calibration program (fc-rfcal-vcxo) to tell the RF test
+system what kind of frequency offset measurement is being made, i.e., which step
+in the VCXO calibration sequence is being performed; the hint keywords remain
+to be defined.
+
+signal-gen-setup band
+
+This command is sent to the TSID to prepare the RF test system for tests in
+which the latter acts as a signal generator.  The argument after the command
+keyword is the band number (850, 900, 1800 or 1900), and it selects the
+downlink frequency band in which the signal generator will need to operate.
+
+signal-gen-sine arfcn offset level
+
+This command directs the RF test system to turn on its signal generator and put
+out a pure sine wave at the frequency corresponding to the specified ARFCN (in
+the downlink band previously selected with signal-get-setup) plus the specified
+offset in kHz (-67 or +67 with TI's Rx path calibration procedures), and at the
+specified Tx power level in dBm.
+
+signal-gen-off
+
+Turn off the signal generator.
+
+The commands for Tx power level calibration remain to be defined.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/VCXO-notes	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,227 @@
+How to calibrate the VCXO on your FreeCalypso development board
+===============================================================
+
+The process of calibrating the VCXO on a Calypso+Iota+Rita GSM MS consists of
+the following fundamental parts:
+
+* The antenna needs to be disconnected and the FreeCalypso device's RF output
+  (SMA on the FCDEV3B) needs to be connected to an RF test station such as an
+  R&S CMU200.
+
+* The DUT is commanded to transmit semi-continuously as if it were transmitting
+  on TCH: Tx in one timeslot out of 8, but with the DUT running its own notion
+  of the TDMA frame not synchronized to anything, and keep transmitting
+  endlessly in 1/8 out of every 4.615 ms.
+
+* The RF test station connected to the DUT is used in the RF analyzer mode to
+  measure the frequency offset of the DUT's signal, relative to the ideal uplink
+  frequency corresponding to the selected ARFCN.
+
+* The above frequency offset measurement is performed with the AFC DAC on the
+  Calypso device set to different values, the results of the initial
+  measurements are used to guide some additional measurements, some computations
+  are made from these results, and the computed values are written into the
+  FreeCalypso device's FFS.
+
+This procedure is meant to be automated by way of a program that talks both to
+the FreeCalypso DUT and to the RF test station and orchestrates all of the
+measurement and computation steps, but until this program gets written (we
+weren't able to get a hold of TI's original, hence we have to develop our own),
+use the following instructions to perform the VCXO calibration procedure
+manually.  You still need a CMU200 or equivalent, though - it is not possible
+to do any kind of calibration on a Calypso device by itself, without connecting
+it to some appropriate RF test equipment.
+
+Reference documentation
+=======================
+
+We have the following two TI documents which describe some of the RF calibration
+procedures including the one for the VCXO:
+
+ftp://ftp.freecalypso.org/pub/GSM/Calypso/rf_calibration.pdf
+
+https://www.freecalypso.org/LoCosto-docs/Production%20test%20and%20calibration/i_sample_rf_test_and_calibration_13_03_04_01991%20-%20v026.pdf
+
+Unfortunately neither of them corresponds to the exact evolutionary time point
+of interest to us: the first one corresponds to some chipset much earlier than
+the one we are working with, and to firmware versions much earlier than ours,
+whereas the second one is for TI's later LoCosto chipset.
+
+Commanding the DUT to transmit semi-continuously
+================================================
+
+There is only one VCXO calibration that is subsequently used for all bands in
+normal MS operation.  Both of the calibration instruction documents above
+instruct the operator to run the Tx in GSM900 mode on ARFCN 40, hence we shall
+do likewise until and unless we find some good reason to do differently.
+
+Issue the following commands through fc-tmsh to start the semi-continuous Tx:
+
+tms 1		# enter RF Test Mode
+rfpw 7 6 0	# select GSM 900+1800 band pair, GSM900 band within the pair
+rfpw 2 40	# set ARFCN to 40
+rfpw 8 0	# disable AFC algorithm, i.e., control the AFC DAC manually
+txpw 1 12	# Tx power level
+rfe 3		# start Rx & Tx without network sync
+
+WARNING: Before issuing the above commands, ensure that the antenna is
+disconnected and that the RF output will be going into your test equipment,
+not on the air!  Do not EVER issue these commands with a real antenna connected,
+unless your intent is to operate a rogue transmitter or jammer.
+
+At this point your CMU200 or equivalent should detect the uplink signal
+generated by the DUT (on the CMU200 one needs to set TSC to 5, dunno about
+other test equipment), and you should see some frequency offset.
+
+The actual calibration procedure
+================================
+
+1. Set the AFC DAC to -2048:
+
+   rfpw 9 -2048
+
+   and measure the frequency offset.  Note it down.
+
+2. Set the AFC DAC to +2048:
+
+   rfpw 9 2048
+
+   and again measure the frequency offset.  Note it down.
+
+Now you need to create an ASCII text file with your frequency offset
+measurements.  Each line represents one measurement and consists of two fields:
+the first field is the DAC value and the second field is the measured frequency
+offset in Hz.  On my FCDEV3B S/N 001 the first two measurements were:
+
+-2048	-30008
++2048	+21394
+
+Next you need to apply a linear model to the VCXO frequency offset as a function
+of the DAC input: if x is the DAC value and F is the resulting frequency offset,
+then the linear model is F = ax + b, where a and b need to be determined from
+two measured points (x1, F1) and (x2, F2).  Then once you have a and b, find the
+x value that should produce F = 0.  The fc-vcxo-linear utility will do this math
+for you: run it with the name of your text file with measurements as its only
+argument.
+
+With my measurements, the DAC_CENTER value computed by fc-vcxo-linear is 343.
+However, the linear model is not perfect, thus when you write this computed
+value into the DAC with the rfpw 9 command, the resulting frequency offset on
+the CMU200 screen may be quite far from 0.
+
+TI's instructions in the LoCosto document direct the calibration operator to do
+two more measurements at DAC_CENTER-100 and DAC_CENTER+100, where DAC_CENTER is
+the value we just computed by applying the linear model to the first two
+measurements.  However, in my case the frequency offset at DAC=343 (DAC_CENTER)
+was so negative that at DAC=443 (DAC_CENTER+100) it was still negative - and I
+assume that TI's intent was to capture a close range around the zero crossing.
+
+Therefore, when I get to writing the automated calibration program, I intend to
+change this part of the algorithm as follows: instead of adding or subtracting
+100 right now, first do an rfpw 9 with the DAC_CENTER value as computed from
+the linear model, make a frequency offset measurement, and see if it is negative
+or positive.  Then step the DAC value in the appropriate direction by some
+reasonable increment (e.g., 100) until the frequency offset changes sign.  Then
+take the two DAC values closest to the output frequency offset sign change.
+
+After doing the above, my measurement notes file became:
+
+-2048	-30008
++2048	+21394
+443	-669
+543	634
+
+This file needs to contain all four measurements, with the first two being at
+the extreme DAC values and with the second two hugging the empirically located
+zero crossing, when you feed it to the next step:
+
+fc-vcxo-param myvcxo.meas
+
+The fc-vcxo-param utility will compute the final math steps to produce the
+actual calibration values which will need to be uploaded to the FreeCalypso
+device and stored in its FFS.  With my measurements above, I got the following
+output:
+
+rf_table afcparams
+
+      3434	# Psi_sta_inv
+        15	# Psi_st
+   1000341	# Psi_st_32
+      4293	# Psi_st_inv
+
+      3954	# DAC_INIT * 8
+     -5860	# DAC_MIN * 8
+     11351	# DAC_MAX * 8
+      2560	# snr_thr
+
+# DAC_INIT: rfpw 10 494
+
+The output from fc-vcxo-param is in the rf_table format which our implementation
+of the rftw command takes as input, and the latter is the fc-tmsh command which
+you will need to issue in order to send this table to the FreeCalypso firmware
+in the DUT.
+
+Explanation of the numbers:
+
+* The Psi constants are computed from the slope of the VCXO, and are
+  subsequently used for the steering: when the DSP reports a particular
+  frequency offset (in the form of an angle in radians), by how much should the
+  DAC value be adjusted?  The slope I use for computing these Psi constants is
+  the one from the first two measurements at the extreme DAC values, as the
+  LoCosto document seems to indicate.
+
+* DAC_INIT is the DAC value at which the resulting frequency offset should be 0;
+  it is computed per the linear model from the second pair of measurements.
+
+* DAC_MIN and DAC_MAX are the DAC values which should produce frequency offsets
+  of -15 and +15 ppm, respectively, according to the LoCosto document.  I
+  compute them per the linear model from the first pair of measurements (the
+  extreme DAC ones), as that is what the LoCosto document says.
+
+* The SNR threshold is a constant that never needs to change.
+
+The 3 dac_* values in the afcparams structure are stored in the times 8 form.
+Examination of the afcparams values read out of several Openmoko-made GTA02
+units shows that the low 3 bits aren't necessarily zeros, indicating that TI's
+calibration program probably multiplied by 8 before converting from floating
+point to integer; I do likewise in fc-vcxo-param.
+
+Examination of the same afcparams values read out of Openmoko-made units also
+shows that the center, min and max DAC values do vary quite a bit from one unit
+to the next, whereas the Psi constants change very little.  The Psi constants
+which my program computed from my manual measurements on FCDEV3B S/N 001 are in
+the same range as those read out of Openmoko-made units, which is definitely a
+reassuring sign.
+
+Writing your VCXO calibration into FFS
+======================================
+
+Save the fc-vcxo-param output in a file, e.g.:
+
+fc-vcxo-param myvcxo.meas myvcxo.param
+
+Upload the generated afcparams table to your FreeCalypso device:
+
+rftw 9 myvcxo.param
+
+There is one more variable in the firmware, outside of the afcparams structure,
+which also holds the DAC_INIT value.  Set it with an rfpw 10 command as
+instructed in the last comment line emitted by fc-vcxo-param; in my case it was:
+
+rfpw 10 494
+
+Now save all of these values in the non-volatile flash file system:
+
+me 102
+me 103
+
+Cleaning up
+===========
+
+To shut off the transmitter you started earlier, issue this command:
+
+rfe 0
+
+Now power off your FreeCalypso device, disconnect the RF test setup, connect the
+antenna back, insert a SIM, do a fresh boot and see if you can connect to a real
+live GSM network with your VCXO calibration!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tsid-test/Makefile	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,16 @@
+CC=	gcc
+CFLAGS=	-O2
+PROGS=	fc-tsid-shell
+INSTBIN=/opt/freecalypso/bin
+
+all:	${PROGS}
+
+fc-tsid-shell:	fc-tsid-shell.c
+	${CC} ${CFLAGS} -o $@ $@.c
+
+install:
+	mkdir -p ${INSTBIN}
+	install -c ${PROGS} ${INSTBIN}
+
+clean:
+	rm -f *.o *.out *errs ${PROGS}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tsid-test/fc-tsid-shell.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,109 @@
+/*
+ * This program connects to the RF calibration Test System Interface Daemon
+ * (TSID) over the local socket interface and allows manual human interaction
+ * with the TSID for development.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+extern int errno;
+
+static char default_socket_pathname[] = "/tmp/fc_rftest_socket";
+
+char *socket_pathname;
+int sock;
+
+connect_local_socket()
+{
+	/* local socket binding voodoo copied from osmocon */
+	struct sockaddr_un local;
+	unsigned int namelen;
+	int rc;
+
+	sock = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (sock < 0) {
+		perror("socket(AF_UNIX, SOCK_STREAM, 0)");
+		exit(1);
+	}
+
+	local.sun_family = AF_UNIX;
+	strncpy(local.sun_path, socket_pathname, sizeof(local.sun_path));
+	local.sun_path[sizeof(local.sun_path) - 1] = '\0';
+
+	/* we use the same magic that X11 uses in Xtranssock.c for
+	 * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+	local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+	namelen = SUN_LEN(&local);
+#else
+	namelen = strlen(local.sun_path) +
+		  offsetof(struct sockaddr_un, sun_path) + 1;
+#endif
+
+	rc = connect(sock, (struct sockaddr *) &local, namelen);
+	if (rc != 0) {
+		perror(socket_pathname);
+		exit(1);
+	}
+
+	return(0);
+}
+
+main(argc, argv)
+	char **argv;
+{
+	char buf[BUFSIZ];
+	fd_set fds, fds1;
+	register int i, cc, max;
+
+	switch (argc) {
+	case 1:
+		socket_pathname = default_socket_pathname;
+		break;
+	case 2:
+		socket_pathname = argv[1];
+		break;
+	default:
+		fprintf(stderr, "usage: %s [socket-pathname]\n", argv[0]);
+		exit(1);
+	}
+	connect_local_socket();
+	FD_ZERO(&fds);
+	FD_SET(0, &fds);
+	FD_SET(sock, &fds);
+	max = sock + 1;
+	for (;;) {
+		bcopy(&fds, &fds1, sizeof(fd_set));
+		i = select(max, &fds1, NULL, NULL, NULL);
+		if (i < 0) {
+			if (errno == EINTR)
+				continue;
+			perror("select");
+			exit(1);
+		}
+		if (FD_ISSET(0, &fds1)) {
+			cc = read(0, buf, sizeof buf);
+			if (cc <= 0)
+				exit(0);
+			write(sock, buf, cc);
+		}
+		if (FD_ISSET(sock, &fds1)) {
+			cc = read(sock, buf, sizeof buf);
+			if (cc <= 0) {
+				fprintf(stderr, "EOF/error on socket read\n");
+				exit(1);
+			}
+			write(1, buf, cc);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vcxo-manual/Makefile	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,22 @@
+CC=	gcc
+CFLAGS=	-O2
+PROGS=	fc-vcxo-linear fc-vcxo-param
+INSTBIN=/opt/freecalypso/bin
+
+LINEAR_OBJS=	linear.o readmeas.o
+GENPARAMS_OBJS=	genparams.o readmeas.o
+
+all:	${PROGS}
+
+fc-vcxo-linear:	${LINEAR_OBJS}
+	${CC} ${CFLAGS} -o $@ ${LINEAR_OBJS}
+
+fc-vcxo-param:	${GENPARAMS_OBJS}
+	${CC} ${CFLAGS} -o $@ ${GENPARAMS_OBJS}
+
+install:
+	mkdir -p ${INSTBIN}
+	install -c ${PROGS} ${INSTBIN}
+
+clean:
+	rm -f *.o *.out *errs ${PROGS}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vcxo-manual/genparams.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,77 @@
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "meas.h"
+
+struct meas meas[4];
+float lin_a, lin_b, lin_a2, lin_b2;
+float dac_min, dac_max, dac_init;
+float Psi_sta, Psi_st;
+
+write_output(filename)
+	char *filename;
+{
+	FILE *outf;
+
+	if (filename) {
+		outf = fopen(filename, "w");
+		if (!outf) {
+			perror(filename);
+			exit(1);
+		}
+	} else
+		outf = stdout;
+
+	fputs("rf_table afcparams\n\n", outf);
+	/* Psi parameters */
+	fprintf(outf, "%10u\t# Psi_sta_inv\n", (unsigned)(1.0f / Psi_sta));
+	fprintf(outf, "%10u\t# Psi_st\n", (unsigned)(Psi_st * 65536.0f));
+	fprintf(outf, "%10u\t# Psi_st_32\n",
+		(unsigned)(Psi_st * 65536.0f * 65536.0f));
+	fprintf(outf, "%10u\t# Psi_st_inv\n\n", (unsigned)(1.0f / Psi_st));
+	/* DAC settings */
+	fprintf(outf, "%10d\t# DAC_INIT * 8\n", (int)(dac_init * 8.0f));
+	fprintf(outf, "%10d\t# DAC_MIN * 8\n", (int)(dac_min * 8.0f));
+	fprintf(outf, "%10d\t# DAC_MAX * 8\n", (int)(dac_max * 8.0f));
+	fprintf(outf, "%10d\t# snr_thr\n", 2560);
+	/* rfpw 10 setting in a comment line */
+	fprintf(outf, "\n# DAC_INIT: rfpw 10 %d\n", (int)dac_init);
+
+	if (filename)
+		fclose(outf);
+}
+
+main(argc, argv)
+	char **argv;
+{
+	if (argc < 2 || argc > 3) {
+		fprintf(stderr, "usage: %s meas-file [outfile]\n", argv[0]);
+		exit(1);
+	}
+	read_meas_file(argv[1], meas, 4);
+
+	/* first linear approximation */
+	lin_a = (float)(meas[1].freq_offset - meas[0].freq_offset) /
+		(float)(meas[1].dac_value - meas[0].dac_value);
+	lin_b = (float)meas[1].freq_offset - lin_a * meas[1].dac_value;
+
+	/* second linear approximation */
+	lin_a2 = (float)(meas[3].freq_offset - meas[2].freq_offset) /
+		(float)(meas[3].dac_value - meas[2].dac_value);
+	lin_b2 = (float)meas[3].freq_offset - lin_a2 * meas[3].dac_value;
+
+	/* DAC settings */
+	dac_min = (-13500.0f - lin_b) / lin_a;
+	dac_max = (13500.0f - lin_b) / lin_a;
+	dac_init = -lin_b2 / lin_a2;
+
+	/* Psi computations */
+	Psi_sta = 2.0f * (float)M_PI *
+		(float)(meas[1].freq_offset - meas[0].freq_offset) /
+		((float)(meas[1].dac_value - meas[0].dac_value) * 270833.0f);
+	Psi_st = Psi_sta * 0.8f;
+
+	/* spit it all out */
+	write_output(argv[2]);
+	exit(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vcxo-manual/linear.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,27 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "meas.h"
+
+struct meas meas[2];
+float lin_a, lin_b, target_off;
+int target_dac;
+
+main(argc, argv)
+	char **argv;
+{
+	if (argc < 2 || argc > 3) {
+		fprintf(stderr, "usage: %s meas-file [target]\n", argv[0]);
+		exit(1);
+	}
+	read_meas_file(argv[1], meas, 2);
+	if (argc > 2)
+		target_off = atof(argv[2]);
+	else
+		target_off = 0;
+	lin_a = (float)(meas[1].freq_offset - meas[0].freq_offset) /
+		(float)(meas[1].dac_value - meas[0].dac_value);
+	lin_b = (float)meas[1].freq_offset - lin_a * meas[1].dac_value;
+	target_dac = (target_off - lin_b) / lin_a;
+	printf("%d\n", target_dac);
+	exit(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vcxo-manual/meas.h	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,4 @@
+struct meas {
+	int	dac_value;
+	int	freq_offset;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vcxo-manual/readmeas.c	Sat May 20 18:49:35 2017 +0000
@@ -0,0 +1,61 @@
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "meas.h"
+
+read_meas_file(filename, meas_table, nmeas)
+	char *filename;
+	struct meas *meas_table;
+{
+	FILE *f;
+	char linebuf[256], *cp, *np;
+	int lineno;
+	struct meas *mtp;
+	int got_meas;
+
+	f = fopen(filename, "r");
+	if (!f) {
+		perror(filename);
+		exit(1);
+	}
+	mtp = meas_table;
+	got_meas = 0;
+	for (lineno = 1; fgets(linebuf, sizeof linebuf, f); lineno++) {
+		for (cp = linebuf; isspace(*cp); cp++)
+			;
+		if (*cp == '\0' || *cp == '#')
+			continue;
+		if (!isdigit(*cp) && *cp != '-' && *cp != '+') {
+inv:			fprintf(stderr, "%s line %d: invalid syntax\n",
+				filename, lineno);
+			exit(1);
+		}
+		np = cp++;
+		while (isdigit(*cp))
+			cp++;
+		if (!isspace(*cp))
+			goto inv;
+		mtp->dac_value = atoi(np);
+		while (isspace(*cp))
+			cp++;
+		if (!isdigit(*cp) && *cp != '-' && *cp != '+')
+			goto inv;
+		np = cp++;
+		while (isdigit(*cp))
+			cp++;
+		if (*cp && !isspace(*cp))
+			goto inv;
+		mtp->freq_offset = atoi(np);
+		mtp++;
+		got_meas++;
+		if (got_meas >= nmeas)
+			break;
+	}
+	fclose(f);
+	if (got_meas < nmeas) {
+		fprintf(stderr, "error: need %d measurements, got %d in %s\n",
+			nmeas, got_meas, filename);
+		exit(1);
+	}
+	return 0;
+}