/*********************************************************************** * * * Program to control LORAN-C radio * * * * This program controls a special-purpose radio designed to receive * * transmissions from the US Coast Guard LORAN-C navigation system. * * These stations operate on an assigned radio frequency of 100 kHz * * and can be received over the continental US, adjacent coastal areas * * and significant areas elsewhere in the world. * * * * The analog radio and integrated digital controller are contained on * * an 8" PC card that plugs directly into the IBM PC bus. The radio * * receives LORAN-C signals consisting of a eight-pulse biphase- * * modulated pulse groups transmitted at a 1-kHz rate. Each of these * * pulse groups is repeated at an interval characteristic of the * * particular LORAN-C chain, which consists of a master station and up * * to four slave stations. The radio includes a synchronous detector * * driven by a quadrature-phase clock, two integrators with adjustable * * gain and a peak-reading, signal-level detector. * * * * The radio is controlled by this program using a special-purpose * * interface, which processes the received signals using an * * analog/digital converter and multiplexor. It generates the digital * * timing and analog control signals using an AMD 9513A System Timing * * Controller (STC) chip, two digital/analog converters and * * miscellaneous logic components. The radio provides three analog * * signals, one for the in-phase integrator, another for the * * quadrature-phase integrator and a third for the signal-level * * detector. This program computes the master oscillator frequency- * * adjustment voltage and receiver gain-control voltage. * * * * The receiver supports both an internal uncompensated crystal * * oscillator and an external oven-controlled crystal oscillator used * * to derive all timing signals used by the receiver and this program. * * The 5-MHz output of these oscillators is adjustable over a small * * range by this program to coincide with the LORAN-C signal as * * broadcast to within a few parts in 1e10 in both frequency and time. * * It is intended for use as a laboratory frequency standard. The * * external oscillator should have good intrinsic stability and * * setability to within less than 0.5 Hz at 5 MHz (0.1 ppm), since it * * must maintain the master clock to within 100 us over the * * pulse-group scan interval up to several minutes. * * * * The PC running this program generates the control signals necessary * * to run the radio and produces a 1-pps signal synchronized to * * UTC(LORAN) to within a fraction of a microsecond. When manually * * adjusted using time-of-coincidence (TOC) data published by US Naval * * Observatory, this signal is suitable for use as a precision source * * of standard time. The system can generate all sorts of external * * signals as well, as programmed in the 9513A. * * * * This program requires a data file ("loran.dat") containing the * * geographic coordinates and other information about the LORAN-C * * chains being used. The format of this file is described under the * * subroutine heading init_loran(). There are four files containing * * the source of this program: * * * * loran.c main program (this file) * * gri.c signal processing subroutines * * subs.c input/output and utility subroutines * * tables.c initialization tables * * loran.h header file used by all programs * * * * This program was developed using Microsoft QuickC for Windows, but * * the production versions are normally compiled to run under ordinary * * polymorphic DOS versions. * * * * David L. Mills * * Electrical Engineering Department * * University of Delaware * * Newark, DE 19716 * * mills@udel.edu * * * *********************************************************************** */ #include "loran.h" #include /* * External function declarations */ extern void pulse_group(struct station *, double, double, double); extern struct station *receive(void); extern void timerq(struct station *, long); extern void command(void); extern void init_station(struct station *); int init_loran(long); /* * Imported from gri.c */ extern int vcodac; /* vco dac bias (dac a) */ extern int agcdac; /* agc dac bias (dac b) */ extern int nstation; /* number of stations allocated */ extern struct station *chain[]; /* station structure pointers */ extern int dindex; /* display index */ extern int par; /* mode register */ extern long gri; /* group repetition interval (CYCLE) */ extern long fri; /* frame interval (2 * gri) (CYCLE) */ extern long offset; /* current frame offset (CYCLE) */ /* * Imported from tables.c */ extern int init[]; /* stc initialization vector */ /* * Local data declarations */ double iofs; /* i-integrator offset */ double qofs; /* q-integrator offset */ int peak_detect; /* s-signal (adc chan 2) */ double agcavg; /* receiver agc smoothed signal */ double agcofs; /* receiver agc offset (zero signal) */ /* * Navigation stuff */ char rcvr_name[20]; /* receiver name */ double rcvr_lat; /* receiver latitude (rad) */ double rcvr_lon; /* receiver longitude (rad) */ double rcvr_delay; /* antenna-receiver delay (us) */ /* * File stuff */ FILE *fp_in; /* file handle */ char loran_data[] = LORSTA; /* loran-c station data file */ /* * Main program * * Programming note: There is usually enough time between gri intervals * for one display line, but not two, at least on a 386/20. The compile * parameter DGUARD can be changed to allow more time for this. Note * also that the floating-point coprocessor is necessary for this thing * to run at all. * * Usage: * assigned LORAN-C group repitition interval (default 9960) * initial agc dac (0-4095) (default agc parameter) * initial vco dac (0-4095) (default vco parameter) * * Variables and functions used * sptr station structure pointer * agcdac agc dac bias (dac b) * vcodac vco dac bias (dac a) * gri group repetition interval (CYCLE) * fri frame interval (2 * gri) (CYCLE) * offset current frame offset (CYCLE) * nstation number of stations allocated * chain[] vector of station pointers * peak_detect s-signal (adc chan 2) * agcavg receiver agc smoothed signal * agcofs receiver agc offset (zero signal) * command() decode keyboard tinkle * pulse_group() process pulse group * init_loran() initialize loran chain data * init_station() initialize station data structure * receive() wait for next gri and read adc * timerq() insert delay on timer queue */ int main(argc, argv) int argc; char *argv[]; { struct station *sptr; /* station pointer temps */ int i; /* utility ints */ /* * Decode command-line arguments * */ gri = 9960; /* default northeast chain */ if (argc > 1) sscanf(argv[1], "%li", &gri); if (argc > 2) sscanf(argv[3], "%i", &agcdac); if (argc > 3) sscanf(argv[4], "%i", &vcodac); /* * Initialization * * This section runs only once. It resets the timing generator, * loads its registers with default values and clears arrays. The * program then begins the initialization sequence using the default * station structure. */ #if !defined(DEBUG) outp(TGC, RESET); outp(TGC, LOAD+0x1f); /* reset STC chip */ outp(TGC, LOADDP+MASTER); outp(TGD, 0xf0); outp(TGD, 0x8a); outp(TGC, LOADDP+1); for (i = 0; i < 5*3; i++) { outp(TGD, init[i]); outp(TGD, init[i] >> 8); } outp(TGC, LOADARM+0x1f); /* let the good times roll */ /* par = outp(PAR, par | ENG); outp(ADC, I); /* i */ #endif /* * Initialize up to six stations for selected chain. Start the first * one in mode 1 to calibrate the receiver. The others will be * started in mode 4. Give up if there are no stations found. */ for (i = 0; i < NSTA; i++) { sptr = (struct station *)malloc(sizeof(struct station)); chain[i] = sptr; memset(sptr, 0, sizeof(struct station)); sptr->index = i; init_station(sptr); sptr->type = ' '; sptr->kbd = 'l'; /* *** temp *** */ } nstation = init_loran(gri); if (nstation == 0) { printf("No stations found\n"); return (-1); } fri = 2 * gri; offset = 0; sptr = chain[nstation - 1]; sptr->mode = MODE_MIN; timerq(sptr, 0); sprintf(sptr->report, "Calibrating receiver"); /* * Main loop * * This is the main receiver loop and runs until escaped by a ^C * signal. The main loop runs twice per frame or once each gri * (pulse groups a and b) and performs the main receiver update * between the end of pulse group b and the beginning of pulse group * a. */ while (1) { /* * Process i-phase, q-phase and agc * * Note that a LORAN frame consists of two gri intervals a and * b, each with individual pulse codes. The receiver integrates * each gri using the assigned pulse codes. There are two sets * of pulse codes, one for the master station and the other for * slave stations, of which there may be as many as four. Each * LORAN chain is assigned a unique gri interval in the range * 40-100 ms. */ sptr = receive(); if (sptr->codesw > 0) { sptr->codesw -= 2; /* * At the end of gri b the receiver variables are updated * and set up for the next frame (gri a and gri b). */ sptr->count++; pulse_group(sptr, sptr->isig - iofs, sptr->qsig - qofs, peak_detect - agcofs); } else { /* * At the end of gri a the command interpreter is * interrogated and the display updated, if necessary. */ #if !defined(DEBUG) if (kbhit()) command(); #endif if (sptr->report[0] != '\0') { puts(sptr->report); sptr->report[0] = '\0'; } } sptr->codesw++; timerq(sptr, sptr->phase); sptr->phase = 0; } } /* * Subroutine init_loran() * * This subroutine to fetch station data and initializes the station * data structures. See the tables.c module for an index of the chain * gri, chain names and location names. * * The first line in the station data file has the format: * * 39 40 48.18 75 45 03.06 10.0 Newark, DE * xxxxxxxxxxx yyyyyyyyyyy dddd nnnnnnnnnn * * x receiver north latitude (deg, min, sec.frac) * y receiver west longitude (deg, min, sec.frac) * d receiver internal delay (us) * n receiver location name * * The remaining lines have the format: * * 9960 x 41 15 12.046 69 58 38.536 26969.93 325 Nantucket, MA * gggg s xxxxxxxxxxxx yyyyyyyyyyyy dddddddd ppp nnnnnnnnnnnnn * * g chain gri (cycles) * s transmitter station identifier (master m; slaves w,...) * x transmitter north latitude (deg, min, sec.frac) * y transmitter west longitude (deg, min, sec.frac) * d transmitter emission delay (us) * p transmitter radiated power (kW) * n transmitter location name * * Note: the stations must be listed master first followed by the slaves * in order of emission delay. * * Calling sequence: n = init_loran(sel) * * sel gri of selected chain * n number of stations allocated * * Variables and functions used * sptr station data structure * loran_data name of station data file * chain[] vector of station data structure pointers */ int init_loran(sel) long sel; /* gri of selected chain */ { long gri; /* group repetition interval (CYCLE) */ char type; /* station ident (m, w, x, y, z) */ double lat[3]; /* north latitude (deg, min, sec) */ double lon[3]; /* west longitude (deg, min, sec) */ double ems_delay; /* emission delay (us) */ double rad_power; /* radiated power (kw) */ char name[20]; /* location name */ int temp; /* int temps */ struct station *sptr; /* station pointer temps */ double theta, d; /* double temps */ /* * Open data file read-only and display header. */ temp = 0; if ((fp_in = fopen (loran_data, "r")) == NULL) { printf("LORAN-C data file %s not found\n", loran_data); return (temp); } fscanf(fp_in, "%lf%lf%lf%lf%lf%lf%lf %20[^\n]", &lat[0], &lat[1], &lat[2], &lon[0], &lon[1], &lon[2], &rcvr_delay, &rcvr_name); rcvr_lat = (lat[0] + (lat[1] + lat[2] / 60) / 60) * D2R; rcvr_lon = (lon[0] + (lon[1] + lon[2] / 60) / 60) * D2R; printf("LORAN-C stations at GRI%5i\nLocation Name Latitude Longitude Em Delay Pa Delay kW\n", sel); printf("%-20s %c%10.5lf %10.5lf%10.2lf\n", rcvr_name, 'r', rcvr_lat * R2D, rcvr_lon * R2D, rcvr_delay); /* * Read next record and filter out all except requested gri. */ while (fscanf(fp_in, "%li %c%lf%lf%lf%lf%lf%lf%lf%lf %20[^\n]", &gri, &type, &lat[0], &lat[1], &lat[2], &lon[0], &lon[1], &lon[2], &ems_delay, &rad_power, name) != EOF) { if (sel != gri) continue; /* * Allocate and initialize data structure. Convert geographic * coordinates to radians north and radians west. */ sptr = chain[temp]; temp ++; sptr->type = type; sptr->lat = (lat[0] + (lat[1] + lat[2] / 60) / 60) * D2R; sptr->lon = (lon[0] + (lon[1] + lon[2] / 60) / 60) * D2R; sptr->ems_delay = ems_delay; sptr->rad_power = rad_power; strcpy(sptr->name, name); /* * Compute transmitter and receiver bearings, great-circle * distance and path delay. */ theta = rcvr_lon - sptr->lon; if (theta >= PI) theta = theta - PID; if (theta <= -PI) theta = theta + PID; d = acos(sin(rcvr_lat) * sin(sptr->lat) + cos(rcvr_lat) * cos(sptr->lat) * cos(theta)); if (d < 0) d = PI + d; sptr->rcvr_azim = acos((sin(sptr->lat) - sin(rcvr_lat) * cos(d)) / (cos(rcvr_lat) * sin(d))); if (sptr->rcvr_azim < 0) sptr->rcvr_azim = PI + sptr->rcvr_azim; if (theta < 0) sptr->rcvr_azim = PID - sptr->rcvr_azim; sptr->xmtr_azim = acos((sin(rcvr_lat) - sin(sptr->lat) * cos(d)) / (cos(sptr->lat) * sin(d))); if (sptr->xmtr_azim < 0) sptr->xmtr_azim = PI + sptr->xmtr_azim; if (theta >= 0) sptr->xmtr_azim = PID - sptr->xmtr_azim; sptr->path_delay = R * d / VOFL * 1e9; printf("%-20s %c%10.5lf %10.5lf%10.2lf%10.2lf%5.0lf\n", sptr->name, sptr->type, sptr->lat * R2D, sptr->lon * R2D, sptr->ems_delay, sptr->path_delay, sptr->rad_power); } fclose(fp_in); return (temp); } /* end program */