// QRP_LABS_WSPR // Arduino, QRP Labs Arduino shield, SI5351 clock, QRP Labs RX module, QRP Labs relay board. // NOTE: The tx bias pot works in reverse, fully clockwise is off. // Added a CANADAUINO WWVB interface to keep time. // common startup commands with WWVB logging // enter 1 CAT command, ?V for Rx only or #0 to stay in FRAME TX mode // The CAT emulation is TenTec Argonaut V at 1200 baud. // New encoder switch commands: // Tap : change tuning step 10hz to 1Meg // DTap: toggle mode WSPR TX Frame to CAT control - RX mode // Long: change band // To set a new operation frequency for stand alone Frame Mode, restart the Arduino, start wsjt-x or HRD. // Tune to one of the magic WSPR frequencies and toggle TX (tune in wsjt will work). // The new frequency will be stored in EEPROM. // If using the band hopping feature of WSJT, make sure the first transmit is on the band you wish for // the default. Only one save is performed per Arduino reset. // // A 4:1 frequency relationship between the tx freq and the rx clock is maintained using the // R dividers in the SI5351. Dividers 1 - rx and 4 - tx will cover 1mhz to 30mhz // Dividers 16 - rx and 64 - tx will cover 40 khz to 2 mhz // When using even output divider, PLL c parameter... // For 1 hz resolution, c = CLOCK / divider. // For 1.46 hz resolution, c = CLOCK / ( 1.46 * divider ). // Can then send WSPR by just adding to the b parameter 0,1,2,or3. // Smallest divider for 1 hz resolution, about 26. // Max value of c is 1048575 // Use of the R dividers will complicate this. #include // one UNO I have does not work correctly with this library, another one does #include #include #define ROW0 0 // text based rows for the 128x64 OLED #define ROW1 8 #define ROW2 16 #define ROW3 24 #define ROW4 32 #define ROW5 40 #define ROW6 48 #define ROW7 56 #define SI5351 0x60 // i2c address #define PLLA 26 // register address offsets for PLL's #define PLLB 34 #define CLK0_EN 1 #define CLK1_EN 2 #define CLK2_EN 4 // the starting frequency will be read out of EEPROM except the 1st time when EEPROM is blank #define FREQ 7038600 // starting freq when EEPROM is blank #define DIV 28 // starting divider for 4*freq*RDIV #define RDIV 1 // starting sub divider. 1 will cover less than 1mhz to > 30mhz. // 16 will cover 37khz to 2.3 mhz ( with the 4x factor it is 64 when transmitting ) #define CAT_MODE 0 // computer control of TX #define FRAME_MODE 1 // self timed frame (stand alone mode) #define MUTE A1 // receiver module T/R switch pin #define START_CLOCK_FREQ 27004456L // ( 4498, 4466 ) //#define START_CLOCK_FREQ 2700466600 // test too high //#define START_CLOCK_FREQ 2700426600 // test too low //#define CLK_UPDATE_THRESHOLD 59 // errors allowed per minute to consider valid sync to WWVB #define CLK_UPDATE_THRESHOLD2 48 #define DEADBAND 25 // wwvb signal timing +-deadband #define stage(c) Serial.write(c) OLED1306 LCD; // a modified version of LCD_BASIC by Rinky-Dink Electronics extern unsigned char SmallFont[]; extern unsigned char MediumNumbers[]; extern unsigned char BigNumbers[]; int last_error_count = 60; char val_print = ' '; // using even dividers between 6 and 254 for lower jitter // freq range 2 to 150 without using the post dividers // we are using the post dividers and can receive down to 40khz // vco 600 to 900 uint32_t clock_freq = START_CLOCK_FREQ; uint32_t drift; // calibrate freq result used as a measure of temperature and mapped to correct // 27 master clock drift uint32_t freq = FREQ; // ssb vfo freq const uint32_t cal_freq = 3000000; // calibrate frequency long cal_result; const uint32_t cal_divider = 200; uint32_t divider = DIV; uint32_t audio_freq = 1466; // wspr 1400 to 1600 offset from base vfo freq uint8_t Rdiv = RDIV; uint8_t operate_mode = FRAME_MODE; //FRAME_MODE CAT_MODE // start in stand alone timing mode uint8_t wspr_tx_enable; // transmit enable uint8_t wspr_tx_cancel; // CAT control RX command cancels tx uint8_t cal_enable; long tm_correct_count = 60000; // add or sub one ms for time correction per this many ms int8_t tm_correction = 0; // 0, 1 or -1 time correction int8_t tm_correction2; // frame sync to wwvb signal falling edge // Download WSPRcode.exe from http://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe and run it in a dos window // Bad link now - K1JT has moved to SourceForge // Type (for example): WSPRcode "K1ABC FN33 37" 37 is 5 watts, 30 is 1 watt, 33 is 2 watts, 27 is 1/2 watt // ( Use capital letters in your call and locator when typing in the message string. No extra spaces ) // Using the editing features of the dos window, mark and copy the last group of numbers // Paste into notepad and replace all 3 with "3," all 2 with "2," all 1 with "1," all 0 with "0," // Remove the comma on the end // the current message is "KE1MU FN54 23" const char wspr_msg[] = { 3, 1, 2, 2, 0, 2, 2, 2, 3, 2, 2, 2, 1, 1, 3, 2, 0, 0, 3, 0, 0, 1, 0, 1, 3, 3, 1, 2, 0, 2, 0, 2, 2, 2, 3, 2, 0, 1, 0, 1, 2, 2, 0, 0, 0, 0, 3, 0, 3, 1, 2, 0, 3, 1, 0, 1, 2, 0, 2, 3, 1, 2, 3, 2, 0, 0, 0, 1, 1, 2, 1, 0, 1, 2, 3, 0, 1, 0, 2, 3, 0, 0, 3, 0, 3, 1, 0, 2, 0, 3, 3, 2, 3, 2, 1, 0, 2, 2, 1, 2, 0, 0, 0, 2, 3, 2, 2, 1, 0, 2, 1, 3, 1, 0, 1, 1, 0, 0, 3, 1, 2, 3, 2, 0, 0, 1, 1, 3, 2, 2, 0, 2, 2, 3, 0, 1, 2, 0, 3, 1, 2, 2, 0, 0, 0, 2, 2, 1, 1, 0, 3, 2, 1, 1, 0, 2, 2, 1, 3, 0, 0, 2 }; uint8_t band; // current band struct BAND { int pin; // band relay switching uint32_t low; // low frequency limit uint32_t high; // high frewquency limit }; uint8_t wband = 3; // wspr band switching via encoder long press uint8_t sstate[1]; // switch state, one switch // relay board was jumpered to NOT have filter 1 always in line and antenna connects to the bnc // on the arduino shield. ( otherwise highest freq would need to be in position 1 and output would // be from the relay board ) struct BAND band_info[6] = { // filter selected { 7, 40000, 600000 }, // 630m { A0, 600000, 2500000 }, // 160m { 10, 2500000, 5000000 }, // 80m { 11, 5000000, 11500000 }, // 30m { 12,11500000, 20000000 }, // 17m { A3,20000000, 30000000 } // 10m }; // wspr frequencies for eeprom save routine. Only these frequencies will be saved. const uint32_t magic_freq[10] = { 474200, 1836600, 3568600, 7038600, 10138700, 14095600, 18104600, 21094600, 24924600, 28124600 }; #define WWVB_OUT 9 #define WWVB_PWDN 8 // was the low enable. Rewired the WWVB receiver to get power from this I/O pin. // this reverses the logic so it is now high to enable. With only two wires in the // cable, this seems like the best way to remove the need for the 9 volt battery // that was powering the wwvb receiver and to avoid taking the WSPR unit apart to // switch the wiring to +5 volts instead of an I/O pin. uint64_t wwvb_data, wwvb_sync, wwvb_errors; uint8_t wwvb_quiet = 0; // wwvb debug print flag, set to 1 for printing // or enter 1 CAT command( ?V for Rx only or #0 to stay in FRAME mode with logging ) uint8_t wwvb_stats[8]; // bit distribution over 60 seconds uint8_t wwvb_last_err; // display last error character received ( will show what causes just one error ) uint8_t DST; // daylight savings bit uint8_t frame_sec; // frame timer counts 0 to 120 int frame_msec; // uint8_t tick; // start each minute, what was this for ? to match displayed time with computer time // but disturbs the trending bit display // very long term time correction int FF = 0; // fixed part of fudge factor for frequency counter result ( counting 3 mhz signal ) int ff = 0; // fractional part of the fudge factor ( floats not useful as limited in significant figures ) uint8_t dbug_print_state; // print messages at 1200 baud without blocking // date, time keeping int gmon = 1,gday = 1,gyr = 1,ghr,gmin; int tot_days = 1; uint16_t leap = 1; // #define TK 4 // keep time has been run #define TS 2 // time was set from WWVB decode // #define TP 1 // print decode indicator uint8_t time_flags; // WWVB encodes the previous minute, flags to print the correct time uint8_t trends[60]; uint8_t clr_trends; unsigned int decodes; uint8_t report_i; // see if a single trend shows the lsb of minutes position in time. long int stp = 1000; int debug_i; /***************************************************************************/ void ee_save(){ uint8_t i; static uint8_t last_i = 255; if( last_i != 255 ) return; // save only the freq of the 1st time transmitting after reset for( i = 0; i < 10; ++i ){ if( freq == magic_freq[i] ) break; } if( i == 10 ) return; // not a wspr frequency // if( i == last_i ) return; // already wrote this one. ( redundant - only saving 1st power up tx freq now // instead of the last transmit freq. This allows wsjt band hopping tx // without wearing out the eeprom last_i = i; EEPROM.put(0,Rdiv); // put does not write if data matches EEPROM.put(1,freq); // and hopefully this will not take long when there is a match EEPROM.put(5,divider); } void ee_restore(){ if( EEPROM[0] == 255 ) return; // blank eeprom EEPROM.get(0,Rdiv); EEPROM.get(1,freq); EEPROM.get(5,divider); //Serial.println(Rdiv); //Serial.println(freq); //Serial.println(divider); } void setup() { uint8_t i; Serial.begin(1200); // TenTec Argo V baud rate 1200 i2init(); ee_restore(); // get default freq for frame mode from eeprom pinMode(MUTE,OUTPUT); // receiver t/r switch digitalWrite(MUTE,LOW); // enable the receiver pinMode(WWVB_OUT, INPUT); // sample wwvb receiver signal pinMode(WWVB_PWDN, OUTPUT); // digitalWrite(WWVB_PWDN,LOW); // enable wwvb receiver digitalWrite(WWVB_PWDN,HIGH); // power the wwvb receiver with the I/O pin // set up the relay pins, exercise the relays, delay is 1.2 seconds, so reset at 59 seconds odd minute to be on time for( i = 0; i < 6; ++i ){ pinMode(band_info[i].pin,OUTPUT); digitalWrite( band_info[i].pin,LOW ); delay(200); digitalWrite( band_info[i].pin,HIGH ); } i2cd(SI5351,16,0x4f); // clock 0, PLLA i2cd(SI5351,17,0x4f); // clock 1, PLLA i2cd(SI5351,18,0x6f); // clock 2, PLLB // set some divider registers that will never change for(i = 0; i < 3; ++i ){ i2cd(SI5351,42+8*i,0); i2cd(SI5351,43+8*i,1); i2cd(SI5351,47+8*i,0); i2cd(SI5351,48+8*i,0); i2cd(SI5351,49+8*i,0); } si_pll_x(PLLB,cal_freq,cal_divider,0); // calibrate frequency on clock 2 si_load_divider(cal_divider,2,0,1); si_pll_x(PLLA,Rdiv*4*freq,divider,0); // receiver 4x clock si_load_divider(divider,0,0,Rdiv*4); // TX clock 1/4th of the RX clock si_load_divider(divider,1,1,Rdiv); // load divider for clock 1 and reset pll's i2cd(SI5351,3,0xff ^ (CLK1_EN + CLK2_EN) ); // turn on clocks, receiver and calibrate // i2cd(SI5351,3,0xff ^ (CLK0_EN + CLK1_EN + CLK2_EN)); // testing only all on, remove tx PWR digitalWrite(band_info[band].pin,LOW); // in case this turns out to be the correct relay band_change(); // select the correct relay //Serial.println(F("Starting...")); pinMode(13,OUTPUT); LCD.InitLCD(); // using a modified Nokia library for the OLED LCD.setFont(SmallFont); LCD.clrScr(); LCD.print("QRP-LABS WSPR SDR",0,ROW0); i2flush(); delay(2000); // LCD.clrRow(0); // ? clear to end from current position LCD.clrScr(); freq_display(); mode_display(); } int8_t encoder(){ /* read encoder, return 1, 0, or -1 */ static int8_t mod; /* encoder is divided by 4 because it has detents */ static int8_t dir; /* need same direction as last time, effective debounce */ static int8_t last; /* save the previous reading */ int8_t new_; /* this reading */ int8_t b; new_ = (PIND >> 2) & 3; if( new_ == last ) return 0; /* no change */ b = ( (last << 1) ^ new_ ) & 2; /* direction 2 or 0 from xor of last shifted and new data */ last = new_; if( b != dir ){ dir = b; return 0; /* require two in the same direction serves as debounce */ } mod = (mod + 1) & 3; /* divide by 4 for encoder with detents */ if( mod != 2 ) return 0; if( dir == 2 ) return 1; /* swap return values if it works backwards */ else return -1; } /* switch states */ #define IDLE_ 0 #define ARM 1 #define DARM 2 #define DONE 3 #define TAP 4 #define DTAP 5 #define LONGP 6 /* run the switch state machine, generic code for multiple switches even though have only one here */ int8_t switches(){ static uint8_t press_, nopress; static uint32_t tm; int i,j; int8_t sw; int8_t s; if( tm == millis() ) return 0; // run once per millisecond tm = millis(); /* get the switch readings, low active but invert bits */ sw = ((PIND & 0x10) >> 4) ^ 0x01; if( sw ) ++press_, nopress = 0; /* only acting on one switch at a time */ else ++nopress, press_ = 0; /* so these simple vars work for all of them */ /* run the state machine for all switches in a loop */ for( i = 0, j = 1; i < 1; ++i ){ s = sstate[i]; switch(s){ case DONE: if( nopress >= 100 ) s = IDLE_; break; case IDLE_: if( ( j & sw ) && press_ >= 30 ) s = ARM; break; /* pressed */ case ARM: if( nopress >= 30 ) s = DARM; /* it will be a tap or double tap */ if( press_ >= 240 ) s = LONGP; // long press break; case DARM: if( nopress >= 240 ) s = TAP; if( press_ >= 30 ) s = DTAP; break; } sstate[i] = s; j <<= 1; } return sstate[0]; // only one switch implemented so can return its value } uint8_t band_change(){ if( freq > band_info[band].low && freq <= band_info[band].high ) return 0; // band change needed digitalWrite(band_info[band].pin,HIGH); for( band = 0; band < 6; ++band ){ if( freq > band_info[band].low && freq <= band_info[band].high ) break; } if( band == 6 ) band = 5; // default band digitalWrite( band_info[band].pin,LOW); return 1; } void qsy(uint32_t new_freq){ // change frequency unsigned char divf; uint32_t f4; static uint32_t old_freq = 0; divf = 0; // flag if we need to reset the PLL's if( (abs((int32_t)old_freq - (int32_t)new_freq) > 500000) || old_freq == 0){ divf = 1; // large qsy from our current dividers old_freq = new_freq; } freq = new_freq; if( band_change() ) divf = 1; // check the proper relay is selected // force freq above a lower limit if( freq < 40000 ) freq = 40000; if( freq > 2000000 && Rdiv != 1 ) Rdiv = 1, divf = 1; // tx R is 4 if( freq < 1000000 && Rdiv != 16 ) Rdiv = 16, divf = 1; // tx R is 64 f4 = Rdiv * 4 * freq; f4 = f4 / 100000; // divide by zero next line if go below 100k on 4x vfo if( divf ) divider = 7500 / f4; // else we are using the current divider if( divider & 1) divider += 1; // make it even if( divider > 254 ) divider = 254; if( divider < 6 ) divider = 6; // setup the PLL and dividers if needed si_pll_x(PLLA,Rdiv*4*freq,divider,0); if( divf ){ si_load_divider(divider,0,0,Rdiv*4); // tx at 1/4 the rx freq si_load_divider(divider,1,1,Rdiv); // load rx divider and reset PLL } freq_display(); } void loop() { static unsigned long ms; static int temp; // just for flashing the LED when there is I2C activity. Check for I2C hangup. int8_t t; if( Serial.availableForWrite() > 20 ) radio_control(); temp += i2poll(); if( ms != millis()){ // run once each ms ms = millis(); frame_timer(ms); if( wspr_tx_enable || wspr_tx_cancel ) wspr_tx(ms); if( cal_enable ) run_cal(); //wwvb_sample(ms); wwvb_sample2(ms); if( temp ){ if( temp > 100 ) temp = 100; --temp; if( temp ) digitalWrite(13,HIGH); else digitalWrite(13,LOW); } // print out debug messages without waiting for the serial ready if( wwvb_quiet == 1 && dbug_print_state && Serial.availableForWrite() > 20) dbug_errors( 0, 0, 0, 0, 0 ); t = encoder(); if( t ){ qsy( freq + t * stp ); } t = switches(); if( t > DONE ){ switch(t){ case TAP: stp /= 10; if( stp == 1 ) stp = 1000000; break; case DTAP: operate_mode ^= 1; break; case LONGP: if(++wband > 9 ) wband = 0; qsy(magic_freq[wband]); break; } sstate[0] = DONE; mode_display(); } } } void calc_date(){ // from total days and leap flag const int cal[2][12] = { 31,28,31,30,31,30,31,31,30,31,30,31, 31,29,31,30,31,30,31,31,30,31,30,31 }; int i,d; d = tot_days; for( i = 0; i < 12; ++i ){ if( d <= cal[leap][i] ) break; d -= cal[leap][i]; } gmon = i + 1; gday = d; } void wwvb_decode(){ // WWVB transmits the data for the previous minute just ended uint16_t tmp; uint16_t tmp2; uint16_t yr; uint16_t hr; uint16_t mn; uint16_t dy; uint8_t i; tmp2 = frame_sec; tmp = frame_msec; // capture milliseconds value before it is corrected so we can print it. ++decodes; yr = wwvb_decode2( 53, 0x1ff ); // year is 0 to 99 dy = wwvb_decode2( 33, 0xfff ); // day is 0 to 365/366 hr = wwvb_decode2( 18, 0x7f ); mn = wwvb_decode2( 8, 0xff ); leap = wwvb_decode2( 55, 0x1 ); DST = wwvb_decode2( 57, 0x1 ); // in effect bit ( using bit 58 gave wrong time for one day ) if( ( mn & 1 ) == 0 ){ //last minute was even so just hit the 60 second mark in the frame // only apply clock corrections in the middle of the two minute frame or may // otherwise mess up the frame timing if( frame_sec == 59 && frame_msec >= 500 ) ; // ok else if( frame_sec == 60 && frame_msec < 500 ) ; // ok else{ // way off, reset to the correct time frame_sec = 60; frame_msec = 0; FF = 0, ff = 0; // reset timing fudge factor clr_trends = 1; // the trend buckets will be incorrect now } } if( wwvb_quiet == 1 ){ // wwvb logging mode // Serial.print(decodes); // Serial.write(' '); Serial.print(tmp2); Serial.write('.'); Serial.print(tmp); // show jitter // Serial.print(" WWVB "); // Serial.print("20"); // the year 2100 bug // Serial.print( yr ); Serial.write(' '); // Serial.print( dy ); Serial.write(' '); // Serial.print( hr ); Serial.write(':'); // if( mn < 10 ) Serial.write('0'); // Serial.println(mn); } ghr = hr; gmin = mn; gyr = yr; tot_days = dy; keep_time(); // wwvb sends minute just ended info, so increment calc_date( ); time_flags |= TS; } // wwvb fields decode about the same way uint16_t wwvb_decode2( uint8_t pos, uint16_t mask ){ uint16_t tmp; uint16_t val; tmp = ( wwvb_data >> ( 59 - pos ) ) & mask; val = 0; if( tmp & 0x800 ) val += 200; if( tmp & 0x400 ) val += 100; if( tmp & 0x100 ) val += 80; if( tmp & 0x80 ) val += 40; if( tmp & 0x40 ) val += 20; if( tmp & 0x20 ) val += 10; val += (tmp & 0xf); return val; } // the original idea was to correct the 27 mhz clock using the UNO 16 mhz clock as a reference. // calibrating the SI5351 against the 16mhz clock does not seem to be viable. // the 16mhz clock varies as much or more than the 27mhz clock with changes in temperature // this function has been changed to correct the time keeping of the 16 meg clock based upon the 27 mhz reference // this seems to be working very well with no change in WSPR received delta time for over 48 hours. void run_cal(){ // count pulses on clock 2 wired to pin 5 // IMPORTANT: jumper W4 to W7 on the arduino shield long error1,error2; if( cal_enable == 1 ){ FreqCount.begin(1000); // ++cal_enable; } if( FreqCount.available() ){ cal_result = (long)FreqCount.read() + (long)FF; tm_correction = 0; // default if( cal_result > 2999996L && cal_result < 3000004 )tm_correction = 0, tm_correct_count = 30000; else{ if( cal_result < 3000000L ){ tm_correction = -1, error1 = 3000000L - cal_result; error2 = error1 - 1; } else if( cal_result > 3000000L ){ tm_correction = 1, error1 = cal_result - 3000000L; error2 = error1 + 1; } if( error1 != 0 ) error1 = 3000000L / error1; if( error2 != 0 ) error2 = 3000000L / error2; tm_correct_count = interpolate( error1,error2,ff ); } if( wwvb_quiet == 1 && tm_correction == 0 ){ // wwvb logging mode // Serial.print( result ); Serial.write(' '); // Serial.print( error ); Serial.write(' '); // Serial.print(tm_correction); Serial.write(' '); // Serial.print(tm_correct_count); Serial.write(' '); } FreqCount.end(); cal_enable = 0; temp_correction(); } } // amt is a value from 0 to 99 representing the percentage long interpolate( long val1, long val2, int amt ){ long diff; long result; diff = val2 - val1; result = (diff * amt) / 100; result += val1; // Serial.print( val1 ); Serial.write(' '); Serial.print( val2 ); Serial.write(' '); return result; } void clock_correction( int8_t val ){ // time keeping only, change the fudge factor ff += val; // val is always -1,0,or 1 if( ff >= 100 ) ff = 0, ++FF; // keep ff in 0 to 99 range if( ff < 0 ) ff = 99, --FF; } void temp_correction( ){ // short term drift correction with a linear map uint32_t local_drift; // corrects for drift due to day/night temperature changes if( wspr_tx_enable ) return; // ignore this when transmitting local_drift = map( cal_result, 2999900, 3000000, 20, 0 ); // 20 if( local_drift != drift ){ drift = local_drift; si_pll_x(PLLB,cal_freq,cal_divider,0); si_pll_x(PLLA,Rdiv*4*freq,divider,0); // new receiver 4x clock, transmitter 1x clock } } // correct errors on bits that do not change often // this version moves the counters up to a hard limit and moves down on different bit decoded // slips in time on weak signal as is called from frame_sync // this version does not use bit_errors but instead requires trends to be at the limit in order to decode // less decodes, less false decodes // bump the trends type for each minute to be what is expected char wwvb_trends( char val, uint8_t dat ){ static int i = 60; static int unslip; uint8_t count; uint8_t trend_t,new_t; int w; static int s_count; // counts from last sync - detect when spaced 9 apart. Most are 10 apart. #define LIMIT 6 // 2 min 30 max #define SYNC 32 #define ONES 64 #define ZEROS 128 #define ERR 0 if( clr_trends ){ // reset the data for( w = 0; w < 60; ++w ) trends[w] = 0; clr_trends = 0; } if( unslip ) unslip = 0; else if( ++i >= 60 ){ i = 0; // the sample bucket ends at 1 sec past the time sampled. Test once per two minute frame if( frame_sec < 60 && (frame_msec < 400 || frame_msec > 600) ){ if( frame_sec == 0 && frame_msec > 600 ) ; // ok, just early else if( frame_sec == 1 && frame_msec < 400 ) ; // also ok else if( frame_sec < 30 && frame_sec > 0 ) ++i, report_i = 0; // running slow is normal on weak signals else unslip = 1, report_i = 0;; // probably a time reset or decode happened } } count = trends[i] & 31; // get existing trend data trend_t = trends[i] & ( ONES | SYNC | ZEROS ); new_t = ERR; // assume error if( val == 'S' ) new_t = SYNC; if( val == '1' ) new_t = ONES; if( val == '0' ) new_t = ZEROS; ++s_count; // count from the last sync. Attempt early sync to 9 syncs spacing. if( new_t == SYNC ){ if( s_count == 9 ) report_i = i; if( s_count == 1 ) report_i = (i+9) % 60; // double sync detect s_count = 0; } if( trend_t == new_t && trend_t != ERR ){ // increment the trend if match if( count < LIMIT ) ++count; } if( trend_t != new_t && new_t != ERR ){ // valid decode, bit changed, age type if( count == LIMIT && i > 8 ) val = 'x'; // question this change even though valid decode if( count > 1 ) count -= ( trend_t == SYNC ? 1 : 2 ); // age existing type, favor sync else{ count = 1; trend_t = new_t; } } trends[i] = trend_t + count; // save new trend values // some unnessary playing around. Partial decode turned out to be not useful. // if( decodes == 0 && ( count >= LIMIT/2 || trend_t == SYNC || i == 8 ) ) part_decode(i); // return history on errors with only 1 bit incorrect // if( new_t == ERR && count == LIMIT ){ // if( trend_t == ZEROS ){ // if( bit_errors(dat,0xfc,i) < 2 ) val = 'o'; // } // if( trend_t == ONES ){ // if( bit_errors(dat,0xf0,i) < 2 ) val = 'i'; // } // if( trend_t == SYNC ){ // if( bit_errors(dat,0xc0,i) < 2 ) val = 's'; // } // } if( i == 0 && val == '.' ) val = 'Z'; // view index on no decode no history if( i == 0 ) disp_date_time(); LCD.gotoRowCol(7,110); LCD.putch(val); LCD.printNumI(i,75,ROW7,2,'0'); if( wwvb_quiet == 1 ){ Serial.write(val); if( i%10 == 9 ) Serial.write(' '); } //debug_i = i; return val; } /* // return the number of bits different between mask and value int bit_errors( uint8_t val, uint8_t mask, int i ){ int count; uint8_t b; // only here if the val did not decode as exact // provide different levels of correction depending upon what field the val is in. // bit 1 that follows the double sync seems to be the most likely bit to decode incorrectly // require the double sync and other bits to all decode without correcting any bit errors bits 59-9 if( i <= 9 || i == 59 ) return 8; // require exact decode in the minutes field // hours field changes once per hour, don't correct bit pattern 11111000 which has one bit error for // both a 0 and a 1. if( val == 0xf8 && i > 11 && i < 19 && i != 14 ) return 8; // 10,11,14 are unused always zero // the rest of the fields change once a year or are don't care bytes for this application. // count how many bits are different from the mask. Decode from trends if only one bit is incorrect. count = 0, b = 0x80; val = val ^ mask; // val = error bits for( i = 0; i < 8; ++i ){ // count the errors if( val & b ) ++count; b >>= 1; } return count; } */ /* // correct errors on bits that do not change often // this version moves the counters up to a hard limit and moves down on different bit decoded // slips in time on weak signal as is called from frame_sync char wwvb_trends( char val ){ static int i = 60; static int unslip; static uint8_t z; // stale out data counter uint8_t count; uint8_t trend_t,new_t; int w; #define LIMIT 10 // 2 min 30 max #define SYNC 32 #define ONES 64 #define ZEROS 128 #define ERR 0 if( clr_trends ){ // reset the data for( w = 0; w < 60; ++w ) trends[w] = 0; clr_trends = 0; } if( unslip ) unslip = 0; else if( ++i >= 60 ){ i = 0; // the sample bucket ends at 1 sec past the time sampled. Test once per two minute frame if( frame_sec < 60 && (frame_msec < 400 || frame_msec > 600) ){ if( frame_sec == 0 && frame_msec > 600 ) ; // ok, just early else if( frame_sec == 1 && frame_msec < 400 ) ; // also ok else if( frame_sec < 30 ) ++i; // running slow is normal on weak signals else unslip = 1; // probably a time reset or decode happened } } count = trends[i] & 31; // get existing trend data trend_t = trends[i] & ( ONES | SYNC | ZEROS ); new_t = ERR; // assume error if( val == 'S' ) new_t = SYNC; if( val == '1' ) new_t = ONES; if( val == '0' ) new_t = ZEROS; if( trend_t == new_t && trend_t != ERR ){ // increment the trend if match if( count < LIMIT ) ++count; } if( trend_t != new_t && new_t != ERR ){ // valid decode, bit changed, age type if( count > 1 ) count -= ( trend_t == SYNC ? 1 : 2 ); // age existing type, favor sync else{ count = 1; trend_t = new_t; } } if( new_t == ERR && wspr_tx_enable == 0){ // slowly stale out the data on errors ++z; // reduce count on 1 out of 16 errors z &= 15; if( z == 0 && count > 1 ) --count; } trends[i] = trend_t + count; // save new trend values // return history on errors, do not send trend for the minutes,hours fields if( new_t == ERR && ( i >= 19 || trend_t == SYNC || i == 4 || i == 10 || i == 11 || i == 14 ) ){ if( count == LIMIT ){ if( trend_t == ZEROS ) val = 'o'; if( trend_t == ONES ) val = 'i'; if( trend_t == SYNC ) val = 's'; } } if( i == 0 && val == '.' ) val = 'x'; // view index on no decode no history if( wwvb_quiet == 1 ){ Serial.write(val); if( i%10 == 9 ) Serial.write(' '); } return val; } */ /* // remove any errors on don't care bits, force to zero // force syncs when we think we are in sync char wwvb_trends( char val ){ const int zeros[17] = {4,10,11,14,20,21,24,34,35,36,37,38,44,54,56,57,58}; static int i,z; static char last_val; static int mod10; static int q; static int force; static int fix; static int syncs[4]; static int disable; int w; // double sync reset if( last_val == 'S' && val == 'S' ){ if( i != 59 ){ i = 59; // index reset next lines of code force = 0; } disable = 0; } last_val = val; if( ++i >= 60 ){ // start of next minute i = 0; z = 0; fix = 0; if( frame_sec == 4 ) ++i; // slipping in time. Happens on very weak wwvb signal // this test is 1 sec after the print at second 59 if( frame_sec > 4 && frame_sec < 56 ) disable = 1; // out of correction range, wait for double sync } if( val == 'S' && i > 4 && i < 56 ){ // syncs should all be modulo 9, double sync ignored, syncs[0] = syncs[1]; syncs[1] = syncs[2]; syncs[2] = syncs[3]; syncs[3] = i; // for reporting only w = i%10; if( w == mod10 ) ++q; else q = 0, force = 0; mod10 = w; if( q >= 3 && disable == 0 ){ // quorum of syncs on same modulus index if( mod10 == 9 ) force = 1; // correct index else if( mod10 >= 4 ) ++i; // slipped in time else --i; // started early or some other issue like a time reset q = 0; } } w = val; if( i == zeros[z] ){ ++z; if( force && val == '.' ) val = 'o'; // remove errors on don't care bits } if( force && ( i == 0 || i%10 == 9 ) && val != 'S' ) val = 's'; if( w != val ) ++fix; if( fix == 9 ) force = 0; // too many fails a minute if( wwvb_quiet == 1 && i == 59 ){ // report syncs if( frame_sec < 100 ) Serial.write(' '); if( frame_sec < 10 ) Serial.write(' '); Serial.print(frame_sec); Serial.write(' '); Serial.print("F "); Serial.print(force); Serial.print(" Fix "); // if( fix < 10 ) Serial.write(' '); Serial.print(fix); Serial.print(" Sync "); for( w = 0; w < 4; ++w ){ if( syncs[w] < 10 ) Serial.write(' '); Serial.print(syncs[w]); Serial.write(' '); } } return val; } */ void frame_timer( unsigned long t ){ static unsigned long old_t; static uint8_t slot; static long time_adjust; // 16mhz clock measured at 16001111. Will gain 1ms in approx 14401 ms. Or 1 second in 4 hours. // the calibrate function has been repurposed to correct the time keeping of the Arduino. frame_msec += ( t - old_t ); time_adjust += (t - old_t); if( time_adjust >= tm_correct_count && frame_msec != 0 ){ time_adjust -= tm_correct_count; frame_msec += tm_correction; // add one, sub one or no change depending upon tm_correction value } if( tm_correction2 && frame_msec > 400 && frame_msec < 600 ){ frame_msec += tm_correction2; tm_correction2 = 0; } old_t = t; if( frame_msec >= 1000 ){ frame_msec -= 1000; if( ++frame_sec >= 120 ){ // 2 minute slot time frame_sec -= 120; if( ++slot >= 8 ) slot = 0; // 10 slots is a 20 minute frame if( slot == 1 && operate_mode == FRAME_MODE ) wspr_tx_enable = 1; } if( frame_sec == 116 ) cal_enable = 1; // do once per slot in wspr quiet time if( frame_sec == 90 || frame_sec == 30 ) keep_time(); // half minute to avoid colliding with wwvb decodes // if( frame_sec == 0 || frame_sec == 60 ) tick = 1; // print wwvb decode info if enabled } } void keep_time(){ if( ++gmin >= 60 ){ gmin = 0; if( ++ghr >= 24 ){ ghr = 0; ++tot_days; if( tot_days > 365 + leap ) ++gyr, tot_days = 1; calc_date(); } } // if( time_flags & TS ) time_flags = TP + TK; // clear TS but flag a print of wwvb decode indicator // else time_flags = TK; } void wspr_tx( unsigned long t ){ static int i; static unsigned long timer; static uint8_t mod; static unsigned int one_second; // delay one second before starting transmission, this function is called once per millisecond if( one_second < 1000 && wspr_tx_cancel == 0 ){ ++one_second; return; } if( wspr_tx_cancel ){ // quit early or just the end of the message if( i < 160 ) i = 162; // let finish if near the end, else force done } if( (t - timer) < 683 ) return; // baud time is 682.66666666 ms timer = t; if( ++mod == 3 ) mod = 0, ++timer; // delay 683, 683, 682, etc. if( i == 162 ){ tx_off(); i = 0; // setup for next time to begin at zero index wspr_tx_cancel = wspr_tx_enable = 0; // flag done one_second = 0; return; } // set the frequency si_pll_x(PLLA,Rdiv*4*(freq+audio_freq),divider,Rdiv*4*wspr_msg[i]); if( i == 0 ) tx_on(); ++i; } void tx_on(){ digitalWrite(MUTE,HIGH); digitalWrite(WWVB_PWDN,LOW); i2cd(SI5351,3,0xff ^ (CLK0_EN)); // tx clock on, other clocks off during tx } void tx_off(){ i2cd(SI5351,3,0xff ^ (CLK1_EN + CLK2_EN) ); // turn off tx, turn on rx and cal clocks i2flush(); // wait for tx off to complete before enabling the rx si_pll_x(PLLA,Rdiv*4*freq,divider,0); // return to RX frequency digitalWrite(MUTE,LOW); // enable receiver digitalWrite(WWVB_PWDN,HIGH); // enable wwvb receiver ee_save(); // save this freq to use during stand alone mode(FRAME MODE). } void i2cd( unsigned char addr, unsigned char reg, unsigned char dat ){ // direct register writes. A possible speed up could be realized if one were // to use the auto register inc feature of the SI5351 i2start(addr); i2send(reg); i2send(dat); i2stop(); } /****** SI5351 functions ******/ void si_pll_x(unsigned char pll, uint32_t freq, uint32_t out_divider, uint32_t fraction ){ uint32_t a,b,c; uint32_t bc128; // floor 128 * b/c term of equations uint32_t pll_freq; uint32_t cl_freq; uint32_t P1; // PLL config register P1 uint32_t P2; // PLL config register P2 uint32_t P3; // PLL config register P3 uint32_t r; uint32_t f; // fudge freq and fraction if remainder is near max ( c ) cl_freq = clock_freq; //if( pll == PLLA ) cl_freq += drift; // drift not applied to 3 mhz calibrate freq cl_freq += drift; // drift applied to 3 mhz. Pick one of these. if( pll == PLLB ) c = 1000000; // max 1048575, cal freq pll else{ // set c such that each 4 steps of fraction changes freq by 1.46 hz ( R divisor of 4 in use ) c = (float)(cl_freq) / ( 1.46 * (float)(out_divider)); while( c > 1048575 ) c /= 2, fraction /= 2; // !!! fraction /2 or *2 ? } // pll_freq = 100ULL * (uint32_t)freq + fraction; pll_freq = freq * out_divider; a = pll_freq / cl_freq ; r = pll_freq - a * cl_freq ; b = ( (uint64_t)c * (uint64_t)r ) / (uint64_t)cl_freq; f = Rdiv*4*3 + 1; // max wspr deviation, R is 4 and Rdiv is further R divider for low freq // probably should skip this for PLLB if( b + f >= c ) b -= f; // fudge b if go over max b value on wspr steps, tx off desired freq by 5 hz bc128 = (128 * r)/ cl_freq; b += fraction; // wspr offset P1 = 128 * a + bc128 - 512; P2 = 128 * b - c * bc128; //if( P2 > c ) P2 = 0; // avoid negative numbers P3 = c; i2cd(SI5351, pll + 0, (P3 & 0x0000FF00) >> 8); i2cd(SI5351, pll + 1, (P3 & 0x000000FF)); i2cd(SI5351, pll + 2, (P1 & 0x00030000) >> 16); i2cd(SI5351, pll + 3, (P1 & 0x0000FF00) >> 8); i2cd(SI5351, pll + 4, (P1 & 0x000000FF)); i2cd(SI5351, pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16)); i2cd(SI5351, pll + 6, (P2 & 0x0000FF00) >> 8); i2cd(SI5351, pll + 7, (P2 & 0x000000FF)); // i2cd( SI5351, 177, 0xAC ); // PLLA PLLB soft reset } // load new divider for specified clock, reset PLLA and PLLB if desired void si_load_divider( uint32_t val, uint8_t clk , uint8_t rst, uint8_t Rdiv){ uint8_t R; R = 0; Rdiv >>= 1; // calc what goes in the R divisor field while( Rdiv ){ ++R; Rdiv >>= 1; } R <<= 4; val = 128 * val - 512; i2cd( SI5351, 44+8*clk, ((val >> 16 ) & 3) | R ); i2cd( SI5351, 45+8*clk, ( val >> 8 ) & 0xff ); i2cd( SI5351, 46+8*clk, val & 0xff ); if( rst ) i2cd( SI5351, 177, 0xAC ); // PLLA PLLB soft reset needed } void freq_display(){ int rem; LCD.setFont(MediumNumbers); LCD.printNumI(freq/1000,0,ROW0,5,'/'); // '/' is a leading space with this font table LCD.setFont(SmallFont); rem = freq % 1000; LCD.printNumI(rem,64,ROW0,3,'0'); } // display mode and step void mode_display(){ LCD.setFont(SmallFont); if( operate_mode == CAT_MODE ) LCD.print(" CAT",RIGHT,ROW0); else LCD.print("WSPR",RIGHT,ROW0); LCD.printNumI(stp,RIGHT,ROW1,7,' '); } /*****************************************************************************************/ // TenTec Argonaut V CAT emulation //int un_stage(){ /* send a char on serial */ //char c; // if( stg_in == stg_out ) return 0; // c = stg_buf[stg_out++]; // stg_out &= ( STQUESIZE - 1); // Serial.write(c); // return 1; //} #define CMDLEN 20 char command[CMDLEN]; uint8_t vfo = 'A'; void radio_control() { static int expect_len = 0; static int len = 0; static char cmd; char c; int done; if (Serial.available() == 0) return; done = 0; while( Serial.available() ){ c = Serial.read(); command[len] = c; if(++len >= CMDLEN ) len= 0; /* something wrong */ if( len == 1 ) cmd = c; /* first char */ /* sync ok ? */ if( cmd == '?' || cmd == '*' || cmd == '#' ); /* ok */ else{ len= 0; return; } if( len == 2 && cmd == '*' ) expect_len = lookup_len(c); /* for binary data on the link */ if( (expect_len == 0 && c == '\r') || (len == expect_len) ){ done = 1; break; } } if( done == 0 ) return; /* command not complete yet */ if( cmd == '?' ){ get_cmd(); operate_mode = CAT_MODE; // switch modes on query cat command if( wwvb_quiet < 2 ) ++wwvb_quiet; // only one CAT command enables wwvb logging, 2nd or more turns it off mode_display(); } if( cmd == '*' ) set_cmd(); if( cmd == '#' ){ pnd_cmd(); if( wwvb_quiet < 2 ) ++wwvb_quiet; // allow FRAME mode and the serial logging at the same time } /* prepare for next command */ len = expect_len= 0; stage('G'); /* they are all good commands */ stage('\r'); } int lookup_len(char cmd2){ /* just need the length of the command */ int len; switch(cmd2){ /* get length of argument */ case 'X': len = 0; break; case 'A': case 'B': len = 4; break; case 'E': case 'P': case 'M': len = 2; break; default: len = 1; break ; } return len+3; /* add in *A and cr on the end */ } void set_cmd(){ char cmd2; unsigned long val4; cmd2 = command[1]; switch(cmd2){ case 'X': stage_str("RADIO START"); stage('\r'); break; case 'O': /* split */ break; case 'A': // set frequency case 'B': val4 = get_long(); qsy(val4); break; case 'E': if( command[2] == 'V' ) vfo = command[3]; break; case 'W': /* bandwidth */ break; case 'K': /* keying speed */ break; case 'T': /* added tuning rate as a command */ break; } /* end switch */ } void get_cmd(){ char cmd2; long arg; int len; cmd2 = command[1]; stage(cmd2); switch(cmd2){ case 'A': // get frequency case 'B': arg = freq; stage_long(arg); break; case 'V': /* version */ stage_str("ER 1010-516"); break; case 'W': /* receive bandwidth */ stage(30); break; case 'M': /* mode. 1 is USB USB ( 3 is CW ) */ stage('1'); stage('1'); break; case 'O': /* split */ stage(0); break; case 'P': /* passband slider */ stage_int( 3000 ); break; case 'T': /* added tuning rate command */ break; case 'E': /* vfo mode */ stage('V'); stage(vfo); break; case 'S': /* signal strength */ stage(7); stage(0); break; case 'C': // transmitting status stage(0); if( wspr_tx_enable ) stage(1); else stage(0); break; case 'K': /* wpm on noise blanker slider */ stage( 15 - 10 ); break; default: /* send zeros for unimplemented commands */ len= lookup_len(cmd2) - 3; while( len-- ) stage(0); break; } stage('\r'); } void stage_str( String st ){ int i; char c; for( i = 0; i < st.length(); ++i ){ c= st.charAt( i ); stage(c); } } void stage_long( long val ){ unsigned char c; c= val >> 24; stage(c); c= val >> 16; stage(c); c= val >> 8; stage(c); c= val; stage(c); } unsigned long get_long(){ union{ unsigned long v; unsigned char ch[4]; }val; int i; for( i = 0; i < 4; ++i) val.ch[i] = command[5-i]; // or i+2 for other endian return val.v; } void stage_int( int val ){ unsigned char c; c= val >> 8; stage(c); c= val; stage(c); } void stage_num( int val ){ /* send number in ascii */ char buf[35]; char c; int i; itoa( val, buf, 10 ); i= 0; while( c = buf[i++] ) stage(c); } void pnd_cmd(){ char cmd2; cmd2 = command[1]; switch(cmd2){ case '0': // enter rx mode if( wspr_tx_enable ) wspr_tx_cancel = 1; break; case '1': wspr_tx_enable = 1; break; // TX } } /***** non-blocking, write only, I2C functions ******/ #define I2BUFSIZE 64 int i2buf[I2BUFSIZE]; uint8_t i2in, i2out; // use some upper bits in the buffer for control #define ISTART 0x100 #define ISTOP 0x200 void i2init() { TWBR = 72; // 12 400k for 16 meg clock. 72 100k ((F_CPU/freq)-16)/2 TWSR = 0; TWDR = 0xFF; PRR = 0; } void i2start( unsigned char adr ){ int dat; // shift the address over and add the start flag dat = ( adr << 1 ) | ISTART; i2send( dat ); } void i2send( unsigned int data ){ // just save stuff in the buffer uint8_t t; // but check for space first t = ( i2in + 1 ) & (I2BUFSIZE - 1); while( t == i2out ) i2poll(); // wait for space i2buf[i2in++] = data; i2in &= (I2BUFSIZE - 1); i2poll(); } void i2stop( ){ i2send( ISTOP ); // que a stop condition } void i2flush(){ // call poll to empty out the buffer. while( i2poll() ); } uint8_t i2poll(){ // everything happens here. Call this from loop. static uint8_t state = 0; static int data; static uint8_t delay_counter; // the library code has a delay after loading the transmit buffer // and before the status bits are tested for transmit active if( delay_counter ){ --delay_counter; return (16 + delay_counter); } switch( state ){ case 0: // idle state or between characters if( i2in != i2out ){ // get next character data = i2buf[i2out++]; i2out &= (I2BUFSIZE - 1 ); if( data & ISTART ){ // start data &= 0xff; // set start condition TWCR = (1< (counts[wwvb_count] >> 1) ) ? 0 : 128; wwvb_tmp >>= 1; wwvb_tmp |= b; wwvb_sum = 0; // 8 dumps of the integrator is one second, decode this bit ? wwvb_count++; wwvb_count &= 7; wwvb_clk = counts[wwvb_count]; // 100 100 150 150 150 150 100 100 // decode 0 1 sync stop should be high if( wwvb_count == 0 ){ // decode time // clocks late or early, just dither them back and forth across the falling edge // when not in sync, more 1's than 0's are detected and this slips in time. if( wwvb_tmp != 0xff && wwvb_tmp != 0x00 ){ if( digitalRead(WWVB_OUT) == 0 ){ ++late; // sampling late wwvb_clk -= dither; // adjust sample to earlier } else{ ++early; // need to sample later wwvb_clk += dither; // longer clock } } // decode // 11111100 is a zero, 11110000 is a one, 11000000 is a sync b = 0; s = 0; e = 1; // assume it is an error // strict decode works well, use loose decode for common bit errors ? if( wwvb_tmp == 0xfc || wwvb_tmp == 0xfd /*|| wwvb_tmp == 0xfe*/ ) e = 0, b = 0; if( wwvb_tmp == 0xf0 /*|| wwvb_tmp == 0xf1*/ ) e = 0, b = 1; if( wwvb_tmp == 0xc0 /*|| wwvb_tmp == 0xc1*/ ) e = 0, s = 1; gather_stats( wwvb_tmp , e ); // for serial logging display if( e ) ch = '.'; else if( s ) ch = 'S'; else if( b == 0 ) ch = '0'; else if( b == 1 ) ch = '1'; ch = wwvb_trends(ch , wwvb_tmp); if( e == 0 ) debug_print( wwvb_tmp, 20 ); //debug_i <<= 1; //if( e ) debug_i |= 1; //if( (secs & 7) == 0 ){ // modulus 8 without divide // debug_print( debug_i, 80 ); // debug_i = 0; //} if( e ) debug_print( wwvb_tmp, 80 ); // display data for last error // decode from trends //if( ch == 'o' ) b = 0, e = 0; //if( ch == 's' ) s = 1, e = 0; //if( ch == 'i' ) b = 1, e = 0; //if( ch == 'x' ) e = 1; // flag as error if decoded different from the past history if( e ) ++errors; wwvb_data <<= 1; wwvb_data |= b; // shift 64 bits data wwvb_sync <<= 1; wwvb_sync |= s; // sync wwvb_errors <<= 1; wwvb_errors |= e; // errors // magic 64 bits of sync ( looking at 60 seconds of data with 4 seconds of the past minute ) // xxxx1000000001 0000000001 0000000001 0000000001 0000000001 0000000001 // wwvb_sync &= 0x0fffffffffffffff; // mask off the old bits from previous minute // instead of masking, use the old bits to see the double sync bits at 0 of this minute // and 59 seconds of the previous minute. This decodes at zero time. if( wwvb_sync == 0b0001100000000100000000010000000001000000000100000000010000000001 ){ if( wwvb_errors == 0 ){ // decode if no bit errors wwvb_decode(); } } if( ++secs >= 60 /* || tick */ ){ // adjust dither each minute // tick = 0; val_print = ' '; frame_sync2( errors,frame_msec ); // running in print, wwvb decode, keep_time order or // in wwvb decode, print, keep_time order this case needs a time update // if( time_flags & TS ) keep_time(); // need to increment the time since the last decode // new method = time +- 30 seconds is printed as current time. This routine will eventually seem to // skip ahead a minute to catch up in actual time. // debug print out some stats when in test mode // break this up for 1200 baud, takes too much time and causes missed decode after line feed if( wwvb_quiet == 1 ){ Serial.write(' '); Serial.print(report_i); Serial.write(' '); print_date_time(); Serial.write(' '); if( frame_msec < 100 ) Serial.write(' '); if( frame_msec < 10 ) Serial.write(' '); Serial.print(frame_msec); dbug_errors(1,errors,val_print,early,late); // Serial.print(" Err "); Serial.print(errors); Serial.write(val_print); // Serial.print(" Clk "); Serial.print(early); // Serial.write(','); Serial.print(late); // print_stats(1,errors); // Serial.print(" FF "); Serial.print(FF); // Serial.write(' '); Serial.print(ff); // Serial.print(" Drift "); Serial.print((int)drift/100); // Serial.print(" CC "); Serial.print(tm_correct_count); // Serial.print(" Cal "); Serial.print(cal_result); // Serial.println(); } else print_stats(0,errors); // use stats for an early sync to correct second if( decodes == 0 && report_i > 0 && report_i < 20 ){ if( report_i < 9 ) tm_correction2 += 100; if( report_i > 9 ) tm_correction2 -= 100; // if( report_i != 9 ) report_i = 0; // one time only for each detect( now one second adjust ) } // time_flags = 0; // LCD.setFont(MediumNumbers); // LCD.printNumI(errors,RIGHT,ROW0,2,'/'); // LCD.printNumI(frame_msec,RIGHT,ROW5,3,' '); if( val_print == '-' || val_print == '+' ) valp[vali++] = val_print; // time trend slow or fast if( val_print == ' ' ){ // time trend keeping time well ++valc; valc &= 63; if( valc == 0 ) valp[vali++] = val_print; } vali &= 7; LCD.print(valp,RIGHT,ROW5); LCD.printNumI(FF,100,ROW6); LCD.putch(' '); LCD.printNumI(ff,RIGHT,ROW6,2,' '); dither = ( errors >> 4 ) + 1; early = late = secs = errors = 0; // reset the stats for the next minute } } // end decode time } // end integration timer } // loops - repeat for lost milliseconds if any } void dbug_errors(uint8_t st,uint8_t errors,char val_print,uint8_t early, uint8_t late){ static uint8_t err, vp, earl, lt; if( st == 1 ) dbug_print_state = 1; // flag for start of new data switch( dbug_print_state ){ case 1: // save the data err = errors, vp = val_print, earl = early, lt = late; ++dbug_print_state; break; case 2: // print first group Serial.print(" Err "); Serial.print(err); Serial.write(vp); Serial.print(" Clk "); Serial.print(earl); Serial.write(','); Serial.print(lt); ++dbug_print_state; break; case 3: print_stats(1,err); ++dbug_print_state; break; case 4: Serial.print(" FF "); Serial.print(FF); Serial.write(' '); Serial.print(ff); Serial.print(" Drift "); Serial.print((int)drift); ++dbug_print_state; break; case 5: Serial.print(" CC "); Serial.print(tm_correct_count); Serial.print(" Cal "); Serial.print(cal_result); Serial.println(); dbug_print_state = 0; break; } } void print_date_time(){ if( gmon < 10 ) Serial.write('0'); Serial.print(gmon); Serial.write('/'); if( gday < 10 ) Serial.write('0'); Serial.print(gday); Serial.write('/'); if( gyr < 10 ) Serial.write('0'); Serial.print(gyr); Serial.write(' '); if( ghr < 10 ) Serial.write('0'); Serial.print(ghr); Serial.write(':'); if( gmin < 10 ) Serial.write('0'); Serial.print(gmin); if( time_flags & TS ) Serial.write('*'), time_flags = 0; // decode from wwvb flagged else Serial.write(' '); } void disp_date_time(){ int local_hr; // display local time for eastern timezone local_hr = ghr - 5 + (int)DST; if( local_hr < 0 ) local_hr += 24; if( local_hr > 11 ) local_hr -= 12; if( local_hr == 0 ) local_hr = 12; LCD.setFont(MediumNumbers); LCD.printNumI(gmon,LEFT,ROW2,2,'0'); LCD.printNumI(gday,40,ROW2,2,'0'); LCD.printNumI(gyr,80,ROW2,2,'0'); LCD.setFont(BigNumbers); //LCD.printNumI(ghr,0,ROW5,2,'/'); // UTC time LCD.printNumI(local_hr,0,ROW5,2,'/'); // local time LCD.printNumI(gmin,40,ROW5,2,'0'); LCD.setFont(SmallFont); } void gather_stats( uint8_t data, uint8_t err ){ uint8_t i; if( err ) wwvb_last_err = data; // capture the last failed data bits for Serial log for( i = 0; i < 8; ++i ){ if( data & 1 ) ++wwvb_stats[i]; data >>= 1; } } void print_stats(uint8_t prnt, int tot){ uint8_t i; if( prnt ){ // ones and zeros distribution Serial.print(" "); // when in sync with WWVB, will see a display such as 11XXxx00 if( tot > 3 || tot == 0 ){ for( i = 7; i < 8; --i ){ if( wwvb_stats[i] > 55 ) Serial.write('1'); else if( wwvb_stats[i] < 5 ) Serial.write('0'); else if( wwvb_stats[i] > 40 ) Serial.write('X'); else Serial.write('x'); } } else{ // print an error in binary, example failing data for( i = 7; i < 8; --i ){ if( wwvb_last_err & 0x80 ) Serial.write('1'); else Serial.write('0'); wwvb_last_err <<= 1; } } } for( i = 0; i < 8; ++i ) wwvb_stats[i] = 0; } // adjust frame timing based upon undecoded wwvb statistics, locks to the falling edge of the // wwvb signal. void frame_sync2(int err, long tm){ int8_t t,i; int loops; int cnt; if( err >= CLK_UPDATE_THRESHOLD2 ){ val_print = '^'; return; } tm = ( tm < 500 ) ? tm : tm - 1000 ; cnt = CLK_UPDATE_THRESHOLD2 - err; tm = last_time_average( tm, cnt ); if( tm >= 0 && tm < DEADBAND ) return; if( tm < 0 && tm > -DEADBAND) return; loops = tm/100; if( loops < 0 ) loops = -loops; ++loops; t = 0; for( i = 0; i < loops; ++i ){ // run mult times for faster convergence if( tm > 0 ) t = -1; if( tm < 0 ) t = 1; tm_correction2 += t; clock_correction( t ); // long term clock drift correction } if( t == 1 ) val_print = '+'; if( t == -1) val_print = '-'; } long last_time_average( long val, int count ){ // average values static long run_ave; // weighted running average int wt; long rval; val <<= 8; // scale up to keep a fractional part wt = ( count > 64 ) ? 64 : count; run_ave = ( 64 - wt ) * run_ave + wt * val; run_ave >>= 6; rval = run_ave >> 8; if( rval > DEADBAND ) run_ave -= 256; // sub one ms when frame timing will be changed if( rval < -DEADBAND ) run_ave += 256; // Serial.print(rval); Serial.write(' '); return rval; } /*************************** some old code with interesting algorithms **************************** // sample the wwvb signal and detect bits, syncs, and errors void wwvb_sample(unsigned long t){ static unsigned long old_t; int loops; static uint8_t bounce; static uint8_t state; static long ms; static unsigned int low_counter; static unsigned int high_counter; static long ave_time; static long ave_count; uint8_t b,s,e; int adj; // int clock_adj_sum; static uint64_t wwvb_data, wwvb_sync, wwvb_errors; // defeat this algorithm by not using the globals loops = t - old_t; old_t = t; while( loops-- ){ // repeat for any missed milliseconds bounce <<= 1; if( digitalRead(WWVB_OUT) == HIGH ) bounce |= 1; // debounce, looking for zero or 255 value ++ms; switch(state){ case 0: // looking for a low signal ++high_counter; if( bounce == 0 ){ // found the low, decode the bit for the previous second state = 1; b = s = e = 0; high_counter += low_counter; // get total frame ms if( high_counter < 800 || high_counter > 1200 ) e = 1; // too short or too long if( low_counter > 100 && low_counter < 300 ) b = 0; // decode the bit else if( low_counter > 400 && low_counter < 600 ) b = 1; else if( low_counter > 700 && low_counter < 900 ) s = 1; else e = 1; // no valid decode low_counter = 0; wwvb_data <<= 1; wwvb_data |= b; // shift 64 bits data wwvb_sync <<= 1; wwvb_sync |= s; // sync wwvb_errors <<= 1; wwvb_errors |= e; // errors // magic 64 bits of sync ( looking at 60 seconds of data with 4 seconds of the past minute ) // xxxx1000000001 0000000001 0000000001 0000000001 0000000001 0000000001 // use the old bits to see the double sync bits at 0 of this minute // and 59 seconds of the previous minute. This decodes at zero time rather than some // algorithms that decode at 1 second past. if( wwvb_sync == 0b0001100000000100000000010000000001000000000100000000010000000001 ){ if( wwvb_errors == 0 ){ // decode if no bit errors // wwvb_decode(); // not used if commented } } // gather some stats for printing if( e == 0 ){ ave_time += frame_msec; if( frame_msec >= 500 ) ave_time -= 1000; // 500-999 averaged in as -(1000-frame_msec) ++ave_count; // implemented as +frame_msec - 1000 } } break; case 1: // looking for a high signal ++low_counter; if( bounce == 255 ){ state = 0; high_counter = 0; } break; } if( ms >= 60000 ){ // print out some stats if in printing mode if( ave_count ){ ave_time = ave_time/ave_count; if( ave_time < 0 ) ave_time += 1000; } val_print = ' '; adj = frame_sync( 60-ave_count, ave_time ); // sync our seconds to wwvb falling edge signal // debug print out some stats when in test mode if( wwvb_quiet == 1 && val_print == '*'){ //clock_adj_sum = clock_freq - START_CLOCK_FREQ; Serial.print("Tm "); if( ave_time < 100 ) Serial.write(' '); if( ave_time < 10 ) Serial.write(' '); Serial.print(ave_time); Serial.print(" Ave"); if( adj >= 0 ) Serial.write(' '); if( abs(adj) < 100 ) Serial.write(' '); if( abs(adj) < 10 ) Serial.write(' '); Serial.print(adj); Serial.print(" Valid "); if( ave_count < 10 ) Serial.write(' '); Serial.print(ave_count); Serial.write(val_print); Serial.print(" FF "); Serial.print(ff); Serial.write(' '); Serial.print(hourFF); Serial.write(','); Serial.print(dayFF); Serial.print(" Drift "); Serial.print((int)drift/100); Serial.print(" CC "); Serial.print(tm_correct_count); Serial.print(" Cal Freq "); Serial.print(cal_result); Serial.println(); } // reset the stats for the next minute ms = ave_count = ave_time = 0; } } // loops - repeat for lost milliseconds if any } long median( long val ){ // return the median of 3 values int i,j,k; static long vals[3]; static int in; // first time if( vals[0] == 0 ){ vals[0] = vals[1] = vals[2] = val; return val; } vals[in++] = val; if( in > 2 ) in = 0; i = 0, j = 1, k = 2; // assume in correct order low to high if( vals[i] > vals[k] ) i = 2, k = 0; // swap low and high if( vals[j] < vals[i] ) j = i; // mid val >= low val if( vals[j] > vals[k] ) j = k; // mid val <= high val //Serial.print( vals[0] ); Serial.write(' '); //Serial.print( vals[1] ); Serial.write(' '); //Serial.print( vals[2] ); Serial.write(' '); return vals[j]; } // adjust frame timing based upon undecoded wwvb statistics, locks to the falling edge of the // wwvb signal. int frame_sync(int err, long tm){ int8_t t,i; static int last_time_error; //static int last_error_count = 60; // made global for printing int loops; int cnt; int temp; if( tm > 980 || tm < 20 ) tm = 0; // deadband for clock corrections cnt = 60 - err; loops = last_time_error/100; // loop 1,2,3,4 or 5 times for error <100, <200, <300, <400, <500 if( loops < 0 ) loops = -loops; ++loops; if( last_error_count <= CLK_UPDATE_THRESHOLD ) ++last_error_count; // relax the test threshold // average the values for large error counts temp = ( tm < 500 ) ? tm : tm - 1000 ; temp = last_time_average( temp, cnt ); for( i = 0; i < loops; ++i ){ // run mult times for faster correction convergence t = 0; // signal better than the relaxing threshold ? if( err < last_error_count ){ t = ( tm < 500 ) ? -1 : 1 ; if( tm == 0 ) t = 0; last_time_error = temp + t; //if( cnt < 3 ) last_time_error >>= 3; // small valid signals, limit time delta allowed last_time_error = constrain(last_time_error,-5*cnt,5*cnt); // limit change for larger errors count last_error_count = err; // new threshold val_print = '*'; } if( t == 0 ){ // use old data for the correction amount if( last_time_error > 0 ) last_time_error--, t = -1; if( last_time_error < 0 ) last_time_error++, t = 1; } tm_correction2 += t; clock_correction( t ); // long term clock drift correction err = 60; // use last_time info for the 2nd pass in the loop } //return -(last_time_error); // return value for printing return temp; } void clock_correction( int8_t val ){ // long term frequency correction to time fudge factor FF static int8_t time_trend; // a change of +-100 is 1hz change uint8_t changed; // correct for seasonal temperature variations to clocks // using the WWVB time to arduino time errors if( wspr_tx_enable ) return; // ignore this when transmitting time_trend -= val; // or should it be += val; changed = 0; if( time_trend >= CLK_UPDATE_MIN ) clock_freq += CLK_UPDATE_AMT, changed = 1; if( time_trend <= -CLK_UPDATE_MIN ) clock_freq -= CLK_UPDATE_AMT, changed = 1; if( changed ){ // set new dividers for the new master clock freq value to take effect time_trend = 0; if( clock_freq > 2700486600ULL ) clock_freq -= 100; // stay within some bounds, +- 400 if( clock_freq < 2700406600ULL ) clock_freq += 100; // +- 100 at 7mhz si_pll_x(PLLB,cal_freq,cal_divider,0); // calibrate frequency on clock 2 si_pll_x(PLLA,Rdiv*4*freq,divider,0); // receiver 4x clock } } // regular decode needs 64 bits correct on the same minute. This decode builds each bit by itsself. // Will probably decode incorrectly at some error rate. Found to be not very useful as weak signals decode // mostly as zeros and when the signal improves and these algorithms get a decode, // an error free regular decode quickly follows void part_decode( int i ){ #define SYNC 32 #define ONES 64 #define ZEROS 128 static int8_t offset = SYNC; uint8_t count,type,b; static int8_t sync_val, sync_mod; if( i < 8 || i > 56 ) return; if( i == 8 ) sync_val = 0, sync_mod = 128; // reset sync count type = trends[i] & ( SYNC | ONES | ZEROS ); count = trends[i] & 31; // first need to find the offset so bits go in correct place. i should be close to 9 mod 10. if( offset == SYNC ){ if( type != SYNC ) return; b = i % 10; if( b != sync_mod ) sync_val = 0, sync_mod = b; // reset on no match sync_val += count; if( sync_val < 4 ) return; offset = sync_mod; if( offset < 5 ) offset += 10; offset = 9 - offset; // Serial.print("Offset "); Serial.println(offset); return; } if( type == SYNC ) return; // don't need to look at syncs anymore i = i + offset; b = ( type == ONES )? 1:0; if( i < 19 ) part_decode_hours(i,b); if( i > 21 ) part_decode_date(i,b); } void part_decode_hours( int i, uint8_t dat ){ static uint8_t mask,val; uint8_t setb,clearb; uint64_t save; setb = 1; clearb = 0xff; i = 18 - i; while( i-- ) setb <<= 1; clearb ^= setb; mask |= setb; if( dat ) val |= setb; else val &= clearb; if( mask == 0x7f ){ // Serial.print("Hour bits"); Serial.println(val,BIN); save = wwvb_data; wwvb_data = val; wwvb_data <<= 59-18; ghr = wwvb_decode2( 18, 0x3f ); wwvb_data = save; mask = 0; // start over or just disable. Will it decode every minute now. } } void part_decode_date( int i, uint8_t dat ){ static uint16_t mask = 0xf010; static uint16_t val; uint16_t setb,clearb; uint64_t save; if( i == 55 ){ leap = dat; return; } if( i <= 33 ){ setb = 1; clearb = 0xffff; i = 33 - i; while( i-- ) setb <<= 1; clearb ^= setb; mask |= setb; if( dat ) val |= setb; else val &= clearb; if( mask == 0xffff ){ //Serial.println("Date"); save = wwvb_data; wwvb_data = val; wwvb_data <<= 59-33; tot_days = wwvb_decode2( 33, 0xfff ); calc_date(); wwvb_data = save; mask = 0xf010; } } else part_decode_year(i,dat); } // this will fail year 2080 as only looking at 8 bits void part_decode_year( int i, uint8_t dat ){ static uint8_t mask = 0x10; static uint8_t val; uint8_t setb,clearb; uint64_t save; if( i > 53 || i < 46 ) return; setb = 1; clearb = 0xff; i = 53 - i; while( i-- ) setb <<= 1; clearb ^= setb; mask |= setb; if( dat ) val |= setb; else val &= clearb; if( mask == 0xff ){ save = wwvb_data; wwvb_data = val; wwvb_data <<= 59-53; gyr = wwvb_decode2( 53, 0xff ); wwvb_data = save; mask = 0x10; } } **************************************************************************************************/