FreeCalypso > hg > fc-rfcal-tools
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; +}