// **************** Problem Log ************** // 1. Need to fix the display on turn-on so // that all digits are blanked except the // seconds count. // // 2. Midnight rolls over to 2:00 instead of 12:00 // // // // ***************** Revisions ********************* // Initial working program (N/C) 11/09/2008 // // Rev. A: 11/11/08 Fixed one-minute error problem // caused by wwv message "on-time" at the // start of a new message // // Rev. B: 1/27/2009 Fixed midnight rollover // problem in Update_display() to show // 12:00 instead of 2:00 #include //Target PIC16F628 configuration word #pragma DATA _CONFIG, _PWRTE_OFF & _WDT_OFF & _XT_OSC & _CP_OFF //Set clock frequency #pragma CLOCK_FREQ 4000000 // variable declarations char varl@0x0E; // TMR1L register absolute address char varh@0x0F; // TMR1H register absolute address // bit patterns for correlation per WWVB description unsigned long sync = 0xFC000000; unsigned long one = 0xFFFF0000; unsigned long zero = 0xFFFFFF80; unsigned long samples = 0; // input sample register unsigned long sync_temp; unsigned char i; unsigned char j; unsigned char k; unsigned char n; unsigned char sync_count; unsigned char one_count; unsigned char zero_count; unsigned char sync_threshold = 31; // stringent sync threshold unsigned char bit_threshold = 28; // less stringent bit threshold unsigned char sync_bit_1; unsigned char sync_bit_2; unsigned char sample_flag = 0; unsigned char seconds = 0; unsigned char ten_seconds = 0; unsigned char seconds_counter = 0; unsigned char seconds_temp = 0; unsigned char local_hours; unsigned char wwv_minutes; unsigned char wwv_ten_minutes; unsigned char wwv_hours; unsigned char wwv_ten_hours; unsigned char error; unsigned char msg_valid = 0; unsigned char internal_minutes_counter = 0; unsigned char internal_ten_min = 0; unsigned char internal_min = 0; unsigned char internal_hours = 0; // *************************** sample() ******************* // // This routine collects samples from the wwvb receiver data // output and places them into the sample register. The sample // register is a FIFO. // // Called from: Interrupt service routine // // ******************************************* sample() { samples >>= 1; samples.31 = portb.0; sample_flag = 1; } // ************************** end of sample() ***************** // ****************** update_display() ************************* // // This routine writes minutes, ten minutes, hours, and ten hour // data to the display drivers. // // Parameters // Entry: minutes, ten_minutes and hours from wwvb or from internal time // Exit: none // // Called from: update_internal_time() // // *************************************************************** void update_display() { unsigned char display_hours = 0; unsigned char display_ten_hours = 0; // convert hrs back to 2-byte format if (internal_hours == 0) { display_hours = 2; // next starement added to correct midnight rollover // problem display_ten_hours = 1; } if ((internal_hours > 0) && (internal_hours < 10)) display_hours = internal_hours; if ((internal_hours > 9) && (internal_hours < 13)) { display_hours = internal_hours - 10; display_ten_hours = 1; } if ((internal_hours > 12) && (internal_hours < 22)) { display_hours = internal_hours - 12; display_ten_hours = 0; } if ((internal_hours > 21) && (internal_hours <= 23)) { display_hours = internal_hours - 22; display_ten_hours = 1; } // port a values contain data for the 4511 display drivers // port b values are addresses for the hct138 1-8 decoder // to distribute the strobes for the individual display // digits. porta = internal_min; portb.1 = 0; portb.2 = 1; portb.3 = 0; portb.4 = 1; delay_us(10); portb.4 = 0; porta = internal_ten_min; portb.1 = 1; portb.2 = 1; portb.3 = 0; portb.4 = 1; delay_us(10); portb.4 = 0; porta = display_hours; portb.1 = 0; portb.2 = 0; portb.3 = 1; portb.4 = 1; delay_us(10); portb.4 = 0; if (display_ten_hours != 1) porta = 0x0F; // this will cause the 4511 chip to blank the // ten-hours digit else porta = display_ten_hours; portb.1 = 1; portb.2 = 0; portb.3 = 1; portb.4 = 1; delay_us(10); portb.4 = 0; } // ************************* end of update_display() ***************** // *********************** update_internal_time() ****************** // // This routine keeps time either using the internal 32 sample/sec // time base or wwv received time. The time is in the following format: // // internal_minutes: 0-9 // internal_ten_minutes: 0-5 // internal_hours: 0-23 // // Parameters // Entry: none // Exit: none // // ****************************************************************** void update_internal_time() { unsigned char temp_int_min = 0; unsigned char temp_internal_ten_minutes = 0; unsigned char temp_wwv_ten_min = 0; if (msg_valid == 0) { if (internal_minutes_counter < 59) { internal_minutes_counter++; temp_int_min = internal_minutes_counter; internal_ten_min = 0; while( temp_int_min > 9) { internal_ten_min++; temp_int_min -= 10; } internal_min = temp_int_min; } else { internal_minutes_counter = 0; internal_min = 0; internal_ten_min = 0; if (internal_hours < 23) internal_hours++; else internal_hours = 0; } } else // msg_valid == 1 { temp_wwv_ten_min = wwv_ten_minutes; temp_internal_ten_minutes = 0; while( temp_wwv_ten_min > 0 ) { temp_wwv_ten_min--; temp_internal_ten_minutes += 10; } internal_minutes_counter = temp_internal_ten_minutes + wwv_minutes; internal_min = wwv_minutes; internal_ten_min = wwv_ten_minutes; internal_hours = local_hours; } update_display(); msg_valid = 0; } // ************************* end of update_internal_time ************************* // ********************************************************************************************** // Timer 1 interrupt handler. This interrupt obtains new input data samples 32 times per second // ********************************************************************************************** void interrupt( void ) { //Handle timer1 interrupt if( pir1 & (1< 9) { ten_seconds++; seconds_temp -= 10; } seconds = seconds_temp; porta = seconds; clear_bit(portb, 1); clear_bit(portb, 2); clear_bit(portb, 3); set_bit(portb, 4); delay_10us(1); clear_bit(portb, 4); porta = ten_seconds; portb.1 = 1; portb.2 = 0; portb.3 = 0; portb.4 = 1; delay_10us(1); portb.4 = 0; } } } // ******************* end of timer 1 interrupt handler ***************** // ************************** configure_mpu *********************** // // Set up the 16F628 internal registers // // Parameters // Entry: none // Exit: Registers set // // Called from: Main() // // ***************************************************************** void configure_mpu() { //Configure port A trisa = 0x00; // port a configured as outputs (except porta.5 which is // input only. //Configure port B trisb = 0xC1; // port b 0, 6, and 7 are inputs // port b 1, 2, 3, 4, and 5 are outputs //Initialize port A porta = 0x00; //Initialize port B portb = 0x00; cmcon = 7; //disable comparators //Set Timer0 mode clear_bit( option_reg, T0CS ); //configure timer0 as a timer //Set prescaler assignment clear_bit( option_reg, PSA ); //prescaler is assigned to timer0 //Set prescaler rate clear_bit( option_reg, PS2 ); //prescaler rate 1:2 clear_bit( option_reg, PS1 ); clear_bit( option_reg, PS0 ); //Set timer0 source edge selection set_bit( option_reg, T0SE ); //increment on high-to-low transition on RA4/T0CKI pin //Set timer 1 prescaler rate clear_bit( t1con, T1CKPS1 ); //prescaler rate 1:2 set_bit( t1con, T1CKPS0 ); //Set timer 1 mode clear_bit( t1con, TMR1CS ); //Internal clock (FOSC/4) clear_bit( pir1, TMR1IF ); //clear timer 1 interrupt bit clear_bit( t1con, TMR1ON ); //stop timer 1 varh = 0xC3; // 0xC301 is a adjusted number to yield 32.0 samples/sec varl = 0x01; set_bit( t1con, TMR1ON ); //enable timer 1 //Set timer 2 prescaler rate clear_bit( t2con, T2CKPS1 ); //prescaler rate 1:1 clear_bit( t2con, T2CKPS0 ); //Set timer 2 postscaler rate clear_bit( t2con, TOUTPS3 ); //postscaler rate 1:1 clear_bit( t2con, TOUTPS2 ); clear_bit( t2con, TOUTPS1 ); clear_bit( t2con, TOUTPS0 ); //Set timer 2 mode (enable or disable) clear_bit( t2con, TMR2ON ); //enable timer 2 //Enable interrupts (Timer1) intcon = 0xC0; // enable global interrupts, disable timer0 interrupt bit pie1 = 0x01; // enable timer1 overflow interrupt bit } // ***************************** end of configure_mpu() ***************** // ************************ find_sync() ************************* // // This routine correlates the sample register with the wwvb 2 bit // sync sequence that starts each message. The first bit looks for // correlation on a bit-by-bit basis. Once the first sync bit is // detected, the second bit is tested as a complete 32-bit word. // Sync is declared when both sync bits pass the sync threshold test // // Parameters: // Entry: sync_bit_1 and sync_bit_2 are reset // Exit: sync_bit_1 and sync_bit_2 are set. // // Called from: main() // // ******************************************************************* unsigned char find_sync() { // seconds digit decimal point OFF = no sync portb.5 = 0; while ((sync_bit_1 && sync_bit_2) == 0) { while(sample_flag == 0) { ; // wait for 32 samples/sec interrupt and get new sample } sample_flag = 0; // Set up a counter which will count the number of agreements // between the sample register and the sync pattern sync_count = 0; // XOR the sample register with the sync pattern to see which bits agree sync_temp = sync ^ samples; // Count the number of bit agreements between samples and initial sync pattern for (i = 0; i < 32; i++) { if (sync_temp.0 == 0) sync_count++; sync_temp >>= 1; } // compare agreement count to the sync_threshold value, set sync bit flags accordingly if ((sync_count >= sync_threshold) && (sync_bit_1 == 0)) { sync_bit_1 = 1; if (sync_bit_1 == 1) { for (j = 0; j < 32; j++) // get 32 new samples { while(sample_flag == 0) { ; } sample_flag = 0; } sync_count = 0; sync_temp = sync ^ samples; for (i = 0; i < 32; i++) { if (sync_temp.0 == 0) sync_count++; sync_temp >>= 1; } if (sync_count >= sync_threshold) // sync found!!! sync_bit_2 = 1; else // sync not found sync_bit_1 = 0; } } } } // ***************** end of find_sync ************************* // ************************** get_bit() *********************** // // this routine determines whether the received bit is a // one or zero // // Parameters: // Entry: none // Exit: none // // Called from: main() // // *************************************************************** void get_bit() { unsigned char j; unsigned long one_temp; unsigned long zero_temp; for (j = 0; j < 32; j++) // get 32 new samples { while(sample_flag == 0) { ; // wait for sample time interrupt } sample_flag =0; } one_count = 0; zero_count = 0; one_temp = one ^ samples; zero_temp = zero ^ samples; for (i = 0; i < 32; i++) { if (one_temp.0 == 0) one_count++; one_temp >>= 1; if (zero_temp.0 == 0) zero_count++; zero_temp >>= 1; } } // ************* end of get_bit() ********************** // *************** skip_bit() ************************** // // this routine is used to skip over the wwvb reserved bits and // other bits not used for this clock design // // Parameters // Entry: none // Exit: none // // Called from: main() // // ******************************************************* void skip_bit() { unsigned char j; for (j = 0; j < 32; j++) { while(sample_flag == 0) { ; // wait for sample time interrupt } sample_flag = 0; } } // ***************** end of skip_bit() ************************** // *************************** main routine starts here ***************** void main() { // variable declarations unsigned char k; unsigned char temp_ten_minutes; unsigned char temp_minutes; unsigned char temp_hours; unsigned char temp_ten_hours; unsigned char utc_hours; unsigned char ten_utc_hours; unsigned char a; unsigned char x; unsigned char y; unsigned char z; unsigned char last_msg_valid_flag = 0; unsigned char dst = 0; // daylight savings time flag. 1 = dst, 0 = std time configure_mpu(); seconds = 0; ten_seconds = 0; //Endless loop while( 1 ) { sync_bit_1 = 0; sync_bit_2 = 0; find_sync(); // sync found, process bits // seconds and ten_seconds reset here seconds_counter = 0; n = 31; wwv_minutes = 0; wwv_ten_minutes = 0; utc_hours = 0; ten_utc_hours = 0; wwv_hours = 0; wwv_ten_hours = 0; portb.5 = 1; // colons ON = sync error = 0; // first get 3 bits of 10-minutes data (40, 20, 10, MSB first format) // the 4th bit is a reserved (don't care) bit for (k = 0; k < 3; k++) { get_bit(); if (one_count >= bit_threshold) { wwv_ten_minutes <<= 1; wwv_ten_minutes.0 = 1; } else if (zero_count >= bit_threshold) { wwv_ten_minutes <<= 1; wwv_ten_minutes.0 = 0; } else error = 1; // handle error, did not detect 1 or 0 bit } // ************ could add error check here to be sure ten-minutes are between 0 and 5 ********** // skip over the don't care bit skip_bit(); // first get 4 bits of minutes data (8, 4, 2, 1, MSB first format) if (error == 0) { for (k = 0; k < 4; k++) { get_bit(); if (one_count >= bit_threshold) { wwv_minutes <<= 1; wwv_minutes.0 = 1; } else if (zero_count >= bit_threshold) { wwv_minutes <<= 1; wwv_minutes.0 = 0; } else error = 1; // handle error, did not detect 1 or 0 bit } } // ************ could add error check here to be sure minutes are between 0 and 9 ********** // now get the next 2 bits representing 10 utc hour data (24-hour format) if (error == 0) { // skip over the position marker and (2) reserved bits for (k = 0; k < 3; k++) skip_bit(); for (k = 0; k < 2; k++) { get_bit(); if (one_count >= bit_threshold) { ten_utc_hours <<= 1; ten_utc_hours.0 = 1; } else if (zero_count >= bit_threshold) { ten_utc_hours <<= 1; ten_utc_hours.0 = 0; } else error = 1; //handle error, did not detect 1 or 0 bit } } // ************ could add error check here to be sure ten-utc_hours are between 0 and 2 ********** // now get the 4 bits representing utc_hours if (error == 0) { // skip the next (reserved) bit skip_bit(); for (k = 0; k < 4; k++) { get_bit(); if (one_count >= bit_threshold) { utc_hours <<= 1; utc_hours.0 = 1; } else if (zero_count >= bit_threshold) { utc_hours <<= 1; utc_hours.0 = 0; } else error = 1; // handle error, did not detect 1 or 0 bit } } // correct for time being valid at the previous frame sync marker if (error == 0) { temp_ten_minutes = wwv_ten_minutes; x = 0; while ( temp_ten_minutes > 0) { x += 10; temp_ten_minutes--; } y = x + wwv_minutes; // y is now minutes in a single // char format z = 0; temp_ten_hours = ten_utc_hours; while( temp_ten_hours > 0) { z += 10; temp_ten_hours--; } a = z + utc_hours; // a is now hours in a single // char format if (y < 59) y++; // bump minutes else // y = 59 { y = 0; // bump hours if (a < 23) a++; else a = 0; } // now unpack back to 2-byte format temp_ten_minutes = 0; while (y > 9) { temp_ten_minutes++; y -= 10; } wwv_ten_minutes = temp_ten_minutes; wwv_minutes = y; temp_ten_hours = 0; while (a > 9) { temp_ten_hours++; a -= 10; } ten_utc_hours = temp_ten_hours; utc_hours = a; } // daylight savings time info is in bits 57 and 58 // we will only test bit 57 for dst and do nothing for bit 58 // to allow re-sync if (error == 0) { // skip the next 38 bits to get to message bit 57 for (k = 0; k < 38; k++) skip_bit(); // get bit 57 to see if DST is in effect get_bit(); if (one_count >= bit_threshold) dst = 1; else if (zero_count >= bit_threshold) dst = 0; else error = 1; //handle error, did not detect 1 or 0 bit } // ************ could add error check here to be sure utc_hours are between 0 and 9 ********** // ok, now we need to convert UTC hours to local (PST) time // ********** CHANGE THIS CODE SEGMENT FOR DIFFERENT TIME ZONES!!!!! ********* if (error == 0) { temp_hours = 0; for (k = 0; k < ten_utc_hours; k++) temp_hours += 10; temp_hours = temp_hours + utc_hours; // now have a single word representing utc hours if (dst == 0) // standard time { if (temp_hours <= 7) local_hours = temp_hours + 16; if ((temp_hours > 7) && (temp_hours <= 23)) local_hours = temp_hours - 8; } if (dst == 1) // daylight savings time { if( temp_hours <= 6 ) local_hours = temp_hours + 17; if( (temp_hours > 6) && (temp_hours <= 23)) local_hours = temp_hours - 7; } // if we made it this far with error = 0 // then the message is valid msg_valid = 1; } else msg_valid = 0; } // end of while(1) endless loop }