// ***************************** // * * // * DCFRx.c DCF77 receiver * // * * // ***************************** // // C code for dsPIC33FJ32GP202, MPLAB IDE w/ C30 compiler // // DSP-based DCF77 receiver w/ pseudo-random phase demodulation // // REVISION HISTORY: -------------------------------------------------------------- // // 03-12-10 v211 Started from MSFRx v110/v111 // 12-12-10 v301 Added cross-correlating pseudo-random phase discriminator etc // 16-12-10 v302 improved VCO PLL control P + I // 23-12-10 v400 migrate to DSPIC33FJ32GP202 w/ new Fx = 19.2MHz & 12b ADC // 02-01-11 v401 fiddled with lock/loss-of-lock detection & LED colour // 07-01-11 v402 adj MINSIG, squelch Vc if COff OR poor signal, COn mechanism added // 13-01-11 v403 add serial tx mechanism logs Vcnom, AGC_PWM, Sig etc in HEX // 15-01-11 v404 loss-of-lock detection also checks for phase inactivity, log av Y // 14-04-11 v405 compensate for the (overlooked) built-in delay of 61cy in ECOS // when producing the PRP-enhanced second marker pulse. // 15-04-11 v406 provide propagation delay adjustment facility. // // TO DO: ------------------------------------------------------------------------ // // Spike-filter the AM signal during the DCF pass-through phase // Save FILT (PLL VCXO Vc) in NV store for use next power up // Log lockCnt // // I/O: --------------------------------------------------------------------------- // // Analog in: AN0 LPF filtered, AGC'd RF input // // TENHZ out: RA4 10Hz reference frq o/p // DCF+ out: RB4 ---_--- raw signal to "DCF" LED -ve // DCF- out: RB8 ___-___ re-timed signal to inverting o/p buffer // PWM1 out: RP15 RF AGC voltage control // PWM2 out: RP7 VCXO voltage control // U1TX out: RP6 9600BN81 serial status & debug // LED+ out: RB3 LED +ve. When hi LED shows red, when lo LED shows green // TEST out: RB13 Toggles to indicate buffer over-runs // n/a out: RB9 // // SERIAL PROTOCOL ---------------------------------------------------------------- // // The serial output is active only when the PRS phase decoder is running. // The packet is of one of two formats: // // "!LDsseeeeddddpppaaayy" or // ":LDsseeeeddddpppaaayyooccrrrrrzzzzz" // // Where... // ! the phase decoder is transitioning between locked and unlocked // : the phase decoder is stable // L locked status of master clock PLL. 0 or 1 // D phase decoder output i.e. time code bit from the previous second // ss signal strength, <40 is poor, >100 is very good, max 160. // eeee main pseudo-random phase correlator output* gives data polarity // dddd differential pseudo-random phase correlator output* // ppp filtered PWM value to VCXO, the PLL control voltage 0..1023 // aaa current AGC PWM value, the RF gain control voltage 0..1023 // yy signal amplitude at the end of phase modulation. 0..160 // oo phase correlator happiness factor -1 fail, 0 poor, 60 max* // cc last adjustment made to the phase correlator starting point* // rrrrr absolute phase of GPS reference input if present. 0...77499 // zzzzz absolute phase of second marker. 0...77499 // // All fields are expressed in hexadecimal notation. // * means that the value can be negative, if its MSB is set then // compliment the data, add one and set sign to negative. // // NOTES -------------------------------------------------------------------------- // // This code implements a cross correlation based notch filter, to remove // interference from the received RF signal. The amplitude of the filtered // signal is thresholded to demodulte the AM time-code bit-stream. A phase // locked loop is implemeted to keep the master crystal oscillator in // sync with the RF carrier frequency. An AGC loop keeps the ADC input at the // optimum level and a cross correlating pseudo-random phase demodulator // discriminates accurate second markers. // // The notch filter is implemented using the correlation method in which the raw // signal samples are multiplied by two sets of reference samples 90 degrees out // of phase - we run two cross-correlation engines in parallel. The // results from the multiplications are stored in ring buffers from which moving // averages are re-calculated at every update. The length of the buffers determines // the bandwidth of the notch filter - the longer the buffer the sharper the // filter, or the higher the Q-factor. With an Fs of 4*Fc the reference signal // is simply 0,1,0,-1,0,1, etc. Using two reference sequences 90 degrees out of // phase (ie delayed by 1 sample at 4*Fc), and referred to here as the SIN and COS // sequences we can easily calculate the amplitude of the filtered result // by vector summing the SIN and COS moving averages: AMPL = sqrt(SIN^2 + COS^2). // AMPL is sampled every milliseond and accumulated into a 32 entry averaging // filter (MTAV) to produce a wew result every 32ms. The most recent // 64 of these averages are stored in a ring buffer from which are extracted // MAX and MIN values from the preceeding rolling two second period. From // these MAX & MIN are derived the upper and lower threshold values used to // demodulate the original AMPL signal (with hysteresis) to a binary output. // // The code also controls an external harware variable gain stage via a PWM // output using the PIC's Timer_2 and Output_Compare_1 peripherals. Timer2 is // setup as a 10bit timer cycling at around 55KHz (Fcy/1024), setting the // AGC_PWM's characteristic frequency and 10-bit range. The desired PWM ouptut // is set by updating OC1's compare register with values between 0 and 1024. // The external hardware is designed so that a AGC_PWM of 0 produces a gain of 1 // and a AGC_PWM of 1024 produces a gain of aproximately 50 in the fifth RF stage. // The AGC algorithm merely increments AGC_PWM until a few of the peak raw // input ADC samples are >95% of full range, conversely if too many raw input // samples are >95% of full range it decreases the AGC_PWM accordingly. // The AGC_PWM value thus ends up hunting around in such a way as to // maintain optimum usage of the ADC's dynamic range. // // A phase locked loop, implemented largely in code, keeps the external crystal // oscillator's frequency in sync with the 77.5KHz RF carrier, Fc. This is achieved // by using a voltage controlled xtal oscillator driven by a PWM output from the PIC. // Software is used to detect the phase relationship between the ADC sampling rate // and the RF carrier signal. The phase signal is used to update the PIC's PWM2 // output (using OC2 driven by TIM3) to produce a PWM signal fed to the VCXO. // // A low frequency reference output at 10Hz is provided which is squelched should // the PLL go out of lock, or if the carrier is lost, and is only reinstated 10s // or so after stable lock is restored. This can serve as a useful reference // for an attached RTC. // // // DCF's pseudo-random phase modulation scheme: // // It is not possible to extract timimg with a precision much better than a millisecond // from the AM signal because of the inherent need for a low bandwidth receiving // channel. The rx bandwidth of this decoder is in the region of 650Hz giving a // best possible response time of around 1ms. Furthermore it's not possible to // compensate for or filter out the inherent delay as it varies tremendously with // signal strength and interference conditions. To enable a better precision to be // gained from DCF it is phase modulated at transmission with a 512 bit pseudo-random // binary sequence. This is de-modulated by means of another two cross correlators // operating on measurements of the carrier phase and used to improve the timing // accuracy of the leading edge of the AM second marker pulse. // // Signal propagation time correction must be done by the RTC down-stream of DCFRx // Eg Distance from Mainflingen to Halifax = 858km or 2.86ms or 221cy // // // DCF Minute marker: // // When the pseudo random phase demodulator is running the code produces a // foreshortened second marker pulse (of 50ms duration) as a minute marker // - when the AM DCF pulse is normally suppressed. // CPU setup -------------------------------------------------------------- // #include "p33FJ32GP202.h" // // Configuration Reg Settings: // We must start up with the FRC oscillator (we switch over to the VCXO in software) // _FOSC(FCKSM_CSECMD & OSCIOFNC_OFF & POSCMD_HS) // clk sw en, clk mon dis, no IO on OSC2, osc = HS (10MHz+) _FWDT(FWDTEN_OFF) // watchdog off _FPOR(FPWRT_PWR128) // pwr on rst timer = 128ms _FICD(ICS_PGD1 & JTAGEN_OFF) // ICD on PGEx1, no JTAG // Propagation delay adjustment (optional) ------------------------------------------- // // Here we set up the timing correction for the propagation delay between the DCF transmitter // at Mainflingen, Germany (50N, 9E) and the location of the receiver, use: // http://www.daftlogic.com/projects-google-maps-distance-calculator.htm // to get your distance, D, in km from Mainflingen. Then define PROP_DELAY below to the // nearest integer to D * 0.2585. This will bring the output pulse forward by the // apropriate number of carrier cycles to compensate for your particular propagation delay. // Note that PROPD values greater than 377 are not allowed - they will crash the code. // #define PROP_DELAY 0 // // We also need to make a fixed adjustment to compensate for the inherent 60cy delay // built into the ECOS source data PLUS the 1cy delay built into the ADC sampling process. // #define ZTIME (61 + PROP_DELAY) // we emit the second marker pulse ZTIME cycles aerly // CPU / PERIPHERAL CLOCK SETTINGS ---------------------------------------------------- // // The values defined in this section make numerical sense, they are transmogrified later into the // PIC-speak values that the apropriate registers require. // // Under software conrol we re-configure the oscillator & PLL to operate the device at an Fcy // from which can be derived our ADC sampling clock of 4Fc ie 310KHz. // We choose a master oscillator frequency of 19.2MHz and an Fcy of 55.8MHz, note that we have // to significantly over-clock the (nom 40MHz) PIC to get enough MIPS for the application. // The following factors M, N1, N2, SAMC, ADCS & BRG are produced by DCFRX12.php: // #define PLL_N1 8 // pre-divider: 2..33 #define PLL_M 93 // osc PLL feedback divisor #define PLL_N2 2 // post-divider: 2,4,8 // // ADC scampling rate control // Fs = 310KS/s @ Fcy 39.68MHz/ADC_ADCS -> ADCclk, /(12 + ADC_SAMC) -> 4*Fc // #define ADC_SAMC 6 // ADC_SAMC: 0..31 SAMC adds to the 14 Tads for 12b conversion #define ADC_ADCS 9 // ADC_ADCS: 1..64 (=> 0..63) clock divisor (we subtract the setting offset later) // // UART Baud rate divisor // // UART_BRG = (Fcy / (16 * BAUD_RATE)) - 1 ie 39860000 / (16 * 9600) - 1 = 258.5 (BRGH=0) // #define UART_BRG 362 // Internal timimgs and thresholds etc ----------------------------------------------- // #define Fc 77500 // Fc carrier frequency in Hz #define TENHZ (Fc/20) // Counting at Fc, TENHZ give the half-cycle time of the low frq ref o/p = Fc/20 #define FRQ (Fc/1000) // Carrier frequency to nearest KHz (not critical) = Fc/1000, used to generate ~1KHz software timer #define LOCKTIME 200 // lockCnt incs @20Hz, decs @ beat frq so loss-of-lock recovery time is min LOCKTIME * 50ms #define ADClimit 1720 // 90% of signed 12 bit sample range #define MINSIG 40 // Threshold signal amplitude below which we choose not to use phase data for PLL adjustment // 40 seems to work ok but it's an empiricly derived value which might benefit from some tweeking // CONSTANTS ------------------------------------------------------------------------- // // DCF Transmitter's pseudo random phase modulation sequence.. // Note both the D/PRS arrays contain a leading zero to make them addressable from 1 to 512 // See DCFRxPRS.php // const int PRS[513] = { 0, -1,-1,-1,-1,-1, 1,-1,-1,-1, 1, 1,-1,-1,-1,-1, 1, -1,-1, 1, 1, 1,-1,-1, 1,-1, 1,-1, 1,-1, 1, 1,-1, -1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1,-1,-1, 1, 1, -1, 1, 1, 1,-1,-1, 1,-1,-1,-1, 1,-1, 1,-1,-1,-1, -1, 1,-1, 1,-1, 1, 1,-1, 1,-1,-1, 1, 1, 1, 1, 1, 1,-1, 1, 1,-1,-1, 1,-1,-1, 1,-1,-1, 1,-1, 1, 1, -1, 1, 1, 1, 1, 1, 1,-1,-1, 1,-1,-1, 1, 1,-1, 1, -1, 1,-1,-1, 1, 1,-1,-1, 1, 1,-1,-1,-1,-1,-1,-1, -1, 1, 1,-1,-1,-1, 1, 1,-1,-1, 1,-1, 1,-1,-1,-1, 1, 1,-1, 1,-1,-1, 1,-1, 1, 1, 1, 1, 1, 1, 1,-1, 1,-1,-1,-1, 1,-1, 1, 1,-1,-1,-1, 1, 1, 1,-1, 1, -1, 1, 1,-1,-1, 1,-1, 1, 1,-1,-1, 1, 1, 1, 1,-1, -1,-1, 1, 1, 1, 1, 1,-1, 1, 1, 1,-1, 1,-1,-1,-1, -1,-1, 1, 1,-1, 1,-1, 1, 1,-1, 1, 1,-1, 1, 1, 1, -1, 1, 1,-1,-1,-1,-1,-1, 1,-1, 1, 1,-1, 1,-1, 1, 1, 1, 1, 1,-1, 1,-1, 1,-1, 1,-1, 1,-1,-1,-1,-1, -1,-1, 1,-1, 1,-1,-1, 1,-1, 1,-1, 1, 1, 1, 1,-1, -1, 1,-1, 1, 1, 1,-1, 1, 1, 1,-1,-1,-1,-1,-1,-1, 1, 1, 1,-1,-1, 1, 1, 1,-1, 1,-1,-1, 1,-1,-1, 1, 1, 1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 1,-1,-1,-1, 1,-1,-1, 1,-1,-1,-1,-1, 1, 1,-1,-1, 1, 1, 1,-1, -1,-1,-1, 1,-1, 1, 1, 1, 1,-1, 1, 1,-1, 1, 1,-1, -1, 1, 1,-1, 1,-1,-1,-1,-1, 1, 1, 1,-1, 1, 1, 1, 1,-1,-1,-1,-1, 1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, -1,-1,-1, 1, 1, 1, 1,-1, 1, 1, 1, 1, 1,-1,-1,-1, 1,-1, 1, 1, 1,-1,-1, 1, 1,-1,-1, 1,-1,-1,-1,-1, -1, 1,-1,-1, 1,-1, 1,-1,-1, 1, 1, 1,-1, 1, 1,-1, 1,-1,-1,-1, 1, 1, 1, 1,-1,-1, 1, 1, 1, 1, 1,-1, -1, 1, 1,-1, 1, 1,-1,-1,-1, 1,-1, 1,-1, 1,-1,-1, 1,-1,-1,-1, 1, 1, 1,-1,-1,-1, 1, 1,-1, 1, 1,-1, 1,-1, 1,-1, 1, 1, 1,-1,-1,-1, 1,-1,-1, 1, 1,-1, -1,-1, 1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1, 1 }; // Differential pseudo random sequence.. // x[n] = PRS[n-1] - PRS[n] // See DCFRxPRS.php // const int DPRS[513] = { 0, 0, 0, 0, 0, 0,-1, 1, 0, 0,-1, 0, 1, 0, 0, 0,-1, 1, 0,-1, 0, 0, 1, 0,-1, 1,-1, 1,-1, 1,-1, 0, 1, 0, 0, 0,-1, 0, 1,-1, 0, 0, 0, 1,-1, 1, 0,-1, 0, 1,-1, 0, 0, 1, 0,-1, 1, 0, 0,-1, 1,-1, 1, 0, 0, 0,-1, 1,-1, 1,-1, 0, 1,-1, 1, 0,-1, 0, 0, 0, 0, 0, 1,-1, 0, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1,-1, 0, 1,-1, 0, 0, 0, 0, 0, 1, 0,-1, 1, 0,-1, 0, 1,-1, 1,-1, 1, 0,-1, 0, 1, 0,-1, 0, 1, 0, 0, 0, 0, 0, 0,-1, 0, 1, 0, 0,-1, 0, 1, 0,-1, 1,-1, 1, 0, 0, -1, 0, 1,-1, 1, 0,-1, 1,-1, 0, 0, 0, 0, 0, 0, 1, -1, 1, 0, 0,-1, 1,-1, 0, 1, 0, 0,-1, 0, 0, 1,-1, 1,-1, 0, 1, 0,-1, 1,-1, 0, 1, 0,-1, 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 1,-1, 0, 0, 1,-1, 1, 0, 0, 0, 0,-1, 0, 1,-1, 1,-1, 0, 1,-1, 0, 1,-1, 0, 0, 1,-1, 0, 1, 0, 0, 0, 0,-1, 1,-1, 0, 1,-1, 1,-1, 0, 0, 0, 0, 1,-1, 1,-1, 1,-1, 1,-1, 1, 0, 0, 0, 0, 0,-1, 1,-1, 1, 0,-1, 1,-1, 1,-1, 0, 0, 0, 1, 0,-1, 1,-1, 0, 0, 1,-1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0,-1, 0, 0, 1,-1, 1, 0,-1, 1, 0,-1, 0, 0, 0, 1,-1, 1,-1, 0, 0, 1,-1, 1,-1, 1, 0, 0, -1, 1, 0,-1, 1, 0, 0, 0,-1, 0, 1, 0,-1, 0, 0, 1, 0, 0, 0,-1, 1,-1, 0, 0, 0, 1,-1, 0, 1,-1, 0, 1, 0,-1, 0, 1,-1, 1, 0, 0, 0,-1, 0, 0, 1,-1, 0, 0, 0, 1, 0, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,-1, 0, 0, 0, 1,-1, 0, 0, 0, 0, 1, 0, 0, -1, 1,-1, 0, 0, 1, 0,-1, 0, 1, 0,-1, 1, 0, 0, 0, 0,-1, 1, 0,-1, 1,-1, 1, 0,-1, 0, 0, 1,-1, 0, 1, -1, 1, 0, 0,-1, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 1, 0,-1, 0, 1,-1, 0, 1, 0, 0,-1, 1,-1, 1,-1, 1, 0, -1, 1, 0, 0,-1, 0, 0, 1, 0, 0,-1, 0, 1,-1, 0, 1, -1, 1,-1, 1,-1, 0, 0, 1, 0, 0,-1, 1, 0,-1, 0, 1, 0, 0,-1, 1, 0, 0,-1, 1, 0, 0, 0, 0, 0, 0, 0, 0 // // Note: the last entry (was -1) is censored (to 0) so as to ensure an over-all // total of zero - to reject any DC offset in the phase data. }; // How much to right-shift a number to get it to zero // const unsigned char SHLUT[256] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 }; // Binary nibble to ascii hex look-up // const unsigned char BIN2HEX[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; // Rotary encoder LUT // const char RLUT[16] = { // New Prev // S C S C P N Q // ----------------------- 0, // 0 0 0 0 0 0 . -1, // 0 0 0 1 1 0 - 1, // 0 0 1 0 2 0 + 0, // 0 0 1 1 3 0 X 1, // 0 1 0 0 0 1 + 0, // 0 1 0 1 1 1 . 0, // 0 1 1 0 2 1 X -1, // 0 1 1 1 3 1 - -1, // 1 0 0 0 0 2 - 0, // 1 0 0 1 1 2 X 0, // 1 0 1 0 2 2 . 1, // 1 0 1 1 3 2 + 0, // 1 1 0 0 0 3 X 1, // 1 1 0 1 1 3 + -1, // 1 1 1 0 2 3 - 0 // 1 1 1 1 3 3 . }; // MAIN ------------------------------------------------------------------------------- // int main() { // VARIABLE DECLARATIONS ------------------------------------------------------------- // int SIN[120], COS[120]; // SIN & COS xc buffer, length corresponds to a DCF pseudo-random bit time int sin, cos; // semi-processed sin & cos data long Esin, Ecos; // full precision SIN & COS xc accumulators int ESIN, ECOS; // SIN & COS xc accls scaled to 16 bits unsigned char p; // SIN[]/COS[] buffer pointer 0..239 unsigned int Xc, Xs; // intermediate variables unsigned int X; // intermediate variables unsigned int Y1, Y0; // intermediates int Y; // Current amplitude // unsigned int MTAV; // medium-term average accumulator unsigned char psr; // medium-term average accumulation count unsigned char dpsr; // max-min scan count unsigned char pmm; // pointer into MAXMIN unsigned int MAXMIN[64]; // Max Min buffer of medium-term averages unsigned int max, min; // current MAX and MIN values unsigned int Hthr, Lthr; // upper and lower amplitude thresholds unsigned int Sig; // max-min signal measure unsigned int Mthr; // mid-way amplitude threshold // unsigned int SatCount; // counts saturated ADC events over a 32ms period unsigned int AGC_PWM; // 0..1023 controls RF gain via PWM o/p. gain increases with AGC_PWM // unsigned int OneSec; // counts 1000 ms long c77500; // count 77500 Fc cycles long Pref; // logs ref PPS input signal phase // unsigned char cnt64; // counts 64 XC buffer times = about 100ms long FILT; // IIR Vc filter // unsigned char COff; // Carrier Off detector for Vc acq period unsigned char COn; // Carrier On detector for AGC speed control unsigned char locked; // PLL locked state binary indicator unsigned char TenHzph; // 10Hz phase memory; int lockCnt; // helps detect lock/loss of lock int TenHz; // slow ref requency support // unsigned int oldBUFS; // store prev BUFS value so we can spot a change unsigned char pr; // 1ms loop counter // unsigned char TxBuf[64]; // serial tx buffer unsigned char TxBrp; // and its read pointer // int EECOS[64], EESIN[64]; // moving average buffers for mid-term phase filters long Eesin, Eecos; // ECOS / ESIN moving averaging accumulators unsigned char Eeh, Een; // rotary encoder history, filter r/w pointer char Eeacc; // rotary accumulator 1 unit = 90degrees unsigned char PhActv; // phase activity detector, no activity -> no signal // unsigned char i; // an unsigned char int INT; // an integer unsigned int UI; // an unsigned integer char c; // a char long L; // a long // char PRSOK; // -1 = init, while PRS is good PRSOK++, while PRS bad PRSOK-- long EPRS, EDPRS, EY; // sigmas for PRS and DPRS cross correlators and average amplitude unsigned int cy; // cycle counter int st; // PRS demodulator state, see below // -3 - waiting for 700ms of carrier on 54250cy // -2 - waiting for carrier to go off * // -1 - waiting for 80ms of carrier off 6200cy // + jump to st0 with cy=9180 (assumes 120cy delay in ampl demod) // 0 - waiting 200ms dead-time 15500cy // + assrt second marker for first 50ms (3875cy) // 1..512 - cross correlating carrier phase 120cy ea // 513 - waiting 120cy dead-time & calcs 120cy // 514 - waiting nom 440cy dead-time ~440cy // + go back to state 0 // At last, CODE ----------------------------------------------------------------------- // Configure the oscillator & PLL to operate the device as per DCFRX12.PHP // First the PLL prescaler, PLL postscaler & PLL feed-back divisor // PLLFBD = PLL_M - 2; // M: 2..513 => PLLFBD: 0..511 CLKDIVbits.PLLPOST = (PLL_N2 >> 1) - 1; // N2: 2,4,8 => PLLPOST: 0,1,3 CLKDIVbits.PLLPRE = PLL_N1 - 2; // N1: 2..33 => PLLPRE: 0..31 // // We started up with FRC as our osc src so must now switch to XT w/ PLL in code // (with an XTAL outside the 4-8MHz range we cannot start up in XT/PLL mode) // i = OSCCON >> 8; __builtin_write_OSCCONH((i & 0b11111000) + 3); // NOSC<2:0> := 3 Pri XT/HS w/PLL // i = OSCCON & 255; __builtin_write_OSCCONL(i | 1); // set OSWEN to enable the switch // while(OSCCONbits.LOCK != 1); // wait for PLL to lock while(OSCCONbits.OSWEN == 1); // wait for OSWEN to clear // Set up UART for debug messages // U1BRG = UART_BRG; // Setup baud rate // 0bFEDCBA9876543210 U1MODE = 0b1000100000000000; // Enabled, non-stop, no IRDA, no RTS, TX+RX only, BRGH=0, 8N1 U1STA = 0b0100010000000000; // Tx en inv + default settings // Set up ADC // See notes in #define section above // // 0bFEDCBA9876543210 AD1PCFGL = 0b1111111111111110; // AD0 (RA0) set for analog AD1CON1 = 0b0000010111101100; // OFF, 12b, signed intgr format, auto AD1CON2 = 0b0000000000001110; // no vrefs, no-scan, ch0, int-every-4 AD1CON3bits.SAMC = ADC_SAMC; // Set sampling delay and... AD1CON3bits.ADCS = ADC_ADCS - 1; // ADC_ADCS: 1..64 => .ADCS: 0..63 clock divisor AD1CON3bits.ADRC = 0; // master clock source = Fcy AD1CHS0 = 0; // CH0 = AN0 AD1CON1bits.ADON = 1; // run... // Setup Remappable pins for outputs: OC1 & OC2 & U1TX // // We must first unlock the IO remapper by clearing the IOLOCK bit in OSCCON.6 // i = OSCCON & 255; __builtin_write_OSCCONL(i & 0b10111111); // unlock the port re-mapper... // // re-map the IO // RPOR7bits.RP15R = 0b10010; // OUTPUT RP15 (RB15, pin26) --> OC1 AGC_PWM RPOR3bits.RP7R = 0b10011; // OUTPUT RP7 (RB7, pin16) --> OC2 VCXO_PWM RPOR3bits.RP6R = 0b00011; // OUTPUT RP6 (RB6, pin15) --> U1TX SERIAL OUT // // re-lock the remapper // __builtin_write_OSCCONL(i | 0b01000000); // lock it back up // Setup OC1 for PWM mode using timer-2 (while timer2 is still dissabled) // OC1 PWM output controls the RF GAIN, higher values produce higher gains // OC1CON = 0b0110; // use TMR2, PWM-no-fault mode OC1RS = 64; // initial AGC_PWM value, start fairly low, range is 0..1024 // Setup OC2 for PWM mode using timer-3 (while timer3 is still dissabled) // OC2 PWM derived from Fc phase wrt to ADC sample clock // OC2 PWM sets the bias voltage Vc to the oscillator's control input // higher values => higher frequency // OC2CON = 0b1110; // use TMR3, PWM-no-fault mode OC2RS = 512; // Set mid-point initial PWM value, range is 0..1024 // Setup Timer2 (to drive OC1) at 39.68KHz (Fcy / 1024) yielding 10 bits of AGC control // T2CON = 0b1000000000000000; // ON, no gate, 1:1pre, 16b, int clk TMR2 = 0; PR2 = 1023; // reload reg = rqd cycle count - 1 // Setup Timer3 (to drive OC2) similarly // T3CON = 0b1000000000000000; // ON, no gate, 1:1pre, 16b, int clk TMR3 = 0; PR3 = 1023; // reload reg = rqd cycle count - 1 // Set up digital output ports // LATAbits.LATA1 = 0; // n/a LATBbits.LATB2 = 0; // Start with LED+ @Hi LATBbits.LATB3 = 1; // Start with LED+ @Hi LATBbits.LATB5 = 0; // n/a LATBbits.LATB8 = 0; // Start with DCF- low (into inv buffer) LATBbits.LATB9 = 0; // n/a LATBbits.LATB10 = 0; // n/a LATBbits.LATB11 = 0; // n/a LATBbits.LATB12 = 0; // n/a LATBbits.LATB13 = 1; // Start with TEST o/p lo (after inv buffer) LATBbits.LATB14 = 0; // n/a // // Enable outputs // TRISAbits.TRISA1 = 0; // n/a TRISAbits.TRISA4 = 0; // Set RA4 as digital o/p for 10Hz ref frequency TRISBbits.TRISB2 = 0; // n/a TRISBbits.TRISB3 = 0; // Set RB3 as digital o/p for LED+ TRISBbits.TRISB4 = 0; // Set RB4 as digital o/p for amplitude demodulated DCF+ to drive LED TRISBbits.TRISB5 = 0; // n/a TRISBbits.TRISB8 = 0; // Set RB8 as digital o/p for re-timed DCF- to drive inv buffer TRISBbits.TRISB9 = 0; // n/a TRISBbits.TRISB10 = 0; // n/a TRISBbits.TRISB11 = 0; // n/a TRISBbits.TRISB12 = 0; // n/a TRISBbits.TRISB13 = 0; // Set RB13 as digital o/p for TEST TRISBbits.TRISB14 = 0; // n/a // Set pull-ups on RB0 & 1 inputs (ISP_DAT & _CLK) // CNPU1bits.CN4PUE = 1; CNPU1bits.CN5PUE = 1; // Initialise arrays and other variables ready for main loop // for (i = 0; i < 120; i++) SIN[i] = COS[i] = 0; for (i = 0; i < 64; i++) MAXMIN[i] = EESIN[i] = EECOS[i] = TxBuf[i] = 0; // Esin = Ecos = 0L; ESIN = ECOS = p = 0; Y = 255; MTAV = psr = 0; pr = 0; dpsr = 0; pmm = 0; oldBUFS = AD1CON2bits.BUFS; Hthr = min = 255; Lthr = max = 0; SatCount = 0; AGC_PWM = 64; TenHz = TENHZ; locked = 0; // start off out-of-lock lockCnt = 0; // start off out-of-lock COff = 1; // Carrier is off COn = 0; // Carrier is not on EPRS = EDPRS = 0; st = -3; // PRS state machine at 'wait for carrier' state cy = 3875; // 50ms PRSOK = -1; // PRS decoder not yet ready cnt64 = 0; // counts 64 PRS bit times TxBrp = 0; FILT = PR3 << 6; // ie PR3/2 << 7 starting point for Vc FILTer Eesin = Eecos = 0; Eeh = Een = 0; Eeacc = 0; c77500 = -1; // stall the counter until first PRS cycle Pref = -1; U1TXREG = 'R'; U1TXREG = 'S'; U1TXREG = 'T'; // MAIN LOOP --------------------------------------------------------- // // The single for-ever loop runs a Fc rate, <13us per loop. // while(1) // Forever... { // Wait for the AD1CON2bits.BUFS bit to change, => new data // while (oldBUFS == AD1CON2bits.BUFS); oldBUFS = AD1CON2bits.BUFS; // use four samples from the appropriate half of the ADC buffer // if (oldBUFS == 0) { // when BUFS is lo read from upper half of ADC buffer ADC1BUF8..A // sin = (int)ADC1BUF8 - (int)ADC1BUFA; cos = (int)ADC1BUF9 - (int)ADC1BUFB; // if ( ((int)ADC1BUF8 > ADClimit) || ((int)ADC1BUF8 < -ADClimit) || ((int)ADC1BUF9 > ADClimit) || ((int)ADC1BUF9 < -ADClimit) || ((int)ADC1BUFA > ADClimit) || ((int)ADC1BUFA < -ADClimit) || ((int)ADC1BUFB > ADClimit) || ((int)ADC1BUFB < -ADClimit) ) SatCount++; } else { // when BUFS is hi read from lower half of ADC buffer ADC1BUF8..A // sin = (int)ADC1BUF0 - (int)ADC1BUF2; cos = (int)ADC1BUF1 - (int)ADC1BUF3; // if ( ((int)ADC1BUF0 > ADClimit) || ((int)ADC1BUF0 < -ADClimit) || ((int)ADC1BUF1 > ADClimit) || ((int)ADC1BUF1 < -ADClimit) || ((int)ADC1BUF2 > ADClimit) || ((int)ADC1BUF2 < -ADClimit) || ((int)ADC1BUF3 > ADClimit) || ((int)ADC1BUF3 < -ADClimit) ) SatCount++; } // // SIN & COS buffer management & xc accumulation // Ecos += cos - COS[p]; // Add in the new, subtract out the old COS[p] = cos; Esin += sin - SIN[p]; // Add in the new, subtract out the old SIN[p] = sin; p++; if (p == 120) p = 0; // p re-cycles once every 1.55ms @Fc=77.5KHz // // Ecos & Esin are each the sum of 120 samples // Each sample is in the range -4095...+4095 // Ecos and Esin are therefore in the range -491400...+491400 // ECOS = Ecos >> 4; // scale back from nom 20bits to nom 16 ESIN = Esin >> 4; // // ECOS & ESIN are now in the range -30712..30712 // ECOS is used to derive a measure of phase wrt to sampling clock and is minimised // (magnitude tends towards zero) by the action of the PLL control loop. // This has the effect of maximising (the magnitude of) ESIN // Cycle counter // if (c77500 >= 0) c77500++; if (c77500 == Fc) c77500 = 0; // Look for +ve edge on RB0 // if ((PORTBbits.RB0 != 0) && (Pref == -1)) Pref = c77500; if (PORTBbits.RB0 == 0) Pref = -1; // Generate the low frequency reference o/p, timing is important so // this code runs first // TenHz--; if (TenHz == 0) { TenHz = TENHZ; TenHzph = !TenHzph; if (lockCnt >= LOCKTIME) // not in lock until lockCnt reaches LOCKTIME { if (locked == 0) Eeacc = 0; // re-sync the odometer at the moment of locking locked = 1; } else if (PhActv != 0) lockCnt++; // if (locked != 0) // IF LOCKED... LATAbits.LATA4 = TenHzph; // every half-period we toggle the o/p bit else LATAbits.LATA4 = 0; // force 10Hz low (board o/p goes hi) } // Count (at Fc rate) up to 1ms // pr++; // pr counts carrier cycles and if (pr == FRQ) pr = 0; // re-cycles aprx once per millisecond -> 1ms s/w timing source // Time for the geometric addition to extract amplitude from the XC data. Since the // sqrt algorithm is CPU intensive we must run it in 16bit arithmetic so we scale ESIN // and ECOS back further until they fit into 8 bits (they're the sum of 120 x 13b signed values /16) // Note we can also discard the sign bit as it is redundant in the vector addition to come. // We must do this because we don't have time for operations on long variables, // 16b interger operations are much faster. However, we record how much scaling we // have to do and apply a correction after the sqrt algorithm, it's a kind of // rough and ready floating point scheme and it gives us a bit more dynamic range. // if (ESIN >= 0) Xs = ESIN; else Xs = -1 * ESIN; // if (ECOS >= 0) Xc = ECOS; else Xc = -1 * ECOS; // // Xc & Xs are now unsigned and in the range 0..30172 and therefore fit into 15 bits // Scale back again to fit into 8 bits // i = SHLUT[(Xc + Xs) >> 8]; Xs >>= i; Xc >>= i; // // We would expect Ye to be typ 7, Xc to be close to zero, Xs to be upto 235 // X = (Xs * Xs) + (Xc * Xc); // X fits into a 16-bit number // // Max expected value for X would be 55225 // Take the square root of X using the itterative 'Babylonian' method // THIS CODE IS TOO SLOW... // // if (Y1 == 0) Y1++; // if (X > 0) // { // do // { // Y0 = Y1; // Y1 = (Y0 + (X / Y0)) >> 1; // } while ((Y0 > (Y1 + 1)) || (Y1 > (Y0 + 1))); // } // else Y1 = 0; // // Take the square root of X using the bit-by-bit method... // Initialisation (while loop expanded for higher speed)... // Y1 = 0; Y0 = 0x4000; if (Y0 > X) { Y0 >>= 2; if (Y0 > X) { Y0 >>= 2; if (Y0 > X) { Y0 >>= 2; if (Y0 > X) { Y0 >>= 2; if (Y0 > X) { Y0 >>= 2; if (Y0 > X) { Y0 >>= 2; if (Y0 > X) Y0 >>= 2; } } } } } } // Now the calculation... // // while (Y0 != 0) // { // if (X >= Y1 + Y0) // { // X -= Y1 + Y0; // Y1 = (Y1 >> 1) + Y0; // } // else Y1 >>= 1; // Y0 >>= 2; // } // // Expand the above loop to speed things up, allow up to 8 loops // if (Y0 != 0) { if (X >= Y1 + Y0) // 1 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 2 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 3 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 4 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 5 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 6 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 7 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { if (X >= Y1 + Y0) // 8 { X -= Y1 + Y0; Y1 = (Y1 >> 1) + Y0; } else Y1 >>= 1; Y0 >>= 2; if (Y0 != 0) { Y0 = 0; } } } } } } } } } // Note max expected value for Y, sqrt(X), = 235 // Restore the exponent, but also divide by 128 // to yield a nominal 8-bit unsigned Y // if (i == 7) Y = Y1; else { if (i > 7) Y = Y1 << (i - 7); else Y = Y1 >> (7 - i); } // // Y is our latest amplitude measurement, it has a theoretical max magnitude of 235 // for a pure Fc sine wave input. Typ values in practice are 40 (min workable) up to 160. // Use the Hthr and Lthr threshold values to demodulate the raw AM signal to binary // and output it to the LED // if (Y > Hthr) LATBbits.LATB4 = 1; else if (Y < Lthr) LATBbits.LATB4 = 0; // DCF+ on RB4 drives the LED // Carrier is assumed to be off (or at least below useable limits) if Y < MINSIG // if (Y < MINSIG) COff = 1; // detect loss of carrier, see later // PRS phase demodulation state machine --------------------------------------------- // cy--; switch(st) { case -10: // debug state turns PRS decoding off // cy++; break; case -3: // Waiting for 50ms of carrier on // PRSOK = -1; if ((LATBbits.LATB4 == 0) || (locked == 0)) cy = 3875; // re-start if (cy == 0) { cy = 1; st++; } break; case -2: // Waiting for carrier to go off // if (LATBbits.LATB4 == 0) { cy = 3875; // 50ms st++; } else cy++; break; case -1: // Waiting for 50ms of continuous carrier off // if (cy == 0) { // We have now acquired a good quality -ve edge, we use this to // initialise the PRS decoder timing. Note that the PRS decoder needs // an initial accuracy of +/- 1.5ms in order to start within it's pull-in // range and this may not always be acheived. We want to start the PRS // decoder exactly 200ms after the second marker, we're already 50ms // into that so we need another nominal 150ms from here, we also need // to take account of the delays built into the AM demodulator. // cy = 11555; // = 200ms - 50ms - 0.9ms fudge factor st++; } else if ((LATBbits.LATB4 == 1) || (locked == 0)) { cy = 3875; // reset to waiting for 50ms of carrier on st = -3; } break; case 0: // killing 200ms dead time // if (cy == 0) { LATBbits.LATB8 = 0; // DCF- o/p forced inactive lo EDPRS = EPRS = EY = 0; // reset xc accumulators cy = 120; // start the first (of 512) XC sequence st++; } else if ((cy < 11625) && (PRSOK > 0)) LATBbits.LATB8 = !LATBbits.LATB4; // ie after the first 50ms DCF- = !LED break; // // cases 1..512 (the xc calculation bit-times) are dealt with by the default case, see below // case 513: // killing 120cy dead-time while printing & pondering xc results // if ((cy == 90) && (EPRS < 0)) EDPRS *= -1; // correct the sign of EDPRS // if (cy == 80) { // Create status byte... // TxBuf[0] = BIN2HEX[locked & 1]; // bit 4 = locked TxBuf[1] = BIN2HEX[(EPRS >> 15) & 1]; // bit 0 = EPRS sign = DCF data bit, -ve:1, +ve:0 } // if (cy == 70) { UI = Sig; if (UI > 255) UI = 255; TxBuf[2] = BIN2HEX[(UI >> 4) & 15]; TxBuf[3] = BIN2HEX[UI & 15]; } if (cy == 60) { L = EPRS; if (L > 32767) L = 32767; if (L < -32768) L = -32768; TxBuf[4] = BIN2HEX[(L >> 12) & 15]; TxBuf[5] = BIN2HEX[(L >> 8) & 15]; TxBuf[6] = BIN2HEX[(L >> 4) & 15]; TxBuf[7] = BIN2HEX[L & 15]; } // if (cy == 50) { L = EDPRS; if (L > 32767) L = 32767; if (L < -32768) L = -32768; TxBuf[8] = BIN2HEX[(L >> 12) & 15]; TxBuf[9] = BIN2HEX[(L >> 8) & 15]; TxBuf[10] = BIN2HEX[(L >> 4) & 15]; TxBuf[11] = BIN2HEX[L & 15]; } // if (cy == 40) { UI = FILT >> 7; // Vcnom TxBuf[12] = BIN2HEX[(UI >> 8) & 15]; TxBuf[13] = BIN2HEX[(UI >> 4) & 15]; TxBuf[14] = BIN2HEX[UI & 15]; } // if (cy == 30) { UI = AGC_PWM; TxBuf[15] = BIN2HEX[(UI >> 8) & 15]; TxBuf[16] = BIN2HEX[(UI >> 4) & 15]; TxBuf[17] = BIN2HEX[UI & 15]; } // if (cy == 20) { UI = EY >> 9; TxBuf[18] = BIN2HEX[(UI >> 4) & 15]; TxBuf[19] = BIN2HEX[UI & 15]; } // if (cy == 10) // &&& was == 80) { // Was this a good or a bad second's worth of data? // if (((EDPRS > 2000) || (EDPRS < -2000)) // large EDPRS = BAD || ((EPRS < 2000) && (EPRS > -2000))) // small EPRS = BAD { if (PRSOK > 0) PRSOK--; else { lockCnt = 1; // put lock on a hair trigger TxBuf[20] = 0; // tx a shortened warning message when PRS loop resets U1TXREG = '!'; st = -3; // go back and start again cy = 3875; } } else if (((EDPRS < 1000) && (EDPRS > -1000)) // small EDPRS = GOOD && ((EPRS > 3000) || (EPRS < -3000)) // large EPRS = GOOD && (PRSOK < 60) ) PRSOK++; } // if (cy == 6) { TxBuf[20] = BIN2HEX[(PRSOK >> 4) & 15]; TxBuf[21] = BIN2HEX[PRSOK & 15]; } // if (cy == 0) { // do variable delay calculations... // Typ max values for fully replete EDPRS & EPRS are +/- 5000 and +/- 13000 resp // the variable delay feedback loop aims to keep EDPRS close to zero and // thus to maximise the magnitude of EPRS. // // we set an arbitrary threshold for EPRS below which we assume we're so off // track that we need a re-sync to the AM signal. // c = 0; if (PRSOK < 0) { INT = EDPRS >> 7; // hunt aggressively while we're initialising if (INT > 120) c = 120; else if (INT < -120) c = -120; else c = (char)INT; } else { if (EDPRS > 1) c++; // we're in the sweet-spot, tweak the state-514 delay... else if (EDPRS < -1) c--; // accordingly } TxBuf[22] = BIN2HEX[(c >> 4) & 15]; TxBuf[23] = BIN2HEX[c & 15]; cy = 440 + c; // set duration for st514 st++; } break; case 514: // // Killing nominal 440cy dead-time before re-starting the PRS XC loop // This section is where the variable delay happens and where we generate // the output pulse and start the debug serial transmission // if (cy == ZTIME) { // ZERO-TIME - start serial tx and assert DCF out... // We do this ZTIME cycles early in order to compensate for the inherent 60cy delay // built into the ECOS source data PLUS the 1cy delay built into the ADC // sampling process PLUS any user-set propagation delay adjustment // if (c77500 < 0) c77500 = 32768; TxBuf[29] = BIN2HEX[(c77500 >> 16) & 15]; TxBuf[30] = BIN2HEX[(c77500 >> 12) & 15]; TxBuf[31] = BIN2HEX[(c77500 >> 8) & 15]; TxBuf[32] = BIN2HEX[(c77500 >> 4) & 15]; TxBuf[33] = BIN2HEX[ c77500 & 15]; TxBuf[34] = 0; U1TXREG = ':'; // start transmission of the assembled data LATBbits.LATB8 = 1; // set DCF- o/p active high (into inv buffer) } if (cy == 10) // // Prepare Pref debug message section (the abs phase to the GPS PPS signal)... // I know this looks a bit late but it needs to be to ensure that the PPS has // actually happened (it should happen at cy 61) and provided it gets done before // the serial output demon reaches this point in the message we'll get away with it. // (worst case 430cy = 5.7ms and at 9600 baud it'll take 23ms to reach here) { TxBuf[24] = BIN2HEX[(Pref >> 16) & 15]; TxBuf[25] = BIN2HEX[(Pref >> 12) & 15]; TxBuf[26] = BIN2HEX[(Pref >> 8) & 15]; TxBuf[27] = BIN2HEX[(Pref >> 4) & 15]; TxBuf[28] = BIN2HEX[ Pref & 15]; } if (cy == 0) { cy = 15500; // 200ms to kill until PRS phase modulation re-starts st = 0; } break; default: // st1..st512 - do DPRS & PRS cross-correlations, half a bit-time out of phase // // We're using ECOS / Y as our basic measure of phase for input to the cross correlators. // ECOS itself is an average measure summed over the last 120 cycles it therefore has // an average age of 60cycles. // if (cy == 60) if ((Y != 0) && (ESIN < 0)) EDPRS += DPRS[st] * ECOS / Y; if ((cy == 30) && (st > 1)) EY += Y; // take a reading for the average amplitude if (cy == 0) { if ((Y != 0) && (ESIN < 0)) EPRS += PRS[st] * ECOS / Y; cy = 120; st++; } } // ------------------------------------------------------------------------------------ // Once every 120 Fc cycles (p's loop time, 1.55ms@77.5KHz) extract phase info, // filter it and update the VCO's PWM value with the combined result // // Check if there's been carrier present during the entire acq time, // ie if the carrier hasn't been modulated away at any point. // If there's been no carrier then don't bother working out the phase... // if (p == 0) // arbitrary timing choice, p cycles at 120 x (Fc period) = 1.55ms @ 77.5KHz { if ((COff == 0) && (Y > MINSIG)) // Ensure the carrier was on for the duration of our signal acquisition { // Calc current average (over 1.5ms) phase of RF carrier... // ESIN/ECOS's range is -30720..30480 and magnitude is <= Y * 128 // INT = ECOS / Y; // X is roughly an 8 bit value theoretically in the range -120 to +120 for sinewave inputs // We can safely assume Y is non-zero when COff == 0 // // INT is a measure of phase, it should have a range of -128 .. +128 // However the PLL action makes it tend towards zero. // We limit-clip the result to -127 .. +127 anyway // if (INT < -127) c = -127; else if (INT > 127) c = 127; else c = (char)INT; // } else c = 0; // when there's no carrier maintain the status quo by assuming zero phase // // Maintain a mid-term (~100ms) IIR filter for PLL PWM data // we use it to add some effective integral contol // UI = (FILT >> 7) + c; FILT = UI + (((FILT * 127) + 63) >> 7); OC2RS = UI; // update the PLL VCXO PWM // // Tidy up // LATBbits.LATB3 = !locked; // LED bias o/p: use RED LED when not locked COff = 0; } if (p == 16) // arbitrary timing choice, once every 120cy... { // VCO PLL out-of-lock detection... // // We watch the changing relationship between 100ms moving averages of ECOS and ESIN // to spot gross and consistent phase changes which we interpret as a loss of lock. // "lockCnt" is the loss-of-lock recovery timer, it counts up @ 20Kz during stable lock, // and is cleared here if the phase angle drifts by > +/- 270 deg from nominal. // When "lockCnt" is cleared we also clear the "locked" flag. // When "locked" is clear the 10Hz output is squelched. // Eesin += ESIN - EESIN[Een]; EESIN[Een] = ESIN; Eecos += ECOS - EECOS[Een]; EECOS[Een] = ECOS; Een = (Een + 1) & 63; Eeh >>= 2; if (Eecos < 0) Eeh += 4; if (Eesin < 0) Eeh += 8; // Eeh is now a composite of new and previous data c = RLUT[Eeh]; // work out from that whether we're rotating clockwise or anticlockwise or not if ((c != 0) || (Y > MINSIG)) PhActv = 1; // look for phase activity during low signal periods, its a sign that the PLL is still ok Eeacc += c; // accumulate rotations if ((Eeacc >= 4) || (Eeacc <= -4)) // set 4 quadrant limit on phase rotation { locked = 0; // go out of lock lockCnt = 0; Eeacc = 0; // re-sync the odometer } } if (pr == 2) // arbitrary timing choice, pr cycles every ms { // Every ms service the OneSec counter // OneSec = (OneSec + 1) & 1023; if (OneSec == 0) { // every second check for signal / phase activity // if (PhActv == 0) { lockCnt = 0; locked = 0; } PhActv = 0; } } if (pr == 4) // arbitrary timing choice, pr incs at Fc rate and resets every ms { // Every millisecond update the medium-term average. // The medium-term average feeds filtered amplitude data into the MAXMIN buffer // MTAV += Y; // MTAV sums 32 unsigned amplitudes // // Also do 2 snips of the 64 entry max/min buffer scan // We do this to spread the CPU load evenly over time // if (MAXMIN[dpsr] > max) max = MAXMIN[dpsr]; if (MAXMIN[dpsr] < min) min = MAXMIN[dpsr]; dpsr++; if (MAXMIN[dpsr] > max) max = MAXMIN[dpsr]; if (MAXMIN[dpsr] < min) min = MAXMIN[dpsr]; dpsr = (dpsr + 1) & 63; } if ((pr == 6) && (psr == 16)) // arbitrary timing choice { // Every 32ms check and act on the saturation counter // we want the saturation counter to be in the range say 10..32 // Don't allow aggresive gain increases if there's no carrier (ie if it's modulated to 25%) // if ((SatCount == 0) && (COn > 15)) AGC_PWM += 4; if (SatCount < 8) AGC_PWM++; if (SatCount > 32) AGC_PWM -= SatCount >> 5; if (AGC_PWM > 1023) AGC_PWM = 1023; if (AGC_PWM < 0) AGC_PWM = 0; OC1RS = AGC_PWM; SatCount = 0; COn = 0; } else if ((Y > Hthr) && (pr == 6)) COn++; if (pr == 0) { psr++; if (psr > 31) { psr = 0; // psr resets every 32ms... // // Update the max/min buffer with the latest medium-term filter output, // The max/min buffer stores filtered amplitudes covering a 2048ms period. // (we need a 2 second buffer with sufficient resolution (~64) in order to // properly cover DCF's missing sec59 pulse period and still have it // see at least one carrier low period) // MAXMIN[pmm] = MTAV >> 5; // MAXMIN[] holds > 2s worth of filtered amplitude measurements MTAV = 0; pmm = (pmm + 1) & 63; // // ...and create new threshold values now that MAX & MIN have been refreshed by code above // Sig = max - min; Hthr = min + ((Sig >> 2) * 3); // 75% of range Mthr = min + (Sig >> 1); // 50% of range Lthr = min + (Sig >> 2); // 25% of range min = 255; // re-start the max-min scan max = 0; } } if (U1STAbits.TRMT == 0) // Serial Tx sequence active, stuff the uart tx buffer.. { if (U1STAbits.UTXBF == 0) // more room in buffer { if (TxBuf[TxBrp] != 0) // end of data reached yet? { U1TXREG = TxBuf[TxBrp]; TxBuf[TxBrp] = 0; TxBrp++; } else TxBrp = 0; } } if (oldBUFS != AD1CON2bits.BUFS) LATBbits.LATB13 = !LATBbits.LATB13; // toggle TEST o/p on buffer crash } }